mirror of
https://github.com/google/pebble.git
synced 2025-05-12 04:13:17 -04:00
298 lines
9.3 KiB
C
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
|