| /* |
| * Copyright (c) 2017 Linaro Ltd |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define LOG_LEVEL CONFIG_USB_DEVICE_NETWORK_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(usb_eem); |
| |
| #include <zephyr/net/net_pkt.h> |
| #include <zephyr/net/ethernet.h> |
| #include <net_private.h> |
| |
| #include <zephyr/usb/usb_device.h> |
| #include <zephyr/usb/class/usb_cdc.h> |
| |
| #include "netusb.h" |
| |
| static uint8_t sentinel[] = { 0xde, 0xad, 0xbe, 0xef }; |
| |
| #define EEM_FRAME_SIZE (NET_ETH_MAX_FRAME_SIZE + sizeof(sentinel) + \ |
| sizeof(uint16_t)) /* EEM header */ |
| |
| static uint8_t tx_buf[EEM_FRAME_SIZE], rx_buf[EEM_FRAME_SIZE]; |
| |
| struct usb_cdc_eem_config { |
| struct usb_if_descriptor if0; |
| struct usb_ep_descriptor if0_in_ep; |
| struct usb_ep_descriptor if0_out_ep; |
| } __packed; |
| |
| USBD_CLASS_DESCR_DEFINE(primary, 0) struct usb_cdc_eem_config cdc_eem_cfg = { |
| /* Interface descriptor 0 */ |
| /* CDC Communication interface */ |
| .if0 = { |
| .bLength = sizeof(struct usb_if_descriptor), |
| .bDescriptorType = USB_DESC_INTERFACE, |
| .bInterfaceNumber = 0, |
| .bAlternateSetting = 0, |
| .bNumEndpoints = 2, |
| .bInterfaceClass = USB_BCC_CDC_CONTROL, |
| .bInterfaceSubClass = EEM_SUBCLASS, |
| .bInterfaceProtocol = EEM_PROTOCOL, |
| .iInterface = 0, |
| }, |
| |
| /* Data Endpoint IN */ |
| .if0_in_ep = { |
| .bLength = sizeof(struct usb_ep_descriptor), |
| .bDescriptorType = USB_DESC_ENDPOINT, |
| .bEndpointAddress = CDC_EEM_IN_EP_ADDR, |
| .bmAttributes = USB_DC_EP_BULK, |
| .wMaxPacketSize = |
| sys_cpu_to_le16(CONFIG_CDC_EEM_BULK_EP_MPS), |
| .bInterval = 0x00, |
| }, |
| |
| /* Data Endpoint OUT */ |
| .if0_out_ep = { |
| .bLength = sizeof(struct usb_ep_descriptor), |
| .bDescriptorType = USB_DESC_ENDPOINT, |
| .bEndpointAddress = CDC_EEM_OUT_EP_ADDR, |
| .bmAttributes = USB_DC_EP_BULK, |
| .wMaxPacketSize = |
| sys_cpu_to_le16(CONFIG_CDC_EEM_BULK_EP_MPS), |
| .bInterval = 0x00, |
| }, |
| }; |
| |
| static uint8_t eem_get_first_iface_number(void) |
| { |
| return cdc_eem_cfg.if0.bInterfaceNumber; |
| } |
| |
| #define EEM_OUT_EP_IDX 0 |
| #define EEM_IN_EP_IDX 1 |
| |
| static struct usb_ep_cfg_data eem_ep_data[] = { |
| { |
| /* Use transfer API */ |
| .ep_cb = usb_transfer_ep_callback, |
| .ep_addr = CDC_EEM_OUT_EP_ADDR |
| }, |
| { |
| /* Use transfer API */ |
| .ep_cb = usb_transfer_ep_callback, |
| .ep_addr = CDC_EEM_IN_EP_ADDR |
| }, |
| }; |
| |
| static inline uint16_t eem_pkt_size(uint16_t hdr) |
| { |
| if (hdr & BIT(15)) { |
| return hdr & 0x07ff; |
| } else { |
| return hdr & 0x3fff; |
| } |
| } |
| |
| static int eem_send(struct net_pkt *pkt) |
| { |
| uint16_t *hdr = (uint16_t *)&tx_buf[0]; |
| int ret, len, b_idx = 0; |
| |
| /* With EEM, it's possible to send multiple ethernet packets in one |
| * transfer, we don't do that for now. |
| */ |
| len = net_pkt_get_len(pkt) + sizeof(sentinel); |
| |
| if (len + sizeof(uint16_t) > sizeof(tx_buf)) { |
| LOG_WRN("Trying to send too large packet, drop"); |
| return -ENOMEM; |
| } |
| |
| /* Add EEM header */ |
| *hdr = sys_cpu_to_le16(0x3FFF & len); |
| b_idx += sizeof(uint16_t); |
| |
| if (net_pkt_read(pkt, &tx_buf[b_idx], net_pkt_get_len(pkt))) { |
| return -ENOBUFS; |
| } |
| |
| b_idx += len - sizeof(sentinel); |
| |
| /* Add crc-sentinel */ |
| memcpy(&tx_buf[b_idx], sentinel, sizeof(sentinel)); |
| b_idx += sizeof(sentinel); |
| |
| /* transfer data to host */ |
| ret = usb_transfer_sync(eem_ep_data[EEM_IN_EP_IDX].ep_addr, |
| tx_buf, b_idx, |
| USB_TRANS_WRITE); |
| if (ret != b_idx) { |
| LOG_ERR("Transfer failure"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static void eem_read_cb(uint8_t ep, int size, void *priv) |
| { |
| uint8_t *ptr = rx_buf; |
| |
| do { |
| uint16_t eem_hdr, eem_size; |
| struct net_pkt *pkt; |
| |
| if (size < sizeof(uint16_t)) { |
| break; |
| } |
| |
| eem_hdr = sys_get_le16(ptr); |
| eem_size = eem_pkt_size(eem_hdr); |
| |
| if (eem_size + sizeof(uint16_t) > size) { |
| /* eem pkt greater than transferred data */ |
| LOG_ERR("pkt size error"); |
| break; |
| } |
| |
| size -= sizeof(uint16_t); |
| ptr += sizeof(uint16_t); |
| |
| if (eem_hdr & BIT(15)) { |
| /* EEM Command, do nothing for now */ |
| goto done; |
| } |
| |
| LOG_DBG("hdr 0x%x, eem_size %d, size %d", |
| eem_hdr, eem_size, size); |
| |
| if (!size || !eem_size) { |
| LOG_DBG("no payload"); |
| break; |
| } |
| |
| pkt = net_pkt_rx_alloc_with_buffer(netusb_net_iface(), |
| eem_size - sizeof(sentinel), |
| AF_UNSPEC, 0, K_FOREVER); |
| if (!pkt) { |
| LOG_ERR("Unable to alloc pkt"); |
| break; |
| } |
| |
| /* copy payload and discard 32-bit sentinel */ |
| if (net_pkt_write(pkt, ptr, eem_size - sizeof(sentinel))) { |
| LOG_ERR("Unable to write into pkt"); |
| net_pkt_unref(pkt); |
| break; |
| } |
| |
| netusb_recv(pkt); |
| |
| done: |
| size -= eem_size; |
| ptr += eem_size; |
| } while (size); |
| |
| usb_transfer(eem_ep_data[EEM_OUT_EP_IDX].ep_addr, rx_buf, |
| sizeof(rx_buf), USB_TRANS_READ, eem_read_cb, NULL); |
| } |
| |
| static int eem_connect(bool connected) |
| { |
| if (connected) { |
| eem_read_cb(eem_ep_data[EEM_OUT_EP_IDX].ep_addr, 0, NULL); |
| } else { |
| /* Cancel any transfer */ |
| usb_cancel_transfer(eem_ep_data[EEM_OUT_EP_IDX].ep_addr); |
| usb_cancel_transfer(eem_ep_data[EEM_IN_EP_IDX].ep_addr); |
| } |
| |
| return 0; |
| } |
| |
| static struct netusb_function eem_function = { |
| .connect_media = eem_connect, |
| .send_pkt = eem_send, |
| }; |
| |
| static inline void eem_status_interface(const uint8_t *desc) |
| { |
| const struct usb_if_descriptor *if_desc = (void *)desc; |
| uint8_t iface_num = if_desc->bInterfaceNumber; |
| |
| LOG_DBG(""); |
| |
| if (iface_num != eem_get_first_iface_number()) { |
| return; |
| } |
| |
| netusb_enable(&eem_function); |
| } |
| |
| static void eem_status_cb(struct usb_cfg_data *cfg, |
| enum usb_dc_status_code status, |
| const uint8_t *param) |
| { |
| ARG_UNUSED(cfg); |
| |
| /* Check the USB status and do needed action if required */ |
| switch (status) { |
| case USB_DC_DISCONNECTED: |
| LOG_DBG("USB device disconnected"); |
| netusb_disable(); |
| break; |
| |
| case USB_DC_INTERFACE: |
| LOG_DBG("USB interface selected"); |
| eem_status_interface(param); |
| break; |
| |
| case USB_DC_ERROR: |
| case USB_DC_RESET: |
| case USB_DC_CONNECTED: |
| case USB_DC_CONFIGURED: |
| case USB_DC_SUSPEND: |
| case USB_DC_RESUME: |
| LOG_DBG("USB unhandled state: %d", status); |
| break; |
| |
| case USB_DC_SOF: |
| break; |
| |
| case USB_DC_UNKNOWN: |
| default: |
| LOG_DBG("USB unknown state: %d", status); |
| break; |
| } |
| } |
| |
| static void eem_interface_config(struct usb_desc_header *head, |
| uint8_t bInterfaceNumber) |
| { |
| ARG_UNUSED(head); |
| |
| cdc_eem_cfg.if0.bInterfaceNumber = bInterfaceNumber; |
| } |
| |
| USBD_DEFINE_CFG_DATA(cdc_eem_config) = { |
| .usb_device_description = NULL, |
| .interface_config = eem_interface_config, |
| .interface_descriptor = &cdc_eem_cfg.if0, |
| .cb_usb_status = eem_status_cb, |
| .interface = { |
| .class_handler = NULL, |
| .custom_handler = NULL, |
| .vendor_handler = NULL, |
| }, |
| .num_endpoints = ARRAY_SIZE(eem_ep_data), |
| .endpoint = eem_ep_data, |
| }; |