mirror of
https://github.com/google/pebble.git
synced 2026-02-11 07:57:19 -05:00
182 lines
6.2 KiB
C
182 lines
6.2 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 "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 '?';
|
|
}
|
|
}
|
|
|