This is an automated email from the ASF dual-hosted git repository.

kasjer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-core.git

commit 4db12a93696470383cf0f43b579036cc913b716c
Author: Jerzy Kasenberg <[email protected]>
AuthorDate: Tue Oct 13 12:53:44 2026 +0200

    tinyusb/net_lwip: Add lwIP network interface for USB network function
    
    Add package that registers TinyUSB network function (NCM by default,
    ECM or RNDIS with USBD_NET) as lwIP network interface "us".  It can
    be used next to Ethernet; the interface gets static IPv4 address
    (USBD_NET_IP_ADDR) and becomes default only when no other interface
    is default.
    
    TinyUSB network drivers are not thread safe and run their callbacks
    in USB task, while lwIP runs in tcpip thread:
     - received frames are copied to pbufs in USB task and passed to
       tcpip_input(); receive buffer is always renewed, also for empty
       frames, so reception never stalls,
     - frames from lwIP are queued and sent from a function deferred to
       USB task with usbd_defer_func(); tud_event_hook_cb() reschedules it
       when a transfer completes,
     - lwIP link state follows USB state (configured, not suspended and
       for ECM data interface active) and is applied in tcpip thread.
       ECM keeps can_xmit across bus reset, gating on host activity avoids
       sending before data endpoints are open.
    
    Device and host MAC addresses are locally administered addresses
    derived from hal_bsp_hw_id() unless USBD_NET_MAC_ADDR is set.
    
    Application has to set USBD_COMPOSITE_DEVICE since the function uses
    Interface Association Descriptor.
    
    Assisted-by: Claude:claude-opus-5-5
    Signed-off-by: Jerzy Kasenberg <[email protected]>
---
 .../tinyusb/net_lwip/include/net_lwip/net_lwip.h   |  44 +++
 hw/usb/tinyusb/net_lwip/pkg.yml                    |  46 +++
 hw/usb/tinyusb/net_lwip/src/net_lwip.c             | 431 +++++++++++++++++++++
 hw/usb/tinyusb/net_lwip/syscfg.yml                 |  57 +++
 4 files changed, 578 insertions(+)

diff --git a/hw/usb/tinyusb/net_lwip/include/net_lwip/net_lwip.h 
b/hw/usb/tinyusb/net_lwip/include/net_lwip/net_lwip.h
new file mode 100644
index 000000000..cd93579b7
--- /dev/null
+++ b/hw/usb/tinyusb/net_lwip/include/net_lwip/net_lwip.h
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_NET_LWIP_
+#define H_NET_LWIP_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct netif;
+
+/**
+ * Returns lwIP network interface of USB network function.
+ *
+ * Interface is added during sysinit (USBD_NET_SYSINIT_STAGE) with static
+ * IPv4 address USBD_NET_IP_ADDR.  Its link is up while USB host has the
+ * network function configured.
+ *
+ * @return pointer to lwIP network interface
+ */
+struct netif *usb_net_lwip_netif(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_NET_LWIP_ */
diff --git a/hw/usb/tinyusb/net_lwip/pkg.yml b/hw/usb/tinyusb/net_lwip/pkg.yml
new file mode 100644
index 000000000..db8d956d2
--- /dev/null
+++ b/hw/usb/tinyusb/net_lwip/pkg.yml
@@ -0,0 +1,46 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+pkg.name: hw/usb/tinyusb/net_lwip
+pkg.description: >
+    lwIP network interface for TinyUSB network function (CDC-NCM,
+    CDC-ECM or RNDIS selected with USBD_NET).
+pkg.author: "Apache Mynewt <[email protected]>"
+pkg.homepage: "https://mynewt.apache.org/";
+pkg.keywords:
+    - usb
+    - tinyusb
+    - lwip
+    - ncm
+    - ecm
+    - rndis
+
+# Package provides TinyUSB callbacks that override weak defaults
+# (tud_event_hook_cb, tud_network_set_packet_filter_cb).
+pkg.whole_archive: true
+
+pkg.deps:
+    - "@apache-mynewt-core/kernel/os"
+    - "@apache-mynewt-core/hw/hal"
+    - "@apache-mynewt-core/net/ip/lwip_mn"
+    - "@apache-mynewt-core/hw/usb/tinyusb"
+    - "@apache-mynewt-core/hw/usb/tinyusb/std_descriptors"
+
+pkg.init:
+    usb_net_lwip_init: 'MYNEWT_VAL(USBD_NET_SYSINIT_STAGE)'
diff --git a/hw/usb/tinyusb/net_lwip/src/net_lwip.c 
b/hw/usb/tinyusb/net_lwip/src/net_lwip.c
new file mode 100644
index 000000000..80f68153a
--- /dev/null
+++ b/hw/usb/tinyusb/net_lwip/src/net_lwip.c
@@ -0,0 +1,431 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * lwIP network interface on top of TinyUSB network function.
+ *
+ * TinyUSB network drivers are not thread safe and all their callbacks run
+ * in USB task.  lwIP runs in tcpip thread (or any task holding the core
+ * lock).  Therefore:
+ *  - received frames are copied to pbufs in USB task and passed to lwIP
+ *    with tcpip_input(),
+ *  - frames from lwIP are queued and sent from USB task by a function
+ *    deferred with usbd_defer_func(); USB events reschedule it when
+ *    transmission was blocked,
+ *  - link state follows USB state and is passed to lwIP with a tcpip
+ *    callback message.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include <os/mynewt.h>
+#include <hal/hal_bsp.h>
+
+#include <lwip/tcpip.h>
+#include <lwip/netif.h>
+#include <lwip/pbuf.h>
+#include <lwip/etharp.h>
+#include <lwip/ethip6.h>
+#include <netif/ethernet.h>
+
+#include <tusb.h>
+#include <device/dcd.h>
+#include <device/usbd_pvt.h>
+
+#include <net_lwip/net_lwip.h>
+
+#define USB_NET_TXQ_LEN     MYNEWT_VAL(USBD_NET_TX_QUEUE_LEN)
+/* ECM opens data endpoints only when host selects data alternate setting */
+#define USB_NET_IS_ECM      MYNEWT_VAL_CHOICE(USBD_NET, ECM)
+
+struct usb_net {
+    struct netif nif;
+    /* Device side MAC address */
+    uint8_t mac[ETHARP_HWADDR_LEN];
+    /* Frames queued by lwIP, sent from USB task */
+    struct pbuf *txq[USB_NET_TXQ_LEN];
+    uint8_t txq_head;
+    volatile uint8_t txq_count;
+    /* Deferred service function is queued in USB task */
+    volatile bool service_pending;
+    /* Bus reset or disconnect seen since last service run */
+    volatile bool reset_seen;
+    /* ECM data interface activated by host since last reset */
+    volatile bool ecm_active;
+    /* Data path usable, changed by USB task only */
+    volatile bool data_up;
+    /* Link state change message for tcpip thread */
+    struct tcpip_callback_msg *link_msg;
+    uint32_t rx_drops;
+    uint32_t tx_drops;
+};
+
+static struct usb_net usb_net;
+
+/* Host side MAC address, reported to host by TinyUSB network function */
+uint8_t tud_network_mac_address[6];
+
+#if defined(MYNEWT_VAL_USBD_NET_MAC_ADDR)
+#define _Args(...)              __VA_ARGS__
+#define STRIP_PARENS(X)         X
+#define UNMANGLE_MYNEWT_VAL(X)  STRIP_PARENS(_Args X)
+
+static const uint8_t usb_net_cfg_mac[ETHARP_HWADDR_LEN] = {
+    UNMANGLE_MYNEWT_VAL(MYNEWT_VAL_USBD_NET_MAC_ADDR)
+};
+#endif
+
+static void usb_net_service(void *arg);
+
+struct netif *
+usb_net_lwip_netif(void)
+{
+    return &usb_net.nif;
+}
+
+/*
+ * Device and host MAC addresses: from syscfg, or locally administered
+ * unicast address derived from hardware id so each board is different.
+ * Host address differs from device address in last bit.
+ */
+static void
+usb_net_mac_init(void)
+{
+#if defined(MYNEWT_VAL_USBD_NET_MAC_ADDR)
+    memcpy(usb_net.mac, usb_net_cfg_mac, ETHARP_HWADDR_LEN);
+#else
+    uint8_t id[32];
+    uint32_t hash = 2166136261u;
+    int len;
+    int i;
+
+    /* FNV-1a hash of hardware id */
+    len = hal_bsp_hw_id(id, sizeof(id));
+    for (i = 0; i < len; ++i) {
+        hash = (hash ^ id[i]) * 16777619u;
+    }
+    usb_net.mac[0] = 0x02;
+    usb_net.mac[1] = (uint8_t)(hash >> 24);
+    usb_net.mac[2] = (uint8_t)(hash >> 16);
+    usb_net.mac[3] = (uint8_t)(hash >> 8);
+    usb_net.mac[4] = (uint8_t)hash;
+    hash = (hash ^ 0xA5) * 16777619u;
+    usb_net.mac[5] = (uint8_t)(hash >> 24);
+#endif
+    memcpy(tud_network_mac_address, usb_net.mac, ETHARP_HWADDR_LEN);
+    tud_network_mac_address[5] ^= 1;
+}
+
+/*
+ * Requests usb_net_service() to run in USB task.  At most one request is
+ * queued so USB task queue is never flooded.
+ */
+static void
+usb_net_schedule(bool in_isr)
+{
+    os_sr_t sr;
+    bool pending;
+
+    OS_ENTER_CRITICAL(sr);
+    pending = usb_net.service_pending;
+    usb_net.service_pending = true;
+    OS_EXIT_CRITICAL(sr);
+
+    if (!pending) {
+        usbd_defer_func(usb_net_service, NULL, in_isr);
+    }
+}
+
+static struct pbuf *
+usb_net_txq_peek(void)
+{
+    struct pbuf *p = NULL;
+    os_sr_t sr;
+
+    OS_ENTER_CRITICAL(sr);
+    if (usb_net.txq_count) {
+        p = usb_net.txq[usb_net.txq_head];
+    }
+    OS_EXIT_CRITICAL(sr);
+
+    return p;
+}
+
+static void
+usb_net_txq_pop(void)
+{
+    os_sr_t sr;
+
+    OS_ENTER_CRITICAL(sr);
+    usb_net.txq_head = (usb_net.txq_head + 1) % USB_NET_TXQ_LEN;
+    usb_net.txq_count--;
+    OS_EXIT_CRITICAL(sr);
+}
+
+static void
+usb_net_txq_flush(void)
+{
+    struct pbuf *p;
+
+    while ((p = usb_net_txq_peek()) != NULL) {
+        usb_net_txq_pop();
+        pbuf_free(p);
+    }
+}
+
+/* Runs in tcpip thread, applies data path state to lwIP link state */
+static void
+usb_net_link_cb(void *arg)
+{
+    if (usb_net.data_up) {
+        netif_set_link_up(&usb_net.nif);
+    } else {
+        netif_set_link_down(&usb_net.nif);
+    }
+}
+
+/* Runs in USB task */
+static void
+usb_net_service(void *arg)
+{
+    struct pbuf *p;
+    os_sr_t sr;
+    bool up;
+
+    OS_ENTER_CRITICAL(sr);
+    usb_net.service_pending = false;
+    if (usb_net.reset_seen) {
+        usb_net.reset_seen = false;
+        usb_net.ecm_active = false;
+    }
+    OS_EXIT_CRITICAL(sr);
+
+    up = tud_mounted() && !tud_suspended();
+#if USB_NET_IS_ECM
+    up = up && usb_net.ecm_active;
+#endif
+    if (up != usb_net.data_up) {
+        usb_net.data_up = up;
+        if (!up) {
+            usb_net_txq_flush();
+        }
+        if (tcpip_callbackmsg_trycallback(usb_net.link_msg) != ERR_OK) {
+            /* tcpip mailbox full, wait for space */
+            tcpip_callback(usb_net_link_cb, NULL);
+        }
+    }
+
+    while (usb_net.data_up) {
+        p = usb_net_txq_peek();
+        if (p == NULL) {
+            break;
+        }
+        if (!tud_network_can_xmit(p->tot_len)) {
+            /* Retried when transfer completes, see tud_event_hook_cb() */
+            break;
+        }
+        /* Frame is copied by tud_network_xmit_cb(), pbuf can be released */
+        tud_network_xmit(p, 0);
+        usb_net_txq_pop();
+        pbuf_free(p);
+    }
+}
+
+/*
+ * Called by TinyUSB for each event put in USB task queue, possibly from
+ * interrupt.  Events that may change USB state or free transmit buffer
+ * schedule service function.
+ */
+void
+tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr)
+{
+    switch (eventid) {
+    case DCD_EVENT_BUS_RESET:
+    case DCD_EVENT_UNPLUGGED:
+        usb_net.reset_seen = true;
+        usb_net_schedule(in_isr);
+        break;
+    case DCD_EVENT_SUSPEND:
+    case DCD_EVENT_RESUME:
+    case DCD_EVENT_SETUP_RECEIVED:
+        usb_net_schedule(in_isr);
+        break;
+    case DCD_EVENT_XFER_COMPLETE:
+        if (usb_net.txq_count) {
+            usb_net_schedule(in_isr);
+        }
+        break;
+    default:
+        /* SOF and deferred function calls are ignored */
+        break;
+    }
+}
+
+/*
+ * Host sets packet filter after it activated network function (ECM, NCM).
+ * Runs in USB task.
+ */
+void
+tud_network_set_packet_filter_cb(uint16_t packet_filter)
+{
+    usb_net.ecm_active = true;
+    usb_net_schedule(false);
+}
+
+/* Frame received from host, runs in USB task */
+bool
+tud_network_recv_cb(const uint8_t *src, uint16_t size)
+{
+    struct pbuf *p;
+
+    if (size > 0) {
+#if USB_NET_IS_ECM
+        if (!usb_net.ecm_active) {
+            /* Data received so data interface is active */
+            usb_net.ecm_active = true;
+            usb_net_schedule(false);
+        }
+#endif
+        p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL);
+        if (p == NULL) {
+            usb_net.rx_drops++;
+        } else {
+            pbuf_take(p, src, size);
+            if (usb_net.nif.input(p, &usb_net.nif) != ERR_OK) {
+                usb_net.rx_drops++;
+                pbuf_free(p);
+            }
+        }
+    }
+    /*
+     * Frame was copied, let driver receive next one.  This is needed also
+     * for empty frames, otherwise reception stops.
+     */
+    tud_network_recv_renew();
+
+    return true;
+}
+
+/* Copies frame to TinyUSB buffer, called from tud_network_xmit() */
+uint16_t
+tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg)
+{
+    struct pbuf *p = ref;
+
+    return pbuf_copy_partial(p, dst, p->tot_len, 0);
+}
+
+/* lwIP output, called with tcpip core lock held */
+static err_t
+usb_net_linkoutput(struct netif *nif, struct pbuf *p)
+{
+    os_sr_t sr;
+    uint8_t idx;
+
+    if (!usb_net.data_up) {
+        return ERR_IF;
+    }
+    if (p->tot_len > CFG_TUD_NET_MTU) {
+        usb_net.tx_drops++;
+        return ERR_IF;
+    }
+
+    pbuf_ref(p);
+    OS_ENTER_CRITICAL(sr);
+    if (usb_net.txq_count >= USB_NET_TXQ_LEN) {
+        OS_EXIT_CRITICAL(sr);
+        pbuf_free(p);
+        usb_net.tx_drops++;
+        return ERR_MEM;
+    }
+    idx = (usb_net.txq_head + usb_net.txq_count) % USB_NET_TXQ_LEN;
+    usb_net.txq[idx] = p;
+    usb_net.txq_count++;
+    OS_EXIT_CRITICAL(sr);
+
+    usb_net_schedule(false);
+
+    return ERR_OK;
+}
+
+static err_t
+usb_net_if_init(struct netif *nif)
+{
+    nif->name[0] = 'u';
+    nif->name[1] = 's';
+    nif->output = etharp_output;
+#if LWIP_IPV6
+    nif->output_ip6 = ethip6_output;
+#endif
+    nif->linkoutput = usb_net_linkoutput;
+    nif->mtu = CFG_TUD_NET_MTU - SIZEOF_ETH_HDR;
+    nif->hwaddr_len = ETHARP_HWADDR_LEN;
+    memcpy(nif->hwaddr, usb_net.mac, ETHARP_HWADDR_LEN);
+    nif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP |
+                 NETIF_FLAG_ETHERNET;
+#if LWIP_IGMP
+    nif->flags |= NETIF_FLAG_IGMP;
+#endif
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+    nif->flags |= NETIF_FLAG_MLD6;
+#endif
+
+    return ERR_OK;
+}
+
+void
+usb_net_lwip_init(void)
+{
+    ip4_addr_t addr;
+    ip4_addr_t mask;
+    struct netif *nif;
+    int rc;
+
+    /* Ensure this function only gets called by sysinit. */
+    SYSINIT_ASSERT_ACTIVE();
+
+    usb_net_mac_init();
+
+    rc = ip4addr_aton(MYNEWT_VAL(USBD_NET_IP_ADDR), &addr);
+    SYSINIT_PANIC_ASSERT(rc);
+    rc = ip4addr_aton(MYNEWT_VAL(USBD_NET_NETMASK), &mask);
+    SYSINIT_PANIC_ASSERT(rc);
+
+    usb_net.link_msg = tcpip_callbackmsg_new(usb_net_link_cb, NULL);
+    SYSINIT_PANIC_ASSERT(usb_net.link_msg != NULL);
+
+    LOCK_TCPIP_CORE();
+    nif = netif_add(&usb_net.nif, &addr, &mask, IP4_ADDR_ANY4, NULL,
+                    usb_net_if_init, tcpip_input);
+    if (nif != NULL) {
+        /* Interface is up, link follows USB state */
+        netif_set_up(nif);
+#if LWIP_IPV6
+        nif->ip6_autoconfig_enabled = 1;
+        netif_create_ip6_linklocal_address(nif, 1);
+#endif
+        /* Other interfaces (e.g. Ethernet) keep default route */
+        if (netif_default == NULL) {
+            netif_set_default(nif);
+        }
+    }
+    UNLOCK_TCPIP_CORE();
+    SYSINIT_PANIC_ASSERT(nif != NULL);
+}
diff --git a/hw/usb/tinyusb/net_lwip/syscfg.yml 
b/hw/usb/tinyusb/net_lwip/syscfg.yml
new file mode 100644
index 000000000..5f99767d4
--- /dev/null
+++ b/hw/usb/tinyusb/net_lwip/syscfg.yml
@@ -0,0 +1,57 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+syscfg.defs:
+    USBD_NET_IP_ADDR:
+        description: >
+            IPv4 address of the device on USB network interface.
+        value: '"192.168.7.1"'
+    USBD_NET_NETMASK:
+        description: >
+            IPv4 network mask of USB network interface.
+        value: '"255.255.255.0"'
+    USBD_NET_MAC_ADDR:
+        description: >
+            Device side MAC address as 6 comma separated bytes, e.g.
+            0x02,0x00,0x00,0x00,0x00,0x01.  Host side uses the same address
+            with last byte xor 1.  Empty: locally administered address
+            derived from hal_bsp_hw_id().
+        value:
+    USBD_NET_TX_QUEUE_LEN:
+        description: >
+            Number of outgoing frames lwIP can queue for USB transmission.
+        value: 8
+    USBD_NET_SYSINIT_STAGE:
+        description: >
+            Sysinit stage of USB network interface.  Must be after lwIP
+            (IP_SYSINIT_STAGE) and before USB stack start
+            (USBD_SYSINIT_STAGE) since host MAC address is reported in USB
+            descriptors.
+        value: 250
+
+syscfg.vals:
+    USBD_NET: NCM
+    USBD_NET_DESCRIPTOR_STRING: '"Mynewt network"'
+
+syscfg.restrictions:
+    - '(USBD_NET == "NCM") || (USBD_NET == "ECM") || (USBD_NET == "RNDIS")'
+    # Network function uses Interface Association Descriptor
+    - 'USBD_COMPOSITE_DEVICE'
+    - 'USBD_NET_SYSINIT_STAGE > IP_SYSINIT_STAGE'
+    - 'USBD_NET_SYSINIT_STAGE < USBD_SYSINIT_STAGE'

Reply via email to