linux网络设备应用与驱动编程学习4——模板与实例(A)源码来自lpc32xx_mii.c 1. 模块初始化卸载 static int __init lpc32xx_net_init(void) { return platform_driver_register(&lpc32xx_net_driver); }
static void __exit lpc32xx_net_cleanup(void) { platform_driver_unregister(&lpc32xx_net_driver); } 2. 平台驱动相关方法 static struct platform_driver lpc32xx_net_driver = { .probe = lpc32xx_net_drv_probe, .remove = __devexit_p(lpc32xx_net_drv_remove), .suspend = lpc32xx_net_drv_suspend, .resume = lpc32xx_net_drv_resume, .driver = { .name = MODNAME, }, }; 3. probe方法分析 static int lpc32xx_net_drv_probe(struct platform_device *pdev) { struct resource *res; struct net_device *ndev; struct netdata_local *pldat; struct phy_device *phydev; dma_addr_t dma_handle; int irq, ret;
第一步:从平台上获取资源信息 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); /*static struct resource net_resources[] = { [0] = { .start = ETHERNET_BASE, .end = ETHERNET_BASE + SZ_4K - 1, .flags = IORESOURCE_MEM, },
[1] = { .start = IRQ_ETHERNET, .end = IRQ_ETHERNET, .flags = IORESOURCE_IRQ, }, };*/ if ((!res) || (irq < 0) || (irq >= NR_IRQS)) { dev_err(&pdev->dev, "error getting resources.\n"); ret = -ENXIO; goto err_exit; }
第二步:分配和初始化net_device结构,这一步也可放在模块初始化中完成 ndev = alloc_etherdev(sizeof(struct netdata_local)); /*alloc_etherdev函数最终调用alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);函数*/ if (!ndev) { dev_err(&pdev->dev, "could not allocate device.\n"); ret = -ENOMEM; goto err_exit; }
SET_NETDEV_DEV(ndev, &pdev->dev); /*将ndev的父设备指向平台设备。即将ndev设备挂到平台设备表里*/
pldat = netdev_priv(ndev); //获得ndev的私有指针,指针内的成员由驱动作者自己定义。 pldat->pdev = pdev; pldat->ndev = ndev;
spin_lock_init(&pldat->lock); /* Save resources */ pldat->net_region_start = res->start; pldat->net_region_size = res->end - res->start + 1; ndev->irq = irq;
//第三步:申请硬件资源 /* Get clock for the device */ pldat->clk = clk_get(&ndev->dev, "net_ck"); if (IS_ERR(pldat->clk)) { ret = PTR_ERR(pldat->clk); goto err_out_free_dev; } /*以上为初始化私有结构体*/
/* Enable network clock */ __lpc32xx_net_clock_enable(pldat, 1); //使能时钟 /* Map IO space */ pldat->net_base = ioremap(pldat->net_region_start, pldat->net_region_size); if (!pldat->net_base) { dev_err(&pdev->dev, "failed to map registers, aborting.\n"); ret = -ENOMEM; goto err_out_disable_clocks; } //将网卡物理空间动态映射到内核空间。 ret = request_irq(ndev->irq, __lpc32xx_eth_interrupt, 0, ndev->name, ndev); if (ret) { printk(KERN_ERR "%s: Unable to request IRQ %d (error %d)\n", ndev->name, ndev->irq, ret); goto err_out_iounmap; } //申请中断
//第四步:设备操作接口初始化 /* Fill in the fields of the device structure with ethernet values. */ ether_setup(ndev); /*ndev申请完之后并没有初始化,ether_setup()函数就是完成ndev有关于以太网确定成员的初始化*/ /*probe的以上部分完成了网络设备驱动的“网络设备接口层”的工作,以下对设备操作函数的具体实现便是“设备驱动功能层”的事情,即网络设备驱动的主体工作*/ /* Setup driver functions */ ndev->open = lpc32xx_net_open; ndev->stop = lpc32xx_net_close; //设备打开与关闭时调用 ndev->hard_start_xmit = lpc32xx_net_hard_start_xmit; //设备数据发送时调用 ndev->tx_timeout = lpc32xx_net_timeout; //发送超时调用 ndev->watchdog_timeo = msecs_to_jiffies(watchdog); //设定超时时间,单位jiffies ndev->set_multicast_list = lpc32xx_net_set_multicast_list; /*当设备的组播列表改变或设备标志改变时调用*/ ndev->ethtool_ops = &lpc32xx_net_ethtool_ops; //结构体中的成员用于更改或报告网络设备的设置 ndev->do_ioctl = &lpc32xx_net_ioctl; //设备特定的I/O控制 #ifdef CONFIG_NET_POLL_CONTROLLER ndev->poll_controller = lpc32xx_net_poll_controller; //支持纯粹的netconsole(用于kgdb调试),它以轮询方式接收数据包 #endif ndev->base_addr = pldat->net_region_start; // 继续初始化ndev:虚拟基地址
//第五步:其它及DMA初始化 /* Save board specific configuration */ pldat->ncfg = (struct lpc32xx_net_cfg *) pdev->dev.platform_data; /* .platform_data = &lpc32xx_netdata,
struct lpc32xx_net_cfg lpc32xx_netdata = { .get_mac_addr = &return_mac_address, .phy_irq = -1, .phy_mask = 0xFFFFFFF0,
}; */ if (pldat->ncfg == NULL) { printk(KERN_INFO "%s : WARNING: No board MAC address provided\n", ndev->name); pldat->ncfg = &__lpc32xx_local_net_config; }
/* Get size of DMA buffers/descriptors region */ pldat->dma_buff_size = (ENET_TX_DESC + ENET_RX_DESC) * (ENET_MAXF_SIZE + sizeof(struct txrx_desc_t) + sizeof(struct rx_status_t)); /*计算DMA缓冲区所需要的空间,DMA空间包括帧片断(描述符)数组大小,状态大小,帧片断数量*/ #if defined(CONFIG_MACH_LPC32XX_IRAM_FOR_NET) pldat->dma_buff_base_v = (u32) io_p2v(IRAM_BASE); dma_handle = (dma_addr_t) IRAM_BASE; //初始化DMA缓冲区的基地址 #else pldat->dma_buff_size += 4096; /* Allows room for alignment */
/* Align on the next highest page entry size */ pldat->dma_buff_size &= 0Xfffff000; pldat->dma_buff_size += 0X00001000; //如果DMA缓冲区不在内部RAM中,则进行页对齐 /* Allocate a chunk of memory for the DMA ethernet buffers and descriptors */ pldat->dma_buff_base_v = (u32) dma_alloc_coherent(&pldat->pdev->dev, pldat->dma_buff_size, &dma_handle, GFP_KERNEL); #endif //申请一致性缓冲区,初始化DMA缓冲区的基地址 if (pldat->dma_buff_base_v == (u32) NULL) { dev_err(&pdev->dev, "error getting DMA region.\n"); ret = -ENOMEM; goto err_out_free_irq; } pldat->dma_buff_base_p = (u32) dma_handle;
#ifdef NET_DEBUG printk(KERN_INFO "Ethernet net MAC resources\n"); printk(KERN_INFO "IO address start :0x%08x\n", (u32) pldat->net_region_start); printk(KERN_INFO "IO address size :%d\n", (u32) pldat->net_region_size); printk(KERN_INFO "IO address (mapped) :0x%08x\n", (u32) pldat->net_base); printk(KERN_INFO "IRQ number :%d\n", ndev->irq); printk(KERN_INFO "DMA buffer size :%d\n", pldat->dma_buff_size); printk(KERN_INFO "DMA buffer P address :0x%08x\n", pldat->dma_buff_base_p); printk(KERN_INFO "DMA buffer V address :0x%08x\n", pldat->dma_buff_base_v); #endif
/* Get the board MAC address */ if (pldat->ncfg->get_mac_addr != NULL) { ret = pldat->ncfg->get_mac_addr(ndev->dev_addr); //在探测阶段先随便指定一个mac完成初始化 if (ret) { /* Mac address load error */ goto err_out_dma_unmap; } }
if (!is_valid_ether_addr(ndev->dev_addr)) { printk(KERN_INFO "%s: Invalid ethernet MAC address. Please " "set using ifconfig\n", ndev->name); }
第六步:以太网控制器相关 /* Reset the ethernet controller */ __lpc32xx_eth_reset(pldat); //__lpc32xx_eth_reset()函数是一些读写寄存器构成
/* then shut everything down to save power */ __lpc32xx_net_shutdown(pldat);
/* Set default parameters */ pldat->msg_enable = NETIF_MSG_LINK;
/* Force an MII interface reset and clock setup */ __lpc32xx_mii_mngt_reset(pldat);
/* Force default PHY interface setup in chip, this will probably be changed by the PHY driver */ pldat->link = 0; pldat->speed = 100; pldat->duplex = DUPLEX_FULL; __lpc32xx_params_setup(pldat); //__lpc32xx_params_setup()函数就是根据pldat的speed,duplex设置完成相应寄存器设置。
ret = register_netdev(ndev); //以上代码主要就是完成了ndev及其私有指针pldat指向结构的部分初始化 if (ret) { dev_err(&pdev->dev, "Cannot register net device, aborting.\n"); goto err_out_dma_unmap; } platform_set_drvdata(pdev, ndev); //将ndev作为pdev->drvdata,方便pdev与ndev之间结构信息共享
if (lpc32xx_mii_init(pldat) != 0) { goto err_out_unregister_netdev; } // lpc32xx_mii_init()完成mii接口的初始化见“lpc32xx_mii_init()函数分析”
printk(KERN_INFO "%s: LPC32XX mac at 0x%08lx irq %d\n", ndev->name, ndev->base_addr, ndev->irq);
//最后初始化了一个总线设备。下面的是一些错误处理。 phydev = pldat->phy_dev; printk(KERN_INFO "%s: attached PHY driver [%s] " "(mii_bus:phy_addr=%s, irq=%d)\n", ndev->name, phydev->drv->name, phydev->dev.bus_id, phydev->irq);
return 0;
err_out_unregister_netdev: platform_set_drvdata(pdev, NULL); unregister_netdev(ndev); err_out_dma_unmap: dma_free_coherent(&pldat->pdev->dev, pldat->dma_buff_size, (void *) pldat->dma_buff_base_v, (dma_addr_t) pldat->dma_buff_base_p); err_out_free_irq: free_irq(ndev->irq, ndev); err_out_iounmap: iounmap(pldat->net_base); err_out_disable_clocks: clk_disable(pldat->clk); clk_put(pldat->clk); err_out_free_dev: free_netdev(ndev); err_exit: printk("%s: not found (%d).\n", MODNAME, ret); return ret; }
总结一下lpc32xx_net_drv_probe()函数:首先根据平台设备的 resource结构获得空间和中断信息,并利用这些作息初始化申请的net_device结构体,再向内核申请这些资源。再次,填充ndev的设备操作 函数成员,让内核得到一些控制网络传输的方法。接着,根据芯片特点,申请了DMA缓冲区,初始化了mac。而后便是初始化以太网控制器及其与phy的数据 交互接口mii,最后是一些错误处理。可以说一个probe方法完成了整个网络设备驱动的构架工作。
lpc32xx_mii_init()函数分析 static int lpc32xx_mii_init(struct netdata_local *pldat) { int err = -ENXIO, i;
/* Setup MII mode */ #if defined (MAC_LPC32XX_MII_SUPPORT) __raw_writel(COMMAND_PASSRUNTFRAME, ENET_COMMAND(pldat->net_base)); #else __raw_writel((COMMAND_PASSRUNTFRAME | COMMAND_RMII), ENET_COMMAND(pldat->net_base)); __raw_writel(SUPP_RESET_RMII, ENET_SUPP(pldat->net_base)); #endif
pldat->mii_bus.name = "LPC32XX_mii_bus"; pldat->mii_bus.read = &lpc32xx_mdio_read; pldat->mii_bus.write = &lpc32xx_mdio_write; pldat->mii_bus.reset = &lpc32xx_mdio_reset; snprintf(pldat->mii_bus.id, MII_BUS_ID_SIZE, "%x", pldat->pdev->id); pldat->mii_bus.priv = pldat; pldat->mii_bus.dev = &pldat->ndev->dev; pldat->mii_bus.phy_mask = 0xFFFFFFF0; /*在plat的结构中,mii_bus是一种PHY设备挂接的总线.该总线介于mac于phy之间,以上是它的初始化。该总线提供了read,write,reset方法,有点像字符设备中fop提供的方法*/
if (pldat->ncfg) { pldat->mii_bus.phy_mask = pldat->ncfg->phy_mask; }
pldat->mii_bus.irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); if (!pldat->mii_bus.irq) { err = -ENOMEM; goto err_out; }
for (i = 0; i < PHY_MAX_ADDR; i++) { pldat->mii_bus.irq[i] = PHY_POLL; } /*申请了一片断区域,并初始化。PHY_MAX_ADDR代表总线能接受的设备数量 一个设备将来对就这里申请的一个中断位置*/ platform_set_drvdata(pldat->ndev, &pldat->mii_bus);
//像这样的函数,个人以为,只为作指针引用方便 if (mdiobus_register(&pldat->mii_bus)) { goto err_out_free_mdio_irq; }
if (lpc32xx_mii_probe(pldat->ndev) != 0) { goto err_out_unregister_bus; }
return 0;
err_out_unregister_bus: mdiobus_unregister(&pldat->mii_bus); err_out_free_mdio_irq: kfree(pldat->mii_bus.irq); err_out: return err; }
追踪(mdiobus_register(&pldat->mii_bus) int mdiobus_register(struct mii_bus *bus) { int i; int err = 0;
if (NULL == bus || NULL == bus->name || NULL == bus->read || NULL == bus->write) return -EINVAL; //检查总线是否被初始化
mutex_init(&bus->mdio_lock);
if (bus->reset) bus->reset(bus);
for (i = 0; i < PHY_MAX_ADDR; i++) { struct phy_device *phydev;
if (bus->phy_mask & (1 << i)) { bus->phy_map[i] = NULL; continue; }
phydev = get_phy_device(bus, i);
if (IS_ERR(phydev)) return PTR_ERR(phydev);
/* There's a PHY at this address * We need to set: * 1) IRQ * 2) bus_id * 3) parent * 4) bus * 5) mii_bus * And, we need to register it */ if (phydev) { phydev->irq = bus->irq[i];
phydev->dev.parent = bus->dev; /*pldat->mii_bus.dev = &pldat->ndev->dev; 这说明phydev的父设备是ndev,即物理层PHY设备的父设备为MAC设备*/ phydev->dev.bus = &mdio_bus_type; snprintf(phydev->dev.bus_id, BUS_ID_SIZE, PHY_ID_FMT, bus->id, i);
phydev->bus = bus;
/* Run all of the fixups for this PHY */ phy_scan_fixups(phydev);
err = device_register(&phydev->dev);
if (err) { printk(KERN_ERR "phy %d failed to register\n", i); phy_device_free(phydev); phydev = NULL; } }
bus->phy_map[i] = phydev; }
pr_info("%s: probed\n", bus->name);
return err; } EXPORT_SYMBOL(mdiobus_register); /*经过以上源代码分析,可以看出mdiobus_register()函数为总线上所有设备进行了设置,并注册进了设备模型。从名子上看是总线注册,实际是总线上的设备注册。*/
追踪lpc32xx_mii_probe() static int lpc32xx_mii_probe(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); struct phy_device *phydev = NULL; int phy_addr;
/* find the first phy */ for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) { if (pldat->mii_bus.phy_map[phy_addr]) { phydev = pldat->mii_bus.phy_map[phy_addr]; break; } }
if (!phydev) { printk (KERN_ERR "%s: no PHY found\n", ndev->name); return -1; }
/* Attach to the PHY */ #if defined (MAC_LPC32XX_MII_SUPPORT) phydev = phy_connect(ndev, phydev->dev.bus_id, &lpc32xx_handle_link_change, 0, PHY_INTERFACE_MODE_MII); #else phydev = phy_connect(ndev, phydev->dev.bus_id, &lpc32xx_handle_link_change, 0, PHY_INTERFACE_MODE_RMII); #endif
if (IS_ERR(phydev)) { printk(KERN_ERR "%s: Could not attach to PHY\n", ndev->name); return PTR_ERR(phydev); }
/* mask with MAC supported features */ phydev->supported &= PHY_BASIC_FEATURES;
phydev->advertising = phydev->supported;
pldat->link = 0; pldat->speed = 0; pldat->duplex = -1; pldat->phy_dev = phydev;
return 0; } lpc32xx_mii_probe()完成了这样一个事情:找到第一个phy设备,然后根据内核配置,选用MII接口或RMII接口,之后再进行简单的配置。这个探测方法完成的是phy设备的探测。 总结一下: lpc32xx_mii_init()函数完成的是mii接口的初始化,包括注册注册mii_bus总线上的设备,然后根据找到总线上第一个phy设备进行一些初始化设置。 linux网络设备应用与驱动编程学习4——模板与实例(B)——打开和释放方法l open方法 static int lpc32xx_net_open(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev);
/* if the phy is not yet registered, retry later*/ if (!pldat->phy_dev) { return -EAGAIN; }
if (netif_msg_ifup(pldat)) { dev_dbg(&pldat->pdev->dev, "enabling %s\n", ndev->name); }
if (!is_valid_ether_addr(ndev->dev_addr)) { return -EADDRNOTAVAIL; }
__lpc32xx_net_clock_enable(pldat, 1);
/* Reset and initialize */ __lpc32xx_eth_reset(pldat); __lpc32xx_eth_init(pldat);
/* schedule a link state check */ phy_start(pldat->phy_dev); // 更换PHY状态,启动PHY netif_start_queue(ndev); //激活发送队列 return 0; } 总结:open中最重要的两个函数是__lpc32xx_eth_init()和netif_start()一个做数据处理前的准备工作,另一个激活发送队列
追踪__lpc32xx_eth_init static void __lpc32xx_eth_init(struct netdata_local *pldat) { u32 tmp;
/* Disable controller and reset */ tmp = __raw_readl(ENET_COMMAND(pldat->net_base)); tmp &= ~COMMAND_RXENABLE | COMMAND_TXENABLE; __raw_writel(tmp, ENET_COMMAND(pldat->net_base)); tmp = __raw_readl(ENET_MAC1(pldat->net_base)); tmp &= ~MAC1_RECV_ENABLE; __raw_writel(tmp, ENET_MAC1(pldat->net_base)); /*在命令寄存器中禁能接收和发送,然后又在MAC1中禁止接收帧*/
/* Initial MAC setup */ __raw_writel(MAC1_PASS_ALL_RX_FRAMES, ENET_MAC1(pldat->net_base)); __raw_writel((MAC2_PAD_CRC_ENABLE | MAC2_CRC_ENABLE), ENET_MAC2(pldat->net_base)); __raw_writel(ENET_MAXF_SIZE, ENET_MAXF(pldat->net_base)); /*在MAC初始化配置:传递所有帧,每帧上添加CRC,MAC填充所有短帧,最大帧长度为1536个字节*/
/* Collision window, gap */ __raw_writel((CLRT_LOAD_RETRY_MAX(0xF) | CLRT_LOAD_COLLISION_WINDOW(0x37)), ENET_CLRT(pldat->net_base)); __raw_writel(IPGR_LOAD_PART2(0x12), ENET_IPGR(pldat->net_base)); /*冲突窗口/重试寄存器,冲突之后重复发送次数15次,冲突窗口(导言区和SFD)之后有56个字节窗口;非背对背的内部包间隔寄存器:包间隔为18*/
#if defined (MAC_LPC32XX_MII_SUPPORT) __raw_writel(COMMAND_PASSRUNTFRAME, ENET_COMMAND(pldat->net_base)); //MII模式,小于64字节的短帧传递到寄存器 #else __raw_writel((COMMAND_PASSRUNTFRAME | COMMAND_RMII), ENET_COMMAND(pldat->net_base)); __raw_writel(SUPP_RESET_RMII, ENET_SUPP(pldat->net_base)); #endif /*RMII模式,100Mbps模式*/ __lpc32xx_params_setup(pldat);
/* Setup TX and RX descriptors */ __lpc32xx_txrx_desc_setup(pldat); //建立描述符,这是接收和发送过程的第一步
/* Setup packet filtering */ __raw_writel((RXFLTRW_ACCEPTUBROADCAST | RXFLTRW_ACCEPTPERFECT), ENET_RXFILTER_CTRL(pldat->net_base));
/* Clear and enable interrupts */ __raw_writel(0xFFFF, ENET_INTCLEAR(pldat->net_base)); __raw_writel((MACINT_RXDONEINTEN | MACINT_TXDONEINTEN), ENET_INTENABLE(pldat->net_base)); //设置过滤寄存器和使能中断
/* Get the next TX buffer output index */ pldat->num_used_tx_buffs = 0; pldat->last_tx_idx = __raw_readl(ENET_TXCONSUMEINDEX(pldat->net_base));
/* Enable controller */ tmp = __raw_readl(ENET_COMMAND(pldat->net_base)); tmp |= COMMAND_RXENABLE | COMMAND_TXENABLE; __raw_writel(tmp, ENET_COMMAND(pldat->net_base)); tmp = __raw_readl(ENET_MAC1(pldat->net_base)); tmp |= MAC1_RECV_ENABLE; __raw_writel(tmp, ENET_MAC1(pldat->net_base)); } /*使能发送和接收通道。接收时还要使能MAC。这是接收和发送过程的第二步*/ 总结:__lpc32xx_eth_init()函数完成了以太网模块工作方式的初始化:工作模式,速度,帧格式,建立描述符,使能接收和发送通道。这些都是接收数据前的准备工作。
追踪__lpc32xx_params_setup(pldat); static void __lpc32xx_params_setup(struct netdata_local *pldat) { u32 tmp;
if (pldat->duplex == DUPLEX_FULL) { tmp = __raw_readl(ENET_MAC2(pldat->net_base)); tmp |= MAC2_FULL_DUPLEX; __raw_writel(tmp, ENET_MAC2(pldat->net_base)); //配置为全双工模式 tmp = __raw_readl(ENET_COMMAND(pldat->net_base)); tmp |= COMMAND_FULLDUPLEX; __raw_writel(tmp, ENET_COMMAND(pldat->net_base)); //全双工模式下操作 __raw_writel(IPGT_LOAD(0x15), ENET_IPGT(pldat->net_base)); //包结尾与包开始的时间间隔 } else { tmp = __raw_readl(ENET_MAC2(pldat->net_base)); tmp &= ~MAC2_FULL_DUPLEX; __raw_writel(tmp, ENET_MAC2(pldat->net_base)); tmp = __raw_readl(ENET_COMMAND(pldat->net_base)); tmp &= ~COMMAND_FULLDUPLEX; __raw_writel(tmp, ENET_COMMAND(pldat->net_base)); __raw_writel(IPGT_LOAD(0x12), ENET_IPGT(pldat->net_base)); } //或者设置成半双工模式
if (pldat->speed == SPEED_100) { __raw_writel(SUPP_SPEED, ENET_SUPP(pldat->net_base)); } else { __raw_writel(0, ENET_SUPP(pldat->net_base)); } } //RMII的附加设置,设置速度为10Mbps或100Mbps
追踪__lpc32xx_txrx_desc_setup(pldat); static void __lpc32xx_txrx_desc_setup(struct netdata_local *pldat) { u32 tbuff, *ptxstat; int i; struct txrx_desc_t *ptxrxdesc; struct rx_status_t *prxstat; // txrx_desc_t结构用来描述帧片断描述符的,rx_status_t结构用来描述描述符态的
tbuff = __ptr_align(pldat->dma_buff_base_v); //16字节对齐tbuff
/* Setup TX descriptors, status, and buffers */ for (i = 0; i < ENET_TX_DESC; i++) { pldat->tx_desc_v [i] = tbuff; tbuff += sizeof(struct txrx_desc_t); } for (i = 0; i < ENET_TX_DESC; i++) { pldat->tx_stat_v [i] = tbuff; tbuff += sizeof(u32); } tbuff = __ptr_align(tbuff); for (i = 0; i < ENET_TX_DESC; i++) { pldat->tx_buff_v [i] = tbuff; tbuff += ENET_MAXF_SIZE; }
/* Setup RX descriptors, status, and buffers */ for (i = 0; i < ENET_RX_DESC; i++) { pldat->rx_desc_v [i] = tbuff; tbuff += sizeof(struct txrx_desc_t); } tbuff = __ptr_align(tbuff); for (i = 0; i < ENET_RX_DESC; i++) { pldat->rx_stat_v [i] = tbuff; tbuff += sizeof(struct rx_status_t); } tbuff = __ptr_align(tbuff); for (i = 0; i < ENET_RX_DESC; i++) { pldat->rx_buff_v [i] = tbuff; tbuff += ENET_MAXF_SIZE; } /*以上内容分别给发送和接收描述符,状态,相关数据区做了空间分配*/
/* Map the TX descriptors to the TX buffers in hardware */ for (i = 0; i < ENET_TX_DESC; i++) { ptxstat = (u32 *) pldat->tx_stat_v [i]; ptxrxdesc = (struct txrx_desc_t *) pldat->tx_desc_v [i];
ptxrxdesc->packet = __va_to_pa(pldat->tx_buff_v [i], pldat); ptxrxdesc->control = 0; *ptxstat = 0; }
/* Map the RX descriptors to the RX buffers in hardware */ for (i = 0; i < ENET_RX_DESC; i++) { prxstat = (struct rx_status_t *) pldat->rx_stat_v [i]; ptxrxdesc = (struct txrx_desc_t *) pldat->rx_desc_v [i];
ptxrxdesc->packet = __va_to_pa(pldat->rx_buff_v [i], pldat); ptxrxdesc->control = 0x80000000 | (ENET_MAXF_SIZE - 1); prxstat->statusinfo = 0; prxstat->statushashcrc = 0; } /* Setup base addresses in hardware to point to buffers and descriptors */ __raw_writel((ENET_TX_DESC - 1), ENET_TXDESCRIPTORNUMBER(pldat->net_base)); __raw_writel(__va_to_pa(pldat->tx_desc_v [0], pldat), ENET_TXDESCRIPTOR(pldat->net_base)); __raw_writel(__va_to_pa(pldat->tx_stat_v [0], pldat), ENET_TXSTATUS(pldat->net_base)); __raw_writel((ENET_RX_DESC - 1), ENET_RXDESCRIPTORNUMBER(pldat->net_base)); __raw_writel(__va_to_pa(pldat->rx_desc_v [0], pldat), ENET_RXDESCRIPTOR(pldat->net_base)); __raw_writel(__va_to_pa(pldat->rx_stat_v [0], pldat), ENET_RXSTATUS(pldat->net_base)); } /*初始化了描述符和状态寄存器的数值,然后写入相应寄存器,packet指向的是一个物理地址*/ 总结:正如函数的名字__lpc32xx_txrx_desc_setup,它的作用就是建立描述符,和相关状态初始化。
static int lpc32xx_net_close(struct net_device *ndev) { unsigned long flags; struct netdata_local *pldat = netdev_priv(ndev);
if (netif_msg_ifdown(pldat)) { dev_dbg(&pldat->pdev->dev, "shutting down %s\n", ndev->name); }
netif_stop_queue(ndev);
if (pldat->phy_dev) { phy_stop(pldat->phy_dev); }
spin_lock_irqsave(&pldat->lock, flags); __lpc32xx_eth_reset(pldat); netif_carrier_off(ndev); __raw_writel(0, ENET_MAC1(pldat->net_base)); __raw_writel(0, ENET_MAC2(pldat->net_base)); spin_unlock_irqrestore(&pldat->lock, flags);
__lpc32xx_net_clock_enable(pldat, 0);
return 0; } 和open方法相反,调用netif_stop_queue()停止传输包,重启以太网模块,改变设备连接状态,停止时钟。
linux网络设备应用与驱动编程学习4——模板与实例(C)——其它方法为了便于分析,贴出对应的方法 ndev->open = lpc32xx_net_open; ndev->stop = lpc32xx_net_close; ndev->hard_start_xmit = lpc32xx_net_hard_start_xmit; ndev->tx_timeout = lpc32xx_net_timeout; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ndev->set_multicast_list = lpc32xx_net_set_multicast_list; ndev->ethtool_ops = &lpc32xx_net_ethtool_ops; ndev->do_ioctl = &lpc32xx_net_ioctl; #ifdef CONFIG_NET_POLL_CONTROLLER ndev->poll_controller = lpc32xx_net_poll_controller;
n hard_start_xmit方法(发送数据时调用) static int lpc32xx_net_hard_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); unsigned int len, txidx; u32 *ptxstat; struct txrx_desc_t *ptxrxdesc;
len = skb->len; //skb的大小
spin_lock_irq(&pldat->lock);
if (pldat->num_used_tx_buffs >= (ENET_TX_DESC - 1)) { /* This function should never be called when there are no buffers, log the error */ netif_stop_queue(ndev); spin_unlock_irq(&pldat->lock); dev_err(&pldat->pdev->dev, "BUG! TX request when no free TX buffers!\n"); return 1; }
/* Get the next TX descriptor index */ txidx = __raw_readl(ENET_TXPRODUCEINDEX(pldat->net_base)); //获得发送描述符的生产者索引
/* Setup control for the transfer */ ptxstat = (u32 *) pldat->tx_stat_v [txidx]; *ptxstat = 0; //将描述符状态写为0 ptxrxdesc = (struct txrx_desc_t *) pldat->tx_desc_v [txidx]; ptxrxdesc->control = (len - 1) | 0xC0000000; //描述符的控制字,写入skb的长度,最后希望发送完成后,产生中断。
/* Copy data to the DMA buffer */ memcpy((void *) pldat->tx_buff_v [txidx], skb->data, len); //将skb拷贝到DMA的数据缓冲区中。 /* Save the buffer and increment the buffer counter */ pldat->skb[txidx] = skb; pldat->num_used_tx_buffs++;
/* Start transmit */ txidx++; if (txidx >= ENET_TX_DESC) { txidx = 0; } //生产者索引到最大值时应回0 __raw_writel((u32) txidx, ENET_TXPRODUCEINDEX(pldat->net_base)); //更新发送描述符的生产者索引应由软件完成。
/* Stop queue if no more TX buffers */ if (pldat->num_used_tx_buffs >= (ENET_TX_DESC - 1)) { netif_stop_queue(ndev); }
spin_unlock_irq(&pldat->lock); ndev->trans_start = jiffies; //记录发送的时间戳
return 0; } 这个函数只发送了一个描述符,然后更新了一次发送生产者索引,一个描述符对应一个skb n tx_timeout(发送超时时调用) static void lpc32xx_net_timeout(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev);
/* This should never happen and indicates a problem */ dev_err(&pldat->pdev->dev, "BUG! TX timeout occurred!\n"); } 这个函数从实现的结果看,它只是调试方便才实现的。 n watchdog_timeo(设置超时时间) n set_multicast_list(组播列表改变或设备标志改变时) static void lpc32xx_net_set_multicast_list(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); struct dev_mc_list *mcptr = ndev->mc_list; //组播mac地址 int i, mc_cnt = ndev->mc_count; //组播(地址)数 u32 tmp32, hash_val, hashlo, hashhi; unsigned long flags;
spin_lock_irqsave(&pldat->lock, flags);
/* Set station address */ __lpc32xx_set_mac(pldat, ndev->dev_addr); //将dev_addr中的MAC地址存入站寄存器
tmp32 = RXFLTRW_ACCEPTUBROADCAST | RXFLTRW_ACCEPTPERFECT;
if (ndev->flags & IFF_PROMISC) { tmp32 |= RXFLTRW_ACCEPTUNICAST | RXFLTRW_ACCEPTUNICASTHASH | RXFLTRW_ACCEPTUMULTICASTHASH; } if (ndev->flags & IFF_ALLMULTI) { tmp32 |= RXFLTRW_ACCEPTUMULTICAST; } __raw_writel(tmp32, ENET_RXFILTER_CTRL(pldat->net_base)); //设置滤波存器
/* Set initial hash table */ hashlo = 0x0; hashhi = 0x80000000;
/* 64 bits : multicast address in hash table */ for (i = 0; i < mc_cnt; i++, mcptr = mcptr->next) { hash_val = ether_crc_le(6, mcptr->dmi_addr) & 0x3f;
if (hash_val >= 32) { hashhi |= 1 << (32 - hash_val); } else { hashlo |= 1 << hash_val; } }
__raw_writel(hashlo, ENET_HASHFILTERL(pldat->net_base)); __raw_writel(hashhi, ENET_HASHFILTERH(pldat->net_base)); //填充Hash滤波器表
spin_unlock_irqrestore(&pldat->lock, flags); } 总结:这个函数重填了MAC地址,根据标志重设了滤波寄存器,重填了 hash滤波表。 n ethtool_ops(更改或报告网络设备的设置) static const struct ethtool_ops lpc32xx_net_ethtool_ops = { .get_drvinfo = lpc32xx_net_ethtool_getdrvinfo, .get_settings = lpc32xx_net_ethtool_getsettings, .set_settings = lpc32xx_net_ethtool_setsettings, .get_msglevel = lpc32xx_net_ethtool_getmsglevel, .set_msglevel = lpc32xx_net_ethtool_setmsglevel, .get_link = ethtool_op_get_link, }; lpc32xx_net_ethtool_getdrvinfo,用于获得驱动的信息,如驱动版本,总线等 lpc32xx_net_ethtool_getsettings,lpc32xx_net_ethtool_setsettings分别用于获得和设置一些信息。视命令而定 ethtool_op_get_link,获知网络连接状态的
除了以上方法,还有中断 static irqreturn_t __lpc32xx_eth_interrupt(int irq, void *dev_id) { struct net_device *ndev = dev_id; struct netdata_local *pldat = netdev_priv(ndev); u32 tmp;
spin_lock(&pldat->lock);
/* Get the interrupt status */ tmp = __raw_readl(ENET_INTSTATUS(pldat->net_base)); /*每当发生中断时,执行此函数,此函数先查询中断状态寄存器*/ while (tmp) { /* Clear interrupts */ __raw_writel(tmp, ENET_INTCLEAR(pldat->net_base));
/* Transmit complete? */ if (tmp & (MACINT_TXUNDERRUNINTEN | MACINT_TXERRORINTEN | MACINT_TXFINISHEDINTEN | MACINT_TXDONEINTEN)) /*如果发生了重大的溢出错误,发送出现错误,单个或所有描述符发送完成都会调用以下函数*/ { __lpc32xx_handle_xmit(ndev); }
/* Receive buffer available */ if (tmp & (MACINT_RXOVERRUNINTEN | MACINT_RXERRORONINT | MACINT_RXFINISHEDINTEN | MACINT_RXDONEINTEN)) { __lpc32xx_handle_recv(ndev); }
/* Recheck the interrupt status */ tmp = __raw_readl(ENET_INTSTATUS(pldat->net_base)); }
spin_unlock(&pldat->lock);
return IRQ_HANDLED; } 函数处理了发送与接收中断。
追踪__lpc32xx_handle_xmit static void __lpc32xx_handle_xmit(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); struct sk_buff *skb; unsigned int txcidx, *ptxstat, txstat;
txcidx = __raw_readl(ENET_TXCONSUMEINDEX(pldat->net_base)); //获得发送消费者索引(要发送的描述符) while (pldat->last_tx_idx != txcidx) { skb = (struct sk_buff *) pldat->skb[pldat->last_tx_idx]; //获得最近已发送描述符对应的skb /* A buffer is available, get buffer status */ ptxstat = (unsigned int *) pldat->tx_stat_v[pldat->last_tx_idx]; txstat = *ptxstat; //将上一次发送的描述符状态赋给txstat. /* Next buffer and decrement used buffer counter */ pldat->num_used_tx_buffs--; pldat->last_tx_idx++; if (pldat->last_tx_idx >= ENET_TX_DESC) { pldat->last_tx_idx = 0; }
/* Update collision counter */ ndev->stats.collisions += ((txstat >> 21) & 0xF); //更新总的冲突次数
/* Any errors occurred? */ if (txstat & 0x80000000) { if (txstat & 0x20000000) { /* FIFO underrun */ ndev->stats.tx_fifo_errors++; ndev->stats.tx_errors++; } if (txstat & 0x10000000) { /* Late collision */ ndev->stats.tx_aborted_errors++; ndev->stats.tx_errors++; } if (txstat & 0x08000000) { /* Excessive collision */ ndev->stats.tx_aborted_errors++; ndev->stats.tx_errors++; } if (txstat & 0x04000000) { /* Defer limit */ ndev->stats.tx_aborted_errors++; ndev->stats.tx_errors++; } /*更新发送描述符过程中的各种错误统计*/
/* Buffer transmit failed, requeue it */ lpc32xx_net_hard_start_xmit(skb, ndev); //统计完这些信息后,重发。 } else { /* Update stats */ ndev->stats.tx_packets++; ndev->stats.tx_bytes += skb->len;
/* Free buffer */ dev_kfree_skb_irq(skb); //如果没有错误就更新数据缓冲区指针,释放刚使用的skb构。 }
txcidx = __raw_readl(ENET_TXCONSUMEINDEX(pldat->net_base)); }
if (netif_queue_stopped(ndev)) { netif_wake_queue(ndev); //重新启动发送队列 } } 总结:__lpc32xx_handle_xmit,出现 underrun,latecollision,excessivecollision,,excessivedefer错误时,统计错误信息,重新发送 目前的skb。或是其它的中断如txfinishedint和txdoneint则更新发送状态。(似乎对于NoDescriptor这样的中断是按正常 中断处理的)
追踪__lpc32xx_handle_recv static void __lpc32xx_handle_recv(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); struct sk_buff *skb; int rxconsidx, len, ethst; struct rx_status_t *prxstat; u8 *prdbuf;
/* Get the current RX buffer indexes */ rxconsidx = (int) __raw_readl(ENET_RXCONSUMEINDEX(pldat->net_base)); //获得接收消费者索引 while (rxconsidx != (int) __raw_readl(ENET_RXPRODUCEINDEX(pldat->net_base))) //当描述符数组不为空时 { /* Get pointer to receive status */ prxstat = (struct rx_status_t *) pldat->rx_stat_v [rxconsidx]; len = (prxstat->statusinfo & 0x7FF) + 1; //获得要处理的描述符的状态和数据缓冲区的字节数
/* Status error? */ ethst = prxstat->statusinfo; if ((ethst & 0xBF800000) == 0x84000000) { /* Range error, can be ignored */ ethst &= ~0x80000000; } 忽略“接收的包超出包最大限制”的错误。
if (ethst & 0x80000000) { /* Check statuses */ if (prxstat->statusinfo & (1 << 28)) { /* Overrun error */ ndev->stats.rx_fifo_errors++; } else if (prxstat->statusinfo & (1 << 23)) { /* CRC error */ ndev->stats.rx_crc_errors++; } else if (prxstat->statusinfo & (1 << 25)) { /* Length error */ ndev->stats.rx_length_errors++; } else if (prxstat->statusinfo & 0x80000000) { /* Other error */ ndev->stats.rx_length_errors++; } ndev->stats.rx_errors++; } //如果状态字出现错误,则统计错误信息。 else { /* Packet is good */ skb = dev_alloc_skb(len + 8); //申请一个skb结构体,大小为接收的skb的大小再加8 if (!skb) { ndev->stats.rx_dropped++; } else { skb_reserve(skb, 8); //将skb后移8个字节。 prdbuf = skb_put(skb, (len - 0)); //skb尾部增加len大小。 /* Copy packer from buffer */ memcpy(prdbuf, (void *) pldat->rx_buff_v [rxconsidx], len); //将接收描述符拷贝入skb结构中。 /* Pass to upper layer */ skb->protocol = eth_type_trans(skb, ndev); netif_rx(skb); ndev->last_rx = jiffies; ndev->stats.rx_packets++; ndev->stats.rx_bytes += len; } }
/* Increment consume index */ rxconsidx = rxconsidx + 1; if (rxconsidx >= ENET_RX_DESC) { rxconsidx = 0; } __raw_writel((u32) rxconsidx, ENET_RXCONSUMEINDEX(pldat->net_base)); } } //最后更新接收描述符的消费索引 总结:依照对__lpc32xx_handle_recv的分析,它只能对错误信息进行统计,然后会继续接收新的描述符。所谓接收处理即将接收描述符的数据指针指向的数据定入申请的skb,然后发送给上一层协议。
为了分析的完整性,还有平台的resume,remove,suspend方法。 static struct platform_driver lpc32xx_net_driver = { .probe = lpc32xx_net_drv_probe, .remove = __devexit_p(lpc32xx_net_drv_remove), .suspend = lpc32xx_net_drv_suspend, .resume = lpc32xx_net_drv_resume, .driver = { .name = MODNAME, }, };
static int lpc32xx_net_drv_remove(struct platform_device *pdev) { struct net_device *ndev = platform_get_drvdata(pdev); struct netdata_local *pldat = netdev_priv(ndev);
unregister_netdev(ndev);//注销ndev platform_set_drvdata(pdev, NULL);//释放pdev的drvdata dma_free_coherent(&pldat->pdev->dev, pldat->dma_buff_size, (void *) pldat->dma_buff_base_v, (dma_addr_t) pldat->dma_buff_base_p); //释放dma一致性缓冲区。 free_irq(ndev->irq, ndev);//释放中断 iounmap(pldat->net_base);//去除寄存器映射 clk_disable(pldat->clk); clk_put(pldat->clk);//释放时钟 free_netdev(ndev);//释放ndev
return 0; }
static int lpc32xx_net_drv_suspend(struct platform_device *pdev, pm_message_t state) { struct net_device *ndev = platform_get_drvdata(pdev); struct netdata_local *pldat = netdev_priv(ndev);
if (ndev) { if (netif_running(ndev)) //推测设备是否停下来。 { netif_device_detach(ndev); //断开网络设备 __lpc32xx_net_shutdown(pldat); //重启以太网块,复位MAC配置寄存器 clk_disable(pldat->clk); } }
return 0; }
static int lpc32xx_net_drv_resume(struct platform_device *pdev) { struct net_device *ndev = platform_get_drvdata(pdev); struct netdata_local *pldat;
if (ndev) { if (netif_running(ndev)) { pldat = netdev_priv(ndev);
/* Enable interface clock */ clk_enable(pldat->clk); //获得时钟
/* Reset and initialize */ __lpc32xx_eth_reset(pldat); __lpc32xx_eth_init(pldat);
netif_device_attach(ndev); //连接网络设备 } }
return 0; }
总结:网络设备驱动也是有自己的框架,要做的就是填充ndev的成员和操作函数,然后处理好中断接收。还有一个工作就是错误处理和信息统计。
在分析lpc32xx_mii.c时,也遇到一些问题:比如mdiobus_register()看似是注册总线,实际上注册的是设备。而且注册的 设备,像字符设备一样,拥有自己一系列的操作方法。内核中,给出了一种名为mdio_bus的总线,通过mdiobus_register注册的设备在设 备模型上隶属于mdio_bus,但是实际却并没有相应的驱动。这样的设备本身拥有mii_bus总线,问题是这个总线却没有向内核注册。该设备上的方法 就是mii_bus结构中的成员。问题多多,以后再说
linux网络设备应用与驱动编程学习4——模板与实例(C)——其它方法为了便于分析,贴出对应的方法 ndev->open = lpc32xx_net_open; ndev->stop = lpc32xx_net_close; ndev->hard_start_xmit = lpc32xx_net_hard_start_xmit; ndev->tx_timeout = lpc32xx_net_timeout; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ndev->set_multicast_list = lpc32xx_net_set_multicast_list; ndev->ethtool_ops = &lpc32xx_net_ethtool_ops; ndev->do_ioctl = &lpc32xx_net_ioctl; #ifdef CONFIG_NET_POLL_CONTROLLER ndev->poll_controller = lpc32xx_net_poll_controller;
n hard_start_xmit方法(发送数据时调用) static int lpc32xx_net_hard_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); unsigned int len, txidx; u32 *ptxstat; struct txrx_desc_t *ptxrxdesc;
len = skb->len; //skb的大小
spin_lock_irq(&pldat->lock);
if (pldat->num_used_tx_buffs >= (ENET_TX_DESC - 1)) { /* This function should never be called when there are no buffers, log the error */ netif_stop_queue(ndev); spin_unlock_irq(&pldat->lock); dev_err(&pldat->pdev->dev, "BUG! TX request when no free TX buffers!\n"); return 1; }
/* Get the next TX descriptor index */ txidx = __raw_readl(ENET_TXPRODUCEINDEX(pldat->net_base)); //获得发送描述符的生产者索引
/* Setup control for the transfer */ ptxstat = (u32 *) pldat->tx_stat_v [txidx]; *ptxstat = 0; //将描述符状态写为0 ptxrxdesc = (struct txrx_desc_t *) pldat->tx_desc_v [txidx]; ptxrxdesc->control = (len - 1) | 0xC0000000; //描述符的控制字,写入skb的长度,最后希望发送完成后,产生中断。
/* Copy data to the DMA buffer */ memcpy((void *) pldat->tx_buff_v [txidx], skb->data, len); //将skb拷贝到DMA的数据缓冲区中。 /* Save the buffer and increment the buffer counter */ pldat->skb[txidx] = skb; pldat->num_used_tx_buffs++;
/* Start transmit */ txidx++; if (txidx >= ENET_TX_DESC) { txidx = 0; } //生产者索引到最大值时应回0 __raw_writel((u32) txidx, ENET_TXPRODUCEINDEX(pldat->net_base)); //更新发送描述符的生产者索引应由软件完成。
/* Stop queue if no more TX buffers */ if (pldat->num_used_tx_buffs >= (ENET_TX_DESC - 1)) { netif_stop_queue(ndev); }
spin_unlock_irq(&pldat->lock); ndev->trans_start = jiffies; //记录发送的时间戳
return 0; } 这个函数只发送了一个描述符,然后更新了一次发送生产者索引,一个描述符对应一个skb n tx_timeout(发送超时时调用) static void lpc32xx_net_timeout(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev);
/* This should never happen and indicates a problem */ dev_err(&pldat->pdev->dev, "BUG! TX timeout occurred!\n"); } 这个函数从实现的结果看,它只是调试方便才实现的。 n watchdog_timeo(设置超时时间) n set_multicast_list(组播列表改变或设备标志改变时) static void lpc32xx_net_set_multicast_list(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); struct dev_mc_list *mcptr = ndev->mc_list; //组播mac地址 int i, mc_cnt = ndev->mc_count; //组播(地址)数 u32 tmp32, hash_val, hashlo, hashhi; unsigned long flags;
spin_lock_irqsave(&pldat->lock, flags);
/* Set station address */ __lpc32xx_set_mac(pldat, ndev->dev_addr); //将dev_addr中的MAC地址存入站寄存器
tmp32 = RXFLTRW_ACCEPTUBROADCAST | RXFLTRW_ACCEPTPERFECT;
if (ndev->flags & IFF_PROMISC) { tmp32 |= RXFLTRW_ACCEPTUNICAST | RXFLTRW_ACCEPTUNICASTHASH | RXFLTRW_ACCEPTUMULTICASTHASH; } if (ndev->flags & IFF_ALLMULTI) { tmp32 |= RXFLTRW_ACCEPTUMULTICAST; } __raw_writel(tmp32, ENET_RXFILTER_CTRL(pldat->net_base)); //设置滤波存器
/* Set initial hash table */ hashlo = 0x0; hashhi = 0x80000000;
/* 64 bits : multicast address in hash table */ for (i = 0; i < mc_cnt; i++, mcptr = mcptr->next) { hash_val = ether_crc_le(6, mcptr->dmi_addr) & 0x3f;
if (hash_val >= 32) { hashhi |= 1 << (32 - hash_val); } else { hashlo |= 1 << hash_val; } }
__raw_writel(hashlo, ENET_HASHFILTERL(pldat->net_base)); __raw_writel(hashhi, ENET_HASHFILTERH(pldat->net_base)); //填充Hash滤波器表
spin_unlock_irqrestore(&pldat->lock, flags); } 总结:这个函数重填了MAC地址,根据标志重设了滤波寄存器,重填了 hash滤波表。 n ethtool_ops(更改或报告网络设备的设置) static const struct ethtool_ops lpc32xx_net_ethtool_ops = { .get_drvinfo = lpc32xx_net_ethtool_getdrvinfo, .get_settings = lpc32xx_net_ethtool_getsettings, .set_settings = lpc32xx_net_ethtool_setsettings, .get_msglevel = lpc32xx_net_ethtool_getmsglevel, .set_msglevel = lpc32xx_net_ethtool_setmsglevel, .get_link = ethtool_op_get_link, }; lpc32xx_net_ethtool_getdrvinfo,用于获得驱动的信息,如驱动版本,总线等 lpc32xx_net_ethtool_getsettings,lpc32xx_net_ethtool_setsettings分别用于获得和设置一些信息。视命令而定 ethtool_op_get_link,获知网络连接状态的
除了以上方法,还有中断 static irqreturn_t __lpc32xx_eth_interrupt(int irq, void *dev_id) { struct net_device *ndev = dev_id; struct netdata_local *pldat = netdev_priv(ndev); u32 tmp;
spin_lock(&pldat->lock);
/* Get the interrupt status */ tmp = __raw_readl(ENET_INTSTATUS(pldat->net_base)); /*每当发生中断时,执行此函数,此函数先查询中断状态寄存器*/ while (tmp) { /* Clear interrupts */ __raw_writel(tmp, ENET_INTCLEAR(pldat->net_base));
/* Transmit complete? */ if (tmp & (MACINT_TXUNDERRUNINTEN | MACINT_TXERRORINTEN | MACINT_TXFINISHEDINTEN | MACINT_TXDONEINTEN)) /*如果发生了重大的溢出错误,发送出现错误,单个或所有描述符发送完成都会调用以下函数*/ { __lpc32xx_handle_xmit(ndev); }
/* Receive buffer available */ if (tmp & (MACINT_RXOVERRUNINTEN | MACINT_RXERRORONINT | MACINT_RXFINISHEDINTEN | MACINT_RXDONEINTEN)) { __lpc32xx_handle_recv(ndev); }
/* Recheck the interrupt status */ tmp = __raw_readl(ENET_INTSTATUS(pldat->net_base)); }
spin_unlock(&pldat->lock);
return IRQ_HANDLED; } 函数处理了发送与接收中断。
追踪__lpc32xx_handle_xmit static void __lpc32xx_handle_xmit(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); struct sk_buff *skb; unsigned int txcidx, *ptxstat, txstat;
txcidx = __raw_readl(ENET_TXCONSUMEINDEX(pldat->net_base)); //获得发送消费者索引(要发送的描述符) while (pldat->last_tx_idx != txcidx) { skb = (struct sk_buff *) pldat->skb[pldat->last_tx_idx]; //获得最近已发送描述符对应的skb /* A buffer is available, get buffer status */ ptxstat = (unsigned int *) pldat->tx_stat_v[pldat->last_tx_idx]; txstat = *ptxstat; //将上一次发送的描述符状态赋给txstat. /* Next buffer and decrement used buffer counter */ pldat->num_used_tx_buffs--; pldat->last_tx_idx++; if (pldat->last_tx_idx >= ENET_TX_DESC) { pldat->last_tx_idx = 0; }
/* Update collision counter */ ndev->stats.collisions += ((txstat >> 21) & 0xF); //更新总的冲突次数
/* Any errors occurred? */ if (txstat & 0x80000000) { if (txstat & 0x20000000) { /* FIFO underrun */ ndev->stats.tx_fifo_errors++; ndev->stats.tx_errors++; } if (txstat & 0x10000000) { /* Late collision */ ndev->stats.tx_aborted_errors++; ndev->stats.tx_errors++; } if (txstat & 0x08000000) { /* Excessive collision */ ndev->stats.tx_aborted_errors++; ndev->stats.tx_errors++; } if (txstat & 0x04000000) { /* Defer limit */ ndev->stats.tx_aborted_errors++; ndev->stats.tx_errors++; } /*更新发送描述符过程中的各种错误统计*/
/* Buffer transmit failed, requeue it */ lpc32xx_net_hard_start_xmit(skb, ndev); //统计完这些信息后,重发。 } else { /* Update stats */ ndev->stats.tx_packets++; ndev->stats.tx_bytes += skb->len;
/* Free buffer */ dev_kfree_skb_irq(skb); //如果没有错误就更新数据缓冲区指针,释放刚使用的skb构。 }
txcidx = __raw_readl(ENET_TXCONSUMEINDEX(pldat->net_base)); }
if (netif_queue_stopped(ndev)) { netif_wake_queue(ndev); //重新启动发送队列 } } 总结:__lpc32xx_handle_xmit,出现 underrun,latecollision,excessivecollision,,excessivedefer错误时,统计错误信息,重新发送 目前的skb。或是其它的中断如txfinishedint和txdoneint则更新发送状态。(似乎对于NoDescriptor这样的中断是按正常 中断处理的)
追踪__lpc32xx_handle_recv static void __lpc32xx_handle_recv(struct net_device *ndev) { struct netdata_local *pldat = netdev_priv(ndev); struct sk_buff *skb; int rxconsidx, len, ethst; struct rx_status_t *prxstat; u8 *prdbuf;
/* Get the current RX buffer indexes */ rxconsidx = (int) __raw_readl(ENET_RXCONSUMEINDEX(pldat->net_base)); //获得接收消费者索引 while (rxconsidx != (int) __raw_readl(ENET_RXPRODUCEINDEX(pldat->net_base))) //当描述符数组不为空时 { /* Get pointer to receive status */ prxstat = (struct rx_status_t *) pldat->rx_stat_v [rxconsidx]; len = (prxstat->statusinfo & 0x7FF) + 1; //获得要处理的描述符的状态和数据缓冲区的字节数
/* Status error? */ ethst = prxstat->statusinfo; if ((ethst & 0xBF800000) == 0x84000000) { /* Range error, can be ignored */ ethst &= ~0x80000000; } 忽略“接收的包超出包最大限制”的错误。
if (ethst & 0x80000000) { /* Check statuses */ if (prxstat->statusinfo & (1 << 28)) { /* Overrun error */ ndev->stats.rx_fifo_errors++; } else if (prxstat->statusinfo & (1 << 23)) { /* CRC error */ ndev->stats.rx_crc_errors++; } else if (prxstat->statusinfo & (1 << 25)) { /* Length error */ ndev->stats.rx_length_errors++; } else if (prxstat->statusinfo & 0x80000000) { /* Other error */ ndev->stats.rx_length_errors++; } ndev->stats.rx_errors++; } //如果状态字出现错误,则统计错误信息。 else { /* Packet is good */ skb = dev_alloc_skb(len + 8); //申请一个skb结构体,大小为接收的skb的大小再加8 if (!skb) { ndev->stats.rx_dropped++; } else { skb_reserve(skb, 8); //将skb后移8个字节。 prdbuf = skb_put(skb, (len - 0)); //skb尾部增加len大小。 /* Copy packer from buffer */ memcpy(prdbuf, (void *) pldat->rx_buff_v [rxconsidx], len); //将接收描述符拷贝入skb结构中。 /* Pass to upper layer */ skb->protocol = eth_type_trans(skb, ndev); netif_rx(skb); ndev->last_rx = jiffies; ndev->stats.rx_packets++; ndev->stats.rx_bytes += len; } }
/* Increment consume index */ rxconsidx = rxconsidx + 1; if (rxconsidx >= ENET_RX_DESC) { rxconsidx = 0; } __raw_writel((u32) rxconsidx, ENET_RXCONSUMEINDEX(pldat->net_base)); } } //最后更新接收描述符的消费索引 总结:依照对__lpc32xx_handle_recv的分析,它只能对错误信息进行统计,然后会继续接收新的描述符。所谓接收处理即将接收描述符的数据指针指向的数据定入申请的skb,然后发送给上一层协议。
为了分析的完整性,还有平台的resume,remove,suspend方法。 static struct platform_driver lpc32xx_net_driver = { .probe = lpc32xx_net_drv_probe, .remove = __devexit_p(lpc32xx_net_drv_remove), .suspend = lpc32xx_net_drv_suspend, .resume = lpc32xx_net_drv_resume, .driver = { .name = MODNAME, }, };
static int lpc32xx_net_drv_remove(struct platform_device *pdev) { struct net_device *ndev = platform_get_drvdata(pdev); struct netdata_local *pldat = netdev_priv(ndev);
unregister_netdev(ndev);//注销ndev platform_set_drvdata(pdev, NULL);//释放pdev的drvdata dma_free_coherent(&pldat->pdev->dev, pldat->dma_buff_size, (void *) pldat->dma_buff_base_v, (dma_addr_t) pldat->dma_buff_base_p); //释放dma一致性缓冲区。 free_irq(ndev->irq, ndev);//释放中断 iounmap(pldat->net_base);//去除寄存器映射 clk_disable(pldat->clk); clk_put(pldat->clk);//释放时钟 free_netdev(ndev);//释放ndev
return 0; }
static int lpc32xx_net_drv_suspend(struct platform_device *pdev, pm_message_t state) { struct net_device *ndev = platform_get_drvdata(pdev); struct netdata_local *pldat = netdev_priv(ndev);
if (ndev) { if (netif_running(ndev)) //推测设备是否停下来。 { netif_device_detach(ndev); //断开网络设备 __lpc32xx_net_shutdown(pldat); //重启以太网块,复位MAC配置寄存器 clk_disable(pldat->clk); } }
return 0; }
static int lpc32xx_net_drv_resume(struct platform_device *pdev) { struct net_device *ndev = platform_get_drvdata(pdev); struct netdata_local *pldat;
if (ndev) { if (netif_running(ndev)) { pldat = netdev_priv(ndev);
/* Enable interface clock */ clk_enable(pldat->clk); //获得时钟
/* Reset and initialize */ __lpc32xx_eth_reset(pldat); __lpc32xx_eth_init(pldat);
netif_device_attach(ndev); //连接网络设备 } }
return 0; }
总结:网络设备驱动也是有自己的框架,要做的就是填充ndev的成员和操作函数,然后处理好中断接收。还有一个工作就是错误处理和信息统计。
在分析lpc32xx_mii.c时,也遇到一些问题:比如mdiobus_register()看似是注册总线,实际上注册的是设备。而且注册的 设备,像字符设备一样,拥有自己一系列的操作方法。内核中,给出了一种名为mdio_bus的总线,通过mdiobus_register注册的设备在设 备模型上隶属于mdio_bus,但是实际却并没有相应的驱动。这样的设备本身拥有mii_bus总线,问题是这个总线却没有向内核注册。该设备上的方法 就是mii_bus结构中的成员。问题多多,以后再说 |
|
来自: WUCANADA > 《ethernet》