在uboot阶段,没有挂载中断,接收通过轮询来实现的,所以发送和接收这两个过程跟Linux内核中有区别。
在发送阶段,网口将被启动,发送函数首先找到一个可用的Buffer Descriptor,将上层软件组好的包的地址赋给该BD的指针,置相应的标志位和长度,然后通知DMA来搬运。搬运结束后,发送函数会清除相应的BD标识位。DMA将数据从内存搬运到Tx FIFO后, MAC会给其加上数据链路层的首部后通过GMII口发送到PHY层。
在接收阶段,硬件会检测TSECn_RX_DV和TSECn_COL信号,并会检查有效的preamble,若找到,则检查MAC地址,校验等等,若都合格,则剥掉链路层的包头后,塞给Rx FIFO,DMA会将其搬到现在一个有效的Rx BD中,我们的接收程序会轮询该Buffer Descriptor,直到它有数据时,便将数据提交到上层,然后清除BD的一些状态位。
static int tsec_send(struct eth_device *dev, volatile void *packet, int length)
{
int i;
int result = 0;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
/*找一块空的Buffer Descriptor*/
for (i = 0; rtx.txbd[txIdx].status & TXBD_READY; i++) {
if (i >= TOUT_LOOP) {
printf("%s: tsec: tx buffers full/n", dev->name);
return result;
}
}
rtx.txbd[txIdx].bufPtr = (uint) packet;
rtx.txbd[txIdx].length = length;
rtx.txbd[txIdx].status |=
(TXBD_READY | TXBD_LAST | TXBD_CRC | TXBD_INTERRUPT);
/* 通过设置寄存器让DMA来从BD中搬运到FIFO中 */
regs->tstat = TSTAT_CLEAR_THALT;
/* 等到BD搬运完成,清除标志位*/
for (i = 0; rtx.txbd[txIdx].status & TXBD_READY; i++) {
if (i >= TOUT_LOOP) {
printf("%s: tsec: tx error/n", dev->name);
return result;
}
}
txIdx = (txIdx + 1) % TX_BUF_CNT;
result = rtx.txbd[txIdx].status & TXBD_STATS;
return result;
}
static int tsec_recv(struct eth_device *dev)
{
int length;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
while (!(rtx.rxbd[rxIdx].status & RXBD_EMPTY)) {
length = rtx.rxbd[rxIdx].length;
/* 有数据来时,检测BD的status,如果没有报错,就扔给上层协议栈 */
if (!(rtx.rxbd[rxIdx].status & RXBD_STATS)) {
NetReceive(NetRxPackets[rxIdx], length - 4);
} else {
printf("Got error %x/n",
(rtx.rxbd[rxIdx].status & RXBD_STATS));
}
rtx.rxbd[rxIdx].length = 0;
/* 如果是最后一个BD就设置W位 */
rtx.rxbd[rxIdx].status =
RXBD_EMPTY | (((rxIdx + 1) == PKTBUFSRX) ? RXBD_WRAP : 0);
rxIdx = (rxIdx + 1) % PKTBUFSRX;
}
if (regs->ievent & IEVENT_BSY) {
regs->ievent = IEVENT_BSY;
regs->rstat = RSTAT_CLEAR_RHALT;
}
return -1;
}
Uboot下的网口驱动就这么多内容。
Linux 网络驱动设备模型
Linux网络设备模型如下图,从上到下可以划分为4层,分别是网络协议接口层,网络设备接口层,设备驱动功能层和网络设备媒介层。
网络协议接口层向网络层协议提供统一的数据包收发接口,通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。
网络设备结构层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device。
设备驱动功能层各函数是网络设备结构层net_device数据结构的具体成员,通过hard_start_xmit()函数发送,通过中断触发接收函数。
网络设备媒介层就是完成数据包发送和接收的物理实体。
套接字缓冲区
套接字缓冲区 sk_buff的结构体非常重要,用于在Linux网络子系统中的各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”。
当发送数据时,Linux内核的网络处理模块必须建立一个包含要传输的数据包sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样,当网络设备从网络媒介上接收数据包后,它必须将接收到的数据转化为sk_buff数据结构并传递给上层,各层剥去相应的协议头,直至交给用户。
Skb有四个指针,head和end分别指向数据缓冲区的启始地址和结尾地址,而data和tail分别指向有效数据的开始地址和结尾地址。
Skb的操作有:alloc_skb()分配一个套接字缓冲区和一个数据缓冲区;Kree_skb进行套接字缓冲区的释放;skb_push()将data指针上移,主要用于添加协议头部;skb_pull将data指针下移,用于剥去头部。