驱动程序源码解析(转)

来源:互联网 发布:看电影什么软件最好 编辑:程序博客网 时间:2024/05/23 21:47
////////////////    I.与网络设备相关的宏:        /////////////////////////////////

/*下列宏定义于<linux/if_ether.h>中
  ETH_ALEN        网络设备的硬件地址(即MAC地址)长度,单位为一个八位字节,该值通常为6。
  ETH_DATA_LEN        每次传输的最大数据量(不包括14位设备报头)。该值通常为1500。
  ETH_HLEN        设备报头长度。该值通常为14。(包括目的地址(DA长度6)、源地址(SA长度6)、类型字段(TYPE长度2))
  ETH_ZLEN              整个数据包的最小长度。该值通常为60。    
*/    

/*MAX_ADDR_LEN          该宏定义于<linux/netdevice.h>中,指的是网络设备地址(即MAC地址)的最大长度,该值通常为32。*/



///////////////          II.与网络设备相关的结构体:      //////////////////////////////////

/*********************************     struct net_device     *************************************************/
/*为了屏蔽网络环境中物理网络设备的多样性,Linux对所有的物理网络设备进行抽象并定义了一个统一的概念,
  称之为接口(Interface)。每个接口在内部都表现为一个这样的结构体。以下为该结构体的定义:*/
struct net_device    
{    
    /* 以下为该结构体的可见成员,用户可从Space.c等文件中查看到它们*/
    
    char            name[IFNAMSIZ];    /* 网络设备的名称*/
    
    unsigned long        mem_end;    /* 设备发送和接收数据时需要占用内存空间,该值为内存区域的结束地址*/
    
    unsigned long        mem_start;    /* 共享内存区域的起始地址*/
    
    unsigned long        base_addr;    /* 设备I/O操作的基地址,由驱动程序设置,该成员可通过ifconfig来查看或更新*/
    
    unsigned int        irq;        /* 设备的中断号,该成员可通过ifconfig来查看或更新*/


    /*
     *    下列成员只用于部分设备,它们不会出现在Space.c文件中
     */

    unsigned char        if_port;    /* 端口传输特性,用于多端口设备,可选值如下:
                        enum {
                                IF_PORT_UNKNOWN = 0,    //未知方式
                                IF_PORT_10BASE2,    //10兆同轴方式
                                    IF_PORT_10BASET,    //10兆双绞方式
                                IF_PORT_AUI,
                                IF_PORT_100BASET,    
                                IF_PORT_100BASETX,
                                IF_PORT_100BASEFX
                        };*/
                        
    unsigned char        dma;        /* 设备的DMA通道,通常只在与外部总线通讯时才有意义,通过ifconfig可查看该成员*/

    unsigned long        state;        /* 设备的当前状态*/

    struct net_device    *next;        /* 指向全局设备链表中下一个设备的指针*/
    
    
    int            (*init)(struct net_device *dev);    /* 设备的初始化函数,仅需调用一次 */

    /* 至此,在Space.c文件中可见的成员介绍完毕 */
    //////////////////////////////////////////////////////////////////////////////////////////
    
    struct net_device    *next_sched;

    /* 设备索引,唯一的设备标识    */
    int            ifindex;
    int            iflink;


    struct net_device_stats* (*get_stats)(struct net_device *dev);
    struct iw_statistics*    (*get_wireless_stats)(struct net_device *dev);

    /*用于处理无线传输的函数,ioctl的替代品,详情查看 <net/iw_handler.h>*/
    struct iw_handler_def *    wireless_handlers;

    struct ethtool_ops *ethtool_ops;

    /* 至此,该结构体的可见成员已介绍完毕,以下的所有成员都属于系统底层,
     * 而且可能会有不定期变动。*/
    

    /* 在未来处理网络设备掉电的代码中也许会用到以下成员:*/
    unsigned long        trans_start;    /* 进行最后一次发送操作的时间(以jiffies表示)*/
    
    unsigned long        last_rx;    /* 进行最后一次接收操作的时间*/

    unsigned short        flags;        /* 设备标记。可用标记查看<linux/if.h>*/
    
    unsigned short        gflags;        /* 全局标记(?)*/
    
        unsigned short          priv_flags;     /* 类似于flags,但用户空间不可见 */
        
        unsigned short          unused_alignment_fixer; /* Because we need priv_flags,
                 * and we want to be 32-bit aligned.
                 */

    unsigned        mtu;        /* 每次传输的最大数据量(即ETH_DATA_LEN),缺省值为1500字节,该成员可通过ifconfig更改*/
    
    unsigned short        type;        /* 设备类型。(ARP)地址解析协议根据该成员来判断接口支持何种硬件地址。对于以太网设备
                              来说,合适的值为ARPHRD_ETHER。在<linux/if_arp.h>中有所有设备类型。该成员通过ether_setup设置*/
                       
    unsigned short        hard_header_len;/* 设备报头长度。即位于发送数据包中IP报头之前的数据长度(即ETH_HLEN),
                           以太网设备的该值为14。*/
                               
    void            *priv;        /* 相当于filp->private_data。通过alloc_netdev设置,通过netdev_priv访问*/

    struct net_device    *master;     /* 该指针指向当前设备组中该设备所属的主设备*/
    

    /* 设备地址相关信息 */
    unsigned char        broadcast[MAX_ADDR_LEN];    /* 广播地址。由ether_setup 进行分配*/
    
    unsigned char        dev_addr[MAX_ADDR_LEN];        /* 设备地址(MAC地址),通过读取设备信息获得*/
    
    unsigned char        addr_len;    /* 设备地址长度(MAC地址),通常为6*/

    struct dev_mc_list    *mc_list;    /* 用于多点传送的MAC地址列表    */
    
    int            mc_count;    /* 多点传送时MAC地址列表中的元素个数    */
    int            promiscuity;
    int            allmulti;

    int            watchdog_timeo; /* 该成员是一个以jiffies为单位的时间数。表示的是网络层判定传输超时的最小时间标准*/
    struct timer_list    watchdog_timer; /* 用来为超时计数的定时器(?)*/

    /* Protocol specific pointers */
    
    void             *atalk_ptr;    /* AppleTalk link     */
    void            *ip_ptr;    /* IPv4 specific data    */  
    void                 *dn_ptr;        /* DECnet specific data */
    void                 *ip6_ptr;       /* IPv6 specific data */
    void            *ec_ptr;    /* Econet specific data    */
    void            *ax25_ptr;    /* AX.25 specific data */

    struct list_head    poll_list;    /* Link to poll list    */
    int            quota;
    int            weight;

    struct Qdisc        *qdisc;
    struct Qdisc        *qdisc_sleeping;
    struct Qdisc        *qdisc_ingress;
    struct list_head    qdisc_list;
    unsigned long        tx_queue_len;    /* Max frames per queue allowed */

    /* ingress path synchronizer */
    spinlock_t        ingress_lock;
    /* hard_start_xmit synchronizer */
    spinlock_t        xmit_lock;    /* 该锁用来防止同一时间内对hard_start_xmit函数进行多次调用*/
    /* cpu id of processor entered to hard_start_xmit or -1,
       if nobody entered there.
     */
    int            xmit_lock_owner; /* 该成员指的是获得了xmit_lock的CPU的ID号。-1表示没有当前CPU获得xmit_lock*/
    /* device queue lock */
    spinlock_t        queue_lock;
    /* Number of references to this device */
    atomic_t        refcnt;
    /* delayed register/unregister */
    struct list_head    todo_list;
    /* device name hash chain */
    struct hlist_node    name_hlist;
    /* device index hash chain */
    struct hlist_node    index_hlist;

    /* register/unregister state machine */
    enum { NETREG_UNINITIALIZED=0,
           NETREG_REGISTERING,    /* called register_netdevice */
           NETREG_REGISTERED,    /* completed register todo */
           NETREG_UNREGISTERING,    /* called unregister_netdevice */
           NETREG_UNREGISTERED,    /* completed unregister todo */
           NETREG_RELEASED,        /* called free_netdev */
    } reg_state;

    /* 网络设备的特性 */
    int            features;
#define NETIF_F_SG        1    /* 分散/整合的I/O操作。标志设备可以将分散的数据包整合后再发送 */    
#define NETIF_F_IP_CSUM        2    /* 只对TCP/UDP包进行校验 */
#define NETIF_F_NO_CSUM        4    /* 设备无需进行包校验操作 */
#define NETIF_F_HW_CSUM        8    /* 校验所有类型的数据包 */
#define NETIF_F_HIGHDMA        32    /* 可直接存取内存中的高地址 */
#define NETIF_F_FRAGLIST    64    /* 分散/整合的I/O操作。标志设备可以在接收到分散的数据后将其整合*/
#define NETIF_F_HW_VLAN_TX    128    /* Transmit VLAN hw acceleration */
#define NETIF_F_HW_VLAN_RX    256    /* Receive VLAN hw acceleration */
#define NETIF_F_HW_VLAN_FILTER    512    /* Receive filtering on VLAN */
#define NETIF_F_VLAN_CHALLENGED    1024    /* Device cannot handle VLAN packets */
#define NETIF_F_TSO        2048    /* Can offload TCP/IP segmentation */
#define NETIF_F_LLTX        4096    /* LockLess TX */

    /* 当设备与网络断开时调用该函数 */
    void            (*uninit)(struct net_device *dev);
    /* 设备的析构函数,当设备的所有外部引用都消失后调用该函数 */
    void            (*destructor)(struct net_device *dev);

    /********** 设备操作函数指针 ***********/
    int            (*open)(struct net_device *dev);        /*设备的打开函数。当ifconfig激活设备后设备就会被打开,
                                          该函数的工作是注册设备所需的系统资源(I/O端口,中断号及DMA通道等),
                                          打开硬件设备并进行设备所需的其他设置。 */
                                          
    int            (*stop)(struct net_device *dev);        /*设备的终止函数。当接口被析构时设备就会被停止。该函数执行与open函数相反的操作。*/
    
    int            (*hard_start_xmit) (struct sk_buff *skb,    /*发送一个完整的数据包*/
                            struct net_device *dev);
#define HAVE_NETDEV_POLL
    int            (*poll) (struct net_device *dev, int *quota);   /*由NAPI驱动程序提供的以轮询方式工作的设备操作函数*/
    
    int            (*hard_header) (struct sk_buff *skb,        /*该函数根据源设备及目标设备的地址组合一个设备报头。*/
                        struct net_device *dev,         /*以太网设备的该函数默认为eth_header。*/
                        unsigned short type,        /*该函数在调用hard_start_xmit之前被调用。*/
                        void *daddr,
                        void *saddr,
                        unsigned len);
                        
    int            (*rebuild_header)(struct sk_buff *skb);        /*该函数负责重建设备报头以完善其信息。
                                          它在地址解析完成后,发送首个数据包之前被调用。*/
                                          
#define HAVE_MULTICAST            
    void            (*set_multicast_list)(struct net_device *dev);    /*当设备多点传送的地址列表和设备标志被更改时调用该函数。*/
    
#define HAVE_SET_MAC_ADDR          
    int            (*set_mac_address)(struct net_device *dev,    /*在设备支持的情况下设置设备的MAC地址。*/
                           void *addr);
#define HAVE_PRIVATE_IOCTL
    int            (*do_ioctl)(struct net_device *dev,        /*实现设备特有的ioctl命令。不需要时可将参数1设为NULL。*/
                        struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
    int            (*set_config)(struct net_device *dev,        /*旧有的更改设备设置的函数。目前的设备不需要该函数。*/
                          struct ifmap *map);
                          
#define HAVE_HEADER_CACHE
    int            (*hard_header_cache)(struct neighbour *neigh,    /*将地址解析的结果数据填充到hh_cache结构体中。*/
                             struct hh_cache *hh);    /*以太网设备的该函数默认为eth_header_cache 。*/
                             
    void            (*header_cache_update)(struct hh_cache *hh,     /*当地址解析的结果变更时更新hh_cache结构体中的信息。*/
                               struct net_device *dev,  /*以太网设备的默认函数是eth_header_cache_update。*/
                               unsigned char *  haddr);
                               
#define HAVE_CHANGE_MTU
    int            (*change_mtu)(struct net_device *dev, int new_mtu);    /*改变设备单次传输的最大数据量*/

#define HAVE_TX_TIMEOUT
    void            (*tx_timeout) (struct net_device *dev);        /*当一个数据包发送失败时(可能是错过一次中断或设备被锁),
                                          该函数负责处理相应问题并恢复传输。*/

    void            (*vlan_rx_register)(struct net_device *dev,    
                            struct vlan_group *grp);
    void            (*vlan_rx_add_vid)(struct net_device *dev,
                           unsigned short vid);
    void            (*vlan_rx_kill_vid)(struct net_device *dev,
                            unsigned short vid);

    int            (*hard_header_parse)(struct sk_buff *skb,    /*该函数从skb包含的数据包中取出源地址,将其复制到haddr指向*/
                             unsigned char *haddr);    /*的缓冲区中,并返回源地址长度。以太网设备的默认函数为eth_header_parse*/
                             
    int            (*neigh_setup)(struct net_device *dev, struct neigh_parms *);
    int            (*accept_fastpath)(struct net_device *, struct dst_entry*);
#ifdef CONFIG_NETPOLL
    int            netpoll_rx;
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
    void                 (*poll_controller)(struct net_device *dev);     /*当禁用中断时,该函数用于请求驱动程序检查设备事件状况。*/
#endif

    /* bridge stuff */
    struct net_bridge_port    *br_port;

#ifdef CONFIG_NET_DIVERT
    /* this will get initialized at each interface type init routine?*/
    struct divert_blk    *divert;
#endif /* CONFIG_NET_DIVERT */

    /* class/net/name entry */
    struct class_device    class_dev;
    /* how much padding had been added by alloc_netdev() */
    int padded;
};


/**********************************    struct net_device_stats     ***********************************/
/*该结构体用来描述网络设备进行数据传输的统计信息。以下为该结构体的定义:*/

struct net_device_stats
{
    unsigned long    rx_packets;        /* 已接收到的数据包总数    */
    unsigned long    tx_packets;        /* 已发送的数据包总数    */
    unsigned long    rx_bytes;        /* 已接收到的字节总数     */
    unsigned long    tx_bytes;        /* 已发送的字节总数    */
    unsigned long    rx_errors;        /* 接收失败的数据包总数    */
    unsigned long    tx_errors;        /* 传输失败的数据包总数    */
    unsigned long    rx_dropped;        /* 由于接收缓冲区的空间不足而被丢掉的数据包总数    */
    unsigned long    tx_dropped;        /* 由于发送缓冲区的空间不足而被丢掉的数据包总数    */
    unsigned long    multicast;        /* 多点传送时接收到的数据包总数    */
    unsigned long    collisions;        /* 由于媒介堵塞所引发的冲突次数 */

    /* 接收失败的各种情况 */
    unsigned long    rx_length_errors;    /* 接收的数据长度不符 */
    unsigned long    rx_over_errors;        /* 接收缓冲区溢出     */
    unsigned long    rx_crc_errors;        /* CRC校验错误        */
    unsigned long    rx_frame_errors;    /* 帧同步错误         */
    unsigned long    rx_fifo_errors;        /* 先入先出操作超度   */
    unsigned long    rx_missed_errors;    /* 丢包               */

    /* 发送失败的各种情况 */
    unsigned long    tx_aborted_errors;
    unsigned long    tx_carrier_errors;
    unsigned long    tx_fifo_errors;
    unsigned long    tx_heartbeat_errors;
    unsigned long    tx_window_errors;
    
    /* for cslip etc */
    unsigned long    rx_compressed;
    unsigned long    tx_compressed;
};


/**********************************    struct sk_buff     ***********************************/

struct sk_buff {
    /* 这两个成员必须放在最前面 */
    struct sk_buff        *next;        
    struct sk_buff        *prev;

    struct sk_buff_head    *list;
    struct sock        *sk;
    struct timeval        stamp;
    struct net_device    *dev;        /* 发送或接收该缓冲区的网络设备 */
    struct net_device    *input_dev;
    struct net_device    *real_dev;

    union {
        struct tcphdr    *th;
        struct udphdr    *uh;
        struct icmphdr    *icmph;
        struct igmphdr    *igmph;
        struct iphdr    *ipiph;
        struct ipv6hdr    *ipv6h;
        unsigned char    *raw;
    } h;    /* 指向数据包报头中的传输层报头*/

    union {
        struct iphdr    *iph;
        struct ipv6hdr    *ipv6h;
        struct arphdr    *arph;
        unsigned char    *raw;
    } nh;    /* 指向数据包报头中的网络层报头*/

    union {
          unsigned char     *raw;
    } mac;    /* 指向数据包报头中的链路层报头*/

    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[40];

    unsigned int        len,        /* 数据包中数据总长度 */
                data_len,    /* 数据包中分段存储的数据长度。除非使用了分散/整合的I/O操作,否则该成员始终为0*/
                mac_len,     /* MAC地址长度(?) */
                csum;
    unsigned char        local_df,
                cloned,
                pkt_type,    /* 数据包类别。该成员由eth_type_trans 负责设置。常用值包括:
                           PACKET_HOST (发送给本机的包)
                           PACKET_OTHERHOST (发送给其他主机的包)
                           PACKET_BROADCAST (广播发送的包)
                           PACKET_MULTICAST (多点传送的包)*/
                           
                ip_summed;    /* 数据包的校验策略 */
    __u32            priority;
    unsigned short        protocol,
                security;

    void            (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_NETFILTER
        unsigned long        nfmark;
    __u32            nfcache;
    __u32            nfctinfo;
    struct nf_conntrack    *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
        unsigned int        nf_debug;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
    struct nf_bridge_info    *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#if defined(CONFIG_HIPPI)
    union {
        __u32        ifield;
    } private;
#endif
#ifdef CONFIG_NET_SCHED
       __u32            tc_index;        /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
    __u32           tc_verd;               /* traffic control verdict */
    __u32           tc_classid;            /* traffic control classid */
#endif

#endif

    /* 这些成员必须放在最后,详细原因查看alloc_skb() */
    unsigned int        truesize;
    atomic_t        users;
    
    /* 以下成员用于对数据包中的数据进行寻址*/
    unsigned char        *head,        /* 指向所分配空间的起始地址 */
                *data,        /* 指向有效数据的起始地址   */
                *tail,        /* 指向有效数据的结束地址   */
                *end;        /* 指向所分配空间的结束地址 */
};




//////////////////////          III.rtl8019.c源码分析         /////////////////////////////////

/******************** 变量、宏、结构体 *******************/
#define    outportb(port, data)    *((volatile u8 *)(port)) = (u8)(data)    /* 8位的端口写入函数 */
#define    inportb(port)        *((volatile u8 *)(port))        /* 8位的端口读取函数 */


#define    outportw(port, data)    *((volatile u16 *)(port)) = (u16)(data)    /* 16位的端口写入函数 */    
#define    inportw(port)        *((volatile u16 *)(port))        /* 16位的端口读取函数 */

#define    ETH_FRAME_LEN        1514    /* 整个封包的最大长度 */

/* Rtl8019的缓冲区范围为0x40~0x80,大小为16k,分为64页,每页大小256b。其中: */
#define    RPSTART            0x4c    /* Rtl8019的RAM中接收缓冲区的起始页地址 */
#define    RPSTOP            0x80    /* Rtl8019的RAM中接收缓冲区的结束页地址 */
#define    SPSTART            0x40    /* Rtl8019的RAM中发送缓冲区的起始页地址 */

static int timeout = 100;    /* 发送超时 tx watchdog ticks 100 = 1s */
static char *version = "Samsung S3C44B0 Rtl8019as driver version 1.0/n"; /* 版本号 */

static u8 rBNRY;        /* 当前读取的页地址 */
static u8 SrcMacID[ETH_ALEN] = {0x00,0x80,0x48,0x12,0x34,0x56};         /* MAC地址 */


/* 设备私有的结构体。用于接收及发送数据包。 */
struct nic_8019_priv {
    struct net_device_stats stats;        /* 设备的传输统计对象 */
    spinlock_t lock;            /* 自旋锁 */
    struct sk_buff *skb;            /* socket缓冲区 */
};


/************************    操作函数    *******************************/

/***** 选择寄存器页(Rtl8019as共有Page0~3四个寄存器页)*****/
static void SetRegPage( u8 PageIdx)
{
    u8 temp;
    temp = inportb(BaseAddr);        /* 读取Rtl8019第0寄存器页中的CR寄存器值 */
    temp = (temp&0x3b)|(PageIdx<<6);    /* 将寄存器当前值与上3b得出前6位的无效值。CR寄存器的高两位用于设置页号,
                           所以需要将页号左移6位再进行或运算 */
    outportb(BaseAddr, temp);        /* 设置CR寄存器的值 */
}

/***** 设备的初始化函数 *****/
static int nic_8019_init(struct net_device *dev)
{
    int i;
    TRACE("init/n");    /* 输出跟踪信息 */
    ether_setup(dev);    /* ether_setup用来设置设备的各个成员 */

    // 设置设备的操作函数
    dev->open = nic_8019_open;            /* 设备的打开函数 */
    dev->stop = nic_8019_stop;            /* 设备的终止函数 */
    dev->get_stats = nic_8019_get_stats;        /* 设备的统计函数 */
    dev->hard_start_xmit = nic_8019_start_xmit;    /* 设备的发送函数 */

    // 设置设备的操作参数
    dev->watchdog_timeo = timeout;            /* 设置超时为timeout(100 ticks=1s) */
    dev->irq = S3C44B0X_INTERRUPT_EINT1;        /* 设置中断号。S3C44B0X_INTERRUPT_EINT1定义于<include/asm/arch/irqsh.h>,默认值为24 */
    dev->dma = 0;                    /* 设置dma通道 */

    // 设置MAC地址
    printk(KERN_INFO "%s: ", dev->name);        /* 输出设备名称以便明确操作 */
    for(i=0; i<6; i++) {                /* 给设备的dev_addr[MAX_ADDR_LEN]成员赋值并逐位输出MAC地址 */
        dev->dev_addr[i] = SrcMacID[i];        
        printk("%2.2x%c", dev->dev_addr[i], (i==5) ? ' ' : ':');
    }
    printk("/n");

    SET_MODULE_OWNER(dev);                /* 该宏定义于<linux/net_device.h>中。在2.6版的内核中该宏已没有意义 */

    dev->priv = kmalloc(sizeof(struct nic_8019_priv), GFP_KERNEL);    /*为设备的priv成员分配内存空间以备用 */
    if(dev->priv == NULL)                        /* 若分配失败则返回-ENOMEM */
        return -ENOMEM;

    memset(dev->priv, 0, sizeof(struct nic_8019_priv));        /* 若分配成功则将该内存区域全初始化为0 */
    spin_lock_init(&((struct nic_8019_priv *) dev->priv)->lock);    /* 初始化设备的priv成员的自旋锁 */
    return 0;
}

/*****  定义设备对象 *****/
static struct net_device nic_8019_netdevs = {        /* 该操作定义了一个struct net_device类型的设备对象,并将其init成员 */
    init: nic_8019_init,                /* 设置为nic_8019_init函数以待调用 */
};


/*****  模块的初始化函数 *****/
int __init nic_8019_init_module(void)
{
    int result;
    TRACE("init_module/n");                /* 输出跟踪信息 */

    printk(KERN_INFO "%s", version);        /* 输出版本信息 */

    /* 注册设备。该过程会调用设备的init成员所指向的初始化函数(即nic_8019_init)来对设备进行初始化。
       注册网络设备,该函数执行成功时会返回1,否则为0。*/
    if((result = register_netdev(&nic_8019_netdevs)))
        printk("Rtl8019as eth: Error %i registering device /"%s/"/n", result, nic_8019_netdevs.name);
        
    return result ? 0 : -ENODEV;            /* 若注册成功则返回0,否则返回-ENODEV */
}



/***** 设备的打开函数 *****/
static int nic_8019_open(struct net_device *dev)
{
    int i,j;

    MOD_INC_USE_COUNT;        /* 该宏定义于<linux/module.h>中,2.4的内核中用它来累计模块的调用次数,
                       每次打开模块时需要累加该值。 */
    TRACE("open/n");        /* 输出跟踪信息 */
    
    disable_irq(dev->irq);        /* 在注册中断响应函数之前应先禁用设备的中断功能 */
    
    /* 注册设备的中断响应函数 */
    if (request_irq(dev->irq, &nic_8019_rx, SA_INTERRUPT, "eth rx isr", dev)) {
        printk(KERN_ERR "Rtl8019: Can't get irq %d/n", dev->irq);
        return -EAGAIN;        /* 若注册失败则返回-EAGAIN通知内核重新调用open函数 */
    }

    /* 激活Rtl8019as */
    SetRegPage(3);            /* 切换到Rtl8019寄存器表的页3 */
    outportb(CR9346, 0xcf);        /* 将9346CR寄存器的EEM1和EEM0都设置为1,以便启用配置寄存器CONFIG3的写入功能 */
    outportb(CONFIG3, 0x60);    /* 清除Rtl8019的sleep(休眠)和pwrdn(低功耗)模式, 将led0设置为led_col(响应传输冲突),
                       将led1设置为led_crs(响应数据传输,包括发送和接收)。*/            
    outportb(CR9346, 0x3f);     /* 禁用配置寄存器CONFIG3的写入功能 */

    /* 初始化Rtl8019as */
    outportb(RstAddr, 0x5a);    /* 重置寄存器(?) */
    i = 20000;
    while(i--);            /* 作少许延时 */


    SetRegPage(0);            /* 切换到Rtl8019寄存器表的页0 */
    inportb(ISR);            /* 读取中断状态寄存器以便清空它(?) */
    outportb(BaseAddr, 0x21);    /* 执行Rtl8019的stop命令,中止任何数据传输 */
    outportb(Pstart, RPSTART);    /* 设置Rtl8019的RAM中接收缓冲区的起始地址为RPSTART(0x4c) */
    outportb(Pstop, RPSTOP);    /* 设置Rtl8019的RAM中接收缓冲区的结束地址为RPSTOP(0x80) */
    outportb(BNRY, RPSTART);    /* 界线寄存器。该寄存器的值通常是接收缓冲区内用户已经读取的最后页地址。
                       它的作用是防止接收缓冲区的未读数据被覆盖。在这里将它设置为缓冲区的起始地址,
                       表示还未进行任何读取操作。 */    
    outportb(TPSR, SPSTART);    /* 设置Rtl8019的RAM中发送缓冲区的起始地址为SPSTART(0x40) */
    outportb(RCR, 0xcc);        /* 设置Rtl8019的接收配置值为0xcc。意义如下:
                       1.将接收的数据包缓存到内存;
                       2.进行MAC地址比较;
                       3.支持多点传送;
                       4.不接收小于64b的包;
                       5.不接收发生传输错误的包。*/    
    outportb(TCR, 0xe0);        /* 设置Rtl8019的接收配置值为0xe0。意义如下:
                       1.启用传输冲突的抵消功能;
                       2.禁用常规传输的自动发送功能;
                       3.设置传输方式为常规模式;
                       4.启用CRC校验功能。*/
                       
#ifdef RTL8019_OP_16            /* 该宏用来标志是否进行的是16位操作 */
    outportb(DCR, 0xc9);        /* 设置Rtl8019的数据配置为0xc9, 16位的DMA */    
#else
    outportb(DCR, 0xc8);        /* 设置Rtl8019的数据配置为0xc8, 8位的DMA */    
#endif

    outportb(IMR, 0x03);        /* 设置Rtl8019的中断掩码位0x03,即启用接收和发送中断 */
    outportb(ISR, 0xff);        /* 清空Rtl8019的中断状态寄存器 */

    SetRegPage(1);            /* 切换到Rtl8019寄存器表的页1 */
    
    for(i=0; i<6; i++)        /* 将设备的MAC地址输入到Page1中的PAR0-5寄存器中 */
#ifdef RTL8019_OP_6
      outportb(BaseAddr+(1+i)*2, dev->dev_addr[i]);    
#else   
      outportb(BaseAddr+(1+i), dev->dev_addr[i]);
#endif

    outportb(CURR, RPSTART+1);    /* 写页地址寄存器。该寄存器存储的是用于放置下一个数据包的起始页地址。
                       这里将它设置为缓冲区的起始页地址加1,表示当前没有收到任何数据。*/
                       
    outportb(MAR0, 0x00);        /* 设置在Hash校验时用于对多点传送地址进行过滤的位值 */
    outportb(MAR1, 0x41);
    outportb(MAR2, 0x00);
    outportb(MAR3, 0x80);
    outportb(MAR4, 0x00);
    outportb(MAR5, 0x00);
    outportb(MAR6, 0x00);
    outportb(MAR7, 0x00);
    outportb(BaseAddr, 0x22);    /* 切换到Rtl8019寄存器表的页0并执行Rtl8019的start命令,准备进行数据传输 */    
    rBNRY = RPSTART;        /* 将Rtl8019接收缓冲区中的当前读取地址(即起始地址)保存备用 */
    enable_irq(dev->irq);         /* 启用设备的中断功能 */
    
    /* 开启设备的数据传输队列(即从此允许设备进行收发数据包) */
    netif_start_queue(dev);

    return 0;
}


/***** 设备的发送函数 *****/
static int nic_8019_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    int i;
#ifdef RTL8019_OP_16
    u16 len,TxLen;
    u16 *data;
#else
    int len, TxLen;            /* 向Rtl8019的RAM写入的数据长度及Rtl8019向外发送的数据长度 */
    u8  *data;            /* 数据缓冲区 */
#endif
    struct nic_8019_priv *priv = (struct nic_8019_priv *) dev->priv;    /* 获得设备的私有结构体 */

    TRACE("start_xmit/n");        /* 输出跟踪信息 */

    len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;     /* 获取数据包的长度,如果该长度小于最小长度,就等于最小长度 */
    TRACE("/nTx Length = %i,%x,%x/n", len, skb->data[12], skb->data[13]);    /* 输出设备报头的最后两位(即类型字段) */
    
#ifdef RTL8019_OP_16            
    data =(u16*) skb->data;
#else
    data = (u8*) skb->data;        /* 获取数据包中的数据并进行类型转换 */
#endif

    /***** 主机CPU先以远程DMA方式将数据写入到Rtl8019的RAM中 *****/
    outportb(BaseAddr,0x22);      /* 切换到Rtl8019寄存器表的页0并中止远程DMA传输 */
    if (inportb(BaseAddr)&4)    /* 如果最后一次远程DMA操作没有完成,则返回1表示设备繁忙的错误,然后重新执行该函数 */
        return 1;
#ifdef bug_fix_for_write
    //read page 42,0,42,0 before write if you have problem
#endif
    outportb(RSAR0, 0);        /* 设置远程DMA传输的起始地址为SPSTART(0x40)。先打入低8位,*/
    outportb(RSAR1, SPSTART);    /* 后打入高8位 */
    outportb(RBCR0, len&0xff);    /* 设置远程DMA传输的数据长度。先打入低8位,*/
    outportb(RBCR1, len>>8);    /* 后打入高8位 */        
    outportb(BaseAddr, 0x12);    /* 开始远程DMA写入操作 */
    dev->trans_start = jiffies;    /* 记录此次发送操作的开始时间 */
    
#ifdef RTL8019_OP_16
    TxLen=(len+1)/2;        /* 如果是16位DMA操作则将长度减半 */
#else
    TxLen=len;            /* 否则直接获得数据长度 */
#endif

    for(i=0; i<TxLen; i++) {                
#ifdef RTL8019_OP_16                        
        outportw(RWPORT, data[i]);            
        TRACE("%2X,%2X,",data[i]&0xff,data[i]>>8);      
#else
        outportb(RWPORT, data[i]);    /* 通过Rtl8019的Remote DMA Port将数据包内的数据拷贝到Rtl8019的RAM中 */
        TRACE("%2X,",skb->data[i]);    /* 同时将数据作为跟踪信息输出以便查阅 */        
#endif
    }    
    TRACE("/n");
    
    while(inportb(BaseAddr)&4);     /* 等待传输操作完成 */
    
    /***** 然后Rtl8019再将自己RAM中的数据发送出去 *****/
    outportb(TPSR,  SPSTART);       /* 设置Rtl8019的RAM中要发送数据包的起始地址为SPSTART(0x40) */
    outportb(TBCR0, len&0xff);    /* 设置要传输的数据包长度。先打入低8位, */
    outportb(TBCR1, len>>8);    /* 后打入高8位, */            
    outportb(BaseAddr, 0x1e);    /* 开始发送数据包 */    
    dev_kfree_skb(skb);        /* 释放Socket缓冲区 */
    return 0;
}


/***** 设备的中断响应函数 *****/
irqreturn_t nic_8019_rx(int irq, void *dev_id, struct pt_regs *regs)
{
    u8 RxPageBeg, RxPageEnd;    /* 接收缓冲区内数据的起始地址和结束地址 */
    u8 RxNextPage;            /* 下一个数据页的起始地址 */
    u8 RxStatus;            /* 中断状态 */
#ifdef RTL8019_OP_16
    u16 *data,temp;
    u16 i, RxLength,RxLen;
#else
    u8* data;            /* 数据缓冲区 */
    int i, RxLength, RxLen;        /* 数据总长度及已获得的长度 */
#endif

    struct sk_buff *skb;                            /* 定义Socket缓冲区 */
    struct net_device *dev = (struct net_device *) dev_id;            /* 获得设备对象 */
    struct nic_8019_priv *priv = (struct nic_8019_priv *) dev->priv;    /* 获得设备的私有结构体 */

    TRACE("TX/RX Interupt!/n");    /* 输出跟踪信息 */
    spin_lock(&priv->lock);        /* 获得自旋锁 */
    SetRegPage(0);            /* 切换到Rtl8019寄存器表的页0 */
    outportb(BNRY, rBNRY);        /* 将边界寄存器的值设置为接收缓冲区的起始地址(rBNRY在open函数中以赋值) */
    RxStatus = inportb(ISR);    /* 获得引发此次中断的操作类型 */
    
    /***** 若操作类型为发送操作 *****/
    if (RxStatus & 2) {        
        outportb(ISR, 0x2);                /* 清空中断状态寄存器中的发送状态位 */
        priv->stats.tx_packets++;            /* 累加设备私有结构体中的已接收数据包的个数*/
        TRACE("transmit one packet complete!/n");    /* 输出跟踪信息 */
    }
    
    /***** 若操作类型为接收操作 *****/
    if (RxStatus & 1) {            
        TRACE("Receivex packet..../n");    /* 输出跟踪信息 */
        outportb(ISR, 0x1);            /* 清空中断状态寄存器中的接收状态位 */    
        SetRegPage(1);            /* 切换到Rtl8019寄存器表的页1 */
        RxPageEnd = inportb(CURR);    /* 将页结束地址初始化为下一个数据页的起始地址 */    

        SetRegPage(0);            /* 切换到Rtl8019寄存器表的页0 */
        RxPageBeg = rBNRY+1;        /* 将页起始地址初始化为边界地址加1 */
        if(RxPageBeg>=RPSTOP)        /* 如果起始地址大于或等于接收缓冲区的结束地址,*/
            RxPageBeg = RPSTART;    /* 那么就将它设置为接收缓冲区的起始地址 */    
        outportb(BaseAddr, 0x22);    /* 中止远程DMA操作 */

    
        /***** 主机CPU从Rtl8019的RAM中将数据包的前4位读出,这4位标志是Rtl8019单独添加的,
               通过其中的长度位来判断本次封包的长度是否合法 *****/
        //outport(RSAR0, RxPageBeg<<8);
        //outport(RBCR0, 256);        
        outportb(RSAR0, 0);        /* 设置远程DMA传输的读取地址 */
        outportb(RSAR1, RxPageBeg);
        outportb(RBCR0, 4);        /* 设置远程DMA传输的数据长度 */
        outportb(RBCR1, 0);    
        outportb(BaseAddr, 0xa);    /* 执行Rtl8019的start命令起动远程DMA读取操作 */

#ifdef RTL8019_OP_16
        temp       = inportw(RWPORT);
        RxNextPage = temp>>8;
        RxStatus   = temp&0xff;
        RxLength   = inportw(RWPORT);
#else        /* 获得前4个字节 */
        RxStatus   = inportb(RWPORT);    /* 获得接收状态 */
        RxNextPage = inportb(RWPORT);    /* 获得下一数据页的起始地址 */
        RxLength   = inportb(RWPORT);    /* 获得整个数据包长度 */
        RxLength  |= inportb(RWPORT)<<8;
#endif        
        TRACE("/nRxBeg = %x, RxEnd = %x,  nextpage = %x,  size = %i/n", RxPageBeg, RxPageEnd, RxNextPage, RxLength);        
        if(RxLength == 0){        /* 如果数据长度为0,则输出调试信息并返回IRQ_HANDLED表示已有中断被触发 */
          TRACE("Rxlength == 0/n");    
          return IRQ_HANDLED;
        }

        RxLength -= 4;                /* 获得实际有效数据的长度 */
        if (RxLength>ETH_FRAME_LEN) {        /* 如果数据长度大于封包最大长度 */
            if (RxPageEnd==RPSTART)        /* 如果接收数据的结束页地址等于缓冲区的起始地址,那么说明所有数据已被读取, */
                rBNRY = RPSTOP-1;    /* 则将边界地址设置为缓冲区的结束页地址减1,表示已经读取到缓冲区尾部。*/
            else
                rBNRY = RxPageEnd-1;    /* 否则将边界地址设置为接收数据的结束页地址减1 */
                
            outportb(BNRY, rBNRY);        /* 设置边界寄存器的值 */
            TRACE("RxLength more long than %x/n", ETH_FRAME_LEN);  /* 输出数据过长的跟踪信息 */
            return IRQ_HANDLED;        /* 返回IRQ_HANDLED表示已有中断被触发 */
        }
#ifdef RTL8019_OP_16
        skb = dev_alloc_skb(RxLength+2);
#else
        skb = dev_alloc_skb(RxLength);        /* 为Socket缓冲区分配空间 */
#endif
        if (!skb) {                /* 如果分配失败 */
            TRACE("Rtl8019as eth: low on mem - packet dropped/n");    /* 输出失败的跟踪信息 */
            priv->stats.rx_dropped++;    /* 累加设备私有结构体中的丢掉的数据包计数 */
            return IRQ_HANDLED;        /* 返回IRQ_HANDLED表示已有中断被触发 */
        }

        skb->dev = dev;                /* 设置Socket缓冲区中的设备句柄 */    
        skb_reserve(skb, 2);            /* 在Socket缓冲区的前面预留2个字节的空间 */
        skb_put(skb, RxLength);            /* 在Socket缓冲区内为接收数据分配空间并更新缓冲区的tail和len成员 */
#ifdef RTL8019_OP_16
        data = ( u16 *)skb->data;
#else
        data = ( u8  *)skb->data;        /* 获取Socket缓冲区内的数据起始地址 */
#endif
        //        eth_copy_and_sum(skb, data, len, 0);
        outportb(RSAR0, 4);            /* 设置远程DMA传输的读取地址 */
        outportb(RSAR1, RxPageBeg);
        outportb(RBCR0, RxLength);        /* 设置远程DMA传输的数据长度 */
        outportb(RBCR1, RxLength>>8);    
        outportb(BaseAddr, 0xa);        /* 执行Rtl8019的start命令起动远程读取操作 */
#ifdef RTL8019_OP_16
        i = 2;
        data -= 2;
        RxLen=(RxLength+1)/2;    
#else
        i = 4;                    
        data -= 4;                /* 将数据缓冲区的地址提前4个字节,以便获取实际数据 */
        RxLen=RxLength;                /* 获取接收的数据长度 */
#endif
        for(; RxLen--;) {
#ifdef RTL8019_OP_16
            static const int cmp_val = 0x7f;
#else
            static const int cmp_val = 0xff;
#endif
            if (!(i & cmp_val)) {
                outportb(BNRY, RxPageBeg);                
                RxPageBeg++;
                if(RxPageBeg>=RPSTOP)
                    RxPageBeg = RPSTART;                    
            }
#ifdef RTL8019_OP_16
            data[i++] = inportw(RWPORT);        
            TRACE("%2X,%2X,", data[i-1]&0xff,data[i-1]>>8);
#else
            data[i++] = inportb(RWPORT);     /* 从Rtl8019的Remote DMA port读取数据 */    
            TRACE("%2X,", data[i-1]);     /* 将数据作为跟踪信息输出 */    
#endif
        }

        TRACE("/n");
        outportb(BNRY, RxPageBeg);         /* 设置边界寄存器的值 */
        rBNRY = RxPageBeg;             /* 保存当前读页地址 */

        skb->protocol = eth_type_trans(skb, dev);/* 设置skb->pkt_type用来判断该包是否属于该主机,并返回一个协议编号 */
        TRACE("/nprotocol=%x/n", skb->protocol); /* 输出协议编号 */    
        priv->stats.rx_packets++;         /* 累计接收到的数据包计数 */
        priv->stats.rx_bytes +=RxLength;     /* 累计接收到的字节计数 */
        netif_rx(skb);                 /* 如果skb->pkt_type为PACKET_HOST,则通知内核已经收到新的数据包,
                                如果为PACKET_OTHERHOST,则丢掉该包。*/
    } else {
        outportb(ISR, 0xfe);             /* 清空Rtl8019的中断状态寄存器(接收状态位除外) */
    }

    spin_unlock(&priv->lock);             /* 释放自旋锁 */
    return IRQ_HANDLED;
}


/***** 获得设备的统计信息 *****/
static struct net_device_stats *nic_8019_get_stats(struct net_device *dev)
{
    struct nic_8019_priv *priv = (struct nic_8019_priv *) dev->priv;    /* 获得设备私有结构体的指针 */
    TRACE("get_stats/n");    /* 输出跟踪信息 */
    return &priv->stats;    /* 返回设备的统计信息 */
}


/***** 设备的终止函数 *****/
static int nic_8019_stop(struct net_device *dev)
{
    TRACE("stop/n");        /* 输出跟踪信息 */
    SetRegPage(3);            /* 切换到Rtl8019寄存器表的页3 */
    outportb(CR9346, 0xcf);        /* 将9346CR寄存器的EEM1和EEM0都设置为1,以便启用配置寄存器CONFIG3的写入功能 */
    outportb(CONFIG3, 0x66);    /* 进入Rtl8019的sleep(休眠)和pwrdn(低功耗)模式, 将led0设置为led_col(响应传输冲突),
                       将led1设置为led_crs(响应数据传输,包括发送和接收)。*/    
    outportb(CR9346, 0x3f);     /* 禁用配置寄存器CONFIG3的写入功能 */
    
    free_irq(dev->irq, dev);    /* 释放设备中断 */
    netif_stop_queue(dev);        /* 停止设备的传输队列 */
    MOD_DEC_USE_COUNT;        /* 递减该模块的调用次数 */

    return 0;
}


/***** 模块的清理函数 *****/
void __exit nic_8019_cleanup(void)
{
    TRACE("cleanup/n");        /* 输出跟踪信息 */
    kfree(nic_8019_netdevs.priv);    /* 释放设备的私有结构体 */
    unregister_netdev(&nic_8019_netdevs);    /* 注销设备 */
    return;
}


/* 指定模块的初始化和清理函数 */
module_init(nic_8019_init_module);
module_exit(nic_8019_cleanup);

/* 设置模块信息 */
MODULE_DESCRIPTION("Rtl8019as ethernet driver");
MODULE_AUTHOR("antiscle <savieetan@yahoo.com.cn>");
MODULE_LICENSE("GPL");
原创粉丝点击