1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

usbus: Initial work to a unified USB stack

This commit is contained in:
Koen Zandberg 2019-01-31 22:44:12 +01:00
parent 35af9b9fb7
commit 74e0b5b85b
No known key found for this signature in database
GPG Key ID: 0895A893E6D2985B
10 changed files with 1877 additions and 0 deletions

View File

@ -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

View File

@ -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))))

533
sys/include/usb/usbus.h Normal file
View 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 */
/** @} */

View 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 */
/** @} */

View 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
View File

@ -0,0 +1,4 @@
SRCS := usbus.c
SRCS += usbus_hdrs.c
include $(RIOTBASE)/Makefile.base

361
sys/usb/usbus/usbus.c Normal file
View 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;
}
}
}
}

View 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;
}
}

View 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
View 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);
}