该驱动基于linux-2.6.24.4内核。
首先,需要在arch/arm/mach-s3c2410/mach-smdk2410.c文件中添加如下代码:
static struct resource s3c_dm9000_resource [] = {
[0] = {
.start = 0x10000000,
.end = 0x10000040,
.flags = IORESOURCE_MEM
},
[1] = {
.start = IRQ_EINT2,
.end = IRQ_EINT2,
.flags = IORESOURCE_IRQ,
}
};
|
注意上面的start、end等地址是指的网卡的物理地址。然后,还要在该文件中加入如下代码:
struct platform_device s3c_device_dm9000 = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_dm9000_resource),
.resource = s3c_dm9000_resource,
};
|
需要特别注意上面的name字段,当设备驱动程序寻找设别资源时,会根据该字段对设备进行匹配。另外,该文件中的smdk2410_devices[]数组中,还需要加入s3c_device_dm9000,不然系统启动时没有找到该资源就不会调用相应的probe函数。
下面分析驱动程序的probe函数。若驱动被编译进内核,则在系统启动的时候,该函数会被调用。该函数的源代码如下:
static int dm9k_drv_probe(struct platform_device *pdev) { struct net_device *ndev; unsigned long base; unsigned int *addr = NULL; int ret = -ENODEV; ndev = alloc_etherdev(sizeof(struct board_info)); if (!ndev) { printk("%s: could not allocate device./n", CARDNAME); return -ENOMEM; } ndev->dma = (unsigned char)-1; if (pdev->num_resources < 2 || pdev->num_resources > 3) { printk("DM9000: Wrong num of resources %d/n", pdev->num_resources); ret = -ENODEV; goto out; } base = pdev->resource[0].start; ndev->irq = pdev->resource[1].start; /* * Request the regions. */ if (!request_mem_region(base, 4, ndev->name)) { ret = -EBUSY; goto out; }
addr = ioremap(base, 4); if (!addr) { ret = -ENOMEM; goto release_mem; } ret = dm9k_probe(ndev, (unsigned long)addr); if (ret != 0) { iounmap(addr); release_mem: release_mem_region(base, 4); out: printk("%s: not found (%d)./n", CARDNAME, ret); kfree(ndev); } return ret; }
|
函数首先调用alloc_etherdev,该函数在include/linux/etherdevice.h中声明,其中有如下语句:
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
|
而alloc_etherdev_mq函数又定义在net/ethernet/eth.c中,如下:
struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count) { return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count); }
|
可见,该函数只是用自己的参数来调用alloc_netdev_mq函数。alloc_netdev_mq函数定义在net/core/dev.c中,原型如下:
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name, void (*setup)(struct net_device *), unsigned int queue_count)
|
关于该函数的说明:
/**
* alloc_netdev_mq - allocate network device
* @sizeof_priv: size of private data to allocate space for
* @name: device name format string
* @setup: callback to initialize device
* @queue_count: the number of subqueues to allocate
*
* Allocates a struct net_device with private data area for driver use
* and performs basic initialization. Also allocates subquue structs
* for each queue on the device at the end of the netdevice.
*/
可见,alloc_etherdev为设备驱动分配了私有数据空间,并对设备驱动做了一些初始化工作。
接下来,设备驱动将要检查设备的resources的数量,如果数量小于2或者大于3,则初始化函数自动返回,初始化失败。我们的设备驱动中,resources的数量为2:一个表示设备的IO地址,另一个是设备的中断号。
代码
base = pdev->resource[0].start; ndev->irq = pdev->resource[1].start;
|
分别得到设备的端口地址和中断号。
接下来,驱动程序将向系统申请io内存,从地址base开始,大小为4个字节。如果申请成功,接下来需要做的就是将地址重新映射,从地址base开始,长度为4个字节。这样做的原因主要是驱动程序一般不直接访问物理地址,而访问虚拟地址。地址重新映射成功后,就调用dm9k_probe函数进行设备初始化。
dm9k_probe函数的全部代码如下
int __init dm9k_probe(struct net_device *dev, unsigned long addr) { struct board_info *db; /* Point a board information structure */ u32 id_val; u16 i, j; int retval; /* Search for DM9000 serial NIC */ PUTB(DM9KS_VID_L, addr); id_val = GETB(addr + 2); /* Change offset to 2 ^^^^^ */ PUTB(DM9KS_VID_H, addr); id_val |= GETB(addr + 2) << 8; PUTB(DM9KS_PID_L, addr); id_val |= GETB(addr + 2) << 16; PUTB(DM9KS_PID_H, addr); id_val |= GETB(addr + 2) << 24; if (id_val != DM9KS_ID && id_val != DM9010_ID) { /* Dm9k chip not found */ printk("dmfe_probe(): DM9000 not found. ID=%08X/n", id_val); return -ENODEV; } printk("<DM9KS> I/O: %lx, VID: %x /n",addr, id_val); /* Allocated board information structure */ memset(dev->priv, 0, sizeof(struct board_info)); db = (board_info_t *)dev->priv; dmfe_dev = dev; db->io_addr = addr; db->io_data = addr + 2; /* Change offset to 2 ^^^^^ */ /* driver system function */ dev->base_addr = addr; dev->irq = IRQ_EINT2; dev->open = &dmfe_open; dev->hard_start_xmit = &dmfe_start_xmit; dev->watchdog_timeo = HZ; dev->tx_timeout = dmfe_timeout; dev->stop = &dmfe_stop; dev->get_stats = &dmfe_get_stats; dev->set_multicast_list = &dm9000_hash_table; dev->do_ioctl = &dmfe_do_ioctl; for(i=0,j=0x10; i<6; i++,j++) { db->srom[i] = ior(db, j); } /* Set Node Address */ for (i=0; i<6; i++) dev->dev_addr[i] = db->srom[i]; retval = register_netdev(dev); if (retval == 0) { /* now, print out the card info, in a short format.. */ printk("%s: at %#lx IRQ %d/n", dev->name, dev->base_addr, dev->irq); if (dev->dma != (unsigned char)-1) printk(" DMA %d/n", dev->dma); if (!is_valid_ether_addr(dev->dev_addr)) { printk("%s: Invalid ethernet MAC address. Please " "set using ifconfig/n", dev->name); } else { /* Print the Ethernet address */ printk("%s: Ethernet addr: ", dev->name); for (i = 0; i < 5; i++) printk("%2.2x:", dev->dev_addr[i]); printk("%2.2x/n", dev->dev_addr[5]); } } return 0; }
|
函数首先调用PUTB来写dm9000a芯片,来看看PUTB的实现
#define PUTB(d,a) *((volatile unsigned char *) (a)) = d
|
可见,PUTB是直接使用的指针,而没有使用内核提供的write等函数,同样,GETB函数如下
#define GETB(a) *((volatile unsigned char *) (a))
|
注意,这里的地址都是虚拟地址,因为前面调用函数dm9k_probe时传递的addr时重新映射后的,而不是直接传送的物理地址。
具体操作涉及到dm9000a的硬件实现,做简单的说明。dm9000a有两个PORT,一个是INDEX PORT,另一个就是DATA PORT。具体访问哪一个是根据CMD引脚的信号来确定的:CMD为0,则访问INDEX,否则,访问DATA。访问寄存器之前,必须将寄存器的地址存放在INDEX PORT。
首先,驱动程序需要读芯片的ID。DM9000A的ID存放在四个不同的字节中,分别叫做Vendor ID和Product ID。将着四个字节读出来,组合后应该得到0x90000A46,如果读出来的ID与该值不相等,说明不是DM9000A网卡,程序将返回,初始化失败。
读出ID相同后,就可以认为系统中存在dm9000a网卡了,接下来就开始进行其他初始化工作。主要工作
dev->base_addr = addr; dev->irq = IRQ_EINT2; dev->open = &dmfe_open; dev->hard_start_xmit = &dmfe_start_xmit; dev->watchdog_timeo = HZ; dev->tx_timeout = dmfe_timeout; dev->stop = &dmfe_stop; dev->get_stats = &dmfe_get_stats; dev->set_multicast_list = &dm9000_hash_table; dev->do_ioctl = &dmfe_do_ioctl;
|
就是为net_device的成员指定功能函数,以便系统需要的时候进行调用。完成这些基本的工作后,就可以向系统注册设备了
retval = register_netdev(dev);
|
注册完成,该函数就返回。probe函数剩下的就是对返回值的判断了,若注册成功,直接推出,probe完成;失败的话,还需要将ioremap过的地方ioumap掉,request_mem_region的地方release掉。
前面分析了dm9000a网卡的probe部分,接下来继续其他部分。
当用户在命令行下使用ifconfig等命令的时候,网卡设备将打开,系统将调用open函数。dm9000a的open函数如下
static int dmfe_open(struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; u8 reg_nsr; int i; if (request_irq(dev->irq,&dmfe_interrupt,IRQF_SHARED,dev->name,dev)) return -EAGAIN; /* Grab the IRQ */ set_irq_type(dev->irq, IRQ_TYPE_EDGE_RISING); /* Initilize DM910X board */ dmfe_init_dm9000(dev); /* Init driver variable */ db->reset_counter = 0; db->reset_tx_timeout = 0; db->cont_rx_pkt_cnt = 0; /* check link state and media speed */ db->Speed =10; i=0; do { reg_nsr = ior(db,0x1); if(reg_nsr & 0x40) /* link OK!! */ { /* wait for detected Speed */ mdelay(200); reg_nsr = ior(db,0x1); if(reg_nsr & 0x80) db->Speed =10; else db->Speed =100; break; } i++; mdelay(1); }while(i<3000); /* wait 3 second */ //printk("i=%d Speed=%d/n",i,db->Speed);
/* set and active a timer process */ init_timer(&db->timer); db->timer.expires = DMFE_TIMER_WUT * 2; db->timer.data = (unsigned long)dev; db->timer.function = &dmfe_timer; add_timer(&db->timer); //Move to DM9000 initiallization was finished.
netif_start_queue(dev); return 0; }
|
函数首先向系统申请中断,利用内核提供的request_irq函数。该函数声明于include/linux/interrupt.h中,而它的定于位于kernel/irq/manage.c中。关于它的说明和原型如下
/** * request_irq - allocate an interrupt line * @irq: Interrupt line to allocate * @handler: Function to be called when the IRQ occurs * @irqflags: Interrupt type flags * @devname: An ascii name for the claiming device * @dev_id: A cookie passed back to the handler function * * This call allocates interrupt resources and enables the * interrupt line and IRQ handling. From the point this * call is made your handler function may be invoked. Since * your handler function must clear any interrupt the board * raises, you must take care both to initialise your hardware * and to set up the interrupt handler in the right order. * * Dev_id must be globally unique. Normally the address of the * device data structure is used as the cookie. Since the handler * receives this value it makes sense to use it. * * If your interrupt is shared you must pass a non NULL dev_id * as this is required when freeing the interrupt. * * Flags: * * IRQF_SHARED Interrupt is shared * IRQF_DISABLED Disable local interrupts while processing * IRQF_SAMPLE_RANDOM The interrupt can be used for entropy * */ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
|
可见,传递给该函数的第一个参数是申请的中断号;第二个参数是一个函数指针,当发生相应的中断时,系统将调用该函数处理中断;第三个参数是中断的标志,可以是它说明文档中提到的三种中的任何一种,我们用的是IRQF_SHARED,表示中断可以共享;第四个参数就是设备的简称,可以在/proc/interrupts列表中找到。
申请完成中断后,驱动程序设置了中断的类型,使用set_irq_type函数。该函数声明于include/linux/interrupt.h,而定义于kernel/irq/chip.c,原型如下
/** * set_irq_type - set the irq type for an irq * @irq: irq number * @type: interrupt type - see include/linux/interrupt.h */ int set_irq_type(unsigned int irq, unsigned int type)
|
第一个参数是中断号,第二歌参数是中断类型,表示上升沿触发、下降沿触发、高电平触发或者低电平触发。我们使用的是IRQ_TYPE_EDGE_RISING,即上升沿触发。
中断设置完成后,驱动程序需要对dm9000a芯片进行初始化,调用dmfe_init_dm9000函数,如下
static void dmfe_init_dm9000(struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; /* set the internal PHY power-on, GPIOs normal, and wait 2ms */ iow(db, DM9KS_GPR, 1); /* Power-Down PHY */ udelay(500); iow(db, DM9KS_GPR, 0); /* GPR (reg_1Fh)bit GPIO0=0 pre-activate PHY */ udelay(20); /* wait 2ms for PHY power-on ready */ /* do a software reset and wait 20us */ iow(db, DM9KS_NCR, 3); udelay(20); /* wait 20us at least for software reset ok */ iow(db, DM9KS_NCR, 3); /* NCR (reg_00h) bit[0] RST=1 & Loopback=1, reset on */ udelay(20); /* wait 20us at least for software reset ok */ /* I/O mode */ db->io_mode = ior(db, DM9KS_ISR) >> 6; /* ISR bit7:6 keeps I/O mode */ /* Set PHY */ db->op_mode = media_mode; set_PHY_mode(db); /* Program operating register */ iow(db, DM9KS_NCR, 0); iow(db, DM9KS_TCR, 0); /* TX Polling clear */ iow(db, DM9KS_BPTR, 0x3f); /* Less 3kb, 600us */ iow(db, DM9KS_SMCR, 0); /* Special Mode */ iow(db, DM9KS_NSR, 0x2c); /* clear TX status */ iow(db, DM9KS_ISR, 0x0f); /* Clear interrupt status */ /* Added by jackal at 03/29/2004 */ /* Set address filter table */ dm9000_hash_table(dev); /* Activate DM9000A/DM9010 */ iow(db, DM9KS_RXCR, DM9KS_REG05 | 1); /* RX enable */ iow(db, DM9KS_IMR, DM9KS_REGFF); // Enable TX/RX interrupt mask
/* Init Driver variable */ db->tx_pkt_cnt = 0; netif_carrier_on(dev); spin_lock_init(&db->lock); }
|
该函数中使用了iow函数,看一看它的实现
static void iow(board_info_t *db, int reg, u8 value) { PUTB(reg, db->io_addr); PUTB(value, db->io_data); }
|
即将value写入reg表示的寄存器中。dmfe_init_dm9000函数的具体功能函数里面已经有注释,更详细的可以查看datasheet。dmfe_init_dm9000函数返回后,open函数还做了一些工作。首先,初始化一些设备变量
db->reset_counter = 0; db->reset_tx_timeout = 0; db->cont_rx_pkt_cnt = 0;
|
这些值在发送和接收数据的时候将会使用到,到讨论那些函数的时候将详细介绍。接下来,驱动程序需要为自己增加一个timer
init_timer(&db->timer); db->timer.expires = DMFE_TIMER_WUT * 2; db->timer.data = (unsigned long)dev; db->timer.function = &dmfe_timer; add_timer(&db->timer); //Move to DM9000 initiallization was finished.
|
timer的expires变量决定定时时间,当定时时间到的时候,就会执行function指定的函数。最后,使用add_timer()函数将初始化的timer插入挂起定时器全局队列。关于function指定的函数,将在后面说明。
最后,open函数调用netif_start_queue,该函数的定义位于include/linux/netdevice.h中
/** * netif_start_queue - allow transmit * @dev: network device * * Allow upper layers to call the device hard_start_xmit routine. */ static inline void netif_start_queue(struct net_device *dev) { clear_bit(__LINK_STATE_XOFF, &dev->state); }
|
可见,该函数告诉系统可以使用hard_start_xmit进行数据发送了。
open函数到此就结束了。
前面讨论了probe函数和open函数,下面继续。
内核发送数据在底层是通过dmfe_start_xmit函数来实现的
static int dmfe_start_xmit(struct sk_buff *skb, struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; char * data_ptr; int i, tmplen; if(db->Speed == 10) {if (db->tx_pkt_cnt >= 1) return 1;} else {if (db->tx_pkt_cnt >= 2) return 1;} /* packet counting */ db->tx_pkt_cnt++; db->stats.tx_packets++; db->stats.tx_bytes+=skb->len; if (db->Speed == 10) {if (db->tx_pkt_cnt >= 1) netif_stop_queue(dev);} else {if (db->tx_pkt_cnt >= 2) netif_stop_queue(dev);} /* Disable all interrupt */ iow(db, DM9KS_IMR, DM9KS_DISINTR); /* Set TX length to reg. 0xfc & 0xfd */ iow(db, DM9KS_TXPLL, (skb->len & 0xff)); iow(db, DM9KS_TXPLH, (skb->len >> 8) & 0xff); /* Move data to TX SRAM */ data_ptr = (char *)skb->data; PUTB(DM9KS_MWCMD, db->io_addr); // Write data into SRAM trigger
switch(db->io_mode) { case DM9KS_BYTE_MODE: for (i = 0; i < skb->len; i++) PUTB((data_ptr[i] & 0xff), db->io_data); break; case DM9KS_WORD_MODE: tmplen = (skb->len + 1) / 2; for (i = 0; i < tmplen; i++) PUTW(((u16 *)data_ptr)[i], db->io_data); break; case DM9KS_DWORD_MODE: tmplen = (skb->len + 3) / 4; for (i = 0; i< tmplen; i++) PUTL(((u32 *)data_ptr)[i], db->io_data); break; } #if !defined(ETRANS) /* Issue TX polling command */ iow(db, DM9KS_TCR, 0x1); /* Cleared after TX complete*/ #endif /* Saved the time stamp */ dev->trans_start = jiffies; db->cont_rx_pkt_cnt =0; /* Free this SKB */ dev_kfree_skb(skb); /* Re-enable interrupt */ iow(db, DM9KS_IMR, DM9KS_REGFF); return 0; }
|
该函数首先判断设备使用的模式,若speed为10,则发送的数据包个数最大为1,若speed为100,则最大个数为2.超过这两个值,函数立即返回。若不超过,则说明可以进行数据发送。然后是更新系统的统计信息。如果待发送包达到上限,则调用netif_stop_queue,告诉内核暂时停止内核与驱动程序间的数据传递。
这些完成后,就可以开始真正的数据传输了。先禁止dm9000a的所有中断,通过写它的Interrupt Mask Register来实现。然后将要传递的数据的长度信息写入TX Packet Length Register中。
需要注意的是char * data_ptr在这里不能理解为一个指向char变量的指针,而应该理解为一个char数组。根据芯片的硬件连接方式,选择字节、半字或者字的方式对数据进行发送。发送完成后,记录下时间戳,并释放skb的空间,然后允许dm9000a的中断,以便继续进行发送或者接收。
stop方法和open函数的方法作用相反,即停止网络设备
static int dmfe_stop(struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; /* deleted timer */ del_timer(&db->timer); netif_stop_queue(dev); /* free interrupt */ free_irq(dev->irq, dev); /* RESET devie */ phy_write(db, 0x00, 0x8000); /* PHY RESET */ iow(db, DM9KS_GPR, 0x01); /* Power-Down PHY */ iow(db, DM9KS_IMR, DM9KS_DISINTR); /* Disable all interrupt */ iow(db, DM9KS_RXCR, 0x00); /* Disable RX */ /* Dump Statistic counter */ return 0; }
|
完成的工作主要有删除定时器、释放中断、调用netif_stop_queue()告诉内核停止内核与驱动程序之间的数据交换,最后使dm9000a网卡处于power-down模式。
下面分析一个重要的函数--中断处理函数
static irqreturn_t dmfe_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; board_info_t *db; int int_status,i; u8 reg_save; /* A real interrupt coming */ db = (board_info_t *)dev->priv; spin_lock(&db->lock); /* Save previous register address */ reg_save = GETB(db->io_addr); /* Disable all interrupt */ iow(db, DM9KS_IMR, DM9KS_DISINTR); /* Got DM9000A/DM9010 interrupt status */ int_status = ior(db, DM9KS_ISR); /* Got ISR */ iow(db, DM9KS_ISR, int_status); /* Clear ISR status */ /* Link status change */ if (int_status & DM9KS_LINK_INTR) { netif_stop_queue(dev); for(i=0; i<500; i++) /*wait link OK, waiting time =0.5s */ { phy_read(db,0x1); if(phy_read(db,0x1) & 0x4) /*Link OK*/ { /* wait for detected Speed */ for(i=0; i<200;i++) udelay(1000); /* set media speed */ if(phy_read(db,0)&0x2000) db->Speed =100; else db->Speed =10; break; } udelay(1000); } netif_wake_queue(dev); //printk("[INTR]i=%d speed=%d/n",i, (int)(db->Speed));
} /* Received the coming packet */ if (int_status & DM9KS_RX_INTR) dmfe_packet_receive(dev); /* Trnasmit Interrupt check */ if (int_status & DM9KS_TX_INTR) dmfe_tx_done(0); if (db->cont_rx_pkt_cnt>=CONT_RX_PKT_CNT) { iow(db, DM9KS_IMR, 0xa2); } else { /* Re-enable interrupt mask */ iow(db, DM9KS_IMR, DM9KS_REGFF); } /* Restore previous register address */ PUTB(reg_save, db->io_addr); spin_unlock(&db->lock); return IRQ_HANDLED; }
|
前面已经提到,注册中断的时候该函数作为request_irq()函数的第二个参数。发生中断时,系统将调用该函数进行相关处理。
函数首先需要获得自旋锁,然后将当前的寄存器地址保存下来,以便返回的时候继续进行被打断的作业;接着就是屏蔽所有的中断,读取中断状态寄存器并清除中断状态寄存器,然后就开始真正的中断处理了。
在进行中断处理之前,需要首先判断是发生了什么中断。有如下几种可能:连接状态改变、数据接收中断或者数据发送中断。连接状态改变的处理比较简单,就不讨论了。
首先看数据接收中断。当发生接收中断时,中断函数调用dmfe_packet_receive()函数
static void dmfe_packet_receive(struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; struct sk_buff *skb; u8 rxbyte, val; u16 i, GoodPacket, tmplen = 0, MDRAH, MDRAL; u32 tmpdata; rx_t rx; u16 * ptr = (u16*)℞ u8* rdptr; do { /*store the value of Memory Data Read address register*/ MDRAH=ior(db, DM9KS_MDRAH); MDRAL=ior(db, DM9KS_MDRAL); ior(db, DM9KS_MRCMDX); /* Dummy read */ rxbyte = GETB(db->io_data); /* Got most updated data */ /* packet ready to receive check */ if(!(val = check_rx_ready(rxbyte))) break; /* A packet ready now & Get status/length */ GoodPacket = TRUE; PUTB(DM9KS_MRCMD, db->io_addr); /* Read packet status & length */ switch (db->io_mode) { case DM9KS_BYTE_MODE: *ptr = GETB(db->io_data) + (GETB(db->io_data) << 8); *(ptr+1) = GETB(db->io_data) + (GETB(db->io_data) << 8); break; case DM9KS_WORD_MODE: *ptr = GETW(db->io_data); *(ptr+1) = GETW(db->io_data); break; case DM9KS_DWORD_MODE: tmpdata = GETL(db->io_data); *ptr = tmpdata; *(ptr+1) = tmpdata >> 16; break; default: break; } /* Packet status check */ if (rx.desc.status & 0xbf) { GoodPacket = FALSE; if (rx.desc.status & 0x01) { db->stats.rx_fifo_errors++; printk("<RX FIFO error>/n"); } if (rx.desc.status & 0x02) { db->stats.rx_crc_errors++; printk("<RX CRC error>/n"); } if (rx.desc.status & 0x80) { db->stats.rx_length_errors++;
|