TTY驅動程序架構:
1. TTY概念解析
1.1 /dev/ttySCA0
1.2 /dev/tty1-n
1.3 /dev/console
在linux系統中,終端是一類字符型設備,它包括多種類型,通常使用tty來簡稱各種類型的終端設備。
. 串口終端(/dev/ttyS*)
串口終端是使用計算機串口連接的終端設備。Linux把每個串行端口都看做是一個字符設備。這些串行端口所對應的設備名稱是/dev/ttySAC*;
.控制台終端(/dev/console)
在Linux系統中,計算機的輸出設備通常被稱為控制台終端,這裡特指printk信息輸出到設備。/dev/console是一個虛擬的設備,它需要映射到真正的tty上,比如通過內核啟動參數“console=ttySCA0”就把console映射到了串口0
. 虛擬終端(、dev/tty*)
當用戶登錄時,使用的是虛擬終端。使用Ctcl+Alt[F1 - F6]組合鍵時,我們就可以切換到tty1、tty2、tty3等上面去。tty*就稱為虛擬終端,而tty0則是當前所使用虛擬終端的一個別名。
2. TTY架構分析
下面來通過一個回溯功能從內核代碼來印證上面一張圖的tty架構各函數調用關系!
第一紅色箭頭對應的函數就是上圖中的driver_write()
上圖中第二個紅色箭頭部分:在該函數處加上一個dump_stack函數保存然後重新編譯內核
通過tftp下載到開發板啟動內核會看到串口終端打印出這個函數調用的回溯信息!
今天電腦難產了!編譯內核用了好久,之前幾分鐘就搞定了!
開發板啟動內核然後dmesg命令查看回溯打印的信息(這裡為什麼沒直接打印到串口要用dmesg和printk打印級別有關)
這裡打印的比較多,仔細看其實都是重復的這裡我裡面的一段復制粘貼下來:
[] (dump_stack+0x18/0x1c) from [ ] (s3c24xx_serial_start_tx+0x14/0xb4) [ ] (s3c24xx_serial_start_tx+0x14/0xb4) from [ ] (uart_start+0x64/0x68) [ ] (uart_start+0x64/0x68) from [ ] (uart_write+0xc0/0xe4) [ ] (uart_write+0xc0/0xe4) from [ ] (do_output_char+0x16c/0x1d8) [ ] (do_output_char+0x16c/0x1d8) from [ ] (process_output+0x38/0x54) [ ] (process_output+0x38/0x54) from [ ] (n_tty_write+0x204/0x444) [ ] (n_tty_write+0x204/0x444) from [ ] (tty_write+0x14c/0x244) [ ] (tty_write+0x14c/0x244) from [ ] (redirected_tty_write+0x58/0x68) [ ] (redirected_tty_write+0x58/0x68) from [ ] (vfs_write+0xbc/0x150) [ ] (vfs_write+0xbc/0x150) from [ ] (sys_write+0x44/0x74) [ ] (sys_write+0x44/0x74) from [ ] (ret_fast_syscall+0x0/0x30)
這函數dump_stack()功能好強大!(要是能做成圖形化界面直接顯示下面的圖形結構真是逆天了)
上面的有個函數n_tty_write函數其實是線路規程裡面的write!
下面先來分析串口初始化:先上圖
內核中2440、6410的串口部分用的都是s3c24xx.. 這個函數,內部實現都差不多,肯定是內核驅動人員偷懶了!
串口驅動分析之初始化:
總結來說最重要的有四項工作:串口初始化、串口打開、串口讀取操作、串口寫操作
內核代碼裡面最重要和串口相關文件一個是samsung.c,一個是s3c6400.c
1. 串口驅動程序結構
看下面的圖(下面2. 串口中的重要數據結構那張分析流程圖):首先用戶空間的write串口寫函數找到內核中對應的數據結構
這裡可以看到用戶空間的write函數對應的file_operations中的 tty_write,這裡還有一些其他的常用函數
繼續對照著下面的圖看tty_write對應的應該是線路規程中的tty_ldisc_ops中的函數:
同樣可以從內核代碼中找到線路規程的ops數據結構:
這裡總結一下用戶空間的write函數會調用系統調用接口的tty-write, 然後tty-write又會調用線路規程裡面n_tty_write。
還是對照著下面的圖繼續分析,可以看到n_tty_write會調用tty_operations裡面的函數,內核代碼中n_tty_write函數有點長,這裡截了兩張圖:
下面來瞅瞅tty_operations結構定義:
由這個可以看出n_tty_write又對應著這裡的uart_write函數!跳來跳去的好麻煩啊!感覺都是套路!
下面還是繼續分析uart_write廬山真面目,同樣可以從下面的圖中可以看到uart_write又對應著驅動裡面寫函數:
這裡面有幾個重要的數據結構:第二第三個箭頭指向的部分:這裡可以看到從struct uart_state結構中拿到了port端口號uart_port, 下面再跳到uart_start(tty)函數中瞅瞅:
繼續跳:
從上圖可以看出這個函數又通過port結構跳到ops裡面的函數!其實port就是uart_ops數據結構類型!上面的圖中可以函數這個數據結構裡面有很多的函數指針!然後就可以利用這些函數指針在驅動層來操作硬件了!
上面要特別注意struct uart_state數據結構,把上面截圖中的一行代碼扣出來分析:
struct uart_state *state = tty->driver_data;
可以看到這個數據結構是從tty的driver_data裡面來的,那麼這個數據結構又是怎麼放到driver_data裡面去的呢?這個是uart_open函數裡面做的:
從上面的函數中可以看出從uart_open函數中拿到state 然後將state放入tty的driver_data(void*型)結構中。
下面繼續分析state是如何獲得的:
從sourceInsight中可以看到state是從driver裡面uart_state的數據結構中拿到的:
上面分析了那麼多其實總結最重要的幾種數據結構的包含關系可以總結如下:
uart_driver ------> uart_state ------> uart_port ------> uart_ops
2. 串口驅動中的重要數據結構
第一步串口調用過程都是以下面這張圖來分析的,上一步中所說的下圖都是這下面這張圖
3. 初始化分析
老規矩,從原有的內核源碼中開始分析!重復造輪子!
在SI中打開samsung.c
module_init(s3c24xx_serial_modinit); module_exit(s3c24xx_serial_modexit);
這裡可以看到在模塊初始化部分使用了uart_register_driver函數注冊了一個串口驅動,下面來看看函數參數的類型:
結合上面的第一步的分析和第二步的流程圖看看是不是清晰多了!(這裡分析的其實也是對照著上面的流程圖來的)
下面再來分析s3c6400.c文件中的模塊初始化:
繼續跳:
可以看到這個函數又調用了平台驅動注冊函數(上一篇博文有詳細講解),注冊平台驅動的時候,平台總線會將平台驅動和我們系統中的平台設備進行一一的匹配,如果有匹配的上的,他就會調用平台驅動的probe函數
跳來跳去跳到這了:
下面來細致的分析上面的函數:
首先看第一個箭頭所指的地方,看看這個結構體:
這個數組存放的全部是uart_port的串口信息。
然後會調用串口初始化函數:
ret = s3c24xx_serial_init_port(ourport, info, dev);
函數比較長這裡做為兩張圖截了,可以看到這個函數主要是做一些初始化工作!其中比較重要的是上圖中紅色箭頭所指部分,第一個箭頭上一行首先是基地址初始化,這裡使用的是靜態映射(linux在啟動的時候就把物理地址和虛擬地址之間的關系映射好了)!拿虛擬地址,拿中斷號, 復位fifo
跳回去繼續分析probe函數:
看看第一個箭頭所指向的函數,這個函數主要建立uart_driver和uart_port之間的聯系的:
/** * uart_add_one_port - attach a driver-defined port structure * @drv: pointer to the uart low level driver structure for this port * @uport: uart port structure to use for this port. * * This allows the driver to register its own uart_port structure * with the core driver. The main purpose is to allow the low * level uart drivers to expand uart_port, rather than having yet * more levels of structures. */ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport) { struct uart_state *state; struct tty_port *port; int ret = 0; struct device *tty_dev; BUG_ON(in_interrupt()); if (uport->line >= drv->nr) return -EINVAL; state = drv->state + uport->line; port = &state->port; mutex_lock(&port_mutex); mutex_lock(&port->mutex); if (state->uart_port) { ret = -EINVAL; goto out; } state->uart_port = uport; state->pm_state = -1; uport->cons = drv->cons; uport->state = state; /* * If this port is a console, then the spinlock is already * initialised. */ if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) { spin_lock_init(&uport->lock); lockdep_set_class(&uport->lock, &port_lock_key); } uart_configure_port(drv, state, uport); /* * Register the port whether it's detected or not. This allows * setserial to be used to alter this ports parameters. */ tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev); if (likely(!IS_ERR(tty_dev))) { device_init_wakeup(tty_dev, 1); device_set_wakeup_enable(tty_dev, 0); } else printk(KERN_ERR "Cannot register tty device on line %d\n", uport->line); /* * Ensure UPF_DEAD is not set. */ uport->flags &= ~UPF_DEAD; out: mutex_unlock(&port->mutex); mutex_unlock(&port_mutex); return ret; }
安裝驅動我們在/sys/目錄下看到的串口相關信息就是這個函數實現的:
又是尼瑪的跳來跳去,好幾把套路臥槽!
這裡這個函數暫時就跳到這吧!心累!沒完沒了的!先顧大局!
ret = s3c24xx_serial_cpufreq_register(ourport);//動態頻率調節函數
又是套路,這裡暫時只要知道和cpu有關就行了,暫時放過!
下面再來一張串口初始化分析流程圖:
至此為止整個串口初始化流程就分析完了!內核代碼還真不是那麼簡單!
先注冊平台總線驅動,然後平台總線驅動和平台設備相匹配,匹配上了就會調用probe函數,然後probe函數就會執行一些初始化工作!
下一篇開始著手邊分析邊撸代碼!四步走戰略:串口初始化 、打開串口、串口寫操作、串口讀操作