接下来告诉内核我们的设备属于网络设备,由于netdevice不属于char或者block设备,因此不能用常规的驱动方法来设计:
SET_NETDEV_DEV(ndev, &pdev->dev); 定义如下: /* Set the sysfs physical device reference for the network logical device
* if set prior to registration will cause a symlink during initialization.
*/
#define SET_NETDEV_DEV(net, pdev) ((net)->dev.parent = (pdev))
分配完netdevice以后,通过下面的函数获取指向分配的私有数据的指针。
/* setup board info structure */
fep = netdev_priv(ndev);
下面用ioremap映射,建立内核到寄存器物理地址的页表,以及给platform_device指针赋值。
fep->hwp = ioremap(r->start, resource_size(r));
fep->pdev = pdev;
if (!fep->hwp) {
ret = -ENOMEM;
goto failed_ioremap;
}
接着设置platform driver的私有数据
platform_set_drvdata(pdev, ndev);
这里其实最终是作了pdev->dev->p->driver_data = ndev的指针指向。
然后是接收之前在mach-mx6文件夹下静态添加device的platform_data:
pdata = pdev->dev.platform_data; 接着是对pdata的数据进行解析:
if (pdata)
fep->phy_interface = pdata->phy;
这里的phy是用来判断MAC层和以太网物理层的接口的,这里MX6Q的接口是PHY_INTERFACE_MODE_RGMII,也就是RGMII,千兆以太网接口。然后是获取以太网控制器对应的中断号:
if (pdata->gpio_irq > 0) { gpio_request(pdata->gpio_irq, "gpio_enet_irq"); gpio_direction_input(pdata->gpio_irq); irq = gpio_to_irq(pdata->gpio_irq); ret = request_irq(irq, fec_enet_interrupt, IRQF_TRIGGER_RISING, pdev->name, ndev); if (ret) goto failed_irq;} else { /* This device has up to three irqs on some platforms */ for (i = 0; i < 3; i++) { irq = platform_get_irq(pdev, i); if (i && irq < 0) break; ret = request_irq(irq, fec_enet_interrupt, IRQF_DISABLED, pdev->name, ndev); if (ret) { while (--i >= 0) { irq = platform_get_irq(pdev, i); free_irq(irq, ndev); } goto failed_irq; } }}
这里用于判断Ethernet的中断使用gpio边沿出发的中断还是从platform device的resource传过来的中断号。之前已经看到传递给driver的私有数据fec_data已经被静态地初始化为
static struct fec_platform_data fec_data __initdata = {
.init = mx6q_sabresd_fec_phy_init,
.phy = PHY_INTERFACE_MODE_RGMII,
.gpio_irq = MX6_ENET_IRQ,
};
这里MX6_ENET_IRQ的值是6,但是在添加私有数据前可以找到下面这句话,
if (enet_to_gpio_6)
/* Make sure the IOMUX_OBSRV_MUX1 is set to ENET_IRQ. */
mxc_iomux_set_specialbits_register(
IOMUX_OBSRV_MUX1_OFFSET,
OBSRV_MUX1_ENET_IRQ,
OBSRV_MUX1_MASK);
else
fec_data.gpio_irq = -1;
imx6_init_fec(fec_data);
用来判断是否对fec_data的gpio_irq字段赋值-1,而enet_to_gpio_6是一个全局变量,定义在arch/arm/mach-mx6/cpu.c中,默认为0,同样在该c文件中可以找到下面这段代码:
static int __init set_enet_irq_to_gpio(char *p)
{
enet_to_gpio_6 = true;
return 0;
}
early_param("enet_gpio_6", set_enet_irq_to_gpio);
early_param用来解析uboot传递给内核的参数,只有当参数中出现“enet_gpio_6”字段时,才会调用set_enet_irq_to_gpio,那么实际上是否调用是取决于运行时uboot的配置,默认情况下MX6Q的uboot是不带有这个参数,因此实际执行时,gpio_irq = -1。所以上面的if(pdata->gpio_irq > 0) {}不会执行,而是执行else分支,即实际从platform device的resource参数传递的信息中获取irq号,从前面的分析可以知道IRQ号被定义成宏MX6Q_INT_FEC,即150号中断,而具体irq的初始化过程有时间我再写一篇**分析一下吧。对应的中断注册函数是fec_enet_interrupt()。
中断处理完了之后就到了时钟了,linux内核有一组专门的clock api用来处理时钟,简单点来说就只要知道首先clk_get()然后再clk_enable()就可以了,driver remove的时候反之即clk_disable()再clk_put()
,具体的时钟框架我有时间再写一篇**分析一下,现在linux定义了一套全新的CCF框架(Common Clock Framework),资料也大多数为英文。
fep->clk = clk_get(&pdev->dev, "fec_clk");
if (IS_ERR(fep->clk)) {
ret = PTR_ERR(fep->clk);
goto failed_clk;
}
fep->mdc_clk = clk_get(&pdev->dev, "fec_mdc_clk");
if (IS_ERR(fep->mdc_clk)) {
ret = PTR_ERR(fep->mdc_clk);
goto failed_clk;
}
clk_enable(fep->clk);
这里fec_clk指的是Ethernet控制器的clock,而fec_mdc_clk指的是MAC层与物理层接口MDIO的Clock。紧接着就到了MAC层的初始化函数:
ret = fec_enet_init(ndev);
if (ret)
goto failed_init;
再接着就是MAC层与物理层接口的初始化函数:
ret = fec_enet_mii_init(pdev);
if (ret)
goto failed_mii_init;
再接着就是对IEEE 1588 时钟同步协议的初始化:
if (fec_ptp_malloc_priv(&(fep->ptp_priv))) {
if (fep->ptp_priv) {
fep->ptp_priv->hwp = fep->hwp;
ret = fec_ptp_init(fep->ptp_priv, pdev->id);
if (ret)
printk(KERN_WARNING "IEEE1588: ptp-timer is unavailable\n");
else
fep->ptimer_present = 1;
} else
printk(KERN_ERR "IEEE1588: failed to malloc memory\n");
}
然后把内核网络层的传输队列关闭(即禁止发送),关闭时钟,等一些列辅助操作:
/* Carrier starts down, phylib will bring it up */
netif_carrier_off(ndev);
clk_disable(fep->clk);
INIT_DELAYED_WORK(&fep->fixup_trigger_tx, fixup_trigger_tx_func);
最后也是最重要步骤,即向内核注册之前分配的net_device:
ret = register_netdev(ndev);
if (ret)
goto failed_register;
接下来将要详细讲述MAC层,物理层接口以及1588协议支持的代码。
|