Merge pull request #10916 from bergzand/pr/usb/usbus

USBUS: Initial work towards an USB stack
This commit is contained in:
Dylan Laduranty 2019-06-05 15:07:23 +02:00 committed by GitHub
commit e98376c70d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2199 additions and 1 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

@ -0,0 +1,31 @@
# name of your application
APPLICATION = usbus_minimal
# If no BOARD is found in the environment, use this default:
BOARD ?= samr21-xpro
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1
USEMODULE += usbus
# USB device vendor and product ID
USB_VID ?= 1209
USB_PID ?= 0001
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
CFLAGS += -DUSB_CONFIG_VID=0x$(USB_VID) -DUSB_CONFIG_PID=0x$(USB_PID)
include $(RIOTBASE)/Makefile.include
ifeq ($(USB_VID):$(USB_PID), 1209:0001)
$(shell $(COLOR_ECHO) "$(COLOR_RED)Private testing pid.codes USB VID/PID used!, do not use it outside of test environments!$(COLOR_RESET)" 1>&2)
$(shell $(COLOR_ECHO) "$(COLOR_RED)MUST NOT be used on any device redistributed, sold or manufactured, VID/PID is not unique!$(COLOR_RESET)" 1>&2)
endif

View File

@ -0,0 +1,16 @@
# usbus_minimal example
This is a minimalistic example for RIOT's USB stack. The application will
initialize and start the USB stack. The stack is started without any USB
handlers, it should show up as an empty USB device on the host.
RIOT doesn't own any USB vendor and product ID. To compile this example, add
your own vendor and product ID to the makefile:
```
CFLAGS += -DUSB_CONFIG_VID=0xYOURVID -DUSB_CONFIG_PID=0xYOURPID
```
The example demonstrates basic USB communication between a host and a RIOT
based USB peripheral. Tools such as `lsusb` should display the device and
detailed information about the device such as descriptor strings.

View File

@ -0,0 +1,44 @@
/*
* 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 examples
* @{
*
* @file
* @brief Example application for demonstrating the RIOT USB stack
*
* @author Koen Zandberg <koen@bergzand.net>
*
* @}
*/
#include <stdio.h>
#include "usb/usbus.h"
static char _stack[USBUS_STACKSIZE];
static usbus_t usbus;
/* TODO: remove as soon as we have decent auto_init */
#include "periph_conf.h"
#include "sam_usb.h"
int main(void)
{
puts("RIOT USB stack example application");
/* TODO: remove as soon as we have decent auto_init */
usbdev_t *usbdev_ctx = usbdev_get_ctx(0);
/* start usb stack */
usbus_init(&usbus, usbdev_ctx);
usbus_create(_stack, sizeof(_stack), USBUS_PRIO, USBUS_TNAME, &usbus);
/* start shell */
puts("Started USB stack!");
return 0;
}

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

View File

@ -69,7 +69,14 @@ extern "C" {
* @note Not to be be confused with the USB version number
*/
#ifndef USB_CONFIG_PRODUCT_BCDVERSION
#define USB_CONFIG_PRODUCT_BCDVERSION "0x0100"
#define USB_CONFIG_PRODUCT_BCDVERSION 0x0100
#endif
/**
* @brief USB specification version
*/
#ifndef USB_CONFIG_SPEC_BCDVERSION
#define USB_CONFIG_SPEC_BCDVERSION 0x0200
#endif
/**

View File

@ -0,0 +1,223 @@
/*
* Copyright (C) 2018 Koen Zandberg <koen@bergzand.net>
*
* 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_descriptor USB descriptors
* @ingroup usb
* @brief Provides USB protocol descriptors and defines for USB frameworks.
* @{
*
* @file
* @brief Definitions for USB protocol messages
*
* @author Koen Zandberg <koen@bergzand.net>
*/
#ifndef USB_DESCRIPTOR_H
#define USB_DESCRIPTOR_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name USB standard device request codes (USB 2.0 spec table 9-4)
* @{
*/
#define USB_SETUP_REQ_GET_STATUS 0x00 /**< Status request */
#define USB_SETUP_REQ_CLEAR_FEATURE 0x01 /**< Clear feature */
#define USB_SETUP_REQ_SET_FEATURE 0x03 /**< Set feature */
#define USB_SETUP_REQ_SET_ADDRESS 0x05 /**< Set address */
#define USB_SETUP_REQ_GET_DESCRIPTOR 0x06 /**< Get descriptor */
#define USB_SETUP_REQ_SET_DESCRIPTOR 0x07 /**< Set descriptor */
#define USB_SETUP_REQ_GET_CONFIGURATION 0x08 /**< Get configuration */
#define USB_SETUP_REQ_SET_CONFIGURATION 0x09 /**< Set configuration */
#define USB_SETUP_REQ_GET_INTERFACE 0x0a /**< Get interface */
#define USB_SETUP_REQ_SET_INTERFACE 0x0b /**< Set interface */
/** @} */
/**
* @name USB descriptor types (USB 2.0 spec table 9-5)
* @{
*/
#define USB_TYPE_DESCRIPTOR_DEVICE 0x01 /**< Device descriptor */
#define USB_TYPE_DESCRIPTOR_CONFIGURATION 0x02 /**< Configuration Descriptor */
#define USB_TYPE_DESCRIPTOR_STRING 0x03 /**< String descriptor */
#define USB_TYPE_DESCRIPTOR_INTERFACE 0x04 /**< Interface descriptor */
#define USB_TYPE_DESCRIPTOR_ENDPOINT 0x05 /**< Endpoint descriptor */
#define USB_TYPE_DESCRIPTOR_DEV_QUALIFIER 0x06 /**< Device qualifier */
#define USB_TYPE_DESCRIPTOR_SPEED_CONFIG 0x07 /**< Other speed configuration */
#define USB_TYPE_DESCRIPTOR_IFACE_POWER 0x08 /**< Interface power */
#define USB_TYPE_DESCRIPTOR_INTERFACE_ASSOC 0x0b /**< Interface association */
/** @} */
/**
* @name USB configuration attributes
* @anchor USB_CONF_ATTR
* @{
*/
#define USB_CONF_ATTR_RESERVED 0x80 /**< Reserved bit (always 1) */
#define USB_CONF_ATTR_SELF_POWERED 0x40 /**< Self powered device flag */
#define USB_CONF_ATTR_REM_WAKEUP 0x20 /**< Remote wake-up flag */
/** @} */
/**
* @name USB setup packet requests types
* @{
*/
#define USB_SETUP_REQUEST_DEVICE2HOST 0x80 /**< Request direction
From device to host if
the bit is set */
#define USB_SETUP_REQUEST_RECIPIENT_MASK 0x1f /**< Recipient mask */
#define USB_SETUP_REQUEST_RECIPIENT_DEVICE 0x00 /**< Device request */
#define USB_SETUP_REQUEST_RECIPIENT_INTERFACE 0x01 /**< Interface request */
#define USB_SETUP_REQUEST_RECIPIENT_ENDPOINT 0x02 /**< Endpoint request */
#define USB_SETUP_REQUEST_RECIPIENT_OTHER 0x03 /**< Other type request */
#define USB_SETUP_REQUEST_TYPE_MASK 0x60 /**< Mask to select the type */
#define USB_SETUP_REQUEST_TYPE_STANDARD 0x00 /**< Standard request */
#define USB_SETUP_REQUEST_TYPE_CLASS 0x20 /**< Class request */
#define USB_SETUP_REQUEST_TYPE_VENDOR 0x40 /**< Vendor specific request */
/** @} */
/**
* @brief USB isochronous endpoint interval
*/
#define USB_ENDPOINT_DESCRIPTOR_INTERVAL_ISOCHRONOUS 1
/**
* @name USB device class numbers
* @{
*/
#define USB_CLASS_AUDIO 0x01 /**< Audio device */
#define USB_CLASS_CDC_CONTROL 0x02 /**< CDC control interface */
#define USB_CLASS_HID 0x03 /**< Human Interface device */
#define USB_CLASS_PHYSICAL 0x05 /**< Physical device class */
#define USB_CLASS_IMAGE 0x06 /**< Image device class */
#define USB_CLASS_PRINTER 0x07 /**< Printer device class */
#define USB_CLASS_MASS_STORAGE 0x08 /**< Mass storage device class */
#define USB_CLASS_CDC_DATA 0x0a /**< CDC data specification */
#define USB_CLASS_VENDOR 0xff /**< Vendor specific class */
/** @} */
/**
* @brief USB device descriptor (USB 2.0 spec table 9-8)
*/
typedef struct __attribute__((packed)) {
uint8_t length; /**< Size of this descriptor */
uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_DEVICE) */
uint16_t bcd_usb; /**< Binary-coded decimal USB release specification */
uint8_t class; /**< Device class code */
uint8_t subclass; /**< Device subclass code */
uint8_t protocol; /**< Device protocol code */
uint8_t max_packet_size; /**< EP0 max packet size (8, 16, 32 or 64 bytes) */
uint16_t vendor_id; /**< Vendor ID (as assigned by the USB-IF) */
uint16_t product_id; /**< Product ID */
uint16_t bcd_device; /**< Binary-coded decimal device release */
uint8_t manufacturer_idx; /**< Manufacturer string index number */
uint8_t product_idx; /**< Product string index number */
uint8_t serial_idx; /**< Device serial number string index number */
uint8_t num_configurations; /**< Number of possible configurations */
} usb_descriptor_device_t;
/**
* @brief USB configuration descriptor (USB 2.0 spec table 9-10)
*/
typedef struct __attribute__((packed)) {
uint8_t length; /**< Size of this descriptor */
uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_CONFIGURATION) */
uint16_t total_length; /**< Total length of the configuration */
uint8_t num_interfaces; /**< Number of interfaces supported by this configuration */
uint8_t val; /**< Value to select this configuration by */
uint8_t idx; /**< Configuration descriptor string index */
uint8_t attributes; /**< Configuration attributes (@ref USB_CONF_ATTR) */
uint8_t max_power; /**< Maximum power consumption in 2mA steps */
} usb_descriptor_configuration_t;
/**
* @brief USB interface descriptor (USB 2.0 spec table 9-12)
*/
typedef struct __attribute__((packed)) {
uint8_t length; /**< Size of this descriptor */
uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_INTERFACE) */
uint8_t interface_num; /**< Interface number */
uint8_t alternate_setting; /**< Alternate setting index */
uint8_t num_endpoints; /**< Number of endpoints used by this interface */
uint8_t class; /**< Interface class code as assigned by the USB-IF */
uint8_t subclass; /**< Subclass code as assigned by the USB-IF */
uint8_t protocol; /**< Protocol code */
uint8_t idx; /**< Interface descriptor string index */
} usb_descriptor_interface_t;
/**
* @brief USB endpoint descriptor (USB 2.0 spec table 9-13)
*/
typedef struct __attribute__((packed)) {
uint8_t length; /**< Size of this descriptor */
uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_ENDPOINT) */
uint8_t address; /**< Address of the endpoint */
uint8_t attributes; /**< Attributes of the endpoint */
uint16_t max_packet_size; /**< Maximum packet size of the endpoint */
uint8_t interval; /**< Polling interval for the endpoint */
} usb_descriptor_endpoint_t;
/**
* @brief USB string descriptor (USB 2.0 spec table 9-16)
*/
typedef struct __attribute__((packed)) {
uint8_t length; /**< Size of this descriptor */
uint8_t type; /**< String descriptor type */
} usb_descriptor_string_t;
/**
* @brief USB interface association descriptor (Interface Association
* Descriptors table 9-Z)
*/
typedef struct __attribute__((packed)) {
uint8_t length; /**< Size of this descriptor */
uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_INTERFACE_ASSOC) */
uint8_t first_interface; /**< Index of the first associated interface */
uint8_t interface_count; /**< Number of associated interfaces */
uint8_t class; /**< Non-zero class code */
uint8_t subclass; /**< Subclass code */
uint8_t protocol; /**< Protocol code */
uint8_t idx; /**< Descriptor string index */
} usb_descriptor_interface_association_t;
/**
* @brief USB setup packet (USB 2.0 spec table 9-2)
*/
typedef struct __attribute__((packed)) {
uint8_t type; /**< Request type */
uint8_t request; /**< Specific request */
uint16_t value; /**< Value word */
uint16_t index; /**< Index/offset word */
uint16_t length; /**< Length of the data stage */
} usb_setup_t;
/**
* @brief getter for setup packet direction
*
* @param[in] pkt setup packet
*
* @return nonzero if it is a read request
* @return zero if it is a write request
*/
static inline bool usb_setup_is_read(usb_setup_t *pkt)
{
return pkt->type & USB_SETUP_REQUEST_DEVICE2HOST;
}
#ifdef __cplusplus
}
#endif
#endif /* USB_DESCRIPTOR_H */
/** @} */

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