Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View 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
View 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
View 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);

View 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);

View 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);
}

View 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);

View 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);

View 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);
}

View 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);

View 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);
}
}

View 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
View 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
View 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);

File diff suppressed because it is too large Load diff

View 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
View 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
View 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
View 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

View 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

View 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);

View 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

View 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

View 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);
}
}
}

View 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
};

View 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);

View 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
};

View 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
};

View 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

View 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
};

View 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

View 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

View 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);

View 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);
}

View 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
View 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);
}

View 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
View 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;
}
}

View 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

View 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

View 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

View 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

View 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;
}

View 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
View 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
View 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);