linux 网络驱动
谨以此文纪念过往的岁月一.前言在linux中网络驱动也是一个大头,如何去理解网络驱动是作为一个linux驱动工程师必备的技能。不过同样的设备,在不同人的手中会有不同的效果,其原因就在于驱动的好与否。二.设备注册学习网络的驱动与学习普通cdev驱动一样,都是学习其模板,然后再创造学习。在学习网络驱动过程中,我们忽略其对硬件的具体操作,这样会更具有通用性。以dm9000A为例。网络驱动亦如usb驱动一样,其内核将许多工作都完成了。DM9000A认采用了platform bus的办法来实现设备与驱动的匹配。在前文中也说,其设备所属的设备类型与设备的类型并没有关系,就如usb总线一下,其usb键盘属于cdev,而usb storage则属于block设备,但是他们都是usb总线设备。网络设备也是一样的。所以这里的设备采用platform总线反而更加的直观。对于platform设备的注册和驱动的注册咱们就不说了。不过在这之前还需要说明一点东西,即是内存映射,该映射非彼映射。以s3c6410和DM9000A为例,DM9000A的data和address线连接在cpu的srom1的接口。在里面就需要理解一个宏定义#define S3C64XX_PA_DM9000 (0x18000000)和#define DM9000_CMD 0X04第一个就是DM9000的物理地址为什么是0x18000000,因为dm9000的data和addr是连接在srom1的接口,而srom1的起始地址为0x18000000,这里是用于外设的地址。第二个是cmd地址为什么是0x04,因为dm9000的cmd与data区分的那个引脚连接在srom1的addr[2]上,故cmd的地址为0x04,如果学过数电这个就很好懂的。我们就从设备的探测开始来开始我们的网络驱动之旅。2.1 设备探测在网络设备探测中,其各个不同的设备的硬件初始化是不同的,不过其本质还是一样一样的。void dm9000_probe(struct platform_device *pdev){ struct net_device *ndev; --这个东西可是核心 ndev = alloc_etherdev(sizeof (struct private_data)); --后面的参数其实是ndev的私用数据 SET_NETDEV_DEV(ndev, &pdev->dev); --#define SET_NETDEV_DEV(net, pdev) ((net)->dev.parent = (pdev)) 设置ndev的父设备为pdev ether_setup(ndev); --函数的核心是初始化ndev的成员。 --下面是设置ndev函数成员。 ndev->open = &dm9000_open; --设备打开 ndev->hard_start_xmit = &dm9000_start_xmit; --开始传输 ndev->tx_timeout = &dm9000_timeout; --定时溢出处理函数 ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ndev->stop = &dm9000_stop; --关闭。 ndev->set_multicast_list = &dm9000_hash_table; --设置组播地址。 db->msg_enable = NETIF_MSG_LINK; db->mii.phy_id_mask = 0x1f; --这个就是mii接口 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; platform_set_drvdata(pdev, ndev); --将ndev设置为pdev的私用函数,留着以后卸载时用 register_netdev(ndev); --注册ndev}probe中对于ndev的操作分为三部 开辟ndev -> 初始化ndev ->注册ndev,有没有发现这个过程怎么这么类似于其他的驱动模型呢。这里面的核心是ndev2.2 网络设备的打开和释放在open中,往往都会去申请中断,对硬件进行复位,并且激活设备发送队列,以dm9000_open为例static int dm9000_open(struct net_device *dev){ if (request_irq(dev->irq, &dm9000_interrupt, DM9000_IRQ_FLAGS, dev->name, dev)) --申请中断资源 return -EAGAIN; mii_check_media(&db->mii, netif_msg_link(db), 1); --其核心在于此,检测mii的接口状态 netif_start_queue(dev); --激活设备发送队列 return 0;}在close中则相反,主要是资源的释放和停止设备发送队列。static int dm9000_stop(struct net_device *ndev){ board_info_t *db = (board_info_t *) ndev->priv; netif_stop_queue(ndev); netif_carrier_off(ndev); free_irq(ndev->irq, ndev); return 0;}2.3 数据发送将从上层传入的数据发送的media中。static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev){ unsigned long flags; board_info_t *db = (board_info_t *) dev->priv; if (db->tx_pkt_cnt > 1) --包数不超过2个 return 1; spin_lock_irqsave(&db->lock, flags); writeb(DM9000_MWCMD, db->io_addr); (db->outblk)(db->io_data, skb->data, skb->len); dev->stats.tx_bytes += skb->len; db->tx_pkt_cnt++; if (db->tx_pkt_cnt == 1) { iow(db, DM9000_TXPLL, skb->len & 0xff); iow(db, DM9000_TXPLH, (skb->len >> 8) & 0xff); iow(db, DM9000_TCR, TCR_TXREQ); dev->trans_start = jiffies; --保存时间戳 } else { db->queue_pkt_len = skb->len; netif_stop_queue(dev); } spin_unlock_irqrestore(&db->lock, flags); dev_kfree_skb(skb); return 0;}2.4 中断处理对于media每次接送或发送完成一帧数据后都会产生一个中断,根据中断flags来判断是发送完成还是接受完成。static irqreturn_t dm9000_interrupt(int irq, void *dev_id){ if (int_status & ISR_PRS) --接受数据中断。 dm9000_rx(dev); if (int_status & ISR_PTS) dm9000_tx_done(dev, db);}static void dm9000_tx_done(struct net_device *dev, board_info_t * db){ netif_wake_queue(dev); --唤醒等待队列}2.5 数据接受对于数据接受而言,其实就是将数据从media的缓冲区读出,然后提交给上层。读取数据的真正核心是下面的代码static void dm9000_rx(struct net_device *dev){ skb = dev_alloc_skb(RxLen + 4)); --分配一个skb skb_reserve(skb, 2); --保留2两个字节 rdptr = (u8 *) skb_put(skb, RxLen - 4); --硬件读取数据 (db->inblk)(db->io_data, rdptr, RxLen); dev->stats.rx_bytes += RxLen; skb->protocol = eth_type_trans(skb, dev); --获取上册协议类型 netif_rx(skb); --向上层提交数据包 dev->stats.rx_packets++;}2.6 在驱动中会设定一个定时器该定时器的功能就是定时查询mii的状态。三.总结网络驱动的核心在于ndev和skb,需要好好去理解。这次的网络驱动学习的很是粗糙,因为网络驱动是一个很大的部分,需要慢慢去细嚼其中的每一点。前途是光明的,不过道路是曲折的。