libn64/os: Add a libn64_time() syscall.
authorTyler J. Stachecki <stachecki.tyler@gmail.com>
Sat, 2 Dec 2017 11:17:09 +0000 (06:17 -0500)
committerTyler J. Stachecki <stachecki.tyler@gmail.com>
Sat, 2 Dec 2017 11:36:10 +0000 (06:36 -0500)
The syscall returns a timespec struct that has two fields
populated: tv_sec and tv_usec. Since the N64 doesn't have a
RTC, this function does the "next best thing" and returns
the time offset since libn64 started.

The syscall uses the COUNT register to precisely measure
time. It can effectively count upto a couple hundred years
before it "rolls over".

Since the COUNT register is effectively driven at "only",
46.875MHz the time source is just barely accurate within a
hundred usec or so.

Signed-off-by: Tyler J. Stachecki <stachecki.tyler@gmail.com>
libn64/Makefile
libn64/include/syscall.h
libn64/include/time.h [new file with mode: 0644]
libn64/os/asm/exception.s
libn64/os/asm/syscall.s
libn64/os/main.c
libn64/os/thread.c
libn64/os/time.c [new file with mode: 0644]
libn64/priv_include/os/time.h [new file with mode: 0644]

index 4fcc909..7ab468b 100644 (file)
@@ -40,6 +40,7 @@ CFILES = $(call FIXPATH,\
        os/mm.c \
        os/panic.c \
        os/thread.c \
+       os/time.c \
        rcp/vi.c \
 )
 
@@ -63,7 +64,7 @@ libn64.a: $(OBJFILES)
        @echo $(call FIXPATH,"Assembling: libn64/$<")
        @$(CC) -x assembler-with-cpp $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
 
-%.o: %.c
+%.o: %.c include/syscall.h
        @echo $(call FIXPATH,"Compiling: libn64/$<")
        @$(CC) $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
 
index e6a0429..41dca63 100644 (file)
 #define LIBN64_SYSCALL_THREAD_UNREG_INTR 4
 #define LIBN64_SYSCALL_PAGE_ALLOC        5
 #define LIBN64_SYSCALL_PAGE_FREE         6
+#define LIBN64_SYSCALL_TIME              7
 
-#define LIBN64_SYSCALL_SEND_MESSAGE      7
-#define LIBN64_SYSCALL_RECV_MESSAGE      8
-#define LIBN64_SYSCALL_INVALID           9
+#define LIBN64_SYSCALL_SEND_MESSAGE      8
+#define LIBN64_SYSCALL_RECV_MESSAGE      9
+#define LIBN64_SYSCALL_INVALID           10
 
 #ifndef __ASSEMBLER__
 #include <stdint.h>
+#include <time.h>
 
 enum libn64_interrupt {
   LIBN64_INTERRUPT_AI = 0x430,
@@ -182,6 +184,31 @@ static inline void libn64_page_free(void *p) {
   );
 }
 
+// Returns a timeval struct corresponding to the time elapsed since boot.
+libn64func __attribute__((always_inline))
+static inline struct timeval libn64_time(void) {
+  struct timeval time;
+  register uint32_t v0 __asm__("$v0");
+  register uint32_t v1 __asm__("$v1");
+
+  __asm__ __volatile__(
+    ".set noreorder\n\t"
+    ".set noat\n\t"
+    "li $at, %2\n\t"
+    "syscall\n\t"
+    ".set reorder\n\t"
+    ".set at\n\t"
+
+    : "=r" (v0), "=r" (v1)
+    : "K" (LIBN64_SYSCALL_TIME)
+    : "memory"
+  );
+
+  time.tv_sec = v0;
+  time.tv_usec = v1;
+  return time;
+}
+
 // Sends a message with no parameter values to the specified thread.
 libn64func __attribute__((always_inline))
 static inline void libn64_send_message(libn64_thread thread, uint32_t message) {
diff --git a/libn64/include/time.h b/libn64/include/time.h
new file mode 100644 (file)
index 0000000..ef72be2
--- /dev/null
@@ -0,0 +1,22 @@
+//
+// libn64/include/time.h: time.
+//
+// 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.
+//
+
+#ifndef LIBN64_INCLUDE_TIME_H
+#define LIBN64_INCLUDE_TIME_H
+
+#include <stdint.h>
+
+struct timeval {
+  uint32_t tv_sec;
+  uint32_t tv_usec;
+};
+
+#endif
+
index d22fd9b..95e0581 100644 (file)
@@ -296,9 +296,7 @@ libn64_exception_handler_interrupt:
   bne $k1, $zero, libn64_exception_handler_rcp_interrupt
   andi $k1, $k0, 0x8000
   bne $k1, $zero, libn64_exception_handler_timer_interrupt
-  andi $k1, $k0, 0x0800
-  beql $k1, $zero, libn64_panic
-  addu $k0, $zero, $zero
+  nop
 
 # We got an unexpected interrupt. Since we don't know how to handle it, panic.
 # Currently, this will happen for software interrupts and the Indy debugger-
@@ -307,12 +305,15 @@ libn64_exception_handler_64dd_interrupt:
   j libn64_panic
   addu $k0, $zero, $zero
 
-# A timer interrupt occurred. Bounce the compare register to silence it.
-# In the future, we probably want to pump a message out on a message queue
-# or something here to acknowledge that a timer interrupt occurred.
+# A timer interrupt occurred. Bounce the compare register to silence it
+# and increment the "rollover" counter that we use to track time.
 libn64_exception_handler_timer_interrupt:
-  mfc1 $k1, $11
-  mtc1 $k1, $11
+  mfc0 $k1, $11
+  mtc0 $k1, $11
+  lui $k0, 0x8000
+  lw $k1, 0x448($k0)
+  addiu $k1, $k1, 0x1
+  sw $k1, 0x448($k0)
   eret
 
 # Handle a RCP interrupt: pump messages out to the listeners.
index 01fd26b..4a2d897 100644 (file)
@@ -118,7 +118,7 @@ libn64_syscall_thread_create_start_new:
   lw $a0, 0x01C($k1)
   mtc0 $a1, $14
 
-  addiu $at, $zero, 0x403
+  xori $at, $zero, 0x8403
   mtc0 $at, $12
   srl $at, $k1, 0x9
   andi $at, $at, 0xFF
@@ -355,6 +355,45 @@ libn64_syscall_page_free:
 .size libn64_syscall_page_free,.-libn64_syscall_page_free
 
 # -------------------------------------------------------------------
+#  libn64::time
+#
+#  TODO: Preserve HI and LO registers.
+# -------------------------------------------------------------------
+.type libn64_syscall_time, @function
+.align 5
+libn64_syscall_time:
+  lui $k0, 0x8000
+  ld $k0, 0x448($k0)
+  mtc0 $k1, $14
+  dmfc0 $k1, $9
+  or $k0, $k0, $k1
+
+  # 62,500,000 * 1.5 / 2 = 46,875,000 ticks per second.
+  lui $k1, 0x02CB
+  ori $k1, 0x4178
+  ddivu $k0, $k1
+
+  # tv_sec is the result of the div. Compute tv_usec next.
+  mflo $v0 # div result -> tv_sec
+  mfhi $v1 # mod result * 1e6
+  lui $k0, 0x000F
+  ori $k0, 0x4240
+  multu $v1, $k0 
+
+  mfhi $v1
+  mflo $k0
+  dsll32 $k0, $k0, 0
+  dsll32 $v1, $v1, 0
+  dsrl32 $k0, $k0, 0
+  or $v1, $v1, $k0
+  ddivu $v1, $k1
+  mflo $v1
+  nop
+  eret
+
+.size libn64_syscall_time,.-libn64_syscall_time
+
+# -------------------------------------------------------------------
 #  libn64::send_message
 #    $a0 = recipient
 #    $a1 = message
@@ -441,6 +480,7 @@ libn64_syscall_table:
 .long libn64_syscall_thread_unreg_intr
 .long libn64_syscall_page_alloc
 .long libn64_syscall_page_free
+.long libn64_syscall_time
 .long libn64_syscall_send_message
 .long libn64_syscall_recv_message
 
index 923ed1d..3347015 100644 (file)
@@ -12,6 +12,7 @@
 #include <os/idle_thread.h>
 #include <os/mm.h>
 #include <os/thread.h>
+#include <os/time.h>
 #include <stddef.h>
 #include <syscall.h>
 
@@ -20,6 +21,9 @@ void main(void *);
 // Entry point (invoked from IPL handler).
 libn64func __attribute__((noreturn))
 void libn64_main(uint32_t kernel_sp, uint32_t bss_end) {
+
+  // Initialize critical subsystems.
+  libn64_time_init();
   libn64_thread_early_init(kernel_sp);
 
   // Give libn64 the minimum amount of memory allowable (~24KiB)
index 109094d..ef0fd57 100644 (file)
@@ -63,7 +63,7 @@ libn64_thread libn64_thread_early_init(uint32_t kernel_sp) {
     "srl $at, %0, 0x9\n\t"
     "andi $at, $at, 0xFF\n\t"
     "mtc0 $at, $10\n\t"
-    "addiu $at, $zero, 0x401\n\t"
+    "xori $at, $zero, 0x8401\n\t"
     "mtc0 $at, $12\n\t"
     ".set reorder\n\t"
     ".set at\n\t"
diff --git a/libn64/os/time.c b/libn64/os/time.c
new file mode 100644 (file)
index 0000000..8787c9f
--- /dev/null
@@ -0,0 +1,30 @@
+//
+// libn64/os/time.c: OS time functions.
+//
+// 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>
+
+// Initialize the time subsystem.
+void libn64_time_init(void) {
+
+  // Bounce the timer so that it starts firing.
+  // Initialize the global time counter to zero.
+  __asm__ __volatile__(
+    ".set noat\n\t"
+    ".set gp=64\n\t"
+    "addiu $at, $zero, -0x1\n\t"
+    "mtc0 $at, $11\n\t"
+    "mtc0 $zero, $9\n\t"
+    "lui $at, 0x8000\n\t"
+    "sd $zero, 0x448($at)\n\t"
+    ".set gp=default\n\t"
+    ".set at\n\t"
+  );
+}
+
diff --git a/libn64/priv_include/os/time.h b/libn64/priv_include/os/time.h
new file mode 100644 (file)
index 0000000..c8ee567
--- /dev/null
@@ -0,0 +1,21 @@
+//
+// libn64/priv_include/os/time.h: OS time definitions.
+//
+// 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.
+//
+
+#ifndef LIBN64_PRIV_INCLUDE_OS_TIME_H
+#define LIBN64_PRIV_INCLUDE_OS_TIME_H
+
+#include <libn64.h>
+
+// Initializes the time subsystem.
+libn64func
+void libn64_time_init(void);
+
+#endif
+