Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/host/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,8 @@ static void process_new_status(tuh_xfer_t* xfer) {
}
};
hcd_event_handler(&event, false);
processed = true; // usbh queue status after handled this in (de)enumeration
// skip status for attach event, usbh will do it after handled this enumeration
processed = (event.event_id == HCD_EVENT_DEVICE_ATTACH);
break;
}

Expand Down
254 changes: 126 additions & 128 deletions src/host/usbh.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
#include "hub.h"

//--------------------------------------------------------------------+
// USBH Configuration
// Configuration
//--------------------------------------------------------------------+
#ifndef CFG_TUH_TASK_QUEUE_SZ
#define CFG_TUH_TASK_QUEUE_SZ 16
Expand Down Expand Up @@ -89,7 +89,7 @@ TU_ATTR_WEAK bool hcd_dcache_clean_invalidate(const void* addr, uint32_t data_si
}

//--------------------------------------------------------------------+
// USBH-HCD common data structure
// Data Structure
//--------------------------------------------------------------------+
typedef struct {
tuh_bus_info_t bus_info;
Expand Down Expand Up @@ -131,8 +131,60 @@ typedef struct {

} usbh_device_t;

// sum of end device + hub
#define TOTAL_DEVICES (CFG_TUH_DEVICE_MAX + CFG_TUH_HUB)

// all devices excluding zero-address
// hub address start from CFG_TUH_DEVICE_MAX+1
// TODO: hub can has its own simpler struct to save memory
static usbh_device_t _usbh_devices[TOTAL_DEVICES];

// Mutex for claiming endpoint
#if OSAL_MUTEX_REQUIRED
static osal_mutex_def_t _usbh_mutexdef;
static osal_mutex_t _usbh_mutex;
#else
#define _usbh_mutex NULL
#endif

// Event queue: usbh_int_set() is used as mutex in OS NONE config
OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t);
static osal_queue_t _usbh_q;

// Control transfers: since most controllers do not support multiple control transfers
// on multiple devices concurrently and control transfers are not used much except for
// enumeration, we will only execute control transfers one at a time.
typedef struct {
uint8_t* buffer;
tuh_xfer_cb_t complete_cb;
uintptr_t user_data;

volatile uint8_t stage;
uint8_t daddr;
volatile uint16_t actual_len;
uint8_t failed_count;
} usbh_ctrl_xfer_info_t;

typedef struct {
uint8_t controller_id; // controller ID
uint8_t enumerating_daddr; // device address of the device being enumerated
uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing
tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration
usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer
} usbh_data_t;

static usbh_data_t _usbh_data = {
.controller_id = TUSB_INDEX_INVALID_8,
};

typedef struct {
TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request);
TUH_EPBUF_DEF(ctrl, CFG_TUH_ENUMERATION_BUFSIZE);
} usbh_epbuf_t;
CFG_TUH_MEM_SECTION static usbh_epbuf_t _usbh_epbuf;

//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF
// Class Driver
//--------------------------------------------------------------------+
#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL
#define DRIVER_NAME(_name) _name
Expand Down Expand Up @@ -235,82 +287,62 @@ static inline usbh_class_driver_t const *get_driver(uint8_t drv_id) {
}

//--------------------------------------------------------------------+
// INTERNAL OBJECT & FUNCTION DECLARATION
// Function Inline and Prototypes
//--------------------------------------------------------------------+
static bool enum_new_device(hcd_event_t* event);
static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port);
static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size);
static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);

// sum of end device + hub
#define TOTAL_DEVICES (CFG_TUH_DEVICE_MAX + CFG_TUH_HUB)

// all devices excluding zero-address
// hub address start from CFG_TUH_DEVICE_MAX+1
// TODO: hub can has its own simpler struct to save memory
static usbh_device_t _usbh_devices[TOTAL_DEVICES];

// Mutex for claiming endpoint
#if OSAL_MUTEX_REQUIRED
static osal_mutex_def_t _usbh_mutexdef;
static osal_mutex_t _usbh_mutex;
#else
#define _usbh_mutex NULL
#endif

// Event queue: usbh_int_set() is used as mutex in OS NONE config
OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t);
static osal_queue_t _usbh_q;

// Control transfers: since most controllers do not support multiple control transfers
// on multiple devices concurrently and control transfers are not used much except for
// enumeration, we will only execute control transfers one at a time.
typedef struct {
uint8_t* buffer;
tuh_xfer_cb_t complete_cb;
uintptr_t user_data;

volatile uint8_t stage;
uint8_t daddr;
volatile uint16_t actual_len;
uint8_t failed_count;
} usbh_ctrl_xfer_info_t;

typedef struct {
uint8_t controller_id; // controller ID
uint8_t enumerating_daddr; // device address of the device being enumerated
uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing
tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration
usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer
} usbh_data_t;

static usbh_data_t _usbh_data = {
.controller_id = TUSB_INDEX_INVALID_8,
};

typedef struct {
TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request);
TUH_EPBUF_DEF(ctrl, CFG_TUH_ENUMERATION_BUFSIZE);
} usbh_epbuf_t;
CFG_TUH_MEM_SECTION static usbh_epbuf_t _usbh_epbuf;

//------------- Helper Function -------------//
TU_ATTR_ALWAYS_INLINE static inline usbh_device_t* get_device(uint8_t dev_addr) {
TU_VERIFY(dev_addr > 0 && dev_addr <= TOTAL_DEVICES, NULL);
return &_usbh_devices[dev_addr-1];
}

static bool enum_new_device(hcd_event_t* event);
static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port);
static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size);
static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
TU_ATTR_ALWAYS_INLINE static inline bool is_hub_addr(uint8_t daddr) {
return (CFG_TUH_HUB > 0) && (daddr > CFG_TUH_DEVICE_MAX);
}

TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, bool in_isr) {
TU_ASSERT(osal_queue_send(_usbh_q, event, in_isr));
tuh_event_hook_cb(event->rhport, event->event_id, in_isr);
return true;
}

TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) {
if (_usbh_data.ctrl_xfer_info.stage != stage) {
(void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER);
_usbh_data.ctrl_xfer_info.stage = stage;
(void) osal_mutex_unlock(_usbh_mutex);
}
}

TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const uint8_t setup_packet[8]) {
const uint8_t rhport = usbh_get_rhport(daddr);
const bool ret = hcd_setup_send(rhport, daddr, setup_packet);
if (!ret) {
_control_set_xfer_stage(CONTROL_STAGE_IDLE);
}
return ret;
}

TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) {
hcd_device_close(rhport, daddr);

// abort any ongoing control transfer
if (daddr == _usbh_data.ctrl_xfer_info.daddr) {
_control_set_xfer_stage(CONTROL_STAGE_IDLE);
}

// invalidate if enumerating
if (daddr == _usbh_data.enumerating_daddr) {
_usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8;
}
}

//--------------------------------------------------------------------+
// Device API
//--------------------------------------------------------------------+

bool tuh_mounted(uint8_t dev_addr) {
usbh_device_t *dev = get_device(dev_addr);
TU_VERIFY(dev);
Expand Down Expand Up @@ -530,16 +562,16 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) {
break;

case HCD_EVENT_DEVICE_REMOVE:
TU_LOG_USBH("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port);
process_removed_device(event.rhport, event.connection.hub_addr, event.connection.hub_port);

#if CFG_TUH_HUB
// TODO remove
if (event.connection.hub_addr != 0 && event.connection.hub_port != 0) {
// done with hub, waiting for next data on status pipe
(void) hub_edpt_status_xfer(event.connection.hub_addr);
TU_LOG1("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port);
if (_usbh_data.enumerating_daddr == 0 &&
event.rhport == _usbh_data.dev0_bus.rhport &&
event.connection.hub_addr == _usbh_data.dev0_bus.hub_addr &&
event.connection.hub_port == _usbh_data.dev0_bus.hub_port) {
// dev0 is unplugged while enumerating (not yet assigned an address)
usbh_device_close(_usbh_data.dev0_bus.rhport, 0);
} else {
process_removed_device(event.rhport, event.connection.hub_addr, event.connection.hub_port);
}
#endif
break;

case HCD_EVENT_XFER_COMPLETE: {
Expand Down Expand Up @@ -623,23 +655,6 @@ static void _control_blocking_complete_cb(tuh_xfer_t* xfer) {
*((xfer_result_t*) xfer->user_data) = xfer->result;
}

TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) {
if (_usbh_data.ctrl_xfer_info.stage != stage) {
(void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER);
_usbh_data.ctrl_xfer_info.stage = stage;
(void) osal_mutex_unlock(_usbh_mutex);
}
}

TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const uint8_t setup_packet[8]) {
const uint8_t rhport = usbh_get_rhport(daddr);
const bool ret = hcd_setup_send(rhport, daddr, setup_packet);
if (!ret) {
_control_set_xfer_stage(CONTROL_STAGE_IDLE);
}
return ret;
}

// TODO timeout_ms is not supported yet
bool tuh_control_xfer (tuh_xfer_t* xfer) {
TU_VERIFY(xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet
Expand Down Expand Up @@ -1263,28 +1278,13 @@ uint8_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_i
//--------------------------------------------------------------------+
// Detaching
//--------------------------------------------------------------------+

TU_ATTR_ALWAYS_INLINE static inline bool is_hub_addr(uint8_t daddr) {
return (CFG_TUH_HUB > 0) && (daddr > CFG_TUH_DEVICE_MAX);
}

// a device unplugged from rhport:hub_addr:hub_port
static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) {
// if dev0 is unplugged while enumerating (not yet assigned an address)
if (_usbh_data.enumerating_daddr == 0) {
const tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus;
if ((rhport == dev0_bus->rhport) && (hub_addr == dev0_bus->hub_addr) && (hub_port == dev0_bus->hub_port)) {
hcd_device_close(dev0_bus->rhport, 0);
if (_usbh_data.ctrl_xfer_info.daddr == 0) {
_control_set_xfer_stage(CONTROL_STAGE_IDLE);
}
_usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8;
return;
}
}
// Find the all devices (star-network) under port that is unplugged
#if CFG_TUH_HUB
uint8_t removing_hubs[CFG_TUH_HUB] = { 0 };
#endif

//------------- find the all devices (star-network) under port that is unplugged -------------//
uint32_t removing_hubs = 0;
do {
for (uint8_t dev_id = 0; dev_id < TOTAL_DEVICES; dev_id++) {
usbh_device_t* dev = &_usbh_devices[dev_id];
Expand All @@ -1296,10 +1296,13 @@ static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub
(hub_port == 0 || dev->bus_info.hub_port == hub_port)) {
TU_LOG_USBH("[%u:%u:%u] unplugged address = %u\r\n", rhport, hub_addr, hub_port, daddr);

#if CFG_TUH_HUB
if (is_hub_addr(daddr)) {
TU_LOG_USBH(" is a HUB device %u\r\n", daddr);
removing_hubs |= TU_BIT(dev_id - CFG_TUH_DEVICE_MAX);
} else {
removing_hubs[dev_id - CFG_TUH_DEVICE_MAX] = 1;
} else
#endif
{
// Invoke callback before closing driver (maybe call it later ?)
if (tuh_umount_cb) {
tuh_umount_cb(daddr);
Expand All @@ -1314,41 +1317,32 @@ static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub
}
}

hcd_device_close(rhport, daddr);
usbh_device_close(rhport, daddr);
clear_device(dev);

// abort ongoing control xfer on this device if any
if (daddr == _usbh_data.ctrl_xfer_info.daddr) {
_control_set_xfer_stage(CONTROL_STAGE_IDLE);
}

if (daddr == _usbh_data.enumerating_daddr) {
_usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8;
}
}
}

// if removing a hub, we need to remove all of its downstream devices
#if CFG_TUH_HUB
if (removing_hubs == 0) {
#if CFG_TUH_HUB
// if a hub is removed, we need to remove all of its downstream devices
if (tu_mem_is_zero(removing_hubs, CFG_TUH_HUB)) {
break;
}

// find a marked hub to process
for (uint8_t h_id = 0; h_id < CFG_TUH_HUB; h_id++) {
if (tu_bit_test(removing_hubs, h_id)) {
removing_hubs &= ~TU_BIT(h_id);
if (removing_hubs[h_id]) {
removing_hubs[h_id] = 0;

// update hub_addr and hub_port for next loop
hub_addr = h_id + 1 + CFG_TUH_DEVICE_MAX;
hub_port = 0;
break;
}
}
#else
(void) removing_hubs;
#else
break;
#endif
#endif

} while(1);
}

Expand Down Expand Up @@ -1560,6 +1554,10 @@ static void process_enumeration(tuh_xfer_t* xfer) {
}

case ENUM_SET_ADDR: {
// Due to physical debouncing, some devices can cause multiple attaches (actually reset) without detach event
// Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists
process_removed_device(dev0_bus->rhport, dev0_bus->hub_addr, dev0_bus->hub_port);

const tusb_desc_device_t *desc_device = (const tusb_desc_device_t *) _usbh_epbuf.ctrl;
const uint8_t new_addr = enum_get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB);
TU_ASSERT(new_addr != 0,);
Expand All @@ -1582,7 +1580,7 @@ static void process_enumeration(tuh_xfer_t* xfer) {
new_dev->addressed = 1;
_usbh_data.enumerating_daddr = new_addr;

hcd_device_close(dev0_bus->rhport, 0); // close dev0
usbh_device_close(dev0_bus->rhport, 0); // close dev0

TU_ASSERT(usbh_edpt_control_open(new_addr, new_dev->ep0_size),); // open new control endpoint

Expand Down
Loading