二十、Linux驅動之移植DM9000C網卡驅動(上)

1. 基本概念

    DM9000C是一款完全集成的和符合成本效益單芯片快速以太網MAC控制器與一般處理接口,一個10/100M自適應的PHY和4K DWORD值的SRAMDM9000C內部還集成了接收緩衝區,可以在接收到數據的時候把數據存放到緩衝區中,鏈路層可以直接把數據從緩衝區取走。針對不同的處理器, 接口支持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有兩種模式,即同時支持10Mbps100Mbps兩種速率。同時我們的芯片又有一個很強大的功能就是自協商,即通信速率通過雙方協商,這裏默認是自協商模式。然後進入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個字節的信息頭。可以使用MRCMDXMRCMD寄存器來得到這些信 息。第一個字節用來檢查數據包是否接收到了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上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章