赞
踩
一般如果说一款SOC支持以太网,那么就是说SOC里集成了MAC芯片。而要想完成网络的通信不仅需要MAC还需要PHY芯片。
PHY(物理层)芯片负责将网络数据在物理层面上转换为电子信号或光信号,以便在网络中传输。它处理数据的物理传输,包括电气、光学和无线形式,并且可以处理一些基本的信号调制和解调任务。例如,以太网中的PHY芯片将数字数据转换为模拟信号以进行电气传输,或将数字数据转换为光信号以进行光纤传输。
MAC(媒体访问控制)芯片负责管理数据在网络中的传输和访问。它处理数据的逻辑传输,决定哪个设备可以发送数据、何时发送数据、如何发送数据等。例如,以太网中的MAC芯片负责实现CSMA/CD协议来协调设备之间的传输,防止数据碰撞。
简单的说就是MAC芯片是用于控制数据传输的(它的作用其实与I2C,SPI控制器差不多),而PHY的作用主要是将数据转化成物理的形式传输出去。
①、内部 MAC 外设会有专用的加速模块,比如专用的 DMA,加速网速数据的处理。
②、网速快,可以支持 10/100/1000M 网速。
③、外接 PHY 可选择性多,成本低。
硬件连接图:
它们使用的芯片是MAC+PHY一体的(如:DM9000)
缺点:速度慢,成本高
硬件连接图:
由于我们是IMX6ULL的平台,内部支持了MAC,所以我们现在讨论内置MAC的连接图。
作用:用于MAC与PHY芯片之间传输数据。
精简的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 数据线,一根 MDC 时钟线。驱动程序可以通过 MDIO 和
MDC 这两根线访问 PHY 芯片的任意一个寄存器。MDIO 接口支持多达 32 个 PHY。同一时刻
内只能对一个 PHY 进行操作,那么如何区分这 32 个 PHY 芯片呢?和 IIC 一样,使用器件地址
即可。同一 MDIO 接口下的所有 PHY 芯片,其器件地址不能冲突,必须保证唯一,具体器件
地址值要查阅相应的 PHY 数据手册。
RJ45接口的作用是用于供网线插入的,一般RJ45接口与PHY芯片连接在一起,但是中间需要一个网络变压器,网络变压器用于隔离
以及滤波等,网络变压器也是一个芯片。(现在一般RJ45接口内部集成了变压器,但是我们还是得确定一下是否集成了)
I.MX6ULL 内部自
带的 ENET 外设其实就是一个网络 MAC,支持 10/100M。实现了三层网络加速,用于加速那些
通用的网络协议,比如 IP、TCP、UDP 和 ICMP 等,为客户端应用程序提供加速服务。
其实对于我们来说PHY才是重点,正如I2C控制器的控制器不是我们重点,而从设备才是,PHY就是MAC的从设备。
PHY 是 IEEE 802.3 规定的一个标准模块,它的前16位寄存器是指定好的,可以提供查看IEEE 802.3英文文档查看,而后16位可以由不同厂家自定义,但是前16位已经包含了基本的通信信息。所以在内核中有一种通用的PHY驱动,它虽然不能驱动厂家的特殊功能,但是基本上的通信功能是能胜任的。(不一定会成功,需要我们调试)
正点原子使用的就是这款PHY
PHY 地址设置:
正点原子 ALPHA 开发板 ENET1 网络的 SR8201F 上的 LED1/PHYAD1 引脚上拉,LED0/PHYDAD0 引脚下来下拉,因此 ENET1 上的 SR8201F 地址为 0X02。ENET2 网络上的SR8201F 的 LED1/PHYAD1 引脚下拉,LED0/PHYAD0 引脚上拉,因此 ENET2 上的 SR8201F
地址为 1。
SR8021F 内部寄存器:
我们说的配置 PHY 芯片,重点就是配置 BCR 寄存器
此节点可以参考Documentation/devicetree/bindings/net/fsl-fec.txt
fec2: ethernet@020b4000 { compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec"; reg = <0x020b4000 0x4000>; interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>; 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"; };
其中phy相关节点部分可以参考:Documentation/devicetree/bindings/net/phy.txt
&fec2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet2//MDIO与MDC两个引脚的IO复用 &pinctrl_enet2_reset>;//PHY中Reset引脚的IO复用为普通GPIO phy-mode = "rmii";//我们使用的是rmii与PHY通信 phy-handle = <ðphy1>;//子节点 phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;//配置GPIO phy-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 = <2>; }; */ ethphy1: ethernet-phy@1 { compatible = "ethernet-phy-ieee802.3-c22"; smsc,disable-energy-detect; reg = <1>;//芯片地址 }; }; };
pinctrl_enet2: enet2grp { fsl,pins = < MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b031 >; }; pinctrl_enet2_reset: enet2resetgrp { fsl,pins = < MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0 >; };
注意:其中pinctrl_enet2_reset要放在&iomuxc_snvs节点下,因为MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO0中有SNVS,而pinctrl_enet2放在普通的&iomuxc节点下。
如何查找到控制器驱动所在文件呢?可以根据设备树控制器节点的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);//复位phy if (fep->bufdesc_ex) fec_ptp_init(pdev); ret = fec_enet_init(ndev);//初始化net_device(包括两个ops变量的赋值),以及设置NAPI的POLL if (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]; else fep->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:
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; }
首先我们知道网络设备中收发数据都会在底层产生中断传输给上层。而这时就会执行到我们前面注册的中断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的作用其实就是为了避免频繁的进入中断而浪费性能,它采用的是轮询的方式,一旦有中断到来,先关中断,然后在轮询中不断主动检测,直到没有数据收发,于是退出轮询,开启中断。
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();//申请mdiobus if (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的device of_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的device if (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_device else phy = get_phy_device(mdio, addr, is_c45);//获取phy_device if (!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总线是在哪里注册的。
在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驱动,但是一般厂家自己也会提供驱动。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。