/** * @file task.c * Provides multitasking functionality * * Copyright (C) 2018 Clyne Sullivan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "clock.h" #include "elf.h" #include "heap.h" #include "task.h" #include #define YIELD { SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; } static task_t *task_current; static task_t *task_queue; static uint8_t task_disable = 0; static pid_t task_next_pid = 0; void task_svc(uint32_t n, uint32_t *ret, uint32_t *args) { switch (n) { case 0: task_exit(args[0]); break; case 1: *((int *)ret) = task_fork(); break; case 2: *((int *)ret) = task_getpid(); break; case 3: *((int *)ret) = task_waitpid(args[0], (int *)args[1], args[2]); break; case 4: *((void **)ret) = task_sbrk(args[0]); break; case 5: *((uint32_t *)ret) = elf_execve((const char *)args[0], (char * const *)args[1], (char * const *)args[2]); break; default: break; } } void *task_sbrk(uint32_t bytes) { return malloc(bytes); /*if (task_current->heap == 0) task_current->heap = malloc(1024 * 16); if (bytes == 0) { alloc_t *alloc = (alloc_t *)((uint8_t *)task_current->heap - sizeof(alloc_t)); return (uint8_t *)(((uint32_t)task_current->heap + alloc->size) & ~0xFFF); } return (void *)-1;*/ } void task_hold(uint8_t hold) { if (hold != 0) task_disable++; else if (task_disable > 0) task_disable--; } void task_sleep(uint32_t ms) { task_current->status.state = TASK_SLEEPING; task_current->status.value = clock_millis() + ms; YIELD; } pid_t task_getpid(void) { return task_current != 0 ? task_current->pid : 0; } pid_t task_waitpid(pid_t pid, int *wstatus, int options) { (void)options; *wstatus = 0; // Find the process task_t *task = task_queue; while (task != 0 && task->pid != pid) task = task->next; if (task == 0) return (pid_t)-1; // Check process's state if (task->status.state == TASK_EXITED) { *wstatus = task->status.value | (1 << 8); task->status.state = TASK_ZOMBIE; } return pid; } /*vfs_node *task_getcwd(void) { return task_current->cwd; }*/ void task_exit(int code) { task_current->status.state = TASK_EXITED; task_current->status.value = code & 0xFF; YIELD; } void task_purge(void) { // Remove task from the chain if (task_queue == task_current) { task_queue = task_queue->next; } else { task_t *prev = task_queue; while (prev->next != 0 && prev->next != task_current) prev = prev->next; if (prev->next != 0) prev->next = task_current->next; } // Free this thread's stack, and task data. // The scheduler still needs to use the data we're about to free; however, // our single-core implementation keeps us safe from anyone else claiming // this memory in the meantime. free(task_current->stack); free(task_current->heap); free(task_current); YIELD; while (1); } /** * Exits the task (userspace call). */ __attribute__ ((naked)) void task_doexit(void) { asm("eor r0, r0; eor r1, r1; svc 0"); while (1); } /** * 'Prepares' task for running. * Calls the task's main code after making task_doexit() (_exit) main's return * point. */ __attribute__ ((naked)) void task_crt0(void) { asm("\ mov r4, lr; \ ldr lr, =task_doexit; \ bx r4; \ "); } task_t *task_create(void (*code)(void), uint16_t stackSize) { task_t *t = (task_t *)malloc(sizeof(task_t)); t->next = 0; t->pid = task_next_pid++; t->pgid = t->pid; t->status.state = TASK_RUNNING; t->status.value = 0; t->heap = 0; t->stack = (uint32_t *)malloc(stackSize); void *sp = (uint8_t *)t->stack + stackSize - 68; // exception stack + regs t->sp = sp; /* sp[0-7] - r4-r11 sp[8] - r14 (lr) sp[9-12] - r0-r3 sp[13] - r12 sp[14] - LR sp[15] - PC sp[16] - xPSR */ for (uint8_t i = 0; i < 14; i++) t->sp[i] = 0; t->sp[8] = 0xFFFFFFFD; t->sp[14] = (uint32_t)code; t->sp[15] = (uint32_t)task_crt0; t->sp[16] = 0x01000000; return t; } void task_init(void (*init)(void), uint16_t stackSize) { // Create a dummy current task that we'll "exit" from task_current = (task_t *)malloc(sizeof(task_t)); task_current->next = 0; task_current->stack = 0; // free() is called on this task_current->sp = 0; task_current->status.state = TASK_SLEEPING; task_current->status.value = 1000; // Place the init task in the queue to take the dummy task's place task_queue = task_create(init, stackSize); task_disable = 0; // Enter userspace process mode // bit 0 - priv, bit 1 - psp/msp asm("\ isb; \ cpsie i; \ mov r0, sp; \ msr psp, r0; \ mrs r0, control; \ orr r0, r0, #3; \ msr control, r0; \ "); // Exit the current (fake) task task_doexit(); } void task_start(void (*task)(void), uint16_t stackSize) { task_hold(1); task_t *t = task_create(task, stackSize); t->next = task_queue; task_queue = t; task_hold(0); } int task_fork_ret(void) { return 0; } // Return 0 for child, non-zero for parent int task_fork(void) { asm("cpsid i"); uint32_t sp; asm("\ mrs r0, psp; \ stmdb r0!, {r4-r11, r14}; \ mov %0, r0; \ " : "=r" (sp)); //// 1. Prepare child task // Get parent task's stack info alloc_t *stackInfo = (alloc_t *)(((uint8_t *)task_current->stack) - sizeof(alloc_t)); // Create child task data task_t *childTask = (task_t *)malloc(sizeof(task_t)); childTask->stack = (uint32_t *)malloc(stackInfo->size - sizeof(alloc_t)); childTask->pid = task_next_pid++; childTask->pgid = task_current->pid; childTask->status.state = TASK_RUNNING; childTask->status.value = 0; // Copy parent's stack for (uint32_t i = 0; i < (stackInfo->size - sizeof(alloc_t)); i++) childTask->stack[i] = task_current->stack[i]; childTask->sp = (uint32_t *)((uint32_t)childTask->stack + (sp - (uint32_t)task_current->stack)); childTask->sp[8] = 0xFFFFFFFD; childTask->sp[15] = (uint32_t)task_fork_ret; //childTask->sp[16] = 0x01000000; //// 2. Insert child into task chain childTask->next = task_queue; task_queue = childTask; //// 3. Re-enable scheduler, make change happen asm("cpsie i"); YIELD; return childTask->pid; } __attribute__ ((naked)) void PendSV_Handler(void) { if (task_disable != 0) asm("bx lr"); // TODO get back to c, implement task sleeping TODO did we do this? // Finish saving current state asm("\ mrs r0, psp; \ isb; \ stmdb r0!, {r4-r11, r14}; \ mov %0, r0; \ " : "=r" (task_current->sp)); // Load next task uint32_t ticks = clock_millis(); do { task_current = task_current->next; if (task_current == 0) task_current = task_queue; if (task_current->status.state == TASK_SLEEPING && task_current->status.value < ticks) task_current->status.state = TASK_RUNNING; } while (task_current->status.state != TASK_RUNNING); task_current->status.state = TASK_RUNNING; task_current->status.value = 0; // Load stack pointer, return asm("\ mov r0, %0; \ ldmia r0!, {r4-r11, r14}; \ msr psp, r0; \ bx lr; \ " :: "r" (task_current->sp)); }