1. 基本概念
DM9000C是一款完全集成的和符合成本效益单芯片快速以太网MAC控制器与一般处理接口,一个10/100M自适应的PHY和4K DWORD值的SRAM。DM9000C内部还集成了接收缓冲区,可以在接收到数据的时候把数据存放到缓冲区中,链路层可以直接把数据从缓冲区取走。针对不同的处理器, 接口支持8位、 16位。
本节内容先分析DM9000厂家提供的网卡驱动程序dm9dev9000c.c,然后将其移植到JZ2440开发板上使用,网卡驱动基本知识参考十九、Linux驱动之虚拟网卡驱动。
2. 分析dm9dev9000c.c
接下来分析DM9000厂家提供的网卡驱动程序dm9dev9000c.c。首先分析驱动的入口函数,完整代码如下:
// static int mode = DM9KS_AUTO;
int __init init_module(void)
{
switch(mode) { //模式选择
case DM9KS_10MHD:
case DM9KS_100MHD:
case DM9KS_10MFD:
case DM9KS_100MFD:
media_mode = mode;
break;
default:
media_mode = DM9KS_AUTO;
}
dmfe_dev = dmfe_probe();
if(IS_ERR(dmfe_dev))
return PTR_ERR(dmfe_dev);
return 0;
}
首先就是DM9000C的模式选择,我们知道DM9000有两种模式,即同时支持10Mbps和100Mbps两种速率。同时我们的芯片又有一个很强大的功能就是自协商,即通信速率通过双方协商,这里默认是自协商模式。然后进入dmfe_probe()函数。
2.1 dmfe_probe()函数
dmfe_probe()函数部分代码如下:
struct net_device * __init dmfe_probe(void)
{
struct net_device *dev;
int err;
DMFE_DBUG(0, "dmfe_probe()",0);
dev= alloc_etherdev(sizeof(struct board_info)); /* 分配一个net_device结构体*/
if(!dev)
return ERR_PTR(-ENOMEM);
SET_MODULE_OWNER(dev);
err = dmfe_probe1(dev); /* 设置net_device结构体 */
if (err)
goto out;
err = register_netdev(dev); /* 注册这个net_device结构体 */
if (err)
goto out1;
return dev;
out1:
release_region(dev->base_addr,2);
out:
free_netdev(dev);
return ERR_PTR(err);
}
正如上一节十九、Linux驱动之虚拟网卡驱动分析,网卡驱动框架:
1. 分配一个net_device结构体
2. 设置net_device结构体
3. 注册net_device结构体
可见我们的重点在设置net_device结构体里即dmfe_probe1()函数。
2.2 dmfe_probe1()函数
部分代码如下:
int __init dmfe_probe1(struct net_device *dev)
{
struct board_info *db; /* Point a board information structure */
u32 id_val;
u16 i, dm9000_found = FALSE;
u8 MAC_addr[6]={0x00,0x60,0x6E,0x33,0x44,0x55};
u8 HasEEPROM=0,chip_info;
DMFE_DBUG(0, "dmfe_probe1()",0);
/* Search All DM9000 serial NIC */
do {
outb(DM9KS_VID_L, iobase);
id_val = inb(iobase + 4); /* 读厂家ID的低字节 */
outb(DM9KS_VID_H, iobase);
id_val |= inb(iobase + 4) << 8; /* 读厂家ID的高字节 */
outb(DM9KS_PID_L, iobase);
id_val |= inb(iobase + 4) << 16; /* 读设备ID的低字节 */
outb(DM9KS_PID_H, iobase);
id_val |= inb(iobase + 4) << 24; /* 读设备ID的高字节 */
if (id_val == DM9KS_ID || id_val == DM9010_ID) { /* 将读到的与芯片的比较,如果一样则继续运行 */
/* Request IO from system */
if(!request_region(iobase, 2, dev->name)) /* 分配网络设配器所占内存 */
return -ENODEV;
printk(KERN_ERR"<DM9KS> I/O: %x, VID: %x \n",iobase, id_val);
dm9000_found = TRUE;
/* Allocated board information structure */
memset(dev->priv, 0, sizeof(struct board_info)); /* 设置net_device结构体的私有数据大小为board_info大小 */
db = (board_info_t *)dev->priv; /* 将board_info结构体放到dev的私有数据中 */
dmfe_dev = dev;
db->io_addr = iobase; /* 设置芯片命令寄存器的基地址 */
db->io_data = iobase + 4; /* 设置芯片数据寄存器的基地址,而此处为什么加4,是因为CMD引脚与地址laddr2引脚相连 */
db->chip_revision = ior(db, DM9KS_CHIPR); /* 获取芯片的版本信息,CHIPR为芯片版本寄存器地址为2CH */
chip_info = ior(db,0x43); /* 而这句话我在芯片手册上没有找到相应的寄存器,所以不知道读什么信息 */
if((db->chip_revision!=0x1A) || ((chip_info&(1<<5))!=0) || ((chip_info&(1<<2))!=1)) return -ENODEV;
/* driver system function,下面就是驱动系统函数的设置了 */
dev->base_addr = iobase; /* 设置IO基地址 */
dev->irq = irq; /* 设置中断序列号,在本程序中使用外部中断7,这在后面设置 */
dev->open = &dmfe_open; /* 设置open函数,当打开网卡时调用该函数 */
dev->hard_start_xmit = &dmfe_start_xmit; /* 设置传输函数,当要传输数据时,调用该函数 */
dev->watchdog_timeo = 5*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;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,28)
dev->ethtool_ops = &dmfe_ethtool_ops;
#endif
#ifdef CHECKSUM
dev->features |= NETIF_F_IP_CSUM|NETIF_F_SG;
#endif
/* 设置MII总线 */
db->mii.dev = dev; /* 设置MII接口 */
db->mii.mdio_read = mdio_read; /* MII方式读函数 */
db->mii.mdio_write = mdio_write; /* MII方式写函数 */
db->mii.phy_id = 1;
db->mii.phy_id_mask = 0x1F;
db->mii.reg_num_mask = 0x1F;
/* Read SROM content,如果芯片接有EEPROM,读EEPROM的内容 */
for (i=0; i<64; i++)
((u16 *)db->srom)[i] = read_srom_word(db, i);
/* Get the PID and VID from EEPROM to check,从EEPROM中获得PID和VID进行比较 */
id_val = (((u16 *)db->srom)[4])|(((u16 *)db->srom)[5]<<16);
printk("id_val=%x\n", id_val);
if (id_val == DM9KS_ID || id_val == DM9010_ID)
HasEEPROM =1;
/* Set Node Address,设置节点地址 */
for (i=0; i<6; i++)
{
if (HasEEPROM) /* use EEPROM,如果有EEPROM使用EEPROM中的值 */
dev->dev_addr[i] = db->srom[i];
else /* No EEPROM ,如果没有,使用厂家提供的MAC值*/
dev->dev_addr[i] = MAC_addr[i];
}
}//end of if()
iobase += 0x10;
}while(!dm9000_found && iobase <= DM9KS_MAX_IO);
return dm9000_found ? 0:-ENODEV;
}
该函数总结如下:
1. 读取芯片的厂家ID和设备ID,并与厂家提供的进行比较
2. 设置board_info结构体
3. 设置net_device结构体
4. 设置MII总线
5. 看DM9000芯片是否外接有EEPROM芯片,并做相应的设置
在上一节已经分析过net_device结构体了,board_info结构体如下:
typedef struct board_info {
u32 io_addr; /* Register I/O base address,命令寄存器IO基地址 */
u32 io_data; /* Data I/O address ,数据寄存器IO地址*/
u8 op_mode; /* PHY operation mode ,PHY操作模式*/
u8 io_mode; /* 0:word, 2:byte ,IO模式,0位字,而2为字节*/
u8 Speed; /* current speed,当前的速度 */
u8 chip_revision;
int rx_csum; /* 0:disable, 1:enable,接收计数使能,0为不使能,1为使能 */
u32 reset_counter;/* counter: RESET */
u32 reset_tx_timeout;/* RESET caused by TX Timeout */
int tx_pkt_cnt; /* 传输包计数 */
int cont_rx_pkt_cnt;/* current number of continuos rx packets , */
struct net_device_stats stats; /* 设备传输统计 */
struct timer_list timer; /* 时间列表 */
unsigned char srom[128];
spinlock_t lock; /* 自旋锁 */
struct mii_if_info mii; /* MII总线信息 */
} board_info_t;
在该函数中对net_device结构体成员进行了一系列填充,接下来一个个分析这些驱动函数是做什么的。
2.3 dmfe_open()函数
当ifconfig使用网卡时,会调用到该网卡net_device->open()函数,在该文件也就是dmfe_open()函数。部分代码如下:
static int dmfe_open(struct net_device *dev)
{
board_info_t *db = (board_info_t *)dev->priv;
u8 reg_nsr;
int i;
DMFE_DBUG(0, "dmfe_open", 0);
if (request_irq(dev->irq,&dmfe_interrupt,0,dev->name,dev)) /* 注册中断函数 */
return -EAGAIN;
/* 初始化DM9000 */
dmfe_init_dm9000(dev);
/* 初始化驱动变量 */
db->reset_counter = 0;
db->reset_tx_timeout = 0;
db->cont_rx_pkt_cnt = 0;
/* 检测连接状态和设备速度 */
db->Speed =10;
i=0;
do {
reg_nsr = ior(db,DM9KS_NSR);
if(reg_nsr & 0x40) /* link OK!! */
{
/* wait for detected Speed */
mdelay(200);
reg_nsr = ior(db,DM9KS_NSR);
if(reg_nsr & 0x80)
db->Speed =10;
else
db->Speed =100;
break;
}
i++;
mdelay(1);
}while(i<3000); /* wait 3 second */
/* 设置添加定时器 */
init_timer(&db->timer);
db->timer.expires = DMFE_TIMER_WUT;
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;
}
该函数总结如下:
1. 申请中断
2. 初始化DM9000
3. 设置board_info结构体
下面对以上几点进行详细分析。
2.3.1 申请中断
// #define DM9KS_IRQ 3
// static int irq = DM9KS_IRQ;
// dev->irq = irq; //dmfe_probe1()函数中
request_irq(dev->irq,&dmfe_interrupt,0,dev->name,dev)
第一个参数中使用的是dev->irq,在dmfe_probe1()函数中填充,默认是3,需要根据自己的开发板修改,JZ2440用的是外部中断7;第二个参数是中断处理函数,后面的接收数据部分就是在该函数里完成的。而第三个参数是中断触发方式,在这里的参数是0,即没有边沿是不合理的,需要改为上升沿触发。
2.3.2 初始化DM9000
使用dmfe_init_dm9000()函数进行DM9000的初始化,函数代码如下:
static void dmfe_init_dm9000(struct net_device *dev)
{
board_info_t *db = (board_info_t *)dev->priv;
DMFE_DBUG(0, "dmfe_init_dm9000()", 0);
spin_lock_init(&db->lock); /* 初始化自旋锁 */
iow(db, DM9KS_GPR, 0); /* 设置通用目的寄存器(寄存器号为1Fh),设置PHY启动 */
mdelay(20); /* 等待PHY启动就绪 */
/* 软件重置然后在等20us */
iow(db, DM9KS_NCR, 3); /* 设置网络设置寄存器,使用软件重置 */
udelay(20); /* 为软件重置成功,等待20us */
iow(db, DM9KS_NCR, 3); /* 设置网络设置寄存器NCR (reg_00h)的bit[0] 软件复位并且设置回环模式为MAC内部回环 */
udelay(20); /* 为软件重置成功,等待20us */
/* I/O mode */
db->io_mode = ior(db, DM9KS_ISR) >> 6; /* 中断状态寄存器ISR的bit7:6 保存着I/O mode,0为16bit而1为8bit */
/* Set PHY */
db->op_mode = media_mode; /* 设置芯片的模型,我们在开始的时候已经将其设置为自协商模式 */
set_PHY_mode(db);
/* 操作寄存器设置DM9000 */
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 */
iow(db, DM9KS_TCR2, 0x80); /* Set LED mode 1 */
if (db->chip_revision == 0x1A){
/* Data bus current driving/sinking capability */
iow(db, DM9KS_BUSCR, 0x01); /* default: 2mA */
}
#ifdef FLOW_CONTROL
iow(db, DM9KS_BPTR, 0x37);
iow(db, DM9KS_FCTR, 0x38);
iow(db, DM9KS_FCR, 0x29);
#endif
if (dev->features & NETIF_F_HW_CSUM){
printk(KERN_INFO "DM9KS:enable TX checksum\n");
iow(db, DM9KS_TCCR, 0x07); /* TX UDP/TCP/IP checksum enable */
}
if (db->rx_csum){
printk(KERN_INFO "DM9KS:enable RX checksum\n");
iow(db, DM9KS_RCSR, 0x02); /* RX checksum enable */
}
/* Set address filter table */
dm9000_hash_table(dev);
/* Activate DM9000/DM9010 */
iow(db, DM9KS_IMR, DM9KS_REGFF); /* Enable TX/RX interrupt mask */
iow(db, DM9KS_RXCR, DM9KS_REG05 | 1); /* RX enable */
/* Init Driver variable */
db->tx_pkt_cnt = 0;
netif_carrier_on(dev);
}
该函数总结如下:
1. 初始化自旋锁
2. 启动PHY
3. 软件重置
4. 设置IO模式(即16bit还是8bit)和PHY
5. 操作寄存器,设置DM9000
2.3.3 设置board_info结构体
设置初始化了一些驱动变量,初始化添加定时器等。最后调用netif_start_queue()函数通知上层可以开始传输数据包了。接下来看发送函数做了什么。
2.4 dmfe_start_xmit()发送函数
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;
u16 MDWAH, MDWAL;
DMFE_DBUG(0, "dmfe_start_xmit", 0);
if (db->chip_revision != 0x1A)
{
if(db->Speed == 10)
{if (db->tx_pkt_cnt >= 1) return 1;}
else
{if (db->tx_pkt_cnt >= 2) return 1;}
}else
if (db->tx_pkt_cnt >= 2) return 1;
/* packet counting,发送数据包加一 */
db->tx_pkt_cnt++;
db->stats.tx_packets++; /* 发送数据包加1 */
db->stats.tx_bytes+=skb->len; /* 发送数据长度加数据的长度 */
if (db->chip_revision != 0x1A)
{
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);} /* 停止内核数据包的发送队列 */
}else
if (db->tx_pkt_cnt >= 2) netif_stop_queue(dev); /* 停止内核数据包的发送队列 */
/* Disable all interrupt */
iow(db, DM9KS_IMR, DM9KS_DISINTR); /* 关闭所有中断 */
MDWAH = ior(db,DM9KS_MDWAH); /* 读内存数据写地址寄存器高字节 */
MDWAL = ior(db,DM9KS_MDWAL); /* 读内存数据写地址寄存器低字节 */
/* 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; /* 将sk_buff中数据的地址赋值给SRAM */
outb(DM9KS_MWCMD, db->io_addr); /* 操作内存数据写命令寄存器,向发送SRAM中写数据 */
switch(db->io_mode) /* 选择IO模式,为16bit或者8bit */
{
case DM9KS_BYTE_MODE:
for (i = 0; i < skb->len; i++)
outb((data_ptr[i] & 0xff), db->io_data);
break;
case DM9KS_WORD_MODE:
tmplen = (skb->len + 1) / 2; /* 计算发送长度 */
for (i = 0; i < tmplen; i++) /* 向SRAM中写入数据 */
outw(((u16 *)data_ptr)[i], db->io_data);
break;
case DM9KS_DWORD_MODE:
tmplen = (skb->len + 3) / 4;
for (i = 0; i< tmplen; i++)
outl(((u32 *)data_ptr)[i], db->io_data);
break;
}
/* Saved the time stamp,保存时间戳 */
dev->trans_start = jiffies; /* 写入发送数据包的时间戳 */
db->cont_rx_pkt_cnt =0;
/* Free this SKB,释放sk_buff */
dev_kfree_skb(skb);
/* Re-enable interrupt ,重新开启全部中断*/
iow(db, DM9KS_IMR, DM9KS_REGFF);
return 0;
}
该函数总结如下:
1. 程序进行发送流程时,首先通过 tx_pkt_cnt变量判断是否发送第一个数据包,DM9000的驱动设计第一个数据包可以被发送,第二个数据包通过 dm9000_tx_done()函数发送。如果发送的是第一数据包,则程序把发送数据包个数加1,通过设置 DM9000控制寄存器,通知发送数据包长度,然后向 DM9000写入发送命令.
2. 统计接收数据增加,包括统计接收数据包数和统计数据长度。
3. 停止接收队列,该队列是内核与网卡驱动之间的数据包队列,内核把发送的数据包放到队列中,网卡驱动从队列中取出数据包进行发送。
4. 停止全部中断,目的是防止在发送数据包的过程中被打断,因为内核的代码都是可重入的,这点需要注意。
5. 读内存数据写地址寄存器。
6. 数据包长度写入传输数据包长度寄存器。
7. 将sk_buff中数据的地址赋值给SRAM。
8. 操作内存数据写命令寄存器,向发送SRAM中写数据。
9. 计算发送长度。
10. 向SRAM中写入数据。
11. 保存时间戳。
12. 释放sk_buff。
13. 重新开启全部中断。
介绍完发送函数,先来介绍接收中断函数。
2.5 dmfe_interrupt()中断处理函数
当DM9000网卡芯片有数据发来时,会触发2.3中dmfe_open()函数里注册的dmfe_interrupt()中断处理函数,函数内容如下:
static void dmfe_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct net_device *dev = dev_id;
board_info_t *db;
int int_status,i;
u8 reg_save;
DMFE_DBUG(0, "dmfe_interrupt()", 0);
/* A real interrupt coming */
db = (board_info_t *)dev->priv;
spin_lock(&db->lock); /* 对临界资源加锁 */
/* Save previous register address */
reg_save = inb(db->io_addr); /* 保存寄存器基地址 */
/* Disable all interrupt */
iow(db, DM9KS_IMR, DM9KS_DISINTR); /* 关闭所有中断 */
/* Got DM9000/DM9010 interrupt status */
int_status = ior(db, DM9KS_ISR); /* 读中断状态寄存器,获得中断状态*/
iow(db, DM9KS_ISR, int_status); /* 向中断状态寄存器获得中断位置写1,清除该中断 */
/* 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));
}
/* 接收数据包 */
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 */
outb(reg_save, db->io_addr); /* 恢复中断处理前中断寄存器地址 */
spin_unlock(&db->lock); /* 对临界资源解锁 */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
return IRQ_HANDLED;
#endif
}
该函数总结如下:
1. 关闭所有中断
2. 读中断状态寄存器,获得中断状态
3. 判断连接状态
4. 判断是否是接收中断,是则进入接收函数
5. 判断是否是发送完成中断
6. 使能所有中断
当DM9000芯片有数据发来就会触发接收中断函数,然后进入dmfe_packet_receive()接收函数。
2.6 dmfe_packet_receive()接收函数
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;
u16 i, GoodPacket, tmplen = 0, MDRAH, MDRAL;
u32 tmpdata;
rx_t rx;
u16 * ptr = (u16*)℞
u8* rdptr;
DMFE_DBUG(0, "dmfe_packet_receive()", 0);
db->cont_rx_pkt_cnt=0;
do {
/*保存内存数据读地址寄存器的值*/
MDRAH=ior(db, DM9KS_MDRAH); /* 读内存数据寄存器高字节数据 */
MDRAL=ior(db, DM9KS_MDRAL); /* 读内存数据寄存器低字节数据 */
/* 从接收SRAM中读数据,读取该指令之后,指向内部SRAM的读指针不变。DM9000A开始预取SRAM中数据到内部数据缓冲中 */
ior(db, DM9KS_MRCMDX); /* 读内存数据预取读命令寄存器 */
rxbyte = inb(db->io_data); /* 获取最新的更新信息 */
#ifdef CHECKSUM
if (rxbyte&0x2) /* 检查接收字节 */
{
printk("dm9ks: abnormal!\n");
dmfe_reset(dev);
break;
}else {
if (!(rxbyte&0x1))
break;
}
#else
if (rxbyte==0)
break;
if (rxbyte>1)
{
printk("dm9ks: Rxbyte error!\n");
dmfe_reset(dev);
break;
}
#endif
/* 数据包准备好,准备接收数据长度和状态 */
GoodPacket = TRUE;
outb(DM9KS_MRCMD, db->io_addr); /* 向寄存器发送读命令 */
/* Read packet status & length */
switch (db->io_mode) /* 选择IO模式有8bit,16bit和32bit */
{
case DM9KS_BYTE_MODE:
*ptr = inb(db->io_data) +
(inb(db->io_data) << 8);
*(ptr+1) = inb(db->io_data) +
(inb(db->io_data) << 8);
break;
case DM9KS_WORD_MODE:
*ptr = inw(db->io_data);
*(ptr+1) = inw(db->io_data); /* 将数据地址传给SRAM指针 */
break;
case DM9KS_DWORD_MODE:
tmpdata = inl(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(KERN_INFO"<RX FIFO error>\n"); /* 检查FIFO是否出错 */
}
if (rx.desc.status & 0x02)
{
db->stats.rx_crc_errors++;
printk(KERN_INFO"<RX CRC error>\n");/* 检查CRC是否出错 */
}
if (rx.desc.status & 0x80) /* 检查包长度是否出错 */
{
db->stats.rx_length_errors++;
printk(KERN_INFO"<RX Length error>\n");
}
if (rx.desc.status & 0x08)/* 检查物理层是否出错 */
printk(KERN_INFO"<Physical Layer error>\n");
}
if (!GoodPacket) /* 如果不是正确的包,就丢掉从新接收 */
{
// drop this packet!!!
switch (db->io_mode)
{
case DM9KS_BYTE_MODE:
for (i=0; i<rx.desc.length; i++)
inb(db->io_data);
break;
case DM9KS_WORD_MODE:
tmplen = (rx.desc.length + 1) / 2;
for (i = 0; i < tmplen; i++)
inw(db->io_data);
break;
case DM9KS_DWORD_MODE:
tmplen = (rx.desc.length + 3) / 4;
for (i = 0; i < tmplen; i++)
inl(db->io_data);
break;
}
continue;/*next the packet*/
}
skb = dev_alloc_skb(rx.desc.length+4); /* 分配sk_buff */
if (skb == NULL )
{
printk(KERN_INFO "%s: Memory squeeze.\n", dev->name);
/*re-load the value into Memory data read address register*/
iow(db,DM9KS_MDRAH,MDRAH);
iow(db,DM9KS_MDRAL,MDRAL);
return;
}
else
{
/* Move data from DM9000 */
skb->dev = dev;
skb_reserve(skb, 2);
rdptr = (u8*)skb_put(skb, rx.desc.length - 4);
/* Read received packet from RX SARM,从接收SRAM中读取接收的包数据 */
switch (db->io_mode)
{
case DM9KS_BYTE_MODE:
for (i=0; i<rx.desc.length; i++)
rdptr[i]=inb(db->io_data);
break;
case DM9KS_WORD_MODE:
tmplen = (rx.desc.length + 1) / 2;
for (i = 0; i < tmplen; i++)
((u16 *)rdptr)[i] = inw(db->io_data); /* 读取包数据 */
break;
case DM9KS_DWORD_MODE:
tmplen = (rx.desc.length + 3) / 4;
for (i = 0; i < tmplen; i++)
((u32 *)rdptr)[i] = inl(db->io_data);
break;
}
/* Pass to upper layer */
skb->protocol = eth_type_trans(skb,dev); /* 通知上层协议栈处理 */
#ifdef CHECKSUM
if((rxbyte&0xe0)==0) /* receive packet no checksum fail */
skb->ip_summed = CHECKSUM_UNNECESSARY;
#endif
netif_rx(skb); /* 发送sk_buff */
dev->last_rx=jiffies; /* 设置时间戳 */
db->stats.rx_packets++; /* 更新统计数据包个数 */
db->stats.rx_bytes += rx.desc.length;/* 更新统计数据包长度 */
db->cont_rx_pkt_cnt++;
if (db->cont_rx_pkt_cnt>=CONT_RX_PKT_CNT)
{
dmfe_tx_done(0);
break;
}
}
}while((rxbyte & 0x01) == DM9KS_PKT_RDY);
DMFE_DBUG(0, "[END]dmfe_packet_receive()", 0);
}
该函数总结如下:
1. 保存内存数据读地址寄存器的值
2. 读内存数据预取读命令寄存器
3. 获取最新的更新信息
4. 向寄存器发送读命令
5. 将数据地址传给SRAM指针
6. 包状态检测
7. 如果不是正确的包,就丢掉从新接收
8. 如果是正确的包 分配sk_buff
9. 从接收SRAM中读取接收的包数据
10. 通知上层协议栈处理
11. 发送sk_buff
12. 设置时间戳
13. 更新统计信息
接收的数据存储在RX SRAM中,地址是0C00h~3FFFh。存储在RX_SRAM中的每个包都有4个字节的信息头。可以使用MRCMDX和MRCMD寄存器来得到这些信 息。第一个字节用来检查数据包是否接收到了RX_SRAM中,如果这个字节是"01",意味着一个包已经接收。如果是"00",则还没有数据包被接收到RX_SRAM中。第二个字节保存接收到的数据包的信息,格式和RSR寄存器一样。根据这个格式,接收到的包能被校验是正确的还是错误的包。第三和第四字 节保存了接收的数据包的长度。这四个字节以外的其他字节就是接收包的数据。如下图:
2.7 流量控制机制
因为dm9000可以发送两个数据包,当内核要发送的数据包个数太多时,不进行控制,就会造成丢包现象,所以需要进行发包流量控制。
当内核要发送数据时,即调用该驱动的dmfe_start_xmit()函数,该函数中流量控制部分代码如下:
if (db->chip_revision != 0x1A)
{
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);} /* 停止内核数据包的发送队列 */
}else
if (db->tx_pkt_cnt >= 2) netif_stop_queue(dev); /* 停止内核数据包的发送队列 */
当要发送的数据包个数大于2时,调用netif_stop_queue()函数停止内核数据包的发送队列,那么又是在哪里启动内核数据发送队列呢?
在上面2.5点中dmfe_interrupt()中断处理函数里会判断一个数据包是否传输完成,完成则调用dmfe_tx_done()函数,代码如下:
if (int_status & DM9KS_TX_INTR)
dmfe_tx_done(0);
dmfe_tx_done()函数实际是对netif_wake_queue()函数的封装,netif_wake_queue()除了实现netif_start_queue()的作用外,还会将设备的发送队列加入到CPU的发送队列。
介绍完dm9dev9000c.c的主要函数,下半节将移植该驱动到JZ2440上。