Chinaunix首页 | 论坛 | 博客
  • 博客访问: 294120
  • 博文数量: 99
  • 博客积分: 225
  • 博客等级: 二等列兵
  • 技术积分: 644
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-03 10:22
个人简介

遇顺境,处之淡然;遇逆境,处之泰然

文章分类
文章存档

2014年(12)

2013年(78)

2012年(9)

分类: LINUX

2013-03-20 10:59:24


和所有其他模块一样,函数init_module是cs8900的入口:

    module_init (cs8900_init);

    module_exit (cs8900_cleanup);

cs8900_init函数填充net_device结构体。net_device是网络驱动中最为重要的一个结构,需要认真阅读,其原型在 include/linux/netdevice.h中。为了填充如此复杂的结构,内核提供了ether_setup函数作为辅助。关于ether_setup函数,你只要知道它的功能就可以了。当然,ether_setup不是万能的,你还要手工完成类似如下一些内容: 

    ndev->open               = cs8900_start; 

    ndev->stop               = cs8900_stop; 

    ndev->hard_start_xmit    = cs8900_send_start; 

    ndev->get_stats          = cs8900_get_stats; 

    ndev->set_multicast_list = cs8900_set_receive_mode; 

    ndev->tx_timeout         = cs8900_transmit_timeout;

而下面语句的含义也非常明确:

    cs8900_dev->init = cs8900_probe

cs8900_probe是初始化函数(Driver initialization routines),主要完成一些初始化操作,我们在下一章中介绍。cs8900_init中使用了另外一个函数还没有说-->alloc_etherdev。alloc_etherdev是alloc_netdev的封装函数,它负责在内核空间为 net_device结构体分配内存(kmalloc)。alloc_etherdev的原型在include/linux/etherdevice.h中。

在函数最后,通过register_netdev完成设备注册。register_netdev函数原型在net/core/dev.c中,但是如果你就是为了写驱动,你可以不往下看了。

一句话总结:cs8900_init在使用各种手段填充了net_device结构体后,通过register_netdev向系统注册了一个网络设备。

cs8900_probe函数本身并不难,但是你必须清楚probe函数的重要职能。因为你很有可能要为属于你的设备编写一个probe函数。

先来看一行最易懂的代码:             printk(VERSION_STRING"/n"); 显然,内核启动时显示的相关信息就是这句话打印出来的。

net_device中的dev_addr就是网卡的MAC地址,你应该提供:

#if defined(CONFIG_ARCH_SMDK2410)  dev->dev_addr[0] = 0x00;  dev->dev_addr[1] = 0x00;  dev->dev_addr[2] = 0x3e;  dev->dev_addr[3] = 0x26;  dev->dev_addr[4] = 0x0a;  dev->dev_addr[5] = 0x00; #endif

使用类似的代码,为我们的网络芯片提供  dev->if_port   = IF_PORT_10BASET; (支持的多种接口)  dev->priv      = (void *) &priv; (设备私有数据结构)

哦,还要多说一句,IF_PORT_10BASET在include/linux/netdevice.h中定义,你通过这个枚举类型,可以了解更多的接口信息:

enum {         IF_PORT_UNKNOWN = 0,         IF_PORT_10BASE2,         IF_PORT_10BASET,         IF_PORT_AUI,         IF_PORT_100BASET,         IF_PORT_100BASETX,         IF_PORT_100BASEFX };

下面的代码对于移植是非常关键的。vSMDK2410_ETH_IO是网卡的虚拟地址,IRQ_EINT9是硬件中断。这两个值和另外一个地址(网卡的物理地址)必须在头文件中定义,而且要正确的定义。

#if defined(CONFIG_ARCH_SMDK2410)     dev->base_addr = vSMDK2410_ETH_IO + 0x300;     dev->irq = IRQ_EINT9; #endif /* #if defined(CONFIG_ARCH_SMDK2410) */

接下来分配I/O端口资源。使用的函数是check_mem_region和request_mem_region。LDD中的理论结合这里的实践,相信你很快就掌握驱动编程中的这个API了。

下面的代码实现了硬件的读写。使用的函数是cs8900_read和cs8900_write。虽然只需很短的篇幅就可以解释这对读写函数,但是为了不分散你对cs8900_probe函数的注意力,我还是留到下一节中说,不要着急:) 哦,差点忘了,这里使用了内核的自旋锁(spin_lock_init),不过关于自旋锁的机制已经超出本文的范围了。你现在只需要知道自旋锁用于并发控制,以及如何动态初始化自旋锁就可以了。如果你还想继续了解,请自行学习与自旋锁相关的API,它们是:

spinlock_t spin; //定义自旋锁 spin_lock(lock); //获得自旋锁 spin_trylock(lock); //尝试获得自旋锁 spin_unlock(lock); //释放自旋锁

一句话总结:cs8900_probe函数继续为设备进行初始化,并申请各种资源。

上回留下了cs8900_readcs8900_write没有讲。本节会专门讲解这两个函数,以及相关联的内容。其实就函数本身并不难,说白了就是向硬件进行读写,完成硬件的控制。但是这个过程是整个驱动程序设计中最关键的。换句话说,你要写驱动,很重要的一部分工作就是设置你的网络芯片,使它能乖乖的为你工作。

 

好,先看函数原型,它们都在cs8900.c文件中定义:

static inline u16 cs8900_read (struct net_device *dev,u16 reg)

{

         outw (reg,dev->base_addr + PP_Address);

         return (inw (dev->base_addr + PP_Data));

}

 

static inline void cs8900_write (struct net_device *dev,u16 reg,u16 value)

{

         outw (reg,dev->base_addr + PP_Address);

         outw (value,dev->base_addr + PP_Data);

}

看起来真的很简单。reg表示寄存器(不知道寄存器?赶快google去),value表示要赋的值。

/*******

几乎每一种外设都是通过读写设备上的寄存器来进行的。外设寄存器也称为“I/O端口,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。

********/

打开cs8900.h和硬件芯片的datasheet,你就会明白了:

#define PP_Address                   0x0a          /* PacketPage Pointer Port (Section 4.10.10) */

#define PP_Data                         0x0c          /* PacketPage Data Port (Section 4.10.10) */

 

曾经有一个学生和我争论,说他的Linux驱动中使用了就是类似0x0a之类的实地址,而不是我讲的映射后的虚拟地址。其实是他忘记了代码中是使用dev->base_addr+偏移的形式。所以不要再犯同样的错误哦。

 

剩下的一点知识就是Linux提供的I/O端口访问方法。除了本例使用的outw/inw外,还有下面几个(linux/include/asm-arm/io.h)。它们的区别我记得在LDD中有了解释。

 

#ifdef __io

#define outb(v,p)               __raw_writeb(v,__io(p))

#define outw(v,p)              __raw_writew((__force __u16) /

                                               cpu_to_le16(v),__io(p))

#define outl(v,p)                __raw_writel((__force __u32) /

                                               cpu_to_le32(v),__io(p))

 

#define inb(p)  ({ __u8 __v = __raw_readb(__io(p)); __v; })

#define inw(p) ({ __u16 __v = le16_to_cpu((__force __le16) /

                            __raw_readw(__io(p))); __v; })

#define inl(p)    ({ __u32 __v = le32_to_cpu((__force __le32) /

                            __raw_readl(__io(p))); __v; })

 

#define outsb(p,d,l)          __raw_writesb(__io(p),d,l)

#define outsw(p,d,l)                  __raw_writesw(__io(p),d,l)

#define outsl(p,d,l)           __raw_writesl(__io(p),d,l)

 

#define insb(p,d,l)             __raw_readsb(__io(p),d,l)

#define insw(p,d,l)            __raw_readsw(__io(p),d,l)

#define insl(p,d,l)              __raw_readsl(__io(p),d,l)

#endif

 

 

一句话总结:cs8900_readcs8900_write函数用来读写设备。


我们最初分析的函数是cs8900_init,并由此展开,跟踪到了cs8900_probecs8900_readcs8900_write等,现在收回来,还是回到cs8900_init中。

 

cs8900_init里找到如下一行代码:

ndev->open               = cs8900_start;

如果你之前了解过字符设备,你很容易联想到file_operation中的open。其实它们是一样的。open函数在网络设备被激活时(ifconfig)调用。因此,我们在编写网卡驱动时,要考虑网卡激活时,需要完成哪些事情。通常要进行中断的申请、资源的申请等。在cs8900的驱动中,主要完成两件事情:激活网卡和申请中断。激活网卡的代码如下:

cs8900_set (dev,PP_RxCFG,RxOKiE | BufferCRC | CRCerroriE | RuntiE | ExtradataiE);

         cs8900_set (dev,PP_RxCTL,RxOKA | IndividualA | BroadcastA);

         cs8900_set (dev,PP_TxCFG,TxOKiE | Out_of_windowiE | JabberiE);

         cs8900_set (dev,PP_BufCFG,Rdy4TxiE | RxMissiE | TxUnderruniE | TxColOvfiE | MissOvfloiE);

         cs8900_set (dev,PP_LineCTL,SerRxON | SerTxON);

         cs8900_set (dev,PP_BusCTL,EnableRQ);

 

申请中断的方法和以前一样:

request_irq (dev->irq, &cs8900_interrupt, 0, dev->name, dev))

 

显然,我们下一个任务就是完成中断处理函数了(cs8900_interrupt),我们后面再说。

当资源准备就绪后,需要调用netif_start_queue函数开启网络接口的接收和发送数据队列。这个函数原型在netdevivce.h中。与它类似的还有一个函数,叫netif_wake_queue。有人更喜欢使用netif_wake_queue函数,因为它可以通知网络系统可再次开始传输数据包。

static inline void netif_start_queue(struct net_device *dev)

{

         clear_bit(__LINK_STATE_XOFF, &dev->state);

}

static inline void netif_wake_queue(struct net_device *dev)

{

#ifdef CONFIG_NETPOLL_TRAP

         if (netpoll_trap()) {

                   clear_bit(__LINK_STATE_XOFF, &dev->state);

                   return;

         }

#endif

         if (test_and_clear_bit(__LINK_STATE_XOFF, &dev->state))

                   __netif_schedule(dev);

}

一句话总结:我们需要实现一个open函数,用来完成资源申请和开启网络接口的数据队列任务。


上次说到的cs8900_start函数中,完成了中断申请任务。什么是中断?中断是一种电信号,由硬件设备产生,并直接送入中断控制器的输入引脚上,然后再由中断控制器向处理器发送相应的信号。

 

其中中断处理函数cs8900_interrupt没有讲。中断处理函数是中断发生时系统要执行的函数。本文将主要介绍中断处理函数的功能。我们不分析中断实现机制,而是关心中断发生时,要做哪些事情。要知道,中断处理函数设计的好坏,会直接关心到性能乃至稳定问题。

 

虽然我不会讲中断的实现,但是你必须清楚中断的两种类型:

1.        轮询(polling 让内核定期对设备的状态进行查询,然后做出相应的处理;

2.        中断(interrupt 让硬件在需要的时候向内核发出信号(变内核主动为硬件主动)。

 

如果你对中断的实现感兴趣,你应该去了解hw_interrupt_typeirq_desc_t  irqaction这些数据结构。

 

我们的中断处理函数通过switch/case语句判断中断类型,并进行相应处理。具体过程是:首先读出ISQ寄存器的值,然后根据ISQ的值分别处理各种情况。当中断发生时,这些中断实际反映在相应的寄存器中,ISQ寄存器用低6位记录了当前寄存器的编号,高10位记录了当前寄存器的实际内容。代码如下:

while ((status = cs8900_read (dev, PP_ISQ))) {

handled = 1;

switch (RegNum (status)) {

case RxEvent:               //接收到了数据包

cs8900_receive (dev);

break;

 

case TxEvent:               //根据open中的设置,分别处理各种类型的发送事件。

priv->stats.collisions += ColCount (cs8900_read (dev,PP_TxCOL));

if (!(RegContent (status) & TxOK)) {

priv->stats.tx_errors++;

if ((RegContent (status) & Out_of_window)) priv->stats.tx_window_errors++;

if ((RegContent (status) & Jabber)) priv->stats.tx_aborted_errors++;

break;

} else if (priv->txlen) {

priv->stats.tx_packets++;

priv->stats.tx_bytes += priv->txlen;

}

priv->txlen = 0;

netif_wake_queue (dev);

break;

case BufEvent:              //两种情况,分别进行处理。

//RxMiss置位,表示传输过程中丢帧,于是读寄存器获取丢失的包的数目。

                            if ((RegContent (status) & RxMiss)) {

                                     u16 missed = MissCount (cs8900_read (dev,PP_RxMISS));

                                     priv->stats.rx_errors += missed;

                                     priv->stats.rx_missed_errors += missed;

                            }

//TxUnderrun置位,表示在帧结束前网卡运行已过时。改变网络状态结构体中对应元素的值,接着通知上层可往下发送包数据。

                            if ((RegContent (status) & TxUnderrun)) {

                                     priv->stats.tx_errors++;

                                     priv->stats.tx_fifo_errors++;

 

                                     priv->txlen = 0;

                                     netif_wake_queue (dev);

                            }

                            /* FIXME: if Rdy4Tx, transmit last sent packet (if any) */

                            break;

 

                   case TxCOL:                //当传输出现冲突错误时,通过读寄存器值得到当前冲突的个数,加到统计结构体中的对应元素值上。

                            priv->stats.collisions += ColCount (cs8900_read (dev,PP_TxCOL));

                            break;

 

                   case RxMISS:              //读寄存器获取丢失帧的个数。

                            status = MissCount (cs8900_read (dev,PP_RxMISS));

                            priv->stats.rx_errors += status;

                            priv->stats.rx_missed_errors += status;

                            break;

                   }

 

其中的细节我们下次再说。

 

一句话总结:中断处理函数是中断产生时执行的函数,它根据中断种类进行处理。

是时候讲解数据收发的时候了。用来接收数据的函数是cs8900_receive,它在上文中出现过。那么数据如何处理的?简单的说,1)把数据放到skb中,2)执行netif_rx 函数。

skb又是什么?它是网络设备驱动中,和net_device一样重要的数据结构。skb就是sk_buff结构,也就是套接字缓冲区。它在skbuff.h中定义。有多重要呢?不描述了,干脆把代码列出来,虽然有点长,但毕竟也不是书,大家就忍了吧。反正这个结构是必须清楚的。

 

struct sk_buff {

         /* These two members must be first. */

         struct sk_buff               *next;

         struct sk_buff               *prev;

 

         struct sock                    *sk;

         ktime_t                          tstamp;

         struct net_device           *dev;

 

         struct  dst_entry          *dst;

         struct sec_path              *sp;

 

         /*

          * This is the control buffer. It is free to use for every

          * layer. Please put your private variables there. If you

          * want to keep them across layers you have to do a skb_clone()

          * first. This is owned by whoever has the skb queued ATM.

          */

         char                     cb[48];

 

         unsigned int         len,

                                     data_len;

         __u16                  mac_len,

                                     hdr_len;

         union {

                   __wsum     csum;

                   struct {

                            __u16         csum_start;

                            __u16         csum_offset;

                   };

         };

         __u32                  priority;

         __u8                    local_df:1,

                                     cloned:1,

                                     ip_summed:2,

                                     nohdr:1,

                                     nfctinfo:3;

         __u8                    pkt_type:3,

                                     fclone:2,

                                     ipvs_property:1,

                                     peeked:1,

                                     nf_trace:1;

         __be16                protocol;

 

         void                     (*destructor)(struct sk_buff *skb);

#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)

         struct nf_conntrack       *nfct;

         struct sk_buff               *nfct_reasm;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

         struct nf_bridge_info    *nf_bridge;

#endif

 

         int                        iif;

#ifdef CONFIG_NETDEVICES_MULTIQUEUE

         __u16                           queue_mapping;

#endif

#ifdef CONFIG_NET_SCHED

         __u16                           tc_index;    /* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

         __u16                           tc_verd;      /* traffic control verdict */

#endif

#endif

         /* 2 byte hole */

 

#ifdef CONFIG_NET_DMA

         dma_cookie_t               dma_cookie;

#endif

#ifdef CONFIG_NETWORK_SECMARK

         __u32                           secmark;

#endif

 

         __u32                           mark;

 

         sk_buff_data_t              transport_header;

         sk_buff_data_t              network_header;

         sk_buff_data_t              mac_header;

         /* These elements must be at the end, see alloc_skb() for details.  */

         sk_buff_data_t              tail;

         sk_buff_data_t              end;

         unsigned char               *head,

                                              *data;

         unsigned int                  truesize;

         atomic_t                        users;

};

 

也许你看到这个庞大的结构(实际上它并不是很庞大)有些晕!但是更令人晕的是内核把sk_buff组织成一个双向链表。而且这个链表的结构要比常见的双向链表的结构还要复杂。sk_buff中用两个指针(nextprev)表示前后一个节点。这个链表还有另一个需求:每个sk_buff结构都必须能够很快找到链表头节点。为了满足这个需求,在第一个节点前面会插入另一个结构sk_buff_head,这是一个辅助节点。在Linux内核世界中,图示永远比代码好理解,所以你要善于看图说话。

本来我很想多介绍一下skb,但是又怕那将令大家再一次陷入内核的迷雾,所以还是见好就收吧。你现在知道,网卡传输数据离不开skb,因此我们需要填充这个skb。当skb搞定后,只要下面一句就可以:

    netif_rx (skb);

它把skb扔到了上层。netif_rx函数原型在net/core/dev.c中,它有两个返回值:

NET_RX_SUCCESS  (no congestion)

NET_RX_DROP     (packet was dropped)

 

一句话总结:网卡的数据接收是通过netif_rx完成的,而传递给netif_rx的是一个包含数据的socket buffer


阅读(1613) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~