/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #if PULSE_EVERYWHERE #include "pulse.h" #include "pulse2_reliable_retransmit_timer.h" #include "pulse2_transport_impl.h" #include "pulse_internal.h" #include "console/cobs.h" #include "console/console_internal.h" #include "console/control_protocol.h" #include "console/control_protocol_impl.h" #include "console/dbgserial.h" #include "drivers/rtc.h" #include "drivers/task_watchdog.h" #include "kernel/pbl_malloc.h" #include "kernel/pebble_tasks.h" #include "mcu/interrupts.h" #include "os/mutex.h" #include "services/common/regular_timer.h" #include "system/passert.h" #include "util/attributes.h" #include "util/crc32.h" #include "util/likely.h" #include "util/math.h" #include "util/net.h" #include "util/size.h" #include "FreeRTOS.h" #include "semphr.h" #include "queue.h" #include "task.h" #include #include #include #define LCP_PROTOCOL_NUMBER (0xC021) #define FRAME_MAX_SEND_SIZE PULSE_MAX_RECEIVE_UNIT #define RX_QUEUE_SIZE (PULSE_MAX_RECEIVE_UNIT * 3) #define RX_MAX_FRAME_SIZE (PULSE_MAX_RECEIVE_UNIT + PULSE_MIN_FRAME_LENGTH) #define FRAME_DELIMITER '\x55' #define LINK_HEADER_LEN sizeof(net16) // Link Control Protocol // ===================== static void prv_on_lcp_up(PPPControlProtocol *this) { #define ON_PACKET(...) #define ON_INIT(...) #define ON_LINK_STATE_CHANGE(ON_UP, ON_DOWN) ON_UP(); #include "console/pulse2_transport_registry.def" #undef ON_PACKET #undef ON_INIT #undef ON_LINK_STATE_CHANGE } static void prv_on_lcp_down(PPPControlProtocol *this) { #define ON_PACKET(...) #define ON_INIT(...) #define ON_LINK_STATE_CHANGE(ON_UP, ON_DOWN) ON_DOWN(); #include "console/pulse2_transport_registry.def" #undef ON_PACKET #undef ON_INIT #undef ON_LINK_STATE_CHANGE } static void prv_on_code_reject(PPPControlProtocol *this, struct LCPPacket *packet) { // TODO } static void prv_on_protocol_reject(PPPControlProtocol *this, struct LCPPacket *packet) { // TODO } static void prv_on_echo_request(PPPControlProtocol *this, struct LCPPacket *packet) { if (this->state->link_state == LinkState_Opened) { struct LCPPacket *reply = pulse_link_send_begin(this->protocol_number); memcpy(reply, packet, ntoh16(packet->length)); reply->code = ControlCode_EchoReply; pulse_link_send(reply, ntoh16(packet->length)); } } static void prv_on_echo_reply(PPPControlProtocol *this, struct LCPPacket *packet) { // TODO } static bool prv_handle_extended_lcp_codes(PPPControlProtocol *this, LCPPacket *packet) { switch (packet->code) { case ControlCode_ProtocolReject: prv_on_protocol_reject(this, packet); return true; case ControlCode_EchoRequest: prv_on_echo_request(this, packet); return true; case ControlCode_EchoReply: prv_on_echo_reply(this, packet); return true; case ControlCode_DiscardRequest: return true; default: return false; } } static PPPControlProtocolState s_lcp_state = {}; static PPPControlProtocol s_lcp_protocol = { .protocol_number = LCP_PROTOCOL_NUMBER, .state = &s_lcp_state, .on_this_layer_up = prv_on_lcp_up, .on_this_layer_down = prv_on_lcp_down, .on_receive_code_reject = prv_on_code_reject, .on_receive_unrecognized_code = prv_handle_extended_lcp_codes, }; PPPControlProtocol * const PULSE2_LCP = &s_lcp_protocol; static void prv_lcp_handle_unknown_protocol(uint16_t protocol, void *body, size_t body_len) { // TODO: send Protocol-Reject } static void prv_lcp_on_packet(void *packet, size_t length) { ppp_control_protocol_handle_incoming_packet(PULSE2_LCP, packet, length); } // Data link layer // =============== // PULSE task // ---------- // // This task handles both the processing of bytes received over dbgserial and // running the reliable transport receive expiry timer. static TaskHandle_t s_pulse_task_handle; static QueueHandle_t s_pulse_task_queue; // Wake up the PULSE task to process the receive queue or start the timer. static SemaphoreHandle_t s_pulse_task_service_semaphore; static volatile bool s_pulse_task_idle = true; static uint8_t s_current_rx_frame[RX_MAX_FRAME_SIZE]; static PebbleMutex *s_tx_buffer_mutex; static char s_tx_buffer[MAX_SIZE_AFTER_COBS_ENCODING( FRAME_MAX_SEND_SIZE + PULSE_MIN_FRAME_LENGTH) + COBS_OVERHEAD(FRAME_MAX_SEND_SIZE)]; // Lock for exclusive access to the reliable timer state. static PebbleMutex *s_reliable_timer_state_lock; // Ticks since boot for timer expiry if timer is pending, or 0 if not pending. static volatile RtcTicks s_reliable_timer_expiry_time_tick; static volatile uint8_t s_reliable_timer_sequence_number; static void prv_process_received_frame(size_t frame_length) { if (frame_length < PULSE_MIN_FRAME_LENGTH || frame_length == SIZE_MAX) { // Decoding failed, this frame is bogus return; } uint32_t fcs; if (crc32(CRC32_INIT, s_current_rx_frame, frame_length) == CRC32_RESIDUE) { net16 protocol_be; memcpy(&protocol_be, s_current_rx_frame, sizeof(protocol_be)); uint16_t protocol = ntoh16(protocol_be); void *body = &s_current_rx_frame[sizeof(protocol_be)]; size_t body_len = frame_length - sizeof(protocol_be) - sizeof(fcs); switch (protocol) { case LCP_PROTOCOL_NUMBER: prv_lcp_on_packet(body, body_len); break; #define ON_PACKET(NUMBER, HANDLER) \ case NUMBER: \ HANDLER(body, body_len); \ break; #define ON_INIT(...) #define ON_LINK_STATE_CHANGE(...) #include "console/pulse2_transport_registry.def" #undef ON_PACKET #undef ON_INIT #undef ON_LINK_STATE_CHANGE default: prv_lcp_handle_unknown_protocol(protocol, body, body_len); } } } void pulse2_reliable_retransmit_timer_start(unsigned int timeout_ms, uint8_t sequence_number) { mutex_lock(s_reliable_timer_state_lock); RtcTicks timeout_ticks = timeout_ms * RTC_TICKS_HZ / 1000; s_reliable_timer_expiry_time_tick = rtc_get_ticks() + timeout_ticks; s_reliable_timer_sequence_number = sequence_number; // Wake up the PULSE task to get it to notice the newly-started timer. xSemaphoreGive(s_pulse_task_service_semaphore); mutex_unlock(s_reliable_timer_state_lock); } void pulse2_reliable_retransmit_timer_cancel(void) { mutex_lock(s_reliable_timer_state_lock); s_reliable_timer_expiry_time_tick = 0; // No need to wake up the PULSE task. It will notice that the timer was // cancelled when it wakes up to service the timer. mutex_unlock(s_reliable_timer_state_lock); } // Check the state of the timer. // // If there is no timer running, portMAX_DELAY is returned. // // If there is a timer running but it has not expired yet, the number of ticks // (not milliseconds) remaining before the timer expires is returned. // // If there is a timer running that has expired, the timer state is cleared so // that subsequent calls do not expire the same timer twice, sequence_number is // filled with the sequence number of the expired timer, and 0 is returned. static TickType_t prv_poll_timer(uint8_t *const sequence_number) { mutex_lock(s_reliable_timer_state_lock); RtcTicks timer_expiry_tick = s_reliable_timer_expiry_time_tick; TickType_t timeout = portMAX_DELAY; if (timer_expiry_tick) { // A timer is pending RtcTicks now = rtc_get_ticks(); if (now >= timer_expiry_tick) { // Timer has expired // Clear the timer pending state. s_reliable_timer_expiry_time_tick = 0; timeout = 0; *sequence_number = s_reliable_timer_sequence_number; } else { _Static_assert(pdMS_TO_TICKS(1000) == RTC_TICKS_HZ, "RtcTicks uses different units than FreeRTOS ticks"); timeout = timer_expiry_tick - now; } } mutex_unlock(s_reliable_timer_state_lock); return timeout; } static void prv_pulse_task_feed_watchdog(void) { task_watchdog_bit_set(PebbleTask_PULSE); } static void prv_pulse_task_idle_timer_callback(void* data) { if (s_pulse_task_idle && uxQueueMessagesWaiting(s_pulse_task_queue) == 0) { prv_pulse_task_feed_watchdog(); } } static void prv_pulse_task_main(void *unused) { task_watchdog_mask_set(PebbleTask_PULSE); static RegularTimerInfo idle_watchdog_timer = { .cb = prv_pulse_task_idle_timer_callback }; regular_timer_add_seconds_callback(&idle_watchdog_timer); CobsDecodeContext frame_decode_ctx; cobs_streaming_decode_start(&frame_decode_ctx, s_current_rx_frame, RX_MAX_FRAME_SIZE); while (true) { uint8_t timer_sequence_number; TickType_t timeout = prv_poll_timer(&timer_sequence_number); if (timeout && uxQueueMessagesWaiting(s_pulse_task_queue) == 0) { s_pulse_task_idle = true; xSemaphoreTake(s_pulse_task_service_semaphore, timeout); s_pulse_task_idle = false; // Read the timer state again in case it changed while we were waiting. timeout = prv_poll_timer(&timer_sequence_number); } // Even if the timer expired, drain the received bytes queue first. // We don't want to risk the queue filling up while the timer // handler is running. char c; while (xQueueReceive(s_pulse_task_queue, &c, 0) == pdTRUE) { if (UNLIKELY(c == FRAME_DELIMITER)) { size_t decoded_length = cobs_streaming_decode_finish(&frame_decode_ctx); prv_process_received_frame(decoded_length); cobs_streaming_decode_start(&frame_decode_ctx, s_current_rx_frame, RX_MAX_FRAME_SIZE); // Break out after processing one complete frame so that we handle // the timer and kick the watchdog within a reasonable amount of // time, even if the queue is filling as fast as we can drain it. break; } else { if (c == '\0') { c = FRAME_DELIMITER; } cobs_streaming_decode(&frame_decode_ctx, c); } } // Finally handle the timer. if (timeout == 0) { pulse2_reliable_retransmit_timer_expired_handler(timer_sequence_number); } prv_pulse_task_feed_watchdog(); } } static void prv_forge_terminate_ack(void) { // Send an LCP Terminate-Ack without invoking the control_protocol // API since we need to send these packets from precarious situations // when the OS and PULSE may not have been initialized yet. uint8_t *packet = pulse_link_send_begin(LCP_PROTOCOL_NUMBER); packet[0] = 6; // Code: Terminate-Ack packet[1] = 255; // Identifier packet[2] = 0; // Length MSB packet[3] = 4; // Length LSB pulse_link_send(packet, 4); } void pulse_early_init(void) { // Forge an LCP Terminate-Ack packet to synchronize the host's state // in case we crashed without terminating the connection. prv_forge_terminate_ack(); } void pulse_init(void) { s_tx_buffer_mutex = mutex_create(); PBL_ASSERTN(s_tx_buffer_mutex != INVALID_MUTEX_HANDLE); } void pulse_start(void) { s_pulse_task_queue = xQueueCreate(RX_QUEUE_SIZE, sizeof(uint8_t)); s_pulse_task_service_semaphore = xSemaphoreCreateBinary(); s_reliable_timer_state_lock = mutex_create(); TaskParameters_t task_params = { .pvTaskCode = prv_pulse_task_main, .pcName = "PULSE", .usStackDepth = 1024 / sizeof( StackType_t ), .uxPriority = (tskIDLE_PRIORITY + 3) | portPRIVILEGE_BIT, .puxStackBuffer = NULL, }; pebble_task_create(PebbleTask_PULSE, &task_params, &s_pulse_task_handle); // FIXME: the initializers could be run more than once if pulse_start is // called more than one time. These initializers can't be run during // pulse_init since that runs even earlier than NewTimer init, which is // required for LCP to initialize. ppp_control_protocol_init(PULSE2_LCP); #define ON_PACKET(...) #define ON_INIT(INITIALIZER) INITIALIZER(); #define ON_LINK_STATE_CHANGE(...) #include "console/pulse2_transport_registry.def" #undef ON_PACKET #undef ON_INIT #undef ON_LINK_STATE_CHANGE serial_console_set_state(SERIAL_CONSOLE_STATE_PULSE); ppp_control_protocol_lower_layer_is_up(PULSE2_LCP); ppp_control_protocol_open(PULSE2_LCP); } void pulse_end(void) { ppp_control_protocol_close(PULSE2_LCP, PPPCPCloseWait_WaitForClosed); } void pulse_prepare_to_crash(void) { // We're crashing so it's not safe to use control_protocol APIs. prv_forge_terminate_ack(); } static void prv_assert_tx_buffer(void *buf) { // Ensure the buffer is actually a PULSE transmit buffer bool buf_valid = false; if (buf == s_tx_buffer + COBS_OVERHEAD(FRAME_MAX_SEND_SIZE) + LINK_HEADER_LEN) { buf_valid = true; } PBL_ASSERT(buf_valid, "Buffer is not from the PULSE transmit buffer pool"); } void pulse_handle_character(char c, bool *should_context_switch) { portBASE_TYPE tmp; xQueueSendToBackFromISR(s_pulse_task_queue, &c, &tmp); xSemaphoreGiveFromISR(s_pulse_task_service_semaphore, &tmp); *should_context_switch = (tmp == pdTRUE); } static bool prv_safe_to_touch_mutex(void) { return !(portIN_CRITICAL() || mcu_state_is_isr() || xTaskGetSchedulerState() != taskSCHEDULER_RUNNING); } void *pulse_link_send_begin(const uint16_t protocol) { if (prv_safe_to_touch_mutex()) { mutex_lock(s_tx_buffer_mutex); } net16 header = hton16(protocol); memcpy(s_tx_buffer + COBS_OVERHEAD(FRAME_MAX_SEND_SIZE), &header, sizeof(header)); return s_tx_buffer + COBS_OVERHEAD(FRAME_MAX_SEND_SIZE) + sizeof(header); } void pulse_link_send(void *buf, const size_t payload_length) { prv_assert_tx_buffer(buf); PBL_ASSERT(payload_length <= FRAME_MAX_SEND_SIZE, "PULSE frame payload too long"); // Rewind the pointer to the beginning of the buffer char *frame = ((char *) buf) - COBS_OVERHEAD(FRAME_MAX_SEND_SIZE) - LINK_HEADER_LEN; size_t length = LINK_HEADER_LEN + payload_length; uint32_t fcs = crc32(CRC32_INIT, frame + COBS_OVERHEAD(FRAME_MAX_SEND_SIZE), length); memcpy(&frame[length + COBS_OVERHEAD(FRAME_MAX_SEND_SIZE)], &fcs, sizeof(fcs)); length += sizeof(fcs); length = cobs_encode(frame, frame + COBS_OVERHEAD(FRAME_MAX_SEND_SIZE), length); // TODO: DMA dbgserial_putchar_lazy(FRAME_DELIMITER); for (size_t i = 0; i < length; ++i) { if (frame[i] == FRAME_DELIMITER) { dbgserial_putchar_lazy('\0'); } else { dbgserial_putchar_lazy(frame[i]); } } dbgserial_putchar_lazy(FRAME_DELIMITER); if (prv_safe_to_touch_mutex()) { mutex_unlock(s_tx_buffer_mutex); } } void pulse_link_send_cancel(void *buf) { prv_assert_tx_buffer(buf); mutex_unlock(s_tx_buffer_mutex); } size_t pulse_link_max_send_size(void) { return FRAME_MAX_SEND_SIZE - LINK_HEADER_LEN; } #endif