[usb] first stab at a usb device stack
This commit is contained in:
262
dev/usb/usb.c
262
dev/usb/usb.c
@@ -21,9 +21,267 @@
|
|||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include <debug.h>
|
#include <debug.h>
|
||||||
#include <dev/usb.h>
|
#include <err.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <dev/usbc.h>
|
#include <dev/usbc.h>
|
||||||
#include <hw/usb.h>
|
#include <dev/usb.h>
|
||||||
|
|
||||||
|
//#include <lib/pmux.h>
|
||||||
|
//#include <lib/usbmass.h>
|
||||||
|
//#include <lib/dfu.h>
|
||||||
|
//#include <target/usbconfig.h>
|
||||||
|
|
||||||
|
#define LOCAL_TRACE 1
|
||||||
|
|
||||||
|
static usb_config *config;
|
||||||
|
|
||||||
|
static uint8_t active_config;
|
||||||
|
static bool usb_active = false;
|
||||||
|
|
||||||
|
static void append_desc_data(usb_descriptor *desc, const void *dat, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t *ptr = malloc(desc->len + len);
|
||||||
|
|
||||||
|
memcpy(ptr, desc->desc, desc->len);
|
||||||
|
memcpy(ptr + desc->len, dat, len);
|
||||||
|
free(desc->desc);
|
||||||
|
desc->desc = ptr;
|
||||||
|
desc->len += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returns the interface number assigned */
|
||||||
|
static int usb_append_interface(usb_descriptor *desc, const uint8_t *int_descr, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t *ptr = malloc(len);
|
||||||
|
int interface_num;
|
||||||
|
|
||||||
|
// create a temporary copy of the interface
|
||||||
|
memcpy(ptr, int_descr, len);
|
||||||
|
|
||||||
|
// find the last interface used
|
||||||
|
interface_num = ((uint8_t *)desc->desc)[4]; // current interface
|
||||||
|
|
||||||
|
// patch our interface descriptor with the new id
|
||||||
|
ptr[2] = interface_num;
|
||||||
|
|
||||||
|
// append it to our config desriptor
|
||||||
|
append_desc_data(desc, ptr, len);
|
||||||
|
free(ptr);
|
||||||
|
|
||||||
|
// patch the total length of the config descriptor and set the number of interfaces
|
||||||
|
((uint16_t *)desc->desc)[1] += len;
|
||||||
|
interface_num++;
|
||||||
|
((uint8_t *)desc->desc)[4] = interface_num;
|
||||||
|
|
||||||
|
return interface_num - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb_append_interface_highspeed(const uint8_t *int_descr, size_t len)
|
||||||
|
{
|
||||||
|
return usb_append_interface(&config->highspeed.config, int_descr, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb_append_interface_lowspeed(const uint8_t *int_descr, size_t len)
|
||||||
|
{
|
||||||
|
return usb_append_interface(&config->lowspeed.config, int_descr, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb_set_string_descriptor(usb_descriptor *desc, const char *string)
|
||||||
|
{
|
||||||
|
int len = strlen(string);
|
||||||
|
ushort *data;
|
||||||
|
int datalen = len * 2 + 2;
|
||||||
|
|
||||||
|
data = malloc(datalen);
|
||||||
|
|
||||||
|
/* write length field */
|
||||||
|
data[0] = 0x0300 + datalen;
|
||||||
|
|
||||||
|
/* copy the string into the uint16_t based usb string */
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
data[i + 1] = string[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
desc->desc = (void *)data;
|
||||||
|
desc->len = datalen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_usb_id(uint16_t vendor, uint16_t product)
|
||||||
|
{
|
||||||
|
// patch the current configuration to with the vendor/product id
|
||||||
|
((uint16_t *)config->lowspeed.device.desc)[4] = vendor;
|
||||||
|
((uint16_t *)config->lowspeed.device.desc)[5] = product;
|
||||||
|
|
||||||
|
((uint16_t *)config->highspeed.device.desc)[4] = vendor;
|
||||||
|
((uint16_t *)config->highspeed.device.desc)[5] = product;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int default_usb_callback(usbc_callback_op_t op, const union usb_callback_args *args)
|
||||||
|
{
|
||||||
|
LTRACEF("op %d, args %p\n", op, args);
|
||||||
|
|
||||||
|
/* start looking for specific things to handle */
|
||||||
|
if (op == CB_SETUP_MSG) {
|
||||||
|
const struct usb_setup *setup = args->setup;
|
||||||
|
DEBUG_ASSERT(setup);
|
||||||
|
LTRACEF("SETUP: req_type=%#x req=%#x value=%#x index=%#x len=%#x\n", setup->request_type, setup->request, setup->value, setup->index, setup->length);
|
||||||
|
|
||||||
|
if ((setup->request_type & TYPE_MASK) == TYPE_STANDARD) {
|
||||||
|
switch (setup->request) {
|
||||||
|
case SET_ADDRESS:
|
||||||
|
LTRACEF("SET_ADDRESS 0x%x\n", setup->value);
|
||||||
|
usbc_ep0_ack();
|
||||||
|
break;
|
||||||
|
case SET_FEATURE:
|
||||||
|
case CLEAR_FEATURE:
|
||||||
|
// OTAY
|
||||||
|
LTRACEF("SET/CLEAR_FEATURE, feature 0x%x\n", setup->value);
|
||||||
|
usbc_ep0_ack();
|
||||||
|
break;
|
||||||
|
case SET_DESCRIPTOR:
|
||||||
|
LTRACEF("SET_DESCRIPTOR\n");
|
||||||
|
usbc_ep0_stall();
|
||||||
|
break;
|
||||||
|
case GET_DESCRIPTOR: {
|
||||||
|
/* Get the right descriptors based on current speed */
|
||||||
|
const struct usb_descriptor_speed *speed;
|
||||||
|
if (usbc_is_highspeed()) {
|
||||||
|
speed = &config->highspeed;
|
||||||
|
} else {
|
||||||
|
speed = &config->lowspeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((setup->request_type & RECIP_MASK) == RECIP_DEVICE) {
|
||||||
|
switch (setup->value) {
|
||||||
|
case 0x100: /* device */
|
||||||
|
LTRACEF("got GET_DESCRIPTOR, device descriptor\n");
|
||||||
|
usbc_ep0_send(speed->device.desc, speed->device.len,
|
||||||
|
setup->length);
|
||||||
|
break;
|
||||||
|
case 0x200: /* CONFIGURATION */
|
||||||
|
LTRACEF("got GET_DESCRIPTOR, config descriptor\n");
|
||||||
|
usbc_ep0_send(speed->config.desc, speed->config.len,
|
||||||
|
setup->length);
|
||||||
|
break;
|
||||||
|
case 0x300: /* Language ID */
|
||||||
|
LTRACEF("got GET_DESCRIPTOR, language id\n");
|
||||||
|
usbc_ep0_send(config->langid.desc,
|
||||||
|
config->langid.len, setup->length);
|
||||||
|
break;
|
||||||
|
case 0x301: /* Manufacturer string */
|
||||||
|
LTRACEF("got GET_DESCRIPTOR, mfg string\n");
|
||||||
|
usbc_ep0_send(config->mfg_string.desc,
|
||||||
|
config->mfg_string.len,
|
||||||
|
setup->length);
|
||||||
|
break;
|
||||||
|
case 0x302: /* Device string */
|
||||||
|
LTRACEF("got GET_DESCRIPTOR, device string\n");
|
||||||
|
usbc_ep0_send(config->device_string.desc,
|
||||||
|
config->device_string.len,
|
||||||
|
setup->length);
|
||||||
|
break;
|
||||||
|
case 0x303: /* Serial number string */
|
||||||
|
LTRACEF("got GET_DESCRIPTOR, serial number string\n");
|
||||||
|
if (config->serial_string.desc) {
|
||||||
|
usbc_ep0_send(config->serial_string.desc,
|
||||||
|
config->serial_string.len,
|
||||||
|
setup->length);
|
||||||
|
} else {
|
||||||
|
/* stall */
|
||||||
|
usbc_ep0_stall();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x600: /* DEVICE QUALIFIER */
|
||||||
|
LTRACEF("got GET_DESCRIPTOR, device qualifier\n");
|
||||||
|
usbc_ep0_send(speed->device_qual.desc,
|
||||||
|
speed->device_qual.len, setup->length);
|
||||||
|
break;
|
||||||
|
case 0xa00:
|
||||||
|
/* we aint got one of these */
|
||||||
|
LTRACEF("got GET_DESCRIPTOR, debug descriptor\n");
|
||||||
|
usbc_ep0_stall();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LTRACEF("unhandled descriptor %#x\n", setup->value);
|
||||||
|
// stall
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// interface/endpoint descriptors? let someone else handle it
|
||||||
|
// STALL
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SET_CONFIGURATION:
|
||||||
|
LTRACEF("SET_CONFIGURATION %d\n", setup->value);
|
||||||
|
active_config = setup->value;
|
||||||
|
usbc_ep0_ack();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GET_CONFIGURATION:
|
||||||
|
LTRACEF("GET_CONFIGURATION\n");
|
||||||
|
usbc_ep0_send(&active_config, 1, setup->length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SET_INTERFACE:
|
||||||
|
LTRACEF("SET_INTERFACE %d\n", setup->value);
|
||||||
|
usbc_ep0_ack();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GET_INTERFACE: {
|
||||||
|
static uint8_t i = 1;
|
||||||
|
LTRACEF("GET_INTERFACE\n");
|
||||||
|
usbc_ep0_send(&i, 1, setup->length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_STATUS: {
|
||||||
|
static uint16_t i = 1; // self powered
|
||||||
|
LTRACEF("GET_STATUS\n");
|
||||||
|
usbc_ep0_send(&i, 2, setup->length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LTRACEF("unhandled standard request 0x%x\n", setup->request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb_setup(usb_config *_config)
|
||||||
|
{
|
||||||
|
ASSERT(_config);
|
||||||
|
|
||||||
|
config = _config;
|
||||||
|
|
||||||
|
ASSERT(usb_active == false);
|
||||||
|
|
||||||
|
// set the default usb control callback handler
|
||||||
|
usbc_set_callback(&default_usb_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb_start(void)
|
||||||
|
{
|
||||||
|
ASSERT(config);
|
||||||
|
ASSERT(usb_active == false);
|
||||||
|
|
||||||
|
// go online
|
||||||
|
usbc_set_active(true);
|
||||||
|
usb_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb_stop(void)
|
||||||
|
{
|
||||||
|
ASSERT(usb_active == true);
|
||||||
|
|
||||||
|
usb_active = false;
|
||||||
|
usbc_set_active(false);
|
||||||
|
}
|
||||||
|
|
||||||
void usb_init(void)
|
void usb_init(void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,9 +23,39 @@
|
|||||||
#ifndef __DEV_USB_H
|
#ifndef __DEV_USB_H
|
||||||
#define __DEV_USB_H
|
#define __DEV_USB_H
|
||||||
|
|
||||||
/* device side usb stack api */
|
#include <sys/types.h>
|
||||||
|
#include <compiler.h>
|
||||||
|
|
||||||
|
/* top level initialization for usb client, abstracts away the interfaces */
|
||||||
|
typedef struct {
|
||||||
|
void *desc;
|
||||||
|
size_t len;
|
||||||
|
} usb_descriptor __ALIGNED(2);
|
||||||
|
|
||||||
|
/* complete usb config struct, passed in to usb_setup() */
|
||||||
|
typedef struct {
|
||||||
|
struct usb_descriptor_speed {
|
||||||
|
usb_descriptor device;
|
||||||
|
usb_descriptor device_qual;
|
||||||
|
usb_descriptor config;
|
||||||
|
} lowspeed, highspeed;
|
||||||
|
usb_descriptor device_string;
|
||||||
|
usb_descriptor mfg_string;
|
||||||
|
usb_descriptor serial_string;
|
||||||
|
usb_descriptor langid;
|
||||||
|
} usb_config;
|
||||||
|
|
||||||
void usb_init(void);
|
void usb_init(void);
|
||||||
|
|
||||||
|
/* external code needs to set up the usb stack via the following calls */
|
||||||
|
void usb_setup(usb_config *config);
|
||||||
|
|
||||||
|
/* apped new interface descriptors to the existing config if desired */
|
||||||
|
int usb_append_interface_highspeed(const uint8_t *int_descr, size_t len);
|
||||||
|
int usb_append_interface_lowspeed(const uint8_t *int_descr, size_t len);
|
||||||
|
|
||||||
|
void usb_start(void);
|
||||||
|
void usb_stop(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,76 @@
|
|||||||
#ifndef __DEV_USBC_H
|
#ifndef __DEV_USBC_H
|
||||||
#define __DEV_USBC_H
|
#define __DEV_USBC_H
|
||||||
|
|
||||||
/* device side usb controller api (used by the usb stack) */
|
#include <sys/types.h>
|
||||||
|
#include <debug.h>
|
||||||
|
#include <hw/usb.h>
|
||||||
|
|
||||||
void usbc_init(void);
|
void usbc_init(void);
|
||||||
|
|
||||||
|
typedef uint ep_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
IN = 0,
|
||||||
|
OUT
|
||||||
|
} ep_dir_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CB_RESET,
|
||||||
|
CB_SUSPEND,
|
||||||
|
CB_RESUME,
|
||||||
|
CB_DISCONNECT,
|
||||||
|
CB_ONLINE,
|
||||||
|
CB_OFFLINE,
|
||||||
|
CB_SETUP_MSG,
|
||||||
|
|
||||||
|
/* endpoint transfer stuff */
|
||||||
|
CB_EP_RXCOMPLETE,
|
||||||
|
CB_EP_TXCOMPLETE,
|
||||||
|
CB_EP_TRANSFER_CANCELLED,
|
||||||
|
} usbc_callback_op_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *buf;
|
||||||
|
size_t buflen;
|
||||||
|
uint bufpos;
|
||||||
|
int result;
|
||||||
|
void *extra; // extra pointer to store whatever you want
|
||||||
|
} usbc_transfer;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
USB_TRANSFER_RESULT_OK = 0,
|
||||||
|
USB_TRANSFER_RESULT_ERR = -1,
|
||||||
|
USB_TRANSFER_RESULT_CANCELLED = -2,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef int (*ep_callback)(ep_t endpoint, usbc_callback_op_t op, usbc_transfer *transfer);
|
||||||
|
|
||||||
|
void usbc_setup_endpoint(ep_t ep, ep_dir_t dir, bool active, ep_callback callback, uint width, uint blocksize);
|
||||||
|
int usbc_queue_rx(ep_t ep, usbc_transfer *transfer);
|
||||||
|
int usbc_queue_tx(ep_t ep, usbc_transfer *transfer);
|
||||||
|
|
||||||
|
/* setup arg is valid during CB_SETUP_MSG */
|
||||||
|
union usb_callback_args {
|
||||||
|
const struct usb_setup *setup;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef int (*usb_callback)(usbc_callback_op_t op, const union usb_callback_args *args);
|
||||||
|
|
||||||
|
int usbc_set_callback(usb_callback);
|
||||||
|
int usbc_set_active(bool active);
|
||||||
|
|
||||||
|
/* called back from within a callback to handle setup responses */
|
||||||
|
void usbc_ep0_ack(void);
|
||||||
|
void usbc_ep0_stall(void);
|
||||||
|
void usbc_ep0_send(const void *buf, size_t len, size_t maxlen);
|
||||||
|
void usbc_ep0_recv(void *buf, size_t len, ep_callback);
|
||||||
|
|
||||||
|
bool usbc_is_highspeed(void);
|
||||||
|
|
||||||
|
static inline void usbc_dump_transfer(const usbc_transfer *t)
|
||||||
|
{
|
||||||
|
printf("usb transfer %p: buf %p, buflen %zd, bufpos %u, result %d\n", t, t->buf, t->buflen, t->bufpos, t->result);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user