diff --git a/CMakeLists.txt b/CMakeLists.txt index 17fddfe..411936a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,9 +55,11 @@ target_sources(app PRIVATE src/events/keyboard_hid_report_event.c src/events/mode_switch_event.c src/events/proto_rx_event.c + src/events/proto_transport_state_event.c src/events/proto_tx_event.c src/events/set_protocol_event.c src/events/theme_rgb_update_event.c src/events/time_sync_event.c + src/events/usb_control_event.c src/events/usb_state_event.c ) diff --git a/inc/events/proto_common.h b/inc/events/proto_common.h index 479e851..f04c398 100644 --- a/inc/events/proto_common.h +++ b/inc/events/proto_common.h @@ -11,6 +11,11 @@ enum proto_transport { PROTO_TRANSPORT_COUNT, }; +enum proto_transport_link_state { + PROTO_TRANSPORT_LINK_DOWN = 0, + PROTO_TRANSPORT_LINK_READY, +}; + #ifdef __cplusplus } #endif diff --git a/inc/events/proto_transport_state_event.h b/inc/events/proto_transport_state_event.h new file mode 100644 index 0000000..587c0fd --- /dev/null +++ b/inc/events/proto_transport_state_event.h @@ -0,0 +1,36 @@ +#ifndef BLINKY_PROTO_TRANSPORT_STATE_EVENT_H_ +#define BLINKY_PROTO_TRANSPORT_STATE_EVENT_H_ + +#include +#include + +#include "proto_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct proto_transport_state_event { + struct app_event_header header; + enum proto_transport transport; + enum proto_transport_link_state state; +}; + +APP_EVENT_TYPE_DECLARE(proto_transport_state_event); + +static inline void submit_proto_transport_state_event( + enum proto_transport transport, enum proto_transport_link_state state) +{ + struct proto_transport_state_event *event = + new_proto_transport_state_event(); + + event->transport = transport; + event->state = state; + APP_EVENT_SUBMIT(event); +} + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_PROTO_TRANSPORT_STATE_EVENT_H_ */ diff --git a/inc/events/usb_control_event.h b/inc/events/usb_control_event.h new file mode 100644 index 0000000..2bd94ed --- /dev/null +++ b/inc/events/usb_control_event.h @@ -0,0 +1,71 @@ +#ifndef BLINKY_USB_CONTROL_EVENT_H_ +#define BLINKY_USB_CONTROL_EVENT_H_ + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum usb_control_event_type { + USB_CONTROL_EVENT_CDC_LINE_STATE = 0, + USB_CONTROL_EVENT_CDC_LINE_CODING, +}; + +struct usb_control_event { + struct app_event_header header; + enum usb_control_event_type type; + const struct device *dev; + union { + struct { + bool dtr; + } cdc_line_state; + struct { + uint32_t baudrate; + uint8_t data_bits; + uint8_t stop_bits; + uint8_t parity; + uint8_t flow_ctrl; + } cdc_line_coding; + } data; +}; + +APP_EVENT_TYPE_DECLARE(usb_control_event); + +static inline void submit_usb_control_cdc_line_state_event( + const struct device *dev, bool dtr) +{ + struct usb_control_event *event = new_usb_control_event(); + + event->type = USB_CONTROL_EVENT_CDC_LINE_STATE; + event->dev = dev; + event->data.cdc_line_state.dtr = dtr; + APP_EVENT_SUBMIT(event); +} + +static inline void submit_usb_control_cdc_line_coding_event( + const struct device *dev, uint32_t baudrate, uint8_t data_bits, + uint8_t stop_bits, uint8_t parity, uint8_t flow_ctrl) +{ + struct usb_control_event *event = new_usb_control_event(); + + event->type = USB_CONTROL_EVENT_CDC_LINE_CODING; + event->dev = dev; + event->data.cdc_line_coding.baudrate = baudrate; + event->data.cdc_line_coding.data_bits = data_bits; + event->data.cdc_line_coding.stop_bits = stop_bits; + event->data.cdc_line_coding.parity = parity; + event->data.cdc_line_coding.flow_ctrl = flow_ctrl; + APP_EVENT_SUBMIT(event); +} + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_USB_CONTROL_EVENT_H_ */ diff --git a/inc/protocol_module.h b/inc/protocol_module.h index 437acb8..b3134c6 100644 --- a/inc/protocol_module.h +++ b/inc/protocol_module.h @@ -18,8 +18,6 @@ int protocol_module_process_message(enum proto_transport transport, size_t rsp_payload_buf_size, size_t *rsp_payload_len); -void protocol_module_reset_transport_state(enum proto_transport transport); - #ifdef __cplusplus } #endif diff --git a/src/ble_nus_module.c b/src/ble_nus_module.c index 7972dfd..9256c77 100644 --- a/src/ble_nus_module.c +++ b/src/ble_nus_module.c @@ -14,30 +14,122 @@ #include #include "proto_rx_event.h" +#include "proto_transport_state_event.h" #include "proto_tx_event.h" -#include "protocol_module.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); -static struct bt_conn *active_conn; -static bool initialized; -static bool running; -static bool ble_ready; -static bool tx_notify_enabled; +enum ble_nus_business_state { + BLE_NUS_STACK_OFFLINE = 0, + BLE_NUS_IDLE, + BLE_NUS_WAIT_NOTIFY, + BLE_NUS_SESSION_READY, +}; -static void notif_enabled(bool enabled, void *ctx) +struct ble_nus_ctx { + enum module_state lifecycle; + enum ble_nus_business_state business; + struct bt_conn *active_conn; +}; + +static struct ble_nus_ctx ctx = { + .lifecycle = MODULE_STATE_OFF, + .business = BLE_NUS_STACK_OFFLINE, + .active_conn = NULL, +}; + +static bool lifecycle_is_ready(void) { - ARG_UNUSED(ctx); - - tx_notify_enabled = enabled; - LOG_INF("BLE NUS TX notify %s", enabled ? "enabled" : "disabled"); + return ctx.lifecycle == MODULE_STATE_READY; } -static void received(struct bt_conn *conn, const void *data, uint16_t len, void *ctx) +static enum proto_transport_link_state transport_link_state_get(void) { - ARG_UNUSED(ctx); + return (lifecycle_is_ready() && + (ctx.business == BLE_NUS_SESSION_READY)) ? + PROTO_TRANSPORT_LINK_READY : + PROTO_TRANSPORT_LINK_DOWN; +} - if (!running || !ble_ready || (conn != active_conn)) { +static void state_reconcile(enum module_state old_lifecycle, + enum ble_nus_business_state old_business) +{ + enum proto_transport_link_state old_link = + ((old_lifecycle == MODULE_STATE_READY) && + (old_business == BLE_NUS_SESSION_READY)) ? + PROTO_TRANSPORT_LINK_READY : + PROTO_TRANSPORT_LINK_DOWN; + enum proto_transport_link_state new_link = transport_link_state_get(); + + if (old_link != new_link) { + submit_proto_transport_state_event(PROTO_TRANSPORT_BLE_NUS, + new_link); + } +} + +static void lifecycle_set(enum module_state new_state) +{ + enum module_state old_lifecycle = ctx.lifecycle; + enum ble_nus_business_state old_business = ctx.business; + + if (ctx.lifecycle == new_state) { + return; + } + + ctx.lifecycle = new_state; + state_reconcile(old_lifecycle, old_business); + module_set_state(new_state); +} + +static void business_state_set(enum ble_nus_business_state new_state) +{ + enum module_state old_lifecycle = ctx.lifecycle; + enum ble_nus_business_state old_business = ctx.business; + + if (ctx.business == new_state) { + return; + } + + ctx.business = new_state; + state_reconcile(old_lifecycle, old_business); +} + +static void business_state_set_stack_ready(void) +{ + if (ctx.business == BLE_NUS_STACK_OFFLINE) { + business_state_set(BLE_NUS_IDLE); + } +} + +static void business_state_set_from_notify(bool enabled) +{ + if (ctx.business == BLE_NUS_STACK_OFFLINE) { + return; + } + + if (ctx.active_conn == NULL) { + return; + } + + business_state_set(enabled ? BLE_NUS_SESSION_READY : + BLE_NUS_WAIT_NOTIFY); +} + +static void notif_enabled(bool enabled, void *ctx_ptr) +{ + ARG_UNUSED(ctx_ptr); + + LOG_INF("BLE NUS TX notify %s", enabled ? "enabled" : "disabled"); + business_state_set_from_notify(enabled); +} + +static void received(struct bt_conn *conn, const void *data, uint16_t len, + void *ctx_ptr) +{ + ARG_UNUSED(ctx_ptr); + + if (!lifecycle_is_ready() || (ctx.business == BLE_NUS_STACK_OFFLINE) || + (conn != ctx.active_conn)) { return; } @@ -51,9 +143,11 @@ static struct bt_nus_cb nus_listener = { static void reset_connection_state(void) { - active_conn = NULL; - tx_notify_enabled = false; - protocol_module_reset_transport_state(PROTO_TRANSPORT_BLE_NUS); + ctx.active_conn = NULL; + + if (ctx.business != BLE_NUS_STACK_OFFLINE) { + business_state_set(BLE_NUS_IDLE); + } } static int module_init(void) @@ -66,45 +160,41 @@ static int module_init(void) return err; } - reset_connection_state(); + ctx = (struct ble_nus_ctx) { + .lifecycle = MODULE_STATE_OFF, + .business = BLE_NUS_STACK_OFFLINE, + .active_conn = NULL, + }; + return 0; } -static int module_start(void) +static void module_start(void) { - if (running) { - return 0; - } - - running = true; - return 0; + lifecycle_set(MODULE_STATE_READY); } static void module_pause(void) { - if (!running) { - return; - } - - running = false; - tx_notify_enabled = false; + lifecycle_set(MODULE_STATE_STANDBY); } static bool handle_ble_peer_event(const struct ble_peer_event *event) { switch (event->state) { case PEER_STATE_CONNECTED: - if (active_conn != NULL) { + if (ctx.active_conn != NULL) { return false; } - active_conn = event->id; - tx_notify_enabled = false; - protocol_module_reset_transport_state(PROTO_TRANSPORT_BLE_NUS); + ctx.active_conn = event->id; + if (ctx.business != BLE_NUS_STACK_OFFLINE) { + business_state_set(BLE_NUS_WAIT_NOTIFY); + } return false; case PEER_STATE_DISCONNECTED: - if (active_conn != event->id) { + if (ctx.active_conn != event->id) { return false; } @@ -124,11 +214,13 @@ static bool handle_proto_tx_event(const struct proto_tx_event *event) return false; } - if (!running || !ble_ready || (active_conn == NULL) || !tx_notify_enabled) { + if ((transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) || + (ctx.active_conn == NULL)) { return false; } - err = bt_nus_send(active_conn, event->dyndata.data, (uint16_t)event->dyndata.size); + err = bt_nus_send(ctx.active_conn, event->dyndata.data, + (uint16_t)event->dyndata.size); if (err) { LOG_WRN("bt_nus_send failed (%d)", err); } @@ -147,32 +239,29 @@ static bool app_event_handler(const struct app_event_header *aeh) } if (is_module_state_event(aeh)) { - const struct module_state_event *event = cast_module_state_event(aeh); - int err; + const struct module_state_event *event = + cast_module_state_event(aeh); if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { - if (!initialized) { - err = module_init(); + if (ctx.lifecycle == MODULE_STATE_OFF) { + int err = module_init(); + if (err) { - module_set_state(MODULE_STATE_ERROR); + lifecycle_set(MODULE_STATE_ERROR); return false; } - - initialized = true; } - err = module_start(); - if (err) { - module_set_state(MODULE_STATE_ERROR); - } + module_start(); return false; } if (check_state(event, MODULE_ID(ble_state), MODULE_STATE_READY)) { - ble_ready = true; - if (running) { - module_set_state(MODULE_STATE_READY); + business_state_set_stack_ready(); + + if (lifecycle_is_ready()) { + lifecycle_set(MODULE_STATE_READY); } return false; @@ -182,23 +271,16 @@ static bool app_event_handler(const struct app_event_header *aeh) } if (is_power_down_event(aeh)) { - if (initialized) { + if (ctx.lifecycle != MODULE_STATE_OFF) { 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 if (ble_ready) { - module_set_state(MODULE_STATE_READY); - } + if (ctx.lifecycle != MODULE_STATE_OFF) { + module_start(); } return false; diff --git a/src/events/proto_transport_state_event.c b/src/events/proto_transport_state_event.c new file mode 100644 index 0000000..dabaeb2 --- /dev/null +++ b/src/events/proto_transport_state_event.c @@ -0,0 +1,56 @@ +#include "proto_transport_state_event.h" + +static const char *transport_name(enum proto_transport transport) +{ + switch (transport) { + case PROTO_TRANSPORT_USB_CDC: + return "usb_cdc"; + case PROTO_TRANSPORT_BLE_NUS: + return "ble_nus"; + default: + return "?"; + } +} + +static const char *state_name(enum proto_transport_link_state state) +{ + switch (state) { + case PROTO_TRANSPORT_LINK_DOWN: + return "down"; + case PROTO_TRANSPORT_LINK_READY: + return "ready"; + default: + return "?"; + } +} + +static void log_proto_transport_state_event(const struct app_event_header *aeh) +{ + const struct proto_transport_state_event *event = + cast_proto_transport_state_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "transport:%s state:%s", + transport_name(event->transport), + state_name(event->state)); +} + +static void profile_proto_transport_state_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct proto_transport_state_event *event = + cast_proto_transport_state_event(aeh); + + nrf_profiler_log_encode_uint8(buf, event->transport); + nrf_profiler_log_encode_uint8(buf, event->state); +} + +APP_EVENT_INFO_DEFINE(proto_transport_state_event, + ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8), + ENCODE("transport", "state"), + profile_proto_transport_state_event); + +APP_EVENT_TYPE_DEFINE(proto_transport_state_event, + log_proto_transport_state_event, + &proto_transport_state_event_info, + APP_EVENT_FLAGS_CREATE( + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/events/usb_control_event.c b/src/events/usb_control_event.c new file mode 100644 index 0000000..2f86d8c --- /dev/null +++ b/src/events/usb_control_event.c @@ -0,0 +1,82 @@ +#include "usb_control_event.h" + +static const char *control_event_name(enum usb_control_event_type type) +{ + switch (type) { + case USB_CONTROL_EVENT_CDC_LINE_STATE: + return "cdc_line_state"; + case USB_CONTROL_EVENT_CDC_LINE_CODING: + return "cdc_line_coding"; + default: + return "?"; + } +} + +static void log_usb_control_event(const struct app_event_header *aeh) +{ + const struct usb_control_event *event = cast_usb_control_event(aeh); + + switch (event->type) { + case USB_CONTROL_EVENT_CDC_LINE_STATE: + APP_EVENT_MANAGER_LOG(aeh, "type:%s dtr:%u", + control_event_name(event->type), + event->data.cdc_line_state.dtr); + break; + + case USB_CONTROL_EVENT_CDC_LINE_CODING: + APP_EVENT_MANAGER_LOG(aeh, + "type:%s baud:%u data:%u stop:%u parity:%u flow:%u", + control_event_name(event->type), + event->data.cdc_line_coding.baudrate, + event->data.cdc_line_coding.data_bits, + event->data.cdc_line_coding.stop_bits, + event->data.cdc_line_coding.parity, + event->data.cdc_line_coding.flow_ctrl); + break; + + default: + APP_EVENT_MANAGER_LOG(aeh, "type:%s", + control_event_name(event->type)); + break; + } +} + +static void profile_usb_control_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct usb_control_event *event = cast_usb_control_event(aeh); + + nrf_profiler_log_encode_uint8(buf, event->type); + switch (event->type) { + case USB_CONTROL_EVENT_CDC_LINE_STATE: + nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_state.dtr); + break; + + case USB_CONTROL_EVENT_CDC_LINE_CODING: + nrf_profiler_log_encode_uint32(buf, event->data.cdc_line_coding.baudrate); + nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_coding.data_bits); + nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_coding.stop_bits); + nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_coding.parity); + nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_coding.flow_ctrl); + break; + + default: + break; + } +} + +APP_EVENT_INFO_DEFINE(usb_control_event, + ENCODE(NRF_PROFILER_ARG_U8, + NRF_PROFILER_ARG_U32, + NRF_PROFILER_ARG_U8, + NRF_PROFILER_ARG_U8, + NRF_PROFILER_ARG_U8, + NRF_PROFILER_ARG_U8), + ENCODE("type", "baud_or_zero", "arg1", "arg2", "arg3", "arg4"), + profile_usb_control_event); + +APP_EVENT_TYPE_DEFINE(usb_control_event, + log_usb_control_event, + &usb_control_event_info, + APP_EVENT_FLAGS_CREATE( + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/protocol_module.c b/src/protocol_module.c index d1765fe..1315fdf 100644 --- a/src/protocol_module.c +++ b/src/protocol_module.c @@ -22,6 +22,7 @@ #include "function_bitmap_update_event.h" #include "hid_led_event.h" #include "proto_rx_event.h" +#include "proto_transport_state_event.h" #include "proto_tx_event.h" #include "protocol_module.h" #include "theme_rgb_update_event.h" @@ -39,7 +40,14 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); static bool initialized; static bool running; -static bool hello_done[PROTO_TRANSPORT_COUNT]; + +enum proto_session_state { + PROTO_SESSION_DOWN = 0, + PROTO_SESSION_WAIT_HELLO, + PROTO_SESSION_ACTIVE, +}; + +static enum proto_session_state session_state[PROTO_TRANSPORT_COUNT]; static int decode_body(const uint8_t *payload, size_t payload_len, CdcPacketBody *body) @@ -151,8 +159,8 @@ static int encode_function_bitmap_state(const uint8_t *bitmap, uint8_t *payload, static int module_init(void) { - for (size_t i = 0; i < ARRAY_SIZE(hello_done); i++) { - hello_done[i] = false; + for (size_t i = 0; i < ARRAY_SIZE(session_state); i++) { + session_state[i] = PROTO_SESSION_DOWN; } return 0; @@ -174,22 +182,13 @@ static void module_pause(void) return; } - for (size_t i = 0; i < ARRAY_SIZE(hello_done); i++) { - hello_done[i] = false; + for (size_t i = 0; i < ARRAY_SIZE(session_state); i++) { + session_state[i] = PROTO_SESSION_DOWN; } running = false; } -void protocol_module_reset_transport_state(enum proto_transport transport) -{ - if (transport >= PROTO_TRANSPORT_COUNT) { - return; - } - - hello_done[transport] = false; -} - int protocol_module_process_message(enum proto_transport transport, const uint8_t *req_payload, size_t req_payload_len, @@ -216,6 +215,10 @@ int protocol_module_process_message(enum proto_transport transport, switch (body.which_body) { case CdcPacketBody_hello_req_tag: + if (session_state[transport] == PROTO_SESSION_DOWN) { + return -EAGAIN; + } + LOG_INF("HelloReq transport:%u protocol_version:%u", transport, body.body.hello_req.protocol_version); @@ -224,11 +227,11 @@ int protocol_module_process_message(enum proto_transport transport, body.body.hello_req.protocol_version); } - hello_done[transport] = true; + session_state[transport] = PROTO_SESSION_ACTIVE; return encode_hello_rsp(rsp_payload, rsp_payload_buf_size, rsp_payload_len); case CdcPacketBody_bitmap_tag: - if (!hello_done[transport]) { + if (session_state[transport] != PROTO_SESSION_ACTIVE) { return encode_error(CdcPacketBody_bitmap_tag, ErrorCode_ERROR_CODE_NOT_READY, rsp_payload, rsp_payload_buf_size, @@ -255,7 +258,7 @@ int protocol_module_process_message(enum proto_transport transport, rsp_payload_buf_size, rsp_payload_len); case CdcPacketBody_time_sync_tag: - if (!hello_done[transport]) { + if (session_state[transport] != PROTO_SESSION_ACTIVE) { return encode_error(CdcPacketBody_time_sync_tag, ErrorCode_ERROR_CODE_NOT_READY, rsp_payload, rsp_payload_buf_size, @@ -278,7 +281,7 @@ int protocol_module_process_message(enum proto_transport transport, rsp_payload_buf_size, rsp_payload_len); case CdcPacketBody_theme_rgb_tag: - if (!hello_done[transport]) { + if (session_state[transport] != PROTO_SESSION_ACTIVE) { return encode_error(CdcPacketBody_theme_rgb_tag, ErrorCode_ERROR_CODE_NOT_READY, rsp_payload, rsp_payload_buf_size, @@ -340,6 +343,29 @@ static bool handle_proto_rx_event(const struct proto_rx_event *event) return false; } +static bool handle_proto_transport_state_event( + const struct proto_transport_state_event *event) +{ + if (event->transport >= PROTO_TRANSPORT_COUNT) { + return false; + } + + switch (event->state) { + case PROTO_TRANSPORT_LINK_DOWN: + session_state[event->transport] = PROTO_SESSION_DOWN; + break; + + case PROTO_TRANSPORT_LINK_READY: + session_state[event->transport] = PROTO_SESSION_WAIT_HELLO; + break; + + default: + return false; + } + + return false; +} + static bool handle_function_bitmap_state_event( const struct function_bitmap_state_event *event) { @@ -353,7 +379,7 @@ static bool handle_function_bitmap_state_event( for (enum proto_transport transport = 0; transport < PROTO_TRANSPORT_COUNT; transport++) { - if (!hello_done[transport]) { + if (session_state[transport] != PROTO_SESSION_ACTIVE) { continue; } @@ -388,7 +414,7 @@ static bool handle_hid_led_event(const struct hid_led_event *event) PROTO_TRANSPORT_USB_CDC : PROTO_TRANSPORT_BLE_NUS; - if (!hello_done[transport]) { + if (session_state[transport] != PROTO_SESSION_ACTIVE) { return false; } @@ -412,6 +438,11 @@ static bool app_event_handler(const struct app_event_header *aeh) return handle_proto_rx_event(cast_proto_rx_event(aeh)); } + if (is_proto_transport_state_event(aeh)) { + return handle_proto_transport_state_event( + cast_proto_transport_state_event(aeh)); + } + if (is_function_bitmap_state_event(aeh)) { return handle_function_bitmap_state_event( cast_function_bitmap_state_event(aeh)); @@ -480,5 +511,6 @@ APP_EVENT_SUBSCRIBE(MODULE, function_bitmap_state_event); APP_EVENT_SUBSCRIBE(MODULE, hid_led_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, proto_rx_event); +APP_EVENT_SUBSCRIBE(MODULE, proto_transport_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event); diff --git a/src/usb_cdc_module.c b/src/usb_cdc_module.c index 378e434..30b4655 100644 --- a/src/usb_cdc_module.c +++ b/src/usb_cdc_module.c @@ -17,8 +17,9 @@ #include #include "proto_rx_event.h" +#include "proto_transport_state_event.h" #include "proto_tx_event.h" -#include "protocol_module.h" +#include "usb_control_event.h" #include "usb_state_event.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); @@ -27,10 +28,22 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); #define USB_CDC_TX_RING_BUF_SIZE 256 #define USB_CDC_RX_CHUNK_SIZE 32 #define USB_CDC_PROTO_RX_BUF_SIZE 128 -#define USB_CDC_CONTROL_POLL_INTERVAL K_MSEC(100) #define USB_CDC_PROTO_RX_FLUSH_DELAY K_MSEC(3) #define USB_CDC_EXPECTED_BAUDRATE 115200U +enum usb_cdc_business_state { + USB_CDC_BUS_OFFLINE = 0, + USB_CDC_WAIT_DTR, + USB_CDC_SESSION_READY, +}; + +struct usb_cdc_ctx { + enum module_state lifecycle; + enum usb_cdc_business_state business; + bool usb_active; + size_t proto_rx_len; +}; + static const struct device *const cdc_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart); static uint8_t rx_ring_buffer[USB_CDC_RX_RING_BUF_SIZE]; @@ -38,15 +51,37 @@ static uint8_t tx_ring_buffer[USB_CDC_TX_RING_BUF_SIZE]; static struct ring_buf rx_ringbuf; static struct ring_buf tx_ringbuf; static struct k_work rx_work; -static struct k_work_delayable control_work; static struct k_work_delayable rx_flush_work; -static bool initialized; -static bool running; -static bool usb_active; -static bool dtr_ready; -static bool rx_enabled; static uint8_t proto_rx_buf[USB_CDC_PROTO_RX_BUF_SIZE]; -static size_t proto_rx_len; +static struct usb_cdc_ctx ctx = { + .lifecycle = MODULE_STATE_OFF, + .business = USB_CDC_BUS_OFFLINE, + .usb_active = false, + .proto_rx_len = 0U, +}; + +static void validate_line_coding(void); + +static bool lifecycle_is_ready(void) +{ + return ctx.lifecycle == MODULE_STATE_READY; +} + +static enum proto_transport_link_state transport_link_state_get(void) +{ + return (lifecycle_is_ready() && + (ctx.business == USB_CDC_SESSION_READY)) ? + PROTO_TRANSPORT_LINK_READY : + PROTO_TRANSPORT_LINK_DOWN; +} + +static bool control_poll_needed(enum module_state lifecycle, + enum usb_cdc_business_state business) +{ + ARG_UNUSED(lifecycle); + ARG_UNUSED(business); + return false; +} static void reset_ring_buffers(void) { @@ -62,16 +97,98 @@ static void disable_uart_io(void) { uart_irq_rx_disable(cdc_dev); uart_irq_tx_disable(cdc_dev); - rx_enabled = false; - dtr_ready = false; - proto_rx_len = 0U; + ctx.proto_rx_len = 0U; k_work_cancel_delayable(&rx_flush_work); reset_ring_buffers(); } +static void state_reconcile(enum module_state old_lifecycle, + enum usb_cdc_business_state old_business) +{ + enum proto_transport_link_state old_link = + ((old_lifecycle == MODULE_STATE_READY) && + (old_business == USB_CDC_SESSION_READY)) ? + PROTO_TRANSPORT_LINK_READY : + PROTO_TRANSPORT_LINK_DOWN; + enum proto_transport_link_state new_link = transport_link_state_get(); + + if ((old_lifecycle == MODULE_STATE_READY) && + (old_business == USB_CDC_SESSION_READY) && + (new_link == PROTO_TRANSPORT_LINK_DOWN)) { + disable_uart_io(); + } + + if ((old_link == PROTO_TRANSPORT_LINK_DOWN) && + (new_link == PROTO_TRANSPORT_LINK_READY)) { + int err; + + validate_line_coding(); + + err = uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DCD, 1); + if (err) { + LOG_WRN("Failed to set DCD (%d)", err); + } + + err = uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DSR, 1); + if (err) { + LOG_WRN("Failed to set DSR (%d)", err); + } + + uart_irq_rx_enable(cdc_dev); + } + + if (old_link != new_link) { + submit_proto_transport_state_event(PROTO_TRANSPORT_USB_CDC, + new_link); + } +} + +static void lifecycle_set(enum module_state new_state) +{ + enum module_state old_lifecycle = ctx.lifecycle; + enum usb_cdc_business_state old_business = ctx.business; + + if (ctx.lifecycle == new_state) { + return; + } + + ctx.lifecycle = new_state; + state_reconcile(old_lifecycle, old_business); + module_set_state(new_state); +} + +static void business_state_set(enum usb_cdc_business_state new_state) +{ + enum module_state old_lifecycle = ctx.lifecycle; + enum usb_cdc_business_state old_business = ctx.business; + + if (ctx.business == new_state) { + return; + } + + ctx.business = new_state; + state_reconcile(old_lifecycle, old_business); +} + +static void business_state_sync_from_usb(void) +{ + if (!ctx.usb_active) { + business_state_set(USB_CDC_BUS_OFFLINE); + return; + } + + if (!lifecycle_is_ready()) { + return; + } + + if (ctx.business == USB_CDC_BUS_OFFLINE) { + business_state_set(USB_CDC_WAIT_DTR); + } +} + static void kick_tx(void) { - if (!running || !usb_active || !dtr_ready) { + if (transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) { return; } @@ -133,15 +250,15 @@ static void rx_work_handler(struct k_work *work) return; } - if ((proto_rx_len + len) > sizeof(proto_rx_buf)) { + if ((ctx.proto_rx_len + len) > sizeof(proto_rx_buf)) { LOG_WRN("Drop oversized CDC protobuf message len:%u", - (uint32_t)(proto_rx_len + len)); - proto_rx_len = 0U; + (uint32_t)(ctx.proto_rx_len + len)); + ctx.proto_rx_len = 0U; } if (len > 0U) { - memcpy(&proto_rx_buf[proto_rx_len], buffer, len); - proto_rx_len += len; + memcpy(&proto_rx_buf[ctx.proto_rx_len], buffer, len); + ctx.proto_rx_len += len; k_work_reschedule(&rx_flush_work, USB_CDC_PROTO_RX_FLUSH_DELAY); } } @@ -151,59 +268,14 @@ static void rx_flush_work_handler(struct k_work *work) { ARG_UNUSED(work); - if (!running || !usb_active || !dtr_ready || (proto_rx_len == 0U)) { + if ((transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) || + (ctx.proto_rx_len == 0U)) { return; } - (void)submit_proto_rx_event(PROTO_TRANSPORT_USB_CDC, proto_rx_buf, proto_rx_len); - proto_rx_len = 0U; -} - -static void control_work_handler(struct k_work *work) -{ - uint32_t dtr = 0U; - int err; - - ARG_UNUSED(work); - - if (!running || !usb_active) { - return; - } - - err = uart_line_ctrl_get(cdc_dev, UART_LINE_CTRL_DTR, &dtr); - if (err) { - LOG_WRN("Failed to get CDC DTR (%d)", err); - goto reschedule; - } - - if (dtr && !dtr_ready) { - dtr_ready = true; - LOG_INF("CDC DTR set"); - validate_line_coding(); - - err = uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DCD, 1); - if (err) { - LOG_WRN("Failed to set DCD (%d)", err); - } - - err = uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DSR, 1); - if (err) { - LOG_WRN("Failed to set DSR (%d)", err); - } - - if (!rx_enabled) { - uart_irq_rx_enable(cdc_dev); - rx_enabled = true; - } - - kick_tx(); - } else if (!dtr && dtr_ready) { - LOG_INF("CDC DTR cleared"); - disable_uart_io(); - } - -reschedule: - k_work_reschedule(&control_work, USB_CDC_CONTROL_POLL_INTERVAL); + (void)submit_proto_rx_event(PROTO_TRANSPORT_USB_CDC, proto_rx_buf, + ctx.proto_rx_len); + ctx.proto_rx_len = 0U; } static void cdc_interrupt_handler(const struct device *dev, void *user_data) @@ -229,7 +301,8 @@ static void cdc_interrupt_handler(const struct device *dev, void *user_data) irq_unlock(key); if (written < (uint32_t)recv_len) { - LOG_WRN("Drop %d CDC RX bytes", recv_len - (int)written); + LOG_WRN("Drop %d CDC RX bytes", + recv_len - (int)written); } k_work_submit(&rx_work); @@ -271,61 +344,97 @@ static int module_init(void) reset_ring_buffers(); k_work_init(&rx_work, rx_work_handler); - k_work_init_delayable(&control_work, control_work_handler); k_work_init_delayable(&rx_flush_work, rx_flush_work_handler); uart_irq_callback_set(cdc_dev, cdc_interrupt_handler); - proto_rx_len = 0U; + ctx = (struct usb_cdc_ctx) { + .lifecycle = MODULE_STATE_OFF, + .business = USB_CDC_BUS_OFFLINE, + .usb_active = false, + .proto_rx_len = 0U, + }; + return 0; } -static int module_start(void) +static void module_start(void) { - if (running) { - return 0; - } - - running = true; - if (usb_active) { - k_work_reschedule(&control_work, K_NO_WAIT); - } - - return 0; + lifecycle_set(MODULE_STATE_READY); + business_state_sync_from_usb(); } static void module_pause(void) { - if (!running) { - return; - } - - k_work_cancel_delayable(&control_work); - k_work_cancel_delayable(&rx_flush_work); - disable_uart_io(); - running = false; + business_state_set(USB_CDC_BUS_OFFLINE); + lifecycle_set(MODULE_STATE_STANDBY); } static bool handle_usb_state_event(const struct usb_state_event *event) { bool new_usb_active = (event->state == USB_STATE_ACTIVE); - if (new_usb_active == usb_active) { + if (new_usb_active == ctx.usb_active) { return false; } - usb_active = new_usb_active; - - if (!usb_active) { - k_work_cancel_delayable(&control_work); - k_work_cancel_delayable(&rx_flush_work); - protocol_module_reset_transport_state(PROTO_TRANSPORT_USB_CDC); - disable_uart_io(); - } else if (running) { - k_work_reschedule(&control_work, K_NO_WAIT); - } + ctx.usb_active = new_usb_active; + business_state_sync_from_usb(); return false; } +static bool handle_usb_control_event(const struct usb_control_event *event) +{ + if (event->dev != cdc_dev) { + return false; + } + + switch (event->type) { + case USB_CONTROL_EVENT_CDC_LINE_STATE: + if (!ctx.usb_active || !lifecycle_is_ready()) { + return false; + } + + if (event->data.cdc_line_state.dtr) { + LOG_INF("CDC DTR set"); + business_state_set(USB_CDC_SESSION_READY); + kick_tx(); + } else { + LOG_INF("CDC DTR cleared"); + business_state_set(USB_CDC_WAIT_DTR); + } + return false; + + case USB_CONTROL_EVENT_CDC_LINE_CODING: + if (event->data.cdc_line_coding.baudrate != 0U) { + LOG_INF("CDC baudrate %u", + event->data.cdc_line_coding.baudrate); + if (event->data.cdc_line_coding.baudrate != + USB_CDC_EXPECTED_BAUDRATE) { + LOG_WRN("Expected CDC baudrate %u, got %u", + USB_CDC_EXPECTED_BAUDRATE, + event->data.cdc_line_coding.baudrate); + } + } + +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE + if ((event->data.cdc_line_coding.data_bits != 0U) || + (event->data.cdc_line_coding.stop_bits != 0U) || + (event->data.cdc_line_coding.parity != 0U) || + (event->data.cdc_line_coding.flow_ctrl != 0U)) { + LOG_INF("CDC line coding data:%u stop:%u parity:%u flow:%u", + event->data.cdc_line_coding.data_bits, + event->data.cdc_line_coding.stop_bits, + event->data.cdc_line_coding.parity, + event->data.cdc_line_coding.flow_ctrl); + } +#endif + return false; + + default: + return false; + } +} + static bool handle_proto_tx_event(const struct proto_tx_event *event) { uint32_t written; @@ -335,7 +444,7 @@ static bool handle_proto_tx_event(const struct proto_tx_event *event) return false; } - if (!running || !usb_active || !dtr_ready) { + if (transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) { return false; } @@ -365,51 +474,41 @@ static bool app_event_handler(const struct app_event_header *aeh) return handle_proto_tx_event(cast_proto_tx_event(aeh)); } + if (is_usb_control_event(aeh)) { + return handle_usb_control_event(cast_usb_control_event(aeh)); + } + if (is_module_state_event(aeh)) { - const struct module_state_event *event = cast_module_state_event(aeh); + const struct module_state_event *event = + cast_module_state_event(aeh); if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { - int err; + if (ctx.lifecycle == MODULE_STATE_OFF) { + int err = module_init(); - if (!initialized) { - err = module_init(); if (err) { - module_set_state(MODULE_STATE_ERROR); + lifecycle_set(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); - } + module_start(); } return false; } if (is_power_down_event(aeh)) { - if (initialized) { + if (ctx.lifecycle != MODULE_STATE_OFF) { 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); - } + if (ctx.lifecycle != MODULE_STATE_OFF) { + module_start(); } return false; @@ -421,6 +520,7 @@ static bool app_event_handler(const struct app_event_header *aeh) APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, proto_tx_event); +APP_EVENT_SUBSCRIBE(MODULE, usb_control_event); APP_EVENT_SUBSCRIBE(MODULE, usb_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event); diff --git a/src/usb_device_module.c b/src/usb_device_module.c index adbe9a8..83ed9bd 100644 --- a/src/usb_device_module.c +++ b/src/usb_device_module.c @@ -9,12 +9,14 @@ #include #include +#include #include #include #include #include "usb_function_hook.h" +#include "usb_control_event.h" #include "usb_state_event.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); @@ -147,6 +149,54 @@ static void usbd_msg_cb(struct usbd_context *const usbd_ctx, { ARG_UNUSED(usbd_ctx); + if (msg->type == USBD_MSG_CDC_ACM_CONTROL_LINE_STATE) { + uint32_t dtr = 0U; + int err = uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_DTR, &dtr); + + if (err) { + LOG_WRN("Failed to get CDC DTR (%d)", err); + } else { + submit_usb_control_cdc_line_state_event(msg->dev, dtr != 0U); + } + + return; + } + + if (msg->type == USBD_MSG_CDC_ACM_LINE_CODING) { + uint32_t baudrate = 0U; + uint8_t data_bits = 0U; + uint8_t stop_bits = 0U; + uint8_t parity = 0U; + uint8_t flow_ctrl = 0U; + int err; + + err = uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_BAUD_RATE, &baudrate); + if (err) { + LOG_WRN("Failed to get CDC baudrate (%d)", err); + } + +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE + { + struct uart_config cfg; + + err = uart_config_get(msg->dev, &cfg); + if (err) { + LOG_WRN("uart_config_get failed (%d)", err); + } else { + data_bits = (uint8_t)cfg.data_bits; + stop_bits = (uint8_t)cfg.stop_bits; + parity = (uint8_t)cfg.parity; + flow_ctrl = (uint8_t)cfg.flow_ctrl; + } + } +#endif + + submit_usb_control_cdc_line_coding_event(msg->dev, baudrate, + data_bits, stop_bits, + parity, flow_ctrl); + return; + } + switch (msg->type) { case USBD_MSG_VBUS_READY: update_power_manager_restriction(true);