第五課. 內核中斷系統中的設備樹

這節課講解如何在中斷系統中使用設備樹,也就是用設備樹如何描述中斷。
中斷體系在4.x內核中變化很大,中斷體系又跟pinctrl系統密切相關,pinctrl中又涉及GPIO子系統,這樣講下去的話,設備樹課程就變成驅動專題了,所以我打算只講中斷體系統,對於pinctrl、gpio等系統留待以後在驅動課程中擴展。<br>
這一課的參考資料鏈接如下:

本課視頻預計分爲五節。
其中第01節描述中斷概念的引入與處理流程,這節視頻來自"韋東山第1期裸板視頻加強版", 如果已經理解了中斷的概念, 請忽略該節。

第01節_中斷概念的引入與處理流程

  • 點擊下面鏈接跳轉到相應文章頁面
    [[第014課_異常與中斷 | 第01節_中斷概念的引入與處理流程文章地址 ]]

    第02節_Linux對中斷處理的框架及代碼流程簡述

當CPU發生中斷時,CPU會跳到一個固定的地址去執行中斷,對於中斷來說,
中斷地址是在24的地方,

.globl _start
0---> _start: b reset
4---> ldr pc, _undefined_instruction
8---> ldr pc, _software_interrupt
c---> ldr pc, _prefetch_abort
16--> ldr pc, _data_abort
20--> ldr pc, _not_used
24--> ldr pc, _irq //發生中斷時,CPU跳到這個地址執行該指令
ldr pc, _fiq

這些地址就是vector,可以放在0地址,也可以放在0xffff0000(對於這個地址是啓動mmu之後才存在的)
對於其它芯片,向量所在地址可能不同,但都是用來處理異常
打開內核源碼
a. 異常向量入口: <code>arch\arm\kernel\entry-armv.S</code>

    .section .vectors, "ax", %progbits
.L__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)  pc, .L__vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq <----發生中斷時執行這條指令
    W(b)    vector_fiq

使用宏vector_stub表示這個vector_irq
b. 中斷向量: vector_irq

/*
 * Interrupt dispatcher
 */
    vector_stub irq, IRQ_MODE, 4   // 相當於 vector_irq: ..., 
                                   // 它會根據SPSR寄存器的值,
                                   // 判斷被中斷時CPU是處於USR狀態還是SVC狀態, 
                                   // 然後調用下面的__irq_usr或__irq_svc

    .long   __irq_usr               @  0  (USR_26 / USR_32)
    .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
    .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
    .long   __irq_svc               @  3  (SVC_26 / SVC_32)
    .long   __irq_invalid           @  4
    .long   __irq_invalid           @  5
    .long   __irq_invalid           @  6
    .long   __irq_invalid           @  7
    .long   __irq_invalid           @  8
    .long   __irq_invalid           @  9
    .long   __irq_invalid           @  a
    .long   __irq_invalid           @  b
    .long   __irq_invalid           @  c
    .long   __irq_invalid           @  d
    .long   __irq_invalid           @  e
    .long   __irq_invalid           @  f

<code>arch\arm\kernel\entry-armv.S</code>

__irq_usr

    .align  5
__irq_usr:
    usr_entry
    kuser_cmpxchg_check
    irq_handler
    get_thread_info tsk
    mov why, #0
    b   ret_to_user_from_irq
 UNWIND(.fnend      )
ENDPROC(__irq_usr)

處理完成後返回到被中斷的現場

__irq_svc

    .align  5
__irq_svc:
    svc_entry
    irq_handler

#ifdef CONFIG_PREEMPT
    ldr r8, [tsk, #TI_PREEMPT]      @ get preempt count
    ldr r0, [tsk, #TI_FLAGS]        @ get flags
    teq r8, #0              @ if preempt count != 0
    movne   r0, #0              @ force flags to 0
    tst r0, #_TIF_NEED_RESCHED
    blne    svc_preempt
#endif

    svc_exit r5, irq = 1            @ return from exception
 UNWIND(.fnend      )
ENDPROC(__irq_svc)

c.<code> __irq_usr/__irq_svc</code>

這2個函數的處理過程類似:
保存現場
調用 irq_handler
恢復現場

d. 核心是irq_handler: 將會調用C函數 handle_arch_irq

    .macro  irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
    ldr r1, =handle_arch_irq
    mov r0, sp
    badr    lr, 9997f
    ldr pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm

linux-4.19-rc3\kernel\irq\handle.c

e. handle_arch_irq的處理過程: 請看視頻和圖片

  • 讀取寄存器獲得中斷信息: hwirq
  • 把hwirq轉換爲virq
  • 調用 irq_desc[virq].handle_irq

對於S3C2440,通過set_handle_irq函數設置 s3c24xx_handle_irq 是用於處理中斷的C語言入口函數
中斷處理流程:
假設中斷結構如下:

sub int controller ---> int controller ---> cpu

中斷控制中有32位,每一位代表一種中斷,也就是控制器可以向CPU發出32種中斷,每一種中斷的處理方式不同,如何管理這些中斷呢?
最簡單方法是創建指針數組,每一項對應一箇中斷,在這個中斷裏存放處理函數,這個數組用irq_desc中斷結構體來表示
第五課. 內核中斷系統中的設備樹

handle_irq操作

  • 調用irqaction鏈表中的handler
  • 清中斷(芯片相關)irq_data.chip

發生中斷時內核的執行過程
cpu跳到<code>vector_irq</code>, 保存現場, 調用C函數<code>handle_arch_irq</code>

handle_arch_irq:

  • a. 讀 int controller, 得到hwirq
  • b. 根據hwirq得到virq
  • c. 調用<code> irq_desc[virq].handle_irq</code>

如果該中斷沒有子中斷, irq_desc[virq].handle_irq的操作:

  • a. 取出irq_desc[virq].action鏈表中的每一個handler, 執行它
  • b. 使用irq_desc[virq].irq_data.chip的函數清中斷

對於0號中斷,加上一個或控制器,形成一個共享中斷,可以控制網卡中斷irq_net和攝像頭中斷irq_camera,
在0號中斷上有兩個設備,那麼就需要有兩個irqaction,其中irqaction *next指向下一個irqaction
當irq_net發生中斷時,會執行irq_desc.handle_irq會把鏈表裏面所有的handler都取出來執行一遍,在irq_net中要訪問網卡來判斷下中斷是否是網卡產生的,如果不是則返回不做任何處理,如果是則做網絡處理

鏈表支持 共享中斷,

第五課. 內核中斷系統中的設備樹

如果使用中斷4_7則一旦產生中斷,那麼都會向cpu發出4號中斷,也可以通過irqdesc.irq_action構造4個irqaction結構體,
將四個中斷鏈接到一起,當發生中斷時,這四個函數都會被調用一次,這種方式可以用,但是並不好用
對於sub interrupt controller(子中斷控制器)對於可以讀取SUBSRCPND寄存器用來確定是哪一個產生了更加細緻的中斷
那麼我們就可以讓 irq_desc.handle_irq指向s3c_irq_demux(中斷分發函數)

  • hwirq(表示硬件中斷號)
  • (virq(表示虛擬中斷號)

s3c_irq_demux做了以下幾件事
如果該中斷是由子中斷產生, irq_desc[virq].handle_irq的操作:

  • a. 讀 sub int controller, 得到hwirq'
  • b. 根據hwirq'得到virq
  • c. 調用 irq_desc[virq].handle_irq

硬件中斷號和虛擬中斷號<br>

 cpu
 ||
 \/
 INTC //讀intc得到hwirq
 ||
 \/
 SUB INTC//讀subintc 得到hwirq'

我們可以通過硬件中斷號,得到虛擬中斷號,這些虛擬中斷號就是irq_desc[]數組項的下標
可以定義這麼一個公式

 virq = hwirq  + offset + 1
      = hwirq + 16
 virq = hwirq' + offset + 2
      = hwirq' + 58 + 16

我們假設中斷5接有一個按鍵irq_key,我們註冊這個中斷時會註冊對應的中斷號(這裏是37),這時irq_desc會創建一個irqaction這個handle就等於irq_key,當我們按下時,這個子中斷控制器會向上一級中斷控制器發出信號,上一級中斷控制器就會向cpu發出信號,cpu讀取控制器時會找到對應的virq通過irq_desc找到這一項對應的handle_irq,讓後去讀寄存器進一步來分辨是發生了哪一個子中斷,得到虛擬中斷號,進入irq_desc鏈表得到irqaction取出handler來執行irq_key

第03節_中斷號的演變與irq_domain

第五課. 內核中斷系統中的設備樹

irq_desc[]下標是中斷號virq(對應硬件中斷),根據硬件中斷確定是那個中斷髮生
得出公式

virq = hwirq + 1
hwirq = virq - 1

假設增加一個子中斷控制器

virq =hwirq' + 36
hwirq' = virq - 36

在加一箇中斷控制器
在這種情況下如何使用中斷?<br>
以前,對於每一個硬件中斷(hwirq)都預先確定它的中斷號(virq),這些中斷號一般都寫在一個頭文件裏,
比如<code>arch\arm\mach-s3c24xx\include\mach\irqs.h</code>使用時

a.執行reguest_irq(virq,my_handler):內核根據virq可以知道對應的硬件中斷,讓後去設置,使能中斷等操作
b.發生硬件中斷時,內核讀取硬件信息,確定hwirq,反算出virq,讓後調用irq_desc[virq].handle_irq,最終會用到my_handler

內核怎麼根據硬件中斷號,反算出虛擬中斷號?

對於不同中斷控制器裏面的硬件中斷號,它們的轉化公式是不同的
硬件上有多個intc(中斷控制器),對於同一個hwirq數值,會對應不同的virq所以在講hwirq時,應該強調“是哪一個intc的hwirq”,在描述hwirq轉換爲virq時,
引入一個概念:irq_domain,域,在這個域裏hwirq轉換爲某個virq
以前中斷號(virq)跟硬件密切相關,現在的趨勢是中斷號跟軟件無關,僅僅是一個標號而已

這種老方法的缺陷

當cpu只有一兩個中斷控制器時這種一一對應的方法很好用,當中斷控制器數量變多時,有成百上千,這種虛擬中斷號和硬件中斷號一一對應的方式就很麻煩<br>

解決辦法
virq和hwirq之間的聯繫取消掉,當我想使用硬件中斷時,查找這個irq_desc數組裏的空餘項

第五課. 內核中斷系統中的設備樹

假設使用 int2
如何查找空閒項?
從中斷號開始依次查找,直到找到最後空閒項,
如果bit=0則虛擬中斷號就是2

虛擬中斷號保存在irq_domain結構體中

irq_domain
    linear_revmap[hwirq] = virq

把hwirq對應的virq保存在irq_domain的linear_revmap
假如使用子EINT4中斷

使用子中斷EINT4的過程
1.使用父中斷(intc,4)設置irq_desc:
    a.找空閒項,virq=4,保存起來:intc's irq_domain.linear_revmap[4] = 4
    b.設置irq_desc[4].handle_irq = s3c_irq_demux

2.爲子中斷eint4(subintc,4)設置irq_desc:
    a.找空閒項,virq=5,保存起來:subintc's irq_domain.linear_revmap[4] = 5

3.驅動程序request_irq(5.my_handler),
    會把my_handler保存在irq_desc[5].action鏈表中

4.發生了中斷,內核讀取intc,得到hwirq=4,virq = intc's irq_domain.liner_revmap[4] = 4調用irq_desc[4].handle_irq,即s3c_irq_demux

5.s3c_irq_demux:
    讀取subintc,得到<code>hwirq=4,virq = subintc's </code> <code>irq_domain.liner_remap[4] = 5</code>,調用irq_desc[5].handle_irq,它會調用action鏈表中保存的my_handler
每一箇中斷控制器都有一個<code>irq_domain.linera_revmap</code>得到虛擬中斷號,
調用irq_desc.handle_irq;這個分發函數會讀取下級中斷控制器,得到子中斷控制器的4號中斷,
再次讀取子中斷控制器對應的irq_domain.liner_revmap[4] = 5對應的虛擬中斷號是5,那麼就會調用第五個irq_desc,執行handle_irq

在<code>irq_domain.linear_revmap[]</code>大部分數組項都是空閒的<br>

怎麼兼容老的固定中斷號?

也要保存的硬件中斷號和虛擬中斷號之間的對應關係,在irq_domain也有liner_revmap[]對每一個[hwirq] = 預先設置號的virq
我們寫驅動程序直接 request_irq(virq,....),中斷號是通過宏方式進行定義的,所以直接使用中斷號進行註冊<br>

如何使用新型中斷號,註冊irq
先在設備樹中表明要使用那個中斷,內核解析設備樹時纔會把這個中斷號和某一個虛擬中斷號掛鉤,這些信息會轉換成(intc,hwirq) ==> virq
這時纔可以 request_irq

通過irq_domain得到

.liner_revmap[4] = 5
.xlate (解析設備樹,得到hwirq,irq_type)
.map(hwirq,virq) (map就是建立聯繫的作用,若是子中斷,去設置父中斷)

第04節_示例_在S3C2440上使用設備樹描述中斷體驗

我們這節課之前所使用的設備樹和內核是一個閹割版本,這個版本根本沒有能力去描述中斷,<br>
在以前的mach-s3c24xx.c中手動註冊了很多平臺設備,這些平臺設備中指定了很多設備資源,比如i2c控制器提前指定了中斷號和內存等資源
這些中斷號可以從某個頭文件指定

內核不斷演變,虛擬中斷號和硬件中斷號不再綁定,這也就意味着不能在平臺資源裏事先確定所使用的中斷資源,就需要用設備樹描述這些中斷資源
所用文件在: <code>doc_and_sources_for_device_tree\source_and_images\第5,6課的源碼及映像文件(使用了完全版的設備樹)\內核補丁及設備樹</code>

先解壓原始內核(source_and_images\kernel):

tar xzf linux-4.19-rc3.tar.gz

打上補丁:
cd linux-4.19-rc3
patch -p1 < ../linux-4.19-rc3_device_tree_for_irq_jz2440.patch

在內核目錄下執行:

export PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin
cp config_ok .config
make uImage // 生成 arch/arm/boot/uImage
make dtbs // 生成 arch/arm/boot/dts/jz2440_irq.dtb

把jz2440_irq.dtb反彙編成 jz2440_irq_all.dts便於分析

老內核中斷:

 / # cat /proc/interrupts
           CPU0
 29:      17593       s3c  13 Edge      samsung_time_irq 
 42:          0       s3c  26 Edge      ohci_hcd:usb1
 43:          0       s3c  27 Edge      s3c2440-i2c.0
 74:         86  s3c-level   0 Edge      s3c2440-uart
 75:        561  s3c-level   1 Edge      s3c2440-uart
 83:          0  s3c-level   9 Edge      ts_pen
 84:          0  s3c-level  10 Edge      adc
 87:          0  s3c-level  13 Edge      s3c2410-wdt

samsung_time_irq對應中斷號是29,在irqs.h中對應的宏是
<code> #define IRQ_TIMER3 S3C2410_IRQ(13)</code>
2440使用第三個定時器作爲系統滴答,使用老內核中斷號都是固定

使用新內核啓動:
nfs 30000000 192.168.1.124:/work/nfs_root/uImage; nfs 32000000 192.168.1.124:/work/nfs_root/jz2440_irq.dtb; bootm 30000000 - 32000000

 / # cat /proc/interrupts

           CPU0
  8:          0       s3c   8 Edge      s3c2410-rtc tick
 13:        936       s3c  13 Edge      samsung_time_irq
 30:          0       s3c  30 Edge      s3c2410-rtc alarm
 32:         15  s3c-level  32 Level     50000000.serial
 33:         60  s3c-level  33 Level     50000000.serial
 59:          0  s3c-level  59 Edge      53000000.watchdog

samsung_time_irq中斷號從以前的29變成了現在的13,現在我們不能指定中斷號只能在設備樹描述會用到什麼中斷號。

我們如何描述這些信息?
參考<code> jz2440_irq_all.dts</code>

第五課. 內核中斷系統中的設備樹

gpio控制器實際並不是中斷控制器,但我們可以在軟件上認爲這是一個控制器

如何描述硬件資源?

比如我們網卡要使用中斷資源

    srom-cs4@20000000 {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        reg = <0x20000000 0x8000000>;
        ranges;

        ethernet@20000000 {
            compatible = "davicom,dm9000";
            reg = <0x20000000 0x2 0x20000004 0x2>;
            interrupt-parent = <&gpf>; /*使用gpf中斷控制器*/
            interrupts = <7 IRQ_TYPE_EDGE_RISING>;/*使用gpf控制器中的第七號中斷,IRQ_TYPE_EDGE_RISING爲中斷觸發方式*/
            local-mac-address = [00 00 de ad be ef];
            davicom,no-eeprom;
        };
    };

interrupt-parent = <&gpf>; ---------------->使用gpf中斷控制器
interrupts = <7 IRQ_TYPE_EDGE_RISING>;-----><hwirq type> 具體含義由中斷控制器解釋

用多少個u32也是由中斷控制器指定

a. 某個設備要使用中斷, 需要在設備樹中描述中斷, 如何描述?
它要用哪一個中斷? 這個中斷連接到哪一個中斷控制器去?
即: 使用哪一個中斷控制器的哪一個中斷?
至少有有2個屬性:

interrupt-parent // 這個中斷要接到哪一個設備去? 即父中斷控制器是誰
interrupts // 表示要使用哪一個中斷, 中斷的觸發類型等等

中斷控制器如何表述

總中斷控制器,也就是跟中斷控制器

    interrupt-controller@4a000000 {
        compatible = "samsung,s3c2410-irq";
        reg = <0x4a000000 0x100>;
        interrupt-controller;
        #interrupt-cells = <0x4>;
        phandle = <0x1>;
    };

interrupt-controller;------>表明這個設備是中斷控制器
#interrupt-cells = <0x4>;-->表明下一級的設備要用多少個32位的數據來描述這個中斷

GPIO子中斷控制器

        gpf {
            gpio-controller;
            #gpio-cells = <0x2>;
            interrupt-controller;
            #interrupt-cells = <0x2>;
            phandle = <0x6>;
        };

        gpg {
            gpio-controller;
            #gpio-cells = <0x2>;
            interrupt-controller;
            #interrupt-cells = <0x2>;
        };

如何查找父節點控制器,通過一級一級往上查找最後找到<code>interrupt-controller@4a000000</code>

第五課. 內核中斷系統中的設備樹

b. 上述的interrupts屬性用多少個u32來表示?
這應該由它的父中斷控制器來描述,
在父中斷控制器中, 至少有2個屬性:

interrupt-controller; // 表示自己是一箇中斷控制器
#interrupt-cells // 表示自己的子設備裏應該 有幾個U32的數據來描述中斷

=第05節_示例_使用設備樹描述按鍵中斷=

在上節視頻裏我們體驗了怎麼在設備樹中描述中斷,這一節我們來寫一個按鍵驅動程序來看看怎麼使用設備樹來描述按鍵驅動程序所使用的引腳和所使用的中斷。
這個驅動程序就不現場編寫了,畢竟我們主題是講設備樹,而不是講怎麼寫驅動程序。

源碼路徑

我們在以前按鍵驅動程序的基礎上修改按鍵驅動程序。
按鍵驅動程序百度雲盤路徑:

100ask分享的所有文件
    009_UBOOT移植_LINUX移植_驅動移植(免費)
        源碼文檔圖片.zip
            源碼文檔圖片
                源碼文檔
                    畢業班_文檔_圖片_源碼_bin
                        畢業班第4課移植驅動到3.4.2內核_文檔_圖片_源碼
                            drivers_and_test_new
                                jz2440
                                    7th_buttons_all

該源碼已經先下載下來,放在設備樹的文件夾裏,路徑爲:

100ask分享的所有文件
    018_設備樹詳解
        doc_and_sources_for_device_tree
            source_and_images
                第5,6課的源碼及映像文件(使用了完全版的設備樹)
                    第5課第5節_按鍵驅動_源碼_設備樹
                        000th_origin_code

<code>000th_origin_code</code>是從前面畢業班視頻裏直接拷貝過來的;
<code>001th_buttons_drv</code>是用在之前閹割版本的內核,裏面沒有支持用設備樹描述中斷;
<code>002th_buttons_drv</code>是本節視頻使用的驅動程序,在設備樹裏可以描述中斷了;
將<code>000th</code>和<code>001th</code>進行對比:

  • Makefile有變化,兩個的內核路徑不一樣,因爲編譯驅動需要藉助內核源碼;
  • 驅動有少量變量,比如頭文件,一些結構體名字,定時器相關的函數,gpio讀取函數名字等;
    可以看到這兩個驅動程序的變化不大,使用的中斷號都是和硬件綁定的;

按鍵中斷

在上節我們知道了中斷抽象出了三個中斷控制器。
根據原理圖可知,我們的按鍵涉及eint0、eint2、eint11、eint19。
其中eint0、eint2接在最頂層的中斷控制器,eint11、eint19接在gpf中斷控制器。
以前我們在設備樹中描述中斷時,需要指定這個中斷是發給哪一個中斷控制器,它屬於這個中斷中的哪一個中斷。即在<code>interrupt-parent</code>指定中斷控制器,在<code>interrupts</code>指定是該中斷控制器中的哪個中斷,並且指定中斷類型。

現在我們有四個中斷,分別屬於兩個中斷控制器,它向兩個中斷控制器發送信號,就不能使用老方法了,我們需要引入一個新的屬性。

參考設備樹的官方文檔,裏面有個中斷擴展屬性,在裏面可以指定多箇中斷,參考如下:

interrupts-extended = <&pic 0xA 8>, <&gic 0xda>;

比如該示例有兩個中斷控制器,每個後面緊跟對應得描述內容,描述內容的多少由中斷控制器決定。
如下圖截取按鍵中斷的描述:
第五課. 內核中斷系統中的設備樹

首先是指定中斷控制器,讓再描述哪一個中斷。

對於<code>intc</code>中斷控制器:
第一個表示是發給主控制器還是子控制器;
第二個表示子中斷控制器發給主控制器的哪一個;
第三個表示是這個中斷控制器裏的哪一個中斷;
第四個表示中斷的觸發方式;

對於<code>gpg</code>中斷控制器:
第一個表示是這個中斷控制器裏的哪一個中斷;
第二個表表示中斷的觸發方式;
可以看到兩個不同的中斷控制器,它們後面的描述數據的數量是不一樣的,這個數量的多少,是在設備樹裏面<code>#interrupt-cells</code>裏定義的。

至此,對中斷屬性的解釋已經清楚了,我們的驅動程序需要設備號,中斷號是一個軟件的概念,那麼這些中斷信息怎麼轉換成中斷號呢?
在設備樹的設備節點中描述"中斷的硬件信息",表明使用了"哪一個中斷控制器裏的哪一個中斷, 及中斷觸發方式"。
設備節點會被轉換爲 platform_device, "中斷的硬件信息" 會轉換爲"中斷號", 保存在platform_device的"中斷資源"裏。
驅動程序從platform_device的"中斷資源"取出中斷號, 就可以request_irq了。

實驗

a.
把"002th_buttons_drv/jz2440_irq.dts" 放入內核 arch/arm/boot/dts目錄,
在內核根目錄下執行:

make dtbs   // 得到 arch/arm/boot/dts/jz2440_irq.dtb

使用上節視頻的uImage或這個jz2440_irq.dtb啓動內核;
b. 編譯、測試驅動:
    b.1 把 002th_buttons_drv 上傳到ubuntu
    b.2 編譯驅動:

export  PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin

cd 002th_buttons_drv

make   // 得到 buttons.ko

b.3 編譯測試程序:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/arm/4.3.2/bin

cd 002th_buttons_drv

arm-linux-gcc -o buttons_test  buttons_test.c

b.4 測試:

insmod buttons.ko
./buttons_test &

然後按鍵

第06節_內核對設備樹中斷信息的處理過程

  • 中斷結構
    硬件結構上看, 處理過程分上下兩個層面: 中斷控制器, 使用中斷的設備;
    軟件結構上看, 處理過程分左右兩個部分: 在設備樹中描述信息, 在驅動中處理設備樹;

(1) 中斷控制器

這又分爲root irq controller, gpf/gpg irq controller
a. root irq controller
    a.1 在設備樹中的描述
    a.2 在內核中的驅動
b. 對於S3C2440, 還有: gpf/gpg irq controller
    b.1 在設備樹中的描述(在pinctrl節點裏)
    b.2 在內核中的驅動 (在pinctrl驅動中)

(2) 設備的中斷

a.1 在設備節點中描述(表明使用"哪一個中斷控制器裏的哪一個中斷, 及中斷觸發方式")
a.2 在內核中的驅動 (在platform_driver.probe中獲得IRQ資源, 即中斷號)

irq_domain是核心:

a. 每一箇中斷控制器都有一個irq_domain
b. 對設備中斷信息的解析,
    b.1 需要調用 <code>irq_domain->ops->xlate </code>(即從設備樹中獲得hwirq, type)
    b.2 獲取未使用的virq, 保存: <code>irq_domain->linear_revmap[hwirq] = virq;</code>
    b.3 在hwirq和virq之間建立聯繫:

    要調用 irq_domain->ops->map, 比如根據hwirq的屬性設置virq的中斷處理函數(是一個分發函數還是可以直接處理中斷)
        <code>irq_desc[virq].handle_irq = 常規函數;</code>
    如果這個hwirq有上一級中斷, 假設它的中斷號爲virq', 還要設置:
        <code>irq_desc[virq'].handle_irq = 中斷分發函數;</code>

中斷相關代碼調用關係

s3c2440設備樹中斷相關代碼調用關係:
(1) 上述處理過程如何觸發?
a. 內核啓動時初始化中斷的入口:

start_kernel // init/main.c
    init_IRQ();
        if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
            irqchip_init();   // 一般使用它
        else
            machine_desc->init_irq();

b. 設備樹中的中斷控制器的處理入口:

irqchip_init // drivers/irqchip/irqchip.c
    of_irq_init(__irqchip_of_table);  // 對設備樹文件中每一箇中斷控制器節點, 調用對應的處理函數
        爲每一個符合的"interrupt-controller"節點,
        分配一個of_intc_desc結構體, desc->irq_init_cb = match->data; // = IRQCHIP_DECLARE中傳入的函數
        並調用處理函數

        (先調用root irq controller對應的函數, 再調用子控制器的函數, 再調用更下一級控制器的函數...)

(2) root irq controller的驅動調用過程

a. 爲root irq controller定義處理函數:

IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);  //drivers/irqchip/irq-s3c24xx.c

其中:

#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn) \
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type)           \
    static const struct of_device_id __of_table_##name      \
        __used __section(__##table##_of_table)          \
         = { .compatible = compat,              \
             .data = (fn == (fn_type)NULL) ? fn : fn  }

展開爲:

    static const struct of_device_id __of_table_s3c2410_irq     \
        __used __section("__irqchip_of_table")          \
         = { .compatible = "samsung,s3c2410-irq",               \
             .data = s3c2410_init_intc_of  }

它定義了一個<code>of_device_id</code>結構體, 段屬性爲<code>irqchip_of_table</code>, 在編譯內核時這些段被放在<code>irqchip_of_table</code>地址處。

即<code>__irqchip_of_table</code>起始地址處,放置了一個或多個 <code>of_device_id,</code> 它含有<code>compatible</code>成員;

設備樹中的設備節點含有<code>compatible</code>屬性,

如果雙方的<code>compatible</code>相同, 並且設備節點含有<code>interrupt-controller</code>屬性,則調用<code>of_device_id</code>中的函數來處理該設備節點。

所以'''<code>IRQCHIP_DECLARE</code>是用來聲明設備樹中的中斷控制器的處理函數'''。

b. root irq controller處理函數的執行過程:

s3c2410_init_intc_of  // drivers/irqchip/irq-s3c24xx.c
    // 初始化中斷控制器: intc, subintc
    s3c_init_intc_of(np, interrupt_parent, s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));

        // 爲中斷控制器創建irq_domain
        domain = irq_domain_add_linear(np, num_ctrl * 32,
                                 &s3c24xx_irq_ops_of, NULL);

        intc->domain = domain;

        // 設置handle_arch_irq, 即中斷處理的C語言總入口函數
        set_handle_irq(s3c24xx_handle_irq);

'''(3) pinctrl系統中gpf/gpg irq controller的驅動調用過程'''

a. pinctrl系統的驅動程序:

a.1 源代碼: <code>drivers/pinctrl/samsung/pinctrl-samsung.c</code>

static struct platform_driver samsung_pinctrl_driver = {
    .probe      = samsung_pinctrl_probe,
    .driver = {
        .name   = "samsung-pinctrl",
        .of_match_table = samsung_pinctrl_dt_match, // 含有 { .compatible = "samsung,s3c2440-pinctrl", .data = &s3c2440_of_data },
        .suppress_bind_attrs = true,
        .pm = &samsung_pinctrl_pm_ops,
    },
};

a.2 設備樹中:

pinctrl@56000000 {
    reg = <0x56000000 0x1000>;
    compatible = "samsung,s3c2440-pinctrl";  // 據此找到驅動

a.3 驅動中的操作:

samsung_pinctrl_probe  // drivers/pinctrl/samsung/pinctrl-samsung.c
    最終會調用到 s3c24xx_eint_init // drivers/pinctrl/samsung/pinctrl-s3c24xx.c

        // eint0,1,2,3的處理函數在處理root irq controller時已經設置; 
        // 設置eint4_7, eint8_23的處理函數(它們是分發函數)
        for (i = 0; i < NUM_EINT_IRQ; ++i) {
            unsigned int irq;

            if (handlers[i]) /* add by [email protected], 不再設置eint0,1,2,3的處理函數 */
            {
                irq = irq_of_parse_and_map(eint_np, i);
                if (!irq) {
                    dev_err(dev, "failed to get wakeup EINT IRQ %d\n", i);
                    return -ENXIO;
                }

                eint_data->parents[i] = irq;
                irq_set_chained_handler_and_data(irq, handlers[i], eint_data);
            }
        }

        // 爲GPF、GPG設置irq_domain
        for (i = 0; i < d->nr_banks; ++i, ++bank) {

            ops = (bank->eint_offset == 0) ? &s3c24xx_gpf_irq_ops
                               : &s3c24xx_gpg_irq_ops;

            bank->irq_domain = irq_domain_add_linear(bank->of_node, bank->nr_pins, ops, ddata);
        }

(4) 使用中斷的驅動調用過程:

a. 在設備節點中描述(表明使用"哪一個中斷控制器裏的哪一個中斷, 及中斷觸發方式"),比如:

    buttons {
        compatible = "jz2440_button";
        eint-pins  = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>;
        interrupts-extended = <&intc 0 0 0 3>,
                              <&intc 0 0 2 3>,
                              <&gpg 3 3>,
                              <&gpg 11 3>;
    };

b. 設備節點會被轉換爲 platform_device, "中斷的硬件信息" 會轉換爲"中斷號", 保存在platform_device的"中斷資源"裏

第3課第05節_device_node轉換爲platform_device, 講解了設備樹中設備節點轉換爲 platform_device 的過程;

我們只關心裏面對中斷信息的處理:

of_device_alloc (drivers/of/platform.c)
    dev = platform_device_alloc("", PLATFORM_DEVID_NONE);  // 分配 platform_device

    num_irq = of_irq_count(np);  // 計算中斷數

    of_irq_to_resource_table(np, res, num_irq) // drivers/of/irq.c, 根據設備節點中的中斷信息, 構造中斷資源
        of_irq_to_resource
            int irq = of_irq_get(dev, index);  // 獲得virq, 中斷號
                            rc = of_irq_parse_one(dev, index, &oirq); // drivers/of/irq.c, 解析設備樹中的中斷信息, 保存在of_phandle_args結構體中

                            domain = irq_find_host(oirq.np);   // 查找irq_domain, 每一箇中斷控制器都對應一個irq_domain

                            irq_create_of_mapping(&oirq);             // kernel/irq/irqdomain.c, 創建virq和中斷信息的映射
                                irq_create_fwspec_mapping(&fwspec);
                                    irq_create_fwspec_mapping(&fwspec);
                                        irq_domain_translate(domain, fwspec, &hwirq, &type) // 調用irq_domain->ops->xlate, 把設備節點裏的中斷信息解析爲hwirq, type

                                        virq = irq_find_mapping(domain, hwirq); // 看看這個hwirq是否已經映射, 如果virq非0就直接返回

                                        virq = irq_create_mapping(domain, hwirq); // 否則創建映射
                                                    virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);  // 返回未佔用的virq

                                                    irq_domain_associate(domain, virq, hwirq) // 調用irq_domain->ops->map(domain, virq, hwirq), 做必要的硬件設置                                 

c. 驅動程序從<code>platform_device</code>的"中斷資源"取出中斷號, 就可以<code>request_irq</code>了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章