當(dāng)前位置:首頁 > 嵌入式 > 嵌入式教程
[導(dǎo)讀]linux UART串口驅(qū)動開發(fā)文檔

W83697/W83977 super I/O串口驅(qū)動開發(fā)

內(nèi)容簡介: 介紹了Linux下的串口驅(qū)動的設(shè)計層次及接口, 并指出串口與TTY終端之間的關(guān)聯(lián)層次(串口可作TTY終端使用), 以及Linux下的中斷處理機制/中斷共享機制, 還有串口緩沖機制當(dāng)中涉及的軟中斷機制; 其中有關(guān)w83697/w83977 IC方面的知識, 具體參考相關(guān)手冊, 對串口的配置寄存器有詳細介紹, 本文不再進行說明.

目錄索引:

一. Linux的串口接口及層次.

二. Linux的中斷機制及中斷共享機制.

三. Linux的軟中斷機制.

四. TTY與串口的具體關(guān)聯(lián).

一. Linux的串口接口及層次.

串口是使用已經(jīng)非常廣的設(shè)備了, 因此在linux下面的支持已經(jīng)很完善了, 具有統(tǒng)一的編程接口, 驅(qū)動開發(fā)者所要完整的工作就是針對不同的串口IC來做完成相應(yīng)的配置宏, 這此配置宏包括讀與寫, 中斷打開與關(guān)閉(如傳送與接收中斷), 接收狀態(tài)處理, 有FIFO時還要處理FIFO的狀態(tài). 如下我們就首先切入這一部分, 具體了解一下與硬件串口IC相關(guān)的部分在驅(qū)動中的處理, 這一部分可以說是串口驅(qū)動中的最基礎(chǔ)部分, 直接與硬件打交道, 完成最底層具體的串口數(shù)據(jù)傳輸.

1. 串口硬件資源的處理.

W83697及W83977在ep93xx板子上的映射的硬件物理空間如下:

W83697: 0x20000000起1K空間.

W83977: 0x30000000起1K空間.

因為串口設(shè)備的特殊性, 可以當(dāng)作終端使用, 但是終端的使用在內(nèi)核還未完全初始化之前(關(guān)于串口與終端的關(guān)聯(lián)及層次在第四節(jié)中詳細), 此時還沒有通過mem_init()建立內(nèi)核的虛存管理機制, 所以不能通過ioreamp來進行物理內(nèi)存到虛存的映射(物理內(nèi)存必須由內(nèi)核映射成系統(tǒng)管理的虛擬內(nèi)存后才能進行讀寫訪問), 這與先前所講的frAMEbuffer的物理內(nèi)存映射是不同的, 具體原因如下:

√終端在注冊并使用的調(diào)用路徑如下:

STart_kernel→cONsole_init→uart_console_init→ep93xxuart_console_init→register_console→csambuart_console_write.

√FrameBuffer顯卡驅(qū)動中的物理內(nèi)存映射調(diào)用路徑如下:

start_kernel→ rest_init→init(內(nèi)核初始線程)→ do_basic_setup→ do_initcalls→fbmem_init→lanrryfb_init

(Linux下用__setup啟動初期初始機制與__initcall系統(tǒng)初始化完成后的調(diào)用機制, 這兩個機制本質(zhì)沒有什么差別,主要是執(zhí)行時所處的系統(tǒng)時段)

√串口物理內(nèi)存映射到虛存的時機:

依據(jù)上面所介紹的兩條執(zhí)行路徑,再看內(nèi)核的內(nèi)存初始化的調(diào)用時期,只有完成這個初始化后才能進行物理內(nèi)存到虛存的映射,內(nèi)存的初始化主要是在start_kernel中調(diào)用的mem_init,這個調(diào)用明顯在uart_console_init之后,在fbmem_init之后,到此就全部說明了為何不能在對串口使用ioremap進行物理內(nèi)存的映射了。那么究竟要在什么時機用什么方法進行串口物理內(nèi)存的映射呢?

√串口物理內(nèi)存的映射方式:

參考ep93xx的板載I/O的映射處理,它的處理方式是一次性將所有的物理I/O所在的內(nèi)存空間映射到虛存空間,映射的基址是IO_BASE_VIRT,大小是IO_SIZE.

/* Where in virtual memory the IO devices (TImers, system controllers

* and so on). This gets used in arch/ARM/mach-ep93xx/mm.c.*/

#define IO_BASE_VIRT 0xFF000000 // Virtual addrESS of IO

#define IO_BASE_PHYS 0x80000000 // Physical address of IO

#define IO_SIZE 0x00A00000 // How much?

完成映射的函數(shù)是ep93xx_map_io, 所有要進行映射內(nèi)存都在ep93xx_io_desc結(jié)構(gòu)當(dāng)中描述,我們的串口映射也加在這個地方,基址分別如下:

文件: linux-2.4.21/include/asm-arm/arch-ep93xx/regmap.h

#define IO_W83697_UART_BASE 0x20000000

#define IO_W83697_UART_SIZE 0x1000

#define IO_W83977_UART_BASE 0x30000000

#define IO_W83977_UART_SIZE 0x1000

#define IO_SIZE_2 (IO_SIZE+0x100000)

#define IO_BASE83697_VIRT IO_BASE_VIRT+IO_SIZE

#define IO_BASE83977_VIRT IO_BASE_VIRT+IO_SIZE_2

ep93xx_map_io完成是在arch初始化中賦值給struct machine_desc mdesc這個機器描述結(jié)構(gòu)體,主要由位于mach-ep93xxarch.c文件中如下宏完成此結(jié)構(gòu)的初始化:

MACHINE_START(EDB9302, "edb9302")

…..

MAPIO(ep93xx_map_io) //初始化. map_io= ep93xx_map_io….

MACHINE_END

最終這個函數(shù)在調(diào)用路徑如下:

start_kernel→setup_arch→paging_init→(mdesc->map_io())

至此完成串口物理內(nèi)存的映射,這個過程在console_init調(diào)用之前,因此不會有問題, 此種方法建立映射是直接創(chuàng)建物理內(nèi)存頁與虛存頁的對應(yīng),大小為4k一頁,最終調(diào)用的是create_mapping(), 建立頁表映射是與具體的平臺相關(guān)的,位于mach_ep93xx/mm/ proc-arm920.S文件中提供了與具體平臺相關(guān)的頁表建立函數(shù),其中包括TLB表操作/Cache操作/頁表操作等:

在上層的start_kernel→setup_arch→ setup_processor調(diào)用下,會在proc-arm920.S文件中查找".proc.info"節(jié)的__arm920_proc_info,并從中找到配置的process相關(guān)的操作函數(shù),具體的arm頁表建立的詳情須要參看ARM內(nèi)存管理的相關(guān)手冊.

.section ".proc.info", #alloc, #execinstr

.type __arm920_proc_info,#object

__arm920_proc_info:

.long 0x41009200

……

.long arm920_processor_functions

.size __arm920_proc_info, . - __arm920_proc_info

在arm920_processor_functions中包含的頁表操作如下:

/* pgtable */

.word CPU_arm920_set_pgd

.word cpu_arm920_set_pmd

.word cpu_arm920_set_pte

2. 與串口硬件相關(guān)的宏主.

如下, 下面將詳術(shù)如下, 并指出其具體被使用的環(huán)境上下文:

<1>. 讀寫數(shù)據(jù).[!--empirenews.page--]

#define UART_GET_CHAR(p) ((readb((p)->membase + W83697_UARTDR)) & 0xff)

#define UART_PUT_CHAR(p, c) writeb((c), (p)->membase + W83697_UARTDR)

<2>. 接收發(fā)送狀態(tài).

#define UART_GET_RSR(p) ((readb((p)->membase + W83697_UARTRSR)) & 0xff)

#define UART_PUT_RSR(p, c) writeb((c), (p)->membase + W83697_UARTRSR)

<3>. 發(fā)送及接收中斷狀態(tài).

#define UART_GET_CR(p) ((readb((p)->membase + W83697_UARTCR)) & 0xff)

#define UART_PUT_CR(p,c) writeb((c), (p)->membase + W83697_UARTCR)

#define UART_GET_INT_STATUS(p) ((readb((p)->membase + W83697_UARTIIR)) & 0xff)

<4>. 以及其它的中斷使能設(shè)置等, 在傳送時打開傳送中斷即會產(chǎn)生傳送中斷.

#define UART_PUT_ICR(p, c) writeb((c), (p)->membase + W83697_UARTICR)

<5>. FIFO的狀態(tài), 是否讀空/是否寫滿; 每次讀時必須讀至FIFO空, 寫時必須等到FIFO不滿時才能寫(要等硬件傳送完) .

接收中斷讀空FIFO的判斷:

status = UART_GET_FR(port);

while (UART_RX_DATA(status) && max_count--) {

……

}

發(fā)送中斷寫FIFO: 當(dāng)發(fā)送緩沖區(qū)中有數(shù)據(jù)要傳送時, 置發(fā)送中斷使能, 隨后即產(chǎn)生傳送中斷, 此時FIFO為空, 傳送半個FIFO大小的字節(jié), 如果發(fā)送緩沖區(qū)數(shù)據(jù)傳完,則關(guān)閉發(fā)送中斷.

<6>. 傳送時可直接寫串口數(shù)據(jù)口, 而不使用中斷, 但必須等待檢測FIFO的狀態(tài)

do {

status = UART_GET_FR(port);

} while (!UART_TX_READY(status)); //wait for tx buffer not full...

3. 串口驅(qū)動的參數(shù)配置

串口的參數(shù)主要包括如下幾個參數(shù),全部都記錄在uart_port結(jié)構(gòu)上,為靜態(tài)的賦值,本串口驅(qū)動支持6個設(shè)備,所以驅(qū)動中就包括了6個port,一個串口對應(yīng)一個port口,他們之間除了對應(yīng)的中斷號/寄存器起始基址/次設(shè)備號不同之外,其它的參數(shù)基本相同.

√串口對應(yīng)中斷, 這里六個串口,其中有3個串口使用的系統(tǒng)外部中斷0/1/2, 其中另外幾個中斷用提GPIO中斷,具體有關(guān)GPIO中斷的內(nèi)容可參見EP93XX芯片手冊, GPIO中斷共享一個系統(tǒng)中斷向量,涉及中斷共享的問題,后面將詳述LINUX中的中斷共享支持.

√串口時鐘, 串口時鐘用來轉(zhuǎn)換計算須要設(shè)置到配置寄存器當(dāng)中的波特率比值,其計算方法為:quot = (port->uartclk / (16 * baud)); baud為當(dāng)前設(shè)置的波特率,可為115200等值, 取決于所選的串口時鐘源, quot即為要設(shè)置到寄存器當(dāng)中的比值.

√串口基址, 即串口所有配置寄存器基礎(chǔ)址.

√串口次設(shè)備號(由驅(qū)動中的最低次設(shè)備號依次累加)

前面已經(jīng)講過了六個串口中斷,這里詳細列出對應(yīng)情況如下,方便查找:

w83697的三個串口對應(yīng)中斷如下:

uart 1: 讀寫數(shù)據(jù)寄存器偏移為00x3F8, 對應(yīng)系統(tǒng)外部中斷INT_EXT[0].

uart 2: 讀寫數(shù)據(jù)寄存器偏移為00x2F8, 對應(yīng)系統(tǒng)外部中斷INT_EXT[1].

uart 3: 讀寫數(shù)據(jù)寄存器偏移為00x3e8, 對應(yīng)系統(tǒng)外部中斷INT_EXT[2].

uart 4: 讀寫數(shù)據(jù)寄存器偏移為00x3e8, 對應(yīng)EGPIO[8].

w83977的兩個串口對應(yīng)中斷如下:

uart 1: 讀寫數(shù)據(jù)寄存器偏移為00x3F8, 對應(yīng)EGPIO[1].

uart 2: 讀寫數(shù)據(jù)寄存器偏移為00x2F8, 對應(yīng)EGPIO[2].

下面列出其中一個具體的串口port的定義如下:

{

.port = {

.membase = (void *)W83697_UART4_BASE,

.mapbase = W83697_UART4_BASE,

.iotype = SERIAL_IO_MEM,

.irq = W83697_IRQ_UART4, //串口中斷號

.uartclk = 1846100, //uart時鐘,默認.

.fifosize = 8, //硬件fifo大小.

.ops = &amba_pops, //底層驅(qū)動的硬件操作集,如開關(guān)中斷等.

.flags = ASYNC_BOOT_AUTOCONF,

.line = 3, //串口在次設(shè)備數(shù)組中的索引號,須注意從0計起…

},

.dtr_mask = 0,

.rts_mask = 0,

}

4. 串口驅(qū)動的底層接口函數(shù)

驅(qū)動文件:linux-2.4.21/drivers/serial/Ep93xx_w83697.c

相關(guān)文件: linux-2.4.21/drivers/serial/core.c 下面詳述.

函數(shù): w83697uart_rx_chars(struct uart_port *port, struct pt_regs *regs)

描述: 串口接收數(shù)據(jù)中斷, 此函數(shù)中應(yīng)當(dāng)注意的要點如下:

接收數(shù)據(jù)時,要注意判斷FIFO是否讀空(參見上述2點中說明).

接收數(shù)據(jù)放入flip緩沖區(qū),此緩沖區(qū)專供緩存中斷中接收到的數(shù)據(jù),是最原始的串口數(shù)據(jù),為更上一層中各種終端處理模式的原始數(shù)據(jù),可以進行各種加工處理。

接收數(shù)據(jù)到flip緩沖區(qū)中時,須根據(jù)硬件接收狀態(tài),置每一個接收到的字符的接收標(biāo)志,放在flag_buf_ptr當(dāng)中, 標(biāo)志類型有TTY_NORMAL/ TTY_PARITY/ TTY_FRAME等,分別表示正常/校驗出錯/幀出錯(無停止位)等.

每接收數(shù)據(jù)之后,會通過在退出中斷前調(diào)用tty_flip_buffer_push()來往tq_timer任務(wù)列表中加一個隊列任務(wù),串口的隊列任務(wù)主要是負責(zé)將中斷接收到flip緩沖區(qū)中的數(shù)據(jù)往上傳輸至終端終沖區(qū), 隊列任務(wù)的機制將在后面介紹,它是一種異步執(zhí)行機制,在軟中斷中觸發(fā)執(zhí)行.

函數(shù): static void w83697uart_tx_chars(struct uart_port *port)

描述: 串口發(fā)送數(shù)據(jù)中斷, 發(fā)送中斷中要做的事比較少,比起接收中斷簡單了好多,注意事項如下:

當(dāng)上層要發(fā)送數(shù)據(jù)時,就會打開串口發(fā)送中斷,此時FIFO為空,傳送半個FIFO大小數(shù)據(jù)即退出, 通常打開中斷是通過更上一層的uart_flush_chars()調(diào)用,最終調(diào)用的是w83697uart_start_tx().

檢測當(dāng)沒有數(shù)據(jù)要傳輸?shù)臅r候,關(guān)閉傳送中斷,在傳送之前與傳送完之后都有檢測.

最重要的一點是如果傳送緩沖區(qū)當(dāng)中的字符數(shù)已經(jīng)小于WAKEUP_CHARS, 則可以喚醒當(dāng)前正在使用串口進行傳送的進程,這里是通過tasklet機制來完成,這也是一異步執(zhí)行機制.[!--empirenews.page--]

順帶介紹開關(guān)中斷接口:

static void w83697uart_start_tx(struct uart_port *port, unsigned int tty_start)

static void w83697uart_stop_tx(struct uart_port *port, unsigned int tty_stop)

函數(shù): static void w83697uart_int(int irq, void *dev_id, struct pt_regs *regs)

描述: 中斷處理函數(shù),為3個使用系統(tǒng)外部中斷的的串口的中斷入口,其中必須處理的中斷狀態(tài)分為如下幾種, 注意必須在處理中斷時根據(jù)手冊中的說明來清除中斷,通常是讀或?qū)懩承┘拇嫫骷纯伞?/p>

接收中斷.

傳送中斷.

FIFO超時中斷.

其它不具體處理的中斷,必須讀相應(yīng)寄存器清中斷.

函數(shù): static void w83697uart_int2(int irq, void *dev_id, struct pt_regs *regs)

描述: 中斷處理函數(shù),為另外幾個使用串口使用的GPIO中斷入口,GPIO中斷共享同一個系統(tǒng)中斷向量, 必須根據(jù)GPIO的中斷狀態(tài)寄存器的相應(yīng)位來判斷對應(yīng)的中斷是屬哪一個串口的,從而進行相應(yīng)的處理,其實這個判斷也是無所謂的,因為中斷產(chǎn)生時傳進來的參數(shù)已經(jīng)含有了相應(yīng)串口的參數(shù), 在判斷完中斷產(chǎn)生的GPIO口后立即調(diào)用w83697uart_int2 完成具體的中斷處理.

函數(shù): static int w83697uart_startup(struct uart_port *port)

描述: 串口開啟后的初始化函數(shù),主要完成初始化配置,以及安裝中斷處理了函數(shù),初始化配置包括打開中斷使能標(biāo)志。

函數(shù): static void w83697uart_shutdown(struct uart_port *port)

描述: 串口關(guān)閉函數(shù),清除配置,半閉中斷.

函數(shù): static void w83697uart_change_speed(struct uart_port *port, unsigned int cflag, unsigned int iflag, unsigned int quot)

描述: 配置函數(shù),經(jīng)由上次調(diào)用下來,主要配制串口的波特率比,以及各種容錯處理,在串口打開初始化時會被調(diào)用,在必變串口波特率/校驗方式/停止位/傳送位數(shù)等參數(shù)時會被調(diào)用.

5. 串口驅(qū)動與上層的接口關(guān)聯(lián)

文件: linux-2.4.21/drivers/serial/core.c

這一層接口是串口驅(qū)動中的共用部分代碼, 核心結(jié)構(gòu)為struct uart_driver. 這一層上承TTY終端,下啟串口底層,串口底層主要處理了與串口硬件相關(guān)的部分,并向上提供uart中間層向下的接口. Uart coar向下與底層驅(qū)動的接口,通過一個static struct uart_ops amba_pops結(jié)構(gòu)完成? 這個結(jié)構(gòu)直接賦值給串口struct uart_amba_port amba_ports 的.ops成員,最后將串口的port加入到uart_driver當(dāng)中完成關(guān)聯(lián), 通過uart_add_one_port加入.

static int __init w83697uart_init(void)

{

int ret, i;

ret = uart_register_driver(&amba_reg);

if (ret == 0) {

for (i = 0; i < UART_NR; i++)

uart_add_one_port(&amba_reg, &amba_ports[i].port);

}

return ret;

}

二. Linux的中斷機制及中斷共享機制.

前面講到了有6個串口,除了w83697中的前三個串使用的是獨立的系統(tǒng)外部中斷之外,其它的在個串口是共享一個系統(tǒng)中斷向量的,現(xiàn)在我們來看看多個中斷是如何掛在一個系統(tǒng)中斷向量表當(dāng)中的,共享中斷到底是什么樣的一種機制?

進行分析代碼可知,linux下的中斷采用的是中斷向量的方式,每一個中斷對應(yīng)一個中斷描述數(shù)組當(dāng)中的一項, 結(jié)構(gòu)為struct irqdesc,其當(dāng)中對應(yīng)一成員結(jié)構(gòu)為struct irqactionr 的成員action, 這個即表示此中斷向量對應(yīng)的中斷處理動作,這里引用從網(wǎng)上下載的一幅圖講明中斷向量表與中斷動作之間的關(guān)系:

struct irqaction {

void (*handler)(int, void *, struct pt_regs *);

unsigned long flags;

unsigned long mask;

const char *name;

void *dev_id;

struct irqaction *next;

};

從上面的結(jié)構(gòu)體與圖當(dāng)中,我們就可以很清楚的看到,一個中斷向量表可以對應(yīng)一個irqaction,也可能對應(yīng)多個由鏈表鏈在一起的一個鏈表irqaction, 這當(dāng)中主要在安裝中斷的時候通過中斷的標(biāo)志位來決定:

安裝中斷處理,不可共享:

retval = request_irq(port->irq, w83697uart_int, 0, "w83697_uart3", port);

安裝中斷處理,可共享:

retval = request_irq(port->irq, w83697uart_int2, SA_SHIRQ, "w83977_uart5", port);

由上即可知,安裝共享中斷時,只須指定安裝的中斷標(biāo)志位flag為SA_SHIRQ, 進入分析安裝中斷的處理可知,在安裝時,會檢測已經(jīng)安裝的中斷是否支持共享中斷,如果不支持,則新的中斷安裝動作失敗;如果已經(jīng)安裝的中斷支持共享中斷,則還必須檢測將要安裝的新中斷是否支持中斷共享,如果不支持則安裝還是會失敗,如果支持則將此新的中斷處理鏈接到此中斷向量對應(yīng)的中斷動作處理鏈表當(dāng)中.

在產(chǎn)生中斷時,共享中斷向量中對應(yīng)的中斷處理程序鏈表中的每一個都會被調(diào)用,依據(jù)鏈表的次序來,這樣處理雖然會有影響到效率,但是一般情況下中斷傳到用戶的中斷處理服務(wù)程序中時,由用戶根據(jù)硬件的狀態(tài)來決定是否處理中斷,所以能常情況下都是立即就返回了,效率的影響不會是大的問題.

三. Linux的軟中斷機制.

前面已經(jīng)簡單講過了LINUX下的硬中斷處理機制,其實硬中斷的處理都由LINUX底層代碼具體完成了,使用者一般在處理硬中斷時是相當(dāng)簡單的,只須要用request_irq()簡單的掛上中斷即可,這里我們進一步介紹一下LINUX下的軟中斷機制,軟中斷機制相比起硬中斷機制稍微復(fù)雜一些,而且在LINUX內(nèi)核本身應(yīng)用非常的廣, 它作為一種軟性的異步執(zhí)行機制,只有深入理解了它才能靈活的運用.

之所以提到內(nèi)核的softirq機制,主要是因為在串口中斷也使用了這些機制,理解了這些機制就能更加明白串口驅(qū)動一些問題, 現(xiàn)在先提出幾個問題如下:

前面提供到中斷接收后數(shù)據(jù),先放到flip緩沖區(qū)當(dāng)中,這樣讓人很容易進一步想知道,中斷處理的緩沖區(qū)的數(shù)據(jù),用戶進程讀取串口時如何讀到的?很明顯中斷處于內(nèi)核空間,用戶讀取串口輸入進程是在用戶空間,中斷緩沖區(qū)中的數(shù)據(jù)如何被處理到終端緩沖區(qū)中,供用戶讀取的?[!--empirenews.page--]

另外寫串口時,是向終端緩沖區(qū)當(dāng)中寫入,那么上層的寫操作如何知道下層緩沖區(qū)中的的數(shù)據(jù)是否傳送完成?用戶空間的寫串口進程處于什么樣的狀態(tài)?如果是寫完緩沖區(qū)就睡眠以保證高效的CPU使用率,那么何時才應(yīng)該醒過來? 由誰負責(zé)醒過來?

1. 往tq_timer任務(wù)隊列中添加一項任務(wù).

根據(jù)以上這兩個問題,我們來深入代碼分析,首先看接收緩沖區(qū)中的數(shù)據(jù)如何上傳, 前面已經(jīng)提到過,接收中斷處理完成后,會調(diào)用tty_flip_buffer_push(),這個函數(shù)完成的功能就是往一系統(tǒng)定義的任務(wù)隊列當(dāng)中加入一個任務(wù),下面我們將詳細的分析加入的任務(wù)最終是如何執(zhí)行起來的.[任務(wù):這里所講的任務(wù)可以直接理解成為一個相應(yīng)的回調(diào)函數(shù),LINUX下術(shù)語稱作tasklet]

void tty_flip_buffer_push(struct tty_struct *tty)

{

if (tty->low_latency)

flush_to_ldisc((void *) tty);

else

queue_task(&tty->flip.tqueue, &tq_timer);

}

2. tq_timer的執(zhí)行路徑分析.

tq_timer是一個雙鏈表結(jié)構(gòu)任務(wù)隊列,每項任務(wù)包含一個函數(shù)指針成員, 它通過run_task_queue每次將當(dāng)中的所有任務(wù)(其實是一些函數(shù)指針)全部調(diào)用一次,然后清空隊列, 最終的執(zhí)行tq_timer的是在中斷底半的tqueue_bh 中執(zhí)行,如下:

void tqueue_bh(void)

{

run_task_queue(&tq_timer);

}

在void __init sched_init(void)當(dāng)中初始化底半的向量如, tqueue_bh初始化在bh_base的TIMER_BH位置,bh_base為一結(jié)構(gòu)很簡單的數(shù)組,在什么位置調(diào)用什么樣的了函數(shù)基本已經(jīng)形成默認的習(xí)慣:

init_bh(TIMER_BH, timer_bh);

init_bh(TQUEUE_BH, tqueue_bh);

init_bh(IMMEDIATE_BH, immediate_bh);

看看init_bh相當(dāng)于初始底半的服務(wù)程序,非常簡單:

void init_bh(int nr, void (*routine)(void))

{

bh_base[nr] = routine;

mb();

}

最終真正的執(zhí)行bh_base中保存的函數(shù)指針的,在bh_action()當(dāng)中:

static void bh_action(unsigned long nr)

{

if (bh_base[nr])

bh_base[nr]();

}

關(guān)于這里所指出的bh_base, 我們在后面就直接稱作bh,意即中斷底半所做的事.

3. tq_timer實現(xiàn)所依賴的tasklet.

那么bh_action在什么時候執(zhí)行呢?bh_action被初始化成bh_task_vec這32個tasklet調(diào)用的任務(wù), 因此它的依賴機制是tasklet機制,后面將進行簡單介紹.

void __init softirq_init()

{

int i;

for (i=0; i<32; i++)

tasklet_init(bh_task_vec+i, bh_action, i);

….

}

至此已經(jīng)把任務(wù)隊列的執(zhí)行流程及原理分析完成,tasklet是須要激活的,這里我們先指出任務(wù)隊列是如何激活的,在時鐘中斷的do_timer()當(dāng)中會調(diào)用mark_bh(TIMER_BH), 來激時鐘底半所依賴運行的tasklet,其中bh_task_vec的所有成員的函數(shù)指針全部指向bh_action.

static inline void mark_bh(int nr)

{

tasklet_hi_schedule(bh_task_vec+nr);

}

tasklet_hi_schedule的功能就是往tasklet當(dāng)中加入一個新的tasklet.

4. tasklet的機制簡單分析.

講到tasklet,我們才與我們真正要講的softirq最近了,因為目前在軟中斷當(dāng)中有主要的應(yīng)用就是tasklet,而且在所有32個軟中斷中僅有限的幾個軟中斷如下:

enum{

HI_SOFTIRQ=0,

NET_TX_SOFTIRQ,

NET_RX_SOFTIRQ,

TASKLET_SOFTIRQ

};

struct softirq_action{

void (*action)(struct softirq_action *);

void *data;

};

static struct softirq_action softirq_vec[32] __cacheline_aligned; //軟中斷的中斷向量表,實為數(shù)組.

[1]. 初始化軟中斷向量.

我們這里所要講的,就是HI_SOFTIRQ / TASKLET_SOFTIRQ 兩項,據(jù)我理解這兩項根本在實現(xiàn)機制上一樣的,之所以分開兩個名字叫主要是為了將不同的功能分開,就類似于雖然同是軟中斷,但是各處所完成的功能不一樣,所以分在兩個軟中斷完成, 后面我們僅取其中用于執(zhí)行時鐘底半的任務(wù)隊列HI_SOFTIRQ為例進行講解, 而且我們不講及多個CPU情況下的tasklet相關(guān)機制, 這兩項軟中斷的實始化如下:

void __init softirq_init()

{

….

open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);

open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);

}

open_softirq下所做的事相當(dāng)簡單, 即往軟中斷向量中賦值, 相當(dāng)于硬中斷當(dāng)中的request_irq掛硬件中斷:

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)

{

softirq_vec[nr].data = data;

softirq_vec[nr].action = action;

}

[2]. 軟中斷中斷服務(wù)程序

對于HI_SOFTIRQ , 相應(yīng)的中斷服務(wù)程序為tasklet_hi_action , 由上文所講的初始化過程給出,這個函數(shù)目前完成的功能相當(dāng)簡單,它的任務(wù)就是遍歷執(zhí)行此中斷所對應(yīng)一個tasklet鏈表,

NR_CPUS= 1.

struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;

[3]. 往軟中斷對應(yīng)的tasklet鏈表中加入新的tasklet, 加在尾部.

void __tasklet_hi_schedule(struct tasklet_struct *t)

{

t->next = tasklet_hi_vec[cpu].list;

tasklet_hi_vec[cpu].list = t;

cpu_raise_softirq(cpu, HI_SOFTIRQ);

}

最重要的一點是,在安裝了新的tasklet后,還必須將軟中斷設(shè)置為激活,告訴系統(tǒng)有軟中斷須要執(zhí)行了,下面一點即提到系統(tǒng)如何檢測是否有軟中斷須要處理:

#define __cpu_raise_softirq(cpu, nr) do { softirq_pending(cpu) |= 1UL << (nr); } while (0)

[4]. 軟中斷所依賴的執(zhí)行機制.[!--empirenews.page--]

講到最后還沒有指出軟中斷是如何觸發(fā)執(zhí)行的,其實很簡單:

在系統(tǒng)處理所有硬中斷信號時,他們的入口是統(tǒng)一的,在這個入口函數(shù)當(dāng)中除了執(zhí)行do_IRQ()完成硬件中斷的處理之外,還會執(zhí)行do_softirq()來檢測是否有軟中斷須要執(zhí)行,所以軟中斷所依賴的是硬件中斷機制;

另外還有一個專門處理軟中斷內(nèi)核線程ksoftirqd(),這個線程處理軟中斷級別是比較低的,他是一個無限LOOP不停的檢測是否有軟中斷須要處理,如果沒有則進行任務(wù)調(diào)度.

在do_softirq()中有如下的判斷,以決定是否有軟中斷須要執(zhí)行,如果沒有就直接退出,在[3]中提到的激活軟中斷時,要將相應(yīng)軟中斷位置1, 軟中斷有32個,因此一個整型數(shù)即可以表示32個軟中斷,即可判斷有什么樣的軟中斷須要處理,代碼如下:

pending = softirq_pending(cpu);

if (pending) {

}

….

do { //檢測32個軟中斷位標(biāo)志中是否有為1的…

if (pending & 1)

h->action(h);

h++;

pending >>= 1;

} while (pending);

[4]. 軟中斷所依賴的執(zhí)行時期問題.

之所以將這個問題單獨列開來講,是因為他特別的重要,上面我已經(jīng)講過了軟中斷是依賴硬中斷觸發(fā)執(zhí)行的,但是產(chǎn)生如下疑問:

是不是一有硬中斷發(fā)生就會觸發(fā)軟中斷的執(zhí)行?

軟中斷的執(zhí)行會不會影響到系統(tǒng)的性能?

會不會影響到硬中斷的處理效率?也就是說會不會導(dǎo)致在處理軟中斷時而引起硬中斷無法及時響應(yīng)呢?

再看do_softirq的代碼當(dāng)中有如下判斷:

if (in_interrupt())

return;

這個條件就是能否進行軟中斷處理的關(guān)鍵條件,因此由此也可以了解到軟中斷是一種優(yōu)先級低于硬中斷的軟性機制,具體來看看這個判斷條件是什么:

/*Are we in an interrupt context? Either doing bottom half

* or hardware interrupt processing?*/

#define in_interrupt() ({ const int __cpu = smp_processor_id();

(local_irq_count(__cpu) + local_bh_count(__cpu) != 0); })

/* softirq.h is sensitive to the offsets of these fields */

typedef struct {

unsigned int __softirq_pending;

unsigned int __local_irq_count;

unsigned int __local_bh_count;

unsigned int __syscall_count;

struct task_struct * __ksoftirqd_task; /* waitqueue is too large */

} ____cacheline_aligned irq_cpustat_t;

#define irq_enter(cpu,irq) (local_irq_count(cpu)++)

#define irq_exit(cpu,irq) (local_irq_count(cpu)--)

看到這里,不得不再多注意一個結(jié)構(gòu),那就是irq_cpustat_t, 先前我們講是否有軟中斷產(chǎn)生的標(biāo)志位,但沒有提到__softirq_pending,這個變量就是記載32個軟中斷是否產(chǎn)生的標(biāo)志,每一個軟中斷對應(yīng)一個位; 在中斷執(zhí)行的do_softirq中有如下幾個重要的動作,說明如下:

in_interrupt判斷是否可以進行軟中斷處理,判斷的條件就是沒有沒處在硬件中斷環(huán)境中,而且還沒有軟中斷正在執(zhí)行(即不允許軟中斷嵌套),軟中斷的嵌套避免是通過local_bh_disable()/local_bh_enable()實現(xiàn),至于帶有bh,其意也即指softirq是中斷底半(bh), 在處理硬件中斷時,一進行即會調(diào)用irq_enter來表示已經(jīng)進入硬件中斷處理程序,處理完硬件中斷后再調(diào)用irq_exit表示已經(jīng)完成處理;

pending判斷是否有軟中斷須要處理, 每個位用作當(dāng)作一個軟中斷是否產(chǎn)生的標(biāo)志.

清除所有軟中斷標(biāo)志位,因為下面即將處理; 但清除之前先緩存起來, 因為下面還要使用這個變量一次.

在進入軟中斷處理后,會關(guān)閉bh功能的執(zhí)行,執(zhí)行完后才打開,這樣在in_interrupt判斷當(dāng)中就會直接發(fā)現(xiàn)已經(jīng)有bh在執(zhí)行,不會再次進入bh執(zhí)行了,這嚴格保證了bh執(zhí)行的串行化.

打開硬件中斷,讓軟中斷在有硬件中斷的環(huán)境下執(zhí)行.

處理完軟中斷后關(guān)閉硬中斷,再次檢測是否有新的軟中斷產(chǎn)生,如果有的話,卻只須立即處理本次軟中斷過程未發(fā)生過的軟中斷向量. 之所以會有新的軟中斷產(chǎn)生,那是因為軟中斷是在開硬件中斷的情況下執(zhí)行,硬件中斷處理是可能又產(chǎn)生了新的軟中斷. 之所以只處理本次軟中斷未發(fā)生的軟中斷向量,依據(jù)我自己的理解,其目的是為了不加重軟中斷處理的負擔(dān)而不馬上處理,只是相應(yīng)的喚醒一個wakeup_softirqd線程,這是專門處理軟中斷的,這樣雖然延誤了軟中斷的處理,但避免了在硬中斷服務(wù)程序中拖延太長的時間.[關(guān)于軟中斷的處理在后緒版本變化也很大,可以進一步學(xué)習(xí)研究,如何使軟中斷不至影響中斷處理效率]

軟中斷處理這個函數(shù)雖然不長,但是相當(dāng)?shù)年P(guān)鍵,每一句代碼都很重要,結(jié)合上面所說的幾點,與源碼交互起來理解才能根本理解軟中斷的設(shè)計機制:

asmlinkage void do_softirq()

{

int cpu = smp_processor_id();

__u32 pending;

unsigned long flags;

__u32 mask;

if (in_interrupt()) return;

local_irq_save(flags);

pending = softirq_pending(cpu);

if (pending) {

struct softirq_action *h;

mask = ~pending;

local_bh_disable();

restart:

/* Reset the pending bitmask before enabling irqs */

softirq_pending(cpu) = 0;

local_irq_enable();

h = softirq_vec;

do {

if (pending & 1)

h->action(h);

h++;

pending >>= 1;

} while (pending);

local_irq_disable();

pending = softirq_pending(cpu);

if (pending & mask) {

mask &= ~pending;

goto restart;

}

__local_bh_enable();

if (pending)

wakeup_softirqd(cpu);

}

local_irq_restore(flags);

}

}

四. TTY與串口的具體關(guān)聯(lián).

串口設(shè)備可以當(dāng)作TTY終端來使用,這又使串口設(shè)備比一般的設(shè)備稍微復(fù)雜一些,因為他還必須與終端驅(qū)動關(guān)聯(lián)起來,雖然這部分與TTY的關(guān)聯(lián)已經(jīng)是屬于公用部分的代碼,并不須要驅(qū)動編寫者特別做些什么來進行支持,但對它與TTY的層次關(guān)聯(lián)的了解有助于理解整個串口的數(shù)據(jù)流向.[!--empirenews.page--]

串口要能夠成為終端,必須客外加入終端注冊及初始化的代碼,這部分很簡單,基本上所有的串口驅(qū)動都是固定的模式,并無什么修改,主要包括如下結(jié)構(gòu):

static struct console cs_amba_console = {

.name = "ttyBM",

.write = w83697uart_console_write,

.device = w83697uart_console_device,

.setup = w83697uart_console_setup,

.flags = CON_PRINTBUFFER,

.index = -1,

};

串口終端的注冊通過下面的函數(shù),將cs_amba_console注冊成為終端, 這個函數(shù)調(diào)用路徑是:

start_kernel()→console_init()→ep93xxuart_w83697_console_init()

void __init ep93xxuart_w83697_console_init(void)

終端會對應(yīng)一種具體設(shè)備的driver, 相對于串口這個結(jié)構(gòu)是uart_driver, 在驅(qū)動中我們已經(jīng)提供了一個這樣的結(jié)構(gòu). static struct uart_driver amba_reg, uart_register_driver會將它注冊成為終端對應(yīng)的driver, 因此真正串口與終端的關(guān)聯(lián)就在此處建立.

函數(shù): static int __init w83697uart_init(void)

描述: 調(diào)用uart_register_driver()完成串口與終端的關(guān)聯(lián),將串口注冊成為一種TTY設(shè)備,在uart_register_driver()當(dāng)中調(diào)用tty_register_driver()完成TTY設(shè)備注冊; 其次是完成串口port口的注冊,將靜態(tài)描述的所有串口port(結(jié)構(gòu)為struct uart_port)注冊到uart_driver當(dāng)中.

特別說明: 注冊串口TTY設(shè)備時,由于歷史的原因會注冊兩個TTY設(shè)備,一個是normal, 另一個是callout, 是兩個設(shè)備來的, 在我們這里兩者沒有什么差別,請看源碼中的注解:

.normal_name = "ttyBM",

.callout_name = "cuaam",

/*

* The callout device is just like the normal device except for

* the major number and the subtype code.

*/

函數(shù): static void __exit w83697uart_exit(void)

描述: 卸截設(shè)備,卸截port口,因為我編譯的驅(qū)動是與內(nèi)核綁定在一起的,因此實際上根本不會調(diào)用此函數(shù).

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉