USB host support (#527)
This commit is contained in:
@@ -9,4 +9,4 @@ idf_component_register(
|
||||
REQUIRES TactilityKernel driver vfs fatfs
|
||||
)
|
||||
|
||||
idf_component_optional_requires(PRIVATE bt)
|
||||
idf_component_optional_requires(PRIVATE bt usb espressif__usb_host_hid espressif__usb_host_msc)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
description: ESP32 USB Host HID class driver
|
||||
|
||||
compatible: "espressif,esp32-usbhost-hid"
|
||||
|
||||
properties:
|
||||
_unused:
|
||||
type: int
|
||||
default: 0
|
||||
description: Placeholder required by the binding schema; this driver has no device-tree configuration.
|
||||
@@ -0,0 +1,9 @@
|
||||
description: ESP32 USB Host MIDI class driver
|
||||
|
||||
compatible: "espressif,esp32-usbhost-midi"
|
||||
|
||||
properties:
|
||||
_unused:
|
||||
type: int
|
||||
default: 0
|
||||
description: Placeholder required by the binding schema; this driver has no device-tree configuration.
|
||||
@@ -0,0 +1,9 @@
|
||||
description: ESP32 USB Host MSC class driver (mass storage)
|
||||
|
||||
compatible: "espressif,esp32-usbhost-msc"
|
||||
|
||||
properties:
|
||||
_unused:
|
||||
type: int
|
||||
default: 0
|
||||
description: Placeholder required by the binding schema; this driver has no device-tree configuration.
|
||||
@@ -0,0 +1,15 @@
|
||||
description: ESP32 USB Host controller
|
||||
|
||||
compatible: "espressif,esp32-usbhost"
|
||||
|
||||
properties:
|
||||
peripheral-map:
|
||||
type: int
|
||||
default: 0
|
||||
description: |
|
||||
Bitmask of USB OTG controllers to use. Only meaningful on ESP32-P4,
|
||||
which has two independent USB DWC cores. On ESP32-S2/S3 this field
|
||||
is ignored (single controller, always used).
|
||||
Default 0 selects the default peripheral (P4 default: controller 0, HS/UTMI).
|
||||
BIT(0) = controller 0 (HS, UTMI PHY - GPIO 49/50 aka USB2_OTG pins, e.g. USB-A on Tab5)
|
||||
BIT(1) = controller 1 (FS, FSLS PHY - GPIO 24/25 aka USB1 pins, e.g. USB-C OTG on Tab5)
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <tactility/drivers/esp32_usbhost.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(esp32_usbhost, struct Esp32UsbHostConfig)
|
||||
DEFINE_DEVICETREE(esp32_usbhost_hid, struct Esp32UsbHostChildConfig)
|
||||
DEFINE_DEVICETREE(esp32_usbhost_midi, struct Esp32UsbHostChildConfig)
|
||||
DEFINE_DEVICETREE(esp32_usbhost_msc, struct Esp32UsbHostChildConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Esp32UsbHostConfig {
|
||||
uint8_t peripheral_map; /**< BIT(0)=controller 0 (HS/UTMI, e.g. USB-A on Tab5), BIT(1)=controller 1 (FS/FSLS, e.g. USB-C OTG on Tab5) */
|
||||
};
|
||||
|
||||
struct Esp32UsbHostChildConfig {
|
||||
int _unused;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,161 @@
|
||||
#include <sdkconfig.h>
|
||||
#ifdef CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/esp32_usbhost.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <usb/usb_host.h>
|
||||
#include <esp_intr_alloc.h>
|
||||
|
||||
#define TAG "esp32_usbhost"
|
||||
|
||||
#define GET_CONFIG(device) ((const Esp32UsbHostConfig*)(device)->config)
|
||||
|
||||
constexpr auto USB_LIB_TASK_STACK = 4096;
|
||||
constexpr auto USB_LIB_TASK_PRIORITY = 10;
|
||||
constexpr auto USB_LIB_EVENT_TIMEOUT_MS = 500;
|
||||
constexpr auto USB_HOST_STOP_TIMEOUT_MS = 3000;
|
||||
constexpr auto USB_HOST_STOP_RETRY_MS = 1000;
|
||||
|
||||
struct UsbHostContext {
|
||||
TaskHandle_t lib_task = nullptr;
|
||||
SemaphoreHandle_t lib_task_done = nullptr;
|
||||
};
|
||||
|
||||
static void usbLibTask(void* arg) {
|
||||
auto* ctx = static_cast<UsbHostContext*>(arg);
|
||||
LOG_I(TAG, "lib task started");
|
||||
|
||||
while (true) {
|
||||
uint32_t flags = 0;
|
||||
esp_err_t err = usb_host_lib_handle_events(pdMS_TO_TICKS(USB_LIB_EVENT_TIMEOUT_MS), &flags);
|
||||
if (err != ESP_OK && err != ESP_ERR_TIMEOUT) {
|
||||
LOG_W(TAG, "usb_host_lib_handle_events: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
if (flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
|
||||
LOG_I(TAG, "no more USB clients, freeing all devices");
|
||||
usb_host_device_free_all();
|
||||
}
|
||||
if (flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
|
||||
LOG_I(TAG, "all USB devices freed");
|
||||
}
|
||||
|
||||
if (ulTaskNotifyTake(pdFALSE, 0) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_I(TAG, "lib task stopping");
|
||||
xSemaphoreGive(ctx->lib_task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start_device(struct Device* device) {
|
||||
auto* cfg = GET_CONFIG(device);
|
||||
if (!cfg) {
|
||||
LOG_E(TAG, "device config is null");
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
usb_host_config_t host_cfg = {
|
||||
.skip_phy_setup = false,
|
||||
.root_port_unpowered = false,
|
||||
.intr_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.enum_filter_cb = nullptr,
|
||||
.fifo_settings_custom = {},
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
.peripheral_map = cfg->peripheral_map,
|
||||
#endif
|
||||
};
|
||||
|
||||
esp_err_t ret = usb_host_install(&host_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
LOG_E(TAG, "usb_host_install failed: %s", esp_err_to_name(ret));
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
auto* ctx = new UsbHostContext();
|
||||
|
||||
ctx->lib_task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->lib_task_done) {
|
||||
LOG_E(TAG, "failed to create lib_task_done semaphore");
|
||||
delete ctx;
|
||||
usb_host_uninstall();
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
BaseType_t result = xTaskCreate(usbLibTask, "usb_lib", USB_LIB_TASK_STACK,
|
||||
ctx, USB_LIB_TASK_PRIORITY, &ctx->lib_task);
|
||||
if (result != pdPASS) {
|
||||
LOG_E(TAG, "failed to create usb_lib task");
|
||||
vSemaphoreDelete(ctx->lib_task_done);
|
||||
delete ctx;
|
||||
usb_host_uninstall();
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
LOG_I(TAG, "started (peripheral_map=0x%02x)", cfg->peripheral_map);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbHostContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return ERROR_NONE;
|
||||
|
||||
xTaskNotifyGive(ctx->lib_task);
|
||||
|
||||
bool exited = (xSemaphoreTake(ctx->lib_task_done, pdMS_TO_TICKS(USB_HOST_STOP_TIMEOUT_MS)) == pdTRUE);
|
||||
if (!exited) {
|
||||
// Free all devices to unblock the NO_CLIENTS / ALL_FREE flags, then retry.
|
||||
usb_host_device_free_all();
|
||||
exited = (xSemaphoreTake(ctx->lib_task_done, pdMS_TO_TICKS(USB_HOST_STOP_RETRY_MS)) == pdTRUE);
|
||||
}
|
||||
if (!exited) {
|
||||
LOG_E(TAG, "lib task stop timed out after %dms, force terminating — USB host restart required",
|
||||
USB_HOST_STOP_TIMEOUT_MS + USB_HOST_STOP_RETRY_MS);
|
||||
vTaskDelete(ctx->lib_task);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
// Skip usb_host_uninstall — USB stack is in undefined state.
|
||||
ctx->lib_task = nullptr;
|
||||
vSemaphoreDelete(ctx->lib_task_done);
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
ctx->lib_task = nullptr;
|
||||
vSemaphoreDelete(ctx->lib_task_done);
|
||||
|
||||
esp_err_t uninstall_err = usb_host_uninstall();
|
||||
if (uninstall_err != ESP_OK) {
|
||||
LOG_W(TAG, "usb_host_uninstall: %s", esp_err_to_name(uninstall_err));
|
||||
}
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
LOG_I(TAG, "stopped");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_usbhost_driver = {
|
||||
.name = "esp32_usbhost",
|
||||
.compatible = (const char*[]) { "espressif,esp32-usbhost", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = nullptr,
|
||||
.device_type = nullptr,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
@@ -0,0 +1,506 @@
|
||||
#include <sdkconfig.h>
|
||||
#ifdef CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/usb_host_hid.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include <usb/hid_host.h>
|
||||
#include <usb/hid_usage_keyboard.h>
|
||||
#include <usb/hid_usage_mouse.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
#define TAG "esp32_usbhost_hid"
|
||||
|
||||
constexpr auto HID_EVENT_QUEUE_SIZE = 8;
|
||||
constexpr auto HID_PROC_TASK_STACK = 4096;
|
||||
constexpr auto HID_PROC_TASK_PRIORITY = 5;
|
||||
constexpr auto HID_STOP_TIMEOUT_MS = 2000;
|
||||
constexpr auto MAX_SUBSCRIBERS = 4;
|
||||
|
||||
typedef struct {
|
||||
hid_host_device_handle_t handle;
|
||||
hid_host_driver_event_t event;
|
||||
void* arg;
|
||||
} hid_dev_event_t;
|
||||
|
||||
struct UsbHidContext {
|
||||
std::atomic<bool> mouse_connected{false};
|
||||
std::atomic<bool> device_connected{false};
|
||||
QueueHandle_t hid_event_queue = nullptr;
|
||||
TaskHandle_t hid_proc_task = nullptr;
|
||||
SemaphoreHandle_t hid_proc_task_done = nullptr;
|
||||
std::atomic<bool> hid_proc_running{false};
|
||||
|
||||
uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = {};
|
||||
uint32_t pressed_lv_keys[256] = {};
|
||||
bool caps_lock_active = false;
|
||||
bool num_lock_active = true;
|
||||
bool scroll_lock_active = false;
|
||||
bool prev_mouse_button2 = false;
|
||||
|
||||
std::atomic<hid_host_device_handle_t> kb_handle{nullptr};
|
||||
std::atomic<bool> kb_led_pending{false};
|
||||
|
||||
QueueHandle_t subscribers[MAX_SUBSCRIBERS] = {};
|
||||
SemaphoreHandle_t sub_mutex = nullptr;
|
||||
};
|
||||
|
||||
static const uint8_t keycode2ascii[57][2] = {
|
||||
{0, 0}, {0, 0}, {0, 0}, {0, 0},
|
||||
{'a', 'A'}, {'b', 'B'}, {'c', 'C'}, {'d', 'D'}, {'e', 'E'},
|
||||
{'f', 'F'}, {'g', 'G'}, {'h', 'H'}, {'i', 'I'}, {'j', 'J'},
|
||||
{'k', 'K'}, {'l', 'L'}, {'m', 'M'}, {'n', 'N'}, {'o', 'O'},
|
||||
{'p', 'P'}, {'q', 'Q'}, {'r', 'R'}, {'s', 'S'}, {'t', 'T'},
|
||||
{'u', 'U'}, {'v', 'V'}, {'w', 'W'}, {'x', 'X'}, {'y', 'Y'},
|
||||
{'z', 'Z'},
|
||||
{'1', '!'}, {'2', '@'}, {'3', '#'}, {'4', '$'}, {'5', '%'},
|
||||
{'6', '^'}, {'7', '&'}, {'8', '*'}, {'9', '('}, {'0', ')'},
|
||||
{'\r', '\r'}, {0, 0}, {'\b', 0}, {'\t', '\t'}, {' ', ' '},
|
||||
{'-', '_'}, {'=', '+'}, {'[', '{'}, {']', '}'},
|
||||
{'\\', '|'}, {'\\', '|'}, {';', ':'}, {'\'', '"'},
|
||||
{'`', '~'}, {',', '<'}, {'.', '>'}, {'/', '?'},
|
||||
};
|
||||
|
||||
static uint32_t hid_keycode_to_key(uint8_t modifier, uint8_t key_code,
|
||||
bool caps_lock, bool num_lock) {
|
||||
bool shift = (modifier & HID_LEFT_SHIFT) || (modifier & HID_RIGHT_SHIFT);
|
||||
bool ctrl = (modifier & HID_LEFT_CONTROL) || (modifier & HID_RIGHT_CONTROL);
|
||||
bool alt = (modifier & HID_LEFT_ALT) || (modifier & HID_RIGHT_ALT);
|
||||
|
||||
switch (key_code) {
|
||||
case HID_KEY_ENTER: return USB_HID_KEY_ENTER;
|
||||
case HID_KEY_ESC: return USB_HID_KEY_ESC;
|
||||
case HID_KEY_DEL: return USB_HID_KEY_BACKSPACE;
|
||||
case HID_KEY_DELETE: return USB_HID_KEY_DEL;
|
||||
case HID_KEY_TAB: return shift ? USB_HID_KEY_PREV : USB_HID_KEY_NEXT;
|
||||
case HID_KEY_UP: return USB_HID_KEY_UP;
|
||||
case HID_KEY_DOWN: return USB_HID_KEY_DOWN;
|
||||
case HID_KEY_LEFT: return USB_HID_KEY_LEFT;
|
||||
case HID_KEY_RIGHT: return USB_HID_KEY_RIGHT;
|
||||
case HID_KEY_HOME: return USB_HID_KEY_HOME;
|
||||
case HID_KEY_END: return USB_HID_KEY_END;
|
||||
case HID_KEY_KEYPAD_ENTER: return USB_HID_KEY_ENTER;
|
||||
case HID_KEY_KEYPAD_ADD: return '+';
|
||||
case HID_KEY_KEYPAD_SUB: return '-';
|
||||
case HID_KEY_KEYPAD_MUL: return '*';
|
||||
case HID_KEY_KEYPAD_DIV: return '/';
|
||||
case HID_KEY_KEYPAD_0: return num_lock ? (uint32_t)'0' : 0u;
|
||||
case HID_KEY_KEYPAD_1: return num_lock ? (uint32_t)'1' : (uint32_t)USB_HID_KEY_END;
|
||||
case HID_KEY_KEYPAD_2: return num_lock ? (uint32_t)'2' : (uint32_t)USB_HID_KEY_DOWN;
|
||||
case HID_KEY_KEYPAD_3: return num_lock ? (uint32_t)'3' : 0u;
|
||||
case HID_KEY_KEYPAD_4: return num_lock ? (uint32_t)'4' : (uint32_t)USB_HID_KEY_LEFT;
|
||||
case HID_KEY_KEYPAD_5: return num_lock ? (uint32_t)'5' : 0u;
|
||||
case HID_KEY_KEYPAD_6: return num_lock ? (uint32_t)'6' : (uint32_t)USB_HID_KEY_RIGHT;
|
||||
case HID_KEY_KEYPAD_7: return num_lock ? (uint32_t)'7' : (uint32_t)USB_HID_KEY_HOME;
|
||||
case HID_KEY_KEYPAD_8: return num_lock ? (uint32_t)'8' : (uint32_t)USB_HID_KEY_UP;
|
||||
case HID_KEY_KEYPAD_9: return num_lock ? (uint32_t)'9' : 0u;
|
||||
case HID_KEY_KEYPAD_DELETE: return num_lock ? (uint32_t)'.' : (uint32_t)USB_HID_KEY_DEL;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (ctrl || alt) return 0;
|
||||
|
||||
if (key_code < (sizeof(keycode2ascii) / sizeof(keycode2ascii[0]))) {
|
||||
bool is_letter = (key_code >= 0x04 && key_code <= 0x1D);
|
||||
bool effective_shift = is_letter ? (shift ^ caps_lock) : shift;
|
||||
uint8_t ch = keycode2ascii[key_code][effective_shift ? 1 : 0];
|
||||
if (ch != 0) return (uint32_t)ch;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void publish_event(UsbHidContext* ctx, const UsbHidEvent* evt) {
|
||||
if (xSemaphoreTake(ctx->sub_mutex, pdMS_TO_TICKS(10)) != pdTRUE) {
|
||||
LOG_W(TAG, "publish_event: sub_mutex contended, event type=%d dropped", (int)evt->type);
|
||||
return;
|
||||
}
|
||||
bool is_release = (evt->type == USB_HID_EVENT_KEY && !evt->key.pressed);
|
||||
TickType_t send_timeout = is_release ? pdMS_TO_TICKS(10) : 0;
|
||||
for (int i = 0; i < MAX_SUBSCRIBERS; i++) {
|
||||
if (ctx->subscribers[i]) {
|
||||
xQueueSend(ctx->subscribers[i], evt, send_timeout);
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ctx->sub_mutex);
|
||||
}
|
||||
|
||||
static void publish_scroll(UsbHidContext* ctx, int32_t delta) {
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_SCROLL, .scroll = { delta } };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
|
||||
static void hid_interface_callback(hid_host_device_handle_t handle,
|
||||
const hid_host_interface_event_t event,
|
||||
void* arg)
|
||||
{
|
||||
auto* ctx = static_cast<UsbHidContext*>(arg);
|
||||
uint8_t data[64] = {};
|
||||
size_t data_len = 0;
|
||||
hid_host_dev_params_t params;
|
||||
|
||||
if (hid_host_device_get_params(handle, ¶ms) != ESP_OK) return;
|
||||
|
||||
switch (event) {
|
||||
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
|
||||
if (hid_host_device_get_raw_input_report_data(handle, data, sizeof(data), &data_len) != ESP_OK) break;
|
||||
|
||||
if (params.proto == HID_PROTOCOL_KEYBOARD) {
|
||||
if (data_len < sizeof(hid_keyboard_input_report_boot_t)) break;
|
||||
auto* kb = reinterpret_cast<const hid_keyboard_input_report_boot_t*>(data);
|
||||
|
||||
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
|
||||
uint8_t prev_hid = ctx->prev_keys[i];
|
||||
if (prev_hid > HID_KEY_ERROR_UNDEFINED) {
|
||||
bool still_pressed = false;
|
||||
for (int j = 0; j < HID_KEYBOARD_KEY_MAX; j++) {
|
||||
if (kb->key[j] == prev_hid) { still_pressed = true; break; }
|
||||
}
|
||||
if (!still_pressed) {
|
||||
uint32_t lv_key = ctx->pressed_lv_keys[prev_hid];
|
||||
ctx->pressed_lv_keys[prev_hid] = 0;
|
||||
if (lv_key) {
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_KEY, .key = { lv_key, false } };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t hid_code = kb->key[i];
|
||||
if (hid_code > HID_KEY_ERROR_UNDEFINED) {
|
||||
bool was_pressed = false;
|
||||
for (int j = 0; j < HID_KEYBOARD_KEY_MAX; j++) {
|
||||
if (ctx->prev_keys[j] == hid_code) { was_pressed = true; break; }
|
||||
}
|
||||
if (!was_pressed) {
|
||||
if (hid_code == HID_KEY_CAPS_LOCK) {
|
||||
ctx->caps_lock_active = !ctx->caps_lock_active;
|
||||
ctx->kb_led_pending.store(true);
|
||||
continue;
|
||||
}
|
||||
if (hid_code == HID_KEY_NUM_LOCK) {
|
||||
ctx->num_lock_active = !ctx->num_lock_active;
|
||||
ctx->kb_led_pending.store(true);
|
||||
continue;
|
||||
}
|
||||
if (hid_code == HID_KEY_SCROLL_LOCK) {
|
||||
ctx->scroll_lock_active = !ctx->scroll_lock_active;
|
||||
ctx->kb_led_pending.store(true);
|
||||
continue;
|
||||
}
|
||||
bool is_pgup = (hid_code == HID_KEY_PAGEUP) ||
|
||||
(!ctx->num_lock_active && hid_code == HID_KEY_KEYPAD_9);
|
||||
bool is_pgdn = (hid_code == HID_KEY_PAGEDOWN) ||
|
||||
(!ctx->num_lock_active && hid_code == HID_KEY_KEYPAD_3);
|
||||
if (is_pgup || is_pgdn) {
|
||||
publish_scroll(ctx, is_pgup ? -8 : 8);
|
||||
continue;
|
||||
}
|
||||
uint32_t lv_key = hid_keycode_to_key(kb->modifier.val, hid_code,
|
||||
ctx->caps_lock_active, ctx->num_lock_active);
|
||||
if (lv_key) {
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_KEY, .key = { lv_key, true } };
|
||||
publish_event(ctx, &evt);
|
||||
ctx->pressed_lv_keys[hid_code] = lv_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
memcpy(ctx->prev_keys, kb->key, HID_KEYBOARD_KEY_MAX);
|
||||
|
||||
} else if (params.proto == HID_PROTOCOL_MOUSE) {
|
||||
if (data_len < sizeof(hid_mouse_input_report_boot_t)) break;
|
||||
auto* ms = reinterpret_cast<const hid_mouse_input_report_boot_t*>(data);
|
||||
|
||||
if (ms->x_displacement != 0 || ms->y_displacement != 0) {
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_MOUSE_MOVE,
|
||||
.mouse_move = { (int32_t)ms->x_displacement, (int32_t)ms->y_displacement } };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
{
|
||||
bool b1 = ms->buttons.button1 != 0;
|
||||
bool b2 = ms->buttons.button2 != 0;
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_MOUSE_BTN, .mouse_btn = { b1, b2 } };
|
||||
publish_event(ctx, &evt);
|
||||
|
||||
if (b2 != ctx->prev_mouse_button2) {
|
||||
UsbHidEvent key_evt = { .type = USB_HID_EVENT_KEY, .key = { USB_HID_KEY_ESC, b2 } };
|
||||
publish_event(ctx, &key_evt);
|
||||
ctx->prev_mouse_button2 = b2;
|
||||
}
|
||||
}
|
||||
|
||||
if (data_len > sizeof(hid_mouse_input_report_boot_t)) {
|
||||
int8_t wheel = (int8_t)data[sizeof(hid_mouse_input_report_boot_t)];
|
||||
if (wheel != 0) {
|
||||
int32_t delta = (wheel < -8) ? -8 : (wheel > 8) ? 8 : (int32_t)wheel;
|
||||
publish_scroll(ctx, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
|
||||
if (params.proto == HID_PROTOCOL_KEYBOARD) {
|
||||
memset(ctx->prev_keys, 0, sizeof(ctx->prev_keys));
|
||||
memset(ctx->pressed_lv_keys, 0, sizeof(ctx->pressed_lv_keys));
|
||||
ctx->kb_handle.store(nullptr);
|
||||
ctx->kb_led_pending.store(false);
|
||||
} else if (params.proto == HID_PROTOCOL_MOUSE) {
|
||||
ctx->mouse_connected = false;
|
||||
}
|
||||
hid_host_device_close(handle);
|
||||
if (!ctx->kb_handle.load() && !ctx->mouse_connected) {
|
||||
ctx->device_connected = false;
|
||||
}
|
||||
{
|
||||
UsbHidEventType disc_type = (params.proto == HID_PROTOCOL_KEYBOARD)
|
||||
? USB_HID_EVENT_KEYBOARD_DISCONNECTED
|
||||
: USB_HID_EVENT_MOUSE_DISCONNECTED;
|
||||
UsbHidEvent evt = { .type = disc_type };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
|
||||
LOG_W(TAG, "HID transfer error (proto=%d)", params.proto);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_driver_callback(hid_host_device_handle_t handle,
|
||||
const hid_host_driver_event_t event,
|
||||
void* arg)
|
||||
{
|
||||
auto* ctx = static_cast<UsbHidContext*>(arg);
|
||||
hid_dev_event_t evt = { handle, event, arg };
|
||||
if (ctx->hid_event_queue) {
|
||||
xQueueSend(ctx->hid_event_queue, &evt, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_proc_task(void* arg) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(arg);
|
||||
LOG_I(TAG, "HID proc task started");
|
||||
|
||||
while (ctx->hid_proc_running) {
|
||||
hid_dev_event_t dev_evt;
|
||||
if (xQueueReceive(ctx->hid_event_queue, &dev_evt, pdMS_TO_TICKS(100)) != pdTRUE) {
|
||||
if (ctx->kb_led_pending.exchange(false) && ctx->kb_handle.load()) {
|
||||
uint8_t leds = (ctx->num_lock_active ? 0x01 : 0)
|
||||
| (ctx->caps_lock_active ? 0x02 : 0)
|
||||
| (ctx->scroll_lock_active ? 0x04 : 0);
|
||||
hid_class_request_set_report(ctx->kb_handle.load(), HID_REPORT_TYPE_OUTPUT, 0, &leds, 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (dev_evt.event == HID_HOST_DRIVER_EVENT_CONNECTED) {
|
||||
hid_host_dev_params_t params;
|
||||
if (hid_host_device_get_params(dev_evt.handle, ¶ms) != ESP_OK) continue;
|
||||
|
||||
if (params.proto != HID_PROTOCOL_KEYBOARD && params.proto != HID_PROTOCOL_MOUSE) {
|
||||
LOG_D(TAG, "ignoring HID interface with unhandled proto=%d", params.proto);
|
||||
continue;
|
||||
}
|
||||
LOG_I(TAG, "HID device connected (proto=%d)", params.proto);
|
||||
|
||||
const hid_host_device_config_t dev_cfg = {
|
||||
.callback = hid_interface_callback,
|
||||
.callback_arg = ctx,
|
||||
};
|
||||
if (hid_host_device_open(dev_evt.handle, &dev_cfg) != ESP_OK) {
|
||||
LOG_W(TAG, "hid_host_device_open failed");
|
||||
continue;
|
||||
}
|
||||
hid_class_request_set_protocol(dev_evt.handle, HID_REPORT_PROTOCOL_BOOT);
|
||||
if (params.proto == HID_PROTOCOL_KEYBOARD) {
|
||||
hid_class_request_set_idle(dev_evt.handle, 0, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
ctx->kb_handle.store(dev_evt.handle);
|
||||
uint8_t leds = (ctx->num_lock_active ? 0x01 : 0)
|
||||
| (ctx->caps_lock_active ? 0x02 : 0)
|
||||
| (ctx->scroll_lock_active ? 0x04 : 0);
|
||||
hid_class_request_set_report(dev_evt.handle, HID_REPORT_TYPE_OUTPUT, 0, &leds, 1);
|
||||
} else if (params.proto == HID_PROTOCOL_MOUSE) {
|
||||
ctx->mouse_connected = true;
|
||||
}
|
||||
ctx->device_connected = true;
|
||||
{
|
||||
UsbHidEventType conn_type = (params.proto == HID_PROTOCOL_KEYBOARD)
|
||||
? USB_HID_EVENT_KEYBOARD_CONNECTED
|
||||
: USB_HID_EVENT_MOUSE_CONNECTED;
|
||||
UsbHidEvent evt = { .type = conn_type };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
hid_host_device_start(dev_evt.handle);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_I(TAG, "HID proc task stopped");
|
||||
xSemaphoreGive(ctx->hid_proc_task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
static bool api_hid_is_connected(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(device_get_driver_data(device));
|
||||
return ctx && ctx->device_connected.load();
|
||||
}
|
||||
|
||||
static bool api_hid_subscribe(struct Device* device, UsbHidQueueHandle event_queue) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(device_get_driver_data(device));
|
||||
if (!ctx || !event_queue) return false;
|
||||
if (xSemaphoreTake(ctx->sub_mutex, pdMS_TO_TICKS(100)) != pdTRUE) return false;
|
||||
bool added = false;
|
||||
for (int i = 0; i < MAX_SUBSCRIBERS; i++) {
|
||||
if (ctx->subscribers[i] == static_cast<QueueHandle_t>(event_queue)) {
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
if (!ctx->subscribers[i]) {
|
||||
ctx->subscribers[i] = static_cast<QueueHandle_t>(event_queue);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ctx->sub_mutex);
|
||||
if (!added) LOG_W(TAG, "subscriber list full");
|
||||
return added;
|
||||
}
|
||||
|
||||
static void api_hid_unsubscribe(struct Device* device, UsbHidQueueHandle event_queue) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(device_get_driver_data(device));
|
||||
if (!ctx || !event_queue) return;
|
||||
if (xSemaphoreTake(ctx->sub_mutex, pdMS_TO_TICKS(100)) != pdTRUE) {
|
||||
LOG_W(TAG, "unsubscribe: mutex timeout, subscriber slot may remain stale");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < MAX_SUBSCRIBERS; i++) {
|
||||
if (ctx->subscribers[i] == static_cast<QueueHandle_t>(event_queue)) {
|
||||
ctx->subscribers[i] = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ctx->sub_mutex);
|
||||
}
|
||||
|
||||
static const UsbHidApi hid_api = {
|
||||
.is_connected = api_hid_is_connected,
|
||||
.subscribe = api_hid_subscribe,
|
||||
.unsubscribe = api_hid_unsubscribe,
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start_device(struct Device* device) {
|
||||
auto* ctx = new UsbHidContext();
|
||||
|
||||
ctx->sub_mutex = xSemaphoreCreateMutex();
|
||||
if (!ctx->sub_mutex) {
|
||||
LOG_E(TAG, "failed to create subscriber mutex");
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->hid_event_queue = xQueueCreate(HID_EVENT_QUEUE_SIZE, sizeof(hid_dev_event_t));
|
||||
if (!ctx->hid_event_queue) {
|
||||
LOG_E(TAG, "failed to create HID event queue");
|
||||
vSemaphoreDelete(ctx->sub_mutex);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->hid_proc_task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->hid_proc_task_done) {
|
||||
LOG_E(TAG, "failed to create task done semaphore");
|
||||
vQueueDelete(ctx->hid_event_queue);
|
||||
vSemaphoreDelete(ctx->sub_mutex);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const hid_host_driver_config_t hid_cfg = {
|
||||
.create_background_task = true,
|
||||
.task_priority = HID_PROC_TASK_PRIORITY,
|
||||
.stack_size = HID_PROC_TASK_STACK,
|
||||
.core_id = tskNO_AFFINITY,
|
||||
.callback = hid_driver_callback,
|
||||
.callback_arg = ctx,
|
||||
};
|
||||
if (hid_host_install(&hid_cfg) != ESP_OK) {
|
||||
LOG_E(TAG, "hid_host_install failed");
|
||||
vQueueDelete(ctx->hid_event_queue);
|
||||
vSemaphoreDelete(ctx->hid_proc_task_done);
|
||||
vSemaphoreDelete(ctx->sub_mutex);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->hid_proc_running = true;
|
||||
BaseType_t result = xTaskCreate(hid_proc_task, "hid_proc", HID_PROC_TASK_STACK,
|
||||
ctx, HID_PROC_TASK_PRIORITY, &ctx->hid_proc_task);
|
||||
if (result != pdPASS) {
|
||||
LOG_E(TAG, "failed to create hid_proc task");
|
||||
ctx->hid_proc_running = false;
|
||||
hid_host_uninstall();
|
||||
vQueueDelete(ctx->hid_event_queue);
|
||||
vSemaphoreDelete(ctx->hid_proc_task_done);
|
||||
vSemaphoreDelete(ctx->sub_mutex);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
LOG_I(TAG, "started");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return ERROR_NONE;
|
||||
|
||||
ctx->hid_proc_running = false;
|
||||
|
||||
if (xSemaphoreTake(ctx->hid_proc_task_done, pdMS_TO_TICKS(HID_STOP_TIMEOUT_MS)) != pdTRUE) {
|
||||
LOG_W(TAG, "HID proc task stop timed out, force terminating");
|
||||
vTaskDelete(ctx->hid_proc_task);
|
||||
}
|
||||
ctx->hid_proc_task = nullptr;
|
||||
vSemaphoreDelete(ctx->hid_proc_task_done);
|
||||
|
||||
hid_host_uninstall();
|
||||
|
||||
if (ctx->hid_event_queue) { vQueueDelete(ctx->hid_event_queue); ctx->hid_event_queue = nullptr; }
|
||||
if (ctx->sub_mutex) { vSemaphoreDelete(ctx->sub_mutex); ctx->sub_mutex = nullptr; }
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
LOG_I(TAG, "stopped");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_usbhost_hid_driver = {
|
||||
.name = "esp32_usbhost_hid",
|
||||
.compatible = (const char*[]) { "espressif,esp32-usbhost-hid", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = &hid_api,
|
||||
.device_type = &USB_HOST_HID_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
@@ -0,0 +1,314 @@
|
||||
#include <sdkconfig.h>
|
||||
#ifdef CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/usb_host_midi.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/portmacro.h>
|
||||
|
||||
#include <usb/usb_host.h>
|
||||
#include <usb/usb_helpers.h>
|
||||
#include <usb/usb_types_ch9.h>
|
||||
#include <usb/usb_types_stack.h>
|
||||
|
||||
#define TAG "esp32_usbhost_midi"
|
||||
|
||||
constexpr auto MIDI_TASK_STACK = 4096;
|
||||
constexpr auto MIDI_TASK_PRIORITY = 5;
|
||||
constexpr auto MIDI_STOP_TIMEOUT_MS = 2000;
|
||||
constexpr auto MIDI_TRANSFER_BUF_SIZE = 512;
|
||||
|
||||
constexpr uint8_t MIDI_INTF_CLASS = 0x01;
|
||||
constexpr uint8_t MIDI_INTF_SUBCLASS = 0x03;
|
||||
|
||||
struct UsbMidiContext {
|
||||
usb_host_client_handle_t client_hdl = nullptr;
|
||||
usb_device_handle_t dev_hdl = nullptr;
|
||||
usb_transfer_t* transfer = nullptr;
|
||||
uint8_t ep_addr = 0;
|
||||
uint8_t intf_num = 0;
|
||||
std::atomic<bool> connected{false};
|
||||
std::atomic<bool> running{false};
|
||||
TaskHandle_t task_handle = nullptr;
|
||||
SemaphoreHandle_t task_done = nullptr;
|
||||
|
||||
portMUX_TYPE callback_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
usb_midi_message_cb_t callback = nullptr;
|
||||
void* callback_arg = nullptr;
|
||||
};
|
||||
|
||||
static bool find_midi_interface(const usb_config_desc_t* cfg, uint8_t* out_intf, uint8_t* out_ep) {
|
||||
int offset = 0;
|
||||
const usb_standard_desc_t* cur = reinterpret_cast<const usb_standard_desc_t*>(cfg);
|
||||
|
||||
while ((cur = usb_parse_next_descriptor_of_type(
|
||||
cur, cfg->wTotalLength, USB_W_VALUE_DT_INTERFACE, &offset)) != nullptr) {
|
||||
const auto* intf = reinterpret_cast<const usb_intf_desc_t*>(cur);
|
||||
if (intf->bInterfaceClass != MIDI_INTF_CLASS || intf->bInterfaceSubClass != MIDI_INTF_SUBCLASS) continue;
|
||||
|
||||
int ep_offset = offset;
|
||||
const usb_standard_desc_t* ep_cur = cur;
|
||||
for (int e = 0; e < intf->bNumEndpoints; e++) {
|
||||
ep_cur = usb_parse_next_descriptor_of_type(ep_cur, cfg->wTotalLength, USB_W_VALUE_DT_ENDPOINT, &ep_offset);
|
||||
if (!ep_cur) break;
|
||||
const auto* ep = reinterpret_cast<const usb_ep_desc_t*>(ep_cur);
|
||||
usb_transfer_type_t xtype = USB_EP_DESC_GET_XFERTYPE(ep);
|
||||
bool is_in = USB_EP_DESC_GET_EP_DIR(ep) == 1;
|
||||
if (is_in && (xtype == USB_TRANSFER_TYPE_BULK || xtype == USB_TRANSFER_TYPE_INTR)) {
|
||||
*out_intf = intf->bInterfaceNumber;
|
||||
*out_ep = ep->bEndpointAddress;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void dispatch_midi_packets(UsbMidiContext* ctx, const uint8_t* buf, int len) {
|
||||
usb_midi_message_cb_t cb;
|
||||
void* arg;
|
||||
taskENTER_CRITICAL(&ctx->callback_lock);
|
||||
cb = ctx->callback;
|
||||
arg = ctx->callback_arg;
|
||||
taskEXIT_CRITICAL(&ctx->callback_lock);
|
||||
if (!cb) return;
|
||||
|
||||
for (int i = 0; i + 3 < len; i += 4) {
|
||||
uint8_t cin = buf[i] & 0x0F;
|
||||
if (cin < 0x02) continue;
|
||||
usb_midi_message_t msg = {
|
||||
.cable = static_cast<uint8_t>(buf[i] >> 4),
|
||||
.status = buf[i + 1],
|
||||
.data1 = buf[i + 2],
|
||||
.data2 = buf[i + 3],
|
||||
};
|
||||
cb(&msg, arg);
|
||||
}
|
||||
}
|
||||
|
||||
static void midi_transfer_cb(usb_transfer_t* transfer) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(transfer->context);
|
||||
if (transfer->status == USB_TRANSFER_STATUS_COMPLETED && transfer->actual_num_bytes > 0) {
|
||||
dispatch_midi_packets(ctx, transfer->data_buffer, transfer->actual_num_bytes);
|
||||
}
|
||||
if (ctx->running && ctx->connected && transfer->status != USB_TRANSFER_STATUS_NO_DEVICE) {
|
||||
transfer->num_bytes = MIDI_TRANSFER_BUF_SIZE;
|
||||
if (usb_host_transfer_submit(transfer) != ESP_OK) {
|
||||
LOG_E(TAG, "failed to resubmit MIDI transfer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void client_event_cb(const usb_host_client_event_msg_t* msg, void* arg) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(arg);
|
||||
|
||||
if (msg->event == USB_HOST_CLIENT_EVENT_NEW_DEV) {
|
||||
if (ctx->dev_hdl != nullptr || ctx->connected.load()) {
|
||||
LOG_W(TAG, "ignoring additional MIDI device while one is already active");
|
||||
return;
|
||||
}
|
||||
uint8_t addr = msg->new_dev.address;
|
||||
usb_device_handle_t dev_hdl = nullptr;
|
||||
if (usb_host_device_open(ctx->client_hdl, addr, &dev_hdl) != ESP_OK) return;
|
||||
|
||||
const usb_config_desc_t* cfg = nullptr;
|
||||
if (usb_host_get_active_config_descriptor(dev_hdl, &cfg) != ESP_OK) {
|
||||
usb_host_device_close(ctx->client_hdl, dev_hdl);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t intf_num = 0, ep_addr = 0;
|
||||
if (!find_midi_interface(cfg, &intf_num, &ep_addr)) {
|
||||
LOG_D(TAG, "USB device addr=%d: no MIDI Streaming interface found", addr);
|
||||
usb_host_device_close(ctx->client_hdl, dev_hdl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (usb_host_interface_claim(ctx->client_hdl, dev_hdl, intf_num, 0) != ESP_OK) {
|
||||
LOG_E(TAG, "failed to claim MIDI interface %d", intf_num);
|
||||
usb_host_device_close(ctx->client_hdl, dev_hdl);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->dev_hdl = dev_hdl;
|
||||
ctx->intf_num = intf_num;
|
||||
ctx->ep_addr = ep_addr;
|
||||
|
||||
ctx->transfer->device_handle = dev_hdl;
|
||||
ctx->transfer->bEndpointAddress = ep_addr;
|
||||
ctx->transfer->num_bytes = MIDI_TRANSFER_BUF_SIZE;
|
||||
ctx->transfer->callback = midi_transfer_cb;
|
||||
ctx->transfer->context = ctx;
|
||||
ctx->transfer->timeout_ms = 0;
|
||||
|
||||
if (usb_host_transfer_submit(ctx->transfer) == ESP_OK) {
|
||||
ctx->connected = true;
|
||||
LOG_I(TAG, "MIDI device connected (intf=%d ep=0x%02x)", intf_num, ep_addr);
|
||||
} else {
|
||||
LOG_E(TAG, "failed to submit initial MIDI transfer");
|
||||
usb_host_interface_release(ctx->client_hdl, dev_hdl, intf_num);
|
||||
usb_host_device_close(ctx->client_hdl, dev_hdl);
|
||||
ctx->dev_hdl = nullptr;
|
||||
}
|
||||
|
||||
} else if (msg->event == USB_HOST_CLIENT_EVENT_DEV_GONE) {
|
||||
if (ctx->dev_hdl && msg->dev_gone.dev_hdl == ctx->dev_hdl) {
|
||||
LOG_I(TAG, "MIDI device disconnected");
|
||||
ctx->connected = false;
|
||||
usb_host_interface_release(ctx->client_hdl, ctx->dev_hdl, ctx->intf_num);
|
||||
usb_host_device_close(ctx->client_hdl, ctx->dev_hdl);
|
||||
ctx->dev_hdl = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void midi_client_task(void* arg) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(arg);
|
||||
LOG_I(TAG, "MIDI client task started");
|
||||
|
||||
while (ctx->running) {
|
||||
usb_host_client_handle_events(ctx->client_hdl, pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
if (ctx->dev_hdl) {
|
||||
ctx->connected = false;
|
||||
usb_host_interface_release(ctx->client_hdl, ctx->dev_hdl, ctx->intf_num);
|
||||
usb_host_device_close(ctx->client_hdl, ctx->dev_hdl);
|
||||
ctx->dev_hdl = nullptr;
|
||||
}
|
||||
|
||||
LOG_I(TAG, "MIDI client task stopped");
|
||||
xSemaphoreGive(ctx->task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
static void api_set_callback(struct Device* device, usb_midi_message_cb_t callback, void* user_data) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return;
|
||||
taskENTER_CRITICAL(&ctx->callback_lock);
|
||||
ctx->callback = callback;
|
||||
ctx->callback_arg = user_data;
|
||||
taskEXIT_CRITICAL(&ctx->callback_lock);
|
||||
}
|
||||
|
||||
static bool api_is_connected(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(device_get_driver_data(device));
|
||||
return ctx && ctx->connected.load();
|
||||
}
|
||||
|
||||
static const UsbMidiApi midi_api = {
|
||||
.set_callback = api_set_callback,
|
||||
.is_connected = api_is_connected,
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start_device(struct Device* device) {
|
||||
auto* ctx = new UsbMidiContext();
|
||||
|
||||
if (usb_host_transfer_alloc(MIDI_TRANSFER_BUF_SIZE, 0, &ctx->transfer) != ESP_OK) {
|
||||
LOG_E(TAG, "failed to allocate MIDI transfer");
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->task_done) {
|
||||
LOG_E(TAG, "failed to create task done semaphore");
|
||||
usb_host_transfer_free(ctx->transfer);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const usb_host_client_config_t client_cfg = {
|
||||
.is_synchronous = false,
|
||||
.max_num_event_msg = 5,
|
||||
.async = {
|
||||
.client_event_callback = client_event_cb,
|
||||
.callback_arg = ctx,
|
||||
},
|
||||
};
|
||||
if (usb_host_client_register(&client_cfg, &ctx->client_hdl) != ESP_OK) {
|
||||
LOG_E(TAG, "failed to register USB host client");
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
usb_host_transfer_free(ctx->transfer);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->running = true;
|
||||
BaseType_t result = xTaskCreate(midi_client_task, "midi_client", MIDI_TASK_STACK,
|
||||
ctx, MIDI_TASK_PRIORITY, &ctx->task_handle);
|
||||
if (result != pdPASS) {
|
||||
LOG_E(TAG, "failed to create midi_client task");
|
||||
ctx->running = false;
|
||||
usb_host_client_deregister(ctx->client_hdl);
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
usb_host_transfer_free(ctx->transfer);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
LOG_I(TAG, "started");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return ERROR_NONE;
|
||||
|
||||
ctx->running = false;
|
||||
usb_host_client_unblock(ctx->client_hdl);
|
||||
|
||||
if (xSemaphoreTake(ctx->task_done, pdMS_TO_TICKS(MIDI_STOP_TIMEOUT_MS)) != pdTRUE) {
|
||||
LOG_E(TAG, "MIDI client task stop timed out after %dms — a full USB host restart may be required", MIDI_STOP_TIMEOUT_MS);
|
||||
if (ctx->dev_hdl) {
|
||||
ctx->connected = false;
|
||||
if (usb_host_interface_release(ctx->client_hdl, ctx->dev_hdl, ctx->intf_num) != ESP_OK) {
|
||||
LOG_W(TAG, "failed to release MIDI interface during force-stop");
|
||||
}
|
||||
if (usb_host_device_close(ctx->client_hdl, ctx->dev_hdl) != ESP_OK) {
|
||||
LOG_W(TAG, "failed to close MIDI device during force-stop");
|
||||
}
|
||||
ctx->dev_hdl = nullptr;
|
||||
}
|
||||
vTaskDelete(ctx->task_handle);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
ctx->task_handle = nullptr;
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
|
||||
usb_host_client_deregister(ctx->client_hdl);
|
||||
ctx->client_hdl = nullptr;
|
||||
|
||||
usb_host_transfer_free(ctx->transfer);
|
||||
ctx->transfer = nullptr;
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
LOG_I(TAG, "stopped");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_usbhost_midi_driver = {
|
||||
.name = "esp32_usbhost_midi",
|
||||
.compatible = (const char*[]) { "espressif,esp32-usbhost-midi", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = &midi_api,
|
||||
.device_type = &USB_HOST_MIDI_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
@@ -0,0 +1,377 @@
|
||||
#include <sdkconfig.h>
|
||||
#ifdef CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/usb_host_msc.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/portmacro.h>
|
||||
|
||||
#include <usb/msc_host.h>
|
||||
#include <usb/msc_host_vfs.h>
|
||||
#include <esp_vfs_fat.h>
|
||||
|
||||
#define TAG "esp32_usbhost_msc"
|
||||
|
||||
#define USB_MSC_MOUNT_PATH_PREFIX "/usb"
|
||||
|
||||
constexpr auto MAX_MSC_DEVICES = 2;
|
||||
constexpr auto MSC_EVENT_QUEUE_SIZE = 8;
|
||||
constexpr auto MSC_PROC_TASK_STACK = 4096;
|
||||
constexpr auto MSC_PROC_TASK_PRIORITY = 5;
|
||||
constexpr auto MSC_STOP_TIMEOUT_MS = 2000;
|
||||
|
||||
typedef struct {
|
||||
uint8_t usb_addr;
|
||||
msc_host_device_handle_t device;
|
||||
msc_host_vfs_handle_t vfs;
|
||||
char mount_path[16];
|
||||
struct FileSystem* fs_entry;
|
||||
bool mounted;
|
||||
} msc_dev_entry_t;
|
||||
|
||||
// The anonymous enum inside msc_host_event_t is C-only scoped; use a local alias in C++.
|
||||
using msc_event_id_t = decltype(msc_host_event_t{}.event);
|
||||
static constexpr msc_event_id_t kMscDeviceConnected = static_cast<msc_event_id_t>(0);
|
||||
static constexpr msc_event_id_t kMscDeviceDisconnected = static_cast<msc_event_id_t>(1);
|
||||
|
||||
enum class MscMsgId : uint8_t { Connected, Disconnected };
|
||||
|
||||
typedef struct {
|
||||
MscMsgId id;
|
||||
union {
|
||||
uint8_t address;
|
||||
msc_host_device_handle_t handle;
|
||||
};
|
||||
} msc_msg_t;
|
||||
|
||||
struct UsbMscContext {
|
||||
msc_dev_entry_t* devs[MAX_MSC_DEVICES] = {};
|
||||
portMUX_TYPE devs_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
QueueHandle_t event_queue = nullptr;
|
||||
TaskHandle_t proc_task = nullptr;
|
||||
SemaphoreHandle_t proc_task_done = nullptr;
|
||||
std::atomic<bool> proc_running{false};
|
||||
};
|
||||
|
||||
static error_t usb_fs_mount(void* /*data*/) { return ERROR_NONE; }
|
||||
static error_t usb_fs_unmount(void* /*data*/) { return ERROR_NONE; }
|
||||
static bool usb_fs_is_mounted(void* data) {
|
||||
if (!data) return false;
|
||||
return static_cast<msc_dev_entry_t*>(data)->mounted;
|
||||
}
|
||||
static error_t usb_fs_get_path(void* data, char* out_path, size_t out_path_size) {
|
||||
if (!data) return ERROR_INVALID_ARGUMENT;
|
||||
snprintf(out_path, out_path_size, "%s", static_cast<msc_dev_entry_t*>(data)->mount_path);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static const FileSystemApi usb_fs_api = {
|
||||
.mount = usb_fs_mount,
|
||||
.unmount = usb_fs_unmount,
|
||||
.is_mounted = usb_fs_is_mounted,
|
||||
.get_path = usb_fs_get_path,
|
||||
};
|
||||
|
||||
static int find_free_slot(UsbMscContext* ctx) {
|
||||
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
|
||||
if (ctx->devs[i] == nullptr) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_slot_by_handle(UsbMscContext* ctx, msc_host_device_handle_t handle) {
|
||||
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
|
||||
if (ctx->devs[i] && ctx->devs[i]->device == handle) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void free_msc_device(UsbMscContext* ctx, int slot) {
|
||||
if (slot < 0 || slot >= MAX_MSC_DEVICES || !ctx->devs[slot]) return;
|
||||
if (ctx->devs[slot]->fs_entry) {
|
||||
ctx->devs[slot]->mounted = false;
|
||||
file_system_remove(ctx->devs[slot]->fs_entry);
|
||||
ctx->devs[slot]->fs_entry = nullptr;
|
||||
}
|
||||
if (ctx->devs[slot]->vfs) {
|
||||
msc_host_vfs_unregister(ctx->devs[slot]->vfs);
|
||||
}
|
||||
if (ctx->devs[slot]->device) {
|
||||
msc_host_uninstall_device(ctx->devs[slot]->device);
|
||||
}
|
||||
free(ctx->devs[slot]);
|
||||
ctx->devs[slot] = nullptr;
|
||||
}
|
||||
|
||||
static void free_all_msc_devices(UsbMscContext* ctx) {
|
||||
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
|
||||
free_msc_device(ctx, i);
|
||||
}
|
||||
}
|
||||
|
||||
static void msc_event_cb(const msc_host_event_t* event, void* arg) {
|
||||
auto* ctx = static_cast<UsbMscContext*>(arg);
|
||||
if (!ctx->event_queue) return;
|
||||
msc_msg_t msg = {};
|
||||
if (event->event == kMscDeviceConnected) {
|
||||
msg.id = MscMsgId::Connected;
|
||||
msg.address = event->device.address;
|
||||
if (xQueueSend(ctx->event_queue, &msg, 0) != pdTRUE) {
|
||||
LOG_W(TAG, "event queue full, dropped Connected event (addr=%d)", event->device.address);
|
||||
}
|
||||
} else if (event->event == kMscDeviceDisconnected) {
|
||||
msg.id = MscMsgId::Disconnected;
|
||||
msg.handle = event->device.handle;
|
||||
if (xQueueSend(ctx->event_queue, &msg, 0) != pdTRUE) {
|
||||
LOG_W(TAG, "event queue full, dropped Disconnected event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void msc_proc_task(void* arg) {
|
||||
auto* ctx = static_cast<UsbMscContext*>(arg);
|
||||
LOG_I(TAG, "MSC proc task started");
|
||||
|
||||
while (ctx->proc_running) {
|
||||
msc_msg_t msg;
|
||||
if (xQueueReceive(ctx->event_queue, &msg, pdMS_TO_TICKS(100)) != pdTRUE) continue;
|
||||
|
||||
if (msg.id == MscMsgId::Connected) {
|
||||
LOG_I(TAG, "USB drive connected (addr=%d)", msg.address);
|
||||
|
||||
// Find a free slot under the lock, then allocate outside it.
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
int slot = find_free_slot(ctx);
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
if (slot < 0) {
|
||||
LOG_W(TAG, "no free slots for USB drive");
|
||||
continue;
|
||||
}
|
||||
auto* entry = static_cast<msc_dev_entry_t*>(calloc(1, sizeof(msc_dev_entry_t)));
|
||||
if (!entry) {
|
||||
LOG_E(TAG, "failed to allocate MSC device entry");
|
||||
continue;
|
||||
}
|
||||
// Re-check the slot is still free before committing.
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
if (ctx->devs[slot] != nullptr) {
|
||||
slot = find_free_slot(ctx); // another path claimed it; find a new one
|
||||
if (slot >= 0) ctx->devs[slot] = entry;
|
||||
} else {
|
||||
ctx->devs[slot] = entry;
|
||||
}
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
if (slot < 0) {
|
||||
free(entry);
|
||||
LOG_W(TAG, "no free slots for USB drive after allocation");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msc_host_install_device(msg.address, &ctx->devs[slot]->device) != ESP_OK) {
|
||||
LOG_E(TAG, "msc_host_install_device failed");
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
free(ctx->devs[slot]);
|
||||
ctx->devs[slot] = nullptr;
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
continue;
|
||||
}
|
||||
ctx->devs[slot]->usb_addr = msg.address;
|
||||
snprintf(ctx->devs[slot]->mount_path, sizeof(ctx->devs[slot]->mount_path),
|
||||
USB_MSC_MOUNT_PATH_PREFIX "%d", slot);
|
||||
const char* mount_path = ctx->devs[slot]->mount_path;
|
||||
|
||||
const esp_vfs_fat_mount_config_t mount_cfg = {
|
||||
.format_if_mount_failed = false,
|
||||
.max_files = 4,
|
||||
.allocation_unit_size = 4096,
|
||||
.disk_status_check_enable = false,
|
||||
.use_one_fat = false,
|
||||
};
|
||||
esp_err_t vfs_err = msc_host_vfs_register(ctx->devs[slot]->device, mount_path, &mount_cfg, &ctx->devs[slot]->vfs);
|
||||
if (vfs_err != ESP_OK) {
|
||||
LOG_E(TAG, "msc_host_vfs_register failed for %s: %s", mount_path, esp_err_to_name(vfs_err));
|
||||
msc_host_uninstall_device(ctx->devs[slot]->device);
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
free(ctx->devs[slot]);
|
||||
ctx->devs[slot] = nullptr;
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
continue;
|
||||
}
|
||||
LOG_I(TAG, "USB drive mounted at %s", mount_path);
|
||||
ctx->devs[slot]->fs_entry = file_system_add(&usb_fs_api, ctx->devs[slot]);
|
||||
if (!ctx->devs[slot]->fs_entry) {
|
||||
LOG_E(TAG, "failed to register filesystem for %s", mount_path);
|
||||
free_msc_device(ctx, slot);
|
||||
continue;
|
||||
}
|
||||
ctx->devs[slot]->mounted = true;
|
||||
|
||||
} else if (msg.id == MscMsgId::Disconnected) {
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
int slot = find_slot_by_handle(ctx, msg.handle);
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
if (slot >= 0) {
|
||||
LOG_I(TAG, "USB drive disconnected, unmounting slot %d", slot);
|
||||
free_msc_device(ctx, slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free_all_msc_devices(ctx);
|
||||
LOG_I(TAG, "MSC proc task stopped");
|
||||
xSemaphoreGive(ctx->proc_task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
static bool api_eject(struct Device* device, const char* mount_path) {
|
||||
auto* ctx = static_cast<UsbMscContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return false;
|
||||
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
msc_dev_entry_t* entry = nullptr;
|
||||
int found = -1;
|
||||
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
|
||||
if (ctx->devs[i] && strcmp(ctx->devs[i]->mount_path, mount_path) == 0) {
|
||||
found = i;
|
||||
entry = ctx->devs[i];
|
||||
ctx->devs[i] = nullptr; // claim atomically under the lock
|
||||
break;
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
|
||||
if (found >= 0) {
|
||||
LOG_I(TAG, "ejecting USB drive at %s (slot %d)", mount_path, found);
|
||||
// Free outside the lock; slot is already claimed (nullptr) so msc_proc_task won't touch it.
|
||||
if (entry->fs_entry) {
|
||||
entry->mounted = false;
|
||||
file_system_remove(entry->fs_entry);
|
||||
entry->fs_entry = nullptr;
|
||||
}
|
||||
if (entry->vfs) {
|
||||
msc_host_vfs_unregister(entry->vfs);
|
||||
}
|
||||
if (entry->device) {
|
||||
msc_host_uninstall_device(entry->device);
|
||||
}
|
||||
free(entry);
|
||||
LOG_I(TAG, "USB drive ejected, safe to remove");
|
||||
return true;
|
||||
}
|
||||
LOG_W(TAG, "no drive mounted at %s", mount_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const UsbMscApi msc_api = {
|
||||
.eject = api_eject,
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start_device(struct Device* device) {
|
||||
auto* ctx = new UsbMscContext();
|
||||
|
||||
ctx->event_queue = xQueueCreate(MSC_EVENT_QUEUE_SIZE, sizeof(msc_msg_t));
|
||||
if (!ctx->event_queue) {
|
||||
LOG_E(TAG, "failed to create MSC event queue");
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->proc_task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->proc_task_done) {
|
||||
LOG_E(TAG, "failed to create task done semaphore");
|
||||
vQueueDelete(ctx->event_queue);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const msc_host_driver_config_t msc_cfg = {
|
||||
.create_backround_task = true,
|
||||
.task_priority = MSC_PROC_TASK_PRIORITY,
|
||||
.stack_size = MSC_PROC_TASK_STACK,
|
||||
.core_id = tskNO_AFFINITY,
|
||||
.callback = msc_event_cb,
|
||||
.callback_arg = ctx,
|
||||
};
|
||||
if (msc_host_install(&msc_cfg) != ESP_OK) {
|
||||
LOG_E(TAG, "msc_host_install failed");
|
||||
vQueueDelete(ctx->event_queue);
|
||||
vSemaphoreDelete(ctx->proc_task_done);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->proc_running = true;
|
||||
BaseType_t result = xTaskCreate(msc_proc_task, "msc_proc", MSC_PROC_TASK_STACK,
|
||||
ctx, MSC_PROC_TASK_PRIORITY, &ctx->proc_task);
|
||||
if (result != pdPASS) {
|
||||
LOG_E(TAG, "failed to create msc_proc task");
|
||||
ctx->proc_running = false;
|
||||
msc_host_uninstall();
|
||||
vQueueDelete(ctx->event_queue);
|
||||
vSemaphoreDelete(ctx->proc_task_done);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
LOG_I(TAG, "started");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbMscContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return ERROR_NONE;
|
||||
|
||||
ctx->proc_running = false;
|
||||
|
||||
bool exited = (xSemaphoreTake(ctx->proc_task_done, pdMS_TO_TICKS(MSC_STOP_TIMEOUT_MS)) == pdTRUE);
|
||||
if (!exited) {
|
||||
// Retry once with a short extra wait before resorting to force-delete.
|
||||
exited = (xSemaphoreTake(ctx->proc_task_done, pdMS_TO_TICKS(500)) == pdTRUE);
|
||||
}
|
||||
if (!exited) {
|
||||
LOG_W(TAG, "MSC proc task stop timed out, force terminating");
|
||||
vTaskDelete(ctx->proc_task);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
// Task was killed mid-cleanup — free devices ourselves as best-effort.
|
||||
free_all_msc_devices(ctx);
|
||||
}
|
||||
ctx->proc_task = nullptr;
|
||||
vSemaphoreDelete(ctx->proc_task_done);
|
||||
|
||||
msc_host_uninstall();
|
||||
|
||||
if (ctx->event_queue) { vQueueDelete(ctx->event_queue); ctx->event_queue = nullptr; }
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
LOG_I(TAG, "stopped");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_usbhost_msc_driver = {
|
||||
.name = "esp32_usbhost_msc",
|
||||
.compatible = (const char*[]) { "espressif,esp32-usbhost-msc", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = &msc_api,
|
||||
.device_type = &USB_HOST_MSC_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
@@ -25,6 +25,12 @@ extern Driver esp32_ble_serial_driver;
|
||||
extern Driver esp32_ble_midi_driver;
|
||||
extern Driver esp32_ble_hid_device_driver;
|
||||
#endif
|
||||
#if SOC_USB_OTG_SUPPORTED
|
||||
extern Driver esp32_usbhost_driver;
|
||||
extern Driver esp32_usbhost_hid_driver;
|
||||
extern Driver esp32_usbhost_midi_driver;
|
||||
extern Driver esp32_usbhost_msc_driver;
|
||||
#endif
|
||||
|
||||
static error_t start() {
|
||||
/* We crash when construct fails, because if a single driver fails to construct,
|
||||
@@ -42,6 +48,12 @@ static error_t start() {
|
||||
check(driver_construct_add(&esp32_ble_serial_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_ble_midi_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_ble_hid_device_driver) == ERROR_NONE);
|
||||
#endif
|
||||
#if SOC_USB_OTG_SUPPORTED
|
||||
check(driver_construct_add(&esp32_usbhost_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_usbhost_hid_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_usbhost_midi_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_usbhost_msc_driver) == ERROR_NONE);
|
||||
#endif
|
||||
return ERROR_NONE;
|
||||
}
|
||||
@@ -49,6 +61,12 @@ static error_t start() {
|
||||
static error_t stop() {
|
||||
/* We crash when destruct fails, because if a single driver fails to destruct,
|
||||
* there is no guarantee that the previously destroyed drivers can be recovered */
|
||||
#if SOC_USB_OTG_SUPPORTED
|
||||
check(driver_remove_destruct(&esp32_usbhost_msc_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_usbhost_midi_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_usbhost_hid_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_usbhost_driver) == ERROR_NONE);
|
||||
#endif
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
check(driver_remove_destruct(&esp32_ble_hid_device_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_ble_midi_driver) == ERROR_NONE);
|
||||
|
||||
Reference in New Issue
Block a user