mirror of
https://github.com/google/pebble.git
synced 2025-06-05 09:43:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
161
src/fw/console/best_effort_transport.c
Normal file
161
src/fw/console/best_effort_transport.c
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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_protocol_impl.h"
|
||||
|
||||
#include "console/control_protocol.h"
|
||||
#include "console/control_protocol_impl.h"
|
||||
#include "console/pulse.h"
|
||||
#include "console/pulse2_transport_impl.h"
|
||||
#include "console/pulse_control_message_protocol.h"
|
||||
#include "system/passert.h"
|
||||
#include <util/attributes.h>
|
||||
#include <util/net.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static bool s_layer_up = false;
|
||||
|
||||
// Best Effort transport Control Protocol
|
||||
// ======================================
|
||||
|
||||
static void prv_on_this_layer_up(PPPControlProtocol *this) {
|
||||
s_layer_up = true;
|
||||
#define REGISTER_PROTOCOL(N, HANDLER, LAYER_STATE_HANDLER) \
|
||||
LAYER_STATE_HANDLER(PulseLinkState_Open);
|
||||
#include "console/pulse_protocol_registry.def"
|
||||
#undef REGISTER_PROTOCOL
|
||||
}
|
||||
|
||||
static void prv_on_this_layer_down(PPPControlProtocol *this) {
|
||||
s_layer_up = false;
|
||||
#define REGISTER_PROTOCOL(N, HANDLER, LAYER_STATE_HANDLER) \
|
||||
LAYER_STATE_HANDLER(PulseLinkState_Closed);
|
||||
#include "console/pulse_protocol_registry.def"
|
||||
#undef REGISTER_PROTOCOL
|
||||
}
|
||||
|
||||
static void prv_on_receive_code_reject(PPPControlProtocol *this,
|
||||
LCPPacket *packet) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
static PPPControlProtocolState s_becp_state = {};
|
||||
|
||||
static PPPControlProtocol s_becp_protocol = {
|
||||
.protocol_number = PULSE2_BEST_EFFORT_CONTROL_PROTOCOL,
|
||||
.state = &s_becp_state,
|
||||
.on_this_layer_up = prv_on_this_layer_up,
|
||||
.on_this_layer_down = prv_on_this_layer_down,
|
||||
.on_receive_code_reject = prv_on_receive_code_reject,
|
||||
};
|
||||
|
||||
PPPControlProtocol * const PULSE2_BECP = &s_becp_protocol;
|
||||
|
||||
void pulse2_best_effort_control_on_packet(void *packet, size_t length) {
|
||||
ppp_control_protocol_handle_incoming_packet(PULSE2_BECP, packet, length);
|
||||
}
|
||||
|
||||
// Best Effort Application Transport protocol
|
||||
// ==========================================
|
||||
|
||||
typedef struct PACKED BestEffortPacket {
|
||||
net16 protocol;
|
||||
net16 length;
|
||||
char information[];
|
||||
} BestEffortPacket;
|
||||
|
||||
static PulseControlMessageProtocol s_best_effort_pcmp = {
|
||||
.send_begin_fn = pulse_best_effort_send_begin,
|
||||
.send_fn = pulse_best_effort_send,
|
||||
};
|
||||
|
||||
void pulse2_best_effort_transport_on_packet(void *raw_packet, size_t length) {
|
||||
if (!s_layer_up) {
|
||||
return;
|
||||
}
|
||||
|
||||
BestEffortPacket *packet = raw_packet;
|
||||
if (length < ntoh16(packet->length)) {
|
||||
// Packet truncated; discard
|
||||
return;
|
||||
}
|
||||
size_t info_length = ntoh16(packet->length) - sizeof(BestEffortPacket);
|
||||
// This variable is read in the macro-expansion below, but linters
|
||||
// have a hard time figuring that out.
|
||||
(void)info_length;
|
||||
|
||||
switch (ntoh16(packet->protocol)) {
|
||||
case PULSE_CONTROL_MESSAGE_PROTOCOL:
|
||||
pulse_control_message_protocol_on_packet(
|
||||
&s_best_effort_pcmp, packet->information, info_length);
|
||||
break;
|
||||
#define REGISTER_PROTOCOL(N, HANDLER, LAYER_STATE_HANDLER) \
|
||||
case N: \
|
||||
HANDLER(packet->information, info_length); \
|
||||
break;
|
||||
#include "console/pulse_protocol_registry.def"
|
||||
#undef REGISTER_PROTOCOL
|
||||
default:
|
||||
pulse_control_message_protocol_send_port_closed_message(
|
||||
&s_best_effort_pcmp, packet->protocol);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void *pulse_best_effort_send_begin(const uint16_t app_protocol) {
|
||||
PBL_ASSERTN(s_layer_up);
|
||||
BestEffortPacket *packet = pulse_link_send_begin(
|
||||
PULSE2_BEST_EFFORT_TRANSPORT_PROTOCOL);
|
||||
packet->protocol = hton16(app_protocol);
|
||||
return &packet->information;
|
||||
}
|
||||
|
||||
void pulse_best_effort_send(void *buf, const size_t length) {
|
||||
PBL_ASSERTN(s_layer_up);
|
||||
PBL_ASSERT(length <= pulse_link_max_send_size() - sizeof(BestEffortPacket),
|
||||
"Packet too big to send");
|
||||
// We're blindly assuming that buf is the same pointer returned by
|
||||
// pulse_best_effort_send_begin. If it isn't, we'll either crash here
|
||||
// when trying to dereference it or we'll hit the assert in
|
||||
// pulse_link_send.
|
||||
BestEffortPacket *packet =
|
||||
(void *)((char *)buf - offsetof(BestEffortPacket, information));
|
||||
size_t packet_size = length + sizeof(BestEffortPacket);
|
||||
packet->length = hton16(packet_size);
|
||||
pulse_link_send(packet, packet_size);
|
||||
}
|
||||
|
||||
// Shared events
|
||||
// =============
|
||||
void pulse2_best_effort_on_link_up(void) {
|
||||
ppp_control_protocol_lower_layer_is_up(PULSE2_BECP);
|
||||
}
|
||||
|
||||
void pulse2_best_effort_on_link_down(void) {
|
||||
ppp_control_protocol_lower_layer_is_down(PULSE2_BECP);
|
||||
}
|
||||
|
||||
void pulse2_best_effort_init(void) {
|
||||
ppp_control_protocol_init(PULSE2_BECP);
|
||||
ppp_control_protocol_open(PULSE2_BECP);
|
||||
}
|
||||
|
||||
#endif
|
113
src/fw/console/cobs.c
Normal file
113
src/fw/console/cobs.c
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "console/cobs.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util/likely.h"
|
||||
|
||||
void cobs_streaming_decode_start(CobsDecodeContext * restrict ctx,
|
||||
void * restrict output_buffer,
|
||||
size_t length) {
|
||||
ctx->output = output_buffer;
|
||||
ctx->output_length = length;
|
||||
ctx->decoded_length = 0;
|
||||
ctx->payload_remaining = 0;
|
||||
ctx->block_is_terminated = false;
|
||||
}
|
||||
|
||||
bool cobs_streaming_decode(CobsDecodeContext * restrict ctx, char in) {
|
||||
if (ctx->output == NULL) {
|
||||
// Uninitialized context or decoding has already failed.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UNLIKELY(in == '\0')) {
|
||||
// Zero byte is never allowed in a COBS stream.
|
||||
ctx->output = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UNLIKELY(ctx->payload_remaining == 0)) {
|
||||
// Incoming byte is a code byte.
|
||||
ctx->payload_remaining = (uint8_t)in - 1;
|
||||
if (ctx->decoded_length + ctx->payload_remaining +
|
||||
(ctx->block_is_terminated? 1 : 0) > ctx->output_length) {
|
||||
// Full decoded output cannot fit into the buffer; fail fast.
|
||||
ctx->output = NULL;
|
||||
return false;
|
||||
}
|
||||
// Since we've started a new block, write out the trailing zero left over
|
||||
// from the previous block. This wasn't done when the last character of the
|
||||
// previous block was written out as it could have been the last block in
|
||||
// the COBS stream.
|
||||
if (ctx->block_is_terminated) {
|
||||
ctx->output[ctx->decoded_length++] = '\0';
|
||||
}
|
||||
ctx->block_is_terminated = (in != '\xff');
|
||||
} else {
|
||||
// Incoming byte is contained within a COBS block.
|
||||
// It is safe to assume that there is enough space in the buffer for the
|
||||
// incoming byte as that check has already been performed when the code byte
|
||||
// was received.
|
||||
ctx->output[ctx->decoded_length++] = in;
|
||||
ctx->payload_remaining -= 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t cobs_streaming_decode_finish(CobsDecodeContext * restrict ctx) {
|
||||
if (ctx->output == NULL || ctx->payload_remaining != 0) {
|
||||
return SIZE_MAX;
|
||||
}
|
||||
return ctx->decoded_length;
|
||||
}
|
||||
|
||||
size_t cobs_encode(void *dst_ptr, const void *src_ptr, size_t length) {
|
||||
const char *src = src_ptr;
|
||||
char *dst = dst_ptr;
|
||||
uint8_t code = 0x01;
|
||||
size_t code_idx = 0;
|
||||
size_t dst_idx = 1;
|
||||
|
||||
for (size_t src_idx = 0; src_idx < length; ++src_idx) {
|
||||
if (src[src_idx] == '\0') {
|
||||
dst[code_idx] = code;
|
||||
code_idx = dst_idx++;
|
||||
code = 0x01;
|
||||
} else {
|
||||
dst[dst_idx++] = src[src_idx];
|
||||
code++;
|
||||
if (code == 0xff) {
|
||||
if (src_idx == length - 1) {
|
||||
// Special case: the final encoded block is 254 bytes long with no
|
||||
// zero after it. While it's technically a valid encoding if a
|
||||
// trailing zero is appended, it causes the output to be one byte
|
||||
// longer than it needs to be. This violates consistent overhead
|
||||
// contract and could overflow a carefully sized buffer.
|
||||
break;
|
||||
}
|
||||
dst[code_idx] = code;
|
||||
code_idx = dst_idx++;
|
||||
code = 0x01;
|
||||
}
|
||||
}
|
||||
}
|
||||
dst[code_idx] = code;
|
||||
return dst_idx;
|
||||
}
|
64
src/fw/console/cobs.h
Normal file
64
src/fw/console/cobs.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! An implementation of Consistent Overhead Byte Stuffing
|
||||
//!
|
||||
//! http://conferences.sigcomm.org/sigcomm/1997/papers/p062.pdf
|
||||
//! http://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
|
||||
|
||||
//! Evaluates to the offset required when encoding in-place
|
||||
#define COBS_OVERHEAD(n) (((n) + 253) / 254)
|
||||
//! Evaluates to the maximum buffer size required to hold n bytes of data
|
||||
//! after COBS encoding.
|
||||
#define MAX_SIZE_AFTER_COBS_ENCODING(n) ((n) + COBS_OVERHEAD(n))
|
||||
|
||||
typedef struct CobsDecodeContext {
|
||||
char *output;
|
||||
size_t output_length;
|
||||
size_t decoded_length;
|
||||
uint8_t payload_remaining;
|
||||
bool block_is_terminated;
|
||||
} CobsDecodeContext;
|
||||
|
||||
//! Initialize the COBS decoding context.
|
||||
void cobs_streaming_decode_start(CobsDecodeContext * restrict ctx,
|
||||
void * restrict output_buffer,
|
||||
size_t length);
|
||||
|
||||
//! Decode a byte in the COBS stream.
|
||||
//!
|
||||
//! @return false if decoding has failed.
|
||||
bool cobs_streaming_decode(CobsDecodeContext * restrict ctx, char in);
|
||||
|
||||
//! Complete a COBS stream.
|
||||
//!
|
||||
//! @return length of decoded stream, or SIZE_MAX if decoding has failed.
|
||||
size_t cobs_streaming_decode_finish(CobsDecodeContext * restrict ctx);
|
||||
|
||||
//! COBS-encode a buffer out to another buffer.
|
||||
//!
|
||||
//! @param [out] dst destination buffer. The buffer must be at least
|
||||
//! MAX_SIZE_AFTER_COBS_ENCODING(length) bytes long.
|
||||
//! @param [in] src source buffer
|
||||
//! @param length length of src
|
||||
size_t cobs_encode(void * restrict dst, const void * restrict src,
|
||||
size_t length);
|
35
src/fw/console/console_internal.h
Normal file
35
src/fw/console/console_internal.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef enum SerialConsoleState {
|
||||
SERIAL_CONSOLE_STATE_PROMPT,
|
||||
SERIAL_CONSOLE_STATE_LOGGING,
|
||||
#ifdef UI_DEBUG
|
||||
SERIAL_CONSOLE_STATE_LAYER_NUDGING,
|
||||
#endif
|
||||
SERIAL_CONSOLE_STATE_HCI_PASSTHROUGH,
|
||||
SERIAL_CONSOLE_STATE_ACCESSORY_PASSTHROUGH,
|
||||
SERIAL_CONSOLE_STATE_PROFILER,
|
||||
SERIAL_CONSOLE_STATE_PULSE,
|
||||
SERIAL_CONSOLE_NUM_STATES
|
||||
} SerialConsoleState;
|
||||
|
||||
// This function cannot be called in a > systick priority IRQ
|
||||
void serial_console_set_state(SerialConsoleState new_state);
|
||||
|
||||
SerialConsoleState serial_console_get_state(void);
|
511
src/fw/console/control_protocol.c
Normal file
511
src/fw/console/control_protocol.c
Normal file
|
@ -0,0 +1,511 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "console/control_protocol.h"
|
||||
#include "console/control_protocol_impl.h"
|
||||
|
||||
#include "console/pulse2_transport_impl.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include <util/attributes.h>
|
||||
#include <util/math.h>
|
||||
#include <util/net.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_CONFIGURE (10)
|
||||
#define MAX_TERMINATE (2)
|
||||
#define RESTART_TIMEOUT_MS (150)
|
||||
|
||||
#define LCP_HEADER_LEN (sizeof(LCPPacket))
|
||||
|
||||
|
||||
static void prv_on_timeout(void *context);
|
||||
|
||||
static void prv_start_timer(PPPControlProtocol *this) {
|
||||
PBL_ASSERTN(this->state->restart_timer != TIMER_INVALID_ID);
|
||||
new_timer_start(this->state->restart_timer, RESTART_TIMEOUT_MS,
|
||||
prv_on_timeout, (void *)this, 0);
|
||||
}
|
||||
|
||||
static void prv_stop_timer(PPPControlProtocol *this) {
|
||||
new_timer_stop(this->state->restart_timer);
|
||||
}
|
||||
|
||||
static void prv_transition_to(PPPControlProtocol *this,
|
||||
enum LinkState nextstate) {
|
||||
if (nextstate == LinkState_Initial ||
|
||||
nextstate == LinkState_Starting ||
|
||||
nextstate == LinkState_Closed ||
|
||||
nextstate == LinkState_Stopped ||
|
||||
nextstate == LinkState_Opened) {
|
||||
prv_stop_timer(this);
|
||||
}
|
||||
|
||||
if (nextstate == LinkState_Opened &&
|
||||
this->state->link_state != LinkState_Opened) {
|
||||
this->on_this_layer_up(this);
|
||||
}
|
||||
if (this->state->link_state == LinkState_Opened &&
|
||||
nextstate != LinkState_Opened) {
|
||||
this->on_this_layer_down(this);
|
||||
}
|
||||
|
||||
this->state->link_state = nextstate;
|
||||
}
|
||||
|
||||
static void prv_send_configure_request(PPPControlProtocol *this) {
|
||||
this->state->restart_count--;
|
||||
prv_start_timer(this);
|
||||
// Don't try to be fancy about changing the request identifier only when
|
||||
// necessary; keep it simple and increment it for every request sent.
|
||||
uint8_t id = this->state->last_configure_request_id + 1;
|
||||
this->state->last_configure_request_id = id;
|
||||
|
||||
struct LCPPacket *request = pulse_link_send_begin(this->protocol_number);
|
||||
*request = (struct LCPPacket) {
|
||||
.code = ControlCode_ConfigureRequest,
|
||||
.identifier = id,
|
||||
.length = hton16(LCP_HEADER_LEN),
|
||||
};
|
||||
pulse_link_send(request, LCP_HEADER_LEN);
|
||||
}
|
||||
|
||||
static void prv_send_configure_ack(PPPControlProtocol *this,
|
||||
struct LCPPacket *triggering_packet) {
|
||||
if (ntoh16(triggering_packet->length) > pulse_link_max_send_size()) {
|
||||
// Too big to send and truncation will corrupt the packet.
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Configure-Request too large to Ack");
|
||||
return;
|
||||
}
|
||||
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
|
||||
memcpy(packet, triggering_packet, ntoh16(triggering_packet->length));
|
||||
packet->code = ControlCode_ConfigureAck;
|
||||
pulse_link_send(packet, ntoh16(triggering_packet->length));
|
||||
}
|
||||
|
||||
static void prv_send_configure_reject(PPPControlProtocol *this,
|
||||
struct LCPPacket *bad_packet) {
|
||||
if (ntoh16(bad_packet->length) > pulse_link_max_send_size()) {
|
||||
// Too big to send and truncation will corrupt the packet.
|
||||
// There isn't really anything we can do.
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Configure-Request too large to Reject");
|
||||
return;
|
||||
}
|
||||
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
|
||||
memcpy(packet, bad_packet, ntoh16(bad_packet->length));
|
||||
packet->code = ControlCode_ConfigureReject;
|
||||
pulse_link_send(packet, ntoh16(bad_packet->length));
|
||||
}
|
||||
|
||||
static void prv_send_terminate_request(PPPControlProtocol *this) {
|
||||
this->state->restart_count--;
|
||||
prv_start_timer(this);
|
||||
uint8_t id = this->state->next_terminate_id++;
|
||||
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
|
||||
*packet = (struct LCPPacket) {
|
||||
.code = ControlCode_TerminateRequest,
|
||||
.identifier = id,
|
||||
.length = hton16(LCP_HEADER_LEN),
|
||||
};
|
||||
pulse_link_send(packet, LCP_HEADER_LEN);
|
||||
}
|
||||
|
||||
static void prv_send_terminate_ack(PPPControlProtocol *this, int identifier) {
|
||||
if (identifier < 0) { // Not in response to a Terminate-Request
|
||||
// Pick an arbitrary identifier to send in ack
|
||||
identifier = this->state->next_terminate_id++;
|
||||
} else {
|
||||
// Update the next-terminate-id so that the next ack sent not in response to
|
||||
// a Terminate-Request does not look like a retransmission.
|
||||
this->state->next_terminate_id = identifier + 1;
|
||||
}
|
||||
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
|
||||
*packet = (struct LCPPacket) {
|
||||
.code = ControlCode_TerminateAck,
|
||||
.identifier = identifier,
|
||||
.length = hton16(LCP_HEADER_LEN),
|
||||
};
|
||||
pulse_link_send(packet, LCP_HEADER_LEN);
|
||||
}
|
||||
|
||||
static void prv_send_code_reject(PPPControlProtocol *this,
|
||||
struct LCPPacket *bad_packet) {
|
||||
struct LCPPacket *packet = pulse_link_send_begin(this->protocol_number);
|
||||
packet->code = ControlCode_CodeReject;
|
||||
packet->identifier = this->state->next_code_reject_id++;
|
||||
size_t body_len = MIN(ntoh16(bad_packet->length),
|
||||
pulse_link_max_send_size() - LCP_HEADER_LEN);
|
||||
memcpy(packet->data, bad_packet, body_len);
|
||||
pulse_link_send(packet, LCP_HEADER_LEN + body_len);
|
||||
}
|
||||
|
||||
static void prv_on_timeout(void *context) {
|
||||
PPPControlProtocol *this = context;
|
||||
mutex_lock(this->state->lock);
|
||||
if (this->state->restart_count > 0) { // TO+
|
||||
switch (this->state->link_state) {
|
||||
case LinkState_Closing:
|
||||
case LinkState_Stopping:
|
||||
prv_send_terminate_request(this);
|
||||
break;
|
||||
case LinkState_RequestSent:
|
||||
case LinkState_AckReceived:
|
||||
case LinkState_AckSent:
|
||||
prv_send_configure_request(this);
|
||||
if (this->state->link_state == LinkState_AckReceived) {
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else { // TO-
|
||||
switch (this->state->link_state) {
|
||||
case LinkState_Stopping:
|
||||
case LinkState_RequestSent:
|
||||
case LinkState_AckReceived:
|
||||
case LinkState_AckSent:
|
||||
prv_transition_to(this, LinkState_Stopped);
|
||||
break;
|
||||
case LinkState_Closing:
|
||||
prv_transition_to(this, LinkState_Closed);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(this->state->lock);
|
||||
}
|
||||
|
||||
static bool prv_handle_configure_request(PPPControlProtocol *this,
|
||||
struct LCPPacket *packet) {
|
||||
if (ntoh16(packet->length) == LCP_HEADER_LEN) { // The request has no options
|
||||
prv_send_configure_ack(this, packet);
|
||||
return true;
|
||||
} else {
|
||||
// Packet has options but we don't support any options yet.
|
||||
prv_send_configure_reject(this, packet);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_on_configure_request(PPPControlProtocol *this,
|
||||
struct LCPPacket *packet) {
|
||||
switch (this->state->link_state) {
|
||||
case LinkState_Closing:
|
||||
case LinkState_Stopping:
|
||||
// Do nothing
|
||||
break;
|
||||
case LinkState_Closed:
|
||||
prv_send_terminate_ack(this, -1);
|
||||
break;
|
||||
case LinkState_Stopped:
|
||||
this->state->restart_count = MAX_CONFIGURE;
|
||||
// fallthrough
|
||||
case LinkState_Opened:
|
||||
prv_send_configure_request(this);
|
||||
// fallthrough
|
||||
case LinkState_RequestSent:
|
||||
case LinkState_AckSent:
|
||||
if (prv_handle_configure_request(this, packet)) {
|
||||
prv_transition_to(this, LinkState_AckSent);
|
||||
} else {
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
}
|
||||
break;
|
||||
case LinkState_AckReceived:
|
||||
if (prv_handle_configure_request(this, packet)) {
|
||||
prv_transition_to(this, LinkState_Opened);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_on_configure_ack(PPPControlProtocol *this,
|
||||
struct LCPPacket *packet) {
|
||||
if (packet->identifier != this->state->last_configure_request_id) {
|
||||
// Invalid packet; silently discard
|
||||
return;
|
||||
}
|
||||
if (ntoh16(packet->length) != LCP_HEADER_LEN) {
|
||||
// Only configure requests with no options are sent at the moment.
|
||||
// If the length is greater than four, there are options in the Ack
|
||||
// which means that the Ack'ed options list does not match the
|
||||
// options list from the request. The Ack packet is invalid.
|
||||
PBL_LOG(LOG_LEVEL_WARNING,
|
||||
"Configure-Ack received with options list which differs from "
|
||||
"the sent Configure-Request. Discarding.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->state->link_state) {
|
||||
case LinkState_Closed:
|
||||
case LinkState_Stopped:
|
||||
prv_send_terminate_ack(this, -1);
|
||||
break;
|
||||
case LinkState_Closing:
|
||||
case LinkState_Stopping:
|
||||
// Do nothing
|
||||
break;
|
||||
case LinkState_RequestSent:
|
||||
this->state->restart_count = MAX_CONFIGURE;
|
||||
prv_transition_to(this, LinkState_AckReceived);
|
||||
break;
|
||||
case LinkState_AckReceived:
|
||||
case LinkState_Opened:
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Unexpected duplicate Configure-Ack");
|
||||
prv_send_configure_request(this);
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
break;
|
||||
case LinkState_AckSent:
|
||||
this->state->restart_count = MAX_CONFIGURE;
|
||||
prv_transition_to(this, LinkState_Opened);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_nak_or_reject(PPPControlProtocol *this,
|
||||
struct LCPPacket *packet) {
|
||||
// Process nak/rej options
|
||||
// respond with new configure request
|
||||
// TODO: we don't send options, so no nak/rej is expected yet
|
||||
}
|
||||
|
||||
static void prv_on_configure_nak_or_reject(PPPControlProtocol *this,
|
||||
struct LCPPacket *packet) {
|
||||
if (packet->identifier != this->state->last_configure_request_id) {
|
||||
// Invalid packet; silently discard
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->state->link_state) {
|
||||
case LinkState_Closed:
|
||||
case LinkState_Stopped:
|
||||
prv_send_terminate_ack(this, -1);
|
||||
break;
|
||||
case LinkState_Closing:
|
||||
case LinkState_Stopping:
|
||||
// Do nothing
|
||||
break;
|
||||
case LinkState_RequestSent:
|
||||
this->state->restart_count = MAX_CONFIGURE;
|
||||
// fallthrough
|
||||
case LinkState_AckReceived:
|
||||
case LinkState_Opened:
|
||||
PBL_LOG(LOG_LEVEL_WARNING,
|
||||
"Unexpected Configure-Nak/Rej received after Ack");
|
||||
prv_handle_nak_or_reject(this, packet);
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
break;
|
||||
case LinkState_AckSent:
|
||||
prv_handle_nak_or_reject(this, packet);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_on_terminate_request(PPPControlProtocol *this,
|
||||
struct LCPPacket *packet) {
|
||||
if (this->state->link_state == LinkState_AckReceived ||
|
||||
this->state->link_state == LinkState_AckSent) {
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
} else if (this->state->link_state == LinkState_Opened) {
|
||||
this->state->restart_count = 0;
|
||||
prv_start_timer(this);
|
||||
prv_transition_to(this, LinkState_Stopping);
|
||||
}
|
||||
prv_send_terminate_ack(this, packet->identifier);
|
||||
}
|
||||
|
||||
static void prv_on_terminate_ack(PPPControlProtocol *this,
|
||||
struct LCPPacket *packet) {
|
||||
if (this->state->link_state == LinkState_Closing) {
|
||||
prv_transition_to(this, LinkState_Closed);
|
||||
} else if (this->state->link_state == LinkState_Stopping) {
|
||||
prv_transition_to(this, LinkState_Stopped);
|
||||
} else if (this->state->link_state == LinkState_AckReceived) {
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
} else if (this->state->link_state == LinkState_Opened) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Terminate-Ack received on an open connection");
|
||||
prv_send_configure_request(this);
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
}
|
||||
}
|
||||
|
||||
// Protected interface (control_protocol_impl.h)
|
||||
// =============================================
|
||||
|
||||
void ppp_control_protocol_init(PPPControlProtocol *this) {
|
||||
*this->state = (PPPControlProtocolState) {
|
||||
.lock = mutex_create(),
|
||||
.link_state = LinkState_Initial,
|
||||
.restart_count = 0,
|
||||
.restart_timer = new_timer_create(),
|
||||
.last_configure_request_id = -1,
|
||||
.next_code_reject_id = 0,
|
||||
.next_terminate_id = 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Public interface (control_protocol.h)
|
||||
// =====================================
|
||||
|
||||
void ppp_control_protocol_lower_layer_is_up(PPPControlProtocol *this) {
|
||||
mutex_lock(this->state->lock);
|
||||
if (this->state->link_state == LinkState_Initial) {
|
||||
prv_transition_to(this, LinkState_Closed);
|
||||
} else if (this->state->link_state == LinkState_Starting) {
|
||||
this->state->restart_count = MAX_CONFIGURE;
|
||||
prv_send_configure_request(this);
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
}
|
||||
mutex_unlock(this->state->lock);
|
||||
}
|
||||
|
||||
void ppp_control_protocol_lower_layer_is_down(PPPControlProtocol *this) {
|
||||
mutex_lock(this->state->lock);
|
||||
switch (this->state->link_state) {
|
||||
case LinkState_Closed:
|
||||
case LinkState_Closing:
|
||||
prv_transition_to(this, LinkState_Initial);
|
||||
break;
|
||||
case LinkState_Stopped:
|
||||
case LinkState_Stopping:
|
||||
case LinkState_RequestSent:
|
||||
case LinkState_AckReceived:
|
||||
case LinkState_AckSent:
|
||||
case LinkState_Opened:
|
||||
prv_transition_to(this, LinkState_Starting);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mutex_unlock(this->state->lock);
|
||||
}
|
||||
|
||||
void ppp_control_protocol_open(PPPControlProtocol *this) {
|
||||
mutex_lock(this->state->lock);
|
||||
if (this->state->link_state == LinkState_Initial) {
|
||||
prv_transition_to(this, LinkState_Starting);
|
||||
} else if (this->state->link_state == LinkState_Closed) {
|
||||
this->state->restart_count = MAX_CONFIGURE;
|
||||
prv_send_configure_request(this);
|
||||
prv_transition_to(this, LinkState_RequestSent);
|
||||
} else if (this->state->link_state == LinkState_Stopping) {
|
||||
prv_transition_to(this, LinkState_Closing);
|
||||
}
|
||||
mutex_unlock(this->state->lock);
|
||||
}
|
||||
|
||||
void ppp_control_protocol_close(PPPControlProtocol *this,
|
||||
PPPCPCloseWait wait) {
|
||||
mutex_lock(this->state->lock);
|
||||
switch (this->state->link_state) {
|
||||
case LinkState_Starting:
|
||||
prv_transition_to(this, LinkState_Initial);
|
||||
break;
|
||||
case LinkState_Stopped:
|
||||
prv_transition_to(this, LinkState_Closed);
|
||||
break;
|
||||
case LinkState_RequestSent:
|
||||
case LinkState_AckReceived:
|
||||
case LinkState_AckSent:
|
||||
case LinkState_Opened:
|
||||
this->state->restart_count = MAX_TERMINATE;
|
||||
prv_send_terminate_request(this);
|
||||
// fallthrough
|
||||
case LinkState_Stopping:
|
||||
prv_transition_to(this, LinkState_Closing);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mutex_unlock(this->state->lock);
|
||||
|
||||
if (wait == PPPCPCloseWait_WaitForClosed) {
|
||||
// Poll for the state machine to finish closing.
|
||||
while (1) {
|
||||
mutex_lock(this->state->lock);
|
||||
LinkState state = this->state->link_state;
|
||||
mutex_unlock(this->state->lock);
|
||||
if (state == LinkState_Initial || state == LinkState_Closed) {
|
||||
return;
|
||||
}
|
||||
psleep(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ppp_control_protocol_handle_incoming_packet(
|
||||
PPPControlProtocol *this, void *raw_packet, size_t length) {
|
||||
mutex_lock(this->state->lock);
|
||||
if (this->state->link_state == LinkState_Initial ||
|
||||
this->state->link_state == LinkState_Starting) {
|
||||
// No packets should be received while the lower layer is down;
|
||||
// silently discard.
|
||||
goto done;
|
||||
}
|
||||
|
||||
struct LCPPacket *packet = raw_packet;
|
||||
if (length < sizeof(*packet) ||
|
||||
ntoh16(packet->length) < sizeof(*packet) ||
|
||||
length < ntoh16(packet->length)) {
|
||||
// Invalid packet; silently discard
|
||||
goto done;
|
||||
}
|
||||
|
||||
switch (packet->code) {
|
||||
case ControlCode_ConfigureRequest:
|
||||
prv_on_configure_request(this, packet);
|
||||
break;
|
||||
case ControlCode_ConfigureAck:
|
||||
prv_on_configure_ack(this, packet);
|
||||
break;
|
||||
case ControlCode_ConfigureNak:
|
||||
case ControlCode_ConfigureReject:
|
||||
prv_on_configure_nak_or_reject(this, packet);
|
||||
break;
|
||||
case ControlCode_TerminateRequest:
|
||||
prv_on_terminate_request(this, packet);
|
||||
break;
|
||||
case ControlCode_TerminateAck:
|
||||
prv_on_terminate_ack(this, packet);
|
||||
break;
|
||||
case ControlCode_CodeReject:
|
||||
this->on_receive_code_reject(this, packet);
|
||||
break;
|
||||
default:
|
||||
if (!this->on_receive_unrecognized_code ||
|
||||
!this->on_receive_unrecognized_code(this, packet)) {
|
||||
prv_send_code_reject(this, packet);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
done:
|
||||
mutex_unlock(this->state->lock);
|
||||
}
|
45
src/fw/console/control_protocol.h
Normal file
45
src/fw/console/control_protocol.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef const struct PPPControlProtocol PPPControlProtocol;
|
||||
|
||||
typedef enum PPPCPCloseWait {
|
||||
PPPCPCloseWait_NoWait,
|
||||
PPPCPCloseWait_WaitForClosed
|
||||
} PPPCPCloseWait;
|
||||
|
||||
//! Notify the control protocol that the lower layer is ready to carry traffic.
|
||||
void ppp_control_protocol_lower_layer_is_up(PPPControlProtocol *protocol);
|
||||
|
||||
//! Notify the control protocol that the lower layer is no longer ready
|
||||
//! to carry traffic.
|
||||
void ppp_control_protocol_lower_layer_is_down(PPPControlProtocol *protocol);
|
||||
|
||||
//! Notify the control protocol that the layer is administratively available
|
||||
//! for carrying traffic.
|
||||
void ppp_control_protocol_open(PPPControlProtocol *protocol);
|
||||
|
||||
//! Notify the control protocol that the layer is not allowed to be opened.
|
||||
void ppp_control_protocol_close(PPPControlProtocol *protocol,
|
||||
PPPCPCloseWait wait);
|
||||
|
||||
//! Pass an incoming packet to the control protocol.
|
||||
void ppp_control_protocol_handle_incoming_packet(PPPControlProtocol *protocol,
|
||||
void *packet, size_t length);
|
99
src/fw/console/control_protocol_impl.h
Normal file
99
src/fw/console/control_protocol_impl.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "os/mutex.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include <util/attributes.h>
|
||||
#include <util/net.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct PACKED LCPPacket {
|
||||
uint8_t code;
|
||||
uint8_t identifier;
|
||||
net16 length;
|
||||
char data[];
|
||||
} LCPPacket;
|
||||
|
||||
typedef enum ControlCode {
|
||||
ControlCode_ConfigureRequest = 1,
|
||||
ControlCode_ConfigureAck = 2,
|
||||
ControlCode_ConfigureNak = 3,
|
||||
ControlCode_ConfigureReject = 4,
|
||||
ControlCode_TerminateRequest = 5,
|
||||
ControlCode_TerminateAck = 6,
|
||||
ControlCode_CodeReject = 7,
|
||||
ControlCode_ProtocolReject = 8,
|
||||
ControlCode_EchoRequest = 9,
|
||||
ControlCode_EchoReply = 10,
|
||||
ControlCode_DiscardRequest = 11,
|
||||
ControlCode_Identification = 12,
|
||||
} ControlCode;
|
||||
|
||||
typedef enum LinkState {
|
||||
LinkState_Initial, //!< Lower layer is Down; this layer is Closed
|
||||
LinkState_Starting, //!< Lower layer is Down; this layer is Open
|
||||
LinkState_Closed, //!< Lower layer is Up; this layer is Closed
|
||||
LinkState_Stopped, //!< Waiting passively for a new connection
|
||||
LinkState_Closing, //!< Connection is being terminated before Closed
|
||||
LinkState_Stopping, //!< Connection is being terminated before Stopped
|
||||
LinkState_RequestSent, //!< Configure-Request sent
|
||||
LinkState_AckReceived, //!< Configure-Request sent, Configure-Ack received
|
||||
LinkState_AckSent, //!< Configure-Request and Configure-Ack sent
|
||||
LinkState_Opened,
|
||||
} LinkState;
|
||||
|
||||
typedef struct PPPControlProtocolState {
|
||||
PebbleMutex *lock;
|
||||
LinkState link_state;
|
||||
int restart_count;
|
||||
TimerID restart_timer;
|
||||
int last_configure_request_id;
|
||||
uint8_t next_code_reject_id;
|
||||
uint8_t next_terminate_id;
|
||||
} PPPControlProtocolState;
|
||||
|
||||
typedef const struct PPPControlProtocol PPPControlProtocol;
|
||||
struct PPPControlProtocol {
|
||||
PPPControlProtocolState * const state;
|
||||
//! Called when the layer is ready to carry traffic.
|
||||
void (*const on_this_layer_up)(PPPControlProtocol *this);
|
||||
//! Called when the layer is no longe ready to carry traffic.
|
||||
void (*const on_this_layer_down)(PPPControlProtocol *this);
|
||||
//! Called when a Code-Reject packet is received.
|
||||
void (*const on_receive_code_reject)(PPPControlProtocol *this,
|
||||
LCPPacket *packet);
|
||||
//! Called when a packet is received with a code not handled by the
|
||||
//! base Control Protocol implementation. May be NULL if no extended
|
||||
//! codes are supported by the implementation.
|
||||
//!
|
||||
//! \return true if the code is handled, false if the code is also
|
||||
//! unknown to the implementation.
|
||||
//!
|
||||
//! If the code is unknown to the implementation, a Code-Reject
|
||||
//! response packet is sent by the base Control Protocol
|
||||
//! implementation.
|
||||
bool (*const on_receive_unrecognized_code)(PPPControlProtocol *this,
|
||||
LCPPacket *packet);
|
||||
//! PPP Encapsulation protocol number for the control protocol.
|
||||
uint16_t protocol_number;
|
||||
};
|
||||
|
||||
//! Initialize the state struct for a PPPControlProtocol
|
||||
void ppp_control_protocol_init(PPPControlProtocol *this);
|
74
src/fw/console/dbgserial.c
Normal file
74
src/fw/console/dbgserial.c
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "dbgserial.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/uart.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
#if PULSE_EVERYWHERE
|
||||
#define DEFAULT_SERIAL_BAUD_RATE 1000000
|
||||
#else
|
||||
#define DEFAULT_SERIAL_BAUD_RATE 230400
|
||||
#endif
|
||||
|
||||
|
||||
void dbgserial_init(void) {
|
||||
uart_init(DBG_UART);
|
||||
dbgserial_restore_baud_rate();
|
||||
}
|
||||
|
||||
void dbgserial_change_baud_rate(uint32_t new_baud) {
|
||||
uart_set_baud_rate(DBG_UART, new_baud);
|
||||
}
|
||||
|
||||
void dbgserial_restore_baud_rate(void) {
|
||||
dbgserial_change_baud_rate(DEFAULT_SERIAL_BAUD_RATE);
|
||||
}
|
||||
|
||||
void dbgserial_putstr(const char* str) {
|
||||
while (*str) {
|
||||
dbgserial_putchar(*str);
|
||||
++str;
|
||||
}
|
||||
dbgserial_putchar('\r');
|
||||
dbgserial_putchar('\n');
|
||||
}
|
||||
|
||||
void dbgserial_putchar(uint8_t c) {
|
||||
dbgserial_putchar_lazy(c);
|
||||
dbgserial_flush();
|
||||
}
|
||||
|
||||
void dbgserial_putchar_lazy(uint8_t c) {
|
||||
uart_write_byte(DBG_UART, c);
|
||||
}
|
||||
|
||||
void dbgserial_flush(void) {
|
||||
uart_wait_for_tx_complete(DBG_UART);
|
||||
}
|
||||
|
||||
void dbgserial_putstr_fmt(char* buffer, unsigned int buffer_size, const char* fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsniprintf(buffer, buffer_size, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
dbgserial_putstr(buffer);
|
||||
}
|
42
src/fw/console/dbgserial.h
Normal file
42
src/fw/console/dbgserial.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void dbgserial_init(void);
|
||||
|
||||
void dbgserial_putchar(uint8_t c);
|
||||
|
||||
//! Version of dbgserial_putchar that may return before the character is finished writing.
|
||||
//! Use if you don't need a guarantee that your character will be written.
|
||||
void dbgserial_putchar_lazy(uint8_t c);
|
||||
|
||||
void dbgserial_putstr(const char* str);
|
||||
|
||||
void dbgserial_putstr_fmt(char* buffer, unsigned int buffer_size, const char* fmt, ...)
|
||||
__attribute__((__format__(__printf__, 3, 4)));
|
||||
|
||||
void dbgserial_change_baud_rate(uint32_t new_baud);
|
||||
|
||||
//! Restore the dbgserial baud rate to the default (e.g. after a call to
|
||||
//! dbgserial_change_baud_rate).
|
||||
void dbgserial_restore_baud_rate(void);
|
||||
|
||||
//! Finish writing all characters to dbgserial output.
|
||||
void dbgserial_flush(void);
|
129
src/fw/console/dbgserial_input.c
Normal file
129
src/fw/console/dbgserial_input.c
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "dbgserial_input.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/dma.h"
|
||||
#include "drivers/exti.h"
|
||||
#include "drivers/uart.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "os/tick.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/likely.h"
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#define STOP_MODE_TIMEOUT_MS (2000)
|
||||
|
||||
static void dbgserial_interrupt_handler(bool *should_context_switch);
|
||||
|
||||
static DbgSerialCharacterCallback s_character_callback;
|
||||
static TimerID s_stop_mode_timeout_timer;
|
||||
//! Use a seperate variable so it's safe to check from the ISR.
|
||||
static bool s_stop_mode_inhibited = false;
|
||||
|
||||
//! We DMA into this buffer as a circular buffer
|
||||
#define DMA_BUFFER_LENGTH (200)
|
||||
static uint8_t DMA_BSS s_dma_buffer[DMA_BUFFER_LENGTH];
|
||||
static bool s_dma_enabled = false;
|
||||
|
||||
|
||||
static void stop_mode_timeout_timer_callback(void* cb_data) {
|
||||
// re-enable stop mode
|
||||
if (s_stop_mode_inhibited) {
|
||||
stop_mode_enable(InhibitorDbgSerial);
|
||||
s_stop_mode_inhibited = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_uart_irq_handler(UARTDevice *dev, uint8_t data, const UARTRXErrorFlags *err_flags) {
|
||||
bool should_context_switch = false;
|
||||
if (s_character_callback) {
|
||||
s_character_callback(data, &should_context_switch);
|
||||
}
|
||||
return should_context_switch;
|
||||
}
|
||||
|
||||
void dbgserial_input_init(void) {
|
||||
exti_configure_pin(BOARD_CONFIG.dbgserial_int, ExtiTrigger_Falling, dbgserial_interrupt_handler);
|
||||
|
||||
// some platforms have a seperate pin for the EXTI int and the USART
|
||||
if (BOARD_CONFIG.dbgserial_int_gpio.gpio != NULL) {
|
||||
gpio_input_init(&BOARD_CONFIG.dbgserial_int_gpio);
|
||||
}
|
||||
|
||||
// set up the USART interrupt on RX
|
||||
uart_set_rx_interrupt_handler(DBG_UART, prv_uart_irq_handler);
|
||||
uart_set_rx_interrupt_enabled(DBG_UART, true);
|
||||
|
||||
s_stop_mode_timeout_timer = new_timer_create();
|
||||
|
||||
// Enable receive interrupts
|
||||
dbgserial_enable_rx_exti();
|
||||
}
|
||||
|
||||
void dbgserial_enable_rx_exti(void) {
|
||||
exti_enable(BOARD_CONFIG.dbgserial_int);
|
||||
}
|
||||
|
||||
void dbgserial_register_character_callback(DbgSerialCharacterCallback callback) {
|
||||
s_character_callback = callback;
|
||||
}
|
||||
|
||||
// This callback gets installed by dbgserial_interrupt_handler()
|
||||
// using system_task_add_callback_from_isr().
|
||||
// It is used to start up our timer since doing so from an ISR is not allowed.
|
||||
static void prv_start_timer_callback(void* data) {
|
||||
new_timer_start(s_stop_mode_timeout_timer, STOP_MODE_TIMEOUT_MS, stop_mode_timeout_timer_callback,
|
||||
NULL, 0 /*flags*/);
|
||||
}
|
||||
|
||||
static void dbgserial_interrupt_handler(bool *should_context_switch) {
|
||||
exti_disable(BOARD_CONFIG.dbgserial_int);
|
||||
|
||||
// Start the timer
|
||||
system_task_add_callback_from_isr(prv_start_timer_callback, (void *)0, should_context_switch);
|
||||
|
||||
if (!s_stop_mode_inhibited) {
|
||||
// We don't bother cancelling the timer if we leave the state where we don't want to stop mode
|
||||
// anymore. For example, if we ctrl-c to enter the prompt (disable stop and start timer),
|
||||
// ctrl-d to leave the prompt, and then ctrl-c again before the timer goes off, we'll have the
|
||||
// timer still running. If we were to disable stop again after rescheduling the timer, the timer
|
||||
// would only go off once for the two disables and we'd end up jamming the reference count.
|
||||
stop_mode_disable(InhibitorDbgSerial);
|
||||
s_stop_mode_inhibited = true;
|
||||
}
|
||||
}
|
||||
|
||||
void dbgserial_set_rx_dma_enabled(bool enabled) {
|
||||
#if TARGET_QEMU
|
||||
// we can't use DMA on QEMU
|
||||
enabled = false;
|
||||
#endif
|
||||
if (enabled == s_dma_enabled) {
|
||||
return;
|
||||
}
|
||||
s_dma_enabled = enabled;
|
||||
if (enabled) {
|
||||
uart_start_rx_dma(DBG_UART, s_dma_buffer, DMA_BUFFER_LENGTH);
|
||||
} else {
|
||||
uart_stop_rx_dma(DBG_UART);
|
||||
}
|
||||
}
|
35
src/fw/console/dbgserial_input.h
Normal file
35
src/fw/console/dbgserial_input.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
//! @file dbgserial_input.h
|
||||
//! Contains the input related functionality of the debug serial port.
|
||||
|
||||
//! Initializes the input portions of the dbgserial driver.
|
||||
void dbgserial_input_init(void);
|
||||
|
||||
typedef void (*DbgSerialCharacterCallback)(char c, bool* should_context_switch);
|
||||
void dbgserial_register_character_callback(DbgSerialCharacterCallback callback);
|
||||
|
||||
void dbgserial_enable_rx_exti(void);
|
||||
|
||||
//! Enables/disables DMA-based receiving
|
||||
void dbgserial_set_rx_dma_enabled(bool enabled);
|
452
src/fw/console/prompt.c
Normal file
452
src/fw/console/prompt.c
Normal file
|
@ -0,0 +1,452 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "prompt.h"
|
||||
#include "prompt_commands.h"
|
||||
|
||||
#include "console_internal.h"
|
||||
#include "dbgserial.h"
|
||||
#include "drivers/rtc.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "pulse_protocol_impl.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/likely.h"
|
||||
|
||||
#include "services/common/system_task.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define PROMPT_RESP_ACK (100)
|
||||
#define PROMPT_RESP_DONE (101)
|
||||
#define PROMPT_RESP_MESSAGE (102)
|
||||
|
||||
static void start_prompt(void);
|
||||
static void pulse_send_message(const int message_type, const char* response);
|
||||
static void prv_pulse_done_command(void);
|
||||
|
||||
static void prv_dbgserial_response_callback(const char* response) {
|
||||
if (serial_console_get_state() == SERIAL_CONSOLE_STATE_PULSE) {
|
||||
pulse_send_message(PROMPT_RESP_MESSAGE, response);
|
||||
} else {
|
||||
dbgserial_putstr(response);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_dbgserial_command_complete_callback(void) {
|
||||
SerialConsoleState state = serial_console_get_state();
|
||||
if (state == SERIAL_CONSOLE_STATE_PULSE) {
|
||||
prv_pulse_done_command();
|
||||
} else if (state == SERIAL_CONSOLE_STATE_PROMPT) {
|
||||
start_prompt();
|
||||
}
|
||||
}
|
||||
|
||||
static PromptContext s_dbgserial_prompt_context = {
|
||||
.response_callback = prv_dbgserial_response_callback,
|
||||
.command_complete_callback = prv_dbgserial_command_complete_callback,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
ExecutingCommandNone = 0,
|
||||
ExecutingCommandDbgSerial, //!< Currently executing command through dbgserial
|
||||
ExecutingCommandPulse, //!< Currently executing command is through Pulse
|
||||
ExecutingCommandContext, //!< Currently executing command is a custom \ref PromptContext
|
||||
} ExecutingCommand;
|
||||
|
||||
static bool s_command_continues_after_return = false;
|
||||
|
||||
static ExecutingCommand s_executing_command = ExecutingCommandNone;
|
||||
|
||||
//! Currently used prompt context. This is set so that we know which response
|
||||
//! and completion callbacks to use.
|
||||
static PromptContext *s_current_context = NULL;
|
||||
|
||||
static void start_prompt(void) {
|
||||
dbgserial_putchar('>');
|
||||
s_dbgserial_prompt_context.write_index = 0;
|
||||
}
|
||||
|
||||
void console_switch_to_prompt(void) {
|
||||
serial_console_set_state(SERIAL_CONSOLE_STATE_PROMPT);
|
||||
dbgserial_putstr("");
|
||||
start_prompt();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Prompt infrastructure
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef void (*CommandFuncNoParam)(void);
|
||||
typedef void (*CommandFuncOneParam)(const char*);
|
||||
typedef void (*CommandFuncTwoParams)(const char*, const char*);
|
||||
typedef void (*CommandFuncThreeParams)(const char*, const char*, const char*);
|
||||
typedef void (*CommandFuncFourParams)(const char*, const char*, const char*, const char*);
|
||||
|
||||
#define NUM_SUPPORTED_PARAM_COUNT 4
|
||||
|
||||
void command_help(void) {
|
||||
prompt_send_response("Available Commands:");
|
||||
char buffer[32];
|
||||
for (unsigned int i = 0; i < NUM_PROMPT_COMMANDS; ++i) {
|
||||
const Command* cmd = &s_prompt_commands[i];
|
||||
if (cmd->num_params) {
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer),
|
||||
"%s {%u args}", cmd->cmd_str, cmd->num_params);
|
||||
} else {
|
||||
prompt_send_response(cmd->cmd_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct CommandArgs {
|
||||
unsigned int num_args;
|
||||
const char* args[NUM_SUPPORTED_PARAM_COUNT];
|
||||
} CommandArgs;
|
||||
|
||||
static CommandArgs prv_parse_arguments(char* buffer, char* buffer_end) {
|
||||
CommandArgs args;
|
||||
|
||||
for (int i = 0; i < NUM_SUPPORTED_PARAM_COUNT; ++i) {
|
||||
// Consume leading whitespace
|
||||
while (buffer <= buffer_end && *buffer == ' ') {
|
||||
++buffer;
|
||||
}
|
||||
if (buffer >= buffer_end) {
|
||||
args.num_args = i;
|
||||
return args;
|
||||
}
|
||||
|
||||
// Yay, a param!
|
||||
args.args[i] = buffer;
|
||||
|
||||
// Skip over the param and set us up for the next one.
|
||||
while (buffer < buffer_end && *buffer != ' ') {
|
||||
++buffer;
|
||||
}
|
||||
*(buffer++) = 0;
|
||||
}
|
||||
|
||||
args.num_args = NUM_SUPPORTED_PARAM_COUNT;
|
||||
return args;
|
||||
}
|
||||
|
||||
static void prv_execute_given_command(const Command* cmd, char* param_str, char* param_str_end) {
|
||||
CommandArgs args = prv_parse_arguments(param_str, param_str_end);
|
||||
|
||||
if (args.num_args != cmd->num_params) {
|
||||
char buffer[128];
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer),
|
||||
"Incorrect number of arguments: Wanted %u Got %u",
|
||||
cmd->num_params, args.num_args);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (cmd->num_params == 4) {
|
||||
((CommandFuncFourParams) cmd->func)(args.args[0], args.args[1], args.args[2], args.args[3]);
|
||||
} else if (cmd->num_params == 3) {
|
||||
((CommandFuncThreeParams) cmd->func)(args.args[0], args.args[1], args.args[2]);
|
||||
} else if (cmd->num_params == 2) {
|
||||
((CommandFuncTwoParams) cmd->func)(args.args[0], args.args[1]);
|
||||
} else if (cmd->num_params == 1) {
|
||||
((CommandFuncOneParam) cmd->func)(args.args[0]);
|
||||
} else if (cmd->num_params == 0) {
|
||||
((CommandFuncNoParam) cmd->func)();
|
||||
}
|
||||
|
||||
done:
|
||||
if (!s_command_continues_after_return && s_current_context) {
|
||||
prompt_command_finish();
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_find_and_execute_command(char* cmd, size_t cmd_len, PromptContext *context) {
|
||||
if (!cmd_len) {
|
||||
// Empty command.
|
||||
s_executing_command = ExecutingCommandNone;
|
||||
return;
|
||||
}
|
||||
|
||||
s_current_context = context;
|
||||
|
||||
bool command_found = false;
|
||||
for (unsigned int i = 0; i < NUM_PROMPT_COMMANDS; ++i) {
|
||||
const Command *cmd_iter = &s_prompt_commands[i];
|
||||
|
||||
const size_t cmd_iter_length = strlen(cmd_iter->cmd_str);
|
||||
if (cmd_len >= cmd_iter_length &&
|
||||
memcmp(cmd_iter->cmd_str, cmd, cmd_iter_length) == 0) {
|
||||
|
||||
prv_execute_given_command(cmd_iter, cmd + cmd_iter_length, cmd + cmd_len);
|
||||
|
||||
command_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!command_found) {
|
||||
cmd[cmd_len] = '\0';
|
||||
|
||||
char buffer[64];
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "Invalid command <%s>! Try 'help'", cmd);
|
||||
prompt_command_finish();
|
||||
}
|
||||
}
|
||||
|
||||
void prompt_context_execute(PromptContext *context) {
|
||||
s_executing_command = ExecutingCommandContext;
|
||||
prv_find_and_execute_command(context->buffer, context->write_index, context);
|
||||
|
||||
context->write_index = 0;
|
||||
}
|
||||
|
||||
static void prv_execute_command_from_dbgserial(void *data) {
|
||||
dbgserial_putstr("");
|
||||
|
||||
char* buffer = s_dbgserial_prompt_context.buffer;
|
||||
|
||||
if (s_dbgserial_prompt_context.write_index > 0 && buffer[0] == '!') {
|
||||
// Go to log mode immediately.
|
||||
serial_console_set_state(SERIAL_CONSOLE_STATE_LOGGING);
|
||||
++buffer;
|
||||
}
|
||||
|
||||
const char *end_of_buffer =
|
||||
s_dbgserial_prompt_context.buffer + s_dbgserial_prompt_context.write_index;
|
||||
const size_t buffer_length = end_of_buffer - buffer;
|
||||
|
||||
prv_find_and_execute_command(buffer, buffer_length,
|
||||
&s_dbgserial_prompt_context);
|
||||
}
|
||||
|
||||
bool prompt_context_append_char(PromptContext *prompt_context, char c) {
|
||||
if (UNLIKELY(prompt_context->write_index + 1 >= PROMPT_BUFFER_SIZE_BYTES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
prompt_context->buffer[prompt_context->write_index++] = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Crank up the optimization on this bad boy.
|
||||
OPTIMIZE_FUNC(2) void prompt_handle_character(char c, bool* should_context_switch) {
|
||||
if (UNLIKELY(prompt_command_is_executing())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (LIKELY(c >= 0x20 && c < 127)) {
|
||||
// Printable character.
|
||||
if (!prompt_context_append_char(&s_dbgserial_prompt_context, c)) {
|
||||
dbgserial_putchar(0x07); // bell
|
||||
return;
|
||||
}
|
||||
|
||||
// Echo
|
||||
dbgserial_putchar_lazy(c);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle unprintable control characters.
|
||||
|
||||
if (UNLIKELY(c == 0x3)) { // CTRL-C
|
||||
dbgserial_putstr("");
|
||||
start_prompt(); // Start over
|
||||
return;
|
||||
}
|
||||
|
||||
if (UNLIKELY(c == 0x4)) { // CTRL-D
|
||||
dbgserial_putstr("^D");
|
||||
serial_console_set_state(SERIAL_CONSOLE_STATE_LOGGING);
|
||||
return;
|
||||
}
|
||||
|
||||
if (UNLIKELY(c == 0xd)) { // Enter key
|
||||
s_executing_command = ExecutingCommandDbgSerial;
|
||||
system_task_add_callback_from_isr(prv_execute_command_from_dbgserial, NULL,
|
||||
should_context_switch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (UNLIKELY(c == 0x7f)) { // Backspace
|
||||
if (s_dbgserial_prompt_context.write_index != 0) {
|
||||
s_dbgserial_prompt_context.write_index--;
|
||||
dbgserial_putchar(0x8); // move cursor back one character
|
||||
dbgserial_putchar(0x20); // replace that character with a space, advancing the cursor
|
||||
dbgserial_putchar(0x8); // move the cursor back again
|
||||
} else {
|
||||
dbgserial_putchar(0x07); // bell
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool prompt_command_is_executing(void) {
|
||||
return s_executing_command != ExecutingCommandNone;
|
||||
}
|
||||
|
||||
void prompt_watchdog_feed(void) {
|
||||
system_task_watchdog_feed();
|
||||
}
|
||||
|
||||
void prompt_send_response(const char* response) {
|
||||
PBL_ASSERTN(s_current_context && s_current_context->response_callback);
|
||||
s_current_context->response_callback(response);
|
||||
}
|
||||
|
||||
void prompt_send_response_fmt(char* buffer, size_t buffer_size, const char* fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsniprintf(buffer, buffer_size, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
prompt_send_response(buffer);
|
||||
}
|
||||
|
||||
void prompt_command_continues_after_returning(void) {
|
||||
s_command_continues_after_return = true;
|
||||
}
|
||||
|
||||
void prompt_command_finish(void) {
|
||||
PBL_ASSERTN(s_current_context);
|
||||
PromptContext *last_context = s_current_context;
|
||||
s_current_context = NULL;
|
||||
s_command_continues_after_return = false;
|
||||
s_executing_command = ExecutingCommandNone;
|
||||
if (last_context->command_complete_callback) {
|
||||
last_context->command_complete_callback();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// PULSE infrastructure
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !PULSE_EVERYWHERE
|
||||
static uint16_t s_latest_cookie = UINT16_MAX;
|
||||
|
||||
typedef struct __attribute__((__packed__)) PromptCommand {
|
||||
uint8_t cookie;
|
||||
char command[];
|
||||
} PromptCommand;
|
||||
#endif
|
||||
|
||||
typedef struct __attribute__((__packed__)) PromptResponseContents {
|
||||
uint8_t message_type;
|
||||
uint64_t time_ms;
|
||||
char message[];
|
||||
} PromptResponseContents;
|
||||
|
||||
static void pulse_send_message(const int message_type, const char *response) {
|
||||
size_t response_length = 0;
|
||||
#if PULSE_EVERYWHERE
|
||||
PromptResponseContents *contents = pulse_reliable_send_begin(
|
||||
PULSE2_RELIABLE_PROMPT_PROTOCOL);
|
||||
if (!contents) {
|
||||
// Transport went down while waiting to send. Just throw away the message;
|
||||
// there's not much else we can do.
|
||||
return;
|
||||
}
|
||||
#else
|
||||
PromptResponseContents *contents = pulse_best_effort_send_begin(
|
||||
PULSE_PROTOCOL_PROMPT);
|
||||
#endif
|
||||
|
||||
if (response) {
|
||||
response_length = strlen(response);
|
||||
}
|
||||
|
||||
size_t total_size = sizeof(PromptResponseContents) + response_length;
|
||||
contents->message_type = message_type;
|
||||
|
||||
time_t time_s;
|
||||
uint16_t time_ms;
|
||||
rtc_get_time_ms(&time_s, &time_ms);
|
||||
contents->time_ms = (uint64_t) time_s * 1000 + time_ms;
|
||||
|
||||
if (response_length > 0) {
|
||||
strncpy(contents->message, response, response_length);
|
||||
}
|
||||
#if PULSE_EVERYWHERE
|
||||
pulse_reliable_send(contents, total_size);
|
||||
#else
|
||||
pulse_best_effort_send(contents, total_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void prv_pulse_done_command(void) {
|
||||
pulse_send_message(PROMPT_RESP_DONE, NULL);
|
||||
s_executing_command = ExecutingCommandNone;
|
||||
}
|
||||
|
||||
#if PULSE_EVERYWHERE
|
||||
|
||||
void pulse2_prompt_packet_handler(void *packet, size_t length) {
|
||||
if (prompt_command_is_executing()) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Ignoring prompt command as another command is "
|
||||
"currently executing");
|
||||
return;
|
||||
}
|
||||
|
||||
s_executing_command = ExecutingCommandPulse;
|
||||
memcpy(s_dbgserial_prompt_context.buffer, packet, length);
|
||||
s_dbgserial_prompt_context.buffer[length] = '\0';
|
||||
s_dbgserial_prompt_context.write_index = strlen(s_dbgserial_prompt_context.buffer);
|
||||
system_task_add_callback(prv_execute_command_from_dbgserial, NULL);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void pulse_prompt_handler(void *packet, size_t length) {
|
||||
PromptCommand *command = packet;
|
||||
// Check for duplicate command and ignore it
|
||||
if (s_latest_cookie == command->cookie) {
|
||||
if (s_executing_command == ExecutingCommandPulse) {
|
||||
pulse_send_message(PROMPT_RESP_ACK, NULL);
|
||||
} else {
|
||||
pulse_send_message(PROMPT_RESP_DONE, NULL);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ACK the command
|
||||
pulse_send_message(PROMPT_RESP_ACK, NULL);
|
||||
s_latest_cookie = command->cookie;
|
||||
s_executing_command = ExecutingCommandPulse;
|
||||
|
||||
// Discount the size of the cookie
|
||||
size_t command_length = length - sizeof(PromptCommand);
|
||||
strncpy(s_dbgserial_prompt_context.buffer, command->command, command_length);
|
||||
s_dbgserial_prompt_context.buffer[command_length] = 0;
|
||||
s_dbgserial_prompt_context.write_index = strlen(s_dbgserial_prompt_context.buffer);
|
||||
|
||||
system_task_add_callback(prv_execute_command_from_dbgserial, NULL);
|
||||
}
|
||||
|
||||
void pulse_prompt_link_state_handler(PulseLinkState link_state) {
|
||||
if (link_state != PulseLinkState_Open) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the cookie to an 'impossible' value on link open
|
||||
s_latest_cookie = UINT16_MAX;
|
||||
}
|
||||
|
||||
#endif
|
91
src/fw/console/prompt.h
Normal file
91
src/fw/console/prompt.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! @file prompt.h
|
||||
//!
|
||||
//! This file handles the prompt mode of our serial console. This allows a user to enter and
|
||||
//! execute commands. It also has support for other modules to execute their own commands by
|
||||
//! supplying their own PromptContext
|
||||
//!
|
||||
//! TODO: We should probably split this in the future so there's one module to handle the
|
||||
//! dbgserial part and another module to handle executing commands.
|
||||
|
||||
#include "util/attributes.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef void (*PromptResponseCallback)(const char *response);
|
||||
typedef void (*PromptCommandCompleteCallback)(void);
|
||||
|
||||
#define PROMPT_BUFFER_SIZE_BYTES 128
|
||||
typedef struct PromptContext {
|
||||
//! Function to call to send the response text from executed commands.
|
||||
PromptResponseCallback response_callback;
|
||||
//! Function to call when the command has completed.
|
||||
PromptCommandCompleteCallback command_complete_callback;
|
||||
|
||||
//! Which index we were currently writing to, should never be higher than
|
||||
//! PROMPT_BUFFER_SIZE_BYTES - 1.
|
||||
unsigned int write_index;
|
||||
|
||||
char buffer[PROMPT_BUFFER_SIZE_BYTES + 1]; // Leave space for a trailing null always.
|
||||
} PromptContext;
|
||||
|
||||
//! Asks the console to switch to prompt mode. Used by other console modes to flip back to the
|
||||
//! prompt when they're done.
|
||||
void console_switch_to_prompt(void);
|
||||
|
||||
//! Called on an ISR. Handles a new character from the dbgserial when we're in prompt mode.
|
||||
void prompt_handle_character(char c, bool* should_context_switch);
|
||||
|
||||
//! Appends a character to a given context.
|
||||
//! @return true if the character fits, false if the buffer is full
|
||||
bool prompt_context_append_char(PromptContext *context, char c);
|
||||
|
||||
//! Executes a command in the given context.
|
||||
void prompt_context_execute(PromptContext *context);
|
||||
|
||||
//! Feed the task watchdog for the thread that commands run on. Call this regularly if your
|
||||
//! command takes a long time (multiple seconds).
|
||||
void prompt_watchdog_feed(void);
|
||||
|
||||
//! Use this from a prompt command to respond to a command. The output will directed out the
|
||||
//! appropriate output terminal depending on who ran the command (dbgserial or accessory
|
||||
//! connector).
|
||||
//! @param response NULL-terminated string
|
||||
void prompt_send_response(const char* response);
|
||||
|
||||
//! Use this from a prompt command to respond to a command. The output will directed out the
|
||||
//! appropriate output terminal depending on who ran the command (dbgserial or accessory
|
||||
//! connector). This option allows the use of printf style formatters to create output.
|
||||
void prompt_send_response_fmt(char* buffer, size_t buffer_size, const char* fmt, ...)
|
||||
FORMAT_PRINTF(3, 4);
|
||||
|
||||
//! Finishes the currently running prompt command, and sends the prompt command complete message.
|
||||
//! This is only to be used if \ref prompt_command_continues_after_returning has been called,
|
||||
//! or if the command cannot possibly return.
|
||||
void prompt_command_finish(void);
|
||||
|
||||
//! Holds the prompt open after the currently executing command callback returns.
|
||||
//! This allows for responses to be sent back from callbacks.
|
||||
//! Make sure to finish the command with \ref prompt_command_finish
|
||||
void prompt_command_continues_after_returning(void);
|
||||
|
||||
//! Returns true if there is currently a prompt command executing, false otherwise.
|
||||
bool prompt_command_is_executing(void);
|
1503
src/fw/console/prompt_commands.c
Normal file
1503
src/fw/console/prompt_commands.c
Normal file
File diff suppressed because it is too large
Load diff
687
src/fw/console/prompt_commands.h
Normal file
687
src/fw/console/prompt_commands.h
Normal file
|
@ -0,0 +1,687 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "console/prompt.h"
|
||||
#include "console/pulse_internal.h"
|
||||
#include "services/normal/filesystem/pfs.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/size.h"
|
||||
|
||||
extern void command_help(void);
|
||||
|
||||
extern void command_log_level_set(const char*);
|
||||
extern void command_log_level_get(void);
|
||||
|
||||
extern void command_log_dump_current(void);
|
||||
extern void command_log_dump_last(void);
|
||||
extern void command_log_dump_spam(void);
|
||||
extern void command_log_dump_generation(const char*);
|
||||
|
||||
extern void command_put_raw_button_event(const char*, const char*);
|
||||
extern void command_put_button_event(const char*, const char*);
|
||||
extern void command_button_press(const char*, const char*);
|
||||
extern void command_button_press_multiple(const char *, const char *, const char *, const char *);
|
||||
extern void command_button_press_short(const char*);
|
||||
|
||||
extern void command_stats_dump_now(void);
|
||||
extern void command_stats_dump_current(void);
|
||||
extern void command_stats_dump_last(void);
|
||||
extern void command_stats_dump_generation(const char*);
|
||||
|
||||
extern void command_crash(void);
|
||||
extern void command_hard_crash(void);
|
||||
extern void command_reset(void);
|
||||
extern void command_boot_prf(void);
|
||||
extern void command_factory_reset(void);
|
||||
extern void command_factory_reset_fast(void);
|
||||
|
||||
extern void command_infinite_loop(void);
|
||||
extern void command_assert_fail(void);
|
||||
extern void command_stuck_timer(void);
|
||||
|
||||
extern void command_croak(void);
|
||||
extern void command_hardfault(void);
|
||||
|
||||
extern void command_dump_malloc_kernel(void);
|
||||
extern void command_dump_malloc_app(void);
|
||||
extern void command_dump_malloc_worker(void);
|
||||
extern void command_dump_malloc_bt(void);
|
||||
|
||||
extern void command_read_word(const char*);
|
||||
|
||||
extern void command_power_2v5(const char*);
|
||||
|
||||
extern void command_backlight_ctl(const char*);
|
||||
extern void command_rgb_set_color(const char*);
|
||||
|
||||
extern void command_battery_charge_option(const char*);
|
||||
|
||||
extern void command_print_battery_status(void);
|
||||
extern void command_compass_peek(void);
|
||||
extern void command_accel_peek(void);
|
||||
extern void command_accel_num_samples(char *num_samples);
|
||||
extern void command_accel_status(void);
|
||||
extern void command_accel_selftest(void);
|
||||
extern void command_accel_softreset(void);
|
||||
|
||||
extern void command_dump_flash(const char*, const char*);
|
||||
extern void command_crc_flash(const char*, const char*);
|
||||
extern void command_format_flash(void);
|
||||
extern void command_erase_flash(const char*, const char*);
|
||||
extern void command_flash_read(const char*, const char*);
|
||||
extern void command_flash_switch_mode(const char*);
|
||||
extern void command_flash_fill(const char*, const char*, const char*);
|
||||
extern void command_flash_test(const char* test_case_num_str, const char* iterations_str);
|
||||
extern void command_flash_test_locked_sectors(void);
|
||||
extern void command_flash_stress(void);
|
||||
extern void command_flash_validate(void);
|
||||
extern void command_flash_apicheck(const char *len);
|
||||
extern void command_flash_unprotect(void);
|
||||
extern void command_flash_signal_test_init(void);
|
||||
extern void command_flash_signal_test_run(void);
|
||||
extern void command_flash_show_erased_sectors(const char *arg);
|
||||
|
||||
extern void command_get_time(void);
|
||||
extern void command_set_time(const char *arg);
|
||||
extern void command_timezone_clear(void);
|
||||
|
||||
extern void command_vibe_ctl(const char *arg);
|
||||
|
||||
// extern void command_print_task_list(void);
|
||||
extern void command_timers(void);
|
||||
|
||||
extern void command_bt_airplane_mode(const char*);
|
||||
extern void command_bt_prefs_wipe(void);
|
||||
extern void command_bt_print_mac(void);
|
||||
extern void command_bt_set_addr(const char *bd_addr);
|
||||
extern void command_bt_set_name(const char *bt_name);
|
||||
|
||||
extern void command_bt_status(void);
|
||||
|
||||
// extern void command_get_remote_prefs(void);
|
||||
// extern void command_del_remote_pref(const char*);
|
||||
// extern void command_bt_sniff_bounce(void);
|
||||
// extern void command_bt_active_enter(void);
|
||||
// extern void command_bt_active_exit(void);
|
||||
|
||||
extern void command_get_active_app_metadata(void);
|
||||
|
||||
extern void command_app_list(void);
|
||||
extern void command_app_launch(const char* app_num_str);
|
||||
extern void command_app_remove(const char* app_num_str);
|
||||
|
||||
extern void command_worker_launch(const char* app_num_str);
|
||||
extern void command_worker_kill(void);
|
||||
|
||||
extern void command_boot_bit_set(const char* bit, const char* value);
|
||||
extern void command_boot_bits_get(void);
|
||||
|
||||
extern void command_window_stack_info(void);
|
||||
extern void command_modal_stack_info(void);
|
||||
extern void command_animations_info(void);
|
||||
extern void command_legacy2_animations_info(void);
|
||||
|
||||
extern void command_sim_panic(const char*);
|
||||
|
||||
extern void command_alarm(void);
|
||||
|
||||
extern void command_bt_test_start(void);
|
||||
extern void command_bt_test_stop(void);
|
||||
|
||||
extern void command_bt_test_hci_passthrough();
|
||||
extern void command_bt_test_bt_sig_rf_mode();
|
||||
|
||||
extern void command_watch(void);
|
||||
|
||||
extern void command_print_now_playing(void);
|
||||
|
||||
extern void command_selftest(void);
|
||||
extern void command_enter_mfg(void);
|
||||
extern void command_enter_standby(void);
|
||||
extern void command_enter_consumer_mode(void);
|
||||
extern void command_power_5v(const char*);
|
||||
|
||||
extern void command_accessory_imaging_start(void);
|
||||
|
||||
extern void command_serial_read(void);
|
||||
extern void command_hwver_read(void);
|
||||
extern void command_pcba_serial_read(void);
|
||||
extern void command_color_read(void);
|
||||
extern void command_disp_offset_read(void);
|
||||
extern void command_rtcfreq_read(void);
|
||||
extern void command_model_read(void);
|
||||
|
||||
extern void command_serial_write(const char*);
|
||||
extern void command_hwver_write(const char*);
|
||||
extern void command_pcba_serial_write(const char*);
|
||||
extern void command_color_write(const char*);
|
||||
extern void command_disp_offset_write(const char*);
|
||||
extern void command_rtcfreq_write(const char*);
|
||||
extern void command_model_write(const char*);
|
||||
extern void command_bootloader_test(const char*);
|
||||
|
||||
extern void command_version_info(void);
|
||||
|
||||
extern void command_als_read(void);
|
||||
extern void command_temperature_read(void);
|
||||
|
||||
extern void command_get_connected_os(void);
|
||||
|
||||
extern void command_dump_window(void);
|
||||
extern void command_layer_nudge(const char *address);
|
||||
|
||||
extern void command_scheduler_force_active(void);
|
||||
extern void command_scheduler_resume_normal(void);
|
||||
|
||||
extern void command_button_read(const char*);
|
||||
|
||||
extern void memory_layout_dump_mpu_regions_to_dbgserial(void);
|
||||
|
||||
extern void command_dls_list(void);
|
||||
extern void command_dls_show(const char *id);
|
||||
extern void command_dls_erase_all(void);
|
||||
extern void command_dls_send_all(void);
|
||||
|
||||
extern void pfs_command_fs_format(const char *erase_headers);
|
||||
extern void pfs_command_fs_ls(void);
|
||||
extern void pfs_command_cat(const char *filename, const char *num_chars);
|
||||
extern void pfs_command_dump_hdr(const char *page);
|
||||
extern void pfs_command_crc(const char *filename);
|
||||
extern void pfs_command_stress(void);
|
||||
|
||||
extern void command_fs_reset(void);
|
||||
extern void command_fs_format(const char *erase_headers);
|
||||
extern void command_fs_ls(void);
|
||||
extern void command_fs_du(void);
|
||||
extern void command_fs_stat(const char *filename);
|
||||
extern void command_fs_rm(const char *filename);
|
||||
extern void command_fs_header(const char *page_num);
|
||||
|
||||
extern void command_pmic_read_registers(void);
|
||||
|
||||
extern void command_ping_send(void);
|
||||
|
||||
extern void command_display_set(const char *color);
|
||||
#if CAPABILITY_HAS_ACCESSORY_CONNECTOR
|
||||
extern void command_accessory_power_set(const char *on);
|
||||
extern void command_accessory_stress_test(void);
|
||||
extern void command_smartstrap_status(void);
|
||||
#endif
|
||||
extern void command_mic_start(char *timeout_str, char *sample_size_str, char *sample_rate_str,
|
||||
char *volume_str);
|
||||
extern void command_mic_read(void);
|
||||
extern void command_pmic_rails(void);
|
||||
|
||||
extern void dump_current_runtime_stats(void);
|
||||
|
||||
extern void command_set_runlevel(const char *runlevel);
|
||||
|
||||
extern void command_litter_filesystem(void);
|
||||
|
||||
typedef struct Command {
|
||||
char* cmd_str;
|
||||
void* func;
|
||||
unsigned int num_params;
|
||||
} Command;
|
||||
|
||||
extern void command_profiler_start(void);
|
||||
extern void command_profiler_stop(void);
|
||||
extern void command_profiler_stats(void);
|
||||
|
||||
extern void command_battery_ui_display(const char *, const char *, const char *);
|
||||
extern void command_battery_ui_update(const char *, const char *, const char *);
|
||||
extern void command_battery_ui_dismiss(void);
|
||||
|
||||
extern void command_gapdb_dump(void);
|
||||
extern void command_force_shared_prf_flush(void);
|
||||
extern void command_waste_time(const char *count_arg, const char *delay_arg);
|
||||
extern void command_bt_sprf_nuke(void);
|
||||
|
||||
extern void command_pause_animations(void);
|
||||
extern void command_resume_animations(void);
|
||||
|
||||
extern void command_change_le_mode(char *mode);
|
||||
extern void command_le_mode_chaos_monkey(char *enabled_str);
|
||||
extern void command_ble_send_service_changed_indication(void);
|
||||
extern void command_ble_rediscover(void);
|
||||
extern void command_ble_logging_set_level(const char *level);
|
||||
extern void command_ble_logging_get_level(void);
|
||||
extern void command_ble_core_dump(const char *command);
|
||||
|
||||
extern void command_low_power_debug(char *enable_arg);
|
||||
|
||||
extern void command_audit_delay_us(void);
|
||||
extern void command_enter_stop(void);
|
||||
|
||||
extern void command_dump_notif_pref_db(void);
|
||||
|
||||
extern void command_bt_conn_param_set(
|
||||
char *interval_min_ms, char *interval_max_ms, char *slave_latency, char *timeout_ms);
|
||||
extern void command_bt_disc_start(char *start_handle, char *end_handle);
|
||||
extern void command_bt_disc_stop(void);
|
||||
|
||||
// Commands to support MFG Bluetooth LE-only testing
|
||||
extern void command_btle_test_le_tx_start(
|
||||
char *tx_channel, char *tx_packet_length, char *packet_payload_type);
|
||||
extern void command_btle_test_rx_start(char *rx_channel);
|
||||
extern void command_btle_test_end(void);
|
||||
extern void command_btle_pa_set(char *option);
|
||||
extern void command_btle_unmod_tx_start(char *tx_channel);
|
||||
extern void command_btle_unmod_tx_stop(void);
|
||||
|
||||
#if MFG_INFO_RECORDS_TEST_RESULTS
|
||||
extern void command_mfg_info_test_results(void);
|
||||
#endif
|
||||
|
||||
extern void command_perftest_line(const char *, const char *);
|
||||
extern void command_perftest_line_all(void);
|
||||
extern void command_perftest_text(const char *, const char *, const char *);
|
||||
extern void command_perftest_text_all(void);
|
||||
|
||||
extern void command_bt_sleep_check(const char *iters);
|
||||
|
||||
|
||||
#if PLATFORM_TINTIN && !TARGET_QEMU
|
||||
// We don't have space for anything that's not absolutely required for firmware development
|
||||
// (imaging resources over PULSE). Rip it all out. Note that this breaks test automation on tintin,
|
||||
// but QEMU will be used as a placeholder. We plan on reintroducing test automation support through
|
||||
// continued space savings efforts and by introducing RPC commands over PULSE. Stay tuned.
|
||||
// For reference, this saves about 12k of space. If we leave just the test automation commands in
|
||||
// roughly 7k of space is saved.
|
||||
#define KEEP_NON_ESSENTIAL_COMMANDS 0
|
||||
#else
|
||||
#define KEEP_NON_ESSENTIAL_COMMANDS 1
|
||||
#endif
|
||||
static const Command s_prompt_commands[] = {
|
||||
// PULSE entry point, needed for anything PULSE-related to work
|
||||
{ "PULSEv1", pulse_start, 0 },
|
||||
#if KEEP_NON_ESSENTIAL_COMMANDS == 1
|
||||
// ====================================================================================
|
||||
// NOTE: The following commands are used by test automation.
|
||||
// Disabling/removing them will break testing against those FW builds.
|
||||
{ "click short", command_button_press_short, 1 },
|
||||
{ "click multiple", command_button_press_multiple, 4 },
|
||||
{ "click long", command_button_press, 2 },
|
||||
{ "reset", command_reset, 0 },
|
||||
{ "crash", command_crash, 0 },
|
||||
{ "hard crash", command_hard_crash, 0 },
|
||||
#ifndef RECOVERY_FW
|
||||
{ "factory reset fast", command_factory_reset_fast, 0 },
|
||||
#endif
|
||||
{ "factory reset", command_factory_reset, 0 },
|
||||
{ "set time", command_set_time, 1 },
|
||||
{ "version", command_version_info, 0 },
|
||||
{ "boot bit set", command_boot_bit_set, 2 },
|
||||
{ "window stack", command_window_stack_info, 0 },
|
||||
{ "modal stack", command_modal_stack_info, 0 },
|
||||
{ "battery chargeopt", command_battery_charge_option, 1},
|
||||
{ "bt airplane mode", command_bt_airplane_mode, 1 },
|
||||
{ "bt prefs wipe", command_bt_prefs_wipe, 0 },
|
||||
{ "bt mac", command_bt_print_mac, 0 },
|
||||
{ "bt set addr", command_bt_set_addr, 1 },
|
||||
{ "bt set name", command_bt_set_name, 1 },
|
||||
{ "bt cp set", command_bt_conn_param_set, 4 },
|
||||
{ "bt disc start", command_bt_disc_start, 2 },
|
||||
{ "bt disc stop", command_bt_disc_stop, 0 },
|
||||
{ "timezone clear", command_timezone_clear, 0 },
|
||||
{ "battery status", command_print_battery_status, 0 },
|
||||
#ifndef RELEASE
|
||||
{ "audit delay", command_audit_delay_us, 0 },
|
||||
{ "enter stop", command_enter_stop, 0},
|
||||
#endif
|
||||
#ifndef RECOVERY_FW
|
||||
{ "app list", command_app_list, 0 },
|
||||
{ "app launch", command_app_launch, 1 },
|
||||
{ "app remove", command_app_remove, 1 },
|
||||
#endif
|
||||
// End of automation commands
|
||||
// ====================================================================================
|
||||
|
||||
{ "erase flash", command_erase_flash, 2 },
|
||||
{ "crc flash", command_crc_flash, 2 },
|
||||
#ifndef RECOVERY_FW
|
||||
#if CAPABILITY_HAS_TEMPERATURE
|
||||
{ "temp read", command_temperature_read, 0 },
|
||||
#endif
|
||||
{ "als read", command_als_read, 0},
|
||||
#ifndef RELEASE
|
||||
{ "litter pfs", command_litter_filesystem, 0 },
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ====================================================================================
|
||||
// Following commands are used for manufacturing. We use a PRF firmware for manufacturing, so
|
||||
// we can only include these commands when we're building for PRF. Some of the commands are
|
||||
// specific to snowy manufacturing as well
|
||||
#ifdef RECOVERY_FW
|
||||
#if CAPABILITY_HAS_ACCESSORY_CONNECTOR
|
||||
{ "accessory imaging start", command_accessory_imaging_start, 0 },
|
||||
#endif
|
||||
|
||||
{ "info", command_version_info, 0 },
|
||||
|
||||
{ "enter mfg", command_enter_mfg, 0 },
|
||||
|
||||
{ "enter standby", command_enter_standby, 0 },
|
||||
|
||||
{ "enter consumer", command_enter_consumer_mode, 0 },
|
||||
|
||||
{ "serial read", command_serial_read, 0 },
|
||||
{ "hwver read", command_hwver_read, 0 },
|
||||
{ "pcbaserial read", command_pcba_serial_read, 0 },
|
||||
{ "color read", command_color_read, 0 },
|
||||
#if PBL_ROUND
|
||||
{ "disp offset read", command_disp_offset_read, 0 },
|
||||
#endif
|
||||
{ "rtcfreq read", command_rtcfreq_read, 0 },
|
||||
{ "model read", command_model_read, 0 },
|
||||
|
||||
{ "serial write", command_serial_write, 1 },
|
||||
{ "hwver write", command_hwver_write, 1 },
|
||||
{ "pcbaserial write", command_pcba_serial_write, 1 },
|
||||
#if MANUFACTURING_FW
|
||||
{ "color write", command_color_write, 1 },
|
||||
#if PBL_ROUND
|
||||
{ "disp offset write", command_disp_offset_write, 2 },
|
||||
#endif
|
||||
{ "rtcfreq write", command_rtcfreq_write, 1 },
|
||||
{ "model write", command_model_write, 1 },
|
||||
#endif // MANUFACTURING_FW
|
||||
{ "bootloader test", command_bootloader_test, 1 },
|
||||
|
||||
{ "scheduler force active", command_scheduler_force_active, 0 },
|
||||
{ "scheduler resume normal", command_scheduler_resume_normal, 0 },
|
||||
|
||||
{ "bt status", command_bt_status, 0 },
|
||||
{ "bt test start", command_bt_test_start, 0 },
|
||||
{ "bt test stop", command_bt_test_stop, 0 },
|
||||
{ "bt test hcipass", command_bt_test_hci_passthrough, 0 },
|
||||
#if BT_CONTROLLER_DA14681
|
||||
{ "bt sleep check", command_bt_sleep_check, 1 },
|
||||
{ "btle tx test start", command_btle_test_le_tx_start, 3 },
|
||||
{ "btle rx test start", command_btle_test_rx_start, 1 },
|
||||
{ "btle test end", command_btle_test_end, 0 },
|
||||
{ "btle umod tx test start", command_btle_unmod_tx_start, 1 },
|
||||
{ "btle umod tx test stop", command_btle_unmod_tx_stop, 0 },
|
||||
# if PLATFORM_ROBERT
|
||||
{ "btle test pa", command_btle_pa_set, 1 },
|
||||
# endif
|
||||
#endif
|
||||
|
||||
{ "bt test bt_sig_rf", command_bt_test_bt_sig_rf_mode, 0},
|
||||
|
||||
{ "backlight", command_backlight_ctl, 1 },
|
||||
|
||||
{ "button read", command_button_read, 1 },
|
||||
|
||||
#if CAPABILITY_HAS_MAGNETOMETER
|
||||
{ "compass peek", command_compass_peek, 0 },
|
||||
#endif // CAPABILITY_HAS_MAGNETOMETER
|
||||
{ "accel read", command_accel_peek, 0 },
|
||||
|
||||
{ "als read", command_als_read, 0},
|
||||
|
||||
#ifdef PLATFORM_TINTIN // TINTIN/BIANCA only
|
||||
{ "power 2.5", command_power_2v5, 1 },
|
||||
#else
|
||||
{ "selftest", command_selftest, 0 },
|
||||
|
||||
{ "flash read", command_flash_read, 2},
|
||||
{ "flash switchmode", command_flash_switch_mode, 1},
|
||||
{ "flash fill", command_flash_fill, 3},
|
||||
#if CAPABILITY_USE_PARALLEL_FLASH
|
||||
{ "flash test", command_flash_test, 2},
|
||||
#endif
|
||||
{ "flash validate", command_flash_validate, 0},
|
||||
{ "flash erased_sectors", command_flash_show_erased_sectors, 1},
|
||||
#if !RELEASE && (PLATFORM_SILK || PLATFORM_ROBERT || PLATFORM_CALCULUS)
|
||||
{ "flash apicheck", command_flash_apicheck, 1},
|
||||
{ "flash signal test init", command_flash_signal_test_init, 0 },
|
||||
{ "flash signal test run", command_flash_signal_test_run, 0 },
|
||||
#endif
|
||||
|
||||
{ "pmic rails", command_pmic_rails, 0},
|
||||
|
||||
|
||||
{ "disp", command_display_set, 1},
|
||||
|
||||
#if MFG_INFO_RECORDS_TEST_RESULTS
|
||||
{ "mfg ui test results", command_mfg_info_test_results, 0 },
|
||||
#endif // MFG_INFO_RECORDS_TEST_RESULTS
|
||||
|
||||
#endif // PLATFORM_TINTIN
|
||||
#endif // RECOVERY_FW
|
||||
|
||||
#if CAPABILITY_HAS_ACCESSORY_CONNECTOR
|
||||
{ "accessory power", command_accessory_power_set, 1 },
|
||||
{ "accessory stress", command_accessory_stress_test, 0 },
|
||||
#if !RELEASE && !RECOVERY_FW
|
||||
{ "smartstrap status", command_smartstrap_status, 0 },
|
||||
#endif // RELEASE
|
||||
#endif // CAPABILITY_HAS_ACCESSORY_CONNECTOR
|
||||
|
||||
#if CAPABILITY_HAS_PMIC
|
||||
{"pmic regs", command_pmic_read_registers, 0},
|
||||
#endif
|
||||
|
||||
#if CAPABILITY_HAS_MICROPHONE
|
||||
{ "mic start", command_mic_start, 4},
|
||||
{ "mic read", command_mic_read, 0},
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// End of manufacturing commands
|
||||
// ====================================================================================
|
||||
|
||||
|
||||
// The rest of the commands are pretty much a misc free-for-all of functionality that's useful
|
||||
// for debugging.
|
||||
|
||||
// Meta
|
||||
{ "help", command_help, 0 },
|
||||
{ "lowpowerdebug", command_low_power_debug, 1 },
|
||||
|
||||
{ "log level set", command_log_level_set, 1 },
|
||||
{ "log level get", command_log_level_get, 0 },
|
||||
|
||||
{ "log dump current", command_log_dump_current, 0 },
|
||||
{ "log dump last", command_log_dump_last, 0 },
|
||||
{ "log spam", command_log_dump_spam, 0 },
|
||||
{ "log dump gen", command_log_dump_generation, 1 },
|
||||
|
||||
{ "ble mode", command_change_le_mode, 1 },
|
||||
{ "ble ind svc", command_ble_send_service_changed_indication, 0 },
|
||||
{ "ble rediscover", command_ble_rediscover, 0 },
|
||||
// { "ble mode_monkey", command_le_mode_chaos_monkey, 1 },
|
||||
{ "ble set log level", command_ble_logging_set_level, 1},
|
||||
{ "ble get log level", command_ble_logging_get_level, 0},
|
||||
{ "ble core dump", command_ble_core_dump, 1 },
|
||||
|
||||
/*
|
||||
{ "stats dump now", command_stats_dump_now, 0 },
|
||||
{ "stats dump current", command_stats_dump_current, 0 },
|
||||
{ "stats dump last", command_stats_dump_last, 0 },
|
||||
{ "stats dump generation", command_stats_dump_generation, 1 },
|
||||
*/
|
||||
|
||||
// Buttons
|
||||
{ "raw button event", command_put_raw_button_event, 2 },
|
||||
// { "click button event", command_put_button_event, 2 },
|
||||
|
||||
// General utils
|
||||
// { "boot prf", command_boot_prf, 0 },
|
||||
|
||||
/*
|
||||
{ "infinite loop", command_infinite_loop, 0 },
|
||||
{ "assert fail", command_assert_fail, 0 },
|
||||
{ "stuck timer", command_stuck_timer, 0 },
|
||||
{ "hard fault", command_hardfault, 0 },
|
||||
*/
|
||||
{ "croak", command_croak, 0 },
|
||||
|
||||
#ifdef MALLOC_INSTRUMENTATION
|
||||
{ "dump malloc kernel", command_dump_malloc_kernel, 0 },
|
||||
{ "dump malloc app", command_dump_malloc_app, 0 },
|
||||
{ "dump malloc worker", command_dump_malloc_worker, 0 },
|
||||
#if BT_CONTROLLER_CC2564X
|
||||
{ "dump malloc bt", command_dump_malloc_bt, 0 },
|
||||
#endif /* BT_CONTROLLER_CC2564X */
|
||||
#endif /* MALLOC_INSTRUMENTATION */
|
||||
|
||||
/*
|
||||
{ "read word", command_read_word, 1 },
|
||||
|
||||
{ "remote os", command_get_connected_os, 0 },
|
||||
*/
|
||||
|
||||
#ifdef UI_DEBUG
|
||||
{ "window dump", command_dump_window, 0 },
|
||||
{ "layer nudge", command_layer_nudge, 1 },
|
||||
#endif
|
||||
|
||||
|
||||
// Drivers
|
||||
//{ "rgb", command_rgb_set_color, 1 },
|
||||
|
||||
// { "watch", command_watch, 0 },
|
||||
|
||||
// Flash manipulation commands
|
||||
{ "dump flash", command_dump_flash, 2 },
|
||||
// { "format flash", command_format_flash, 0 },
|
||||
|
||||
#if !PLATFORM_TINTIN
|
||||
{ "flash unprotect", command_flash_unprotect, 0 },
|
||||
#endif
|
||||
|
||||
#ifndef RECOVERY_FW
|
||||
{ "worker launch", command_worker_launch, 1 },
|
||||
{ "worker kill", command_worker_kill, 0},
|
||||
#endif
|
||||
|
||||
#ifdef TEST_FLASH_LOCK_PROTECTION
|
||||
{ "flash lock test", command_flash_test_locked_sectors, 0 },
|
||||
#endif
|
||||
|
||||
/*
|
||||
{ "get time", command_get_time, 0 },
|
||||
*/
|
||||
|
||||
// Firmware specific
|
||||
//{ "task-list", command_print_task_list, 0 },
|
||||
|
||||
//{ "cpustats", dump_current_runtime_stats, 0 },
|
||||
//{ "bt prefs get", command_get_remote_prefs, 0 },
|
||||
//{ "bt prefs del", command_del_remote_pref, 1 },
|
||||
//{ "bt sniff bounce", command_bt_sniff_bounce, 0 },
|
||||
//{ "bt active enter", command_bt_active_enter, 0 },
|
||||
//{ "bt active exit", command_bt_active_exit, 0 },
|
||||
|
||||
|
||||
#if !defined(RECOVERY_FW)
|
||||
{ "get active app metadata", command_get_active_app_metadata, 0 },
|
||||
#endif
|
||||
// { "boot bits get", command_boot_bits_get, 0 },
|
||||
|
||||
{ "animations", command_animations_info, 0 },
|
||||
{ "pause animations", command_pause_animations, 0 },
|
||||
{ "resume animations", command_resume_animations, 0 },
|
||||
|
||||
// { "animations_l2", command_legacy2_animations_info, 0 },
|
||||
|
||||
// #if !defined(RECOVERY_FW)
|
||||
// { "sim panic", command_sim_panic, 1 },
|
||||
// #endif
|
||||
|
||||
#if !defined(RECOVERY_FW)
|
||||
{ "alarm", command_alarm, 0 },
|
||||
|
||||
//{ "now playing", command_print_now_playing, 0 },
|
||||
|
||||
{ "dls list", command_dls_list, 0 },
|
||||
// { "dls show", command_dls_show, 1 },
|
||||
{ "dls wipe", command_dls_erase_all, 0 },
|
||||
{ "dls send", command_dls_send_all, 0 },
|
||||
|
||||
#endif // !RECOVERY_FW
|
||||
|
||||
{ "dump mpu", memory_layout_dump_mpu_regions_to_dbgserial, 0 },
|
||||
|
||||
#ifndef RECOVERY_FW
|
||||
{"pfs format", pfs_command_fs_format, 1},
|
||||
{"pfs ls", pfs_command_fs_ls, 0},
|
||||
// {"pfs cat", pfs_command_cat, 2},
|
||||
// {"pfs rmall", pfs_remove_all, 0},
|
||||
{"pfs rm", pfs_remove, 1},
|
||||
{"pfs hdr", pfs_command_dump_hdr, 1},
|
||||
// {"pfs stress", pfs_command_stress, 0 },
|
||||
{"pfs crc", pfs_command_crc, 1},
|
||||
|
||||
// This command is dangerous to your flash, so it is commented out by default.
|
||||
// {"flash stress", command_flash_stress, 0 },
|
||||
#endif
|
||||
|
||||
{ "ping", command_ping_send, 0},
|
||||
|
||||
{ "runlevel", command_set_runlevel, 1 },
|
||||
|
||||
#if defined(PROFILER)
|
||||
{ "profiler start", command_profiler_start, 0 },
|
||||
{ "profiler stop", command_profiler_stop, 0 },
|
||||
{ "profiler stats", command_profiler_stats, 0 },
|
||||
#endif
|
||||
|
||||
#if (LOG_DOMAIN_BT_PAIRING_INFO != 0)
|
||||
// Note to future codespace saver ... this is on by default for debug builds
|
||||
// Removing it will save ~2400 bytes but it is super useful for BT bringup debug!
|
||||
{ "gapdb dump", command_gapdb_dump, 0 },
|
||||
{ "sprf nuke", command_bt_sprf_nuke, 0 },
|
||||
#if !RECOVERY_FW
|
||||
{ "sprf sync", command_force_shared_prf_flush, 0},
|
||||
#endif // !RECOVERY_FW
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
{ "battui show", command_battery_ui_display, 3 },
|
||||
{ "battui update", command_battery_ui_update, 3 },
|
||||
{ "battui dismiss", command_battery_ui_dismiss, 0 },
|
||||
#endif
|
||||
|
||||
{ "waste time", command_waste_time, 2 },
|
||||
#if !defined(RECOVERY_FW)
|
||||
{ "dump notif_pref_db", command_dump_notif_pref_db, 0 },
|
||||
#endif
|
||||
|
||||
#if PERFORMANCE_TESTS
|
||||
{ "perftest all line", command_perftest_line_all, 0 },
|
||||
{ "perftest all text", command_perftest_text_all, 0 },
|
||||
{ "perftest line", command_perftest_line, 2 },
|
||||
{ "perftest text", command_perftest_text, 3 },
|
||||
#endif
|
||||
#endif // KEEP_NON_ESSENTIAL_COMMANDS
|
||||
|
||||
#if PLATFORM_SILK && !TARGET_QEMU
|
||||
{ "accel samp", command_accel_num_samples, 1 },
|
||||
{ "accel status", command_accel_status, 0 },
|
||||
{ "accel selftest", command_accel_selftest, 0 },
|
||||
{ "accel reset", command_accel_softreset, 0 },
|
||||
#endif // PLATFORM_SILK
|
||||
{ "vibe", command_vibe_ctl, 1 },
|
||||
};
|
||||
|
||||
#define NUM_PROMPT_COMMANDS ARRAY_LENGTH(s_prompt_commands)
|
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
|
26
src/fw/console/pulse.h
Normal file
26
src/fw/console/pulse.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! Perform any required link-maintenance tasks before pulse_init.
|
||||
//!
|
||||
//! This function should be called as early in boot as possible,
|
||||
//! preferably as soon as dbgserial output has been initialized.
|
||||
void pulse_early_init(void);
|
||||
|
||||
//! Initialize multitasking PULSE
|
||||
void pulse_init(void);
|
465
src/fw/console/pulse2.c
Normal file
465
src/fw/console/pulse2.c
Normal file
|
@ -0,0 +1,465 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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
|
50
src/fw/console/pulse2_reliable_protocol_registry.def
Normal file
50
src/fw/console/pulse2_reliable_protocol_registry.def
Normal file
|
@ -0,0 +1,50 @@
|
|||
// PULSE2 reliable transport application event registry
|
||||
|
||||
#ifndef _PULSE2_RELIABLE_APP_PROTOCOL_NUMBERS_DEFINED
|
||||
#define _PULSE2_RELIABLE_APP_PROTOCOL_NUMBERS_DEFINED
|
||||
|
||||
#define PULSE2_RELIABLE_PROMPT_PROTOCOL (0x3e20)
|
||||
#define PULSE2_BULKIO_PROTOCOL (0x3e21)
|
||||
#define PULSE2_PEBBLE_PROTOCOL (0x3e22)
|
||||
|
||||
#endif
|
||||
|
||||
// Available directives:
|
||||
//
|
||||
// ON_PACKET(number, packet_handler)
|
||||
//
|
||||
// number:
|
||||
// The link-layer protocol number (uint16_t)
|
||||
//
|
||||
// packet_handler:
|
||||
// Handler function for incoming packets.
|
||||
// The handler function must have the signature
|
||||
//
|
||||
// void packet_handler(void *packet, size_t length);
|
||||
//
|
||||
// ON_TRANSPORT_STATE_CHANGE(transport_up_handler, transport_down_handler)
|
||||
//
|
||||
// transport_up_handler:
|
||||
// Handler function for when the reliable transport comes up. It will be
|
||||
// called when the reliable transport is ready to carry traffic.
|
||||
//
|
||||
// transport_down_handler:
|
||||
// Handler function for when the reliable transport goes down. It will be
|
||||
// called when the reliable transport is no longer available to carry
|
||||
// traffic.
|
||||
//
|
||||
// Both handler functions must have the signature
|
||||
//
|
||||
// void transport_state_handler(void);
|
||||
|
||||
#if !DISABLE_PROMPT
|
||||
ON_PACKET(PULSE2_RELIABLE_PROMPT_PROTOCOL, pulse2_prompt_packet_handler)
|
||||
#endif
|
||||
|
||||
ON_PACKET(PULSE2_BULKIO_PROTOCOL, pulse2_bulkio_packet_handler)
|
||||
ON_TRANSPORT_STATE_CHANGE(pulse2_bulkio_link_open_handler, pulse2_bulkio_link_closed_handler)
|
||||
|
||||
ON_PACKET(PULSE2_PEBBLE_PROTOCOL, pulse_pp_transport_handle_received_data)
|
||||
ON_TRANSPORT_STATE_CHANGE(pulse_pp_transport_open_handler, pulse_pp_transport_closed_handler)
|
||||
|
||||
// vim: filetype=c
|
35
src/fw/console/pulse2_reliable_retransmit_timer.h
Normal file
35
src/fw/console/pulse2_reliable_retransmit_timer.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//! Retransmit timer for PULSEv2 Reliable Transport
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//! Start or restart the PULSEv2 reliable transport retransmit timer.
|
||||
void pulse2_reliable_retransmit_timer_start(
|
||||
unsigned int timeout_ms, uint8_t sequence_number);
|
||||
|
||||
//! Cancel a running retransmit timer if it has not already expired.
|
||||
//!
|
||||
//! It is a no-op to call this function when the timer is already stopped.
|
||||
void pulse2_reliable_retransmit_timer_cancel(void);
|
||||
|
||||
//! The function which is called when the retransmit timer expires.
|
||||
//!
|
||||
//! It is executed from the context of the PULSE task.
|
||||
void pulse2_reliable_retransmit_timer_expired_handler(uint8_t sequence_number);
|
56
src/fw/console/pulse2_transport_impl.h
Normal file
56
src/fw/console/pulse2_transport_impl.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Get the max send size of the link.
|
||||
size_t pulse_link_max_send_size(void);
|
||||
|
||||
//! Begin constructing a packet to send out the PULSE2 link.
|
||||
//!
|
||||
//! \param protocol encapsulation protocol number
|
||||
void *pulse_link_send_begin(uint16_t protocol);
|
||||
|
||||
//! Send a packet out the PULSE2 link.
|
||||
//! \param [out] buf buffer containing the packet data to send. Must be
|
||||
//! a buffer pointer returned by \ref pulse_link_send_begin.
|
||||
//! \param length length of the packet in buf. Must not exceed the value
|
||||
//! returned by \ref pulse_link_max_send_size.
|
||||
void pulse_link_send(void *buf, size_t length);
|
||||
|
||||
//! Release a transmit buffer without sending the packet.
|
||||
//!
|
||||
//! \param buf buffer to be released. Must be a buffer pointer returned
|
||||
//! by \ref pulse_link_send_begin.
|
||||
void pulse_link_send_cancel(void *buf);
|
||||
|
||||
|
||||
// Use preprocessor magic to generate function signatures for all protocol
|
||||
// handler functions.
|
||||
#define ON_PACKET(NUMBER, PACKET_HANDLER) \
|
||||
void PACKET_HANDLER(void *packet, size_t length);
|
||||
#define ON_INIT(INITIALIZER) \
|
||||
void INITIALIZER(void);
|
||||
#define ON_LINK_STATE_CHANGE(ON_UP, ON_DOWN) \
|
||||
void ON_UP(void); \
|
||||
void ON_DOWN(void);
|
||||
#include "console/pulse2_transport_registry.def"
|
||||
#undef ON_PACKET
|
||||
#undef ON_INIT
|
||||
#undef ON_LINK_STATE_CHANGE
|
71
src/fw/console/pulse2_transport_registry.def
Normal file
71
src/fw/console/pulse2_transport_registry.def
Normal file
|
@ -0,0 +1,71 @@
|
|||
// PULSE2 transport protocol event registry
|
||||
|
||||
#ifndef _PULSE2_TRANSPORT_PROTOCOL_NUMBERS_DEFINED
|
||||
#define _PULSE2_TRANSPORT_PROTOCOL_NUMBERS_DEFINED
|
||||
|
||||
#define PULSE2_BEST_EFFORT_CONTROL_PROTOCOL (0xBA29)
|
||||
#define PULSE2_BEST_EFFORT_TRANSPORT_PROTOCOL (0x3A29)
|
||||
|
||||
#define PULSE2_RELIABLE_CONTROL_PROTOCOL (0xBA33)
|
||||
#define PULSE2_RELIABLE_TRANSPORT_COMMAND (0x3A33)
|
||||
#define PULSE2_RELIABLE_TRANSPORT_RESPONSE (0x3A35)
|
||||
|
||||
#endif
|
||||
|
||||
// Available directives:
|
||||
//
|
||||
// ON_PACKET(number, packet_handler)
|
||||
//
|
||||
// number:
|
||||
// The link-layer protocol number (uint16_t)
|
||||
//
|
||||
// packet_handler:
|
||||
// Handler function for incoming packets.
|
||||
// The handler function must have the signature
|
||||
//
|
||||
// void packet_handler(void *packet, size_t length);
|
||||
//
|
||||
// ON_INIT(initializer)
|
||||
//
|
||||
// initializer:
|
||||
// Initializer function for the protocol. It will be called exactly once
|
||||
// sometime before any of the handler functions are called. The function
|
||||
// must have the signature
|
||||
//
|
||||
// void init(void);
|
||||
//
|
||||
// This argument may be NULL if no initialization is required.
|
||||
//
|
||||
// ON_LINK_STATE_CHANGE(link_layer_up_handler, link_layer_down_handler)
|
||||
//
|
||||
// link_layer_up_handler:
|
||||
// Handler function for when the link layer comes up. It will be called
|
||||
// when the link layer is ready to carry traffic.
|
||||
//
|
||||
// link_layer_down_handler:
|
||||
// Handler function for when the link layer goes down. It will be called
|
||||
// when the link layer is no longer available to carry traffic.
|
||||
//
|
||||
// Both handler functions must have the signature
|
||||
//
|
||||
// void link_state_handler(void);
|
||||
|
||||
ON_PACKET(PULSE2_BEST_EFFORT_CONTROL_PROTOCOL,
|
||||
pulse2_best_effort_control_on_packet)
|
||||
ON_PACKET(PULSE2_BEST_EFFORT_TRANSPORT_PROTOCOL,
|
||||
pulse2_best_effort_transport_on_packet)
|
||||
ON_INIT(pulse2_best_effort_init)
|
||||
ON_LINK_STATE_CHANGE(pulse2_best_effort_on_link_up,
|
||||
pulse2_best_effort_on_link_down)
|
||||
|
||||
ON_PACKET(PULSE2_RELIABLE_CONTROL_PROTOCOL,
|
||||
pulse2_reliable_control_on_packet)
|
||||
ON_PACKET(PULSE2_RELIABLE_TRANSPORT_COMMAND,
|
||||
pulse2_reliable_transport_on_command_packet)
|
||||
ON_PACKET(PULSE2_RELIABLE_TRANSPORT_RESPONSE,
|
||||
pulse2_reliable_transport_on_response_packet)
|
||||
ON_INIT(pulse2_reliable_init)
|
||||
ON_LINK_STATE_CHANGE(pulse2_reliable_on_link_up,
|
||||
pulse2_reliable_on_link_down)
|
||||
|
||||
// vim: filetype=c
|
536
src/fw/console/pulse_bulkio.c
Normal file
536
src/fw/console/pulse_bulkio.c
Normal file
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "console/pulse_bulkio_domain_handler.h"
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
#include "console/pulse2_transport_impl.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/crc32.h"
|
||||
#include "util/math.h"
|
||||
#include "util/size.h"
|
||||
|
||||
// Defines how many PULSE file descriptors may be open concurrently
|
||||
// This is shared across all supported domains
|
||||
#define MAX_PULSE_FDS 3
|
||||
|
||||
#define BULKIO_CMD_DOMAIN_OPEN (1)
|
||||
#define BULKIO_CMD_DOMAIN_CLOSE (2)
|
||||
#define BULKIO_CMD_DOMAIN_READ (3)
|
||||
#define BULKIO_CMD_DOMAIN_WRITE (4)
|
||||
#define BULKIO_CMD_DOMAIN_CRC (5)
|
||||
#define BULKIO_CMD_DOMAIN_STAT (6)
|
||||
#define BULKIO_CMD_DOMAIN_ERASE (7)
|
||||
|
||||
#define BULKIO_RESP_DOMAIN_OPEN (128)
|
||||
#define BULKIO_RESP_DOMAIN_CLOSE (129)
|
||||
#define BULKIO_RESP_DOMAIN_READ (130)
|
||||
#define BULKIO_RESP_DOMAIN_WRITE (131)
|
||||
#define BULKIO_RESP_DOMAIN_CRC (132)
|
||||
#define BULKIO_RESP_DOMAIN_STAT (133)
|
||||
#define BULKIO_RESP_DOMAIN_ERASE (134)
|
||||
|
||||
#define BULKIO_RESP_MALFORMED_CMD (192)
|
||||
#define BULKIO_RESP_INTERNAL_ERROR (193)
|
||||
|
||||
typedef struct PACKED Command {
|
||||
uint8_t opcode;
|
||||
union {
|
||||
uint8_t fd;
|
||||
struct PACKED OpenCommand {
|
||||
uint8_t domain;
|
||||
uint8_t data[0];
|
||||
} open;
|
||||
struct PACKED CloseCommand {
|
||||
uint8_t fd;
|
||||
} close;
|
||||
struct PACKED ReadCommand {
|
||||
uint8_t fd;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
} read;
|
||||
struct PACKED WriteCommand {
|
||||
uint8_t fd;
|
||||
uint32_t address;
|
||||
uint8_t data[0];
|
||||
} write;
|
||||
struct PACKED CRCCommand {
|
||||
uint8_t fd;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
} crc;
|
||||
struct PACKED StatCommand {
|
||||
uint8_t fd;
|
||||
} stat;
|
||||
struct PACKED EraseCommand {
|
||||
uint8_t domain;
|
||||
uint8_t cookie;
|
||||
uint8_t data[0];
|
||||
} erase;
|
||||
};
|
||||
} Command;
|
||||
|
||||
typedef struct PACKED OpenResponse {
|
||||
uint8_t opcode;
|
||||
uint8_t fd;
|
||||
} OpenResponse;
|
||||
|
||||
typedef struct PACKED CloseResponse {
|
||||
uint8_t opcode;
|
||||
uint8_t fd;
|
||||
} CloseResponse;
|
||||
|
||||
typedef struct PACKED ReadResponse {
|
||||
uint8_t opcode;
|
||||
uint8_t fd;
|
||||
uint32_t offset;
|
||||
uint8_t data[0];
|
||||
} ReadResponse;
|
||||
|
||||
typedef struct PACKED WriteResponse {
|
||||
uint8_t opcode;
|
||||
uint8_t fd;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
} WriteResponse;
|
||||
|
||||
typedef struct PACKED CRCResponse {
|
||||
uint8_t opcode;
|
||||
uint8_t fd;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
uint32_t crc;
|
||||
} CRCResponse;
|
||||
|
||||
typedef struct PACKED StatResponse {
|
||||
uint8_t opcode;
|
||||
uint8_t fd;
|
||||
uint8_t data[0];
|
||||
} StatResponse;
|
||||
|
||||
typedef struct PACKED EraseResponse {
|
||||
uint8_t opcode;
|
||||
uint8_t domain;
|
||||
uint8_t cookie;
|
||||
int8_t status;
|
||||
} EraseResponse;
|
||||
|
||||
typedef struct PACKED InternalErrorResponse {
|
||||
uint8_t opcode;
|
||||
int32_t status_code;
|
||||
uint8_t bad_command[0];
|
||||
} InternalErrorResponse;
|
||||
|
||||
#define REGISTER_BULKIO_HANDLER(domain_type, domain_id, vtable) \
|
||||
extern PulseBulkIODomainHandler vtable;
|
||||
#include "pulse_bulkio_handler.def"
|
||||
#undef REGISTER_BULKIO_HANDLER
|
||||
|
||||
static PulseBulkIODomainHandler * const s_domain_handlers[] = {
|
||||
#define REGISTER_BULKIO_HANDLER(domain_type, domain_id, vtable) \
|
||||
[PulseBulkIODomainType_ ## domain_type] = &vtable,
|
||||
#include "pulse_bulkio_handler.def"
|
||||
#undef REGISTER_BULKIO_HANDLER
|
||||
};
|
||||
|
||||
#define NUM_DOMAIN_HANDLERS ARRAY_LENGTH(s_domain_handlers)
|
||||
|
||||
typedef struct ReadTransferState {
|
||||
uint32_t offset;
|
||||
uint32_t bytes_left;
|
||||
} ReadTransferState;
|
||||
|
||||
typedef struct PulseTransferFD {
|
||||
// impl == NULL means that the FD is free
|
||||
PulseBulkIODomainHandler *impl;
|
||||
void *domain_state;
|
||||
ReadTransferState transfer_state;
|
||||
} PulseTransferFD;
|
||||
|
||||
typedef struct BulkIOPacketCallbackData {
|
||||
size_t length;
|
||||
uint8_t packet[0];
|
||||
} BulkIOPacketCallbackData;
|
||||
|
||||
static PulseTransferFD s_transfer_fds[MAX_PULSE_FDS];
|
||||
|
||||
static void prv_respond_malformed_command(void *cmd, size_t length,
|
||||
const char *message) {
|
||||
uint8_t *resp = pulse_reliable_send_begin(PULSE2_BULKIO_PROTOCOL);
|
||||
resp[0] = BULKIO_RESP_MALFORMED_CMD;
|
||||
|
||||
size_t message_len = strlen(message) + 1;
|
||||
memcpy(resp + 1, message, message_len);
|
||||
|
||||
size_t response_len = sizeof(*resp) + message_len;
|
||||
size_t command_len = MIN(length, PULSE_MAX_SEND_SIZE - response_len);
|
||||
|
||||
memcpy(resp + response_len, cmd, command_len);
|
||||
response_len += command_len;
|
||||
|
||||
pulse_reliable_send(resp, response_len);
|
||||
}
|
||||
|
||||
static void prv_respond_internal_error(Command *cmd, size_t length,
|
||||
status_t status_code) {
|
||||
InternalErrorResponse *resp = pulse_reliable_send_begin(
|
||||
PULSE2_BULKIO_PROTOCOL);
|
||||
resp->opcode = BULKIO_RESP_INTERNAL_ERROR;
|
||||
resp->status_code = status_code;
|
||||
|
||||
size_t response_len = sizeof(*resp);
|
||||
size_t command_len = MIN(length, PULSE_MAX_SEND_SIZE - response_len);
|
||||
response_len += command_len;
|
||||
|
||||
memcpy(resp->bad_command, cmd, command_len);
|
||||
pulse_reliable_send(resp, response_len);
|
||||
}
|
||||
|
||||
static int prv_get_fresh_fd(PulseBulkIODomainHandler *domain_handler, PulseTransferFD **fd) {
|
||||
for (int i=0; i < MAX_PULSE_FDS; ++i) {
|
||||
if (s_transfer_fds[i].impl == NULL) {
|
||||
s_transfer_fds[i] = (PulseTransferFD) {
|
||||
.impl = domain_handler,
|
||||
.domain_state = NULL,
|
||||
.transfer_state = { 0 }
|
||||
};
|
||||
*fd = &s_transfer_fds[i];
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void prv_free_fd(int fd) {
|
||||
s_transfer_fds[fd].impl = NULL;
|
||||
}
|
||||
|
||||
PulseTransferFD* prv_get_fd(Command *cmd, size_t length) {
|
||||
int fd = cmd->fd;
|
||||
PulseTransferFD *pulse_fd = &s_transfer_fds[fd];
|
||||
if (fd >= 0 && fd < MAX_PULSE_FDS && pulse_fd && pulse_fd->impl) {
|
||||
return pulse_fd;
|
||||
} else {
|
||||
// Invalid, closed or out of range FD
|
||||
prv_respond_internal_error(cmd, length, E_INVALID_ARGUMENT);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static PulseBulkIODomainHandler* prv_get_domain_handler(uint8_t domain_id) {
|
||||
for (uint8_t i = 0; i < NUM_DOMAIN_HANDLERS; i++) {
|
||||
PulseBulkIODomainHandler *domain_handler = s_domain_handlers[i];
|
||||
if (domain_handler && domain_handler->id == domain_id) {
|
||||
return domain_handler;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void prv_domain_read_cb(void *data) {
|
||||
unsigned int fd_num = (uintptr_t)data;
|
||||
PulseTransferFD *pulse_fd = &s_transfer_fds[fd_num];
|
||||
|
||||
const size_t max_read_len = (PULSE_MAX_SEND_SIZE - sizeof(ReadResponse));
|
||||
size_t read_len = MIN(pulse_fd->transfer_state.bytes_left, max_read_len);
|
||||
|
||||
ReadResponse *resp = pulse_reliable_send_begin(PULSE2_BULKIO_PROTOCOL);
|
||||
resp->opcode = BULKIO_RESP_DOMAIN_READ;
|
||||
resp->offset = pulse_fd->transfer_state.offset;
|
||||
|
||||
int ret = pulse_fd->impl->read_proc(resp->data, resp->offset, read_len, pulse_fd->domain_state);
|
||||
if (ret > 0) {
|
||||
read_len = ret;
|
||||
pulse_fd->transfer_state.bytes_left -= read_len;
|
||||
pulse_fd->transfer_state.offset += read_len;
|
||||
|
||||
pulse_reliable_send(resp, read_len + sizeof(ReadResponse));
|
||||
|
||||
if (pulse_fd->transfer_state.bytes_left > 0) {
|
||||
system_task_add_callback(prv_domain_read_cb, (void*)(uintptr_t)fd_num);
|
||||
}
|
||||
} else {
|
||||
pulse_reliable_send_cancel(resp);
|
||||
|
||||
Command cmd = {
|
||||
.opcode = BULKIO_CMD_DOMAIN_READ,
|
||||
.read = {
|
||||
.fd = fd_num
|
||||
}
|
||||
};
|
||||
|
||||
prv_respond_internal_error(&cmd, sizeof(cmd), ret);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_open(Command *cmd, size_t length) {
|
||||
PulseBulkIODomainHandler *domain_handler = prv_get_domain_handler(cmd->open.domain);
|
||||
|
||||
if (!domain_handler) {
|
||||
prv_respond_malformed_command(cmd, length, "Unknown domain");
|
||||
return;
|
||||
}
|
||||
|
||||
PulseTransferFD *state = NULL;
|
||||
int fd = prv_get_fresh_fd(domain_handler, &state);
|
||||
|
||||
if (FAILED(fd)) {
|
||||
prv_respond_internal_error(cmd, length, E_OUT_OF_RESOURCES);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t payload_length = length - sizeof(cmd->opcode) - sizeof(cmd->open);
|
||||
status_t ret = state->impl->open_proc(cmd->open.data, payload_length, &state->domain_state);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
prv_free_fd(fd);
|
||||
|
||||
if (ret == E_INVALID_ARGUMENT) {
|
||||
prv_respond_malformed_command(cmd, length, "Invalid domain data");
|
||||
} else {
|
||||
prv_respond_internal_error(cmd, length, ret);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
OpenResponse *resp = pulse_reliable_send_begin(PULSE2_BULKIO_PROTOCOL);
|
||||
resp->opcode = BULKIO_RESP_DOMAIN_OPEN;
|
||||
resp->fd = fd;
|
||||
pulse_reliable_send(resp, sizeof(*resp));
|
||||
}
|
||||
|
||||
static void prv_handle_close(Command *cmd, size_t length) {
|
||||
PulseTransferFD *pulse_fd = prv_get_fd(cmd, length);
|
||||
if (!pulse_fd) {
|
||||
// prv_get_fd has already sent an error response
|
||||
return;
|
||||
}
|
||||
|
||||
status_t status = pulse_fd->impl->close_proc(pulse_fd->domain_state);
|
||||
|
||||
if (FAILED(status)) {
|
||||
prv_respond_internal_error(cmd, length, status);
|
||||
return;
|
||||
}
|
||||
|
||||
CloseResponse *resp = pulse_reliable_send_begin(PULSE2_BULKIO_PROTOCOL);
|
||||
resp->opcode = BULKIO_RESP_DOMAIN_CLOSE;
|
||||
resp->fd = cmd->close.fd;
|
||||
pulse_reliable_send(resp, sizeof(*resp));
|
||||
|
||||
prv_free_fd(cmd->close.fd);
|
||||
}
|
||||
|
||||
static void prv_handle_read(Command *cmd, size_t length) {
|
||||
if (cmd->read.length == 0) {
|
||||
prv_respond_internal_error(cmd, length, E_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
PulseTransferFD *pulse_fd = prv_get_fd(cmd, length);
|
||||
if (!pulse_fd) {
|
||||
// prv_get_fd has already sent an error response
|
||||
return;
|
||||
}
|
||||
|
||||
pulse_fd->transfer_state.offset = cmd->read.address;
|
||||
pulse_fd->transfer_state.bytes_left = cmd->read.length;
|
||||
|
||||
system_task_add_callback(prv_domain_read_cb, (void*)(uintptr_t)cmd->fd);
|
||||
}
|
||||
|
||||
static void prv_handle_write(Command *cmd, size_t length) {
|
||||
PulseTransferFD *pulse_fd = prv_get_fd(cmd, length);
|
||||
if (!pulse_fd) {
|
||||
// prv_get_fd has already sent an error response
|
||||
return;
|
||||
}
|
||||
|
||||
size_t payload_length = length - sizeof(cmd->opcode) - sizeof(cmd->write);
|
||||
int ret = pulse_fd->impl->write_proc(cmd->write.data, cmd->write.address, payload_length,
|
||||
pulse_fd->domain_state);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
prv_respond_internal_error(cmd, length, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
WriteResponse *resp = pulse_reliable_send_begin(PULSE2_BULKIO_PROTOCOL);
|
||||
*resp = (WriteResponse) {
|
||||
.opcode = BULKIO_RESP_DOMAIN_WRITE,
|
||||
.fd = cmd->write.fd,
|
||||
.address = cmd->write.address,
|
||||
.length = payload_length
|
||||
};
|
||||
pulse_reliable_send(resp, sizeof(*resp));
|
||||
}
|
||||
|
||||
static void prv_handle_crc(Command *cmd, size_t length) {
|
||||
PulseTransferFD *pulse_fd = prv_get_fd(cmd, length);
|
||||
if (!pulse_fd) {
|
||||
// prv_get_fd has already sent an error response
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t bytes_read = 0;
|
||||
const unsigned int chunk_size = 128;
|
||||
uint8_t buffer[chunk_size];
|
||||
|
||||
uint32_t crc = crc32(0, NULL, 0);
|
||||
while (bytes_read < cmd->crc.length) {
|
||||
uint32_t read_len = MIN(cmd->crc.length - bytes_read, chunk_size);
|
||||
int ret = pulse_fd->impl->read_proc(buffer, cmd->crc.address+bytes_read, read_len,
|
||||
pulse_fd->domain_state);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
prv_respond_internal_error(cmd, length, E_INTERNAL);
|
||||
return;
|
||||
}
|
||||
|
||||
bytes_read += ret;
|
||||
crc = crc32(crc, buffer, read_len);
|
||||
}
|
||||
|
||||
CRCResponse *resp = pulse_reliable_send_begin(PULSE2_BULKIO_PROTOCOL);
|
||||
*resp = (CRCResponse) {
|
||||
.opcode = BULKIO_RESP_DOMAIN_CRC,
|
||||
.fd = cmd->crc.fd,
|
||||
.address = cmd->crc.address,
|
||||
.length = bytes_read,
|
||||
.crc = crc
|
||||
};
|
||||
pulse_reliable_send(resp, sizeof(*resp));
|
||||
}
|
||||
|
||||
static void prv_handle_stat(Command *cmd, size_t length) {
|
||||
PulseTransferFD *pulse_fd = prv_get_fd(cmd, length);
|
||||
if (!pulse_fd) {
|
||||
// prv_get_fd has already sent an error response
|
||||
return;
|
||||
}
|
||||
|
||||
StatResponse *resp = pulse_reliable_send_begin(PULSE2_BULKIO_PROTOCOL);
|
||||
*resp = (StatResponse) {
|
||||
.opcode = BULKIO_RESP_DOMAIN_STAT,
|
||||
.fd = cmd->stat.fd
|
||||
};
|
||||
size_t data_max_len = PULSE_MAX_SEND_SIZE - sizeof(StatResponse);
|
||||
int ret = pulse_fd->impl->stat_proc(resp->data, data_max_len, pulse_fd->domain_state);
|
||||
if (ret >= 0) {
|
||||
pulse_reliable_send(resp, ret + sizeof(*resp));
|
||||
} else {
|
||||
pulse_reliable_send_cancel(resp);
|
||||
prv_respond_internal_error(cmd, length, ret);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_erase(Command *cmd, size_t length) {
|
||||
PulseBulkIODomainHandler *domain_handler = prv_get_domain_handler(cmd->erase.domain);
|
||||
|
||||
if (domain_handler) {
|
||||
size_t payload_length = length - sizeof(cmd->opcode) - sizeof(cmd->erase);
|
||||
status_t ret = domain_handler->erase_proc(cmd->erase.data, payload_length, cmd->erase.cookie);
|
||||
|
||||
if (ret == E_INVALID_ARGUMENT) {
|
||||
prv_respond_malformed_command(cmd, length, "Invalid domain data");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret == S_TRUE) {
|
||||
// Handler will send responses itself, we're done here
|
||||
return;
|
||||
}
|
||||
|
||||
pulse_bulkio_erase_message_send(cmd->erase.domain, ret, cmd->erase.cookie);
|
||||
} else {
|
||||
prv_respond_malformed_command(cmd, length, "Unknown domain");
|
||||
}
|
||||
}
|
||||
|
||||
void pulse_bulkio_erase_message_send(PulseBulkIODomainType domain_type, status_t status,
|
||||
uint8_t cookie) {
|
||||
EraseResponse *resp = pulse_reliable_send_begin(PULSE2_BULKIO_PROTOCOL);
|
||||
resp->opcode = BULKIO_RESP_DOMAIN_ERASE;
|
||||
resp->domain = domain_type;
|
||||
resp->status = status;
|
||||
resp->cookie = cookie;
|
||||
pulse_reliable_send(resp, sizeof(*resp));
|
||||
}
|
||||
|
||||
static void prv_handle_packet(void *data) {
|
||||
BulkIOPacketCallbackData *callback_data = data;
|
||||
Command *cmd = (Command*)&callback_data->packet;
|
||||
size_t length = callback_data->length;
|
||||
if (length) {
|
||||
switch (cmd->opcode) {
|
||||
case BULKIO_CMD_DOMAIN_OPEN:
|
||||
prv_handle_open(cmd, length);
|
||||
break;
|
||||
case BULKIO_CMD_DOMAIN_CLOSE:
|
||||
prv_handle_close(cmd, length);
|
||||
break;
|
||||
case BULKIO_CMD_DOMAIN_READ:
|
||||
prv_handle_read(cmd, length);
|
||||
break;
|
||||
case BULKIO_CMD_DOMAIN_WRITE:
|
||||
prv_handle_write(cmd, length);
|
||||
break;
|
||||
case BULKIO_CMD_DOMAIN_CRC:
|
||||
prv_handle_crc(cmd, length);
|
||||
break;
|
||||
case BULKIO_CMD_DOMAIN_STAT:
|
||||
prv_handle_stat(cmd, length);
|
||||
break;
|
||||
case BULKIO_CMD_DOMAIN_ERASE:
|
||||
prv_handle_erase(cmd, length);
|
||||
break;
|
||||
default:
|
||||
prv_respond_malformed_command(cmd, length, "Unknown command opcode");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
prv_respond_malformed_command(cmd, length, "Empty command");
|
||||
}
|
||||
|
||||
kernel_free(data);
|
||||
}
|
||||
|
||||
void pulse2_bulkio_packet_handler(void *packet, size_t length) {
|
||||
BulkIOPacketCallbackData *data = kernel_malloc_check(length + sizeof(length));
|
||||
data->length = length;
|
||||
memcpy(&data->packet, packet, length);
|
||||
system_task_add_callback(prv_handle_packet, data);
|
||||
}
|
||||
|
||||
void pulse2_bulkio_link_open_handler(void) {
|
||||
}
|
||||
|
||||
void pulse2_bulkio_link_closed_handler(void) {
|
||||
for (int i = 0; i < MAX_PULSE_FDS; i++) {
|
||||
if (s_transfer_fds[i].impl) {
|
||||
PulseTransferFD *pulse_fd = &s_transfer_fds[i];
|
||||
pulse_fd->impl->close_proc(pulse_fd->domain_state);
|
||||
prv_free_fd(i);
|
||||
}
|
||||
}
|
||||
}
|
98
src/fw/console/pulse_bulkio_coredump.c
Normal file
98
src/fw/console/pulse_bulkio_coredump.c
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "pulse_bulkio_domain_handler.h"
|
||||
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "kernel/core_dump.h"
|
||||
#include "kernel/core_dump_private.h"
|
||||
#include "system/status_codes.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct PACKED CoredumpStatResp {
|
||||
uint8_t flags;
|
||||
uint8_t unread;
|
||||
uint32_t size;
|
||||
} CoredumpStatResp;
|
||||
|
||||
static uint32_t prv_get_coredump_index(void *packet_data) {
|
||||
return *(uint32_t*)packet_data;
|
||||
}
|
||||
|
||||
static int coredump_domain_read(uint8_t *buf, uint32_t address, uint32_t length,
|
||||
void *context) {
|
||||
uint32_t index = (uintptr_t)context;
|
||||
uint32_t core_base_addr = core_dump_get_slot_address(index) + sizeof(CoreDumpFlashRegionHeader);
|
||||
flash_read_bytes(buf, core_base_addr + address, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static int coredump_domain_write(uint8_t *buf, uint32_t address, uint32_t length,
|
||||
void *context) {
|
||||
return E_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
static int coredump_domain_stat(uint8_t *resp, size_t resp_max_len, void *context) {
|
||||
uint32_t index = (uintptr_t)context;
|
||||
uint32_t addr = core_dump_get_slot_address(index);
|
||||
|
||||
CoredumpStatResp *stat_resp = (CoredumpStatResp*)resp;
|
||||
*stat_resp = (CoredumpStatResp) {
|
||||
.flags = 0,
|
||||
.unread = core_dump_is_unread_available(addr),
|
||||
// Size of 0 indicates no core dump available
|
||||
.size = 0
|
||||
};
|
||||
|
||||
if (stat_resp->unread == 1) {
|
||||
status_t ret = core_dump_size(addr, &stat_resp->size);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return sizeof(CoredumpStatResp);
|
||||
}
|
||||
|
||||
static status_t coredump_domain_erase(uint8_t *packet_data, size_t length, uint8_t cookie) {
|
||||
uint32_t index = prv_get_coredump_index(packet_data);
|
||||
uint32_t addr = core_dump_get_slot_address(index);
|
||||
core_dump_mark_read(addr);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static status_t coredump_domain_open(uint8_t *packet_data, size_t length, void **resp) {
|
||||
*resp = (void*)(uintptr_t)prv_get_coredump_index(packet_data);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static status_t coredump_domain_close(void *context) {
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
PulseBulkIODomainHandler pulse_bulkio_domain_coredump = {
|
||||
.id = PulseBulkIODomainType_Coredump,
|
||||
.open_proc = coredump_domain_open,
|
||||
.close_proc = coredump_domain_close,
|
||||
.read_proc = coredump_domain_read,
|
||||
.write_proc = coredump_domain_write,
|
||||
.stat_proc = coredump_domain_stat,
|
||||
.erase_proc = coredump_domain_erase
|
||||
};
|
126
src/fw/console/pulse_bulkio_domain_handler.h
Normal file
126
src/fw/console/pulse_bulkio_domain_handler.h
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "system/status_codes.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
#define REGISTER_BULKIO_HANDLER(domain_type, domain_id, vtable) \
|
||||
PulseBulkIODomainType_ ## domain_type = domain_id,
|
||||
#include "pulse_bulkio_handler.def"
|
||||
#undef REGISTER_BULKIO_HANDLER
|
||||
} PulseBulkIODomainType;
|
||||
|
||||
typedef const struct PulseBulkIODomainHandler {
|
||||
PulseBulkIODomainType id;
|
||||
|
||||
//! Open a new Pulse BulkIO context
|
||||
//!
|
||||
//! @param packet_data pointer to domain specific data passed by the host
|
||||
//! which you may use to identify what was requested to be opened
|
||||
//! @param length length of the provided packet_data buffer
|
||||
//! @param [out] resp state pointer that can be used by domains to store
|
||||
//! state, to be passed to future calls to this handler for this
|
||||
//! specific context
|
||||
//! @return S_SUCCESS if the domain context has been opened, E_INVALID_ARGUMENT
|
||||
//! if opening the domain context has failed because the domain data is
|
||||
//! malformed or otherwise bad, or E_INTERNAL if opening the domain
|
||||
//! context has failed for reasons unrelated to the domain data. Any
|
||||
//! other negative return values are treated the same as E_INTERNAL.
|
||||
//!
|
||||
//! Any resources that the open_proc method has acquired to open the domain
|
||||
//! context are owned by the domain handler; it is the domain handler's
|
||||
//! responsibility to release these resources in the domain handler's
|
||||
//! close_proc.
|
||||
//!
|
||||
//! The caller will discard the state pointer and will not call close_proc when
|
||||
//! open_proc returns an error status code. The open_proc method must
|
||||
//! release any resources it has acquired before returning an error status.
|
||||
status_t (*open_proc)(uint8_t *packet_data, size_t length, void **resp);
|
||||
|
||||
//! Close an existing open Pulse BulkIO context
|
||||
//!
|
||||
//! @param data state pointer as set by the open_proc method
|
||||
//! @return non-negative value on success, negative status code if an
|
||||
//! internal error occurred.
|
||||
//!
|
||||
//! The domain context is assumed to be closed and related resources released
|
||||
//! when this method returns, regardless of return value.
|
||||
status_t (*close_proc)(void *context);
|
||||
|
||||
//! Read data from an open Pulse BulkIO context
|
||||
//!
|
||||
//! @param [out] buf buffer for read data to be copied into
|
||||
//! @param address offset which data has been requested to be read from
|
||||
//! @param length length of data requested to be read
|
||||
//! @param data state pointer as set by the open_proc method
|
||||
//! @return number of bytes read or a negative error code (see status_codes.h)
|
||||
//! If an error code is returned, it is sent to the host as an internal
|
||||
//! error response and no further read calls will be made until a new
|
||||
//! command is received.
|
||||
int (*read_proc)(uint8_t *buf, uint32_t address, uint32_t length, void *context);
|
||||
|
||||
//! Write data to an open Pulse BulkIO context
|
||||
//!
|
||||
//! @param buf buffer of data to be written
|
||||
//! @param address offset which data has been requested to be written to
|
||||
//! @param length length of data requested to be written
|
||||
//! @param data state pointer as set by the open_proc method
|
||||
//! @return number of bytes written or a negative error code (see status_codes.h)
|
||||
//! If an error code is returned, it is sent to the host as an internal
|
||||
//! error response.
|
||||
int (*write_proc)(uint8_t *buf, uint32_t address, uint32_t length, void *context);
|
||||
|
||||
//! Stat an existing PULSE BulkIO context. This operation should be used to allow the host to
|
||||
//! query for information (size, flags etc) about a specific item within the domain
|
||||
//! or the entire domain if there is no concept of multiple items (eg framebuffer
|
||||
//! domain)
|
||||
//!
|
||||
//! @param [out] resp buffer for domain specific stat response to be written to.
|
||||
//! @param resp_max_len length of the resp buffer, response may be smaller than this
|
||||
//! @param data state pointer as set by the open_proc method
|
||||
//! @return number of bytes written to buffer or a negative error code (see status_codes.h)
|
||||
//! If an error code is returned, it is sent to the host as an internal
|
||||
//! error response and the data in the buffer is discarded.
|
||||
int (*stat_proc)(uint8_t *resp, size_t resp_max_len, void *context);
|
||||
|
||||
//! Erase data in this domain
|
||||
//!
|
||||
//! @param packet_data pointer to domain specific data passed by the host
|
||||
//! which erase_proc may use to identify what was requested to be erased
|
||||
//! @param length length of the provided packet_data buffer
|
||||
//! @param cookie an opaque cookie value to be passed through to all calls to
|
||||
//! pulse_bulkio_erase_message_send
|
||||
//! @return status of erase operation - return S_TRUE if the erase is still in progress
|
||||
//! and the handler will send status updates as it progresses, or S_SUCCESS
|
||||
//! if the operation failed or was completed. If erase_proc returns S_TRUE
|
||||
//! to denote an erase still in progress, the domain handler must send
|
||||
//! further status updates using pulse_bulkio_erase_message_send. The
|
||||
//! caller will not send any response itself.
|
||||
status_t (*erase_proc)(uint8_t *packet_data, size_t length, uint8_t cookie);
|
||||
} PulseBulkIODomainHandler;
|
||||
|
||||
//! Send erase progress for an ongoing erase operation
|
||||
//!
|
||||
//! @param domain_type domain from which the message should originate
|
||||
//! @param status status of erase operation. Status codes may be treated differently
|
||||
//! by different domain handlers, the code is sent verbatim in an erase response.
|
||||
//! @param cookie an opaque cookie value to be passed through for all erase responses.
|
||||
//! Value provided via erase_proc argument.
|
||||
void pulse_bulkio_erase_message_send(PulseBulkIODomainType domain_type, status_t status,
|
||||
uint8_t cookie);
|
113
src/fw/console/pulse_bulkio_external_flash.c
Normal file
113
src/fw/console/pulse_bulkio_external_flash.c
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "pulse_bulkio_domain_handler.h"
|
||||
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/status_codes.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct PACKED ExternalFlashEraseOptions {
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
} ExternalFlashEraseOptions;
|
||||
|
||||
typedef struct ExternalFlashEraseState {
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
unsigned int next_sector;
|
||||
uint8_t cookie;
|
||||
} ExternalFlashEraseState;
|
||||
|
||||
static int external_flash_domain_read(uint8_t *buf, uint32_t address, uint32_t length,
|
||||
void *context) {
|
||||
flash_read_bytes(buf, address, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static int external_flash_domain_write(uint8_t *buf, uint32_t address, uint32_t length,
|
||||
void *context) {
|
||||
flash_write_bytes(buf, address, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static int external_flash_domain_stat(uint8_t *resp, size_t resp_max_len, void *context) {
|
||||
return E_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
static void prv_erase_sector(void *context, status_t result) {
|
||||
ExternalFlashEraseState *state = context;
|
||||
|
||||
const unsigned int sectors_to_erase = (
|
||||
state->length + SECTOR_SIZE_BYTES - 1) / SECTOR_SIZE_BYTES;
|
||||
|
||||
if (FAILED(result)) {
|
||||
pulse_bulkio_erase_message_send(PulseBulkIODomainType_ExternalFlash, result, state->cookie);
|
||||
kernel_free(state);
|
||||
} else if (state->next_sector < sectors_to_erase) {
|
||||
unsigned int sector_addr = state->address + state->next_sector * SECTOR_SIZE_BYTES;
|
||||
state->next_sector += 1;
|
||||
pulse_bulkio_erase_message_send(PulseBulkIODomainType_ExternalFlash, S_TRUE, state->cookie);
|
||||
flash_erase_sector(sector_addr, prv_erase_sector, state);
|
||||
} else {
|
||||
pulse_bulkio_erase_message_send(PulseBulkIODomainType_ExternalFlash, S_SUCCESS, state->cookie);
|
||||
kernel_free(state);
|
||||
}
|
||||
}
|
||||
|
||||
static status_t external_flash_domain_erase(uint8_t *packet_data, size_t length, uint8_t cookie) {
|
||||
if (length != sizeof(ExternalFlashEraseOptions)) {
|
||||
return E_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ExternalFlashEraseOptions *options = (ExternalFlashEraseOptions*)packet_data;
|
||||
|
||||
ExternalFlashEraseState *state = kernel_malloc(sizeof(ExternalFlashEraseState));
|
||||
*state = (ExternalFlashEraseState) {
|
||||
.address = options->address,
|
||||
.length = options->length,
|
||||
.next_sector = 0,
|
||||
.cookie = cookie
|
||||
};
|
||||
|
||||
prv_erase_sector(state, 0);
|
||||
|
||||
// Return a non-zero code to indicate the erase is still in process
|
||||
return S_TRUE;
|
||||
}
|
||||
|
||||
static status_t external_flash_domain_open(uint8_t *packet_data, size_t length, void **resp) {
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static status_t external_flash_domain_close(void *context) {
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
PulseBulkIODomainHandler pulse_bulkio_domain_external_flash = {
|
||||
.id = PulseBulkIODomainType_ExternalFlash,
|
||||
.open_proc = external_flash_domain_open,
|
||||
.close_proc = external_flash_domain_close,
|
||||
.read_proc = external_flash_domain_read,
|
||||
.write_proc = external_flash_domain_write,
|
||||
.stat_proc = external_flash_domain_stat,
|
||||
.erase_proc = external_flash_domain_erase
|
||||
};
|
100
src/fw/console/pulse_bulkio_framebuffer.c
Normal file
100
src/fw/console/pulse_bulkio_framebuffer.c
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "pulse_bulkio_domain_handler.h"
|
||||
|
||||
#include "applib/graphics/framebuffer.h"
|
||||
#include "applib/ui/animation_private.h"
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
#include "drivers/display/display.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "services/common/compositor/compositor.h"
|
||||
#include "services/common/compositor/compositor_display.h"
|
||||
#include "system/status_codes.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
typedef struct PACKED FramebufferStatResp {
|
||||
uint8_t flags;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t bpp;
|
||||
uint32_t length;
|
||||
} FramebufferStatResp;
|
||||
|
||||
static int framebuffer_domain_read(uint8_t *buf, uint32_t address, uint32_t length,
|
||||
void *context) {
|
||||
uint8_t *fb_offset = (uint8_t*)compositor_get_framebuffer()->buffer + address;
|
||||
memcpy(buf, fb_offset, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static int framebuffer_domain_write(uint8_t *buf, uint32_t address, uint32_t length,
|
||||
void *context) {
|
||||
uint8_t *fb_offset = (uint8_t*)compositor_get_framebuffer()->buffer + address;
|
||||
memcpy(fb_offset, buf, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static int framebuffer_domain_stat(uint8_t *resp, size_t resp_max_len, void *context) {
|
||||
FramebufferStatResp *stat_resp = (FramebufferStatResp*) resp;
|
||||
*stat_resp = (FramebufferStatResp) {
|
||||
.flags = 0,
|
||||
.length = FRAMEBUFFER_SIZE_BYTES,
|
||||
.width = DISP_COLS,
|
||||
.height = DISP_ROWS,
|
||||
.bpp = SCREEN_COLOR_DEPTH_BITS
|
||||
};
|
||||
|
||||
return sizeof(FramebufferStatResp);
|
||||
}
|
||||
|
||||
static status_t framebuffer_domain_erase(uint8_t *packet_data, size_t length, uint8_t cookie) {
|
||||
return E_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
static status_t framebuffer_domain_open(uint8_t *packet_data, size_t length, void **resp) {
|
||||
animation_private_pause();
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static void framebuffer_domain_close_cb(void *foo) {
|
||||
FrameBuffer *fb = compositor_get_framebuffer();
|
||||
framebuffer_dirty_all(fb);
|
||||
compositor_display_update(NULL);
|
||||
}
|
||||
|
||||
static status_t framebuffer_domain_close(void* data) {
|
||||
animation_private_resume();
|
||||
|
||||
// Force the compositor to redraw the framebuffer
|
||||
launcher_task_add_callback(framebuffer_domain_close_cb, NULL);
|
||||
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
PulseBulkIODomainHandler pulse_bulkio_domain_framebuffer = {
|
||||
.id = PulseBulkIODomainType_Framebuffer,
|
||||
.open_proc = framebuffer_domain_open,
|
||||
.close_proc = framebuffer_domain_close,
|
||||
.read_proc = framebuffer_domain_read,
|
||||
.write_proc = framebuffer_domain_write,
|
||||
.stat_proc = framebuffer_domain_stat,
|
||||
.erase_proc = framebuffer_domain_erase
|
||||
};
|
13
src/fw/console/pulse_bulkio_handler.def
Normal file
13
src/fw/console/pulse_bulkio_handler.def
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Syntax: REGISTER_BULKIO_HANDLER(domain_type, domain_id, vtable)
|
||||
// where vtable is a PulseBulkIODomainHandler, domain_id is a
|
||||
// number that defines that domain and must never change,
|
||||
// and domain_type is a name for the domain.
|
||||
|
||||
REGISTER_BULKIO_HANDLER(Memory, 1, pulse_bulkio_domain_memory)
|
||||
REGISTER_BULKIO_HANDLER(ExternalFlash, 2, pulse_bulkio_domain_external_flash)
|
||||
REGISTER_BULKIO_HANDLER(Framebuffer, 3, pulse_bulkio_domain_framebuffer)
|
||||
REGISTER_BULKIO_HANDLER(Coredump, 4, pulse_bulkio_domain_coredump)
|
||||
|
||||
#if !RECOVERY_FW
|
||||
REGISTER_BULKIO_HANDLER(PFS, 5, pulse_bulkio_domain_pfs)
|
||||
#endif
|
73
src/fw/console/pulse_bulkio_memory.c
Normal file
73
src/fw/console/pulse_bulkio_memory.c
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "pulse_bulkio_domain_handler.h"
|
||||
|
||||
#include "system/status_codes.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct PACKED MemoryEraseOptions {
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
} MemoryEraseOptions;
|
||||
|
||||
static int memory_domain_read(uint8_t *buf, uint32_t address, uint32_t length,
|
||||
void *context) {
|
||||
memcpy(buf, (void *)address, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static int memory_domain_write(uint8_t *buf, uint32_t address, uint32_t length,
|
||||
void *context) {
|
||||
memcpy(buf, (void *)address, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static int memory_domain_stat(uint8_t *resp, size_t resp_max_len, void *context) {
|
||||
return E_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
static status_t memory_domain_erase(uint8_t *packet_data, size_t length, uint8_t cookie) {
|
||||
if (length != sizeof(MemoryEraseOptions)) {
|
||||
return E_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
MemoryEraseOptions *options = (MemoryEraseOptions*)packet_data;
|
||||
|
||||
memset((void *)options->address, 0x0, length);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static status_t memory_domain_open(uint8_t *packet_data, size_t length, void **resp) {
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static status_t memory_domain_close(void *context) {
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
PulseBulkIODomainHandler pulse_bulkio_domain_memory = {
|
||||
.id = PulseBulkIODomainType_Memory,
|
||||
.open_proc = memory_domain_open,
|
||||
.close_proc = memory_domain_close,
|
||||
.read_proc = memory_domain_read,
|
||||
.write_proc = memory_domain_write,
|
||||
.stat_proc = memory_domain_stat,
|
||||
.erase_proc = memory_domain_erase
|
||||
};
|
118
src/fw/console/pulse_bulkio_pfs.c
Normal file
118
src/fw/console/pulse_bulkio_pfs.c
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "pulse_bulkio_domain_handler.h"
|
||||
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "services/normal/filesystem/pfs.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/status_codes.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct PACKED PFSStatResp {
|
||||
uint8_t flags;
|
||||
uint32_t size;
|
||||
} PFSStatResp;
|
||||
|
||||
typedef struct PACKED PFSOpenOptions {
|
||||
uint8_t op_flags;
|
||||
uint8_t filetype;
|
||||
uint32_t start_size;
|
||||
char filename[0];
|
||||
} PFSOpenOptions;
|
||||
|
||||
static int prv_open_file(void *packet_data, size_t length) {
|
||||
PFSOpenOptions *options = packet_data;
|
||||
|
||||
size_t filename_length = MIN(FILE_MAX_NAME_LEN, length - sizeof(*options));
|
||||
|
||||
char filename[filename_length+1];
|
||||
strncpy(filename, options->filename, filename_length);
|
||||
filename[filename_length] = '\0';
|
||||
|
||||
int fd = pfs_open(filename, options->op_flags, options->filetype, options->start_size);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int prv_fd_from_context(void *context) {
|
||||
return (uintptr_t)context;
|
||||
}
|
||||
|
||||
static int pfs_domain_read(uint8_t *buf, uint32_t address, uint32_t length, void *context) {
|
||||
int fd = prv_fd_from_context(context);
|
||||
pfs_seek(fd, address, FSeekSet);
|
||||
return pfs_read(fd, buf, length);
|
||||
}
|
||||
|
||||
static int pfs_domain_write(uint8_t *buf, uint32_t address, uint32_t length, void *context) {
|
||||
int fd = prv_fd_from_context(context);
|
||||
pfs_seek(fd, address, FSeekSet);
|
||||
return pfs_write(fd, buf, length);
|
||||
}
|
||||
|
||||
static int pfs_domain_stat(uint8_t *resp, size_t resp_max_len, void *context) {
|
||||
int fd = prv_fd_from_context(context);
|
||||
|
||||
PFSStatResp *stat_resp = (PFSStatResp*)resp;
|
||||
*stat_resp = (PFSStatResp) {
|
||||
.flags = 0,
|
||||
.size = (uint32_t)pfs_get_file_size(fd)
|
||||
};
|
||||
|
||||
return sizeof(PFSStatResp);
|
||||
}
|
||||
|
||||
static status_t pfs_domain_erase(uint8_t *packet_data, size_t length, uint8_t cookie) {
|
||||
char filename[FILE_MAX_NAME_LEN + 1];
|
||||
strncpy(filename, (char*)packet_data, FILE_MAX_NAME_LEN);
|
||||
|
||||
length = MIN(FILE_MAX_NAME_LEN, length);
|
||||
filename[length] = '\0';
|
||||
|
||||
return pfs_remove(filename);
|
||||
}
|
||||
|
||||
static status_t pfs_domain_open(uint8_t *packet_data, size_t length, void **resp) {
|
||||
int fd = prv_open_file(packet_data, length);
|
||||
if (fd < 0) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
*resp = (void*)(uintptr_t)fd;
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static status_t pfs_domain_close(void *context) {
|
||||
int fd = prv_fd_from_context(context);
|
||||
return pfs_close(fd);
|
||||
}
|
||||
|
||||
#if !RECOVERY_FW
|
||||
PulseBulkIODomainHandler pulse_bulkio_domain_pfs = {
|
||||
.id = PulseBulkIODomainType_PFS,
|
||||
.open_proc = pfs_domain_open,
|
||||
.read_proc = pfs_domain_read,
|
||||
.write_proc = pfs_domain_write,
|
||||
.close_proc = pfs_domain_close,
|
||||
.stat_proc = pfs_domain_stat,
|
||||
.erase_proc = pfs_domain_erase
|
||||
};
|
||||
#endif
|
81
src/fw/console/pulse_control_message_protocol.c
Normal file
81
src/fw/console/pulse_control_message_protocol.c
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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_control_message_protocol.h"
|
||||
|
||||
#include <util/attributes.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct PACKED PCMPPacket {
|
||||
uint8_t code;
|
||||
char information[];
|
||||
} PCMPPacket;
|
||||
|
||||
enum PCMPCode {
|
||||
PCMPCode_EchoRequest = 1,
|
||||
PCMPCode_EchoReply = 2,
|
||||
PCMPCode_DiscardRequest = 3,
|
||||
|
||||
PCMPCode_PortClosed = 129,
|
||||
PCMPCode_UnknownCode = 130,
|
||||
};
|
||||
|
||||
void pulse_control_message_protocol_on_packet(
|
||||
PulseControlMessageProtocol *this, void *raw_packet, size_t packet_length) {
|
||||
if (packet_length < sizeof(PCMPPacket)) {
|
||||
// Malformed packet; silently discard.
|
||||
return;
|
||||
}
|
||||
|
||||
PCMPPacket *packet = raw_packet;
|
||||
switch (packet->code) {
|
||||
case PCMPCode_EchoRequest: {
|
||||
PCMPPacket *reply = this->send_begin_fn(PULSE_CONTROL_MESSAGE_PROTOCOL);
|
||||
memcpy(reply, packet, packet_length);
|
||||
reply->code = PCMPCode_EchoReply;
|
||||
this->send_fn(reply, packet_length);
|
||||
break;
|
||||
}
|
||||
|
||||
case PCMPCode_EchoReply:
|
||||
case PCMPCode_DiscardRequest:
|
||||
case PCMPCode_PortClosed:
|
||||
case PCMPCode_UnknownCode:
|
||||
break;
|
||||
|
||||
default: {
|
||||
PCMPPacket *reply = this->send_begin_fn(PULSE_CONTROL_MESSAGE_PROTOCOL);
|
||||
reply->code = PCMPCode_UnknownCode;
|
||||
memcpy(reply->information, &packet->code, sizeof(packet->code));
|
||||
this->send_fn(reply, sizeof(PCMPPacket) + sizeof(packet->code));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pulse_control_message_protocol_send_port_closed_message(
|
||||
PulseControlMessageProtocol *this, net16 port) {
|
||||
PCMPPacket *message = this->send_begin_fn(PULSE_CONTROL_MESSAGE_PROTOCOL);
|
||||
message->code = PCMPCode_PortClosed;
|
||||
memcpy(message->information, &port, sizeof(port));
|
||||
this->send_fn(message, sizeof(PCMPPacket) + sizeof(port));
|
||||
}
|
||||
|
||||
#endif
|
35
src/fw/console/pulse_control_message_protocol.h
Normal file
35
src/fw/console/pulse_control_message_protocol.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <util/net.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PULSE_CONTROL_MESSAGE_PROTOCOL (0x0001)
|
||||
|
||||
typedef const struct PulseControlMessageProtocol {
|
||||
void *(*send_begin_fn)(uint16_t app_protocol);
|
||||
void (*send_fn)(void *buf, size_t length);
|
||||
} PulseControlMessageProtocol;
|
||||
|
||||
void pulse_control_message_protocol_on_packet(PulseControlMessageProtocol *this,
|
||||
void *information, size_t length);
|
||||
|
||||
void pulse_control_message_protocol_send_port_closed_message(
|
||||
PulseControlMessageProtocol *this, net16 port);
|
330
src/fw/console/pulse_flash_imaging.c
Normal file
330
src/fw/console/pulse_flash_imaging.c
Normal file
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "resource/resource_storage_flash.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#define IMAGING_CMD_ERASE (1)
|
||||
#define IMAGING_CMD_WRITE (2)
|
||||
#define IMAGING_CMD_CRC (3)
|
||||
#define IMAGING_CMD_QUERY_REGION (4)
|
||||
#define IMAGING_CMD_FINALIZE_REGION (5)
|
||||
|
||||
#define IMAGING_RESP_ACK_ERASE (128)
|
||||
#define IMAGING_RESP_ACK_WRITE (129)
|
||||
#define IMAGING_RESP_CRC (130)
|
||||
#define IMAGING_RESP_REGION_GEOMETRY (131)
|
||||
#define IMAGING_RESP_FINALIZE_REGION (132)
|
||||
|
||||
#define IMAGING_RESP_MALFORMED_CMD (192)
|
||||
#define IMAGING_RESP_INTERNAL_ERROR (193)
|
||||
|
||||
#define FLASH_REGION_PRF (1)
|
||||
#define FLASH_REGION_SYSTEM_RESOURCES (2)
|
||||
|
||||
typedef union Command {
|
||||
uint8_t opcode;
|
||||
struct PACKED EraseCommand {
|
||||
uint8_t opcode;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
} erase;
|
||||
struct PACKED WriteCommand {
|
||||
uint8_t opcode;
|
||||
uint32_t address;
|
||||
uint8_t data[0];
|
||||
} write;
|
||||
struct PACKED CrcCommand {
|
||||
uint8_t opcode;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
} crc;
|
||||
struct PACKED RegionCommand {
|
||||
uint8_t opcode;
|
||||
uint8_t region;
|
||||
} region;
|
||||
} Command;
|
||||
|
||||
|
||||
static void prv_handle_erase(Command *cmd, size_t length);
|
||||
static void prv_handle_write(Command *cmd, size_t length);
|
||||
static void prv_handle_crc(Command *cmd, size_t length);
|
||||
static void prv_handle_query_region(Command *cmd, size_t length);
|
||||
static void prv_handle_finalize_region(Command *cmd, size_t length);
|
||||
static void prv_respond_malformed_command(Command *cmd, size_t length,
|
||||
const char *message);
|
||||
|
||||
void pulse_flash_imaging_handler(void *packet, size_t length) {
|
||||
Command *command = packet;
|
||||
if (length) {
|
||||
switch (command->opcode) {
|
||||
case IMAGING_CMD_ERASE:
|
||||
prv_handle_erase(command, length);
|
||||
return;
|
||||
case IMAGING_CMD_WRITE:
|
||||
prv_handle_write(command, length);
|
||||
return;
|
||||
case IMAGING_CMD_CRC:
|
||||
prv_handle_crc(command, length);
|
||||
return;
|
||||
case IMAGING_CMD_QUERY_REGION:
|
||||
prv_handle_query_region(command, length);
|
||||
return;
|
||||
case IMAGING_CMD_FINALIZE_REGION:
|
||||
prv_handle_finalize_region(command, length);
|
||||
return;
|
||||
default:
|
||||
prv_respond_malformed_command(command, length, "Unknown command opcode");
|
||||
return;
|
||||
}
|
||||
}
|
||||
prv_respond_malformed_command(command, length, "Empty command");
|
||||
}
|
||||
|
||||
void pulse_flash_imaging_link_state_handler(PulseLinkState link_state) {
|
||||
}
|
||||
|
||||
static bool s_erase_in_progress = false;
|
||||
static uint32_t s_erase_start_address;
|
||||
static uint32_t s_erase_length;
|
||||
|
||||
typedef struct PACKED EraseAndWriteAck {
|
||||
uint8_t opcode;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
uint8_t complete;
|
||||
} EraseAndWriteAck;
|
||||
|
||||
static void prv_erase_complete(void *ignored, status_t result);
|
||||
|
||||
static void prv_handle_erase(Command *cmd, size_t length) {
|
||||
if (length != sizeof(cmd->erase)) {
|
||||
prv_respond_malformed_command(cmd, length, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_erase_in_progress) {
|
||||
EraseAndWriteAck *ack = pulse_best_effort_send_begin(
|
||||
PULSE_PROTOCOL_FLASH_IMAGING);
|
||||
*ack = (EraseAndWriteAck) {
|
||||
.opcode = IMAGING_RESP_ACK_ERASE,
|
||||
.address = s_erase_start_address,
|
||||
.length = s_erase_length,
|
||||
.complete = 0
|
||||
};
|
||||
pulse_best_effort_send(ack, sizeof(EraseAndWriteAck));
|
||||
} else {
|
||||
s_erase_in_progress = true;
|
||||
s_erase_start_address = cmd->erase.address;
|
||||
s_erase_length = cmd->erase.length;
|
||||
|
||||
EraseAndWriteAck *message = pulse_best_effort_send_begin(
|
||||
PULSE_PROTOCOL_FLASH_IMAGING);
|
||||
*message = (EraseAndWriteAck) {
|
||||
.opcode = IMAGING_RESP_ACK_ERASE,
|
||||
.address = s_erase_start_address,
|
||||
.length = s_erase_length,
|
||||
.complete = 0
|
||||
};
|
||||
pulse_best_effort_send(message, sizeof(EraseAndWriteAck));
|
||||
|
||||
uint32_t end_address = cmd->erase.address + cmd->erase.length;
|
||||
flash_erase_optimal_range(
|
||||
cmd->erase.address, cmd->erase.address, end_address,
|
||||
(end_address + SECTOR_SIZE_BYTES - 1) & SECTOR_ADDR_MASK,
|
||||
prv_erase_complete, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_erase_complete(void *ignored, status_t result) {
|
||||
EraseAndWriteAck *message = pulse_best_effort_send_begin(
|
||||
PULSE_PROTOCOL_FLASH_IMAGING);
|
||||
*message = (EraseAndWriteAck) {
|
||||
.opcode = IMAGING_RESP_ACK_ERASE,
|
||||
.address = s_erase_start_address,
|
||||
.length = s_erase_length,
|
||||
.complete = 1
|
||||
};
|
||||
|
||||
if (FAILED(result)) {
|
||||
message->opcode = IMAGING_RESP_INTERNAL_ERROR;
|
||||
pulse_best_effort_send(message, sizeof(message->opcode));
|
||||
} else {
|
||||
pulse_best_effort_send(message, sizeof(EraseAndWriteAck));
|
||||
}
|
||||
|
||||
s_erase_in_progress = false;
|
||||
}
|
||||
|
||||
static void prv_handle_write(Command *cmd, size_t command_length) {
|
||||
if (command_length <= sizeof(cmd->write)) {
|
||||
prv_respond_malformed_command(cmd, command_length, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t write_length = command_length - sizeof(cmd->write);
|
||||
flash_write_bytes(&cmd->write.data[0], cmd->write.address, write_length);
|
||||
|
||||
EraseAndWriteAck *ack = pulse_best_effort_send_begin(
|
||||
PULSE_PROTOCOL_FLASH_IMAGING);
|
||||
*ack = (EraseAndWriteAck) {
|
||||
.opcode = IMAGING_RESP_ACK_WRITE,
|
||||
.address = cmd->write.address,
|
||||
.length = write_length,
|
||||
.complete = 1
|
||||
};
|
||||
pulse_best_effort_send(ack, sizeof(EraseAndWriteAck));
|
||||
|
||||
// Since packets arrive so rapidly when writing, flash imaging can consume
|
||||
// all of the available CPU time and completely block lower-priority tasks.
|
||||
// To prevent DoSing KernelBG and tripping the watchdog, suspend the current
|
||||
// task for a couple ticks after each write to let other tasks catch up.
|
||||
psleep(2);
|
||||
}
|
||||
|
||||
static void prv_handle_crc(Command *cmd, size_t length) {
|
||||
if (length != sizeof(cmd->crc)) {
|
||||
prv_respond_malformed_command(cmd, length, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
typedef struct PACKED CrcAck {
|
||||
uint8_t opcode;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
uint32_t crc;
|
||||
} CrcAck;
|
||||
|
||||
uint32_t crc = flash_calculate_legacy_defective_checksum(cmd->crc.address, cmd->crc.length);
|
||||
|
||||
CrcAck *ack = pulse_best_effort_send_begin(PULSE_PROTOCOL_FLASH_IMAGING);
|
||||
*ack = (CrcAck) {
|
||||
.opcode = IMAGING_RESP_CRC,
|
||||
.address = cmd->crc.address,
|
||||
.length = cmd->crc.length,
|
||||
.crc = crc
|
||||
};
|
||||
pulse_best_effort_send(ack, sizeof(CrcAck));
|
||||
}
|
||||
|
||||
static void prv_handle_query_region(Command *cmd, size_t length) {
|
||||
if (length != sizeof(cmd->region)) {
|
||||
prv_respond_malformed_command(cmd, length, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t region_base, region_length;
|
||||
switch (cmd->region.region) {
|
||||
case FLASH_REGION_PRF:
|
||||
// assume a query of the region means we are going to write to it
|
||||
flash_prf_set_protection(false);
|
||||
region_base = FLASH_REGION_SAFE_FIRMWARE_BEGIN;
|
||||
region_length = FLASH_REGION_SAFE_FIRMWARE_END -
|
||||
FLASH_REGION_SAFE_FIRMWARE_BEGIN;
|
||||
break;
|
||||
case FLASH_REGION_SYSTEM_RESOURCES: {
|
||||
const SystemResourceBank *bank = resource_storage_flash_get_unused_bank();
|
||||
region_base = bank->begin;
|
||||
region_length = bank->end - bank->begin;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
region_base = 0;
|
||||
region_length = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
typedef struct PACKED RegionGeometry {
|
||||
uint8_t opcode;
|
||||
uint8_t region;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
} RegionGeometry;
|
||||
|
||||
RegionGeometry *resp = pulse_best_effort_send_begin(
|
||||
PULSE_PROTOCOL_FLASH_IMAGING);
|
||||
*resp = (RegionGeometry) {
|
||||
.opcode = IMAGING_RESP_REGION_GEOMETRY,
|
||||
.region = cmd->region.region,
|
||||
.address = region_base,
|
||||
.length = region_length
|
||||
};
|
||||
|
||||
pulse_best_effort_send(resp, sizeof(RegionGeometry));
|
||||
}
|
||||
|
||||
static void prv_handle_finalize_region(Command *cmd, size_t length) {
|
||||
if (length != sizeof(cmd->region)) {
|
||||
prv_respond_malformed_command(cmd, length, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd->region.region) {
|
||||
case FLASH_REGION_PRF:
|
||||
flash_prf_set_protection(true);
|
||||
break;
|
||||
case FLASH_REGION_SYSTEM_RESOURCES:
|
||||
boot_bit_set(BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cmd->region.opcode = IMAGING_RESP_FINALIZE_REGION;
|
||||
|
||||
Command *resp = pulse_best_effort_send_begin(PULSE_PROTOCOL_FLASH_IMAGING);
|
||||
resp->region.opcode = cmd->region.opcode;
|
||||
resp->region.region = cmd->region.region;
|
||||
|
||||
pulse_best_effort_send(resp, sizeof(resp->region));
|
||||
}
|
||||
|
||||
static void prv_respond_malformed_command(Command *cmd, size_t length,
|
||||
const char *message) {
|
||||
typedef struct PACKED MalformedCommandResponse {
|
||||
uint8_t opcode;
|
||||
uint8_t bad_command[9];
|
||||
char error_message[40];
|
||||
} MalformedCommandResponse;
|
||||
|
||||
MalformedCommandResponse *resp = pulse_best_effort_send_begin(
|
||||
PULSE_PROTOCOL_FLASH_IMAGING);
|
||||
resp->opcode = IMAGING_RESP_MALFORMED_CMD;
|
||||
|
||||
memcpy(resp->bad_command, cmd, MIN(length, sizeof(resp->bad_command)));
|
||||
size_t response_length = sizeof(resp->opcode) + sizeof(resp->bad_command);
|
||||
if (message) {
|
||||
for (size_t i = 0; i < sizeof(resp->error_message); ++i) {
|
||||
if (message[i] == '\0') {
|
||||
break;
|
||||
}
|
||||
resp->error_message[i] = message[i];
|
||||
response_length += 1;
|
||||
}
|
||||
}
|
||||
pulse_best_effort_send(resp, response_length);
|
||||
}
|
44
src/fw/console/pulse_internal.h
Normal file
44
src/fw/console/pulse_internal.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PULSE_KEEPALIVE_TIMEOUT_DECISECONDS (30)
|
||||
#if PULSE_EVERYWHERE
|
||||
#define PULSE_MAX_RECEIVE_UNIT (1500)
|
||||
#define PULSE_MIN_FRAME_LENGTH (6)
|
||||
#else
|
||||
#define PULSE_MAX_RECEIVE_UNIT (520)
|
||||
#define PULSE_MIN_FRAME_LENGTH (5)
|
||||
#endif
|
||||
|
||||
//! Start a PULSE session on dbgserial.
|
||||
void pulse_start(void);
|
||||
|
||||
//! End a PULSE session on dbgserial.
|
||||
void pulse_end(void);
|
||||
|
||||
//! Terminate the PULSE session in preparation for the firmware to crash.
|
||||
void pulse_prepare_to_crash(void);
|
||||
|
||||
//! PULSE ISR receive character handler.
|
||||
void pulse_handle_character(char c, bool *should_context_switch);
|
||||
|
||||
//! Change the dbgserial baud rate.
|
||||
void pulse_change_baud_rate(uint32_t new_baud);
|
147
src/fw/console/pulse_llc.c
Normal file
147
src/fw/console/pulse_llc.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "console/pulse_llc.h"
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
|
||||
#include "console/pulse_internal.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#define LLC_INMSG_LINK_ESTABLISHMENT_REQUEST (1)
|
||||
#define LLC_INMSG_LINK_CLOSE_REQUEST (3)
|
||||
#define LLC_INMSG_ECHO_REQUEST (5)
|
||||
#define LLC_INMSG_CHANGE_BAUD (7)
|
||||
|
||||
#define LLC_OUTMSG_LINK_OPENED (2)
|
||||
#define LLC_OUTMSG_LINK_CLOSED (4)
|
||||
#define LLC_OUTMSG_ECHO_REPLY (6)
|
||||
#define LLC_OUTMSG_INVALID_LLC_MESSAGE (128)
|
||||
#define LLC_OUTMSG_UNKNOWN_PROTOCOL_NUMBER (129)
|
||||
|
||||
|
||||
static void prv_bad_packet_response(uint8_t type, uint8_t bad_id, void *body,
|
||||
size_t body_length);
|
||||
static void prv_handle_change_baud(void *body, size_t body_length);
|
||||
|
||||
|
||||
void pulse_llc_handler(void *packet, size_t length) {
|
||||
if (!length) {
|
||||
// Message too small; doesn't contain a type field.
|
||||
uint8_t *message = pulse_best_effort_send_begin(PULSE_PROTOCOL_LLC);
|
||||
*message = LLC_OUTMSG_INVALID_LLC_MESSAGE;
|
||||
pulse_best_effort_send(message, sizeof(uint8_t));
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t type = *(uint8_t*)packet;
|
||||
switch (type) {
|
||||
case LLC_INMSG_LINK_ESTABLISHMENT_REQUEST:
|
||||
pulse_llc_send_link_opened_msg();
|
||||
return;
|
||||
case LLC_INMSG_LINK_CLOSE_REQUEST:
|
||||
pulse_end();
|
||||
return;
|
||||
case LLC_INMSG_ECHO_REQUEST:
|
||||
*(uint8_t*)packet = LLC_OUTMSG_ECHO_REPLY;
|
||||
|
||||
uint8_t *message = pulse_best_effort_send_begin(PULSE_PROTOCOL_LLC);
|
||||
memcpy(message, packet, length);
|
||||
|
||||
pulse_best_effort_send(message, length);
|
||||
return;
|
||||
case LLC_INMSG_CHANGE_BAUD:
|
||||
prv_handle_change_baud((char*)packet + 1, length - 1);
|
||||
return;
|
||||
default:
|
||||
prv_bad_packet_response(LLC_OUTMSG_INVALID_LLC_MESSAGE, type,
|
||||
(char*)packet + 1, length - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void pulse_llc_link_state_handler(PulseLinkState link_state) {
|
||||
}
|
||||
|
||||
void pulse_llc_send_link_opened_msg(void) {
|
||||
typedef struct PACKED Response {
|
||||
uint8_t type;
|
||||
uint8_t pulse_version;
|
||||
uint16_t mtu;
|
||||
uint16_t mru;
|
||||
uint8_t timeout;
|
||||
} Response;
|
||||
|
||||
Response *response = pulse_best_effort_send_begin(PULSE_PROTOCOL_LLC);
|
||||
*response = (Response) {
|
||||
.type = LLC_OUTMSG_LINK_OPENED,
|
||||
.pulse_version = 1,
|
||||
.mtu = PULSE_MAX_SEND_SIZE + PULSE_MIN_FRAME_LENGTH,
|
||||
.mru = PULSE_MAX_RECEIVE_UNIT,
|
||||
.timeout = PULSE_KEEPALIVE_TIMEOUT_DECISECONDS
|
||||
};
|
||||
|
||||
pulse_best_effort_send(response, sizeof(Response));
|
||||
}
|
||||
|
||||
void pulse_llc_send_link_closed_msg(void) {
|
||||
uint8_t *link_close_response = pulse_best_effort_send_begin(
|
||||
PULSE_PROTOCOL_LLC);
|
||||
*link_close_response = LLC_OUTMSG_LINK_CLOSED;
|
||||
|
||||
pulse_best_effort_send(link_close_response, sizeof(uint8_t));
|
||||
}
|
||||
|
||||
static void prv_bad_packet_response(uint8_t type, uint8_t bad_id, void *body,
|
||||
size_t body_length) {
|
||||
typedef struct PACKED Response {
|
||||
uint8_t type;
|
||||
uint8_t bad_identifier;
|
||||
char body[8];
|
||||
} Response;
|
||||
|
||||
Response *response = pulse_best_effort_send_begin(PULSE_PROTOCOL_LLC);
|
||||
*response = (Response) {
|
||||
.type = type,
|
||||
.bad_identifier = bad_id
|
||||
};
|
||||
|
||||
body_length = MIN(sizeof(response->body), body_length);
|
||||
if (body_length) {
|
||||
memcpy(response->body, body, body_length);
|
||||
}
|
||||
|
||||
pulse_best_effort_send(response, 2 + body_length);
|
||||
}
|
||||
|
||||
void pulse_llc_unknown_protocol_handler(uint8_t protocol, void *packet,
|
||||
size_t length) {
|
||||
prv_bad_packet_response(LLC_OUTMSG_UNKNOWN_PROTOCOL_NUMBER, protocol, packet,
|
||||
length);
|
||||
}
|
||||
|
||||
void prv_handle_change_baud(void *body, size_t body_length) {
|
||||
if (body_length != sizeof(uint32_t)) {
|
||||
// Don't send a response; the client will have already changed its receiver
|
||||
// baud rate.
|
||||
return;
|
||||
}
|
||||
uint32_t *new_baud = body;
|
||||
pulse_change_baud_rate(*new_baud);
|
||||
}
|
26
src/fw/console/pulse_llc.h
Normal file
26
src/fw/console/pulse_llc.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void pulse_llc_send_link_opened_msg(void);
|
||||
|
||||
void pulse_llc_send_link_closed_msg(void);
|
||||
|
||||
void pulse_llc_unknown_protocol_handler(uint8_t protocol, void *packet,
|
||||
size_t length);
|
256
src/fw/console/pulse_pp.c
Normal file
256
src/fw/console/pulse_pp.c
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <bluetooth/bt_driver_comm.h>
|
||||
|
||||
#include "comm/bt_lock.h"
|
||||
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
#include "console/pulse2_transport_impl.h"
|
||||
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
|
||||
#include "services/common/comm_session/session_transport.h"
|
||||
|
||||
#include "system/passert.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#include "util/attributes.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define PULSE_PP_OPCODE_DATA (1)
|
||||
#define PULSE_PP_OPCODE_OPEN (2)
|
||||
#define PULSE_PP_OPCODE_CLOSE (3)
|
||||
|
||||
#define PULSE_PP_OPCODE_UNKNOWN (255)
|
||||
|
||||
typedef struct PACKED PulsePPPacket {
|
||||
uint8_t opcode;
|
||||
uint8_t data[0];
|
||||
} PulsePPPacket;
|
||||
|
||||
typedef struct PACKED PulsePPCallbackPacket {
|
||||
size_t packet_length;
|
||||
PulsePPPacket packet;
|
||||
} PulsePPCallbackPacket;
|
||||
|
||||
typedef struct {
|
||||
CommSession *session;
|
||||
} PULSETransport;
|
||||
|
||||
//! The CommSession that the PULSE transport is managing.
|
||||
//! Currently there's only one for the System session.
|
||||
static PULSETransport s_transport;
|
||||
|
||||
static void prv_send_next(Transport *transport) {
|
||||
CommSession *session = s_transport.session;
|
||||
PBL_ASSERTN(session);
|
||||
|
||||
size_t bytes_remaining = comm_session_send_queue_get_length(session);
|
||||
size_t mss = pulse_reliable_max_send_size() - sizeof(PulsePPPacket);
|
||||
|
||||
while (bytes_remaining) {
|
||||
bt_unlock();
|
||||
PulsePPPacket *resp = (PulsePPPacket*) pulse_reliable_send_begin(PULSE2_PEBBLE_PROTOCOL);
|
||||
bt_lock();
|
||||
|
||||
if (resp) {
|
||||
resp->opcode = PULSE_PP_OPCODE_DATA;
|
||||
|
||||
const size_t bytes_to_copy = MIN(bytes_remaining, mss);
|
||||
comm_session_send_queue_copy(session, 0 /* start_offset */,
|
||||
bytes_to_copy, &resp->data[0]);
|
||||
pulse_reliable_send(resp, bytes_to_copy + sizeof(PulsePPPacket));
|
||||
comm_session_send_queue_consume(session, bytes_to_copy);
|
||||
|
||||
bytes_remaining -= bytes_to_copy;
|
||||
} else {
|
||||
// Reliable transport went down while waiting to send.
|
||||
// The CommSession has already been closed so simply return without doing
|
||||
// anything further.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_reset(Transport *transport) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Unimplemented");
|
||||
}
|
||||
|
||||
static void prv_granted_kernel_main_cb(void *ctx) {
|
||||
ResponsivenessGrantedHandler granted_handler = ctx;
|
||||
granted_handler();
|
||||
}
|
||||
|
||||
static void prv_set_connection_responsiveness(
|
||||
Transport *transport, BtConsumer consumer, ResponseTimeState state, uint16_t max_period_secs,
|
||||
ResponsivenessGrantedHandler granted_handler) {
|
||||
if (granted_handler) {
|
||||
launcher_task_add_callback(prv_granted_kernel_main_cb, granted_handler);
|
||||
}
|
||||
}
|
||||
|
||||
static CommSessionTransportType prv_get_type(struct Transport *transport) {
|
||||
return CommSessionTransportType_PULSE;
|
||||
}
|
||||
|
||||
static void prv_send_job(void *data) {
|
||||
CommSession *session = (CommSession *)data;
|
||||
bt_driver_run_send_next_job(session, true);
|
||||
}
|
||||
|
||||
static bool prv_schedule_send_next_job(CommSession *session) {
|
||||
launcher_task_add_callback(prv_send_job, session);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_is_current_task_schedule_task(struct Transport *transport) {
|
||||
return launcher_task_is_current_task();
|
||||
}
|
||||
|
||||
//! Defined in session.c
|
||||
extern void comm_session_set_capabilities(
|
||||
CommSession *session, CommSessionCapability capability_flags);
|
||||
|
||||
bool pulse_transport_is_connected(void) {
|
||||
return (s_transport.session != NULL);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
void pulse_transport_set_connected(bool is_connected) {
|
||||
if (pulse_transport_is_connected() == is_connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const TransportImplementation s_pulse_transport_implementation = {
|
||||
.send_next = prv_send_next,
|
||||
.reset = prv_reset,
|
||||
.set_connection_responsiveness = prv_set_connection_responsiveness,
|
||||
.get_type = prv_get_type,
|
||||
.schedule = prv_schedule_send_next_job,
|
||||
.is_current_task_schedule_task = prv_is_current_task_schedule_task,
|
||||
};
|
||||
|
||||
bool send_event = true;
|
||||
|
||||
if (is_connected) {
|
||||
s_transport.session = comm_session_open((Transport *) &s_transport,
|
||||
&s_pulse_transport_implementation,
|
||||
TransportDestinationHybrid);
|
||||
if (!s_transport.session) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "CommSession couldn't be opened");
|
||||
send_event = false;
|
||||
}
|
||||
|
||||
// Give it the appropriate capabilities
|
||||
const CommSessionCapability capabilities = CommSessionRunState |
|
||||
CommSessionInfiniteLogDumping |
|
||||
CommSessionVoiceApiSupport |
|
||||
CommSessionAppMessage8kSupport |
|
||||
CommSessionWeatherAppSupport |
|
||||
CommSessionExtendedNotificationService;
|
||||
comm_session_set_capabilities(s_transport.session, capabilities);
|
||||
} else {
|
||||
comm_session_close(s_transport.session, CommSessionCloseReason_UnderlyingDisconnection);
|
||||
s_transport.session = NULL;
|
||||
}
|
||||
|
||||
if (send_event) {
|
||||
PebbleEvent e = {
|
||||
.type = PEBBLE_BT_CONNECTION_EVENT,
|
||||
.bluetooth = {
|
||||
.connection = {
|
||||
.state = (s_transport.session) ? PebbleBluetoothConnectionEventStateConnected
|
||||
: PebbleBluetoothConnectionEventStateDisconnected
|
||||
}
|
||||
}
|
||||
};
|
||||
event_put(&e);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_pulse_pp_transport_set_connected(bool connected) {
|
||||
bt_lock();
|
||||
pulse_transport_set_connected(connected);
|
||||
bt_unlock();
|
||||
}
|
||||
|
||||
static void prv_pulse_pp_handle_data(void *data, size_t length) {
|
||||
bt_lock();
|
||||
|
||||
if (!s_transport.session) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Received PULSE serial data, but session not connected!");
|
||||
goto unlock;
|
||||
}
|
||||
comm_session_receive_router_write(s_transport.session, data, length);
|
||||
|
||||
unlock:
|
||||
bt_unlock();
|
||||
}
|
||||
|
||||
static void prv_pulse_pp_send_cb(void *data) {
|
||||
PulsePPCallbackPacket *cb_data = data;
|
||||
uint8_t *resp = pulse_reliable_send_begin(PULSE2_PEBBLE_PROTOCOL);
|
||||
memcpy(resp, &cb_data->packet, cb_data->packet_length);
|
||||
pulse_reliable_send(resp, cb_data->packet_length);
|
||||
kernel_free(data);
|
||||
}
|
||||
|
||||
static void prv_pulse_pp_send(uint8_t opcode, uint8_t *data, size_t data_length) {
|
||||
size_t packet_length = sizeof(PulsePPCallbackPacket) + data_length;
|
||||
PulsePPCallbackPacket *cb_data = kernel_malloc_check(packet_length);
|
||||
cb_data->packet_length = sizeof(PulsePPPacket) + data_length;
|
||||
cb_data->packet.opcode = opcode;
|
||||
|
||||
if (data) {
|
||||
memcpy(&cb_data->packet.data[0], data, data_length);
|
||||
}
|
||||
|
||||
launcher_task_add_callback(prv_pulse_pp_send_cb, cb_data);
|
||||
}
|
||||
|
||||
void pulse_pp_transport_open_handler(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
void pulse_pp_transport_closed_handler(void) {
|
||||
prv_pulse_pp_transport_set_connected(false);
|
||||
}
|
||||
|
||||
void pulse_pp_transport_handle_received_data(void *data, size_t length) {
|
||||
PulsePPPacket *packet = data;
|
||||
|
||||
switch (packet->opcode) {
|
||||
case PULSE_PP_OPCODE_DATA:
|
||||
prv_pulse_pp_handle_data(&packet->data[0], length - sizeof(PulsePPPacket));
|
||||
break;
|
||||
case PULSE_PP_OPCODE_OPEN:
|
||||
prv_pulse_pp_send(PULSE_PP_OPCODE_OPEN, NULL, 0);
|
||||
prv_pulse_pp_transport_set_connected(true);
|
||||
break;
|
||||
case PULSE_PP_OPCODE_CLOSE:
|
||||
prv_pulse_pp_transport_set_connected(false);
|
||||
prv_pulse_pp_send(PULSE_PP_OPCODE_CLOSE, NULL, 0);
|
||||
break;
|
||||
default:
|
||||
prv_pulse_pp_send(PULSE_PP_OPCODE_UNKNOWN, &packet->opcode, sizeof(packet->opcode));
|
||||
break;
|
||||
}
|
||||
}
|
84
src/fw/console/pulse_protocol_impl.h
Normal file
84
src/fw/console/pulse_protocol_impl.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Maximum number of data bytes that an outgoing PULSE frame can hold.
|
||||
#define PULSE_MAX_SEND_SIZE (520)
|
||||
|
||||
//! Possible link states for the PULSE link, used to notify protocol handlers
|
||||
typedef enum {
|
||||
PulseLinkState_Open,
|
||||
PulseLinkState_Closed,
|
||||
} PulseLinkState;
|
||||
|
||||
//! Retrieve the buffer to fill the frame.
|
||||
//!
|
||||
//! @param protocol protocol number
|
||||
#if PULSE_EVERYWHERE
|
||||
void *pulse_best_effort_send_begin(uint16_t protocol);
|
||||
#else
|
||||
void *pulse_best_effort_send_begin(uint8_t protocol);
|
||||
#endif
|
||||
|
||||
//! Send a PULSE frame.
|
||||
//!
|
||||
//! @param [out] buf buffer containing the frame data to send. Must be a buffer
|
||||
//! pointer returned by pulse_send_begin
|
||||
//! @param length length of the buffer pointed to by buf. Must not exceed
|
||||
//! PULSE_MAX_SEND_SIZE.
|
||||
void pulse_best_effort_send(void *buf, size_t length);
|
||||
|
||||
//! Release a TX buffer, without sending the frame.
|
||||
//!
|
||||
//! @param [out] buf buffer to be released. Must be a buffer
|
||||
//! pointer returned by pulse_best_effort_send_begin
|
||||
void pulse_best_effort_send_cancel(void *buf);
|
||||
|
||||
void *pulse_push_send_begin(uint16_t protocol);
|
||||
void pulse_push_send(void *buf, size_t length);
|
||||
|
||||
void *pulse_reliable_send_begin(uint16_t protocol);
|
||||
void pulse_reliable_send(void *buf, size_t length);
|
||||
void pulse_reliable_send_cancel(void *buf);
|
||||
|
||||
size_t pulse_reliable_max_send_size(void);
|
||||
|
||||
#if !PULSE_EVERYWHERE
|
||||
// PULSEv1 has no equivalent to the PUSH protocol.
|
||||
#define pulse_push_send_begin pulse_best_effort_send_begin
|
||||
#define pulse_push_send pulse_best_effort_send
|
||||
#endif
|
||||
|
||||
// Use preprocessor magic to generate function signatures for all protocol
|
||||
// handler functions.
|
||||
#define REGISTER_PROTOCOL(n, message_handler, link_state_handler) \
|
||||
void message_handler(void *packet, size_t length); \
|
||||
void link_state_handler(PulseLinkState link_state);
|
||||
#include "console/pulse_protocol_registry.def"
|
||||
#undef REGISTER_PROTOCOL
|
||||
|
||||
#define ON_PACKET(N, PACKET_HANDLER) \
|
||||
void PACKET_HANDLER(void *packet, size_t length);
|
||||
#define ON_TRANSPORT_STATE_CHANGE(UP_HANDLER, DOWN_HANDLER) \
|
||||
void UP_HANDLER(void); \
|
||||
void DOWN_HANDLER(void);
|
||||
#include "console/pulse2_reliable_protocol_registry.def"
|
||||
#undef ON_PACKET
|
||||
#undef ON_TRANSPORT_STATE_CHANGE
|
32
src/fw/console/pulse_protocol_registry.def
Normal file
32
src/fw/console/pulse_protocol_registry.def
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Registry for protocols which handle PULSE frames.
|
||||
// http://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-Macros
|
||||
|
||||
#ifndef _PULSE_PROTOCOL_NUMBERS_DEFINED
|
||||
#define _PULSE_PROTOCOL_NUMBERS_DEFINED
|
||||
|
||||
#define PULSE_PROTOCOL_LLC (1)
|
||||
#define PULSE_PROTOCOL_FLASH_IMAGING (2)
|
||||
#define PULSE_PROTOCOL_LOGGING (3)
|
||||
#define PULSE_PROTOCOL_PROMPT (4)
|
||||
|
||||
#endif // _PULSE_PROTOCOL_NUMBERS_DEFINED
|
||||
|
||||
// Syntax: REGISTER_PROTOCOL(number, handler_function)
|
||||
// where handler_function is a function with the signature
|
||||
// void handler_function(void *packet, size_t length)
|
||||
// This function will be called whenever a PULSE frame is received which bears
|
||||
// the specified protocol number.
|
||||
//
|
||||
// A function signature for each handler is automatically generated in
|
||||
// pulse_protocol_impl.h. Simply include that header in the source file
|
||||
// where the protocol handler is defined to get the function signature
|
||||
// definition.
|
||||
|
||||
// DO NOT REMOVE THIS PROTOCOL!
|
||||
#if !PULSE_EVERYWHERE
|
||||
REGISTER_PROTOCOL(PULSE_PROTOCOL_LLC, pulse_llc_handler, pulse_llc_link_state_handler)
|
||||
REGISTER_PROTOCOL(PULSE_PROTOCOL_PROMPT, pulse_prompt_handler, pulse_prompt_link_state_handler)
|
||||
#endif
|
||||
REGISTER_PROTOCOL(PULSE_PROTOCOL_FLASH_IMAGING, pulse_flash_imaging_handler, pulse_flash_imaging_link_state_handler)
|
||||
|
||||
// vim:filetype=c
|
57
src/fw/console/push_transport.c
Normal file
57
src/fw/console/push_transport.c
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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_protocol_impl.h"
|
||||
|
||||
#include "console/pulse2_transport_impl.h"
|
||||
#include "system/passert.h"
|
||||
#include <util/attributes.h>
|
||||
#include <util/net.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PULSE2_PUSH_TRANSPORT_PROTOCOL (0x5021)
|
||||
|
||||
typedef struct PACKED PushPacket {
|
||||
net16 protocol;
|
||||
net16 length;
|
||||
char information[];
|
||||
} PushPacket;
|
||||
|
||||
void *pulse_push_send_begin(uint16_t app_protocol) {
|
||||
PushPacket *packet = pulse_link_send_begin(PULSE2_PUSH_TRANSPORT_PROTOCOL);
|
||||
packet->protocol = hton16(app_protocol);
|
||||
return &packet->information;
|
||||
}
|
||||
|
||||
void pulse_push_send(void *buf, size_t length) {
|
||||
PBL_ASSERT(length <= pulse_link_max_send_size() - sizeof(PushPacket),
|
||||
"Packet to big to send");
|
||||
// We're blindly assuming that buf is the same pointer returned by
|
||||
// pulse_push_send_begin. If it isn't, we'll either crash here
|
||||
// when trying to dereference it or we'll hit the assert in
|
||||
// pulse_link_send.
|
||||
PushPacket *packet = (void *)((char *)buf - offsetof(PushPacket,
|
||||
information));
|
||||
size_t packet_size = length + sizeof(PushPacket);
|
||||
packet->length = hton16(packet_size);
|
||||
pulse_link_send(packet, packet_size);
|
||||
}
|
||||
|
||||
#endif
|
423
src/fw/console/reliable_transport.c
Normal file
423
src/fw/console/reliable_transport.c
Normal file
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* 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_protocol_impl.h"
|
||||
#include "pulse2_reliable_retransmit_timer.h"
|
||||
|
||||
#include "console/control_protocol.h"
|
||||
#include "console/control_protocol_impl.h"
|
||||
#include "console/pulse.h"
|
||||
#include "console/pulse2_transport_impl.h"
|
||||
#include "console/pulse_control_message_protocol.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/passert.h"
|
||||
#include <util/attributes.h>
|
||||
#include <util/net.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "semphr.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
//! Modulus for sequence numbers
|
||||
#define MODULUS (128u)
|
||||
#define MAX_RETRANSMITS (10)
|
||||
#define RETRANSMIT_TIMEOUT_MS (200)
|
||||
|
||||
// Reliable Transport protocol
|
||||
// ===========================
|
||||
|
||||
//! A buffer for holding a reliable info packet in memory while it is un-ACKed.
|
||||
typedef struct ReliableInfoBuffer {
|
||||
uint16_t app_protocol;
|
||||
uint16_t length;
|
||||
char information[];
|
||||
} ReliableInfoBuffer;
|
||||
|
||||
enum SupervisoryKind {
|
||||
SupervisoryKind_ReceiveReady = 0b00,
|
||||
SupervisoryKind_ReceiveNotReady = 0b01,
|
||||
SupervisoryKind_Reject = 0b10,
|
||||
};
|
||||
|
||||
typedef union ReliablePacket {
|
||||
bool is_supervisory:1;
|
||||
struct PACKED ReliableInfoPacket {
|
||||
bool is_supervisory:1;
|
||||
uint8_t sequence_number:7;
|
||||
bool poll:1;
|
||||
uint8_t ack_number:7;
|
||||
net16 protocol;
|
||||
net16 length;
|
||||
char information[];
|
||||
} i;
|
||||
struct PACKED ReliableSupervisoryPacket {
|
||||
bool is_supervisory:1;
|
||||
bool is_unnumbered:1; // is_unnumbered=true is unsupported
|
||||
enum SupervisoryKind kind:2;
|
||||
char _reserved:4;
|
||||
bool poll_or_final:1;
|
||||
uint8_t ack_number:7;
|
||||
} s;
|
||||
} ReliablePacket;
|
||||
|
||||
static PulseControlMessageProtocol s_reliable_pcmp = {
|
||||
.send_begin_fn = pulse_reliable_send_begin,
|
||||
.send_fn = pulse_reliable_send,
|
||||
};
|
||||
|
||||
_Static_assert(sizeof((ReliablePacket){0}.i) == 6,
|
||||
"sizeof ReliablePacket.i is wrong");
|
||||
_Static_assert(sizeof((ReliablePacket){0}.s) == 2,
|
||||
"sizeof ReliablePacket.s is wrong");
|
||||
_Static_assert(sizeof((ReliablePacket){0}.i) == sizeof(ReliablePacket),
|
||||
"Something is really wrong here");
|
||||
|
||||
static bool s_layer_up = false;
|
||||
static ReliableInfoBuffer *s_tx_buffer;
|
||||
static SemaphoreHandle_t s_tx_lock;
|
||||
|
||||
//! The sequence number of the next in-sequence I-packet to be transmitted.
|
||||
//! V(S) in the LAPB spec.
|
||||
static uint8_t s_send_variable;
|
||||
static uint8_t s_retransmit_count;
|
||||
static uint8_t s_last_ack_number; //!< N(R) of most recently received packet.
|
||||
static uint8_t s_receive_variable; //!< V(R) in the LAPB spec.
|
||||
|
||||
static void prv_bounce_ncp_state(void);
|
||||
|
||||
size_t pulse_reliable_max_send_size(void) {
|
||||
return pulse_link_max_send_size() - sizeof(ReliablePacket);
|
||||
}
|
||||
|
||||
static void prv_send_supervisory_response(enum SupervisoryKind kind,
|
||||
bool final) {
|
||||
ReliablePacket *packet = pulse_link_send_begin(
|
||||
PULSE2_RELIABLE_TRANSPORT_RESPONSE);
|
||||
packet->s = (struct ReliableSupervisoryPacket) {
|
||||
.is_supervisory = true,
|
||||
.kind = kind,
|
||||
.poll_or_final = final,
|
||||
.ack_number = s_receive_variable,
|
||||
};
|
||||
pulse_link_send(packet, sizeof(packet->s));
|
||||
}
|
||||
|
||||
static void prv_send_info_packet(uint8_t sequence_number,
|
||||
uint16_t app_protocol,
|
||||
const char *information,
|
||||
uint16_t info_length) {
|
||||
PBL_ASSERT(info_length <= pulse_reliable_max_send_size(),
|
||||
"Packet too big to send");
|
||||
|
||||
ReliablePacket *packet = pulse_link_send_begin(
|
||||
PULSE2_RELIABLE_TRANSPORT_COMMAND);
|
||||
size_t packet_size = sizeof(ReliablePacket) + info_length;
|
||||
packet->i = (struct ReliableInfoPacket) {
|
||||
.sequence_number = sequence_number,
|
||||
.poll = true,
|
||||
.ack_number = s_receive_variable,
|
||||
.protocol = hton16(app_protocol),
|
||||
.length = hton16(packet_size),
|
||||
};
|
||||
memcpy(&packet->i.information[0], information, info_length);
|
||||
pulse_link_send(packet, packet_size);
|
||||
}
|
||||
|
||||
static void prv_process_ack(uint8_t ack_number) {
|
||||
if ((ack_number - 1) % MODULUS == s_send_variable) {
|
||||
pulse2_reliable_retransmit_timer_cancel();
|
||||
s_retransmit_count = 0;
|
||||
s_send_variable = (s_send_variable + 1) % MODULUS;
|
||||
xSemaphoreGive(s_tx_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_send_port_closed_message(void *context) {
|
||||
net16 bad_port;
|
||||
memcpy(&bad_port, &context, sizeof(bad_port));
|
||||
pulse_control_message_protocol_send_port_closed_message(
|
||||
&s_reliable_pcmp, bad_port);
|
||||
}
|
||||
|
||||
void pulse2_reliable_transport_on_command_packet(
|
||||
void *raw_packet, size_t length) {
|
||||
if (!s_layer_up) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (length < sizeof((ReliablePacket){0}.s)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Received malformed command packet");
|
||||
prv_bounce_ncp_state();
|
||||
return;
|
||||
}
|
||||
ReliablePacket *packet = raw_packet;
|
||||
|
||||
if (packet->is_supervisory) {
|
||||
if (packet->s.kind != SupervisoryKind_ReceiveReady &&
|
||||
packet->s.kind != SupervisoryKind_Reject) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Received a command packet of type %" PRIu8
|
||||
" which is not supported by this implementation.",
|
||||
(uint8_t)packet->s.kind);
|
||||
// Pretend it is an RR packet
|
||||
}
|
||||
prv_process_ack(packet->s.ack_number);
|
||||
if (packet->s.poll_or_final) {
|
||||
prv_send_supervisory_response(SupervisoryKind_ReceiveReady,
|
||||
/* final */ true);
|
||||
}
|
||||
} else { // Information transfer packet
|
||||
if (length < sizeof(ReliablePacket)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Received malformed Information packet");
|
||||
prv_bounce_ncp_state();
|
||||
return;
|
||||
}
|
||||
if (packet->i.sequence_number == s_receive_variable) {
|
||||
s_receive_variable = (s_receive_variable + 1) % MODULUS;
|
||||
if (ntoh16(packet->i.length) <= length) {
|
||||
size_t info_length = ntoh16(packet->i.length) - sizeof(ReliablePacket);
|
||||
// This variable is read in the macro-expansion below, but linters
|
||||
// have a hard time figuring that out.
|
||||
(void)info_length;
|
||||
|
||||
switch (ntoh16(packet->i.protocol)) {
|
||||
case PULSE_CONTROL_MESSAGE_PROTOCOL:
|
||||
// TODO PBL-37695: PCMP sends packets synchronously, which will
|
||||
// trip the not-KernelMain check on pulse_reliable_send_begin.
|
||||
// pulse_control_message_protocol_on_packet(
|
||||
// &s_reliable_pcmp, packet->i.information, info_length);
|
||||
break;
|
||||
#define ON_PACKET(N, HANDLER) \
|
||||
case N: \
|
||||
HANDLER(packet->i.information, info_length); \
|
||||
break;
|
||||
#define ON_TRANSPORT_STATE_CHANGE(...)
|
||||
#include "console/pulse2_reliable_protocol_registry.def"
|
||||
#undef ON_PACKET
|
||||
#undef ON_TRANSPORT_STATE_CHANGE
|
||||
default: {
|
||||
// Work around PBL-37695 by sending the Port-Closed message
|
||||
// from KernelBG.
|
||||
uintptr_t bad_port;
|
||||
memcpy(&bad_port, &packet->i.protocol, sizeof(packet->i.protocol));
|
||||
system_task_add_callback(prv_send_port_closed_message,
|
||||
(void *)bad_port);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Received truncated or corrupt info packet "
|
||||
"field (expeced %" PRIu16 ", got %" PRIu16 " data bytes). "
|
||||
"Discarding.", ntoh16(packet->i.length), (uint16_t)length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
prv_send_supervisory_response(SupervisoryKind_ReceiveReady, packet->i.poll);
|
||||
}
|
||||
}
|
||||
|
||||
void pulse2_reliable_transport_on_response_packet(
|
||||
void *raw_packet, size_t length) {
|
||||
if (!s_layer_up) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (length < sizeof((ReliablePacket){0}.s)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Received malformed response packet");
|
||||
prv_bounce_ncp_state();
|
||||
return;
|
||||
}
|
||||
ReliablePacket *packet = raw_packet;
|
||||
|
||||
if (!packet->is_supervisory) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Received Information packet response; this is "
|
||||
"not permitted by the protocol (Information packets can only be "
|
||||
"commands). Discarding.");
|
||||
return;
|
||||
}
|
||||
|
||||
prv_process_ack(packet->s.ack_number);
|
||||
|
||||
if (packet->s.kind != SupervisoryKind_ReceiveReady &&
|
||||
packet->s.kind != SupervisoryKind_Reject) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Received a command packet of type %" PRIu8
|
||||
" which is not supported by this implementation.",
|
||||
(uint8_t)packet->s.kind);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_start_retransmit_timer(uint8_t sequence_number);
|
||||
|
||||
void pulse2_reliable_retransmit_timer_expired_handler(
|
||||
uint8_t retransmit_sequence_number) {
|
||||
if (s_send_variable != retransmit_sequence_number) {
|
||||
// ACK was received and processed between the time that the
|
||||
// retransmit timer expired and this callback ran.
|
||||
return;
|
||||
}
|
||||
if (++s_retransmit_count < MAX_RETRANSMITS) {
|
||||
prv_send_info_packet(retransmit_sequence_number,
|
||||
s_tx_buffer->app_protocol,
|
||||
&s_tx_buffer->information[0],
|
||||
s_tx_buffer->length);
|
||||
prv_start_retransmit_timer(retransmit_sequence_number);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Reached maximum number of retransmit attempts.");
|
||||
prv_bounce_ncp_state();
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_start_retransmit_timer(uint8_t sequence_number) {
|
||||
pulse2_reliable_retransmit_timer_start(
|
||||
RETRANSMIT_TIMEOUT_MS, sequence_number);
|
||||
}
|
||||
|
||||
static void prv_assert_reliable_buffer(void *buf) {
|
||||
PBL_ASSERT(buf == &s_tx_buffer->information[0],
|
||||
"The passed-in buffer pointer is not a buffer given by "
|
||||
"pulse_reliable_send_begin");
|
||||
}
|
||||
|
||||
void *pulse_reliable_send_begin(const uint16_t app_protocol) {
|
||||
// The PULSE task processes ACKs and retransmits timed-out packets.
|
||||
// We will deadlock if we ever have to wait on s_tx_lock from the PULSE task.
|
||||
PBL_ASSERT_NOT_TASK(PebbleTask_PULSE);
|
||||
if (!s_layer_up) {
|
||||
return NULL;
|
||||
}
|
||||
xSemaphoreTake(s_tx_lock, portMAX_DELAY);
|
||||
if (!s_layer_up) {
|
||||
// Transport went down while waiting for the lock
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Transport went down while waiting for lock");
|
||||
xSemaphoreGive(s_tx_lock);
|
||||
return NULL;
|
||||
}
|
||||
s_tx_buffer->app_protocol = app_protocol;
|
||||
return &s_tx_buffer->information[0];
|
||||
}
|
||||
|
||||
void pulse_reliable_send_cancel(void *buf) {
|
||||
prv_assert_reliable_buffer(buf);
|
||||
xSemaphoreGive(s_tx_lock);
|
||||
}
|
||||
|
||||
void pulse_reliable_send(void *buf, const size_t length) {
|
||||
if (!s_layer_up) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Transport went down before send");
|
||||
return;
|
||||
}
|
||||
prv_assert_reliable_buffer(buf);
|
||||
|
||||
s_tx_buffer->length = length;
|
||||
uint8_t sequence_number = s_send_variable;
|
||||
|
||||
prv_start_retransmit_timer(sequence_number);
|
||||
|
||||
prv_send_info_packet(sequence_number,
|
||||
s_tx_buffer->app_protocol,
|
||||
&s_tx_buffer->information[0],
|
||||
s_tx_buffer->length);
|
||||
|
||||
// As soon as we send the packet we could get ACK'd, preempting this thread and releasing the
|
||||
// s_tx_lock. Don't do anything here that assumes the s_tx_lock is held.
|
||||
}
|
||||
|
||||
// Reliable Transport Control Protocol
|
||||
// ===================================
|
||||
|
||||
static void prv_on_this_layer_up(PPPControlProtocol *this) {
|
||||
s_layer_up = true;
|
||||
s_send_variable = 0;
|
||||
s_receive_variable = 0;
|
||||
s_retransmit_count = 0;
|
||||
s_last_ack_number = 0;
|
||||
xSemaphoreGive(s_tx_lock);
|
||||
|
||||
#define ON_PACKET(...)
|
||||
#define ON_TRANSPORT_STATE_CHANGE(UP_HANDLER, DOWN_HANDLER) \
|
||||
UP_HANDLER();
|
||||
#include "console/pulse2_reliable_protocol_registry.def"
|
||||
#undef ON_PACKET
|
||||
#undef ON_TRANSPORT_STATE_CHANGE
|
||||
}
|
||||
|
||||
static void prv_on_this_layer_down(PPPControlProtocol *this) {
|
||||
pulse2_reliable_retransmit_timer_cancel();
|
||||
s_layer_up = false;
|
||||
xSemaphoreGive(s_tx_lock);
|
||||
|
||||
#define ON_PACKET(...)
|
||||
#define ON_TRANSPORT_STATE_CHANGE(UP_HANDLER, DOWN_HANDLER) \
|
||||
DOWN_HANDLER();
|
||||
#include "console/pulse2_reliable_protocol_registry.def"
|
||||
#undef ON_PACKET
|
||||
#undef ON_TRANSPORT_STATE_CHANGE
|
||||
}
|
||||
|
||||
static void prv_on_receive_code_reject(PPPControlProtocol *this,
|
||||
LCPPacket *packet) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
static PPPControlProtocolState s_traincp_state = {};
|
||||
|
||||
static PPPControlProtocol s_traincp_protocol = {
|
||||
.protocol_number = PULSE2_RELIABLE_CONTROL_PROTOCOL,
|
||||
.state = &s_traincp_state,
|
||||
.on_this_layer_up = prv_on_this_layer_up,
|
||||
.on_this_layer_down = prv_on_this_layer_down,
|
||||
.on_receive_code_reject = prv_on_receive_code_reject,
|
||||
};
|
||||
|
||||
PPPControlProtocol * const PULSE2_TRAINCP = &s_traincp_protocol;
|
||||
|
||||
void pulse2_reliable_control_on_packet(void *packet, size_t length) {
|
||||
ppp_control_protocol_handle_incoming_packet(PULSE2_TRAINCP, packet, length);
|
||||
}
|
||||
|
||||
// Shared events
|
||||
// =============
|
||||
void pulse2_reliable_on_link_up(void) {
|
||||
ppp_control_protocol_lower_layer_is_up(PULSE2_TRAINCP);
|
||||
}
|
||||
|
||||
void pulse2_reliable_on_link_down(void) {
|
||||
ppp_control_protocol_lower_layer_is_down(PULSE2_TRAINCP);
|
||||
}
|
||||
|
||||
void pulse2_reliable_init(void) {
|
||||
ppp_control_protocol_init(PULSE2_TRAINCP);
|
||||
ppp_control_protocol_open(PULSE2_TRAINCP);
|
||||
s_tx_buffer = kernel_zalloc_check(sizeof(ReliableInfoBuffer) +
|
||||
pulse_reliable_max_send_size());
|
||||
s_tx_lock = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(s_tx_lock);
|
||||
}
|
||||
|
||||
static void prv_bounce_ncp_state(void) {
|
||||
ppp_control_protocol_lower_layer_is_down(PULSE2_TRAINCP);
|
||||
ppp_control_protocol_lower_layer_is_up(PULSE2_TRAINCP);
|
||||
}
|
||||
|
||||
#endif
|
154
src/fw/console/serial_console.c
Normal file
154
src/fw/console/serial_console.c
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "serial_console.h"
|
||||
|
||||
#include "console/dbgserial_input.h"
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
#include "console_internal.h"
|
||||
#include "prompt.h"
|
||||
#include "ui_nudge.h"
|
||||
|
||||
#include "console/pulse_internal.h"
|
||||
#include "drivers/mic.h"
|
||||
#include "drivers/watchdog.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "os/tick.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <bluetooth/bt_test.h>
|
||||
|
||||
SerialConsoleState s_serial_console_state = SERIAL_CONSOLE_STATE_LOGGING;
|
||||
static bool s_serial_console_initialized;
|
||||
|
||||
static bool s_prompt_enabled = false;
|
||||
|
||||
static void logging_handle_character(char c, bool* should_context_switch) {
|
||||
#ifdef DISABLE_PROMPT
|
||||
return;
|
||||
#endif
|
||||
// Remember, you're in an interrupt here!
|
||||
|
||||
if (c == 0x3) { // CTRL-C
|
||||
if (!s_prompt_enabled) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Ignoring prompt request, not yet ready!");
|
||||
return;
|
||||
}
|
||||
console_switch_to_prompt();
|
||||
}
|
||||
}
|
||||
|
||||
void serial_console_init(void) {
|
||||
if (s_serial_console_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
dbgserial_register_character_callback(logging_handle_character);
|
||||
|
||||
s_serial_console_state = SERIAL_CONSOLE_STATE_LOGGING;
|
||||
s_serial_console_initialized = true;
|
||||
}
|
||||
|
||||
bool serial_console_is_prompt_enabled(void) {
|
||||
if (!s_serial_console_initialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (s_serial_console_state == SERIAL_CONSOLE_STATE_PROMPT);
|
||||
}
|
||||
|
||||
bool serial_console_is_logging_enabled(void) {
|
||||
if (!s_serial_console_initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return s_serial_console_state == SERIAL_CONSOLE_STATE_LOGGING ||
|
||||
s_serial_console_state == SERIAL_CONSOLE_STATE_PULSE;
|
||||
}
|
||||
|
||||
void serial_console_enable_prompt(void) {
|
||||
s_prompt_enabled = true;
|
||||
}
|
||||
|
||||
void serial_console_write_log_message(const char* msg) {
|
||||
while (*msg) {
|
||||
dbgserial_putchar(*(msg++));
|
||||
}
|
||||
}
|
||||
|
||||
void serial_console_set_state(SerialConsoleState new_state) {
|
||||
PBL_ASSERTN(s_serial_console_initialized);
|
||||
PBL_ASSERTN(new_state < SERIAL_CONSOLE_NUM_STATES);
|
||||
|
||||
// This function is called from the USART3 IRQ, the new timer thread,
|
||||
// and the system task. It thus needs a critical section.
|
||||
portENTER_CRITICAL();
|
||||
|
||||
if (new_state == s_serial_console_state) {
|
||||
portEXIT_CRITICAL();
|
||||
return;
|
||||
}
|
||||
|
||||
#if !PULSE_EVERYWHERE
|
||||
if (new_state == SERIAL_CONSOLE_STATE_LOGGING) {
|
||||
stop_mode_enable(InhibitorDbgSerial);
|
||||
dbgserial_enable_rx_exti();
|
||||
} else if (s_serial_console_state == SERIAL_CONSOLE_STATE_LOGGING) {
|
||||
stop_mode_disable(InhibitorDbgSerial);
|
||||
}
|
||||
#endif
|
||||
|
||||
s_serial_console_state = new_state;
|
||||
|
||||
switch (s_serial_console_state) {
|
||||
#if !DISABLE_PROMPT
|
||||
case SERIAL_CONSOLE_STATE_PROMPT:
|
||||
dbgserial_register_character_callback(prompt_handle_character);
|
||||
dbgserial_set_rx_dma_enabled(false);
|
||||
break;
|
||||
#endif
|
||||
case SERIAL_CONSOLE_STATE_LOGGING:
|
||||
dbgserial_register_character_callback(logging_handle_character);
|
||||
dbgserial_set_rx_dma_enabled(false);
|
||||
break;
|
||||
#ifdef UI_DEBUG
|
||||
case SERIAL_CONSOLE_STATE_LAYER_NUDGING:
|
||||
dbgserial_register_character_callback(layer_debug_nudging_handle_character);
|
||||
dbgserial_set_rx_dma_enabled(false);
|
||||
break;
|
||||
#endif
|
||||
case SERIAL_CONSOLE_STATE_HCI_PASSTHROUGH:
|
||||
dbgserial_register_character_callback(bt_driver_test_handle_hci_passthrough_character);
|
||||
dbgserial_set_rx_dma_enabled(false);
|
||||
break;
|
||||
case SERIAL_CONSOLE_STATE_PULSE:
|
||||
dbgserial_register_character_callback(pulse_handle_character);
|
||||
dbgserial_set_rx_dma_enabled(true);
|
||||
break;
|
||||
default:
|
||||
WTF; // Don't know this state
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
SerialConsoleState serial_console_get_state(void) {
|
||||
SerialConsoleState state = __atomic_load_n(&s_serial_console_state, __ATOMIC_RELAXED);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
32
src/fw/console/serial_console.h
Normal file
32
src/fw/console/serial_console.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void serial_console_init(void);
|
||||
|
||||
bool serial_console_is_logging_enabled(void);
|
||||
bool serial_console_is_prompt_enabled(void);
|
||||
|
||||
//! Allow the prompt to be started. By default the prompt is disabled it at system boot, and needs
|
||||
//! to be enabled once the rest of the system is ready to handle prompt commands.
|
||||
//! FIXME: This is probably in the wrong place, but we're reworking prompt in general once PULSEv2
|
||||
//! lands so no need to rearrange the deck chairs on the titanic.
|
||||
void serial_console_enable_prompt(void);
|
||||
|
||||
void serial_console_write_log_message(const char* msg);
|
123
src/fw/console/ui_nudge.c
Normal file
123
src/fw/console/ui_nudge.c
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef UI_DEBUG
|
||||
#include "ui_nudge.h"
|
||||
|
||||
#include "console_internal.h"
|
||||
#include "dbgserial.h"
|
||||
|
||||
#include "applib/ui/layer.h"
|
||||
#include "applib/ui/ui_debug.h"
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
#include "applib/ui/window_stack_private.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
|
||||
#include "services/common/compositor/compositor.h"
|
||||
#include "util/string.h"
|
||||
|
||||
static Layer *s_nudge_layer = NULL;
|
||||
|
||||
static void prv_flush_kernel_main_cb(void *unused) {
|
||||
// FIXME compositor_flush();
|
||||
}
|
||||
|
||||
void layer_debug_nudging_handle_character(char c, bool *should_context_switch) {
|
||||
GRect frame = s_nudge_layer->frame;
|
||||
GRect bounds = s_nudge_layer->bounds;
|
||||
switch (c) {
|
||||
case 0x3: // CTRL - C
|
||||
s_nudge_layer = NULL;
|
||||
// Back to log mode:
|
||||
serial_console_set_state(SERIAL_CONSOLE_STATE_LOGGING);
|
||||
// Dump window:
|
||||
command_dump_window();
|
||||
return;
|
||||
|
||||
case 'A':
|
||||
case 'a':
|
||||
--frame.origin.x;
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
case 'd':
|
||||
++frame.origin.x;
|
||||
break;
|
||||
|
||||
case 'W':
|
||||
case 'w':
|
||||
++frame.origin.y;
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
case 's':
|
||||
--frame.origin.y;
|
||||
break;
|
||||
|
||||
case '[':
|
||||
--frame.size.w;
|
||||
bounds.size = frame.size;
|
||||
break;
|
||||
|
||||
case ']':
|
||||
++frame.size.w;
|
||||
bounds.size = frame.size;
|
||||
break;
|
||||
|
||||
case '{':
|
||||
--frame.size.h;
|
||||
bounds.size = frame.size;
|
||||
break;
|
||||
|
||||
case '}':
|
||||
++frame.size.h;
|
||||
bounds.size = frame.size;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
layer_set_frame(s_nudge_layer, &frame);
|
||||
layer_set_bounds(s_nudge_layer, &bounds);
|
||||
|
||||
PebbleEvent event = {
|
||||
.type = PEBBLE_CALLBACK_EVENT,
|
||||
.callback = {
|
||||
.callback = prv_flush_kernel_main_cb,
|
||||
.data = NULL,
|
||||
},
|
||||
};
|
||||
*should_context_switch = event_put_isr(&event);
|
||||
}
|
||||
|
||||
void command_layer_nudge(const char *address_str) {
|
||||
intptr_t address = str_to_address(address_str);
|
||||
if (address == -1) {
|
||||
return;
|
||||
}
|
||||
// Simple sanity check:
|
||||
if (((Layer *)address)->window != app_window_stack_get_top_window()) {
|
||||
dbgserial_putstr("Specify valid Layer address!");
|
||||
return;
|
||||
}
|
||||
s_nudge_layer = (Layer *)address;
|
||||
|
||||
dbgserial_putstr("Layer nudging mode, CTRL-C to stop");
|
||||
dbgserial_putstr("Keys:\nWASD: Move frame.origin\n[]: Change frame.size.w & bounds.size.w\n{}: Change frame.size.h & bounds.size.h");
|
||||
serial_console_set_state(SERIAL_CONSOLE_STATE_LAYER_NUDGING);
|
||||
}
|
||||
#endif /* UI_DEBUG */
|
24
src/fw/console/ui_nudge.h
Normal file
24
src/fw/console/ui_nudge.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
|
||||
//! Enters nudging mode with the layer at given address
|
||||
void command_layer_nudge(const char *address_str);
|
||||
|
||||
//! Prompt character handler
|
||||
void layer_debug_nudging_handle_character(char c, bool *should_context_switch);
|
Loading…
Add table
Add a link
Reference in a new issue