Linux驅動開發-14、串行設備驅動模型

串行設備驅動模型

1、TTY概念解析

a) 串口終端(/dev/ttyS*

串口終端是使用計算機串口連接的終端設備;Linux以字符設備來處理這種串行端口;這些端口所對應的設備名稱是/dev/ttySAC0-N

 

b) 控制檯終端(/dev/console

計算機的輸出設備通常被稱爲控制檯終端,特指printk信息輸出到的設備;/dev/console是一個虛擬設備;它需要映射到真正的tty上,這可以在內核啓動參數中配置

 

c) 虛擬終端(/dev/tty*

用戶登錄時,使用的是虛擬終端 ,tty0是當前使用虛擬終端的別名

 

2、TTY子系統架構

a) tty 核心:爲tty驅動提供接口,隔離上層應用與底層硬件

b) tty線路規程:加工與tty驅動交互的數據(數據的格式化等),勾勒串行層的行爲,有助於複用底層的代碼來支持不同的技術

i. N_TTY -----> /dev/ttySX    (終端)

ii. N_IRDA----> /dev/ircommX (紅外)

iii. N_PPP -----> ppp0

 

c) tty驅動:關注uart或者其他底層串行硬件特徵的驅動程序

d) 串行子系統有提供一些內核API

e) 架構圖

 

 

 

3、回溯函數:dump_stack(); 使用方法:將該函數加入到要回溯的函數中去,之後內核啓動會自動串口打印回溯信息

 

4、串口驅動重要數據結構:

struct uart_driver:一個結構表示一個驅動;驅動能支持多個串口

struct uart_port:一個結構表示一個實在的串口:如串口0,串口1

struct uart_ops:串口操作集合,TTY最終調用的讀寫功能都在裏面定義關聯函數

 

 

5、串口驅動爲了將自身和內核聯繫起來,必須完成兩個步驟:

a) 通過調用:uart_register_driver(struct uart_driver *);向串行核心註冊

b) 通過調用:uart_add_one_port(struct uart_driver*,struct uart_port *),註冊其支持的每個端口

 

傳統的UART使用TTY驅動程序爲

/driver/serial/serial_core.c


USB-串行端口轉換器的TTY驅動程序在目錄:

 driver/usb/serial/usb-serial.c

TTY架構驅動追蹤分析

1、分析思路

a) TTY屬於字符設備

b) TTY爲分層架構

 

2、初始化設備

a) 串口在CPU啓動時引導向內核註冊爲平臺設備

b) 設備驅動初始化在prob函數進行

c) 初始化需要完成以下內容:

i. 獲取端口(一個端口就爲一個串口)

ii. 初始化端口

iii. 添加端口

iv. 向內核申請私有空間

v. 創建屬性設備文件

vi. 提供調頻支持

 

 

 

 

 

3、打開設備:上層調用到底層的歷程:

a) 思路:用戶打開open函數;系統需要尋找對應的file_operations

b) file_operations 是由誰註冊的?驅動中找找

 

底層驅動-----------------------------------------------------------------------------------------------------------

c) 查看驅動只有uart_register_driver();

 

TTY核心---------------------------------tty_io.c/h----------------------------------------------------------------

 

d) 進入查看,串口驅動向tty核心註冊了tty_drivertty_register_driver(normal)

e) 查看tty_register_driver():在這裏註冊了字符設備

i. cdev_init(&driver->cdev, &tty_fops);

ii. cdev_add(&driver->cdev, dev, driver->num);

f) 註冊字符設備對應的操作函數集在

i. &tty_fops  --> static const struct file_operations tty_fops

ii.  ---->tty_fops.open

iii.  ----> tty_open(struct inode *inode, struct file *filp)

iv. 在tty_open函數中對struct tty_operations uart_ops進行操作,通過下面語句

v. ----->retval = tty->ops->open(tty, filp); 通過這層調用進入TTY驅動層

 

TTY驅動----------------------------------serial_core.c/h------------------------------------------------------

 

vi. ----> 跟蹤 struct tty_operations uart_ops結構體中的 open函數

vii. ----> 進入static int uart_open(struct tty_struct *tty, struct file *filp)

viii. ----> 該函數又調用 uart_startup()函數

ix. ----> 該函數通過retval = uport->ops->startup(uport);進入驅動層

 

底層驅動--------------------------------------------------------------------------------------------------------

 

x. ---->最終調用到驅動程序中的struct uart_ops中的操作集

xi. ----> int (*startup)(struct uart_port *);

4、總結:用戶應用 的 open 通過層層調用到達驅動函數中的 xxx_startup(...);

xxx_startup函數中需要完成的任務

1、使能串口接收功能:rx_enabled(port) = 1;

2、註冊數據接收中斷處理程序:request_irq();

3、使能發送功能:tx_enabled(port) = 1;

4、註冊發送中斷處理函數:request_irq();

5、出錯處理

 

 

 

6、寫設備

a) 思路:用戶打開write函數;系統需要尋找對應的file_operations

b) file_operations 是由誰註冊的?驅動中找找

 

TTY核心---------------------------------tty_io.c/h----------------------------------------------------------------

 

c) 進入查看,串口驅動向tty核心註冊了tty_drivertty_register_driver(normal)

d) 查看tty_register_driver():在這裏註冊了字符設備

i. cdev_init(&driver->cdev, &tty_fops);

ii. cdev_add(&driver->cdev, dev, driver->num);

e) 註冊字符設備對應的操作函數集在

i. &tty_fops  --> static const struct file_operations tty_fops

ii.  ---->tty_fops.write函數

iii.  ----> tty_write(struct inode *inode, struct file *filp)

iv. 在該函數中調用了:do_tty_write(ld->ops->write, tty, file, buf, count)

 

TTY線路規程------------------------------tty_ldisc.c/h  n_tty.c--------------------------------------

 

v. 以上傳遞進來的是ld->ops  ==  struct tty_ldisc_ops *ops;

vi. ops->write 調用到struct tty_ldisc_ops tty_ldisc_N_TTY.write函數

vii. 在n_tty_write函數中對struct tty_operations uart_ops進行操作,通過下面語句

viii. -----> tty->ops->write(tty, b, nr);通過這層調用進入TTY驅動層

 

TTY驅動----------------------------------serial_core.c/h------------------------------------------------------

 

ix. ----> 跟蹤 struct tty_operations uart_ops結構體中的 write函數

x. ----> 進入uart_write

xi. ----> 該函數又調用了uart_ops結構中的 uart_startup()函數

xii. ---->接着再調用void __uart_start(struct tty_struct *tty)

xiii. ----> 之後再進過指針傳遞調用驅動程序

 

底層驅動--------------------------------------------------------------------------------------------------------

 

xiv. ---->最終調用到驅動程序中的struct uart_ops中的操作集

xv. ---->  void (*start_tx)(struct uart_port *);

在發送函數中需要做的就是

1、使能發送中斷

2、具體發送在中斷函數中進行

a) 判斷x_char 是否爲0,不爲0則發送x_char (x_char用來通知數據緩存是否忙碌)

b) 判斷髮送換從是否爲空:uart_circ_empty();或者是驅動設置爲停止發送狀態:uart_tx_stoped(),則取消發送

c) 循環發送直到循環緩衝爲空

i. 發送FIFO滿,退出發送

ii. 將要發送的數據寫入發送寄存器

iii. 修改循環緩衝位置

d) 如果發送緩衝有空閒空間,則喚醒發送進程: uart_wake_up();

e) 如果發送緩衝爲空,則關閉發送使能:uart_tx_stoped();

 

 

7、設備讀

a) 同理從用戶層到驅動,最終調用到驅動的

i. 其中要注意的是線路規程中的n_tty_read函數,不直接操作底層驅動的串口緩存,需要通過tty->read_buf[tty->read_tail]; 其中的數據是串口驅動通過發送函數(tty_push將數據推送到該buf中)

b) 驅動中的發送函數處理流程

藍色底爲內核提供的函數

while(max_count-- > 0) /*這個用來平衡系統的性能*/

{

i. 讀取UPFCON寄存器rd_regl();

ii. 讀取UFSTAT寄存器  rd_regl();

iii. 通過以上讀取的數據判斷FIFO緩存是否爲空,空則退出循環

iv. 讀取UERSTAT寄存器

v. 讀取URXH,從該寄存器中取出字符

vi. 流控處理

vii. 根據UFSTAT記錄錯誤類型

viii. 如果收到的是sysrq字符,進行特出處理,調用內核函數:

1. uart_handle_sysrq_char(port, ch)

ix. 把接收到的字符送到串口驅動的uart_insert_char();

}

最後一步:將串口緩存中的數據推送到tty->read_buftty_flip_buffer_push();

 

 

 

流控:Linux使用的是自動硬件流控 或者是 軟件流控-->使用x_char來通訊標識

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