diff --git a/Makefile.dep b/Makefile.dep index 560384edeb..444ee863aa 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -832,6 +832,12 @@ ifneq (,$(filter tlsf-malloc,$(USEMODULE))) USEPKG += tlsf endif +ifneq (,$(filter usbus,$(USEMODULE))) + FEATURES_REQUIRED += periph_usbdev + USEMODULE += core_thread_flags + USEMODULE += event +endif + ifneq (,$(filter uuid,$(USEMODULE))) USEMODULE += hashes USEMODULE += random diff --git a/sys/Makefile b/sys/Makefile index 308103ba94..9f94ead66e 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -145,6 +145,9 @@ endif ifneq (,$(filter bluetil_%,$(USEMODULE))) DIRS += net/ble/bluetil endif +ifneq (,$(filter usbus usbus_%,$(USEMODULE))) + DIRS += usb/usbus +endif DIRS += $(dir $(wildcard $(addsuffix /Makefile, $(USEMODULE)))) diff --git a/sys/include/usb/usbus.h b/sys/include/usb/usbus.h new file mode 100644 index 0000000000..982b29a8ae --- /dev/null +++ b/sys/include/usb/usbus.h @@ -0,0 +1,533 @@ +/* + * 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 usb_usbus USBUS device and endpoint manager + * @ingroup usb + * @brief USBUS (Universal Serial Bus Unified Stack), USB device + * management interface + * + * @{ + * + * @file + * @brief USBUS basic interface + * + * @author Koen Zandberg + */ + +#ifndef USB_USBUS_H +#define USB_USBUS_H + +#include +#include + +#include "clist.h" +#include "event.h" +#include "kernel_types.h" +#include "msg.h" +#include "thread.h" + +#include "usb.h" +#include "periph/usbdev.h" +#include "usb/descriptor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief USBUS thread stack size + */ +#ifndef USBUS_STACKSIZE +#define USBUS_STACKSIZE (THREAD_STACKSIZE_DEFAULT) +#endif + +/** + * @brief USBUS thread priority + */ +#ifndef USBUS_PRIO +#define USBUS_PRIO (THREAD_PRIORITY_MAIN - 6) +#endif + +/** + * @brief USBUS thread name + */ +#define USBUS_TNAME "usbus" + +/** + * @brief USBUS auto attach setting + * + * When set, the USBUS thread will automatically enable the USB pull-up + * resistor after initializing the thread. This will signal to the host + * that the USB peripheral is ready for use. + */ +#ifndef USBUS_AUTO_ATTACH +#define USBUS_AUTO_ATTACH (1) +#endif + +/** + * @brief USBUS endpoint 0 buffer size + * + * This configures the buffer size of the control endpoint. Unless you transfer + * large amount of data often over the control endpoint, a minimal size should + * be sufficient + */ +#ifndef USBUS_EP0_SIZE +#define USBUS_EP0_SIZE 64 +#endif + +/** + * @name USBUS thread flags + * + * Thread flags used by the USBUS thread. @ref THREAD_FLAG_EVENT is also used, + * but defined elsewhere + * @{ + */ +#define USBUS_THREAD_FLAG_USBDEV (0x02) /**< usbdev esr needs handling */ +#define USBUS_THREAD_FLAG_USBDEV_EP (0x04) /**< One or more endpoints requires + servicing */ +/** @} */ + +/** + * @name USBUS handler subscription flags + * + * @{ + */ +#define USBUS_HANDLER_FLAG_RESET (0x0001) /**< Report reset event */ +#define USBUS_HANDLER_FLAG_SOF (0x0002) /**< Report SOF events */ +#define USBUS_HANDLER_FLAG_SUSPEND (0x0004) /**< Report suspend events */ +#define USBUS_HANDLER_FLAG_RESUME (0x0008) /**< Report resume from suspend */ +#define USBUS_HANDLER_FLAG_TR_FAIL (0x0010) /**< Report transfer fail */ +#define USBUS_HANDLER_FLAG_TR_STALL (0x0020) /**< Report transfer stall complete */ +/** @} */ + +/** + * @brief USB handler events + */ +typedef enum { + USBUS_EVENT_USB_RESET, /**< USB reset event */ + USBUS_EVENT_USB_SOF, /**< USB start of frame received */ + USBUS_EVENT_USB_SUSPEND, /**< USB suspend condition detected */ + USBUS_EVENT_USB_RESUME, /**< USB resume condition detected */ +} usbus_event_usb_t; + +/** + * @brief USB endpoint transfer status events + */ +typedef enum { + USBUS_EVENT_TRANSFER_COMPLETE, /**< Transfer succesfully completed */ + USBUS_EVENT_TRANSFER_FAIL, /**< Transfer nack replied by peripheral */ + USBUS_EVENT_TRANSFER_STALL, /**< Transfer stall replied by peripheral */ +} usbus_event_transfer_t; + +/** + * @brief state machine states for the global USBUS thread + */ +typedef enum { + USBUS_STATE_DISCONNECT, /**< Device is disconnected from the host */ + USBUS_STATE_RESET, /**< Reset condition received */ + USBUS_STATE_ADDR, /**< Address configured */ + USBUS_STATE_CONFIGURED, /**< Peripheral is configured */ + USBUS_STATE_SUSPEND, /**< Peripheral is suspended by the host */ +} usbus_state_t; + +/** + * @brief USBUS setup request state machine + */ +typedef enum { + USBUS_SETUPRQ_READY, /**< Ready for new setup request */ + USBUS_SETUPRQ_INDATA, /**< Request received with expected DATA IN stage */ + USBUS_SETUPRQ_OUTACK, /**< Expecting a zero-length ack out request + from the host */ + USBUS_SETUPRQ_OUTDATA, /**< Data OUT expected */ + USBUS_SETUPRQ_INACK, /**< Expecting a zero-length ack in request + from the host */ +} usbus_setuprq_state_t; + +/** + * @brief USBUS string type + */ +typedef struct usbus_string { + struct usbus_string *next; /**< Ptr to the next registered string */ + const char *str; /**< C string to use as content */ + uint16_t idx; /**< USB string index */ +} usbus_string_t; + +/** + * @brief USBUS context forward declaration + */ +typedef struct usbus usbus_t; + +/** + * @brief USBUS event handler forward declaration + */ +typedef struct usbus_handler usbus_handler_t; + +/** + * @brief Header length types for USB descriptor generators + */ +typedef enum { + USBUS_HDR_LEN_FIXED, /**< Header always generates a fixed length */ + USBUS_HDR_LEN_FUNC, /**< Header length is calculated by a function */ +} usbus_hdr_len_type_t; + +/** + * @brief USBUS header generator function pointers + */ +typedef struct { + /** + * @brief function pointer to retrieve the header content of this header + * generator + * + * @param usbus The usbus context + * @param arg Additional argument for the header generator + * + * @return Length of the generated header + */ + size_t (*get_header)(usbus_t *usbus, void *arg); + union { + /** + * @brief USBUS generic header generator generated length + * + * Must return the length of the header that will be generated by + * @ref get_header + * + * @param usbus The usbus context + * @param arg Additional argument for the header generator + * + * @return Length of the generated header + */ + size_t (*get_header_len)(usbus_t *usbus, void *arg); + size_t fixed_len; /**< length of the header if it is a fixed length */ + } len; /**< Fixed or generated length of the header */ + usbus_hdr_len_type_t len_type; /**< Either USBUS_HDR_LEN_FIXED or USBUS_HDR_LEN_FUNC */ +} usbus_hdr_gen_funcs_t; + +/** + * @brief USBUS header generator + * + * The functions are called to allow custom modules to define their own + * headers in addition to the USB descriptor. The top level (@ref usbus_t), the + * interface (@ref usbus_interface_t), interface alternative settings + * (@ref usbus_interface_alt_t) and endpoints (@ref usbus_endpoint_t) allow for + * generating additional headers + */ +typedef struct usbus_hdr_gen { + struct usbus_hdr_gen *next; /**< ptr to the next header generator */ + const usbus_hdr_gen_funcs_t *funcs; /**< Function pointers */ + void *arg; /**< Extra context argument for the + headers functions */ +} usbus_hdr_gen_t; + +/** + * @brief USBUS endpoint context + */ +typedef struct usbus_endpoint { + struct usbus_endpoint *next; /**< Next endpoint in the + @ref usbus_interface_t list of + endpoints */ + usbus_hdr_gen_t *hdr_gen; /**< Optional additional header generator */ + usbdev_ep_t *ep; /**< ptr to the matching usbdev endpoint */ + uint16_t maxpacketsize; /**< Max packet size of this endpoint */ + uint8_t interval; /**< Poll interval for interrupt endpoints */ + bool active; /**< If the endpoint should be activated after + reset */ +} usbus_endpoint_t; + +/** + * @brief USBUS interface alternative setting + * + * Used for specifying alternative interfaces for an @ref usbus_interface_t + */ +typedef struct usbus_interface_alt { + struct usbus_interface_alt *next; /**< Next alternative setting */ + usbus_hdr_gen_t *hdr_gen; /**< Optional additional header + generator */ + usbus_endpoint_t *ep; /**< List of associated endpoints for + this alternative setting */ +} usbus_interface_alt_t; + +/** + * @brief USBUS interface + */ +typedef struct usbus_interface { + struct usbus_interface *next; /**< Next interface (set by USBUS during + registration) */ + usbus_hdr_gen_t *hdr_gen; /**< Optional additional header + generators */ + usbus_endpoint_t *ep; /**< Linked list of endpoints belonging + to this interface */ + struct usbus_interface_alt *alts; /**< List of alt settings */ + usbus_handler_t *handler; /**< Handlers for this interface */ + usbus_string_t *descr; /**< Descriptor string */ + uint16_t idx; /**< Interface index, (set by USBUS + during registration */ + uint8_t class; /**< USB interface class */ + uint8_t subclass; /**< USB interface subclass */ + uint8_t protocol; /**< USB interface protocol */ +} usbus_interface_t; + +/** + * @brief USBUS event handler function pointers + */ +typedef struct usbus_handler_driver { + + /** + * @brief Initialize the event handler + * + * This function is called in the USBUS thread context to initialize the + * event handler + * + * @param usbus USBUS context + * @param handler handler context + */ + void (*init)(usbus_t * usbus, struct usbus_handler *handler); + + /** + * @brief event handler function + * + * This function is passed USBUS events + * + * @param usbus USBUS context + * @param handler handler context + * @param event @ref usbus_event_usb_t event to handle + */ + void (*event_handler)(usbus_t * usbus, struct usbus_handler *handler, + usbus_event_usb_t event); + + /** + * @brief transfer handler function + * + * This function receives transfer based events + * + * @param usbus USBUS context + * @param handler handler context + * @param ep usbdev endpoint that triggered the event + * @param event @ref usbus_event_transfer_t event + */ + void (*transfer_handler)(usbus_t * usbus, struct usbus_handler *handler, + usbdev_ep_t *ep, usbus_event_transfer_t event); + + /** + * @brief setup request handler function + * + * This function receives USB setup requests from the USBUS stack. + * + * @param usbus USBUS context + * @param handler handler context + * @param state setup request state + * @param setup setup packet + * + * @return Size of the returned data when the request is handled + * @return negative to have the stack return an USB stall to the + * host + * @return zero when the request is not handled by this handler + */ + int (*setup_handler)(usbus_t * usbus, struct usbus_handler *handler, + usbus_setuprq_state_t state, usb_setup_t *request); +} usbus_handler_driver_t; + +/** + * @brief USBUS handler struct + * + * Inherit from this struct for custom USB functionality + */ +struct usbus_handler { + struct usbus_handler *next; /**< List of handlers (to be used by + @ref usbus_t) */ + const usbus_handler_driver_t *driver; /**< driver for this handler */ + usbus_interface_t *iface; /**< Interface this handler belongs + to */ + uint32_t flags; /**< Report flags */ +}; + +/** + * @brief USBUS context struct + */ +struct usbus { + usbus_string_t manuf; /**< Manufacturer string */ + usbus_string_t product; /**< Product string */ + usbus_string_t config; /**< Configuration string */ + usbus_endpoint_t ep_out[USBDEV_NUM_ENDPOINTS]; /**< USBUS OUT endpoints */ + usbus_endpoint_t ep_in[USBDEV_NUM_ENDPOINTS]; /**< USBUS IN endpoints */ + event_queue_t queue; /**< Event queue */ + usbdev_t *dev; /**< usb phy device of the usb manager */ + usbus_handler_t *control; /**< Ptr to the control endpoint handler */ + usbus_hdr_gen_t *hdr_gen; /**< Top level header generators */ + usbus_string_t *strings; /**< List of descriptor strings */ + usbus_interface_t *iface; /**< List of USB interfaces */ + usbus_handler_t *handlers; /**< List of event callback handlers */ + uint32_t ep_events; /**< bitflags with endpoint event state */ + kernel_pid_t pid; /**< PID of the usb manager's thread */ + uint16_t str_idx; /**< Number of strings registered */ + usbus_state_t state; /**< Current state */ + usbus_state_t pstate; /**< state to recover to from suspend */ + uint8_t addr; /**< Address of the USB peripheral */ +}; + +/** + * @brief Submit an event to the usbus thread + * + * @param usbus USBUS context + * @param event event to post + */ +static inline void usbus_event_post(usbus_t *usbus, event_t *event) +{ + event_post(&usbus->queue, event); +} + +/** + * @brief Add a string descriptor to the USBUS thread context + * + * @param[in] usbus USBUS context + * @param[in] desc string descriptor context + * @param[in] str C string to use + * + * @return Index of the string descriptor + */ +uint16_t usbus_add_string_descriptor(usbus_t *usbus, usbus_string_t *desc, + const char *str); + +/** + * @brief Add an interface to the USBUS thread context + * + * @param[in] usbus USBUS context + * @param[in] iface USB interface to add + * + * @return interface index + */ +uint16_t usbus_add_interface(usbus_t *usbus, usbus_interface_t *iface); + +/** + * @brief Add an endpoint to the specified interface + * + * An @ref usbdev_ep_t is requested from the low level peripheral matching the + * type, direction and buffer length. + * + * @param[in] usbus USBUS context + * @param[in] iface USB interface to add the endpoint to + * @param[in] type USB endpoint type + * @param[in] dir USB endpoint direction + * @param[in] len Buffer space for the endpoint to allocate + * + * @return Pointer to the endpoint struct + * @return NULL when no endpoint available + */ +usbus_endpoint_t *usbus_add_endpoint(usbus_t *usbus, usbus_interface_t *iface, + usb_ep_type_t type, usb_ep_dir_t dir, + size_t len); + +/** + * @brief Add a generator for generating additional top level USB descriptor + * content + * + * @param[in] usbus USBUS context + * @param[in] hdr_gen Header generator to add + */ +void usbus_add_conf_descriptor(usbus_t *usbus, usbus_hdr_gen_t *hdr_gen); + +/** + * @brief Add an event handler to the USBUS context + * + * The handler must also belong to an interface + * (@ref usbus_interface_t::handler must point to @p handler) for + * transfer event callbacks to work. + * + * @param[in] usbus USBUS context + * @param[in] handler event handler to register + */ +void usbus_register_event_handler(usbus_t *usbus, usbus_handler_t *handler); + +/** + * @brief Initialize an USBUS context. + * + * @param[in] usbus context to initialize + * @param[in] usbdev usbdev peripheral to use by USBUS + */ +void usbus_init(usbus_t *usbus, usbdev_t *usbdev); + +/** + * @brief Create and start the USBUS thread + * + * @param[in] stack The stack for the USBUS thread. + * @param[in] stacksize Size of @p stack. + * @param[in] priority Priority for the USBUS thread. + * @param[in] name Name for the USBUS thread May be NULL. + * @param[in] usbus context to start the thread for + */ +void usbus_create(char *stack, int stacksize, char priority, + const char *name, usbus_t *usbus); + +/** + * @brief Enable an endpoint + * + * @note must only be used before the usb peripheral is attached to the host + * + * @param[in] ep endpoint to enable + */ +static inline void usbus_enable_endpoint(usbus_endpoint_t *ep) +{ + ep->active = true; +} + +/** + * @brief Disable an endpoint + * + * @note must only be used before the usb peripheral is attached to the host + * + * @param[in] ep endpoint to disable + */ +static inline void usbus_disable_endpoint(usbus_endpoint_t *ep) +{ + ep->active = false; +} + +/** + * @brief enable a specific handler flag + * + * @param[in] handler handler to enable the flag for + * @param[in] flag flag to enable + */ +static inline void usbus_handler_set_flag(usbus_handler_t *handler, + uint32_t flag) +{ + handler->flags |= flag; +} + +/** + * @brief disable a specific handler flag + * + * @param[in] handler handler to disable the flag for + * @param[in] flag flag to disable + */ +static inline void usbus_handler_remove_flag(usbus_handler_t *handler, + uint32_t flag) +{ + handler->flags &= ~flag; +} + +/** + * @brief check if a specific handler flag is set + * + * @param[in] handler handler to check for flag + * @param[in] flag flag to check + * + * @return true if the flag is set for this handler + */ +static inline bool usbus_handler_isset_flag(usbus_handler_t *handler, + uint32_t flag) +{ + return handler->flags & flag; +} + + +#ifdef __cplusplus +} +#endif +#endif /* USB_USBUS_H */ +/** @} */ diff --git a/sys/include/usb/usbus/control.h b/sys/include/usb/usbus/control.h new file mode 100644 index 0000000000..cdf1cb9552 --- /dev/null +++ b/sys/include/usb/usbus/control.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2019 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. + */ + +/** + * @ingroup usb_usbus + * @brief USBUS control endpoint module + * + * @{ + * + * @file + * @brief USBUS control endpoint module interface + * + * @author Koen Zandberg + */ + +#ifndef USB_USBUS_CONTROL_H +#define USB_USBUS_CONTROL_H + +#include "usb/usbus.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief helper struct to divide control messages in multiple parts + */ +typedef struct { + size_t start; /**< Start offset of the current part */ + size_t cur; /**< Current position in the message */ + size_t len; /**< Length of the full message */ + size_t transfered; /**< Number of bytes transfered */ + size_t reqlen; /**< Maximum length of the request */ +} usbus_control_slicer_t; + +/** + * @brief Endpoint zero event handler + */ +typedef struct { + usbus_handler_t handler; /**< Inherited generic handler */ + usb_setup_t setup; /**< Last received setup packet */ + usbus_setuprq_state_t setup_state; /**< Setup request state machine */ + usbus_control_slicer_t slicer; /**< Slicer for multipart control + messages */ + usbdev_ep_t *out; /**< EP0 out endpoint */ + usbdev_ep_t *in; /**< EP0 in endpoint */ +} usbus_control_handler_t; + +/** + * @brief Initialize the control endpoint handler + * + * @param[in] usbus USBUS context + * @param[in] handler control handler to initialize + */ +void usbus_control_init(usbus_t *usbus, usbus_control_handler_t *handler); + +/** + * @brief Helper function for adding bytes to the current control message part + * + * @param[in] usbus USBUS context + * @param[in] buf Buffer to add bytes from + * @param[in] len Length of @p buf + * + * @return Actual number of bytes written + */ +size_t usbus_control_slicer_put_bytes(usbus_t *usbus, const uint8_t *buf, + size_t len); + +/** + * @brief Helper function for adding single bytes to the current control + * message part + * + * @param[in] usbus USBUS context + * @param[in] c byte to add + * + * @return Actual number of bytes written + */ +size_t usbus_control_slicer_put_char(usbus_t *usbus, char c); + +/** + * @brief Helper function to signal the end of the control message + * + * @param[in] usbus USBUS context + */ +void usbus_control_slicer_ready(usbus_t *usbus); + +/** + * @brief Initialize the next slice of the control message + * + * @param[in] usbus USBUS context + * + * @return 1 when there is a next slice + * @return 0 when the data is fully transfered + */ +int usbus_control_slicer_nextslice(usbus_t *usbus); + +#ifdef __cplusplus +} +#endif +#endif /* USB_USBUS_CONTROL_H */ +/** @} */ diff --git a/sys/include/usb/usbus/fmt.h b/sys/include/usb/usbus/fmt.h new file mode 100644 index 0000000000..e4593d81f9 --- /dev/null +++ b/sys/include/usb/usbus/fmt.h @@ -0,0 +1,54 @@ +/* + * 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 usb_usbus_fmt USBUS descriptor formatter functions + * @ingroup usb_usbus + * + * @{ + * + * @file + * @brief USBUS descriptor formatter functions + * + * @author Koen Zandberg + */ + +#ifndef USB_USBUS_FMT_H +#define USB_USBUS_FMT_H + +#include +#include +#include "usb/usbus.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief generator for the USB configuration descriptor + * + * @param[in] usbus USBUS context + * + * @return the generated descriptor size in bytes + */ +size_t usbus_fmt_hdr_conf(usbus_t *usbus); + +/** + * @brief generator for the USB device descriptor + * + * @param[in] usbus USBUS context + * + * @return the generated descriptor size in bytes + */ +size_t usbus_fmt_hdr_dev(usbus_t *usbus); + +#ifdef __cplusplus +} +#endif +#endif /* USB_USBUS_FMT_H */ +/** @} */ diff --git a/sys/usb/usbus/Makefile b/sys/usb/usbus/Makefile new file mode 100644 index 0000000000..b7b193117f --- /dev/null +++ b/sys/usb/usbus/Makefile @@ -0,0 +1,4 @@ +SRCS := usbus.c +SRCS += usbus_hdrs.c + +include $(RIOTBASE)/Makefile.base diff --git a/sys/usb/usbus/usbus.c b/sys/usb/usbus/usbus.c new file mode 100644 index 0000000000..830b8fa156 --- /dev/null +++ b/sys/usb/usbus/usbus.c @@ -0,0 +1,361 @@ +/* + * 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. + */ + +/** + * @ingroup usb_usbus + * @{ + * @file + * @brief USBUS USB manager thread, handles USB interaction + * + * @author Koen Zandberg + * @} + */ + +#include "bitarithm.h" +#include "event.h" +#include "thread.h" +#include "thread_flags.h" +#include "periph/usbdev.h" +#include "usb/descriptor.h" +#include "usb/usbus.h" +#include "usb/usbus/fmt.h" +#include "usb/usbus/control.h" + +#include "usb.h" +#include "cpu.h" + +#include +#include +#include + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define _USBUS_MSG_QUEUE_SIZE (16) + +/* Forward declaration of the generic USBUS event callback */ +static void _event_cb(usbdev_t *usbdev, usbdev_event_t event); +/* Forward declaration of the endpoint USBUS event callback */ +static void _event_ep_cb(usbdev_ep_t *ep, usbdev_event_t event); + +static void *_usbus_thread(void *args); + +void usbus_init(usbus_t *usbus, usbdev_t *usbdev) +{ + memset(usbus, 0, sizeof(usbus_t)); + usbus->dev = usbdev; +} + +void usbus_create(char *stack, int stacksize, char priority, + const char *name, usbus_t *usbus) +{ + int res = thread_create(stack, stacksize, priority, THREAD_CREATE_STACKTEST, + _usbus_thread, (void *)usbus, name); + + (void)res; + assert(res > 0); +} + +uint16_t usbus_add_string_descriptor(usbus_t *usbus, usbus_string_t *desc, + const char *str) +{ + desc->next = usbus->strings; + usbus->strings = desc; + desc->idx = usbus->str_idx++; + desc->str = str; + DEBUG("usbus: Adding string descriptor number %u for: \"%s\"\n", desc->idx, + str); + return desc->idx; +} + +void usbus_add_conf_descriptor(usbus_t *usbus, usbus_hdr_gen_t *hdr_gen) +{ + hdr_gen->next = usbus->hdr_gen; + usbus->hdr_gen = hdr_gen; +} + +static usbus_handler_t *_ep_to_handler(usbus_t *usbus, usbdev_ep_t *ep) +{ + if (ep->num == 0) { + return usbus->handlers; + } + for (usbus_interface_t *iface = usbus->iface; iface; iface = iface->next) { + for (usbus_endpoint_t *pep = iface->ep; pep; pep = pep->next) { + if (pep->ep == ep) { + return iface->handler; + } + } + for (usbus_interface_alt_t *alt = iface->alts; alt; alt = alt->next) { + for (usbus_endpoint_t *pep = alt->ep; pep; pep = pep->next) { + if (pep->ep == ep) { + return iface->handler; + } + } + } + } + return NULL; +} + +uint16_t usbus_add_interface(usbus_t *usbus, usbus_interface_t *iface) +{ + /* While it is possible to us clist.h here, this results in less flash + * usages. Furthermore, the O(1) append is not really necessary as this is + * only used at init */ + uint16_t idx = 0; + usbus_interface_t *last = usbus->iface; + + if (last) { + idx++; + while (last->next) { + last = last->next; + idx++; + } + last->next = iface; + } + else { + usbus->iface = iface; + } + iface->idx = idx; + return idx; +} + +void usbus_register_event_handler(usbus_t *usbus, usbus_handler_t *handler) +{ + /* See note above for reasons against clist.h */ + usbus_handler_t *last = usbus->handlers; + + if (last) { + while (last->next) { + last = last->next; + } + last->next = handler; + } + else { + usbus->handlers = handler; + } +} + +usbus_endpoint_t *usbus_add_endpoint(usbus_t *usbus, usbus_interface_t *iface, + usb_ep_type_t type, usb_ep_dir_t dir, + size_t len) +{ + usbus_endpoint_t *ep = NULL; + usbdev_ep_t *usbdev_ep = usbdev_new_ep(usbus->dev, type, dir, len); + + if (usbdev_ep) { + ep = dir == USB_EP_DIR_IN ? &usbus->ep_in[usbdev_ep->num] + : &usbus->ep_out[usbdev_ep->num]; + ep->maxpacketsize = usbdev_ep->len; + ep->ep = usbdev_ep; + if (iface) { + ep->next = iface->ep; + iface->ep = ep; + } + } + return ep; +} + +static inline uint32_t _get_ep_bitflag(usbdev_ep_t *ep) +{ + /* Endpoint activity bit flag, lower USBDEV_NUM_ENDPOINTS bits are + * useb as OUT endpoint flags, upper bit are IN endpoints */ + return 1 << ((ep->dir == USB_EP_DIR_IN ? USBDEV_NUM_ENDPOINTS + : 0x00) + ep->num); +} + +static uint32_t _get_and_reset_ep_events(usbus_t *usbus) +{ + unsigned state = irq_disable(); + uint32_t res = usbus->ep_events; + + usbus->ep_events = 0; + irq_restore(state); + return res; +} + +static void _signal_handlers(usbus_t *usbus, uint16_t flag, + uint16_t msg) +{ + for (usbus_handler_t *handler = usbus->handlers; + handler; handler = handler->next) { + if (handler->flags & flag) { + handler->driver->event_handler(usbus, handler, msg); + } + } +} + +static void _usbus_init_handlers(usbus_t *usbus) +{ + for (usbus_handler_t *handler = usbus->handlers; + handler; handler = handler->next) { + handler->driver->init(usbus, handler); + } +} + +static void *_usbus_thread(void *args) +{ + usbus_t *usbus = (usbus_t *)args; + usbus_control_handler_t ep0_handler; + + event_queue_init(&usbus->queue); + + usbus->control = &ep0_handler.handler; + + usbus_control_init(usbus, &ep0_handler); + + usbdev_t *dev = usbus->dev; + usbus->pid = sched_active_pid; + usbus->addr = 0; + usbus->iface = NULL; + usbus->str_idx = 1; + DEBUG("usbus: starting thread %i\n", sched_active_pid); + /* setup the link-layer's message queue */ + /* register the event callback with the device driver */ + dev->cb = _event_cb; + dev->epcb = _event_ep_cb; + /* initialize low-level driver */ + dev->context = usbus; + usbdev_init(dev); + + usbus_add_string_descriptor(usbus, &usbus->config, + USB_CONFIG_CONFIGURATION_STR); + usbus_add_string_descriptor(usbus, &usbus->product, USB_CONFIG_PRODUCT_STR); + usbus_add_string_descriptor(usbus, &usbus->manuf, USB_CONFIG_MANUF_STR); + + usbus->state = USBUS_STATE_DISCONNECT; + + /* Initialize handlers */ + _usbus_init_handlers(usbus); + +#if (USBUS_AUTO_ATTACH) + static const usbopt_enable_t _enable = USBOPT_ENABLE; + usbdev_set(dev, USBOPT_ATTACH, &_enable, + sizeof(usbopt_enable_t)); +#endif + + while (1) { + thread_flags_t flags = thread_flags_wait_any( + USBUS_THREAD_FLAG_USBDEV | + USBUS_THREAD_FLAG_USBDEV_EP | + THREAD_FLAG_EVENT + ); + if (flags & USBUS_THREAD_FLAG_USBDEV) { + usbdev_esr(dev); + } + if (flags & USBUS_THREAD_FLAG_USBDEV_EP) { + uint32_t events = _get_and_reset_ep_events(usbus); + while (events) { + unsigned num = bitarithm_lsb(events); + events &= ~(1 << num); + if (num < USBDEV_NUM_ENDPOINTS) { + /* OUT endpoint */ + usbdev_ep_esr(usbus->ep_out[num].ep); + } + else { + /* IN endpoint */ + usbdev_ep_esr(usbus->ep_in[num - USBDEV_NUM_ENDPOINTS].ep); + } + } + + } + if (flags & THREAD_FLAG_EVENT) { + event_t *event = event_get(&usbus->queue); + if (event) { + event->handler(event); + } + } + + } + return NULL; +} + +/* USB event callback */ +static void _event_cb(usbdev_t *usbdev, usbdev_event_t event) +{ + usbus_t *usbus = (usbus_t *)usbdev->context; + + if (event == USBDEV_EVENT_ESR) { + thread_flags_set((thread_t *)thread_get(usbus->pid), + USBUS_THREAD_FLAG_USBDEV); + } + else { + usbus_event_usb_t msg; + uint16_t flag; + switch (event) { + case USBDEV_EVENT_RESET: + usbus->state = USBUS_STATE_RESET; + usbus->addr = 0; + usbdev_set(usbus->dev, USBOPT_ADDRESS, &usbus->addr, + sizeof(uint8_t)); + flag = USBUS_HANDLER_FLAG_RESET; + msg = USBUS_EVENT_USB_RESET; + break; + case USBDEV_EVENT_SUSPEND: + DEBUG("usbus: USB suspend detected\n"); + usbus->pstate = usbus->state; + usbus->state = USBUS_STATE_SUSPEND; + flag = USBUS_HANDLER_FLAG_SUSPEND; + msg = USBUS_EVENT_USB_SUSPEND; + break; + case USBDEV_EVENT_RESUME: + DEBUG("usbus: USB resume detected\n"); + usbus->state = usbus->pstate; + flag = USBUS_HANDLER_FLAG_RESUME; + msg = USBUS_EVENT_USB_RESUME; + break; + default: + DEBUG("usbus: unhandled event %x\n", event); + return; + } + _signal_handlers(usbus, flag, msg); + } +} + +/* USB generic endpoint callback */ +static void _event_ep_cb(usbdev_ep_t *ep, usbdev_event_t event) +{ + usbus_t *usbus = (usbus_t *)ep->dev->context; + + if (event == USBDEV_EVENT_ESR) { + assert(irq_is_in()); + usbus->ep_events |= _get_ep_bitflag(ep); + thread_flags_set((thread_t *)thread_get(usbus->pid), + USBUS_THREAD_FLAG_USBDEV_EP); + } + else { + usbus_handler_t *handler = _ep_to_handler(usbus, ep); + if (handler) { + switch (event) { + case USBDEV_EVENT_TR_COMPLETE: + handler->driver->transfer_handler(usbus, handler, ep, + USBUS_EVENT_TRANSFER_COMPLETE); + break; + case USBDEV_EVENT_TR_FAIL: + if (usbus_handler_isset_flag(handler, + USBUS_HANDLER_FLAG_TR_FAIL)) { + handler->driver->transfer_handler(usbus, handler, ep, + USBUS_EVENT_TRANSFER_FAIL); + } + break; + case USBDEV_EVENT_TR_STALL: + if (usbus_handler_isset_flag(handler, + USBUS_HANDLER_FLAG_TR_STALL)) { + handler->driver->transfer_handler(usbus, handler, ep, + USBUS_EVENT_TRANSFER_STALL); + static const usbopt_enable_t disable = USBOPT_DISABLE; + usbdev_ep_set(ep, USBOPT_EP_STALL, &disable, + sizeof(usbopt_enable_t)); + } + break; + default: + DEBUG("unhandled event: %x\n", event); + break; + } + } + } +} diff --git a/sys/usb/usbus/usbus_control.c b/sys/usb/usbus/usbus_control.c new file mode 100644 index 0000000000..917e70d16c --- /dev/null +++ b/sys/usb/usbus/usbus_control.c @@ -0,0 +1,426 @@ +/* + * 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. + */ + +/** + * @ingroup usb_usbus + * @{ + * @file + * @brief USBUS control endpoint handling + * + * @author Koen Zandberg + * @} + */ +#include "periph/usbdev.h" +#include "usb/descriptor.h" +#include "usb/usbus.h" +#include "usb/usbus/fmt.h" +#include "usb/usbus/control.h" + +#include +#include +#include + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static void _init(usbus_t *usbus, usbus_handler_t *handler); +static void _handler_ep0_event(usbus_t *usbus, usbus_handler_t *handler, + usbus_event_usb_t event); +static void _handler_ep0_transfer(usbus_t *usbus, usbus_handler_t *handler, + usbdev_ep_t *ep, usbus_event_transfer_t event); + +const usbus_handler_driver_t _ep0_driver = { + .init = _init, + .event_handler = _handler_ep0_event, + .transfer_handler = _handler_ep0_transfer, +}; + +void usbus_control_init(usbus_t *usbus, usbus_control_handler_t *handler) +{ + handler->handler.driver = &_ep0_driver; + + /* Ensure that ep0 is the first handler */ + handler->handler.next = usbus->handlers; + usbus->handlers = &handler->handler; +} + +static void _activate_endpoints(usbus_t *usbus) +{ + for (usbus_interface_t *iface = usbus->iface; iface; iface = iface->next) { + for (usbus_endpoint_t *ep = iface->ep; ep; ep = ep->next) { + if (ep->active) { + static const usbopt_enable_t enable = USBOPT_ENABLE; + usbdev_ep_set(ep->ep, USBOPT_EP_ENABLE, &enable, + sizeof(usbopt_enable_t)); + DEBUG("usbus_control: activated endpoint %d, dir %s\n", + ep->ep->num, + ep->ep->dir == USB_EP_DIR_OUT ? "out" : "in"); + } + } + for (usbus_interface_alt_t *alt = iface->alts; alt; alt = alt->next) { + for (usbus_endpoint_t *ep = alt->ep; ep; ep = ep->next) { + if (ep->active) { + static const usbopt_enable_t enable = USBOPT_ENABLE; + usbdev_ep_set(ep->ep, USBOPT_EP_ENABLE, &enable, + sizeof(usbopt_enable_t)); + DEBUG("usbus_control: activated endpoint %d, dir %s\n", + ep->ep->num, + ep->ep->dir == USB_EP_DIR_OUT ? "out" : "in"); + } + } + } + } +} + +static size_t _cpy_str_to_utf16(usbus_t *usbus, const char *str) +{ + size_t len = 0; + + while (*str) { + usbus_control_slicer_put_char(usbus, *str); + usbus_control_slicer_put_char(usbus, 0); + len += 2; /* Two bytes added each iteration */ + str++; + } + return len; +} + +static usbus_string_t *_get_descriptor(usbus_t *usbus, uint16_t idx) +{ + for (usbus_string_t *str = usbus->strings; str; str = str->next) { + if (str->idx == idx) { + return str; + } + } + return NULL; +} + +static int _req_status(usbus_t *usbus) +{ + uint8_t status[2]; + + memset(status, 0, sizeof(status)); + usbus_control_slicer_put_bytes(usbus, status, sizeof(status)); + return sizeof(status); +} + +static int _req_str(usbus_t *usbus, uint16_t idx) +{ + /* Return an error condition by default */ + int res = -1; + + /* Language ID must only be supported if there are string descriptors + * available */ + if (usbus->strings) { + if (idx == 0) { + usb_descriptor_string_t desc; + desc.type = USB_TYPE_DESCRIPTOR_STRING; + desc.length = sizeof(uint16_t) + sizeof(usb_descriptor_string_t); + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&desc, sizeof(desc)); + /* Only one language ID supported */ + uint16_t us = USB_CONFIG_DEFAULT_LANGID; + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&us, sizeof(uint16_t)); + res = 1; + } + else { + usb_descriptor_string_t desc; + desc.type = USB_TYPE_DESCRIPTOR_STRING; + usbus_string_t *str = _get_descriptor(usbus, idx); + if (str) { + desc.length = sizeof(usb_descriptor_string_t); + desc.length += 2 * strlen(str->str); /* USB strings are UTF-16 */ + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&desc, + sizeof(desc)); + _cpy_str_to_utf16(usbus, str->str); + res = 1; + } + } + } + return res; +} + +static int _req_dev(usbus_t *usbus) +{ + return usbus_fmt_hdr_dev(usbus); +} + +static int _req_config(usbus_t *usbus) +{ + return usbus_fmt_hdr_conf(usbus); +} + +static int _req_dev_qualifier(usbus_t *usbus) +{ + usb_speed_t speed = USB_SPEED_LOW; + + usbus->dev->driver->get(usbus->dev, USBOPT_MAX_SPEED, &speed, + sizeof(usb_speed_t)); + if (speed == USB_SPEED_HIGH) { + /* TODO: implement device qualifier support (only required + * for High speed) */ + } + /* Signal a stall condition */ + return -1; +} + +static int _req_descriptor(usbus_t *usbus, usb_setup_t *pkt) +{ + uint8_t type = pkt->value >> 8; + uint8_t idx = (uint8_t)pkt->value; + + /* Decode descriptor type */ + switch (type) { + case USB_TYPE_DESCRIPTOR_DEVICE: + return _req_dev(usbus); + case USB_TYPE_DESCRIPTOR_CONFIGURATION: + return _req_config(usbus); + case USB_TYPE_DESCRIPTOR_STRING: + return _req_str(usbus, idx); + case USB_TYPE_DESCRIPTOR_DEV_QUALIFIER: + return _req_dev_qualifier(usbus); + default: + DEBUG("usbus: unknown descriptor request %u, signalling stall\n", + type); + return -1; + } +} + +static int _recv_dev_setup(usbus_t *usbus, usb_setup_t *pkt) +{ + usbus_control_handler_t *ep0 = (usbus_control_handler_t *)usbus->control; + int res = -1; + + if (usb_setup_is_read(pkt)) { + switch (pkt->request) { + case USB_SETUP_REQ_GET_STATUS: + res = _req_status(usbus); + break; + case USB_SETUP_REQ_GET_DESCRIPTOR: + res = _req_descriptor(usbus, pkt); + break; + default: + DEBUG("usbus: Unknown read request %u\n", pkt->request); + break; + } + } + else { + switch (pkt->request) { + case USB_SETUP_REQ_SET_ADDRESS: + DEBUG("usbus_control: Setting address\n"); + usbus->addr = (uint8_t)pkt->value; + res = 0; + break; + case USB_SETUP_REQ_SET_CONFIGURATION: + /* Nothing configuration dependent to do here, only one + * configuration supported */ + usbus->state = USBUS_STATE_CONFIGURED; + _activate_endpoints(usbus); + res = 0; + break; + default: + DEBUG("usbus: Unknown write request %u\n", pkt->request); + break; + } + /* Signal zero-length packet */ + usbdev_ep_ready(ep0->in, 0); + } + return res; +} + +static int _recv_interface_setup(usbus_t *usbus, usb_setup_t *pkt) +{ + usbus_control_handler_t *ep0_handler = + (usbus_control_handler_t *)usbus->control; + uint16_t destination = pkt->index & 0x0f; + + /* Find interface handler */ + for (usbus_interface_t *iface = usbus->iface; iface; iface = iface->next) { + if (destination == iface->idx && + iface->handler->driver->setup_handler) { + return iface->handler->driver->setup_handler(usbus, iface->handler, + ep0_handler->setup_state, + pkt); + } + } + return -1; +} + +static void _recv_setup(usbus_t *usbus, usbus_control_handler_t *handler) +{ + usb_setup_t *pkt = &handler->setup; + + DEBUG("usbus_control: Received setup %x %x @ %d\n", pkt->type, + pkt->request, pkt->length); + if (usb_setup_is_read(pkt)) { + handler->setup_state = USBUS_SETUPRQ_INDATA; + } + else { + if (pkt->length) { + handler->setup_state = USBUS_SETUPRQ_OUTDATA; + usbdev_ep_ready(handler->out, 0); + } + else { + handler->setup_state = USBUS_SETUPRQ_INACK; + usbdev_ep_ready(handler->in, 0); + } + } + uint8_t destination = pkt->type & USB_SETUP_REQUEST_RECIPIENT_MASK; + int res = 0; + switch (destination) { + case USB_SETUP_REQUEST_RECIPIENT_DEVICE: + res = _recv_dev_setup(usbus, pkt); + break; + case USB_SETUP_REQUEST_RECIPIENT_INTERFACE: + res = _recv_interface_setup(usbus, pkt); + break; + default: + DEBUG("usbus_control: Unhandled setup request\n"); + } + if (res < 0) { + /* Signal stall to indicate unsupported (USB 2.0 spec 9.6.2 */ + static const usbopt_enable_t enable = USBOPT_ENABLE; + usbdev_ep_set(handler->in, USBOPT_EP_STALL, &enable, + sizeof(usbopt_enable_t)); + handler->setup_state = USBUS_SETUPRQ_READY; + } + else if (res) { + usbus_control_slicer_ready(usbus); + } +} + +static void _usbus_config_ep0(usbus_control_handler_t *ep0_handler) +{ + DEBUG("usbus_control: Enabling EP0\n"); + static const usbopt_enable_t enable = USBOPT_ENABLE; + usbdev_ep_init(ep0_handler->in); + usbdev_ep_init(ep0_handler->out); + usbdev_ep_set(ep0_handler->in, USBOPT_EP_ENABLE, &enable, + sizeof(usbopt_enable_t)); + usbdev_ep_set(ep0_handler->out, USBOPT_EP_ENABLE, &enable, + sizeof(usbopt_enable_t)); + usbdev_ep_ready(ep0_handler->out, 0); +} + +static void _init(usbus_t *usbus, usbus_handler_t *handler) +{ + DEBUG("usbus_control: Initializing EP0\n"); + usbus_control_handler_t *ep0_handler = (usbus_control_handler_t *)handler; + usbus_handler_set_flag(handler, USBUS_HANDLER_FLAG_RESET); + ep0_handler->setup_state = USBUS_SETUPRQ_READY; + + ep0_handler->in = usbus_add_endpoint(usbus, NULL, USB_EP_TYPE_CONTROL, + USB_EP_DIR_IN, USBUS_EP0_SIZE)->ep; + ep0_handler->out = usbus_add_endpoint(usbus, NULL, USB_EP_TYPE_CONTROL, + USB_EP_DIR_OUT, USBUS_EP0_SIZE)->ep; +} + +static int _handle_tr_complete(usbus_t *usbus, + usbus_control_handler_t *ep0_handler, + usbdev_ep_t *ep) +{ + switch (ep0_handler->setup_state) { + case USBUS_SETUPRQ_INACK: + if (ep->dir == USB_EP_DIR_IN) { + if (usbus->addr && usbus->state == USBUS_STATE_RESET) { + usbdev_set(usbus->dev, USBOPT_ADDRESS, &usbus->addr, + sizeof(usbus->addr)); + /* Address configured */ + usbus->state = USBUS_STATE_ADDR; + } + ep0_handler->setup_state = USBUS_SETUPRQ_READY; + } + break; + case USBUS_SETUPRQ_OUTACK: + if (ep->dir == USB_EP_DIR_OUT) { + memset(&ep0_handler->slicer, 0, sizeof(usbus_control_slicer_t)); + ep0_handler->setup_state = USBUS_SETUPRQ_READY; + } + break; + case USBUS_SETUPRQ_INDATA: + if (ep->dir == USB_EP_DIR_IN) { + if (usbus_control_slicer_nextslice(usbus)) { + _recv_setup(usbus, ep0_handler); + ep0_handler->setup_state = USBUS_SETUPRQ_INDATA; + } + else { + /* Ready out ZLP */ + usbdev_ep_ready(ep0_handler->out, 0); + ep0_handler->setup_state = USBUS_SETUPRQ_OUTACK; + } + } + break; + case USBUS_SETUPRQ_OUTDATA: + if (ep->dir == USB_EP_DIR_OUT) { + /* Ready in ZLP */ + ep0_handler->setup_state = USBUS_SETUPRQ_INACK; + size_t len = 0; + usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t)); + DEBUG("Expected len: %d, received: %d\n", + ep0_handler->setup.length, len); + if (ep0_handler->setup.length == len) { + DEBUG("DATA complete\n"); + usbdev_ep_ready(ep0_handler->in, 0); + } + /* Flush OUT buffer */ + usbdev_ep_ready(ep0_handler->out, 0); + } + else { + DEBUG("usbus_control: Invalid state OUTDATA with IN request\n"); + } + break; + case USBUS_SETUPRQ_READY: + if (ep->dir == USB_EP_DIR_OUT) { + memset(&ep0_handler->slicer, 0, sizeof(usbus_control_slicer_t)); + memcpy(&ep0_handler->setup, ep0_handler->out->buf, + sizeof(usb_setup_t)); + ep0_handler->slicer.reqlen = ep0_handler->setup.length; + _recv_setup(usbus, ep0_handler); + } + else { + DEBUG("usbus_control: invalid state, READY with IN request\n"); + } + break; + default: + DEBUG("usbus_control: Invalid state\n"); + assert(false); + break; + } + return 0; +} + +/* USB endpoint 0 callback */ +static void _handler_ep0_event(usbus_t *usbus, usbus_handler_t *handler, + usbus_event_usb_t event) +{ + usbus_control_handler_t *ep0_handler = (usbus_control_handler_t *)handler; + + (void)usbus; + switch (event) { + case USBUS_EVENT_USB_RESET: + DEBUG("usbus_control: Reset event triggered\n"); + ep0_handler->setup_state = USBUS_SETUPRQ_READY; + _usbus_config_ep0(ep0_handler); + break; + default: + break; + } +} + +static void _handler_ep0_transfer(usbus_t *usbus, usbus_handler_t *handler, + usbdev_ep_t *ep, usbus_event_transfer_t event) +{ + usbus_control_handler_t *ep0_handler = (usbus_control_handler_t *)handler; + + switch (event) { + case USBUS_EVENT_TRANSFER_COMPLETE: + _handle_tr_complete(usbus, ep0_handler, ep); + break; + default: + break; + } +} diff --git a/sys/usb/usbus/usbus_control_slicer.c b/sys/usb/usbus/usbus_control_slicer.c new file mode 100644 index 0000000000..8250dce1e6 --- /dev/null +++ b/sys/usb/usbus/usbus_control_slicer.c @@ -0,0 +1,101 @@ +/* + * 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. + */ + +/** + * @ingroup usb_usbus + * @{ + * @file + * @brief USBUS multipart control message handling + * + * @author Koen Zandberg + * @} + */ + +#include +#include "periph/usbdev.h" +#include "usb/usbus.h" +#include "usb/usbus/control.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +int usbus_control_slicer_nextslice(usbus_t *usbus) +{ + usbus_control_handler_t *ep0 = (usbus_control_handler_t *)usbus->control; + usbus_control_slicer_t *bldr = &ep0->slicer; + size_t end = bldr->start + ep0->in->len; + + if (bldr->cur > end && bldr->start < bldr->reqlen && + bldr->transfered < bldr->reqlen) { + bldr->start += ep0->in->len; + bldr->cur = 0; + bldr->len = 0; + return 1; + } + return 0; +} + +size_t usbus_control_slicer_put_bytes(usbus_t *usbus, const uint8_t *buf, + size_t len) +{ + usbus_control_handler_t *ep0 = (usbus_control_handler_t *)usbus->control; + usbus_control_slicer_t *bldr = &ep0->slicer; + size_t end = bldr->start + ep0->in->len; + size_t byte_len = 0; /* Length of the string to copy */ + + /* Calculate start offset of the supplied bytes */ + size_t byte_offset = + (bldr->start > bldr->cur) ? bldr->start - bldr->cur : 0; + + /* Check for string before or beyond window */ + if ((bldr->cur >= end) || (byte_offset > len)) { + bldr->cur += len; + return 0; + } + /* Check if string is over the end of the window */ + if ((bldr->cur + len) >= end) { + byte_len = end - (bldr->cur + byte_offset); + } + else { + byte_len = len - byte_offset; + } + size_t start_offset = bldr->cur - bldr->start + byte_offset; + bldr->cur += len; + bldr->len += byte_len; + memcpy(ep0->in->buf + start_offset, buf + byte_offset, byte_len); + return byte_len; +} + +size_t usbus_control_slicer_put_char(usbus_t *usbus, char c) +{ + usbus_control_handler_t *ep0 = (usbus_control_handler_t *)usbus->control; + usbus_control_slicer_t *bldr = &ep0->slicer; + size_t end = bldr->start + ep0->in->len; + + /* Only copy the char if it is within the window */ + if ((bldr->start <= bldr->cur) && (bldr->cur < end)) { + uint8_t *pos = ep0->in->buf + bldr->cur - bldr->start; + *pos = c; + bldr->cur++; + bldr->len++; + return 1; + } + bldr->cur++; + return 0; +} + +void usbus_control_slicer_ready(usbus_t *usbus) +{ + usbus_control_handler_t *ep0 = (usbus_control_handler_t *)usbus->control; + usbus_control_slicer_t *bldr = &ep0->slicer; + size_t len = bldr->len; + + len = len < bldr->reqlen - bldr->start ? len : bldr->reqlen - bldr->start; + bldr->transfered += len; + usbdev_ep_ready(ep0->in, len); +} diff --git a/sys/usb/usbus/usbus_fmt.c b/sys/usb/usbus/usbus_fmt.c new file mode 100644 index 0000000000..fe9738b119 --- /dev/null +++ b/sys/usb/usbus/usbus_fmt.c @@ -0,0 +1,282 @@ +/* + * 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. + */ + +/** + * @ingroup usb_usbus_fmt + * @{ + * @file + * @brief USBUS protocol message formatting functions + * + * @author Koen Zandberg + * @} + */ + +#include +#include +#include "usb/descriptor.h" +#include "usb/usbus/fmt.h" +#include "usb/usbus/control.h" + +static size_t _num_ifaces(usbus_t *usbus) +{ + size_t num = 0; + + for (usbus_interface_t *iface = usbus->iface; + iface; + iface = iface->next) { + num++; + } + return num; +} + +static size_t _num_endpoints(usbus_interface_t *iface) +{ + size_t num = 0; + + for (usbus_endpoint_t *ep = iface->ep; + ep; ep = ep->next) { + num++; + } + return num; +} + +static uint8_t _type_to_attribute(usbus_endpoint_t *ep) +{ + switch (ep->ep->type) { + case USB_EP_TYPE_CONTROL: + return 0x00; + case USB_EP_TYPE_ISOCHRONOUS: + return 0x01; + case USB_EP_TYPE_BULK: + return 0x02; + case USB_EP_TYPE_INTERRUPT: + return 0x03; + default: + assert(false); + break; + } + return 0x00; +} + +static size_t _num_endpoints_alt(usbus_interface_alt_t *alt) +{ + size_t num = 0; + + for (usbus_endpoint_t *ep = alt->ep; + ep; ep = ep->next) { + num++; + } + return num; +} + +static inline size_t call_get_header_len(usbus_t *usbus, usbus_hdr_gen_t *hdr) +{ + return hdr->funcs->len_type == USBUS_HDR_LEN_FIXED ? + hdr->funcs->len.fixed_len : + hdr->funcs->len.get_header_len(usbus, hdr->arg); +} + +static size_t _hdr_gen_size(usbus_t *usbus, usbus_hdr_gen_t *hdr) +{ + size_t len = 0; + + for (; hdr; hdr = hdr->next) { + len += call_get_header_len(usbus, hdr); + } + return len; +} + +static size_t _ep_size(usbus_t *usbus, usbus_endpoint_t *ep) +{ + size_t len = 0; + + for (; ep; ep = ep->next) { + len += sizeof(usb_descriptor_endpoint_t); + len += _hdr_gen_size(usbus, ep->hdr_gen); + } + return len; +} + +static size_t _alt_size(usbus_t *usbus, usbus_interface_alt_t *alt) +{ + size_t len = 0; + + for (; alt; alt = alt->next) { + len += sizeof(usb_descriptor_interface_t); + len += _hdr_gen_size(usbus, alt->hdr_gen); + len += _ep_size(usbus, alt->ep); + } + return len; +} + +static size_t _hdrs_config_size(usbus_t *usbus) +{ + size_t len = sizeof(usb_descriptor_configuration_t); + + len += _hdr_gen_size(usbus, usbus->hdr_gen); + for (usbus_interface_t *iface = usbus->iface; + iface; + iface = iface->next) { + len += sizeof(usb_descriptor_interface_t); + len += _hdr_gen_size(usbus, iface->hdr_gen); + len += _ep_size(usbus, iface->ep); + len += _alt_size(usbus, iface->alts); + } + return len; +} + +static inline size_t call_get_header(usbus_t *usbus, usbus_hdr_gen_t *hdr) +{ + return hdr->funcs->get_header(usbus, hdr->arg); +} + +static size_t _hdrs_fmt_additional(usbus_t *usbus, usbus_hdr_gen_t *hdr) +{ + size_t len = 0; + + for (; hdr; hdr = hdr->next) { + len += call_get_header(usbus, hdr); + } + return len; +} + +static size_t _hdrs_fmt_hdrs(usbus_t *usbus) +{ + return _hdrs_fmt_additional(usbus, usbus->hdr_gen); +} + +static size_t _hdrs_fmt_endpoints(usbus_t *usbus, usbus_endpoint_t *ep) +{ + size_t len = 0; + + while (ep) { + usb_descriptor_endpoint_t usb_ep; + memset(&usb_ep, 0, sizeof(usb_descriptor_endpoint_t)); + usb_ep.length = sizeof(usb_descriptor_endpoint_t); + usb_ep.type = USB_TYPE_DESCRIPTOR_ENDPOINT; + usb_ep.address = ep->ep->num; + if (ep->ep->dir == USB_EP_DIR_IN) { + usb_ep.address |= 0x80; + } + usb_ep.attributes = _type_to_attribute(ep); + usb_ep.max_packet_size = ep->maxpacketsize; + usb_ep.interval = ep->interval; + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&usb_ep, + sizeof(usb_descriptor_endpoint_t)); + _hdrs_fmt_additional(usbus, ep->hdr_gen); + len += usb_ep.length; + /* iterate to next endpoint */ + ep = ep->next; + } + return len; +} + +static void _hdrs_fmt_iface(usbus_interface_t *iface, + usb_descriptor_interface_t *usb_iface) +{ + memset(usb_iface, 0, sizeof(usb_descriptor_interface_t)); + usb_iface->length = sizeof(usb_descriptor_interface_t); + usb_iface->type = USB_TYPE_DESCRIPTOR_INTERFACE; + usb_iface->interface_num = iface->idx; + usb_iface->class = iface->class; + usb_iface->subclass = iface->subclass; + usb_iface->protocol = iface->protocol; +} + +static size_t _hdrs_fmt_iface_alts(usbus_t *usbus, usbus_interface_t *iface) +{ + size_t len = 0; + uint8_t alts = 1; + + for (usbus_interface_alt_t *alt = iface->alts; + alt; + alt = alt->next) { + usb_descriptor_interface_t usb_iface; + _hdrs_fmt_iface(iface, &usb_iface); + usb_iface.alternate_setting = alts++; + usb_iface.num_endpoints = _num_endpoints_alt(alt); + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&usb_iface, + sizeof(usb_descriptor_interface_t)); + len += _hdrs_fmt_additional(usbus, alt->hdr_gen); + len += _hdrs_fmt_endpoints(usbus, alt->ep); + } + return len; +} + +static size_t _hdrs_fmt_ifaces(usbus_t *usbus) +{ + size_t len = 0; + + for (usbus_interface_t *iface = usbus->iface; + iface; + iface = iface->next) { + usb_descriptor_interface_t usb_iface; + _hdrs_fmt_iface(iface, &usb_iface); + usb_iface.num_endpoints = _num_endpoints(iface); + if (iface->descr) { + usb_iface.idx = iface->descr->idx; + } + else { + usb_iface.idx = 0; + } + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&usb_iface, + sizeof(usb_descriptor_interface_t)); + len += sizeof(usb_descriptor_interface_t); + len += _hdrs_fmt_additional(usbus, iface->hdr_gen); + len += _hdrs_fmt_endpoints(usbus, iface->ep); + len += _hdrs_fmt_iface_alts(usbus, iface); + } + return len; +} + +size_t usbus_fmt_hdr_conf(usbus_t *usbus) +{ + size_t len = 0; + usb_descriptor_configuration_t conf; + + memset(&conf, 0, sizeof(usb_descriptor_configuration_t)); + conf.length = sizeof(usb_descriptor_configuration_t); + conf.type = USB_TYPE_DESCRIPTOR_CONFIGURATION; + conf.total_length = sizeof(usb_descriptor_configuration_t); + conf.val = 1; + conf.attributes = USB_CONF_ATTR_RESERVED; + if (USB_CONFIG_SELF_POWERED) { + conf.attributes |= USB_CONF_ATTR_SELF_POWERED; + } + /* TODO: upper bound */ + /* USB max power is reported in increments of 2 mA */ + conf.max_power = USB_CONFIG_MAX_POWER / 2; + conf.num_interfaces = _num_ifaces(usbus); + len += sizeof(usb_descriptor_configuration_t); + conf.total_length = _hdrs_config_size(usbus); + conf.idx = usbus->config.idx; + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&conf, sizeof(conf)); + len += _hdrs_fmt_hdrs(usbus); + len += _hdrs_fmt_ifaces(usbus); + return len; +} + +size_t usbus_fmt_hdr_dev(usbus_t *usbus) +{ + usb_descriptor_device_t desc; + + memset(&desc, 0, sizeof(usb_descriptor_device_t)); + desc.length = sizeof(usb_descriptor_device_t); + desc.type = USB_TYPE_DESCRIPTOR_DEVICE; + desc.bcd_usb = USB_CONFIG_SPEC_BCDVERSION; + desc.max_packet_size = USBUS_EP0_SIZE; + desc.vendor_id = USB_CONFIG_VID; + desc.product_id = USB_CONFIG_PID; + desc.manufacturer_idx = usbus->manuf.idx; + desc.product_idx = usbus->product.idx; + /* USBUS supports only a single config at the moment */ + desc.num_configurations = 1; + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&desc, + sizeof(usb_descriptor_device_t)); + return sizeof(usb_descriptor_device_t); +}