qmk-keychron-q3-colemak-dh/tmk_core/protocol/chibios/usb_main.c
James Laird-Wah 39bd760faf Use a single endpoint for HID reports (#3951)
* Unify multiple HID interfaces into one

This reduces the number of USB endpoints required, which frees them up
for other things.

NKRO and EXTRAKEY always use the shared endpoint.

By default, MOUSEKEY also uses it. This means it won't work as a Boot
Procotol mouse in some BIOSes, etc. If you really think your
keyboard needs to work as a mouse in your BIOS, set
MOUSE_SHARED_EP = no in your rules.mk.

By default, the core keyboard does not use the shared endpoint, as not
all BIOSes are standards compliant and that's one place you don't want
to find out your keyboard doesn't work.. If you are really confident,
you can set KEYBOARD_SHARED_EP = yes to use the shared endpoint here
too.

* unify endpoints: ChibiOS protocol implementation

* fixup: missing #ifdef EXTRAKEY_ENABLEs

broke build on AVR with EXTRAKEY disabled

* endpoints: restore error when too many endpoints required

* lufa: wait up to 10ms to send keyboard input

This avoids packets being dropped when two reports are sent in quick
succession (eg. releasing a dual role key).

* endpoints: fix compile on ARM_ATSAM

* endpoint: ARM_ATSAM fixes

No longer use wrong or unexpected endpoint IDs

* endpoints: accommodate VUSB protocol

V-USB has its own, understandably simple ideas about the report formats.
It already blasts the mouse and extrakeys through one endpoint with
report IDs. We just stay out of its way.

* endpoints: document new endpoint configuration options

* endpoints: respect keyboard_report->mods in NKRO

The caller(s) of host_keyboard_send expect to be able to just drop
modifiers in the mods field and not worry about whether NKRO is in use.
This is a good thing. So we just shift it over if needs be.

* endpoints: report.c: update for new keyboard_report format
2018-11-15 22:22:05 -08:00

885 lines
27 KiB
C

/*
* (c) 2015 flabberast <s3+flabbergast@sdfeu.org>
*
* Based on the following work:
* - Guillaume Duc's raw hid example (MIT License)
* https://github.com/guiduc/usb-hid-chibios-example
* - PJRC Teensy examples (MIT License)
* https://www.pjrc.com/teensy/usb_keyboard.html
* - hasu's TMK keyboard code (GPL v2 and some code Modified BSD)
* https://github.com/tmk/tmk_keyboard/
* - ChibiOS demo code (Apache 2.0 License)
* http://www.chibios.org
*
* Since some GPL'd code is used, this work is licensed under
* GPL v2 or later.
*/
#include "ch.h"
#include "hal.h"
#include "usb_main.h"
#include "host.h"
#include "debug.h"
#include "suspend.h"
#ifdef SLEEP_LED_ENABLE
#include "sleep_led.h"
#include "led.h"
#endif
#include "wait.h"
#include "usb_descriptor.h"
#include "usb_driver.h"
#ifdef NKRO_ENABLE
#include "keycode_config.h"
extern keymap_config_t keymap_config;
#endif
/* ---------------------------------------------------------
* Global interface variables and declarations
* ---------------------------------------------------------
*/
#ifndef usb_lld_connect_bus
#define usb_lld_connect_bus(usbp)
#endif
#ifndef usb_lld_disconnect_bus
#define usb_lld_disconnect_bus(usbp)
#endif
uint8_t keyboard_idle __attribute__((aligned(2))) = 0;
uint8_t keyboard_protocol __attribute__((aligned(2))) = 1;
uint16_t keyboard_led_stats __attribute__((aligned(2))) = 0;
volatile uint16_t keyboard_idle_count = 0;
static virtual_timer_t keyboard_idle_timer;
static void keyboard_idle_timer_cb(void *arg);
report_keyboard_t keyboard_report_sent = {{0}};
#ifdef MOUSE_ENABLE
report_mouse_t mouse_report_blank = {0};
#endif /* MOUSE_ENABLE */
#ifdef EXTRAKEY_ENABLE
uint8_t extra_report_blank[3] = {0};
#endif /* EXTRAKEY_ENABLE */
/* ---------------------------------------------------------
* Descriptors and USB driver objects
* ---------------------------------------------------------
*/
/* HID specific constants */
#define HID_GET_REPORT 0x01
#define HID_GET_IDLE 0x02
#define HID_GET_PROTOCOL 0x03
#define HID_SET_REPORT 0x09
#define HID_SET_IDLE 0x0A
#define HID_SET_PROTOCOL 0x0B
/*
* Handles the GET_DESCRIPTOR callback
*
* Returns the proper descriptor
*/
static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype, uint8_t dindex, uint16_t wIndex) {
(void)usbp;
static USBDescriptor desc;
uint16_t wValue = ((uint16_t)dtype << 8) | dindex;
desc.ud_string = NULL;
desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void** const)&desc.ud_string);
if (desc.ud_string == NULL)
return NULL;
else
return &desc;
}
#ifndef KEYBOARD_SHARED_EP
/* keyboard endpoint state structure */
static USBInEndpointState kbd_ep_state;
/* keyboard endpoint initialization structure (IN) */
static const USBEndpointConfig kbd_ep_config = {
USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
NULL, /* SETUP packet notification callback */
kbd_in_cb, /* IN notification callback */
NULL, /* OUT notification callback */
KEYBOARD_EPSIZE, /* IN maximum packet size */
0, /* OUT maximum packet size */
&kbd_ep_state, /* IN Endpoint state */
NULL, /* OUT endpoint state */
2, /* IN multiplier */
NULL /* SETUP buffer (not a SETUP endpoint) */
};
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
/* mouse endpoint state structure */
static USBInEndpointState mouse_ep_state;
/* mouse endpoint initialization structure (IN) */
static const USBEndpointConfig mouse_ep_config = {
USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
NULL, /* SETUP packet notification callback */
mouse_in_cb, /* IN notification callback */
NULL, /* OUT notification callback */
MOUSE_EPSIZE, /* IN maximum packet size */
0, /* OUT maximum packet size */
&mouse_ep_state, /* IN Endpoint state */
NULL, /* OUT endpoint state */
2, /* IN multiplier */
NULL /* SETUP buffer (not a SETUP endpoint) */
};
#endif
#ifdef SHARED_EP_ENABLE
/* shared endpoint state structure */
static USBInEndpointState shared_ep_state;
/* shared endpoint initialization structure (IN) */
static const USBEndpointConfig shared_ep_config = {
USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
NULL, /* SETUP packet notification callback */
shared_in_cb, /* IN notification callback */
NULL, /* OUT notification callback */
SHARED_EPSIZE, /* IN maximum packet size */
0, /* OUT maximum packet size */
&shared_ep_state, /* IN Endpoint state */
NULL, /* OUT endpoint state */
2, /* IN multiplier */
NULL /* SETUP buffer (not a SETUP endpoint) */
};
#endif
typedef struct {
size_t queue_capacity_in;
size_t queue_capacity_out;
USBInEndpointState in_ep_state;
USBOutEndpointState out_ep_state;
USBInEndpointState int_ep_state;
USBEndpointConfig in_ep_config;
USBEndpointConfig out_ep_config;
USBEndpointConfig int_ep_config;
const QMKUSBConfig config;
QMKUSBDriver driver;
} usb_driver_config_t;
#define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) { \
.queue_capacity_in = stream##_IN_CAPACITY, \
.queue_capacity_out = stream##_OUT_CAPACITY, \
.in_ep_config = { \
.ep_mode = stream##_IN_MODE, \
.setup_cb = NULL, \
.in_cb = qmkusbDataTransmitted, \
.out_cb = NULL, \
.in_maxsize = stream##_EPSIZE, \
.out_maxsize = 0, \
/* The pointer to the states will be filled during initialization */ \
.in_state = NULL, \
.out_state = NULL, \
.ep_buffers = 2, \
.setup_buf = NULL \
}, \
.out_ep_config = { \
.ep_mode = stream##_OUT_MODE, \
.setup_cb = NULL, \
.in_cb = NULL, \
.out_cb = qmkusbDataReceived, \
.in_maxsize = 0, \
.out_maxsize = stream##_EPSIZE, \
/* The pointer to the states will be filled during initialization */ \
.in_state = NULL, \
.out_state = NULL, \
.ep_buffers = 2, \
.setup_buf = NULL, \
}, \
.int_ep_config = { \
.ep_mode = USB_EP_MODE_TYPE_INTR, \
.setup_cb = NULL, \
.in_cb = qmkusbInterruptTransmitted, \
.out_cb = NULL, \
.in_maxsize = CDC_NOTIFICATION_EPSIZE, \
.out_maxsize = 0, \
/* The pointer to the states will be filled during initialization */ \
.in_state = NULL, \
.out_state = NULL, \
.ep_buffers = 2, \
.setup_buf = NULL, \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.bulk_in = stream##_IN_EPNUM, \
.bulk_out = stream##_OUT_EPNUM, \
.int_in = notification, \
.in_buffers = stream##_IN_CAPACITY, \
.out_buffers = stream##_OUT_CAPACITY, \
.in_size = stream##_EPSIZE, \
.out_size = stream##_EPSIZE, \
.fixed_size = fixedsize, \
.ib = (uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]) {}, \
.ob = (uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY,stream##_EPSIZE)]) {}, \
} \
}
typedef struct {
union {
struct {
#ifdef CONSOLE_ENABLE
usb_driver_config_t console_driver;
#endif
#ifdef RAW_ENABLE
usb_driver_config_t raw_driver;
#endif
#ifdef MIDI_ENABLE
usb_driver_config_t midi_driver;
#endif
#ifdef VIRTSER_ENABLE
usb_driver_config_t serial_driver;
#endif
};
usb_driver_config_t array[0];
};
} usb_driver_configs_t;
static usb_driver_configs_t drivers = {
#ifdef CONSOLE_ENABLE
#define CONSOLE_IN_CAPACITY 4
#define CONSOLE_OUT_CAPACITY 4
#define CONSOLE_IN_MODE USB_EP_MODE_TYPE_INTR
#define CONSOLE_OUT_MODE USB_EP_MODE_TYPE_INTR
.console_driver = QMK_USB_DRIVER_CONFIG(CONSOLE, 0, true),
#endif
#ifdef RAW_ENABLE
#define RAW_IN_CAPACITY 4
#define RAW_OUT_CAPACITY 4
#define RAW_IN_MODE USB_EP_MODE_TYPE_INTR
#define RAW_OUT_MODE USB_EP_MODE_TYPE_INTR
.raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false),
#endif
#ifdef MIDI_ENABLE
#define MIDI_STREAM_IN_CAPACITY 4
#define MIDI_STREAM_OUT_CAPACITY 4
#define MIDI_STREAM_IN_MODE USB_EP_MODE_TYPE_BULK
#define MIDI_STREAM_OUT_MODE USB_EP_MODE_TYPE_BULK
.midi_driver = QMK_USB_DRIVER_CONFIG(MIDI_STREAM, 0, false),
#endif
#ifdef VIRTSER_ENABLE
#define CDC_IN_CAPACITY 4
#define CDC_OUT_CAPACITY 4
#define CDC_IN_MODE USB_EP_MODE_TYPE_BULK
#define CDC_OUT_MODE USB_EP_MODE_TYPE_BULK
.serial_driver = QMK_USB_DRIVER_CONFIG(CDC, CDC_NOTIFICATION_EPNUM, false),
#endif
};
#define NUM_USB_DRIVERS (sizeof(drivers) / sizeof(usb_driver_config_t))
/* ---------------------------------------------------------
* USB driver functions
* ---------------------------------------------------------
*/
/* 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) {
switch(event) {
case USB_EVENT_ADDRESS:
return;
case USB_EVENT_CONFIGURED:
osalSysLockFromISR();
/* Enable the endpoints specified into the configuration. */
#ifndef KEYBOARD_SHARED_EP
usbInitEndpointI(usbp, KEYBOARD_IN_EPNUM, &kbd_ep_config);
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
usbInitEndpointI(usbp, MOUSE_IN_EPNUM, &mouse_ep_config);
#endif
#ifdef SHARED_EP_ENABLE
usbInitEndpointI(usbp, SHARED_IN_EPNUM, &shared_ep_config);
#endif
for (int i=0;i<NUM_USB_DRIVERS;i++) {
usbInitEndpointI(usbp, drivers.array[i].config.bulk_in, &drivers.array[i].in_ep_config);
usbInitEndpointI(usbp, drivers.array[i].config.bulk_out, &drivers.array[i].out_ep_config);
if (drivers.array[i].config.int_in) {
usbInitEndpointI(usbp, drivers.array[i].config.int_in, &drivers.array[i].int_ep_config);
}
qmkusbConfigureHookI(&drivers.array[i].driver);
}
osalSysUnlockFromISR();
return;
case USB_EVENT_SUSPEND:
#ifdef SLEEP_LED_ENABLE
sleep_led_enable();
#endif /* SLEEP_LED_ENABLE */
/* Falls into.*/
case USB_EVENT_UNCONFIGURED:
/* Falls into.*/
case USB_EVENT_RESET:
for (int i=0;i<NUM_USB_DRIVERS;i++) {
chSysLockFromISR();
/* Disconnection event on suspend.*/
qmkusbSuspendHookI(&drivers.array[i].driver);
chSysUnlockFromISR();
}
return;
case USB_EVENT_WAKEUP:
//TODO: from ISR! print("[W]");
for (int i=0;i<NUM_USB_DRIVERS;i++) {
chSysLockFromISR();
/* Disconnection event on suspend.*/
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 */
return;
case USB_EVENT_STALLED:
return;
}
}
/* Function used locally in os/hal/src/usb.c for getting descriptors
* need it here for HID descriptor */
static uint16_t get_hword(uint8_t *p) {
uint16_t hw;
hw = (uint16_t)*p++;
hw |= (uint16_t)*p << 8U;
return hw;
}
/*
* Appendix G: HID Request Support Requirements
*
* The following table enumerates the requests that need to be supported by various types of HID class devices.
* Device type GetReport SetReport GetIdle SetIdle GetProtocol SetProtocol
* ------------------------------------------------------------------------------------------
* Boot Mouse Required Optional Optional Optional Required Required
* Non-Boot Mouse Required Optional Optional Optional Optional Optional
* Boot Keyboard Required Optional Required Required Required Required
* Non-Boot Keybrd Required Optional Required Required Optional Optional
* Other Device Required Optional Optional Optional Optional Optional
*/
#ifdef SHARED_EP_ENABLE
static uint8_t set_report_buf[2] __attribute__((aligned(2)));
static void set_led_transfer_cb(USBDriver *usbp) {
if ((set_report_buf[0] == REPORT_ID_KEYBOARD) ||
(set_report_buf[0] == REPORT_ID_NKRO)) {
keyboard_led_stats = set_report_buf[1];
}
}
#endif
/* Callback for SETUP request on the endpoint 0 (control) */
static bool usb_request_hook_cb(USBDriver *usbp) {
const USBDescriptor *dp;
int has_report_id;
/* usbp->setup fields:
* 0: bmRequestType (bitmask)
* 1: bRequest
* 2,3: (LSB,MSB) wValue
* 4,5: (LSB,MSB) wIndex
* 6,7: (LSB,MSB) wLength (number of bytes to transfer if there is a data phase) */
/* Handle HID class specific requests */
if(((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) &&
((usbp->setup[0] & USB_RTYPE_RECIPIENT_MASK) == USB_RTYPE_RECIPIENT_INTERFACE)) {
switch(usbp->setup[0] & USB_RTYPE_DIR_MASK) {
case USB_RTYPE_DIR_DEV2HOST:
switch(usbp->setup[1]) { /* bRequest */
case HID_GET_REPORT:
switch(usbp->setup[4]) { /* LSB(wIndex) (check MSB==0?) */
case KEYBOARD_INTERFACE:
usbSetupTransfer(usbp, (uint8_t *)&keyboard_report_sent, sizeof(keyboard_report_sent), NULL);
return TRUE;
break;
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
case MOUSE_INTERFACE:
usbSetupTransfer(usbp, (uint8_t *)&mouse_report_blank, sizeof(mouse_report_blank), NULL);
return TRUE;
break;
#endif
default:
usbSetupTransfer(usbp, NULL, 0, NULL);
return TRUE;
break;
}
break;
case HID_GET_PROTOCOL:
if((usbp->setup[4] == KEYBOARD_INTERFACE) && (usbp->setup[5] == 0)) { /* wIndex */
usbSetupTransfer(usbp, &keyboard_protocol, 1, NULL);
return TRUE;
}
break;
case HID_GET_IDLE:
usbSetupTransfer(usbp, &keyboard_idle, 1, NULL);
return TRUE;
break;
}
break;
case USB_RTYPE_DIR_HOST2DEV:
switch(usbp->setup[1]) { /* bRequest */
case HID_SET_REPORT:
switch(usbp->setup[4]) { /* LSB(wIndex) (check MSB==0 and wLength==1?) */
case KEYBOARD_INTERFACE:
#if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP)
case SHARED_INTERFACE:
#endif
/* keyboard_led_stats = <read byte from next OUT report>
* keyboard_led_stats needs be word (or dword), otherwise we get an exception on F0 */
has_report_id = 0;
#if defined(SHARED_EP_ENABLE)
if (usbp->setup[4] == SHARED_INTERFACE) {
has_report_id = 1;
}
#endif
if (usbp->setup[4] == KEYBOARD_INTERFACE && !keyboard_protocol) {
has_report_id = 0;
}
if (has_report_id) {
usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_led_transfer_cb);
} else {
usbSetupTransfer(usbp, (uint8_t *)&keyboard_led_stats, 1, NULL);
}
return TRUE;
break;
}
break;
case HID_SET_PROTOCOL:
if((usbp->setup[4] == KEYBOARD_INTERFACE) && (usbp->setup[5] == 0)) { /* wIndex */
keyboard_protocol = ((usbp->setup[2]) != 0x00); /* LSB(wValue) */
#ifdef NKRO_ENABLE
keymap_config.nkro = !!keyboard_protocol;
if(!keymap_config.nkro && keyboard_idle) {
#else /* NKRO_ENABLE */
if(keyboard_idle) {
#endif /* NKRO_ENABLE */
/* arm the idle timer if boot protocol & idle */
osalSysLockFromISR();
chVTSetI(&keyboard_idle_timer, 4*MS2ST(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp);
osalSysUnlockFromISR();
}
}
usbSetupTransfer(usbp, NULL, 0, NULL);
return TRUE;
break;
case HID_SET_IDLE:
keyboard_idle = usbp->setup[3]; /* MSB(wValue) */
/* arm the timer */
#ifdef NKRO_ENABLE
if(!keymap_config.nkro && keyboard_idle) {
#else /* NKRO_ENABLE */
if(keyboard_idle) {
#endif /* NKRO_ENABLE */
osalSysLockFromISR();
chVTSetI(&keyboard_idle_timer, 4*MS2ST(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp);
osalSysUnlockFromISR();
}
usbSetupTransfer(usbp, NULL, 0, NULL);
return TRUE;
break;
}
break;
}
}
/* Handle the Get_Descriptor Request for HID class (not handled by the default hook) */
if((usbp->setup[0] == 0x81) && (usbp->setup[1] == USB_REQ_GET_DESCRIPTOR)) {
dp = usbp->config->get_descriptor_cb(usbp, usbp->setup[3], usbp->setup[2], get_hword(&usbp->setup[4]));
if(dp == NULL)
return FALSE;
usbSetupTransfer(usbp, (uint8_t *)dp->ud_string, dp->ud_size, NULL);
return TRUE;
}
for (int i=0;i<NUM_USB_DRIVERS;i++) {
if (drivers.array[i].config.int_in) {
// NOTE: Assumes that we only have one serial driver
return qmkusbRequestsHook(usbp);
}
}
return FALSE;
}
/* Start-of-frame callback */
static void usb_sof_cb(USBDriver *usbp) {
kbd_sof_cb(usbp);
osalSysLockFromISR();
for (int i=0; i<NUM_USB_DRIVERS;i++) {
qmkusbSOFHookI(&drivers.array[i].driver);
}
osalSysUnlockFromISR();
}
/* USB driver configuration */
static const USBConfig usbcfg = {
usb_event_cb, /* USB events callback */
usb_get_descriptor_cb, /* Device GET_DESCRIPTOR request callback */
usb_request_hook_cb, /* Requests hook callback */
usb_sof_cb /* Start Of Frame callback */
};
/*
* Initialize the USB driver
*/
void init_usb_driver(USBDriver *usbp) {
for (int i=0; i<NUM_USB_DRIVERS;i++) {
QMKUSBDriver* driver = &drivers.array[i].driver;
drivers.array[i].in_ep_config.in_state = &drivers.array[i].in_ep_state;
drivers.array[i].out_ep_config.out_state = &drivers.array[i].out_ep_state;
drivers.array[i].int_ep_config.in_state = &drivers.array[i].int_ep_state;
qmkusbObjectInit(driver, &drivers.array[i].config);
qmkusbStart(driver, &drivers.array[i].config);
}
/*
* Activates the USB driver and then the USB bus pull-up on D+.
* Note, a delay is inserted in order to not have to disconnect the cable
* after a reset.
*/
usbDisconnectBus(usbp);
wait_ms(1500);
usbStart(usbp, &usbcfg);
usbConnectBus(usbp);
chVTObjectInit(&keyboard_idle_timer);
}
/* ---------------------------------------------------------
* Keyboard functions
* ---------------------------------------------------------
*/
/* keyboard IN callback hander (a kbd report has made it IN) */
#ifndef KEYBOARD_SHARED_EP
void kbd_in_cb(USBDriver *usbp, usbep_t ep) {
/* STUB */
(void)usbp;
(void)ep;
}
#endif
/* start-of-frame handler
* TODO: i guess it would be better to re-implement using timers,
* so that this is not going to have to be checked every 1ms */
void kbd_sof_cb(USBDriver *usbp) {
(void)usbp;
}
/* Idle requests timer code
* callback (called from ISR, unlocked state) */
static void keyboard_idle_timer_cb(void *arg) {
USBDriver *usbp = (USBDriver *)arg;
osalSysLockFromISR();
/* check that the states of things are as they're supposed to */
if(usbGetDriverStateI(usbp) != USB_ACTIVE) {
/* do not rearm the timer, should be enabled on IDLE request */
osalSysUnlockFromISR();
return;
}
#ifdef NKRO_ENABLE
if(!keymap_config.nkro && keyboard_idle && keyboard_protocol) {
#else /* NKRO_ENABLE */
if(keyboard_idle && keyboard_protocol) {
#endif /* NKRO_ENABLE */
/* TODO: are we sure we want the KBD_ENDPOINT? */
if(!usbGetTransmitStatusI(usbp, KEYBOARD_IN_EPNUM)) {
usbStartTransmitI(usbp, KEYBOARD_IN_EPNUM, (uint8_t *)&keyboard_report_sent, KEYBOARD_EPSIZE);
}
/* rearm the timer */
chVTSetI(&keyboard_idle_timer, 4*MS2ST(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp);
}
/* do not rearm the timer if the condition above fails
* it should be enabled again on either IDLE or SET_PROTOCOL requests */
osalSysUnlockFromISR();
}
/* LED status */
uint8_t keyboard_leds(void) {
return (uint8_t)(keyboard_led_stats & 0xFF);
}
/* prepare and start sending a report IN
* not callable from ISR or locked state */
void send_keyboard(report_keyboard_t *report) {
osalSysLock();
if(usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) {
osalSysUnlock();
return;
}
osalSysUnlock();
#ifdef NKRO_ENABLE
if(keymap_config.nkro && keyboard_protocol) { /* NKRO protocol */
/* need to wait until the previous packet has made it through */
/* can rewrite this using the synchronous API, then would wait
* until *after* the packet has been transmitted. I think
* this is more efficient */
/* busy wait, should be short and not very common */
osalSysLock();
if(usbGetTransmitStatusI(&USB_DRIVER, SHARED_IN_EPNUM)) {
/* Need to either suspend, or loop and call unlock/lock during
* every iteration - otherwise the system will remain locked,
* no interrupts served, so USB not going through as well.
* Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */
osalThreadSuspendS(&(&USB_DRIVER)->epc[SHARED_IN_EPNUM]->in_state->thread);
}
usbStartTransmitI(&USB_DRIVER, SHARED_IN_EPNUM, (uint8_t *)report, sizeof(struct nkro_report));
osalSysUnlock();
} else
#endif /* NKRO_ENABLE */
{ /* regular protocol */
/* need to wait until the previous packet has made it through */
/* busy wait, should be short and not very common */
osalSysLock();
if(usbGetTransmitStatusI(&USB_DRIVER, KEYBOARD_IN_EPNUM)) {
/* Need to either suspend, or loop and call unlock/lock during
* every iteration - otherwise the system will remain locked,
* no interrupts served, so USB not going through as well.
* Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */
osalThreadSuspendS(&(&USB_DRIVER)->epc[KEYBOARD_IN_EPNUM]->in_state->thread);
}
uint8_t *data, size;
if (keyboard_protocol) {
data = (uint8_t*)report;
size = KEYBOARD_REPORT_SIZE;
} else { /* boot protocol */
data = &report->mods;
size = 8;
}
usbStartTransmitI(&USB_DRIVER, KEYBOARD_IN_EPNUM, data, size);
osalSysUnlock();
}
keyboard_report_sent = *report;
}
/* ---------------------------------------------------------
* Mouse functions
* ---------------------------------------------------------
*/
#ifdef MOUSE_ENABLE
#ifndef MOUSE_SHARED_EP
/* mouse IN callback hander (a mouse report has made it IN) */
void mouse_in_cb(USBDriver *usbp, usbep_t ep) {
(void)usbp;
(void)ep;
}
#endif
void send_mouse(report_mouse_t *report) {
osalSysLock();
if(usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) {
osalSysUnlock();
return;
}
if(usbGetTransmitStatusI(&USB_DRIVER, MOUSE_IN_EPNUM)) {
/* Need to either suspend, or loop and call unlock/lock during
* every iteration - otherwise the system will remain locked,
* no interrupts served, so USB not going through as well.
* Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */
if (osalThreadSuspendTimeoutS(&(&USB_DRIVER)->epc[MOUSE_IN_EPNUM]->in_state->thread, MS2ST(10)==MSG_TIMEOUT)) {
osalSysUnlock();
return;
}
}
usbStartTransmitI(&USB_DRIVER, MOUSE_IN_EPNUM, (uint8_t *)report, sizeof(report_mouse_t));
osalSysUnlock();
}
#else /* MOUSE_ENABLE */
void send_mouse(report_mouse_t *report) {
(void)report;
}
#endif /* MOUSE_ENABLE */
/* ---------------------------------------------------------
* Shared EP functions
* ---------------------------------------------------------
*/
#ifdef SHARED_EP_ENABLE
/* shared IN callback hander */
void shared_in_cb(USBDriver *usbp, usbep_t ep) {
/* STUB */
(void)usbp;
(void)ep;
}
#endif
/* ---------------------------------------------------------
* Extrakey functions
* ---------------------------------------------------------
*/
#ifdef EXTRAKEY_ENABLE
static void send_extra_report(uint8_t report_id, uint16_t data) {
osalSysLock();
if(usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) {
osalSysUnlock();
return;
}
report_extra_t report = {
.report_id = report_id,
.usage = data
};
usbStartTransmitI(&USB_DRIVER, SHARED_IN_EPNUM, (uint8_t *)&report, sizeof(report_extra_t));
osalSysUnlock();
}
void send_system(uint16_t data) {
send_extra_report(REPORT_ID_SYSTEM, data);
}
void send_consumer(uint16_t data) {
send_extra_report(REPORT_ID_CONSUMER, data);
}
#else /* EXTRAKEY_ENABLE */
void send_system(uint16_t data) {
(void)data;
}
void send_consumer(uint16_t data) {
(void)data;
}
#endif /* EXTRAKEY_ENABLE */
/* ---------------------------------------------------------
* Console functions
* ---------------------------------------------------------
*/
#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);
}
// Just a dummy function for now, this could be exposed as a weak function
// Or connected to the actual QMK console
static void console_receive( uint8_t *data, uint8_t length ) {
(void)data;
(void)length;
}
void console_task(void) {
uint8_t buffer[CONSOLE_EPSIZE];
size_t size = 0;
do {
size_t size = chnReadTimeout(&drivers.console_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
if (size > 0) {
console_receive(buffer, size);
}
} while(size > 0);
}
#else /* CONSOLE_ENABLE */
int8_t sendchar(uint8_t c) {
(void)c;
return 0;
}
#endif /* CONSOLE_ENABLE */
void sendchar_pf(void *p, char c) {
(void)p;
sendchar((uint8_t)c);
}
#ifdef RAW_ENABLE
void raw_hid_send( uint8_t *data, uint8_t length ) {
// TODO: implement variable size packet
if ( length != RAW_EPSIZE )
{
return;
}
chnWrite(&drivers.raw_driver.driver, data, length);
}
__attribute__ ((weak))
void raw_hid_receive( uint8_t *data, uint8_t length ) {
// Users should #include "raw_hid.h" in their own code
// and implement this function there. Leave this as weak linkage
// so users can opt to not handle data coming in.
}
void raw_hid_task(void) {
uint8_t buffer[RAW_EPSIZE];
size_t size = 0;
do {
size_t size = chnReadTimeout(&drivers.raw_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
if (size > 0) {
raw_hid_receive(buffer, size);
}
} while(size > 0);
}
#endif
#ifdef MIDI_ENABLE
void send_midi_packet(MIDI_EventPacket_t* event) {
chnWrite(&drivers.midi_driver.driver, (uint8_t*)event, sizeof(MIDI_EventPacket_t));
}
bool recv_midi_packet(MIDI_EventPacket_t* const event) {
size_t size = chnReadTimeout(&drivers.midi_driver.driver, (uint8_t*)event, sizeof(MIDI_EventPacket_t), TIME_IMMEDIATE);
return size == sizeof(MIDI_EventPacket_t);
}
#endif
#ifdef VIRTSER_ENABLE
void virtser_send(const uint8_t byte) {
chnWrite(&drivers.serial_driver.driver, &byte, 1);
}
__attribute__ ((weak))
void virtser_recv(uint8_t c)
{
// Ignore by default
}
void virtser_task(void) {
uint8_t numBytesReceived = 0;
uint8_t buffer[16];
do {
numBytesReceived = chnReadTimeout(&drivers.serial_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
for (int i=0;i<numBytesReceived;i++) {
virtser_recv(buffer[i]);
}
} while (numBytesReceived > 0);
}
#endif