mirror of
https://github.com/google/pebble.git
synced 2025-06-06 10:13:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
420
src/fw/process_management/worker_manager.c
Normal file
420
src/fw/process_management/worker_manager.c
Normal file
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
* 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 "process_manager.h"
|
||||
#include "worker_manager.h"
|
||||
#include "process_loader.h"
|
||||
|
||||
// Pebble stuff
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/util/segment.h"
|
||||
#include "kernel/util/task_init.h"
|
||||
#include "mcu/cache.h"
|
||||
#include "mcu/privilege.h"
|
||||
#include "os/tick.h"
|
||||
#include "popups/crashed_ui.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_management/process_heap.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
#include "shell/prefs.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
// FreeRTOS stuff
|
||||
#include "FreeRTOS.h"
|
||||
#include "freertos_application.h"
|
||||
#include "task.h"
|
||||
#include "queue.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
static const int MAX_TO_WORKER_EVENTS = 8;
|
||||
static ProcessContext s_worker_task_context;
|
||||
static QueueHandle_t s_to_worker_event_queue;
|
||||
|
||||
extern char __WORKER_RAM__[];
|
||||
extern char __WORKER_RAM_end__[];
|
||||
extern char __stack_guard_size__[];
|
||||
|
||||
//! Used by the "pebble gdb" command to locate the loaded worker in memory.
|
||||
void * volatile g_worker_load_address;
|
||||
|
||||
typedef struct NextWorker {
|
||||
const PebbleProcessMd *md;
|
||||
const void *args;
|
||||
} NextWorker;
|
||||
|
||||
static NextWorker s_next_worker;
|
||||
|
||||
static bool s_workers_enabled = true;
|
||||
|
||||
static AppInstallId s_last_worker_crashed_install_id;
|
||||
static time_t s_last_worker_crash_timestamp;
|
||||
static bool s_worker_crash_relaunches_disabled;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
void worker_manager_init(void) {
|
||||
s_to_worker_event_queue = xQueueCreate(MAX_TO_WORKER_EVENTS, sizeof(PebbleEvent));
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// This is the wrapper function for the worker. It's not allowed to return as it's the top frame on the stack
|
||||
// created for the application.
|
||||
static void prv_worker_task_main(void *entry_point) {
|
||||
// Init worker state variables
|
||||
worker_state_init();
|
||||
task_init();
|
||||
|
||||
// about to start the worker in earnest. No longer safe to kill.
|
||||
s_worker_task_context.safe_to_kill = false;
|
||||
|
||||
// Enter unprivileged mode!
|
||||
const bool is_unprivileged = s_worker_task_context.app_md->is_unprivileged;
|
||||
if (is_unprivileged) {
|
||||
mcu_state_set_thread_privilege(false);
|
||||
}
|
||||
|
||||
const PebbleMain main_func = entry_point;
|
||||
main_func();
|
||||
|
||||
// Clean up after the worker. Remember to put only non-critical cleanup here,
|
||||
// as the worker may crash or otherwise misbehave. If something really needs to
|
||||
// be cleaned up, make it so the kernel can do it on the worker's behalf and put
|
||||
// the call at the bottom of prv_worker_cleanup.
|
||||
worker_state_deinit();
|
||||
|
||||
sys_exit();
|
||||
}
|
||||
|
||||
//! Heap locking function for our app heap. Our process heaps don't actually
|
||||
//! have to be locked because they're the sole property of the process and no
|
||||
//! other tasks should be touching it. All this function does is verify that
|
||||
//! this condition is met before continuing without locking.
|
||||
static void prv_heap_lock(void* unused) {
|
||||
PBL_ASSERT_TASK(PebbleTask_Worker);
|
||||
}
|
||||
|
||||
static size_t prv_get_worker_segment_size(const PebbleProcessMd *app_md) {
|
||||
// 12 KiB - 640 bytes workerlib static = 11648 bytes
|
||||
return 11648;
|
||||
}
|
||||
|
||||
static size_t prv_get_worker_stack_size(const PebbleProcessMd *app_md) {
|
||||
return 1400;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool worker_manager_launch_new_worker_with_args(const PebbleProcessMd *app_md, const void *args) {
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelMain);
|
||||
|
||||
// Don't launch workers in recovery mode to reduce the chance of crashes
|
||||
#ifdef RECOVERY_FW
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// If workers are disabled, don't launch
|
||||
if (!s_workers_enabled) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Workers disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we are trying to start another worker, then we want to enable relaunches on crashes.
|
||||
s_worker_crash_relaunches_disabled = false;
|
||||
|
||||
// If there is a different worker currently running, tell it to quit first. When it sees s_next_worker
|
||||
// set, it will call us again once it finishes closing the current worker
|
||||
if (s_worker_task_context.app_md != NULL && s_worker_task_context.app_md != app_md) {
|
||||
s_next_worker = (NextWorker) {
|
||||
.md = app_md,
|
||||
.args = args,
|
||||
};
|
||||
worker_manager_close_current_worker(true /*graceful*/);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clear the next worker settings
|
||||
s_next_worker = (NextWorker) {};
|
||||
|
||||
// Error if a worker already launched
|
||||
if (pebble_task_get_handle_for_task(PebbleTask_Worker) != NULL) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Worker already launched");
|
||||
return false;
|
||||
}
|
||||
|
||||
process_manager_init_context(&s_worker_task_context, app_md, args);
|
||||
s_worker_task_context.to_process_event_queue = s_to_worker_event_queue;
|
||||
|
||||
// Set up the worker's memory and load the binary into it.
|
||||
const size_t worker_segment_size = prv_get_worker_segment_size(app_md);
|
||||
// The stack guard is counted as part of the app segment size...
|
||||
const size_t stack_guard_size = (uintptr_t)__stack_guard_size__;
|
||||
// ...and is carved out of the stack.
|
||||
const size_t stack_size =
|
||||
prv_get_worker_stack_size(app_md) - stack_guard_size;
|
||||
|
||||
MemorySegment worker_ram = { __WORKER_RAM__, __WORKER_RAM_end__ };
|
||||
memset((char *)worker_ram.start + stack_guard_size, 0,
|
||||
memory_segment_get_size(&worker_ram) - stack_guard_size);
|
||||
|
||||
MemorySegment worker_segment;
|
||||
PBL_ASSERTN(memory_segment_split(&worker_ram, &worker_segment,
|
||||
worker_segment_size));
|
||||
PBL_ASSERTN(memory_segment_split(&worker_segment, NULL, stack_guard_size));
|
||||
// No (accessible) memory segments can be placed between the top of WORKER_RAM
|
||||
// and the end of stack. Stacks always grow towards lower memory addresses, so
|
||||
// we want a stack overflow to touch the stack guard region before it begins
|
||||
// to clobber actual data. And syscalls assume that the stack is always at the
|
||||
// top of WORKER_RAM; violating this assumption will result in syscalls
|
||||
// sometimes failing when the worker hasn't done anything wrong.
|
||||
portSTACK_TYPE *stack = memory_segment_split(&worker_segment, NULL,
|
||||
stack_size);
|
||||
PBL_ASSERTN(stack);
|
||||
s_worker_task_context.load_start = worker_segment.start;
|
||||
g_worker_load_address = worker_segment.start;
|
||||
void *entry_point = process_loader_load(app_md, PebbleTask_Worker,
|
||||
&worker_segment);
|
||||
s_worker_task_context.load_end = worker_segment.start;
|
||||
if (!entry_point) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Tried to launch an invalid worker in bank %u!",
|
||||
process_metadata_get_code_bank_num(app_md));
|
||||
return false;
|
||||
}
|
||||
|
||||
// The rest of worker_ram is available for worker state to use as it sees fit.
|
||||
if (!worker_state_configure(&worker_ram)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Worker state configuration failed");
|
||||
return false;
|
||||
}
|
||||
// The remaining space in worker_segment is assigned to the worker's
|
||||
// heap. Worker state needs to be configured before initializing the
|
||||
// heap as the WorkerState struct holds the worker heap's Heap object.
|
||||
Heap *worker_heap = worker_state_get_heap();
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Worker heap init %p %p",
|
||||
worker_segment.start, worker_segment.end);
|
||||
heap_init(worker_heap, worker_segment.start, worker_segment.end,
|
||||
/* enable_heap_fuzzing */ false);
|
||||
heap_set_lock_impl(worker_heap, (HeapLockImpl) {
|
||||
.lock_function = prv_heap_lock,
|
||||
});
|
||||
process_heap_set_exception_handlers(worker_heap, app_md);
|
||||
|
||||
// Init services required for this process before it starts to execute
|
||||
process_manager_process_setup(PebbleTask_Worker);
|
||||
|
||||
char task_name[configMAX_TASK_NAME_LEN];
|
||||
snprintf(task_name, sizeof(task_name), "Worker <%s>", process_metadata_get_name(s_worker_task_context.app_md));
|
||||
|
||||
TaskParameters_t task_params = {
|
||||
.pvTaskCode = prv_worker_task_main,
|
||||
.pcName = task_name,
|
||||
.usStackDepth = stack_size / sizeof(portSTACK_TYPE),
|
||||
.pvParameters = entry_point,
|
||||
.uxPriority = (tskIDLE_PRIORITY + 1) | portPRIVILEGE_BIT,
|
||||
.puxStackBuffer = stack,
|
||||
};
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Starting %s", task_name);
|
||||
|
||||
pebble_task_create(PebbleTask_Worker, &task_params, &s_worker_task_context.task_handle);
|
||||
|
||||
// If no default yet, set as the default so that it can be relaunched upon system reset
|
||||
if (worker_manager_get_default_install_id() == INSTALL_ID_INVALID) {
|
||||
worker_manager_set_default_install_id(s_worker_task_context.install_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Reset the data we're tracking for workers that crash
|
||||
static void prv_reset_last_worker_crashed_data(void) {
|
||||
// No need to reset s_last_worker_crash_timestamp because we always check the install id before
|
||||
// we check the timestamp
|
||||
s_last_worker_crashed_install_id = INSTALL_ID_INVALID;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Launch the next worker, if there is one
|
||||
void worker_manager_launch_next_worker(AppInstallId previous_worker_install_id) {
|
||||
// Is there another worker set to switch to?
|
||||
if (s_next_worker.md != NULL) {
|
||||
worker_manager_launch_new_worker_with_args(s_next_worker.md, s_next_worker.args);
|
||||
} else {
|
||||
// Do we have a default worker we should switch to that is different from the previous worker?
|
||||
AppInstallId default_id = worker_manager_get_default_install_id();
|
||||
if (default_id != INSTALL_ID_INVALID && default_id != previous_worker_install_id) {
|
||||
worker_manager_put_launch_worker_event(default_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_handle_remove_current_worker(void) {
|
||||
s_worker_crash_relaunches_disabled = true;
|
||||
worker_manager_close_current_worker(true);
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_close_current_worker(bool gracefully) {
|
||||
|
||||
// This method can be called as a result of receiving a PEBBLE_PROCESS_KILL_EVENT notification
|
||||
// from an app, telling us that it just finished it's deinit.
|
||||
|
||||
// Shouldn't be called from app. Use process_manager_put_kill_process_event() instead.
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelMain);
|
||||
|
||||
// If no worker running, nothing to do
|
||||
if (!s_worker_task_context.app_md) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the process is safe to kill. If this method returns false, it will have set a timer
|
||||
// to post another KILL event in a few seconds, thus giving the process a chance to clean up.
|
||||
if (!process_manager_make_process_safe_to_kill(PebbleTask_Worker, gracefully)) {
|
||||
// Maybe next time...
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Worker not ready to exit");
|
||||
return;
|
||||
}
|
||||
|
||||
// Save which worker we are exiting
|
||||
AppInstallId closing_worker_install_id = s_worker_task_context.install_id;
|
||||
|
||||
// Perform generic process cleanup
|
||||
process_manager_process_cleanup(PebbleTask_Worker);
|
||||
|
||||
// Notify the app install manager that we finally exited
|
||||
app_install_notify_worker_closed();
|
||||
|
||||
// If the worker was closed gracefully, launch any next/default worker and return
|
||||
if (gracefully) {
|
||||
// Reset the data tracking the last worker that crashed since the closing worker did not crash
|
||||
prv_reset_last_worker_crashed_data();
|
||||
worker_manager_launch_next_worker(closing_worker_install_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// We arrive here if the worker crashed...
|
||||
|
||||
// If the worker's app is in the foreground, close it
|
||||
if (closing_worker_install_id == app_manager_get_current_app_id()) {
|
||||
app_manager_force_quit_to_launcher();
|
||||
} else {
|
||||
const time_t current_time = rtc_get_time();
|
||||
const time_t WORKER_CRASH_RESET_TIMEOUT_SECONDS = 60;
|
||||
if ((closing_worker_install_id == s_last_worker_crashed_install_id) &&
|
||||
((current_time - s_last_worker_crash_timestamp) <= WORKER_CRASH_RESET_TIMEOUT_SECONDS)) {
|
||||
// Reset the data tracking the last worker that crashed since we are going to show crash UI
|
||||
prv_reset_last_worker_crashed_data();
|
||||
// Show the crash UI, which will ask the user if they want to launch the worker's app
|
||||
crashed_ui_show_worker_crash(closing_worker_install_id);
|
||||
} else {
|
||||
// Record that this worker crashed and what time it crashed
|
||||
s_last_worker_crashed_install_id = closing_worker_install_id;
|
||||
s_last_worker_crash_timestamp = current_time;
|
||||
// Silently restart the worker if we are allowing relaunches of crashed workers
|
||||
if (!s_worker_crash_relaunches_disabled) {
|
||||
worker_manager_put_launch_worker_event(closing_worker_install_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
const PebbleProcessMd* worker_manager_get_current_worker_md(void) {
|
||||
return s_worker_task_context.app_md;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
AppInstallId worker_manager_get_current_worker_id(void) {
|
||||
return s_worker_task_context.install_id;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
ProcessContext* worker_manager_get_task_context(void) {
|
||||
return &s_worker_task_context;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_put_launch_worker_event(AppInstallId id) {
|
||||
PBL_ASSERTN(id != INSTALL_ID_INVALID);
|
||||
|
||||
PebbleEvent e = {
|
||||
.type = PEBBLE_WORKER_LAUNCH_EVENT,
|
||||
.launch_app = {
|
||||
.id = id,
|
||||
},
|
||||
};
|
||||
|
||||
event_put(&e);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
AppInstallId worker_manager_get_default_install_id(void) {
|
||||
return worker_preferences_get_default_worker();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_set_default_install_id(AppInstallId id) {
|
||||
worker_preferences_set_default_worker(id);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_enable(void) {
|
||||
if (!s_workers_enabled) {
|
||||
s_workers_enabled = true;
|
||||
AppInstallId id = worker_manager_get_default_install_id();
|
||||
if (id != INSTALL_ID_INVALID) {
|
||||
worker_manager_put_launch_worker_event(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_disable(void) {
|
||||
if (s_workers_enabled) {
|
||||
s_workers_enabled = false;
|
||||
process_manager_put_kill_process_event(PebbleTask_Worker, true /*graceful*/);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void command_worker_kill(void) {
|
||||
process_manager_put_kill_process_event(PebbleTask_Worker, true /*graceful*/);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
DEFINE_SYSCALL(AppInstallId, sys_worker_manager_get_current_worker_id, void) {
|
||||
return worker_manager_get_current_worker_id();
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue