mirror of
https://github.com/google/pebble.git
synced 2025-05-07 10:21:40 -04:00
271 lines
9.1 KiB
C
271 lines
9.1 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#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);
|
|
}
|