blob: 13cbfd07bd24485533432e0306519e0a0318428d [file] [log] [blame]
/*
* Copyright (c) 2015-2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief WebUSB enabled custom class driver
*
* This is a modified version of CDC ACM class driver
* to support the WebUSB.
*/
#define LOG_LEVEL CONFIG_USB_DEVICE_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(webusb);
#include <zephyr/sys/byteorder.h>
#include <zephyr/usb/usb_device.h>
#include <usb_descriptor.h>
#include "webusb.h"
/* Max packet size for Bulk endpoints */
#if IS_ENABLED(CONFIG_USB_DC_HAS_HS_SUPPORT)
#define WEBUSB_BULK_EP_MPS 512
#else
#define WEBUSB_BULK_EP_MPS 64
#endif
/* Number of interfaces */
#define WEBUSB_NUM_ITF 0x01
/* Number of Endpoints in the custom interface */
#define WEBUSB_NUM_EP 0x02
#define WEBUSB_IN_EP_IDX 0
#define WEBUSB_OUT_EP_IDX 1
static struct webusb_req_handlers *req_handlers;
uint8_t rx_buf[WEBUSB_BULK_EP_MPS];
#define INITIALIZER_IF(num_ep, iface_class) \
{ \
.bLength = sizeof(struct usb_if_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE, \
.bInterfaceNumber = 0, \
.bAlternateSetting = 0, \
.bNumEndpoints = num_ep, \
.bInterfaceClass = iface_class, \
.bInterfaceSubClass = 0, \
.bInterfaceProtocol = 0, \
.iInterface = 0, \
}
#define INITIALIZER_IF_EP(addr, attr, mps, interval) \
{ \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = addr, \
.bmAttributes = attr, \
.wMaxPacketSize = sys_cpu_to_le16(mps), \
.bInterval = interval, \
}
USBD_CLASS_DESCR_DEFINE(primary, 0) struct {
struct usb_if_descriptor if0;
struct usb_ep_descriptor if0_in_ep;
struct usb_ep_descriptor if0_out_ep;
} __packed webusb_desc = {
.if0 = INITIALIZER_IF(WEBUSB_NUM_EP, USB_BCC_VENDOR),
.if0_in_ep = INITIALIZER_IF_EP(AUTO_EP_IN, USB_DC_EP_BULK,
WEBUSB_BULK_EP_MPS, 0),
.if0_out_ep = INITIALIZER_IF_EP(AUTO_EP_OUT, USB_DC_EP_BULK,
WEBUSB_BULK_EP_MPS, 0),
};
/**
* @brief Custom handler for standard requests in order to
* catch the request and return the WebUSB Platform
* Capability Descriptor.
*
* @param pSetup Information about the request to execute.
* @param len Size of the buffer.
* @param data Buffer containing the request result.
*
* @return 0 on success, negative errno code on fail.
*/
int webusb_custom_handle_req(struct usb_setup_packet *pSetup,
int32_t *len, uint8_t **data)
{
LOG_DBG("");
/* Call the callback */
if (req_handlers && req_handlers->custom_handler) {
return req_handlers->custom_handler(pSetup, len, data);
}
return -EINVAL;
}
/**
* @brief Handler called for WebUSB vendor specific commands.
*
* @param pSetup Information about the request to execute.
* @param len Size of the buffer.
* @param data Buffer containing the request result.
*
* @return 0 on success, negative errno code on fail.
*/
int webusb_vendor_handle_req(struct usb_setup_packet *pSetup,
int32_t *len, uint8_t **data)
{
/* Call the callback */
if (req_handlers && req_handlers->vendor_handler) {
return req_handlers->vendor_handler(pSetup, len, data);
}
return -EINVAL;
}
/**
* @brief Register Custom and Vendor request callbacks
*
* This function registers Custom and Vendor request callbacks
* for handling the device requests.
*
* @param [in] handlers Pointer to WebUSB request handlers structure
*/
void webusb_register_request_handlers(struct webusb_req_handlers *handlers)
{
req_handlers = handlers;
}
static void webusb_write_cb(uint8_t ep, int size, void *priv)
{
LOG_DBG("ep %x size %u", ep, size);
}
static void webusb_read_cb(uint8_t ep, int size, void *priv)
{
struct usb_cfg_data *cfg = priv;
LOG_DBG("cfg %p ep %x size %u", cfg, ep, size);
if (size <= 0) {
goto done;
}
usb_transfer(cfg->endpoint[WEBUSB_IN_EP_IDX].ep_addr, rx_buf, size,
USB_TRANS_WRITE, webusb_write_cb, cfg);
done:
usb_transfer(ep, rx_buf, sizeof(rx_buf), USB_TRANS_READ,
webusb_read_cb, cfg);
}
/**
* @brief Callback used to know the USB connection status
*
* @param status USB device status code.
*/
static void webusb_dev_status_cb(struct usb_cfg_data *cfg,
enum usb_dc_status_code status,
const uint8_t *param)
{
ARG_UNUSED(param);
ARG_UNUSED(cfg);
/* Check the USB status and do needed action if required */
switch (status) {
case USB_DC_ERROR:
LOG_DBG("USB device error");
break;
case USB_DC_RESET:
LOG_DBG("USB device reset detected");
break;
case USB_DC_CONNECTED:
LOG_DBG("USB device connected");
break;
case USB_DC_CONFIGURED:
LOG_DBG("USB device configured");
webusb_read_cb(cfg->endpoint[WEBUSB_OUT_EP_IDX].ep_addr,
0, cfg);
break;
case USB_DC_DISCONNECTED:
LOG_DBG("USB device disconnected");
break;
case USB_DC_SUSPEND:
LOG_DBG("USB device suspended");
break;
case USB_DC_RESUME:
LOG_DBG("USB device resumed");
break;
case USB_DC_UNKNOWN:
default:
LOG_DBG("USB unknown state");
break;
}
}
/* Describe EndPoints configuration */
static struct usb_ep_cfg_data webusb_ep_data[] = {
{
.ep_cb = usb_transfer_ep_callback,
.ep_addr = AUTO_EP_IN
},
{
.ep_cb = usb_transfer_ep_callback,
.ep_addr = AUTO_EP_OUT
}
};
USBD_DEFINE_CFG_DATA(webusb_config) = {
.usb_device_description = NULL,
.interface_descriptor = &webusb_desc.if0,
.cb_usb_status = webusb_dev_status_cb,
.interface = {
.class_handler = NULL,
.custom_handler = webusb_custom_handle_req,
.vendor_handler = webusb_vendor_handle_req,
},
.num_endpoints = ARRAY_SIZE(webusb_ep_data),
.endpoint = webusb_ep_data
};