Network Host
=================

本节主要介绍 Host USB 网卡的使用，当前已经支持和测试以下 USB 网卡:

- 4G 网卡：EC20(ECM/RNDIS)、手机（RNDIS）、SIMCOM7600(RNDIS)、ML307R(RNDIS)、AIR780(RNDIS)

.. caution:: 请注意，部分 4G 网卡默认不带自动拨号功能，请更换固件或者使用 AT 配置成自动拨号，否则无法获取 IP。

- USB 以太网卡：ASIX AX88772，REALTEK RTL8152
- USB WIFI 网卡： 博流 BL616（RNDIS/ECM）

USB 网卡相关的宏和文件
------------------------

网卡相关的宏如下，主要用于根据不同的网络组件注册网卡驱动：

.. code-block:: C

    // #define CONFIG_USBHOST_PLATFORM_CDC_ECM
    // #define CONFIG_USBHOST_PLATFORM_CDC_RNDIS
    // #define CONFIG_USBHOST_PLATFORM_CDC_NCM
    // #define CONFIG_USBHOST_PLATFORM_ASIX
    // #define CONFIG_USBHOST_PLATFORM_RTL8152

.. note:: 如果使用了 Kconfig 系统，上述宏自定生成，其他平台请手动定义。

USB 网卡传输层面已经对接好了相关网络组件，列举如下：

- 自定义 OS + LWIP 请使用 **platform/lwip/usbh_lwip.c**，需要自行包含该文件，并使能上述相关的宏。并在初始化 USB 之前调用 `tcpip_init(NULL, NULL)`
- RT-THREAD + LWIP 请使用 **platform/rtthread/usbh_lwip.c**，在 Kconfig 中使能对应的网卡驱动后自动勾选该文件，勾选 rt-thread lwip以后自动调用 `tcpip_init(NULL, NULL)`
- ESP-IDF + LWIP 请使用 **platform/freertos/usbh_net.c**，在 Kconfig 中使能对应的网卡驱动后自动勾选该文件，并且在初始化 USB 之前调用 `esp_netif_init()` + `esp_event_loop_create_default()`
- NUTTX + NUTTX 网络组件 请使用 **platform/nuttx/usbh_net.c**，在 Kconfig 中使能对应的网卡驱动后自动勾选该文件，勾选网络组件以后自动调用

.. note:: 如果是自行添加代码，别忘了添加  USB 网卡驱动相关的源文件，例如 **class/usbh_cdc_ecm.c**。所以我们推荐搭配对应平台使用哦，省去自己添加文件的麻烦

USB 网卡对接过程
-------------------

下面举例对接 LWIP 的对接过程。

- 在 USB 网卡枚举完成以后，会 **自动** 调用 `usbh_xxx_run` 函数，此时注册 netif 驱动，并且开启 DHCP 客户端和获取 IP 的定时器。

.. code-block:: C

    void usbh_cdc_ecm_run(struct usbh_cdc_ecm *cdc_ecm_class)
    {
        struct netif *netif = &g_cdc_ecm_netif;

        netif->hwaddr_len = 6;
        memcpy(netif->hwaddr, cdc_ecm_class->mac, 6);

        IP4_ADDR(&g_ipaddr, 0, 0, 0, 0);
        IP4_ADDR(&g_netmask, 0, 0, 0, 0);
        IP4_ADDR(&g_gateway, 0, 0, 0, 0);

        netif = netif_add(netif, &g_ipaddr, &g_netmask, &g_gateway, NULL, usbh_cdc_ecm_if_init, tcpip_input);
        netif_set_default(netif);
        while (!netif_is_up(netif)) {
        }

        dhcp_handle = usb_osal_timer_create("dhcp", 200, dhcp_timeout, netif, true);
        if (dhcp_handle == NULL) {
            USB_LOG_ERR("timer creation failed! \r\n");
            while (1) {
            }
        }

        usb_osal_thread_create("usbh_cdc_ecm_rx", 2048, CONFIG_USBHOST_PSC_PRIO + 1, usbh_cdc_ecm_rx_thread, NULL);
    #if LWIP_DHCP
        dhcp_start(netif);
        usb_osal_timer_start(dhcp_handle);
    #endif
    }

- `usbh_lwip_eth_output_common` 用于将发送 pbuf 组装成 USB 网卡数据包
- `usbh_lwip_eth_input_common` 用于将 USB 网卡数据组装成 pbuf
- 实际网卡发送和接收处理

.. code-block:: C

    static err_t usbh_cdc_ecm_linkoutput(struct netif *netif, struct pbuf *p)
    {
        int ret;
        (void)netif;

        usbh_lwip_eth_output_common(p, usbh_cdc_ecm_get_eth_txbuf());
        ret = usbh_cdc_ecm_eth_output(p->tot_len);
        if (ret < 0) {
            return ERR_BUF;
        } else {
            return ERR_OK;
        }
    }

    void usbh_cdc_ecm_eth_input(uint8_t *buf, uint32_t buflen)
    {
        usbh_lwip_eth_input_common(&g_cdc_ecm_netif, buf, buflen);
    }

- USB 网卡 拔出以后会 **自动** 调用 `usbh_xxx_stop` 函数，此时需要停止 DHCP 客户端，删除定时器，并且移除 netif。

.. code-block:: C

    void usbh_cdc_ecm_stop(struct usbh_cdc_ecm *cdc_ecm_class)
    {
        struct netif *netif = &g_cdc_ecm_netif;
        (void)cdc_ecm_class;

    #if LWIP_DHCP
        dhcp_stop(netif);
        dhcp_cleanup(netif);
        usb_osal_timer_delete(dhcp_handle);
    #endif
        netif_set_down(netif);
        netif_remove(netif);
    }

- 因为 USB 网卡内部已经对接了LWIP，因此用户可以直接使用 LWIP 的 API，无需关心 USB 的实现。

USB 网卡 LWIP 配置宏相关注意事项
------------------------------------

**LWIP_TCPIP_CORE_LOCKING_INPUT** 用于不使用 lwip 内置的 tcpip 线程，而使用 USB 自己的接收处理线程。

**LWIP_TCPIP_CORE_LOCKING** 在现在 lwip 版本中默认是打开的，也推荐必须打开。

**PBUF_POOL_BUFSIZE** 推荐大于1600，搭配 LWIP_TCPIP_CORE_LOCKING_INPUT 使用，因为我们提供了使用 zero mempy 的方式，使用静态 pbuf，而不是把数据 copy 到 pbuf 中。

**TCPIP_THREAD_STACKSIZE** 推荐大于 1K，防止栈溢出。

.. code-block:: C

    #if LWIP_TCPIP_CORE_LOCKING_INPUT != 1
    #warning suggest you to set LWIP_TCPIP_CORE_LOCKING_INPUT to 1, usb handles eth input with own thread
    #endif

    #if LWIP_TCPIP_CORE_LOCKING != 1
    #error must set LWIP_TCPIP_CORE_LOCKING to 1
    #endif

    #if PBUF_POOL_BUFSIZE < 1600
    #error PBUF_POOL_BUFSIZE must be larger than 1600
    #endif

    #if TCPIP_THREAD_STACKSIZE < 1024
    #error TCPIP_THREAD_STACKSIZE must be >= 1024
    #endif


总结
--------------

.. note:: 通过以上内容，我们可以看到 CherryUSB 对 USB 网卡的支持是非常完善的，用户只需要使能对应的宏或者勾选，就可以实现 USB 网卡的自动识别和驱动注册，无需手动初始化网卡相关配置，用户只需关注应用层，极大地方便了用户的使用。

具体移植文章可以参考开发者的一些笔记 https://club.rt-thread.org/ask/article/5cf3e9e0b2d95800.html