From 9af6c69a93d35b84eae54f605bd0a87b9ed5fcca Mon Sep 17 00:00:00 2001 From: Koen Zandberg Date: Thu, 23 Aug 2018 14:42:55 +0200 Subject: [PATCH] usbdev: Introduce a common API for USB peripherals This commit adds usbdev, a common API to interface USB peripheral devices. The API is split into two parts, one for the USB device itself and one for the USB endpoint. --- drivers/include/periph/usbdev.h | 559 ++++++++++++++++++++++++++++++++ 1 file changed, 559 insertions(+) create mode 100644 drivers/include/periph/usbdev.h diff --git a/drivers/include/periph/usbdev.h b/drivers/include/periph/usbdev.h new file mode 100644 index 0000000000..d5bcd69ad4 --- /dev/null +++ b/drivers/include/periph/usbdev.h @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2018 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ +/** + * @defgroup drivers_periph_usbdev usbdev - USB Device Driver API + * @ingroup drivers_periph + * @brief This is a generic low-level USB driver interface + * @{ + * + * # About + * + * usbdev specifies a common USB device API for low level USB interfaces. The + * interface is split in a definition for the USB interface hardware and for + * individual USB endpoints. + * + * # Design goals + * + * 1. Support for multiple instances on a single board + * 2. Interface optimized for MCU peripheral interfaces. + * + * # Details + * + * The driver interface is split in two separate parts. One part is a global USB + * device interface, the other is an endpoint control API. + * + * The usb device driver can manage parts of the USB interface itself such as + * the pull up resistor state or the USB speed. + * + * The endpoint control API manages a single endpoint. This allows for a modular + * approach where multiple USB functions/interfaces can be multiplexed over a + * single USB interface. Each interface can be implemented as a separate module. + * The interface handler does not have to care about the usb device itself or + * it's endpoint number. It can simply request an available endpoint from the + * usb device with the @ref usbdev_new_ep function. + * + * Data transmission is done by requesting the endpoint with a max packet size. + * Memory for this buffer is allocated from dedicated memory of the MCU USB + * peripheral or from a buffer allocated by the peripheral specific usbdev + * struct. Received data from the host ends up at this buffer automatically + * by the low level drivers. Signalling that the data at the specified address + * is ready to be reused is done with the @ref usbdev_ep_ready function by + * supplying a size of 0 for the @p len argument. + * + * For transmitting data back to the host, a similar approach is used. The data + * to be transmitted is written to the specified address and the + * @ref usbdev_ep_ready function is called with the size of the data as @p len + * argument. + * + * This approach of setting the address and only indicating new data available + * is done to allow the low level USB peripheral to use DMA to transfer the data + * from and to the MCU memory. + * + * A callback function is required for signalling events from the driver. The + * @ref USBDEV_EVENT_ESR is special in that it indicates that the USB peripheral + * had an interrupt that needs to be serviced in a non-interrupt context. This + * requires the usbdev_esr function to be called. In the case that the event is + * signalled via the @ref usbdev_t::epcb callback, the @ref usbdev_ep_esr + * should be called to service the endpoint specific events from the + * peripheral. + * + * @file + * @brief Definitions low-level USB driver interface + * + * @author Koen Zandberg + */ + +#ifndef PERIPH_USBDEV_H +#define PERIPH_USBDEV_H + +#include +#include + +#include "assert.h" +#include "usb.h" +#include "usb/usbopt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief usbdev_t forward declaration + */ +typedef struct usbdev usbdev_t; + +/** + * @brief usbdev_ep_t forward declaration + */ +typedef struct usbdev_ep usbdev_ep_t; + +/** + * @brief Statically allocated buffer space for endpoints. + * + * When the device doesn't have dedicated memory for endpoint buffers, a + * buffer of this size is allocated to contain the endpoint buffers. Only + * needs to be as big as the total buffer space required by all endpoints + */ +#ifndef USBDEV_EP_BUF_SPACE +#define USBDEV_EP_BUF_SPACE 1024 +#endif + +/** + * @brief Number of USB IN and OUT endpoints allocated + * + * Configures the number of endpoints allocated. An equal number of IN and OUT + * endpoints are allocated + */ +#ifndef USBDEV_NUM_ENDPOINTS +#define USBDEV_NUM_ENDPOINTS 8 +#endif + +/** + * @brief List of event types that can be send from the device driver to the + * upper layer + */ +typedef enum { + /** + * @brief Driver needs it's ESR (event service routine) handled + */ + USBDEV_EVENT_ESR = 1, + + /** + * @brief Host connection detected + * + * A host has connected to the device. Detection should happen by detecting + * the USB power, but other detection mechanisms might be used. + */ + USBDEV_EVENT_HOST_CONNECT, + + /** + * @brief Host disconnected from the device + * + * Similar to @ref USBDEV_EVENT_HOST_CONNECT, except that the host + * disconnected instead. + */ + USBDEV_EVENT_HOST_DISCONNECT, + + /** + * @brief Line reset occured + * + * A line reset is a host initiated USB reset to the peripheral + * + * The peripheral driver must clears the following settings before + * emitting this event: + * - Device address + * - Endpoint configuration, including stall settings + */ + USBDEV_EVENT_RESET, + + /** + * @brief Start of Frame received + * + * A Start of Frame (SoF) packet is received from the host. + */ + USBDEV_EVENT_SOF, + + /** + * @brief Transaction completed event. + * + * An endpoint must emit this event after a transaction with the host + * occured to indicate that the data in the buffer is used or new + * depending on the endpoint direction + */ + USBDEV_EVENT_TR_COMPLETE, + + /** + * @brief Transaction stall event. + * + * An endpoint should emit this event after a stall reply has been + * transmitted to the host + */ + USBDEV_EVENT_TR_STALL, + + /** + * @brief Transaction fail event. + * + * An endpoint should emit this event after a nack reply to the host. + */ + USBDEV_EVENT_TR_FAIL, + /* expand list if required */ +} usbdev_event_t; + +/** + * @brief Event callback for signaling usbdev event to upper layers + * + * @param[in] usbdev usbdev context + * @param[in] event type of the event + */ +typedef void (*usbdev_event_cb_t)(usbdev_t *usbdev, usbdev_event_t event); + +/** + * @brief Event callback for signaling endpoint events to upper layers + * + * @param[in] ep usbdev endpoint context + * @param[in] event type of the event + */ +typedef void (*usbdev_ep_event_cb_t)(usbdev_ep_t *ep, + usbdev_event_t event); + +/** + * @brief usbdev device descriptor + */ +struct usbdev { + const struct usbdev_driver *driver; /**< usbdev driver struct */ + usbdev_event_cb_t cb; /**< Event callback supplied by + * upper layer */ + usbdev_ep_event_cb_t epcb; /**< Endpoint event callback for + * upper layer */ + void *context; /**< Ptr to the thread context */ +}; + +/** + * @brief usbdev endpoint descriptor + */ +struct usbdev_ep { + usbdev_t *dev; /**< USB device this endpoint belongs to */ + uint8_t *buf; /**< Ptr to the data buffer */ + size_t len; /**< Size of the data buffer in bytes */ + usb_ep_dir_t dir; /**< Endpoint direction */ + usb_ep_type_t type; /**< Endpoint type */ + uint8_t num; /**< Endpoint number */ +}; + +/** + * @brief usbdev driver functions + * + * Helpers (such as @ref usbdev_init) are provided and should be used instead. + * Directly calling these functions is not recommended. + */ +typedef struct usbdev_driver { + + /** + * @brief Initialize the USB peripheral device + * + * This initializes the USB device but must not enable the USB pull up. + * + * @param[in] dev USB device descriptor + */ + void (*init)(usbdev_t *usbdev); + + /** + * @brief Retrieve an USB endpoint of the specified type + * + * requesting an endpoint of @ref USB_EP_TYPE_CONTROL must always return + * endpoint 0 of the specified direction + * + * @param[in] dev USB device descriptor + * @param[in] type USB endpoint type + * @param[in] dir USB endpoint direction + * @param[in] buf_len optimal USB endpoint buffer size + * + * @return ptr to the new USB endpoint descriptor + * @return NULL on error + */ + usbdev_ep_t *(*new_ep)(usbdev_t *dev, usb_ep_type_t type, usb_ep_dir_t dir, size_t buf_len); + + /** + * @brief Get an option value from a given usb device + * + * @param[in] dev USB device descriptor + * @param[in] opt option type + * @param[out] value pointer to store the option's value in + * @param[in] max_len maximal amount of byte that fit into @p value + * + * @return number of bytes written to @p value + * @return `< 0` on error 0 + */ + int (*get)(usbdev_t *usbdev, usbopt_t opt, + void *value, size_t max_len); + + /** + * @brief Set an option value for a given usb device + * + * @param[in] dev USB device descriptor + * @param[in] opt option type + * @param[in] value value to set + * @param[in] value_len the length of @p value + * + * @return number of bytes used from @p value + * @return `< 0` on error + */ + int (*set)(usbdev_t *usbdev, usbopt_t opt, + const void *value, size_t value_len); + + /** + * @brief a driver's user-space event service handler + * + * This function will be called from a USB stack's loop when being + * notified by usbdev_event_isr. + * + * @param[in] dev USB device descriptor + */ + void (*esr)(usbdev_t *dev); + + /** + * @brief Initialize the USB endpoint + * + * This initializes the USB endpoint with the settings from the + * @ref usbdev_ep. + * + * @param[in] ep USB endpoint descriptor + */ + void (*ep_init)(usbdev_ep_t *ep); + + /** + * @brief Get an option value from a given usb device endpoint + * + * @param[in] ep USB endpoint descriptor + * @param[in] opt option type + * @param[out] value pointer to store the option's value in + * @param[in] max_len maximum number of byte that fit into @p value + * + * @return number of bytes written to @p value + * @return `< 0` on error + */ + int (*ep_get)(usbdev_ep_t *ep, usbopt_ep_t opt, + void *value, size_t max_len); + + /** + * @brief Set an option value for a given usb device endpoint + * + * @param[in] ep USB endpoint descriptor + * @param[in] opt option type + * @param[in] value pointer to the value + * @param[in] value_len the length of @p value + * + * @return number of bytes used from @p value + * @return `< 0` on error + */ + int (*ep_set)(usbdev_ep_t *ep, usbopt_ep_t opt, + const void *value, size_t value_len); + + /** + * @brief an endpoint's user-space event handler + * + * Must be called in response to an @ref USBDEV_EVENT_ESR event in + * userspace context. + * + * @param[in] ep USB endpoint descriptor to service + */ + void (*ep_esr)(usbdev_ep_t *ep); + + /** + * @brief Signal data buffer ready for data transmission + * + * This clears the stall setting in the endpoint if that is enabled. + * + * @param[in] ep USB endpoint descriptor + * @param[in] len length of the data to be transmitted + */ + int (*ready)(usbdev_ep_t *ep, size_t len); +} usbdev_driver_t; + +/** + * @brief Initialize the USB peripheral device + * + * @see @ref usbdev_driver_t::init + * + * @pre `(dev != NULL)` + * + * @param dev usb device to initialize + */ +static inline void usbdev_init(usbdev_t *dev) +{ + assert(dev); + dev->driver->init(dev); +} + +/** + * @brief Retrieve an USB endpoint of the specified type + * + * @see @ref usbdev_driver_t::new_ep + * + * @pre `(dev != NULL)` + * + * @param[in] dev USB device descriptor + * @param[in] type USB endpoint type + * @param[in] dir USB endpoint direction + * @param[in] buf_len optimal USB endpoint buffer size + * + * @return ptr to the new USB endpoint descriptor + * @return NULL on error + */ +static inline usbdev_ep_t * usbdev_new_ep(usbdev_t *dev, usb_ep_type_t type, + usb_ep_dir_t dir, size_t buf_len) +{ + assert(dev); + return dev->driver->new_ep(dev, type, dir, buf_len); +} + +/** + * @brief Get an option value from a given usb device + * + * @see @ref usbdev_driver_t::get + * + * @pre `(dev != NULL)` + * + * @param[in] dev USB device descriptor + * @param[in] opt option type + * @param[out] value pointer to store the option's value in + * @param[in] max_len maximal amount of byte that fit into @p value + * + * @return number of bytes written to @p value + * @return `< 0` on error 0 + */ +static inline int usbdev_get(usbdev_t *dev, usbopt_ep_t opt, + void *value, size_t max_len) +{ + assert(dev); + return dev->driver->get(dev, opt, value, max_len); +} + +/** + * @brief Set an option value for a given usb device + * + * @see @ref usbdev_driver_t::set + * + * @pre `(dev != NULL)` + * + * @param[in] dev USB device descriptor + * @param[in] opt option type + * @param[in] value value to set + * @param[in] value_len the length of @p value + * + * @return number of bytes used from @p value + * @return `< 0` on error + */ +static inline int usbdev_set(usbdev_t *dev, usbopt_ep_t opt, + const void *value, size_t value_len) +{ + assert(dev); + return dev->driver->set(dev, opt, value, value_len); +} + +/** + * @brief a driver's user-space event service handler + * + * @see @ref usbdev_driver_t::esr + * + * @pre `(dev != NULL)` + * + * @param[in] dev USB device descriptor + */ +static inline void usbdev_esr(usbdev_t *dev) +{ + assert(dev); + dev->driver->esr(dev); +} + +/** + * @brief Initialize the USB endpoint + * + * @see @ref usbdev_driver_t::ep_init + * + * @pre `(ep != NULL)` + * @pre `(ep->dev != NULL)` + * + * @param[in] ep USB endpoint descriptor + */ +static inline void usbdev_ep_init(usbdev_ep_t *ep) +{ + assert(ep); + assert(ep->dev); + ep->dev->driver->ep_init(ep); +} + +/** + * @brief Get an option value from a given usb device endpoint + * + * @see @ref usbdev_driver_t::ep_get + * + * @pre `(ep != NULL)` + * @pre `(ep->dev != NULL)` + * + * @param[in] ep USB endpoint descriptor + * @param[in] opt option type + * @param[out] value pointer to store the option's value in + * @param[in] max_len maximum number of byte that fit into @p value + * + * @return number of bytes written to @p value + * @return `< 0` on error + */ +static inline int usbdev_ep_get(usbdev_ep_t *ep, usbopt_ep_t opt, + void *value, size_t max_len) +{ + assert(ep); + assert(ep->dev); + return ep->dev->driver->ep_get(ep, opt, value, max_len); +} + +/** + * @brief Set an option value for a given usb device endpoint + * + * @see @ref usbdev_driver_t::ep_set + * + * @pre `(ep != NULL)` + * @pre `(ep->dev != NULL)` + * + * @param[in] ep USB endpoint descriptor + * @param[in] opt option type + * @param[in] value pointer to the value + * @param[in] value_len the length of @p value + * + * @return number of bytes used from @p value + * @return `< 0` on error + */ +static inline int usbdev_ep_set(usbdev_ep_t *ep, usbopt_ep_t opt, + const void *value, size_t value_len) +{ + assert(ep); + assert(ep->dev); + return ep->dev->driver->ep_set(ep, opt, value, value_len); +} + +/** + * @brief an endpoint's user-space event handler + * + * @see @ref usbdev_driver_t::ep_esr + * + * @pre `(ep != NULL)` + * @pre `(ep->dev != NULL)` + * + * @param[in] ep USB endpoint descriptor to service + */ +static inline void usbdev_ep_esr(usbdev_ep_t *ep) +{ + assert(ep); + assert(ep->dev); + ep->dev->driver->ep_esr(ep); +} + +/** + * @brief Signal data buffer ready for data transmission + * + * @see @ref usbdev_driver_t::ready + * + * @pre `(ep != NULL)` + * @pre `(ep->dev != NULL)` + * + * @param[in] ep USB endpoint descriptor + * @param[in] len length of the data to be transmitted + */ +static inline int usbdev_ep_ready(usbdev_ep_t *ep, size_t len) +{ + assert(ep); + assert(ep->dev); + return ep->dev->driver->ready(ep, len); +} + +#ifdef __cplusplus +} +#endif + +#endif /* PERIPH_USBDEV_H */ +/** @} */