網(wǎng)絡(luò)驅(qū)動是linux里面驅(qū)動三巨頭之一,linux下的網(wǎng)絡(luò)功能非常強(qiáng)大,嵌入式linux中也常常用到網(wǎng)絡(luò)功能。前面已經(jīng)講過了字符設(shè)備驅(qū)動和塊設(shè)備驅(qū)動,本章就來學(xué)習(xí)一下linux里面的網(wǎng)絡(luò)設(shè)備驅(qū)動。
嵌入式網(wǎng)絡(luò)簡介
嵌入式下的網(wǎng)絡(luò)硬件接口
本次筆記中討論的都是有線網(wǎng)絡(luò)!
提起網(wǎng)絡(luò),一般想到的硬件就是“網(wǎng)卡”。在電腦領(lǐng)域的“原始社會”,網(wǎng)卡是獨(dú)立的硬件,如果電腦要上網(wǎng)就得買個網(wǎng)卡插上去,類似現(xiàn)在的顯卡一樣。但是現(xiàn)在隨著技術(shù)的不斷發(fā)展,只需要一個芯片就可以實(shí)現(xiàn)有線網(wǎng)卡功能,因此網(wǎng)卡芯片都直接放到了主板上。
首先,嵌入式網(wǎng)絡(luò)硬件分為兩部分:MAC和PHY,都是通過看數(shù)據(jù)手冊來判斷一款SoC是否支持網(wǎng)絡(luò),如果一款芯片數(shù)據(jù)手冊說自己支持網(wǎng)絡(luò),一般都是說的這款SoC內(nèi)置MAC,MAC類似I2C控制器、SPI控制器一樣的外設(shè)。但是光有MAC還不能直接驅(qū)動網(wǎng)絡(luò),還需要另外一個芯片:PHY,因此對于內(nèi)置MAC的SoC,其外部必須搭配一個PHY芯片。但是有些SoC內(nèi)部沒有MAC,那也沒法搭配PHY芯片了,這些內(nèi)部沒有MAC的芯片上網(wǎng)就要采用另外的嵌入式網(wǎng)絡(luò)硬件方案。
SoC內(nèi)部沒有網(wǎng)絡(luò)MAC外設(shè)
一般說某個SoC不支持網(wǎng)絡(luò),說的就是它沒有網(wǎng)絡(luò)MAC。可以找個外置的MAC芯片,一般這種外置的網(wǎng)絡(luò)芯片都是MAC+PHY一體的。比如三星linux開發(fā)板里面用的最多的DM9000,因?yàn)槿堑男酒緵]有內(nèi)部MAC(比如S3C2440S5PV210,4412 等),所以三星的開發(fā)板都是通過外置的DM9000來完成有線網(wǎng)絡(luò)功能的,DM9000對SoC 提供了一個SRAM接口,SoC會以SRAM的方式操作DM9000。
有些外置的網(wǎng)絡(luò)芯片更強(qiáng)大,內(nèi)部甚至集成了硬件TCP/IP協(xié)議棧,對外提供一個SPI接口,比如W5500。這個一般用于單片機(jī)領(lǐng)域,單片機(jī)通過SPI接口與W5500進(jìn)行通信,由于W5500內(nèi)置了硬件TCP/IP協(xié)議棧,因此單片機(jī)就不需要移植負(fù)責(zé)的軟件協(xié)議棧,直接通過SPI來操作W5500,簡化了單片機(jī)聯(lián)網(wǎng)方案。
這種方案的優(yōu)點(diǎn)就是讓不支持網(wǎng)絡(luò)的SoC能夠另辟蹊徑,實(shí)現(xiàn)網(wǎng)絡(luò)功能,但是缺點(diǎn)就是網(wǎng)絡(luò)效率不高,因?yàn)橐话阈酒瑑?nèi)置的MAC會有網(wǎng)絡(luò)加速引擎,比如網(wǎng)絡(luò)專用DMA,網(wǎng)絡(luò)處理效率會很高。而且此類芯片網(wǎng)速都不快,基本就是10/100M。另外,相比PHY芯片而言,此類芯片的成本也比較高,可選擇比較少。
SoC與外部MAC+PHY芯片的連接如下圖所示:
SoC內(nèi)部集成網(wǎng)絡(luò)MAC外設(shè)
一般說某個SoC支持網(wǎng)絡(luò),說的就是他內(nèi)部集成網(wǎng)絡(luò)MAC外設(shè),此時還需要外接一個網(wǎng)絡(luò)PHY芯片。目前將PHY也集成到芯片里面的SoC很少見。一般常見的通用SoC都會集成網(wǎng)絡(luò)MAC外設(shè),比如STM32F4/F7/H7系列、NXP的I.MX系列以及STM32MP1系列,內(nèi)部集成網(wǎng)絡(luò)MAC的優(yōu)點(diǎn)如下:
- 內(nèi)部MAC外設(shè)會有專用的加速模塊,比如專用的DMA,加速網(wǎng)速數(shù)據(jù)的處理。
- 網(wǎng)速快,可以支持10/100/1000M網(wǎng)速。
- 外接PHY可選擇性多,成本低。
內(nèi)部的MAC外設(shè)會通過相應(yīng)的接口來連接外部PHY芯片,根據(jù)數(shù)據(jù)傳輸模式不同,大致
可以分為以下兩類:
- MII/RMII 接口:支持10Mbit/s和100Mbit/s數(shù)據(jù)傳輸模式;
- GMII/RGMII接口:支持10Mbit/s、100Mbit/s 以及1000Mbit/s數(shù)據(jù)傳輸模式。
從這里可以知道,MII/RMII接口最大傳輸速率為100Mbit/s,而GMII/RGMII接口最大傳輸速率可達(dá)1000Mbit/s;所以一般把MII/RMII稱為百兆以太網(wǎng)接口,而把GMII/RGMII稱為千兆以太網(wǎng)接口。
關(guān)于這兩組接口更加詳細(xì)的內(nèi)容會在后面給大家進(jìn)行介紹,MII/RMII或GMII/RGMII接口是用來傳輸網(wǎng)絡(luò)數(shù)據(jù)的,另外主控SoC需要配置或讀取PHY芯片,也就是讀寫PHY的內(nèi)部寄存器,所以還需要一個控制接口,叫做MIDO,MDIO很類似IIC,也是兩根線,一根數(shù)據(jù)線叫做MDIO,一根時鐘線叫做MDC。
SoC內(nèi)部MAC外設(shè)與外部PHY芯片的連接如下圖所示:
STM32MP1就有一顆10M/100M/1000M的網(wǎng)絡(luò)MAC外設(shè),正點(diǎn)原子STM32MP1開發(fā)板板載了一顆PHY芯片,V1.2版本及其以前的核心板使用RTL8211F-CG這顆PHY芯片, V1.3版本及其以后核心板使用YT8511C/H這顆PHY芯片。
因此,這里只講解SoC內(nèi)部MAC+外置PHY芯片這種方案。
MII/RMII、GMII\RGMII接口
MII接口
MII全稱是Media Independent Interface,直譯過來就是介質(zhì)獨(dú)立接口,它是IEEE-802.3定義的以太網(wǎng)標(biāo)準(zhǔn)接口,MII接口用于以太網(wǎng)MAC連接PHY芯片,連接示意圖如下圖所示:
MII接口一共有16根信號線,含義如下:
- TX_CLK:發(fā)送時鐘,如果網(wǎng)速為100M的話時鐘頻率為25MHz,10M網(wǎng)速的話時鐘頻率為2.5MHz,此時鐘由PHY產(chǎn)生并發(fā)送給MAC。
- TX_EN:發(fā)送使能信號。
- TX_ER:發(fā)送錯誤信號,高電平有效,表示TX_ER有效期內(nèi)傳輸?shù)臄?shù)據(jù)無效。10Mpbs網(wǎng)速下TX_ER不起作用。
- TXD[3:0]:發(fā)送數(shù)據(jù)信號線,一共4根。
- RXD[3:0]:接收數(shù)據(jù)信號線,一共4根。
- RX_CLK:接收時鐘信號,如果網(wǎng)速為100M的話時鐘頻率為25MHz,10M網(wǎng)速的話時鐘頻率為2.5MHz,RX_CLK也是由PHY產(chǎn)生的。
- RX_ER:接收錯誤信號,高電平有效,表示RX_ER有效期內(nèi)傳輸?shù)臄?shù)據(jù)無效。10Mpbs網(wǎng)速下RX_ER不起作用。
- RX_DV:接收數(shù)據(jù)有效,作用類似TX_EN。
- CRS:載波偵聽信號。
- COL:沖突檢測信號。
MII接口的缺點(diǎn)就是所需信號線太多,這還沒有算MDIO和MDC這兩根管理接口的數(shù)據(jù)線,因此MII接口使用已經(jīng)越來越少了。
RMII接口
RMII全稱是Reduced Media Independent Interface,翻譯過來就是精簡的介質(zhì)獨(dú)立接口,也就是MII接口的精簡版本。RMII接口只需要7根數(shù)據(jù)線,相比MII直接減少了9根,極大的方便了板子布線,RMII接口連接PHY芯片的示意圖如下圖所示:
- TX_EN:發(fā)送使能信號。
- TXD[1:0]:發(fā)送數(shù)據(jù)信號線,一共2根。
- RXD[1:0]:接收數(shù)據(jù)信號線,一共2根。
- CRS_DV:相當(dāng)于MII接口中的RX_DV和CRS這兩個信號的混合。
- REF_CLK:參考時鐘,由外部時鐘源提供,頻率為50MHz。這里與MII不同,MII的接收和發(fā)送時鐘是獨(dú)立分開的,而且都是由PHY芯片提供的。
GMII接口
GMII(Gigabit Media Independant Interface),千兆MII接口。GMII采用8位接口數(shù)據(jù),工作時鐘125MHz,因此傳輸速率可達(dá)1000Mbps;同時兼容MII所規(guī)定的10/100Mbps工作方式。GMII接口數(shù)據(jù)結(jié)構(gòu)符合IEEE以太網(wǎng)標(biāo)準(zhǔn),該接口定義見IEEE 802.3-2000。信號定義如下:
- GTX_CLK:1000M工作模式下的發(fā)送時鐘(25MHz)。
- TX_EN:發(fā)送使能信號。
- TX_ER:發(fā)送錯誤信號,高電平有效,表示TX_ER有效期內(nèi)傳輸?shù)臄?shù)據(jù)無效。
- TXD[7:0]:發(fā)送數(shù)據(jù)信號線,一共8根。
- RXD[7:0]:接收數(shù)據(jù)信號線,一共8根。
- RX_CLK:接收時鐘信號。
- RX_ER:接收錯誤信號,高電平有效,表示RX_ER有效期內(nèi)傳輸?shù)臄?shù)據(jù)無效。
- RX_DV:接收數(shù)據(jù)有效,作用類似TX_EN。
- CRS:載波偵聽信號。
- COL:沖突檢測信號。
與MII接口相比,GMII的數(shù)據(jù)寬度由4位變?yōu)?位,GMII接口中的控制信號如TX_ER、TX_EN、RX_ER、RX_DV、CRS和COL的作用同MII接口中的一樣,發(fā)送參考時鐘GTX_CLK和接收參考時鐘RX_CLK的頻率均為125MHz(在1000Mbps工作模式下)。
在實(shí)際應(yīng)用中,絕大多數(shù)GMII接口都是兼容MII接口的,所以,一般的GMII接口都有兩個發(fā)送參考時鐘:TX_CLK和GTX_CLK(兩者的方向是不一樣的,前面已經(jīng)說過了),在用作MII模式時,使用TX_CLK和8根數(shù)據(jù)線中的4根。
RGMII接口
RGMII(Reduced Gigabit Media Independant Interface),精簡版GMII接口。將接口信號線數(shù)量從24根減少到14根(COL/CRS 端口狀態(tài)指示信號,這里沒有畫出),時鐘頻率仍舊為125MHz,TX/RX數(shù)據(jù)寬度從8為變?yōu)?位,為了保持1000Mbps的傳輸速率不變,RGMII接口在時鐘的上升沿和下降沿都采樣數(shù)據(jù),在參考時鐘的上升沿發(fā)送GMII接口中的TXD[3:0]/RXD[3:0],在參考時鐘的下降沿發(fā)送GMII接口中的TXD[7:4]/RXD[7:4]。RGMII同時也兼容100Mbps 和10Mbps兩種速率,此時參考時鐘速率分別為25MHz和2.5MHz。
TX_EN信號線上傳送TX_EN 和TX_ER兩種信息,在TX_CLK的上升沿發(fā)送TX_EN,下降沿發(fā)送TX_ER;同樣的,RX_DV信號線上也傳送RX_DV和RX_ER兩種信息,在RX_CLK的上升沿發(fā)送RX_DV,下降沿發(fā)送RX_ER。
RGMII接口定義如下所示:
關(guān)于這些接口定義相關(guān)的內(nèi)容就講到這里,除了上面說到4 種接口以外,還有其他接口,比如SMII、SSMII和SGMII等,關(guān)于其他接口基本都是大同小異的,這里就不做講解了。正點(diǎn)原子STM32MP1開發(fā)板上的網(wǎng)口是采用RGMII接口來連接MAC與外部PHY芯片。
MDIO接口
MDIO全稱是Management Data Input/Output,直譯過來就是管理數(shù)據(jù)輸入輸出接口,是一個簡單的兩線串行接口,一根MDIO數(shù)據(jù)線,一根MDC時鐘線。驅(qū)動程序可以通過MDIO和MDC這兩根線訪問PHY芯片的任意一個寄存器。MDIO接口支持多達(dá)32個PHY。同一時刻內(nèi)只能對一個PHY進(jìn)行操作。和IIC一樣,使用器件地址即可區(qū)分PHY芯片。同一MDIO接口下的所有PHY芯片,其器件地址不能沖突,必須保證唯一,具體器件地址值要查閱相應(yīng)的PHY數(shù)據(jù)手冊。
因此,MAC和外部PHY芯片進(jìn)行連接的時候主要是MII/RMII(百兆網(wǎng))或GMII/RGMII(千兆網(wǎng))和MDIO接口,另外可能還需要復(fù)位、中斷等其他引腳。
RJ45接口
網(wǎng)絡(luò)設(shè)備是通過網(wǎng)線連接起來的,插入網(wǎng)線的叫做RJ45座,如下圖所示:
RJ45座要與PHY芯片連接在一起,但是中間需要一個網(wǎng)絡(luò)變壓器,網(wǎng)絡(luò)編譯器用于隔離以及濾波等,網(wǎng)絡(luò)變壓器也是一個芯片,外形一般如下圖所示:
但是現(xiàn)在很多RJ45座子內(nèi)部已經(jīng)集成了網(wǎng)絡(luò)變壓器,比如正點(diǎn)原子的STM32MP1開發(fā)板所使用的ATK911130A就是內(nèi)置網(wǎng)絡(luò)變壓器的RJ45座。內(nèi)置網(wǎng)絡(luò)變壓器的RJ45座和不內(nèi)置的引腳一樣,但是一般不內(nèi)置的RJ45座會短一點(diǎn)。因此,在畫板的時候一定要考慮所使用的RJ45座是否內(nèi)置網(wǎng)絡(luò)變壓器,如果不內(nèi)置的話就要自行添加網(wǎng)絡(luò)變壓器部分電路!同理,如果設(shè)計(jì)的硬件是需要內(nèi)置網(wǎng)絡(luò)變壓器的RJ45座,肯定不能隨便焊接一個不內(nèi)置變壓器的RJ45座,否則網(wǎng)絡(luò)工作不正常!
RJ45座子上一般有兩個燈,一個黃色(橙色),一個綠色,一般綠色亮的話表示網(wǎng)絡(luò)連接正常,黃色閃爍的話說明當(dāng)前正在進(jìn)行網(wǎng)絡(luò)通信,當(dāng)然了有時候兩個燈的狀態(tài)會反過來,以實(shí)際為準(zhǔn)。這兩個燈由PHY芯片控制,對于千M網(wǎng)絡(luò)PHY芯片,一般PHY芯片會有3個LED燈引腳,多了一個千M網(wǎng)絡(luò)狀態(tài)指示燈。PHY芯片會通過這幾個LED燈引腳來連接RJ45座上的這兩個燈(千M PHY會有3個LED燈引腳,一般硬件設(shè)計(jì)人員會自行選擇將其中的哪兩個連接到RJ45座上)。由于正點(diǎn)原子STM32MP157開發(fā)板采用的千M網(wǎng)絡(luò)PHY,所以后面只講千M網(wǎng)絡(luò)。內(nèi)部MAC+外部PHY+RJ45座(內(nèi)置網(wǎng)絡(luò)變壓器)就組成了一個完整的嵌入式網(wǎng)絡(luò)接口硬件,如下圖所示:
STM32MP1 GMAC接口簡介
STM32MP1內(nèi)核集成了一個10M/100M/1000M的網(wǎng)絡(luò)MAC,符合IEEE802.3-2002標(biāo)準(zhǔn),MAC層支持雙工或者半雙工模式下運(yùn)行。MAC可編程,有直接存儲器接口的專用DMA,介質(zhì)訪問控制器(MAC)和支持多種格式的PHY接口模塊。
STM32MP1內(nèi)部ENET外設(shè)主要特性如下:
- 、支持全工和半雙工操作。
- 全雙工流控制操作(IEEE 802.3X暫停包和優(yōu)先級流控制)。
- 報(bào)頭和幀起始數(shù)據(jù)(SFD)在發(fā)送模式下自動插入、在接收中自動刪除。
- 可逐幀控制CRC和pad自動生成。
- 可編程數(shù)據(jù)包長度,支持標(biāo)準(zhǔn)以太網(wǎng)數(shù)據(jù)包或高達(dá)16KB的巨型以太網(wǎng)數(shù)據(jù)包。
- 可編程數(shù)據(jù)包間隙。
- 兩組 FIFO:一個具有可編程閾值功能的4096字節(jié)發(fā)送FIFO和一個具有可配置閾值功能的4096字節(jié)接收FIFO。
- ……
STM32MP1的GMAC外設(shè)內(nèi)容比較多,詳細(xì)的介紹請查閱《STM32MP1 參考手冊》的“Ethernet (ETH): Gigabit media access control(GMAC) with DMA controller”章節(jié)。在編寫驅(qū)動的時候其實(shí)并不需要關(guān)注GMAC控制器外設(shè)的具體內(nèi)容,因?yàn)檫@部分驅(qū)動是SoC廠商寫的,重點(diǎn)關(guān)注的是更換PHY芯片以后哪里需要調(diào)整。
PHY芯片詳解
PHY基礎(chǔ)知識簡介
PHY是IEEE 802.3規(guī)定的一個標(biāo)準(zhǔn)模塊,前面說了,SoC可以對PHY進(jìn)行配置或者讀取PHY相關(guān)狀態(tài),這個就需要PHY內(nèi)部寄存器去實(shí)現(xiàn)了。PHY 芯片寄存器地址空間為5位,地址0-31共32個寄存器,IEEE定義了0-15這16個寄存器的功能,16-31這16個寄存器由廠商自行實(shí)現(xiàn)。也就是說不管用的哪個廠家的PHY芯片,其中0-15這16個寄存器是一模一樣的。僅靠這16個寄存器是完全可以驅(qū)動起PHY芯片的,至少能保證基本的網(wǎng)絡(luò)數(shù)據(jù)通信,因此Linux內(nèi)核有通用PHY驅(qū)動。在實(shí)際開發(fā)中可能會遇到一些其他的問題導(dǎo)致Linux內(nèi)核的通用PHY驅(qū)動工作不正常,這個時候就需要驅(qū)動開發(fā)人員去調(diào)試了。但是,隨著現(xiàn)在的PHY芯片性能越來越強(qiáng)大,32個寄存器可能滿足不了廠商的需求,因此很多廠商采用分頁技術(shù)來擴(kuò)展寄存器地址空間,以求定義更多的寄存器。這些多出來的寄存器可以用于實(shí)現(xiàn)廠商特有的一些技術(shù),因此Linux內(nèi)核的通用PHY驅(qū)動就無法驅(qū)動這些特色
功能了,這個時候就需要PHY廠商提供相應(yīng)的驅(qū)動源碼了,所以也會在Linux內(nèi)核里面看到很多具體的PHY芯片驅(qū)動源碼。不管PHY芯片有多少特色功能,按道理來講,Linux內(nèi)核的通用PHY驅(qū)動是絕對可以讓這PHY芯片實(shí)現(xiàn)基本的網(wǎng)絡(luò)通信,因此也不用擔(dān)心更換PHY芯片以后網(wǎng)絡(luò)驅(qū)動編寫是不是會很復(fù)雜。
IEEE802.3協(xié)議英文原版已經(jīng)放到了開發(fā)板光盤中,其中有對PHY的前16個寄存器功能進(jìn)行了規(guī)定,如下圖所示:
關(guān)于這16個寄存器的內(nèi)容協(xié)議里面也進(jìn)行了詳細(xì)的講解,這里就不分析了。后面會以正點(diǎn)原子STM32MP1開發(fā)板所使用的RTL8211F-CG(YT8511C/H)這個PHY為例,詳細(xì)的分析一下PHY芯片的寄存器。
RTL8211F-CG詳解
此小節(jié)是針對核心板為V1.2版本的PHY講解RTL8211F-CG這顆PHY芯片是由realtek公司出品的。
本小節(jié)以RTL8211F-CG為例做一個簡單第介紹,雖然講解的是RTL8211F-CG這顆PHY,但是前面說了,IEEE規(guī)定了PHY的前16個寄存器的功能,因此如果所使用的板子用的其它廠家的PHY芯片,也是可以看本小節(jié)的。
RTL8211F-CG簡介
Realtek RTL8211F-CG是高度集成的以太網(wǎng)收發(fā)器,符合10Base-T、100Base-TX和1000Base-T IEEE 802.3標(biāo)準(zhǔn)。它提供了所有通過CAT.5 UTP電纜收發(fā)以太網(wǎng)數(shù)據(jù)包所需的必要物理層功能。
RTL8211F使用最新的DSP技術(shù)和模擬前端(AFE),可以通過UTP電纜進(jìn)行高速數(shù)據(jù)傳輸和接收。RTL8211F中實(shí)現(xiàn)了交叉檢測和自動校正、極性校正、自適應(yīng)均衡、串?dāng)_消除、回聲消除、定時恢復(fù)和錯誤校正等功能,以提供10Mbps100Mbps或1000Mbps的強(qiáng)大收發(fā)功能。
MAC和PHY之間的數(shù)據(jù)傳輸是通過RGMII接口進(jìn)行的,RTL8211E支持RGMII的1.5V信號。
RTL8211E的主要特點(diǎn)如下:
- 兼容1000Base-T IEEE 802.3ab標(biāo)準(zhǔn)。
- 兼容100Base-TX IEEE 802.3u標(biāo)準(zhǔn)。
- 兼容10Base-T IEEE 802.3標(biāo)準(zhǔn)。
- 支持GMII、RGMII接口。
- 支持IEEE 802.3az-2010(節(jié)能以太網(wǎng))。
- 內(nèi)置LAN喚醒(WOL)。
- 支持中斷、并行檢測、交叉檢測、自動校正、自動極性校正。
- 支持120m的1000Base-T的CAT.5類電纜。
- 支持RGMII的1.5V信號。
- ……
RTL8211F千兆PHY的系統(tǒng)應(yīng)用場景如下:
- 數(shù)字電視(DTV)。
- 媒體訪問單元(MAU)。
- 通訊和網(wǎng)絡(luò)提升板(CNR)。
- 游戲機(jī)。
- 打印機(jī)和辦公機(jī)器。
- DVD播放機(jī)和刻錄機(jī)。
- 以太網(wǎng)集線器、交換機(jī)。
RTL8211F功能框圖如下所示:
節(jié)能以太網(wǎng)(EEE)
RTL8211F支持IEEE 802.3az-2010(也稱為節(jié)能以太網(wǎng)(EEE)),速率為10Mbps、100Mbps或1000Mbps時它提供了一個協(xié)議,可根據(jù)鏈路利用率協(xié)調(diào)進(jìn)出較低功耗級別(低功耗空閑模式)的轉(zhuǎn)換,當(dāng)沒有數(shù)據(jù)包傳輸時,系統(tǒng)進(jìn)入低功耗空閑模式以降低功耗,一旦需要發(fā)送數(shù)據(jù)包,系統(tǒng)將返回正常模式,并且無需更改鏈接狀態(tài)和丟棄/破壞幀即可進(jìn)行此操作。
為了節(jié)省功率,當(dāng)系統(tǒng)處于低功耗空閑模式時,大多數(shù)電路功能都被禁用;但是,低功耗空閑模式的過渡時間保持足夠小,以使得對上層協(xié)議和應(yīng)用程序透明。
中斷管理
每當(dāng)RTL8211F檢測到介質(zhì)狀態(tài)發(fā)生變化時,它就會將中斷引腳(INTB)拉低發(fā)出中斷事件。SoC MAC端會感應(yīng)到狀態(tài)更改,并通過MDC/MDIO接口訪問相關(guān)的寄存器。
一旦MAC通過MDC/MDIO讀取了這些狀態(tài)寄存器,就會將INTB置為無效。所以不需要通過輪訓(xùn)的方式去反復(fù)查詢狀態(tài)寄存器的變化。
PHY地址設(shè)置
MAC層通過MDIO/MDC總線對PHY進(jìn)行讀寫操作,MDIO最多可以控制32個PHY芯片,通過不同的PHY芯片地址來對不同的PHY操作。RTL8211F通過配置PHYAD[2:0]這三個引腳來設(shè)置PHY地址,其中PHYAD2與RXCTL共用一個引腳PHYAD1與RXC共用一個引腳、PHYAD0與RXD3共用一個引腳??稍O(shè)置的PHY地址范圍為:0X001-0X111,如下圖所示:
自動協(xié)商
RTL8211F可以開始啟動協(xié)商功能,將PHYSR寄存器的bit12置1即可使能自動協(xié)商,如下圖所示:
RGMII接口電平支持
RTL8211F支持RGMII接口,可以通過CFG_EXT、CFG_LDO0和CFG_LDO1這三個引腳去配置RGMII的電平信號,配置方法如下圖所示:
正點(diǎn)原子V1.2版本之前的核心板CFG_EXT引腳外接3.3V上拉電阻,CFG_LDO0和CFG_LDO1都是接到低了,因此RGMII電壓信號為 3.3V。
RTL8211F內(nèi)部寄存器
RTL8211F的前16個寄存器滿足IEEE的要求,在這里只介紹幾個常用的寄存器,首先是BMCR(Basic Mode Control Register)寄存器,地址為0,BMCR 寄存器各位如下圖所示:
之前說的配置PHY芯片,重點(diǎn)就是配置BMCR寄存器。
接下來看一下BMSR(Basic Mode Status Register)寄存器,地址為1。此寄存器為PHY的狀態(tài)寄存器,通過此寄存器可以獲取到PHY芯片的工作狀態(tài),BMSR寄存器各位如下圖所示:
從上圖中可以看出,不管什么PHY芯片,只要它實(shí)現(xiàn)了的位和IEEE規(guī)定相符就行。通過讀取BMSR寄存器的值可以得到當(dāng)前的連接速度、雙工狀態(tài)和連接狀態(tài)等。
接下來看一下RTL8211F的PHY ID 1和PHY ID 2這兩個寄存器,這兩個寄存器的地址分別為2和3,后面就把這兩個寄存器叫做寄存器2和寄存器3。IEEE規(guī)定寄存器2和寄存器3為PHY的ID寄存器,這兩個寄存器組成一個32位的唯一ID值。IEEE規(guī)定了一個叫做OUI的ID組成方式,全稱是Organizationally Unique Identifier,OUI一共32位,分為三部分:22位的ID+6位廠商型號ID+4位廠商版本ID,組成如下圖所示:
RTL8211F的ID寄存器2如下圖所示:
ID 寄存器3如下圖所示:
接著來看一下RTL8211F的GBCR(1000Base-T Control Register)寄存器,寄存器地址為0x9;該寄存器定義了1000Base-T功能的相關(guān)控制位,寄存器描述信息如下所示:
關(guān)于RTL8211F這個PHY就講解到這里,更加詳細(xì)的內(nèi)容大家可以查看RTL8211F對應(yīng)的參考手冊RTL8211F-CG.pdf。
YT8511C詳解
此小節(jié)針對V1.3版本及以后核心板,YT8511C這顆PHY芯片是由蘇州裕太車通電子科技有限公司出品的。
前面說了,PHY芯片的前16個寄存器都是一樣的,而且只需要使用這前16個寄存器基本就可以驅(qū)動起來PHY芯片。這里就不詳細(xì)講解了,YT8511C和RTL8211F區(qū)別不大,此章節(jié)主
要是講一下YT8511C的對于RTL8211F差異。
PHY地址設(shè)置
YT8511通過PHYADDR[2:0]這三個引腳來確定 PHY芯片地址 , 其中PHYADDR2-PHYADDR0分別對應(yīng)LED_AC、RXD1和RXD0這三個引腳,配置如下圖所示:
正點(diǎn)原子的STM32MP157開發(fā)板上的YT8511的地址為0X00。
低功率模式
YT8511C的低功率模式是由RXD3引腳控制,配置如下圖所示:
從上圖中可以看出來,RXD3為低電平的時候YT8511C進(jìn)入低功率模式,RXD3為高電平的話YT8511C芯片就為正常功率模式。正點(diǎn)原子開發(fā)板RXD3連接高電平,所以YT8511C芯片為正常功率模式。
工作模式
YT8511C的LED_1000引腳用來控制工作模式,配置如下圖所示:
從上圖中可以看出來,LED_1000為低電平的話就進(jìn)入測試模式,為高電平的話就是正常模式。正點(diǎn)原子開發(fā)板LED_1000連接高電平,所以YT8511C芯片為正常模式。
RGMII電壓配置
YT8511C的RGMII電壓是由RX_DV引腳控制,如下圖所示:
從上圖可以看出如果RX_DV為低電平的時候RGMII電平為3.3V,RX_DV為高電平的時候RGMII電平為2.5V。正點(diǎn)原子開發(fā)板RX_DV連接低電平,所以YT8511C的引腳電平為3.3V。
125M時鐘使能
STM32MP157千M網(wǎng)絡(luò)工作的時候需要和PHY之間有一個125MHz的時鐘,在這里YT8511C提供這個125MHz的時鐘。但是默認(rèn)情況下,YT8511C這個125MHz時鐘沒有使能,所以需要配置寄存器,使能這個125MHz。這里需要用到Y(jié)T8511C的時鐘控制寄存器,這是個擴(kuò)展寄存器,地址為0X0C,這個寄存器是YT8511C官方自定義的,不屬于IEEE規(guī)定的寄存器。時鐘控制寄存器的bit2和bit1用于設(shè)置時鐘輸入,如下圖所示:
從上圖可以看出,當(dāng)bit2:1為11的時候,輸出125M時鐘。
芯片YT8511C還有很多設(shè)置,這邊就不一一列出了,可以直接查看芯片原理圖就知道了。
Linux內(nèi)核網(wǎng)絡(luò)驅(qū)動框架
net_device結(jié)構(gòu)體
Linux內(nèi)核使用net_device結(jié)構(gòu)體表示一個具體的網(wǎng)絡(luò)設(shè)備,net_device是整個網(wǎng)絡(luò)驅(qū)動的靈魂。網(wǎng)絡(luò)驅(qū)動的核心就是初始化net_device結(jié)構(gòu)體中的各個成員變量,然后將初始化完成以后的net_device注冊到Linux內(nèi)核中。net_device結(jié)構(gòu)體定義在include/linux/netdevice.h中,net_device是一個龐大的結(jié)構(gòu)體,內(nèi)容如下(有縮減):
示例代碼 52.3.1.1 net_device 結(jié)構(gòu)體
1783 struct net_device {
1784 char name[IFNAMSIZ];
1785 struct hlist_node name_hlist;
1786 struct dev_ifalias __rcu *ifalias;
1787 /*
1788 * I/O specific fields
1789 * FIXME: Merge these and struct ifmap into one
1790 */
1791 unsigned long mem_end;
1792 unsigned long mem_start;
1793 unsigned long base_addr;
1794 int irq;
1795
1796 /*
1797 * Some hardware also needs these fields (state,dev_list,
1798 * napi_list,unreg_list,close_list) but they are not
1799 * part of the usual set specified in Space.c.
1800 */
1801
1802 unsigned long state;
1803
1804 struct list_head dev_list;
1805 struct list_head napi_list;
1806 struct list_head unreg_list;
1807 struct list_head close_list;
......
1841 const struct net_device_ops *netdev_ops;
1842 const struct ethtool_ops *ethtool_ops;
1843 #ifdef CONFIG_NET_L3_MASTER_DEV
1844 const struct l3mdev_ops *l3mdev_ops;
1845 #endif
1846 #if IS_ENABLED(CONFIG_IPV6)
1847 const struct ndisc_ops *ndisc_ops;
1848 #endif
1849
1850 #ifdef CONFIG_XFRM_OFFLOAD
1851 const struct xfrmdev_ops *xfrmdev_ops;
1852 #endif
1853
1854 #if IS_ENABLED(CONFIG_TLS_DEVICE)
1855 const struct tlsdev_ops *tlsdev_ops;
1856 #endif
1857
1858 const struct header_ops *header_ops;
1859
1860 unsigned int flags;
......
1869 unsigned char if_port;
1870 unsigned char dma;
1871
1872 /* Note : dev->mtu is often read without holding a lock.
1873 * Writers usually hold RTNL.
1874 * It is recommended to use READ_ONCE() to annotate the
1875 * reads, and to use WRITE_ONCE() to annotate the writes.
1876 */
1877 unsigned int mtu;
1878 unsigned int min_mtu;
1879 unsigned int max_mtu;
1880 unsigned short type;
1881 unsigned short hard_header_len;
1882 unsigned char min_header_len;
1883
1884 unsigned short needed_headroom;
1885 unsigned short needed_tailroom;
1886
1887 /* Interface address info. */
1888 unsigned char perm_addr[MAX_ADDR_LEN];
1889 unsigned char addr_assign_type;
1890 unsigned char addr_len;
.....
1938 /*
1939 * Cache lines mostly used on receive path (including
1940 eth_type_trans()) */
1941 /* Interface address info used in eth_type_trans() */
1942 unsigned char *dev_addr;
1943
1944 struct netdev_rx_queue *_rx;
1945 unsigned int num_rx_queues;
1946 unsigned int real_num_rx_queues;
......
1967 /*
1968 * Cache lines mostly used on transmit path
1969 */
1970 struct netdev_queue *_tx ____cacheline_aligned_in_smp;
1971 unsigned int num_tx_queues;
1972 unsigned int real_num_tx_queues;
1973 struct Qdisc *qdisc;
1974 #ifdef CONFIG_NET_SCHED
1975 DECLARE_HASHTABLE (qdisc_hash, 4);
1976 #endif
1977 unsigned int tx_queue_len;
1978 spinlock_t tx_global_lock;
1979 int watchdog_timeo;
.....
2061 struct phy_device *phydev;
.....
2069 };
下面介紹一些關(guān)鍵的成員變量,如下:
第1784行:name是網(wǎng)絡(luò)設(shè)備的名字。
第1791行:mem_end是共享內(nèi)存結(jié)束地址。
第1792行:mem_start是共享內(nèi)存起始地址。
第1793行:base_addr是網(wǎng)絡(luò)設(shè)備I/O地址。
第1794行:irq是網(wǎng)絡(luò)設(shè)備的中斷號。
第1804行:dev_list是全局網(wǎng)絡(luò)設(shè)備列表。
第1805行:napi_list是napi網(wǎng)絡(luò)設(shè)備的列表入口。
第1806行:unreg_list是注銷(unregister)的網(wǎng)絡(luò)設(shè)備列表入口。
第1807行:close_list是關(guān)閉的網(wǎng)絡(luò)設(shè)備列表入口。
第1841行:netdev_ops是網(wǎng)絡(luò)設(shè)備的操作集函數(shù),包含了一系列的網(wǎng)絡(luò)設(shè)備操作回調(diào)函數(shù),
類似字符設(shè)備中的file_operations,稍后會講解netdev_ops結(jié)構(gòu)體。
第1842行:ethtool_ops是網(wǎng)絡(luò)管理工具相關(guān)函數(shù)集,用戶空間網(wǎng)絡(luò)管理工具會調(diào)用此結(jié)構(gòu)體中的相關(guān)函數(shù)獲取網(wǎng)卡狀態(tài)或者配置網(wǎng)卡。
第1858行:header_ops是頭部的相關(guān)操作函數(shù)集,比如創(chuàng)建、解析、緩沖等。
第1860行:flags是網(wǎng)絡(luò)接口標(biāo)志,標(biāo)志類型定義在include/uapi/linux/if.h文件中,為一個枚舉類型,內(nèi)容如下:
繼續(xù)回到示例代碼52.3.1.1中,接著看net_device結(jié)構(gòu)體。
第1869行:if_port指定接口的端口類型,如果設(shè)備支持多端口的話就通過if_port來指定所使用的端口類型??蛇x的端口類型定義在include/uapi/linux/netdevice.h中,為一個枚舉類型,如下所示:
第1870行:dma是網(wǎng)絡(luò)設(shè)備所使用的DMA通道,不是所有的設(shè)備都會用到DMA。
第1877行:mtu是網(wǎng)絡(luò)最大傳輸單元,為1500。
第1880行:type用于指定ARP模塊的類型,以太網(wǎng)的ARP接口為ARPHRD_ETHER,Linux內(nèi)核所支持的ARP協(xié)議定義在include/uapi/linux/if_arp.h中,可以自行查閱。
第1888行:perm_addr是永久的硬件地址,如果某個網(wǎng)卡設(shè)備有永久的硬件地址,那么就會填充perm_addr。
第1890行:addr_len是硬件地址長度。
第1942行:dev_addr也是硬件地址,是當(dāng)前分配的MAC地址,可以通過軟件修改。
第1944行:_rx是接收隊(duì)列。
第1945行:num_rx_queues是接收隊(duì)列數(shù)量,在調(diào)用register_netdev注冊網(wǎng)絡(luò)設(shè)備的時候會分配指定數(shù)量的接收隊(duì)列。
第1946行:real_num_rx_queues是當(dāng)前活動的隊(duì)列數(shù)量。
第1970行:_tx是發(fā)送隊(duì)列。
第1971行:num_tx_queues是發(fā)送隊(duì)列數(shù)量,通過alloc_netdev_mq函數(shù)分配指定數(shù)量的發(fā)送隊(duì)列。
第1972行:real_num_tx_queues是當(dāng)前有效的發(fā)送隊(duì)列數(shù)量。
第2061行:phydev是對應(yīng)的PHY設(shè)備。
申請net_device
編寫網(wǎng)絡(luò)驅(qū)動的時候首先要申請net_device,使用alloc_netdev函數(shù)來申請net_device,這
是一個宏,宏定義如下:
#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \
alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)
可以看出alloc_netdev的本質(zhì)是alloc_netdev_mqs 函數(shù),此函數(shù)原型如下:
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
unsigned char name_assign_type,
void (*setup)(struct net_device *),
unsigned int txqs, unsigned int rxqs)
函數(shù)參數(shù)和返回值含義如下:
- sizeof_priv:私有數(shù)據(jù)塊大小。
- name:設(shè)備名字。
- name_assign_type:設(shè)備名字的來源。
- setup:回調(diào)函數(shù),初始化設(shè)備的設(shè)備后調(diào)用此函數(shù)。
- txqs:分配的發(fā)送隊(duì)列數(shù)量。
- rxqs:分配的接收隊(duì)列數(shù)量。
- 返回值:如果申請成功的話就返回申請到的net_device指針,失敗的話就返回NULL。
事實(shí)上網(wǎng)絡(luò)設(shè)備有多種,不要以為就只有以太網(wǎng)一種。Linux內(nèi)核支持的網(wǎng)絡(luò)接口有很多,比如光纖分布式數(shù)據(jù)接口(FDDI)、以太網(wǎng)設(shè)備(Ethernet)、紅外數(shù)據(jù)接口(InDA)、高性能并行接口(HPPI)、CAN網(wǎng)絡(luò)等。內(nèi)核針對不同的網(wǎng)絡(luò)設(shè)備在alloc_netdev的基礎(chǔ)上提供了一層封裝,比如本章講解的以太網(wǎng),針對以太網(wǎng)封裝的net_device申請函數(shù)是alloc_etherdev和alloc_etherdev_mq,這也是一個宏,內(nèi)容如下:
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count)
可以看出,alloc_etherdev最終依靠的是alloc_etherdev_mqs函數(shù) ,此函數(shù)就是對alloc_netdev_mqs的簡單封裝,函數(shù)內(nèi)容如下:
第414行調(diào)用alloc_netdev_mqs來申請net_device,注意這里設(shè)置網(wǎng)卡的名字為“eth%d”,這是格式化字符串,大家進(jìn)入開發(fā)板的linux系統(tǒng)以后看到的“eth0”、“eth1”這樣的網(wǎng)卡名字就是從這里來的。同樣的,這里設(shè)置了以太網(wǎng)的setup函數(shù)為ether_setup,不同的網(wǎng)絡(luò)設(shè)備其setup函數(shù)不同,比如CAN網(wǎng)絡(luò)里面setup函數(shù)就是can_setup。
ether_setup函數(shù)會對net_device做初步的初始化,函數(shù)內(nèi)容如下所示:
關(guān)于net_device的申請就講解到這里,對于網(wǎng)絡(luò)設(shè)備而言,使用alloc_etherdev或alloc_etherdev_mqs來申請net_device。ST官方編寫的網(wǎng)絡(luò)驅(qū)動就是采用alloc_etherdev_mqs來申請net_device。
刪除net_device
當(dāng)注銷網(wǎng)絡(luò)驅(qū)動的時候需要釋放掉前面已經(jīng)申請到的net_device,釋放函數(shù)為free_netdev,函數(shù)原型如下:
void free_netdev(struct net_device *dev)
函數(shù)參數(shù)和返回值含義如下:
- dev:要釋放掉的net_device指針。
- 返回值:無。
注冊net_device
net_device申請并初始化完成以后就需要向內(nèi)核注冊net_device,要用到函數(shù)register_netdev,函數(shù)原型如下:
int register_netdev(struct net_device *dev)
函數(shù)參數(shù)和返回值含義如下:
- dev:要注冊的net_device指針。
- 返回值:0,注冊成功;負(fù)值,注冊失敗。
注銷net_device
既然有注冊,那么必然有注銷,注銷net_device 使用函數(shù)unregister_netdev,函數(shù)原型如下:
void unregister_netdev(struct net_device *dev)
函數(shù)參數(shù)和返回值含義如下:
- dev:要注銷的net_device指針。
- 返回值:無。
net_device_ops結(jié)構(gòu)體
net_device有個非常重要的成員變量netdev_ops,為net_device_ops結(jié)構(gòu)體指針類型,這就是網(wǎng)絡(luò)設(shè)備的操作集。net_device_ops結(jié)構(gòu)體定義在include/linux/netdevice.h文件中,net_device_ops結(jié)構(gòu)體里面都是一些以“ndo_”開頭的函數(shù),這些函數(shù)就需要網(wǎng)絡(luò)驅(qū)動編寫人員去實(shí)現(xiàn),不需要全部都實(shí)現(xiàn),根據(jù)實(shí)際驅(qū)動情況實(shí)現(xiàn)其中一部分即可。結(jié)構(gòu)體內(nèi)容如下所示(結(jié)
構(gòu)體成員變量比較多,這里有縮減):
第1250行:ndo_init函數(shù),當(dāng)?shù)谝淮巫跃W(wǎng)絡(luò)設(shè)備的時候此函數(shù)會執(zhí)行,設(shè)備可以在此函數(shù)中做一些需要推后初始化的內(nèi)容,不過一般驅(qū)動中不使用此函數(shù),虛擬網(wǎng)絡(luò)設(shè)備可能會使用。
第1251行:ndo_uninit函數(shù),卸載網(wǎng)絡(luò)設(shè)備的時候此函數(shù)會執(zhí)行。
第1252行:ndo_open函數(shù),打開網(wǎng)絡(luò)設(shè)備的時候此函數(shù)會執(zhí)行,網(wǎng)絡(luò)驅(qū)動程序需要實(shí)現(xiàn)此函數(shù),非常重要!以STM32MP1系列SoC網(wǎng)絡(luò)驅(qū)動為例會在此函數(shù)中做如下工作:
- 初始化接收緩沖區(qū)。
- 如果使用NAPI的話要使能NAPI模塊,通過napi_enable函數(shù)來使能。
- STM32MP1 MAC控制器硬件相關(guān)初始化。
- 開啟PHY。
- 調(diào)用netif_tx_start_all_queues來使能傳輸隊(duì)列,也可能調(diào)用netif_start_queue函數(shù)。
- ……
第1253行:ndo_stop函數(shù),關(guān)閉網(wǎng)絡(luò)設(shè)備的時候此函數(shù)會執(zhí)行,網(wǎng)絡(luò)驅(qū)動程序也需要實(shí)現(xiàn)此函數(shù)。以STM32MP1系列SoC網(wǎng)絡(luò)驅(qū)動為例,會在此函數(shù)中做如下工作:
- 停止PHY。
- 停止NAPI功能。
- 停止發(fā)送功能。
- 關(guān)閉MAC。
- 斷開PHY連接。
- 釋放數(shù)據(jù)緩沖區(qū)。
- ……
第1254行:ndo_start_xmit函數(shù),當(dāng)需要發(fā)送數(shù)據(jù)的時候此函數(shù)就會執(zhí)行,此函數(shù)有一個參數(shù)為sk_buff結(jié)構(gòu)體指針,sk_buff結(jié)構(gòu)體在Linux的網(wǎng)絡(luò)驅(qū)動中非常重要,sk_buff保存了上層傳遞給網(wǎng)絡(luò)驅(qū)動層的數(shù)據(jù)。也就是說,要發(fā)送出去的數(shù)據(jù)都存在了sk_buff中,關(guān)于sk_buff稍后會做詳細(xì)的講解。如果發(fā)送成功的話此函數(shù)返回NETDEV_TX_OK,如果發(fā)送失敗了就返回NETDEV_TX_BUSY,如果發(fā)送失敗了就需要停止隊(duì)列。
第1259行:do_select_queue函數(shù),當(dāng)設(shè)備支持多傳輸隊(duì)列的時候選擇使用哪個隊(duì)列。
第1264行:ndo_set_rx_mode函數(shù),此函數(shù)用于改變地址過濾列表,根據(jù)net_device的flags成員變量來設(shè)置SoC的網(wǎng)絡(luò)外設(shè)寄存器。比如flags可能為IFF_PROMISC、IFF_ALLMULTI或IFF_MULTICAST,分別表示混雜模式、單播模式或多播模式。
第1265行:ndo_set_mac_address函數(shù),此函數(shù)用于修改網(wǎng)卡的MAC地址,設(shè)置net_device的dev_addr成員變量,并且將MAC地址寫入到網(wǎng)絡(luò)外設(shè)的硬件寄存器中。
第1267行:ndo_validate_addr函數(shù),驗(yàn)證MAC地址是否合法,也即是驗(yàn)證net_devic的dev_addr中的MAC地址是否合法,直接調(diào)用is_valid_ether_addr函數(shù)。
第1268行:ndo_do_ioctl函數(shù),用戶程序調(diào)用ioctl的時候此函數(shù)就會執(zhí)行,比如PHY芯片相關(guān)的命令操作,一般會直接調(diào)用phy_mii_ioctl函數(shù)。
第1272行:ndo_change_mtu函數(shù),更改MTU大小。
第1276行:ndo_tx_timeout函數(shù),當(dāng)發(fā)送超時的時候產(chǎn)生會執(zhí)行,一般都是網(wǎng)絡(luò)出問題了導(dǎo)致發(fā)送超時。一般可能會重啟MAC和PHY,重新開始數(shù)據(jù)發(fā)送等。
第1291行:ndo_poll_controller函數(shù),使用查詢方式來處理網(wǎng)卡數(shù)據(jù)的收發(fā)。
第1368行:ndo_set_features函數(shù),修改net_device的features屬性,設(shè)置相應(yīng)的硬件屬性。
sk_buff結(jié)構(gòu)體
網(wǎng)絡(luò)是分層的,對于應(yīng)用層而言不用關(guān)心具體的底層是如何工作的,只需要按照協(xié)議將要發(fā)送或接收的數(shù)據(jù)打包好即可。打包好以后都通過dev_queue_xmit函數(shù)將數(shù)據(jù)發(fā)送出去,接收數(shù)據(jù)的話使用netif_rx函數(shù)即可,依次來看一下這兩個函數(shù)。
dev_queue_xmit函數(shù)
此函數(shù)用于將網(wǎng)絡(luò)數(shù)據(jù)發(fā)送出去,函數(shù)定義在include/linux/netdevice.h中,函數(shù)原型如下:
int dev_queue_xmit(struct sk_buff *skb)
函數(shù)參數(shù)和返回值含義如下:
- skb:要發(fā)送的數(shù)據(jù),這是一個sk_buff結(jié)構(gòu)體指針,sk_buff是Linux網(wǎng)絡(luò)驅(qū)動中一個非常重要的結(jié)構(gòu)體,網(wǎng)絡(luò)數(shù)據(jù)就是以sk_buff保存的,各個協(xié)議層在sk_buff中添加自己的協(xié)議頭,最終由底層驅(qū)動將sk_buff中的數(shù)據(jù)發(fā)送出去。網(wǎng)絡(luò)數(shù)據(jù)的接收過程恰好相反,網(wǎng)絡(luò)底層驅(qū)動將接收到的原始數(shù)據(jù)打包成sk_buff,然后發(fā)送給上層協(xié)議,上層會取掉相應(yīng)的頭部,然后將最終的數(shù)據(jù)發(fā)送給用戶。
- 返回值:0,發(fā)送成功;負(fù)值,發(fā)送失敗。
dev_queue_xmit函數(shù)太長,這里就不詳細(xì)的分析了,dev_queue_xmit函數(shù)最終是通過net_device_ops操作集里面的ndo_start_xmit函數(shù)來完成最終發(fā)送了,ndo_start_xmit就是網(wǎng)絡(luò)驅(qū)動編寫人員去實(shí)現(xiàn)的,整個路程如下圖所示:
netif_rx函數(shù)
上層接收數(shù)據(jù)的話使用netif_rx函數(shù),但是最原始的網(wǎng)絡(luò)數(shù)據(jù)一般是通過輪詢、中斷或NAPI的方式來接收。netif_rx函數(shù)定義在 net/core/dev.c 中,函數(shù)原型如下:
int netif_rx(struct sk_buff *skb)
函數(shù)參數(shù)和返回值含義如下:
- skb:保存接收數(shù)據(jù)的sk_buff。
- 返回值:NET_RX_SUCCESS,成功NET_RX_DROP,數(shù)據(jù)包丟棄。
重點(diǎn)來看一下sk_buff這個結(jié)構(gòu)體,sk_buff是Linux網(wǎng)絡(luò)重要的數(shù)據(jù)結(jié)構(gòu),用于管理接收或發(fā)送數(shù)據(jù)包,sk_buff結(jié)構(gòu)體定義在include/linux/skbuff.h中,結(jié)構(gòu)體內(nèi)容如下(由于結(jié)構(gòu)體比較大,為了縮小篇幅只列出部分重要的內(nèi)容):
示例代碼 52.3.3.1 sk_buff 結(jié)構(gòu)體
685 struct sk_buff {
686 union {
687 struct {
688 /* These two members must be first. */
689 struct sk_buff *next;
690 struct sk_buff *prev;
691
692 union {
693 struct net_device *dev;
694 /* Some protocols might use this space to store
695 * information, while device pointer would be NULL.
696 * UDP receive path is one user.
697 */
698 unsigned long dev_scratch;
699 };
700 };
701 struct rb_node rbnode;
702 struct list_head list;
703 };
704
705 union {
706 struct sock *sk;
707 int ip_defrag_offset;
708 };
709
710 union {
711 ktime_t tstamp;
712 u64 skb_mstamp_ns; /* earliest departure time */
713 };
714 /*
715 * This is the control buffer. It is free to use for every
716 * layer. Please put your private variables there. If you
717 * want to keep them across layers you have to do a skb_clone()
718 * first. This is owned by whoever has the skb queued ATM.
719 */
720 char cb[48] __aligned(8);
721
722 union {
723 struct {
724 unsigned long _skb_refdst;
725 void (*destructor)(struct sk_buff *skb);
726 };
727 struct list_head tcp_tsorted_anchor;
728 };
.....
733 unsigned int len,
734 data_len;
735 __u16 mac_len,
736 hdr_len;
......
868 __be16 protocol;
869 __u16 transport_header;
870 __u16 network_header;
871 __u16 mac_header;
872
873 /* private: */
874 __u32 headers_end[0];
875 /* public: */
876
877 /* These elements must be at the end, see alloc_skb() for
details. */
878 sk_buff_data_t tail;
879 sk_buff_data_t end;
880 unsigned char *head,
881 *data;
882 unsigned int truesize;
883 refcount_t users;
884
885 #ifdef CONFIG_SKB_EXTENSIONS
886 /* only useable after checking ->active_extensions != 0 */
887 struct skb_ext *extensions;
888 #endif
889 };
第687-700 行:next和prev分別指向下一個和前一個sk_buff,構(gòu)成一個雙向鏈表。
第693行:dev表示當(dāng)前sk_buff從哪個設(shè)備接收到或者發(fā)出的。
第706行:sk表示當(dāng)前sk_buff所屬的Socket。
第711行:tstamp表示數(shù)據(jù)包接收時或準(zhǔn)備發(fā)送時的時間戳。
第720行:cb為控制緩沖區(qū),不管哪個層都可以自由使用此緩沖區(qū),用于放置私有數(shù)據(jù)。
第725行:destructor函數(shù),當(dāng)釋放緩沖區(qū)的時候可以在此函數(shù)里面完成某些動作。
第733-734行:len為實(shí)際的數(shù)據(jù)長度,包括主緩沖區(qū)中數(shù)據(jù)長度和分片中的數(shù)據(jù)長度。data_len為數(shù)據(jù)長度,只計(jì)算分片中數(shù)據(jù)的長度。
第735-736行:mac_len為連接層頭部長度,也就是MAC頭的長度。
第868行:protocol協(xié)議。
第869行:transport_header為傳輸層頭部。
第870行:network_header為網(wǎng)絡(luò)層頭部。
第871行:mac_header為鏈接層頭部。
第878行:tail指向?qū)嶋H數(shù)據(jù)的尾部。
第879行:end指向緩沖區(qū)的尾部。
第880行:head指向緩沖區(qū)的頭部,data指向?qū)嶋H數(shù)據(jù)的頭部。data和tail指向?qū)嶋H數(shù)據(jù)的頭部和尾部,head和end指向緩沖區(qū)的頭部和尾部。結(jié)構(gòu)如下圖所示:
針對sk_buff內(nèi)核提供了一系列的操作與管理函數(shù),簡單看一些常見的API函數(shù)。
1、分配sk_buff
要使用sk_buff必須先分配,首先來看一下alloc_skb這個函數(shù),此函數(shù)定義include/linux/skbuff.h中,函數(shù)原型如下:
static inline struct sk_buff *alloc_skb(unsigned int size,
gfp_t priority)
函數(shù)參數(shù)和返回值含義如下:
- size:要分配的大小,也就是skb數(shù)據(jù)段大小。
- priority:為GFP MASK宏,比如GFP_KERNEL、GFP_ATOMIC等。
- 返回值:分配成功的話就返回申請到的sk_buff首地址,失敗的話就返回NULL。
在網(wǎng)絡(luò)設(shè)備驅(qū)動中常常使用netdev_alloc_skb來為某個設(shè)備申請一個用于接收的skb_buff,此函數(shù)也定義在include/linux/skbuff.h中,函數(shù)原型如下:
static inline struct sk_buff *netdev_alloc_skb(struct net_device *dev,
unsigned int length)
函數(shù)參數(shù)和返回值含義如下:
- dev:要給哪個設(shè)備分配sk_buff。
- length:要分配的大小。
- 返回值:分配成功的話就返回申請到的sk_buff首地址,失敗的話就返回NULL。
2、釋放sk_buff
當(dāng)使用完成以后就要釋放掉sk_buff,釋放函數(shù)可以使用kfree_skb,函數(shù)定義在include/linux/skbuff.c中,函數(shù)原型如下:
void kfree_skb(struct sk_buff *skb)
函數(shù)參數(shù)和返回值含義如下:
- skb:要釋放的sk_buff。
- 返回值:無。
對于網(wǎng)絡(luò)設(shè)備而言最好使用如下所示釋放函數(shù):
void dev_kfree_skb (struct sk_buff *skb)
函數(shù)只要一個參數(shù)skb,就是要釋放的sk_buff。
3、skb_put、skb_push、skb_pull和skb_reserve
這四個函數(shù)用于變更sk_buff,先來看一下skb_put函數(shù),此函數(shù)用于在尾部擴(kuò)展skb_buff的數(shù)據(jù)區(qū),也就將skb_buff的tail后移n個字節(jié),從而導(dǎo)致skb_buff的len增加n個字節(jié),原型如下:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
函數(shù)參數(shù)和返回值含義如下:
- skb:要操作的sk_buff。
- len:要增加多少個字節(jié)。
- 返回值:擴(kuò)展出來的那一段數(shù)據(jù)區(qū)首地址。
skb_put操作之前和操作之后的數(shù)據(jù)區(qū)如下圖所示:
skb_push函數(shù)用于在頭部擴(kuò)展skb_buff的數(shù)據(jù)區(qū),函數(shù)原型如下所示:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
函數(shù)參數(shù)和返回值含義如下:
- skb:要操作的sk_buff。
- len:要增加多少個字節(jié)。
- 返回值:擴(kuò)展完成以后新的數(shù)據(jù)區(qū)首地址。
skb_push操作之前和操作之后的數(shù)據(jù)區(qū)如下圖所示:
sbk_pull函數(shù)用于從sk_buff的數(shù)據(jù)區(qū)起始位置刪除數(shù)據(jù),函數(shù)原型如下所示:
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
函數(shù)參數(shù)和返回值含義如下:
- skb:要操作的sk_buff。
- len:要刪除的字節(jié)數(shù)。
- 返回值:刪除以后新的數(shù)據(jù)區(qū)首地址。
skb_pull操作之前和操作之后的數(shù)據(jù)區(qū)如下圖所示:
sbk_reserve函數(shù)用于調(diào)整緩沖區(qū)的頭部大小,方法很簡單,將skb_buff的data和tail同時后移n個字節(jié)即可,函數(shù)原型如下所示:
static inline void skb_reserve(struct sk_buff *skb, int len)
函數(shù)參數(shù)和返回值含義如下:
- skb:要操作的sk_buff。
- len:要增加的緩沖區(qū)頭部大小。
- 返回值:無。
網(wǎng)絡(luò)NAPI處理機(jī)制
像IIC、SPI、網(wǎng)絡(luò)等這些通信接口,接收數(shù)據(jù)有兩種方法:輪詢或中斷。Linux里面的網(wǎng)絡(luò)數(shù)據(jù)接收也有輪詢和中斷兩種,中斷的好處就是響應(yīng)快,數(shù)據(jù)量小的時候處理及時,速度快,但是一旦當(dāng)數(shù)據(jù)量大,而且都是短幀的時候會導(dǎo)致中斷頻繁發(fā)生,消耗大量的CPU處理時間在中斷自身處理上。輪詢恰好相反,響應(yīng)沒有中斷及時,但是在處理大量數(shù)據(jù)的時候不需要消耗過多的CPU處理時間。Linux在這兩個處理方式的基礎(chǔ)上提出了另外一種網(wǎng)絡(luò)數(shù)據(jù)接收的處理方法:NAPI(New API),NAPI是一種高效的網(wǎng)絡(luò)處理技術(shù)。NAPI的核心思想就是不全部采用中斷來讀取網(wǎng)絡(luò)數(shù)據(jù),而是采用中斷來喚醒數(shù)據(jù)接收服務(wù)程序,在接收服務(wù)程序中采用POLL的方法來輪詢處理數(shù)據(jù)。這種方法的好處就是可以提高短數(shù)據(jù)包的接收效率,減少中斷處理的時間。目前NAPI已經(jīng)在Linux的網(wǎng)絡(luò)驅(qū)動中得到了大量的應(yīng)用,ST官方編寫的網(wǎng)絡(luò)驅(qū)動都是采用的NAPI機(jī)制。
關(guān)于NAPI詳細(xì)的處理過程本章節(jié)不討論,本章節(jié)就簡單講解一下如何在驅(qū)動中使用NAPI,Linux內(nèi)核使用結(jié)構(gòu)體napi_struct表示NAPI,在使用NAPI之前要先初始化一個napi_struct實(shí)例。
初始化NAPI
首先要初始化一個napi_struct 實(shí)例,使用netif_napi_add函數(shù),此函數(shù)定義在net/core/dev.c中,函數(shù)原型如下:
void netif_napi_add(struct net_device *dev,
struct napi_struct *napi,
int (*poll)(struct napi_struct *, int),
int weight)
函數(shù)參數(shù)和返回值含義如下:
- dev:每個NAPI必須關(guān)聯(lián)一個網(wǎng)絡(luò)設(shè)備,此參數(shù)指定NAPI要關(guān)聯(lián)的網(wǎng)絡(luò)設(shè)備。
- napi:要初始化的NAPI實(shí)例。
- poll:NAPI所使用的輪詢函數(shù),非常重要,一般在此輪詢函數(shù)中完成網(wǎng)絡(luò)數(shù)據(jù)接收的工作。
- weight:NAPI默認(rèn)權(quán)重(weight),一般為NAPI_POLL_WEIGHT。
- 返回值:無。
刪除NAPI
如果要刪除NAPI,使用netif_napi_del函數(shù)即可,函數(shù)原型如下:
void netif_napi_del(struct napi_struct *napi)
函數(shù)參數(shù)和返回值含義如下:
- napi:要刪除的NAPI。
- 返回值:無。
使能NAPI
初始化完NAPI以后,必須使能才能使用,使用函數(shù)napi_enable,函數(shù)原型如下:
void napi_enable(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
- n:要使能的NAPI。
- 返回值:無。
關(guān)閉NAPI
關(guān)閉NAPI使用napi_disable函數(shù)即可,函數(shù)原型如下:
void napi_disable(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
- n:要關(guān)閉的NAPI。
- 返回值:無。
檢查NAPI是否可以進(jìn)行調(diào)度
使用napi_schedule_prep函數(shù)檢查NAPI是否可以進(jìn)行調(diào)度,函數(shù)原型如下:
inline bool napi_schedule_prep(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
- n:要檢查的NAPI。
- 返回值:如果可以調(diào)度就返回真,如果不可調(diào)度就返回假。
NAPI調(diào)度
如果可以調(diào)度的話就進(jìn)行調(diào)度,使用__napi_schedule函數(shù)完成NAPI調(diào)度,函數(shù)原型如下:
void __napi_schedule(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
- n:要調(diào)度的NAPI。
- 返回值:無。
也可以使用napi_schedule函數(shù)來一次完成napi_schedule_prep和__napi_schedule這兩
個函數(shù)的工作,napi_schedule函數(shù)內(nèi)容如下所示:
示例代碼 52.3.4.1 napi_schedule 函數(shù)代碼
442 static inline void napi_schedule(struct napi_struct *n)
443 {
444 if (napi_schedule_prep(n))
445 __napi_schedule(n);
446 }
從示例代碼52.3.4.1可以看出 ,napi_schedule函 數(shù)就是對napi_schedule_prep和__napi_schedule的簡單封裝,一次完成判斷和調(diào)度。
NAPI處理完成
NAPI處理完成以后需要調(diào)用napi_complete函數(shù)來標(biāo)記NAPI處理完成,函數(shù)原型如下:
inline void napi_complete(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
- n:處理完成的NAPI。
- 返回值:無。
原理圖介紹
正點(diǎn)原子的STM32MP1開發(fā)板網(wǎng)絡(luò)相關(guān)的硬件有兩個版本分別為:V1.2版本以前的核心板和V1.3版本以后的核心板。
V1.2版本以前的核心板
V1.2版本以前的核心板的原理圖,如下所示:
在之前的學(xué)習(xí)中已經(jīng)知道了PHY地址是由RXCTL、RXC和RXD3控制的,從上圖中知道了這三個引腳為0、0和1,PHY的地址為0x1,RGMII的電平為3.3V。
V1.3版本以后的核心板
V1.3版本以后的核心板原理圖,如下所示:
STM32MP1網(wǎng)絡(luò)驅(qū)動框架
STM32MP1網(wǎng)絡(luò)外設(shè)設(shè)備樹
上一小節(jié)對Linux的網(wǎng)絡(luò)驅(qū)動框架進(jìn)行了一個簡單的介紹,本節(jié)就來簡單分析一下STM32MP1的網(wǎng)絡(luò)驅(qū)動源碼??隙ㄊ?strong>先分析設(shè)備樹,STM32MP1系列SoC網(wǎng)絡(luò)綁定文檔為Documentation/devicetree/bindings/net/stm32-dwmac.txt,此綁定文檔描述了STM32MP1系列SoC網(wǎng)絡(luò)設(shè)備樹節(jié)點(diǎn)的要求 。 此外還有一份文檔Documentation/devicetree/bindings/net/ethernet.txt,該文檔描述了網(wǎng)絡(luò)設(shè)備節(jié)點(diǎn)的一些通用屬性。
在內(nèi)核源碼目錄arch/arm/boot/dts/stm32mp152.dtsi設(shè)備樹文件中,定義了網(wǎng)絡(luò)設(shè)備節(jié)點(diǎn),如下所示:
第1605行,compatible屬性有兩個分別為st,stm32mp1-dwmac和snps,dwmac-4.20a。在內(nèi)
核源碼目錄里搜索這兩個屬性值,就能找到drivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c文件。dwmac-stm32.c就是stm32mp1的驅(qū)動源碼,后面在分析。
第1606行,reg屬性值表示網(wǎng)絡(luò)的物理地址大小。
第1621行,snps,mixed-burst屬性,表示DMA使用混合突發(fā)模式。
第1622行,snps,pbl屬性值為2,表示突發(fā)長度為2。
第1623行,snps,en-tx-lpi-clockgating屬性,在TX低功耗模式下,使能MAC TX門控時鐘。
第1625行,snps,tso屬性,表示啟動tso功能,tso全稱為TCP Segment Offload,也就是利用網(wǎng)卡的少量處理能力,降低CPU發(fā)送數(shù)據(jù)包負(fù)載的技術(shù),需要網(wǎng)卡硬件及驅(qū)動支持。
示例代碼52.5.1.1是ST官方編寫的,不需要去修改,此示例代碼52.5.1.1是不能正常工作的,還需要根據(jù)實(shí)際情況添加或者修改一些屬性。打開stm32mp157d-atk.dtsi,找到如下內(nèi)容:
從示例代碼52.5.1.2中可以看到,ethernet0就是要追加的內(nèi)容,接下來來看看這些追加屬性表示什么意思。
- status:這個屬性就不用說了,把status屬性值修改為“okay”,使能網(wǎng)絡(luò)。
- pinctrl-XXX:這些屬性不用說了,就是定義了網(wǎng)絡(luò)的引腳復(fù)用功能。在stm32mp15-pinctrl.dtsi里定義了ethernet0_rgmii_pins_a和ethernet0_rgmii_pins_sleep_a。
- phy-mode:該屬性值是一個字符串,表示網(wǎng)絡(luò)所使用的PHY接口模式,是MII、RMII、GMII還是RGMII等,它是一個標(biāo)準(zhǔn)的屬性,支持的字符串值有‘mii’、‘gmii’、‘sgmii’、‘rmii’、‘rgmii’、‘rgmii-id’、‘rgmii-rxid’、‘rgmii-txid’、‘xgmii’等等。第6行中,phy-mode屬性的值等于‘rgmii-id’表示使用的是RGMII接口模式,由PHY提供RX和TX延遲,MAC不需要添加RX和TX延時內(nèi)容。與RGMII接口相關(guān)的值有‘rgmii’、‘rgmii-id’、‘rgmii-rxid’、‘rgmii-txid’這些,ethernet.txt文件里面詳細(xì)講解了這些屬性值的含義,這里就不詳細(xì)介紹了。
- max-speed:PHY支持的最高速度,比如10、100或1000。
- phy- handle:連接到此網(wǎng)絡(luò)設(shè)備的PHY芯片句柄。
- mdio:第10-17行中定義了一個mdio子節(jié)點(diǎn),該節(jié)點(diǎn)用于描述MDIO總線。#address-cell屬性和#size-cells屬性用于描述mdio節(jié)點(diǎn)的子節(jié)點(diǎn)中所定義的reg屬性,這個reg屬性其實(shí)就表示PHY芯片的地址。
STM32MP1網(wǎng)絡(luò)驅(qū)動源碼簡析
重要的結(jié)構(gòu)體
每個驅(qū)動都會自定義自己的結(jié)構(gòu)體,要了解驅(qū)動的注冊過程,就先從自定義結(jié)構(gòu)體看起。前面說了驅(qū)動文件為:drivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c,打開dwmac-stm32.c,找到如下所示內(nèi)容:
示例代碼52.5.2.1中,主要是第101行,ops成員,表示網(wǎng)絡(luò)相關(guān)的操作集,驅(qū)動人員要實(shí)現(xiàn)的,ST官方已經(jīng)寫好了;其它屬性成員都是時鐘相關(guān)的,肯定是通過設(shè)備樹配置時鐘。去了解一下stm32_ops結(jié)構(gòu)體代碼如下所示:
單片機(jī)要使用網(wǎng)絡(luò)的時候,需要初始化網(wǎng)絡(luò)相關(guān)的寄存器,stm32_ops就是根據(jù)設(shè)備樹參數(shù)去設(shè)置網(wǎng)絡(luò)相關(guān)的寄存器,后面會分析stm32_ops。
plat_stmmacenet_data結(jié)構(gòu)體很重要,將網(wǎng)絡(luò)設(shè)備注冊到內(nèi)核的時候需要使用到此結(jié)構(gòu)體。此結(jié)構(gòu)體定義在include/linux/stmmac.h,plat_stmmacenet_data結(jié)構(gòu)體原型如下示例代碼所示:
示例代碼52.5.2.3中的內(nèi)容比較多,有省略,只列出了驅(qū)動要用到的一些參數(shù)。stm32_dwmac和stm32_ops這兩個結(jié)構(gòu)體主要是用作初始化網(wǎng)絡(luò),plat_stmmacenet_data結(jié)構(gòu)體主要用于注冊網(wǎng)絡(luò)設(shè)備。
stm32_dwmac_probe函數(shù)
STM32MP1網(wǎng)絡(luò)驅(qū)動主要分兩部分:STM32MP1網(wǎng)絡(luò)外設(shè)MAC驅(qū)動以及PHY芯片驅(qū)動,MAC驅(qū)動是ST編寫的,PHY芯片有通用驅(qū)動文件,有些PHY芯片廠商還會針對自己的芯片編寫對應(yīng)的PHY驅(qū)動??傮w來說,SoC內(nèi)置網(wǎng)絡(luò)MAC+外置PHY芯片這種方案是不需要編寫什么驅(qū)動的,基本可以直接使用。但是為了學(xué)習(xí),還是要簡單分析一下具體的網(wǎng)絡(luò)驅(qū)動編寫過程。打開dwmac-stm32.c,找到如下所示內(nèi)容:
第528行,匹配表包含“st,stm32mp1-dwmac”,因此設(shè)備樹和驅(qū)動匹配上,當(dāng)匹配成功以后第534行的stm32_dwmac_probe函數(shù)就會執(zhí)行,簡單分析一下stm32_dwmac_probe函數(shù),函數(shù)內(nèi)容如下:
示例代碼 52.5.2.5 stm32_dwmac_probe 函數(shù)
360 static int stm32_dwmac_probe(struct platform_device *pdev)
361 {
362 struct plat_stmmacenet_data *plat_dat;
363 struct stmmac_resources stmmac_res;
364 struct stm32_dwmac *dwmac;
365 const struct stm32_ops *data;
366 int ret;
367
368 ret = stmmac_get_platform_resources(pdev, &stmmac_res);
369 if (ret)
370 return ret;
371
372 plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);
373 if (IS_ERR(plat_dat))
374 return PTR_ERR(plat_dat);
375
376 dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
377 if (!dwmac) {
378 ret = -ENOMEM;
379 goto err_remove_config_dt;
380 }
381
382 data = of_device_get_match_data(&pdev->dev);
383 if (!data) {
384 dev_err(&pdev->dev, "no of match data provided\n");
385 ret = -EINVAL;
386 goto err_remove_config_dt;
387 }
388
389 dwmac->ops = data;
390 dwmac->dev = &pdev->dev;
391
392 ret = stm32_dwmac_parse_data(dwmac, &pdev->dev);
393 if (ret) {
394 dev_err(&pdev->dev, "Unable to parse OF data\n");
395 goto err_remove_config_dt;
396 }
397
398 if (stmmac_res.wol_irq && !dwmac->clk_eth_ck) {
399 ret = stm32_dwmac_wake_init(&pdev->dev, &stmmac_res);
400 if (ret)
401 return ret;
402 }
403
404 plat_dat->bsp_priv = dwmac;
405
406 ret = stm32_dwmac_init(plat_dat);
407 if (ret)
408 goto err_remove_config_dt;
409
410 ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
411 if (ret)
412 goto err_clk_disable;
413
414 return 0;
415
416 err_clk_disable:
417 stm32_dwmac_clk_disable(dwmac);
418 err_remove_config_dt:
419 stmmac_remove_config_dt(pdev, plat_dat);
420
421 return ret;
422 }
第368行,使用stmmac_get_platform_resources函數(shù)獲取設(shè)備樹上的資源,保存到struct
stmmac_resources類型的結(jié)構(gòu)體里。
第372行,plat_dat變量指針類型為plat_stmmacenet_data結(jié)構(gòu)體,plat_dat結(jié)構(gòu)體主要是保存網(wǎng)絡(luò)設(shè)備的參數(shù),根據(jù)plat_dat的參數(shù)將網(wǎng)絡(luò)設(shè)備注冊進(jìn)內(nèi)核。stmmac_probe_config_dt函數(shù)作用是根據(jù)設(shè)備樹上的屬性值去填充plat_dat各個成員。stmmac_probe_config_dt函數(shù)內(nèi)容如下示例代碼所示(有省略):
示例代碼 52.5.2.6 stmmac_probe_config_dt 函數(shù)
395 struct plat_stmmacenet_data *
396 stmmac_probe_config_dt(struct platform_device *pdev,
const char **mac)
397 {
398 struct device_node *np = pdev->dev.of_node;
399 struct plat_stmmacenet_data *plat;
400 struct stmmac_dma_cfg *dma_cfg;
401 int rc;
402
403 plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL);
404 if (!plat)
405 return ERR_PTR(-ENOMEM);
406
407 *mac = of_get_mac_address(np);
408 if (IS_ERR(*mac)) {
409 if (PTR_ERR(*mac) == -EPROBE_DEFER)
410 return ERR_CAST(*mac);
411
412 *mac = NULL;
413 }
414
415 plat->phy_interface = of_get_phy_mode(np);
416 if (plat->phy_interface < 0)
417 return ERR_PTR(plat->phy_interface);
.....
425 plat->phy_node = of_parse_phandle(np, "phy-handle", 0);
426
427 /* PHYLINK automatically parses the phy-handle property */
428 plat->phylink_node = np;
429
430 /* Get max speed of operation from device tree */
431 if (of_property_read_u32(np, "max-speed", &plat->max_speed))
432 plat->max_speed = -1;
433
434 plat->bus_id = of_alias_get_id(np, "ethernet");
435 if (plat->bus_id < 0)
436 plat->bus_id = 0;
437
438 /* Default to phy auto-detection */
439 plat->phy_addr = -1;
......
454 rc = stmmac_dt_phy(plat, np, &pdev->dev);
......
607 return plat;
608
609 error_hw_init:
610 clk_disable_unprepare(plat->pclk);
611 error_pclk_get:
612 clk_disable_unprepare(plat->stmmac_clk);
613
614 return ERR_PTR(-EPROBE_DEFER);
615 }
stmmac_probe_config_dt函數(shù)會新建一個plat_dat 指針變量,從設(shè)備樹獲取數(shù)據(jù)初始化palt_dat里的成員,返回plat_dat的指針地址。
第407行,使用of_get_mac_address函數(shù)從設(shè)備樹獲取MAC地址,這里沒有定義。
第415行,使用of_get_phy_mode函數(shù)從設(shè)備樹獲取接口模式,獲取phy-mode屬性值。這里屬性值為“rgmii-id”。
第425行,使用of_parse_phandle函數(shù)從設(shè)備樹獲取PHY句柄。
第431行,使用of_property_read_u32函數(shù)從設(shè)備樹獲取max-speed屬性值。這里屬性值為“1000”,表示網(wǎng)絡(luò)為1000M網(wǎng)絡(luò)。
第434行,獲取網(wǎng)絡(luò)的別名。
第439行,設(shè)置plat結(jié)構(gòu)體里的phy_addr成員phy_addr設(shè)置為“-1”,phy_addr表示PHY的地址。設(shè)置為-1表示自動檢測,一個MDIO接口最多可以連接32個PHY芯片,設(shè)置為-1就是自動遍歷所有的PHY地址,看看哪個地址上有PHY芯片,然后將對應(yīng)的PHY地址重新賦值給phy_addr。
第454行,使用stmmac_dt_phy函數(shù)去獲取“mdio”節(jié)點(diǎn)中的信息。
繼續(xù)返回示例代碼52.5.2.4中的進(jìn)行解釋。
第376行,給dwmac結(jié)構(gòu)體開空間。
第382行,獲取of_device_id里的data成員值,獲取ST官方自定義的stm32_ops操作集。
第389行,把獲取的stm32_ops地址,賦值到stm32_dwmac結(jié)構(gòu)體里的ops成員,初始化網(wǎng)絡(luò)的時候,可以調(diào)用ops就能操作到網(wǎng)絡(luò)相關(guān)的寄存器。
第392行,使用stm32_dwmac_parse_data函數(shù)主要是負(fù)責(zé)從設(shè)備樹里獲取時鐘和基地址。
第405行,調(diào)用stm32_dwmac_init函數(shù)進(jìn)行,初始化網(wǎng)絡(luò)設(shè)備,函數(shù)的原型如下示例代碼所示:
第121行,調(diào)用stm32_ops結(jié)構(gòu)體里的set_mode函數(shù)進(jìn)行寄存器的初始化。
第126行,使能時鐘。
繼續(xù)返回示例代碼52.5.2.5中stm32_dwmac_probe函數(shù)。
stm32_dwmac_probe函數(shù)第410行,調(diào)用stmmac_dvr_probe函數(shù)進(jìn)行網(wǎng)絡(luò)注冊,同時完成GMII/RGMII接口初始化。
stmmac_dvr_probe函數(shù)調(diào)用netif_napi_add函數(shù)來設(shè)置poll函數(shù),如下圖所示:
從上圖可以知道,通過netif_napi_add函數(shù)向網(wǎng)卡加了一個napi示例,使用NAPI驅(qū)動要提供一個poll函數(shù)來輪詢處理接收數(shù)據(jù),發(fā)送和接收的poll函數(shù)分別為:“stmmac_napi_poll_tx”和“stmmac_napi_poll_rx”。后面分析網(wǎng)絡(luò)數(shù)據(jù)接收處理流程的時候詳細(xì)講解這個兩個函數(shù)。
繼續(xù)分析stmmac_dvr_probe函數(shù),此函數(shù)會調(diào)用stmmac_mdio_register函數(shù)來向內(nèi)核注冊MDIO總線,stmmac_mdio_register函數(shù)重點(diǎn)是下圖中的兩行代碼:
new_bus下的read和write這兩個成員變量分別是讀/寫PHY寄存器的操作函數(shù),這設(shè)置為stmmac_mdio_read和stmmac_mdio_write,這兩個函數(shù)就是STM32MP1系列SoC讀寫PHY內(nèi)部寄存器的函數(shù)。讀寫PHY寄存器都會通過這兩個MDIO總線函數(shù)完成。stmmac_mdio_register函數(shù)最終會向Linux內(nèi)核注冊MDIO總線,相關(guān)代碼如下所示:
第1行,通過of_mdiobus_register向內(nèi)核注冊MDIO總線,如果設(shè)備樹定義了mdio節(jié)點(diǎn)和PHY句柄還會注冊PHY設(shè)備。
第7行,判斷PHY設(shè)備是否注冊成功,沒有就遍歷所有的PHY地址找到對應(yīng)的PHY設(shè)備。
第10-28行,如果設(shè)備樹里沒有mdio節(jié)點(diǎn)和PHY句柄,通過遍歷所有的PHY地址,找到硬件所對應(yīng)的PHY設(shè)備。
最后stmmac_dvr_probe函數(shù)調(diào)用register_netdev函數(shù)進(jìn)行網(wǎng)絡(luò)設(shè)備注冊。如下圖所示:
MDIO總線注冊
MDIO講了很多次了,就是用來管理PHY芯片的,分為MDIO和MDC兩根線,Linux內(nèi)核專門為MDIO準(zhǔn)備一個總線,叫做MDIO總線,采用mii_bus結(jié)構(gòu)體表示,定義在include/linux/phy.h文件中,mii_bus結(jié)構(gòu)體如下所示(限于篇幅,有省略):
重點(diǎn)是第217、218兩行的read和write函數(shù),這兩個函數(shù)就是讀/寫PHY芯片的操作函數(shù),不同的SoC其MDIO主控部分是不一樣的,因此需要驅(qū)動編寫人員去編寫。前面在分析stm32_dwmac_probe函數(shù)的時候已經(jīng)講過了,stm32_dwmac_probe函數(shù)會調(diào)用stmmac_mdio_register函數(shù)完成MII接口的初始化,其中就包括初始化mii_bus下的read和write這兩個函數(shù)。最終通過of_mdiobus_register或者mdiobus_register函數(shù)將初始化以后的mii_bus注冊到Linux內(nèi)核,of_mdiobus_register函數(shù)其實(shí)也是調(diào)用的mdiobus_register函數(shù)來完成mii_bus注冊的。先看下of_mdiobus_register函數(shù),如下示例代碼所示:
示例代碼 52.5.2.10 of_mdiobus_register 函數(shù)
199 int of_mdiobus_register(struct mii_bus *mdio,
struct device_node *np)
200 {
201 struct device_node *child;
202 bool scanphys = false;
203 int addr, rc;
204
205 if (!np)
206 return mdiobus_register(mdio);
207
208 /* Do not continue if the node is disabled */
209 if (!of_device_is_available(np))
210 return -ENODEV;
211
212 /* Mask out all PHYs from auto probing. Instead the PHYs listed
213 * in the device tree are populated after the bus has been
registered */
214 mdio->phy_mask = ~0;
215
216 mdio->dev.of_node = np;
217 mdio->dev.fwnode = of_fwnode_handle(np);
218
219 /* Get bus level PHY reset GPIO details */
220 mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY;
221 of_property_read_u32(np, "reset-delay-us",
&mdio->reset_delay_us);
222
223 /* Register the MDIO bus */
224 rc = mdiobus_register(mdio);
225 if (rc)
226 return rc;
227
228 /* Loop over the child nodes and register a phy_device for each
phy */
229 for_each_available_child_of_node(np, child) {
230 addr = of_mdio_parse_addr(&mdio->dev, child);
231 if (addr < 0) {
232 scanphys = true;
233 continue;
234 }
235
236 if (of_mdiobus_child_is_phy(child))
237 rc = of_mdiobus_register_phy(mdio, child, addr);
238 else
239 rc = of_mdiobus_register_device(mdio, child, addr);
240
241 if (rc == -ENODEV)
242 dev_err(&mdio->dev,
243 "MDIO device at address %d is missing.\n",
244 addr);
245 else if (rc)
246 goto unregister;
247 }
.....
281 }
of_mdiobus_register函數(shù)主要是注冊mii總線(mii_bus),然后遍歷所有的PHY地址,當(dāng)找到對應(yīng)的PHY芯片以后就會創(chuàng)建PHY設(shè)備,最后將這個PHY設(shè)備注冊到內(nèi)核中。使用mdiobus_scan函數(shù)進(jìn)行注冊PHY設(shè)備。
第206行,設(shè)備節(jié)點(diǎn)不存在的時候此行代碼執(zhí)行,本實(shí)驗(yàn)設(shè)備節(jié)點(diǎn)肯定存在,所以之類不會運(yùn)行。
第224行,當(dāng)設(shè)備樹中mdio節(jié)點(diǎn)存在,就會將相關(guān)屬性信息賦值給mdio參數(shù),然后調(diào)用mdiobus_register向內(nèi)核注冊此mii總線。
第229行,輪詢mdio節(jié)點(diǎn)下的所有phy子節(jié)點(diǎn),比如示例代碼52.5.1.2中的“phy0:ethernet-phy@0”這個子節(jié)點(diǎn),它描述的是PHY芯片信息。
第237行,如果找到一個PHY子節(jié)點(diǎn),就說明找到了一個PHY芯片,那么就調(diào)用of_mdiobus_register_phy函數(shù)向內(nèi)核注冊此PHY設(shè)備。
接下來簡單分析一下of_mdiobus_register_phy函數(shù),看看如何向Linux內(nèi)核注冊PHY設(shè)備的,of_mdiobus_register_phy函數(shù)內(nèi)容如下所示:
示例代碼 52.5.2.11 of_mdiobus_register_phy 函數(shù)
1 static int of_mdiobus_register_phy(struct mii_bus *mdio,
2 struct device_node *child, u32 addr)
3 {
4 struct phy_device *phy;
5 bool is_c45;
6 int rc;
7 u32 phy_id;
8
9 is_c45 = of_device_is_compatible(child,
10 "ethernet-phy-ieee802.3-c45");
11
12 if (!is_c45 && !of_get_phy_id(child, &phy_id))
13 phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
14 else
15 phy = get_phy_device(mdio, addr, is_c45);
16 if (IS_ERR(phy))
17 return PTR_ERR(phy);
18
19 rc = of_irq_get(child, 0);
20 if (rc == -EPROBE_DEFER) {
21 phy_device_free(phy);
22 return rc;
23 }
24 if (rc > 0) {
25 phy->irq = rc;
26 mdio->irq[addr] = rc;
27 } else {
28 phy->irq = mdio->irq[addr];
29 }
30
31 if (of_property_read_bool(child, "broken-turn-around"))
32 mdio->phy_ignore_ta_mask |= 1 << addr;
33
34 of_property_read_u32(child, "reset-assert-us",
35 &phy->mdio.reset_assert_delay);
36 of_property_read_u32(child, "reset-deassert-us",
37 &phy->mdio.reset_deassert_delay);
38
39 /* Associate the OF node with the device structure so it
40 * can be looked up later */
41 of_node_get(child);
42 phy->mdio.dev.of_node = child;
43 phy->mdio.dev.fwnode = of_fwnode_handle(child);
44
45 /* All data is now stored in the phy struct;
46 * register it */
47 rc = phy_device_register(phy);
48 if (rc) {
49 phy_device_free(phy);
50 of_node_put(child);
51 return rc;
52 }
53
54 dev_dbg(&mdio->dev, "registered phy %pOFn at address %i\n",
55 child, addr);
56 return 0;
57 }
第9行,使用函數(shù)of_device_is_compatible檢查PHY節(jié)點(diǎn)的compatible屬性是否為“ethernet-phy-ieee802.3-c45”,如果是的話要做其他的處理,這里沒有設(shè)置此屬性。
第15行,調(diào)用get_phy_device函數(shù)獲取PHY設(shè)備,此函數(shù)里面會調(diào)用phy_device_create來創(chuàng)建一個phy_device設(shè)備并返回。
第19行,獲取PHY芯片的中斷信息,本章節(jié)并未用到。
第47行,調(diào)用phy_device_register函數(shù)向Linux內(nèi)核注冊PHY設(shè)備。
從上面的分析可以看出,向Linux內(nèi)核注冊MDIO總線的時候也會同時向Linux內(nèi)核注冊PHY設(shè)備,流程如下圖所示:
注冊MDIO總線的時候會先從設(shè)備樹中查找PHY設(shè)備,沒有就會遍歷所有的PHY設(shè)備,然后通過phy_device_register函數(shù)向內(nèi)核注冊PHY設(shè)備。
stm32_dwmac_remove函數(shù)簡析
卸載STM32MP1網(wǎng)絡(luò)驅(qū)動的時候stm32_dwmac_remove函數(shù)就會執(zhí)行,函數(shù)內(nèi)容如下所示:
第428行調(diào)用了stmmac_dvr_remove函數(shù)進(jìn)行卸載,此函數(shù)內(nèi)容如下所示:
第8行,調(diào)用stmmac_stop_all_dma函數(shù)結(jié)束所有的DMA。
第12行,調(diào)用unregister_netdev函數(shù)注銷前面注冊的net_device。
第24行,調(diào)用stmmac_mdio_unregister函數(shù)來移除掉MDIO總線相關(guān)的內(nèi)容,此函數(shù)會調(diào)用mdiobus_unregister來注銷掉mii_bus,并且通過函數(shù)mdiobus_free釋放掉mii_bus。
第25行,釋放工作隊(duì)列。
stmmac_netdev_ops操作集
stmmac_dvr_probe函數(shù)設(shè)置了網(wǎng)卡驅(qū)動的net_dev_ops操作集為stmmac_netdev_ops,stmmac_netdev_ops內(nèi)容如下:
1、stmmac_open函數(shù)簡析
打開一個網(wǎng)卡的時候stmmac_open函數(shù)就會執(zhí)行,函數(shù)源碼如下所示(限于篇幅原因,有省
略):
示例代碼 52.5.3.1 stmmac_open 函數(shù)
1 static int stmmac_open(struct net_device *dev)
2 {
3 struct stmmac_priv *priv = netdev_priv(dev);
4 int bfsize = 0;
5 u32 chan;
6 int ret;
7
8 if (priv->hw->pcs != STMMAC_PCS_RGMII &&
9 priv->hw->pcs != STMMAC_PCS_TBI &&
10 priv->hw->pcs != STMMAC_PCS_RTBI) {
11 ret = stmmac_init_phy(dev);
12 if (ret) {
13 netdev_err(priv->dev,
14 "%s: Cannot attach to PHY (error: %d)\n",
15 __func__, ret);
16 return ret;
17 }
18 }
......
36 ret = alloc_dma_desc_resources(priv);
37 if (ret < 0) {
38 netdev_err(priv->dev, "%s: DMA descriptors allocation
failed\n",
39 __func__);
40 goto dma_desc_error;
41 }
42
43 ret = init_dma_desc_rings(dev, GFP_KERNEL);
44 if (ret < 0) {
45 netdev_err(priv->dev, "%s: DMA descriptors initialization
failed\n",
46 __func__);
47 goto init_error;
48 }
49
50 ret = stmmac_hw_setup(dev, true);
51 if (ret < 0) {
52 netdev_err(priv->dev, "%s: Hw setup failed\n", __func__);
53 goto init_error;
54 }
55
56 stmmac_init_coalesce(priv);
57
58 phylink_start(priv->phylink);
59
60 /* Request the IRQ lines */
61 ret = request_irq(dev->irq, stmmac_interrupt,
62 IRQF_SHARED, dev->name, dev);
63 if (unlikely(ret < 0)) {
64 netdev_err(priv->dev,
65 "%s: ERROR: allocating the IRQ %d (error: %d)\n",
66 __func__, dev->irq, ret);
67 goto irq_error;
68 }
69
70 /* Request the Wake IRQ in case of another line is used for WoL */
71 if (priv->wol_irq != dev->irq) {
72 ret = request_irq(priv->wol_irq, stmmac_interrupt,
73 IRQF_SHARED, dev->name, dev);
74 if (unlikely(ret < 0)) {
75 netdev_err(priv->dev,
76 "%s: ERROR: allocating the WoL IRQ %d (%d)\n",
77 __func__, priv->wol_irq, ret);
78 goto wolirq_error;
79 }
80 }
81
82 /* Request the IRQ lines */
83 if (priv->lpi_irq > 0) {
84 ret = request_irq(priv->lpi_irq, stmmac_interrupt,
IRQF_SHARED,
85 dev->name, dev);
86 if (unlikely(ret < 0)) {
87 netdev_err(priv->dev,
88 "%s: ERROR: allocating the LPI IRQ %d (%d)\n",
89 __func__, priv->lpi_irq, ret);
90 goto lpiirq_error;
91 }
92 }
93
94 stmmac_enable_all_queues(priv);
95 stmmac_start_all_queues(priv);
96
97 return 0;
98 };
第11行,看名字就知道了,初始化PHY設(shè)備。
第36行,分配DMA資源,分為TX和RX兩個DMA資源。
第43行,初始化TX和RX DMA描述符,并且分配socket緩沖區(qū)。
第50行,設(shè)置mac為可用狀態(tài),配置mac核心寄存器,然后DMA數(shù)據(jù)準(zhǔn)備接收和發(fā)送。
第58行,啟動phylink。
第60-92行,申請中斷,中斷函數(shù)為stmmac_interrupt,重點(diǎn),后面會解析。
第94-95行,使能隊(duì)列和開啟隊(duì)列。
2、stmmac_release函數(shù)簡析
關(guān)閉網(wǎng)卡的時候stmmac_release函數(shù)就會執(zhí)行,函數(shù)內(nèi)容如下:
第10行,停止phylink。
第11行,斷開phylink。
第13行,停止工作隊(duì)列。
第15行,關(guān)閉工作隊(duì)列。
第28行,停止TX/RX的DMA。
第31行,釋放TX/RX DMA資源。
第34行,關(guān)閉MAC TX/RX。
第36行,關(guān)閉網(wǎng)絡(luò)。
3、stmmac_xmit函數(shù)簡析
STM32MP1的網(wǎng)絡(luò)數(shù)據(jù)發(fā)送是通過stmmac_xmit函數(shù)來完成,這個函數(shù)將上層傳遞過來的sk_buff中的數(shù)據(jù)通過DMA發(fā)送出去,函數(shù)源碼如下所示(限于篇幅原因,有省略):
示例代碼 52.5.3.3 stmmac_xmit 函數(shù)
1 static netdev_tx_t stmmac_xmit(struct sk_buff *skb,
struct net_device *dev)
2 {
3 struct stmmac_priv *priv = netdev_priv(dev);
4 unsigned int nopaged_len = skb_headlen(skb);
5 int i, csum_insertion = 0, is_jumbo = 0;
6 u32 queue = skb_get_queue_mapping(skb);
7 int nfrags = skb_shinfo(skb)->nr_frags;
......
164 /* Ready to fill the first descriptor and set the OWN bit w/o
165 * any problems because all the descriptors are actually ready
166 * to be passed to the DMA engine.
167 */
168 if (likely(!is_jumbo)) {
169 bool last_segment = (nfrags == 0);
170
171 des = dma_map_single(priv->device, skb->data,
172 nopaged_len, DMA_TO_DEVICE);
173 if (dma_mapping_error(priv->device, des))
174 goto dma_map_err;
175
176 tx_q->tx_skbuff_dma[first_entry].buf = des;
177
......
190 /* Prepare the first descriptor setting the OWN bit too */
191 stmmac_prepare_tx_desc(priv, first, 1, nopaged_len,
192 csum_insertion, priv->mode, 1, last_segment,
193 skb->len);
194 } else {
195 stmmac_set_tx_owner(priv, first);
196 }
197
198 /* The own bit must be the latest setting done when prepare the
199 * descriptor and then barrier is needed to make sure that
200 * all is coherent before granting the DMA engine.
201 */
202 wmb();
203
204 netdev_tx_sent_queue(netdev_get_tx_queue(dev, queue), skb->len);
205
206 stmmac_enable_dma_transmission(priv, priv->ioaddr);
207
208 tx_q->tx_tail_addr = tx_q->dma_tx_phy + (tx_q->cur_tx *
sizeof(*desc));
209 stmmac_set_tx_tail_ptr(priv, priv->ioaddr, tx_q->tx_tail_addr,
queue);
210 stmmac_tx_timer_arm(priv, queue);
211
212 return NETDEV_TX_OK;
......
219 }
第171行,調(diào)用dma_map_single函數(shù),進(jìn)行DMA映射,返回值就是DMA映射后的虛擬地址。
第176行,保存DMA的虛擬地址,在stmmac_interrupt中斷函數(shù)里使用。
第191行,調(diào)用stmmac_prepare_tx_desc函數(shù),填充描述符其它信息,讓DMA擁有該描述符。
第209行,設(shè)置尾指針,啟動DMA發(fā)送。
4、stmmac_interrupt中斷函數(shù)簡析
當(dāng)DMA發(fā)送完數(shù)據(jù)或者接收數(shù)據(jù)都會產(chǎn)生中斷,調(diào)用stmmac_interrupt函數(shù)就行處理,stmmac_interrupt函數(shù)通過調(diào)用stmmac_dma_interrupt函數(shù)處理DMA相關(guān)中斷,函數(shù)的源碼如下所示(有省略):
第15行,調(diào)用stmmac_napi_check函數(shù)設(shè)置NAPI軟中斷標(biāo)志位,當(dāng)觸發(fā)NAPI機(jī)制軟中斷,這個時候napi的poll函數(shù)就會執(zhí)行,如果是接收數(shù)據(jù)的就會觸發(fā)stmmac_napi_poll_rx函數(shù),是發(fā)送數(shù)據(jù)的就會觸發(fā)stmmac_napi_poll_tx函數(shù)。
5、stmmac_napi_poll_rx函數(shù)簡析
stmmac_napi_poll_rx函數(shù)內(nèi)容如下所示:
第11行,stmmac_rx函數(shù)是用來接收數(shù)據(jù)的。
第12行,napi_complete_done函數(shù)結(jié)束NAPI輪詢。
第13行,重新開啟dma的中斷。
6、stmmac_napi_poll_tx函數(shù)簡析
stmmac_napi_poll_tx函數(shù)內(nèi)容如下所示:
第12行,調(diào)用stmmac_tx_clean進(jìn)行數(shù)據(jù)傳輸,傳輸完成以后會進(jìn)行資源回收。
第19-24行,重啟發(fā)送數(shù)據(jù)。
Linux內(nèi)核PHY子系統(tǒng)與MDIO總線簡析
上一小節(jié)在講解MDIO總線的時候講過,注冊MDIO總線的時候也會向內(nèi)核注冊PHY設(shè)備,本節(jié)就來簡單了解一下PHY子系統(tǒng)。PHY子系統(tǒng)就是用于PHY設(shè)備相關(guān)內(nèi)容的,分為PHY設(shè)備和PHY驅(qū)動,和platform總線一樣,PHY子系統(tǒng)也是一個設(shè)備、總線和驅(qū)動模型。
PHY設(shè)備
首先看一下PHY設(shè)備,Linux內(nèi)核使用phy_device結(jié)構(gòu)體來表示PHY設(shè)備,結(jié)構(gòu)體定義在include/linux/phy.h,結(jié)構(gòu)體內(nèi)容如下(為了縮小篇幅,有省略):
一個PHY設(shè)備對應(yīng)一個phy_device實(shí)例,然后需要向Linux內(nèi)核注冊這個實(shí)例。使用phy_device_register函數(shù)完成PHY設(shè)備的注冊,函數(shù)原型如下:
int phy_device_register(struct phy_device *phy)
函數(shù)參數(shù)和返回值含義如下:
- phy:需要注冊的PHY設(shè)備。
- 返回值:0,成功;負(fù)值,失敗。
PHY設(shè)備的注冊過程一般是先調(diào)用get_phy_device函數(shù)獲取PHY設(shè)備,此函數(shù)內(nèi)容如下:
第827行,調(diào)用get_phy_id函數(shù)獲取PHY ID,也就是讀取PHY芯片的那兩個ID寄存器,得到PHY芯片ID信息。
第835行,調(diào)用phy_device_create函數(shù)創(chuàng)建phy_device,此函數(shù)先申請phy_device內(nèi)存,然后初始化phy_device的各個結(jié)構(gòu)體成員,最終返回創(chuàng)建好的phy_device。phy_device_register函數(shù)注冊的就是這個創(chuàng)建好的phy_device。
PHY驅(qū)動
PHY驅(qū)動使用結(jié)構(gòu)體phy_driver表示,結(jié)構(gòu)體也定義在include/linux/phy.h 文件中,結(jié)構(gòu)體內(nèi)容如下(為了縮小篇幅,省略了注釋部分):
示例代碼 52.5.4.3 phy_driver 結(jié)構(gòu)體
1 struct phy_driver {
2 struct mdio_driver_common mdiodrv;
3 u32 phy_id; /* PHY ID */
4 char *name;
5 u32 phy_id_mask; /* PHY ID 掩碼 */
6 const unsigned long * const features;
7 u32 flags;
8 const void *driver_data;
9
10 int (*soft_reset)(struct phy_device *phydev);
11 int (*config_init)(struct phy_device *phydev);
12 int (*probe)(struct phy_device *phydev);
13 int (*get_features)(struct phy_device *phydev);
14 int (*suspend)(struct phy_device *phydev);
15 int (*resume)(struct phy_device *phydev);
16 int (*config_aneg)(struct phy_device *phydev);
17 int (*aneg_done)(struct phy_device *phydev);
18 int (*read_status)(struct phy_device *phydev);
19 int (*ack_interrupt)(struct phy_device *phydev);
20 int (*config_intr)(struct phy_device *phydev);
21 int (*did_interrupt)(struct phy_device *phydev);
22 int (*handle_interrupt)(struct phy_device *phydev);
23 void (*remove)(struct phy_device *phydev);
24 int (*match_phy_device)(struct phy_device *phydev);
25 int (*ts_info)(struct phy_device *phydev,
struct ethtool_ts_info *ti);
26 int (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);
27 bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb,
int type);
28 void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb,
int type);
29 int (*set_wol)(struct phy_device *dev,
struct ethtool_wolinfo *wol);
30 void (*get_wol)(struct phy_device *dev,
struct ethtool_wolinfo *wol);
31 void (*link_change_notify)(struct phy_device *dev);
32 int (*read_mmd)(struct phy_device *dev, int devnum, u16 regnum);
33 int (*write_mmd)(struct phy_device *dev, int devnum,
u16 regnum,u16 val);
34 int (*read_page)(struct phy_device *dev);
35 int (*write_page)(struct phy_device *dev, int page);
36 int (*module_info)(struct phy_device *dev,
37 struct ethtool_modinfo *modinfo);
38 int (*module_eeprom)(struct phy_device *dev,
39 struct ethtool_eeprom *ee, u8 *data);
40 int (*get_sset_count)(struct phy_device *dev);
41 void (*get_strings)(struct phy_device *dev, u8 *data);
42 void (*get_stats)(struct phy_device *dev,
43 struct ethtool_stats *stats, u64 *data);
44 int (*get_tunable)(struct phy_device *dev,
45 struct ethtool_tunable *tuna, void *data);
46 int (*set_tunable)(struct phy_device *dev,
47 struct ethtool_tunable *tuna,
48 const void *data);
49 int (*set_loopback)(struct phy_device *dev, bool enable);
50 };
可以看出,phy_driver重點(diǎn)是大量的函數(shù),編寫PHY驅(qū)動的主要工作就是實(shí)現(xiàn)這些函數(shù),但是不一定全部實(shí)現(xiàn),稍后會簡單分析一下Linux內(nèi)核通用PHY驅(qū)動。
1、注冊PHY驅(qū)動
phy_driver結(jié)構(gòu)體初始化完成以后,就需要向Linux內(nèi)核注冊,PHY驅(qū)動的注冊使用phy_driver_register函數(shù),注冊phy驅(qū)動時候會設(shè)置驅(qū)動的總線為mdio_bus_type,也就是MDIO總線,關(guān)于MDIO總線稍后會講解,函數(shù)原型如下:
int phy_driver_register(struct phy_driver *new_driver, struct module *owner);
函數(shù)參數(shù)和返回值含義如下:
- new_driver:需要注冊的PHY驅(qū)動。
- owner:驅(qū)動模塊所屬的PHY設(shè)備。
- 返回值:0,成功;負(fù)值,失敗。
2、連續(xù)注冊多個PHY驅(qū)動
一個廠家會生產(chǎn)多種PHY芯片,這些PHY芯片內(nèi)部差別一般不大,如果一個個的去注冊驅(qū)動將會導(dǎo)致一堆重復(fù)的驅(qū)動文件,因此Linux內(nèi)核提供了一個連續(xù)注冊多個PHY驅(qū)動的函數(shù)phy_drivers_register。首先準(zhǔn)備一個phy_driver數(shù)組,一個數(shù)組元素就表示一個PHY芯片的驅(qū)動,然后調(diào)用phy_drivers_register一次性注冊整個數(shù)組中的所有驅(qū)動,函數(shù)原型如下:
int phy_drivers_register(struct phy_driver *new_driver,
int n,
struct module *owner);
函數(shù)參數(shù)和返回值含義如下:
- new_driver:需要注冊的多個PHY驅(qū)動數(shù)組。
- n:要注冊的驅(qū)動數(shù)量。
- owner:驅(qū)動模塊所屬的PHY設(shè)備。
- 返回值:0,成功;負(fù)值,失敗。
3、卸載PHY驅(qū)動
卸載PHY驅(qū)動的話使用phy_driver_unregister函數(shù),函數(shù)原型如下:
void phy_driver_unregister(struct phy_driver *drv)
函數(shù)參數(shù)和返回值含義如下:
- drv:需要卸載的PHY驅(qū)動。
- 返回值:無。
MDIO總線
前面說了,PHY子系統(tǒng)也是遵循設(shè)備、總線、驅(qū)動模型的,設(shè)備和驅(qū)動就是phy_device和phy_driver。總線就是MDIO總線,因?yàn)镻HY芯片是通過MDIO接口來管理的,MDIO總線最主要的工作就是匹配PHY設(shè)備和PHY驅(qū)動。在文件drivers/net/phy/mdio_bus.c中有如下定義:
示例代碼52.5.4.4定義了一個名為“mdio_bus_type”的總線,這個就是MDIO總線,總線的名字為“mdio_bus”,重點(diǎn)是總線的匹配函數(shù)為mdio_bus_match。此函數(shù)內(nèi)容如下:
第5行,采用設(shè)備樹的話先嘗試使用of_driver_match_device來對設(shè)備和驅(qū)動進(jìn)行匹配,也就是檢查compatible屬性值與匹配表of_match_table里面的內(nèi)容是否一致。但是對于本章教程而言,并不是通過of_driver_match_device來完成PHY驅(qū)動和設(shè)備匹配的。
第8-9行,使用PHY驅(qū)動的匹配方法,會調(diào)用phy_bus_match函數(shù),函數(shù)源碼如下所示:
第11、12行,檢查PHY驅(qū)動有沒有提供匹配函數(shù)match_phy_device,如果有的話就直接調(diào)用PHY驅(qū)動提供的匹配函數(shù)完成與設(shè)備的匹配。
第26、27行,phy_driver里面有兩個成員變量phy_id和phy_id_mask,表示此驅(qū)動所匹配的PHY芯片ID以及ID掩碼,PHY驅(qū)動編寫人員需要給這兩個成員變量賦值。phy_device也有一個phy_id成員變量,表示此PHY芯片的ID,phy_device里面的phy_id是在注冊PHY設(shè)備的時候調(diào)用get_phy_id函數(shù)直接讀取PHY芯片內(nèi)部ID寄存器得到的!很明顯PHY驅(qū)動和PHY設(shè)備中的ID要一樣,這樣才能匹配起來。所以最后一種方法就是對比PHY驅(qū)動和PHY設(shè)備中的phy_id是否一致,這里需要與PHY驅(qū)動里面的phy_id_mask進(jìn)行與運(yùn)算,如果結(jié)果一致的話就說明驅(qū)動和設(shè)備匹配。
如果PHY設(shè)備和PHY驅(qū)動匹配,那么就使用指定的PHY驅(qū)動,如果不匹配的話就使用Linux內(nèi)核自帶的通用PHY驅(qū)動。
通用PHY驅(qū)動
前面多次提到Linux內(nèi)核已經(jīng)集成了通用PHY驅(qū)動,通用PHY驅(qū)動名字為“Generic PHY”,打開drivers/net/phy/phy_device.c,找到phy_init函數(shù),內(nèi)容如下:
phy_init是整個PHY子系統(tǒng)的入口函數(shù),第11行和15行都會調(diào)用phy_drivers_register函數(shù)向內(nèi)核直接注冊一個通用PHY驅(qū)動:genphy_c45_driver和genphy_driver;genphy_c45_driver為10G網(wǎng)絡(luò),genphy_driver為10/100/1000M網(wǎng)絡(luò)。它們都是通用PHY驅(qū)動,也就是說Linux系統(tǒng)啟動以后默認(rèn)就已經(jīng)存在了通用PHY驅(qū)動。
genphy_c45_driver和genphy_driver這兩個結(jié)構(gòu)體 , 它們定義分別在drivers/net/phy/phy_device.c和drivers/net/phy/phy-c46.c這兩個文件中,內(nèi)容如下所示:
genphy_c45_driver為10G的PHY驅(qū)動,名字為“Generic Clause 45 PHY”,genphy_driver為10/100/1000M的PHY驅(qū)動,名字為“Generic PHY”。注意,很多專用PHY芯片的驅(qū)動程序中也會用到通用PHY驅(qū)動的一些函數(shù)。
RTL8211F PHY驅(qū)動
這里因?yàn)槲沂亲钚碌拈_發(fā)板,是V1.3版本之后的,所以這一小節(jié)就直接跳過了。
YT8511 PHY驅(qū)動
如果是使用V1.3版本以后的核心板,網(wǎng)絡(luò)的PHY芯片為YT8511芯片,此芯片為國產(chǎn)的網(wǎng)絡(luò)芯片,在Linux內(nèi)核沒有對應(yīng)的PHY驅(qū)動,但是廠家會提供linux下的PHY驅(qū)動。不過這個原廠驅(qū)動有點(diǎn)小問題,那就是沒有使能YT8511的125M時鐘輸出,導(dǎo)致驅(qū)動工作不正常。正點(diǎn)原子已經(jīng)修改好了這個小問題。
在之前的移植Linux驅(qū)動的時候已經(jīng)詳細(xì)講解過了如何在Linux內(nèi)核里面添加YT8511驅(qū)動,但是為了教程的連貫性,這里再講解一遍!
將驅(qū)動文件添加到Linux內(nèi)核
首先需要將28_YT8511C_PHY目錄里面的linux驅(qū)動文件拷貝到Linux內(nèi)核源碼相應(yīng)的目錄中。這里一共有motorcomm.c和motorcomm_phy.h這兩個文件,一個是驅(qū)動C文件,一個是頭文件。把motorcomm.c拷貝到drivers/net/phy下,把motorcomm_phy.h拷貝到include/linux目錄下。
這樣就把YT8511的驅(qū)動文件和頭文件添加到了Linux內(nèi)核源碼中。
修改Makefile
前面已經(jīng)將YT8511的驅(qū)動文件添加到Linux內(nèi)核源碼里面了,但是還不能編譯,因?yàn)檫€沒有添加到Makefile里面。在phy驅(qū)動的源碼目錄drivers/net/phy,打開Makefile文件,添
加如下代碼:
obj-$(CONFIG_MOTORCOMM_PHY) += motorcomm.o |
添加結(jié)果如下所示:
修改Kconfig
最后修改一下drivers/net/phy/Kconfig這個文件,這樣以后就可以通過圖形化界面來使能或者禁止YT8511驅(qū)動了。在Kconfig里面添加如下內(nèi)容:
config MOTORCOMM_PHY tristate "Motorcomm PHYs" ---help--- Supports the YT8010, YT8510, YT8511, YT8512 PHYs. |
添加結(jié)果如下所示:
使能YT8511C PHY驅(qū)動
最后就要進(jìn)入內(nèi)核的圖形界面去使能YT8511驅(qū)動,配置路徑如下:
-> Device Drivers -> Network device support (NETDEVICES [=y]) -> PHY Device support and infrastructure (PHYLIB [=y]) -> <*> Motorcomm PHYs //選中 |
配置界面如圖所示:
使能以后就可以重新編譯Linux內(nèi)核,然后使用新的Linux內(nèi)核啟動開發(fā)板。
YT8511C驅(qū)動源碼簡析
打開motorcomm.c文件,找到Y(jié)T8511驅(qū)動結(jié)構(gòu)體,代碼如下所示:
第21行,PHY_ID_YT8511是一個宏,定義為0x0000010a,是PHY ID。
第22行,驅(qū)動名字為"YT8511 Gigabit Ethernet",系統(tǒng)啟動過程中,加載網(wǎng)絡(luò)設(shè)備驅(qū)動的時候就會提示電氣PHY驅(qū)動文字為"YT8511 Gigabit Ethernet"。
第23行,PHY 的ID掩碼,MOTORCOMM_PHY_ID_MASK是一個宏,定義為0x00000fff,也就是前12位有效,在進(jìn)行匹配的時候只需要比較前12位。
最后,第72行使用module_phy_driver(本質(zhì)是一個宏)來完成ytphy_drvs的注冊,以genphy開頭的函數(shù)都是通用PHY驅(qū)動。
網(wǎng)絡(luò)驅(qū)動實(shí)驗(yàn)測試
RTL8211F PHY驅(qū)動測試
這里因?yàn)闆]有這個PHY芯片,所以直接跳過。
YT8511C PHY驅(qū)動測試
之前已經(jīng)把YT8511C PHY驅(qū)動加進(jìn)入內(nèi)核里,運(yùn)行以下命令重新編譯內(nèi)核:
make uImage LOADADDR=0XC2000040 -j8 |
使用新的uImage內(nèi)核,重新啟動開發(fā)板,當(dāng)系統(tǒng)驅(qū)動以后就打印出當(dāng)前PHY驅(qū)動名字為“YT8511 Gigabit Ethernet”,如下圖所示:
從上圖可以知道,此時PHY網(wǎng)絡(luò)使用的是“YT8511 Gigabit Ethernet”。由于教程中開發(fā)板連接到了千M路由器上,因此此時的網(wǎng)速為1Gpbs,也就是1000M網(wǎng)絡(luò)。測試網(wǎng)絡(luò)就很簡單了,一直都是用網(wǎng)絡(luò)在掛載文件系統(tǒng)的,一直在測試中。
千兆以太網(wǎng)網(wǎng)速測試
注意:這里要連接千兆路由器或者千兆交換機(jī),沒有這兩個設(shè)備可以開發(fā)板和電腦直接連接。
STM32MP1網(wǎng)絡(luò)接口為千兆網(wǎng)絡(luò),理論最大通信速率可達(dá)1000mb/s,RTL8211和YT8511也是千M PHY芯片。本小節(jié)就來測試下這個網(wǎng)口的速率,此時使用開發(fā)板和PC機(jī)直連方式,使用千兆網(wǎng)線,比如CAT-5E類網(wǎng)線或CAT-6類網(wǎng)線。
Linux下網(wǎng)絡(luò)速率測試可以使用iperf3工具,iperf3是一個網(wǎng)絡(luò)性能測試工具,可以測試TCP和UDP的帶寬質(zhì)量、測量TCP最大帶寬、報(bào)告帶寬、延遲抖動和數(shù)據(jù)丟包等;這里僅僅只是使用iperf3工具測試開發(fā)板上網(wǎng)口的帶寬,其它的一些參數(shù)特性就不進(jìn)行詳細(xì)測試了。
iperf3的測試方法需要有一臺主機(jī)作為服務(wù)器端,另一臺主機(jī)作為客戶端,客戶端向服務(wù)器端發(fā)送數(shù)據(jù),這里可以將Ubuntu系統(tǒng)作為服務(wù)器端,而開發(fā)板作為客戶端進(jìn)行測試。
使能iperf3命令
跳轉(zhuǎn)到buildroot源碼目錄下,啟動到圖形配置選項(xiàng),按照以下配置去是能iperf3命令:
-> Target packages -> Networking applications -> [*] iperf3 //選中 |
配置如下圖所示:
保存配置項(xiàng),重新編譯新的文件系統(tǒng),使用新的文件系統(tǒng)啟動開發(fā)板。
Ubuntu下安裝iperf3工具
前面已經(jīng)將iperf3工具移植到開發(fā)板了,接下來需要在Ubuntu下也安裝iperf3 工具,因?yàn)閁buntu系統(tǒng)默認(rèn)沒有安裝這個工具,在Ubuntu系統(tǒng)中執(zhí)行下面這條命令安裝iperf3:
sudo apt-get install iperf3 |
安裝完成之后就可以進(jìn)行測試了!
測試
在Ubuntu系統(tǒng)中執(zhí)行下面這條命令,將Ubuntu系統(tǒng)作為iperf3的服務(wù)器,如下所示:
iperf3 -s |
命令執(zhí)行如下圖所示:
-s 表示將其作為服務(wù)器端。
接下來需要在開發(fā)板終端執(zhí)行下面這條命令,將開發(fā)板作為客戶端,并進(jìn)行測試:
iperf3 -c 192.168.1.250 |
-c 選項(xiàng)表示將其作為客戶端,后面緊跟著的就是服務(wù)器主機(jī)的IP地址,在這里也就是Ubuntu系統(tǒng)的IP地址,所以這個IP地址根據(jù)自己Ubuntu系統(tǒng)的IP進(jìn)行填寫即可,測試結(jié)果如下圖所示:
上圖中執(zhí)行命令共打印出了10次帶寬測試報(bào)告,每一秒鐘報(bào)告一次帶寬,耗費(fèi)時間為10秒鐘。最終測試出來發(fā)送和接收帶寬分別為935M和934M,基本接近1000M的帶寬,說明千M網(wǎng)絡(luò)工作正常。
總結(jié)
這一章節(jié)的內(nèi)容,基本就是底層的PHY芯片+STM32MP1內(nèi)置的MAC芯片+RJ45座構(gòu)成的完整嵌入式網(wǎng)絡(luò)接口硬件。然后學(xué)習(xí)了PHY芯片的一些內(nèi)容,在Linux內(nèi)核中的網(wǎng)絡(luò)通訊相關(guān)知識。
其實(shí)這一部分知識可以去看我之前的lwIP協(xié)議的學(xué)習(xí),那邊就是完整的一個在STM32單片機(jī)上的網(wǎng)絡(luò)硬件+協(xié)議架構(gòu),會更加的詳細(xì)和完整,當(dāng)然了通訊協(xié)議的實(shí)現(xiàn)一個是lwIP,一個是在Linux內(nèi)核。文章來源:http://www.zghlxwxcb.cn/news/detail-796309.html
最后完成了一下網(wǎng)絡(luò)接口的測試,其實(shí)這一部分沒啥好多說的,在當(dāng)時移植Linux內(nèi)核的時候就已經(jīng)完成了網(wǎng)絡(luò)的通訊移植,之后所有的linux驅(qū)動的測試都是掛載在nfs和tfts之上給開發(fā)板加載的,所以肯定是可以通訊的,主要是通過iperf3進(jìn)行一下網(wǎng)速的測試。文章來源地址http://www.zghlxwxcb.cn/news/detail-796309.html
到了這里,關(guān)于正點(diǎn)原子嵌入式linux驅(qū)動開發(fā)——Linux 網(wǎng)絡(luò)設(shè)備驅(qū)動的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!