usbus: Initial work to a unified USB stack
This commit is contained in:
parent
35af9b9fb7
commit
74e0b5b85b
@ -832,6 +832,12 @@ ifneq (,$(filter tlsf-malloc,$(USEMODULE)))
|
|||||||
USEPKG += tlsf
|
USEPKG += tlsf
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifneq (,$(filter usbus,$(USEMODULE)))
|
||||||
|
FEATURES_REQUIRED += periph_usbdev
|
||||||
|
USEMODULE += core_thread_flags
|
||||||
|
USEMODULE += event
|
||||||
|
endif
|
||||||
|
|
||||||
ifneq (,$(filter uuid,$(USEMODULE)))
|
ifneq (,$(filter uuid,$(USEMODULE)))
|
||||||
USEMODULE += hashes
|
USEMODULE += hashes
|
||||||
USEMODULE += random
|
USEMODULE += random
|
||||||
|
|||||||
@ -145,6 +145,9 @@ endif
|
|||||||
ifneq (,$(filter bluetil_%,$(USEMODULE)))
|
ifneq (,$(filter bluetil_%,$(USEMODULE)))
|
||||||
DIRS += net/ble/bluetil
|
DIRS += net/ble/bluetil
|
||||||
endif
|
endif
|
||||||
|
ifneq (,$(filter usbus usbus_%,$(USEMODULE)))
|
||||||
|
DIRS += usb/usbus
|
||||||
|
endif
|
||||||
|
|
||||||
DIRS += $(dir $(wildcard $(addsuffix /Makefile, $(USEMODULE))))
|
DIRS += $(dir $(wildcard $(addsuffix /Makefile, $(USEMODULE))))
|
||||||
|
|
||||||
|
|||||||
533
sys/include/usb/usbus.h
Normal file
533
sys/include/usb/usbus.h
Normal file
@ -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 <koen@bergzand.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB_USBUS_H
|
||||||
|
#define USB_USBUS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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 */
|
||||||
|
/** @} */
|
||||||
107
sys/include/usb/usbus/control.h
Normal file
107
sys/include/usb/usbus/control.h
Normal file
@ -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 <koen@bergzand.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 */
|
||||||
|
/** @} */
|
||||||
54
sys/include/usb/usbus/fmt.h
Normal file
54
sys/include/usb/usbus/fmt.h
Normal file
@ -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 <koen@bergzand.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB_USBUS_FMT_H
|
||||||
|
#define USB_USBUS_FMT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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 */
|
||||||
|
/** @} */
|
||||||
4
sys/usb/usbus/Makefile
Normal file
4
sys/usb/usbus/Makefile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
SRCS := usbus.c
|
||||||
|
SRCS += usbus_hdrs.c
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.base
|
||||||
361
sys/usb/usbus/usbus.c
Normal file
361
sys/usb/usbus/usbus.c
Normal file
@ -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 <koen@bergzand.net>
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
426
sys/usb/usbus/usbus_control.c
Normal file
426
sys/usb/usbus/usbus_control.c
Normal file
@ -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 <koen@bergzand.net>
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
#include "periph/usbdev.h"
|
||||||
|
#include "usb/descriptor.h"
|
||||||
|
#include "usb/usbus.h"
|
||||||
|
#include "usb/usbus/fmt.h"
|
||||||
|
#include "usb/usbus/control.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
101
sys/usb/usbus/usbus_control_slicer.c
Normal file
101
sys/usb/usbus/usbus_control_slicer.c
Normal file
@ -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 <koen@bergzand.net>
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
282
sys/usb/usbus/usbus_fmt.c
Normal file
282
sys/usb/usbus/usbus_fmt.c
Normal file
@ -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 <koen@bergzand.net>
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user