diff options
author | Adrian Bradianu <adrian.bradianu@windriver.com> | 2016-06-01 14:20:02 +0300 |
---|---|---|
committer | Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> | 2016-06-30 17:51:56 +0000 |
commit | 7a16ade6d12ab7db8137ab762caf5bcfa06228c1 (patch) | |
tree | 9686fe400f9c3f923fb21a5b0e3bb2d245235309 /usb | |
parent | 7a6bab308ae6a71f2c1a981c3da1ed127ddee67f (diff) |
usb: Add USB device core layer
USB Device core layer is a hardware independent interface between USB
device controller driver and USB device class drivers or customer
applications. It's a port of the LPCUSB device stack.
Change-Id: I9371ffab7034d20953fec0525e72fbe9e094c931
Signed-off-by: Adrian Bradianu <adrian.bradianu@windriver.com>
Signed-off-by: Jesus Sanchez-Palencia <jesus.sanchez-palencia@intel.com>
Signed-off-by: Jithu Joseph <jithu.joseph@intel.com>
Diffstat (limited to 'usb')
-rw-r--r-- | usb/Kconfig | 36 | ||||
-rw-r--r-- | usb/Makefile | 3 | ||||
-rw-r--r-- | usb/include/usb_device.h | 229 | ||||
-rw-r--r-- | usb/include/usbstruct.h | 111 | ||||
-rw-r--r-- | usb/usb_device.c | 926 |
5 files changed, 1305 insertions, 0 deletions
diff --git a/usb/Kconfig b/usb/Kconfig new file mode 100644 index 000000000..6fe1ff5fd --- /dev/null +++ b/usb/Kconfig @@ -0,0 +1,36 @@ +# Kconfig - USB device stack configuration options + +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +menuconfig USB_DEVICE_STACK + bool + prompt "USB device stack" + depends on USB + default n + help + Enable USB device stack. + +if USB_DEVICE_STACK + +config USB_DEBUG + bool + prompt "Enable USB debug options for the entire USB stack" + default n + help + This option enables the debug features for the USB device stack. + +endif # USB_DEVICE_STACK diff --git a/usb/Makefile b/usb/Makefile new file mode 100644 index 000000000..f4ad7c5dc --- /dev/null +++ b/usb/Makefile @@ -0,0 +1,3 @@ +ccflags-y += -I${srctree}/include/drivers/usb -I${srctree}/usb/include + +obj-$(CONFIG_USB_DEVICE_STACK) += usb_device.o diff --git a/usb/include/usb_device.h b/usb/include/usb_device.h new file mode 100644 index 000000000..c71e97ed6 --- /dev/null +++ b/usb/include/usb_device.h @@ -0,0 +1,229 @@ +/* + * LPCUSB, an USB device driver for LPC microcontrollers + * Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) + * Copyright (c) 2016 Intel Corporation + * + * 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. + */ + +/** + * @file + * @brief USB device core layer APIs and structures + * + * This file contains the USB device core layer APIs and structures. + */ + +#ifndef USB_DEVICE_H_ +#define USB_DEVICE_H_ + +#include "usb_dc.h" +#include "usbstruct.h" + +/************************************************************************* + * USB configuration + **************************************************************************/ + +#define MAX_PACKET_SIZE0 64 /**< maximum packet size for EP 0 */ + +/************************************************************************* + * USB application interface + **************************************************************************/ + +/** setup packet definitions */ +struct usb_setup_packet { + uint8_t bmRequestType; /**< characteristics of the specific request */ + uint8_t bRequest; /**< specific request */ + uint16_t wValue; /**< request specific parameter */ + uint16_t wIndex; /**< request specific parameter */ + uint16_t wLength; /**< length of data transferred in data phase */ +}; + +/** + * Callback function signature for the device + */ +typedef void (*usb_status_callback)(enum usb_dc_status_code status_code); + +/** + * Callback function signature for the USB Endpoint status + */ +typedef void (*usb_ep_callback)(uint8_t ep, + enum usb_dc_ep_cb_status_code cb_status); + +/** + * Function which handles Class specific requests corresponding to an + * interface number specified in the device descriptor table + */ +typedef int (*usb_request_handler) (struct usb_setup_packet *detup, + int *transfer_len, uint8_t **payload_data); + +/* + * USB Endpoint Configuration + */ +struct usb_ep_cfg_data { + /** + * Callback function for notification of data received and + * available to application or transmit done, NULL if callback + * not required by application code + */ + usb_ep_callback ep_cb; + /** + * The number associated with the EP in the device configuration + * structure + * IN EP = 0x80 | \<endpoint number\> + * OUT EP = 0x00 | \<endpoint number\> + */ + uint8_t ep_addr; +}; + +/** + * USB Interface Configuration + */ +struct usb_interface_cfg_data { + /** Handler for USB Class specific Control (EP 0) communications */ + usb_request_handler class_handler; + /** + * The custom request handler gets a first chance at handling + * the request before it is handed over to the 'chapter 9' request + * handler + */ + usb_request_handler custom_handler; + /** + * This data area, allocated by the application, is used to store + * Class specific command data and must be large enough to store the + * largest payload associated with the largest supported Class' + * command set. This data area may be used for USB IN or OUT + * communications + */ + uint8_t *payload_data; +}; + +/* + * @brief USB device configuration + * + * The Application instantiates this with given parameters added + * using the "usb_set_config" function. Once this function is called + * changes to this structure will result in undefined behaviour. This structure + * may only be updated after calls to usb_deconfig + */ +struct usb_cfg_data { + /** + * USB device description, see + * http://www.beyondlogic.org/usbnutshell/usb5.shtml#DeviceDescriptors + */ + const uint8_t *usb_device_description; + /** Callback to be notified on USB connection status change */ + usb_status_callback cb_usb_status; + /** USB interface (Class) handler and storage space */ + struct usb_interface_cfg_data interface; + /** Number of individual endpoints in the device configuration */ + uint8_t num_endpoints; + /** + * Pointer to an array of endpoint structs of length equal to the + * number of EP associated with the device description, + * not including control endpoints + */ + struct usb_ep_cfg_data *endpoint; +}; + +/* + * @brief configure USB controller + * + * Function to configure USB controller. + * Configuration parameters must be valid or an error is returned + * + * @param[in] config Pointer to configuration structure + * + * @return 0 on success, negative errno code on fail + */ +int usb_set_config(struct usb_cfg_data *config); + +/* + * @brief return the USB device to it's initial state + * + * @return 0 on success, negative errno code on fail + */ +int usb_deconfig(void); + +/* + * @brief enable USB for host/device connection + * + * Function to enable USB for host/device connection. + * Upon success, the USB module is no longer clock gated in hardware, + * it is now capable of transmitting and receiving on the USB bus and + * of generating interrupts. + * + * @return 0 on success, negative errno code on fail. + */ +int usb_enable(struct usb_cfg_data *config); + +/* + * @brief disable the USB device. + * + * Function to disable the USB device. + * Upon success, the specified USB interface is clock gated in hardware, + * it is no longer capable of generating interrupts. + * + * @return 0 on success, negative errno code on fail + */ +int usb_disable(void); + +/* + * @brief write data to the specified endpoint + * + * Function to write data to the specified endpoint. The supplied + * usb_ep_callback will be called when transmission is done. + * + * @param[in] ep Endpoint address corresponding to the one listed in the + * device configuration table + * @param[in] data Pointer to data to write + * @param[in] data_len Length of data requested to write. This may be zero for + * a zero length status packet. + * @param[out] bytes_ret Bytes written to the EP FIFO. This value may be NULL if + * the application expects all bytes to be written + * + * @return 0 on success, negative errno code on fail + */ +int usb_write(uint8_t ep, const uint8_t *data, uint32_t data_len, + uint32_t *bytes_ret); + +/* + * @brief read data from the specified endpoint + * + * This function is called by the Endpoint handler function, after an + * OUT interrupt has been received for that EP. The application must + * only call this function through the supplied usb_ep_callback function. + * + * @param[in] ep Endpoint address corresponding to the one listed in + * the device configuration table + * @param[in] data Pointer to data buffer to write to + * @param[in] max_data_len Max length of data to read + * @param[out] ret_bytes Number of bytes read. If data is NULL and + * max_data_len is 0 the number of bytes available + * for read is returned. + * + * @return 0 on success, negative errno code on fail + */ +int usb_read(uint8_t ep, uint8_t *data, uint32_t max_data_len, + uint32_t *ret_bytes); + +#endif /* USB_DEVICE_H_ */ diff --git a/usb/include/usbstruct.h b/usb/include/usbstruct.h new file mode 100644 index 000000000..229065a2a --- /dev/null +++ b/usb/include/usbstruct.h @@ -0,0 +1,111 @@ +/* + * LPCUSB, an USB device driver for LPC microcontrollers + * Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) + * Copyright (c) 2016 Intel Corporation + * + * 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. + */ + +/** + * @file + * @brief standard USB packet stuctures and defines + * + * This file contains stuctures and defines of the standard USB packets + */ + +#ifndef _USBSTRUCT_H_ +#define _USBSTRUCT_H_ + +#define REQTYPE_GET_DIR(x) (((x)>>7)&0x01) +#define REQTYPE_GET_TYPE(x) (((x)>>5)&0x03) +#define REQTYPE_GET_RECIP(x) ((x)&0x1F) + +#define REQTYPE_DIR_TO_DEVICE 0 +#define REQTYPE_DIR_TO_HOST 1 + +#define REQTYPE_TYPE_STANDARD 0 +#define REQTYPE_TYPE_CLASS 1 +#define REQTYPE_TYPE_VENDOR 2 +#define REQTYPE_TYPE_RESERVED 3 + +#define REQTYPE_RECIP_DEVICE 0 +#define REQTYPE_RECIP_INTERFACE 1 +#define REQTYPE_RECIP_ENDPOINT 2 +#define REQTYPE_RECIP_OTHER 3 + +/* standard requests */ +#define REQ_GET_STATUS 0x00 +#define REQ_CLEAR_FEATURE 0x01 +#define REQ_SET_FEATURE 0x03 +#define REQ_SET_ADDRESS 0x05 +#define REQ_GET_DESCRIPTOR 0x06 +#define REQ_SET_DESCRIPTOR 0x07 +#define REQ_GET_CONFIGURATION 0x08 +#define REQ_SET_CONFIGURATION 0x09 +#define REQ_GET_INTERFACE 0x0A +#define REQ_SET_INTERFACE 0x0B +#define REQ_SYNCH_FRAME 0x0C + +/* class requests HID */ +#define HID_GET_REPORT 0x01 +#define HID_GET_IDLE 0x02 +#define HID_GET_PROTOCOL 0x03 +#define HID_SET_REPORT 0x09 +#define HID_SET_IDLE 0x0A +#define HID_SET_PROTOCOL 0x0B + +/* feature selectors */ +#define FEA_ENDPOINT_HALT 0x00 +#define FEA_REMOTE_WAKEUP 0x01 +#define FEA_TEST_MODE 0x02 + +/* + * USB descriptors + */ + +/** USB descriptor header */ +struct usb_desc_header { + uint8_t bLength; /**< descriptor length */ + uint8_t bDescriptorType; /**< descriptor type */ +}; + +#define DESC_DEVICE 1 +#define DESC_CONFIGURATION 2 +#define DESC_STRING 3 +#define DESC_INTERFACE 4 +#define DESC_ENDPOINT 5 +#define DESC_DEVICE_QUALIFIER 6 +#define DESC_OTHER_SPEED 7 +#define DESC_INTERFACE_POWER 8 + +#define CS_INTERFACE 0x24 +#define CS_ENDPOINT 0x25 + +#define DESC_HID_HID 0x21 +#define DESC_HID_REPORT 0x22 +#define DESC_HID_PHYSICAL 0x23 + +#define GET_DESC_TYPE(x) (((x)>>8)&0xFF) +#define GET_DESC_INDEX(x) ((x)&0xFF) + +#endif /* _USBSTRUCT_H_ */ diff --git a/usb/usb_device.c b/usb/usb_device.c new file mode 100644 index 000000000..966e135d0 --- /dev/null +++ b/usb/usb_device.c @@ -0,0 +1,926 @@ +/* + * LPCUSB, an USB device driver for LPC microcontrollers + * Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) + * Copyright (c) 2016 Intel Corporation + * + * 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. + */ + +/** + * @file + * @brief USB device core layer + * + * This module handles control transfer handler, standard request handler and + * USB Interface for customer application. + * + * Control transfers handler is normally installed on the + * endpoint 0 callback. + * + * Control transfers can be of the following type: + * 0 Standard; + * 1 Class; + * 2 Vendor; + * 3 Reserved. + * + * A callback can be installed for each of these control transfers using + * usb_register_request_handler. + * When an OUT request arrives, data is collected in the data store provided + * with the usb_register_request_handler call. When the transfer is done, the + * callback is called. + * When an IN request arrives, the callback is called immediately to either + * put the control transfer data in the data store, or to get a pointer to + * control transfer data. The data is then packetised and sent to the host. + * + * Standard request handler handles the 'chapter 9' processing, specifically + * the standard device requests in table 9-3 from the universal serial bus + * specification revision 2.0 + */ + +#include <errno.h> +#include <stddef.h> +#include <misc/util.h> +#include <misc/__assert.h> +#include <board.h> +#if defined(USB_VUSB_EN_GPIO) +#include <gpio.h> +#endif +#include "usb_device.h" + +#ifndef CONFIG_USB_DEBUG +#define DBG(...) { ; } +#else +#if defined(CONFIG_STDOUT_CONSOLE) +#include <stdio.h> +#define DBG printf +#else +#include <misc/printk.h> +#define DBG printk +#endif /* CONFIG_STDOUT_CONSOLE */ +#endif /* CONFIG_USB_DEBUG */ + +#define MAX_DESC_HANDLERS 4 /** Device, interface, endpoint, other */ + +/* general descriptor field offsets */ +#define DESC_bLength 0 /** Length offset */ +#define DESC_bDescriptorType 1 /** Descriptor type offset */ + +/* config descriptor field offsets */ +#define CONF_DESC_wTotalLength 2 /** Total length offset */ +#define CONF_DESC_bConfigurationValue 5 /** Configuration value offset */ +#define CONF_DESC_bmAttributes 7 /** configuration characteristics */ + +/* interface descriptor field offsets */ +#define INTF_DESC_bAlternateSetting 3 /** Alternate setting offset */ + +/* endpoint descriptor field offsets */ +#define ENDP_DESC_bEndpointAddress 2 /** Endpoint address offset */ +#define ENDP_DESC_bmAttributes 3 /** Bulk or interrupt? */ +#define ENDP_DESC_wMaxPacketSize 4 /** Maximum packet size offset */ + +#define MAX_NUM_REQ_HANDLERS (4) +#define MAX_STD_REQ_MSG_SIZE 8 + +/* Default USB control EP, always 0 and 0x80 */ +#define USB_CONTROL_OUT_EP0 0 +#define USB_CONTROL_IN_EP0 0x80 + +static struct usb_dev_priv { + /** Setup packet */ + struct usb_setup_packet setup; + /** Pointer to data buffer */ + uint8_t *data_buf; + /** Eemaining bytes in buffer */ + int32_t data_buf_residue; + /** Total length of control transfer */ + int32_t data_buf_len; + /** Installed custom request handler */ + usb_request_handler custom_req_handler; + /** USB stack status clalback */ + usb_status_callback status_callback; + /** Pointer to registered descriptors */ + const uint8_t *descriptors; + /** Array of installed request handler callbacks */ + usb_request_handler req_handlers[MAX_NUM_REQ_HANDLERS]; + /** Array of installed request data pointers */ + uint8_t *data_store[MAX_NUM_REQ_HANDLERS]; + /* Buffer used for storing standard usb request data */ + uint8_t std_req_data[MAX_STD_REQ_MSG_SIZE]; + /** Variable to check whether the usb has been enabled */ + bool enabled; + /** Currently selected configuration */ + uint8_t configuration; +} usb_dev; + +/* + * @brief print the contents of a setup packet + * + * @param [in] setup The setup packet + * + */ +static void usb_print_setup(struct usb_setup_packet *setup) +{ + /* avoid compiler warning if DBG is not defined */ + setup = setup; + + DBG("SETUP\n"); + DBG("%x %x %x %x %x\n", + setup->bmRequestType, + setup->bRequest, + setup->wValue, + setup->wIndex, + setup->wLength); +} + +/* + * @brief handle a request by calling one of the installed request handlers + * + * Local function to handle a request by calling one of the installed request + * handlers. In case of data going from host to device, the data is at *ppbData. + * In case of data going from device to host, the handler can either choose to + * write its data at *ppbData or update the data pointer. + * + * @param [in] setup The setup packet + * @param [in,out] len Pointer to data length + * @param [in,out] data Data buffer + * + * @return true if the request was handles successfully + */ +static bool usb_handle_request(struct usb_setup_packet *setup, + int32_t *len, uint8_t **data) +{ + uint32_t type = REQTYPE_GET_TYPE(setup->bmRequestType); + usb_request_handler handler = usb_dev.req_handlers[type]; + + DBG("** %d **\n", type); + + if (type >= MAX_NUM_REQ_HANDLERS) { + DBG("Error Incorrect iType %d\n", type); + return false; + } + + if (handler == NULL) { + DBG("No handler for reqtype %d\n", type); + return false; + } + + if ((*handler)(setup, len, data) < 0) { + DBG("Handler Error %d\n", type); + usb_print_setup(setup); + return false; + } + + return true; +} + +/* + * @brief send next chunk of data (possibly 0 bytes) to host + * + * @return N/A + */ +static void usb_data_to_host(void) +{ + uint32_t chunk = min(MAX_PACKET_SIZE0, usb_dev.data_buf_residue); + + /*Always EP0 for control*/ + usb_dc_ep_write(0x80, usb_dev.data_buf, chunk, &chunk); + usb_dev.data_buf += chunk; + usb_dev.data_buf_residue -= chunk; +} + +/* + * @brief handle IN/OUT transfers on EP0 + * + * @param [in] ep Endpoint address + * @param [in] ep_status Endpoint status + * + * @return N/A + */ +static void usb_handle_control_transfer(uint8_t ep, + enum usb_dc_ep_cb_status_code ep_status) +{ + uint32_t chunk = 0; + uint32_t type = 0; + struct usb_setup_packet *setup = &usb_dev.setup; + + DBG("usb_handle_control_transfer ep %x, status %x\n", ep, ep_status); + if (ep == USB_CONTROL_OUT_EP0 && ep_status == USB_DC_EP_SETUP) { + /* + * OUT transfer, Setup packet, + * reset request message state machine + */ + if (usb_dc_ep_read(ep, + (uint8_t *)setup, sizeof(*setup), NULL) < 0) { + DBG("Read Setup Packet failed\n"); + usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); + return; + } + + /* Defaults for data pointer and residue */ + type = REQTYPE_GET_TYPE(setup->bmRequestType); + usb_dev.data_buf = usb_dev.data_store[type]; + usb_dev.data_buf_residue = setup->wLength; + usb_dev.data_buf_len = setup->wLength; + + if (!(setup->wLength == 0) && + !(REQTYPE_GET_DIR(setup->bmRequestType) == + REQTYPE_DIR_TO_HOST)) { + return; + } + + /* Ask installed handler to process request */ + if (!usb_handle_request(setup, + &usb_dev.data_buf_len, &usb_dev.data_buf)) { + DBG("usb_handle_request failed\n"); + usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); + return; + } + + /* Send smallest of requested and offered length */ + usb_dev.data_buf_residue = min(usb_dev.data_buf_len, + setup->wLength); + /* Send first part (possibly a zero-length status message) */ + usb_data_to_host(); + } else if (ep == USB_CONTROL_OUT_EP0) { + /* OUT transfer, data or status packets */ + if (usb_dev.data_buf_residue <= 0) { + /* absorb zero-length status message */ + if (usb_dc_ep_read(USB_CONTROL_OUT_EP0, + usb_dev.data_buf, 0, &chunk) < 0) { + DBG("Read DATA Packet failed\n"); + usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); + } + DBG(chunk > 0 ? "?" : ""); + return; + } + + if (usb_dc_ep_read(USB_CONTROL_OUT_EP0, + usb_dev.data_buf, + usb_dev.data_buf_residue, &chunk) < 0) { + DBG("Read DATA Packet failed\n"); + usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); + return; + } + + usb_dev.data_buf += chunk; + usb_dev.data_buf_residue -= chunk; + if (usb_dev.data_buf_residue == 0) { + /* Received all, send data to handler */ + type = REQTYPE_GET_TYPE(setup->bmRequestType); + usb_dev.data_buf = usb_dev.data_store[type]; + if (!usb_handle_request(setup, + &usb_dev.data_buf_len, &usb_dev.data_buf)) { + DBG("usb_handle_request1 failed\n"); + usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); + return; + } + + /*Send status to host*/ + DBG(">> usb_data_to_host(2)\n"); + usb_data_to_host(); + } + } else if (ep == USB_CONTROL_IN_EP0) { + /* Send more data if available */ + if (usb_dev.data_buf_residue != 0) { + usb_data_to_host(); + } + } else { + __ASSERT_NO_MSG(false); + } +} + + +/* + * @brief register a callback for handling requests + * + * @param [in] type Type of request, e.g. REQTYPE_TYPE_STANDARD + * @param [in] handler Callback function pointer + * @param [in] data_store Data storage area for this type of request + * + * @return N/A + */ +static void usb_register_request_handler(int32_t type, + usb_request_handler handler, uint8_t *data_store) +{ + usb_dev.req_handlers[type] = handler; + usb_dev.data_store[type] = data_store; +} + +/* + * @brief register a pointer to a descriptor block + * + * This function registers a pointer to a descriptor block containing all + * descriptors for the device. + * + * @param [in] usb_descriptors The descriptor byte array + */ +static void usb_register_descriptors(const uint8_t *usb_descriptors) +{ + usb_dev.descriptors = usb_descriptors; +} + +/* + * @brief get specified USB descriptor + * + * This function parses the list of installed USB descriptors and attempts + * to find the specified USB descriptor. + * + * @param [in] type_index Type and index of the descriptor + * @param [in] lang_id Language ID of the descriptor (currently unused) + * @param [out] len Descriptor length + * @param [out] data Descriptor data + * + * @return true if the descriptor was found, false otherwise + */ +static bool usb_get_descriptor(uint16_t type_index, uint16_t lang_id, + int32_t *len, uint8_t **data) +{ + uint8_t type = 0; + uint8_t index = 0; + uint8_t *p = NULL; + int32_t cur_index = 0; + bool found = false; + + __ASSERT_NO_MSG(usb_descriptors != NULL); + + /*Avoid compiler warning until this is used for something*/ + lang_id = lang_id; + + type = GET_DESC_TYPE(type_index); + index = GET_DESC_INDEX(type_index); + + p = (uint8_t *)usb_dev.descriptors; + cur_index = 0; + + while (p[DESC_bLength] != 0) { + if (p[DESC_bDescriptorType] == type) { + if (cur_index == index) { + found = true; + break; + } + cur_index++; + } + /* skip to next descriptor */ + p += p[DESC_bLength]; + } + + if (found) { + /* set data pointer */ + *data = p; + /* get length from structure */ + if (type == DESC_CONFIGURATION) { + /* configuration descriptor is an + * exception, length is at offset + * 2 and 3 + */ + *len = (p[CONF_DESC_wTotalLength]) | + (p[CONF_DESC_wTotalLength + 1] << 8); + } else { + /* normally length is at offset 0 */ + *len = p[DESC_bLength]; + } + } else { + /* nothing found */ + DBG("Desc %x not found!\n", type_index); + } + return found; +} + +/* + * @brief set USB configuration + * + * This function configures the device according to the specified configuration + * index and alternate setting by parsing the installed USB descriptor list. + * A configuration index of 0 unconfigures the device. + * + * @param [in] config_index Configuration index + * @param [in] alt_setting Alternate setting number + * + * @return true if successfully configured false if error or unconfigured + */ +static bool usb_set_configuration(uint8_t config_index, uint8_t alt_setting) +{ + uint8_t *p = NULL; + uint8_t cur_config = 0; + uint8_t cur_alt_setting = 0; + + __ASSERT_NO_MSG(usb_descriptors != NULL); + + if (config_index == 0) { + /* unconfigure device */ + DBG("Device not configured - invalid configuration offset\n"); + return true; + } + + /* configure endpoints for this configuration/altsetting */ + p = (uint8_t *)usb_dev.descriptors; + cur_config = 0xFF; + cur_alt_setting = 0xFF; + + while (p[DESC_bLength] != 0) { + switch (p[DESC_bDescriptorType]) { + case DESC_CONFIGURATION: + /* remember current configuration index */ + cur_config = p[CONF_DESC_bConfigurationValue]; + break; + + case DESC_INTERFACE: + /* remember current alternate setting */ + cur_alt_setting = + p[INTF_DESC_bAlternateSetting]; + break; + + case DESC_ENDPOINT: + if ((cur_config == config_index) && + (cur_alt_setting == alt_setting)) { + struct usb_dc_ep_cfg_data ep_cfg; + /* endpoint found for desired config + * and alternate setting + */ + ep_cfg.ep_type = + p[ENDP_DESC_bmAttributes]; + ep_cfg.ep_mps = + (p[ENDP_DESC_wMaxPacketSize]) | + (p[ENDP_DESC_wMaxPacketSize + 1] + << 8); + ep_cfg.ep_addr = + p[ENDP_DESC_bEndpointAddress]; + usb_dc_ep_configure(&ep_cfg); + usb_dc_ep_enable(ep_cfg.ep_addr); + } + break; + + default: + break; + } + /* skip to next descriptor */ + p += p[DESC_bLength]; + } + if (usb_dev.status_callback) + usb_dev.status_callback(USB_DC_CONFIGURED); + + return true; +} + +/* + * @brief handle a standard device request + * + * @param [in] setup The setup packet + * @param [in,out] len Pointer to data length + * @param [in,out] data_buf Data buffer + * + * @return true if the request was handled successfully + */ +static bool usb_handle_std_device_req(struct usb_setup_packet *setup, + int32_t *len, uint8_t **data_buf) +{ + bool ret = true; + uint8_t *data = *data_buf; + + switch (setup->bRequest) { + case REQ_GET_STATUS: + DBG("REQ_GET_STATUS\n"); + /* bit 0: self-powered */ + /* bit 1: remote wakeup = not supported */ + data[0] = 0; + data[1] = 0; + *len = 2; + break; + + case REQ_SET_ADDRESS: + DBG("REQ_SET_ADDRESS\n"); + usb_dc_set_address(setup->wValue); + break; + + case REQ_GET_DESCRIPTOR: + DBG("REQ_GET_DESCRIPTOR\n"); + ret = usb_get_descriptor(setup->wValue, + setup->wIndex, len, data_buf); + break; + + case REQ_GET_CONFIGURATION: + DBG("REQ_GET_CONFIGURATION\n"); + /* indicate if we are configured */ + data[0] = usb_dev.configuration; + *len = 1; + break; + + case REQ_SET_CONFIGURATION: + DBG("REQ_SET_CONFIGURATION\n"); + if (!usb_set_configuration(setup->wValue & 0xFF, 0)) { + DBG("USBSetConfiguration failed!\n"); + ret = false; + } else { + /* configuration successful, + * update current configuration + */ + usb_dev.configuration = setup->wValue & 0xFF; + } + break; + + case REQ_CLEAR_FEATURE: + DBG("REQ_CLEAR_FEATURE\n"); + break; + case REQ_SET_FEATURE: + DBG("REQ_SET_FEATURE\n"); + + if (setup->wValue == FEA_REMOTE_WAKEUP) { + /* put DEVICE_REMOTE_WAKEUP code here */ + } + + if (setup->wValue == FEA_TEST_MODE) { + /* put TEST_MODE code here */ + } + ret = false; + break; + + case REQ_SET_DESCRIPTOR: + DBG("Device req %x not implemented\n", setup->bRequest); + ret = false; + break; + + default: + DBG("Illegal device req %x\n", setup->bRequest); + ret = false; + break; + } + + return ret; +} + +/* + * @brief handle a standard interface request + * + * @param [in] setup The setup packet + * @param [in,out] len Pointer to data length + * @param [in] data_buf Data buffer + * + * @return true if the request was handled successfully + */ +static bool usb_handle_std_interface_req(struct usb_setup_packet *setup, + int32_t *len, uint8_t **data_buf) +{ + uint8_t *data = *data_buf; + + switch (setup->bRequest) { + case REQ_GET_STATUS: + /* no bits specified */ + data[0] = 0; + data[1] = 0; + *len = 2; + break; + + case REQ_CLEAR_FEATURE: + case REQ_SET_FEATURE: + /* not defined for interface */ + return false; + + case REQ_GET_INTERFACE: + /* there is only one interface, return n-1 (= 0) */ + data[0] = 0; + *len = 1; + break; + + case REQ_SET_INTERFACE: + DBG("REQ_SET_INTERFACE\n"); + *len = 0; + break; + + default: + DBG("Illegal interface req %d\n", setup->bRequest); + return false; + } + + return true; +} + +/* + * @brief handle a standard endpoint request + * + * @param [in] setup The setup packet + * @param [in,out] len Pointer to data length + * @param [in] data_buf Data buffer + * + * @return true if the request was handled successfully + */ +static bool usb_handle_std_endpoint_req(struct usb_setup_packet *setup, + int32_t *len, uint8_t **data_buf) +{ + uint8_t *data = *data_buf; + + switch (setup->bRequest) { + case REQ_GET_STATUS: + /* bit 0 = endpointed halted or not */ + usb_dc_ep_is_stalled(setup->wIndex, &data[0]); + data[1] = 0; + *len = 2; + break; + + case REQ_CLEAR_FEATURE: + if (setup->wValue == FEA_ENDPOINT_HALT) { + /* clear HALT by unstalling */ + usb_dc_ep_clear_stall(setup->wIndex); + break; + } + /* only ENDPOINT_HALT defined for endpoints */ + return false; + + case REQ_SET_FEATURE: + if (setup->wValue == FEA_ENDPOINT_HALT) { + /* set HALT by stalling */ + usb_dc_ep_set_stall(setup->wIndex); + break; + } + /* only ENDPOINT_HALT defined for endpoints */ + return false; + + case REQ_SYNCH_FRAME: + DBG("EP req %d not implemented\n", setup->bRequest); + return false; + + default: + DBG("Illegal EP req %d\n", setup->bRequest); + return false; + } + + return true; +} + + +/* + * @brief default handler for standard ('chapter 9') requests + * + * If a custom request handler was installed, this handler is called first. + * + * @param [in] setup The setup packet + * @param [in,out] len Pointer to data length + * @param [in] data_buf Data buffer + * + * @return true if the request was handled successfully + */ +static int usb_handle_standard_request(struct usb_setup_packet *setup, + int32_t *len, uint8_t **data_buf) +{ + int rc = 0; + /* try the custom request handler first */ + if ((usb_dev.custom_req_handler != NULL) && + (!usb_dev.custom_req_handler(setup, len, data_buf))) + return 0; + + switch (REQTYPE_GET_RECIP(setup->bmRequestType)) { + case REQTYPE_RECIP_DEVICE: + if (usb_handle_std_device_req(setup, len, data_buf) == false) + rc = -EINVAL; + break; + case REQTYPE_RECIP_INTERFACE: + if (usb_handle_std_interface_req(setup, len, data_buf) == false) + rc = -EINVAL; + break; + case REQTYPE_RECIP_ENDPOINT: + if (usb_handle_std_endpoint_req(setup, len, data_buf) == false) + rc = -EINVAL; + break; + default: + rc = -EINVAL; + } + return rc; +} + + +/* + * @brief Registers a callback for custom device requests + * + * In usb_register_custom_req_handler, the custom request handler gets a first + * chance at handling the request before it is handed over to the 'chapter 9' + * request handler. + * + * This can be used for example in HID devices, where a REQ_GET_DESCRIPTOR + * request is sent to an interface, which is not covered by the 'chapter 9' + * specification. + * + * @param [in] handler Callback function pointer + */ +static void usb_register_custom_req_handler(usb_request_handler handler) +{ + usb_dev.custom_req_handler = handler; +} + +/* + * @brief register a callback for device status + * + * This function registers a callback for device status. The registered callback + * is used to report changes in the status of the device controller. + * + * @param [in] cb Callback function pointer + */ +static void usb_register_status_callback(usb_status_callback cb) +{ + usb_dev.status_callback = cb; +} + +/** + * @brief turn on/off USB VBUS voltage + * + * @param on Set to false to turn off and to true to turn on VBUS + * + * @return 0 on success, negative errno code on fail + */ +static int usb_vbus_set(bool on) +{ +#if defined(USB_VUSB_EN_GPIO) + int ret = 0; + struct device *gpio_dev = device_get_binding(USB_GPIO_DRV_NAME); + + if (!gpio_dev) { + DBG("USB requires GPIO. Cannot find %s!\n", USB_GPIO_DRV_NAME); + return -ENODEV; + } + + /* Enable USB IO */ + ret = gpio_pin_configure(gpio_dev, USB_VUSB_EN_GPIO, GPIO_DIR_OUT); + if (ret) + return ret; + + ret = gpio_pin_write(gpio_dev, USB_VUSB_EN_GPIO, on == true ? 1 : 0); + if (ret) + return ret; +#endif + + return 0; +} + +int usb_set_config(struct usb_cfg_data *config) +{ + if (!config) + return -EINVAL; + + /* register descriptors */ + usb_register_descriptors(config->usb_device_description); + + /* register standard request handler */ + usb_register_request_handler(REQTYPE_TYPE_STANDARD, + &(usb_handle_standard_request), usb_dev.std_req_data); + + /* register class request handlers for each interface*/ + if (config->interface.class_handler != NULL) { + usb_register_request_handler(REQTYPE_TYPE_CLASS, + config->interface.class_handler, + config->interface.payload_data); + } + /* register class request handlers for each interface*/ + if (config->interface.custom_handler != NULL) { + usb_register_custom_req_handler( + config->interface.custom_handler); + } + + /* register status callback */ + if (config->cb_usb_status != NULL) { + usb_register_status_callback(config->cb_usb_status); + } + + return 0; +} + +int usb_deconfig(void) +{ + /* unregister descriptors */ + usb_register_descriptors(NULL); + + /* unegister standard request handler */ + usb_register_request_handler(REQTYPE_TYPE_STANDARD, NULL, NULL); + + /* unregister class request handlers for each interface*/ + usb_register_request_handler(REQTYPE_TYPE_CLASS, NULL, NULL); + + /* unregister class request handlers for each interface*/ + usb_register_custom_req_handler(NULL); + + /* unregister status callback */ + usb_register_status_callback(NULL); + + /* Reset USB controller */ + usb_dc_reset(); + + return 0; +} + +int usb_enable(struct usb_cfg_data *config) +{ + int ret; + uint32_t i; + struct usb_dc_ep_cfg_data ep0_cfg; + + if (true == usb_dev.enabled) { + return 0; + } + + /* Enable VBUS if needed */ + ret = usb_vbus_set(true); + if (ret < 0) + return ret; + + ret = usb_dc_set_status_callback(config->cb_usb_status); + if (ret < 0) + return ret; + + ret = usb_dc_attach(); + if (ret < 0) + return ret; + + /* Configure control EP */ + ep0_cfg.ep_mps = MAX_PACKET_SIZE0; + ep0_cfg.ep_type = USB_DC_EP_CONTROL; + + ep0_cfg.ep_addr = USB_CONTROL_OUT_EP0; + ret = usb_dc_ep_configure(&ep0_cfg); + if (ret < 0) + return ret; + + ep0_cfg.ep_addr = USB_CONTROL_IN_EP0; + ret = usb_dc_ep_configure(&ep0_cfg); + if (ret < 0) + return ret; + + /*register endpoint 0 handlers*/ + ret = usb_dc_ep_set_callback(USB_CONTROL_OUT_EP0, + usb_handle_control_transfer); + if (ret < 0) + return ret; + ret = usb_dc_ep_set_callback(USB_CONTROL_IN_EP0, + usb_handle_control_transfer); + if (ret < 0) + return ret; + + /*register endpoint handlers*/ + for (i = 0; i < config->num_endpoints; i++) { + ret = usb_dc_ep_set_callback(config->endpoint[i].ep_addr, + config->endpoint[i].ep_cb); + if (ret < 0) + return ret; + } + + /* enable control EP */ + ret = usb_dc_ep_enable(USB_CONTROL_OUT_EP0); + if (ret < 0) + return ret; + + ret = usb_dc_ep_enable(USB_CONTROL_IN_EP0); + if (ret < 0) + return ret; + + usb_dev.enabled = true; + + return 0; +} + +int usb_disable(void) +{ + int ret; + + if (true != usb_dev.enabled) { + /*Already disabled*/ + return 0; + } + + ret = usb_dc_detach(); + if (ret < 0) + return ret; + + /* Disable VBUS if needed */ + usb_vbus_set(false); + + usb_dev.enabled = false; + + return 0; +} + +int usb_write(uint8_t ep, const uint8_t *data, uint32_t data_len, + uint32_t *bytes_ret) +{ + return usb_dc_ep_write(ep, data, data_len, bytes_ret); +} + +int usb_read(uint8_t ep, uint8_t *data, uint32_t max_data_len, + uint32_t *ret_bytes) +{ + return usb_dc_ep_read(ep, data, max_data_len, ret_bytes); +} |