usb_ncm_iface.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /*
  2. * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
  3. *
  4. * SPDX-License-Identifier: Unlicense OR CC0-1.0
  5. */
  6. /* DESCRIPTION:
  7. * This example contains code to make ESP32-S2/S3 as a USB network Device.
  8. */
  9. #include <stdio.h>
  10. #include "esp_log.h"
  11. #include "esp_netif.h"
  12. #include "esp_event.h"
  13. #include "tinyusb.h"
  14. #include "tinyusb_net.h"
  15. #include "wired_iface.h"
  16. #include "dhcpserver/dhcpserver_options.h"
  17. #include "lwip/esp_netif_net_stack.h"
  18. #include "esp_mac.h"
  19. static const char *TAG = "example_wired_tusb_ncm";
  20. static esp_netif_t *s_netif = NULL;
  21. /**
  22. * In this scenario of WiFi station to Ethernet bridge mode, we have this configuration
  23. *
  24. * (ISP) router ESP32 PC
  25. * [ AP ] <-> [ sta -- USB ] <-> [ USB-NCM device acting as eth-NIC ]
  26. *
  27. * From the PC's NIC perspective the L2 forwarding should be transparent and resemble this configuration:
  28. *
  29. * (ISP) router PC
  30. * [ AP ] <----------> [ virtual wifi-NIC ]
  31. *
  32. * In order for the ESP32 to act as L2 bridge it needs to accept the frames for the NCM device,
  33. * which we have fully under control, we can modify it's MAC address, as well as the WiFi station
  34. * MAC address, which need to be the same so the AP would see one device (virtual eth-NIC).
  35. * No need to modify the ethernet frames here, as we can set the station's MAC to the USB NCM device.
  36. */
  37. void mac_spoof(mac_spoof_direction_t direction, uint8_t *buffer, uint16_t len, uint8_t own_mac[6])
  38. {
  39. }
  40. esp_err_t wired_bridge_init(wired_rx_cb_t rx_cb, wired_free_cb_t free_cb)
  41. {
  42. const tinyusb_config_t tusb_cfg = {
  43. .external_phy = false,
  44. };
  45. ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
  46. tinyusb_net_config_t net_config = {
  47. .on_recv_callback = rx_cb,
  48. .free_tx_buffer = free_cb,
  49. };
  50. esp_read_mac(net_config.mac_addr, ESP_MAC_WIFI_STA);
  51. esp_err_t ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
  52. if (ret != ESP_OK) {
  53. ESP_LOGE(TAG, "USB net init but not connect wifi");
  54. return ret;
  55. }
  56. return ESP_OK;
  57. }
  58. esp_err_t wired_send(void *buffer, uint16_t len, void *buff_free_arg)
  59. {
  60. return tinyusb_net_send_sync(buffer, len, buff_free_arg, pdMS_TO_TICKS(100));
  61. }
  62. static void l2_free(void *h, void *buffer)
  63. {
  64. free(buffer);
  65. }
  66. static esp_err_t netif_transmit (void *h, void *buffer, size_t len)
  67. {
  68. if (wired_send(buffer, len, NULL) != ESP_OK) {
  69. ESP_LOGE(TAG, "Failed to send buffer to USB!");
  70. }
  71. return ESP_OK;
  72. }
  73. static esp_err_t netif_recv_callback(void *buffer, uint16_t len, void *ctx)
  74. {
  75. if (s_netif) {
  76. void *buf_copy = malloc(len);
  77. if (!buf_copy) {
  78. return ESP_ERR_NO_MEM;
  79. }
  80. memcpy(buf_copy, buffer, len);
  81. return esp_netif_receive(s_netif, buf_copy, len, NULL);
  82. }
  83. return ESP_OK;
  84. }
  85. /**
  86. * In this scenario of configuring WiFi, we setup USB-Ethernet to create a virtual network and run DHCP server,
  87. * so it could assign an IP address to the PC
  88. *
  89. * ESP32 PC
  90. * | lwip MAC=...01 | eth NIC MAC=...02
  91. * | <DHCP server> usb | <-> [ USB-NCM device acting as eth-NIC ]
  92. * | <HTTP server> |
  93. * | (wifi-provisioning) |
  94. *
  95. * From the PC's NIC perspective the board acts as a separate network with it's own IP and MAC address,
  96. * but the virtual ethernet NIC has also it's own IP and MAC address (configured via tinyusb_net_init()).
  97. * That's why we need to create the virtual network with *different* MAC address.
  98. * Here, we use two different OUI range MAC addresses.
  99. */
  100. esp_err_t wired_netif_init(void)
  101. {
  102. const tinyusb_config_t tusb_cfg = {
  103. .external_phy = false,
  104. };
  105. ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
  106. const tinyusb_net_config_t net_config = {
  107. // locally administrated address for the ncm device as it's going to be used internally
  108. // for configuration only
  109. .mac_addr = {0x02, 0x02, 0x11, 0x22, 0x33, 0x01},
  110. .on_recv_callback = netif_recv_callback,
  111. };
  112. esp_err_t ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
  113. if (ret != ESP_OK) {
  114. ESP_LOGE(TAG, "Cannot initialize USB Net device");
  115. return ret;
  116. }
  117. // with OUI range MAC to create a virtual netif running http server
  118. // this needs to be different to usb_interface_mac (==client)
  119. uint8_t lwip_addr[6] = {0x02, 0x02, 0x11, 0x22, 0x33, 0x02};
  120. // Definition of
  121. // 1) Derive the base config (very similar to IDF's default WiFi AP with DHCP server)
  122. esp_netif_inherent_config_t base_cfg = {
  123. .flags = ESP_NETIF_DHCP_SERVER | ESP_NETIF_FLAG_AUTOUP, // Run DHCP server; set the netif "ip" immediately
  124. .ip_info = &_g_esp_netif_soft_ap_ip, // Use the same IP ranges as IDF's soft AP
  125. .if_key = "wired", // Set mame, key, priority
  126. .if_desc = "usb ncm config device",
  127. .route_prio = 10
  128. };
  129. // 2) Use static config for driver's config pointing only to static transmit and free functions
  130. esp_netif_driver_ifconfig_t driver_cfg = {
  131. .handle = (void *)1, // not using an instance, USB-NCM is a static singleton (must be != NULL)
  132. .transmit = netif_transmit, // point to static Tx function
  133. .driver_free_rx_buffer = l2_free // point to Free Rx buffer function
  134. };
  135. // 3) USB-NCM is an Ethernet netif from lwip perspective, we already have IO definitions for that:
  136. struct esp_netif_netstack_config lwip_netif_config = {
  137. .lwip = {
  138. .init_fn = ethernetif_init,
  139. .input_fn = ethernetif_input
  140. }
  141. };
  142. // Config the esp-netif with:
  143. // 1) inherent config (behavioural settings of an interface)
  144. // 2) driver's config (connection to IO functions -- usb)
  145. // 3) stack config (using lwip IO functions -- derive from eth)
  146. esp_netif_config_t cfg = {
  147. .base = &base_cfg,
  148. .driver = &driver_cfg,
  149. .stack = &lwip_netif_config
  150. };
  151. s_netif = esp_netif_new(&cfg);
  152. if (s_netif == NULL) {
  153. return ESP_FAIL;
  154. }
  155. esp_netif_set_mac(s_netif, lwip_addr);
  156. // set the minimum lease time
  157. uint32_t lease_opt = 1;
  158. esp_netif_dhcps_option(s_netif, ESP_NETIF_OP_SET, IP_ADDRESS_LEASE_TIME, &lease_opt, sizeof(lease_opt));
  159. // start the interface manually (as the driver has been started already)
  160. esp_netif_action_start(s_netif, 0, 0, 0);
  161. return ESP_OK;
  162. }