串行設備驅動模型
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_driver:tty_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_driver:tty_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_buf中tty_flip_buffer_push();
流控:Linux使用的是自動硬件流控 或者是 軟件流控-->使用x_char來通訊標識