mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
USB mass storage writes ran at 0.03 MB/s in the firmware - to the NAND and to the SD card alike - while the bootloader, with the same driver, wrote the same NAND at 2 MB/s. The firmware adds a HID interface, and without it writes ran at full speed. The UDC's endpoints come in groups of three - bulk OUT, bulk IN, interrupt IN: 1-3, 4-6 and so on - and allocation handed out the first free endpoint of each type, so HID's interrupt endpoint 3 landed in the group of mass storage's bulk endpoints 1 and 2. The host polls it every 16 ms, the idle endpoint NAKs, and each poll costs the group's bulk traffic: writes advanced about one packet per poll. Polled every 125 us instead, they all but stopped; moved to endpoint 6, in a group of its own, they ran at 2.36 MB/s, as without HID. Never give an interrupt endpoint a group with bulk endpoints in use, or the other way round, whichever class asks first. Since which endpoints are available then depends on what is already allocated, the driver tracks the allocation itself - option 2 of usb_drv.h, as usb-designware does - with its context in usb-rk27xx.h, which usb_core.c includes before usb_drv.h. HID keeps working. Tested on a generic rk2705 with ums_stress.py over USB mass storage to the SD card: 0.03 MB/s with HID on endpoint 3, 2.36 MB/s with it on endpoint 6 - the allocation this change produces for mass storage plus HID. (Measured with the driver's earlier allocator, before the USB core took allocation over; this carries the same rule into the new scheme.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Change-Id: Ib80ed206a3705f2c76fc8457b5c0a54d60a46402
1458 lines
46 KiB
C
1458 lines
46 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Copyright (C) 2007 by Björn Stenberg
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "system.h"
|
|
#include "thread.h"
|
|
#include "kernel.h"
|
|
#include "string.h"
|
|
#include "panic.h"
|
|
/*#define LOGF_ENABLE*/
|
|
#include "logf.h"
|
|
|
|
#include "usb.h"
|
|
#include "usb_ch9.h"
|
|
#include "usb_core.h"
|
|
#include "usb_class_driver.h"
|
|
|
|
#if defined(USB_ENABLE_STORAGE)
|
|
#include "usb_storage.h"
|
|
#endif
|
|
|
|
#if defined(USB_ENABLE_SERIAL)
|
|
#include "usb_serial.h"
|
|
#endif
|
|
|
|
#if defined(USB_ENABLE_CHARGING_ONLY)
|
|
#include "usb_charging_only.h"
|
|
#endif
|
|
|
|
#ifdef USB_ENABLE_HID
|
|
#include "usb_hid.h"
|
|
#endif
|
|
|
|
#ifdef USB_ENABLE_AUDIO
|
|
#include "usb_audio.h"
|
|
#include "usb_audio_def.h" // DEBUG
|
|
#endif
|
|
|
|
#ifdef USB_ENABLE_IAP
|
|
#include "usb_iap.h"
|
|
#endif
|
|
|
|
/* include order matters, include driver header before usb_drv.h */
|
|
#if CONFIG_USBOTG == USBOTG_DESIGNWARE
|
|
#include "usb-designware.h"
|
|
#elif CONFIG_USBOTG == USBOTG_RK27XX
|
|
#include "usb-rk27xx.h"
|
|
#endif
|
|
#include "usb_drv.h"
|
|
|
|
/* TODO: Move target-specific stuff somewhere else (serial number reading) */
|
|
|
|
#if defined(IPOD_ARCH) && defined(CPU_PP)
|
|
// no need to include anything
|
|
#elif defined(HAVE_AS3514)
|
|
#include "ascodec.h"
|
|
#include "as3514.h"
|
|
#elif (CONFIG_CPU == IMX233) && IMX233_SUBTARGET >= 3700
|
|
#include "ocotp-imx233.h"
|
|
#elif defined(SANSA_CONNECT)
|
|
#include "cryptomem-sansaconnect.h"
|
|
#elif (CONFIG_STORAGE & STORAGE_ATA)
|
|
#include "ata.h"
|
|
#endif
|
|
|
|
#ifndef USB_MAX_CURRENT
|
|
#define USB_MAX_CURRENT 500
|
|
#endif
|
|
|
|
#ifdef USB_ENABLE_IAP
|
|
#define NUM_CONFIGS 2
|
|
#else
|
|
#define NUM_CONFIGS 1
|
|
#endif
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* USB protocol descriptors: */
|
|
|
|
static struct usb_device_descriptor __attribute__((aligned(2)))
|
|
device_descriptor=
|
|
{
|
|
.bLength = sizeof(struct usb_device_descriptor),
|
|
.bDescriptorType = USB_DT_DEVICE,
|
|
#ifndef USB_NO_HIGH_SPEED
|
|
.bcdUSB = 0x0200,
|
|
#else
|
|
.bcdUSB = 0x0110,
|
|
#endif
|
|
.bDeviceClass = USB_CLASS_PER_INTERFACE,
|
|
.bDeviceSubClass = 0,
|
|
.bDeviceProtocol = 0,
|
|
.bMaxPacketSize0 = 64,
|
|
.idVendor = USB_VENDOR_ID,
|
|
.idProduct = USB_PRODUCT_ID,
|
|
.bcdDevice = 0x0100,
|
|
.iManufacturer = USB_STRING_INDEX_MANUFACTURER,
|
|
.iProduct = USB_STRING_INDEX_PRODUCT,
|
|
.iSerialNumber = USB_STRING_INDEX_SERIAL,
|
|
.bNumConfigurations = NUM_CONFIGS
|
|
} ;
|
|
|
|
static struct usb_config_descriptor __attribute__((aligned(2)))
|
|
config_descriptor =
|
|
{
|
|
.bLength = sizeof(struct usb_config_descriptor),
|
|
.bDescriptorType = USB_DT_CONFIG,
|
|
.wTotalLength = 0, /* will be filled in later */
|
|
.bNumInterfaces = 1,
|
|
.bConfigurationValue = 0, /* will be filled in later */
|
|
.iConfiguration = 0,
|
|
.bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
|
|
.bMaxPower = (USB_MAX_CURRENT + 1) / 2, /* In 2mA units */
|
|
};
|
|
|
|
static const struct usb_qualifier_descriptor __attribute__((aligned(2)))
|
|
qualifier_descriptor =
|
|
{
|
|
.bLength = sizeof(struct usb_qualifier_descriptor),
|
|
.bDescriptorType = USB_DT_DEVICE_QUALIFIER,
|
|
.bcdUSB = 0x0200,
|
|
.bDeviceClass = 0,
|
|
.bDeviceSubClass = 0,
|
|
.bDeviceProtocol = 0,
|
|
.bMaxPacketSize0 = 64,
|
|
.bNumConfigurations = NUM_CONFIGS
|
|
};
|
|
|
|
static const struct usb_string_descriptor usb_string_iManufacturer =
|
|
USB_STRING_INITIALIZER(u"Rockbox.org");
|
|
|
|
static const struct usb_string_descriptor usb_string_iProduct =
|
|
USB_STRING_INITIALIZER(u"Rockbox media player");
|
|
|
|
static struct usb_string_descriptor usb_string_iSerial =
|
|
USB_STRING_INITIALIZER(u"00000000000000000000000000000000000000000");
|
|
|
|
/* Generic for all targets */
|
|
|
|
/* this is stringid #0: languages supported */
|
|
static const struct usb_string_descriptor __attribute__((aligned(2)))
|
|
lang_descriptor =
|
|
USB_STRING_INITIALIZER(u"\x0409"); /* LANGID US English */
|
|
|
|
static const struct usb_string_descriptor* const usb_strings[USB_STRING_INDEX_MAX] =
|
|
{
|
|
[USB_STRING_INDEX_LANGUAGE] = &lang_descriptor,
|
|
[USB_STRING_INDEX_MANUFACTURER] = &usb_string_iManufacturer,
|
|
[USB_STRING_INDEX_PRODUCT] = &usb_string_iProduct,
|
|
[USB_STRING_INDEX_SERIAL] = &usb_string_iSerial,
|
|
};
|
|
|
|
static int usb_address = 0;
|
|
static int usb_config = 0;
|
|
static bool initialized = false;
|
|
static volatile bool bus_reset_pending = false;
|
|
static enum { DEFAULT, ADDRESS, CONFIGURED } usb_state;
|
|
|
|
#ifdef HAVE_USB_CHARGING_ENABLE
|
|
static int usb_charging_mode = USB_CHARGING_DISABLE;
|
|
static int usb_charging_current_requested = 500;
|
|
static struct timeout usb_no_host_timeout;
|
|
static bool usb_no_host = false;
|
|
|
|
static int usb_no_host_callback(struct timeout *tmo)
|
|
{
|
|
(void)tmo;
|
|
usb_no_host = true;
|
|
usb_charger_update();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static struct usb_ctrlrequest handling_request;
|
|
static struct usb_ctrlrequest pending_request;
|
|
static volatile bool have_pending_request;
|
|
|
|
/* control endpoint typical state flow
|
|
* READY -setup_received(IN, wLength==0)->
|
|
* HANDLING_TX_CONTROL -control_response(size==0)->
|
|
* EXPECT_TX_STATUS_COMP -transfer_complete(OUT)->
|
|
* READY
|
|
* -setup_received(IN, wLength>0)->
|
|
* HANDLING_TX_CONTROL -control_response(size>0)->
|
|
* EXPECT_TX_DATA_STATUS_COMP
|
|
* -transfer_complete(OUT)->
|
|
* EXPECT_TX_DATA_COMP -transfer_complete(IN)->
|
|
* READY
|
|
* -transfer_complete(IN)->
|
|
* EXPECT_TX_STATUS_COMP -transfer_complete(OUT)->
|
|
* READY
|
|
* -setup_received(OUT, wLength==0)->
|
|
* HANDLING_RX_CONTROL -control_response(size==0)->
|
|
* EXPECT_RX_STATUS_COMP -transfer_complete(IN)->
|
|
* READY
|
|
* -setup_received(OUT, wLength>0)->
|
|
* HANDLING_RX_CONTROL ->
|
|
* EXPECT_RX_DATA_COMP -transfer_complete(OUT)->
|
|
* HANDLING_TX_CONTROL -control_response(size==0)->
|
|
* EXPECT_RX_STATUS_COMP -transfer_complete()->
|
|
* READY
|
|
* */
|
|
enum {
|
|
EP0_READY, /* waiting for new setup packet */
|
|
/* IN request */
|
|
EP0_HANDLING_TX_CONTROL, /* control in received, usb thread is processing it */
|
|
EP0_EXPECT_TX_DATA_STATUS_COMP, /* sending control in data phase */
|
|
EP0_EXPECT_TX_DATA_COMP, /* status packet received earlier than tx completion */
|
|
EP0_EXPECT_TX_STATUS_COMP, /* receiving status */
|
|
/* OUT request */
|
|
EP0_HANDLING_RX_CONTROL, /* control out received, usb thread is processing it */
|
|
EP0_EXPECT_RX_DATA_COMP, /* receiving control out data phase */
|
|
EP0_EXPECT_RX_STATUS_COMP, /* sending status */
|
|
};
|
|
static volatile int ep0_state;
|
|
static bool control_cancelled;
|
|
|
|
#define TRACE_EP0_STATE 0
|
|
#if TRACE_EP0_STATE == 1
|
|
#define set_ep0_state(new) \
|
|
{ \
|
|
logf("usb_core:%d ep0_state %d -> %d", __LINE__, ep0_state, new); \
|
|
ep0_state = new; \
|
|
}
|
|
#else
|
|
#define set_ep0_state(new) {ep0_state = new;}
|
|
#endif
|
|
|
|
struct usb_transfer_completion_event_data
|
|
{
|
|
struct usb_ctrlrequest* req;
|
|
unsigned char ep;
|
|
int status;
|
|
int length;
|
|
};
|
|
|
|
static struct
|
|
{
|
|
struct usb_class_driver* driver;
|
|
struct usb_transfer_completion_event_data completion_event;
|
|
} ep_data[USB_NUM_ENDPOINTS][2];
|
|
|
|
struct ep_alloc_state {
|
|
int8_t type[2];
|
|
struct usb_class_driver* owner[2];
|
|
};
|
|
|
|
struct config_state {
|
|
struct usb_drv_ep_alloc_ctx ep_alloc_ctx;
|
|
struct ep_alloc_state ep_alloc_states[USB_NUM_ENDPOINTS];
|
|
uint8_t num_interfaces;
|
|
};
|
|
|
|
struct config_state config_states[NUM_CONFIGS];
|
|
|
|
static struct usb_class_driver* drivers[USB_NUM_DRIVERS] =
|
|
{
|
|
#ifdef USB_ENABLE_STORAGE
|
|
[USB_DRIVER_MASS_STORAGE] = &usb_cdrv_storage,
|
|
#endif
|
|
#ifdef USB_ENABLE_SERIAL
|
|
[USB_DRIVER_SERIAL] = &usb_cdrv_serial,
|
|
#endif
|
|
#ifdef USB_ENABLE_CHARGING_ONLY
|
|
[USB_DRIVER_CHARGING_ONLY] = &usb_cdrv_charging_only,
|
|
#endif
|
|
#ifdef USB_ENABLE_HID
|
|
[USB_DRIVER_HID] = &usb_cdrv_hid,
|
|
#endif
|
|
#ifdef USB_ENABLE_AUDIO
|
|
[USB_DRIVER_AUDIO] = &usb_cdrv_audio,
|
|
#endif
|
|
#ifdef USB_ENABLE_IAP
|
|
[USB_DRIVER_IAP] = &usb_cdrv_iap,
|
|
#endif
|
|
};
|
|
|
|
static int usb_core_do_set_config(uint8_t new_config);
|
|
static void usb_core_control_request_handler(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size);
|
|
|
|
#define is_active(driver) ((driver)->enabled && !(driver)->error && (driver)->config == usb_config)
|
|
#define has_if(driver, interface) ((interface) >= (driver)->first_interface && (interface) < (driver)->last_interface)
|
|
|
|
/** NOTE Serial Number
|
|
* The serial number string is split into two parts:
|
|
* - the first character indicates the set of interfaces enabled
|
|
* - the other characters form a (hopefully) unique device-specific number
|
|
* The implementation of set_serial_descriptor should left the first character
|
|
* of usb_string_iSerial unused, ie never write to
|
|
* usb_string_iSerial.wString[0] but should take it into account when
|
|
* computing the length of the descriptor
|
|
*/
|
|
|
|
static const short hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
|
#if defined(IPOD_ARCH) && defined(CPU_PP)
|
|
static void set_serial_descriptor(void)
|
|
{
|
|
#ifdef IPOD_VIDEO
|
|
uint32_t* serial = (uint32_t*)0x20004034;
|
|
#else
|
|
uint32_t* serial = (uint32_t*)0x20002034;
|
|
#endif
|
|
|
|
/* We need to convert from a little-endian 64-bit int
|
|
into a utf-16 string of hex characters */
|
|
short* p = &usb_string_iSerial.wString[24];
|
|
uint32_t x;
|
|
int i, j;
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
x = serial[i];
|
|
for(j = 0; j < 8; j++) {
|
|
*p-- = hex[x & 0xf];
|
|
x >>= 4;
|
|
}
|
|
}
|
|
usb_string_iSerial.bLength = 52;
|
|
}
|
|
#elif defined(HAVE_AS3514)
|
|
static void set_serial_descriptor(void)
|
|
{
|
|
unsigned char serial[AS3514_UID_LEN];
|
|
/* Align 32 digits right in the 40-digit serial number */
|
|
short* p = &usb_string_iSerial.wString[1];
|
|
int i;
|
|
|
|
ascodec_readbytes(AS3514_UID_0, AS3514_UID_LEN, serial);
|
|
for(i = 0; i < AS3514_UID_LEN; i++) {
|
|
*p++ = hex[(serial[i] >> 4) & 0xF];
|
|
*p++ = hex[(serial[i] >> 0) & 0xF];
|
|
}
|
|
usb_string_iSerial.bLength = 36 + (2 * AS3514_UID_LEN);
|
|
}
|
|
#elif (CONFIG_CPU == IMX233) && IMX233_SUBTARGET >= 3700
|
|
// FIXME where is the STMP3600 serial number stored ?
|
|
static void set_serial_descriptor(void)
|
|
{
|
|
short* p = &usb_string_iSerial.wString[1];
|
|
for(int i = 0; i < IMX233_NUM_OCOTP_OPS; i++) {
|
|
uint32_t ops = imx233_ocotp_read(&HW_OCOTP_OPSn(i));
|
|
for(int j = 0; j < 8; j++) {
|
|
*p++ = hex[(ops >> 28) & 0xF];
|
|
ops <<= 4;
|
|
}
|
|
}
|
|
usb_string_iSerial.bLength = 2 + 2 * (1 + IMX233_NUM_OCOTP_OPS * 8);
|
|
}
|
|
#elif defined(SANSA_CONNECT)
|
|
static void set_serial_descriptor(void)
|
|
{
|
|
char deviceid[32];
|
|
short* p = &usb_string_iSerial.wString[1];
|
|
int i;
|
|
|
|
if(!cryptomem_read_deviceid(deviceid)) {
|
|
for(i = 0; i < 32; i++) {
|
|
*p++ = deviceid[i];
|
|
}
|
|
usb_string_iSerial.bLength = 2 + 2 * (1 + 32);
|
|
} else {
|
|
device_descriptor.iSerialNumber = 0;
|
|
}
|
|
}
|
|
#elif (CONFIG_STORAGE & STORAGE_ATA)
|
|
/* If we don't know the device serial number, use the one
|
|
* from the disk */
|
|
static void set_serial_descriptor(void)
|
|
{
|
|
short* p = &usb_string_iSerial.wString[1];
|
|
unsigned short* identify = ata_get_identify();
|
|
char sn[20];
|
|
char length = 20;
|
|
int i;
|
|
|
|
for (i = 0; i < length / 2; i++) {
|
|
((unsigned short*)sn)[i] = htobe16(identify[i + 10]);
|
|
}
|
|
|
|
char is_printable = 1;
|
|
for (i = 0; i < length; i++) {
|
|
if (sn[i] < 32 || sn[i] > 126) {
|
|
is_printable = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_printable) {
|
|
/* trim ALL spaces */
|
|
int totallen = length;
|
|
for (i = 0; i < length; i++) {
|
|
if (sn[i] == ' ') {
|
|
totallen--;
|
|
continue;
|
|
}
|
|
*p++ = sn[i];
|
|
}
|
|
|
|
usb_string_iSerial.bLength = 2 + 2 * (1 + totallen);
|
|
}
|
|
else {
|
|
for (i = 0; i < length; i++) {
|
|
char x = sn[i];
|
|
*p++ = hex[(x >> 4) & 0xF];
|
|
*p++ = hex[x & 0xF];
|
|
}
|
|
|
|
usb_string_iSerial.bLength = 2 + 2 * (1 + length * 2);
|
|
}
|
|
}
|
|
#elif (CONFIG_STORAGE & STORAGE_RAMDISK)
|
|
/* This "serial number" isn't unique, but it should never actually
|
|
appear in non-testing use */
|
|
static void set_serial_descriptor(void)
|
|
{
|
|
short* p = &usb_string_iSerial.wString[1];
|
|
int i;
|
|
for(i = 0; i < 16; i++) {
|
|
*p++ = hex[(2 * i) & 0xF];
|
|
*p++ = hex[(2 * i + 1) & 0xF];
|
|
}
|
|
usb_string_iSerial.bLength = 68;
|
|
}
|
|
#else
|
|
static void set_serial_descriptor(void)
|
|
{
|
|
device_descriptor.iSerialNumber = 0;
|
|
}
|
|
#endif
|
|
|
|
void usb_core_init(void)
|
|
{
|
|
int i;
|
|
if (initialized)
|
|
return;
|
|
|
|
usb_drv_init();
|
|
|
|
/* class driver init functions should be safe to call even if the driver
|
|
* won't be used. This simplifies other logic (i.e. we don't need to know
|
|
* yet which drivers will be enabled */
|
|
for(i = 0; i < USB_NUM_DRIVERS; i++) {
|
|
drivers[i]->enabled = false;
|
|
drivers[i]->error = false;
|
|
drivers[i]->first_interface = 0;
|
|
drivers[i]->last_interface = 0;
|
|
if(drivers[i]->init != NULL) {
|
|
drivers[i]->init();
|
|
}
|
|
}
|
|
|
|
initialized = true;
|
|
usb_state = DEFAULT;
|
|
usb_config = 0;
|
|
#ifdef HAVE_USB_CHARGING_ENABLE
|
|
usb_no_host = false;
|
|
timeout_register(&usb_no_host_timeout, usb_no_host_callback, HZ*10, 0);
|
|
#endif
|
|
logf("usb_core_init() finished");
|
|
}
|
|
|
|
void usb_core_exit(void)
|
|
{
|
|
usb_core_do_set_config(0);
|
|
if(initialized) {
|
|
usb_drv_exit();
|
|
initialized = false;
|
|
}
|
|
usb_state = DEFAULT;
|
|
#ifdef HAVE_USB_CHARGING_ENABLE
|
|
usb_no_host = false;
|
|
usb_charging_maxcurrent_change(usb_charging_maxcurrent());
|
|
#endif
|
|
logf("usb_core_exit() finished");
|
|
}
|
|
|
|
/* for sending/receiving control data */
|
|
static uint8_t usb_control_data[256] USB_DEVBSS_ATTR;
|
|
|
|
void usb_core_handle_transfer_completion(
|
|
struct usb_transfer_completion_event_data* event)
|
|
{
|
|
int num = EP_NUM(event->ep);
|
|
int dir = EP_DIR(event->ep);
|
|
|
|
if(num == EP_CONTROL) {
|
|
logf("ctrl handled %ld req=0x%x", current_tick,event->req->bRequest);
|
|
usb_core_control_request_handler(event->req, usb_control_data, sizeof(usb_control_data));
|
|
return;
|
|
}
|
|
|
|
struct usb_class_driver* driver = ep_data[num][dir].driver;
|
|
if(driver && driver->transfer_complete)
|
|
driver->transfer_complete(num, dir == DIR_IN ? USB_DIR_IN : USB_DIR_OUT, event->status, event->length);
|
|
}
|
|
|
|
void usb_core_enable_driver(int driver, bool enabled)
|
|
{
|
|
drivers[driver]->enabled = enabled;
|
|
}
|
|
|
|
bool usb_core_driver_enabled(int driver)
|
|
{
|
|
return drivers[driver]->enabled;
|
|
}
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
void usb_core_hotswap_event(int volume, bool inserted)
|
|
{
|
|
int i;
|
|
for(i = 0; i < USB_NUM_DRIVERS; i++)
|
|
if(is_active(drivers[i]) && drivers[i]->notify_hotswap != NULL)
|
|
drivers[i]->notify_hotswap(volume, inserted);
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef USB_BATCH_NON_NATIVE
|
|
static uint8_t batch_ep = 0;
|
|
static bool batch_stopped;
|
|
static usb_drv_batch_get_more batch_get_more;
|
|
|
|
int usb_drv_batch_init(int ep, usb_drv_batch_get_more get_more)
|
|
{
|
|
if(batch_ep != 0) {
|
|
logf("usb_core: batch api in use user=0x%02X", batch_ep);
|
|
return -1;
|
|
}
|
|
batch_ep = ep;
|
|
batch_get_more = get_more;
|
|
batch_stopped = false;
|
|
return 0;
|
|
}
|
|
|
|
int usb_drv_batch_deinit(void)
|
|
{
|
|
logf("usb_core: batch deinit");
|
|
usb_drv_batch_stop();
|
|
batch_ep = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int batch_get_and_send(void)
|
|
{
|
|
const void* ptr;
|
|
size_t len;
|
|
batch_get_more(&ptr, &len);
|
|
if(len == 0 || batch_stopped) {
|
|
return 0;
|
|
}
|
|
return usb_drv_send_nonblocking(EP_NUM(batch_ep), (void*)ptr, len);
|
|
}
|
|
|
|
int usb_drv_batch_start(void)
|
|
{
|
|
logf("usb_core: batch start");
|
|
batch_stopped = false;
|
|
return batch_get_and_send();
|
|
}
|
|
|
|
int usb_drv_batch_stop(void)
|
|
{
|
|
batch_stopped = true;
|
|
return 0;
|
|
}
|
|
|
|
static void batch_xfer_complete(void) {
|
|
if(batch_stopped) {
|
|
return;
|
|
}
|
|
batch_get_and_send();
|
|
}
|
|
#endif
|
|
|
|
static void usb_core_set_serial_function_id(void)
|
|
{
|
|
int i, id = 0;
|
|
|
|
for(i = 0; i < USB_NUM_DRIVERS; i++)
|
|
if(drivers[i]->enabled)
|
|
id |= 1 << i;
|
|
|
|
usb_string_iSerial.wString[0] = hex[id];
|
|
}
|
|
|
|
/* synchronize endpoint initialization state to allocation state */
|
|
static void init_deinit_endpoints(int config, bool init) {
|
|
for(int epnum = 0; epnum < USB_NUM_ENDPOINTS; epnum += 1) {
|
|
for(int dir = 0; dir < 2; dir += 1) {
|
|
struct config_state* cstate = &config_states[config - 1];
|
|
struct ep_alloc_state* astate = &cstate->ep_alloc_states[epnum];
|
|
struct usb_class_driver* driver = astate->owner[dir];
|
|
if(driver == NULL) {
|
|
continue;
|
|
}
|
|
int ep = epnum | (dir == DIR_OUT ? USB_DIR_OUT : USB_DIR_IN);
|
|
if(init) {
|
|
usb_drv_ep_init(&cstate->ep_alloc_ctx, ep);
|
|
ep_data[epnum][dir].driver = driver;
|
|
} else {
|
|
usb_drv_ep_deinit(&cstate->ep_alloc_ctx, ep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef usb_drv_ep_alloc_ctx
|
|
/* default endpoint allocator using usb_drv_ep_specs table */
|
|
static void usb_drv_ep_reset_alloc_ctx(struct usb_drv_ep_alloc_ctx* ctx) {
|
|
for(int i = 0; i < USB_NUM_ENDPOINTS; i += 1) {
|
|
ctx->type[i][0] = -1;
|
|
ctx->type[i][1] = -1;
|
|
}
|
|
}
|
|
|
|
static bool usb_drv_ep_allocate(struct usb_drv_ep_alloc_ctx* ctx, int ep, int type, int max_packet_size) {
|
|
const uint8_t epnum = EP_NUM(ep);
|
|
const uint8_t epdir = EP_DIR(ep);
|
|
|
|
struct usb_drv_ep_spec* spec = &usb_drv_ep_specs[epnum];
|
|
|
|
const int8_t spec_type = spec->type[epdir];
|
|
if(spec_type != type && spec_type != USB_ENDPOINT_TYPE_ANY) {
|
|
return false;
|
|
}
|
|
|
|
const int8_t other_type = ctx->type[epnum][!epdir];
|
|
if(usb_drv_ep_specs_flags & USB_ENDPOINT_SPEC_IO_EXCLUSIVE && other_type != -1) {
|
|
/* the other side is allocated */
|
|
return false;
|
|
}
|
|
|
|
if(usb_drv_ep_specs_flags & USB_ENDPOINT_SPEC_FORCE_IO_TYPE_MATCH && other_type != -1 && other_type != type) {
|
|
/* the other side is allocated with another type */
|
|
return false;
|
|
}
|
|
|
|
ctx->type[epnum][epdir] = type;
|
|
ctx->max_packet_size[epnum][epdir] = max_packet_size;
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static void allocate_interfaces_and_endpoints(void)
|
|
{
|
|
if(usb_config != 0) {
|
|
/* deinit currently used endpoints */
|
|
init_deinit_endpoints(usb_config, false);
|
|
}
|
|
|
|
retry:
|
|
/* reset allocations */
|
|
for(int i = 0; i < NUM_CONFIGS; i += 1) {
|
|
config_states[i].num_interfaces = 0;
|
|
memset(config_states[i].ep_alloc_states, 0, sizeof(config_states[i].ep_alloc_states));
|
|
usb_drv_ep_reset_alloc_ctx(&config_states[i].ep_alloc_ctx);
|
|
}
|
|
|
|
for(int i = 0; i < USB_NUM_DRIVERS; i++) {
|
|
struct usb_class_driver* driver = drivers[i];
|
|
struct config_state* cstate = &config_states[driver->config - 1];
|
|
|
|
if(!driver->enabled || driver->error) {
|
|
continue;
|
|
}
|
|
|
|
/* assign endpoints */
|
|
for(int reqnum = 0; reqnum < driver->ep_allocs_size; reqnum += 1) {
|
|
/* find matching ep */
|
|
struct usb_class_driver_ep_allocation* req = &driver->ep_allocs[reqnum];
|
|
req->ep = 0;
|
|
for(int epnum = 1; epnum < USB_NUM_ENDPOINTS; epnum += 1) {
|
|
/* free check */
|
|
struct ep_alloc_state* alloc = &cstate->ep_alloc_states[epnum];
|
|
if(alloc->owner[req->dir] != NULL) {
|
|
continue;
|
|
}
|
|
|
|
/* driver specific check */
|
|
const int ep = epnum | (req->dir == DIR_OUT ? USB_DIR_OUT : USB_DIR_IN);
|
|
if(!usb_drv_ep_allocate(&cstate->ep_alloc_ctx, ep, req->type, req->mps)) {
|
|
continue;
|
|
}
|
|
|
|
/* all checks passed, assign it */
|
|
req->ep = ep;
|
|
alloc->owner[req->dir] = driver;
|
|
alloc->type[req->dir] = req->type;
|
|
break;
|
|
}
|
|
if(req->ep == 0 && !req->optional) {
|
|
/* no matching ep found, retry allocation excluding this driver */
|
|
logf("usb_core: no endpoint allocated for driver %d", i);
|
|
driver->enabled = false;
|
|
goto retry;
|
|
}
|
|
}
|
|
|
|
/* assign interfaces */
|
|
driver->first_interface = cstate->num_interfaces;
|
|
cstate->num_interfaces = driver->set_first_interface(cstate->num_interfaces);
|
|
driver->last_interface = cstate->num_interfaces;
|
|
}
|
|
}
|
|
|
|
static void control_request_handler_drivers(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
int i, interface = req->wIndex & 0xff;
|
|
bool handled = false;
|
|
|
|
for(i = 0; i < USB_NUM_DRIVERS; i++) {
|
|
struct usb_class_driver* driver = drivers[i];
|
|
if(!is_active(driver) || !has_if(driver, interface) || driver->control_request == NULL) {
|
|
continue;
|
|
}
|
|
|
|
/* Check for SET_INTERFACE and GET_INTERFACE */
|
|
if((req->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE &&
|
|
(req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
|
|
if(req->bRequest == USB_REQ_SET_INTERFACE) {
|
|
logf("usb_core: SET INTERFACE 0x%x 0x%x", req->wValue, req->wIndex);
|
|
if(driver->set_interface && driver->set_interface(req->wIndex, req->wValue) >= 0) {
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
handled = true;
|
|
}
|
|
break;
|
|
} else if(req->bRequest == USB_REQ_GET_INTERFACE) {
|
|
int alt = -1;
|
|
logf("usb_core: GET INTERFACE 0x%x", req->wIndex);
|
|
|
|
if(driver->get_interface)
|
|
alt = driver->get_interface(req->wIndex);
|
|
|
|
if(alt >= 0 && alt < 255) {
|
|
reqdata[0] = alt;
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, 1);
|
|
handled = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
handled = driver->control_request(req, reqdata, reqdata_size);
|
|
break; /* no other driver can handle it because it's interface specific */
|
|
}
|
|
if(!handled) {
|
|
/* nope. flag error */
|
|
logf("bad req 0x%x:0x%x:0x%x:0x%x:0x%x", req->bRequestType,req->bRequest,
|
|
req->wValue, req->wIndex, req->wLength);
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
}
|
|
}
|
|
|
|
static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
int size;
|
|
const void* ptr = NULL;
|
|
int length = req->wLength;
|
|
int type = req->wValue >> 8;
|
|
int index = req->wValue & 0xff;
|
|
|
|
switch(type) {
|
|
case USB_DT_DEVICE:
|
|
ptr = &device_descriptor;
|
|
size = sizeof(struct usb_device_descriptor);
|
|
break;
|
|
|
|
case USB_DT_OTHER_SPEED_CONFIG:
|
|
case USB_DT_CONFIG: {
|
|
if(index >= NUM_CONFIGS) {
|
|
logf("invalid config dt index %u", index);
|
|
break;
|
|
}
|
|
int i, max_packet_size;
|
|
|
|
if(type == USB_DT_CONFIG) {
|
|
max_packet_size = (usb_drv_port_speed() ? 512 : 64);
|
|
config_descriptor.bDescriptorType = USB_DT_CONFIG;
|
|
}
|
|
else {
|
|
max_packet_size = (usb_drv_port_speed() ? 64 : 512);
|
|
config_descriptor.bDescriptorType = USB_DT_OTHER_SPEED_CONFIG;
|
|
}
|
|
#ifdef HAVE_USB_CHARGING_ENABLE
|
|
if (usb_charging_mode == USB_CHARGING_DISABLE) {
|
|
config_descriptor.bMaxPower = (100+1)/2;
|
|
usb_charging_current_requested = 100;
|
|
}
|
|
else {
|
|
config_descriptor.bMaxPower = (500+1)/2;
|
|
usb_charging_current_requested = 500;
|
|
}
|
|
#endif
|
|
size = sizeof(struct usb_config_descriptor);
|
|
|
|
for(i = 0; i < USB_NUM_DRIVERS; i++) {
|
|
if(drivers[i]->enabled && !drivers[i]->error && drivers[i]->config == index + 1 && drivers[i]->get_config_descriptor) {
|
|
size += drivers[i]->get_config_descriptor(reqdata + size, max_packet_size);
|
|
}
|
|
}
|
|
|
|
config_descriptor.bNumInterfaces = config_states[index].num_interfaces;
|
|
config_descriptor.bConfigurationValue = index + 1;
|
|
config_descriptor.wTotalLength = (uint16_t)size;
|
|
memcpy(reqdata, &config_descriptor, sizeof(struct usb_config_descriptor));
|
|
|
|
ptr = reqdata;
|
|
} break;
|
|
case USB_DT_STRING:
|
|
logf("STRING %d", index);
|
|
if((unsigned)index < USB_STRING_INDEX_MAX) {
|
|
size = usb_strings[index]->bLength;
|
|
ptr = usb_strings[index];
|
|
}
|
|
else if(index == 0xee) {
|
|
/* We don't have a real OS descriptor, and we don't handle
|
|
* STALL correctly on some devices, so we return any valid
|
|
* string (we arbitrarily pick the manufacturer name)
|
|
*/
|
|
size = usb_string_iManufacturer.bLength;
|
|
ptr = &usb_string_iManufacturer;
|
|
}
|
|
else {
|
|
logf("bad string id %d", index);
|
|
ptr = NULL;
|
|
}
|
|
break;
|
|
|
|
case USB_DT_DEVICE_QUALIFIER:
|
|
ptr = &qualifier_descriptor;
|
|
size = sizeof(struct usb_qualifier_descriptor);
|
|
break;
|
|
|
|
default:
|
|
logf("ctrl desc.");
|
|
control_request_handler_drivers(req, reqdata, reqdata_size);
|
|
return;
|
|
}
|
|
|
|
if(ptr) {
|
|
logf("data %d (%d)", size, length);
|
|
length = MIN(size, length);
|
|
|
|
if (ptr != reqdata)
|
|
memcpy(reqdata, ptr, length);
|
|
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, length);
|
|
} else {
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
}
|
|
}
|
|
|
|
static void usb_core_init_interfaces(void)
|
|
{
|
|
if(usb_state == DEFAULT) {
|
|
set_serial_descriptor();
|
|
usb_core_set_serial_function_id();
|
|
allocate_interfaces_and_endpoints();
|
|
}
|
|
}
|
|
|
|
static void usb_core_set_address(uint8_t address)
|
|
{
|
|
logf("usb_core: SET_ADR %d", address);
|
|
/* A host may address the device before making any other request;
|
|
* the driver answers SET_ADDRESS itself, so nothing has been
|
|
* allocated yet. */
|
|
usb_core_init_interfaces();
|
|
usb_address = address;
|
|
usb_state = ADDRESS;
|
|
}
|
|
|
|
#if !defined(HAVE_PRIORITY_SCHEDULING)
|
|
#define thread_set_priority(...)
|
|
#define thread_get_priority(...)
|
|
#endif /* HAVE_PRIORITY_SCHEDULING */
|
|
|
|
static int usb_core_do_set_config(uint8_t new_config)
|
|
{
|
|
logf("usb_core: SET_CONFIG %d to %d", usb_config, new_config);
|
|
|
|
if(new_config > NUM_CONFIGS) {
|
|
logf("usb_core: invalid config number");
|
|
return -1;
|
|
}
|
|
|
|
/* deactivate old config */
|
|
if(usb_config != 0) {
|
|
for(int i = 0; i < USB_NUM_DRIVERS; i++) {
|
|
if(is_active(drivers[i]) && drivers[i]->disconnect != NULL) {
|
|
drivers[i]->disconnect();
|
|
}
|
|
}
|
|
init_deinit_endpoints(usb_config, false);
|
|
|
|
/* clear any pending transfer completions,
|
|
* because they are depend on contents of ep_data */
|
|
usb_clear_pending_transfer_completion_events();
|
|
/* reset endpoint states */
|
|
memset(ep_data, 0, sizeof(ep_data));
|
|
}
|
|
|
|
usb_config = new_config;
|
|
usb_state = usb_config == 0 ? ADDRESS : CONFIGURED;
|
|
|
|
bool require_exclusive = false;
|
|
bool require_cpu_boost = false;
|
|
|
|
/* activate new config */
|
|
if(usb_config != 0) {
|
|
init_deinit_endpoints(usb_config, true);
|
|
for(int i = 0; i < USB_NUM_DRIVERS; i++) {
|
|
if(!is_active(drivers[i])) {
|
|
continue;
|
|
}
|
|
if(drivers[i]->init_connection != NULL && drivers[i]->init_connection() < 0) {
|
|
drivers[i]->error = true;
|
|
continue;
|
|
}
|
|
require_exclusive |= drivers[i]->needs_exclusive_storage;
|
|
require_cpu_boost |= drivers[i]->needs_cpu_boost;
|
|
}
|
|
}
|
|
|
|
if(require_exclusive) {
|
|
/* Also preserve a pending handover across repeated SET_CONFIG.
|
|
* Restarting it discards acknowledgements and races the USB UI. */
|
|
usb_request_exclusive_storage();
|
|
} else if(!bus_reset_pending) {
|
|
usb_release_exclusive_storage();
|
|
}
|
|
/* A bus reset is not a physical disconnect. Keep the storage handover
|
|
* (and its acknowledgement epoch) until the host selects a new config
|
|
* or the cable is removed; remounting here races re-enumeration.
|
|
* If the host never sends SET_CONFIGURATION again, local storage stays
|
|
* unavailable until unplug. We expect reconfiguration promptly after a
|
|
* reset, but deliberately do not remount on a timer. */
|
|
if(require_cpu_boost) {
|
|
trigger_cpu_boost();
|
|
thread_set_priority(thread_self(), PRIORITY_REALTIME);
|
|
} else {
|
|
thread_set_priority(thread_self(), PRIORITY_SYSTEM);
|
|
cancel_cpu_boost();
|
|
}
|
|
|
|
|
|
#ifdef HAVE_USB_CHARGING_ENABLE
|
|
usb_charging_maxcurrent_change(usb_charging_maxcurrent());
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void usb_core_do_clear_feature(int recip, int recip_nr, int feature)
|
|
{
|
|
logf("usb_core: CLEAR FEATURE (%d,%d,%d)", recip, recip_nr, feature);
|
|
if(recip == USB_RECIP_ENDPOINT)
|
|
{
|
|
if(feature == USB_ENDPOINT_HALT)
|
|
usb_drv_stall(EP_NUM(recip_nr), false, EP_DIR(recip_nr));
|
|
}
|
|
}
|
|
|
|
static void request_handler_device(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
switch(req->bRequest) {
|
|
case USB_REQ_GET_CONFIGURATION:
|
|
logf("usb_core: GET_CONFIG");
|
|
reqdata[0] = usb_config;
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, 1);
|
|
break;
|
|
case USB_REQ_SET_CONFIGURATION:
|
|
usb_drv_cancel_all_transfers();
|
|
if(usb_core_do_set_config(req->wValue) == 0) {
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
} else {
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
}
|
|
break;
|
|
case USB_REQ_GET_DESCRIPTOR:
|
|
logf("usb_core: GET_DESC %d", req->wValue >> 8);
|
|
request_handler_device_get_descriptor(req, reqdata, reqdata_size);
|
|
break;
|
|
case USB_REQ_SET_FEATURE:
|
|
if(req->wValue==USB_DEVICE_TEST_MODE) {
|
|
int mode = req->wIndex >> 8;
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
usb_drv_set_test_mode(mode);
|
|
} else {
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
}
|
|
break;
|
|
case USB_REQ_GET_STATUS:
|
|
reqdata[0] = 0;
|
|
reqdata[1] = 0;
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, 2);
|
|
break;
|
|
#ifdef USB_ENABLE_IAP
|
|
case USB_REQ_APPLE_SET_AVAIL_CURRENT:
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
break;
|
|
#endif
|
|
default:
|
|
logf("bad req:desc %d:%d", req->bRequest, req->wValue);
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void request_handler_interface_standard(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
switch (req->bRequest)
|
|
{
|
|
case USB_REQ_SET_INTERFACE:
|
|
logf("usb_core: SET_INTERFACE");
|
|
case USB_REQ_GET_INTERFACE:
|
|
control_request_handler_drivers(req, reqdata, reqdata_size);
|
|
break;
|
|
case USB_REQ_GET_STATUS:
|
|
reqdata[0] = 0;
|
|
reqdata[1] = 0;
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, 2);
|
|
break;
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
case USB_REQ_SET_FEATURE:
|
|
/* TODO: These used to be ignored (erroneously).
|
|
* Should they be passed to the drivers instead? */
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
break;
|
|
default:
|
|
control_request_handler_drivers(req, reqdata, reqdata_size);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void request_handler_interface(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
switch(req->bRequestType & USB_TYPE_MASK) {
|
|
case USB_TYPE_STANDARD:
|
|
request_handler_interface_standard(req, reqdata, reqdata_size);
|
|
break;
|
|
case USB_TYPE_CLASS:
|
|
control_request_handler_drivers(req, reqdata, reqdata_size);
|
|
break;
|
|
case USB_TYPE_VENDOR:
|
|
default:
|
|
logf("bad req:desc %d", req->bRequest);
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void request_handler_endpoint_drivers(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
int num = EP_NUM(req->wIndex);
|
|
int dir = EP_DIR(req->wIndex);
|
|
|
|
if(num >= USB_NUM_ENDPOINTS)
|
|
goto error;
|
|
|
|
struct usb_class_driver* driver = ep_data[num][dir].driver;
|
|
|
|
if(!driver || !driver->control_request)
|
|
goto error;
|
|
|
|
if(!driver->control_request(req, reqdata, reqdata_size))
|
|
goto error;
|
|
|
|
return;
|
|
|
|
error:
|
|
/* nope. flag error */
|
|
logf("bad req 0x%x:0x%x:0x%x:0x%x:0x%x", req->bRequestType,req->bRequest,
|
|
req->wValue, req->wIndex, req->wLength);
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
}
|
|
|
|
static void request_handler_endpoint_standard(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
switch (req->bRequest) {
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
usb_core_do_clear_feature(USB_RECIP_ENDPOINT,
|
|
req->wIndex,
|
|
req->wValue);
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
break;
|
|
case USB_REQ_SET_FEATURE:
|
|
logf("usb_core: SET FEATURE (%d)", req->wValue);
|
|
if(req->wValue == USB_ENDPOINT_HALT)
|
|
usb_drv_stall(EP_NUM(req->wIndex), true, EP_DIR(req->wIndex));
|
|
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
break;
|
|
case USB_REQ_GET_STATUS:
|
|
reqdata[0] = 0;
|
|
reqdata[1] = 0;
|
|
logf("usb_core: GET_STATUS");
|
|
if(req->wIndex > 0)
|
|
reqdata[0] = usb_drv_stalled(EP_NUM(req->wIndex),
|
|
EP_DIR(req->wIndex));
|
|
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, 2);
|
|
break;
|
|
default:
|
|
request_handler_endpoint_drivers(req, reqdata, reqdata_size);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void request_handler_endpoint(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
switch(req->bRequestType & USB_TYPE_MASK) {
|
|
case USB_TYPE_STANDARD:
|
|
request_handler_endpoint_standard(req, reqdata, reqdata_size);
|
|
break;
|
|
case USB_TYPE_CLASS:
|
|
request_handler_endpoint_drivers(req, reqdata, reqdata_size);
|
|
break;
|
|
case USB_TYPE_VENDOR:
|
|
default:
|
|
logf("bad req:desc %d", req->bRequest);
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Handling USB requests starts here */
|
|
static void usb_core_control_request_handler(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size)
|
|
{
|
|
#ifdef HAVE_USB_CHARGING_ENABLE
|
|
timeout_cancel(&usb_no_host_timeout);
|
|
if(usb_no_host) {
|
|
usb_no_host = false;
|
|
usb_charging_maxcurrent_change(usb_charging_maxcurrent());
|
|
}
|
|
#endif
|
|
usb_core_init_interfaces();
|
|
|
|
switch(req->bRequestType & USB_RECIP_MASK) {
|
|
case USB_RECIP_DEVICE:
|
|
request_handler_device(req, reqdata, reqdata_size);
|
|
break;
|
|
case USB_RECIP_INTERFACE:
|
|
request_handler_interface(req, reqdata, reqdata_size);
|
|
break;
|
|
case USB_RECIP_ENDPOINT:
|
|
request_handler_endpoint(req, reqdata, reqdata_size);
|
|
break;
|
|
default:
|
|
logf("unsupported recipient");
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void do_bus_reset(void) {
|
|
usb_address = 0;
|
|
usb_state = DEFAULT;
|
|
bus_reset_pending = false;
|
|
set_ep0_state(EP0_READY);
|
|
have_pending_request = false;
|
|
}
|
|
|
|
/* called by usb_drv_int() */
|
|
void usb_core_bus_reset(void)
|
|
{
|
|
logf("usb_core: bus reset");
|
|
if(bus_reset_pending) {
|
|
return;
|
|
}
|
|
bus_reset_pending = true;
|
|
if(usb_config == 0) {
|
|
do_bus_reset();
|
|
} else {
|
|
/* need to disconnect class drivers, defer it to usb thread */
|
|
usb_signal_notify(USB_NOTIFY_BUS_RESET, 0);
|
|
}
|
|
}
|
|
|
|
static void signal_xfer_complete(int ep, struct usb_ctrlrequest* req, int status, int length) {
|
|
struct usb_transfer_completion_event_data* completion_event =
|
|
&ep_data[EP_NUM(ep)][EP_DIR(ep)].completion_event;
|
|
|
|
completion_event->req = req;
|
|
completion_event->ep = ep;
|
|
completion_event->status = status;
|
|
completion_event->length = length;
|
|
usb_signal_transfer_completion(completion_event);
|
|
}
|
|
|
|
static void process_setup_request(struct usb_ctrlrequest* req) {
|
|
control_cancelled = false;
|
|
set_ep0_state(req->bRequestType & USB_DIR_IN ? EP0_HANDLING_TX_CONTROL : EP0_HANDLING_RX_CONTROL);
|
|
|
|
if(ep0_state == EP0_HANDLING_TX_CONTROL || req->wLength == 0) {
|
|
signal_xfer_complete(EP_CONTROL | USB_DIR_IN, req, 0, 0);
|
|
return;
|
|
}
|
|
|
|
/* start control out data phase without usb thread interaction */
|
|
if(req->wLength > sizeof(usb_control_data)) {
|
|
logf("usb_core: control write too large %u > %u", req->wLength, sizeof(usb_control_data));
|
|
usb_drv_stall(EP_CONTROL, true, false);
|
|
set_ep0_state(EP0_READY);
|
|
return;
|
|
}
|
|
set_ep0_state(EP0_EXPECT_RX_DATA_COMP);
|
|
usb_drv_recv_nonblocking(EP_CONTROL, usb_control_data, req->wLength);
|
|
return;
|
|
}
|
|
|
|
static bool check_for_new_setup(void) {
|
|
int oldlevel = disable_irq_save();
|
|
if(!have_pending_request) {
|
|
restore_irq(oldlevel);
|
|
return false;
|
|
}
|
|
have_pending_request = false;
|
|
handling_request = pending_request;
|
|
process_setup_request(&handling_request);
|
|
restore_irq(oldlevel);
|
|
return true;
|
|
}
|
|
|
|
/* called by usb_drv_transfer_completed() */
|
|
void usb_core_transfer_complete(int ep, int dir, int status, int length) {
|
|
#ifdef USB_BATCH_NON_NATIVE
|
|
/* batch api */
|
|
if(batch_ep != 0 && (ep | dir) == batch_ep) {
|
|
batch_xfer_complete();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Fast notification */
|
|
struct usb_class_driver* driver = ep_data[ep][EP_DIR(dir)].driver;
|
|
if(driver && driver->fast_transfer_complete && driver->fast_transfer_complete(ep, dir, status, length)) {
|
|
return; /* do not dispatch to the queue if handled */
|
|
}
|
|
|
|
/* Non-control packet handling */
|
|
if(ep != EP_CONTROL) {
|
|
signal_xfer_complete(ep | dir, NULL, status, length);
|
|
return;
|
|
}
|
|
|
|
/* Control packet handling */
|
|
switch(dir | ep0_state) {
|
|
/* EXPECT_TX_DATA_STATUS_COMP -(status comp)-> EXPECT_TX_DATA_COMP -(data comp)-> READY
|
|
* -(data comp)-> EXPECT_TX_STATUS_COMP -(status comp)-> READY */
|
|
case USB_DIR_OUT | EP0_EXPECT_TX_DATA_STATUS_COMP:
|
|
logf("usb_core: control-in done success=%d", status == 0 && length == 0);
|
|
set_ep0_state(EP0_EXPECT_TX_DATA_COMP);
|
|
break;
|
|
case USB_DIR_IN | EP0_EXPECT_TX_DATA_STATUS_COMP:
|
|
set_ep0_state(EP0_EXPECT_TX_STATUS_COMP);
|
|
break;
|
|
case USB_DIR_OUT | EP0_EXPECT_TX_STATUS_COMP:
|
|
logf("usb_core: control-in done success=%d", status == 0 && length == 0);
|
|
set_ep0_state(EP0_READY);
|
|
break;
|
|
case USB_DIR_IN | EP0_EXPECT_TX_DATA_COMP:
|
|
set_ep0_state(EP0_READY);
|
|
break;
|
|
/* EXPECT_RX_DATA_COMP -(data comp)-> HANDLING_RX_CONTROL [-(send status)-> EXPECT_RX_STATUS_COMP] -(status comp)-> READY
|
|
* ^ done in control_response() */
|
|
case USB_DIR_OUT | EP0_EXPECT_RX_DATA_COMP:
|
|
set_ep0_state(EP0_HANDLING_RX_CONTROL);
|
|
signal_xfer_complete(EP_CONTROL | USB_DIR_OUT, &handling_request, status, length);
|
|
break;
|
|
case USB_DIR_IN | EP0_EXPECT_RX_STATUS_COMP:
|
|
logf("usb_core: control-out done success=%d", status == 0 && length == 0);
|
|
set_ep0_state(EP0_READY);
|
|
break;
|
|
default:
|
|
panicf("unhandled endpoint xfer completion ep0_state=%d dir=%d", ep0_state, dir);
|
|
break;
|
|
}
|
|
|
|
if(ep0_state == EP0_READY) {
|
|
check_for_new_setup();
|
|
}
|
|
}
|
|
|
|
void usb_core_handle_notify(long id, intptr_t data)
|
|
{
|
|
switch(id)
|
|
{
|
|
case USB_NOTIFY_SET_ADDR:
|
|
usb_core_set_address(data);
|
|
break;
|
|
case USB_NOTIFY_SET_CONFIG:
|
|
usb_core_do_set_config(data);
|
|
break;
|
|
case USB_NOTIFY_BUS_RESET:
|
|
usb_core_do_set_config(0);
|
|
do_bus_reset();
|
|
#ifdef HAVE_USB_CHARGING_ENABLE
|
|
usb_charging_maxcurrent_change(usb_charging_maxcurrent());
|
|
#endif
|
|
break;
|
|
case USB_NOTIFY_CLASS_DRIVER: {
|
|
/* HACK: index is uint8 but promoted to int to avoid a compiler
|
|
warning when USB_NUM_DRIVERS is 0, mainly in bootloaders.
|
|
This hack can be removed once usb_core is no longer built
|
|
for BOOTLOADER && !HAVE_BOOTLOADER_USB_MODE */
|
|
int index = data >> 24;
|
|
if(index < 0 || index >= USB_NUM_DRIVERS) {
|
|
logf("usb_core: invalid notification destination index=%u", index);
|
|
return;
|
|
}
|
|
if(is_active(drivers[index]) && drivers[index]->notify_event != NULL) {
|
|
drivers[index]->notify_event(data & 0x00ffffff);
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void usb_core_control_cancelled(void)
|
|
{
|
|
/* A newer SETUP supersedes even a request deferred behind a handler. */
|
|
have_pending_request = false;
|
|
/* A queued/running handler still owns handling_request and the shared
|
|
* buffer. Suppress its response; a later SETUP may replace it on return. */
|
|
if(ep0_state == EP0_HANDLING_TX_CONTROL || ep0_state == EP0_HANDLING_RX_CONTROL)
|
|
control_cancelled = true;
|
|
else
|
|
set_ep0_state(EP0_READY);
|
|
}
|
|
|
|
void usb_core_setup_received(struct usb_ctrlrequest* req) {
|
|
if(bus_reset_pending) {
|
|
logf("usb_core: bus resetting tick=%lu", current_tick);
|
|
return;
|
|
}
|
|
/* Drivers cancel abandoned EP0 transfers before delivering replacement
|
|
* SETUP. A handler may still own the request buffer; defer until it returns. */
|
|
if(ep0_state != EP0_READY) {
|
|
logf("usb_core: control pending tick=%lu", current_tick);
|
|
pending_request = *req;
|
|
have_pending_request = true;
|
|
return;
|
|
}
|
|
have_pending_request = false;
|
|
handling_request = *req;
|
|
process_setup_request(&handling_request);
|
|
}
|
|
|
|
void usb_core_control_response(enum usb_control_response response, const void* data, size_t size) {
|
|
logf("usb_core: response ack=%d size=%u ep0_state=%d tick=%lu", response, size, ep0_state, current_tick);
|
|
|
|
int oldlevel = disable_irq_save();
|
|
if(bus_reset_pending) {
|
|
restore_irq(oldlevel);
|
|
return;
|
|
}
|
|
if((ep0_state == EP0_HANDLING_TX_CONTROL || ep0_state == EP0_HANDLING_RX_CONTROL) && check_for_new_setup()) {
|
|
restore_irq(oldlevel);
|
|
return;
|
|
}
|
|
|
|
if(control_cancelled) {
|
|
control_cancelled = false;
|
|
set_ep0_state(EP0_READY);
|
|
restore_irq(oldlevel);
|
|
return;
|
|
}
|
|
|
|
if(response == USB_CONTROL_STALL) {
|
|
set_ep0_state(EP0_READY);
|
|
usb_drv_stall(EP_CONTROL, true, true);
|
|
restore_irq(oldlevel);
|
|
return;
|
|
}
|
|
|
|
switch(ep0_state) {
|
|
case EP0_HANDLING_TX_CONTROL:
|
|
if(size == 0) {
|
|
/* non-data control-in */
|
|
set_ep0_state(EP0_EXPECT_TX_STATUS_COMP);
|
|
usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
|
|
} else {
|
|
/* control-in data phase */
|
|
set_ep0_state(EP0_EXPECT_TX_DATA_STATUS_COMP);
|
|
/* prepare for a status packet before sending data.
|
|
* note that it is driver and host-dependent that which completion
|
|
* of the following two commands is notified first. */
|
|
usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
|
|
/* send data packets */
|
|
usb_drv_send_nonblocking(EP_CONTROL, (void*)data, size);
|
|
}
|
|
break;
|
|
case EP0_HANDLING_RX_CONTROL:
|
|
if(size == 0) {
|
|
/* non-data control-out */
|
|
set_ep0_state(EP0_EXPECT_RX_STATUS_COMP);
|
|
/* send status packet*/
|
|
usb_drv_send_nonblocking(EP_CONTROL, NULL, 0);
|
|
} else {
|
|
/* control-out data phase */
|
|
/* should not happen, we've received it internally */
|
|
logf("usb_core: receiving control data by class drivers is not allowed");
|
|
}
|
|
break;
|
|
default:
|
|
panicf("usb_core: invalid control response ep_state=%d", ep0_state);
|
|
break;
|
|
}
|
|
restore_irq(oldlevel);
|
|
}
|
|
|
|
void usb_core_notify_set_address(uint8_t addr)
|
|
{
|
|
logf("notify set addr received %ld", current_tick);
|
|
usb_signal_notify(USB_NOTIFY_SET_ADDR, addr);
|
|
}
|
|
|
|
void usb_core_notify_set_config(uint8_t config)
|
|
{
|
|
logf("notify set config received %ld", current_tick);
|
|
usb_signal_notify(USB_NOTIFY_SET_CONFIG, config);
|
|
}
|
|
|
|
#ifdef HAVE_USB_CHARGING_ENABLE
|
|
void usb_charging_enable(int state)
|
|
{
|
|
usb_charging_mode = state;
|
|
usb_charging_maxcurrent_change(usb_charging_maxcurrent());
|
|
}
|
|
|
|
int usb_charging_maxcurrent(void)
|
|
{
|
|
if (!initialized || usb_charging_mode == USB_CHARGING_DISABLE)
|
|
return 100;
|
|
if (usb_state == CONFIGURED)
|
|
return usb_charging_current_requested;
|
|
if (usb_charging_mode == USB_CHARGING_FORCE && usb_no_host)
|
|
return 500;
|
|
return 100;
|
|
}
|
|
#endif
|