mirror of
https://github.com/google/pebble.git
synced 2026-02-19 11:46:49 -05:00
Import of the watch repository from Pebble
This commit is contained in:
298
src/fw/console/pulse.c
Normal file
298
src/fw/console/pulse.c
Normal file
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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
|
||||
Reference in New Issue
Block a user