专题4-网卡驱动程序设计

一.网卡驱动架构分析
1.Linux网络子系统
从上到下依次如下图:

(1)系统调用接口
主要是响应用户空间的系统请求,为应用程序提供访问网络子系统的统一方法。
(2)协议无关接口
虽然网络子系统有很多协议,但是我们不必要为了用具体的某一种协议而建立一套相对应的访问接口,也就是说我们可以用一套统一的接口访问不同的网络协议,这个统一的接口就是由协议无关接口层实现。提供通用的方法使用传输层协议。
(3)网络协议栈
具体的各种网络协议的实现,比如TCP,UDP等。
(4)设备无关接口
网络协议与具体的物理网卡之间通信的通用接口。
(5)网卡驱动程序
针对一个具体的物理硬件编写的驱动程序。也就是我们要讨论的核心,它主要是在网络协议之间和物理网卡之间进行数据的交互从而实现Linux的网络功能。
2.重要数据结构
(1)net_device结构:
类比于之前学习到的字符设备以及平台设备,他们都有属于自己的一个结构体来描述他们在内核中存在的形式,然后围绕着该结构的注册和初始化来完成设备驱动程序的设计。当然我们的网卡也有自己的结构体来描述他在内核中的存在形式。其中重要的成员有:
----》char name[IFNAMSIZ]表示设备名,如:
eth%d,内核会自动把%d换成数字。
----》unsigned long base_addr I/O 基地址,也就是我们网卡芯片初始寄存器在整个系统中的物理地址。
----》const struct net_device_ops *netdev_ops;
类比于字符设备,每一个字符设备都有一个file_operarions和他对应表示它所支持的操作。同样网卡设备也有属于自己的net_device_ops描述它所支持的操作。当然它可以作为一个独立的结构。比如:
static const struct net_device_ops dm9000_netdev_ops =
{
.ndo_open= dm9000_open,
.ndo_open= dm9000_open,
.ndo_stop= dm9000_stop,
.ndo_start_xmit = dm9000_start_xmit,
.ndo_do_ioctl= dm9000_ioctl,
.ndo_validate_addr= eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
};
(2)网络数据包
在网络子系统中网络协议栈会向下给物理网卡发送数据包,物理网卡也会将来自外部的数据包向上传递给网络协议栈。而如何描述数据包再Linux内核中的存在就是由网络数据包这个结构来实现。实际上他就是一个套接字缓冲区结构。也就是struct sk_buff 即一个sk_buff结构就是一个网络包,指向sk_buff的指针通常被命名为skb。示意图如下,可见真正的有效数据是在data开始tail结束的。data和tail虽然可变化,但是不可以超出预先分配的范围。
3.架构分析
(1)初始化module_init是后期用到的,前期使用init_module函数作为驱动的入口。

(2)发送(net_send_packet函数)
----》发送期间不希望上层协议再往物理网卡发送数据,因为处理不及时会丢失数据,所以第一步是要通知上层协议,正在发送数据,暂停从上往下写入数据。使用netif_stop_queue(dev);函数
----》将网络数据包的数据写入物理网卡的寄存器发送出去。----》释放掉网络数据包的空间。使用dev_kfree_skb (skb);函数
----》由于网卡发送完数据以后会产生发送中断,所以在发送中断处理函数(request_irq--》net_interrupt)里面,会有重新使能上层协议继续往物理网卡写入数据的操作。使用netif_wake_queue(dev);函数如下图:

(3)接收
-----》发送不同于发送数据,接收是在接收中断处理函数中完成的。接收到一个完整的数据包会产生接收中断。在中断处理程序中从这里入口接受过程:case ISQ_RECEIVER_EVENT:
               /* Got a packet(s). */
               net_rx(dev);
               break;
进入net_rx以后:-----》读取接收的状态(看是否正确接收)和接收到的数据的长度。
status = readword(ioaddr, RX_FRAME_PORT);ength = readword(ioaddr, RX_FRAME_PORT);
-----》构造skb,存放网络数据包,要加上头部数据所以要加2个字节的长度skb = dev_alloc_skb(length + 2);
-----》从物理网卡寄存器读出数据存放到skb里面readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> 1);
-----》将接收到的数据包skb往上传送给协议栈处理。netif_rx(skb);
-----》接收到的包的个数以及接收到的数据长度调整。dev->stats.rx_packets++;
dev->stats.rx_bytes += length;
思维导图如下:

二.回环网卡驱动
1.回环网卡和普通网卡的区别是他是虚拟的不是实际的物理网卡,它相当于吧普通网卡的发送端和接收端短接在一起。
2.在内核源代码里的回环网卡程序(drivers/net/loopback.c)不是以一个模块的形式给出,但是他的初始化(loopback_net_init)和退出函数(loopback_dev_free)会被内核的其他部分调用到。
3.参照网卡初始化的流程图进行设计驱动程序,其中分配net_device结构不能用alloc_etherdev函数,因为该函数是分配以太网卡的结构体的,要用alloc_netdev函数来给回环网卡分配结构体。参考内核源代码别人如何用使用这个函数alloc_netdev(0, "lo", loopback_setup);第一个0表示net_device这个结构体的私有成员的大小,一般选择0,第二个表示网卡名字,ifconfig显示的名称,第三个就是具体的网卡结构体的初始化函数指针。
struct net_device *dev;
dev = alloc_netdev(0, "lo", loopback_setup);
最终会调用到loopback_setup函数。(至于在将函数指针作为参数的时候如何传递形参,就要复习C语言了)
4.回环网卡不需要初始化硬件。所以直接注册回环网卡的结构体到内核(第三步)。最后指定回环网卡注册到网络子系统。
net->loopback_dev = dev;这一步很关键。
5.具体的初始化(loopback_setup)(第二步)
(1)基地址,MAC地址以及中断号都用不着,主要是netdev_ops这个结构体,他包含了这个网卡支持的操作。
dev->netdev_ops          = &loopback_ops;而且
static const struct net_device_ops loopback_ops = {
     .ndo_init      = loopback_dev_init,
     .ndo_start_xmit= loopback_xmit,//数据发送
     .ndo_get_stats64 = loopback_get_stats64,
};
具体函数还需要实现。
(2)表示回环网卡支持的最大的接收数据的包的大小,除了正式数据,还有相关网络协议的头部分。
dev->mtu          = (16 * 1024) + 20 + 20 + 12;有效数据一般定义为16KB。
(3)加上表示回环网卡专有的标志。
dev->flags          = IFF_LOOPBACK;
(4)加上构造报头的结构体指针,这个结构体指针指向的结构体成员是众多构造以太网报头的函数指针。
dev->header_ops          = &eth_header_ops;
理想查找可看到
extern const struct header_ops eth_header_ops;
struct header_ops {
     int     (*create) (struct sk_buff *skb, struct net_device *dev,
                  unsigned short type, const void *daddr,
                  const void *saddr, unsigned len);
     int     (*parse)(const struct sk_buff *skb, unsigned char *haddr);
     int     (*rebuild)(struct sk_buff *skb);
#define HAVE_HEADER_CACHE
     int     (*cache)(const struct neighbour *neigh, struct hh_cache *hh);
     void     (*cache_update)(struct hh_cache *hh,
                    const struct net_device *dev,
                    const unsigned char *haddr);
};
6.数据发送
static netdev_tx_t loopback_xmit(struct sk_buff *skb,
                    struct net_device *dev)
{
     struct pcpu_lstats *lb_stats;
     int len;
     skb_orphan(skb);
     skb->protocol = eth_type_trans(skb, dev);
     /* it's OK to use per_cpu_ptr() because BHs are off */
     lb_stats = this_cpu_ptr(dev->lstats);
     len = skb->len;
     if (likely(netif_rx(skb) == NET_RX_SUCCESS)) {
          u64_stats_update_begin(&lb_stats->syncp);
          lb_stats->bytes += len;
          lb_stats->packets++;
          u64_stats_update_end(&lb_stats->syncp);
     }
     return NETDEV_TX_OK;
}
(1)第一个参数是协议栈传送给回环网卡的包数据,第二个参数是回环网卡的结构体。
(2)停止发送队列
通知上层暂停送数据,好让txd发送已送达的数据,但是不涉及硬件,所以在回环网卡可忽略。相应的,将数据写入寄存器和唤醒再次发送以及释放队列就可忽略。
(3)信息统计,表明上层送下来的包的协议
skb->protocol = eth_type_trans(skb, dev);
(4)统计发送过来的数据大小以及包的个数。
len = skb->len;
     if (likely(netif_rx(skb) == NET_RX_SUCCESS)) {
          u64_stats_update_begin(&lb_stats->syncp);
          lb_stats->bytes += len;//数据大小
          lb_stats->packets++;//包的个数。
          u64_stats_update_end(&lb_stats->syncp);
     }
7.由于从协议栈来的数据包(skb)存放到txd,而且txd不需要往外发送,txd和rxd“连”在一起,所以直接在发送部分调用普通网卡的接收部分的netif_rx(skb) == NET_RX_SUCCESS)函数,所以发送的同时就完成了接收。同时我们更清楚地看到在发送的时候不能释放skb,否则没有可接收的数据。
8.实现获取网卡状态的函数loopback_get_stats64
static struct rtnl_link_stats64 *loopback_get_stats64(struct net_device *dev,
                                    struct rtnl_link_stats64 *stats)
{
     u64 bytes = 0;
     u64 packets = 0;
     int i;
     for_each_possible_cpu(i) {
          const struct pcpu_lstats *lb_stats;
          u64 tbytes, tpackets;
          unsigned int start;
          lb_stats = per_cpu_ptr(dev->lstats, i);
          do {
               start = u64_stats_fetch_begin(&lb_stats->syncp);
               tbytes = lb_stats->bytes;
               tpackets = lb_stats->packets;
          } while (u64_stats_fetch_retry(&lb_stats->syncp, start));
          bytes   += tbytes;
          packets += tpackets;
     }
     stats->rx_packets = packets;
     stats->tx_packets = packets;
     stats->rx_bytes   = bytes;
     stats->tx_bytes   = bytes;
     return stats;
}
从这个结构体可以获取网卡状态信息
/* The main device statistics structure */
struct rtnl_link_stats64 {
     __u64     rx_packets;          /* total packets received     */
     __u64     tx_packets;          /* total packets transmitted     */
     __u64     rx_bytes;          /* total bytes received      */
     __u64     tx_bytes;          /* total bytes transmitted     */
     __u64     rx_errors;          /* bad packets received          */
     __u64     tx_errors;          /* packet transmit problems     */
     __u64     rx_dropped;          /* no space in linux buffers     */
     __u64     tx_dropped;          /* no space available in linux     */
     __u64     multicast;          /* multicast packets received     */
     __u64     collisions;
     /* detailed rx_errors: */
     __u64     rx_length_errors;
     __u64     rx_over_errors;          /* receiver ring buff overflow     */
     __u64     rx_crc_errors;          /* recved pkt with crc error     */
     __u64     rx_frame_errors;     /* recv'd frame alignment error */
     __u64     rx_fifo_errors;          /* recv'r fifo overrun          */
     __u64     rx_missed_errors;     /* receiver missed packet     */
     /* detailed tx_errors */
     __u64     tx_aborted_errors;
     __u64     tx_carrier_errors;
     __u64     tx_fifo_errors;
     __u64     tx_heartbeat_errors;
     __u64     tx_window_errors;
     /* for cslip etc */
     __u64     rx_compressed;
     __u64     tx_compressed;
};
主要是这四个成员
stats->rx_packets = packets;
     stats->tx_packets = packets;
     stats->rx_bytes   = bytes;
     stats->tx_bytes   = bytes;
。实际上自己可以重写这个获取状态的函数,因为struct net_device *dev有一个成员就是struct net_device_stats     stats;所以可以在重写的时候定义一个struct net_device_stats     stats指向形参传递进来的回环网卡结构体的stats成员,同样只需要注意stats成员的上述四个成员即可。
9.在退出该驱动的时候就是取消注册结构体。达到注销网卡的目的。
static void loopback_dev_free(struct net_device *dev)
{
     free_percpu(dev->lstats);
     free_netdev(dev);
}

三.网络子系统深度剖析
1.总体模型
Linux有多个网卡的时候,当要发送数据的时候,就要进行选择网卡。
(1)选择路由
由Linux内核里面的路由表获取路由相关信息,一般包括要发送的目的地址以及网关地址,则可以根据网关地址确定要选择的的网卡。在具体的协议层完成。
(2)邻居子系统(建立邻居信息)
路由器在整个网络数据包传输的过程中充当一个邻居(中介)的作用。
他可以建立邻居信息,即如果已经有了路由器的MAC地址则直接发送,否则通过发送ARP请求获取路由器的MAC地址。一般在IP协议栈完成。
2.SCI和协议无关层
(1)我们一般是通过用户空间的socket函数建立套接字,然后用write发送数据包。找到socket类型的文件对应的file_operarion才能响应用户空间的系统调用,也就是linux系统发包的入口。在内核源代码可以找到socket_file_ops,具体如下
static const struct file_operations socket_file_ops = {
     .owner =     THIS_MODULE,
     .llseek =     no_llseek,
     .aio_read =     sock_aio_read,
     .aio_write =     sock_aio_write,
     .poll =          sock_poll,
     .unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
     .compat_ioctl = compat_sock_ioctl,
#endif
     .mmap =          sock_mmap,
     .open =          sock_no_open,     /* special open code to disallow open via /proc */
     .release =     sock_close,
     .fasync =     sock_fasync,
     .sendpage =     sock_sendpage,
     .splice_write = generic_splice_sendpage,
     .splice_read =     sock_splice_read,
};
其中的.aio_write =     sock_aio_write,就是发包函数在整个linux的入口。
(2)sock_aio_write函数
static ssize_t sock_aio_write(struct kiocb *iocb, const struct iovec *iov,
                 unsigned long nr_segs, loff_t pos)
{
     struct sock_iocb siocb, *x;
     if (pos != 0)
          return -ESPIPE;
     x = alloc_sock_iocb(iocb, &siocb);
     if (!x)
          return -ENOMEM;
     return do_sock_write(&x->async_msg, iocb, iocb->ki_filp, iov, nr_segs);
}
(3)调用顺序:
sock_aio_write-》do_sock_write-》__sock_sendmsg-》__sock_sendmsg_nosec
这四个函数就是系统调用层(SCI)和协议无关层。因为在__sock_sendmsg_nosec函数里的sock->ops->sendmsg函数与具体地网络协议有关系,会进入到协议栈层。假设这里我们用UDP协议来分析,则使用udp_sendmsg函数进入协议栈。
3.协议栈处理
(1)协议栈的入口:udp_sendmsg
(2)选择路由:ip_route_output_flow
(3)udp_push_pending_frames——》udp_send_skb-》ip_send_skb。因为udp协议之下是ip协议,数据进来之后先经过udp处理,之后是ip处理。ip协议的入口就是ip_send_skb,或者传统的是ip_push_pending_frames。但是在3.0以上的内核中ip_push_pending_frames最终也是调用ip_send_skb,所以udp的处理直接跳到ip_send_skb是可以解释的。
(4)IP协议的处理
ip_push_pending_frames-》ip_send_skb-》ip_local_out-》dst_output-》skb_dst(skb)->output(skb);到此skb_dst(skb)->output(skb)这个指针指向的是ip_finish_output,继续调用ip_finish_output2函数。
(5)建立邻居信息在ip_finish_output2函数完成。
if (dst->hh)
          return neigh_hh_output(dst->hh, skb);
     else if (dst->neighbour)
          return dst->neighbour->output(skb);
上述四条代码获取邻居信息之后发送数据。 (先判断是否已经有了邻居信息)dst->neighbour->output(skb);这个函数指针对应arp_generic_ops下的.output =          neigh_resolve_output,
neigh_resolve_output-》rc = neigh->ops->queue_xmit(skb);就将数据发送走了。
4.设备无关接口
(1)queue_xmit(skb)指针对应的函数就是入口函数。也就是dev_queue_xmit函数
(2)dev_queue_xmit-》dev_hard_start_xmit在dev_hard_start_xmit中通过const struct net_device_ops *ops = dev->netdev_ops;获取设备支持的操作函数集。从而调用驱动里实现的rc = ops->ndo_start_xmit(skb, dev);指针对应的函数指针netdev_tx_t          (*ndo_start_xmit) (struct sk_buff *skb,                                 struct net_device *dev);

5.接收部分
(1)用户空间用recvmsg()函数
(2)在底层驱动程序中:
--》在中断处理中判断是接收中断
--》从硬件中读取数据放到skb包-----》用netif——rx函数传送到上层。
(3)所以入口是netif——rx,出口是recvmsg。
(4)具体图示

(5)netif_rx
用到了软中断。一旦网卡接收到了数据包就触发了软中断。在设备无关接口层就是net_rx_action函数作为软中断的处理函数。也是上层的入口。在协议栈调用__netif_receive_skb-》handle_ing-》deliver_skb-》在__netif_receive_skb判断是什么协议类型调用 pt_prev->func(skb, skb->dev, pt_prev, orig_dev);指定的相关协议处理函数。
(6)如果是IP协议就是ip_rcv作为协议相关处理函数(pt_prev->func(skb, skb->dev, pt_prev, orig_dev);对应的)也是IP协议层的入口函数。(7)往上是udp_rcv函数处理udp协议,再往上就是socket处理。

四.DM9000网卡驱动分析
1.用sourceinsght打开内核原代码,找到dm9000.c文件,找到如下调用关系:module_init(dm9000_init);-》dm9000_init-》platform_driver_register
由此知道dm9000是以平台设备的形式来注册驱动的。
2.平台设备的关键之处在于probe函数。在dm9000_init函数里由:return platform_driver_register(&dm9000_driver);这句话表明我们注册的平台设备对应的驱动结构体是dm9000_driver,找到它的定义如下:
static struct platform_driver dm9000_driver = {
     .driver     = {
          .name    = "dm9000",
          .owner     = THIS_MODULE,
          .pm     = &dm9000_drv_pm_ops,
     },
     .probe   = dm9000_probe,
     .remove  = __devexit_p(dm9000_drv_remove),
};
由 .probe   = dm9000_probe,知道dm9000_probe是对应的probe函数,也算是驱动入口函数。在里面进行网卡驱动相关的初始化。
3.初始化(1)分配device结构
ndev = alloc_etherdev(sizeof(struct board_info));
(2)从平台设备获取平台资源。主要获取了地址通道的地址,数据通道的地址,中断号。平台驱动和平台设备通过platform_driver结构体的driver.name成员匹配。在dm9000_driver中我们可以看到.name    = "dm9000",所以必然有一个平台设备(platform_device)叫做dm9000与之匹配。
假设我们使用的是mini2440,那么我们再mini2440.c中可以看见static struct platform_device mini2440_device_eth = {     .name          = "dm9000",
     .id          = -1,
     .num_resources     = ARRAY_SIZE(mini2440_dm9k_resource),
     .resource     = mini2440_dm9k_resource,
     .dev          = {
          .platform_data     = &mini2440_dm9k_pdata,
     },
};这个mini2440_device_eth 平台设备就是合上述平台驱动匹配的设备。深入进去查看他的资源.resource     = mini2440_dm9k_resource,进入mini2440_dm9k_resource
可以看见
static struct resource mini2440_dm9k_resource[] = {
     [0] = {
          .start = MACH_MINI2440_DM9K_BASE,
          .end   = MACH_MINI2440_DM9K_BASE + 3,
          .flags = IORESOURCE_MEM
     },
     [1] = {
          .start = MACH_MINI2440_DM9K_BASE + 4,
          .end   = MACH_MINI2440_DM9K_BASE + 7,
          .flags = IORESOURCE_MEM
     },
     [2] = {
          .start = IRQ_EINT7,
          .end   = IRQ_EINT7,
          .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,
     }
};
dm9000网卡只提供给用户两个接口,就是地址通道和数据通道,因此我们要想访问dm9000里面的内部寄存器,只能先是用地址通道输送内部寄存器的的地址告诉dm9000我要访问这个地址,再用数据通道输送要读写的数据。但是这两个通道地址如何确定呢?假设是mini2440,他的dm9000接在CS4,所以基地址是0x20000000(有的是0x20000300),然后由于dm9000的cmd引脚接在arm处理器的addr2,当addr为1的时候是数据访问,为0的时候是地址访问,所以地址通道是0x20000000也即是MACH_MINI2440_DM9K_BASE,数据通道是0x20000000+4也就是MACH_MINI2440_DM9K_BASE + 4(用二进制表示4===100即表示addr2是高)。有原理图可以看到dm9000的int引脚街道arm的eint7,所以中断号是 IRQ_EINT7。
(3)地址映射
db->io_addr = ioremap(db->addr_res->start, iosize);
db->io_data = ioremap(db->data_res->start, iosize);
(4)读取dm9000芯片编号(类型)。
id_val = ior(db, DM9000_CHIPR);
凡是ior函数都是读取寄存器。
(5)设置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;
(6)读取MAC地址,判断是否有效
/* 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);
(7)注册网卡驱动
ret = register_netdev(ndev);
接下来判断是否注册成功,做相应的操作。
4. 硬件初始化
这一步其实穿插在上述的第(5)步中的
ndev->netdev_ops     = &dm9000_netdev_ops;进入dm9000_netdev_opsstatic 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_multicast_list     = 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
};发现  .ndo_open          = dm9000_open,这里的dm9000_open函数就会进行硬件初始化,至于在什么时候调用--在用ifconfig命令的时候就会调用。真正的硬件相关的初始化就在这里。
(1)复位操作
dm9000_reset(db);
(2)硬件相关操作代码
dm9000_init_dm9000(dev);
在这里面最后使能发送和接收
/* Enable TX/RX interrupt mask */
     iow(db, DM9000_IMR, imr);
(3)注册中断处理函数
f (request_irq(dev->irq, dm9000_interrupt, irqflags, dev->name, dev))
          return -EAGAIN;
(4)具体的涉及到寄存器级别的初始化参考裸机部分。
(5).启动发送队列netif_start_queue(dev);
告诉应用层网卡已经准备好。
5.DM9000的发送过程
(1)又跳转到dm9000_netdev_ops结构体里,知道在dm9000_probe函数里将.ndo_start_xmit          = dm9000_start_xmit,指定为发送函数。于是进入dm9000_start_xmit函数
(2)第一个数据包立即发送,后面的加入队列等待,而且通知上层协议栈暂停向网卡发送数据/* TX control: First packet immediately send, second packet queue */
     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);
     }
(3)第二个数据包及以后的发送在dm9000_send_packet这里完成
--》先写入有效数据长度--》再写入真正的有效数据
iow(dm, DM9000_TXPLL, pkt_len);
     iow(dm, DM9000_TXPLH, pkt_len >> 8);
/* Move data to DM9000 TX RAM */
     writeb(DM9000_MWCMD, db->io_addr);
(4)释放套接字缓冲区队列/* free this SKB */
     dev_kfree_skb(skb);
(5)唤醒等待队列(在数据发送完成的时候),在数据发送完成时产生中断。在具体硬件初始化的第(3)步
---》注册中断处理函数f (request_irq(dev->irq, dm9000_interrupt, irqflags, dev->name, dev))
          return -EAGAIN;
---》相应的dm9000_interrupt就是这个时候我们要进入的中断处理函数。进入以后发现他先贩毒案中断状态是属于发送中断还是接收中断/* Got DM9000 interrupt status */
     int_status = ior(db, DM9000_ISR);     /* Got ISR */
     iow(db, DM9000_ISR, int_status);     /* Clear ISR 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);
---》进入dm9000_tx_done发现如果发送错误就将错误标志变量加1,如果成功发送就唤醒等待队列/* 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);
6.接收过程主要在中断处理程序
(1)在中断处理程序中接收的分支是/* Received the coming packet */
     if (int_status & ISR_PRS)
          dm9000_rx(dev);
(2)进入dm9000_rx函数,先是空读操作ior(db, DM9000_MRCMDX);     /* Dummy read */
(3)判断状态包是不是准备好了/* Get most updated data */
          rxbyte = readb(db->io_data);
          /* Status check: this byte must be 0 or 1 */
          if (rxbyte & DM9000_PKT_ERR)
(4)读取状态和长度
(db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr));读到rxhdr这个结构
(5)根据读取到的长度判断是否有效,根据状态判断是否有效。就是一系列的判断操作。
(6)分配skb((skb = dev_alloc_skb(RxLen + 4)) != NULL))
因为接收到的数据包是01 + status + 长度的高位和低位(之前这些部分占4个字节)DA+SA+type+IP+CFS(之后这些是有效数据包)有效数据不包括CFS部分。
所以考虑到要用长度+4来分配skb
(7) 由于IP包要求4字节对齐,所以由ata指针控制有效数据存放的起始位置是距离skb的起始地址空出了两个字节的,这样IP包的起始位置就四字节对齐了。skb_reserve(skb, 2);
这句话的实质就是让data指针和tail指针保留(向后移动)两个字节。
(8)由于之前的data和tail指向同一个地方,所以接下来需要修改。rdptr = (u8 *) skb_put(skb, RxLen - 4);
(9)将网卡来的数据放到data和tail之间。--》返回打data指针
rdptr = (u8 *) skb_put(skb, RxLen - 4);--》虽然放的时候吧cfs也放进了skb里,但是tail指向了cfs之前的部分,不包括cfs部分。注意这里并没有放
“01 + status + 长度的高位和低位” 这前面的部分。  /* Read received packet from RX SRAM */
               (db->inblk)(db->io_data, rdptr, RxLen);
               dev->stats.rx_bytes += RxLen;
(10)将数据包提交给上层协议栈--》指定协议
skb->protocol = eth_type_trans(skb, dev);--》判断以后,向上层提交
netif_rx(skb);
--》更新接收到的数据包个数dev->stats.rx_packets++;

posted @ 2015-04-15 16:23  生活需要深度  阅读(826)  评论(0)    收藏  举报