《Linux驱动:DM9000网卡驱动分析》

一,前言

前面分析了网络设备驱动的框架以及构建设备驱动的一般流程,现在通过对DM9000C网卡驱动程序的分析进一步加强对网络设备驱动的理解。

二,硬件电路

2.1 DM9000C

SD0~SD15:数据/地址传输。
CMD:该管脚决定是访问DM9000C的数据寄存器还是索引寄存器。将通过LDATA0 ~ LDATA15传输的地址写到DM9000C的索引寄存器中,还是从DM9000C的数据寄存器读取数据通过LDATA0 ~ LDATA15传输给s3c2440的内存控制器。
INT:中断
CS:片选
RST:复位
在这里插入图片描述

2.2 S3c2440

DM9000C的中断号为EINT7
在这里插入图片描述
在这里插入图片描述

2.3 S3c2440内存控制器

操作地址在0x2000 0000 ~ 0x2800 0000之前的地址时,片选nGCS4。
取基地址为0x2000 0000。操作DM9000C的索引寄存器时,需要LADDR2(CMD)为0,即操作DM9000C的索引寄存器就是对地址0x2000 0000访问。操作DM9000C的数据寄存器时,需要LADDR2(CMD)为1,即操作DM9000C的数据寄存器就是对地址(0x2000 0000 + 4)访问。
在这里插入图片描述

需要对内存控制器进行配置,片选nGCS4对应于BANK4。即需要对寄存器BWSCON(0x48000000)和BANKCON4(0x48000014)进行配置。
在这里插入图片描述

三,probe函数分析

static int __init dm9dev9000c_init(void)
{
    
    
	volatile unsigned long *bwscon; // 0x48000000
	volatile unsigned long *bankcon4; // 0x48000014
	unsigned long val;

	iobase = (int)ioremap(0x20000000, 1024);  
	irq    = IRQ_EINT7;



	/* 设置S3C2440的memory controller */
	bwscon   = ioremap(0x48000000, 4);
	bankcon4 = ioremap(0x48000014, 4);

	/* DW4[17:16]: 01-16bit
	 * WS4[18]   : 0-WAIT disable
	 * ST4[19]   : 0 = Not using UB/LB (The pins are dedicated nWBE[3:0])
	 */
	val = *bwscon;
	val &= ~(0xf<<16);
	val |= (1<<16);
	*bwscon = val;

	/*
	 * Tacs[14:13]: 发出片选信号之前,多长时间内要先发出地址信号
	 *              DM9000C的片选信号和CMD信号可以同时发出,
	 *              所以它设为0
	 * Tcos[12:11]: 发出片选信号之后,多长时间才能发出读信号nOE
	 *              DM9000C的T1>=0ns, 
	 *              所以它设为0
	 * Tacc[10:8] : 读写信号的脉冲长度, 
	 *              DM9000C的T2>=10ns, 
	 *              所以它设为1, 表示2个hclk周期,hclk=100MHz,就是20ns
	 * Tcoh[7:6]  : 当读信号nOE变为高电平后,片选信号还要维持多长时间
	 *              DM9000C进行写操作时, nWE变为高电平之后, 数据线上的数据还要维持最少3ns
	 *              DM9000C进行读操作时, nOE变为高电平之后, 数据线上的数据在6ns之内会消失
	 *              我们取一个宽松值: 让片选信号在nOE放为高电平后,再维持10ns, 
	 *              所以设为01
	 * Tcah[5:4]  : 当片选信号变为高电平后, 地址信号还要维持多长时间
	 *              DM9000C的片选信号和CMD信号可以同时出现,同时消失
	 *              所以设为0
	 * PMC[1:0]   : 00-正常模式
	 *
	 */
	*bankcon4 = (1<<8)|(1<<6);	/* 对于DM9000C可以设Tacc为1, 对于DM9000E,Tacc要设大一点,比如最大值7  */
	//*bankcon4 = (7<<8)|(1<<6);  /* MINI2440使用DM9000E,Tacc要设大一点 */

	iounmap(bwscon);
	iounmap(bankcon4);
    
	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;
}


struct net_device * __init dmfe_probe(void)
{
    
    
	struct net_device *dev;
	int err;
	
	DMFE_DBUG(0, "dmfe_probe()",0);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
	dev = init_etherdev(NULL, sizeof(struct board_info));
	//ether_setup(dev);		
#else
	// 申请分配一个net_device结构体
	dev= alloc_etherdev(sizeof(struct board_info)); // -> alloc_netdev(sizeof_priv, "eth%d", ether_setup); 
#endif

	if(!dev)
		return ERR_PTR(-ENOMEM);

     	SET_MODULE_OWNER(dev);
	// 设置net_device结构体
	err = dmfe_probe1(dev);
	if (err)
		goto out;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
	// 注册
	err = register_netdev(dev);
	if (err)
		goto out1;
#endif
	return dev;
out1:
	release_region(dev->base_addr,2);
out:
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
	kfree(dev);
#else
	free_netdev(dev);
#endif
	return ERR_PTR(err);
}

3.1 读取DM9000C的设备ID

.......
		outb(DM9KS_VID_L, iobase);
		id_val = inb(iobase + 4);
		outb(DM9KS_VID_H, iobase);
		id_val |= inb(iobase + 4) << 8;
		outb(DM9KS_PID_L, iobase);
		id_val |= inb(iobase + 4) << 16;
		outb(DM9KS_PID_H, iobase);
		id_val |= inb(iobase + 4) << 24;

		printk(KERN_ERR"line %d <DM9KS> I/O: %x, VID: %x \n", __LINE__, iobase, id_val);
		if (id_val == DM9KS_ID || id_val == DM9010_ID) 
        {
    
    
            .......
        } 
.......

3.2 设置net_device结构体

......
			/* driver system function */				
			dev->base_addr 		= iobase;      			// 设置设备基地址
			dev->irq 		= irq;						// 中断号
			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;				// 提供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;
			dev->features |=  NETIF_F_IP_CSUM|NETIF_F_SG;
#endif
......

3.3 注册中断

......
request_irq(dev->irq,&dmfe_interrupt,0,dev->name,dev)
......

static irqreturn_t dmfe_interrupt(int irq, void *dev_id) /* for kernel 2.6.20*/
{
    
    
	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); 

	
	// 获取网卡中断的类型
	int_status = ior(db, DM9KS_ISR);		/* Got ISR */
	iow(db, DM9KS_ISR, int_status);		/* Clear ISR status */ 

	// 链接状态改变中断
	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);    // -> netif_rx(skb); 将接收到的数据传送到上层网络层

	// 检验中断
	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
}    

四,总结

构建网络设备驱动程序的一般流程(设备驱动功能层)

  • 调用alloc_netdev接口申请分配一个net_device结构体。
  • 设置net_device结构体,至少提供一个发包函数。
  • 根据硬件电路申请一个网卡相关中断,在中断中接收网卡接收到的其他设备发送过来的数据,通过netif_rx函数发送给上层处理。
  • 调用register_netdev接口注册该驱动。

猜你喜欢

转载自blog.csdn.net/qq_40709487/article/details/127150185