pebble/src/fw/console/pulse.c
2025-01-27 11:38:16 -08:00

298 lines
9.3 KiB
C

/*
* 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 <stdbool.h>
#include <stdint.h>
#include <string.h>
#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