diff --git a/arch/lkl/include/asm/cpu.h b/arch/lkl/include/asm/cpu.h index dd2cd0fc2a28db..d86dc17f683e8d 100644 --- a/arch/lkl/include/asm/cpu.h +++ b/arch/lkl/include/asm/cpu.h @@ -3,6 +3,21 @@ int lkl_cpu_get(void); void lkl_cpu_put(void); + +#ifdef DEBUG +/* Bug if you do not have the cpu lock. */ +void lkl_assert_cpu_owner(void); +/* Bug if you do have the cpu lock. */ +void lkl_assert_cpu_not_owner(void); +/* Prints values of the various locking structure to aid debugging. */ +void lkl_print_cpu_lock_state(const char *func_name); +#else +static inline void lkl_assert_cpu_owner(void) {} +static inline void lkl_assert_cpu_not_owner(void) {} +static inline void lkl_print_cpu_lock_state(const char *func_name) {} +#endif + + int lkl_cpu_try_run_irq(int irq); int lkl_cpu_init(void); void lkl_cpu_shutdown(void); diff --git a/arch/lkl/include/asm/signal.h b/arch/lkl/include/asm/signal.h index c71bffd5c6f988..36b9517993131f 100644 --- a/arch/lkl/include/asm/signal.h +++ b/arch/lkl/include/asm/signal.h @@ -1,5 +1,27 @@ +#ifndef _ASM_LKL_SIGNAL_H +#define _ASM_LKL_SIGNAL_H +#include + struct ucontext; -void do_signal(struct pt_regs *regs); + +struct thread_info; +struct ksignal_list_node; +struct ksignal; + void lkl_process_trap(int signr, struct ucontext *uctx); + +/* + * Capture pending signals and move them to a task-specific list. + * Must be called only with the cpu lock held. + */ -#include +void move_signals_to_task(void); + +/* + * Send any signals targeting this task. + * Must be called only with the cpu lock held, it will be unlocked for the call to user code. + */ + +void send_current_signals(struct ucontext *uctx); + +#endif \ No newline at end of file diff --git a/arch/lkl/include/asm/thread_info.h b/arch/lkl/include/asm/thread_info.h index 1022ebd55815e7..522b4dcd46e6a6 100644 --- a/arch/lkl/include/asm/thread_info.h +++ b/arch/lkl/include/asm/thread_info.h @@ -8,6 +8,20 @@ #include #include +#include +#include + +/* + * Used to track a thread's pending signals. + * See signal_list_head/tail below. + */ + +struct ksignal_list_node +{ + struct ksignal sig; + struct ksignal_list_node *next; /* consider using the kernel lists, but they are doubly linked and clumsy in this simple case */ +}; + typedef struct { unsigned long seg; } mm_segment_t; @@ -29,6 +43,12 @@ struct thread_info { /* The task for any child that was created during syscall execution. Only * valid on return from a clone-family syscall. */ struct task_struct *cloned_child; + + /* lock for the list below, the init is in the thread_info creation fn */ + spinlock_t signal_list_lock; + /* a linked list of pending signals, pushed onto here as they are detected in move_signals_to_task */ + struct ksignal_list_node* signal_list_head; + struct ksignal_list_node* signal_list_tail; }; #define INIT_THREAD_INFO(tsk) \ diff --git a/arch/lkl/kernel/cpu.c b/arch/lkl/kernel/cpu.c index 0704067acfb8df..5ce2e75bb32f7e 100644 --- a/arch/lkl/kernel/cpu.c +++ b/arch/lkl/kernel/cpu.c @@ -86,8 +86,10 @@ static void __cpu_try_get_unlock(int lock_ret, int n) void lkl_cpu_change_owner(lkl_thread_t owner) { lkl_ops->mutex_lock(cpu.lock); - if (cpu.count > 1) + if (cpu.count > 1) { + lkl_print_cpu_lock_state(__func__); lkl_bug("bad count while changing owner\n"); + } cpu.owner = owner; lkl_ops->mutex_unlock(cpu.lock); } @@ -117,8 +119,10 @@ void lkl_cpu_put(void) lkl_ops->mutex_lock(cpu.lock); if (!cpu.count || !cpu.owner || - !lkl_ops->thread_equal(cpu.owner, lkl_ops->thread_self())) + !lkl_ops->thread_equal(cpu.owner, lkl_ops->thread_self())) { + lkl_print_cpu_lock_state(__func__); lkl_bug("%s: unbalanced put\n", __func__); + } while (cpu.irqs_pending && !irqs_disabled()) { cpu.irqs_pending = false; @@ -128,9 +132,11 @@ void lkl_cpu_put(void) } if (test_ti_thread_flag(current_thread_info(), TIF_HOST_THREAD) && - !single_task_running() && cpu.count == 1) { - if (in_interrupt()) + !single_task_running() && cpu.count == 1) { + if (in_interrupt()) { + lkl_print_cpu_lock_state(__func__); lkl_bug("%s: in interrupt\n", __func__); + } lkl_ops->mutex_unlock(cpu.lock); thread_sched_jb(); return; @@ -150,6 +156,55 @@ void lkl_cpu_put(void) lkl_ops->mutex_unlock(cpu.lock); } +#ifdef DEBUG + +/* + * Debug tool. Essentially allows for assert(cpuLockTaken); + * + * Returns 1 meaning this thread owns the lock, 0 otherwise. + */ + +static int lkl_check_cpu_owner() +{ + int result; + lkl_ops->mutex_lock(cpu.lock); + lkl_thread_t self = lkl_ops->thread_self(); + lkl_thread_t owner = cpu.owner; + if (!cpu.count || !owner || + !lkl_ops->thread_equal(owner, self)) { + result = 0; + } else { + result = 1; + } + lkl_ops->mutex_unlock(cpu.lock); + return result. +} + +/* Expected the cpu to be locked by this task. */ +void lkl_assert_cpu_owner(void) +{ + BUG_ON(lkl_check_cpu_owner() != 1); +} + +/* Expected the cpu to be unlocked or locked by another task. */ +void lkl_assert_cpu_not_owner(void) +{ + BUG_ON(lkl_check_cpu_owner() != 0); +} + +/* Debugging, print state of flags etc for a particular caller. */ +void lkl_print_cpu_lock_state(const char *func_name) +{ + lkl_thread_t self = lkl_ops->thread_self(); + lkl_thread_t owner = cpu.owner; + unsigned int count = cpu.count; + unsigned int sleepers = cpu.sleepers; + unsigned int shutdown_gate = cpu.shutdown_gate; + + LKL_TRACE("%s: self %lx owner %lx count %u sleepers %u shutdown gate %u\n", func_name, self, owner, count, sleepers, shutdown_gate); +} +#endif + int lkl_cpu_try_run_irq(int irq) { int ret; diff --git a/arch/lkl/kernel/setup.c b/arch/lkl/kernel/setup.c index b06ee2ef6773ce..47c10a2e69174a 100644 --- a/arch/lkl/kernel/setup.c +++ b/arch/lkl/kernel/setup.c @@ -136,6 +136,11 @@ int lkl_is_running(void) void machine_halt(void) { + /* + * All lkl_cpu_shutdown does is set cpu.shutdown_gate to MAX_THREADS + * as a flag. It does return and so machine_halt will return too. + */ + lkl_cpu_shutdown(); } @@ -156,6 +161,12 @@ long lkl_sys_halt(void) LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART, }; err = lkl_syscall(__NR_reboot, params); + + /* + This code does get run even though machine_halt (above) + is called. + */ + if (err < 0) { LKL_TRACE("sys_reboot failed (err=%i)\n", err); return err; diff --git a/arch/lkl/kernel/signal.c b/arch/lkl/kernel/signal.c index 6c41ccd84359f9..5fa8d914772253 100644 --- a/arch/lkl/kernel/signal.c +++ b/arch/lkl/kernel/signal.c @@ -2,6 +2,9 @@ #include #include #include +#include +#include + static void initialize_uctx(struct ucontext *uctx, const struct pt_regs *regs) { @@ -28,47 +31,252 @@ static void initialize_uctx(struct ucontext *uctx, const struct pt_regs *regs) return; } -/* Function to invoke the signal handler of LKL application. This works for +/* + * Function to invoke the signal handler of LKL application. This works for * sig handler installed using sigaction or signal API. This will remove the * overhead of injecting the stack frame to pass the user context to user * space application (could lead to inclusion of ARCH specific code) + * + * MUST be called owning the lock. */ static void handle_signal(struct ksignal *ksig, struct ucontext *uctx) { + lkl_thread_t self; + + /* In DEBUG build check that the lock is currently held by this task */ + lkl_assert_cpu_owner(); + + /* + * Give up the cpu lock while we invoke the signal handler because we are returning + * to userspace. + */ + + lkl_cpu_put(); + + /* In case it was recursively locked */ + lkl_assert_cpu_not_owner(); + + /* Get the current thread before we invoke the signal handler */ + self = lkl_ops->thread_self(); + ksig->ka.sa.sa_handler(ksig->sig, (void*)&ksig->info, (void*)uctx); + + /* + * Check if the apparent current thread changes while processing the signal. + * Should not happen. + */ + + BUG_ON(!lkl_ops->thread_equal(self, lkl_ops->thread_self())); + + /* take back the lock */ + lkl_cpu_get(); } +/* + * Return only the signal of a given number, while snipping it from the list + * Used by the trap handler for sync signals such as SEGV, FPE. + * + * Returns 1 meaning a signal was found, 0 otherwise. + */ +int get_given_ksignal(struct thread_info *task, struct ksignal* sig, int which) +{ + struct ksignal_list_node *next; + struct ksignal_list_node *node; + struct ksignal_list_node *previous; + + spin_lock(&task->signal_list_lock); + node = task->signal_list_head; + previous = NULL; + + while (node != NULL) { + next = node->next; + if (node->sig.sig == which) { + /* first remove the node we found from the linked list */ + + if (previous != NULL) /* not at the front? */ + previous->next = next; + else + task->signal_list_head = next; + + if (next == NULL) /* was that the last node? */ + task->signal_list_tail = previous; /* yes, have the tail point at the previous or be null */ + + spin_unlock(&task->signal_list_lock); + memcpy(sig, &node->sig, sizeof(*sig)); /* copy the signal back to the caller */ + LKL_TRACE("Fetching task %p signal %d\n", current, sig->sig); + kfree(node); + return 1; + } + previous = node; + node = next; + } + + spin_unlock(&task->signal_list_lock); + return 0; +} + +int get_next_ksignal(struct thread_info *task, struct ksignal* sig) +{ + struct ksignal_list_node *next; + struct ksignal_list_node *node; + + spin_lock(&task->signal_list_lock); + node = task->signal_list_head; + + if (!node) { + spin_unlock(&task->signal_list_lock); + return 0; /* no pending signals */ + } + + next = node->next; + task->signal_list_head = next; /* drop the head */ + if (next == NULL) /* was that the last node? */ + task->signal_list_tail = NULL; /* if so there is now no tail */ + + + spin_unlock(&task->signal_list_lock); + + memcpy(sig, &node->sig, sizeof(*sig)); /* copy the signal back to the caller */ + LKL_TRACE("Fetching task %p signal %d\n", current, sig->sig); + kfree(node); + return 1; +} + + +/* + * This is the sync signal case and that signal will have just been forced into the + * regular task queue of signals so the pre-existing code will work. See lkl_do_trap + * + * Up for debate though is whether it would be better to either directly invoke + * the handler in lkl_do_trap or here to have versions of move_signals_to_task, send_current_signals + * that operate by scanning the list for signr, the given signal. + */ + +/* This is always called while owning the cpu */ + void lkl_process_trap(int signr, struct ucontext *uctx) { struct ksignal ksig; + /* In DEBUG build check that the lock is currently held by this task */ + lkl_assert_cpu_owner(); + LKL_TRACE("enter\n"); - while (get_signal(&ksig)) { - LKL_TRACE("ksig.sig=%d", ksig.sig); + /* + * Capture and queue pending signals and traps. + * Expect at least the one 'signr'. + */ + move_signals_to_task(); + + /* + * Walk that queue and find the first one of the type we expect. + * Note, no expectation that a trap will happen in order wrt to any + * async signal already queued. + */ + + while (get_given_ksignal(task_thread_info(current), &ksig, signr)) { /* Handle required signal */ - if(signr == ksig.sig) + if (signr == ksig.sig) { + LKL_TRACE("trap task %p %d\n", current, signr); handle_signal(&ksig, uctx); break; } } } -void do_signal(struct pt_regs *regs) +/* + * Get the end of the list and make it point at the new node, + * if there were no nodes before then bot head and tail point at this one node + */ + +void append_ksignal_node(struct thread_info *task, struct ksignal *ksig) +{ + struct ksignal_list_node *ptr; + + /* first make a place to hold our signal */ + struct ksignal_list_node *node = kmalloc(sizeof(struct ksignal_list_node), GFP_ATOMIC); + if (node == NULL) { + lkl_bug("Unable to allocate memory to hold signal state."); + } + memcpy(&node->sig, ksig, sizeof(*ksig)); + node->next = NULL; + + /* now put it in the linked list */ + spin_lock(&task->signal_list_lock); + ptr = task->signal_list_tail; + if (ptr == NULL) { /* list is empty so point both head and tail at the given node */ + task->signal_list_head = node; + task->signal_list_tail = node; + } else { /* otherwise just the tail changes */ + ptr->next = node; + task->signal_list_tail = node; + } + + spin_unlock(&task->signal_list_lock); +} + +/* + * Must be called with the cpu lock held + */ + +void move_signals_to_task(void) +{ + struct ksignal ksig; /* place to hold retrieved signal */ + /* + * Get the lkl local version of the current task, so we can store the signals + * in a list hanging off it. + */ + struct thread_info *current_task; + + /* In DEBUG build check that the lock is currently held by this task */ + lkl_assert_cpu_owner(); + + current_task = task_thread_info(current); + /* note that get_signal may never return if the task is dead (eg pending a SIGKILL) */ + while (get_signal(&ksig)) { + LKL_TRACE("Appending task %p signal %d\n", current, ksig.sig); + append_ksignal_node(current_task, &ksig); + +/* + * TODO check whether the task is runnable here. If it is INTERUPTABLE then set it to RUNNING + * so it can be scheduled. Check with the ltp test tgkill01. + * + * This will be another PR. + */ + + } +} + +/* + * Must be called WITH the cpu lock held. + * The signal invoke code drops and retakes the lock. + */ + +void send_current_signals(struct ucontext *uctx) { struct ksignal ksig; struct ucontext uc; - + LKL_TRACE("enter\n"); - memset(&uc, 0, sizeof(uc)); - initialize_uctx(&uc, regs); - while (get_signal(&ksig)) { + if (uctx == NULL) { + uctx = &uc; + memset(uctx, 0, sizeof(uc)); + initialize_uctx(uctx, NULL); + } + + /* In DEBUG build check that the lock is currently held by this task */ + lkl_assert_cpu_owner(); + + /* get pending signals */ + while (get_next_ksignal(task_thread_info(current), &ksig)) { LKL_TRACE("ksig.sig=%d", ksig.sig); + + /* Actually deliver the signal. */ - /* Whee! Actually deliver the signal. */ - handle_signal(&ksig, &uc); + handle_signal(&ksig, uctx); } } diff --git a/arch/lkl/kernel/syscalls.c b/arch/lkl/kernel/syscalls.c index d357f94c930aea..19d7e6f7b08593 100644 --- a/arch/lkl/kernel/syscalls.c +++ b/arch/lkl/kernel/syscalls.c @@ -165,7 +165,16 @@ long lkl_syscall(long no, long *params) ret = run_syscall(no, params); task_work_run(); - do_signal(NULL); + + /* + * Do NOT send signals now. That mechanism relies on lkl_cpu_get/put + * which is disabled for shutdown. + * + * Otherwise it would be like: + * + * move_signals_to_task(); + * send_current_signals(NULL); + */ if (no == __NR_reboot) { thread_sched_jb(); @@ -287,7 +296,8 @@ long lkl_syscall(long no, long *params) switch_to_host_task(task); } - do_signal(NULL); + /* capture the signals pending while we still have the cpu lock */ + move_signals_to_task(); if (no == __NR_reboot) { thread_sched_jb(); @@ -295,11 +305,12 @@ long lkl_syscall(long no, long *params) } out: + /* run any signal handlers expected to run on this thread */ + send_current_signals(NULL); lkl_cpu_put(); - + LKL_TRACE("done (no=%li task=%s current=%s ret=%i)\n", no, task ? task->comm : "NULL", current->comm, ret); - return ret; } @@ -325,7 +336,22 @@ static int idle_host_task_loop(void *unused) idle_host_task = current; for (;;) { + /* + any pending signals, capture them in lists per task + so we can send them to the appropriate task later + */ + move_signals_to_task(); + lkl_cpu_put(); + + /* + * This mirrors some of the logic in __switch_to that transfers ownership of the CPU lock between + * threads when they are context switched. This is not quite the same code because in __switch_to it + * transfers the lock to the next LKL thread, whereas this code is transferring owership implicitly + * to whichever thread does a syscall again (or back to itself if the timer interrupt fires and + * successfully acquires the CPU lock). + */ + lkl_ops->sem_down(ti->sched_sem); if (lkl_shutdown) { diff --git a/arch/lkl/kernel/threads.c b/arch/lkl/kernel/threads.c index 989b90bfbc372c..a17a21cdf054ec 100644 --- a/arch/lkl/kernel/threads.c +++ b/arch/lkl/kernel/threads.c @@ -23,6 +23,10 @@ static int init_ti(struct thread_info *ti) ti->tid = 0; ti->cloned_child = NULL; + spin_lock_init(&ti->signal_list_lock); + ti->signal_list_head = NULL; + ti->signal_list_tail = NULL; + return 0; } @@ -42,7 +46,6 @@ unsigned long *alloc_thread_stack_node(struct task_struct *task, int node) } ti->task = task; - return (unsigned long *)ti; } @@ -130,6 +133,19 @@ void free_thread_stack(struct task_struct *tsk) test_tsk_thread_flag(tsk, TIF_SIGPENDING), ti, current->comm); kill_thread(ti); + + /* free any linked list of pending signals */ + /* note that we do not need to take the lock as the thread is dead and ought to be unreachable*/ + + spin_lock(&ti->signal_list_lock); + struct ksignal_list_node* signal_list = ti->signal_list_head; + while (signal_list != NULL) { + struct ksignal_list_node *next = signal_list->next; + kfree(signal_list); + signal_list = next; + } + spin_unlock(&ti->signal_list_lock); + LKL_TRACE("Deallocating %p\n", ti); kfree(ti); } diff --git a/arch/lkl/kernel/traps.c b/arch/lkl/kernel/traps.c index ca041fe9e879f2..6032179326df4b 100644 --- a/arch/lkl/kernel/traps.c +++ b/arch/lkl/kernel/traps.c @@ -41,7 +41,10 @@ void lkl_do_trap(int trapnr, int signr, char *str, struct ucontext *uctx, LKL_TRACE("injecting signal into lkl\n"); force_sig_info((struct kernel_siginfo *)info); - lkl_process_trap(signr, uctx); + /* invoke the signal handler for this trap once we have given up the CPU */ + /* otherwise we may take the CPU lock again and fail. */ + lkl_process_trap(signr, uctx); + lkl_cpu_put(); }