rtl8201以太网卡调试

参考博客: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_registerphy_device_release,driver接口为phy_driver_registerphy_driver_unregister,bus接口为mdiobus_registermdiobus_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工具这些都是在后面应用的时候需要用到的,等我自己深入研究后再进行学习总结。