mirror of
https://github.com/google/pebble.git
synced 2026-02-14 09:27:20 -05:00
Import of the watch repository from Pebble
This commit is contained in:
181
src/fw/debug/advanced_logging.c
Normal file
181
src/fw/debug/advanced_logging.c
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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 "flash_logging.h"
|
||||
|
||||
#include "console/dbgserial.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "os/mutex.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/shared_circular_buffer.h"
|
||||
|
||||
#include "services/common/system_task.h"
|
||||
|
||||
static SharedCircularBuffer s_buffer;
|
||||
static SharedCircularBufferClient s_buffer_client;
|
||||
// 526 bytes is enough to buffer up the worst case bunch of reboot messages - a watchdog reset with a timer
|
||||
// callback. During normal operation, since log messages are hashed, most are only 30-40 bytes long with the longest
|
||||
// being about 80 bytes, so this is enough for 7-15 or so messages.
|
||||
static uint8_t s_buffer_storage[550];
|
||||
static PebbleMutex *s_buffer_mutex = INVALID_MUTEX_HANDLE; //!< Protects s_buffer
|
||||
static PebbleMutex *s_flash_write_mutex = INVALID_MUTEX_HANDLE; //!< Protects log line consistency
|
||||
static bool s_is_flash_write_scheduled; //!< true if handle_buffer_sync KernelBG callback is scheduled
|
||||
|
||||
static void write_message(void) {
|
||||
// Note that we should enter this function with the buffer mutex held.
|
||||
|
||||
const uint8_t *data_read;
|
||||
uint16_t read_length;
|
||||
|
||||
// Read the header part
|
||||
bool result = shared_circular_buffer_read(&s_buffer, &s_buffer_client, sizeof(uint8_t), &data_read, &read_length);
|
||||
PBL_ASSERTN(result);
|
||||
PBL_ASSERT(read_length == sizeof(uint8_t), "read_length %u sizeof(uint8_t) %u", read_length, sizeof(uint8_t));
|
||||
uint8_t msg_length = *data_read;
|
||||
|
||||
if (shared_circular_buffer_get_read_space_remaining(&s_buffer, &s_buffer_client) < msg_length + sizeof(uint8_t)) {
|
||||
return; // Not ready yet, consume nothing.
|
||||
}
|
||||
|
||||
// Flash_logging_log_start can trigger a flash erase. Release the buffer mutex
|
||||
// to allow logging while the (slow) erase completes.
|
||||
mutex_unlock(s_buffer_mutex);
|
||||
uint32_t flash_addr = flash_logging_log_start(msg_length);
|
||||
mutex_lock(s_buffer_mutex);
|
||||
if (flash_addr == FLASH_LOG_INVALID_ADDR) {
|
||||
return;
|
||||
}
|
||||
|
||||
shared_circular_buffer_consume(&s_buffer, &s_buffer_client, read_length);
|
||||
|
||||
while (msg_length > 0) {
|
||||
mutex_unlock(s_buffer_mutex);
|
||||
|
||||
// Note that this buffer read really should be done with the buffer mutex held.
|
||||
// This works only because writes to the buffer do not advance slackers.
|
||||
result = shared_circular_buffer_read(&s_buffer, &s_buffer_client, msg_length, &data_read, &read_length);
|
||||
PBL_ASSERTN(result);
|
||||
msg_length -= read_length;
|
||||
|
||||
flash_logging_write(data_read, flash_addr, read_length);
|
||||
flash_addr += read_length;
|
||||
|
||||
mutex_lock(s_buffer_mutex);
|
||||
shared_circular_buffer_consume(&s_buffer, &s_buffer_client, read_length);
|
||||
}
|
||||
|
||||
// We should still be holding the buffer lock here...
|
||||
}
|
||||
|
||||
static void handle_buffer_sync(void *data) {
|
||||
const bool is_async = (uintptr_t) data;
|
||||
|
||||
mutex_lock(s_flash_write_mutex);
|
||||
mutex_lock(s_buffer_mutex);
|
||||
|
||||
while (shared_circular_buffer_get_read_space_remaining(&s_buffer, &s_buffer_client) > 0) {
|
||||
write_message();
|
||||
// The above function mucks with the mutex
|
||||
mutex_assert_held_by_curr_task(s_buffer_mutex, true /* is_held */);
|
||||
}
|
||||
|
||||
if (is_async) {
|
||||
s_is_flash_write_scheduled = false;
|
||||
}
|
||||
|
||||
mutex_unlock(s_buffer_mutex);
|
||||
mutex_unlock(s_flash_write_mutex);
|
||||
}
|
||||
|
||||
|
||||
void advanced_logging_init(void) {
|
||||
flash_logging_init();
|
||||
|
||||
shared_circular_buffer_init(&s_buffer, s_buffer_storage, sizeof(s_buffer_storage));
|
||||
shared_circular_buffer_add_client(&s_buffer, &s_buffer_client);
|
||||
|
||||
s_buffer_mutex = mutex_create();
|
||||
s_flash_write_mutex = mutex_create();
|
||||
}
|
||||
|
||||
// Return true on success
|
||||
static bool write_buffer_locking(char* buffer, int length, bool async) {
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
mutex_lock(s_buffer_mutex);
|
||||
if (shared_circular_buffer_get_write_space_remaining(&s_buffer) >= length + 1) {
|
||||
// Ideally we could figure out a way to skip out on this copy but then you'd potentially need to sniprintf
|
||||
// into a non-contiguous buffer... whatever, we have CPU to burn.
|
||||
uint8_t msg_length = length;
|
||||
|
||||
// Do not advance slackers. Data loss and/or corruption will occur! See write_message()
|
||||
shared_circular_buffer_write(&s_buffer, &msg_length, sizeof(uint8_t), false /*advance_slackers*/);
|
||||
shared_circular_buffer_write(&s_buffer, (const uint8_t*) buffer, length, false /*advance_slackers*/);
|
||||
|
||||
success = true;
|
||||
}
|
||||
mutex_unlock(s_buffer_mutex);
|
||||
|
||||
// If we failed to buffer this message, flush the buffer to cache to make room.
|
||||
// Otherwise, if this is a sync message, flush this message to flash.
|
||||
if (!success || !async) {
|
||||
handle_buffer_sync((void *)(uintptr_t) false /* !is_async */);
|
||||
}
|
||||
} while (!success); // Loop until the buffer copy succeeds. If sync, also wait until this message
|
||||
// is written to flash.
|
||||
// It's highly unlikely that another task will win the race and completely
|
||||
// fill the buffer between the flash write and the next buffer write attempt.
|
||||
// If so, there are bigger issues.
|
||||
|
||||
if (async) {
|
||||
mutex_lock(s_buffer_mutex);
|
||||
if (!s_is_flash_write_scheduled) {
|
||||
s_is_flash_write_scheduled = true;
|
||||
system_task_add_callback(handle_buffer_sync, (void *)(uintptr_t) true /* is_async */);
|
||||
}
|
||||
mutex_unlock(s_buffer_mutex);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void pbl_log_advanced(char* buffer, int length, bool async) {
|
||||
if (s_buffer_mutex == INVALID_MUTEX_HANDLE) {
|
||||
return;
|
||||
}
|
||||
write_buffer_locking(buffer, length, async);
|
||||
}
|
||||
|
||||
char pbl_log_get_level_char(const uint8_t log_level) {
|
||||
switch (log_level) {
|
||||
case LOG_LEVEL_ALWAYS:
|
||||
return '*';
|
||||
case LOG_LEVEL_ERROR:
|
||||
return 'E';
|
||||
case LOG_LEVEL_WARNING:
|
||||
return 'W';
|
||||
case LOG_LEVEL_INFO:
|
||||
return 'I';
|
||||
case LOG_LEVEL_DEBUG:
|
||||
return 'D';
|
||||
case LOG_LEVEL_DEBUG_VERBOSE:
|
||||
return 'V';
|
||||
default:
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user