/* * task.c – Cooperative multitasking: PCB pool, scheduler, yield/exit. * * Task 0 is the always-present kernel core thread (uses the boot stack). * Additional tasks are created with task_create(), which allocates a * stack from the PMM and sets up a fake context-switch frame so that * context_switch() can "return" into a trampoline that calls the real * entry function. * * Scheduling is purely cooperative: tasks must call task_yield() to * let others run. The scheduler uses round-robin selection among * READY tasks. */ #include "task.h" #include "memory.h" /* Null-safe print helper used throughout the kernel. */ #define SAFE_PRINT(Boot, ...) \ do { \ if ((Boot) != NULL && (Boot)->print != NULL) { \ (Boot)->print(__VA_ARGS__); \ } \ } while (0) /* ================================================================ * Module state * ================================================================ */ static Task tasks[TASK_MAX]; /* PCB pool (static array) */ static Task *current_task = NULL; static UINT32 next_pid = 0; static BootInfo *task_boot = NULL; static BOOLEAN task_ready = FALSE; /* Forward declaration */ static void task_trampoline(void); /* ---------------------------------------------------------------- * Helpers * ---------------------------------------------------------------- */ /* Copy a wide string with a maximum length (including NUL). */ static void wstrcpy16(CHAR16 *dst, const CHAR16 *src, UINTN max) { UINTN i = 0; while (i < max - 1 && src != NULL && src[i] != L'\0') { dst[i] = src[i]; i++; } dst[i] = L'\0'; } /* ---------------------------------------------------------------- * Initialisation – make the current (kernel) thread task 0 * ---------------------------------------------------------------- */ /* * Initialise the scheduler: clear all PCB slots and register the * currently running kernel core thread as task 0. */ void task_init(BootInfo *Boot) { UINTN i; task_boot = Boot; /* Clear all PCB slots */ for (i = 0; i < TASK_MAX; i++) { tasks[i].state = TASK_STATE_FREE; tasks[i].pid = 0; tasks[i].privilege = TASK_PRIV_USER; tasks[i].saved_rsp = 0; tasks[i].stack_base = 0; tasks[i].stack_pages = 0; tasks[i].entry = NULL; tasks[i].arg = NULL; tasks[i].switches = 0; tasks[i].name[0] = L'\0'; } /* * Task 0 = the currently running kernel core thread. * It already has a stack (the kernel's boot stack), so we don't * allocate one. Its saved_rsp will be filled in during the * first context_switch call in task_yield(). */ tasks[0].pid = next_pid++; tasks[0].state = TASK_STATE_RUNNING; tasks[0].privilege = TASK_PRIV_KERNEL; tasks[0].switches = 1; wstrcpy16(tasks[0].name, L"core", TASK_NAME_LEN); current_task = &tasks[0]; task_ready = TRUE; SAFE_PRINT(Boot, L" Tasks: scheduler ready (max %d tasks)\n\r", (UINTN)TASK_MAX); } /* ---------------------------------------------------------------- * Trampoline – first code a new task executes after context_switch * returns into it. It calls the real entry function and then * performs a clean task_exit(). * ---------------------------------------------------------------- */ static void task_trampoline(void) { Task *t = task_current(); if (t != NULL && t->entry != NULL) { t->entry(t->arg); } task_exit(); /* Should never reach here, but just in case: */ for (;;) { __asm__ __volatile__("hlt"); } } /* ---------------------------------------------------------------- * Create a new task * ---------------------------------------------------------------- */ Task *task_create_with_priv(const CHAR16 *name, TaskEntryFn entry, void *arg, TaskPrivilege privilege) { Task *t = NULL; UINTN i; UINT64 stack_phys; UINT64 *sp; if (!task_ready || entry == NULL) { return NULL; } /* Subsystem-level privilege enforcement: prevent privilege escalation. */ { Task *caller = task_current(); if (caller != NULL && privilege > task_get_privilege(caller)) { return NULL; } } /* Find a free PCB slot */ for (i = 0; i < TASK_MAX; i++) { if (tasks[i].state == TASK_STATE_FREE) { t = &tasks[i]; break; } } if (t == NULL) { return NULL; /* out of slots */ } /* Allocate stack pages from the physical memory manager */ stack_phys = pmm_alloc_pages(TASK_STACK_PAGES); if (stack_phys == 0) { return NULL; /* out of memory */ } /* Fill in the PCB */ t->pid = next_pid++; t->state = TASK_STATE_READY; t->privilege = privilege; t->entry = entry; t->arg = arg; t->switches = 0; t->stack_base = stack_phys; t->stack_pages = TASK_STACK_PAGES; wstrcpy16(t->name, name != NULL ? name : L"unnamed", TASK_NAME_LEN); /* * Set up the initial stack frame so that context_switch() can * "return" into task_trampoline(). * * context_switch saves/restores (low → high on stack): * flags, r15, r14, r13, r12, rbx, rbp (pushes) * then `ret` pops the return address (→ trampoline) * * Above the return address we place a safety-net address * (task_exit) so that if the trampoline or entry function does * a bare `ret`, it lands in task_exit(). */ sp = (UINT64 *)(stack_phys + TASK_STACK_SIZE); /* Align stack top to 16 bytes */ sp = (UINT64 *)((UINT64)sp & ~0xFULL); /* Safety-net return address for the trampoline */ *(--sp) = (UINT64)(UINTN)task_exit; /* Return address for context_switch's `ret` → trampoline */ *(--sp) = (UINT64)(UINTN)task_trampoline; /* Callee-saved registers – all zero for fresh task */ *(--sp) = 0; /* rbp */ *(--sp) = 0; /* rbx */ *(--sp) = 0; /* r12 */ *(--sp) = 0; /* r13 */ *(--sp) = 0; /* r14 */ *(--sp) = 0; /* r15 */ /* RFLAGS – interrupts enabled (IF = bit 9) */ *(--sp) = 0x202; /* flags */ t->saved_rsp = (UINT64)(UINTN)sp; return t; } Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) { /* Inherit privilege from the calling task (kernel if no task context). */ Task *caller = task_current(); TaskPrivilege priv = (caller != NULL) ? task_get_privilege(caller) : TASK_PRIV_KERNEL; return task_create_with_priv(name, entry, arg, priv); } /* ---------------------------------------------------------------- * Schedule – pick the next READY task (round-robin) * ---------------------------------------------------------------- */ static Task *schedule_next(void) { UINTN start, idx, i; if (current_task == NULL) { return &tasks[0]; } /* Find current task's index in the array */ start = (UINTN)(current_task - tasks); /* Round-robin: scan from (current+1) wrapping around */ for (i = 1; i <= TASK_MAX; i++) { idx = (start + i) % TASK_MAX; if (tasks[idx].state == TASK_STATE_READY) { return &tasks[idx]; } } /* No other ready task – stay with current if still runnable */ if (current_task->state == TASK_STATE_RUNNING || current_task->state == TASK_STATE_READY) { return current_task; } /* Fallback to task 0 (kernel / shell) */ return &tasks[0]; } /* ---------------------------------------------------------------- * Yield – voluntarily give up the CPU to the next ready task * ---------------------------------------------------------------- */ void task_yield(void) { Task *prev, *next; if (!task_ready) { return; } prev = current_task; next = schedule_next(); if (next == prev) { return; /* nothing else to switch to */ } /* Mark the previous task as READY (still runnable) */ if (prev->state == TASK_STATE_RUNNING) { prev->state = TASK_STATE_READY; } next->state = TASK_STATE_RUNNING; next->switches++; current_task = next; /* * context_switch saves callee-saved regs + flags on prev's stack, * stores prev's RSP into prev->saved_rsp, loads next->saved_rsp * into RSP, restores regs + flags, and `ret`s into next's code. */ context_switch(&prev->saved_rsp, next->saved_rsp); } /* ---------------------------------------------------------------- * Exit – terminate the current task and switch away * ---------------------------------------------------------------- */ void task_exit(void) { Task *prev, *next; if (!task_ready) { return; } prev = current_task; prev->state = TASK_STATE_TERMINATED; /* Free the stack memory back to the PMM */ if (prev->stack_base != 0 && prev->stack_pages != 0) { pmm_free_pages(prev->stack_base, prev->stack_pages); prev->stack_base = 0; prev->stack_pages = 0; } /* Mark the PCB slot as free for reuse */ prev->state = TASK_STATE_FREE; next = schedule_next(); if (next == prev) { /* Shouldn't happen if task 0 (kernel) is always alive */ next = &tasks[0]; } next->state = TASK_STATE_RUNNING; next->switches++; current_task = next; /* One-way switch: we never return to the exited task */ context_switch(&prev->saved_rsp, next->saved_rsp); /* Should never reach here */ for (;;) { __asm__ __volatile__("hlt"); } } /* ---------------------------------------------------------------- * Accessors * ---------------------------------------------------------------- */ Task *task_current(void) { return current_task; } TaskPrivilege task_get_privilege(Task *t) { if (t == NULL) { return TASK_PRIV_KERNEL; } return t->privilege; } void task_set_privilege(Task *t, TaskPrivilege privilege) { if (t == NULL) { return; } t->privilege = privilege; } UINTN task_count(void) { UINTN i, count = 0; for (i = 0; i < TASK_MAX; i++) { if (tasks[i].state != TASK_STATE_FREE) { count++; } } return count; } /* ---------------------------------------------------------------- * Wait for another task to finish * ---------------------------------------------------------------- */ void task_wait(Task *t) { if (!task_ready || t == NULL) { return; } /* * Busy-wait cooperatively until the target task's PCB slot has * been recycled back to FREE by task_exit(). */ while (t->state != TASK_STATE_FREE) { task_yield(); } } /* ---------------------------------------------------------------- * Print task list (implements the `ps` command) * ---------------------------------------------------------------- */ static const CHAR16 *state_str(TaskState s) { switch (s) { case TASK_STATE_FREE: return L"FREE"; case TASK_STATE_READY: return L"READY"; case TASK_STATE_RUNNING: return L"RUNNING"; case TASK_STATE_TERMINATED: return L"ENDED"; default: return L"???"; } } void task_print_list(BootInfo *Boot) { UINTN i; Task *caller; /* Subsystem-level privilege enforcement: task list requires DRIVER. */ caller = task_current(); if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_DRIVER) { SAFE_PRINT(Boot, L"Permission denied: task list requires driver privilege.\n\r"); return; } SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L" PID STATE PRIV SWITCHES NAME\n\r"); SAFE_PRINT(Boot, L" --- ---------- ---- -------- ----\n\r"); for (i = 0; i < TASK_MAX; i++) { if (tasks[i].state == TASK_STATE_FREE) { continue; } SAFE_PRINT(Boot, L" %3d %-10s %4d %8d %s\n\r", tasks[i].pid, state_str(tasks[i].state), (INT32)tasks[i].privilege, tasks[i].switches, tasks[i].name); } SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L" Active tasks: %d / %d\n\r", task_count(), (UINTN)TASK_MAX); SAFE_PRINT(Boot, L"\n\r"); }