mirror of
https://github.com/google/pebble.git
synced 2025-07-17 19:46:40 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
271
src/fw/kernel/pulse_logging.c
Normal file
271
src/fw/kernel/pulse_logging.c
Normal file
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* 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_logging.h"
|
||||
|
||||
#include "logging_private.h"
|
||||
#include "pebble_tasks.h"
|
||||
|
||||
#include "console/pulse.h"
|
||||
#include "console/pulse_protocol_impl.h"
|
||||
#include "kernel/events.h"
|
||||
|
||||
#include "mcu/interrupts.h"
|
||||
#include "mcu/privilege.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/circular_buffer.h"
|
||||
#include "util/math.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
//! This is the format for a PULSEv2 log message when sent out over the wire.
|
||||
typedef struct PACKED MessageContents {
|
||||
uint8_t message_type;
|
||||
char src_filename[16];
|
||||
char log_level_char;
|
||||
unsigned char task_char;
|
||||
uint64_t time_ms;
|
||||
uint16_t line_number;
|
||||
//! Not null-terminated message contents
|
||||
char message[128];
|
||||
} MessageContents;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint64_t timestamp_ms;
|
||||
uint8_t log_level;
|
||||
} BufferedLogInfo;
|
||||
|
||||
//! For ISR contexts, we can't write log message directly. Instead we write them to this
|
||||
//! circular buffer and flush them out when the ISR is completed.
|
||||
static CircularBuffer s_isr_log_buffer;
|
||||
|
||||
//! Underlying storage for s_isr_log_buffer
|
||||
static uint8_t s_isr_log_buffer_storage[256];
|
||||
|
||||
|
||||
static uint64_t prv_get_timestamp_ms(void) {
|
||||
time_t time_s;
|
||||
uint16_t time_ms;
|
||||
rtc_get_time_ms(&time_s, &time_ms);
|
||||
return ((uint64_t) time_s * 1000) + time_ms;
|
||||
}
|
||||
|
||||
static size_t prv_serialize_log_header(MessageContents *contents,
|
||||
uint8_t log_level, uint64_t timestamp_ms, PebbleTask task,
|
||||
const char *src_filename, uint16_t src_line_number) {
|
||||
contents->message_type = 1; // Text
|
||||
|
||||
// Log the log level and the current task+privilege level
|
||||
contents->log_level_char = pbl_log_get_level_char(log_level);
|
||||
contents->task_char = pebble_task_get_char(task);
|
||||
if (mcu_state_is_privileged()) {
|
||||
contents->task_char = toupper(contents->task_char);
|
||||
}
|
||||
|
||||
contents->time_ms = timestamp_ms;
|
||||
|
||||
// Obtain the filename
|
||||
strncpy(contents->src_filename, GET_FILE_NAME(src_filename), sizeof(contents->src_filename));
|
||||
|
||||
// Obtain the line number
|
||||
contents->line_number = src_line_number;
|
||||
|
||||
return offsetof(MessageContents, message);
|
||||
}
|
||||
|
||||
|
||||
//! Serialize a message into contents, returning the number of bytes used.
|
||||
static size_t prv_serialize_log(MessageContents *contents,
|
||||
uint8_t log_level, uint64_t timestamp_ms, PebbleTask task,
|
||||
const char *src_filename, uint16_t src_line_number,
|
||||
const char *message) {
|
||||
|
||||
prv_serialize_log_header(contents, log_level, timestamp_ms, task,
|
||||
src_filename, src_line_number);
|
||||
|
||||
// Write the actual log message.
|
||||
strncpy(contents->message, message, sizeof(contents->message));
|
||||
|
||||
size_t payload_length = MIN(sizeof(MessageContents),
|
||||
offsetof(MessageContents, message) + strlen(message));
|
||||
|
||||
return payload_length;
|
||||
}
|
||||
|
||||
static void prv_send_pulse_packet(uint8_t log_level, const char *src_filename,
|
||||
uint16_t src_line_number, const char *message) {
|
||||
MessageContents *contents = pulse_push_send_begin(PULSE_PROTOCOL_LOGGING);
|
||||
|
||||
const size_t payload_length = prv_serialize_log(
|
||||
contents, log_level, prv_get_timestamp_ms(), pebble_task_get_current(),
|
||||
src_filename, src_line_number, message);
|
||||
|
||||
pulse_push_send(contents, payload_length);
|
||||
}
|
||||
|
||||
|
||||
static bool prv_isr_buffer_read_and_consume(void *buffer, size_t read_length) {
|
||||
portENTER_CRITICAL();
|
||||
|
||||
const bool result = circular_buffer_copy(&s_isr_log_buffer, buffer, read_length);
|
||||
if (result) {
|
||||
circular_buffer_consume(&s_isr_log_buffer, read_length);
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void prv_event_cb(void *data) {
|
||||
while (true) {
|
||||
// No need to worry about reading a partial message here, we write messages to the circular
|
||||
// buffer while disabling interrupts the whole time.
|
||||
|
||||
uint32_t log_length;
|
||||
if (!prv_isr_buffer_read_and_consume(&log_length, sizeof(log_length))) {
|
||||
// No more messages to read if we can't read a length
|
||||
break;
|
||||
}
|
||||
|
||||
if (log_length == sizeof(uint32_t)) {
|
||||
// We dropped a message, log a message to that effect
|
||||
|
||||
MessageContents *contents = pulse_push_send_begin(PULSE_PROTOCOL_LOGGING);
|
||||
|
||||
const size_t payload_length = prv_serialize_log(
|
||||
contents, LOG_LEVEL_ERROR, prv_get_timestamp_ms(), PebbleTask_Unknown,
|
||||
"", 0, "ISR Message Dropped!");
|
||||
|
||||
pulse_push_send(contents, payload_length);
|
||||
} else {
|
||||
BufferedLogInfo log_info;
|
||||
prv_isr_buffer_read_and_consume(&log_info, sizeof(log_info));
|
||||
|
||||
MessageContents *contents = pulse_push_send_begin(PULSE_PROTOCOL_LOGGING);
|
||||
|
||||
const size_t header_length = prv_serialize_log_header(
|
||||
contents, log_info.log_level, log_info.timestamp_ms, PebbleTask_Unknown, "", 0);
|
||||
|
||||
const size_t message_length = log_length - sizeof(uint32_t) - sizeof(BufferedLogInfo);
|
||||
prv_isr_buffer_read_and_consume(&contents->message, message_length);
|
||||
|
||||
pulse_push_send(contents, header_length + message_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_enqueue_log_message(uint8_t log_level, const char *message) {
|
||||
const bool buffer_was_empty = (circular_buffer_get_read_space_remaining(&s_isr_log_buffer) == 0);
|
||||
|
||||
// Need to prevent other interrupts from corrupting the log buffer while we're writing to it
|
||||
portENTER_CRITICAL();
|
||||
|
||||
if (circular_buffer_get_write_space_remaining(&s_isr_log_buffer) < sizeof(uint32_t)) {
|
||||
// Completely out of space, can't do anything.
|
||||
portEXIT_CRITICAL();
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t message_length = MIN(strlen(message), 128);
|
||||
const uint32_t required_space = sizeof(uint32_t) + sizeof(BufferedLogInfo) + message_length;
|
||||
|
||||
if (circular_buffer_get_write_space_remaining(&s_isr_log_buffer) < required_space) {
|
||||
// Not enough space for the full message, just write an empty message with only the length
|
||||
// word to indicate we're dropping the message.
|
||||
const uint32_t insufficient_space_length = sizeof(uint32_t);
|
||||
circular_buffer_write(&s_isr_log_buffer,
|
||||
&insufficient_space_length, sizeof(insufficient_space_length));
|
||||
} else {
|
||||
circular_buffer_write(&s_isr_log_buffer, &required_space, sizeof(required_space));
|
||||
|
||||
const BufferedLogInfo log_info = {
|
||||
.timestamp_ms = prv_get_timestamp_ms(),
|
||||
.log_level = log_level
|
||||
};
|
||||
circular_buffer_write(&s_isr_log_buffer, &log_info, sizeof(log_info));
|
||||
|
||||
circular_buffer_write(&s_isr_log_buffer, message, message_length);
|
||||
}
|
||||
|
||||
if (buffer_was_empty) {
|
||||
PebbleEvent e = {
|
||||
.type = PEBBLE_CALLBACK_EVENT,
|
||||
.callback = {
|
||||
.callback = prv_event_cb
|
||||
}
|
||||
};
|
||||
event_put_isr(&e);
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void pulse_logging_init(void) {
|
||||
circular_buffer_init(&s_isr_log_buffer,
|
||||
s_isr_log_buffer_storage, sizeof(s_isr_log_buffer_storage));
|
||||
}
|
||||
|
||||
void pulse_logging_log(uint8_t log_level, const char* src_filename,
|
||||
uint16_t src_line_number, const char* message) {
|
||||
if (portIN_CRITICAL() || mcu_state_is_isr() ||
|
||||
xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) {
|
||||
// We're in a state where we can't immediately send the message, save it to an internal buffer
|
||||
// instead for later sending.
|
||||
prv_enqueue_log_message(log_level, message);
|
||||
} else {
|
||||
// Send the log line inline
|
||||
prv_send_pulse_packet(log_level, src_filename, src_line_number, message);
|
||||
}
|
||||
}
|
||||
|
||||
void pulse_logging_log_buffer_flush(void) {
|
||||
prv_event_cb(NULL);
|
||||
}
|
||||
|
||||
void pulse_logging_log_sync(uint8_t log_level, const char *src_filename,
|
||||
uint16_t src_line_number, const char *message) {
|
||||
// Send the log line inline, even if we're in a critical section or ISR
|
||||
prv_send_pulse_packet(log_level, src_filename, src_line_number, message);
|
||||
}
|
||||
|
||||
void *pulse_logging_log_sync_begin(
|
||||
uint8_t log_level, const char *src_filename, uint16_t src_line_number) {
|
||||
MessageContents *contents = pulse_push_send_begin(PULSE_PROTOCOL_LOGGING);
|
||||
prv_serialize_log_header(contents, log_level, prv_get_timestamp_ms(),
|
||||
pebble_task_get_current(), src_filename,
|
||||
src_line_number);
|
||||
contents->message[0] = '\0';
|
||||
return contents;
|
||||
}
|
||||
|
||||
void pulse_logging_log_sync_append(void *ctx, const char *message) {
|
||||
MessageContents *contents = ctx;
|
||||
strncat(contents->message, message, sizeof(contents->message));
|
||||
}
|
||||
|
||||
void pulse_logging_log_sync_send(void *ctx) {
|
||||
MessageContents *contents = ctx;
|
||||
size_t payload_length = MIN(
|
||||
sizeof(MessageContents),
|
||||
offsetof(MessageContents, message) + strlen(contents->message));
|
||||
pulse_push_send(contents, payload_length);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue