轉自:https://blog.csdn.net/wenjin359/article/details/82893122
參考博客:https://blog.csdn.net/zpzyf/article/details/52187279
參考博客:https://www.jianshu.com/p/77bb0ba1768c
一、概述
MII:支持10兆和100兆的操作,一個接口由14根線組成,它的支持還是比較靈活的,但是有一個缺點是因為它一個端口用的信號線太多。
RMII:是簡化的MII接口,在數據的收發上它比MII接口少了一倍的信號線,所以它一般要求是50兆的總線時鍾,是MII接口時鍾的兩倍。
SMII:是由思科提出的一種媒體接口,它有比RMII更少的信號線數目,S表示串行的意思。
GMII:是千兆網的MII接口,這個也有相應的RGMII接口,表示簡化了的GMII接口。GMII采用8位接口數據,工作時鍾125MHz,因此傳輸速率可達1000Mbps。同時兼容MII所規定的10/100 Mbps工作方式。RGMII均采用4位數據接口,工作時鍾125MHz,並且在上升沿和下降沿同時傳輸數據,因此傳輸速率可達1000Mbps。同時兼容MII所規定的10/100 Mbps工作方式,支持傳輸速率:10M/100M/1000Mb/s ,其對應clk 信號分別為:2.5MHz/25MHz/125MHz。
MII接口
RMII接口
二、osi網絡模型
(1)物理層
物理層所處理的數據單位是比特(bit),物理層向上為數據鏈路層提供物理鏈路,實現透明的比特流(bit stream)傳輸服務,物理層向下與物理媒體相連,要確定連接物理媒體的網絡接口的機械、電氣、功能和過程方面的特性。
(2)數據鏈路層
數據鏈路層負責在單個鏈路上的結點間傳送以幀(frame)為PDU的數據,在不太可靠的物理鏈路上實現可靠的數據傳輸。數據鏈路層的主要功能包括:建立、維持和釋放數據鏈路的連接,鏈路的訪問控制,流量控制和差錯控制。
(3)網絡層
網絡層傳送的PDU稱為分組或包(packet),在物理網絡間傳送分組,負責將源端主機的報文通過中間轉發結點傳送到目的端。網絡層是通信子網的最高層,為主機提供虛電路和數據報兩種方式的服務。網絡層主要負責分組轉發和路由選擇,根據路由表把分組逐跳地由源站傳送到目的站,並能適應網絡的負載及拓撲結構的變化,動態地更新路由表。
(4)傳輸層
傳輸層傳輸的PDU稱為報文(message),傳輸層為源結點和目的結點的用戶進程之間提供端到端的可靠的傳輸服務。端到端的傳輸指的是源結點和目的結點的兩個傳輸層實體之間,不涉及路由器等中間結點。為了保證可靠的傳輸服務,傳輸層具備以下一些功能:面向連接、流量控制與擁塞控制、差錯控制相網絡服務質量的選擇等。
(5)會話層
會話層在傳輸層服務的基礎上增加控制會話的機制,建立、組織和協調應用進程之間的交互過程。會話層提供的會話服務種類包括雙工、半雙工和單工方式。會話管理的一種方式是令牌管理,只有令牌持有者才能執行某種操作。會話層提供會話的同步控制,當出現故障時,會話活動在故障點之前的同步點進行重復,而不必從頭開始。
(6)表示層
表示層定義用戶或應用程序之間交換數據的格式,提供數據表示之間的轉換服務,保證傳輸的信息到達目的端后意義不變。
(7)應用層
應用層直接面向用戶應用,為用戶提供對各種網絡資源的方便的訪問服務。
網際層協議:包括:IP協議、ICMP協議、ARP協議、RARP協議。
傳輸層協議:TCP協議、UDP協議。
應用層協議:FTP、Telnet、SMTP、HTTP、RIP、NFS、DNS
IP地址位於OSI模型的第三層網絡層,MAC地址位於OSI模型的第二層數據鏈鏈路層.
MAC地址(物理地址)是設備生產廠家建在硬件內部或網卡上的,它也是唯一標識一個節點的. 該地址在OSI模型的數據鏈路層. 是一個48位(6字節)的二進制串,通常寫成16進制數,以冒號分隔,如00:E0:FC:10:20:0C:8A. 該地址由IEEE負責分配,通常分為兩個部分:地址的前3個字節代表廠商代碼,后三個字節由廠商自行分配.
IP地址又稱邏輯地址,在OSI模型的網絡層定義,並且依賴於網絡層中的網絡協議.不同網絡層協議對應邏輯地址的格式也不同.IP協議對應IP地址.這個地址是32位的二進制數,包含兩個部分:網絡部分和主機(節點)部分
如果網絡設備已經有了一個唯一的物理地址(MAC地址),為什么還需要IP地址呢?
首先,每個設備支持不同的物理層地址;IP地址使得連接到光纖,令牌環和串行線的設備不必取得相同的物理地址,就可以使用IP.即,IP地址是獨立於數據鏈路層的.
其次, 物理地址是按廠商設備,而不是擁有它的組織來編號的.將高效的路由方案建立在設備制造商基礎上,而不是網絡所處的位置之上,這種方案是不可行的.IP地址的分配是基於網絡拓撲,而不是誰制造了設備.
最后,也是最重要的,當存在一個附加層的地址尋址時,設備更易於移動和維修.如果一個網卡壞了,可以被更換,而不需取得一個新的IP地址.如果一個IP節點從一個網絡移到另一個網絡,可以給它一個新的IP地址,而無須換一個新的網卡。
DNS則是典型的應用層的協議了!
DNS體系及運行原理,互聯網中的主機是使用IP地址來標識的,但是即便是32位的IPv4地址轉化為十進制后也將是4組0-255的數字,
而記住這一大堆數字組合對普通人 來講是件困難的事情,所以我們給主機起一個便於記憶的名字,
並借助DNS將其解析成IP地址,這樣只需在URL欄中輸入某台主機的名字,就可以訪問到它 了。
當然DNS也承擔着將IP地址解析成域名的任務,也就是反向解析
三、rtl8201調試
rtl8201應該是很早的一款網卡芯片了,用的很普遍。由於以前沒有弄過這部份網絡部份,也沒有看過相關。相當於是從頭開始。先把以上mii的了解了下,再來看driver/net部份的phy與ethernet。如上,phy位於物理層,由mac(數據鏈路層)來與其相連。
調試平台是全志的A64,來看A64 mac接口部份:
可以看到A64 mac接口支持RMII與RGMII二種模式,也就是百兆與千兆。
再看rtl8201的datasheet說明,支持MII與RMII二種模式,所以A64mac與rtl8201的連接方式應該是rmii模式。
a64與rtl8201相連原理圖如下所示:
mdio/mdc是manager控制腳;tx/rx是差分傳輸腳。
配置A64 mac接口管腳、功能與電源,上電量相關的信號:25M clk、3.3v供電、1v供電均正常、rst控制信號也正常、RXDV也上拉成了rmii模式。
軟件上把realtek phy驅動加上:
修改驅動添加支持rtl8201
diff -Npur a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
--- a/drivers/net/phy/realtek.c 2016-05-12 10:32:15.000000000 +0800
+++ b/drivers/net/phy/realtek.c 2016-08-15 13:44:43.420720361 +0800
@@ -16,9 +16,16 @@
#include <linux/phy.h>
#include <linux/module.h>
-#define RTL821x_PHYSR 0x11
-#define RTL821x_PHYSR_DUPLEX 0x2000
-#define RTL821x_PHYSR_SPEED 0xc000
+//#define RTL821x_PHYSR 0x11
+//#define RTL821x_PHYSR_DUPLEX 0x2000
+//#define RTL821x_PHYSR_SPEED 0xc000
+/* page 0 register 30 - interrupt indicators and SNR display register */
+#define RTL8201F_ISR 0x1e
+/* page 0 register 31 - page select register */
+#define RTL8201F_PSR 0x1f
+/* page 7 register 19 - interrupt, WOL enable, and LEDs function register */
+#define RTL8201F_IER 0x13
+
#define RTL821x_INER 0x12
#define RTL821x_INER_INIT 0x6400
#define RTL821x_INSR 0x13
@@ -29,6 +36,15 @@ MODULE_DESCRIPTION("Realtek PHY driver")
MODULE_AUTHOR("Johnson Leung");
MODULE_LICENSE("GPL");
+static int rtl8201f_ack_interrupt(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_read(phydev, RTL8201F_ISR);
+
+ return (err < 0) ? err : 0;
+}
+
static int rtl821x_ack_interrupt(struct phy_device *phydev)
{
int err;
@@ -38,13 +54,29 @@ static int rtl821x_ack_interrupt(struct
return (err < 0) ? err : 0;
}
-static int rtl8211b_config_intr(struct phy_device *phydev)
+static int rtl8201f_config_intr(struct phy_device *phydev)
{
int err;
+ phy_write(phydev, RTL8201F_PSR, 0x0007); /* select page 7 */
+
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
- err = phy_write(phydev, RTL821x_INER,
- RTL821x_INER_INIT);
+ err = phy_write(phydev, RTL8201F_IER, 0x3800 |
+ phy_read(phydev, RTL8201F_IER));
+ else
+ err = phy_write(phydev, RTL8201F_IER, ~0x3800 &
+ phy_read(phydev, RTL8201F_IER));
+
+ phy_write(phydev, RTL8201F_PSR, 0x0000); /* back to page 0 */
+
+ return err;
+}
+
+static int rtl8211b_config_intr(struct phy_device *phydev)
+{
+ int err;
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ err = phy_write(phydev, RTL821x_INER, RTL821x_INER_INIT);
else
err = phy_write(phydev, RTL821x_INER, 0);
@@ -64,6 +96,21 @@ static int rtl8211e_config_intr(struct p
return err;
}
+/* RTL8201F */
+static struct phy_driver rtl8201f_driver = {
+ .phy_id = 0x001cc816,
+ .name = "RTL8201F 10/100Mbps Ethernet",
+ .phy_id_mask = 0x001fffff,
+ .features = PHY_BASIC_FEATURES,
+ .flags = PHY_HAS_INTERRUPT,
+ .config_aneg = &genphy_config_aneg,
+ .read_status = &genphy_read_status,
+ .ack_interrupt = &rtl8201f_ack_interrupt,
+ .config_intr = &rtl8201f_config_intr,
+ .driver = { .owner = THIS_MODULE,},
+};
+
+
/* RTL8211B */
static struct phy_driver rtl8211b_driver = {
.phy_id = 0x001cc912,
@@ -96,16 +143,24 @@ static struct phy_driver rtl8211e_driver
static int __init realtek_init(void)
{
- int ret;
+// int ret;
- ret = phy_driver_register(&rtl8211b_driver);
- if (ret < 0)
+// ret = phy_driver_register(&rtl8211b_driver);
+// if (ret < 0)
+ if(phy_driver_register(&rtl8201f_driver) < 0)
+ {
+ return -ENODEV;
+ }
+ if(phy_driver_register(&rtl8211b_driver) < 0)
+ {
return -ENODEV;
+ }
return phy_driver_register(&rtl8211e_driver);
}
static void __exit realtek_exit(void)
{
+ phy_driver_unregister(&rtl8201f_driver);
phy_driver_unregister(&rtl8211b_driver);
phy_driver_unregister(&rtl8211e_driver);
}
@@ -114,6 +169,7 @@ module_init(realtek_init);
module_exit(realtek_exit);
static struct mdio_device_id __maybe_unused realtek_tbl[] = {
+ { 0x001cc816, 0x001fffff },
{ 0x001cc912, 0x001fffff },
{ 0x001cc915, 0x001fffff },
{ }
但是不通,一直讀不到phy_id。這里說明下,phy device與mac device是分開的,2者通過mdio bus來通信,硬件上的體現就是mdio mdc這二個腳。這里有點像camera驅動,mdio/mdc相當於camera的i2c,通過i2c讀到地址(phy_id),phy與mac就關聯起來,才能進行后續的操作。
再細看rtl8201 datasheet:
在8201F時dvdd 1.0v的供電是不需要的,而且avdd10out與dvdd10out是輸出電平,也就是說上述原理圖中有誤,把這里當成了輸入電平接到了pmu ldo上。實際這里應該是接0.1uf的電容到地的。跟硬件反饋這個問題,硬件修改,發現在sunxi_mac_reset(),init時復位mac准備通信時超時出錯,加大超時時間,再試ok。(當然軟件部份這里還有其它影藏問題,是由於不同項目兼容引起的,但與實際原始sdk驅動無關)
本身來說,rtl8201的調試可以說是很簡單的,但是由於以前一直沒接解過這部份,可以說是一點認識都沒有,這里用了2天時間調試並記錄下調試過程。如果是單純的調通一個現成使用過的應該不難,但是僅僅是調通應該說是很low了,看了下這里的driver架構,相當多。就phy與ethernet部份還有mdio bus,這里沒去細研究,基本phy_id讀到了匹配了就大部份ok了。
識別phy的過程:
1. 在網口驅動的probe中,調用mdiobus_register;
2. 在mdiobus_register函數中,會對從0到PHY_MAX_ADDR(一般是31)依次調用mdiobus_scan;
3. 在mdiobus_scan中會調用get_phy_device,如果返回成功,則調用phy_device_register;
4. 在get_phy_device中,會調用get_phy_id來讀取對應地址上的phy_id,然后如果滿足((phy_id & 0x1fffffff) == 0x1fffffff),則認識該phy不存在。
5. 然后在port的probe中會調用phy_connect來連接phy;
6. 在phy_connect中,會調用bus_find_device_by_name來查找對應的phy是否存在。存在則connect_direct。
在/sys/bus/mdio_bus/devices/下有當前內核掃描到的所有PHY。
以下為https://www.jianshu.com/p/77bb0ba1768c內容:
PHY芯片為OSI的最底層-物理層(Physical Layer),通過MII與數據鏈路層的MAC芯片相連,對於MAC與PHY之間的一些知識可以查看Mac與Phy組成原理的簡單分析,這篇文章進行熟悉。
PHY與MAC整體的連接框架:
連接框架
PHY的硬件系統算是比較復雜的,PHY與MAC相連,MAC與CPU相通,PHY與MAC通過MII和MDIO/MDC相連,MII是走網絡數據的,MDIO/MDC是用來與PHY的寄存器通訊的,對PHY進行配置。類似的SWITCH芯片一般也有兩種接口,MII用來走網絡數據,SPI用來設置SWITCH的寄存器。
跟以前分析I2C/SPI的驅動一樣,分為控制器驅動和設備器驅動。
1、控制器驅動
控制器的驅動使用的一樣是platform總線的連接方式,在arch或dts下面進行對phy的platform進行add,platform_driver的register一般也放在/driver/net/phy/下面,device和driver的name匹配后就會執行platform_driver結構體所對應的probe接口,都大同小異,如下:
static struct platform_driver pfe_platform_driver = {
.probe = pfe_platform_probe,
.remove = pfe_platform_remove,
.driver = {
.name = "pfe",
#ifdef CONFIG_PM
.pm = &pfe_platform_pm_ops,
#endif
},
};
static int __init pfe_module_init(void)
{
return platform_driver_register(&pfe_platform_driver);
}
static void __exit pfe_module_exit(void)
{
platform_driver_unregister(&pfe_platform_driver);
}
MODULE_LICENSE("GPL");
module_init(pfe_module_init);
module_exit(pfe_module_exit);
因為phy與cpu的通訊配置使用的是MII、MDIO/MDC來進行傳輸控制的,所以probe函數里面如要對MII總線進行配置,最后調用mdiobus_register()或of_mdiobus_register()對mdio_bus進行注冊。
of_mdiobus_register()函數位於drivers/of/of_mdio.c中,該函數最后還是會調用mdiobus_register()函數,mdiobus_register()函數位於drivers\net\phy\mdio_bus.c中,通過一層一層的往下撥,會有如下結構:
‐‐> mdiobus_register
‐‐> device_register
‐‐> mdiobus_scan
‐‐> get_phy_device
‐‐> get_phy_id // 讀寄存器
‐‐> phy_device_create // 創建phy設備
‐‐> INIT_DELAYED_WORK(&dev‐>state_queue, phy_state_machine); //初始化狀態機
這邊就是控制器和設備器的交接了,創建phy設備初始化PHY狀態機,接下去就看設備器的驅動。
2、設備器驅動
設備器的驅動也是三個方面device、driver、bus。PHY的device接口為phy_device_register和phy_device_release,driver接口為phy_driver_register和phy_driver_unregister,bus接口為mdiobus_register和mdiobus_unregister。
這樣一分析感覺就跟清晰了,有關PHY驅動的內容都位於/drivers/net/phy中。
phy的設備驅動不像i2c/spi有一個board_info函數進行添加設備,而是直接讀取phy中的寄存器,根據IEEE的規定PHY芯片的前16個寄存器的內容必須是固定的,如下:
其中寄存器0x02和0x03即設備ID的高低位,每一種型號的PHY有其一串ID號,這我們查看對於的手冊即可知道。
在設備的driver中使用MODULE_DEVICE_TABLE將對應的設備ID添加進入,他的效果可以理解為board_info函數所實現的內容,如broadcom的table:
static struct mdio_device_id __maybe_unused broadcom_tbl[] = {
{ PHY_ID_BCM5411, 0xfffffff0 },
{ PHY_ID_BCM5421, 0xfffffff0 },
{ PHY_ID_BCM54210S, 0xfffffff0 },
{ PHY_ID_BCM5461, 0xfffffff0 },
{ PHY_ID_BCM5464, 0xfffffff0 },
{ PHY_ID_BCM5482, 0xfffffff0 },
{ PHY_ID_BCM5482, 0xfffffff0 },
{ PHY_ID_BCM50610, 0xfffffff0 },
{ PHY_ID_BCM50610M, 0xfffffff0 },
{ PHY_ID_BCM57780, 0xfffffff0 },
{ PHY_ID_BCMAC131, 0xfffffff0 },
{ PHY_ID_BCM5241, 0xfffffff0 },
{ }
};
MODULE_DEVICE_TABLE(mdio, broadcom_tbl);
phy_driver的register大概為如下一個過程,進行簡單分析:
drivers/net/phy/phy_device.c
phy_init
‐‐> mdio_bus_init 注冊mdio總線
‐‐> class_register(&mdio_bus_class);
‐‐> bus_register(&mdio_bus_type);
‐‐> phy_driver_register(&genphy_driver);
在mdio總線注冊的時候會調用mdio_bus_match,如果match函數找到設備則會進行設備驅動的注冊,match函數位於/drivers/net/phy/mdio_bus.c中,如下,進行phy_id的打印。
static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
struct phy_device *phydev = to_phy_device(dev);
struct phy_driver *phydrv = to_phy_driver(drv);
printk("phydev->phy_id:%x ",phydev->phy_id);
printk("phydrv->phy_id:%x \n",phydrv->phy_id);
return ((phydrv->phy_id & phydrv->phy_id_mask) ==
(phydev->phy_id & phydrv->phy_id_mask));
}
由log可以看出,設備dev的ID為600d8595,然后就去查找對應的驅動drv的ID,知道找到600d8595則進入probe函數。
[ 23.306611] phydev->phy_id:600d8595 phydrv->phy_id:ffffffff
[ 23.312150] phydev->phy_id:600d8595 phydrv->phy_id:ffffffff
[ 23.317731] phydev->phy_id:600d8595 phydrv->phy_id:4dd072
[ 23.323084] phydev->phy_id:600d8595 phydrv->phy_id:4dd033
[ 23.328461] phydev->phy_id:600d8595 phydrv->phy_id:206070
[ 23.333812] phydev->phy_id:600d8595 phydrv->phy_id:2060e0
[ 23.339182] phydev->phy_id:600d8595 phydrv->phy_id:600d8595
這樣找到ID后就會進行設備驅動的注冊,對應的phy_driver_register()函數才能返回成功,才會執行phy_driver結構體下面的內容,如下:
static struct phy_driver bcm54210s_driver = {
.phy_id = PHY_ID_BCM54210S,
.phy_id_mask = 0xfffffff0,
.name = "Broadcom BCM54210S",
.features = PHY_GBIT_FEATURES |
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
.config_init = bcm54210s_config_init,
.config_aneg = bcm54210s_config_aneg,
.read_status = bcm54210s_read_status,
.ack_interrupt = bcm54xx_ack_interrupt,
.config_intr = bcm54xx_config_intr,
.driver = { .owner = THIS_MODULE },
};
里面對phy的寄存器等進行初始化配置,這邊對PHY的驅動進行簡單的介紹,關於PHY的內容還有好多,比如:PHY狀態機、ethtool工具這些都是在后面應用的時候需要用到的,等我自己深入研究后再進行學習總結。
---------------------
作者:Andy_0755
來源:CSDN
原文:https://blog.csdn.net/wenjin359/article/details/82893122
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!