Skip to content

Commit

Permalink
Handle idle reports and get reports requests async
Browse files Browse the repository at this point in the history
The USB idle rate handling for non-nkro keyboard endpoint was
implemented via a virtual timer that fired asynchronously at the host
requested idle rate. Sending was done in the callback of the virtual
timer in an ISR context. This is no longer an option as the asynchronous
endpoints take ownership of all used USB endpoints and thus assume that
all reports will be send via it's API.

This commit introduces a report storage that holds the last successfully
send USB report for all USB IN **HID** endpoints. This report storage is
modelled in a OOP fashion as the shared endpoint with its multiple
report types complicates the handling.

With this report storage we can not only fullfill the idle rate handling
but also the mandatory get report requests for all USB HID IN endpoints
in a generic fashion.

Signed-off-by: Stefan Kerkmann <[email protected]>
  • Loading branch information
KarlK90 committed Jan 14, 2024
1 parent 79ff6f7 commit 639ac02
Show file tree
Hide file tree
Showing 12 changed files with 571 additions and 195 deletions.
1 change: 1 addition & 0 deletions tmk_core/protocol/chibios/chibios.c
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,5 @@ void protocol_post_task(void) {
#ifdef RAW_ENABLE
raw_hid_task();
#endif
usb_idle_task();
}
1 change: 1 addition & 0 deletions tmk_core/protocol/chibios/chibios.mk
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ SRC += $(CHIBIOS_DIR)/chibios.c
SRC += usb_descriptor.c
SRC += $(CHIBIOS_DIR)/usb_driver.c
SRC += $(CHIBIOS_DIR)/usb_endpoints.c
SRC += $(CHIBIOS_DIR)/usb_report_handling.c
SRC += $(CHIBIOS_DIR)/usb_util.c
SRC += $(LIBSRC)

Expand Down
26 changes: 25 additions & 1 deletion tmk_core/protocol/chibios/usb_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint) {

bqSuspendI(&endpoint->obqueue);
obqResetI(&endpoint->obqueue);
if (endpoint->report_storage != NULL) {
endpoint->report_storage->reset_report(endpoint->report_storage->reports);
}
osalOsRescheduleS();
osalSysUnlock();
}
Expand All @@ -143,6 +146,10 @@ void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint) {
void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint) {
bqSuspendI(&endpoint->obqueue);
obqResetI(&endpoint->obqueue);

if (endpoint->report_storage != NULL) {
endpoint->report_storage->reset_report(endpoint->report_storage->reports);
}
}

void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint) {
Expand Down Expand Up @@ -179,6 +186,7 @@ void usb_endpoint_out_configure_cb(usb_endpoint_out_t *endpoint) {
void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep) {
usb_endpoint_in_t *endpoint = usbp->in_params[ep - 1U];
size_t n;
uint8_t * buffer;

if (endpoint == NULL) {
return;
Expand All @@ -191,11 +199,17 @@ void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep) {

/* Freeing the buffer just transmitted, if it was not a zero size packet.*/
if (!obqIsEmptyI(&endpoint->obqueue) && usbp->epc[ep]->in_state->txsize > 0U) {
/* Store the last send report in the endpoint to be retrieved by a
* GET_REPORT request or IDLE report handling. */
if (endpoint->report_storage != NULL) {
buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
endpoint->report_storage->set_report(endpoint->report_storage->reports, buffer, n);
}
obqReleaseEmptyBufferI(&endpoint->obqueue);
}

/* Checking if there is a buffer ready for transmission.*/
uint8_t *buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
buffer = obqGetFullBufferI(&endpoint->obqueue, &n);

if (buffer != NULL) {
/* The endpoint cannot be busy, we are in the context of the callback,
Expand Down Expand Up @@ -296,6 +310,16 @@ void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded) {
obqFlush(obqp);
}

bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint) {
osalDbgCheck(endpoint != NULL);

osalSysLock();
bool inactive = obqIsEmptyI(&endpoint->obqueue) && !usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep);
osalSysUnlock();

return inactive;
}

bool usb_endpoint_out_receive(usb_endpoint_out_t *endpoint, uint8_t *data, size_t size, sysinterval_t timeout) {
osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U));

Expand Down
94 changes: 49 additions & 45 deletions tmk_core/protocol/chibios/usb_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#include <hal_buffers.h>
#include "usb_descriptor.h"
#include "chibios_config.h"
#include "usb_report_handling.h"
#include "string.h"
#include "timer.h"

#if HAL_USE_USB == FALSE
# error "The USB Driver requires HAL_USE_USB"
Expand All @@ -30,28 +33,28 @@
* Given `USBv1/hal_usb_lld.h` marks the field as "not currently used" this code file
* makes the assumption this is safe to avoid littering with preprocessor directives.
*/
#define QMK_USB_ENDPOINT_IN(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb) \
{ \
.usb_requests_cb = _usb_requests_cb, \
.ep_config = \
{ \
mode, /* EP Mode */ \
NULL, /* SETUP packet notification callback */ \
usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
NULL, /* OUT notification callback */ \
ep_size, /* IN maximum packet size */ \
0, /* OUT maximum packet size */ \
NULL, /* IN Endpoint state */ \
NULL, /* OUT endpoint state */ \
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.ep = ep_num, \
.buffer_capacity = _buffer_capacity, \
.buffer_size = ep_size, \
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
} \
#define QMK_USB_ENDPOINT_IN(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \
{ \
.usb_requests_cb = _usb_requests_cb, .report_storage = _report_storage, \
.ep_config = \
{ \
mode, /* EP Mode */ \
NULL, /* SETUP packet notification callback */ \
usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
NULL, /* OUT notification callback */ \
ep_size, /* IN maximum packet size */ \
0, /* OUT maximum packet size */ \
NULL, /* IN Endpoint state */ \
NULL, /* OUT endpoint state */ \
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.ep = ep_num, \
.buffer_capacity = _buffer_capacity, \
.buffer_size = ep_size, \
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
} \
}

#if !defined(USB_ENDPOINTS_ARE_REORDERABLE)
Expand Down Expand Up @@ -81,28 +84,28 @@

#else

# define QMK_USB_ENDPOINT_IN_SHARED(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb) \
{ \
.usb_requests_cb = _usb_requests_cb, .is_shared = true, \
.ep_config = \
{ \
mode, /* EP Mode */ \
NULL, /* SETUP packet notification callback */ \
usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
usb_endpoint_out_rx_complete_cb, /* OUT notification callback */ \
ep_size, /* IN maximum packet size */ \
ep_size, /* OUT maximum packet size */ \
NULL, /* IN Endpoint state */ \
NULL, /* OUT endpoint state */ \
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.ep = ep_num, \
.buffer_capacity = _buffer_capacity, \
.buffer_size = ep_size, \
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
} \
# define QMK_USB_ENDPOINT_IN_SHARED(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \
{ \
.usb_requests_cb = _usb_requests_cb, .is_shared = true, .report_storage = _report_storage, \
.ep_config = \
{ \
mode, /* EP Mode */ \
NULL, /* SETUP packet notification callback */ \
usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
usb_endpoint_out_rx_complete_cb, /* OUT notification callback */ \
ep_size, /* IN maximum packet size */ \
ep_size, /* OUT maximum packet size */ \
NULL, /* IN Endpoint state */ \
NULL, /* OUT endpoint state */ \
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.ep = ep_num, \
.buffer_capacity = _buffer_capacity, \
.buffer_size = ep_size, \
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
} \
}

/* The current assumption is that there are no standalone OUT endpoints, so the
Expand All @@ -111,7 +114,7 @@
{ \
.ep_config = \
{ \
0 /* Already defined */ \
0 /* Already defined in the IN endpoint */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
Expand Down Expand Up @@ -162,6 +165,7 @@ typedef struct {
usb_endpoint_config_t config;
usbreqhandler_t usb_requests_cb;
bool timed_out;
usb_report_storage_t *report_storage;
} usb_endpoint_in_t;

typedef struct {
Expand Down
Loading

0 comments on commit 639ac02

Please sign in to comment.