/* * 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 #include #include #include "console/cobs.h" #include "console/console_internal.h" #include "console/dbgserial.h" #include "console/pulse_internal.h" #include "console/pulse_llc.h" #include "console/pulse_protocol_impl.h" #include "kernel/pbl_malloc.h" #include "os/mutex.h" #include "services/common/new_timer/new_timer.h" #include "services/common/system_task.h" #include "system/passert.h" #include "util/attributes.h" #include "util/legacy_checksum.h" #include "util/likely.h" #include "util/math.h" #include "util/size.h" #define FRAME_POOL_SIZE (3) #define FRAME_DELIMITER '\0' #define LINK_HEADER_LEN (1) typedef struct IncomingPulseFrame { uint16_t length; bool taken; char data[MAX_SIZE_AFTER_COBS_ENCODING(PULSE_MAX_RECEIVE_UNIT)]; } IncomingPulseFrame; static IncomingPulseFrame *s_receive_buffers[FRAME_POOL_SIZE]; static IncomingPulseFrame *s_current_receive_buffer; static CobsDecodeContext s_frame_decode_ctx; static bool s_drop_rest_of_frame; static PebbleMutex *s_tx_buffer_mutex; static char s_tx_buffer[MAX_SIZE_AFTER_COBS_ENCODING( PULSE_MAX_SEND_SIZE + PULSE_MIN_FRAME_LENGTH) + COBS_OVERHEAD(PULSE_MAX_SEND_SIZE)]; typedef void (*ProtocolHandlerFunc)(void *packet, size_t length); typedef void (*LinkStateChangedHandlerFunc)(PulseLinkState link_state); typedef struct PACKED ProtocolHandler { uint8_t number; ProtocolHandlerFunc handler; LinkStateChangedHandlerFunc link_state_handler; } ProtocolHandler; static const ProtocolHandler s_supported_protocols[] = { #define REGISTER_PROTOCOL(n, f1, f2) { \ .number = (n), \ .handler = (f1), \ .link_state_handler = (f2) \ }, #include "console/pulse_protocol_registry.def" #undef REGISTER_PROTOCOL }; static TimerID s_keepalive_timer = TIMER_INVALID_ID; static void prv_reset_receive_buffer(IncomingPulseFrame *buf) { buf->length = 0; cobs_streaming_decode_start(&s_frame_decode_ctx, &buf->data, sizeof(buf->data)); } static IncomingPulseFrame* prv_take_receive_buffer(void) { IncomingPulseFrame *buf = NULL; for (unsigned int i = 0; i < ARRAY_LENGTH(s_receive_buffers); ++i) { if (s_receive_buffers[i]->taken == false) { buf = s_receive_buffers[i]; buf->taken = true; prv_reset_receive_buffer(buf); return buf; } } return NULL; } static void prv_return_receive_buffer(IncomingPulseFrame *buf) { buf->taken = false; } static void prv_keepalive_timeout_expired(void *data) { pulse_end(); } static void prv_reset_keepalive_timer(void) { if (s_keepalive_timer) { new_timer_start(s_keepalive_timer, PULSE_KEEPALIVE_TIMEOUT_DECISECONDS * 100, prv_keepalive_timeout_expired, NULL, TIMER_START_FLAG_FAIL_IF_EXECUTING); } } static void prv_handlers_notify_state_changed(PulseLinkState link_state) { for (unsigned int i = 0; i < ARRAY_LENGTH(s_supported_protocols); ++i) { s_supported_protocols[i].link_state_handler(link_state); } } void pulse_early_init(void) { } void pulse_init(void) { s_tx_buffer_mutex = mutex_create(); PBL_ASSERTN(s_tx_buffer_mutex != INVALID_MUTEX_HANDLE); } void pulse_start(void) { for (unsigned int i = 0; i < ARRAY_LENGTH(s_receive_buffers); ++i) { s_receive_buffers[i] = kernel_malloc_check(sizeof(IncomingPulseFrame)); prv_return_receive_buffer(s_receive_buffers[i]); } s_current_receive_buffer = prv_take_receive_buffer(); s_drop_rest_of_frame = false; s_keepalive_timer = new_timer_create(); PBL_ASSERTN(s_keepalive_timer != TIMER_INVALID_ID); pulse_init(); serial_console_set_state(SERIAL_CONSOLE_STATE_PULSE); pulse_llc_send_link_opened_msg(); prv_reset_keepalive_timer(); prv_handlers_notify_state_changed(PulseLinkState_Open); } void pulse_end(void) { prv_handlers_notify_state_changed(PulseLinkState_Closed); pulse_llc_send_link_closed_msg(); for (unsigned int i = 0; i < ARRAY_LENGTH(s_receive_buffers); ++i) { kernel_free(s_receive_buffers[i]); } s_current_receive_buffer = NULL; new_timer_delete(s_keepalive_timer); s_keepalive_timer = TIMER_INVALID_ID; mutex_destroy(s_tx_buffer_mutex); dbgserial_restore_baud_rate(); serial_console_set_state(SERIAL_CONSOLE_STATE_LOGGING); } void pulse_prepare_to_crash(void) { } static void prv_process_received_frame(void *frame_ptr) { IncomingPulseFrame *frame = frame_ptr; uint32_t fcs; // Comply with strict aliasing rules. The memcpy is optimized away. memcpy(&fcs, &frame->data[frame->length - sizeof(fcs)], sizeof(fcs)); uint32_t crc = legacy_defective_checksum_memory( &frame->data, frame->length - sizeof(fcs)); if (fcs == crc) { prv_reset_keepalive_timer(); uint8_t protocol = (uint8_t)frame->data[0]; bool protocol_found = false; for (unsigned int i = 0; i < ARRAY_LENGTH(s_supported_protocols); ++i) { if (s_supported_protocols[i].number == protocol) { protocol_found = true; s_supported_protocols[i].handler( &frame->data[sizeof(protocol)], frame->length - sizeof(protocol) - sizeof(fcs)); break; } } if (!protocol_found) { pulse_llc_unknown_protocol_handler( protocol, &frame->data[sizeof(protocol)], frame->length - sizeof(protocol) - sizeof(fcs)); } } prv_return_receive_buffer(frame); } 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(PULSE_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) { // TODO: discard a frame outright if a framing error occurs if (s_current_receive_buffer == NULL) { s_current_receive_buffer = prv_take_receive_buffer(); if (s_current_receive_buffer == NULL) { // No buffers are available to store the char; drop it. if (c != FRAME_DELIMITER) { s_drop_rest_of_frame = true; } return; } } if (UNLIKELY(c == FRAME_DELIMITER)) { s_drop_rest_of_frame = false; size_t decoded_length = cobs_streaming_decode_finish(&s_frame_decode_ctx); if (decoded_length >= PULSE_MIN_FRAME_LENGTH && decoded_length < SIZE_MAX) { // Potentially valid frame; queue up for further processing. s_current_receive_buffer->length = decoded_length; system_task_add_callback_from_isr(prv_process_received_frame, s_current_receive_buffer, should_context_switch); // Prepare to receive the next character. s_current_receive_buffer = prv_take_receive_buffer(); } else { // Not a valid frame; throw it away. prv_reset_receive_buffer(s_current_receive_buffer); } } else if (s_drop_rest_of_frame) { // The frame has already been found to be bad and we haven't yet // seen the start of the next frame. } else if (UNLIKELY(s_current_receive_buffer->length >= sizeof(s_current_receive_buffer->data))) { // Frame too long; invalid. s_drop_rest_of_frame = true; prv_reset_receive_buffer(s_current_receive_buffer); } else { if (!cobs_streaming_decode(&s_frame_decode_ctx, c)) { s_drop_rest_of_frame = true; } } } void *pulse_best_effort_send_begin(const uint8_t protocol) { mutex_lock(s_tx_buffer_mutex); s_tx_buffer[COBS_OVERHEAD(PULSE_MAX_SEND_SIZE)] = protocol; // Expose only the payload of the message return s_tx_buffer + COBS_OVERHEAD(PULSE_MAX_SEND_SIZE) + 1; } void pulse_best_effort_send(void *buf, const size_t payload_length) { prv_assert_tx_buffer(buf); PBL_ASSERT(payload_length <= PULSE_MAX_SEND_SIZE, "PULSE frame payload too long"); // Rewind the pointer to the beginning of the buffer char *frame = ((char *) buf) - COBS_OVERHEAD(PULSE_MAX_SEND_SIZE) - LINK_HEADER_LEN; size_t length = LINK_HEADER_LEN + payload_length; uint32_t fcs = legacy_defective_checksum_memory( frame + COBS_OVERHEAD(PULSE_MAX_SEND_SIZE), length); memcpy(&frame[length + COBS_OVERHEAD(PULSE_MAX_SEND_SIZE)], &fcs, sizeof(fcs)); length += sizeof(fcs); length = cobs_encode(frame, frame + COBS_OVERHEAD(PULSE_MAX_SEND_SIZE), length); // TODO: DMA dbgserial_putchar_lazy(FRAME_DELIMITER); for (size_t i = 0; i < length; ++i) { dbgserial_putchar_lazy(frame[i]); } dbgserial_putchar_lazy(FRAME_DELIMITER); mutex_unlock(s_tx_buffer_mutex); } void pulse_best_effort_send_cancel(void *buf) { prv_assert_tx_buffer(buf); mutex_unlock(s_tx_buffer_mutex); } void pulse_change_baud_rate(uint32_t new_baud) { dbgserial_change_baud_rate(new_baud); } #endif