Linux 串口驱动实例简单分析(x86 8250驱动(16550A),TIOCMGET, TIOCMSET, RTS)
#PS:要轉載請注明出處,本人版權所有
#PS:這個只是 《 我自己 》理解,如果和你的
#原則相沖突,請諒解,勿噴
前言
在我們一個一年前的項目里,由于對方的485串口硬件發生了變更,不能夠通過默認的termios相關內容去read和write了,這里需要控制串口16550A芯片的RTS腳,然后去控制ADM2486 485 modem芯片RTS相關腳的收發。簡單的理解為ADM485需要控制RTS和相關的引腳的高低電平,才能夠控制485的收發。原理圖我就不貼出來了,簡單說明就是16550A的RTS腳接一個反相器然后直連到ADM2486的RTS腳。
首先我這里考慮,這里如果是arm板卡的話,我是非常熟悉的,直接控制gpio就完事兒了。但是這個是個x86板卡,這就是最坑的,x86的io腳我沒有控制過。當然,和對方硬件溝通后,一種方案確實是控制x86的通用gpio,而他們的一套方案是通過串口芯片的RTS腳控制485 modem的RTS腳,因為x86的通用引腳是非常昂貴的,而且,電路也麻煩。
那么問題來了,怎么控制16550A芯片的RTS腳?
相關信息探索
因為以前我們寫了串口程序,操控的是/dev/ttyS0,那么必然現在也是從這個設備下手,我們看看系統啟動日志。
從這里我們知道串口芯片是16550A,也就是說我們之前通過termios相關的接口實現的老版本串口通信程序也是操作的這個芯片。那么意味著os里面已經自帶了這個芯片的相關驅動。到這里,可以預想到后面會有兩種情況:
同時,我查看了相關的串口編程相關內容,發現了一點內容,可以進行一些串口的高級操作,主要還是ioctl這個syscall的這兩個宏定義TIOCMGET, TIOCMSET,但是還是有點迷糊,于是根據以上的這些準備,我去看了linux對應內核的內核源碼。
8250驅動源碼分析
直接打開linux/drivers/char/8250.c 和 linux/drivers/char/8250.h分析了一番,在驅動里面找到了RTS相關控制位的操作
從這里知道,8250驅動確實帶了相關的芯片mctrl引腳控制相關的接口,如圖所示,分別是查詢mctrl引腳狀態和設置mctrl引腳狀態。
到這里,我隱約知道怎么做了,就是用ioctl 這個call的TIOCMSET來控制RTS引腳功能。熟悉linux驅動編寫的人都知道為啥會這樣,因為一個驅動帶了open,close,read,write基本功能,其他的選項功能一般都是ioctl基本syscall里面實現的。
于是帶著這些疑問,我又繼續看源碼分析,不然心里面總是覺得虛的。
linux tty 驅動框架簡單分析
等我看完一系列的8250驅動的調用結構后,我才發現要介紹的應該是linux tty 驅動框架,下面我這里簡單分析一下這個框架(沒必要全懂,知道大概就行,畢竟我們也不寫這個驅動,基本都是改再改)
首先,我們這個串口是一個字符設備。那就是說,應該有類似字符驅動的流程去打開、操作、關閉。(這里不了解的,可以去查一下linux 字符驅動相關的簡單說明)。字符驅動有主設備號和次設備號,對應我們的串口的話,如下圖:
tty設備的主設備為4
我們的串口設備次設備號為64,也就是第0個serial。
下面我們從字符設備開始,一步步看怎么調用起來8250里面的serial8250_get_mctrl() serial8250_set_mctrl()
8250 串口驅動簡單說明
linux/drivers/serial/8250.c
下面是8250驅動的初始化部分,就是把重要的serial8250_reg驅動(uart_register_driver())注冊到uart驅動鏈表上去
我們在內核的啟動日志中,也看見了printk的打印信息。
同時我們看一下uart_ops結構體在8250中的定義
這里我們就成功的看到了serial8250_get_mctrl() serial8250_set_mctrl()這倆的上一層接口名字。
在linux中,一個uart_driver對應多個uart_port,相當于一個串口驅動可以同時用于多個串口設備。
其中在serial8250_register_ports()中的serial8250_isa_init_ports()接口就是關聯uart_port結構體中的ops和serial_8250_pops的。這里我們其實就可以根據uart_port結構體中的set_mctrl和get_mctrl訪問serial8250_get_mctrl() serial8250_set_mctrl()。
然后通過uart_add_one_port把uart_driver中state成員的port成員賦值我們剛剛初始化好的uart_port.
這樣我們就可以通過uart_driver.state.port.ops來訪問我們的函數指針即可。
同時通過tty_register_device在/dev下面創建我們的設備節點。
到這里,我們就成功的把調用serial8250_get_mctrl() serial8250_set_mctrl()這兩個api轉換為可以通過uart_driver來調用了。
我們通過上述說明,基本可以看到一些內容,下面我們來看一個我們現在未講到的東西,就是uart_register_driver
我們可以看到,這里把uart_driver和一個叫做tty_driver的結構體關聯了起來。特別是通過tty_set_operations()把uart_driver中的8250相關的api和tty_driver中的相關api關聯了。
而且通過tty_register_driver()把一個重要內容聯系起來。至于為啥和怎么關聯,我們繼續往下面看。
serial_core簡單說明
linux/drivers/serial/serial_core.c
其實查看這里的源碼后發現,8250.c就是基于serial_core.c的內容進行串口驅動編程。
在serial_core.h里面我們可以看到上述我們見到的大量的uart_*的有用的結構體。
tty_drivers 簡單說明
linux/drivers/char/tty_io.c
我們知道一個簡單的字符串驅動,肯定有個init入口,如圖:
我們看最后的vt部分,這里創建了一個設備號為4,0的/dev/tty0的一個設備。
這里一個tty_core核心驅動就完事兒了。
寫過字符驅動的人都應該知道,我們還應該關注一個file_operations的結構體,因為我們在用戶態熟悉的open/close/read/write/ioctl都是通過這個結構體關聯的。
具體怎么關聯的,可以看一下我這里的這個比較水的記錄:https://blog.csdn.net/u011728480/article/details/51547405
我簡單來說,linux下一切皆是文件,包括我們要操作的串口設備,類似上面的/dev/tty0這種設備。在linux的vfs里面,一個文件對應一個inode結構體,同時內核維護一個file結構體和inode對應。inode結構體里面有個i_cdev成員,這個成員就是我們cdev結構體,就是我們在如圖的初始化入口中的vc0_cdev,這個結構體里面有個重要的成員就是ops,這里面存放的就是各種open/close/read/write/ioctl的實際函數指針。
在《8250 串口驅動簡單說明》小節中,我們說明了serial8250_init()通過調用tty_register_device在/dev中創建了對應的設備節點。并把tty_driver和uart_driver關聯起來,我們可以通過tty_driver去訪問serial8250_get_mctrl() serial8250_set_mctrl()這兩個我想要的東西。
那file_operations結構體和tty_driver是怎么關聯起來的呢?如果我們知道了的話,整個驅動的調用鏈路就理清楚了,也好處理我們遇到的問題。
在《8250 串口驅動簡單說明》小節最后部分提到了8250的初始化調用了tty_register_driver()這個重要的接口,這個結構就是把我們想要的兩個結構體關聯起來的關鍵。
我們在初始化一個字符設備的時候,在這里關聯了file_operations結構體和tty_driver。
我們看看tty_fops的定義:
到了這里,我們的整個調用鏈路都打通了。
這里我們直接看tty_ioctl這個接口,我們就可以看到調用8250的方法了serial8250_get_mctrl() serial8250_set_mctrl()。最后我們在文末總結一下。
8250調用實例分析
我們這里通過調用通過8250驅動設置16550A的 RTS腳電平來回顧一下我們的整個調用鏈路。
當我們調用ioctl的時候,通過傳入參數TIOCMGET或者TIOCMSET,默認我們會調用tty_ioctl方法,然后進一步會調用tty_tiocmget 和tty_tiocmset。如下圖:
在tty_tiocmget 和tty_tiocmset中,分別調用tty_driver中的tiocmset和tiocmget
然后我們上文說了,tty_driver和uart_driver是通過uart_register_driver和tty_set_operations關聯起來的
也就是說,對tty_driver的tiocmget和tiocmset的調用,就是直接對tty_operations的tiocmget和tiocmset的調用。
我們在《8250 串口驅動簡單說明》小節最后部分,說調用tty_set_operations()關聯起來了一個tty_driver和一個tty_operations,而這里注冊的uart_ops就很明顯了
也就是說tty_operations的tiocmget和tiocmset的調用,就是對uart_tiocmget和uart_tiocmset的調用。
uart_tiocmget和uart_tiocmset的調用就是對uart_driver.state.port.ops.set_mctrl和uart_driver.state.port.ops.get_mctrl的調用,而這里就是對serial8250_set_mctrl和serial8250_get_mctrl的調用。
總結
我們可以看到,這里,我們在用戶層對相關vfs的接口進行調用,都會映射為相應的驅動ops。
在這里,用戶態的ioctl轉換為內核態的tty_ioctl,最終一步步到我們要的地方。
因為我是要讀這個驅動是不是有這個功能,而不是寫一個驅動,所以看起來要簡單很多了。
我查了tty相關的驅動框架,內容還是挺多的,特別是tty_read和tty_write和我們這里的調用流程是完全不一致的,但是我這里暫時不需要去看,因為我要的功能有了,如果有需求,我會去看這部分內容。最終,我通過ioctl加上特定的命令,成功的控制了16550A的RTS腳。而且這里通了的話,不需要通過gpio去處理。
其實這個還是挺有意思的,雖然好的抽象的東西看起來很不爽,但是了解通了整個調用流程,我感覺就特別的舒服。
#PS:請尊重原創,不喜勿噴
#PS:要轉載請注明出處,本人版權所有.
有問題請留言,看到后我會第一時間回復
總結
以上是生活随笔為你收集整理的Linux 串口驱动实例简单分析(x86 8250驱动(16550A),TIOCMGET, TIOCMSET, RTS)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 立体匹配算法(局部立体匹配 、全局立体匹
- 下一篇: B站 - 黑客攻防 入门到入狱 [网络安