libn64/os: Block and unblock on send/recv calls.
authorTyler J. Stachecki <stachecki.tyler@gmail.com>
Sun, 4 Jun 2017 19:07:24 +0000 (15:07 -0400)
committerTyler J. Stachecki <stachecki.tyler@gmail.com>
Sun, 9 Jul 2017 15:04:41 +0000 (11:04 -0400)
Signed-off-by: Tyler J. Stachecki <stachecki.tyler@gmail.com>
12 files changed:
libn64/Makefile
libn64/os/asm/context.s
libn64/os/asm/exception.s
libn64/os/asm/message.s
libn64/os/asm/syscall.s
libn64/os/idle_thread.c [new file with mode: 0644]
libn64/os/kthread.c [deleted file]
libn64/os/main.c
libn64/priv_include/os/idle_thread.h [new file with mode: 0644]
libn64/priv_include/os/kthread.h [deleted file]
libn64/priv_include/os/thread.h
threadtest/src/main.c

index 672a06c..88bb894 100644 (file)
@@ -35,7 +35,7 @@ ASMFILES = $(call FIXPATH,\
 
 CFILES = $(call FIXPATH,\
        os/fbtext.c \
-       os/kthread.c \
+       os/idle_thread.c \
        os/main.c \
        os/mm.c \
        os/panic.c \
index d962e5a..ddbb07a 100644 (file)
 .set noreorder
 
 # -------------------------------------------------------------------
+#  Blocks the active thread, places the next ready one on the CPU.
+#  When libn64_unblock_thread is called on the thread blocked by this
+#  function, the blocked thread resumes execution at ErrorEPC + 0x4.
+#
+#  Intended to be invoked as the the return of a libn64_context_save.
+# -------------------------------------------------------------------
+.global libn64_block_thread
+.type libn64_block_thread, @function
+.align 5
+libn64_block_thread:
+  mfc0 $at, $12
+  sw $ra, 0x4($k0)
+  ori $at, $at, 0x4
+  sw $at, 0x80($k0)
+
+# Mark the thread blocked and dequeue it.
+  jal libn64_exception_handler_dequeue_thread
+  lw $k1, 0x8($k0)
+
+# Switch to the next active thread.
+  j libn64_context_restore
+  lw $k1, 0x8($k0)
+
+.size libn64_block_thread,.-libn64_block_thread
+
+# -------------------------------------------------------------------
+#  Queues a new (higher priority) thread and context switches to it.
+#  See the above function as to how this function should be used.
+#
+#  Intended to be invoked as the the return of a libn64_context_save.
+# -------------------------------------------------------------------
+.global libn64_unblock_hp_thread
+.type libn64_unblock_hp_thread, @function
+.align 5
+libn64_unblock_hp_thread:
+  mfc0 $k1, $30
+  lw $at, 0x19C($k1)
+  sw $ra, 0x4($k0)
+  addu $at, $at, 0x4
+
+# Queue the blocked thread again.
+  jal libn64_exception_handler_queue_thread
+  mtc0 $at, $30
+
+# Switch to the now unblocked thread.
+  j libn64_context_restore
+  lw $k1, 0x8($k0)
+
+.size libn64_unblock_hp_thread,.-libn64_unblock_hp_thread
+
+# -------------------------------------------------------------------
 #  Loads the context from the thread pointed to by $k1. After the context is
 #  reloaded, we return from the exception handler (i.e., the thread resumes
 #  execution).
index 8ccb793..e9f714d 100644 (file)
@@ -391,7 +391,7 @@ libn64_exception_handler_allocpage_found:
 #  per loop. Other than that, the only stalls are due to data
 #  accesses that miss the cache.
 #
-#  Clobbers: $a0-$a3
+#  Clobbers: $k1, $a0-$a3
 # -------------------------------------------------------------------
 .global libn64_exception_handler_dequeue_thread
 .type libn64_exception_handler_dequeue_thread, @function
index 48af12f..91cfdd3 100644 (file)
 .set noreorder
 
 # -------------------------------------------------------------------
-#  Receive a message; block if there's nothing in the queue.
-# -------------------------------------------------------------------
-.global libn64_recv_message
-.type libn64_recv_message, @function
-.align 5
-libn64_recv_message:
-  lui $k0, 0x8000
-  lw $k0, 0x420($k0)
-  lw $k0, 0x8($k0)
-  lw $k1, 0x190($k0)
-
-# TODO: Block until a message comes in.
-libn64_recv_message_block:
-  beq $k1, $zero, libn64_recv_message_block
-  nop
-
-# Deque the message at the head/front of the message queue.
-# If the message has a successor, make it the new queue head.
-# If there is no successor, then there is no tail, so update it.
-  lw $at, 0x0($k1)
-  sw $at, 0x190($k0)
-  bnel $at, $zero, libn64_recv_message_after_next_update
-  sw $zero, 0x4($at)
-  sw $zero, 0x194($k0)
-
-# Return the message to the message cache.
-libn64_recv_message_after_next_update:
-  lui $k0, 0x8000
-  lw $at, 0x424($k0)
-  sw $at, 0x0($k1)
-  sw $k1, 0x424($k0)
-
-# Return the contents of the message to the caller.
-  lw $v0, 0x8($k1)
-  jr $ra
-  lw $v1, 0xC($k1)
-
-.size libn64_recv_message,.-libn64_recv_message
-
-# -------------------------------------------------------------------
 #  Sends a message ($a1/message, $a2/data) to a thread ($a0).
+#  Caution: some code depends on $at = 0x8000_0000 at exit.
 # -------------------------------------------------------------------
 .global libn64_send_message
 .type libn64_send_message, @function
index eef889a..86f57b8 100644 (file)
@@ -25,7 +25,6 @@
 .set THREAD_FREE_COUNT, ((LIBN64_THREADS_MAX + 1) * 0x8)
 .set THREAD_FREE_LIST, (THREAD_FREE_COUNT + 0x4)
 
-.global libn64_syscall_thread_create
 .type libn64_syscall_thread_create, @function
 .align 5
 libn64_syscall_thread_create:
@@ -33,73 +32,74 @@ libn64_syscall_thread_create:
   lw $k0, 0x420($k0)
   mtc0 $k1, $14
 
-# Grab the next available thread from the free list.
+# Grab the next available thread from the free thread list.
   lw $k1, THREAD_FREE_COUNT($k0)
   addiu $k1, $k1, -0x1
   sw $k1, THREAD_FREE_COUNT($k0)
 
   sll $k1, $k1, 0x2
   addu $k1, $k1, $k0
-  lw $k1, THREAD_FREE_LIST($k1)
+  lw $v0, THREAD_FREE_LIST($k1)
 
 # Invalidate all of the new thread's L1 page table entries.
-  addiu $at, $k1, 0x40
+  addiu $at, $v0, 0x40
 
 libn64_syscall_thread_create_invalidate_loop:
   cache 0xD, -0x10($at)
   addiu $at, $at, -0x10
   sd $zero, 0x1C0($at)
-  bne $at, $k1, libn64_syscall_thread_create_invalidate_loop
+  bne $at, $v0, libn64_syscall_thread_create_invalidate_loop
   sd $zero, 0x1C8($at)
 
 # Compare the running thread's priority against the new thread.
 # If the running thread has a higher priority, keep it going.
+# Set the thread's initial message queue pointers and priority.
 # Flush out $a0/$a1 to the thread's $a1/$a0 registers as well.
   lw $at, 0x8($k0)
-  cache 0xD, 0x010($k1)
+  cache 0xD, 0x010($v0)
   lw $at, 0x198($at)
-  cache 0xD, 0x198($k1)
-  sd $zero, 0x190($k1)
-  sw $a2, 0x198($k1)
+  cache 0xD, 0x198($v0)
+  sw $a0, 0x010($v0)
+  dsll32 $a0, $a2, 0x0
+  sd $a0, 0x198($v0)
   subu $at, $at, $a2
-  sw $a0, 0x010($k1)
+  sd $zero, 0x190($v0)
   bltz $at, libn64_syscall_thread_create_start_new_thread
-  sw $a1, 0x01C($k1)
+  sw $a1, 0x01C($v0)
 
-# The running/active thread has a higher priority than the new thread.
+# The running thread has a higher priority than the new thread.
 # Queue the new thread so that it runs sometime in the future.
 libn64_syscall_thread_queue_new_thread:
-  sw $a2, 0x014($k1)
-  sw $a3, 0x018($k1)
+  sw $a2, 0x014($v0)
+  sw $a3, 0x018($v0)
 
   la $at, libn64_syscall_thread_create_start_new
   sw $ra, 0x4($k0)
   jal libn64_exception_handler_queue_thread
-  sw $at, 0x07C($k1)
+  sw $at, 0x07C($v0)
 
 # Restore destroyed variables and return.
-  lw $v0, 0x4($k0)
   lw $a0, 0x010($v0)
   lw $a1, 0x01C($v0)
   lw $a2, 0x014($v0)
   lw $a3, 0x018($v0)
   eret
 
-# The running/active thread has a lower/equal priority than the new thread.
+# The running thread has a lower/equal priority than the new thread.
 # Save the current thread context, insert the new thread in its place.
 libn64_syscall_thread_create_start_new_thread:
   addu $at, $k0, $zero
   la $k0, libn64_syscall_thread_create_start_new_thread_continue
-  sw $k1, 0x4($at)
+  sw $v0, 0x4($at)
   j libn64_context_save
   lw $k1, 0x8($at)
 
 libn64_syscall_thread_create_start_new_thread_continue:
-  jal libn64_exception_handler_queue_thread
   lw $k1, 0x4($k0)
+  jal libn64_exception_handler_queue_thread
+  addu $v0, $k1, $zero
 
 # Set the new thread's status/coprocessor status, ASID, and stack/$gp.
-  lw $v0, 0x8($k0)
   lw $a1, 0x010($v0)
   lw $a0, 0x01C($v0)
 
@@ -130,14 +130,12 @@ libn64_thread_exit:
 # -------------------------------------------------------------------
 #  libn64::thread_exit
 # -------------------------------------------------------------------
-.global libn64_syscall_thread_exit
 .type libn64_syscall_thread_exit, @function
 .align 5
 
 libn64_syscall_thread_exit:
   lui $k0, 0x8000
   lw $k0, 0x420($k0)
-  mtc0 $k1, $14
   sw $ra, 0x4($k0)
   jal libn64_exception_handler_dequeue_thread
   mtc0 $k1, $14
@@ -149,7 +147,6 @@ libn64_syscall_thread_exit:
 # -------------------------------------------------------------------
 #  libn64::page_alloc
 # -------------------------------------------------------------------
-.global libn64_syscall_page_alloc
 .type libn64_syscall_page_alloc, @function
 .align 5
 libn64_syscall_page_alloc:
@@ -168,7 +165,6 @@ libn64_syscall_page_alloc:
 #  libn64::page_free
 #    $a0 = page address
 # -------------------------------------------------------------------
-.global libn64_syscall_page_free
 .type libn64_syscall_page_free, @function
 .align 5
 libn64_syscall_page_free:
@@ -202,7 +198,6 @@ libn64_syscall_page_free:
 #    $a1 = message
 #    $a2 = param
 # -------------------------------------------------------------------
-.global libn64_syscall_send_message
 .type libn64_syscall_send_message, @function
 .align 5
 libn64_syscall_send_message:
@@ -210,8 +205,26 @@ libn64_syscall_send_message:
   jal libn64_send_message
   mtc0 $k1, $14
   mfc0 $ra, $30
+
+# Check to see if we unblocked a higher priority thread.
+  lw $at, 0x420($at)
+  mfc0 $ra, $30
+  lw $at, 0x8($at)
+
+  lw $k1, 0x198($a0)
+  lw $k0, 0x198($at)
+
+  subu $k1, $k1, $k0
+  bgtzl $k1, libn64_send_message_unblock_hp_thread
+  mtc0 $a0, $30
   eret
 
+# We did, so context switch to the now unblocked thread.
+libn64_send_message_unblock_hp_thread:
+  la $k0, libn64_unblock_hp_thread
+  j libn64_context_save
+  addu $k1, $at, $zero
+
 .size libn64_syscall_send_message,.-libn64_syscall_send_message
 
 # -------------------------------------------------------------------
@@ -221,19 +234,52 @@ libn64_syscall_send_message:
 .type libn64_syscall_recv_message, @function
 .align 5
 libn64_syscall_recv_message:
-  mtc0 $ra, $30
-  jal libn64_recv_message
   mtc0 $k1, $14
-  mfc0 $ra, $30
+  lui $at, 0x8000
+  lw $at, 0x420($at)
+  lw $at, 0x8($at)
+  lw $k1, 0x190($at)
+
+# If there are no messages available, block the thread.
+libn64_recv_message_block:
+  bnel $k1, $zero, libn64_recv_message_deque
+  lw $k0, 0x0($k1)
+
+# No messages are available; set the thread's unblock return
+# to this syscall, deque it, and schedule the next thread.
+  sw $k0, 0x19C($at)
+  la $k0, libn64_block_thread
+  j libn64_context_save
+  addu $k1, $at, $zero
+
+# Deque the message k0 the head/front of the message queue.
+# If the message has a successor, make it the new queue head.
+# If there is no successor, then there is no tail; update it.
+libn64_recv_message_deque:
+  sw $k0, 0x190($at)
+  bnel $k0, $zero, libn64_recv_message_after_next_update
+  sw $zero, 0x4($k0)
+  sw $zero, 0x194($at)
+
+# Return the message to the message cache.
+libn64_recv_message_after_next_update:
+  lui $at, 0x8000
+  lw $k0, 0x424($at)
+  sw $k0, 0x0($k1)
+  sw $k1, 0x424($at)
+
+# Return the contents of the message to the caller.
+  lw $v0, 0x8($k1)
+  lw $v1, 0xC($k1)
   eret
 
 .size libn64_syscall_recv_message,.-libn64_syscall_recv_message
 
-.section  .rodata
-
 # -------------------------------------------------------------------
 #  System call table.
 # -------------------------------------------------------------------
+.section  .rodata
+
 .global libn64_syscall_table
 .type libn64_syscall_table, @object
 .align 4
diff --git a/libn64/os/idle_thread.c b/libn64/os/idle_thread.c
new file mode 100644 (file)
index 0000000..0d2a2b4
--- /dev/null
@@ -0,0 +1,17 @@
+//
+// libn64/os/idle_thread.c: libn64 idle thread.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#include <os/idle_thread.h>
+
+void libn64_idle_thread(void) {
+  while (1);
+  __builtin_unreachable();
+}
+
diff --git a/libn64/os/kthread.c b/libn64/os/kthread.c
deleted file mode 100644 (file)
index 21fe76b..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// libn64/os/kthread.c: libn64 kernel thread.
-//
-// n64chain: A (free) open-source N64 development toolchain.
-// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
-//
-// This file is subject to the terms and conditions defined in
-// 'LICENSE', which is part of this source code package.
-//
-
-#include <os/kthread.h>
-
-void libn64_kthread(void) {
-  while (1);
-  __builtin_unreachable();
-}
-
index ae67d17..4e08f12 100644 (file)
@@ -9,10 +9,9 @@
 //
 
 #include <libn64.h>
-#include <os/kthread.h>
+#include <os/idle_thread.h>
 #include <os/mm.h>
 #include <os/thread.h>
-#include <os/thread_table.h>
 #include <stddef.h>
 #include <syscall.h>
 
@@ -21,19 +20,20 @@ void main(void *);
 // Entry point (invoked from IPL handler).
 libn64func __attribute__((noreturn))
 void libn64_main(uint32_t kernel_sp, uint32_t bss_end) {
-  libn64_thread kthread = libn64_thread_early_init(kernel_sp);
+  libn64_thread idle_thread = libn64_thread_early_init(kernel_sp);
 
   // Put the given physical memory region under control of the MM.
   libn64_mm_init(bss_end, kernel_sp - 256);
 
   // Send a message to ourself to warm up the message cache.
-  libn64_send_message(kthread, 0);
+  libn64_send_message(idle_thread, 0);
   libn64_recv_message();
 
   // Hand control over to the application (in another thread).
   libn64_thread_create(main, NULL, LIBN64_THREAD_MIN_PRIORITY + 1);
 
-  // This thread becomes the kernel thread.
-  libn64_kthread();
+  // This thread becomes the idle thread.
+  libn64_idle_thread();
+  __builtin_unreachable();
 }
 
diff --git a/libn64/priv_include/os/idle_thread.h b/libn64/priv_include/os/idle_thread.h
new file mode 100644 (file)
index 0000000..d895321
--- /dev/null
@@ -0,0 +1,20 @@
+//
+// libn64/priv_include/os/idle_thread.h: libn64 idle thread.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#include <libn64.h>
+
+#ifndef LIBN64_PRIV_INCLUDE_OS_IDLE_THREAD_H
+#define LIBN64_PRIV_INCLUDE_OS_IDLE_THREAD_H
+
+libn64func __attribute__((noreturn))
+void libn64_idle_thread(void);
+
+#endif
+
diff --git a/libn64/priv_include/os/kthread.h b/libn64/priv_include/os/kthread.h
deleted file mode 100644 (file)
index 000a9f9..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// libn64/priv_include/os/kthread.h: libn64 kernel thread.
-//
-// n64chain: A (free) open-source N64 development toolchain.
-// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
-//
-// This file is subject to the terms and conditions defined in
-// 'LICENSE', which is part of this source code package.
-//
-
-#include <libn64.h>
-
-#ifndef LIBN64_PRIV_INCLUDE_OS_KTHREAD_H
-#define LIBN64_PRIV_INCLUDE_OS_KTHREAD_H
-
-libn64func __attribute__((noreturn))
-void libn64_kthread(void);
-
-#endif
-
index e19b166..f76e91d 100644 (file)
@@ -33,7 +33,7 @@ struct libn64_thread_internal {
 
   struct message *messages_head;
   struct message *messages_tail;
-  unsigned priority, status;
+  uint32_t priority, blocked_call;
 
   uint32_t count[2];
   uint32_t unused[6];
index 9ec6686..876491e 100644 (file)
@@ -38,6 +38,7 @@ struct libn64_fbtext_context fbtext;
 unsigned threads_spawned;
 
 void thread_main(void *arg __attribute__((unused))) {
+  libn64_recv_message();
   unsigned my_prio;
 
   my_prio = (++threads_spawned);
@@ -69,7 +70,8 @@ void main(void *unused __attribute__((unused))) {
       LIBN64_FBTEXT_COLOR_BLACK, 0x140, LIBN64_FBTEXT_16BPP);
 
   threads_spawned = 1;
-  libn64_thread_create(thread_main, &fbtext, 2);
+  libn64_thread child = libn64_thread_create(thread_main, &fbtext, 3);
+  libn64_send_message(child, 2);
 
   libn64_fbtext_puts(&fbtext, "Idle thread!\n");
 }