Linux 驱动框架---dm9000分析
前面学习了下Linux下的网络设备驱动程序的框架Linux 驱动框架---net驱动框架,了解了一个网络设备驱动大致结构,但还是不太清楚具体的细节处是怎么处理的,所以今天就来以dm9000这个网上教程最多的网卡设备驱动实例来详细看一下Linux网络设备驱动的开发更细节的内容。这个驱动比较综合先是以platform总线为基础,中间有用到互斥,延迟任务等知识所以需要先简单了解一下用到的框架的基本原理。因为是基于platform总线的所以肯定是分设备和驱动两部分这里直接上代码了,然后后面在详细阐述工作过程。先看设备的相关代码,其次是关于platform的驱动框架的内容这里不看可以参考。
设备代码
static struct resource smdkv210_dm9000_resources[] = { [0] = DEFINE_RES_MEM(S5PV210_PA_SROM_BANK1, 1), [1] = DEFINE_RES_MEM(S5PV210_PA_SROM_BANK1 + 4, 4), [2] = DEFINE_RES_NAMED(IRQ_EINT(7), 1, NULL, IORESOURCE_IRQ \ | IORESOURCE_IRQ_HIGHLEVEL), }; static struct dm9000_plat_data smdkv210_dm9000_platdata = { .flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM, .dev_addr = { 0x08, 0x90, 0x00, 0xa0, 0x02, 0x10 }, }; static struct platform_device smdkv210_dm9000 = { .name = "dm9000", .id = -1, .num_resources = ARRAY_SIZE(smdkv210_dm9000_resources), .resource = smdkv210_dm9000_resources, .dev = { .platform_data = &smdkv210_dm9000_platdata, }, };
驱动部分
static struct platform_driver dm9000_driver = { .driver = { .name = "dm9000", .owner = THIS_MODULE, .pm = &dm9000_drv_pm_ops, .of_match_table = of_match_ptr(dm9000_of_matches), }, .probe = dm9000_probe, .remove = dm9000_drv_remove, };
这里把网络设备驱动的配置过程放到了总线的probe接口中来实现,本质上没有区别。
/* * Search DM9000 board, allocate space and register it */ static int dm9000_probe(struct platform_device *pdev) {
//获取到前面定义,绑定的platform设备数据 struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev); struct board_info *db; /* Point a board information structure */ struct net_device *ndev; const unsigned char *mac_src; int ret = 0; int iosize; int i; u32 id_val; //驱动支持设备处理相关 if (!pdata) { pdata = dm9000_parse_dt(&pdev->dev); if (IS_ERR(pdata)) return PTR_ERR(pdata); } /* Init network device */ ndev = alloc_etherdev(sizeof(struct board_info)); if (!ndev) return -ENOMEM; SET_NETDEV_DEV(ndev, &pdev->dev); dev_dbg(&pdev->dev, "dm9000_probe()\n"); /* setup board info structure */ db = netdev_priv(ndev); //绑定设备 db->dev = &pdev->dev; db->ndev = ndev; spin_lock_init(&db->lock); mutex_init(&db->addr_lock); //初始化一个delay_work INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work); //获取网卡硬件资源,包括内存和中断 db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (db->addr_res == NULL || db->data_res == NULL || db->irq_res == NULL) { dev_err(db->dev, "insufficient resources\n"); ret = -ENOENT; goto out; } db->irq_wake = platform_get_irq(pdev, 1); if (db->irq_wake >= 0) { dev_dbg(db->dev, "wakeup irq %d\n", db->irq_wake); //申请中断 ret = request_irq(db->irq_wake, dm9000_wol_interrupt, IRQF_SHARED, dev_name(db->dev), ndev); if (ret) { dev_err(db->dev, "cannot get wakeup irq (%d)\n", ret); } else { //设置中断唤醒 /* test to see if irq is really wakeup capable */ ret = irq_set_irq_wake(db->irq_wake, 1); if (ret) { dev_err(db->dev, "irq %d cannot set wakeup (%d)\n", db->irq_wake, ret); ret = 0; } else { irq_set_irq_wake(db->irq_wake, 0); db->wake_supported = 1; } } } //标记资源使用了 iosize = resource_size(db->addr_res); db->addr_req = request_mem_region(db->addr_res->start, iosize, pdev->name); if (db->addr_req == NULL) { dev_err(db->dev, "cannot claim address reg area\n"); ret = -EIO; goto out; } //映射 db->io_addr = ioremap(db->addr_res->start, iosize); if (db->io_addr == NULL) { dev_err(db->dev, "failed to ioremap address reg\n"); ret = -EINVAL; goto out; } iosize = resource_size(db->data_res); db->data_req = request_mem_region(db->data_res->start, iosize, pdev->name); if (db->data_req == NULL) { dev_err(db->dev, "cannot claim data reg area\n"); ret = -EIO; goto out; } db->io_data = ioremap(db->data_res->start, iosize); if (db->io_data == NULL) { dev_err(db->dev, "failed to ioremap data reg\n"); ret = -EINVAL; goto out; } /* fill in parameters for net-dev structure */ ndev->base_addr = (unsigned long)db->io_addr; ndev->irq = db->irq_res->start; //配置IO /* ensure at least we have a default set of IO routines */ dm9000_set_io(db, iosize); /* check to see if anything is being over-ridden */ if (pdata != NULL) { /* check to see if the driver wants to over-ride the * default IO width */ if (pdata->flags & DM9000_PLATF_8BITONLY) dm9000_set_io(db, 1); if (pdata->flags & DM9000_PLATF_16BITONLY) dm9000_set_io(db, 2); if (pdata->flags & DM9000_PLATF_32BITONLY) dm9000_set_io(db, 4); /* check to see if there are any IO routine * over-rides */ if (pdata->inblk != NULL) db->inblk = pdata->inblk; if (pdata->outblk != NULL) db->outblk = pdata->outblk; if (pdata->dumpblk != NULL) db->dumpblk = pdata->dumpblk; db->flags = pdata->flags; } #ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL db->flags |= DM9000_PLATF_SIMPLE_PHY; #endif //复位网卡 dm9000_reset(db); //获取设备硬件信息 /* try multiple times, DM9000 sometimes gets the read wrong */ for (i = 0; i < 8; i++) { id_val = ior(db, DM9000_VIDL); id_val |= (u32)ior(db, DM9000_VIDH) << 8; id_val |= (u32)ior(db, DM9000_PIDL) << 16; id_val |= (u32)ior(db, DM9000_PIDH) << 24; if (id_val == DM9000_ID) break; dev_err(db->dev, "read wrong id 0x%08x\n", id_val); } if (id_val != DM9000_ID) { dev_err(db->dev, "wrong id: 0x%08x\n", id_val); ret = -ENODEV; goto out; } /* Identify what type of DM9000 we are working on */ id_val = ior(db, DM9000_CHIPR); dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val); //根据得到信息判断设备类型 switch (id_val) { case CHIPR_DM9000A: db->type = TYPE_DM9000A; break; case CHIPR_DM9000B: db->type = TYPE_DM9000B; break; default: dev_dbg(db->dev, "ID %02x => defaulting to DM9000E\n", id_val); db->type = TYPE_DM9000E; } /* dm9000a/b are capable of hardware checksum offload */ if (db->type == TYPE_DM9000A || db->type == TYPE_DM9000B) { ndev->hw_features = NETIF_F_RXCSUM | NETIF_F_IP_CSUM; ndev->features |= ndev->hw_features; } /* from this point we assume that we have found a DM9000 */ // 网络接口配置 /* driver system function */ ether_setup(ndev); //设备操作接口绑定 ndev->netdev_ops = &dm9000_netdev_ops; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ndev->ethtool_ops = &dm9000_ethtool_ops; //接口绑定 db->msg_enable = NETIF_MSG_LINK; db->mii.phy_id_mask = 0x1f; db->mii.reg_num_mask = 0x1f; db->mii.force_media = 0; db->mii.full_duplex = 0; db->mii.dev = ndev; db->mii.mdio_read = dm9000_phy_read; db->mii.mdio_write = dm9000_phy_write; mac_src = "eeprom"; //通过接口读取网卡硬件信息 /* try reading the node address from the attached EEPROM */ for (i = 0; i < 6; i += 2) dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i); if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) { mac_src = "platform data"; memcpy(ndev->dev_addr, pdata->dev_addr, ETH_ALEN); } if (!is_valid_ether_addr(ndev->dev_addr)) { /* try reading from mac */ mac_src = "chip"; for (i = 0; i < 6; i++) ndev->dev_addr[i] = ior(db, i+DM9000_PAR); } if (!is_valid_ether_addr(ndev->dev_addr)) { dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please " "set using ifconfig\n", ndev->name); eth_hw_addr_random(ndev); mac_src = "random"; } //绑定设备信息 platform_set_drvdata(pdev, ndev); ret = register_netdev(ndev); if (ret == 0) printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n", ndev->name, dm9000_type_to_char(db->type), db->io_addr, db->io_data, ndev->irq, ndev->dev_addr, mac_src); return 0; out: dev_err(db->dev, "not found (%d).\n", ret); dm9000_release_board(pdev, db); free_netdev(ndev); return ret; }
处理过程大致是
1、申请一个网络设备内存空间+驱动私有数据结构空间,存放顺序是net_device+priv_data。
2、初始化锁和delay_work等私有数据结构成员。
3、DM9000的驱动支持唤醒,所以中间这一段是唤醒中断的设置。
4、内核地址空间申请这里涉及request_mem_region和ioremap的区别的辨析。简单可以理解request_mem_region只是向内核通知这一点地址空间已经被占用但未建立MMU表项。
5、前面声明完地址使用后对特定地址进行MMU映射map包括控制地址和数据地址两部分。
6、调用dm9000_ser_io()初始化私有数据的读写接口。这里实际是执行的它dm9000_set_io(db, 2);最终实际绑定的函数接口和具体的SOC对网卡的接口方式有关。三星这里采用
的是将网卡作为类ROM处理所以读写就是如同读rom。写实际是iowrite16_rep()对应读取就是ioread16_rep();处理之外IO实现还可以有设备数据提供。
7、在此之前还没有对dm9000 网卡进行任何设置,接下来才开始配置网卡芯片。
8、复位网卡芯片dm9000_reset()。通过操作dm9000的控制寄存器实现。寄存器写入操作就是先写入寄存器地址在写入值。复位了两次
9、读取dm9000的芯片ID号,读取的过程很简单直接写入读写地址然后读数据寄存器。识别dm9000网卡类型保存在db->type。因为这个驱动支持多种dm9000类型的网卡。
10、如果是DM9000A和DM9000B网卡他们支持新的特性所以要在网络设备的特性标识中增加新的特性支持标志。
11、调用ether_setup初始化网络设备。
12、接下来就是绑定网络设备的操作接口;
ndev->netdev_ops = &dm9000_netdev_ops;//网络设备功能层实现的内容具体后面分析
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);//超时管理的吧
ndev->ethtool_ops = &dm9000_ethtool_ops;//网络工具功能接口
13、接下来的就是phy接口的配置和接口函数绑定了,dm9000还支持mii接口phy管理。
14、设置网卡MAC地址,这里有多种机制,平台数据,网卡自己的eeprom和随机。
15、最后就是注册网卡设备到系统了,并用私有数据替换设备platform_data。
这个过程只是网络设备接口层的注册的基本流程,具体的网络设备接口层下的功能层代码在第12步时后完成绑定,现在来看下具体的实现。
网络设备功能层
分析网络设备接口层的实现,DM9000的网卡实现了一下这些接口一个个来看都是怎么样的实现机制。
static const struct net_device_ops dm9000_netdev_ops = { .ndo_open = dm9000_open, .ndo_stop = dm9000_stop, .ndo_start_xmit = dm9000_start_xmit, .ndo_tx_timeout = dm9000_timeout, .ndo_set_rx_mode = dm9000_hash_table, .ndo_do_ioctl = dm9000_ioctl, .ndo_change_mtu = eth_change_mtu, .ndo_set_features = dm9000_set_features, .ndo_validate_addr = eth_validate_addr, .ndo_set_mac_address = eth_mac_addr, #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = dm9000_poll_controller, #endif };
dm9000_open
1、状态检查和初始化所需变量初始化然后初始化网卡dm9000_init_dm9000。就是一大堆dm9000网卡的设置看明白应该需要参考datasheet。需要知道的是他配置了发送完成中断和数据接受完成中断。
2、中断申请并绑定中毒action然后使能网卡中断。这里的中断函数回头再来细看。
3、调用netif_start_queue 激活发送queue,并启动delay_work工作,工作函数在probe中指定了。
dm9000_stop
1、停止delay_work
2、netif_stop_queue();
3、netif_carrier_off();
4、释放中断、关闭网卡。
dm9000_start_xmit()需要互斥
1、直接拷贝数局到DM9000的RAM中
2、统计信息更新
3、如果是网卡发送的第一个包则可以直接发送数据,否则将数据包排队。
4、释放上层传来的sk_buff,因为网卡驱动已经记录了必要信息。
dm9000_timeout
当消息发送超时时会被调用
1、读取寄存器并保存
2、调用netif_stop_queue(),重新初始化网卡并开启中断。
3、重启发送netif_wake_queue(dev);恢复寄存器。
未发现明显重发机制。
dm9000_hash_table
1、
dm9000_ioctl
1、
dm9000_set_features
1、修改网络设备特性标志。
2、修改硬件及寄存器配置。
eth_change_mtu
1、很简单直接修改net_device->mtu 成员
eth_validate_addr
1、按MAC地址的规则检查MAC地址信息返回非法与否。
eth_mac_addr
1、检查是否支持修改,检查新地址是否合法。
2、执行修改硬件相关,并拷贝到网络设备结构体。
dm9000_ethtool_ops 接口
static const struct ethtool_ops dm9000_ethtool_ops = { .get_drvinfo = dm9000_get_drvinfo, .get_settings = dm9000_get_settings, .set_settings = dm9000_set_settings, .get_msglevel = dm9000_get_msglevel, .set_msglevel = dm9000_set_msglevel, .nway_reset = dm9000_nway_reset, .get_link = dm9000_get_link, .get_wol = dm9000_get_wol, .set_wol = dm9000_set_wol, .get_eeprom_len = dm9000_get_eeprom_len, .get_eeprom = dm9000_get_eeprom, .set_eeprom = dm9000_set_eeprom, };
这个接口比较简单基本就是对网络设备和驱动的属性信息的修改和获取。最后再来看看中断中的执行流程。
网卡中断
static irqreturn_t dm9000_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; board_info_t *db = netdev_priv(dev); int int_status; unsigned long flags; u8 reg_save; dm9000_dbg(db, 3, "entering %s\n", __func__); /* A real interrupt coming */ /* holders of db->lock must always block IRQs */ spin_lock_irqsave(&db->lock, flags); /* Save previous register address */ reg_save = readb(db->io_addr); dm9000_mask_interrupts(db); /* Got DM9000 interrupt status */ int_status = ior(db, DM9000_ISR); /* Got ISR */ iow(db, DM9000_ISR, int_status); /* Clear ISR status */ if (netif_msg_intr(db)) dev_dbg(db->dev, "interrupt status %02x\n", int_status); /* Received the coming packet */ if (int_status & ISR_PRS) dm9000_rx(dev); /* Trnasmit Interrupt check */ if (int_status & ISR_PTS) dm9000_tx_done(dev, db); if (db->type != TYPE_DM9000E) { if (int_status & ISR_LNKCHNG) { /* fire a link-change request */ schedule_delayed_work(&db->phy_poll, 1); } } dm9000_unmask_interrupts(db); /* Restore previous register address */ writeb(reg_save, db->io_addr); spin_unlock_irqrestore(&db->lock, flags); return IRQ_HANDLED; }
基本流程就是关中断、获取锁,然后读取寄存器当前状态并保存,清除网卡中断标志。
如果是接收到数据则执行dm9000_rx,如果是发送数据的ack则需要执行 dm9000_tx_done 等。
然后开中断、恢复寄存器。其中重点看看dm9000_rx和dm9000_tx_done。
dm9000_rx
1、读取网卡状态寄存器判断数据是否已经可以读取或是否出错。
2、从网卡RAM读取数据到内存。
3、包合法性检查,同时维护网络设备统计信息。
4、数据包合法,申请sk_buff 开始打包成sockt数据包,这里需要循环读直到数据全部被读取
5、检查是接收中断直接透穿到上层或者调用dm9000_rx在其中调用netif_rx(skb);向上层通报。反之当是发送完成中断则执行dm9000_tx_done。
重点看一下dm9000_tx_done
static void dm9000_tx_done(struct net_device *dev, board_info_t *db) { int tx_status = ior(db, DM9000_NSR); /* Got TX status */ if (tx_status & (NSR_TX2END | NSR_TX1END)) { /* One packet sent complete */ db->tx_pkt_cnt--; dev->stats.tx_packets++; if (netif_msg_tx_done(db)) dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status); /* Queue packet check & send */ if (db->tx_pkt_cnt > 0) dm9000_send_packet(dev, db->queue_ip_summed, ======》此处联系前面发送过程中如果当前包还未发送完成时的处理就可以明白 db->queue_pkt_len); netif_wake_queue(dev); } }
基本的执行流程就是:‘
1、获取网卡发送状态信息
2、根据状态前一个发送给完毕则开始发送下一个包,同时更新之前的的包的状态。
这里回头在看看发送时干了什么才能让发送连续起来,先不考虑超时问题。这个成员会因为新增需要发送的数据包而tx_pkt_cnt++,而在成功发送后tx_pkt_cnt--,所以地下这部分时发送的代码节选
if (db->tx_pkt_cnt == 1) { dm9000_send_packet(dev, skb->ip_summed, skb->len); } else { /* Second packet */ db->queue_pkt_len = skb->len; db->queue_ip_summed = skb->ip_summed; netif_stop_queue(dev); }
如果之前的数据已经发送完了,则不用关直接调用网卡的数据包发送;反之如果之前的数据还为发送完成则将当前数据包暂时挂起,此时对于网卡来说已经有两包数据需要发送了此时注意netif_stop_queue()这个调用会阻止上层继续调用设备接口层的数据包发送接口所以上层会暂时向设备接口层请求数据发送。此时再到中断中的dm9000_tx_done中来db->tx_pkt_cnt--;之后再调用网卡的数据包发送接口将数据发送到网卡,之后netif_wake_queue()再次唤醒发送队列,此时上层就有可以调用设备接口层的dm9000_start_xmit()函数发送数据了。
总结
以上就是网络设备dm9000系列的驱动程序的大致内容了,从中可以发现驱动开始过程实现的内容还是大部分在硬件操作相关的和网络驱动架构打交道的都是通过网络驱动框架提供API接口完成的。