diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6df54db --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.DS_Store* + diff --git a/pico_keys_sdk_import.cmake b/pico_keys_sdk_import.cmake index 542cf44..1fb7b0f 100644 --- a/pico_keys_sdk_import.cmake +++ b/pico_keys_sdk_import.cmake @@ -103,6 +103,10 @@ if(USB_ITF_CCID) message(STATUS "USB WebCCID Interface:\t enabled") endif() endif() +if(USB_ITF_LWIP) + add_compile_definitions(USB_ITF_LWIP=1) + message(STATUS "USB LWIP Interface:\t\t enabled") +endif() add_compile_definitions(DEBUG_APDU=${DEBUG_APDU}) if(NOT ESP_PLATFORM) add_compile_definitions(MBEDTLS_CONFIG_FILE="${CMAKE_CURRENT_LIST_DIR}/config/mbedtls_config.h") @@ -444,6 +448,12 @@ if(PICO_PLATFORM) tinyusb_board hardware_pio ) + if(USB_ITF_LWIP) + list(APPEND LIBRARIES + pico_lwip + pico_lwip_nosys + ) + endif() endif() if(ENABLE_PQC) @@ -543,6 +553,7 @@ if(USB_ITF_CCID) ) endif() + if(NOT MSVC) add_compile_options("-fmacro-prefix-map=${CMAKE_CURRENT_LIST_DIR}/=") endif() @@ -600,6 +611,22 @@ if(PICO_PLATFORM) pico_sdk_init() endif() +if(USB_ITF_LWIP) + list(APPEND PICO_KEYS_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/usb/lwip/lwip.c + ${CMAKE_CURRENT_LIST_DIR}/src/usb/lwip/rest_server.c + ${PICO_TINYUSB_PATH}/lib/networking/dhserver.c + ${PICO_TINYUSB_PATH}/lib/networking/dnserver.c + ) + list(APPEND INCLUDES + ${CMAKE_CURRENT_LIST_DIR}/src/usb/lwip + ${PICO_TINYUSB_PATH}/lib/networking + ${PICO_LWIP_PATH}/src/include/lwip/apps + ) + message(STATUS "TINYUSB_PATH:\t\t ${PICO_TINYUSB_PATH}") + message(STATUS "LWIP_PATH:\t\t ${PICO_LWIP_PATH}") +endif() + if(PICO_RP2350) pico_set_uf2_family(${CMAKE_PROJECT_NAME} "rp2350-arm-s") pico_embed_pt_in_binary(${CMAKE_PROJECT_NAME} "${CMAKE_CURRENT_LIST_DIR}/config/rp2350/pt.json") diff --git a/src/fs/phy.h b/src/fs/phy.h index b1db288..872917a 100644 --- a/src/fs/phy.h +++ b/src/fs/phy.h @@ -51,6 +51,7 @@ #define PHY_USB_ITF_WCID 0x2 #define PHY_USB_ITF_HID 0x4 #define PHY_USB_ITF_KB 0x8 +#define PHY_USB_ITF_LWIP 0x10 #define PHY_LED_DRIVER_PICO 0x1 #define PHY_LED_DRIVER_PIMORONI 0x2 diff --git a/src/main.c b/src/main.c index 6a308d4..ffe0718 100644 --- a/src/main.c +++ b/src/main.c @@ -284,6 +284,9 @@ static void execute_tasks(void) { #if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) tud_task(); // tinyusb device task +#ifdef USB_ITF_LWIP + service_traffic(); +#endif #endif usb_task(); led_blinking_task(); diff --git a/src/usb/lwip/lwip.c b/src/usb/lwip/lwip.c new file mode 100644 index 0000000..1338541 --- /dev/null +++ b/src/usb/lwip/lwip.c @@ -0,0 +1,232 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Peter Lawrence + * + * influenced by lrndis https://github.com/fetisov/lrndis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +/* +this appears as either a RNDIS or CDC-ECM USB virtual network adapter; the OS picks its preference + +RNDIS should be valid on Linux and Windows hosts, and CDC-ECM should be valid on Linux and macOS hosts + +The MCU appears to the host as IP address 192.168.7.1, and provides a DHCP server, DNS server, and web server. +*/ +/* +Some smartphones *may* work with this implementation as well, but likely have limited (broken) drivers, +and likely their manufacturer has not tested such functionality. Some code workarounds could be tried: + +The smartphone may only have an ECM driver, but refuse to automatically pick ECM (unlike the OSes above); +try modifying ./examples/devices/net_lwip_webserver/usb_descriptors.c so that CONFIG_ID_ECM is default. + +The smartphone may be artificially picky about which Ethernet MAC address to recognize; if this happens, +try changing the first byte of tud_network_mac_address[] below from 0x02 to 0x00 (clearing bit 1). +*/ + +#include "bsp/board_api.h" +#include "tusb.h" + +#include "dhserver.h" +#include "dnserver.h" +#include "lwip/ethip6.h" +#include "lwip/init.h" +#include "lwip/timeouts.h" +#include "rest_server.h" + +#ifdef INCLUDE_IPERF + #include "lwip/apps/lwiperf.h" +#endif + +#define INIT_IP4(a, b, c, d) \ + { PP_HTONL(LWIP_MAKEU32(a, b, c, d)) } + +/* lwip context */ +static struct netif netif_data; + +/* shared between tud_network_recv_cb() and service_traffic() */ +static struct pbuf *received_frame; + +/* this is used by this code, ./class/net/net_driver.c, and usb_descriptors.c */ +/* ideally speaking, this should be generated from the hardware's unique ID (if available) */ +/* it is suggested that the first byte is 0x02 to indicate a link-local address */ +uint8_t tud_network_mac_address[6] = {0x02, 0x02, 0x84, 0x6A, 0x96, 0x00}; + +/* network parameters of this MCU */ +static const ip4_addr_t ipaddr = INIT_IP4(192, 168, 7, 1); +static const ip4_addr_t netmask = INIT_IP4(255, 255, 255, 0); +static const ip4_addr_t gateway = INIT_IP4(0, 0, 0, 0); + +/* database IP addresses that can be offered to the host; this must be in RAM to store assigned MAC addresses */ +static dhcp_entry_t entries[] = { + /* mac ip address lease time */ + {{0}, INIT_IP4(192, 168, 7, 2), 24 * 60 * 60}, + {{0}, INIT_IP4(192, 168, 7, 3), 24 * 60 * 60}, + {{0}, INIT_IP4(192, 168, 7, 4), 24 * 60 * 60}, +}; + +static const dhcp_config_t dhcp_config = { + .router = INIT_IP4(0, 0, 0, 0), /* router address (if any) */ + .port = 67, /* listen port */ + .dns = INIT_IP4(192, 168, 7, 1), /* dns server (if any) */ + "usb", /* dns suffix */ + TU_ARRAY_SIZE(entries), /* num entry */ + entries /* entries */ +}; + +static err_t linkoutput_fn(struct netif *netif, struct pbuf *p) { + (void) netif; + + for (;;) { + /* if TinyUSB isn't ready, we must signal back to lwip that there is nothing we can do */ + if (!tud_ready()) + return ERR_USE; + + /* if the network driver can accept another packet, we make it happen */ + if (tud_network_can_xmit(p->tot_len)) { + tud_network_xmit(p, 0 /* unused for this example */); + return ERR_OK; + } + + /* transfer execution to TinyUSB in the hopes that it will finish transmitting the prior packet */ + tud_task(); + } +} + +static err_t ip4_output_fn(struct netif *netif, struct pbuf *p, const ip4_addr_t *addr) { + return etharp_output(netif, p, addr); +} + +#if LWIP_IPV6 +static err_t ip6_output_fn(struct netif *netif, struct pbuf *p, const ip6_addr_t *addr) { + return ethip6_output(netif, p, addr); +} +#endif + +static err_t netif_init_cb(struct netif *netif) { + LWIP_ASSERT("netif != NULL", (netif != NULL)); + netif->mtu = CFG_TUD_NET_MTU; + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP; + netif->state = NULL; + netif->name[0] = 'E'; + netif->name[1] = 'X'; + netif->linkoutput = linkoutput_fn; + netif->output = ip4_output_fn; +#if LWIP_IPV6 + netif->output_ip6 = ip6_output_fn; +#endif + return ERR_OK; +} + +static void init_lwip(void) { + struct netif *netif = &netif_data; + + lwip_init(); + + /* the lwip virtual MAC address must be different from the host's; to ensure this, we toggle the LSbit */ + netif->hwaddr_len = sizeof(tud_network_mac_address); + memcpy(netif->hwaddr, tud_network_mac_address, sizeof(tud_network_mac_address)); + netif->hwaddr[5] ^= 0x01; + + netif = netif_add(netif, &ipaddr, &netmask, &gateway, NULL, netif_init_cb, ip_input); +#if LWIP_IPV6 + netif_create_ip6_linklocal_address(netif, 1); +#endif + netif_set_default(netif); +} + +/* handle any DNS requests from dns-server */ +bool dns_query_proc(const char *name, ip4_addr_t *addr) { + if (0 == strcmp(name, "tiny.usb")) { + *addr = ipaddr; + return true; + } + return false; +} + +bool tud_network_recv_cb(const uint8_t *src, uint16_t size) { + /* this shouldn't happen, but if we get another packet before + parsing the previous, we must signal our inability to accept it */ + if (received_frame) return false; + + if (size) { + struct pbuf *p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL); + + if (p) { + /* pbuf_alloc() has already initialized struct; all we need to do is copy the data */ + memcpy(p->payload, src, size); + + /* store away the pointer for service_traffic() to later handle */ + received_frame = p; + } + } + + return true; +} + +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) { + struct pbuf *p = (struct pbuf *) ref; + + (void) arg; /* unused for this example */ + + return pbuf_copy_partial(p, dst, p->tot_len, 0); +} + +void service_traffic(void) { + /* handle any packet received by tud_network_recv_cb() */ + if (received_frame) { + // Surrender ownership of our pbuf unless there was an error + // Only call pbuf_free if not Ok else it will panic with "pbuf_free: p->ref > 0" + // or steal it from whatever took ownership of it with undefined consequences. + // See: https://savannah.nongnu.org/patch/index.php?10121 + if (ethernet_input(received_frame, &netif_data)!=ERR_OK) { + pbuf_free(received_frame); + } + received_frame = NULL; + tud_network_recv_renew(); + } + + sys_check_timeouts(); +} + +void tud_network_init_cb(void) { + /* if the network is re-initializing and we have a leftover packet, we must do a cleanup */ + if (received_frame) { + pbuf_free(received_frame); + received_frame = NULL; + } +} + +int lwip_itf_init(void) { + init_lwip(); + while (!netif_is_up(&netif_data)); + while (dhserv_init(&dhcp_config) != ERR_OK); + while (dnserv_init(IP_ADDR_ANY, 53, dns_query_proc) != ERR_OK); + while (rest_server_init() != ERR_OK); + +#ifdef INCLUDE_IPERF + // test with: iperf -c 192.168.7.1 -e -i 1 -M 5000 -l 8192 -r + lwiperf_start_tcp_server_default(NULL, NULL); +#endif + + return 0; +} diff --git a/src/usb/lwip/lwipopts.h b/src/usb/lwip/lwipopts.h new file mode 100644 index 0000000..8232ad9 --- /dev/null +++ b/src/usb/lwip/lwipopts.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Simon Goldschmidt + * + */ +#ifndef LWIPOPTS_H__ +#define LWIPOPTS_H__ + +/* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */ +#define NO_SYS 1 +#define MEM_ALIGNMENT 4 +#define LWIP_RAW 0 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 +#define LWIP_DHCP 0 +#define LWIP_ICMP 1 +#define LWIP_UDP 1 +#define LWIP_TCP 1 +#define LWIP_IPV4 1 +#define LWIP_IPV6 0 +#define ETH_PAD_SIZE 0 +#define LWIP_IP_ACCEPT_UDP_PORT(p) ((p) == PP_NTOHS(67)) + +#define TCP_MSS (1500 /*mtu*/ - 20 /*iphdr*/ - 20 /*tcphhr*/) +#define TCP_SND_BUF (4 * TCP_MSS) +#define TCP_WND (4 * TCP_MSS) + +#define ETHARP_SUPPORT_STATIC_ENTRIES 1 + +#define LWIP_HTTPD_CGI 0 +#define LWIP_HTTPD_SSI 0 +#define LWIP_HTTPD_SSI_INCLUDE_TAG 0 +#define LWIP_HTTPD_CUSTOM_FILES 0 + +#define LWIP_SINGLE_NETIF 1 +#define LWIP_NETIF_LINK_CALLBACK 1 + +#define PBUF_POOL_SIZE 4 + +#define HTTPD_USE_CUSTOM_FSDATA 0 + +#define LWIP_MULTICAST_PING 1 +#define LWIP_BROADCAST_PING 1 +#define LWIP_IPV6_MLD 0 +#define LWIP_IPV6_SEND_ROUTER_SOLICIT 0 + +#endif /* __LWIPOPTS_H__ */ diff --git a/src/usb/lwip/rest_server.c b/src/usb/lwip/rest_server.c new file mode 100644 index 0000000..14849c8 --- /dev/null +++ b/src/usb/lwip/rest_server.c @@ -0,0 +1,364 @@ +#include "rest_server.h" + +#include "lwip/tcp.h" +#include "lwip/def.h" + +#include +#include +#include +#include +#include +#include +#include + +#define REST_PORT 80 +#define REST_MAX_CONNS 4 +#define REST_MAX_REQUEST_SIZE 8192 +#define REST_MAX_METHOD_SIZE 8 +#define REST_MAX_PATH_SIZE 192 + +typedef struct { + bool in_use; + struct tcp_pcb *pcb; + char request[REST_MAX_REQUEST_SIZE + 1]; + size_t request_len; +} rest_conn_t; + +static struct tcp_pcb *listener_pcb = NULL; +static rest_conn_t conns[REST_MAX_CONNS]; + +static rest_conn_t *alloc_conn(struct tcp_pcb *pcb) { + size_t i; + + for (i = 0; i < REST_MAX_CONNS; i++) { + if (!conns[i].in_use) { + memset(&conns[i], 0, sizeof(conns[i])); + conns[i].in_use = true; + conns[i].pcb = pcb; + return &conns[i]; + } + } + + return NULL; +} + +static void clear_conn(rest_conn_t *conn) { + if (conn == NULL) { + return; + } + + memset(conn, 0, sizeof(*conn)); +} + +static void close_conn(rest_conn_t *conn) { + err_t err; + + if (conn == NULL || conn->pcb == NULL) { + clear_conn(conn); + return; + } + + tcp_arg(conn->pcb, NULL); + tcp_recv(conn->pcb, NULL); + tcp_sent(conn->pcb, NULL); + tcp_poll(conn->pcb, NULL, 0); + tcp_err(conn->pcb, NULL); + + err = tcp_close(conn->pcb); + if (err != ERR_OK) { + tcp_abort(conn->pcb); + } + + clear_conn(conn); +} + +static void send_response_and_close(rest_conn_t *conn, int status_code, const char *status_text, + const char *content_type, const char *body, size_t body_len) { + char headers[256]; + int header_len; + err_t err; + + if (conn == NULL || conn->pcb == NULL) { + return; + } + + header_len = snprintf(headers, sizeof(headers), + "HTTP/1.0 %d %s\r\n" + "Content-Type: %s\r\n" + "Content-Length: %lu\r\n" + "Connection: close\r\n" + "\r\n", + status_code, status_text, content_type, (unsigned long)body_len); + if (header_len <= 0 || (size_t)header_len >= sizeof(headers)) { + close_conn(conn); + return; + } + + err = tcp_write(conn->pcb, headers, (uint16_t)header_len, TCP_WRITE_FLAG_COPY); + if (err == ERR_OK && body_len > 0) { + err = tcp_write(conn->pcb, body, (uint16_t)body_len, TCP_WRITE_FLAG_COPY); + } + + if (err == ERR_OK) { + (void)tcp_output(conn->pcb); + } + + close_conn(conn); +} + +static void send_json_and_close(rest_conn_t *conn, int status_code, const char *status_text, const char *json_body) { + send_response_and_close(conn, status_code, status_text, "application/json", json_body, strlen(json_body)); +} + +static int parse_request(rest_conn_t *conn, + char *method, + size_t method_size, + char *path, + size_t path_size, + const char **body, + size_t *body_len, + bool *json_content_type) { + char *header_end; + char *line_end; + char *cursor; + size_t headers_size; + unsigned long content_length = 0; + + *body = NULL; + *body_len = 0; + *json_content_type = false; + + conn->request[conn->request_len] = '\0'; + header_end = strstr(conn->request, "\r\n\r\n"); + if (header_end == NULL) { + return 0; + } + + headers_size = (size_t)(header_end - conn->request) + 4; + line_end = strstr(conn->request, "\r\n"); + if (line_end == NULL || line_end > header_end) { + return -1; + } + + *line_end = '\0'; + if (sscanf(conn->request, "%7s %191s", method, path) != 2) { + return -1; + } + + cursor = line_end + 2; + while (cursor < header_end) { + char *next = strstr(cursor, "\r\n"); + char *colon; + char *name; + char *value; + char *endptr; + + if (next == NULL || next > header_end) { + return -1; + } + if (next == cursor) { + break; + } + + *next = '\0'; + colon = strchr(cursor, ':'); + if (colon != NULL) { + *colon = '\0'; + name = cursor; + value = colon + 1; + + while (*name != '\0' && isspace((unsigned char)*name)) { + name++; + } + while (*value != '\0' && isspace((unsigned char)*value)) { + value++; + } + while (*name != '\0' && isspace((unsigned char)name[strlen(name) - 1])) { + name[strlen(name) - 1] = '\0'; + } + while (*value != '\0' && isspace((unsigned char)value[strlen(value) - 1])) { + value[strlen(value) - 1] = '\0'; + } + + if (strcasecmp(name, "Content-Length") == 0) { + content_length = strtoul(value, &endptr, 10); + if ((endptr == value) || (*endptr != '\0') || (content_length > REST_MAX_REQUEST_SIZE)) { + return -1; + } + } else if (strcasecmp(name, "Content-Type") == 0) { + if (strncasecmp(value, "application/json", 16) == 0) { + *json_content_type = true; + } + } + } + + cursor = next + 2; + } + + if (conn->request_len < headers_size + content_length) { + return 0; + } + + *body = conn->request + headers_size; + *body_len = (size_t)content_length; + return 1; +} + +static void handle_request(rest_conn_t *conn) { + char method[REST_MAX_METHOD_SIZE] = {0}; + char path[REST_MAX_PATH_SIZE] = {0}; + const char *body; + size_t body_len; + bool is_json; + int parsed; + + parsed = parse_request(conn, method, sizeof(method), path, sizeof(path), &body, &body_len, &is_json); + if (parsed <= 0) { + if (parsed < 0) { + send_json_and_close(conn, 400, "Bad Request", "{\"error\":\"bad_request\"}"); + } + return; + } + + if (strcmp(method, "GET") == 0) { + if (strcmp(path, "/health") == 0) { + send_json_and_close(conn, 200, "OK", "{\"ok\":true}"); + return; + } + send_json_and_close(conn, 404, "Not Found", "{\"error\":\"not_found\"}"); + return; + } + + if (strcmp(method, "POST") == 0 || strcmp(method, "PUT") == 0) { + if (strcmp(path, "/echo") != 0) { + send_json_and_close(conn, 404, "Not Found", "{\"error\":\"not_found\"}"); + return; + } + + if (!is_json) { + send_json_and_close(conn, 415, "Unsupported Media Type", "{\"error\":\"content_type_must_be_application_json\"}"); + return; + } + + send_response_and_close(conn, 200, "OK", "application/json", body, body_len); + return; + } + + if (strcmp(method, "DELETE") == 0) { + if (strcmp(path, "/echo") == 0) { + send_json_and_close(conn, 200, "OK", "{\"deleted\":true}"); + return; + } + send_json_and_close(conn, 404, "Not Found", "{\"error\":\"not_found\"}"); + return; + } + + send_json_and_close(conn, 405, "Method Not Allowed", "{\"error\":\"method_not_allowed\"}"); +} + +static err_t rest_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { + rest_conn_t *conn = (rest_conn_t *)arg; + + LWIP_UNUSED_ARG(pcb); + + if (err != ERR_OK) { + if (p != NULL) { + pbuf_free(p); + } + close_conn(conn); + return err; + } + + if (p == NULL) { + close_conn(conn); + return ERR_OK; + } + + if (conn == NULL) { + tcp_recved(pcb, p->tot_len); + pbuf_free(p); + return ERR_OK; + } + + if (conn->request_len + p->tot_len > REST_MAX_REQUEST_SIZE) { + tcp_recved(pcb, p->tot_len); + pbuf_free(p); + send_json_and_close(conn, 413, "Payload Too Large", "{\"error\":\"payload_too_large\"}"); + return ERR_OK; + } + + pbuf_copy_partial(p, conn->request + conn->request_len, p->tot_len, 0); + conn->request_len += p->tot_len; + tcp_recved(pcb, p->tot_len); + pbuf_free(p); + + handle_request(conn); + return ERR_OK; +} + +static err_t rest_poll(void *arg, struct tcp_pcb *pcb) { + rest_conn_t *conn = (rest_conn_t *)arg; + LWIP_UNUSED_ARG(pcb); + close_conn(conn); + return ERR_OK; +} + +static void rest_err(void *arg, err_t err) { + rest_conn_t *conn = (rest_conn_t *)arg; + LWIP_UNUSED_ARG(err); + clear_conn(conn); +} + +static err_t rest_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { + rest_conn_t *conn; + + LWIP_UNUSED_ARG(arg); + + if (err != ERR_OK || newpcb == NULL) { + if (newpcb != NULL) { + tcp_abort(newpcb); + } + return ERR_ABRT; + } + + conn = alloc_conn(newpcb); + if (conn == NULL) { + tcp_abort(newpcb); + return ERR_ABRT; + } + + tcp_arg(newpcb, conn); + tcp_recv(newpcb, rest_recv); + tcp_poll(newpcb, rest_poll, 8); + tcp_err(newpcb, rest_err); + + return ERR_OK; +} + +err_t rest_server_init(void) { + err_t err; + + if (listener_pcb != NULL) { + return ERR_OK; + } + + listener_pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); + if (listener_pcb == NULL) { + return ERR_MEM; + } + + err = tcp_bind(listener_pcb, IP_ANY_TYPE, REST_PORT); + if (err != ERR_OK) { + tcp_abort(listener_pcb); + listener_pcb = NULL; + return err; + } + + listener_pcb = tcp_listen_with_backlog(listener_pcb, REST_MAX_CONNS); + if (listener_pcb == NULL) { + return ERR_MEM; + } + + tcp_accept(listener_pcb, rest_accept); + return ERR_OK; +} diff --git a/src/usb/lwip/rest_server.h b/src/usb/lwip/rest_server.h new file mode 100644 index 0000000..39b9888 --- /dev/null +++ b/src/usb/lwip/rest_server.h @@ -0,0 +1,8 @@ +#ifndef PICO_KEYS_REST_SERVER_H +#define PICO_KEYS_REST_SERVER_H + +#include "lwip/err.h" + +err_t rest_server_init(void); + +#endif diff --git a/src/usb/lwip/tusb_config.h b/src/usb/lwip/tusb_config.h new file mode 100644 index 0000000..2499f41 --- /dev/null +++ b/src/usb/lwip/tusb_config.h @@ -0,0 +1,145 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lwipopts.h" + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT + #define BOARD_TUD_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUD_MAX_SPEED + #define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// Common Configuration +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS + #define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG + #define CFG_TUSB_DEBUG 0 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION + #define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN + #define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) +#endif + +// Use different configurations to test all net devices (also due to resource limitations) +#ifndef USE_ECM +#if TU_CHECK_MCU(OPT_MCU_LPC15XX, OPT_MCU_LPC40XX, OPT_MCU_LPC51UXX, OPT_MCU_LPC54) + #define USE_ECM 1 +#elif TU_CHECK_MCU(OPT_MCU_SAMD21, OPT_MCU_SAML2X) + #define USE_ECM 1 +#elif TU_CHECK_MCU(OPT_MCU_STM32F0, OPT_MCU_STM32F1) + #define USE_ECM 1 +#elif TU_CHECK_MCU(OPT_MCU_MAX32690, OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX78002) + #define USE_ECM 1 +#else + #define USE_ECM 0 + #define INCLUDE_IPERF +#endif +#endif + +//-------------------------------------------------------------------- +// NCM CLASS CONFIGURATION, SEE "ncm.h" FOR PERFORMANCE TUNING +//-------------------------------------------------------------------- + +// Must be >> MTU +// Can be set to 2048 without impact +#define CFG_TUD_NCM_IN_NTB_MAX_SIZE (2 * TCP_MSS + 100) + +// Must be >> MTU +// Can be set to smaller values if wNtbOutMaxDatagrams==1 +#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE (2 * TCP_MSS + 100) + +// Number of NCM transfer blocks for reception side +#ifndef CFG_TUD_NCM_OUT_NTB_N + #define CFG_TUD_NCM_OUT_NTB_N 1 +#endif + +// Number of NCM transfer blocks for transmission side +#ifndef CFG_TUD_NCM_IN_NTB_N + #define CFG_TUD_NCM_IN_NTB_N 1 +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE + #define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// + +// Network class has 2 drivers: ECM/RNDIS and NCM. +// Only one of the drivers can be enabled +#define CFG_TUD_ECM_RNDIS USE_ECM +#define CFG_TUD_NCM (1 - CFG_TUD_ECM_RNDIS) + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_CONFIG_H_ */ + diff --git a/src/usb/lwip/usb_descriptors.c b/src/usb/lwip/usb_descriptors.c new file mode 100644 index 0000000..c976cb6 --- /dev/null +++ b/src/usb/lwip/usb_descriptors.c @@ -0,0 +1,369 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "bsp/board_api.h" +#include "class/net/net_device.h" +#include "tusb.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] NET | VENDOR | MIDI | HID | MSC | CDC [LSB] + */ +#define PID_MAP(itf, n) ((CFG_TUD_##itf) ? (1 << (n)) : 0) +#define USB_PID \ + (0x4000 | PID_MAP(CDC, 0) | PID_MAP(MSC, 1) | PID_MAP(HID, 2) | PID_MAP(MIDI, 3) | PID_MAP(VENDOR, 4) | \ + PID_MAP(ECM_RNDIS, 5) | PID_MAP(NCM, 5)) + +// String Descriptor Index +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + STRID_INTERFACE, + STRID_MAC, + STRID_COUNT +}; + +enum { + ITF_NUM_CDC = 0, + ITF_NUM_CDC_DATA, + ITF_NUM_TOTAL +}; + +enum { +#if CFG_TUD_ECM_RNDIS + CONFIG_ID_RNDIS = 0, + CONFIG_ID_ECM = 1, +#else + CONFIG_ID_NCM = 0, +#endif + CONFIG_ID_COUNT +}; + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +static const tusb_desc_device_t desc_device = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, +#if CFG_TUD_NCM + .bcdUSB = 0x0201, +#else + .bcdUSB = 0x0200, +#endif + // Use Interface Association Descriptor (IAD) device class + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0101, + + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + + .bNumConfigurations = CONFIG_ID_COUNT // multiple configurations +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +const uint8_t *tud_descriptor_device_cb(void) { + return (const uint8_t *)&desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +#define MAIN_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_RNDIS_DESC_LEN) +#define ALT_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_ECM_DESC_LEN) +#define NCM_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_NCM_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX +// LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number +// 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... +#define EPNUM_NET_NOTIF 0x81 +#define EPNUM_NET_OUT 0x02 +#define EPNUM_NET_IN 0x82 + +#elif CFG_TUSB_MCU == OPT_MCU_CXD56 +// CXD56 USB driver has fixed endpoint type (bulk/interrupt/iso) and direction (IN/OUT) by its number +// 0 control (IN/OUT), 1 Bulk (IN), 2 Bulk (OUT), 3 In (IN), 4 Bulk (IN), 5 Bulk (OUT), 6 In (IN) +#define EPNUM_NET_NOTIF 0x83 +#define EPNUM_NET_OUT 0x02 +#define EPNUM_NET_IN 0x81 + +#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY) +// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h +// e.g EP1 OUT & EP1 IN cannot exist together +#define EPNUM_NET_NOTIF 0x81 +#define EPNUM_NET_OUT 0x02 +#define EPNUM_NET_IN 0x83 + +#else +#define EPNUM_NET_NOTIF 0x81 +#define EPNUM_NET_OUT 0x02 +#define EPNUM_NET_IN 0x82 +#endif + +#if CFG_TUD_ECM_RNDIS + +static uint8_t const rndis_configuration[] = { + // Config number (index+1), interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(CONFIG_ID_RNDIS + 1, ITF_NUM_TOTAL, 0, MAIN_CONFIG_TOTAL_LEN, 0, 100), + + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_RNDIS_DESCRIPTOR( + ITF_NUM_CDC, STRID_INTERFACE, EPNUM_NET_NOTIF, 8, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE), +}; + +static const uint8_t ecm_configuration[] = { + // Config number (index+1), interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(CONFIG_ID_ECM + 1, ITF_NUM_TOTAL, 0, ALT_CONFIG_TOTAL_LEN, 0, 100), + + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_ECM_DESCRIPTOR( + ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN, + CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU), +}; + +#else + +static uint8_t const ncm_configuration[] = { + // Config number (index+1), interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(CONFIG_ID_NCM + 1, ITF_NUM_TOTAL, 0, NCM_CONFIG_TOTAL_LEN, 0, 100), + + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_NCM_DESCRIPTOR( + ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN, + CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU), +}; + +#endif + +// Configuration array: RNDIS and CDC-ECM +// - Windows only works with RNDIS +// - MacOS only works with CDC-ECM +// - Linux will work on both +static const uint8_t *const configuration_arr[CONFIG_ID_COUNT] = { +#if CFG_TUD_ECM_RNDIS + [CONFIG_ID_RNDIS] = rndis_configuration, + [CONFIG_ID_ECM] = ecm_configuration +#else + [CONFIG_ID_NCM] = ncm_configuration +#endif +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { + return (index < CONFIG_ID_COUNT) ? configuration_arr[index] : NULL; +} + +#if CFG_TUD_NCM +//--------------------------------------------------------------------+ +// BOS Descriptor +//--------------------------------------------------------------------+ + +/* Used to automatically load the NCM driver on Windows 10, otherwise manual driver install is needed. + Associate NCM interface with WINNCM driver. */ + +/* Microsoft OS 2.0 registry property descriptor +Per MS requirements https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799(v=vs.85).aspx +device should create DeviceInterfaceGUIDs. It can be done by driver and +in case of real PnP solution device should expose MS "Microsoft OS 2.0 +registry property descriptor". Such descriptor can insert any record +into Windows registry per device/configuration/interface. In our case it +will insert "DeviceInterfaceGUIDs" multistring property. + +GUID is freshly generated and should be OK to use. + +https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb/ +(Section Microsoft OS compatibility descriptors) +*/ + +#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) + +#define MS_OS_20_DESC_LEN 0xB2 + +// BOS Descriptor is required for webUSB +const uint8_t desc_bos[] = { + // total length, number of device caps + TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 1), + + // Microsoft OS 2.0 descriptor + TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, 1)}; + +const uint8_t *tud_descriptor_bos_cb(void) { + return desc_bos; +} + +const uint8_t desc_ms_os_20[] = { + // Set header: length, type, windows version, total length + U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), U32_TO_U8S_LE(0x06030000), + U16_TO_U8S_LE(MS_OS_20_DESC_LEN), + + // Configuration subset header: length, type, configuration index, reserved, configuration total length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), 0, 0, + U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A), + + // Function Subset header: length, type, first interface, reserved, subset length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), ITF_NUM_CDC, 0, + U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08), + + // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID + U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'N', 'C', 'M', 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible + + // MS OS 2.0 Registry property descriptor: length, type + U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08 - 0x08 - 0x14), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), + U16_TO_U8S_LE(0x0007), + U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16 + 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00, 'r', + 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00, + U16_TO_U8S_LE(0x0050), // wPropertyDataLength + //bPropertyData: {12345678-0D08-43FD-8B3E-127CA8AFFF9D} + '{', 0x00, '1', 0x00, '2', 0x00, '3', 0x00, '4', 0x00, '5', 0x00, '6', 0x00, '7', 0x00, '8', 0x00, '-', 0x00, '0', + 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00, '8', 0x00, + 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, 'C', 0x00, 'A', 0x00, '8', 0x00, 'A', + 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, '9', 0x00, 'D', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00}; + +TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size"); + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request) { + // nothing to with DATA & ACK stage + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_VENDOR: + switch (request->bRequest) { + case 1: + if (request->wIndex == 7) { + // Get Microsoft OS 2.0 compatible descriptor + uint16_t total_len; + memcpy(&total_len, desc_ms_os_20 + 8, 2); + + return tud_control_xfer(rhport, request, (void *)(uintptr_t)desc_ms_os_20, total_len); + } else { + return false; + } + + default: + break; // nothing to do + } + break; + + default: + break; // nothing to do + } + + // stall unknown request + return false; +} + +#endif +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +static const char *string_desc_arr[STRID_COUNT] = { + [STRID_LANGID] = (const char[]){0x09, 0x04}, // supported language is English (0x0409) + [STRID_MANUFACTURER] = "TinyUSB", // Manufacturer + [STRID_PRODUCT] = "TinyUSB Device", // Product + [STRID_SERIAL] = NULL, // Serials will use unique ID if possible + [STRID_INTERFACE] = "TinyUSB Network Interface", // Interface Description + [STRID_MAC] = NULL // STRID_MAC index is handled separately +}; + +static uint16_t _desc_str[32 + 1]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + (void)langid; + unsigned int chr_count = 0; + + switch (index) { + case STRID_LANGID: + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + break; + + case STRID_SERIAL: + chr_count = board_usb_get_serial(_desc_str + 1, 32); + break; + + case STRID_MAC: + // Convert MAC address into UTF-16 + for (unsigned i = 0; i < sizeof(tud_network_mac_address); i++) { + _desc_str[1 + chr_count++] = "0123456789ABCDEF"[(tud_network_mac_address[i] >> 4) & 0xf]; + _desc_str[1 + chr_count++] = "0123456789ABCDEF"[(tud_network_mac_address[i] >> 0) & 0xf]; + } + break; + + default: { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if (index >= sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) { + return NULL; + } + + const char *str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + + const size_t max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type + if (chr_count > max_count) { + chr_count = max_count; + } + + // Convert ASCII string into UTF-16 + for (size_t i = 0; i < chr_count; i++) { + _desc_str[1 + i] = str[i]; + } + break; + } + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * chr_count + 2)); + + return _desc_str; +} diff --git a/src/usb/tusb_config.h b/src/usb/tusb_config.h index c82c21a..8c0a98f 100644 --- a/src/usb/tusb_config.h +++ b/src/usb/tusb_config.h @@ -122,6 +122,9 @@ extern "C" { #else #define CFG_TUD_VENDOR 0 #endif +#ifdef USB_ITF_LWIP +#define CFG_TUD_NCM 1 +#endif // HID buffer size Should be sufficient to hold ID (if any) + Data #define CFG_TUD_HID_EP_BUFSIZE 64 diff --git a/src/usb/usb.c b/src/usb/usb.c index ed149a3..df50c30 100644 --- a/src/usb/usb.c +++ b/src/usb/usb.c @@ -62,6 +62,12 @@ pthread_t hcore0, hcore1; uint8_t ITF_SC_TOTAL = 0; extern void ccid_init(void); #endif + +#ifdef USB_ITF_LWIP + uint8_t ITF_LWIP_NET = ITF_INVALID, ITF_LWIP = ITF_INVALID; + uint8_t ITF_LWIP_TOTAL = 0; + extern void lwip_init(void); +#endif uint8_t ITF_TOTAL = 0; void usb_set_timeout_counter(uint8_t itf, uint32_t v) { @@ -92,7 +98,7 @@ void usb_init(void) queue_init(&card_to_usb_q, sizeof(uint32_t), 64); queue_init(&usb_to_card_q, sizeof(uint32_t), 64); - uint8_t enabled_usb_itf = PHY_USB_ITF_CCID | PHY_USB_ITF_WCID | PHY_USB_ITF_HID | PHY_USB_ITF_KB; + uint8_t enabled_usb_itf = PHY_USB_ITF_CCID | PHY_USB_ITF_WCID | PHY_USB_ITF_HID | PHY_USB_ITF_KB | PHY_USB_ITF_LWIP; #ifndef ENABLE_EMULATION if (phy_data.enabled_usb_itf_present) { enabled_usb_itf = phy_data.enabled_usb_itf; @@ -104,6 +110,9 @@ void usb_init(void) #endif #ifdef USB_ITF_CCID ITF_SC_TOTAL = 0; +#endif +#ifdef USB_ITF_LWIP + ITF_LWIP_TOTAL = 0; #endif ITF_TOTAL = 0; #ifdef USB_ITF_HID @@ -111,14 +120,14 @@ void usb_init(void) ITF_HID_CTAP = ITF_HID_TOTAL++; ITF_HID = ITF_TOTAL++; #ifndef ENABLE_EMULATION - string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[5]; + string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[6]; #endif } if (enabled_usb_itf & PHY_USB_ITF_KB) { ITF_HID_KB = ITF_HID_TOTAL++; ITF_KEYBOARD = ITF_TOTAL++; #ifndef ENABLE_EMULATION - string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[6]; + string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[7]; #endif } #endif @@ -127,14 +136,23 @@ void usb_init(void) ITF_SC_CCID = ITF_SC_TOTAL++; ITF_CCID = ITF_TOTAL++; #ifndef ENABLE_EMULATION - string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[7]; + string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[8]; #endif } if (enabled_usb_itf & PHY_USB_ITF_WCID) { ITF_SC_WCID = ITF_SC_TOTAL++; ITF_WCID = ITF_TOTAL++; #ifndef ENABLE_EMULATION - string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[8]; + string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[9]; +#endif + } +#endif +#ifdef USB_ITF_LWIP + if (enabled_usb_itf & PHY_USB_ITF_LWIP) { + ITF_LWIP_NET = ITF_LWIP_TOTAL++; + ITF_LWIP = ITF_TOTAL++; +#ifndef ENABLE_EMULATION + string_desc_itf[ITF_TOTAL - 1] = string_desc_arr[10]; #endif } #endif @@ -152,6 +170,11 @@ void usb_init(void) ccid_init(); } #endif +#ifdef USB_ITF_LWIP + if (ITF_LWIP_TOTAL > 0) { + lwip_itf_init(); + } +#endif #ifdef ESP_PLATFORM usb_desc_setup(); #endif diff --git a/src/usb/usb.h b/src/usb/usb.h index 67a19cb..42b0b47 100644 --- a/src/usb/usb.h +++ b/src/usb/usb.h @@ -61,6 +61,12 @@ enum { ITF_INVALID = 0xFF }; extern uint8_t ITF_CCID, ITF_WCID; extern uint8_t ITF_SC_TOTAL; #endif + +#ifdef USB_ITF_LWIP + extern uint8_t ITF_LWIP, ITF_LWIP_NET; + extern uint8_t ITF_LWIP_TOTAL; +#endif + extern uint8_t ITF_TOTAL; enum { @@ -126,4 +132,9 @@ typedef enum { WRITE_SUCCESS, } write_status_t; +#ifdef USB_ITF_LWIP +extern int lwip_itf_init(void); +extern void service_traffic(void); +#endif + #endif diff --git a/src/usb/usb_descriptors.c b/src/usb/usb_descriptors.c index 481655e..a33b6c9 100644 --- a/src/usb/usb_descriptors.c +++ b/src/usb/usb_descriptors.c @@ -94,6 +94,9 @@ enum { #ifdef USB_ITF_CCID + TUSB_SMARTCARD_CCID_DESC_LEN + TUSB_SMARTCARD_WCID_DESC_LEN #endif +#ifdef USB_ITF_LWIP + + TUD_CDC_NCM_DESC_LEN +#endif ) }; @@ -106,7 +109,8 @@ uint8_t const desc_hid_report_kb[] = { }; #endif -enum { +enum +{ EPNUM_DUMMY = 0, #ifdef USB_ITF_CCID EPNUM_CCID, @@ -118,6 +122,10 @@ enum { #ifdef USB_ITF_HID EPNUM_HID, EPNUM_HID_KB, +#endif +#ifdef USB_ITF_LWIP + EPNUM_LWIP_NOTIF, + EPNUM_LWIP, #endif EPNUM_TOTAL }; @@ -167,13 +175,13 @@ void usb_desc_setup(void) { #ifdef USB_ITF_HID if (ITF_HID != ITF_INVALID) { TUSB_DESC_TOTAL_LEN += TUD_HID_INOUT_DESC_LEN; - const uint8_t desc[] = { TUD_HID_INOUT_DESCRIPTOR(ITF_HID, ITF_HID + 5, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, (uint8_t)TUSB_DIR_IN_MASK | EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 10) }; + const uint8_t desc[] = { TUD_HID_INOUT_DESCRIPTOR(ITF_HID, ITF_HID + 6, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, (uint8_t)TUSB_DIR_IN_MASK | EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 10) }; memcpy(p, desc, sizeof(desc)); p += sizeof(desc); } if (ITF_KEYBOARD != ITF_INVALID) { TUSB_DESC_TOTAL_LEN += TUD_HID_DESC_LEN; - const uint8_t desc_kb[] = { TUD_HID_DESCRIPTOR(ITF_KEYBOARD, ITF_KEYBOARD + 5, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report_kb), (uint8_t)TUSB_DIR_IN_MASK | EPNUM_HID_KB, 16, 5) }; + const uint8_t desc_kb[] = { TUD_HID_DESCRIPTOR(ITF_KEYBOARD, ITF_KEYBOARD + 6, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report_kb), (uint8_t)TUSB_DIR_IN_MASK | EPNUM_HID_KB, 16, 5) }; memcpy(p, desc_kb, sizeof(desc_kb)); p += sizeof(desc_kb); } @@ -181,16 +189,24 @@ void usb_desc_setup(void) { #ifdef USB_ITF_CCID if (ITF_CCID != ITF_INVALID) { TUSB_DESC_TOTAL_LEN += TUSB_SMARTCARD_CCID_DESC_LEN; - const uint8_t desc_ccid[] = { TUD_SMARTCARD_DESCRIPTOR(ITF_CCID, ITF_CCID + 5, EPNUM_CCID, TUSB_DIR_IN_MASK | EPNUM_CCID, TUSB_DIR_IN_MASK | EPNUM_CCID_INT, 64) }; + const uint8_t desc_ccid[] = { TUD_SMARTCARD_DESCRIPTOR(ITF_CCID, ITF_CCID + 6, EPNUM_CCID, TUSB_DIR_IN_MASK | EPNUM_CCID, TUSB_DIR_IN_MASK | EPNUM_CCID_INT, 64) }; memcpy(p, desc_ccid, sizeof(desc_ccid)); p += sizeof(desc_ccid); } if (ITF_WCID != ITF_INVALID) { TUSB_DESC_TOTAL_LEN += TUSB_SMARTCARD_WCID_DESC_LEN; - const uint8_t desc_wcid[] = { TUD_SMARTCARD_DESCRIPTOR_WEB(ITF_WCID, ITF_WCID + 5, EPNUM_WCID, TUSB_DIR_IN_MASK | EPNUM_WCID, 64) }; + const uint8_t desc_wcid[] = { TUD_SMARTCARD_DESCRIPTOR_WEB(ITF_WCID, ITF_WCID + 6, EPNUM_WCID, TUSB_DIR_IN_MASK | EPNUM_WCID, 64) }; memcpy(p, desc_wcid, sizeof(desc_wcid)); p += sizeof(desc_wcid); } +#endif +#ifdef USB_ITF_LWIP + if (ITF_LWIP != ITF_INVALID) { + TUSB_DESC_TOTAL_LEN += TUD_CDC_NCM_DESC_LEN; + const uint8_t desc_lwip[] = { TUD_CDC_NCM_DESCRIPTOR(ITF_LWIP, ITF_LWIP + 6, 5, EPNUM_LWIP_NOTIF, 64, EPNUM_LWIP, EPNUM_LWIP | TUSB_DIR_IN_MASK, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU) }; + memcpy(p, desc_lwip, sizeof(desc_lwip)); + p += sizeof(desc_lwip); + } #endif desc_config[2] = TUSB_DESC_TOTAL_LEN & 0xFF; desc_config[3] = TUSB_DESC_TOTAL_LEN >> 8; @@ -207,7 +223,11 @@ uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { #ifdef USB_ITF_WCID #define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) +#ifdef USB_ITF_LWIP +#define MS_OS_20_DESC_LEN 0xCE +#else #define MS_OS_20_DESC_LEN 0xB2 +#endif enum { @@ -224,9 +244,7 @@ const tusb_desc_webusb_url_t desc_url = .bScheme = 1, // 0: http, 1: https .url = URL }; -#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) -#define MS_OS_20_DESC_LEN 0xB2 uint8_t desc_ms_os_20[] = { // Set header: length, type, windows version, total length U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN), @@ -234,25 +252,35 @@ uint8_t desc_ms_os_20[] = { // Configuration subset header: length, type, configuration index, reserved, configuration total length U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A), - // Function Subset header: length, type, first interface, reserved, subset length - U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), 0/*ITF_WCID*/, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08), + // Function Subset header for WebCCID (WINUSB): length, type, first interface, reserved, subset length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), 0/*ITF_WCID*/, 0, U16_TO_U8S_LE(0x00A0), // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible // MS OS 2.0 Registry property descriptor: length, type - U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08-0x08-0x14), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), + U16_TO_U8S_LE(0x0084), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), U16_TO_U8S_LE(0x0007), U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00, U16_TO_U8S_LE(0x0050), // wPropertyDataLength - //bPropertyData: “{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}”. + //bPropertyData: “{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}”. '{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00, 'D', 0x00, '9', 0x00, '-', 0x00, '0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00, '8', 0x00, 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, 'C', 0x00, 'A', 0x00, '8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, '9', 0x00, 'D', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00 +#ifdef USB_ITF_LWIP + , + // Function Subset header for NCM (WINNCM): length, type, first interface, reserved, subset length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), 0/*ITF_LWIP*/, 0, U16_TO_U8S_LE(0x001C), + + // MS OS 2.0 Compatible ID descriptor for NCM + U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'N', 'C', 'M', 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +#endif }; +TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size"); bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { // nothing to with DATA & ACK stage if (stage != CONTROL_STAGE_SETUP) @@ -269,6 +297,9 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Get Microsoft OS 2.0 compatible descriptor uint16_t total_len; desc_ms_os_20[22] = ITF_WCID; +#ifdef USB_ITF_LWIP + desc_ms_os_20[182] = ITF_LWIP; +#endif memcpy(&total_len, desc_ms_os_20+8, 2); return tud_control_xfer(rhport, request, (void*)(uintptr_t) desc_ms_os_20, total_len); } @@ -322,7 +353,8 @@ char const *string_desc_arr [] = { "Pol Henarejos", // 1: Manufacturer "Pico Key", // 2: Product "11223344", // 3: Serials, should use chip ID - "Config" // 4: Vendor Interface + "Config", // 4: Vendor Interface + "MAC" // 5: MAC address string, handled separately , "HID Interface" , "HID Keyboard Interface" #ifdef USB_ITF_HID @@ -331,6 +363,7 @@ char const *string_desc_arr [] = { , "CCID Interface" #endif , "WebCCID Interface" + , "Network Interface" }; #ifdef ESP_PLATFORM @@ -370,12 +403,21 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { str = phy_data.usb_product; } } - else if (index >= 5 && string_desc_itf[index - 5] != NULL) { - str = string_desc_itf[index - 5]; + else if (index >= 6 && string_desc_itf[index - 6] != NULL) { + str = string_desc_itf[index - 6]; + } + else if (index == 5) { +#ifdef USB_ITF_LWIP + unsigned int chr_count = 0; + for (unsigned i = 0; i < sizeof(tud_network_mac_address); i++) { + _desc_str[1 + chr_count++] = "0123456789ABCDEF"[(tud_network_mac_address[i] >> 4) & 0xf]; + _desc_str[1 + chr_count++] = "0123456789ABCDEF"[(tud_network_mac_address[i] >> 0) & 0xf]; + } +#endif } uint8_t buff_avail = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; - if (index >= 4) { + if (index >= 6) { const char *product = phy_data.usb_product_present ? phy_data.usb_product : string_desc_arr[2]; uint8_t len = (uint8_t)MIN(strlen(product), buff_avail); for (size_t ix = 0; ix < len; chr_count++, ix++) {