三、
net_rx
和
net_send_packet
3.1
net_rx
在这部分将介绍cs8900
驱动的两个最重要的函数,内核通过该两个函数实现了数据的收发。net_rx
函数的主要功能是从cs8900
的片上数据缓冲区中将数据传送给sk_buff
缓冲区,sk_buff
是网络驱动程序与Linux
内核通信的缓冲区。该结构可在\include\linux\skbuff.h
中找到。net_rx
函数的功能可总结如下:(该总结来源于:
)
A
.获取私有数据存放于lp
中;
B
.获取设备缓冲区状态和缓冲长度;
C
.如果状态不为RX_OK
则计数接收数据错误次数count_rx_error()
D
.分配一个sk_buf
区间
E
.字对齐,skb_reserve()
;
F
.插入数据到接收口,insw()
;
G
.写入数据;
H
.初始化sk_buff
结构,eth_type_trans()
I
.进入上层接收函数netif_rx()
;
J
.初始化设备的计数;
net_rx
函数的注解如下所示:
static void net_rx(struct net_device *dev)
{
struct net_local *lp = netdev_priv(dev); //lp
指向驱动程序的私有数据区
struct sk_buff *skb; //
申请skb_buff
指针
int status, length;
int ioaddr = dev->base_addr;
//
得到cs8900
的基地址
status = readword(ioaddr, RX_FRAME_PORT); //
获取cs8900
片上缓冲区的状态
length = readword(ioaddr, RX_FRAME_PORT); //
获取cs8900
片上缓冲区的长度
if ((status & RX_OK) == 0) { //
状态为接收错误,调用count_rx_errors
统计错误
count_rx_errors(status, lp);
return;
}
/* Malloc up new buffer. */
skb = dev_alloc_skb(length + 2); //
分配一个缓冲区,dev_alloc_skb
函数以
//GFP_ATOMIC
优先级调用alloc_skb
。alloc_skb
的功能为分配一个缓冲区
//
并初始化skb->data
,skb->tail
和skb_head
域。dev_alloc_skb
和alloc_skb
的区
//
别为,前者在skb->data
和skb_head
之间保留了一些空间,网络
层使用这
//
一数据空间进行优化工作,驱动程序不该访问该空间。
if (skb == NULL) { //skb
缓冲区分配失败?
……
lp->stats.rx_dropped++; //
直接将丢包数加1
return;
}
skb_reserve(skb, 2); /* longword align L3 header */ //
该函数增加skb
的data
和tail
,
//
该函数可填充缓冲区之
前保留报文头空间,大多数以太网在数据包之前
//
保留2
个字节,这样IP
头可在14
字节的以太网头之
后,在16
字节边界上对
//
齐。这里也空了两个字节,这两个自己加上14
字节的以太网头刚好16
字
//
节。
所以这里的主要作用是字对齐。
skb->dev = dev;
readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> 1); //skb_put
函
//
数的作用是更新skb
的
tail
和len
成员,也即在缓冲区尾部添加数据,该函数返
//
回skb->tail
的先前值。整句代码的含义为,
从cs8900
的数据缓冲区中读取
//length
个字节数据到skb
缓冲区。由于readwords
是以读取字(两个字
节)为
//
单位,所以length
应该保持字对齐,也即length
右移一位。
if (length & 1) //
因为前面length
以字对齐,如果length
为单字节,
//
所以这里应该补上最后一个字节
skb->data[length-1] = readword(ioaddr, RX_FRAME_PORT);
……
skb->protocol=eth_type_trans(skb,dev); //
该函数定义在
linux/net/ethernet/eth.c
中,
//
该处可参见linux
设备
驱动程序相关章节
netif_rx(skb); //
通知内核已经接收到一个数据包,并封装入一个套接字缓冲区
dev->last_rx = jiffies; //
更新最后的接收包时间
lp->stats.rx_packets++; //
接收的总数据包数加1
lp->stats.rx_bytes += length; //
接收的字节数加上length
}
3.1 net_send_packet
net_send_parcket
为内核提供了数据包发送功能,该函数在cs89x0_probe1
中被赋予了net_device
的hard_start_xmit
域,当内核需要发送数据包时,将调用dev-> hard_start_xmit
完成最后的数据包发送。该函数被调用的前提是,在调用该函数之前,内核已经将数据包放入了skb
缓冲区中。该函数的主要任务有:
A
.获取设备私有数据指针
B
.加环形锁,spin_lock_irq()
;
C
.检测缓冲区是否为满,若满则调用netif_stop_queue()
暂停发送队列;
D
.写发送命令和发送长度,writeword()
;
E
.读取发送总线状态readreg()
;
F
.解环形锁,spin_unlock_irq()
;
G
.设置传输时钟计数;
H
.释放相应sk_buff, dev_kfree_skb().
下面为此函数的简单注释:
static int net_send_packet(struct sk_buff *skb, struct net_device *dev)
{
struct net_local *lp = netdev_priv(dev); //
获得驱动程序的私有数据
……
spin_lock_irq(&lp->lock); //
获得自旋锁,以便进入临界区
netif_stop_queue(dev); //
通知内核暂停内核与驱动程序间的数据传递,也即告诉
//
内核不要向skb
缓冲区填充数据。
/* initiate a transmit sequence */ //
初始化cs8900
的发送对列,主要为写命令和数
//
据长度,为数据发送做准备
writeword(dev->base_addr, TX_CMD_PORT, lp->send_cmd);
writeword(dev->base_addr, TX_LEN_PORT, skb->len);
/* Test to see if the chip has allocated memory for the packet * / //
查看cs8900
是否为
//
发送分配了地址空间。
if ((readreg(dev, PP_BusST) & READY_FOR_TX_NOW) == 0) {
…….
spin_unlock_irq(&lp->lock);
if (net_debug) printk("cs89x0: Tx buffer not free!\n");
return 1;
}
/* Write the contents of the packet */
writewords(dev->base_addr, TX_FRAME_PORT,skb->data,(skb->len+1) >>1);
//
将数
//
据交给cs8900
发送
spin_unlock_irq(&lp->lock); //
发送结束,释放自旋锁
lp->stats.tx_bytes += skb->len; //
累加发送的总字节数
dev->trans_start = jiffies; //
更新最后的传输时间
dev_kfree_skb (skb); //
发送完毕,释放skb
缓冲区
……
return 0;
}
总结:
在cs8900
驱动中,主要简解了驱动程序中的部分重要函数,包括初始化、打开/
关闭网络驱动和发送/
接收数据。对于其余的驱动程序代码,如超时处理、状态获取等函数没做解释,它们的实现也比较简单。由于自己板子上没有EEPROM
,所以也没有分析与EEPROM
相关部分的代码。DMA
部分好像编译进去会错,所以也没有去分析,以后有时间再去弄弄DMA
部分。
The End
------ anmnmnly
------ 2007.12.16