From 5841650aa0604b8bea931fc9e90b55f567c173d3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 6 Sep 2024 23:08:08 +0200 Subject: [PATCH] Make AOA open and close asynchronous For AOA keyboard and mouse, only input events were asynchronous. Register/unregister were called from the main thread. This had the benefit to fail immediately if the AOA registration failed, but to support gamepads we want to open/close AOA devices dynamically. Also, it is better to avoid USB I/O from the main thread. --- app/src/usb/aoa_hid.c | 150 +++++++++++++++++++++++++++++-------- app/src/usb/aoa_hid.h | 38 ++++++++-- app/src/usb/keyboard_aoa.c | 9 +-- app/src/usb/mouse_aoa.c | 8 +- 4 files changed, 160 insertions(+), 45 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index f4ff8b8d..b6924868 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -16,7 +16,8 @@ #define DEFAULT_TIMEOUT 1000 -#define SC_AOA_EVENT_QUEUE_MAX 64 +// Drop droppable events above this limit +#define SC_AOA_EVENT_QUEUE_LIMIT 60 static void sc_hid_event_log(const struct sc_hid_event *event) { @@ -35,7 +36,8 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync) { sc_vecdeque_init(&aoa->queue); - if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_MAX)) { + // Add 4 to support 4 non-droppable events without re-allocation + if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_LIMIT + 4)) { return false; } @@ -149,7 +151,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { return true; } -bool +static bool sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) { uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request = ACCESSORY_UNREGISTER_HID; @@ -172,7 +174,7 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) { return true; } -bool +static bool sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, const uint8_t *report_desc, uint16_t report_desc_size) { bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size); @@ -207,8 +209,9 @@ sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa, struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole_noresize(&aoa->queue); - aoa_event->hid = *event; - aoa_event->ack_to_wait = ack_to_wait; + aoa_event->type = SC_AOA_EVENT_TYPE_INPUT; + aoa_event->input.hid = *event; + aoa_event->input.ack_to_wait = ack_to_wait; if (was_empty) { sc_cond_signal(&aoa->event_cond); @@ -221,35 +224,120 @@ sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa, return !full; } -static bool -sc_aoa_process_event(struct sc_aoa *aoa, struct sc_aoa_event *event) { - uint64_t ack_to_wait = event->ack_to_wait; - if (ack_to_wait != SC_SEQUENCE_INVALID) { - LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait); +bool +sc_aoa_push_open(struct sc_aoa *aoa, uint16_t accessory_id, + const uint8_t *report_desc, uint16_t report_desc_size) { + // TODO log verbose - // If some events have ack_to_wait set, then sc_aoa must have been - // initialized with a non NULL acksync - assert(aoa->acksync); + sc_mutex_lock(&aoa->mutex); + bool was_empty = sc_vecdeque_is_empty(&aoa->queue); - // Do not block the loop indefinitely if the ack never comes (it - // should never happen) - sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500); - enum sc_acksync_wait_result result = - sc_acksync_wait(aoa->acksync, ack_to_wait, deadline); - - if (result == SC_ACKSYNC_WAIT_TIMEOUT) { - LOGW("Ack not received after 500ms, discarding HID event"); - // continue to process events - return true; - } else if (result == SC_ACKSYNC_WAIT_INTR) { - // stopped - return false; - } + // an OPEN event is non-droppable, so push it to the queue even above the + // SC_AOA_EVENT_QUEUE_LIMIT + struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue); + if (!aoa_event) { + LOG_OOM(); + sc_mutex_unlock(&aoa->mutex); + return false; } - bool ok = sc_aoa_send_hid_event(aoa, &event->hid); - if (!ok) { - LOGW("Could not send HID event to USB device"); + aoa_event->type = SC_AOA_EVENT_TYPE_OPEN; + aoa_event->open.hid_id = accessory_id; + aoa_event->open.report_desc = report_desc; + aoa_event->open.report_desc_size = report_desc_size; + + if (was_empty) { + sc_cond_signal(&aoa->event_cond); + } + + sc_mutex_unlock(&aoa->mutex); + + return true; +} + +bool +sc_aoa_push_close(struct sc_aoa *aoa, uint16_t accessory_id) { + // TODO log verbose + + sc_mutex_lock(&aoa->mutex); + bool was_empty = sc_vecdeque_is_empty(&aoa->queue); + + // an OPEN event is non-droppable, so push it to the queue even above the + // SC_AOA_EVENT_QUEUE_LIMIT + struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue); + if (!aoa_event) { + LOG_OOM(); + sc_mutex_unlock(&aoa->mutex); + return false; + } + + aoa_event->type = SC_AOA_EVENT_TYPE_CLOSE; + aoa_event->close.hid_id = accessory_id; + + if (was_empty) { + sc_cond_signal(&aoa->event_cond); + } + + sc_mutex_unlock(&aoa->mutex); + + return true; +} + +static bool +sc_aoa_process_event(struct sc_aoa *aoa, struct sc_aoa_event *event) { + switch (event->type) { + case SC_AOA_EVENT_TYPE_INPUT: { + uint64_t ack_to_wait = event->input.ack_to_wait; + if (ack_to_wait != SC_SEQUENCE_INVALID) { + LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait); + + // If some events have ack_to_wait set, then sc_aoa must have + // been initialized with a non NULL acksync + assert(aoa->acksync); + + // Do not block the loop indefinitely if the ack never comes (it + // should never happen) + sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500); + enum sc_acksync_wait_result result = + sc_acksync_wait(aoa->acksync, ack_to_wait, deadline); + + if (result == SC_ACKSYNC_WAIT_TIMEOUT) { + LOGW("Ack not received after 500ms, discarding HID event"); + // continue to process events + return true; + } else if (result == SC_ACKSYNC_WAIT_INTR) { + // stopped + return false; + } + } + + bool ok = sc_aoa_send_hid_event(aoa, &event->input.hid); + if (!ok) { + LOGW("Could not send HID event to USB device: %" PRIu16, + event->input.hid.hid_id); + } + + break; + } + case SC_AOA_EVENT_TYPE_OPEN: { + bool ok = sc_aoa_setup_hid(aoa, event->open.hid_id, + event->open.report_desc, + event->open.report_desc_size); + if (!ok) { + LOGW("Could not open AOA device: %" PRIu16, event->open.hid_id); + } + + break; + } + case SC_AOA_EVENT_TYPE_CLOSE: { + bool ok = sc_aoa_unregister_hid(aoa, event->close.hid_id); + if (!ok) { + LOGW("Could not close AOA device: %" PRIu16, + event->close.hid_id); + } + + break; + } } // continue to process events diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index 87f070ca..b2dc04ac 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -13,9 +13,28 @@ #include "util/tick.h" #include "util/vecdeque.h" +enum sc_aoa_event_type { + SC_AOA_EVENT_TYPE_OPEN, + SC_AOA_EVENT_TYPE_INPUT, + SC_AOA_EVENT_TYPE_CLOSE, +}; + struct sc_aoa_event { - struct sc_hid_event hid; - uint64_t ack_to_wait; + enum sc_aoa_event_type type; + union { + struct { + uint16_t hid_id; + const uint8_t *report_desc; // pointer to static memory + uint16_t report_desc_size; + } open; + struct { + uint16_t hid_id; + } close; + struct { + struct sc_hid_event hid; + uint64_t ack_to_wait; + } input; + }; }; struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event); @@ -46,12 +65,21 @@ sc_aoa_stop(struct sc_aoa *aoa); void sc_aoa_join(struct sc_aoa *aoa); +//bool +//sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, +// const uint8_t *report_desc, uint16_t report_desc_size); +// +//bool +//sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id); + +// report_desc must be a pointer to static memory, accessed at any time from +// another thread bool -sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, - const uint8_t *report_desc, uint16_t report_desc_size); +sc_aoa_push_open(struct sc_aoa *aoa, uint16_t accessory_id, + const uint8_t *report_desc, uint16_t report_desc_size); bool -sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id); +sc_aoa_push_close(struct sc_aoa *aoa, uint16_t accessory_id); bool sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa, diff --git a/app/src/usb/keyboard_aoa.c b/app/src/usb/keyboard_aoa.c index f6bd6849..0052c3d8 100644 --- a/app/src/usb/keyboard_aoa.c +++ b/app/src/usb/keyboard_aoa.c @@ -66,11 +66,11 @@ bool sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) { kb->aoa = aoa; - bool ok = sc_aoa_setup_hid(aoa, SC_HID_ID_KEYBOARD, + bool ok = sc_aoa_push_open(aoa, SC_HID_ID_KEYBOARD, SC_HID_KEYBOARD_REPORT_DESC, SC_HID_KEYBOARD_REPORT_DESC_LEN); if (!ok) { - LOGW("Register HID keyboard failed"); + LOGW("Could not push AOA keyboard open request"); return false; } @@ -97,9 +97,8 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) { void sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) { - // Unregister HID keyboard so the soft keyboard shows again on Android - bool ok = sc_aoa_unregister_hid(kb->aoa, SC_HID_ID_KEYBOARD); + bool ok = sc_aoa_push_close(kb->aoa, SC_HID_ID_KEYBOARD); if (!ok) { - LOGW("Could not unregister HID keyboard"); + LOGW("Could not push AOA keyboard close request"); } } diff --git a/app/src/usb/mouse_aoa.c b/app/src/usb/mouse_aoa.c index 896578c0..84fd8d64 100644 --- a/app/src/usb/mouse_aoa.c +++ b/app/src/usb/mouse_aoa.c @@ -52,11 +52,11 @@ bool sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) { mouse->aoa = aoa; - bool ok = sc_aoa_setup_hid(aoa, SC_HID_ID_MOUSE, + bool ok = sc_aoa_push_open(aoa, SC_HID_ID_MOUSE, SC_HID_MOUSE_REPORT_DESC, SC_HID_MOUSE_REPORT_DESC_LEN); if (!ok) { - LOGW("Register HID mouse failed"); + LOGW("Could not push AOA mouse open request"); return false; } @@ -77,8 +77,8 @@ sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) { void sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) { - bool ok = sc_aoa_unregister_hid(mouse->aoa, SC_HID_ID_MOUSE); + bool ok = sc_aoa_push_close(mouse->aoa, SC_HID_ID_MOUSE); if (!ok) { - LOGW("Could not unregister HID mouse"); + LOGW("Could not push AOA mouse close request"); } }