当前位置:   article > 正文

IMX6ULL以太网卡移植与驱动分析_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 = <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";
            };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
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复用为普通GPIO
    phy-mode = "rmii";//我们使用的是rmii与PHY通信
    phy-handle = <&ethphy1>;//子节点
    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>;//芯片地址
        };
    };  
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
        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
                >;
            };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

注意:其中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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可以发现像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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

我们先分析一下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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
如何使用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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

__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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

____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的执行
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这时就会调用到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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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();//申请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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

二、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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

看来这个总线是内核启动会自动注册的,那么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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

这个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, },
} };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

这里面没有.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来比较
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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

如果想用厂家自己的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来比较
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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

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

在这里插入图片描述


总结:

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/772726
推荐阅读
相关标签
  

闽ICP备14008679号