Skip to content
Snippets Groups Projects
Select Git revision
  • 51dfcb4bb7613ed164952712d9a5235a7b833cde
  • wip-bootstrap default
  • dualcore
  • ch3/leds
  • ch3/time
  • master
6 results

objbool.c

Blame
  • lifecycle.c 8.90 KiB
    #include "epicardium.h"
    #include "os/core.h"
    #include "modules/modules.h"
    #include "os/config.h"
    #include "os/mutex.h"
    #include "user_core/user_core.h"
    #include "api/dispatcher.h"
    #include "l0der/l0der.h"
    
    #include "card10.h"
    
    #include "FreeRTOS.h"
    #include "task.h"
    
    #include <assert.h>
    #include <string.h>
    #include <stdbool.h>
    #include <stdbool.h>
    
    #define PYCARDIUM_IVT (void *)0x100a0000
    #define BLOCK_WAIT pdMS_TO_TICKS(1000)
    /*
     * Loading an empty filename into Pycardium will drop straight into the
     * interpreter.  This define is used to make it more clear when we intend
     * to go into the interpreter.
     */
    #define PYINTERPRETER ""
    
    static TaskHandle_t lifecycle_task = NULL;
    static struct mutex core1_mutex    = { 0 };
    
    enum payload_type {
    	PL_INVALID       = 0,
    	PL_PYTHON_SCRIPT = 1,
    	PL_PYTHON_DIR    = 2,
    	PL_PYTHON_INTERP = 3,
    	PL_L0DABLE       = 4,
    };
    
    struct load_info {
    	bool do_reset;
    	enum payload_type type;
    	char name[256];
    };
    static volatile struct load_info async_load = {
    	.do_reset = false,
    	.name     = { 0 },
    	.type     = PL_INVALID,
    };
    
    /* Whether to write the menu script before attempting to load. */
    static volatile bool write_menu = false;
    static bool execute_elfs        = false;
    
    /* Helpers {{{ */
    
    /*
     * Check if the payload is a valid file (or module) and if so, return its type.
     */
    static int load_stat(char *name)
    {
    	size_t name_len = strlen(name);
    
    	if (name_len == 0) {
    		return PL_PYTHON_INTERP;
    	}
    
    	struct epic_stat stat;
    	if (epic_file_stat(name, &stat) < 0) {
    		return -ENOENT;
    	}
    
    	if (stat.type == EPICSTAT_DIR) {
    		/* This might be a python module. */
    		return PL_PYTHON_DIR;
    	}
    
    	if (strcmp(name + name_len - 3, ".py") == 0) {
    		/* A python script */
    		return PL_PYTHON_SCRIPT;
    	} else if (strcmp(name + name_len - 4, ".elf") == 0) {
    		return PL_L0DABLE;
    	}
    
    	return -ENOEXEC;
    }
    
    /*
     * Actually load a payload into core 1.  Optionally reset the core first.
     */
    static int do_load(struct load_info *info)
    {
    	struct l0dable_info l0dable;
    	int res;
    
    	/* Callers of do_load() must first lock the core1_mutex. */
    	mutex_assert_locked(&core1_mutex);
    
    	if (*info->name == '\0') {
    		LOG_INFO("lifecycle", "Loading Python interpreter ...");
    	} else {
    		LOG_INFO("lifecycle", "Loading \"%s\" ...", info->name);
    	}
    
    	if (info->type == PL_L0DABLE && !execute_elfs) {
    		LOG_WARN(
    			"lifecycle", "Execution of .elf l0dables is disabled."
    		);
    		return -EPERM;
    	}
    
    	mutex_lock(&api_mutex);
    
    	if (info->do_reset) {
    		LOG_DEBUG("lifecycle", "Triggering core 1 reset.");
    
    		core1_trigger_reset();
    	}
    
    	/*
    	 * Wait for the core to become ready to accept a new payload.
    	 *
    	 * If it is not yet ready, hand back control of the API mutex to the
    	 * dispatcher so it can finish dispatching a current API call.  This is
    	 * necessary for payloads which have interrupts disabled during an API
    	 * call.
    	 */
    	while (!core1_is_ready()) {
    		mutex_unlock(&api_mutex);
    		/* Sleep so the dispatcher task can take the lock. */
    		vTaskDelay(8);
    		mutex_lock(&api_mutex);
    	}
    
    	/*
    	 * Reinitialize Hardware & Drivers
    	 */
    	res = hardware_reset();
    	if (res < 0) {
    		goto out_free_api;
    	}
    
    	switch (info->type) {
    	case PL_PYTHON_SCRIPT:
    	case PL_PYTHON_DIR:
    	case PL_PYTHON_INTERP:
    		core1_load(PYCARDIUM_IVT, info->name);
    		break;
    	case PL_L0DABLE:
    		assert(execute_elfs);
    
    		res = l0der_load_path(info->name, &l0dable);
    		if (res != 0) {
    			LOG_ERR("lifecycle", "l0der failed: %d\n", res);
    			res = -ENOEXEC;
    			goto out_free_api;
    		}
    
    		core1_load(l0dable.isr_vector, "");
    		break;
    	default:
    		LOG_ERR("lifecyle",
    			"Attempted to load invalid payload (%s)",
    			info->name);
    		res = -EINVAL;
    		goto out_free_api;
    	}
    
    	res = 0;
    out_free_api:
    	mutex_unlock(&api_mutex);
    	return res;
    }
    
    /*
     * Do a synchroneous load.
     */
    static int load_sync(char *name, bool reset)
    {
    	/* Callers of load_sync() must first lock the core1_mutex. */
    	mutex_assert_locked(&core1_mutex);
    
    	int ret = load_stat(name);
    	if (ret < 0) {
    		return ret;
    	}
    
    	struct load_info info = {
    		.name     = { 0 },
    		.type     = ret,
    		.do_reset = reset,
    	};
    
    	strncpy(info.name, name, sizeof(info.name));
    
    	return do_load(&info);
    }
    
    /*
     * Do an asynchroneous load.  This will return immediately if the payload seems
     * valid and call the lifecycle task to actually perform the load later.
     */
    static int load_async(char *name, bool reset)
    {
    	/* Callers of load_async() must first lock the core1_mutex. */
    	mutex_assert_locked(&core1_mutex);
    
    	int ret = load_stat(name);
    	if (ret < 0) {
    		return ret;
    	}
    
    	async_load.type     = ret;
    	async_load.do_reset = reset;
    
    	strncpy((char *)async_load.name, name, sizeof(async_load.name));
    
    	if (lifecycle_task != NULL) {
    		xTaskNotifyGive(lifecycle_task);
    	}
    
    	return 0;
    }
    
    /*
     * Epicardium contains an embedded default menu script which it writes to
     * external flash if none is found there.  This way, you won't make your card10
     * unusable by accidentally removing the menu script.
     *
     * You can find the sources for the menu-script in `preload/menu.py`.
     */
    
    /*
     * Embed the menu.py script in the Epicardium binary.
     */
    __asm(".section \".rodata\"\n"
          "_menu_script_start:\n"
          ".incbin \"../preload/menu.py\"\n"
          "_menu_script_end:\n"
          ".previous\n");
    
    extern const uint8_t _menu_script_start;
    extern const uint8_t _menu_script_end;
    
    static int write_default_menu(void)
    {
    	const size_t length =
    		(uintptr_t)&_menu_script_end - (uintptr_t)&_menu_script_start;
    	int ret;
    
    	LOG_INFO("lifecycle", "Writing default menu ...");
    
    	int fd = epic_file_open("menu.py", "w");
    	if (fd < 0) {
    		return fd;
    	}
    
    	ret = epic_file_write(fd, &_menu_script_start, length);
    	if (ret < 0) {
    		return ret;
    	}
    
    	ret = epic_file_close(fd);
    	if (ret < 0) {
    		return ret;
    	}
    
    	return 0;
    }
    
    /*
     * Go back to the menu.
     */
    static void load_menu(bool reset)
    {
    	LOG_DEBUG("lifecycle", "Into the menu");
    
    	mutex_lock(&core1_mutex);
    
    	int ret = load_async("menu.py", reset);
    	if (ret < 0) {
    		LOG_WARN("lifecycle", "No menu script found.");
    
    		/* The lifecycle task will perform the write */
    		write_menu = true;
    
    		async_load.type     = PL_PYTHON_SCRIPT;
    		async_load.do_reset = reset;
    		strncpy((char *)async_load.name,
    			"menu.py",
    			sizeof(async_load.name));
    
    		if (lifecycle_task != NULL) {
    			xTaskNotifyGive(lifecycle_task);
    		}
    	}
    
    	mutex_unlock(&core1_mutex);
    }
    /* Helpers }}} */
    
    /* API {{{ */
    /*
     * Restart the firmware
     */
    void epic_system_reset(void)
    {
    	card10_reset();
    }
    
    /*
     * This is NOT the epic_exec() called from Pycardium, but an implementation of
     * the same call for use in Epicardium.  This function is synchroneous and will
     * wait until the call returns.
     */
    int epic_exec(char *name)
    {
    	mutex_lock(&core1_mutex);
    	int ret = load_sync(name, true);
    	mutex_unlock(&core1_mutex);
    	return ret;
    }
    
    /*
     * This is the underlying call for epic_exec() from Pycardium.  It is
     * asynchroneous and will return early to allow Pycardium (or a l0dable) to jump
     * to the reset handler.
     *
     * The lifecycle task will deal with actually loading the new payload.
     */
    int __epic_exec(char *name)
    {
    	mutex_lock(&core1_mutex);
    	int ret = load_async(name, false);
    	mutex_unlock(&core1_mutex);
    	return ret;
    }
    
    /*
     * This is the underlying call for epic_exit() from Pycardium.  It is
     * asynchroneous and will return early to allow Pycardium (or a l0dable) to jump
     * to the reset handler.
     *
     * The lifecycle task will deal with actually loading the new payload.
     */
    void __epic_exit(int ret)
    {
    	if (ret == 0) {
    		LOG_INFO("lifecycle", "Payload returned successfully");
    	} else {
    		LOG_WARN("lifecycle", "Payload returned with %d.", ret);
    	}
    
    	load_menu(false);
    }
    
    /*
     * This function can be used in Epicardium to jump back to the menu.
     *
     * It is asynchroneous and will return immediately.  The lifecycle task will
     * take care of actually jumping back.
     */
    void return_to_menu(void)
    {
    	load_menu(true);
    }
    /* API }}} */
    
    void vLifecycleTask(void *pvParameters)
    {
    	lifecycle_task = xTaskGetCurrentTaskHandle();
    	mutex_create(&core1_mutex);
    	mutex_lock(&core1_mutex);
    
    	LOG_DEBUG("lifecycle", "Booting core 1 ...");
    	core1_boot();
    	vTaskDelay(pdMS_TO_TICKS(10));
    
    	mutex_unlock(&core1_mutex);
    
    	/*
    	 * If `main.py` exists, start it.  Otherwise, start `menu.py`.
    	 *
    	 * We are not using epic_exec() & return_to_menu() here because those
    	 * trigger a reset which is undesirable during startup.
    	 */
    	mutex_lock(&core1_mutex);
    	int ret = load_sync("main.py", false);
    	mutex_unlock(&core1_mutex);
    	if (ret < 0) {
    		load_menu(false);
    	}
    
    	hardware_init();
    
    	execute_elfs = config_get_boolean_with_default("execute_elf", false);
    
    	/* When triggered, reset core 1 to menu */
    	while (1) {
    		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    
    		mutex_lock(&core1_mutex);
    
    		if (write_menu) {
    			write_menu = false;
    			int ret    = write_default_menu();
    			if (ret < 0) {
    				LOG_ERR("lifecycle",
    					"Failed to write default menu: %d",
    					ret);
    				load_async(PYINTERPRETER, "");
    				ulTaskNotifyTake(pdTRUE, 0);
    			}
    		}
    
    		ret = do_load((struct load_info *)&async_load);
    
    		mutex_unlock(&core1_mutex);
    
    		if (ret < 0) {
    			LOG_ERR("lifecycle", "Error loading payload: %d", ret);
    			return_to_menu();
    		}
    	}
    }