国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

正點(diǎn)原子嵌入式linux驅(qū)動開發(fā)——Linux 網(wǎng)絡(luò)設(shè)備驅(qū)動

這篇具有很好參考價(jià)值的文章主要介紹了正點(diǎn)原子嵌入式linux驅(qū)動開發(fā)——Linux 網(wǎng)絡(luò)設(shè)備驅(qū)動。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

網(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芯片的連接如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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)如下:

  1. 內(nèi)部MAC外設(shè)會有專用的加速模塊,比如專用的DMA,加速網(wǎng)速數(shù)據(jù)的處理。
  2. 網(wǎng)速快,可以支持10/100/1000M網(wǎng)速。
  3. 外接PHY可選擇性多,成本低。

內(nèi)部的MAC外設(shè)會通過相應(yīng)的接口來連接外部PHY芯片,根據(jù)數(shù)據(jù)傳輸模式不同,大致
可以分為以下兩類

  1. MII/RMII 接口:支持10Mbit/s和100Mbit/s數(shù)據(jù)傳輸模式;
  2. 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芯片的連接如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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芯片,連接示意圖如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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芯片的示意圖如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

  • 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。信號定義如下:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

  • 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接口定義如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

關(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座,如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

RJ45座要與PHY芯片連接在一起,但是中間需要一個網(wǎng)絡(luò)變壓器,網(wǎng)絡(luò)編譯器用于隔離以及濾波等,網(wǎng)絡(luò)變壓器也是一個芯片,外形一般如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

但是現(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ò)接口硬件,如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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è)主要特性如下:

  1. 、支持全工和半雙工操作。
  2. 全雙工流控制操作(IEEE 802.3X暫停包和優(yōu)先級流控制)。
  3. 報(bào)頭和幀起始數(shù)據(jù)(SFD)在發(fā)送模式下自動插入、在接收中自動刪除。
  4. 可逐幀控制CRC和pad自動生成。
  5. 可編程數(shù)據(jù)包長度,支持標(biāo)準(zhǔn)以太網(wǎng)數(shù)據(jù)包或高達(dá)16KB的巨型以太網(wǎng)數(shù)據(jù)包。
  6. 可編程數(shù)據(jù)包間隙。
  7. 兩組 FIFO:一個具有可編程閾值功能的4096字節(jié)發(fā)送FIFO和一個具有可配置閾值功能的4096字節(jié)接收FIFO。
  8. ……

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ī)定,如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

關(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功能框圖如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

節(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,如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

自動協(xié)商

RTL8211F可以開始啟動協(xié)商功能,將PHYSR寄存器的bit12置1即可使能自動協(xié)商,如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

RGMII接口電平支持

RTL8211F支持RGMII接口,可以通過CFG_EXT、CFG_LDO0和CFG_LDO1這三個引腳去配置RGMII的電平信號,配置方法如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

正點(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 寄存器各位如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

之前說的配置PHY芯片,重點(diǎn)就是配置BMCR寄存器。

接下來看一下BMSR(Basic Mode Status Register)寄存器,地址為1。此寄存器為PHY的狀態(tài)寄存器,通過此寄存器可以獲取到PHY芯片的工作狀態(tài),BMSR寄存器各位如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

從上圖中可以看出,不管什么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,組成如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

RTL8211F的ID寄存器2如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

ID 寄存器3如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

接著來看一下RTL8211F的GBCR(1000Base-T Control Register)寄存器,寄存器地址為0x9;該寄存器定義了1000Base-T功能的相關(guān)控制位,寄存器描述信息如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

關(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這三個引腳,配置如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

正點(diǎn)原子的STM32MP157開發(fā)板上的YT8511的地址為0X00。

低功率模式

YT8511C的低功率模式是由RXD3引腳控制,配置如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

從上圖中可以看出來,RXD3為低電平的時候YT8511C進(jìn)入低功率模式,RXD3為高電平的話YT8511C芯片就為正常功率模式。正點(diǎn)原子開發(fā)板RXD3連接高電平,所以YT8511C芯片為正常功率模式。

工作模式

YT8511C的LED_1000引腳用來控制工作模式,配置如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

從上圖中可以看出來,LED_1000為低電平的話就進(jìn)入測試模式,為高電平的話就是正常模式。正點(diǎn)原子開發(fā)板LED_1000連接高電平,所以YT8511C芯片為正常模式。

RGMII電壓配置

YT8511C的RGMII電壓是由RX_DV引腳控制,如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

從上圖可以看出如果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è)置時鐘輸入,如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

從上圖可以看出,當(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)容如下:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

繼續(xù)回到示例代碼52.3.1.1中,接著看net_device結(jié)構(gòu)體。

第1869行:if_port指定接口的端口類型,如果設(shè)備支持多端口的話就通過if_port來指定所使用的端口類型??蛇x的端口類型定義在include/uapi/linux/netdevice.h中,為一個枚舉類型,如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)容如下:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)容如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

關(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)體成員變量比較多,這里有縮減):

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)的,整個路程如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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)如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

針對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ū)如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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ū)如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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ū)如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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版本以前的核心板的原理圖,如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

在之前的學(xué)習(xí)中已經(jīng)知道了PHY地址是由RXCTL、RXC和RXD3控制的,從上圖中知道了這三個引腳為0、0和1,PHY的地址為0x1,RGMII的電平為3.3V。

V1.3版本以后的核心板

V1.3版本以后的核心板原理圖,如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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),如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)容:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

從示例代碼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)容:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

示例代碼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)體代碼如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

單片機(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)體原型如下示例代碼所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

示例代碼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)容:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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ù)的原型如下示例代碼所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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ù),如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

從上圖可以知道,通過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)是下圖中的兩行代碼:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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)代碼如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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è)備注冊。如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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)體如下所示(限于篇幅,有省略):

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

重點(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è)備,流程如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

注冊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)容如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第428行調(diào)用了stmmac_dvr_remove函數(shù)進(jìn)行卸載,此函數(shù)內(nèi)容如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)容如下:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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)容如下:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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ù)的源碼如下所示(有省略):

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)容如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)容如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)容如下(為了縮小篇幅,有省略):

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

一個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)容如下:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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中有如下定義:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

示例代碼52.5.4.4定義了一個名為“mdio_bus_type”的總線,這個就是MDIO總線,總線的名字為“mdio_bus”,重點(diǎn)是總線的匹配函數(shù)為mdio_bus_match。此函數(shù)內(nèi)容如下:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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ù)源碼如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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)容如下:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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)容如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

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é)果如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

修改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é)果如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

使能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 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

使能以后就可以重新編譯Linux內(nèi)核,然后使用新的Linux內(nèi)核啟動開發(fā)板。

YT8511C驅(qū)動源碼簡析

打開motorcomm.c文件,找到Y(jié)T8511驅(qū)動結(jié)構(gòu)體,代碼如下所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

第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”,如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

從上圖可以知道,此時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 //選中

配置如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

保存配置項(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í)行如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

-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é)果如下圖所示:

嵌入式linux 驅(qū)動開發(fā)工具,linux學(xué)習(xí),linux,驅(qū)動開發(fā),stm32,筆記,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

上圖中執(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)核。

最后完成了一下網(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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包