/* * 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 "sched.h" #include "modules.h" #include "msg.h" #include "thread.h" #include "usb.h" #include "periph/usbdev.h" #include "usb/descriptor.h" #ifdef __cplusplus extern "C" { #endif /** * @defgroup usb_usbus_conf USBUS compile time configurations * @ingroup usb_conf * @{ */ /** * @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 auto attach setting * * When set to 1, 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 CONFIG_USBUS_AUTO_ATTACH /* Check for Kconfig usage */ #if !IS_ACTIVE(CONFIG_MODULE_USBUS) #define CONFIG_USBUS_AUTO_ATTACH 1 #endif #endif /** * @brief USBUS MSC auto MTD setting * * When set to 1, the USBUS MSC module will automatically create a LUN for * each MTD device defined in `board.h`. */ #ifndef CONFIG_USBUS_MSC_AUTO_MTD #define CONFIG_USBUS_MSC_AUTO_MTD 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 */ #if IS_ACTIVE(CONFIG_USBUS_EP0_SIZE_8) #define CONFIG_USBUS_EP0_SIZE 8 #elif IS_ACTIVE(CONFIG_USBUS_EP0_SIZE_16) #define CONFIG_USBUS_EP0_SIZE 16 #elif IS_ACTIVE(CONFIG_USBUS_EP0_SIZE_32) #define CONFIG_USBUS_EP0_SIZE 32 #elif IS_ACTIVE(CONFIG_USBUS_EP0_SIZE_64) #define CONFIG_USBUS_EP0_SIZE 64 #endif #ifndef CONFIG_USBUS_EP0_SIZE #define CONFIG_USBUS_EP0_SIZE 64 #endif /** @} */ /** * @brief USBUS thread name */ #define USBUS_TNAME "usbus" /** * @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_STALL (0x0020) /**< Report transfer stall complete */ /** @} */ /** * @name USBUS URB flags * * @{ */ /** * @brief End the URB with a zero length packet if the URB is a full number of * USB transfers in length */ #define USBUS_URB_FLAG_AUTO_ZLP (0x0001) /** * @brief URB needs a zero length packet and it is requested * @internal */ #define USBUS_URB_FLAG_NEEDS_ZLP (0x1000) /** * @brief URB must be cancelled after the next finished xmit * @internal */ #define USBUS_URB_FLAG_CANCELLED (0x2000) /** @} */ /** * @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 successfully 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 control request state machine */ typedef enum { USBUS_CONTROL_REQUEST_STATE_READY, /**< Ready for new control request */ USBUS_CONTROL_REQUEST_STATE_INDATA, /**< Request received with expected DATA IN stage */ USBUS_CONTROL_REQUEST_STATE_OUTACK, /**< Expecting a zero-length ack OUT request from the host */ USBUS_CONTROL_REQUEST_STATE_OUTDATA, /**< Data OUT expected */ USBUS_CONTROL_REQUEST_STATE_INACK, /**< Expecting a zero-length ack IN request from the host */ } usbus_control_request_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 descriptor length types for USB descriptor generators */ typedef enum { USBUS_DESCR_LEN_FIXED, /**< Descriptor always generates a fixed length */ USBUS_DESCR_LEN_FUNC, /**< Descriptor length is calculated by a function */ } usbus_descr_len_type_t; /** * @brief USBUS descriptor generator function pointers */ typedef struct { /** * @brief function pointer to format the descriptor content of this * descriptor generator. The content of this descriptor is prefixes before * the descriptor of the object it is part of. * * @param usbus The usbus context * @param arg Additional argument for the descriptor generator * * @return Length of the generated descriptor */ size_t (*fmt_pre_descriptor)(usbus_t *usbus, void *arg); /** * @brief function pointer to format the descriptor content of this * descriptor generator. The content of this descriptor is appended after * the descriptor of the object it is part of. * * @param usbus The usbus context * @param arg Additional argument for the descriptor generator * * @return Length of the generated descriptor */ size_t (*fmt_post_descriptor)(usbus_t *usbus, void *arg); union { /** * @brief USBUS generic descriptor generator generated length * * Must return the total length of the descriptors that will be * generated by @ref fmt_pre_descriptor and @ref fmt_post_descriptor * This function is used when @ref len_type is set to * @ref USBUS_DESCR_LEN_FUNC. * * @param usbus The usbus context * @param arg Additional argument for the descriptor generators * * @return Length of the generated descriptors */ size_t (*get_descriptor_len)(usbus_t *usbus, void *arg); /** * @brief Fixed total length of the generated descriptors * * Must return the total length of the descriptors that will be * generated by the @ref fmt_pre_descriptor and @ref fmt_post_descriptor. * This value is used when @ref len_type is set to * @ref USBUS_DESCR_LEN_FIXED. */ size_t fixed_len; } len; /**< Fixed or generated length of the descriptor */ usbus_descr_len_type_t len_type; /**< Either USBUS_DESCR_LEN_FIXED or USBUS_DESCR_LEN_FUNC */ } usbus_descr_gen_funcs_t; /** * @brief USBUS descriptor generator * * The functions are called to allow custom modules to define their own * descriptors 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 descriptors */ typedef struct usbus_descr_gen { struct usbus_descr_gen *next; /**< ptr to the next descriptor generator */ const usbus_descr_gen_funcs_t *funcs; /**< Function pointers */ void *arg; /**< Extra context argument for the descriptor functions */ } usbus_descr_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_descr_gen_t *descr_gen; /**< Linked list of optional additional descriptor generators */ usbdev_ep_t *ep; /**< ptr to the matching usbdev endpoint */ #ifdef MODULE_USBUS_URB clist_node_t urb_list; /**< clist of urbs */ #endif 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 */ bool halted; /**< Endpoint is halted */ } usbus_endpoint_t; /** * @brief USBUS USB request/response block */ typedef struct usbus_urb { clist_node_t list; /**< clist block in the queue */ uint32_t flags; /**< Transfer flags */ uint8_t *buf; /**< Pointer to the (aligned) buffer */ size_t len; /**< Length of the data */ size_t transferred; /**< amount transferred, only valid for OUT */ } usbus_urb_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_descr_gen_t *descr_gen; /**< Linked list of optional additional descriptor generators */ usbus_endpoint_t *ep; /**< List of associated endpoints for this alternative setting */ usbus_string_t *descr; /**< Descriptor string */ } usbus_interface_alt_t; /** * @brief USBUS interface */ typedef struct usbus_interface { struct usbus_interface *next; /**< Next interface (set by USBUS during registration) */ usbus_descr_gen_t *descr_gen; /**< Linked list of optional additional descriptor 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 control request handler function * * This function receives USB control requests from the USBUS stack. * * @param usbus USBUS context * @param handler handler context * @param state control 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 (*control_handler)(usbus_t * usbus, struct usbus_handler *handler, usbus_control_request_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_string_t serial; /**< serial 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_descr_gen_t *descr_gen; /**< Linked list of top level descriptor 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 */ bool wakeup_enabled; /**< Remote wakeup device feature status */ #ifndef CONFIG_USB_SERIAL_STR /** * @brief Hex representation of the device serial number */ char serial_str[2 * CONFIG_USB_SERIAL_BYTE_LENGTH + 1]; #endif }; /** * @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 alternate settings to a given interface * * @param[in] iface USB interface * @param[in] alt alternate settings interface to add */ void usbus_add_interface_alt(usbus_interface_t *iface, usbus_interface_alt_t *alt); /** * @brief Find an endpoint from an interface based on the endpoint properties * * This iterates over the endpoints in an interface and will return the first * endpoint from the interface matching the @p type and @p dir. It will return * NULL when no matching endpoint is found. * * @param[in] interface interface to look in * @param[in] type endpoint type to match * @param[in] dir endpoint direction to match * * @return ptr to the first matching endpoint * @return NULL when no endpoint is found */ usbus_endpoint_t *usbus_interface_find_endpoint(usbus_interface_t *interface, usb_ep_type_t type, usb_ep_dir_t dir); /** * @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] descr_gen descriptor generator to add */ void usbus_add_conf_descriptor(usbus_t *usbus, usbus_descr_gen_t *descr_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 Get the maximum supported bulk endpoint transfer size based on the enumeration speed * * Should only be called after enumeration has finished by the peripheral. Calling this in response * to the @ref USBUS_EVENT_USB_RESET is valid */ size_t usbus_max_bulk_endpoint_size(usbus_t *usbus); /** * @brief Get the maximum supported interrupt endpoint transfer size based on the enumeration speed * * Should only be called after enumeration has finished by the peripheral. Calling this in response * to the @ref USBUS_EVENT_USB_RESET is valid */ size_t usbus_max_interrupt_endpoint_size(usbus_t *usbus); /** * @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 Initialize a new URB. * * Must be called before submitting an URB to an endpoint. * * @note When using this for OUT endpoints, the buffer size and the @p len * argument must allow for a whole number of max length transfers. * * @note Requires the `usbus_urb` module. * * @param[in] urb URB to submit * @param[in] buf Buffer to store or transmit the data from * @param[in] len Length of @p buf in bytes * @param[in] flags Flags to set for the URB such as @ref USBUS_URB_FLAG_AUTO_ZLP */ static inline void usbus_urb_init(usbus_urb_t *urb, uint8_t *buf, size_t len, uint32_t flags) { urb->buf = buf; urb->len = len; urb->flags = flags; urb->transferred = 0; } /** * @brief Submit an URB to an endpoint. * * @note Requires the `usbus_urb` module. * * @param[in] usbus USBUS context * @param[in] endpoint USBUS endpoint the URB is queued to * @param[in] urb URB to submit */ void usbus_urb_submit(usbus_t *usbus, usbus_endpoint_t *endpoint, usbus_urb_t *urb); /** * @brief Cancel and already queued URB. * * The URB will be cancelled after the next transmission if the URB is already * partially completed. It is up to the handler code to gracefully handle the * partially aborted transfer on the endpoint pipe. * * The callback will be called if the URB was already started and cancelled * while (partially) transferred * * @note Requires the `usbus_urb` module. * * @param[in] usbus USBUS context * @param[in] endpoint USBUS endpoint the URB is queued to * @param[in] urb URB to cancel * * @returns 0 if the URB is partially completed * 1 if the URB was not yet started * -1 if the URB was not found in the endpoint queue */ int usbus_urb_cancel(usbus_t *usbus, usbus_endpoint_t *endpoint, usbus_urb_t *urb); /** * @brief Set the halt condition on an endpoint. * * The endpoint will respond with stall to all packets and must explicitly be * cleared by the host by clearing the halt condition or switching interfaces */ void usbus_endpoint_halt(usbus_endpoint_t *ep); /** * @brief Clear the halt condition on an endpoint * * @note Must only be used when the endpoint is halted and when the host issues * a SetInterface request on the interface containing the endpoint */ void usbus_endpoint_clear_halt(usbus_endpoint_t *ep); /** * @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; } /** * @brief enable an URB flag * * @param[in] urb URB to enable the flag for * @param[in] flag flag to enable */ static inline void usbus_urb_set_flag(usbus_urb_t *urb, uint32_t flag) { urb->flags |= flag; } /** * @brief disable an URB flag * * @param[in] urb URB to disable the flag for * @param[in] flag flag to disable */ static inline void usbus_urb_remove_flag(usbus_urb_t *urb, uint32_t flag) { urb->flags &= ~flag; } /** * @brief check if an URB flag is set * * @param[in] urb URB to check for flag * @param[in] flag flag to check * * @return true if the flag is set for this URB */ static inline bool usbus_urb_isset_flag(usbus_urb_t *urb, uint32_t flag) { return urb->flags & flag; } #ifdef __cplusplus } #endif #endif /* USB_USBUS_H */ /** @} */