1. 发送数据
发送和接收数据的流程基本参考了uboot,只是为接收添加了中断支持。我们先来看看发送数据的实现:
int emacTransmit(struct netdev *pnetdev, struct pbuf *pbuf) { struct emac_eth_dev *emac = &emac_dev; u32 desc_num = emac->tx_currdesc; struct emac_dma_desc *desc_p = &emac->tx_chain[desc_num]; int linkup; u32 val; // 检测网络连接状态 netdev_get_linkup(pnetdev, &linkup); if (!linkup) { printk("emac linkdown!\n"); netdev_linkinfo_err_inc(pnetdev); netdev_linkinfo_drop_inc(pnetdev); return -1; } // TODO:描述符用满了,这里做简单处理,直接返回错误 if (EMAC_DESC_OWN_DMA == (desc_p->status & EMAC_DESC_OWN_DMA)) { printk("TX descriptor full!!\n"); return -1; } // 将数据从协议栈复制到 DMA 缓冲区中准备发送 pbuf_copy_partial(pbuf, (void *)(unsigned long)(u32)desc_p->buf_addr, pbuf->tot_len, 0); // 设置发送 DMA 描述符,不使用中断 desc_p->ctl_size = pbuf->tot_len | EMAC_DESC_CHAIN_SECOND | EMAC_DESC_LAST_DESC | EMAC_DESC_FIRST_DESC; desc_p->status = EMAC_DESC_OWN_DMA; KN_SMP_WMB(); // 更新下一个可用描述符位置 if (++desc_num >= CONFIG_TX_DESCR_NUM) desc_num = 0; emac->tx_currdesc = desc_num; // 启动发送 DMA val = readl(emac->mac_reg + EMAC_TX_CTL1); val |= EMAC_TX_CTL1_TX_DMA_START; writel(val, emac->mac_reg + EMAC_TX_CTL1); // 更新发送数据计数 netdev_linkinfo_xmit_inc(pnetdev); netdev_statinfo_total_add(pnetdev, LINK_OUTPUT, pbuf->tot_len); if (((u8 *)pbuf->payload)[0] & 1) { // 广播数据包 netdev_statinfo_mcasts_inc(pnetdev, LINK_OUTPUT); } else { // 单播数据包 netdev_statinfo_ucasts_inc(pnetdev, LINK_OUTPUT); } return 0; }
发送数据做了最简化的处理,我们首先假设512个描述符不会被用完,因为发送时将描述符设置为DMA硬件所有,DMA发送数据完成后会将描述符再次归还给CPU所有,所以一般情况下512个描述符只要数据不是发送特别快,不会被用满的,当然如果发生用满了的情况,这里我们只简单的打印一条错误。在实际驱动中,需要阻塞,然后在发送中断中回收描述符后进行唤醒。
发送数据时数据是在协议栈传送下来的pbuf中,我们需要使用 pbuf_copy_partial 接口将pbuf中的发送数据复制到一个空闲的发送描述符对应的缓冲区中,改接口原型如下:
u16_t pbuf_copy_partial(const struct pbuf *p, void *dataptr, u16_t len, u16_t offset);
- p:携带发送数据的pbuf。
- dataptr:要拷贝的目的缓冲区的地址。
- len:拷贝的数据大小。
- offset:拷贝起始偏移值,一般为0。
随后启动DMA硬件发送数据,然后通过内核接口统计发送的数据计数,还是比较简单的。调试的时候可以在电脑上装上wireshark软件,SylixOS在启动的时候会发送一些数据包,通过在电脑段抓包来辅助确认发送是不是正常的。
2. 接收数据
2.1 接收中断上半部处理
接收需要通过中断来处理,中断上半部的处理如下:
static irqreturn_t sun8i_emac_isr(struct netdev *pnetdev, unsigned long vector) { u32 val; struct emac_eth_dev *emac = &emac_dev; // 清除中断状态 val = readl(emac->mac_reg + EMAC_INT_STA); writel(val, emac->mac_reg + EMAC_INT_STA); // 处理接收中断 if (val & (1 << 8)) { // 放入网络处理线程去处理 netdev_notify(pnetdev, LINK_INPUT, 1); } // 关闭控制器中断 val = readl(emac->mac_reg + EMAC_INT_EN); val &= ~(1 << 8); writel(val, emac->mac_reg + EMAC_INT_EN); return LW_IRQ_HANDLED; }
首先清除中断状态,然后激活网络处理线程去处理,最后关闭控制器的接收中断,因为我们需要在网络处理下半部中轮询检测是否有新数据接收到,如果有就处理,知道处理完所有接收到数据退出。激活网络线程处理接收数据的接口为netdev_notify,原型如下:
int netdev_notify(struct netdev *netdev, netdev_inout inout, int q_en);
- netdev:网络设备。
- inout:方向,接收的话填 LINK_INPUT。
- q_en:是否放入网络处理线程中处理,1表示在线程中处理,0表示直接在中断上下文中调用下半部接口处理接收数据。
一般为了实时性考虑,都需要将接收数据的处理放在下半部网络处理线程中进行,,以便尽快退出中断上半部处理函数。
2.2 接收中断下半部处理
接收下半部处理代码如下:
void emacReceive(struct netdev *pnetdev, int (*input)(struct netdev *, struct pbuf *)) { struct pbuf *pbuf; struct emac_eth_dev *emac = &emac_dev; u32 desc_num = emac->rx_currdesc; struct emac_dma_desc *desc_p = &emac->rx_chain[desc_num]; int length; while (!(desc_p->status & EMAC_DESC_OWN_DMA)) { // 检测接受状态 if (!(desc_p->status & EMAC_DESC_RX_ERROR_MASK)) { length = (desc_p->status >> 16) & 0x3fff; pbuf = netdev_pbuf_alloc(length); length -= ETH_FCS_LEN; if (!pbuf) { // pbuf耗尽 netdev_linkinfo_memerr_inc(pnetdev); netdev_linkinfo_drop_inc(pnetdev); netdev_statinfo_discards_inc(pnetdev, LINK_INPUT); } else { // 复制数据到 pbuf payload pbuf_take(pbuf, (void *)(unsigned long)(u32)desc_p->buf_addr, (u16)length); // 将数据上报协议栈处理 if (ERROR_NONE != input(pnetdev, pbuf)) { // 处理失败 netdev_pbuf_free(pbuf); netdev_statinfo_discards_inc(pnetdev, LINK_INPUT); } else { // 处理成功,更新计数 netdev_linkinfo_recv_inc(pnetdev); netdev_statinfo_total_add(pnetdev, LINK_INPUT, length); if (((u8 *)pbuf->payload)[0] & 1) { // 广播数据包 netdev_statinfo_mcasts_inc(pnetdev, LINK_INPUT); } else { // 单播数据包 netdev_statinfo_ucasts_inc(pnetdev, LINK_INPUT); } } } } else { printk("Receive packet error!!\n"); netdev_linkinfo_err_inc(pnetdev); } // 重新设置 DMA 接受描述符 desc_p->status |= EMAC_DESC_OWN_DMA; // 更新下一个要处理的接受描述符 if (++desc_num >= CONFIG_RX_DESCR_NUM) desc_num = 0; emac->rx_currdesc = desc_num; desc_p = &emac->rx_chain[desc_num]; } // 打开控制器中断 int val; val = readl(emac->mac_reg + EMAC_INT_EN); val |= 1 << 8; writel(val, emac->mac_reg + EMAC_INT_EN); return; }
讲解其中几个比较重要的地方:
- 由于接收到数据时,数据还在DMA描述符对应的缓冲区中,所以需要将其复制到pbuf中,那么必须得先通过 netdev_pbuf_alloc 接口从内核中申请一个pbuf,参数为pbuf所要携带得数据大小,这个大小是从硬件中读取出来的。
- 申请pbuf成功后,通过 pbuf_take 接口将数据从DMA缓冲区复制到pbuf中,参数也很好理解,类似memcpy。
- 数据复制成功后通过 input(pnetdev, pbuf) 回调函数上送协议栈处理接收到得数据。
- 处理成功后和发送流程类似,更新统计数据。
- 处理完一个DMA描述符后,就将这个描述符所属者再次设置为DMA硬件所有,然后继续处理下一个有数据得DMA描述符。
- 全部处理完接收数据之后,打开控制器接收中断。
可以发现接收处理也还算比较简单,当然本次驱动中我们都是做最简化得处理,为的是能尽快的入门网络驱动开发基础的知识。实际驱动中都有比较复杂的处理,比如为了性能需要做零拷贝管理等等。
评论