Linux驱动学习――网络接口DM9000驱动学习 mini2440
2011年06月29日
网络接口DM9000驱动学习:
\drivers\input\touchscreen\s3c2410_ts.c
\drivers\input\s3c2410_ts.c
参考: 等其他网络资料
首先看一下DM9000 的引脚和MINI2440的引脚连接
DM9000 MINI2440 功能描述
SD0 DATA0 数据信号
| |
SD15 DATA15 数据信号
CMD ADDR2 识别为地址还是数据
INT EINT7 中断
IOR# nOE 读命令使能
IOW# nWE 写命令使能
AEN nGCS4 片选使能 可以看出连接了16 条数据线,1 条地址线, 而这唯一的一条地址线用于判断数据线传输的是地址还是数据, 所以这16 条数据线为数据和地址复用
而片选信号使用的BANK4, 则访问0x2000 0000 0x27FF FFFF 这个范围的地址时会激活片选使能信号nGCS4
而在MINI2440 提供的内核中,DM9000 的地址IO 地址为0x2000 0000, 数据IO 为0x2000 0004 一、Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系
Mini2440开发板上DM9000与S3C2440的连接关系如下:
其中片选信号AEN使用了nGCS4,所以网卡的内存区域在BANK4,也就是从地址0x20000000开始。DM9000的TXD[2:0]作为strap pin(表带引脚)在电路图中是空接的,所以IO base是300H。中断使用了EINT7。
\arch\arm\plat-s3c24xx\devs.c
/* DM9000 */
static struct resource s3c_dm9k_resource [] = {
[0] = {
.start = S3C2410_CS4 ,
.end = S3C2410_CS4 + 3, //大小size是3个字节,为什么啊?
.flags = IORESOURCE_MEM,
},
[1] = {
.start = S3C2410_CS4 + 4,
.end = S3C2410_CS4 + 4 + 3,
.flags = IORESOURCE_MEM,
},
[2] = {
.start = IRQ_EINT7,
.end = IRQ_EINT7,
.flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING,
}
};
\arch\arm\mach-s3c2410\include\mach\map.h
/* physical addresses of all the chip-select areas */
#define S3C2410_CS0 (0x00000000)
#define S3C2410_CS1 (0x08000000)
#define S3C2410_CS2 (0x10000000)
#define S3C2410_CS3 (0x18000000)
#define S3C2410_CS4 (0x20000000)
#define S3C2410_CS5 (0x28000000)
#define S3C2410_CS6 (0x30000000)
#define S3C2410_CS7 (0x38000000)
/* for the moment we limit ourselves to 16bit IO until some
* better IO routines can be written and tested
*/
static struct dm9000_plat_data s3c_dm9k_platdata = {
.flags = DM9000_PLATF_16BITONLY,
};
struct platform_device s3c_device_dm9k = {
.name = "dm9000",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_dm9k_resource), //算出单元的个数,3
.resource = s3c_dm9k_resource,
.dev = {
.platform_data = &s3c_dm9k_platdata,
}
};
EXPORT_SYMBOL(s3c_device_dm9k);
platform_device结构体定义如下:
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources; struct resource * resource; }; Probe函数中获取地址和数据资源,探测,检测网口是否在线。
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //MEM中的第一个资源
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); //MEM中的第二个资源
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //IRQ中的第一个资源
platform_get_resource
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type
* @num: resource index
*/
struct resource *platform_get_resource (struct platform_device *dev,
unsigned int type, unsigned int num)
{
int i;
for (i = 0; i num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0) //某一类type资源中的第num个资源
return r;
}
return NULL;
}
二、两个重要的结构体简单介绍:sk_buff 和net_device
*sk_buff
如果把网络传输看成是运送货物的话,那么sk_buff 就是这个" 货物" 了,所有经手这个货物的人都要干点什么事儿,要么加个包装,要么印个戳儿等等。收 货的时候就要拆掉这些包装,得到我们需要的货物(payload data )。没有货物你还运输什么呢?由此可见sk_buff 的重要性了。关于sk_buff 的详细介绍和几个操作它的函数,参考本博客转载的一篇文 章:"linux 内核sk_buff 的结构分析" ,写得非常明白了。赞一个~
*net_device
又是一个庞大的结构体。好吧,我承认我从来就没有看全过这个结构体。它在内核中就是指代了一个网络设备。驱动程序需要在探测的时候分配并初始化这个结构体,然后使用register_netdev 来注册它,这样就可以把操作硬件的函数与内核挂接在一起。 定义一个platform_driver结构,该结构类似普通char类型驱动中的fops
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
.suspend = dm9000_drv_suspend,
.resume = dm9000_drv_resume,
};
该结构的定义如下\include\linux\platform_device.h:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
dm9000_ethtool_ops变量。是ethtool_ops结构体变量,为了支持ethtool,其中的函数主要是用于查询和设置网卡参数(当然也有的驱动程序可能不支持ethtool)。代码清单如下:
static const struct ethtool_ops dm9000_ethtool_ops = {
.get_drvinfo = dm9000_get_drvinfo,
.get_settings = dm9000_get_settings,
.set_settings = dm9000_set_settings,
.get_msglevel = dm9000_get_msglevel,
.set_msglevel = dm9000_set_msglevel,
.nway_reset = dm9000_nway_reset,
.get_link = dm9000_get_link,
.get_eeprom_len = dm9000_get_eeprom_len,
.get_eeprom = dm9000_get_eeprom,
.set_eeprom = dm9000_set_eeprom,
};
Probe函数,探测,检测网口是否在线。 主要完成的任务是:探测设备获得并保存资源信息,根据这些信息申请内存和中断,最后调用register_netdev 注册这个网络设备。以下是代码清单,可以分成几个部分来看: //Search DM9000 board, allocate space and register it static int __devinit dm9000_probe(struct platform_device *pdev) { struct dm9000_plat_data *pdata = pdev->dev.platform_data; struct board_info *db; /* Point a board information structure */ struct net_device *ndev; const unsigned char *mac_src; 1) 首先定义了几个局部变量: struct dm9000_plat_data *pdata = pdev->dev.platform_data;
struct board_info *db; /* Point a board information structure */
struct net_device *ndev; 2) 初始化一个网络设备。关键系统函数:alloc_etherdev() /* Init network device */ ndev = alloc_etherdev (sizeof(struct board_info)); 2.1 )将platform_device 与net_device 关联起来 SET_NETDEV_DEV(ndev, &pdev->dev); 3) 获得资源信息并将其保存在board_info 变量db 中。关键系统函数:netdev_priv(), platform_get_resource()
使用INIT_DELAYED_WORK 来初始化一个延迟工作队列 并关联了一个操作函数m9000_poll_work() /* setup board info structure */ db = netdev_priv (ndev); memset(db, 0, sizeof(*db)); db->dev = &pdev->dev; db->ndev = ndev; spin_lock_init(&db->lock); mutex_init(&db->addr_lock); INIT_DELAYED_WORK (&db->phy_poll, dm9000_poll_work); db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 4) 根据资源信息分配内存,申请中断等等, 并将申请后的资源信息也保存到db 中,并且填充ndev 中的参数。 关键系统函数:request_mem_region(), ioremap() 。 自定义函数:dm9000_set_io() iosize = res_size(db->addr_res); db->addr_req = request_mem_region (db->addr_res->start, iosize, pdev->name); db->io_addr = ioremap (db->addr_res->start, iosize); iosize = res_size(db->data_res); db->data_req = request_mem_region (db->data_res->start, iosize, pdev->name); db->io_data = ioremap (db->data_res->start, iosize); /* fill in parameters for net-dev structure */ ndev->base_addr = (unsigned long)db->io_addr; ndev->irq = db->irq_res->start; /* ensure at least we have a default set of IO routines */ dm9000_set_io (db, iosize ); iosize表示字节数
/* check to see if anything is being over-ridden搁置?背离? */
if (pdata != NULL) {
/* check to see if the driver wants to over-ride the
* default IO width */
5) 完成了第4 步以后,回顾一下db 和ndev 中都有了什么:
struct board_info *db:
addr_res -- 地址资源
data_res -- 数据资源
irq_res -- 中断资源
addr_req -- 分配的地址内存资源
io_addr -- 寄存器I/O 基地址
data_req -- 分配的数据内存资源
io_data -- 数据I/O 基地址
dumpblk -- IO 模式
outblk -- IO 模式
inblk -- IO 模式
lock -- 自旋锁(已经被初始化)
addr_lock -- 互斥锁(已经被初始化)
struct net_device *ndev :
base_addr -- 设备IO 地址
irq -- 设备IRQ 号
6) 设备复位。硬件操作函数dm9000_reset() static void dm9000_reset(board_info_t * db) { dev_dbg(db->dev, "resetting device\n"); /* RESET device */ writeb(DM9000_NCR, db->io_addr ); udelay(200); writeb(NCR_RST, db->io_data ); //io_addr =io_data? udelay(200); } 7) 读一下生产商和制造商的ID ,VID 和PID ,应该是0x9000 0A46 。 关键函数:ior() id_val = ior(db, DM9000_VIDL); id_val |= (u32)ior(db, DM9000_VIDH) dev_addr+i); 12) 很显然ndev 是我们在probe 函数中定义的局部变量,如果我想在其他地方使用它怎么办呢? 这就需要把它保存起来。内核提供了这个方法,使用函数platform_set_drvdata() 可以将ndev 保存成平台总线设备的私有数据。以后再要使用它时只需调用platform_get_drvdata() 就可以了。 platform_set_drvdata (pdev, ndev); 13) 使用register_netdev() 注册ndev 。 ret = register_netdev (ndev); ------------------------------ dm9000_open 进行的工作有向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等。代码清单如下:
* Open the interface.
* The interface is opened whenever "ifconfig" actives it.
*/
static int
dm9000_open(struct net_device *dev)
向内核注册中断,中断服务函数为dm9000_interrupt:
if (request_irq(dev->irq, &dm9000_interrupt , irqflags, dev->name, dev))
return -EAGAIN;
复位,初始化
/* Initialize DM9000 board */
dm9000_reset(db);
dm9000_init_dm9000 (dev);
dm9000_init_dm9000
* Initilize dm9000 board
*/
static void
dm9000_init_dm9000(struct net_device *dev)
读取IO模式寄存器
/* I/O mode */
db->io_mode = ior(db, DM9000_ISR) >> 6; /* ISR bit7:6 keeps I/O mode */
power up PHY,
/* GPIO0 on pre-activate PHY */
iow(db, DM9000_GPR, 0); /* REG_1F bit0 activate phyxcer */
iow(db, DM9000_GPCR, GPCR_GEP_CNTL); /* Let GPIO0 output */
iow(db, DM9000_GPR, 0); /* Enable PHY */
Tx Control Reg, Back Presure,…
/* Program operating register */
iow(db, DM9000_TCR, 0); /* TX Polling clear */
iow(db, DM9000_BPTR, 0x3f); /* Less 3Kb, 200us */
iow(db, DM9000_FCR, 0xff); /* Flow Control */
iow(db, DM9000_SMCR, 0); /* Special Mode */
清楚TX的状态位。
/* clear TX status */
iow(db, DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
iow(db, DM9000_ISR, ISR_CLR_STATUS); /* Clear interrupt status */
设置广播地址,hash table
/* Set address filter table */
dm9000_hash_table(dev);
设置中断掩码寄存器,使能SRAM地址自动归零,Packet Tx,Packet Rx中断,把imr给db(board info),同时,配置DM9000。
imr = IMR_PAR | IMR_PTM | IMR_PRM;
db->imr_all = imr;
/* Enable TX/RX interrupt mask */
iow(db, DM9000_IMR, imr);
初始化驱动变量
/* Init Driver variable */
db->tx_pkt_cnt = 0;
db->queue_pkt_len = 0;
dev->trans_start = 0;
MII检测媒介状态,主要检测Link状态,这里使用msg可以选择获取许多信息
mii_check_media (&db->mii, netif_msg_link(db), 1);
mii_check_media
如果是强制媒介,自动谈判关闭,那么退出。
/* if forced media, go no further */
if (mii->force_media)
return 0; /* duplex did not change */
检测新的媒介
/* check current and old link status */
old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
new_carrier = (unsigned int) mii_link_ok (mii); mii_link_ok 检测phy寄存器的状态位,可以检测到link状态
int mii_link_ok (struct mii_if_info *mii)
{
/* first, a dummy read, needed to latch some MII phys */
mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
return 1;
return 0;
}
这里使用了mii->mdio_read函数,该函数在dm9000_probe中定义:
db->mii.mdio_read = dm9000_phy_read;
db->mii.mdio_write = dm9000_phy_write;
dm9000_phy_read在dm9000.c中就有定义了,可以看出phy寄存器的读取方式采用了间接的方式
由于dm9000没有区分普通寄存器和phy寄存器,所以,通过控制寄存器触发dm9000读取phy,然后放入一个数据寄存器中,就可以读取phy了,phy是不能直接读取的
如果新媒介和旧媒介一样,那么不用改变
如果没有媒介,那么输出信息,返回。
if ((!init_media) && (old_carrier == new_carrier))
return 0; /* duplex did not change */
/* no carrier, nothing much to do */
if (!new_carrier) {
netif_carrier_off(mii->dev);
if (ok_to_print)
printk(KERN_INFO "%s: link down\n", mii->dev->name);
return 0; /* duplex did not change */
}
读取MII advertise and LPA(link partner ability)
if ((!init_media) && (mii->advertising))
advertise = mii->advertising;
else {
advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
mii->advertising = advertise;
}
lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
if (mii->supports_gmii)
lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
打印状态信息
if (ok_to_print)
printk(KERN_INFO "%s: link up, %sMbps, %s-duplex, lpa 0x%04X\n",
mii->dev->name,
lpa2 & (LPA_1000FULL | LPA_1000HALF) ? "1000" :
media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ? "100" : "10",
duplex ? "full" : "half",
lpa);
告诉上层网络驱动层驱动空间有缓冲区可用,开始发送数据包到驱动层。
netif_start_queue(dev);
启动设备工作队列
/*之前在probe函数中已经使用INIT_DELAYED_WORK来初始化一个延迟工作队列 并关联了一个操作函数dm9000_poll_work(), 此时运行schedule来调用这个函数*/
dm9000_schedule_poll(db);
dm9000_close
进行的工作有向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等。代码清单如下:
* Stop the interface.
* The interface is stopped when it is brought.
*/
static int
dm9000_stop(struct net_device *ndev)
{ board_info_t *db = netdev_priv(ndev); if (netif_msg_ifdown(db)) dev_dbg(db->dev, "shutting down %s\n", ndev->name); cancel_delayed_work_sync(&db->phy_poll);/*杀死延迟工作队列phy_poll*/
netif_stop_queue(ndev);
netif_carrier_off(ndev);/*停止传输并清空carrier*/
/* free interrupt */
free_irq(ndev->irq, ndev);
dm9000_shutdown(ndev);
return 0;
}
dm9000_start_xmit() 重要的发送数据包函数。从上层发送sk_buff 包。在看代码之前先来看一下DM9000 是如何发送数据包的。
如上图所示,在DM9000 内部SRAM 中,地址0x0000~0x0BFF 是TX Buffer ,地址0x0C00~0x3FFF 是RX Buffer 。在发送一个包之前,包中的有效数据必须先被存储到TX Buffer 中并且使用输出端口命令来选择MWCMD 寄存器。包的长度定义在TXPLL 和TXPLH 中。最后设置TXCR 寄存器的bit[0] TXREQ 来自动发送包 。如果设置了IMR 寄存器的PTM 位,则DM9000 会产生一个中断触发在ISR 寄存器的bit[1]=PTS=1, 同时设置一个完成标志在NSR 寄存器的bit[2]=TX1END 或者 bit[3]=TX2END ,表示包已经发送完了。发送一个包的具体步骤如下:
Step 1 : 检查存储数据宽度 。通过读取中断状态寄存器(ISR )的bit[7:6] 来确定是8bit ,16bit 还是32bit 。
Step 2 : 写数据到TX SRAM 中 。
Step 3 : 写传输长度到TXPLL 和TXPLH 寄存器中 。
Step 4 : 设置TXCR 寄存器的bit[0]TXREQ 来开始发送一个包。
代码清单如下,让我们看看在获得自旋锁这段期间都干了些什么: /* * Hardware start transmission. * Send a packet to media from the upper layer. */ static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) { 保存中断的当然状态,并禁止本地中断,然后再去获取指定的锁
spin_lock_irqsave (&db->lock, flags);
下面四行代码将skb中的data部分写入DM9000的TX RAM,并更新已发送字节数和发送计数
/* Move data to DM9000 TX RAM */
writeb(DM9000_MWCMD, db->io_addr);
(db->outblk)(db->io_data, skb->data, skb->len);
dev->stats.tx_bytes += skb->len;
db->tx_pkt_cnt++;
如果发送的是第一个包 ,则设置一下包的长度后直接发送
如果发送的是第二个数据包 (表明队列中此时有包发送),则将其加入队列中:将 skb->len和skb->ip_summed(控制校验操作)赋值给board_info_t中有关队列的相关成员。调用函数 netif_stop_queue(dev),通知内核现在queue已满,不能再将发送数据传到队列中,注:第二个包的发送将在tx_done中实现。
/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == 1) {
/* Set TX length to DM9000 */
iow(db, DM9000_TXPLL , skb->len);
iow(db, DM9000_TXPLH , skb->len >> 8);
/* Issue TX polling command */
iow(db, DM9000_TCR , TCR_TXREQ ); /* Cleared after TX complete */
dev->trans_start = jiffies; /* save the time stamp */
} else {
/* Second packet */
db->queue_pkt_len = skb->len;
netif_stop_queue(dev);
}
dm9000_timeout()
当watchdog超时时调用该函数。主要的功能是保存寄存器地址,停止队列,重启并初始化DM9000,唤醒队列,恢复寄存器地址。
dm9000_hash_table()
该函数用来设置DM9000的组播地址。代码清单如下:
dm9000_ioctl()
从源码可以看出,dm9000的ioctl实际上是使用了mii的ioctl。代码清单如下:
dm9000_poll_controller()
当内核配置Netconsole时该函数生效。代码清单如下:
dm9000_get_drvinfo()
该函数去的设备的基本信息(设备名,版本,总线名)传给ethtool_drvinfo结构体变量。代码清单如下:
*dm9000_get_settings()
该函数得到由参数cmd指定的设置信息。
*dm9000_set_settings()
该函数设置由参数cmd指定的信息。
*dm9000_get_msglevel()
*dm9000_set_msglevel()
这两个函数设置和取得message level,实际是设置和取得board_info中的msg_enable信息。
*dm9000_nway_reset()
重启mii的自动协商
*dm9000_get_link()
该函数的到link状态。如果带外部PHY,则返回mii链接状态。否则返回DM9000 NSR寄存器数值。
*dm9000_get_eeprom_len()
dm9000_get_eeprom()
dm9000_set_eeprom()
这三个函数用来读写eeprom。
dm9000_rx /* * Received a packet and pass to upper layer */ static void dm9000_rx(struct net_device *dev) { /* Check packet ready or not */ do { ior(db, DM9000_MRCMDX); /* Dummy read */ 读取最新数据的第一个字节byte
/* Get most updated data */
rxbyte = readb(db->io_data);
检测第一个byte的值
DM9000_PKT_RDY定义是0x01,如果第一个字节大于0x01,则不是正确的状态。因为第一个字节只能是01h或00h
/* Status check: this byte must be 0 or 1 */
if (rxbyte > DM9000_PKT_RDY) {
dev_warn(db->dev, "status check fail: %d\n", rxbyte);
iow(db, DM9000_RCR, 0x00); /* Stop Device */
iow(db, DM9000_ISR, IMR_PAR); /* Stop INT request */
return;
}
if (rxbyte != DM9000_PKT_RDY)
return;
一次性读入四个字节的内容到rxhdr变量,获得接收数据长度
/* A packet ready now & Get status/length */
GoodPacket = true;
writeb(DM9000_MRCMD, db->io_addr);
(db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr));
RxLen = le16_to_cpu(rxhdr.RxLen);
dm9000_rxhdr是接收数据的头,一共4byte
struct dm9000_rxhdr {
u8 RxPktReady;
u8 RxStatus;
__le16 RxLen;
}
检测接收数据长度有效性,太小了不行(不是完整的Eth包?),超过一个包的最大长度也不行
/* Packet Status check */
if (RxLen dev, "RX: Bad Packet (runt)\n");
}
if (RxLen > DM9000_PKT_MAX) {
dev_dbg(db->dev, "RST: RX Len:%x\n", RxLen);
}
检测接收数据的状态字节
/* rxhdr.RxStatus is identical to RSR register. */
if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE |
RSR_PLE | RSR_RWTO |
RSR_LCS | RSR_RF)) {
GoodPacket = false;
if (rxhdr.RxStatus & RSR_FOE) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "fifo error\n");
dev->stats.rx_fifo_errors++;
}
if (rxhdr.RxStatus & RSR_CE) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "crc error\n");
dev->stats.rx_crc_errors++;
}
if (rxhdr.RxStatus & RSR_RF) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "length error\n");
dev->stats.rx_length_errors++;
} } 关键的代码就是这里啦。使用到了上面提到的sk_buff。将RX SRAM中的data段数据放入sk_buff,然后发送给上层,至于怎么发送,不用去驱动操心了。sk_buff的protocol全部搞定。
开始dev_alloc_skb,由于len不包括前4个字节,所以,多分配了4个字节。
skb_reserve用于将数据起始指针向后移2个字节,因为
skb socket buffer通过移动起始指针将帧头去掉
/* Move data from DM9000 */
if (GoodPacket
&& ((skb = dev_alloc_skb (RxLen + 4)) != NULL)) {
skb_reserve (skb, 2);
rdptr = (u8 *) skb_put (skb, RxLen - 4);
/* Read received packet from RX SRAM */
(db->inblk)(db->io_data, rdptr, RxLen);
dev->stats.rx_bytes += RxLen;
/* Pass to upper layer */
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
dev->stats.rx_packets++;
} else {
/* need to dump the packet's data */
(db->dumpblk)(db->io_data, RxLen);
}
中断处理相关函数
DM9000 的驱动程序采用了中断方式而非轮询方式。触发中断的时机发生在:1 )DM9000 接收到一个包以后。2 )DM9000 发送完了一个包以后。
中断处理函数在open 的时候被注册进内核。代码清单如下: static irqreturn_t dm9000_interrupt (int irq, void *dev_id) { 进中断都要使用自旋锁?
屏蔽所有中断,读取中断状态,清楚中断,如果打印中断信息,则打印
/* holders of db->lock must always block IRQs */
spin_lock_irqsave(&db->lock, flags);
/* Save previous register address */
reg_save = readb(db->io_addr);
/* Disable all interrupts */
iow(db, DM9000_IMR, IMR_PAR);
/* Got DM9000 interrupt status */
int_status = ior(db, DM9000_ISR); /* Got ISR */
iow(db, DM9000_ISR, int_status); /* Clear ISR status */
if (netif_msg_intr(db))
dev_dbg(db->dev, "interrupt status %02x\n", int_status);
如果发生收到包中断,那么接收数据
如果发送包中断,那么调用dm9000_tx_done
/* Received the coming packet */
if (int_status & ISR_PRS)
dm9000_rx(dev);
/* Trnasmit Interrupt check */
if (int_status & ISR_PTS)
dm9000_tx_done(dev, db);
中断恢复
/* Re-enable interrupt mask */
iow(db, DM9000_IMR, db->imr_all);
/* Restore previous register address */
writeb(reg_save, db->io_addr);
spin_unlock_irqrestore(&db->lock, flags);
dm9000_tx_done
如果queue中有3个,那么第1个发送完毕,触发中断,调用done,发送第2个,第2个发送完毕,触发中断,调用done,继续发送第3个。
注:dm9000 可以发送两个数据包 ,当发送一个数据包产生中断后,要确认一下队列中有没有第2 个包需要发送。
(1 )读取dm9000 寄存器NSR (Network Status Register )获取发送的状态,存在变量tx_status 中;
(2 )如果发送状态为NSR_TX2END (第2 个包发送完毕)或者NSR_TX1END (第1 个包发送完毕),则将待发送的数据包数量(db->tx_pkt_cnt)减1,已发送的数据包数量(dev->stats.tx_packets)加1;
(3)检查变量db->tx_pkt_cnt(待发送的数据包)是否大于0 (表明还有数据包要发送),则调用函数dm9000_send_packet 发送队列中的数据包;
(4 )调用函数netif_wake_queue(dev)通知内核可以将待发送的数据包进入发送队列。
/*
* DM9000 interrupt handler
* receive the packet to upper layer, free the transmitted packet
*/
static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
int tx_status = ior(db, DM9000_NSR); /* Got TX status */
if (tx_status & (NSR_TX2END | NSR_TX1END )) {
/* One packet sent complete */
db->tx_pkt_cnt--;
dev->stats.tx_packets++;
if (netif_msg_tx_done(db))
dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);
/* Queue packet check & send */
if (db->tx_pkt_cnt > 0) {
iow(db, DM9000_TXPLL, db->queue_pkt_len); iow(db, DM9000_TXPLH, db->queue_pkt_len >> 8); iow(db, DM9000_TCR, TCR_TXREQ); dev->trans_start = jiffies; } netif_wake_queue(dev); } } 7. 一些操作硬件细节的函数。
在看函数之前还是先来看一下DM9000 CMD Pin 和Processor 并行总线的连接关系。CMD 管脚用来设置命令类型。
当CMD 管脚拉高时,这个命令周期访问DATA_PORT 。
如果拉低, 则这个命令周期访问ADDR_PORT 。见下图:
当然,内存映射的I/O 空间读写还是采用最基本的readb(), readw(), readl(), writeb(), writew(), writel() , readsb(), readsw(), readsl(), writesb(), writesw(), writesl() 。
在DM9000 的驱动中还自定义了几个函数,方便操作。
从IO 端口读一个字节。代码清单如下: /* * Read a byte from I/O port */ static u8 ior(board_info_t * db, int reg) { writeb(reg, db->io_addr); /*写reg到ADDR_PORT,用来选择寄存器*/
return readb(db->io_data); /*从DATA_PORT读一个字节,用来读寄存器*/
} 向IO 端口写一个字节。代码清单如下: /* * Write a byte to I/O port */ static void iow(board_info_t * db, int reg, int value) { writeb(reg, db->io_addr); writeb(value, db->io_data); }
2011年06月29日
网络接口DM9000驱动学习:
\drivers\input\touchscreen\s3c2410_ts.c
\drivers\input\s3c2410_ts.c
参考: 等其他网络资料
首先看一下DM9000 的引脚和MINI2440的引脚连接
DM9000 MINI2440 功能描述
SD0 DATA0 数据信号
| |
SD15 DATA15 数据信号
CMD ADDR2 识别为地址还是数据
INT EINT7 中断
IOR# nOE 读命令使能
IOW# nWE 写命令使能
AEN nGCS4 片选使能 可以看出连接了16 条数据线,1 条地址线, 而这唯一的一条地址线用于判断数据线传输的是地址还是数据, 所以这16 条数据线为数据和地址复用
而片选信号使用的BANK4, 则访问0x2000 0000 0x27FF FFFF 这个范围的地址时会激活片选使能信号nGCS4
而在MINI2440 提供的内核中,DM9000 的地址IO 地址为0x2000 0000, 数据IO 为0x2000 0004 一、Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系
Mini2440开发板上DM9000与S3C2440的连接关系如下:
其中片选信号AEN使用了nGCS4,所以网卡的内存区域在BANK4,也就是从地址0x20000000开始。DM9000的TXD[2:0]作为strap pin(表带引脚)在电路图中是空接的,所以IO base是300H。中断使用了EINT7。
\arch\arm\plat-s3c24xx\devs.c
/* DM9000 */
static struct resource s3c_dm9k_resource [] = {
[0] = {
.start = S3C2410_CS4 ,
.end = S3C2410_CS4 + 3, //大小size是3个字节,为什么啊?
.flags = IORESOURCE_MEM,
},
[1] = {
.start = S3C2410_CS4 + 4,
.end = S3C2410_CS4 + 4 + 3,
.flags = IORESOURCE_MEM,
},
[2] = {
.start = IRQ_EINT7,
.end = IRQ_EINT7,
.flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING,
}
};
\arch\arm\mach-s3c2410\include\mach\map.h
/* physical addresses of all the chip-select areas */
#define S3C2410_CS0 (0x00000000)
#define S3C2410_CS1 (0x08000000)
#define S3C2410_CS2 (0x10000000)
#define S3C2410_CS3 (0x18000000)
#define S3C2410_CS4 (0x20000000)
#define S3C2410_CS5 (0x28000000)
#define S3C2410_CS6 (0x30000000)
#define S3C2410_CS7 (0x38000000)
/* for the moment we limit ourselves to 16bit IO until some
* better IO routines can be written and tested
*/
static struct dm9000_plat_data s3c_dm9k_platdata = {
.flags = DM9000_PLATF_16BITONLY,
};
struct platform_device s3c_device_dm9k = {
.name = "dm9000",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_dm9k_resource), //算出单元的个数,3
.resource = s3c_dm9k_resource,
.dev = {
.platform_data = &s3c_dm9k_platdata,
}
};
EXPORT_SYMBOL(s3c_device_dm9k);
platform_device结构体定义如下:
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources; struct resource * resource; }; Probe函数中获取地址和数据资源,探测,检测网口是否在线。
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //MEM中的第一个资源
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); //MEM中的第二个资源
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //IRQ中的第一个资源
platform_get_resource
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type
* @num: resource index
*/
struct resource *platform_get_resource (struct platform_device *dev,
unsigned int type, unsigned int num)
{
int i;
for (i = 0; i num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0) //某一类type资源中的第num个资源
return r;
}
return NULL;
}
二、两个重要的结构体简单介绍:sk_buff 和net_device
*sk_buff
如果把网络传输看成是运送货物的话,那么sk_buff 就是这个" 货物" 了,所有经手这个货物的人都要干点什么事儿,要么加个包装,要么印个戳儿等等。收 货的时候就要拆掉这些包装,得到我们需要的货物(payload data )。没有货物你还运输什么呢?由此可见sk_buff 的重要性了。关于sk_buff 的详细介绍和几个操作它的函数,参考本博客转载的一篇文 章:"linux 内核sk_buff 的结构分析" ,写得非常明白了。赞一个~
*net_device
又是一个庞大的结构体。好吧,我承认我从来就没有看全过这个结构体。它在内核中就是指代了一个网络设备。驱动程序需要在探测的时候分配并初始化这个结构体,然后使用register_netdev 来注册它,这样就可以把操作硬件的函数与内核挂接在一起。 定义一个platform_driver结构,该结构类似普通char类型驱动中的fops
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
.suspend = dm9000_drv_suspend,
.resume = dm9000_drv_resume,
};
该结构的定义如下\include\linux\platform_device.h:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
dm9000_ethtool_ops变量。是ethtool_ops结构体变量,为了支持ethtool,其中的函数主要是用于查询和设置网卡参数(当然也有的驱动程序可能不支持ethtool)。代码清单如下:
static const struct ethtool_ops dm9000_ethtool_ops = {
.get_drvinfo = dm9000_get_drvinfo,
.get_settings = dm9000_get_settings,
.set_settings = dm9000_set_settings,
.get_msglevel = dm9000_get_msglevel,
.set_msglevel = dm9000_set_msglevel,
.nway_reset = dm9000_nway_reset,
.get_link = dm9000_get_link,
.get_eeprom_len = dm9000_get_eeprom_len,
.get_eeprom = dm9000_get_eeprom,
.set_eeprom = dm9000_set_eeprom,
};
Probe函数,探测,检测网口是否在线。 主要完成的任务是:探测设备获得并保存资源信息,根据这些信息申请内存和中断,最后调用register_netdev 注册这个网络设备。以下是代码清单,可以分成几个部分来看: //Search DM9000 board, allocate space and register it static int __devinit dm9000_probe(struct platform_device *pdev) { struct dm9000_plat_data *pdata = pdev->dev.platform_data; struct board_info *db; /* Point a board information structure */ struct net_device *ndev; const unsigned char *mac_src; 1) 首先定义了几个局部变量: struct dm9000_plat_data *pdata = pdev->dev.platform_data;
struct board_info *db; /* Point a board information structure */
struct net_device *ndev; 2) 初始化一个网络设备。关键系统函数:alloc_etherdev() /* Init network device */ ndev = alloc_etherdev (sizeof(struct board_info)); 2.1 )将platform_device 与net_device 关联起来 SET_NETDEV_DEV(ndev, &pdev->dev); 3) 获得资源信息并将其保存在board_info 变量db 中。关键系统函数:netdev_priv(), platform_get_resource()
使用INIT_DELAYED_WORK 来初始化一个延迟工作队列 并关联了一个操作函数m9000_poll_work() /* setup board info structure */ db = netdev_priv (ndev); memset(db, 0, sizeof(*db)); db->dev = &pdev->dev; db->ndev = ndev; spin_lock_init(&db->lock); mutex_init(&db->addr_lock); INIT_DELAYED_WORK (&db->phy_poll, dm9000_poll_work); db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 4) 根据资源信息分配内存,申请中断等等, 并将申请后的资源信息也保存到db 中,并且填充ndev 中的参数。 关键系统函数:request_mem_region(), ioremap() 。 自定义函数:dm9000_set_io() iosize = res_size(db->addr_res); db->addr_req = request_mem_region (db->addr_res->start, iosize, pdev->name); db->io_addr = ioremap (db->addr_res->start, iosize); iosize = res_size(db->data_res); db->data_req = request_mem_region (db->data_res->start, iosize, pdev->name); db->io_data = ioremap (db->data_res->start, iosize); /* fill in parameters for net-dev structure */ ndev->base_addr = (unsigned long)db->io_addr; ndev->irq = db->irq_res->start; /* ensure at least we have a default set of IO routines */ dm9000_set_io (db, iosize ); iosize表示字节数
/* check to see if anything is being over-ridden搁置?背离? */
if (pdata != NULL) {
/* check to see if the driver wants to over-ride the
* default IO width */
5) 完成了第4 步以后,回顾一下db 和ndev 中都有了什么:
struct board_info *db:
addr_res -- 地址资源
data_res -- 数据资源
irq_res -- 中断资源
addr_req -- 分配的地址内存资源
io_addr -- 寄存器I/O 基地址
data_req -- 分配的数据内存资源
io_data -- 数据I/O 基地址
dumpblk -- IO 模式
outblk -- IO 模式
inblk -- IO 模式
lock -- 自旋锁(已经被初始化)
addr_lock -- 互斥锁(已经被初始化)
struct net_device *ndev :
base_addr -- 设备IO 地址
irq -- 设备IRQ 号
6) 设备复位。硬件操作函数dm9000_reset() static void dm9000_reset(board_info_t * db) { dev_dbg(db->dev, "resetting device\n"); /* RESET device */ writeb(DM9000_NCR, db->io_addr ); udelay(200); writeb(NCR_RST, db->io_data ); //io_addr =io_data? udelay(200); } 7) 读一下生产商和制造商的ID ,VID 和PID ,应该是0x9000 0A46 。 关键函数:ior() id_val = ior(db, DM9000_VIDL); id_val |= (u32)ior(db, DM9000_VIDH) dev_addr+i); 12) 很显然ndev 是我们在probe 函数中定义的局部变量,如果我想在其他地方使用它怎么办呢? 这就需要把它保存起来。内核提供了这个方法,使用函数platform_set_drvdata() 可以将ndev 保存成平台总线设备的私有数据。以后再要使用它时只需调用platform_get_drvdata() 就可以了。 platform_set_drvdata (pdev, ndev); 13) 使用register_netdev() 注册ndev 。 ret = register_netdev (ndev); ------------------------------ dm9000_open 进行的工作有向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等。代码清单如下:
* Open the interface.
* The interface is opened whenever "ifconfig" actives it.
*/
static int
dm9000_open(struct net_device *dev)
向内核注册中断,中断服务函数为dm9000_interrupt:
if (request_irq(dev->irq, &dm9000_interrupt , irqflags, dev->name, dev))
return -EAGAIN;
复位,初始化
/* Initialize DM9000 board */
dm9000_reset(db);
dm9000_init_dm9000 (dev);
dm9000_init_dm9000
* Initilize dm9000 board
*/
static void
dm9000_init_dm9000(struct net_device *dev)
读取IO模式寄存器
/* I/O mode */
db->io_mode = ior(db, DM9000_ISR) >> 6; /* ISR bit7:6 keeps I/O mode */
power up PHY,
/* GPIO0 on pre-activate PHY */
iow(db, DM9000_GPR, 0); /* REG_1F bit0 activate phyxcer */
iow(db, DM9000_GPCR, GPCR_GEP_CNTL); /* Let GPIO0 output */
iow(db, DM9000_GPR, 0); /* Enable PHY */
Tx Control Reg, Back Presure,…
/* Program operating register */
iow(db, DM9000_TCR, 0); /* TX Polling clear */
iow(db, DM9000_BPTR, 0x3f); /* Less 3Kb, 200us */
iow(db, DM9000_FCR, 0xff); /* Flow Control */
iow(db, DM9000_SMCR, 0); /* Special Mode */
清楚TX的状态位。
/* clear TX status */
iow(db, DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
iow(db, DM9000_ISR, ISR_CLR_STATUS); /* Clear interrupt status */
设置广播地址,hash table
/* Set address filter table */
dm9000_hash_table(dev);
设置中断掩码寄存器,使能SRAM地址自动归零,Packet Tx,Packet Rx中断,把imr给db(board info),同时,配置DM9000。
imr = IMR_PAR | IMR_PTM | IMR_PRM;
db->imr_all = imr;
/* Enable TX/RX interrupt mask */
iow(db, DM9000_IMR, imr);
初始化驱动变量
/* Init Driver variable */
db->tx_pkt_cnt = 0;
db->queue_pkt_len = 0;
dev->trans_start = 0;
MII检测媒介状态,主要检测Link状态,这里使用msg可以选择获取许多信息
mii_check_media (&db->mii, netif_msg_link(db), 1);
mii_check_media
如果是强制媒介,自动谈判关闭,那么退出。
/* if forced media, go no further */
if (mii->force_media)
return 0; /* duplex did not change */
检测新的媒介
/* check current and old link status */
old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
new_carrier = (unsigned int) mii_link_ok (mii); mii_link_ok 检测phy寄存器的状态位,可以检测到link状态
int mii_link_ok (struct mii_if_info *mii)
{
/* first, a dummy read, needed to latch some MII phys */
mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
return 1;
return 0;
}
这里使用了mii->mdio_read函数,该函数在dm9000_probe中定义:
db->mii.mdio_read = dm9000_phy_read;
db->mii.mdio_write = dm9000_phy_write;
dm9000_phy_read在dm9000.c中就有定义了,可以看出phy寄存器的读取方式采用了间接的方式
由于dm9000没有区分普通寄存器和phy寄存器,所以,通过控制寄存器触发dm9000读取phy,然后放入一个数据寄存器中,就可以读取phy了,phy是不能直接读取的
如果新媒介和旧媒介一样,那么不用改变
如果没有媒介,那么输出信息,返回。
if ((!init_media) && (old_carrier == new_carrier))
return 0; /* duplex did not change */
/* no carrier, nothing much to do */
if (!new_carrier) {
netif_carrier_off(mii->dev);
if (ok_to_print)
printk(KERN_INFO "%s: link down\n", mii->dev->name);
return 0; /* duplex did not change */
}
读取MII advertise and LPA(link partner ability)
if ((!init_media) && (mii->advertising))
advertise = mii->advertising;
else {
advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
mii->advertising = advertise;
}
lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
if (mii->supports_gmii)
lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
打印状态信息
if (ok_to_print)
printk(KERN_INFO "%s: link up, %sMbps, %s-duplex, lpa 0x%04X\n",
mii->dev->name,
lpa2 & (LPA_1000FULL | LPA_1000HALF) ? "1000" :
media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ? "100" : "10",
duplex ? "full" : "half",
lpa);
告诉上层网络驱动层驱动空间有缓冲区可用,开始发送数据包到驱动层。
netif_start_queue(dev);
启动设备工作队列
/*之前在probe函数中已经使用INIT_DELAYED_WORK来初始化一个延迟工作队列 并关联了一个操作函数dm9000_poll_work(), 此时运行schedule来调用这个函数*/
dm9000_schedule_poll(db);
dm9000_close
进行的工作有向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等。代码清单如下:
* Stop the interface.
* The interface is stopped when it is brought.
*/
static int
dm9000_stop(struct net_device *ndev)
{ board_info_t *db = netdev_priv(ndev); if (netif_msg_ifdown(db)) dev_dbg(db->dev, "shutting down %s\n", ndev->name); cancel_delayed_work_sync(&db->phy_poll);/*杀死延迟工作队列phy_poll*/
netif_stop_queue(ndev);
netif_carrier_off(ndev);/*停止传输并清空carrier*/
/* free interrupt */
free_irq(ndev->irq, ndev);
dm9000_shutdown(ndev);
return 0;
}
dm9000_start_xmit() 重要的发送数据包函数。从上层发送sk_buff 包。在看代码之前先来看一下DM9000 是如何发送数据包的。
如上图所示,在DM9000 内部SRAM 中,地址0x0000~0x0BFF 是TX Buffer ,地址0x0C00~0x3FFF 是RX Buffer 。在发送一个包之前,包中的有效数据必须先被存储到TX Buffer 中并且使用输出端口命令来选择MWCMD 寄存器。包的长度定义在TXPLL 和TXPLH 中。最后设置TXCR 寄存器的bit[0] TXREQ 来自动发送包 。如果设置了IMR 寄存器的PTM 位,则DM9000 会产生一个中断触发在ISR 寄存器的bit[1]=PTS=1, 同时设置一个完成标志在NSR 寄存器的bit[2]=TX1END 或者 bit[3]=TX2END ,表示包已经发送完了。发送一个包的具体步骤如下:
Step 1 : 检查存储数据宽度 。通过读取中断状态寄存器(ISR )的bit[7:6] 来确定是8bit ,16bit 还是32bit 。
Step 2 : 写数据到TX SRAM 中 。
Step 3 : 写传输长度到TXPLL 和TXPLH 寄存器中 。
Step 4 : 设置TXCR 寄存器的bit[0]TXREQ 来开始发送一个包。
代码清单如下,让我们看看在获得自旋锁这段期间都干了些什么: /* * Hardware start transmission. * Send a packet to media from the upper layer. */ static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) { 保存中断的当然状态,并禁止本地中断,然后再去获取指定的锁
spin_lock_irqsave (&db->lock, flags);
下面四行代码将skb中的data部分写入DM9000的TX RAM,并更新已发送字节数和发送计数
/* Move data to DM9000 TX RAM */
writeb(DM9000_MWCMD, db->io_addr);
(db->outblk)(db->io_data, skb->data, skb->len);
dev->stats.tx_bytes += skb->len;
db->tx_pkt_cnt++;
如果发送的是第一个包 ,则设置一下包的长度后直接发送
如果发送的是第二个数据包 (表明队列中此时有包发送),则将其加入队列中:将 skb->len和skb->ip_summed(控制校验操作)赋值给board_info_t中有关队列的相关成员。调用函数 netif_stop_queue(dev),通知内核现在queue已满,不能再将发送数据传到队列中,注:第二个包的发送将在tx_done中实现。
/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == 1) {
/* Set TX length to DM9000 */
iow(db, DM9000_TXPLL , skb->len);
iow(db, DM9000_TXPLH , skb->len >> 8);
/* Issue TX polling command */
iow(db, DM9000_TCR , TCR_TXREQ ); /* Cleared after TX complete */
dev->trans_start = jiffies; /* save the time stamp */
} else {
/* Second packet */
db->queue_pkt_len = skb->len;
netif_stop_queue(dev);
}
dm9000_timeout()
当watchdog超时时调用该函数。主要的功能是保存寄存器地址,停止队列,重启并初始化DM9000,唤醒队列,恢复寄存器地址。
dm9000_hash_table()
该函数用来设置DM9000的组播地址。代码清单如下:
dm9000_ioctl()
从源码可以看出,dm9000的ioctl实际上是使用了mii的ioctl。代码清单如下:
dm9000_poll_controller()
当内核配置Netconsole时该函数生效。代码清单如下:
dm9000_get_drvinfo()
该函数去的设备的基本信息(设备名,版本,总线名)传给ethtool_drvinfo结构体变量。代码清单如下:
*dm9000_get_settings()
该函数得到由参数cmd指定的设置信息。
*dm9000_set_settings()
该函数设置由参数cmd指定的信息。
*dm9000_get_msglevel()
*dm9000_set_msglevel()
这两个函数设置和取得message level,实际是设置和取得board_info中的msg_enable信息。
*dm9000_nway_reset()
重启mii的自动协商
*dm9000_get_link()
该函数的到link状态。如果带外部PHY,则返回mii链接状态。否则返回DM9000 NSR寄存器数值。
*dm9000_get_eeprom_len()
dm9000_get_eeprom()
dm9000_set_eeprom()
这三个函数用来读写eeprom。
dm9000_rx /* * Received a packet and pass to upper layer */ static void dm9000_rx(struct net_device *dev) { /* Check packet ready or not */ do { ior(db, DM9000_MRCMDX); /* Dummy read */ 读取最新数据的第一个字节byte
/* Get most updated data */
rxbyte = readb(db->io_data);
检测第一个byte的值
DM9000_PKT_RDY定义是0x01,如果第一个字节大于0x01,则不是正确的状态。因为第一个字节只能是01h或00h
/* Status check: this byte must be 0 or 1 */
if (rxbyte > DM9000_PKT_RDY) {
dev_warn(db->dev, "status check fail: %d\n", rxbyte);
iow(db, DM9000_RCR, 0x00); /* Stop Device */
iow(db, DM9000_ISR, IMR_PAR); /* Stop INT request */
return;
}
if (rxbyte != DM9000_PKT_RDY)
return;
一次性读入四个字节的内容到rxhdr变量,获得接收数据长度
/* A packet ready now & Get status/length */
GoodPacket = true;
writeb(DM9000_MRCMD, db->io_addr);
(db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr));
RxLen = le16_to_cpu(rxhdr.RxLen);
dm9000_rxhdr是接收数据的头,一共4byte
struct dm9000_rxhdr {
u8 RxPktReady;
u8 RxStatus;
__le16 RxLen;
}
检测接收数据长度有效性,太小了不行(不是完整的Eth包?),超过一个包的最大长度也不行
/* Packet Status check */
if (RxLen dev, "RX: Bad Packet (runt)\n");
}
if (RxLen > DM9000_PKT_MAX) {
dev_dbg(db->dev, "RST: RX Len:%x\n", RxLen);
}
检测接收数据的状态字节
/* rxhdr.RxStatus is identical to RSR register. */
if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE |
RSR_PLE | RSR_RWTO |
RSR_LCS | RSR_RF)) {
GoodPacket = false;
if (rxhdr.RxStatus & RSR_FOE) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "fifo error\n");
dev->stats.rx_fifo_errors++;
}
if (rxhdr.RxStatus & RSR_CE) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "crc error\n");
dev->stats.rx_crc_errors++;
}
if (rxhdr.RxStatus & RSR_RF) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "length error\n");
dev->stats.rx_length_errors++;
} } 关键的代码就是这里啦。使用到了上面提到的sk_buff。将RX SRAM中的data段数据放入sk_buff,然后发送给上层,至于怎么发送,不用去驱动操心了。sk_buff的protocol全部搞定。
开始dev_alloc_skb,由于len不包括前4个字节,所以,多分配了4个字节。
skb_reserve用于将数据起始指针向后移2个字节,因为
skb socket buffer通过移动起始指针将帧头去掉
/* Move data from DM9000 */
if (GoodPacket
&& ((skb = dev_alloc_skb (RxLen + 4)) != NULL)) {
skb_reserve (skb, 2);
rdptr = (u8 *) skb_put (skb, RxLen - 4);
/* Read received packet from RX SRAM */
(db->inblk)(db->io_data, rdptr, RxLen);
dev->stats.rx_bytes += RxLen;
/* Pass to upper layer */
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
dev->stats.rx_packets++;
} else {
/* need to dump the packet's data */
(db->dumpblk)(db->io_data, RxLen);
}
中断处理相关函数
DM9000 的驱动程序采用了中断方式而非轮询方式。触发中断的时机发生在:1 )DM9000 接收到一个包以后。2 )DM9000 发送完了一个包以后。
中断处理函数在open 的时候被注册进内核。代码清单如下: static irqreturn_t dm9000_interrupt (int irq, void *dev_id) { 进中断都要使用自旋锁?
屏蔽所有中断,读取中断状态,清楚中断,如果打印中断信息,则打印
/* holders of db->lock must always block IRQs */
spin_lock_irqsave(&db->lock, flags);
/* Save previous register address */
reg_save = readb(db->io_addr);
/* Disable all interrupts */
iow(db, DM9000_IMR, IMR_PAR);
/* Got DM9000 interrupt status */
int_status = ior(db, DM9000_ISR); /* Got ISR */
iow(db, DM9000_ISR, int_status); /* Clear ISR status */
if (netif_msg_intr(db))
dev_dbg(db->dev, "interrupt status %02x\n", int_status);
如果发生收到包中断,那么接收数据
如果发送包中断,那么调用dm9000_tx_done
/* Received the coming packet */
if (int_status & ISR_PRS)
dm9000_rx(dev);
/* Trnasmit Interrupt check */
if (int_status & ISR_PTS)
dm9000_tx_done(dev, db);
中断恢复
/* Re-enable interrupt mask */
iow(db, DM9000_IMR, db->imr_all);
/* Restore previous register address */
writeb(reg_save, db->io_addr);
spin_unlock_irqrestore(&db->lock, flags);
dm9000_tx_done
如果queue中有3个,那么第1个发送完毕,触发中断,调用done,发送第2个,第2个发送完毕,触发中断,调用done,继续发送第3个。
注:dm9000 可以发送两个数据包 ,当发送一个数据包产生中断后,要确认一下队列中有没有第2 个包需要发送。
(1 )读取dm9000 寄存器NSR (Network Status Register )获取发送的状态,存在变量tx_status 中;
(2 )如果发送状态为NSR_TX2END (第2 个包发送完毕)或者NSR_TX1END (第1 个包发送完毕),则将待发送的数据包数量(db->tx_pkt_cnt)减1,已发送的数据包数量(dev->stats.tx_packets)加1;
(3)检查变量db->tx_pkt_cnt(待发送的数据包)是否大于0 (表明还有数据包要发送),则调用函数dm9000_send_packet 发送队列中的数据包;
(4 )调用函数netif_wake_queue(dev)通知内核可以将待发送的数据包进入发送队列。
/*
* DM9000 interrupt handler
* receive the packet to upper layer, free the transmitted packet
*/
static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
int tx_status = ior(db, DM9000_NSR); /* Got TX status */
if (tx_status & (NSR_TX2END | NSR_TX1END )) {
/* One packet sent complete */
db->tx_pkt_cnt--;
dev->stats.tx_packets++;
if (netif_msg_tx_done(db))
dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);
/* Queue packet check & send */
if (db->tx_pkt_cnt > 0) {
iow(db, DM9000_TXPLL, db->queue_pkt_len); iow(db, DM9000_TXPLH, db->queue_pkt_len >> 8); iow(db, DM9000_TCR, TCR_TXREQ); dev->trans_start = jiffies; } netif_wake_queue(dev); } } 7. 一些操作硬件细节的函数。
在看函数之前还是先来看一下DM9000 CMD Pin 和Processor 并行总线的连接关系。CMD 管脚用来设置命令类型。
当CMD 管脚拉高时,这个命令周期访问DATA_PORT 。
如果拉低, 则这个命令周期访问ADDR_PORT 。见下图:
当然,内存映射的I/O 空间读写还是采用最基本的readb(), readw(), readl(), writeb(), writew(), writel() , readsb(), readsw(), readsl(), writesb(), writesw(), writesl() 。
在DM9000 的驱动中还自定义了几个函数,方便操作。
从IO 端口读一个字节。代码清单如下: /* * Read a byte from I/O port */ static u8 ior(board_info_t * db, int reg) { writeb(reg, db->io_addr); /*写reg到ADDR_PORT,用来选择寄存器*/
return readb(db->io_data); /*从DATA_PORT读一个字节,用来读寄存器*/
} 向IO 端口写一个字节。代码清单如下: /* * Write a byte to I/O port */ static void iow(board_info_t * db, int reg, int value) { writeb(reg, db->io_addr); writeb(value, db->io_data); }