IMX6ULL以太网卡移植与驱动分析

IMX6ULL以太网卡移植与驱动分析

一、嵌入式以太网硬件基础知识

一般如果说一款SOC支持以太网,那么就是说SOC里集成了MAC芯片。而要想完成网络的通信不仅需要MAC还需要PHY芯片。

MAC与PHY的区别:

PHY(物理层)芯片负责将网络数据在物理层面上转换为电子信号或光信号,以便在网络中传输。它处理数据的物理传输,包括电气、光学和无线形式,并且可以处理一些基本的信号调制和解调任务。例如,以太网中的PHY芯片将数字数据转换为模拟信号以进行电气传输,或将数字数据转换为光信号以进行光纤传输。

MAC(媒体访问控制)芯片负责管理数据在网络中的传输和访问。它处理数据的逻辑传输,决定哪个设备可以发送数据、何时发送数据、如何发送数据等。例如,以太网中的MAC芯片负责实现CSMA/CD协议来协调设备之间的传输,防止数据碰撞。

简单的说就是MAC芯片是用于控制数据传输的(它的作用其实与I2C,SPI控制器差不多),而PHY的作用主要是将数据转化成物理的形式传输出去。

内置MAC与不内置MAC的区别:

内置MAC:

①、内部 MAC 外设会有专用的加速模块,比如专用的 DMA,加速网速数据的处理。

②、网速快,可以支持 10/100/1000M 网速。

③、外接 PHY 可选择性多,成本低。

硬件连接图:

不内置MAC:(如三星平台一般都是不内置的)

它们使用的芯片是MAC+PHY一体的(如:DM9000)

缺点:速度慢,成本高

硬件连接图:

由于我们是IMX6ULL的平台,内部支持了MAC,所以我们现在讨论内置MAC的连接图。

MII(Media Independent Interface)

作用:用于MAC与PHY芯片之间传输数据。

RMII(Reduced Media Independent Interface)

精简的MII(比MII少了9根线)

TX_EN**:**发送使能信号。

**TXD[1:0]****:**发送数据信号线,一共 2 根。

RXD[1:0]:接收数据信号线,一共 2 根。

CRS_DV**:**相当于 MII 接口中的 RX_DV 和 CRS 这两个信号的混合。

REF_CLK**:**参考时钟,由外部时钟源提供, 频率为 50MHz。

现在一般都不使用MII了。(IMX使用的就是RMII)

MDIO(管理数据输入输出接口)

一个简单的两线串行接口,一根 MDIO 数据线,一根 MDC 时钟线。驱动程序可以通过 MDIO 和

MDC 这两根线访问 PHY 芯片的任意一个寄存器。MDIO 接口支持多达 32 个 PHY。同一时刻

内只能对一个 PHY 进行操作,那么如何区分这 32 个 PHY 芯片呢?和 IIC 一样,使用器件地址

即可。同一 MDIO 接口下的所有 PHY 芯片,其器件地址不能冲突,必须保证唯一,具体器件

地址值要查阅相应的 PHY 数据手册。

RJ45接口

RJ45接口的作用是用于供网线插入的,一般RJ45接口与PHY芯片连接在一起,但是中间需要一个网络变压器,网络变压器用于隔离

以及滤波等,网络变压器也是一个芯片。(现在一般RJ45接口内部集成了变压器,但是我们还是得确定一下是否集成了)

最终内部集成MAC得以太网接口:

二、清楚IMX6ULL的硬件信息

MAC:

I.MX6ULL 内部自

带的 ENET 外设其实就是一个网络 MAC,支持 10/100M。实现了三层网络加速,用于加速那些

通用的网络协议,比如 IP、TCP、UDP 和 ICMP 等,为客户端应用程序提供加速服务。

PHY:

其实对于我们来说PHY才是重点,正如I2C控制器的控制器不是我们重点,而从设备才是,PHY就是MAC的从设备。

PHY 是 IEEE 802.3 规定的一个标准模块,它的前16位寄存器是指定好的,可以提供查看IEEE 802.3英文文档查看,而后16位可以由不同厂家自定义,但是前16位已经包含了基本的通信信息。所以在内核中有一种通用的PHY驱动,它虽然不能驱动厂家的特殊功能,但是基本上的通信功能是能胜任的。(不一定会成功,需要我们调试)

SR8201F

正点原子使用的就是这款PHY

PHY 地址设置:

正点原子 ALPHA 开发板 ENET1 网络的 SR8201F 上的 LED1/PHYAD1 引脚上拉,LED0/PHYDAD0 引脚下来下拉,因此 ENET1 上的 SR8201F 地址为 0X02。ENET2 网络上的SR8201F 的 LED1/PHYAD1 引脚下拉,LED0/PHYAD0 引脚上拉,因此 ENET2 上的 SR8201F

地址为 1。

SR8021F 内部寄存器:

我们说的配置 PHY 芯片,重点就是配置 BCR 寄存器

三、IMX6ULL的网卡驱动(我们默认是大概清楚了内核网络驱动框架的,可以通过看书了解)我们以驱动网卡2为例(1/2都一样)

1.确定设备树:

1.控制器驱动(imx6ull.dtsi)

此节点可以参考Documentation/devicetree/bindings/net/fsl-fec.txt

fec2: ethernet@020b4000 {compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";reg = <0x020b4000 0x4000>;interrupts = ,;clocks = <&clks IMX6UL_CLK_ENET>,<&clks IMX6UL_CLK_ENET_AHB>,<&clks IMX6UL_CLK_ENET_PTP>,<&clks IMX6UL_CLK_ENET2_REF_125M>,<&clks IMX6UL_CLK_ENET2_REF_125M>;clock-names = "ipg", "ahb", "ptp","enet_clk_ref", "enet_out";stop-mode = <&gpr 0x10 4>;fsl,num-tx-queues=<1>;fsl,num-rx-queues=<1>;fsl,magic-packet;fsl,wakeup_irq = <0>; status = "disabled";};

2.自己的设备树中补充控制器节点

其中phy相关节点部分可以参考:Documentation/devicetree/bindings/net/phy.txt

&fec2 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_enet2//MDIO与MDC两个引脚的IO复用&pinctrl_enet2_reset>;//PHY中Reset引脚的IO复用为普通GPIOphy-mode = "rmii";//我们使用的是rmii与PHY通信phy-handle = <ðphy1>;//子节点phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;//配置GPIOphy-reset-duration = <200>;//复位时间status = "okay";mdio {#address-cells = <1>;#size-cells = <0>;

/*ethphy0: ethernet-phy@2 {compatible = "ethernet-phy-ieee802.3-c22";smsc,disable-energy-detect;reg = ;};

*/ethphy1: ethernet-phy@1 {compatible = "ethernet-phy-ieee802.3-c22";smsc,disable-energy-detect;reg = <1>;//芯片地址};};

};

pinctrl_enet2: enet2grp {fsl,pins = ;};pinctrl_enet2_reset: enet2resetgrp {fsl,pins = ;};

注意:其中pinctrl_enet2_reset要放在&iomuxc_snvs节点下,因为MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO0中有SNVS,而pinctrl_enet2放在普通的&iomuxc节点下。

2.分析驱动

一、MAC控制器驱动

如何查找到控制器驱动所在文件呢?可以根据设备树控制器节点的compatible在内核源码中搜索。

grep -r “fsl,imx6ul-fec” .

最终找到:linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\drivers\net\ethernet\freescale\fec_main.c

static struct platform_driver fec_driver = {.driver = {.name = DRIVER_NAME,.pm = &fec_pm_ops,.of_match_table = fec_dt_ids,},.id_table = fec_devtype,.probe = fec_probe,.remove = fec_drv_remove,

};module_platform_driver(fec_driver);MODULE_ALIAS("platform:"DRIVER_NAME);

MODULE_LICENSE("GPL");

可以发现像i2c控制器驱动一样,它也是注册进platform总线里。

主要看看probe:fec_probe

static int

fec_probe(struct platform_device *pdev)

{struct fec_enet_private *fep;//定义私有数据对象struct fec_platform_data *pdata;struct net_device *ndev;// 定义net_device对象,网络驱动框架的核心对象.....int num_tx_qs;int num_rx_qs;/* zuozhongkai 2019/2/20 设置MX6UL_PAD_ENET1_TX_CLK和* MX6UL_PAD_ENET2_TX_CLK这两个IO的复用寄存器的SION位* 为1。*/void __iomem *IMX6U_ENET1_TX_CLK;void __iomem *IMX6U_ENET2_TX_CLK;IMX6U_ENET1_TX_CLK = ioremap(0X020E00DC, 4);writel(0X14, IMX6U_ENET1_TX_CLK);IMX6U_ENET2_TX_CLK = ioremap(0X020E00FC, 4);writel(0X14, IMX6U_ENET2_TX_CLK);fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs);/* Init network device */ndev = alloc_etherdev_mqs(sizeof(struct fec_enet_private),num_tx_qs, num_rx_qs);//分配并初始化net_device............phy_node = of_parse_phandle(np, "phy-handle", 0);//获取phy节点if (!phy_node && of_phy_is_fixed_link(np)) {ret = of_phy_register_fixed_link(np);if (ret < 0) {dev_err(&pdev->dev,"broken fixed-link specification\n");goto failed_phy;}phy_node = of_node_get(np);}fep->phy_node = phy_node;ret = of_get_phy_mode(pdev->dev.of_node);//获取phy的传输描述(使用mii、rmii,..)............fec_reset_phy(pdev);//复位phyif (fep->bufdesc_ex)fec_ptp_init(pdev);ret = fec_enet_init(ndev);//初始化net_device(包括两个ops变量的赋值),以及设置NAPI的POLLif (ret)goto failed_init;for (i = 0; i < FEC_IRQ_NUM; i++) {irq = platform_get_irq(pdev, i);if (irq < 0) {if (i)break;ret = irq;goto failed_irq;}ret = devm_request_irq(&pdev->dev, irq, fec_enet_interrupt,0, pdev->name, ndev);//设置fec_enet_interrupt很重要,发送与接收数据包都要次中断.......ret = of_property_read_u32(np, "fsl,wakeup_irq", &irq);if (!ret && irq < FEC_IRQ_NUM)fep->wake_irq = fep->irq[irq];elsefep->wake_irq = fep->irq[0];init_completion(&fep->mdio_done);ret = fec_enet_mii_init(pdev);//完成 MII/RMII 接口的初始化,主要是配置SOC 读写 PHY 内部寄存器的函数,这里面除了注册mii_bus还注册了phy设备ret = register_netdev(ndev);//注册net_device............return ret;

}

我们先分析一下fec_enet_init:

1.fec_enet_init:(主要作用:初始化net_device,注册NAPI)

static int fec_enet_init(struct net_device *ndev)

{struct fec_enet_private *fep = netdev_priv(ndev);//获取私有数据struct fec_enet_priv_tx_q *txq;struct fec_enet_priv_rx_q *rxq;struct bufdesc *cbd_base;dma_addr_t bd_dma;..........fec_enet_alloc_queue(ndev);//分配请求队列/* Allocate memory for buffer descriptors. */cbd_base = dma_alloc_coherent(NULL, bd_size, &bd_dma,GFP_KERNEL);//分配dma缓冲区if (!cbd_base) {return -ENOMEM;}memset(cbd_base, 0, bd_size);//清空缓冲区/* Get the Ethernet address */fec_get_mac(ndev);//得到mac控制器地址/* make sure MAC we just acquired is programmed into the hw */fec_set_mac_address(ndev, NULL);/* Set receive and transmit descriptor base. *///设置发送与接收描述符地址for (i = 0; i < fep->num_rx_queues; i++) {rxq = fep->rx_queue[i];rxq->index = i;rxq->rx_bd_base = (struct bufdesc *)cbd_base;rxq->bd_dma = bd_dma;if (fep->bufdesc_ex) {bd_dma += sizeof(struct bufdesc_ex) * rxq->rx_ring_size;cbd_base = (struct bufdesc *)(((struct bufdesc_ex *)cbd_base) + rxq->rx_ring_size);} else {bd_dma += sizeof(struct bufdesc) * rxq->rx_ring_size;cbd_base += rxq->rx_ring_size;}}for (i = 0; i < fep->num_tx_queues; i++) {txq = fep->tx_queue[i];txq->index = i;txq->tx_bd_base = (struct bufdesc *)cbd_base;txq->bd_dma = bd_dma;if (fep->bufdesc_ex) {bd_dma += sizeof(struct bufdesc_ex) * txq->tx_ring_size;cbd_base = (struct bufdesc *)(((struct bufdesc_ex *)cbd_base) + txq->tx_ring_size);} else {bd_dma += sizeof(struct bufdesc) * txq->tx_ring_size;cbd_base += txq->tx_ring_size;}}/* The FEC Ethernet specific entries in the device structure */ndev->watchdog_timeo = TX_TIMEOUT;//实习网络设备操作函数,并赋值ndev->netdev_ops = &fec_netdev_ops;//这些都是在这个文件中实习的ndev->ethtool_ops = &fec_enet_ethtool_ops;netif_napi_add(ndev, &fep->napi, fec_enet_rx_napi, NAPI_POLL_WEIGHT);//启用NAPI..........fec_restart(ndev);//重启网络传输return 0;

}

如何使用NAPI实现收发数据?

首先我们知道网络设备中收发数据都会在底层产生中断传输给上层。而这时就会执行到我们前面注册的中断fec_enet_interrupt

fec_enet_interrupt:

static irqreturn_t

fec_enet_interrupt(int irq, void *dev_id)

{struct net_device *ndev = dev_id;struct fec_enet_private *fep = netdev_priv(ndev);......if (napi_schedule_prep(&fep->napi)) {/* Disable the NAPI interrupts */writel(FEC_ENET_MII, fep->hwp + FEC_IMASK);__napi_schedule(&fep->napi);//调用napi}......return ret;

}

__napi_schedule:

void __napi_schedule(struct napi_struct *n)

{unsigned long flags;local_irq_save(flags);____napi_schedule(this_cpu_ptr(&softnet_data), n);local_irq_restore(flags);

}

____napi_schedule:

static inline void ____napi_schedule(struct softnet_data *sd,struct napi_struct *napi)

{list_add_tail(&napi->poll_list, &sd->poll_list);__raise_softirq_irqoff(NET_RX_SOFTIRQ);//触发中断下半部,也就是NAPI的执行

}

这时就会调用到netif_napi_add(ndev, &fep->napi, fec_enet_rx_napi, NAPI_POLL_WEIGHT);//启用NAPI中的fec_enet_rx_napi

fec_enet_rx_napi:

static int fec_enet_rx_napi(struct napi_struct *napi, int budget)

{struct net_device *ndev = napi->dev;struct fec_enet_private *fep = netdev_priv(ndev);int pkts;pkts = fec_enet_rx(ndev, budget);//读fec_enet_tx(ndev);//写if (pkts < budget) {napi_complete(napi);writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);//打开中断}return pkts;

}

napi的作用其实就是为了避免频繁的进入中断而浪费性能,它采用的是轮询的方式,一旦有中断到来,先关中断,然后在轮询中不断主动检测,直到没有数据收发,于是退出轮询,开启中断。

2.fec_enet_mii_init:(主要作用:注册PHY设备与MII_BUS设备,实现PHY总线中驱动与设备的匹配)

static int fec_enet_mii_init(struct platform_device *pdev)

{static struct mii_bus *fec0_mii_bus;//定义mii_bus对象,(mii_bus并不是总线模型里的总线,而是包含这device的对象,而且它里面还有很多与PHY设备相关的信息)............fep->mii_bus = mdiobus_alloc();//申请mdiobusif (fep->mii_bus == NULL) {err = -ENOMEM;goto err_out;}fep->mii_bus->name = "fec_enet_mii_bus";//实现读写函数,并赋值,这里的读写主要是读写phy的寄存器fep->mii_bus->read = fec_enet_mdio_read;fep->mii_bus->write = fec_enet_mdio_write;............node = of_get_child_by_name(pdev->dev.of_node, "mdio");if (node) {err = of_mdiobus_register(fep->mii_bus, node);//在里面既注册了mii_bus的device也注册了PHY的deviceof_node_put(node);} else {err = mdiobus_register(fep->mii_bus);//在of_mdiobus_register中也调用了它}..........return err;

}

of_mdiobus_register:

int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)

{............/* Register the MDIO bus */rc = mdiobus_register(mdio);//注册mii_bus的deviceif (rc)return rc;/* Loop over the child nodes and register a phy_device for each one */for_each_available_child_of_node(np, child) {addr = of_mdio_parse_addr(&mdio->dev, child);if (addr < 0) {scanphys = true;continue;}rc = of_mdiobus_register_phy(mdio, child, addr);//注册PHY设备if (rc)continue;}......return 0;

}

of_mdiobus_register_phy:

static int of_mdiobus_register_phy(struct mii_bus *mdio, struct device_node *child,u32 addr)

{struct phy_device *phy;.......is_c45 = of_device_is_compatible(child,"ethernet-phy-ieee802.3-c45");if (!is_c45 && !of_get_phy_id(child, &phy_id))phy = phy_device_create(mdio, addr, phy_id, 0, NULL);//创建phy_deviceelsephy = get_phy_device(mdio, addr, is_c45);//获取phy_deviceif (!phy || IS_ERR(phy))return 1;............/* All data is now stored in the phy struct;* register it */rc = phy_device_register(phy);//注册phy_device............return 0;

}

二、PHY总线驱动

首先看phy总线是在哪里注册的。

在linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\drivers\net\phy\mdio_bus.c中

struct bus_type mdio_bus_type = {.name = "mdio_bus",.match = mdio_bus_match,.pm = MDIO_BUS_PM_OPS,.dev_groups = mdio_dev_groups,

};

EXPORT_SYMBOL(mdio_bus_type);int __init mdio_bus_init(void)

{int ret;ret = class_register(&mdio_bus_class);if (!ret) {ret = bus_register(&mdio_bus_type);if (ret)class_unregister(&mdio_bus_class);}return ret;

}void mdio_bus_exit(void)

{class_unregister(&mdio_bus_class);bus_unregister(&mdio_bus_type);

}

看来这个总线是内核启动会自动注册的,那么phy_driver是在哪里注册的?

在linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\drivers\net\phy\device.c中

static int __init phy_init(void)

{int rc;rc = mdio_bus_init();if (rc)return rc;rc = phy_drivers_register(genphy_driver,ARRAY_SIZE(genphy_driver));if (rc)mdio_bus_exit();return rc;

}static void __exit phy_exit(void)

{phy_drivers_unregister(genphy_driver,ARRAY_SIZE(genphy_driver));mdio_bus_exit();

}subsys_initcall(phy_init);

module_exit(phy_exit);

这个driver是通用的phy驱动。

static struct phy_driver genphy_driver[] = {

{.phy_id = 0xffffffff,.phy_id_mask = 0xffffffff,.name = "Generic PHY",//从名字就可以看出这是通用的phy驱动.soft_reset = genphy_soft_reset,.config_init = genphy_config_init,.features = PHY_GBIT_FEATURES | SUPPORTED_MII |SUPPORTED_AUI | SUPPORTED_FIBRE |SUPPORTED_BNC,.config_aneg = genphy_config_aneg,.aneg_done = genphy_aneg_done,.read_status = genphy_read_status,.suspend = genphy_suspend,.resume = genphy_resume,.driver = { .owner = THIS_MODULE, },

}, {.phy_id = 0xffffffff,.phy_id_mask = 0xffffffff,.name = "Generic 10G PHY",.soft_reset = gen10g_soft_reset,.config_init = gen10g_config_init,.features = 0,.config_aneg = gen10g_config_aneg,.read_status = gen10g_read_status,.suspend = gen10g_suspend,.resume = gen10g_resume,.driver = {.owner = THIS_MODULE, },

} };

这里面没有.compatible属性,那是如何匹配的呢?

看mdio_bus_match:

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);if (of_driver_match_device(dev, drv))return 1;if (phydrv->match_phy_device)return phydrv->match_phy_device(phydev);return (phydrv->phy_id & phydrv->phy_id_mask) ==(phydev->phy_id & phydrv->phy_id_mask);//最后通过phy的id与phy的mask_id来比较

}

至此驱动的分析就差不多了。

如果想用厂家自己的phy_driver那么可以在内核中配置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-du4JViZp-1680859809927)(C:\Users\cww\AppData\Roaming\Typora\typora-user-images\image-20230406205447986.png)]

总结:

其实网络设备驱动很想I2C或者SPI驱动,它分为MAC与PHY,MAC驱动叫做控制器驱动,是一个platform,内核实现,而PHY驱动是有自己的phy_bus,内核实现了一个通用的phy驱动,但是一般厂家自己也会提供驱动。 .compatible属性,那是如何匹配的呢?

看mdio_bus_match:

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);if (of_driver_match_device(dev, drv))return 1;if (phydrv->match_phy_device)return phydrv->match_phy_device(phydev);return (phydrv->phy_id & phydrv->phy_id_mask) ==(phydev->phy_id & phydrv->phy_id_mask);//最后通过phy的id与phy的mask_id来比较

}

至此驱动的分析就差不多了。

如果想用厂家自己的phy_driver那么可以在内核中配置。

总结:

其实网络设备驱动很想I2C或者SPI驱动,它分为MAC与PHY,MAC驱动叫做控制器驱动,是一个platform,内核实现,而PHY驱动是有自己的phy_bus,内核实现了一个通用的phy驱动,但是一般厂家自己也会提供驱动。

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关推荐

《魔兽世界》任务与解密坐骑大全及获得方法第一篇
杰凯西拉杆箱是品牌吗?质量怎么样?
CDPR:《巫师3》帮助普及了游戏中的“成人主题”
电信卡怎么发短信查流量,电信卡查流量余额的方法

本文标签