DWC Ethernet QOS U-Boot Driver 之操作集

本文基于 U-Boot v2025.10 中 DWC Ethernet QOS 驱动相关代码。

Driver-specific operation set

下面开始介绍 DWC Ethernet QOS 驱动中的操作集,上层应用对以太网上数据收发等相关操作便是通过这些接口调用的。

static const struct eth_ops eqos_ops = {
    .start = eqos_start,
    .stop = eqos_stop,
    .send = eqos_send,
    .recv = eqos_recv,
    .free_pkt = eqos_free_pkt,
    .write_hwaddr = eqos_write_hwaddr,
    .read_rom_hwaddr    = eqos_read_rom_hwaddr,
};

eqos_start & eqos_stop

eqos_start() 中做了一系列的初始化工作以备后续的数据收发,主要是进行了复位 MAC、配置 PHY、配置 MAC、准备描述符这些操作,下面进一步的详细介绍如下:

    ret = eqos->config->ops->eqos_start_resets(dev);
    if (ret < 0) {
        pr_err("eqos_start_resets() failed: %d\n", ret);
        goto err;
    }

    udelay(10);

    eqos->reg_access_ok = true;

    /*
     * Assert the SWR first, the actually reset the MAC and to latch in
     * e.g. i.MX8M Plus GPR[1] content, which selects interface mode.
     */
    setbits_le32(&eqos->dma_regs->mode, EQOS_DMA_MODE_SWR);

    ret = wait_for_bit_le32(&eqos->dma_regs->mode,
                EQOS_DMA_MODE_SWR, false,
                eqos->config->swr_wait, false);
    if (ret) {
        pr_err("EQOS_DMA_MODE_SWR stuck\n");
        goto err_stop_resets;
    }

    ret = eqos->config->ops->eqos_calibrate_pads(dev);
    if (ret < 0) {
        pr_err("eqos_calibrate_pads() failed: %d\n", ret);
        goto err_stop_resets;
    }

    if (eqos->config->ops->eqos_get_tick_clk_rate) {
        rate = eqos->config->ops->eqos_get_tick_clk_rate(dev);

        val = (rate / 1000000) - 1;
        writel(val, &eqos->mac_regs->us_tic_counter);
    }

上面首先进行 PHY 的硬复位操作(如果 eqos_start_resets() 实现),然后尝试 MAC 的软复位,当 DMA_Mode 中的 SWR 置位后,MAC 和 DMA 控制器会复位 DMA、MTL 和 MAC 的逻辑和所有内部寄存器,并在所有复位完成后自动清除该位。

eqos_calibrate_pads() 的作用是在驱动初始化时触发一次以太网 PHY 接口 PAD 的电阻/电压自动调节,保证信号完整性。然后,如果 eqos_get_tick_clk_rate() 函数被定义,则获取时钟频率以计算 MAC_1US_Tic_Counter 的值,为 EQOS 以太网控制器生成精确的 1 微秒内部节拍信号,以便驱动 EEE(Energy Efficient Ethernet)等需要 1 µs 时间基准的硬件事件和统计计数器。

    /*
     * if PHY was already connected and configured,
     * don't need to reconnect/reconfigure again
     */
    if (!eqos->phy) {
        int addr = -1;
        ofnode fixed_node;

        if (IS_ENABLED(CONFIG_PHY_FIXED)) {
            fixed_node = ofnode_find_subnode(dev_ofnode(dev),
                             "fixed-link");
            if (ofnode_valid(fixed_node))
                eqos->phy = fixed_phy_create(dev_ofnode(dev));
        }

        if (!eqos->phy) {
            addr = eqos_get_phy_addr(eqos, dev);
            eqos->phy = phy_connect(eqos->mii, addr, dev,
                        eqos->config->interface(dev));
        }

        if (!eqos->phy) {
            pr_err("phy_connect() failed\n");
            ret = -ENODEV;
            goto err_stop_resets;
        }

        if (eqos->max_speed) {
            ret = phy_set_supported(eqos->phy, eqos->max_speed);
            if (ret) {
                pr_err("phy_set_supported() failed: %d\n", ret);
                goto err_shutdown_phy;
            }
        }

        eqos->phy->node = eqos->phy_of_node;
        ret = phy_config(eqos->phy);
        if (ret < 0) {
            pr_err("phy_config() failed: %d\n", ret);
            goto err_shutdown_phy;
        }
    }

    ret = phy_startup(eqos->phy);
    if (ret < 0) {
        pr_err("phy_startup() failed: %d\n", ret);
        goto err_shutdown_phy;
    }

    if (!eqos->phy->link) {
        pr_err("No link\n");
        ret = -EAGAIN;
        goto err_shutdown_phy;
    }

    ret = eqos_adjust_link(dev);
    if (ret < 0) {
        pr_err("eqos_adjust_link() failed: %d\n", ret);
        goto err_shutdown_phy;
    }

这部分代码则是对 PHY 驱动进行配置与初始化。首先,如果开启了 CONFIG_PHY_FIXED,则尝试创建虚拟 PHY 设备,并跳过下面对物理 PHY 的初始化。

对于物理 PHY 的配置,在 eqos_get_phy_addr() 获取设备树中 phy-handle 节点 reg 所指定的 PHY 的地址,这个地址一般由 PHY 地址引脚的高低电平决定。获取到 PHY 地址后,由 phy_connect() 查找或创建 PHY 设备 (struct phy_device)。

struct phy_device *phy_connect(struct mii_dev *bus, int addr,
                   struct udevice *dev,
                   phy_interface_t interface)
{
    struct phy_device *phydev = NULL;
    uint mask = (addr >= 0) ? (1 << addr) : 0xffffffff;

#ifdef CONFIG_PHY_FIXED
    phydev = phy_connect_fixed(bus, dev);
#endif

#ifdef CONFIG_PHY_NCSI
    if (!phydev && interface == PHY_INTERFACE_MODE_NCSI)
        phydev = phy_device_create(bus, 0, PHY_NCSI_ID, false);
#endif

#ifdef CONFIG_PHY_ETHERNET_ID
    if (!phydev)
        phydev = phy_connect_phy_id(bus, dev, addr);
#endif

#ifdef CONFIG_PHY_XILINX_GMII2RGMII
    if (!phydev)
        phydev = phy_connect_gmii2rgmii(bus, dev);
#endif

    if (!phydev)
        phydev = phy_find_by_mask(bus, mask);

    if (phydev)
        phy_connect_dev(phydev, dev, interface);
    else
        printf("Could not get PHY for %s: addr %d\n", bus->name, addr);
    return phydev;
}

这里在不考虑额外的 CONFIG_PHY_* 的情况下简单介绍 phy_connect() 的调用流程。

phy_connect()
  -> phy_find_by_mask()
    -> get_phy_device_by_mask()
      -> search_for_existing_phy()
      -> create_phy_by_mask()
        -> get_phy_id()
        -> phy_device_create()
          -> get_phy_driver()
  -> phy_connect_dev()

在调用 phy_connect() 时传入的 addr (即 eqos_get_phy_addr() 获得的 PHY 地址)会首先通过计算 (addr >= 0) ? (1 << addr) : 0xffffffff 得到 mask。然后在 get_phy_device_by_mask() 中,首先调用 search_for_existing_phy() 尝试在 bus->phymap[] 中查找已经注册的 PHY 设备,如果没有查找到已注册的 PHY 设备,则通过 create_phy_by_mask() 创建。

static struct phy_device *create_phy_by_mask(struct mii_dev *bus,
                         uint phy_mask, int devad)
{
    u32 phy_id = 0xffffffff;
    bool is_c45;

    while (phy_mask) {
        int addr = ffs(phy_mask) - 1;
        int r = get_phy_id(bus, addr, devad, &phy_id);

        /*
         * If the PHY ID is flat 0 we ignore it.  There are C45 PHYs
         * that return all 0s for C22 reads (like Aquantia AQR112) and
         * there are C22 PHYs that return all 0s for C45 reads (like
         * Atheros AR8035).
         */
        if (r == 0 && phy_id == 0)
            goto next;

        /* If the PHY ID is mostly f's, we didn't find anything */
        if (r == 0 && (phy_id & 0x1fffffff) != 0x1fffffff) {
            is_c45 = (devad == MDIO_DEVAD_NONE) ? false : true;
            return phy_device_create(bus, addr, phy_id, is_c45);
        }
next:
        phy_mask &= ~(1 << addr);
    }
    return NULL;
}

create_phy_by_mask() 会由右至左遍历 phy_mask(也就是之前由 addr 计算得到的 mask)的每个 bit,在 get_phy_id() 中通过 MDIO 读取 PHY 的 PHY Identification Register1 (0x02)PHY Identification Register2 (0x03)

获取到 PHY ID 后会调用 phy_device_create() 创建 PHY 设备并与 PHY 驱动匹配,如果从寄存器中读取到的 PHY ID 与 PHY 驱动中定义的 PHY ID 匹配,就会调用 phy_probe() 初始化 PHY 驱动。其中 PHY 驱动的定义如下,uidmask 便是用于与从寄存器中读取到的 PHY ID 进行匹配的,有些类似于普通设备的设备树中的 compatible 字段。

U_BOOT_PHY_DRIVER(genphy) = {
    .uid        = 0xffffffff,
    .mask       = 0xffffffff,
    .name       = "Generic PHY",
    .features   = PHY_GBIT_FEATURES | SUPPORTED_MII |
              SUPPORTED_AUI | SUPPORTED_FIBRE |
              SUPPORTED_BNC,
    .config     = genphy_config,
    .startup    = genphy_startup,
    .shutdown   = genphy_shutdown,
};

通用 PHY 驱动的定义如上,如有特殊的需要可以另行实现如上形式的自定义 PHY 驱动,其中uidmask 便是用于与从寄存器中读取到的 PHY ID 进行匹配的,类似于普通设备的设备树中的 compatible 字段。

phy_device_create() 会创建 PHY 设备并通过 get_phy_driver() 与 PHY 驱动匹配,如果从寄存器中读取到的 PHY ID 与驱动中定义的 uid & mask 匹配就会返回对应的驱动与设备进行绑定,然后调用 phy_probe() 初始化 PHY 驱动,最后将创建的 PHY 设备添加到 bus->phymap[] 中以便后续可以直接查找到设备。

如果 PHY 设备成功创建,则调用 phy_connect_dev() 将 PHY 设备与网卡设备进行绑定。此后,网卡驱动中便可以找到 PHY 设备并对 PHY 进行操作。

接下来是 phy_config()phy_startup(),它们会最终会分别调用到 PHY 驱动中的 .config.startup 函数。

例如, genphy 中的 genphy_config 会通过读取 PHY 的 Basic Status Register (0x01)Extended status register (0x0F),获得 phydev->supportedphydev->advertising 并调用 genphy_config_aneg() 配置自协商。

phy_startup() 会首先在 genphy_update_link() 中读取 Basic Status Register (0x01) 获取 link 状态,如果开启了自协商,则会判断自协商完成状态;然后会在 genphy_parse_link() 中读取 Basic Control Register (0x00) 获取 PHY 支持的工作模式,如果开启了自协商则会读取 Auto-Negotiation Advertisement (0x04)Auto-Negotiation Link Partner Ability (0x05)Basic Control Register (0x00) 获取链路两端自动协商最高的共同工作模式。

eqos_adjust_link() 会根据前面从 PHY 中获取到的工作模式来配置 MAC 的寄存器来配置全双工/半双工、link 速度等能力。

之后则是进行 DWC Ethernet QOS 的寄存器配置,依次配置了 MTL、MAC、DMA 相关的寄存器,因为这些寄存器过于繁杂,这里不一一进行介绍。

    /* Set up descriptors */

    memset(eqos->tx_descs, 0, eqos->desc_size * EQOS_DESCRIPTORS_TX);
    memset(eqos->rx_descs, 0, eqos->desc_size * EQOS_DESCRIPTORS_RX);

    for (i = 0; i < EQOS_DESCRIPTORS_TX; i++) {
        struct eqos_desc *tx_desc = eqos_get_desc(eqos, i, false);
        eqos->config->ops->eqos_flush_desc(tx_desc);
    }

    for (i = 0; i < EQOS_DESCRIPTORS_RX; i++) {
        struct eqos_desc *rx_desc = eqos_get_desc(eqos, i, true);

        addr64 = (ulong)(eqos->rx_dma_buf + (i * EQOS_MAX_PACKET_SIZE));
        rx_desc->des0 = lower_32_bits(addr64);
        rx_desc->des1 = upper_32_bits(addr64);
        rx_desc->des3 = EQOS_DESC3_OWN | EQOS_DESC3_BUF1V;
        mb();
        eqos->config->ops->eqos_flush_desc(rx_desc);
        eqos->config->ops->eqos_inval_buffer((void *)addr64, EQOS_MAX_PACKET_SIZE);
    }

    addr64 = (ulong)eqos_get_desc(eqos, 0, false);
    writel(upper_32_bits(addr64), &eqos->dma_regs->ch0_txdesc_list_haddress);
    writel(lower_32_bits(addr64), &eqos->dma_regs->ch0_txdesc_list_address);
    writel(EQOS_DESCRIPTORS_TX - 1,
           &eqos->dma_regs->ch0_txdesc_ring_length);

    addr64 = (ulong)eqos_get_desc(eqos, 0, true);
    writel(upper_32_bits(addr64), &eqos->dma_regs->ch0_rxdesc_list_haddress);
    writel(lower_32_bits(addr64), &eqos->dma_regs->ch0_rxdesc_list_address);
    writel(EQOS_DESCRIPTORS_RX - 1,
           &eqos->dma_regs->ch0_rxdesc_ring_length);

    /* Enable everything */
    setbits_le32(&eqos->dma_regs->ch0_tx_control,
             EQOS_DMA_CH0_TX_CONTROL_ST);
    setbits_le32(&eqos->dma_regs->ch0_rx_control,
             EQOS_DMA_CH0_RX_CONTROL_SR);
    setbits_le32(&eqos->mac_regs->configuration,
             EQOS_MAC_CONFIGURATION_TE | EQOS_MAC_CONFIGURATION_RE);

    /* TX tail pointer not written until we need to TX a packet */
    /*
     * Point RX tail pointer at last descriptor. Ideally, we'd point at the
     * first descriptor, implying all descriptors were available. However,
     * that's not distinguishable from none of the descriptors being
     * available.
     */
    last_rx_desc = (ulong)eqos_get_desc(eqos, EQOS_DESCRIPTORS_RX - 1, true);
    writel(last_rx_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer);

最后便是描述符相关的准备,Tx 描述符的准备比较简单,只是将全 0 数据同步 Cache 到内存中;而 Rx 描述符除同步 Cache 以外,还将 RX buffer (接收数据缓冲区)以 EQOS_MAX_PACKET_SIZE 分块的地址填入描述符中,并设置 Buffer 1 Address ValidOWN 表示 RDES0 中的地址有效以及该描述符为 DMA 所有。

随后,将 Tx 与 Rx 第一个描述符的地址分别配置到 DMA_CH0_TxDesc_List_HAddressDMA_CH0_TxDesc_List_AddressDMA_CH0_RxDesc_List_HAddressDMA_CH0_RxDesc_List_Address 中以及分别配置描述符的长度到 DMA_CH0_TxDesc_Ring_LengthDMA_CH0_RxDesc_Ring_Length 中。

最后的最后,配置 DMA_CH0_Tx_ControlDMA_CH0_Rx_Control 使能 Tx 与 Rx DMA,配置 MAC_Configuration 使能数据包的发送与接收。至此,DWC Ethernet QOS 数据包的接收与发送已全部准备完毕,而在获取 Rx 的最后一个描述符的地址并配置到 DMA_CH0_RxDesc_Tail_Pointer 后,数据包的接收便已经开始。

eqos_stop() 中,值得注意的是接收发送能力关闭的顺序,通过读取 MTL_TxQ0_DebugMTL_RxQ0_Debug 以确保接收发送数据包传输完成,最后调用 phy_shutdown()eqos_stop_resets() 完成最后的清理工作。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇