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