/* * Copyright (c) 2017 Linaro Ltd * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(usb_eem, CONFIG_USB_DEVICE_NETWORK_LOG_LEVEL); #include #include #include #include #include #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, };