summaryrefslogtreecommitdiff
path: root/tmk_core/protocol/chibios/usb_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'tmk_core/protocol/chibios/usb_main.c')
-rw-r--r--tmk_core/protocol/chibios/usb_main.c117
1 files changed, 96 insertions, 21 deletions
diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c
index 67ae8520fd..990dc1b91c 100644
--- a/tmk_core/protocol/chibios/usb_main.c
+++ b/tmk_core/protocol/chibios/usb_main.c
@@ -27,6 +27,7 @@
#include <ch.h>
#include <hal.h>
+#include <string.h>
#include "usb_main.h"
@@ -50,6 +51,7 @@ extern keymap_config_t keymap_config;
#ifdef WEBUSB_ENABLE
# include "webusb.h"
#endif
+
#ifdef JOYSTICK_ENABLE
# include "joystick.h"
#endif
@@ -168,6 +170,7 @@ static const USBEndpointConfig shared_ep_config = {
};
#endif
+
#ifdef WEBUSB_ENABLE
/** Microsoft OS 2.0 Descriptor. This is used by Windows to select the USB driver for the device.
*
@@ -397,6 +400,69 @@ static usb_driver_configs_t drivers = {
* ---------------------------------------------------------
*/
+#define USB_EVENT_QUEUE_SIZE 16
+usbevent_t event_queue[USB_EVENT_QUEUE_SIZE];
+uint8_t event_queue_head;
+uint8_t event_queue_tail;
+
+void usb_event_queue_init(void) {
+ // Initialise the event queue
+ memset(&event_queue, 0, sizeof(event_queue));
+ event_queue_head = 0;
+ event_queue_tail = 0;
+}
+
+static inline bool usb_event_queue_enqueue(usbevent_t event) {
+ uint8_t next = (event_queue_head + 1) % USB_EVENT_QUEUE_SIZE;
+ if (next == event_queue_tail) {
+ return false;
+ }
+ event_queue[event_queue_head] = event;
+ event_queue_head = next;
+ return true;
+}
+
+static inline bool usb_event_queue_dequeue(usbevent_t *event) {
+ if (event_queue_head == event_queue_tail) {
+ return false;
+ }
+ *event = event_queue[event_queue_tail];
+ event_queue_tail = (event_queue_tail + 1) % USB_EVENT_QUEUE_SIZE;
+ return true;
+}
+
+static inline void usb_event_suspend_handler(void) {
+#ifdef SLEEP_LED_ENABLE
+ sleep_led_enable();
+#endif /* SLEEP_LED_ENABLE */
+}
+
+static inline void usb_event_wakeup_handler(void) {
+ suspend_wakeup_init();
+#ifdef SLEEP_LED_ENABLE
+ sleep_led_disable();
+ // NOTE: converters may not accept this
+ led_set(host_keyboard_leds());
+#endif /* SLEEP_LED_ENABLE */
+}
+
+void usb_event_queue_task(void) {
+ usbevent_t event;
+ while (usb_event_queue_dequeue(&event)) {
+ switch (event) {
+ case USB_EVENT_SUSPEND:
+ usb_event_suspend_handler();
+ break;
+ case USB_EVENT_WAKEUP:
+ usb_event_wakeup_handler();
+ break;
+ default:
+ // Nothing to do, we don't handle it.
+ break;
+ }
+ }
+}
+
/* Handles the USB driver global events
* TODO: maybe disable some things when connection is lost? */
static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
@@ -431,9 +497,7 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
osalSysUnlockFromISR();
return;
case USB_EVENT_SUSPEND:
-#ifdef SLEEP_LED_ENABLE
- sleep_led_enable();
-#endif /* SLEEP_LED_ENABLE */
+ usb_event_queue_enqueue(USB_EVENT_SUSPEND);
/* Falls into.*/
case USB_EVENT_UNCONFIGURED:
/* Falls into.*/
@@ -454,12 +518,7 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
qmkusbWakeupHookI(&drivers.array[i].driver);
chSysUnlockFromISR();
}
- suspend_wakeup_init();
-#ifdef SLEEP_LED_ENABLE
- sleep_led_disable();
- // NOTE: converters may not accept this
- led_set(host_keyboard_leds());
-#endif /* SLEEP_LED_ENABLE */
+ usb_event_queue_enqueue(USB_EVENT_WAKEUP);
return;
case USB_EVENT_STALLED:
@@ -575,7 +634,7 @@ static bool usb_request_hook_cb(USBDriver *usbp) {
if (!keymap_config.nkro && keyboard_idle) {
#else /* NKRO_ENABLE */
if (keyboard_idle) {
-#endif /* NKRO_ENABLE */
+#endif /* NKRO_ENABLE */
/* arm the idle timer if boot protocol & idle */
osalSysLockFromISR();
chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp);
@@ -867,7 +926,7 @@ void send_mouse(report_mouse_t *report) {
}
#else /* MOUSE_ENABLE */
-void send_mouse(report_mouse_t *report) { (void)report; }
+void send_mouse(report_mouse_t *report) { (void)report; }
#endif /* MOUSE_ENABLE */
/* ---------------------------------------------------------
@@ -923,9 +982,32 @@ void send_consumer(uint16_t data) {
#ifdef CONSOLE_ENABLE
int8_t sendchar(uint8_t c) {
- // The previous implmentation had timeouts, but I think it's better to just slow down
- // and make sure that everything is transferred, rather than dropping stuff
- return chnWrite(&drivers.console_driver.driver, &c, 1);
+ static bool timed_out = false;
+ /* The `timed_out` state is an approximation of the ideal `is_listener_disconnected?` state.
+ *
+ * When a 5ms timeout write has timed out, hid_listen is most likely not running, or not
+ * listening to this keyboard, so we go into the timed_out state. In this state we assume
+ * that hid_listen is most likely not gonna be connected to us any time soon, so it would
+ * be wasteful to write follow-up characters with a 5ms timeout, it would all add up and
+ * unncecessarily slow down the firmware. However instead of just dropping the characters,
+ * we write them with a TIME_IMMEDIATE timeout, which is a zero timeout,
+ * and this will succeed only if hid_listen gets connected again. When a write with
+ * TIME_IMMEDIATE timeout succeeds, we know that hid_listen is listening to us again, and
+ * we can go back to the timed_out = false state, and following writes will be executed
+ * with a 5ms timeout. The reason we don't just send all characters with the TIME_IMMEDIATE
+ * timeout is that this could cause bytes to be lost even if hid_listen is running, if there
+ * is a lot of data being sent over the console.
+ *
+ * This logic will work correctly as long as hid_listen is able to receive at least 200
+ * bytes per second. On a heavily overloaded machine that's so overloaded that it's
+ * unusable, and constantly swapping, hid_listen might have trouble receiving 200 bytes per
+ * second, so some bytes might be lost on the console.
+ */
+
+ const sysinterval_t timeout = timed_out ? TIME_IMMEDIATE : TIME_MS2I(5);
+ const size_t result = chnWriteTimeout(&drivers.console_driver.driver, &c, 1, timeout);
+ timed_out = (result == 0);
+ return result;
}
// Just a dummy function for now, this could be exposed as a weak function
@@ -946,15 +1028,8 @@ void console_task(void) {
} while (size > 0);
}
-#else /* CONSOLE_ENABLE */
-int8_t sendchar(uint8_t c) {
- (void)c;
- return 0;
-}
#endif /* CONSOLE_ENABLE */
-void _putchar(char character) { sendchar(character); }
-
#ifdef RAW_ENABLE
void raw_hid_send(uint8_t *data, uint8_t length) {
// TODO: implement variable size packet