全志D1开发(十四)网络驱动之网络数据传输

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2022年5月3日21:50:11 评论 767

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描述符。
  • 全部处理完接收数据之后,打开控制器接收中断。

可以发现接收处理也还算比较简单,当然本次驱动中我们都是做最简化得处理,为的是能尽快的入门网络驱动开发基础的知识。实际驱动中都有比较复杂的处理,比如为了性能需要做零拷贝管理等等。

gewenbin
  • 本文由 发表于 2022年5月3日21:50:11
  • 转载请务必保留本文链接:http://www.databusworld.cn/10724.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: