mirror of
https://github.com/google/pebble.git
synced 2025-05-12 04:13:17 -04:00
420 lines
16 KiB
C
420 lines
16 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 "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();
|
|
}
|
|
|