diff --git a/CMakeLists.txt b/CMakeLists.txt index 39e62fc..990a654 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,11 +38,14 @@ target_sources(app PRIVATE src/events/bat_state_event.c src/events/ble_serial_rx_event.c src/events/ble_serial_tx_event.c + src/events/cdc_proto_tx_event.c src/events/encoder_event.c + src/events/function_bitmap_update_event.c src/events/hid_led_event.c src/events/hid_report_sent_event.c src/events/hid_transport_state_event.c src/events/hid_tx_report_event.c + src/events/key_function_event.c src/mode_switch_module.c src/events/keyboard_hid_report_event.c src/events/mode_switch_event.c diff --git a/inc/events/cdc_proto_tx_event.h b/inc/events/cdc_proto_tx_event.h new file mode 100644 index 0000000..55a33b7 --- /dev/null +++ b/inc/events/cdc_proto_tx_event.h @@ -0,0 +1,23 @@ +#ifndef BLINKY_CDC_PROTO_TX_EVENT_H_ +#define BLINKY_CDC_PROTO_TX_EVENT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct cdc_proto_tx_event { + struct app_event_header header; + uint8_t type; + struct event_dyndata dyndata; +}; + +APP_EVENT_TYPE_DYNDATA_DECLARE(cdc_proto_tx_event); + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_CDC_PROTO_TX_EVENT_H_ */ diff --git a/inc/events/function_bitmap_update_event.h b/inc/events/function_bitmap_update_event.h new file mode 100644 index 0000000..9e8277a --- /dev/null +++ b/inc/events/function_bitmap_update_event.h @@ -0,0 +1,24 @@ +#ifndef BLINKY_FUNCTION_BITMAP_UPDATE_EVENT_H_ +#define BLINKY_FUNCTION_BITMAP_UPDATE_EVENT_H_ + +#include +#include + +#include "keyboard_core.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct function_bitmap_update_event { + struct app_event_header header; + uint8_t bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES]; +}; + +APP_EVENT_TYPE_DECLARE(function_bitmap_update_event); + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_FUNCTION_BITMAP_UPDATE_EVENT_H_ */ diff --git a/inc/events/key_function_event.h b/inc/events/key_function_event.h new file mode 100644 index 0000000..6e67678 --- /dev/null +++ b/inc/events/key_function_event.h @@ -0,0 +1,28 @@ +#ifndef BLINKY_KEY_FUNCTION_EVENT_H_ +#define BLINKY_KEY_FUNCTION_EVENT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum key_function_action { + KEY_FUNCTION_ACTION_RELEASE = 0U, + KEY_FUNCTION_ACTION_PRESS = 1U, +}; + +struct key_function_event { + struct app_event_header header; + uint16_t usage; + uint8_t action; +}; + +APP_EVENT_TYPE_DECLARE(key_function_event); + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_KEY_FUNCTION_EVENT_H_ */ diff --git a/inc/keyboard_core.h b/inc/keyboard_core.h index 8dfda05..571932f 100644 --- a/inc/keyboard_core.h +++ b/inc/keyboard_core.h @@ -8,6 +8,8 @@ extern "C" { #endif #define KEYBOARD_BOOT_REPORT_SIZE 8U +#define KEYBOARD_PROTOCOL_USAGE_MAX 0xE7U +#define KEYBOARD_PROTOCOL_BITMAP_BYTES ((KEYBOARD_PROTOCOL_USAGE_MAX + 8U) / 8U) #define KEYBOARD_NKRO_USAGE_MAX 0xDFU #define KEYBOARD_NKRO_BITMAP_BYTES ((KEYBOARD_NKRO_USAGE_MAX + 8U) / 8U) #define KEYBOARD_NKRO_REPORT_SIZE (1U + KEYBOARD_NKRO_BITMAP_BYTES) diff --git a/inc/protocol_module.h b/inc/protocol_module.h index e04ab93..64283f7 100644 --- a/inc/protocol_module.h +++ b/inc/protocol_module.h @@ -10,6 +10,10 @@ extern "C" { #define CDC_PROTO_TYPE_HELLO_REQ 0x01U #define CDC_PROTO_TYPE_HELLO_RSP 0x02U +#define CDC_PROTO_TYPE_BITMAP 0x10U +#define CDC_PROTO_TYPE_FUNCTION_KEY_EVENT 0x20U +#define CDC_PROTO_TYPE_ACK 0x7EU +#define CDC_PROTO_TYPE_ERROR 0x7FU int protocol_module_process_cdc_packet(uint8_t req_type, const uint8_t *req_payload, diff --git a/proto/device_comm.options b/proto/device_comm.options new file mode 100644 index 0000000..82623b7 --- /dev/null +++ b/proto/device_comm.options @@ -0,0 +1 @@ +Bitmap.usage_bitmap max_size:29 diff --git a/proto/device_comm.proto b/proto/device_comm.proto index 63ad07d..7463555 100644 --- a/proto/device_comm.proto +++ b/proto/device_comm.proto @@ -13,9 +13,44 @@ message HelloRsp { uint32 capability_flags = 6; } +message Bitmap { + bytes usage_bitmap = 1; +} + +enum KeyAction { + KEY_ACTION_RELEASE = 0; + KEY_ACTION_PRESS = 1; +} + +message FunctionKeyEvent { + uint32 usage = 1; + KeyAction action = 2; +} + +message Ack { + uint32 acked_type = 1; +} + +enum ErrorCode { + ERROR_CODE_NONE = 0; + ERROR_CODE_UNKNOWN_TYPE = 1; + ERROR_CODE_INVALID_LENGTH = 2; + ERROR_CODE_INVALID_PARAM = 3; + ERROR_CODE_NOT_READY = 4; +} + +message Error { + uint32 error_type = 1; + ErrorCode error_code = 2; +} + message CdcPacketBody { oneof body { HelloReq hello_req = 1; HelloRsp hello_rsp = 2; + Bitmap bitmap = 3; + FunctionKeyEvent function_key_event = 4; + Ack ack = 5; + Error error = 6; } } diff --git a/src/cdc_wrapper_module.c b/src/cdc_wrapper_module.c index af110c1..10f41f0 100644 --- a/src/cdc_wrapper_module.c +++ b/src/cdc_wrapper_module.c @@ -12,6 +12,7 @@ #include +#include "cdc_proto_tx_event.h" #include "protocol_module.h" #include "usb_cdc_rx_event.h" #include "usb_cdc_tx_event.h" @@ -106,7 +107,8 @@ static void process_complete_frame(void) return; } - LOG_INF("CDC HelloRsp encoded len:%u", (uint32_t)rsp_payload_len); + LOG_INF("CDC response type:0x%02x len:%u", + rsp_type, (uint32_t)rsp_payload_len); submit_tx_frame(rsp_type, rsp_payload, rsp_payload_len); } @@ -183,6 +185,23 @@ static bool handle_usb_cdc_rx_event(const struct usb_cdc_rx_event *event) return false; } +static bool handle_cdc_proto_tx_event(const struct cdc_proto_tx_event *event) +{ + if (!running) { + return false; + } + + if (event->dyndata.size > CDC_WRAPPER_MAX_PAYLOAD_LEN) { + LOG_WRN("Drop CDC proto TX len:%u max:%u", + (uint32_t)event->dyndata.size, + (uint32_t)CDC_WRAPPER_MAX_PAYLOAD_LEN); + return false; + } + + submit_tx_frame(event->type, event->dyndata.data, event->dyndata.size); + return false; +} + static int module_init(void) { parser_reset(); @@ -215,6 +234,10 @@ static bool app_event_handler(const struct app_event_header *aeh) return handle_usb_cdc_rx_event(cast_usb_cdc_rx_event(aeh)); } + if (is_cdc_proto_tx_event(aeh)) { + return handle_cdc_proto_tx_event(cast_cdc_proto_tx_event(aeh)); + } + if (is_module_state_event(aeh)) { const struct module_state_event *event = cast_module_state_event(aeh); @@ -269,6 +292,7 @@ static bool app_event_handler(const struct app_event_header *aeh) } APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, cdc_proto_tx_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, usb_cdc_rx_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); diff --git a/src/events/cdc_proto_tx_event.c b/src/events/cdc_proto_tx_event.c new file mode 100644 index 0000000..e1dcffe --- /dev/null +++ b/src/events/cdc_proto_tx_event.c @@ -0,0 +1,29 @@ +#include "cdc_proto_tx_event.h" + +static void log_cdc_proto_tx_event(const struct app_event_header *aeh) +{ + const struct cdc_proto_tx_event *event = cast_cdc_proto_tx_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "type:0x%02x len:%zu", + event->type, event->dyndata.size); +} + +static void profile_cdc_proto_tx_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct cdc_proto_tx_event *event = cast_cdc_proto_tx_event(aeh); + + nrf_profiler_log_encode_uint8(buf, event->type); + nrf_profiler_log_encode_uint8(buf, (uint8_t)event->dyndata.size); +} + +APP_EVENT_INFO_DEFINE(cdc_proto_tx_event, + ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8), + ENCODE("type", "len"), + profile_cdc_proto_tx_event); + +APP_EVENT_TYPE_DEFINE(cdc_proto_tx_event, + log_cdc_proto_tx_event, + &cdc_proto_tx_event_info, + APP_EVENT_FLAGS_CREATE( + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/events/function_bitmap_update_event.c b/src/events/function_bitmap_update_event.c new file mode 100644 index 0000000..22c733e --- /dev/null +++ b/src/events/function_bitmap_update_event.c @@ -0,0 +1,24 @@ +#include "function_bitmap_update_event.h" + +static void log_function_bitmap_update_event(const struct app_event_header *aeh) +{ + APP_EVENT_MANAGER_LOG(aeh, "bitmap updated"); +} + +static void profile_function_bitmap_update_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + ARG_UNUSED(buf); + ARG_UNUSED(aeh); +} + +APP_EVENT_INFO_DEFINE(function_bitmap_update_event, + ENCODE(), + ENCODE(), + profile_function_bitmap_update_event); + +APP_EVENT_TYPE_DEFINE(function_bitmap_update_event, + log_function_bitmap_update_event, + &function_bitmap_update_event_info, + APP_EVENT_FLAGS_CREATE( + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/events/key_function_event.c b/src/events/key_function_event.c new file mode 100644 index 0000000..9efb9c5 --- /dev/null +++ b/src/events/key_function_event.c @@ -0,0 +1,43 @@ +#include "key_function_event.h" + +static const char *action_name(uint8_t action) +{ + switch (action) { + case KEY_FUNCTION_ACTION_RELEASE: + return "release"; + + case KEY_FUNCTION_ACTION_PRESS: + return "press"; + + default: + return "unknown"; + } +} + +static void log_key_function_event(const struct app_event_header *aeh) +{ + const struct key_function_event *event = cast_key_function_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "usage:0x%04x action:%s", + event->usage, action_name(event->action)); +} + +static void profile_key_function_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct key_function_event *event = cast_key_function_event(aeh); + + nrf_profiler_log_encode_uint16(buf, event->usage); + nrf_profiler_log_encode_uint8(buf, event->action); +} + +APP_EVENT_INFO_DEFINE(key_function_event, + ENCODE(NRF_PROFILER_ARG_U16, NRF_PROFILER_ARG_U8), + ENCODE("usage", "action"), + profile_key_function_event); + +APP_EVENT_TYPE_DEFINE(key_function_event, + log_key_function_event, + &key_function_event_info, + APP_EVENT_FLAGS_CREATE( + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/keyboard_core_module.c b/src/keyboard_core_module.c index 1c82589..e6c7abe 100644 --- a/src/keyboard_core_module.c +++ b/src/keyboard_core_module.c @@ -15,8 +15,10 @@ #include #include "encoder_event.h" +#include "function_bitmap_update_event.h" #include "keyboard_core.h" #include "keyboard_hid_report_event.h" +#include "key_function_event.h" #include "mode_switch_event.h" #include "set_protocol_event.h" @@ -39,8 +41,8 @@ struct keymap_entry { }; struct keyboard_state { - uint8_t modifiers; - uint8_t keys_bitmap[KEYBOARD_NKRO_BITMAP_BYTES]; + uint8_t pressed_usage_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES]; + uint8_t function_pressed_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES]; uint32_t consumer_bits; }; @@ -85,6 +87,7 @@ static const uint16_t consumer_usage_map[KEYBOARD_CONSUMER_CTRL_COUNT] = { static struct keyboard_state keyboard_state; static struct keyboard_reports_cache reports_cache; +static uint8_t function_usage_mask[KEYBOARD_PROTOCOL_BITMAP_BYTES]; static enum keyboard_protocol_mode transport_protocol_modes[HID_TRANSPORT_COUNT] = { [HID_TRANSPORT_USB] = KEYBOARD_PROTOCOL_MODE_REPORT, [HID_TRANSPORT_BLE] = KEYBOARD_PROTOCOL_MODE_REPORT, @@ -143,40 +146,35 @@ static const struct keymap_entry *keymap_get(uint16_t key_id) return NULL; } -static bool usage_is_modifier(uint16_t usage_id) +static bool usage_bitmap_test(const uint8_t *bitmap, uint16_t usage_id) { - return IN_RANGE(usage_id, KEYBOARD_USAGE_FIRST_MODIFIER, - KEYBOARD_USAGE_LAST_MODIFIER); -} - -static bool keyboard_key_update(uint16_t usage_id, bool pressed) -{ - if (usage_is_modifier(usage_id)) { - uint8_t new_modifiers = keyboard_state.modifiers; - - WRITE_BIT(new_modifiers, usage_id - KEYBOARD_USAGE_FIRST_MODIFIER, pressed); - if (new_modifiers == keyboard_state.modifiers) { - return false; - } - - keyboard_state.modifiers = new_modifiers; - return true; + if ((bitmap == NULL) || (usage_id > KEYBOARD_PROTOCOL_USAGE_MAX)) { + return false; } - if (usage_id > KEYBOARD_NKRO_USAGE_MAX) { + return (bitmap[usage_id / 8U] & BIT(usage_id % 8U)) != 0U; +} + +static bool usage_bitmap_write(uint8_t *bitmap, uint16_t usage_id, bool pressed) +{ + uint8_t byte_idx; + uint8_t bit_idx; + bool was_pressed; + + if ((bitmap == NULL) || (usage_id > KEYBOARD_PROTOCOL_USAGE_MAX)) { LOG_WRN("Unsupported keyboard usage 0x%04x", usage_id); return false; } - uint8_t byte_idx = usage_id / 8U; - uint8_t bit_idx = usage_id % 8U; - bool was_pressed = (keyboard_state.keys_bitmap[byte_idx] & BIT(bit_idx)) != 0U; + byte_idx = usage_id / 8U; + bit_idx = usage_id % 8U; + was_pressed = (bitmap[byte_idx] & BIT(bit_idx)) != 0U; if (was_pressed == pressed) { return false; } - WRITE_BIT(keyboard_state.keys_bitmap[byte_idx], bit_idx, pressed); + WRITE_BIT(bitmap[byte_idx], bit_idx, pressed); return true; } @@ -202,6 +200,11 @@ static void keyboard_state_clear(void) memset(&keyboard_state, 0, sizeof(keyboard_state)); } +static void function_usage_mask_clear(void) +{ + memset(function_usage_mask, 0, sizeof(function_usage_mask)); +} + static void reports_cache_invalidate(void) { reports_cache.boot_valid = false; @@ -209,19 +212,29 @@ static void reports_cache_invalidate(void) reports_cache.consumer_valid = false; } +static void build_effective_hid_bitmap(uint8_t bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES]) +{ + for (size_t i = 0; i < KEYBOARD_PROTOCOL_BITMAP_BYTES; i++) { + bitmap[i] = keyboard_state.pressed_usage_bitmap[i] & + (uint8_t)~keyboard_state.function_pressed_bitmap[i]; + } +} + static void build_boot_report(uint8_t report[KEYBOARD_BOOT_REPORT_SIZE]) { + uint8_t effective_hid_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES]; size_t key_count = 0; + build_effective_hid_bitmap(effective_hid_bitmap); memset(report, 0, KEYBOARD_BOOT_REPORT_SIZE); - report[0] = keyboard_state.modifiers; + report[0] = effective_hid_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES - 1U]; report[1] = KEYBOARD_BOOT_RESERVED_BYTE; for (uint16_t usage_id = 0; usage_id <= KEYBOARD_NKRO_USAGE_MAX; usage_id++) { uint8_t byte_idx = usage_id / 8U; uint8_t bit_idx = usage_id % 8U; - if ((keyboard_state.keys_bitmap[byte_idx] & BIT(bit_idx)) == 0U) { + if ((effective_hid_bitmap[byte_idx] & BIT(bit_idx)) == 0U) { continue; } @@ -238,8 +251,11 @@ static void build_boot_report(uint8_t report[KEYBOARD_BOOT_REPORT_SIZE]) static void build_nkro_report(uint8_t report[KEYBOARD_NKRO_REPORT_SIZE]) { - report[0] = keyboard_state.modifiers; - memcpy(&report[1], keyboard_state.keys_bitmap, KEYBOARD_NKRO_BITMAP_BYTES); + uint8_t effective_hid_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES]; + + build_effective_hid_bitmap(effective_hid_bitmap); + report[0] = effective_hid_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES - 1U]; + memcpy(&report[1], effective_hid_bitmap, KEYBOARD_NKRO_BITMAP_BYTES); } static uint16_t active_consumer_usage_get(void) @@ -275,6 +291,15 @@ static void submit_keyboard_report_event(enum keyboard_report_type report_type, APP_EVENT_SUBMIT(event); } +static void submit_key_function_event(uint16_t usage_id, uint8_t action) +{ + struct key_function_event *event = new_key_function_event(); + + event->usage = usage_id; + event->action = action; + APP_EVENT_SUBMIT(event); +} + static void submit_consumer_fifo_frame(uint16_t usage_id) { uint8_t report_buf[KEYBOARD_CONSUMER_REPORT_SIZE]; @@ -389,6 +414,17 @@ static void emit_all_reports(bool force) } } +static void emit_function_release_events(void) +{ + for (uint16_t usage_id = 0; usage_id <= KEYBOARD_PROTOCOL_USAGE_MAX; usage_id++) { + if (!usage_bitmap_test(keyboard_state.function_pressed_bitmap, usage_id)) { + continue; + } + + submit_key_function_event(usage_id, KEY_FUNCTION_ACTION_RELEASE); + } +} + static void emit_release_reports(enum mode_switch_mode mode) { struct keyboard_hid_report_event *event; @@ -421,6 +457,7 @@ static int module_init(void) { keyboard_state_clear(); reports_cache_invalidate(); + function_usage_mask_clear(); mode_valid = false; transport_protocol_modes[HID_TRANSPORT_USB] = KEYBOARD_PROTOCOL_MODE_REPORT; @@ -449,6 +486,7 @@ static void module_pause(void) if (mode_valid) { emit_release_reports(current_mode); } + emit_function_release_events(); keyboard_state_clear(); reports_cache_invalidate(); @@ -472,8 +510,33 @@ static bool handle_button_event(const struct button_event *event) } if (entry->usage_type == KEY_USAGE_TYPE_KEYBOARD) { - changed = keyboard_key_update(entry->usage_id, event->pressed); - if (changed) { + bool routed_to_function; + + changed = usage_bitmap_write(keyboard_state.pressed_usage_bitmap, + entry->usage_id, event->pressed); + if (!changed) { + return false; + } + + if (event->pressed) { + routed_to_function = + usage_bitmap_test(function_usage_mask, entry->usage_id); + (void)usage_bitmap_write(keyboard_state.function_pressed_bitmap, + entry->usage_id, routed_to_function); + } else { + routed_to_function = + usage_bitmap_test(keyboard_state.function_pressed_bitmap, + entry->usage_id); + (void)usage_bitmap_write(keyboard_state.function_pressed_bitmap, + entry->usage_id, false); + } + + if (routed_to_function) { + submit_key_function_event(entry->usage_id, + event->pressed ? + KEY_FUNCTION_ACTION_PRESS : + KEY_FUNCTION_ACTION_RELEASE); + } else { emit_keys_report(false); } } else { @@ -498,6 +561,7 @@ static bool handle_mode_switch_event(const struct mode_switch_event *event) mode_changed = mode_valid && (current_mode != event->mode); if (mode_changed) { emit_release_reports(current_mode); + emit_function_release_events(); keyboard_state_clear(); reports_cache_invalidate(); } @@ -526,6 +590,13 @@ static bool handle_encoder_event(const struct encoder_event *event) return false; } +static bool handle_function_bitmap_update_event( + const struct function_bitmap_update_event *event) +{ + memcpy(function_usage_mask, event->bitmap, sizeof(function_usage_mask)); + return false; +} + static bool app_event_handler(const struct app_event_header *aeh) { if (is_button_event(aeh)) { @@ -536,6 +607,11 @@ static bool app_event_handler(const struct app_event_header *aeh) return handle_encoder_event(cast_encoder_event(aeh)); } + if (is_function_bitmap_update_event(aeh)) { + return handle_function_bitmap_update_event( + cast_function_bitmap_update_event(aeh)); + } + if (is_set_protocol_event(aeh)) { const struct set_protocol_event *event = cast_set_protocol_event(aeh); enum hid_transport active_transport; @@ -623,6 +699,7 @@ static bool app_event_handler(const struct app_event_header *aeh) APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE(MODULE, button_event); APP_EVENT_SUBSCRIBE(MODULE, encoder_event); +APP_EVENT_SUBSCRIBE(MODULE, function_bitmap_update_event); APP_EVENT_SUBSCRIBE(MODULE, set_protocol_event); APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); diff --git a/src/protocol_module.c b/src/protocol_module.c index 6cd1d2c..29823c8 100644 --- a/src/protocol_module.c +++ b/src/protocol_module.c @@ -2,24 +2,42 @@ #include #include #include +#include + +#include + +#define MODULE protocol_module +#include +#include #include +#include #include #include #include +#include "cdc_proto_tx_event.h" +#include "function_bitmap_update_event.h" +#include "key_function_event.h" #include "protocol_module.h" +#include "usb_device_state_event.h" -LOG_MODULE_REGISTER(protocol_module, LOG_LEVEL_INF); +LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); #define PROTOCOL_VERSION 1U #define PROTOCOL_VENDOR_ID 0x1915U #define PROTOCOL_PRODUCT_ID 0x52F0U #define PROTOCOL_FIRMWARE_MAJOR 0U #define PROTOCOL_FIRMWARE_MINOR 0U -#define PROTOCOL_CAPABILITY_FLAGS 0U +#define PROTOCOL_CAPABILITY_FLAGS BIT(0) + +static bool initialized; +static bool running; +static bool keyboard_core_ready; +static bool usb_active; +static bool hello_done; static bool type_matches_body(uint8_t type, const CdcPacketBody *body) { @@ -28,6 +46,14 @@ static bool type_matches_body(uint8_t type, const CdcPacketBody *body) return body->which_body == CdcPacketBody_hello_req_tag; case CDC_PROTO_TYPE_HELLO_RSP: return body->which_body == CdcPacketBody_hello_rsp_tag; + case CDC_PROTO_TYPE_BITMAP: + return body->which_body == CdcPacketBody_bitmap_tag; + case CDC_PROTO_TYPE_FUNCTION_KEY_EVENT: + return body->which_body == CdcPacketBody_function_key_event_tag; + case CDC_PROTO_TYPE_ACK: + return body->which_body == CdcPacketBody_ack_tag; + case CDC_PROTO_TYPE_ERROR: + return body->which_body == CdcPacketBody_error_tag; default: return false; } @@ -53,15 +79,29 @@ static int decode_body(const uint8_t *payload, size_t payload_len, return 0; } +static int encode_body(const CdcPacketBody *body, uint8_t *payload, + size_t payload_buf_size, size_t *payload_len) +{ + pb_ostream_t stream; + + if ((body == NULL) || (payload == NULL) || (payload_len == NULL)) { + return -EINVAL; + } + + stream = pb_ostream_from_buffer(payload, payload_buf_size); + if (!pb_encode(&stream, CdcPacketBody_fields, body)) { + LOG_WRN("pb_encode failed: %s", PB_GET_ERROR(&stream)); + return -EIO; + } + + *payload_len = stream.bytes_written; + return 0; +} + static int encode_hello_rsp(uint8_t *rsp_payload, size_t rsp_payload_buf_size, size_t *rsp_payload_len) { CdcPacketBody body = CdcPacketBody_init_zero; - pb_ostream_t stream; - - if ((rsp_payload == NULL) || (rsp_payload_len == NULL)) { - return -EINVAL; - } body.which_body = CdcPacketBody_hello_rsp_tag; body.body.hello_rsp.protocol_version = PROTOCOL_VERSION; @@ -71,17 +111,145 @@ static int encode_hello_rsp(uint8_t *rsp_payload, size_t rsp_payload_buf_size, body.body.hello_rsp.firmware_minor = PROTOCOL_FIRMWARE_MINOR; body.body.hello_rsp.capability_flags = PROTOCOL_CAPABILITY_FLAGS; - stream = pb_ostream_from_buffer(rsp_payload, rsp_payload_buf_size); + return encode_body(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len); +} - if (!pb_encode(&stream, CdcPacketBody_fields, &body)) { - LOG_WRN("pb_encode failed: %s", PB_GET_ERROR(&stream)); - return -EIO; +static int encode_ack(uint8_t acked_type, uint8_t *rsp_payload, + size_t rsp_payload_buf_size, size_t *rsp_payload_len) +{ + CdcPacketBody body = CdcPacketBody_init_zero; + + body.which_body = CdcPacketBody_ack_tag; + body.body.ack.acked_type = acked_type; + + return encode_body(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len); +} + +static int encode_error(uint8_t error_type, ErrorCode error_code, + uint8_t *rsp_payload, size_t rsp_payload_buf_size, + size_t *rsp_payload_len) +{ + CdcPacketBody body = CdcPacketBody_init_zero; + + body.which_body = CdcPacketBody_error_tag; + body.body.error.error_type = error_type; + body.body.error.error_code = error_code; + + return encode_body(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len); +} + +static int encode_function_key_event(uint16_t usage, uint8_t action, + uint8_t *payload, + size_t payload_buf_size, + size_t *payload_len) +{ + CdcPacketBody body = CdcPacketBody_init_zero; + + body.which_body = CdcPacketBody_function_key_event_tag; + body.body.function_key_event.usage = usage; + body.body.function_key_event.action = (KeyAction)action; + + return encode_body(&body, payload, payload_buf_size, payload_len); +} + +static int submit_cdc_proto_tx_event(uint8_t type, const uint8_t *payload, + size_t payload_len) +{ + struct cdc_proto_tx_event *event; + + if ((payload == NULL) && (payload_len > 0U)) { + return -EINVAL; } - *rsp_payload_len = stream.bytes_written; + event = new_cdc_proto_tx_event(payload_len); + event->type = type; + if (payload_len > 0U) { + memcpy(event->dyndata.data, payload, payload_len); + } + + APP_EVENT_SUBMIT(event); return 0; } +static int submit_function_bitmap_update_event(const Bitmap *bitmap) +{ + struct function_bitmap_update_event *event; + + if ((bitmap == NULL) || + (bitmap->usage_bitmap.size != KEYBOARD_PROTOCOL_BITMAP_BYTES)) { + return -EINVAL; + } + + event = new_function_bitmap_update_event(); + memcpy(event->bitmap, bitmap->usage_bitmap.bytes, + KEYBOARD_PROTOCOL_BITMAP_BYTES); + APP_EVENT_SUBMIT(event); + + return 0; +} + +static int encode_error_response(uint8_t req_type, ErrorCode error_code, + uint8_t *rsp_type, uint8_t *rsp_payload, + size_t rsp_payload_buf_size, + size_t *rsp_payload_len) +{ + int err; + + err = encode_error(req_type, error_code, rsp_payload, + rsp_payload_buf_size, rsp_payload_len); + if (err) { + return err; + } + + *rsp_type = CDC_PROTO_TYPE_ERROR; + return 0; +} + +static int encode_ack_response(uint8_t acked_type, uint8_t *rsp_type, + uint8_t *rsp_payload, + size_t rsp_payload_buf_size, + size_t *rsp_payload_len) +{ + int err; + + err = encode_ack(acked_type, rsp_payload, rsp_payload_buf_size, + rsp_payload_len); + if (err) { + return err; + } + + *rsp_type = CDC_PROTO_TYPE_ACK; + return 0; +} + +static int module_init(void) +{ + keyboard_core_ready = false; + usb_active = false; + hello_done = false; + return 0; +} + +static int module_start(void) +{ + if (running) { + return 0; + } + + running = true; + return 0; +} + +static void module_pause(void) +{ + if (!running) { + return; + } + + hello_done = false; + running = false; +} + int protocol_module_process_cdc_packet(uint8_t req_type, const uint8_t *req_payload, size_t req_payload_len, @@ -97,15 +265,23 @@ int protocol_module_process_cdc_packet(uint8_t req_type, return -EINVAL; } + if (!running) { + return -EAGAIN; + } + err = decode_body(req_payload, req_payload_len, &body); if (err) { - return err; + return encode_error_response(req_type, ErrorCode_ERROR_CODE_INVALID_LENGTH, + rsp_type, rsp_payload, + rsp_payload_buf_size, rsp_payload_len); } if (!type_matches_body(req_type, &body)) { LOG_WRN("CDC type/body mismatch type:0x%02x body_case:%d", req_type, body.which_body); - return -EBADMSG; + return encode_error_response(req_type, ErrorCode_ERROR_CODE_INVALID_PARAM, + rsp_type, rsp_payload, + rsp_payload_buf_size, rsp_payload_len); } switch (req_type) { @@ -118,6 +294,7 @@ int protocol_module_process_cdc_packet(uint8_t req_type, body.body.hello_req.protocol_version); } + hello_done = true; err = encode_hello_rsp(rsp_payload, rsp_payload_buf_size, rsp_payload_len); if (err) { return err; @@ -126,8 +303,158 @@ int protocol_module_process_cdc_packet(uint8_t req_type, *rsp_type = CDC_PROTO_TYPE_HELLO_RSP; return 0; + case CDC_PROTO_TYPE_BITMAP: + if (!hello_done) { + return encode_error_response(req_type, ErrorCode_ERROR_CODE_NOT_READY, + rsp_type, rsp_payload, + rsp_payload_buf_size, + rsp_payload_len); + } + + if (!keyboard_core_ready) { + return encode_error_response(req_type, ErrorCode_ERROR_CODE_NOT_READY, + rsp_type, rsp_payload, + rsp_payload_buf_size, + rsp_payload_len); + } + + if (body.body.bitmap.usage_bitmap.size != KEYBOARD_PROTOCOL_BITMAP_BYTES) { + LOG_WRN("Bitmap len:%u expected:%u", + (unsigned int)body.body.bitmap.usage_bitmap.size, + KEYBOARD_PROTOCOL_BITMAP_BYTES); + return encode_error_response(req_type, ErrorCode_ERROR_CODE_INVALID_LENGTH, + rsp_type, rsp_payload, + rsp_payload_buf_size, + rsp_payload_len); + } + + err = submit_function_bitmap_update_event(&body.body.bitmap); + if (err) { + return encode_error_response(req_type, ErrorCode_ERROR_CODE_INVALID_PARAM, + rsp_type, rsp_payload, + rsp_payload_buf_size, + rsp_payload_len); + } + + return encode_ack_response(req_type, rsp_type, rsp_payload, + rsp_payload_buf_size, rsp_payload_len); + default: LOG_WRN("Unsupported CDC protocol type:0x%02x", req_type); - return -ENOTSUP; + return encode_error_response(req_type, ErrorCode_ERROR_CODE_UNKNOWN_TYPE, + rsp_type, rsp_payload, + rsp_payload_buf_size, rsp_payload_len); } } + +static bool handle_key_function_event(const struct key_function_event *event) +{ + uint8_t payload[64]; + size_t payload_len; + int err; + + if (!running || !usb_active || !hello_done) { + return false; + } + + err = encode_function_key_event(event->usage, event->action, payload, + sizeof(payload), &payload_len); + if (err) { + LOG_WRN("FunctionKeyEvent encode failed (%d)", err); + return false; + } + + err = submit_cdc_proto_tx_event(CDC_PROTO_TYPE_FUNCTION_KEY_EVENT, + payload, payload_len); + if (err) { + LOG_WRN("FunctionKeyEvent submit failed (%d)", err); + } + + return false; +} + +static bool handle_usb_device_state_event(const struct usb_device_state_event *event) +{ + usb_active = (event->state == USB_DEVICE_STATE_ACTIVE); + if (!usb_active) { + hello_done = false; + } + + return false; +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_key_function_event(aeh)) { + return handle_key_function_event(cast_key_function_event(aeh)); + } + + if (is_usb_device_state_event(aeh)) { + return handle_usb_device_state_event(cast_usb_device_state_event(aeh)); + } + + if (is_module_state_event(aeh)) { + const struct module_state_event *event = cast_module_state_event(aeh); + int err; + + if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { + if (!initialized) { + err = module_init(); + if (err) { + module_set_state(MODULE_STATE_ERROR); + return false; + } + + initialized = true; + } + + err = module_start(); + if (err) { + module_set_state(MODULE_STATE_ERROR); + } else { + module_set_state(MODULE_STATE_READY); + } + + return false; + } + + if (check_state(event, MODULE_ID(keyboard_core_module), MODULE_STATE_READY)) { + keyboard_core_ready = true; + return false; + } + + return false; + } + + if (is_power_down_event(aeh)) { + if (initialized) { + module_pause(); + module_set_state(MODULE_STATE_STANDBY); + } + + return false; + } + + if (is_wake_up_event(aeh)) { + if (initialized) { + int err = module_start(); + + if (err) { + module_set_state(MODULE_STATE_ERROR); + } else { + module_set_state(MODULE_STATE_READY); + } + } + + return false; + } + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, key_function_event); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE(MODULE, usb_device_state_event); +APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); +APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);