Initial commit.
authorTyler J. Stachecki <stachecki.tyler@gmail.com>
Sat, 27 Aug 2016 17:51:08 +0000 (13:51 -0400)
committerTyler J. Stachecki <stachecki.tyler@gmail.com>
Sun, 9 Jul 2017 15:04:36 +0000 (11:04 -0400)
Signed-off-by: Tyler J. Stachecki <stachecki.tyler@gmail.com>
46 files changed:
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
doc/Makefile [new file with mode: 0644]
doc/n64chain-doc.bib [new file with mode: 0644]
doc/n64chain-doc.tex [new file with mode: 0644]
libn64/Makefile [new file with mode: 0644]
libn64/include/libn64.h [new file with mode: 0644]
libn64/include/os/fbtext.h [new file with mode: 0644]
libn64/include/os/kthread.h [new file with mode: 0644]
libn64/include/os/panic.h [new file with mode: 0644]
libn64/include/os/syscall.h [new file with mode: 0644]
libn64/include/os/thread.h [new file with mode: 0644]
libn64/include/os/thread_queue.h [new file with mode: 0644]
libn64/include/os/thread_table.h [new file with mode: 0644]
libn64/include/rcp/mi.h [new file with mode: 0644]
libn64/include/rcp/rsp.h [new file with mode: 0644]
libn64/include/rcp/vi.h [new file with mode: 0644]
libn64/include/stddef.h [new file with mode: 0644]
libn64/include/stdint.h [new file with mode: 0644]
libn64/include/vr4300/cp0.h [new file with mode: 0644]
libn64/os/asm/boot.s [new file with mode: 0644]
libn64/os/asm/exception.s [new file with mode: 0644]
libn64/os/asm/syscall.s [new file with mode: 0644]
libn64/os/asm/thread.s [new file with mode: 0644]
libn64/os/fbtext.c [new file with mode: 0644]
libn64/os/kthread.c [new file with mode: 0644]
libn64/os/main.c [new file with mode: 0644]
libn64/os/panic.c [new file with mode: 0644]
libn64/os/thread.c [new file with mode: 0644]
libn64/rcp/vi.c [new file with mode: 0644]
libn64/rom.ld [new file with mode: 0644]
rspasm/Makefile [new file with mode: 0644]
rspasm/emitter.c [new file with mode: 0644]
rspasm/emitter.h [new file with mode: 0644]
rspasm/lexer.l [new file with mode: 0644]
rspasm/main.c [new file with mode: 0644]
rspasm/opcodes.h [new file with mode: 0644]
rspasm/parser.y [new file with mode: 0644]
rspasm/rspasm.h [new file with mode: 0644]
rspasm/symbols.c [new file with mode: 0644]
rspasm/symbols.h [new file with mode: 0644]
threadtest/Makefile [new file with mode: 0644]
threadtest/src/main.c [new file with mode: 0644]
tools/build-linux64-toolchain.sh [new file with mode: 0755]
tools/build-win64-toolchain.sh [new file with mode: 0755]
tools/checksum.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..65638d3
--- /dev/null
@@ -0,0 +1,27 @@
+/doc/n64chain-doc.aux
+/doc/n64chain-doc.log
+/doc/n64chain-doc.pdf
+
+/rspasm/lexer.c
+/rspasm/lexer.h
+/rspasm/parser.c
+/rspasm/parser.h
+/rspasm/rspasm
+
+/tools/bin
+/tools/include
+/tools/lib
+/tools/libexec
+/tools/mips64-elf
+/tools/share
+
+*.a
+*.bin
+*.d
+*.elf
+*.map
+*.o
+*.z64
+
+*.swp
+
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..b8b347f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,281 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644 (file)
index 0000000..3b7c9fe
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# doc/Makefile: n64chain documentation Makefile.
+#
+# n64chain: A (free) open-source N64 development toolchain.
+# Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+#
+# This file is subject to the terms and conditions defined in
+# 'LICENSE', which is part of this source code package.
+#
+
+DOC = n64chain-doc
+
+TEX = n64chain-doc.tex
+BIBS = n64chain-doc.bib
+FIGS =
+
+all: $(DOC).pdf
+
+$(DOC).pdf: $(TEX) $(BIB) $(FIGS)
+       @pdflatex $(DOC)
+       @pdflatex $(DOC)
+       #@bibtex $(DOC)
+       @pdflatex $(DOC)
+       @pdflatex $(DOC)
+
+.PHONY: clean
+clean:
+       @rm -f $(DOC).pdf *.aux *.bbl *.blg *.log *.out
+
diff --git a/doc/n64chain-doc.bib b/doc/n64chain-doc.bib
new file mode 100644 (file)
index 0000000..16b04e4
--- /dev/null
@@ -0,0 +1,4 @@
+\begin{thebibliography}
+
+\end{thebibliography}
+
diff --git a/doc/n64chain-doc.tex b/doc/n64chain-doc.tex
new file mode 100644 (file)
index 0000000..4fe6e38
--- /dev/null
@@ -0,0 +1,15 @@
+\documentclass [12pt, letterpaper]{article}
+
+\begin{document}
+\title{n64chain: A (free) open-source N64 development toolchain.}
+\author{Tyler J. Stachecki}
+\maketitle
+\pagebreak
+
+\section{Introduction}
+I'll write something here later.
+
+\bibliographystyle{plain}
+\bibliography{n64chain-doc}
+\end{document}
+
diff --git a/libn64/Makefile b/libn64/Makefile
new file mode 100644 (file)
index 0000000..a441f1c
--- /dev/null
@@ -0,0 +1,75 @@
+#
+# libn64/Makefile: Makefile for libn64.
+#
+# 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.
+#
+
+ifdef SystemRoot
+FIXPATH = $(subst /,\,$1)
+RM = del /Q
+else
+FIXPATH = $1
+RM = rm -f
+endif
+
+AS = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-as)
+AR = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-gcc-ar)
+CC = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-gcc)
+
+CFLAGS = -Wall -Wextra -pedantic -std=c99 -I. -Iinclude
+OPTFLAGS = -Os -march=vr4300 -mabi=eabi -mgp32 -mlong32 \
+       -flto -ffat-lto-objects -ffunction-sections -fdata-sections \
+       -G4 -mno-extern-sdata -mgpopt
+
+ASMFILES = $(call FIXPATH,\
+       os/asm/boot.s \
+       os/asm/exception.s \
+       os/asm/syscall.s \
+       os/asm/thread.s \
+)
+
+CFILES = $(call FIXPATH,\
+       os/fbtext.c \
+       os/kthread.c \
+       os/main.c \
+       os/panic.c \
+       os/thread.c \
+       rcp/vi.c \
+)
+
+OBJFILES = \
+       $(CFILES:.c=.o) \
+       $(ASMFILES:.s=.o)
+
+DEPFILES = $(OBJFILES:.o=.d)
+
+#
+# Primary targets.
+#
+libn64.a: $(OBJFILES)
+       @echo $(call FIXPATH,"Building: libn64/$@")
+       @$(AR) rcs $@ $^
+
+#
+# Generic compilation/assembly targets.
+#
+%.o: %.s
+       @echo $(call FIXPATH,"Assembling: libn64/$<")
+       @$(CC) -x assembler-with-cpp $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
+
+%.o: %.c
+       @echo $(call FIXPATH,"Compiling: libn64/$<")
+       @$(CC) $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
+
+#
+# Clean project target.
+#
+.PHONY: clean
+clean:
+       @echo "Cleaning libn64..."
+       @$(RM) libn64.a $(DEPFILES) $(OBJFILES)
+
diff --git a/libn64/include/libn64.h b/libn64/include/libn64.h
new file mode 100644 (file)
index 0000000..ceec5f7
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * libn64/include/libn64.h: Global 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_INCLUDE_LIBN64_H
+#define LIBN64_INCLUDE_LIBN64_H
+
+#ifndef __ASSEMBLER__
+#define libn64func __attribute__ ((section (".text.libn64")))
+#endif
+
+#define LIBN64_THREADS_MAX 31
+
+#endif
+
diff --git a/libn64/include/os/fbtext.h b/libn64/include/os/fbtext.h
new file mode 100644 (file)
index 0000000..b63701f
--- /dev/null
@@ -0,0 +1,59 @@
+//
+// libn64/include/os/fbtext.h: Framebuffer text routines.
+//
+// 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_OS_FBTEXT_H
+#define LIBN64_INCLUDE_OS_FBTEXT_H
+
+#include <libn64.h>
+#include <stdint.h>
+
+#define LIBN64_FBTEXT_COLOR_BG 0
+#define LIBN64_FBTEXT_COLOR_FG 1
+
+#define LIBN64_FBTEXT_COLOR_BLACK (0)
+#define LIBN64_FBTEXT_COLOR_WHITE (~0)
+
+enum libn64_fbtext_mode {
+  LIBN64_FBTEXT_16BPP,
+  LIBN64_FBTEXT_32BPP
+};
+
+struct libn64_fbtext_context {
+  unsigned (*render_char)(const struct libn64_fbtext_context *, uint32_t, char);
+
+  uint32_t colors[2];
+  uint32_t fb_origin;
+  uint16_t fb_width;
+  uint8_t x, y;
+};
+
+// Initializes a framebuffer text rendering component.
+//
+// The framebuffer origin must be in the form of a physical
+// address and aligned to 16 bytes (i.e., 0x100000 = FB @ 1MiB)
+// The framebuffer width should be specified in terms of pixels.
+libn64func
+void libn64_fbtext_init(struct libn64_fbtext_context *context,
+    uint32_t fb_origin, uint32_t fg_color, uint32_t bg_color,
+    uint16_t fb_width, enum libn64_fbtext_mode mode);
+
+// Methods for rendering values to the framebuffer at the context's current
+// x and y position. Handles escape characters. Currently will write past the
+// end of the framebuffer instead of scrolling it up (as probably expected).
+libn64func
+void libn64_fbtext_puts(struct libn64_fbtext_context *context,
+    const char *string);
+
+libn64func
+void libn64_fbtext_putu32(struct libn64_fbtext_context *context,
+    uint32_t u32);
+
+#endif
+
diff --git a/libn64/include/os/kthread.h b/libn64/include/os/kthread.h
new file mode 100644 (file)
index 0000000..730cceb
--- /dev/null
@@ -0,0 +1,20 @@
+//
+// libn64/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_INCLUDE_OS_KTHREAD_H
+#define LIBN64_INCLUDE_OS_KTHREAD_H
+
+libn64func __attribute__((noreturn))
+void libn64_kthread(void);
+
+#endif
+
diff --git a/libn64/include/os/panic.h b/libn64/include/os/panic.h
new file mode 100644 (file)
index 0000000..681d882
--- /dev/null
@@ -0,0 +1,21 @@
+//
+// libn64/include/os/panic.h: Fatal crash handler.
+//
+// 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_OS_PANIC_H
+#define LIBN64_INCLUDE_OS_PANIC_H
+
+#include <libn64.h>
+#include <os/thread.h>
+
+libn64func __attribute__((noreturn))
+void libn64_panic_from_isr(void);
+
+#endif
+
diff --git a/libn64/include/os/syscall.h b/libn64/include/os/syscall.h
new file mode 100644 (file)
index 0000000..80b3ccd
--- /dev/null
@@ -0,0 +1,70 @@
+//
+// libn64/include/os/syscall.h: System call 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_INCLUDE_OS_SYSCALL_H
+#define LIBN64_INCLUDE_OS_SYSCALL_H
+
+// Syscall numbers.
+#define LIBN64_SYSCALL_CREATE_THREAD 0
+#define LIBN64_SYSCALL_EXIT_THREAD   1
+#define LIBN64_SYSCALL_INVALID       2
+
+#ifndef __ASSEMBLER__
+
+// Spawns a new thread with a given priority (which receives arg). If
+// the new thread has a higher priority than the current thread, a
+// context switch will result from this function call.
+//
+// If an insufficient number of threads are available (due to a hard
+// limit, or if too many threads are waiting to be reaped), a non-zero
+// value is returned. Otherwise, zero is returned to indicate success.
+libn64func __attribute__((always_inline))
+static inline int libn64_thread_create(void (*entrypoint)(void *),
+    void *arg, unsigned priority) {
+  register uint32_t rv __asm__("$v0");
+  register void (*a0)(void *) __asm__("$a0") = entrypoint;
+  register void *a1 __asm__("$a1") = arg;
+  register unsigned a2 __asm__("$a2") = priority;
+
+  __asm__ __volatile__(
+    ".set noreorder\n\t"
+    ".set noat\n\t"
+    "li $at, %4\n\t"
+    "syscall\n\t"
+    ".set reorder\n\t"
+    ".set at\n\t"
+
+    : "=r" (rv)
+    : "r" (a0), "r" (a1), "r" (a2), "K" (LIBN64_SYSCALL_CREATE_THREAD)
+    : "memory"
+  );
+
+  return rv;
+}
+
+// Terminates the currently running thread.
+libn64func __attribute__((always_inline))
+static inline void libn64_thread_exit(void) {
+  __asm__ __volatile__(
+    ".set noreorder\n\t"
+    ".set noat\n\t"
+    "li $at, %0\n\t"
+    "syscall\n\t"
+    ".set reorder\n\t"
+    ".set at\n\t"
+
+    :: "K" (LIBN64_SYSCALL_EXIT_THREAD)
+    : "memory"
+  );
+}
+
+#endif
+#endif
+
diff --git a/libn64/include/os/thread.h b/libn64/include/os/thread.h
new file mode 100644 (file)
index 0000000..d7568a7
--- /dev/null
@@ -0,0 +1,41 @@
+//
+// libn64/include/os/thread.h: OS thread 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_INCLUDE_OS_THREAD_H
+#define LIBN64_INCLUDE_OS_THREAD_H
+
+#include <stdint.h>
+
+#define LIBN64_THREAD_MIN_PRIORITY 0
+
+struct libn64_thread_state {
+  uint32_t regs[32];
+
+  uint32_t cp0_status;
+  uint32_t cp0_entryhi;
+  uint32_t cp1_control;
+  uint32_t mi_intr_reg;
+
+  uint64_t fp_regs[32];
+} __attribute__((aligned(16)));
+
+struct libn64_thread {
+  struct libn64_thread_state state;
+
+  unsigned priority;
+  uint32_t unused[3];
+};
+
+// Initializes the threading subsystem.
+libn64func
+void libn64_thread_init(void);
+
+#endif
+
diff --git a/libn64/include/os/thread_queue.h b/libn64/include/os/thread_queue.h
new file mode 100644 (file)
index 0000000..b8ab5fb
--- /dev/null
@@ -0,0 +1,38 @@
+//
+// libn64/include/os/thread_queue.h: OS thread queue 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_INCLUDE_OS_THREAD_QUEUE_H
+#define LIBN64_INCLUDE_OS_THREAD_QUEUE_H
+
+#include <libn64.h>
+#include <os/thread.h>
+
+struct libn64_thread_queue_entry {
+  struct libn64_thread *thread;
+  unsigned priority;
+} __attribute__((aligned(8)));
+
+// Metadata for the queue is kept in the first 8 bytes.
+// The root of the tree also sits in the same cache line.
+struct libn64_thread_queue {
+  uint32_t count;
+
+  // This field is volatile/reserved for use by the ISR.
+  uint32_t isr_temp;
+
+  // Each queue entry is 8b and each cache line is 16b. Make
+  // sure the root is at +8b offset into the cache line so the
+  // left/right children are wholly contained within a cache
+  // line (to reduce the # of cache misses during dequeues).
+  struct libn64_thread_queue_entry heap[LIBN64_THREADS_MAX];
+} __attribute__((aligned(16)));
+
+#endif
+
diff --git a/libn64/include/os/thread_table.h b/libn64/include/os/thread_table.h
new file mode 100644 (file)
index 0000000..a938877
--- /dev/null
@@ -0,0 +1,30 @@
+//
+// libn64/include/os/thread_table.h: OS thread table 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_INCLUDE_OS_THREAD_TABLE_H
+#define LIBN64_INCLUDE_OS_THREAD_TABLE_H
+
+#include <libn64.h>
+#include <os/thread.h>
+#include <os/thread_queue.h>
+
+struct libn64_thread_table {
+  struct libn64_thread_queue ready_queue;
+  struct libn64_thread *blocked_list;
+  unsigned free_threads;
+
+  struct libn64_thread *free_list[LIBN64_THREADS_MAX];
+  struct libn64_thread threads[LIBN64_THREADS_MAX];
+};
+
+extern struct libn64_thread_table *libn64_thread_table;
+
+#endif
+
diff --git a/libn64/include/rcp/mi.h b/libn64/include/rcp/mi.h
new file mode 100644 (file)
index 0000000..9e3274e
--- /dev/null
@@ -0,0 +1,39 @@
+//
+// libn64/include/rcp/mi.h: MI helper 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.
+//
+
+#ifndef LIBN64_INCLUDE_RCP_MI_H
+#define LIBN64_INCLUDE_RCP_MI_H
+
+#include <libn64.h>
+#include <stdint.h>
+
+#define MI_INTR_CLEAR_SP                    (1 <<  0)
+#define MI_INTR_SET_SP                      (1 <<  1)
+#define MI_INTR_CLEAR_SI                    (1 <<  2)
+#define MI_INTR_SET_SI                      (1 <<  3)
+#define MI_INTR_CLEAR_AI                    (1 <<  4)
+#define MI_INTR_SET_AI                      (1 <<  5)
+#define MI_INTR_CLEAR_VI                    (1 <<  6)
+#define MI_INTR_SET_VI                      (1 <<  7)
+#define MI_INTR_CLEAR_PI                    (1 <<  8)
+#define MI_INTR_SET_PI                      (1 <<  9)
+#define MI_INTR_CLEAR_DP                    (1 << 10)
+#define MI_INTR_SET_DP                      (1 << 11)
+
+// Sets the MI_INTR_MASK_REG bits.
+//
+// (see MI_INTR_CLEAR_* and MI_INTR_SET_*)
+libn64func
+static inline void mi_set_intr_mask(uint32_t mask) {
+  *(volatile uint32_t *) 0xA430000C = mask;
+}
+
+#endif
+
diff --git a/libn64/include/rcp/rsp.h b/libn64/include/rcp/rsp.h
new file mode 100644 (file)
index 0000000..45388d4
--- /dev/null
@@ -0,0 +1,85 @@
+//
+// libn64/include/rcp/rsp.h: RSP helper 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.
+//
+
+#ifndef LIBN64_INCLUDE_RCP_RSP_H
+#define LIBN64_INCLUDE_RCP_RSP_H
+
+#include <libn64.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define RSP_STATUS_CLEAR_HALT               (1 <<  0)
+#define RSP_STATUS_SET_HALT                 (1 <<  1)
+#define RSP_STATUS_CLEAR_BROKE              (1 <<  2)
+#define RSP_STATUS_CLEAR_INTR               (1 <<  3)
+#define RSP_STATUS_SET_INTR                 (1 <<  4)
+#define RSP_STATUS_CLEAR_SSTEP              (1 <<  5)
+#define RSP_STATUS_SET_SSTEP                (1 <<  6)
+#define RSP_STATUS_CLEAR_INTR_ON_BREAK      (1 <<  7)
+#define RSP_STATUS_SET_INTR_ON_BREAK        (1 <<  8)
+#define RSP_STATUS_CLEAR_SIGNAL_0           (1 <<  9)
+#define RSP_STATUS_SET_SIGNAL_0             (1 << 10)
+#define RSP_STATUS_CLEAR_SIGNAL_1           (1 << 11)
+#define RSP_STATUS_SET_SIGNAL_1             (1 << 12)
+#define RSP_STATUS_CLEAR_SIGNAL_2           (1 << 13)
+#define RSP_STATUS_SET_SIGNAL_2             (1 << 14)
+#define RSP_STATUS_CLEAR_SIGNAL_3           (1 << 15)
+#define RSP_STATUS_SET_SIGNAL_3             (1 << 16)
+#define RSP_STATUS_CLEAR_SIGNAL_4           (1 << 17)
+#define RSP_STATUS_SET_SIGNAL_4             (1 << 18)
+#define RSP_STATUS_CLEAR_SIGNAL_5           (1 << 19)
+#define RSP_STATUS_SET_SIGNAL_5             (1 << 20)
+#define RSP_STATUS_CLEAR_SIGNAL_6           (1 << 21)
+#define RSP_STATUS_SET_SIGNAL_6             (1 << 22)
+#define RSP_STATUS_CLEAR_SIGNAL_7           (1 << 23)
+#define RSP_STATUS_SET_SIGNAL_7             (1 << 24)
+
+// Issues a DMA to the RSP.
+//
+// Does NOT perform any checks. Be sure that you grab the
+// semaphore as needed, adjust the length (subtract one from
+// the amount you actually want copied), etc.
+libn64func
+static inline void rsp_issue_dma_to_rsp(
+  uint32_t paddr, uint32_t sp_addr, size_t len) {
+  __asm__ __volatile__(
+    "sw %[sp_addr], 0x00(%[sp_region])\n\t"
+    "sw %[paddr],   0x04(%[sp_region])\n\t"
+    "sw %[len],     0x08(%[sp_region])\n\t"
+
+    :: [paddr] "r" (paddr), [sp_addr] "r" (sp_addr), [len] "r" (len),
+       [sp_region] "r" (0xA4040000U)
+  );
+}
+
+// Checks for a pending RSP DMA.
+//
+// Returns 1 if a DMA is pending, 0 otherwise.
+libn64func
+static inline uint32_t rsp_is_dma_pending(void) {
+  return *(volatile const uint32_t *) 0xA4040018;
+}
+
+// Sets the RSP PC register.
+libn64func
+static inline void rsp_set_pc(uint32_t pc) {
+  *(volatile uint32_t *) 0xA4080000 = pc;
+}
+
+// Updates the RSP status flags according to mask.
+//
+// (see RSP_STATUS_CLEAR_* and RSP_STATUS_SET_*)
+libn64func
+static inline void rsp_set_status(uint32_t mask) {
+  *(volatile uint32_t *) 0xA4040010 = mask;
+}
+
+#endif
+
diff --git a/libn64/include/rcp/vi.h b/libn64/include/rcp/vi.h
new file mode 100644 (file)
index 0000000..9358e89
--- /dev/null
@@ -0,0 +1,42 @@
+//
+// libn64/include/rcp/vi.h: VI helper 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.
+//
+
+#ifndef LIBN64_INCLUDE_RCP_VI_H
+#define LIBN64_INCLUDE_RCP_VI_H
+
+#include <libn64.h>
+#include <stdint.h>
+
+typedef struct vi_state_t {
+  uint32_t status;
+  uint32_t origin;
+  uint32_t width;
+  uint32_t intr;
+  uint32_t current;
+  uint32_t burst;
+  uint32_t v_sync;
+  uint32_t h_sync;
+  uint32_t leap;
+  uint32_t h_start;
+  uint32_t v_start;
+  uint32_t v_burst;
+  uint32_t x_scale;
+  uint32_t y_scale;
+} vi_state_t __attribute__ ((aligned (8)));
+
+// Flushes the register data to hardware registers.
+//
+// - Caller is responsible for disabling interrupts.
+// - state _must_ pointed to cached memory (mapped or not).
+libn64func
+void vi_flush_state(const vi_state_t *state);
+
+#endif
+
diff --git a/libn64/include/stddef.h b/libn64/include/stddef.h
new file mode 100644 (file)
index 0000000..a5c3387
--- /dev/null
@@ -0,0 +1,20 @@
+//
+// libn64/include/stddef.h: libc stddef.
+//
+// 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_STDDEF_H
+#define LIBN64_INCLUDE_STDDEF_H
+
+#define NULL ((void *) 0ULL)
+
+typedef int ssize_t;
+typedef unsigned size_t;
+
+#endif
+
diff --git a/libn64/include/stdint.h b/libn64/include/stdint.h
new file mode 100644 (file)
index 0000000..cc3bdae
--- /dev/null
@@ -0,0 +1,27 @@
+//
+// libn64/include/stdint.h: libc stdint.
+//
+// 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_STDINT_H
+#define LIBN64_INCLUDE_STDINT_H
+
+typedef signed char int8_t;
+typedef unsigned char uint8_t;
+typedef signed short int int16_t;
+typedef unsigned short int uint16_t;
+typedef signed int int32_t;
+typedef unsigned int uint32_t;
+typedef signed long long int int64_t;
+typedef unsigned long long int uint64_t;
+
+typedef int32_t intptr_t;
+typedef uint32_t uintptr_t;
+
+#endif
+
diff --git a/libn64/include/vr4300/cp0.h b/libn64/include/vr4300/cp0.h
new file mode 100644 (file)
index 0000000..e2c7f96
--- /dev/null
@@ -0,0 +1,60 @@
+//
+// libn64/vr4300/cp0.h: VR4300/CP0 helper 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.
+//
+
+#ifndef LIBN64_INCLUDE_VR4300_CP0_H
+#define LIBN64_INCLUDE_VR4300_CP0_H
+
+#include <stdint.h>
+
+// Disables VR4300 interrupts.
+static inline void vr4300_cp0_disable_interrupts(void) {
+  uint32_t status;
+
+  __asm__ __volatile__(
+    "mfc0 %[status], $12\n\t"
+    "srl %[status], %[status], 0x1\n\t"
+    "sll %[status], %[status], 0x1\n\t"
+    "mtc0 %[status], $12\n\t"
+
+    : [status] "=r"(status)
+  );
+}
+
+// Enables VR4300 interrupts.
+static inline void vr4300_cp0_enable_interrupts(void) {
+  uint32_t status;
+
+  __asm__ __volatile__(
+    "mfc0 %[status], $12\n\t"
+    "ori %[status], %[status], 0x1\n\t"
+    "mtc0 %[status], $12\n\t"
+
+    : [status] "=r"(status)
+  );
+}
+
+// Installs an interrupt handler (at 0x80000180).
+//
+//   3C1A____  lui  k0,     0x____
+//   375A____  ori  k0, k0, 0x____
+//   03400008  jr   k0
+//   401A6800  mfc0 k0, $13
+static inline void vr4300_install_interrupt_handler(uintptr_t addr) {
+  *(volatile uint32_t *) 0xA0000180 = (0x3C1A0000) | (addr >> 16);
+  *(volatile uint32_t *) 0xA0000184 = (0x375A0000) | ((uint16_t) addr);
+  *(volatile uint32_t *) 0xA0000188 = (0x03400008);
+  *(volatile uint32_t *) 0xA000018C = (0x401A6800);
+
+  // Invalidate the instruction cache line.
+  __builtin_mips_cache(0, (void *) 0xFFFFFFFF80000180ULL);
+}
+
+#endif
+
diff --git a/libn64/os/asm/boot.s b/libn64/os/asm/boot.s
new file mode 100644 (file)
index 0000000..6428a36
--- /dev/null
@@ -0,0 +1,111 @@
+#
+# libn64/os/asm/boot.s: libn64 IPL handoff.
+#
+# 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>
+
+.section .text.libn64.ipl, "ax", @progbits
+
+.set noat
+.set noreorder
+
+# -------------------------------------------------------------------
+#  The IPL loads this to the entrypoint (0x80000400).
+# -------------------------------------------------------------------
+.global libn64_ipl
+.type libn64_ipl, @function
+.align 5
+libn64_ipl:
+
+# Tell the PIF to not throw a NMI.
+  addiu $v0, $zero, 0x8
+  lui $at, 0xBFC0
+  sw $v0, 0x7FC($at)
+
+# Setup a stack at the top of cached RAM.
+  lui $sp, 0x8000
+  lw $at, 0x318($sp)
+  addu $sp, $sp, $at
+
+# Reserve the necessary amount of space immediately above the stack
+# for thread contexts and queues (0x200 bytes per thread, rounded up
+# to the nearest 4kB page).
+  li $v0, LIBN64_THREADS_MAX
+  addiu $v0, $v0, 0x7
+  srl $v0, $v0, 0x3
+  sll $v0, $v0, 0xC
+  subu $sp, $sp, $v0
+
+# Set the global pointer reference value.
+  la $gp, _gp
+
+# Set initial status register value.
+  addiu $v0, $zero, 0x400
+  mtc0 $v0, $12
+
+# DMA interrupt handler on top of the vector.
+# First, write the DRAM (destination) register.
+  lui $at, 0xA460
+  la $v0, (libn64_exception_handler - 0x80000000)
+  sw $v0, ($at)
+
+# Next, convert the exception handler address into a PI
+# cart address and write that to CART (source) register.
+  la $v0, (__bss_end + 0xC00)
+  lui $v1, 0x9000
+  xor $v1, $v0, $v1
+  sw $v1, 0x4($at)
+
+# Finally, write out the length to start the DMA.
+  addiu $v1, $zero, (0x300 - 0x1)
+  sw $v1, 0xC($at)
+
+# These next few instructions are fortunately in the same
+# cache line, so they won't be clobbered by the DMA. We can
+# DMA upto 0x300 bytes safely (anything else will begin
+# overwriting libn64_init, which is not yet in the cache).
+#
+# Wait for the PI to finish DMA'ing the interrupt handler.
+libn64_ipl_pi_wait:
+  xori $v0, $v0, 0x8
+  bnezl $v0, libn64_ipl_pi_wait
+  lw $v0, 0x10($at)
+
+# Clear the cause register and load $ra with the address
+# where we can stop invalidating the cache (0x80000480).
+  lui $v0, 0x8000
+  jal libn64_init
+  mtc0 $zero, $13
+
+# -------------------------------------------------------------------
+#  Initialize any libn64 components that cannot be done from C.
+#  After that's done, branch to libn64's C entrypoint.
+# -------------------------------------------------------------------
+.align 5
+libn64_init:
+  addiu $v0, $v0, 0x20
+
+# Invalidate the instruction cache where the exception
+# vectors lie, since they likely contain cached code.
+libn64_init_inval_icache:
+  cache 0x0, -0x20($v0)
+  bne $v0, $ra, libn64_init_inval_icache
+  addiu $v0, $v0, 0x20
+
+# Write out the address of the thread table (it's above the stack).
+# Done from the ASM side of things; off to C to continue init'ing.
+  la $at, libn64_thread_table
+  j libn64_main
+  sw $sp, ($at)
+
+.size libn64_ipl,.-libn64_ipl
+
+.set at
+.set reorder
+
diff --git a/libn64/os/asm/exception.s b/libn64/os/asm/exception.s
new file mode 100644 (file)
index 0000000..62bf77e
--- /dev/null
@@ -0,0 +1,571 @@
+#
+# libn64/os/asm/exception.s: libn64 exception handler.
+#
+# 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/syscall.h>
+
+.section .text.libn64, "ax", @progbits
+
+.set gp=64
+.set fp=64
+.set noat
+.set noreorder
+
+.global libn64_context_restore
+.type libn64_context_restore, @function
+
+# -------------------------------------------------------------------
+#  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).
+#
+#  The address must be 8-byte aligned and in a cacheable region.
+#
+#  The assembly looks like a nightmare for a reason: instructions have been
+#  ordered carefully so as to reduce the number of stalls (e.g., load-to-
+#  use instances have been cracked where possible to prevent a pipeline stall).
+# -------------------------------------------------------------------
+.align 5
+libn64_context_restore:
+  lw $v0, 0x074($k1)
+  lw $a0, 0x00C($k1)
+  lw $a1, 0x010($k1)
+  mtlo $v0
+  lw $v0, 0x078($k1)
+  lw $a2, 0x014($k1)
+  lw $a3, 0x018($k1)
+  mthi $v0
+  lw $v0, 0x07C($k1)
+  lw $t0, 0x01C($k1)
+  lw $t1, 0x020($k1)
+  mtc0 $v0, $14
+  lw $v0, 0x084($k1)
+  lw $t2, 0x024($k1)
+  lw $t3, 0x028($k1)
+  mtc0 $v0, $10
+  lui $v1, 0xA430
+  lw $at, 0x08C($k1)
+  lw $v0, 0x080($k1)
+  sw $at, 0xC($v1)
+  mtc0 $v0, $12
+  lw $t4, 0x02C($k1)
+  lw $t5, 0x030($k1)
+  lw $t6, 0x034($k1)
+  lw $t7, 0x038($k1)
+  lw $s0, 0x03C($k1)
+  lw $s1, 0x040($k1)
+  lw $s2, 0x044($k1)
+  lw $s3, 0x048($k1)
+  lw $s4, 0x04C($k1)
+  lw $s5, 0x050($k1)
+  lw $s6, 0x054($k1)
+  lw $s7, 0x058($k1)
+  lw $t8, 0x05C($k1)
+  lw $t9, 0x060($k1)
+  lw $gp, 0x064($k1)
+  lw $sp, 0x068($k1)
+  lw $fp, 0x06C($k1)
+  srl $v0, $v0, 26
+  andi $v1, $v0, 0x8
+  bne $v1, $zero, libn64_context_restore_fpu
+  lw $ra, 0x070($k1)
+
+libn64_context_restore_done:
+  lw $at, 0x000($k1)
+  lw $v0, 0x004($k1)
+  lw $v1, 0x008($k1)
+  eret
+
+libn64_context_restore_fpu:
+  ldc1 $0, 0x090($k1)
+  ldc1 $2, 0x098($k1)
+  ldc1 $4, 0x0A0($k1)
+  ldc1 $6, 0x0A8($k1)
+  ldc1 $8, 0x0B0($k1)
+  ldc1 $10, 0x0B8($k1)
+  ldc1 $12, 0x0C0($k1)
+  ldc1 $14, 0x0C8($k1)
+  ldc1 $16, 0x0D0($k1)
+  ldc1 $18, 0x0D8($k1)
+  ldc1 $20, 0x0E0($k1)
+  ldc1 $22, 0x0E8($k1)
+  ldc1 $24, 0x0F0($k1)
+  ldc1 $26, 0x0F8($k1)
+  ldc1 $28, 0x100($k1)
+  ldc1 $30, 0x108($k1)
+  and $v0, $v0, 0x1
+  lw $v1, 0x088($k1)
+  beq $v0, $zero, libn64_context_restore_done
+  ctc1 $v1, $31
+
+  ldc1 $1, 0x110($k1)
+  ldc1 $3, 0x118($k1)
+  ldc1 $5, 0x120($k1)
+  ldc1 $7, 0x128($k1)
+  ldc1 $9, 0x130($k1)
+  ldc1 $11, 0x138($k1)
+  ldc1 $13, 0x140($k1)
+  ldc1 $15, 0x148($k1)
+  ldc1 $17, 0x150($k1)
+  ldc1 $19, 0x158($k1)
+  ldc1 $21, 0x160($k1)
+  ldc1 $23, 0x168($k1)
+  ldc1 $25, 0x170($k1)
+  ldc1 $27, 0x178($k1)
+  ldc1 $29, 0x180($k1)
+  j libn64_context_restore_done
+  ldc1 $31, 0x188($k1)
+
+.size libn64_context_restore,.-libn64_context_restore
+
+.global libn64_context_save
+.type libn64_context_save, @function
+
+# -------------------------------------------------------------------
+#  Saves the context to the address pointed to by $basereg and then branches
+#  back to the $k0 ($ra is not usable because it's part of the thread context).
+#  Before returning, the address of the thread table is loaded to $k0.
+#
+#  The address must be 8-byte aligned and in a cacheable region.
+#
+#  The assembly looks like a nightmare for a reason: instructions have been
+#  ordered carefully so as to reduce the number of stalls (e.g., back-to-
+#  back cache stores are avoided where possible to prevent a pipeline stall).
+#
+#  The CACHE operations within are there to create dirty exclusive lines:
+#  If the current line referenced by the address is dirty (and thus has valid
+#  data), it is written back to memory as would on any normal conflict hit.
+#  However, the line is not initially filled from memory with the conflicting
+#  address- it is simply initialized to the dirty state (since we're going to
+#  fill it anyways). This maximizes the use of the write buffer and prevents a
+#  lot of wasteful cache block reads during context switches.
+# -------------------------------------------------------------------
+.align 5
+libn64_context_save:
+  cache 0xD, 0x000($k1)
+  sw $at, 0x000($k1)
+  addiu $at, $k1, 0x070
+
+libn64_context_save_loop:
+  cache 0xD, 0x010($at)
+  bne $at, $k1, libn64_context_save_loop
+  addiu $at, $at, -0x10
+
+  sw $v0, 0x004($k1)
+  sw $v1, 0x008($k1)
+  sw $a0, 0x00C($k1)
+  sw $a1, 0x010($k1)
+  sw $a2, 0x014($k1)
+  sw $a3, 0x018($k1)
+  sw $t0, 0x01C($k1)
+  sw $t1, 0x020($k1)
+  mflo $v0
+  sw $t2, 0x024($k1)
+  sw $t3, 0x028($k1)
+  sw $v0, 0x074($k1)
+  mfhi $v0
+  sw $t4, 0x02C($k1)
+  sw $t5, 0x030($k1)
+  sw $v0, 0x078($k1)
+  mfc0 $at, $12
+  sw $t6, 0x034($k1)
+  sw $t7, 0x038($k1)
+  sw $at, 0x080($k1)
+  srl $at, $at, 26
+  sw $s0, 0x03C($k1)
+  lui $v1, 0xA430
+  sw $s1, 0x040($k1)
+  lw $v1, 0x00C($v1)
+  sw $s2, 0x044($k1)
+  sw $s3, 0x048($k1)
+  sw $s4, 0x04C($k1)
+  sw $s5, 0x050($k1)
+  sw $s6, 0x054($k1)
+  sw $s7, 0x058($k1)
+  sw $t8, 0x05C($k1)
+  sw $t9, 0x060($k1)
+  sw $gp, 0x064($k1)
+  sw $sp, 0x068($k1)
+  mfc0 $v0, $14
+  sw $fp, 0x06C($k1)
+  sw $v0, 0x07C($k1)
+  mfc0 $v0, $10
+  sw $ra, 0x070($k1)
+  sw $v0, 0x084($k1)
+  and $v0, $at, 0x8
+  bne $v0, $zero, libn64_context_save_fpu
+  sw $v1, 0x08C($k1)
+
+libn64_context_save_done:
+  lw $at, (libn64_thread_table)
+  jr $k0
+  addu $k0, $at, $zero
+
+libn64_context_save_fpu:
+  addiu $v1, $k1, 0x70
+
+libn64_context_save_fpu_loop:
+  cache 0xD, 0x090($v1)
+  bne $v1, $k1, libn64_context_save_fpu_loop
+  addiu $v1, $v1, -0x10
+
+  sdc1 $0, 0x090($k1)
+  sdc1 $2, 0x098($k1)
+  sdc1 $4, 0x0A0($k1)
+  sdc1 $6, 0x0A8($k1)
+  sdc1 $8, 0x0B0($k1)
+  sdc1 $10, 0x0B8($k1)
+  sdc1 $12, 0x0C0($k1)
+  sdc1 $14, 0x0C8($k1)
+  cfc1 $v0, $31
+  sdc1 $16, 0x0D0($k1)
+  sdc1 $18, 0x0D8($k1)
+  sw $v0, 0x088($v1)
+  sdc1 $20, 0x0E0($k1)
+  sdc1 $22, 0x0E8($k1)
+  sdc1 $24, 0x0F0($k1)
+  and $v0, $at, 0x1
+  sdc1 $26, 0x0F8($k1)
+  sdc1 $28, 0x100($k1)
+  beq $v0, $zero, libn64_context_save_done
+  sdc1 $30, 0x108($k1)
+  addiu $v1, $k1, 0x70
+
+libn64_context_save_fpufr_loop:
+  cache 0xD, 0x110($v1)
+  bne $v1, $k1, libn64_context_save_fpufr_loop
+  addiu $v1, $v1, -0x10
+
+  sdc1 $1, 0x110($k1)
+  sdc1 $3, 0x118($k1)
+  sdc1 $5, 0x120($k1)
+  sdc1 $7, 0x128($k1)
+  sdc1 $9, 0x130($k1)
+  sdc1 $11, 0x138($k1)
+  sdc1 $13, 0x140($k1)
+  sdc1 $15, 0x148($k1)
+  sdc1 $17, 0x150($k1)
+  sdc1 $19, 0x158($k1)
+  sdc1 $21, 0x160($k1)
+  sdc1 $23, 0x168($k1)
+  sdc1 $25, 0x170($k1)
+  sdc1 $27, 0x178($k1)
+  sdc1 $29, 0x180($k1)
+  j libn64_context_save_done
+  sdc1 $31, 0x188($k1)
+
+.size libn64_context_save,.-libn64_context_save
+
+# -------------------------------------------------------------------
+#  This part of the exception handler gets loaded directly at 0x80000180
+#  by the loader. Certain large portions of the context handler (i.e., the
+#  context switching) live with the rest of the libn64 (@ 0x80000400+) due
+#  to the fact that we only have about 0x280 bytes for the entirety of this
+#  routine without doing a lot more relocation work.
+# -------------------------------------------------------------------
+.section .exception, "ax", @progbits
+
+.global libn64_exception_handler
+.type libn64_exception_handler, @function
+
+# -------------------------------------------------------------------
+#  We have eight instructions per cache line. Before we fall out of this line,
+#  branch to one of the four specific exception handlers (either interrupt, TLB,
+#  syscall, or a catch-all for all other cases).
+# -------------------------------------------------------------------
+.align 5
+libn64_exception_handler:
+  mfc0 $k0, $13
+  andi $k1, $k0, 0x7C
+  beq $k1, $zero, libn64_exception_handler_interrupt
+  addiu $k1, $k1, -0x10
+  bltz $k1, libn64_exception_handler_tlb
+  addiu $k1, $k1, -0x10
+  beql $k1, $zero, libn64_exception_handler_syscall
+  sll $k0, $at, 0x2
+
+# -------------------------------------------------------------------
+#  Breakpoint/CpU exception handler. Also catches other (unexpected) exceptions.
+# -------------------------------------------------------------------
+.align 5
+libn64_exception_handler_infrequent:
+  addiu $k1, $k1, -0x4
+  beq $k1, $zero, libn64_exception_handler_break
+  addiu $k1, $k1, -0x8
+  bne $k1, $zero, libn64_panic
+
+# We have a CpU exception. If the coprocessor that caused this exception was
+# the FPU, then branch to the dedicated handler. Otherwise, the exception we
+# are catching is unhandled and we should panic.
+  srl $k1, $k0, 0x1C
+  andi $k1, $k1, 0x3
+  addiu $k1, $k1, -0x1
+  beql $k1, $zero, libn64_exception_handler_cpu_fpu
+  mfc0 $k0, $12
+
+# Grant the thread FPU access and return from the exception. We grant access
+# by updating the status register (to enable CP1). The status register is part
+# of the thread context and will be restored/saved at each context switch.
+libn64_exception_handler_cpu_fpu:
+  lui $k1, 0x2000
+  or $k0, $k0, $k1
+  mtc0 $k0, $12
+
+# Set the FPU control register to a default value (trap on divide by zero,
+# clear out all the cause and flag bits, set default rounding mode, etc.)
+  addiu $k0, $zero, 0x400
+  ctc0 $k0, $31
+  eret
+
+# -------------------------------------------------------------------
+#  Dumps the thread context to RDRAM and invokes libn64_panic_from_isr.
+# -------------------------------------------------------------------
+.align 5
+libn64_panic:
+  la $k0, libn64_panic_from_isr_return
+  j libn64_context_save
+  lui $k1, 0x8000
+
+libn64_panic_from_isr_return:
+  mfc0 $k0, $14
+  sw $k0, 0x074($k1)
+  j libn64_panic_from_isr
+  mfc0 $k0, $13
+
+# -------------------------------------------------------------------
+#  Breakpoint exception handler.
+# -------------------------------------------------------------------
+.align 5
+libn64_exception_handler_break:
+  bltz $k0, libn64_panic
+  mfc1 $k1, $14
+  addiu $k1, $k1, 0x4
+  mtc1 $k1, $14
+  eret
+
+# -------------------------------------------------------------------
+#  Interrupt exception handler (RCP, 64DD, timer, etc.)
+# -------------------------------------------------------------------
+.align 5
+libn64_exception_handler_interrupt:
+  andi $k1, $k0, 0x0400
+  bne $k1, $zero, libn64_exception_handler_interrupt_infrequent
+  lui $k1, 0xA430
+
+# Handle a RCP interrupt:
+libn64__exception_handler_rcp_interrupt:
+  lw $k0, 0x8($k1)
+  eret
+
+# Handle infrequent interrupts (64DD, timer, and other unhandled interrupts)
+.align 5
+libn64_exception_handler_interrupt_infrequent:
+  andi $k1, $k0, 0x8000
+  beq $k1, $zero, libn64_exception_handler_timer_interrupt
+  andi $k1, $k0, 0x0800
+  beql $k1, $zero, libn64_exception_handler_64dd_interrupt
+  lui $k0, 0xA500
+
+# 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-
+# reserved external interrupts.
+#
+# In the future, when 64DD is supported, we should move this label elsewhere.
+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.
+.align 5
+libn64_exception_handler_timer_interrupt:
+  mfc1 $k1, $11
+  mtc1 $k1, $11
+  eret
+
+# -------------------------------------------------------------------
+#  TLB exception handler (Mod, TLBL, TLBS)
+# -------------------------------------------------------------------
+.align 5
+libn64_exception_handler_tlb:
+  j libn64_panic
+  nop
+
+# -------------------------------------------------------------------
+#  Syscall exception handler.
+#
+#  Advance the exception program counter by a word so we return to
+#  the instruction following the system call (don't bother trying
+#  to support running the instruction from a branch delay slot).
+#
+#  Unfortunately, saving EPC back to CP0 would mean we would need to
+#  fetch an extra cache line's worth of instructions, so we defer the
+#  restoration of EPC to the syscall itself.
+# -------------------------------------------------------------------
+.align 5
+libn64_exception_handler_syscall:
+  addiu $k1, $k0, -(LIBN64_SYSCALL_INVALID << 2)
+  bgez $k1, libn64_panic
+.set at
+.set reorder
+  lw $k0, libn64_syscall_table($k0)
+.set noat
+.set noreorder
+  mfc0 $k1, $14
+  jr $k0
+  addiu $k1, $k1, 0x4
+
+.size libn64_exception_handler,.-libn64_exception_handler
+
+.section .exception.routines, "ax", @progbits
+
+# -------------------------------------------------------------------
+#  Inserts a thread ($k1) into a thread queue ($k0). Before calling,
+#  save $ra to 0x4($k0) if desired; it will be reloaded after the
+#  jr $ra.
+#
+#  Routine fits in 3 cache lines. The only stalling that should occur
+#  is for data accesses that miss the cache, which is unavoidable.
+#
+#  Clobbers: $k1, $a0-$a3
+# -------------------------------------------------------------------
+.global libn64_exception_handler_queue_thread
+.type libn64_exception_handler_queue_thread, @function
+.align 5
+libn64_exception_handler_queue_thread:
+
+# Increment the thread queue count, write it back.
+  lw $a0, ($k0)
+  lw $at, 0x190($k1)
+  addiu $a0, $a0, 0x1
+  sw $a0, ($k0)
+
+# Construct a new entry within the thread queue (index = $a0).
+# Branch to the while loop condition check (count > 1 check).
+  sll $a0, $a0, 0x3
+  addu $a1, $a0, $k0
+  sw $k1, 0x0($a1)
+  j libn64_exception_handler_queue_thread_loop_check
+  sw $at, 0x4($a1)
+
+# Continually swap the contents of the queue entry at index '$a0'
+# with the one at its parent as long as the parent has a priority
+# that is greater than what is rooted at index '$a0'.
+#
+# Register allocation within this loop works like so:
+#   $a0 = pos << 3, swap address for pos
+#   $a1 = parent << 3
+#   $a2 = swap address for parent
+#   $a3 = priority check, parent contents
+#   $k1 = scratch
+libn64_exception_handler_queue_thread_loop:
+  addu $a2, $a1, $k0
+  lw $a3, 0x4($a2)
+  addu $a0, $a0, $k0
+  subu $a3, $a3, $at
+  bgez $a3, libn64_exception_handler_queue_thread_finish
+  ld $a3, ($a2)
+  ld $k1, ($a0)
+  sd $a3, ($a0)
+  addu $a0, $a1, $zero
+  sd $k1, ($a2)
+
+libn64_exception_handler_queue_thread_loop_check:
+  srl $a1, $a0, 0x4
+  bgtz $a1, libn64_exception_handler_queue_thread_loop
+  sll $a1, $a1, 0x3
+
+libn64_exception_handler_queue_thread_finish:
+  jr $ra
+  lw $ra, 0x4($k0)
+
+.size libn64_exception_handler_queue_thread,. \
+    -libn64_exception_handler_queue_thread
+
+# -------------------------------------------------------------------
+#  Pop the next highest-priority thread off $k0's thread queue. At
+#  least one thread must be in the queue (or bad things happen). On
+#  return, the removed thread is loaded to $k1. Before calling, save
+#  $ra to 0x4($k0) if desired; it will be reloaded after the jr $ra.
+#
+#  Routine fits in 4 cache lines. The one load-after-use stall seems
+#  to be unavoidable and causes an unnecessary one cycle delay once
+#  per loop. Other than that, the only stalls are due to data
+#  accesses that miss the cache.
+#
+#  Clobbers: $a0-$a3
+# -------------------------------------------------------------------
+.global libn64_exception_handler_dequeue_thread
+.type libn64_exception_handler_dequeue_thread, @function
+.align 5
+libn64_exception_handler_dequeue_thread:
+
+# Decrement the thread count, write it back.
+# Take the value of count /before/ decrementing it.
+  lw $a0, ($k0)
+  lw $k1, 0x8($k0)
+  addiu $a1, $a0, 0x1
+  sw $a1, ($k0)
+
+# Promote the last thing in the heap to the front.
+  sll $a0, $a0, 0x3
+  addu $a1, $a0, $k0
+  ld $a2, ($a1)
+  addiu $a1, $zero, 0x8
+  sd $s2, 0x8($k0)
+
+# Drop the root node down into the heap.
+#
+# Register allocation within this loop works like so:
+#   $a0 = count << 3
+#   $a1 = pos << 3, swap address for pos
+#   $a2 = left/right << 3), swap address for left/right
+#   $a3 = index check << 3, priority check, scratch
+#   $at = scratch
+libn64_exception_handler_dequeue_thread_loop:
+  addu $a2, $a1, $a1
+  subu $a3, $a2, $a0
+  bgtz $a3, libn64_exception_handler_dequeue_thread_finish
+  addu $a1, $a1, $k0
+  bgez $a3, libn64_exception_handler_dequeue_thread_leftonly
+  addu $a2, $a2, $k0
+
+# Check which child (left or right) has a higher priority.
+# Pick that child as the potential swap candidate.
+  lw $a3, 0x4($a2)
+  lw $at, 0xC($a2) # stall; use after load
+  subu $at, $at, $a3
+  bgtzl $at, libn64_exception_handler_dequeue_thread_leftonly
+  addiu $a2, $a2, 0x8
+
+libn64_exception_handler_dequeue_thread_leftonly:
+  lw $a3, 0x4($a2)
+  lw $at, 0x4($a1)
+  subu $at, $at, $a3
+  bgtz $at, libn64_exception_handler_dequeue_thread_finish
+  ld $at, ($a2)
+  ld $a3, ($a1)
+  sd $at, ($a1)
+  subu $a1, $a2, $k0
+  j libn64_exception_handler_dequeue_thread_loop
+  sd $a3, ($a2)
+
+libn64_exception_handler_dequeue_thread_finish:
+  jr $ra
+  lw $ra, 0x4($k0)
+
+.size libn64_exception_handler_dequeue_thread,. \
+    -libn64_exception_handler_dequeue_thread
+
+.set gp=default
+.set fp=default
+.set at
+.set reorder
+
diff --git a/libn64/os/asm/syscall.s b/libn64/os/asm/syscall.s
new file mode 100644 (file)
index 0000000..ce0b38d
--- /dev/null
@@ -0,0 +1,107 @@
+#
+# libn64/os/asm/syscall.s: libn64 syscalls.
+#
+# 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>
+       
+.section .text.libn64, "ax", @progbits
+
+.set noat
+.set noreorder
+
+# -------------------------------------------------------------------
+#  libn64::thread_create
+#    $a0 = entrypoint
+#    $a1 = argument
+#    $a2 = priority
+# -------------------------------------------------------------------
+.global libn64_syscall_thread_create
+.type libn64_syscall_thread_create, @function
+.align 5
+libn64_syscall_thread_create:
+  la $k0, libn64_syscall_thread_create_aftersave
+  mtc0 $k1, $14
+  lw $k1, (libn64_thread_table)
+  j libn64_context_save
+  lw $k1, 0x8($k1)
+
+# Grab the next available thread, set it's priority.
+libn64_syscall_thread_create_aftersave:
+  lw $k1, ((LIBN64_THREADS_MAX + 1) * 0x8 + 0x4)($k0)
+  addiu $k1, $k1, -0x1
+  sw $k1, ((LIBN64_THREADS_MAX + 1) * 0x8 + 0x4)($k0)
+
+  sll $k1, $k1, 0x2
+  addu $k1, $k1, $k0
+  lw $k1, ((LIBN64_THREADS_MAX + 1) * 0x8 + 0x8)($k1)
+
+# Set the thread's priority, stack, $gp, and coprocessor status.
+  sw $a2, 0x190($k1)
+  #cache 0xD, 0x060($k1)
+  #lui $at, 0x8000
+  #sw $at, 0x068($k1)
+  la $at, _gp
+  sw $at, 0x064($k1)
+  addiu $at, $zero, 0x402
+  cache 0xD, 0x080($k1)
+  sw $at, 0x080($k1)
+
+# Store the thread's argument to $a0 so that it can be accessed upon start.
+# Store the thread's entrypoint to the exception return address register.
+# Set the return address from thread entrypoint to libn64_thread_exit.
+  cache 0xD, 0x000($k1)
+  sw $a1, 0x00C($k1)
+  cache 0xD, 0x070($k1)
+  sw $a0, 0x07C($k1)
+  la $at, libn64_thread_exit
+
+# Insert the thread into the ready queue and load up the next thread.
+  sw $ra, 0x4($k0)
+  jal libn64_exception_handler_queue_thread
+  sw $at, 0x070($k1)
+
+  j libn64_context_restore
+  lw $k1, 0x8($k0)
+
+.size libn64_syscall_thread_create,.-libn64_syscall_thread_create
+
+# -------------------------------------------------------------------
+#  libn64::thread_exit
+# -------------------------------------------------------------------
+.global libn64_syscall_thread_exit
+.type libn64_syscall_thread_exit, @function
+.align 5
+
+libn64_syscall_thread_exit:
+  lw $k0, (libn64_thread_table)
+  sw $ra, 0x4($k0)
+  jal libn64_exception_handler_dequeue_thread
+  mtc0 $k1, $14
+  j libn64_context_restore
+  lw $k1, 0x8($k0)
+
+.size libn64_syscall_thread_exit,.-libn64_syscall_thread_exit
+
+.set at
+.set reorder
+
+.section       .rodata
+
+# -------------------------------------------------------------------
+#  System call table.
+# -------------------------------------------------------------------
+.global libn64_syscall_table
+.type  libn64_syscall_table, @object
+.align 4
+libn64_syscall_table:
+.long  libn64_syscall_thread_create
+.long  libn64_syscall_thread_exit
+
+.size  libn64_syscall_table,.-libn64_syscall_table
+
diff --git a/libn64/os/asm/thread.s b/libn64/os/asm/thread.s
new file mode 100644 (file)
index 0000000..f9641b4
--- /dev/null
@@ -0,0 +1,32 @@
+#
+# libn64/os/asm/thread.s: libn64 thread syscalls.
+#
+# 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>
+       
+.section .text.libn64, "ax", @progbits
+
+.set noat
+.set noreorder
+
+# -------------------------------------------------------------------
+#  Terminates the actively running thread.
+# -------------------------------------------------------------------
+.global libn64_thread_exit
+.type libn64_thread_exit, @function
+.align 1
+libn64_thread_exit:
+  addiu $at, $zero, 0x1
+  syscall
+
+.size  libn64_thread_exit,.-libn64_thread_exit
+
+.set at
+.set reorder
+
diff --git a/libn64/os/fbtext.c b/libn64/os/fbtext.c
new file mode 100644 (file)
index 0000000..da0ff19
--- /dev/null
@@ -0,0 +1,566 @@
+//
+// libn64/os/fbtext.c: Framebuffer text routines.
+//
+// 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>
+#include <os/fbtext.h>
+#include <stdint.h>
+
+// Methods for rendering a character to a 16 or 32-bit RGBA framebuffer.
+// These methods do not handle escape characters and do not advance x/y.
+// They are strictly intended to render a character.
+libn64func
+static inline const uint8_t *get_font_data(char c);
+
+libn64func
+static unsigned libn64_fbchar16(const struct libn64_fbtext_context *context,
+    uint32_t fb_address, char c);
+
+libn64func
+static unsigned libn64_fbchar32(const struct libn64_fbtext_context *context,
+    uint32_t fb_address, char c);
+
+// 95 member font table; starts with the ' ' char.
+// This blob is licensed under the public domain.
+const uint8_t *get_font_data(char c) {
+  static const uint8_t libn64_font_table[] = {
+    // <space>
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // '!'
+    0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+    0x10,0x10,0x00,0x00,0x10,0x10,0x00,0x00,
+  
+    // '"'
+    0x24,0x24,0x24,0x24,0x24,0x24,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // '#'
+    0x24,0x24,0x24,0x24,0x7E,0x7E,0x24,0x24,
+    0x7E,0x7E,0x24,0x24,0x24,0x24,0x00,0x00,
+  
+    // '$'
+    0x10,0x10,0x3C,0x3C,0x50,0x50,0x38,0x38,
+    0x14,0x14,0x78,0x78,0x10,0x10,0x00,0x00,
+  
+    // '%'
+    0x00,0x00,0x62,0x62,0x64,0x64,0x08,0x08,
+    0x10,0x10,0x26,0x26,0x46,0x46,0x00,0x00,
+  
+    // '&'
+    0x30,0x30,0x48,0x48,0x48,0x48,0x30,0x30,
+    0x4A,0x4A,0x44,0x44,0x3A,0x3A,0x00,0x00,
+  
+    // '''
+    0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // '('
+    0x10,0x10,0x20,0x20,0x40,0x40,0x40,0x40,
+    0x40,0x40,0x20,0x20,0x10,0x10,0x00,0x00,
+  
+    // ')'
+    0x10,0x10,0x08,0x08,0x04,0x04,0x04,0x04,
+    0x04,0x04,0x08,0x08,0x10,0x10,0x00,0x00,
+  
+    // '*'
+    0x10,0x10,0x54,0x54,0x38,0x38,0x10,0x10,
+    0x38,0x38,0x54,0x54,0x10,0x10,0x00,0x00,
+  
+    // '+'
+    0x00,0x00,0x10,0x10,0x10,0x10,0x7C,0x7C,
+    0x10,0x10,0x10,0x10,0x00,0x00,0x00,0x00,
+  
+    // ','
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x08,0x08,0x08,0x08,0x10,0x10,0x00,0x00,
+  
+    // '-'
+    0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x7E,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // '.'
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x10,0x10,0x00,0x00,
+  
+    // '/'
+    0x00,0x00,0x02,0x02,0x04,0x04,0x08,0x08,
+    0x10,0x10,0x20,0x20,0x40,0x40,0x00,0x00,
+  
+    // '0'
+    0x3C,0x3C,0x42,0x42,0x46,0x46,0x5A,0x5A,
+    0x62,0x62,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // '1'
+    0x08,0x08,0x18,0x18,0x08,0x08,0x08,0x08,
+    0x08,0x08,0x08,0x08,0x1C,0x1C,0x00,0x00,
+  
+    // '2'
+    0x3C,0x3C,0x42,0x42,0x02,0x02,0x1C,0x1C,
+    0x20,0x20,0x40,0x40,0x7E,0x7E,0x00,0x00,
+  
+    // '3'
+    0x7E,0x7E,0x02,0x02,0x04,0x04,0x1C,0x1C,
+    0x02,0x02,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // '4'
+    0x04,0x04,0x0C,0x0C,0x14,0x14,0x24,0x24,
+    0x7E,0x7E,0x04,0x04,0x04,0x04,0x00,0x00,
+  
+    // '5'
+    0x7E,0x7E,0x40,0x40,0x7C,0x7C,0x02,0x02,
+    0x02,0x02,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // '6'
+    0x1E,0x1E,0x20,0x20,0x40,0x40,0x7C,0x7C,
+    0x42,0x42,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // '7'
+    0x7E,0x7E,0x02,0x02,0x04,0x04,0x08,0x08,
+    0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
+  
+    // '8'
+    0x3C,0x3C,0x42,0x42,0x42,0x42,0x3C,0x3C,
+    0x42,0x42,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // '9'
+    0x3C,0x3C,0x42,0x42,0x42,0x42,0x3E,0x3E,
+    0x02,0x02,0x04,0x04,0x78,0x78,0x00,0x00,
+  
+    // ':'
+    0x00,0x00,0x00,0x00,0x10,0x10,0x00,0x00,
+    0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // ';'
+    0x00,0x00,0x00,0x00,0x08,0x08,0x00,0x00,
+    0x08,0x08,0x08,0x08,0x10,0x00,0x00,0x00,
+  
+    // '<'
+    0x04,0x04,0x08,0x08,0x10,0x10,0x20,0x20,
+    0x10,0x10,0x08,0x08,0x04,0x04,0x00,0x00,
+  
+    // '='
+    0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,
+    0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // '>'
+    0x20,0x20,0x10,0x10,0x08,0x08,0x04,0x04,
+    0x08,0x08,0x10,0x10,0x20,0x20,0x00,0x00,
+  
+    // '?'
+    0x20,0x20,0x40,0x40,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // '@'
+    0x3C,0x3C,0x42,0x42,0x4A,0x4A,0x56,0x56,
+    0x4C,0x4C,0x40,0x40,0x3E,0x3E,0x00,0x00,
+  
+    // 'A'
+    0x18,0x18,0x24,0x24,0x42,0x42,0x42,0x42,
+    0x7E,0x7E,0x42,0x42,0x42,0x42,0x00,0x00,
+  
+    // 'B'
+    0x7C,0x7C,0x42,0x42,0x42,0x42,0x7C,0x7C,
+    0x42,0x42,0x42,0x42,0x7C,0x7C,0x00,0x00,
+  
+    // 'C'
+    0x3C,0x3C,0x42,0x42,0x40,0x40,0x40,0x40,
+    0x40,0x40,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // 'D'
+    0x7C,0x7C,0x42,0x42,0x42,0x42,0x42,0x42,
+    0x42,0x42,0x42,0x42,0x7C,0x7C,0x00,0x00,
+  
+    // 'E'
+    0x7E,0x7E,0x40,0x40,0x40,0x40,0x7C,0x7C,
+    0x40,0x40,0x40,0x40,0x7E,0x7E,0x00,0x00,
+  
+    // 'F'
+    0x7E,0x7E,0x40,0x40,0x40,0x40,0x7C,0x7C,
+    0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,
+  
+    // 'G'
+    0x3E,0x3E,0x40,0x40,0x40,0x40,0x40,0x4E,
+    0x4E,0x42,0x42,0x42,0x3E,0x3E,0x00,0x00,
+  
+    // 'H'
+    0x42,0x42,0x42,0x42,0x42,0x42,0x7E,0x7E,
+    0x42,0x42,0x42,0x42,0x42,0x42,0x00,0x00,
+  
+    // 'I'
+    0x38,0x38,0x10,0x10,0x10,0x10,0x10,0x10,
+    0x10,0x10,0x10,0x10,0x38,0x38,0x00,0x00,
+  
+    // 'J'
+    0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+    0x02,0x02,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // 'K'
+    0x42,0x42,0x44,0x44,0x48,0x48,0x70,0x70,
+    0x48,0x48,0x44,0x44,0x42,0x42,0x00,0x00,
+  
+    // 'L'
+    0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
+    0x40,0x40,0x40,0x40,0x7E,0x7E,0x00,0x00,
+  
+    // 'M'
+    0x42,0x42,0x66,0x66,0x5A,0x5A,0x5A,0x5A,
+    0x42,0x42,0x42,0x42,0x42,0x42,0x00,0x00,
+  
+    // 'N'
+    0x42,0x42,0x62,0x62,0x52,0x52,0x5A,0x5A,
+    0x4A,0x4A,0x46,0x46,0x42,0x42,0x00,0x00,
+  
+    // 'O'
+    0x3C,0x3C,0x42,0x42,0x42,0x42,0x42,0x42,
+    0x42,0x42,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // 'P'
+    0x7C,0x7C,0x42,0x42,0x42,0x42,0x7C,0x7C,
+    0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,
+  
+    // 'Q'
+    0x3C,0x3C,0x42,0x42,0x42,0x42,0x42,0x42,
+    0x4A,0x4A,0x44,0x44,0x3A,0x3A,0x00,0x00,
+  
+    // 'R'
+    0x7C,0x7C,0x42,0x42,0x42,0x42,0x7C,0x7C,
+    0x48,0x48,0x44,0x44,0x42,0x42,0x00,0x00,
+  
+    // 'S'
+    0x3C,0x3C,0x42,0x42,0x40,0x40,0x3C,0x3C,
+    0x02,0x02,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // 'T'
+    0x7C,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,
+    0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
+  
+    // 'U'
+    0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,
+    0x42,0x42,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // 'V'
+    0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,
+    0x42,0x42,0x24,0x24,0x18,0x18,0x00,0x00,
+  
+    // 'W'
+    0x42,0x42,0x42,0x42,0x42,0x42,0x5A,0x5A,
+    0x5A,0x5A,0x66,0x66,0x42,0x42,0x00,0x00,
+  
+    // 'X'
+    0x42,0x42,0x42,0x42,0x24,0x24,0x18,0x18,
+    0x24,0x24,0x42,0x42,0x42,0x42,0x00,0x00,
+  
+    // 'Y'
+    0x44,0x44,0x44,0x44,0x28,0x28,0x10,0x10,
+    0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
+  
+    // 'Z'
+    0x7E,0x7E,0x02,0x02,0x04,0x04,0x18,0x18,
+    0x20,0x20,0x40,0x40,0x7E,0x7E,0x00,0x00,
+  
+    // '['
+    0x7E,0x7E,0x60,0x60,0x60,0x60,0x60,0x60,
+    0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,
+  
+    // <backslash>
+    0x00,0x00,0x40,0x40,0x20,0x20,0x10,0x10,
+    0x08,0x08,0x04,0x04,0x02,0x02,0x00,0x00,
+  
+    // ']'
+    0x7E,0x7E,0x06,0x06,0x06,0x06,0x06,0x06,
+    0x06,0x06,0x06,0x06,0x7E,0x7E,0x00,0x00,
+  
+    // '^'
+    0x00,0x00,0x00,0x00,0x10,0x10,0x28,0x28,
+    0x44,0x44,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // '_'
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,
+  
+    // '`'
+    0x20,0x20,0x10,0x10,0x08,0x08,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  
+    // 'a'
+    0x00,0x00,0x00,0x00,0x3C,0x3C,0x02,0x02,
+    0x3E,0x3E,0x42,0x42,0x3E,0x3E,0x00,0x00,
+  
+    // 'b'
+    0x40,0x40,0x40,0x40,0x7C,0x7C,0x42,0x42,
+    0x42,0x42,0x42,0x42,0x7C,0x7C,0x00,0x00,
+  
+    // 'c'
+    0x00,0x00,0x00,0x00,0x3E,0x3E,0x40,0x40,
+    0x40,0x40,0x40,0x40,0x3E,0x3E,0x00,0x00,
+  
+    // 'd'
+    0x02,0x02,0x02,0x02,0x3E,0x3E,0x42,0x42,
+    0x42,0x42,0x42,0x42,0x3E,0x3E,0x00,0x00,
+  
+    // 'e'
+    0x00,0x00,0x00,0x00,0x3C,0x3C,0x42,0x42,
+    0x7E,0x7E,0x40,0x40,0x3E,0x3E,0x00,0x00,
+  
+    // 'f'
+    0x1C,0x1C,0x22,0x22,0x20,0x20,0x7C,0x7C,
+    0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x00,
+  
+    // 'g'
+    0x00,0x00,0x00,0x00,0x3C,0x3C,0x42,0x42,
+    0x42,0x42,0x3E,0x3E,0x02,0x02,0x3C,0x3C,
+  
+    // 'h'
+    0x40,0x40,0x40,0x40,0x7C,0x7C,0x42,0x42,
+    0x42,0x42,0x42,0x42,0x42,0x42,0x00,0x00,
+  
+    // 'i'
+    0x10,0x10,0x00,0x00,0x30,0x30,0x10,0x10,
+    0x10,0x10,0x10,0x10,0x38,0x38,0x00,0x00,
+  
+    // 'j'
+    0x04,0x04,0x00,0x00,0x3C,0x3C,0x04,0x04,
+    0x04,0x04,0x04,0x04,0x44,0x44,0x38,0x38,
+  
+    // 'k'
+    0x40,0x40,0x40,0x40,0x42,0x42,0x44,0x44,
+    0x78,0x78,0x44,0x44,0x42,0x42,0x00,0x00,
+  
+    // 'l'
+    0x30,0x30,0x10,0x10,0x10,0x10,0x10,0x10,
+    0x10,0x10,0x10,0x10,0x38,0x38,0x00,0x00,
+  
+    // 'm'
+    0x00,0x00,0x00,0x00,0x66,0x66,0x5A,0x5A,
+    0x5A,0x5A,0x5A,0x5A,0x42,0x42,0x00,0x00,
+  
+    // 'n'
+    0x00,0x00,0x00,0x00,0x7C,0x7C,0x42,0x42,
+    0x42,0x42,0x42,0x42,0x42,0x42,0x00,0x00,
+  
+    // 'o'
+    0x00,0x00,0x00,0x00,0x3C,0x3C,0x42,0x42,
+    0x42,0x42,0x42,0x42,0x3C,0x3C,0x00,0x00,
+  
+    // 'p'
+    0x00,0x00,0x00,0x00,0x7C,0x7C,0x42,0x42,
+    0x42,0x42,0x7C,0x7C,0x40,0x40,0x40,0x40,
+  
+    // 'q'
+    0x00,0x00,0x00,0x00,0x3E,0x3E,0x42,0x42,
+    0x42,0x42,0x3E,0x3E,0x02,0x02,0x02,0x02,
+  
+    // 'r'
+    0x00,0x00,0x00,0x00,0x5E,0x5E,0x60,0x60,
+    0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,
+  
+    // 's'
+    0x00,0x00,0x00,0x00,0x3E,0x3E,0x40,0x40,
+    0x3C,0x3C,0x02,0x02,0x7C,0x7C,0x00,0x00,
+  
+    // 't'
+    0x10,0x10,0x10,0x10,0x7C,0x7C,0x10,0x10,
+    0x10,0x10,0x12,0x12,0x0C,0x0C,0x00,0x00,
+  
+    // 'u'
+    0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,
+    0x42,0x42,0x46,0x46,0x3A,0x3A,0x00,0x00,
+  
+    // 'v'
+    0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,
+    0x42,0x42,0x24,0x24,0x18,0x18,0x00,0x00,
+  
+    // 'w'
+    0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,
+    0x5A,0x5A,0x5A,0x5A,0x66,0x66,0x00,0x00,
+  
+    // 'x'
+    0x00,0x00,0x00,0x00,0x42,0x42,0x24,0x24,
+    0x18,0x18,0x24,0x24,0x42,0x42,0x00,0x00,
+  
+    // 'y'
+    0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,
+    0x42,0x42,0x3E,0x3E,0x02,0x02,0x3C,0x3C,
+  
+    // 'z'
+    0x00,0x00,0x00,0x00,0x7E,0x7E,0x04,0x04,
+    0x18,0x18,0x20,0x20,0x7E,0x7E,0x00,0x00,
+  
+    // '{'
+    0x0E,0x0E,0x18,0x18,0x18,0x18,0x70,0x70,
+    0x18,0x18,0x18,0x18,0x0E,0x0E,0x00,0x00,
+  
+    // '|'
+    0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+    0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
+  
+    // '}'
+    0x70,0x70,0x18,0x18,0x18,0x18,0x0E,0x0E,
+    0x18,0x18,0x18,0x18,0x70,0x70,0x00,0x00,
+  
+    // '~'
+    0x00,0x00,0x00,0x00,0x24,0x24,0x54,0x54,
+    0x48,0x48,0x00,0x00,0x00,0x00,0x00,0x00,
+  };
+  
+  return libn64_font_table + (c << 4) - (' ' << 4);
+}
+
+unsigned libn64_fbchar16(const struct libn64_fbtext_context *context,
+    uint32_t fb_address, char c) {
+       const uint8_t *font = get_font_data(c);
+       unsigned i, j;
+
+  fb_address += (context->x << 4);
+
+       for (i = 0; i < 16; i++) {
+               uint8_t bitmask = font[i];
+
+    // Flush line contents if valid; otherwise flag it dirty.
+    // This prevents an otherwise redundant read from memory.
+    __asm__ __volatile__(
+      "cache 0xD, 0x0(%0)\n\t"
+      :: "r"(fb_address)
+    );
+
+               for (fb_address += 8 << 1, j = 0; j < 8; j++) {
+                       unsigned bit = (bitmask >> j) & 0x1;
+                       uint16_t color = context->colors[bit];
+
+                       __asm__ __volatile__(
+                               "sh %0, -2(%1)\n\t"
+                               :: "r"(color), "r"(fb_address)
+        : "memory"
+                       );
+
+                       fb_address -= 2;
+               }
+
+    // Ensure the line gets written to memory.
+    __asm__ __volatile__(
+      "cache 0x19, 0x0(%0)\n\t"
+      :: "r"(fb_address)
+    );
+
+    fb_address += context->fb_width;
+       }
+
+  return 1;
+}
+
+unsigned libn64_fbchar32(const struct libn64_fbtext_context *context,
+    uint32_t fb_address, char c) {
+       const uint8_t *font = get_font_data(c);
+       unsigned i, j;
+
+  fb_address += (context->x << 5);
+
+       for (i = 0; i < 16; i++) {
+               uint8_t bitmask = font[i];
+
+    // Flush line contents if valid; otherwise flag it dirty.
+    // This prevents an otherwise redundant read from memory.
+    __asm__ __volatile__(
+      "cache 0xD, 0x0(%0)\n\t"
+      "cache 0xD, 0x10(%0)\n\t"
+      :: "r"(fb_address)
+    );
+
+               for (fb_address += 8 << 2, j = 0; j < 8; j ++) {
+                       unsigned bit = (bitmask >> j) & 0x1;
+                       uint16_t color = context->colors[bit];
+
+                       __asm__ __volatile__(
+                               "sw %0, -4(%1)\n\t"
+                               :: "r"(color), "r"(fb_address)
+        : "memory"
+                       );
+
+                       fb_address -= 4;
+               }
+
+    // Ensure the line gets written to memory.
+    __asm__ __volatile__(
+      "cache 0x19, 0x0(%0)\n\t"
+      "cache 0x19, 0x10(%0)\n\t"
+      :: "r"(fb_address)
+    );
+
+
+               fb_address += context->fb_width;
+       }
+
+  return 2;
+}
+
+void libn64_fbtext_init(struct libn64_fbtext_context *context,
+    uint32_t fb_origin, uint32_t fg_color, uint32_t bg_color,
+    uint16_t fb_width, enum libn64_fbtext_mode mode) {
+  context->colors[LIBN64_FBTEXT_COLOR_BG] = bg_color;
+  context->colors[LIBN64_FBTEXT_COLOR_FG] = fg_color;
+
+  context->fb_origin = fb_origin | 0x80000000;
+  context->x = 0;
+  context->y = 0;
+
+  if (mode == LIBN64_FBTEXT_16BPP) {
+    context->render_char = libn64_fbchar16;
+    context->fb_width = fb_width << 1;
+  } else {
+    context->render_char = libn64_fbchar32;
+    context->fb_width = fb_width << 2;
+  }
+}
+
+void libn64_fbtext_puts(struct libn64_fbtext_context *context,
+    const char *string) {
+  uint32_t fb_address;
+  unsigned i;
+
+  for (i = 0; string[i] != '\0'; i++) {
+    unsigned mode;
+
+    switch (string[i]) {
+      case '\n':
+        context->x = 0;
+        context->y++;
+        continue;
+
+      default:
+        break;
+    }
+
+    fb_address = context->fb_origin + context->fb_width * (context->y << 4);
+    mode = context->render_char(context, fb_address, string[i]);
+    context->x++;
+
+    if ((context->x + 1) > context->fb_width / (8 << mode)) {
+      context->x = 0;
+      context->y++;
+    }
+  }
+}
+
+void libn64_fbtext_putu32(struct libn64_fbtext_context *context,
+    uint32_t u32) {
+  char string[9];
+  int i;
+
+  for (string[8] = '\0', i = 7; i >= 0; i--) {
+    uint8_t byte = u32 & 0xF;
+
+    string[i] = byte + (byte < 0xA ? '0' : ('A' - 10));
+    u32 >>= 4;
+  }
+
+  libn64_fbtext_puts(context, string);
+}
+
diff --git a/libn64/os/kthread.c b/libn64/os/kthread.c
new file mode 100644 (file)
index 0000000..21fe76b
--- /dev/null
@@ -0,0 +1,17 @@
+//
+// 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();
+}
+
diff --git a/libn64/os/main.c b/libn64/os/main.c
new file mode 100644 (file)
index 0000000..84d3cfb
--- /dev/null
@@ -0,0 +1,33 @@
+//
+// libn64/os/main.c: libn64 C entry point.
+//
+// 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>
+#include <os/kthread.h>
+#include <os/thread.h>
+#include <os/thread_table.h>
+#include <os/syscall.h>
+#include <stddef.h>
+
+void main(void *);
+
+libn64func
+__attribute__((noreturn)) void libn64_main(void) {
+  libn64_thread_init();
+
+  // Hand control over to the application.
+  // Set default thread stack addresses until we get something better.
+  // Give each thread a 4K stack starting at 1MB (except the current thread).
+  libn64_thread_table->free_list[LIBN64_THREADS_MAX - 2]->state.regs[0x68/4] = 0x80180000;
+  libn64_thread_create(main, NULL, LIBN64_THREAD_MIN_PRIORITY + 1);
+
+  // This thread becomes the kernel thread.
+  libn64_kthread();
+}
+
diff --git a/libn64/os/panic.c b/libn64/os/panic.c
new file mode 100644 (file)
index 0000000..8b39777
--- /dev/null
@@ -0,0 +1,158 @@
+//
+// libn64/os/panic.c: Fatal crash handler.
+//
+// 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>
+#include <os/fbtext.h>
+#include <os/panic.h>
+#include <rcp/vi.h>
+
+// TODO: Detect non-NTSC N64s and adjust accordingly...
+static const vi_state_t libn64_panic_vi_state = {
+  0x0000324E, // status
+  0x003DA800, // origin
+  0x00000140, // width
+  0x00000002, // intr
+  0x00000000, // current
+  0x03E52239, // burst
+  0x0000020D, // v_sync
+  0x00000C15, // h_sync
+  0x0C150C15, // leap
+  0x006C02EC, // h_start
+  0x002501FF, // v_start
+  0x000E0204, // v_burst
+  0x00000200, // x_scale
+  0x00000400, // y_scale
+};
+
+void libn64_panic_from_isr(void) {
+  static const char *exception_strings[32] = {
+    "Interrupt",
+    "TLB Modification",
+    "TLB Miss (Load/Fetch)",
+    "TLB Miss (Store)",
+    "Address Error (Load/Fetch)",
+    "Address Error (Store)",
+    "Bus Error (Fetch)",
+    "Bus Error (Load/Store)",
+    "Syscall",
+    "Breakpoint",
+    "Reserved Instruction",
+    "Coprocessor Unusable",
+    "Arithmetic Overflow",
+    "Trap",
+    "Reserved",
+    "Floating-Point",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Watch",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved"
+  };
+
+  static const char *gp_register_strs[] = {
+    "$at:",
+    " $v0:",
+    " $v1:",
+    "\n $a0:",
+
+    " $a1:",
+    " $a2:",
+    "\n $a3:",
+    " $t0:",
+
+    " $t1:",
+    "\n $t2:",
+    " $t3:",
+    " $t4:",
+
+    "\n $t5:",
+    " $t6:",
+    " $t7:",
+    "\n $s0:",
+
+    " $s1:",
+    " $s2:",
+    "\n $s3:",
+    " $s4:",
+
+    " $s5:",
+    "\n $s6:",
+    " $s7:",
+    " $t8:",
+
+    "\n $t9:",
+    " $gp:",
+    " $sp:",
+    "\n $fp:",
+
+    " $ra:",
+    " $pc:"
+  };
+
+  struct libn64_fbtext_context fbtext;
+  uint32_t fb_cur, fb_end, i;
+
+  register uint32_t sr __asm__ ("$k0");
+  register uint32_t *context __asm__("$k1");
+
+  // Wipe the framebuffer to black.
+  fb_cur = libn64_panic_vi_state.origin | 0x80000000;
+  fb_end = fb_cur + 2 * 320 * 240;
+
+  for (; fb_cur < fb_end; fb_cur += 16) {
+    __asm__ __volatile__(
+      ".set gp=64\n\t"
+      "cache 0xD, 0x0(%[fb_cur])\n\t"
+      "sd $zero, 0x0(%[fb_cur])\n\t"
+      "sd $zero, 0x8(%[fb_cur])\n\t"
+      ".set gp=default\n\t"
+      :: [fb_cur] "r" (fb_cur)
+    );
+  }
+
+  // Dump fault and state of the processor to the framebuffer.
+  libn64_fbtext_init(&fbtext, 0x3DA800, LIBN64_FBTEXT_COLOR_WHITE,
+      LIBN64_FBTEXT_COLOR_BLACK, 0x140, LIBN64_FBTEXT_16BPP);
+
+  fbtext.x = 1; fbtext.y = 1;
+  libn64_fbtext_puts(&fbtext, exception_strings[(sr & 0x7C) >> 2]);
+  libn64_fbtext_puts(&fbtext, " Exception!\n\n ");
+
+  for (i = 0; i < sizeof(gp_register_strs) / sizeof(*gp_register_strs); i++) {
+    libn64_fbtext_puts(&fbtext, gp_register_strs[i]);
+    libn64_fbtext_putu32(&fbtext, context[i]);
+  }
+
+  // Flush the remaining, unwritten lines in cache.
+  for (fb_cur = 0x80000000; fb_cur < 0x80002000; fb_cur += 16) {
+    __asm__ __volatile__(
+      "cache 0x1, 0x0(%[fb_cur])\n\t"
+      :: [fb_cur] "r" (fb_cur)
+    );
+  }
+
+  // Flush the VI state and tie up the system.
+  vi_flush_state(&libn64_panic_vi_state);
+
+  while (1);
+  __builtin_unreachable();
+}
+
diff --git a/libn64/os/thread.c b/libn64/os/thread.c
new file mode 100644 (file)
index 0000000..5f028ba
--- /dev/null
@@ -0,0 +1,36 @@
+//
+// libn64/os/thread.c: OS thread 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>
+#include <os/thread.h>
+#include <os/thread_table.h>
+
+struct libn64_thread_table *libn64_thread_table;
+
+// Initialize the thread table.
+void libn64_thread_init(void) {
+  struct libn64_thread *self;
+  unsigned i;
+
+  // Initialize the thread stack.
+  for (i = 0; i < LIBN64_THREADS_MAX; i++)
+    libn64_thread_table->free_list[i] = libn64_thread_table->threads + i;
+
+  libn64_thread_table->free_threads = LIBN64_THREADS_MAX - 1;
+
+  // Initialize the ready thread queue and initial thread.
+  self = libn64_thread_table->threads + libn64_thread_table->free_threads;
+  libn64_thread_table->ready_queue.count = 1;
+
+  libn64_thread_table->ready_queue.heap[0].priority = LIBN64_THREAD_MIN_PRIORITY;
+  libn64_thread_table->ready_queue.heap[0].thread = self;
+  self->priority = LIBN64_THREAD_MIN_PRIORITY;
+}
+
diff --git a/libn64/rcp/vi.c b/libn64/rcp/vi.c
new file mode 100644 (file)
index 0000000..406cb69
--- /dev/null
@@ -0,0 +1,38 @@
+//
+// libn64/rcp/vi.c: VI helper 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 <rcp/vi.h>
+
+void vi_flush_state(const vi_state_t *state) {
+  uint32_t vi_region = 0xA4400000U;
+  uint32_t data, end_ptr;
+
+  __asm__ __volatile__(
+    ".set noreorder\n\t"
+    ".set gp=64\n\t"
+    "addiu %[end_ptr], %[vi_region], 0x30\n"
+
+    "1:\n\t"
+    "ld %[data], 0x30(%[state])\n\t"
+    "addiu %[state], %[state], -0x8\n\t"
+    "sw %[data], 0x4(%[end_ptr])\n\t"
+    "dsrl32 %[data], %[data], 0x0\n\t"
+    "sw %[data], 0x0(%[end_ptr])\n\t"
+    "bne %[vi_region], %[end_ptr], 1b\n\t"
+    "addiu %[end_ptr], %[end_ptr], -0x8\n\t"
+    ".set gp=default\n\t"
+    ".set reorder\n\t"
+
+    : [state] "=&r" (state), [data] "=&r" (data),
+      [end_ptr] "=&r" (end_ptr), [vi_region] "=&r" (vi_region)
+    : "0" (state), "3" (vi_region)
+  );
+}
+
diff --git a/libn64/rom.ld b/libn64/rom.ld
new file mode 100644 (file)
index 0000000..8de76cf
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * libn64/rom.ld: Linker script for ROMs.
+ *
+ * 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.
+ */
+
+OUTPUT_FORMAT("elf32-bigmips")
+OUTPUT_ARCH(mips)
+
+EXTERN(libn64_ipl)
+ENTRY(libn64_ipl)
+
+SECTIONS {
+  .exception 0xFFFFFFFF80000180 : AT (__bss_end) {
+    *(.exception)
+    *(.exception.routines)
+  }
+
+  /* TEXT */
+  .text 0xFFFFFFFF80000400 : AT (0xFFFFFFFF80000400) {
+    *(.text.libn64.ipl)
+    *(.text.libn64);
+
+    . = ALIGN(32);
+    _ftext = .;
+
+    *(.text.startup .text.startup.*)
+    *(.text.unlikely .text.*_unlikely .text.unlikely.*)
+    *(.text.exit .text.exit.*)
+    *(.text.hot .text.hot.*)
+    *(.text .stub .text.* .gnu.linkonce.t.*)
+    *(.mips16.fn.*) *(.mips16.call.*)
+  }
+
+  .fini : {
+    KEEP (*(SORT_NONE(.fini)))
+  }
+
+  . = ALIGN(32);
+  PROVIDE (__etext = .);
+  PROVIDE (_etext = .);
+  PROVIDE (etext = .);
+
+  /* DATA */
+  .rodata : {
+    *(.rodata.libn64 .rodata .rodata.* .gnu.linkonce.r.*)
+  }
+
+  .rodata1 : {
+    *(.rodata1)
+  }
+
+  .sdata2 : {
+    *(.sdata2 .sdata2.* .gnu.linkonce.s2.*)
+  }
+
+  __sbss2_start = .;
+  .sbss2 : {
+    *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*)
+  }
+  __sbss2_end = .;
+
+  .jcr : {
+    KEEP (*(.jcr))
+  }
+
+  .data.rel.ro : {
+    *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*)
+  }
+
+  .data : {
+    _fdata = . ;
+    *(.data .data.* .gnu.linkonce.d.*)
+  }
+
+  .data1 : {
+    *(.data1)
+  }
+
+  .got.plt : {
+    *(.got.plt)
+  }
+
+  HIDDEN (_gp = ALIGN(16) + 0x7FF0);
+
+  .got : {
+    *(.got)
+  }
+
+  .sdata : {
+    *(.sdata .sdata.* .gnu.linkonce.s.*)
+  }
+
+  .lit8 : {
+    *(.lit8)
+  }
+
+  .lit4 : {
+    *(.lit4)
+  }
+
+  . = ALIGN(16);
+  _edata = .; PROVIDE (edata = .);
+
+  /* BSS */
+  __bss_start = .;
+  _fbss = .;
+
+  .sbss : {
+    *(.dynsbss)
+    *(.sbss .sbss.* .gnu.linkonce.sb.*)
+    *(.scommon)
+  }
+
+  .bss : {
+    *(.dynbss)
+    *(.bss .bss.* .gnu.linkonce.b.*)
+    *(COMMON)
+  }
+
+  . = ALIGN(16);
+  __bss_end = .;
+
+  /* Everything is statically linked, so discard PLTs. */
+  /DISCARD/ : { *(.rel.iplt) *(.rela.iplt) *(.rel.plt) *(.rela.plt) *(.plt) *(.iplt) }
+
+  /* We don't make use of debugging information, so drop that, too. */
+  /DISCARD/ : { *(.debug) *(.debug_srcinfo) *(.debug_sfnames) *(.debug_aranges) *(.debug_pubnames) *(.debug_info .gnu.linkonce.wi.*) *(.debug_abbrev) *(.debug_line .debug_line.* .debug_line_end ) *(.debug_frame) *(.debug_str) *(.debug_loc) *(.debug_macinfo) *(.debug_weaknames) *(.debug_funcnames) *(.debug_typenames) *(.debug_varnames) *(.debug_pubtypes) *(.debug_ranges) *(.debug_macro) *(.mdebug.abi32) *(.mdebug.abiN32) *(.mdebug.abi64) *(.mdebug.abiO64) *(.mdebug.eabi32) *(.mdebug.eabi64) }
+
+  /* Discard things that the standard link script drops, too. */
+  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+
+  /* Don't include MIPS ABI information with the ROM. */
+  /DISCARD/ : { *(.MIPS.abiflags) }
+}
+
diff --git a/rspasm/Makefile b/rspasm/Makefile
new file mode 100644 (file)
index 0000000..2763f3e
--- /dev/null
@@ -0,0 +1,65 @@
+#
+# rspasm/Makefile: RSP assembler Makefile.
+#
+# n64chain: A (free) open-source N64 development toolchain.
+# Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+#
+# This file is subject to the terms and conditions defined in
+# 'LICENSE', which is part of this source code package.
+#
+
+ifdef SystemRoot
+FIXPATH = $(subst /,\,$1)
+RM = del /Q
+else
+FIXPATH = $1
+RM = rm -f
+endif
+
+BISON = bison
+FLEX = flex
+
+CFLAGS = -Wall -Wextra -Wno-unused-parameter -pedantic -std=c99 -I. -D_POSIX_C_SOURCE
+OPTFLAGS = -O2
+
+CFILES = \
+       emitter.c \
+       lexer.c \
+       main.c \
+       parser.c \
+       symbols.c
+
+OBJFILES = \
+       $(CFILES:.c=.o)
+
+DEPFILES = $(OBJFILES:.o=.d)
+
+all: rspasm
+
+rspasm: $(OBJFILES)
+       @echo $(call FIXPATH,"Linking: rspasm/$@")
+       @$(CC) -static $(CFLAGS) $(OPTFLAGS) $^ $(RSPASM_LIBS) -o $@
+
+parser.c: parser.y
+       @echo $(call FIXPATH,"Generating: rspasm/$@")
+       @$(BISON) -d -o $@ $<
+
+lexer.c: lexer.l parser.c
+       @echo $(call FIXPATH,"Generating: rspasm/$@")
+       @$(FLEX) --bison-bridge --yylineno --header-file=lexer.h -o $@ $<
+
+#
+# Generic compilation/assembly targets.
+#
+%.o: %.c parser.c lexer.c
+       @echo $(call FIXPATH,"Compiling: rspasm/$<")
+       @$(CC) $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
+
+#
+# Clean project target.
+#
+.PHONY: clean
+clean:
+       @echo "Cleaning rspasm..."
+       @$(RM) $(DEPFILES) $(OBJFILES) lexer.c lexer.h parser.c parser.h
+
diff --git a/rspasm/emitter.c b/rspasm/emitter.c
new file mode 100644 (file)
index 0000000..dc53771
--- /dev/null
@@ -0,0 +1,282 @@
+//
+// rspasm/emitter.c: RSP assembler code/data generator.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+#include "emitter.h"
+#include "rspasm.h"
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+
+static int rspasm_emit_data_common(
+  const struct rspasm *rspasm, const YYLTYPE *loc, size_t sz);
+
+static int rspasm_emit_instruction_common(
+  const struct rspasm *rspasm, const YYLTYPE *loc);
+
+int rspasm_dmax_assert(struct rspasm *rspasm, const YYLTYPE *loc, long int where) {
+  if (where < 0) {
+    fprintf(stderr, "line %d: "
+      ".dmax expression cannot be negative.\n",
+      loc->first_line);
+
+    return -1;
+  }
+
+  if (where > 0x1000) {
+    fprintf(stderr, "line %d: "
+      ".dmax expression must be less than or equal to 4096.\n",
+      loc->first_line);
+
+    return -1;
+  }
+
+  if ((rspasm->in_text && (long int) (rspasm->ihead - 4096) > where) ||
+    (!rspasm->in_text && (long int) (rspasm->dhead - 0000) > where)) {
+    fprintf(stderr, "line %d: "
+      ".dmax assertion failed.\n", loc->first_line);
+
+    return -1;
+  }
+
+  return 0;
+}
+
+int rspasm_emit_byte(struct rspasm *rspasm, const YYLTYPE *loc, long int byte) {
+  uint8_t ubyte;
+
+  if (rspasm_emit_data_common(rspasm, loc, sizeof(ubyte)))
+    return -1;
+
+  if (byte > UINT8_MAX || byte < INT8_MIN) {
+    fprintf(stderr, "line %d: .byte: "
+      "Value is out of range.\n", loc->first_line);
+
+    return -1;
+  }
+
+  ubyte = byte;
+  memcpy(rspasm->data + rspasm->dhead, &ubyte, sizeof(ubyte));
+  rspasm->dhead += sizeof(ubyte);
+
+  return 0;
+}
+
+int rspasm_emit_data_common(
+  const struct rspasm *rspasm, const YYLTYPE *loc, size_t sz) {
+
+  if (rspasm->in_text) {
+    fprintf(stderr, "line %d: "
+      "Attempted to emit data in .text section.\n", loc->first_line);
+
+    return -1;
+  }
+
+  if (rspasm->dhead >= (0x1000 - sz)) {
+    fprintf(stderr, "line %d: Value does not fit in DMEM.\n", loc->first_line);
+
+    return -1;
+  }
+
+  return 0;
+}
+
+int rspasm_emit_half(struct rspasm *rspasm, const YYLTYPE *loc, long int half) {
+  uint16_t uhalf;
+
+  if (rspasm_emit_data_common(rspasm, loc, sizeof(uhalf)))
+    return -1;
+
+  if (half > UINT16_MAX || half < INT16_MIN) {
+    fprintf(stderr, "line %d: .half: "
+      "Value is out of range.\n", loc->first_line);
+
+    return -1;
+  }
+
+  uhalf = htonl(half);
+  memcpy(rspasm->data + rspasm->dhead, &uhalf, sizeof(uhalf));
+  rspasm->dhead += sizeof(uhalf);
+
+  return 0;
+}
+
+int rspasm_emit_instruction_common(
+  const struct rspasm *rspasm, const YYLTYPE *loc) {
+  if (!rspasm->in_text) {
+    fprintf(stderr, "line %d: "
+      "Attempted to emit instruction in .data section.\n", loc->first_line);
+
+    return -1;
+  }
+
+  if (rspasm->ihead >= 0x2000) {
+    fprintf(stderr, "line %d: "
+      "Instruction does not fit in IMEM.\n", loc->first_line);
+
+    return -1;
+  }
+
+  return 0;
+}
+
+int rspasm_emit_instruction(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode) {
+  uint32_t iw;
+
+  if (rspasm_emit_instruction_common(rspasm, loc))
+    return -1;
+
+  iw = htonl(opcode);
+  memcpy(rspasm->data + rspasm->ihead, &iw, sizeof(iw));
+  rspasm->ihead += sizeof(iw);
+
+  return 0;
+}
+
+int rspasm_emit_instruction_ri(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rt, long imm) {
+  uint32_t iw;
+
+  if (rspasm_emit_instruction_common(rspasm, loc))
+    return -1;
+
+  if (opcode == LUI && (imm > UINT16_MAX || imm < INT16_MIN)) {
+    fprintf(stderr, "line %d: "
+      "Immediate value is out of range.\n", loc->first_line);
+
+    return -1;
+  }
+
+  iw = htonl((opcode << 26) | (rt << 16) | (imm & 0xFFFF));
+  memcpy(rspasm->data + rspasm->ihead, &iw, sizeof(iw));
+  rspasm->ihead += sizeof(iw);
+
+  return 0;
+}
+
+int rspasm_emit_instruction_ro(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rt, long offset, unsigned base) {
+  uint32_t iw;
+
+  if (rspasm_emit_instruction_common(rspasm, loc))
+    return -1;
+
+  if (offset > 0xFFF || offset < 0) {
+    fprintf(stderr, "line %d: "
+      "Offset is out of range.\n", loc->first_line);
+
+    return -1;
+  }
+
+  iw = htonl((opcode << 26) | (base << 21) | (rt << 16) | offset);
+  memcpy(rspasm->data + rspasm->ihead, &iw, sizeof(iw));
+  rspasm->ihead += sizeof(iw);
+
+  return 0;
+}
+
+int rspasm_emit_instruction_rrc0(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rt, unsigned rd) {
+  uint32_t iw;
+
+  if (rspasm_emit_instruction_common(rspasm, loc))
+    return -1;
+
+  if (opcode == MFC0 && rt == 0) {
+    fprintf(stderr, "line %d: Instruction writes to $0.\n",
+      loc->first_line);
+  }
+
+  iw = htonl((1 << 30) | (opcode << 21) | (rt << 16) | (rd << 11));
+  memcpy(rspasm->data + rspasm->ihead, &iw, sizeof(iw));
+  rspasm->ihead += sizeof(iw);
+
+  return 0;
+}
+
+int rspasm_emit_instruction_rri(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rt, unsigned rs, long imm) {
+  uint32_t iw;
+
+  // sign-extended: ADDI, ADDIU, ORI, SLTI, SLTIU
+  if (opcode == ADDI || opcode == ADDIU ||
+    opcode == ORI || opcode == SLTI || opcode == SLTIU) {
+    if (imm > UINT16_MAX || imm < INT16_MIN) {
+      fprintf(stderr, "line %d: "
+        "Immediate value is out of range.\n", loc->first_line);
+
+      return -1;
+    }
+  }
+
+  // zero-extended: ANDI, XORI
+  else {
+    if (imm > UINT16_MAX || imm < 0) {
+      fprintf(stderr, "line %d: "
+        "Immediate value is out of range.\n", loc->first_line);
+
+      return -1;
+    }
+  }
+
+  iw = htonl((opcode << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF));
+  memcpy(rspasm->data + rspasm->ihead, &iw, sizeof(iw));
+  rspasm->ihead += sizeof(iw);
+
+  return 0;
+}
+
+int rspasm_emit_instruction_rrr(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rd, unsigned rs, unsigned rt) {
+  uint32_t iw;
+
+  if (rd == 0) {
+    fprintf(stderr, "line %d: Instruction writes to $0.\n",
+      loc->first_line);
+  }
+
+  iw = htonl(opcode | (rd << 11) | (rt << 16) | (rs << 21));
+  memcpy(rspasm->data + rspasm->ihead, &iw, sizeof(iw));
+  rspasm->ihead += sizeof(iw);
+
+  return 0;
+}
+
+int rspasm_emit_word(struct rspasm *rspasm, const YYLTYPE *loc, long int word) {
+  uint32_t uword;
+
+  if (rspasm_emit_data_common(rspasm, loc, sizeof(uword)))
+    return -1;
+
+  if ((unsigned long int) word > UINT32_MAX || word < INT32_MIN) {
+    fprintf(stderr, "line %d: .word: "
+      "Value is out of range.\n", loc->first_line);
+
+    return -1;
+  }
+
+  uword = htonl(word);
+  memcpy(rspasm->data + rspasm->dhead, &uword, sizeof(uword));
+  rspasm->dhead += sizeof(uword);
+
+  return 0;
+}
+
diff --git a/rspasm/emitter.h b/rspasm/emitter.h
new file mode 100644 (file)
index 0000000..694920f
--- /dev/null
@@ -0,0 +1,51 @@
+//
+// rspasm/emitter.h: RSP assembler code/data generator.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#ifndef RSPASM_EMITTER_H
+#define RSPASM_EMITTER_H
+#define YYLTYPE RSPASMLTYPE
+#define YYSTYPE RSPASMSTYPE
+#include "opcodes.h"
+#include "parser.h"
+#include "lexer.h"
+
+struct rspasm;
+
+int rspasm_dmax_assert(struct rspasm *rspasm, const YYLTYPE *loc, long int where);
+
+int rspasm_emit_byte(struct rspasm *rspasm, const YYLTYPE *loc, long int byte);
+int rspasm_emit_half(struct rspasm *rspasm, const YYLTYPE *loc, long int half);
+int rspasm_emit_word(struct rspasm *rspasm, const YYLTYPE *loc, long int word);
+
+int rspasm_emit_instruction(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode);
+
+int rspasm_emit_instruction_ri(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rt, long imm);
+
+int rspasm_emit_instruction_ro(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rt, long offset, unsigned base);
+
+int rspasm_emit_instruction_rrc0(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rt, unsigned rd);
+
+int rspasm_emit_instruction_rri(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rt, unsigned rs, long imm);
+
+int rspasm_emit_instruction_rrr(struct rspasm *rspasm,
+  const YYLTYPE *loc, enum rsp_opcode opcode,
+  unsigned rd, unsigned rs, unsigned rt);
+
+#endif
+
diff --git a/rspasm/lexer.l b/rspasm/lexer.l
new file mode 100644 (file)
index 0000000..6a7f9f1
--- /dev/null
@@ -0,0 +1,275 @@
+%{
+//
+// rspasm/lexer.l: RSP assembler lexer.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#include "opcodes.h"
+#include "parser.h"
+#include <limits.h>
+#include <stdio.h>
+
+#define YYLTYPE RSPASMLTYPE
+#define YYSTYPE RSPASMSTYPE
+
+#define YY_EXTRA_TYPE struct rspasm_data *
+#define YY_USER_ACTION yylloc->first_line = yylloc->last_line = yylineno; \
+  yylloc->first_column = yycolumn; yylloc->last_column = yycolumn + yyleng - 1;
+
+static void get_constant_with_base(
+  YYSTYPE *val, const char *text, int lineno, unsigned base);
+
+static void get_identifier(YYSTYPE *val, const char *text, int lineno);
+static void get_register_number(YYSTYPE *val, const char *text, int lineno);
+
+%}
+
+/* Rules. */
+%option prefix="rspasm"
+%option reentrant bison-bridge
+%option bison-locations
+%option yylineno
+
+%option noyywrap
+%%
+
+"add" { yylval->opcode = ADD; return OPCODE_RRR; }
+"addi" { yylval->opcode = ADDI; return OPCODE_RRI; }
+"addiu" { yylval->opcode = ADDIU; return OPCODE_RRI; }
+"addu" { yylval->opcode = ADDU; return OPCODE_RRR; }
+"and" { yylval->opcode = AND; return OPCODE_RRR; }
+"andi" { yylval->opcode = ANDI; return OPCODE_RRI; }
+"beq" { yylval->opcode = BEQ; return OPCODE_RRT; }
+"bgez" { yylval->opcode = BGEZ; return OPCODE_RT; }
+"bgezal" { yylval->opcode = BGEZAL; return OPCODE_RT; }
+"bgtz" { yylval->opcode = BGTZ; return OPCODE_RT; }
+"blez" { yylval->opcode = BLEZ; return OPCODE_RT; }
+"bltz" { yylval->opcode = BLTZ; return OPCODE_RT; }
+"bltzal" { yylval->opcode = BLTZAL; return OPCODE_RT; }
+"bne" { yylval->opcode = BNE; return OPCODE_RRT; }
+"break" { yylval->opcode = BREAK; return OPCODE; }
+"cfc2" { yylval->opcode = CFC2; return OPCODE_RZ2; }
+"ctc2" { yylval->opcode = CTC2; return OPCODE_RZ2; }
+"j" { yylval->opcode = J; return OPCODE_T; }
+"jal" { yylval->opcode = JAL; return OPCODE_T; }
+"jalr" { yylval->opcode = JALR; return OPCODE_JALR; }
+"jr" { yylval->opcode = JR; return OPCODE_R; }
+"lb" { yylval->opcode = LB; return OPCODE_RO; }
+"lbu" { yylval->opcode = LBU; return OPCODE_RO; }
+"lbv" { yylval->opcode = LBV; return OPCODE_VO_LWC2; }
+"ldv" { yylval->opcode = LDV; return OPCODE_VO_LWC2; }
+"lfv" { yylval->opcode = LFV; return OPCODE_VO_LWC2; }
+"lh" { yylval->opcode = LH; return OPCODE_RO; }
+"lhu" { yylval->opcode = LHU; return OPCODE_RO; }
+"lhv" { yylval->opcode = LHV; return OPCODE_VO_LWC2; }
+"llv" { yylval->opcode = LLV; return OPCODE_VO_LWC2; }
+"lpv" { yylval->opcode = LPV; return OPCODE_VO_LWC2; }
+"lqv" { yylval->opcode = LQV; return OPCODE_VO_LWC2; }
+"lrv" { yylval->opcode = LRV; return OPCODE_VO_LWC2; }
+"lsv" { yylval->opcode = LSV; return OPCODE_VO_LWC2; }
+"ltv" { yylval->opcode = LTV; return OPCODE_VO_LWC2; }
+"lui" { yylval->opcode = LUI; return OPCODE_RI; }
+"luv" { yylval->opcode = LUV; return OPCODE_VO_LWC2; }
+"lw" { yylval->opcode = LW; return OPCODE_RO; }
+"lwv" { yylval->opcode = LWV; return OPCODE_VO_LWC2; }
+"mfc0" { yylval->opcode = MFC0; return OPCODE_RRC0; }
+"mfc2" { yylval->opcode = MFC2; return OPCODE_RZ2E; }
+"mtc0" { yylval->opcode = MTC0; return OPCODE_RRC0; }
+"mtc2" { yylval->opcode = MTC2; return OPCODE_RZ2E; }
+"nop" { yylval->opcode = NOP; return OPCODE; }
+"nor" { yylval->opcode = NOR; return OPCODE_RRR; }
+"or" { yylval->opcode = OR; return OPCODE_RRR; }
+"ori" { yylval->opcode = ORI; return OPCODE_RRI; }
+"sb" { yylval->opcode = SB; return OPCODE_RO; }
+"sbv" { yylval->opcode = SBV; return OPCODE_VO_SWC2; }
+"sdv" { yylval->opcode = SDV; return OPCODE_VO_SWC2; }
+"sfv" { yylval->opcode = SFV; return OPCODE_VO_SWC2; }
+"sh" { yylval->opcode = SH; return OPCODE_RO; }
+"shv" { yylval->opcode = SHV; return OPCODE_VO_SWC2; }
+"sll" { yylval->opcode = SLL; return OPCODE_RRS; }
+"sllv" { yylval->opcode = SLLV; return OPCODE_RRR; }
+"slt" { yylval->opcode = SLT; return OPCODE_RRR; }
+"slti" { yylval->opcode = SLTI; return OPCODE_RRI; }
+"sltiu" { yylval->opcode = SLTIU; return OPCODE_RRI; }
+"sltu" { yylval->opcode = SLTU; return OPCODE_RRR; }
+"slv" { yylval->opcode = SLV; return OPCODE_VO_SWC2; }
+"spv" { yylval->opcode = SPV; return OPCODE_VO_SWC2; }
+"sqv" { yylval->opcode = SQV; return OPCODE_VO_SWC2; }
+"sra" { yylval->opcode = SRA; return OPCODE_RRS; }
+"srav" { yylval->opcode = SRAV; return OPCODE_RRR; }
+"srl" { yylval->opcode = SRL; return OPCODE_RRS; }
+"srlv" { yylval->opcode = SRLV; return OPCODE_RRR; }
+"srv" { yylval->opcode = SRV; return OPCODE_VO_SWC2; }
+"ssv" { yylval->opcode = SSV; return OPCODE_VO_SWC2; }
+"stv" { yylval->opcode = STV; return OPCODE_VO_SWC2; }
+"sub" { yylval->opcode = SUB; return OPCODE_RRR; }
+"subu" { yylval->opcode = SUBU; return OPCODE_RRR; }
+"suv" { yylval->opcode = SUV; return OPCODE_VO_SWC2; }
+"sw" { yylval->opcode = SW; return OPCODE_RO; }
+"swv" { yylval->opcode = SWV; return OPCODE_VO_SWC2; }
+"vabs" { yylval->opcode = VABS; return OPCODE_VVV; }
+"vadd" { yylval->opcode = VADD; return OPCODE_VVV; }
+"vaddc" { yylval->opcode = VADDC; return OPCODE_VVV; }
+"vand" { yylval->opcode = VAND; return OPCODE_VVV; }
+"vch" { yylval->opcode = VCH; return OPCODE_VVV; }
+"vcl" { yylval->opcode = VCL; return OPCODE_VVV; }
+"vcr" { yylval->opcode = VCR; return OPCODE_VVV; }
+"veq" { yylval->opcode = VEQ; return OPCODE_VVV; }
+"vge" { yylval->opcode = VGE; return OPCODE_VVV; }
+"vlt" { yylval->opcode = VLT; return OPCODE_VVV; }
+"vmacf" { yylval->opcode = VMACF; return OPCODE_VVV; }
+"vmacq" { yylval->opcode = VMACQ; return OPCODE_VVV; }
+"vmacu" { yylval->opcode = VMACU; return OPCODE_VVV; }
+"vmadh" { yylval->opcode = VMADH; return OPCODE_VVV; }
+"vmadl" { yylval->opcode = VMADL; return OPCODE_VVV; }
+"vmadm" { yylval->opcode = VMADM; return OPCODE_VVV; }
+"vmadn" { yylval->opcode = VMADN; return OPCODE_VVV; }
+"vmov" { yylval->opcode = VMOV; return OPCODE_VV; }
+"vmrg" { yylval->opcode = VMRG; return OPCODE_VVV; }
+"vmudh" { yylval->opcode = VMUDH; return OPCODE_VVV; }
+"vmudl" { yylval->opcode = VMUDL; return OPCODE_VVV; }
+"vmudm" { yylval->opcode = VMUDM; return OPCODE_VVV; }
+"vmudn" { yylval->opcode = VMUDN; return OPCODE_VVV; }
+"vmulf" { yylval->opcode = VMULF; return OPCODE_VVV; }
+"vmulq" { yylval->opcode = VMULQ; return OPCODE_VVV; }
+"vmulu" { yylval->opcode = VMULU; return OPCODE_VVV; }
+"vnand" { yylval->opcode = VNAND; return OPCODE_VVV; }
+"vne" { yylval->opcode = VNE; return OPCODE_VVV; }
+"vnop" { yylval->opcode = VNOP; return VOPCODE; }
+"vnor" { yylval->opcode = VNOR; return OPCODE_VVV; }
+"vnxor" { yylval->opcode = VNXOR; return OPCODE_VVV; }
+"vor" { yylval->opcode = VOR; return OPCODE_VVV; }
+"vrcp" { yylval->opcode = VRCP; return OPCODE_VV; }
+"vrcph" { yylval->opcode = VRCPH; return OPCODE_VV; }
+"vrcpl" { yylval->opcode = VRCPL; return OPCODE_VV; }
+"vrndn" { yylval->opcode = VRNDN; return OPCODE_VVV; }
+"vrndp" { yylval->opcode = VRNDP; return OPCODE_VVV; }
+"vrsq" { yylval->opcode = VRSQ; return OPCODE_VV; }
+"vrsqh" { yylval->opcode = VRSQH; return OPCODE_VV; }
+"vrsql" { yylval->opcode = VRSQL; return OPCODE_VV; }
+"vsar" { yylval->opcode = VSAR; return OPCODE_VVV; }
+"vsaw" { yylval->opcode = VSAR; return OPCODE_VVV; }
+"vsub" { yylval->opcode = VSUB; return OPCODE_VVV; }
+"vsubc" { yylval->opcode = VSUBC; return OPCODE_VVV; }
+"vxor" { yylval->opcode = VXOR; return OPCODE_VVV; }
+"xor" { yylval->opcode = XOR; return OPCODE_RRR; }
+"xori" { yylval->opcode = XORI; return OPCODE_RRI; }
+
+".byte" { return DOTBYTE; }
+".data" { return DOTDATA; }
+".dmax" { return DOTDMAX; }
+".half" { return DOTHALF; }
+".text" { return DOTTEXT; }
+".word" { return DOTWORD; }
+
+"," { return COMMA; }
+":" { return COLON; }
+"[" { return LEFT_BRACKET; }
+"(" { return LEFT_PAREN; }
+"]" { return RIGHT_BRACKET;}
+")" { return RIGHT_PAREN; }
+
+0[xX][0-9A-Fa-f]* {
+  get_constant_with_base(yylval, yytext, yylineno, 16);
+  return CONSTANT;
+}
+
+[1-9][0-9]* {
+  get_constant_with_base(yylval, yytext, yylineno, 10);
+  return CONSTANT;
+}
+
+0[0-7]* {
+  get_constant_with_base(yylval, yytext, yylineno, 8);
+  return CONSTANT;
+}
+
+[a-zA-Z_][a-zA-Z0-9_]* {
+  get_identifier(yylval, yytext, yylineno);
+  return IDENTIFIER;
+}
+
+"$at" { yylval->reg = 01; return SCALAR_REG; }
+"$gp" { yylval->reg = 28; return SCALAR_REG; }
+"$sp" { yylval->reg = 29; return SCALAR_REG; }
+"$fp" { yylval->reg = 30; return SCALAR_REG; }
+"$ra" { yylval->reg = 31; return SCALAR_REG; }
+
+"$"[0-9]* {
+  get_register_number(yylval, yytext + 1, yylineno);
+  return SCALAR_REG;
+}
+
+"&" { return OP_AND; }
+"~" { return OP_BNOT; }
+"/" { return OP_DIVIDE; }
+"<<" { return OP_LSHIFT; }
+"-" { return OP_MINUS; }
+"%" { return OP_MOD; }
+"|" { return OP_OR; }
+"+" { return OP_PLUS; }
+">>" { return OP_RSHIFT; }
+"*" { return OP_TIMES; }
+"^" { return OP_XOR; }
+
+#.*$
+;.*$
+"/*" {
+  char c, c1;
+
+  while (1) {
+    while ((c = input(yyscanner)) != '*' && c != 0);
+
+    if ((c1 = input(yyscanner)) != '/' && c != 0) {
+      unput(c1);
+      continue;
+    }
+
+    break;
+  }
+}
+
+[ \t\n]
+
+%%
+
+void get_constant_with_base(YYSTYPE *val,
+  const char *text, int lineno, unsigned base) {
+  val->constant = strtol(text, NULL, base);
+
+  if (val->constant == LONG_MIN || val->constant == LONG_MAX) {
+    if (errno == ERANGE) {
+      fprintf(stderr, "line %d: "
+        "Value is out of assembler's supported range.\n", lineno);
+    }
+  }
+}
+
+void get_identifier(YYSTYPE *val, const char *text, int lineno) {
+  size_t len = strlen(text);
+
+  if (len > 31) {
+    fprintf(stderr, "line %d: "
+      "Identifier exceeds 31 characters in length.\n", lineno);
+
+    len = 31;
+  }
+
+  memcpy(val->identifier, text, len);
+  val->identifier[len] = '\0';
+}
+
+void get_register_number(YYSTYPE *val, const char *text, int lineno) {
+  long reg = strtol(text, NULL, 10);
+
+  if (reg < 0 || reg > 31)
+    fprintf(stderr, "line %d: Invalid scalar register specified.\n", lineno);
+
+  val->reg = reg;
+}
+
diff --git a/rspasm/main.c b/rspasm/main.c
new file mode 100644 (file)
index 0000000..d4cc1e4
--- /dev/null
@@ -0,0 +1,215 @@
+//
+// rspasm/main.c: RSP assembler entry point.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#define YYLTYPE RSPASMLTYPE
+#define YYSTYPE RSPASMSTYPE
+#include "parser.h"
+#include "lexer.h"
+#include "rspasm.h"
+#include "symbols.h"
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int assemble(FILE *in, FILE *out);
+static void print_usage(const char *argv0);
+static int safe_fwrite(FILE *out, const uint8_t *buf, size_t sz);
+
+int assemble(FILE *in, FILE *out) {
+  YY_BUFFER_STATE buf;
+  yyscan_t scanner;
+
+  struct rspasm rspasm;
+  int status;
+
+  if (rspasmlex_init(&scanner)) {
+    fprintf(stderr, "Failed to initialize the assembler's lexer.\n");
+    return EXIT_FAILURE;
+  }
+
+  rspasmset_extra(&rspasm, scanner);
+  memset(&rspasm, 0x00, sizeof(rspasm));
+
+  rspasm.dhead = 0x0000;
+  rspasm.ihead = 0x1000;
+
+  rspasm.first_pass = true;
+  rspasm.in_text = true;
+
+  buf = rspasm_create_buffer(in, YY_BUF_SIZE, scanner);
+  rspasm_switch_to_buffer(buf, scanner);
+  status = rspasmparse(scanner);
+
+  // First pass was successful, go for a second.
+  if (status != EXIT_FAILURE) {
+    rspasm.dhead = 0x0000;
+    rspasm.ihead = 0x1000;
+
+    rspasm.first_pass = false;
+    rspasm.in_text = true;
+
+    if (rspasm_do_symbols_pass(&rspasm))
+      status = EXIT_FAILURE;
+
+    else {
+      rewind(in);
+      status = rspasmparse(scanner);
+
+      if (status != EXIT_FAILURE) {
+        if (safe_fwrite(out, rspasm.data, sizeof(rspasm.data)))
+          status = EXIT_FAILURE;
+      }
+
+      rspasm_free_symbols(&rspasm);
+    }
+  }
+
+  rspasm_delete_buffer(buf, scanner);
+  rspasmlex_destroy(scanner);
+  return status;
+}
+
+int main(int argc, const char *argv[]) {
+  int status = EXIT_SUCCESS;
+  FILE *input, *output;
+  int i;
+
+  if (argc < 2) {
+    print_usage(argv[0]);
+    return EXIT_SUCCESS;
+  }
+
+  for (i = 1, input = NULL, output = NULL; i < argc; i++) {
+
+    // Not a filename.
+    if (argv[i][0] == '-') {
+
+      // Reading from stdin?
+      if (argv[i][1] == '\0') {
+        input = stdin;
+        continue;
+      }
+
+      // Ensure it's a -X style argument.
+      else if (argv[i][2] == '\0') {
+        switch(argv[i][1]) {
+
+          // -h: help
+          case 'h':
+            print_usage(argv[0]);
+            i = argc;
+            break;
+
+          // -o: output file
+          case 'o':
+            if ((i + 1) >= argc) {
+              fprintf(stderr, "-o requires a file path.\n");
+
+              i = argc;
+              status = EXIT_FAILURE;
+              break;
+            }
+
+            else if (output) {
+              fprintf(stderr, "Multiple outputs were specified.\n");
+
+              i = argc;
+              status = EXIT_FAILURE;
+              break;
+            }
+
+            else if (!strcmp(argv[i + 1], "-")) {
+              output = stdout;
+
+              i++;
+              break;
+            }
+
+            else if ((output = fopen(argv[i + 1], "wb")) == NULL) {
+              fprintf(stderr, "Failed to open for writing: %s.\n", argv[i + 1]);
+
+              i = argc;
+              status = EXIT_FAILURE;
+              break;
+            }
+
+            ++i;
+            break;
+        }
+      }
+    }
+
+    // Got an input filename.
+    else if (input != NULL) {
+      fprintf(stderr, "Multiple inputs were specified.\n");
+
+      i = argc;
+      status = EXIT_FAILURE;
+      break;
+    }
+
+    else if ((input = fopen(argv[i], "r")) == NULL) {
+      fprintf(stderr, "Failed to open for reading: %s.\n", argv[i]);
+
+      i = argc;
+      status = EXIT_FAILURE;
+      break;
+    }
+  }
+
+  if (status != EXIT_FAILURE) {
+    status = EXIT_FAILURE;
+
+    if (input == NULL)
+      fprintf(stderr, "An input file was not specified.\n");
+
+    else if (output == NULL)
+      fprintf(stderr, "An output file was not specified.\n");
+
+    else
+      status = assemble(input, output);
+  }
+
+  if (input && input != stdin)
+    fclose(input);
+
+  if (output && output != stdout)
+    fclose(output);
+
+  return status;
+}
+
+void print_usage(const char *argv0) {
+  printf("Reality Signal Processor Assembler (rspasm)\n"
+         "Copyright (C) 2014-15, Tyler J. Stachecki.\n\n"
+
+         "Usage: %s [options] <file>\n"
+         "  -h                          : Display this menu\n"
+         "  -o <file>                   : Output to named file\n"
+
+    , argv0);
+}
+
+int safe_fwrite(FILE *out, const uint8_t *buf, size_t sz) {
+  size_t i, amt;
+
+  for (i = 0; i < sz; i += amt) {
+    amt = fwrite(buf + i, 1, sz - i, out);
+
+    if (ferror(out)) {
+      fprintf(stderr, "Failed to write to output stream.\n");
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
diff --git a/rspasm/opcodes.h b/rspasm/opcodes.h
new file mode 100644 (file)
index 0000000..11a830a
--- /dev/null
@@ -0,0 +1,191 @@
+//
+// rspasm/opcodes.h: RSP opcodes.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#ifndef RSPASM_OPCODES_H
+#define RSPASM_OPCODES_H
+
+enum rsp_opcode {
+  BREAK = 0x0000000D,
+  NOP = 0x00000000,
+  VNOP = 0x4A000037,
+
+  // OPCODE_R
+  JALR = 0x09,
+  JR = 0x08,
+
+  // OPCODE_RRI
+  ADDI = 0x08,
+  ADDIU = 0x09,
+  ANDI = 0x0C,
+  LUI = 0x0F,
+  ORI =  0x0D,
+  SLL = 0x00,
+  SLTI = 0x0A,
+  SLTIU = 0x0B,
+  SRA = 0x03,
+  SRL = 0x02,
+  XORI = 0x0E,
+
+  // OPCODE_RO
+  LB = 0x20,
+  LBU = 0x24,
+  LH = 0x21,
+  LHU = 0x25,
+  LW = 0x23,
+  SB = 0x28,
+  SH = 0x29,
+  SW = 0x2B,
+
+  // OPCODE_RT
+  BGEZ = 0x01,
+  BGEZAL = 0x11,
+  BGTZ = 0x07,
+  BLEZ = 0x06,
+  BLTZ = 0x00,
+  BLTZAL = 0x10,
+
+  // OPCODE_RRT
+  BEQ = 0x04,
+  BNE = 0x05,
+
+  // OPCODE_RRR
+  ADD = 0x20,
+  ADDU = 0x21,
+  AND = 0x24,
+  NOR = 0x27,
+  OR = 0x25,
+  SLLV = 0x04,
+  SLT = 0x2A,
+  SLTU = 0x2B,
+  SRAV = 0x07,
+  SRLV = 0x06,
+  SUB = 0x22,
+  SUBU = 0x23,
+  XOR = 0x26,
+
+  // OPCODE_RZ0
+  MFC0 = 0x00,
+  MTC0 = 0x04,
+
+  // OPCODE_RZ2
+  CFC2 = 0x02,
+  CTC2 = 0x06,
+  MFC2 = 0x00,
+  MTC2 = 0x04,
+
+  // OPCODE_T
+  J = 0x02,
+  JAL = 0x03,
+
+  // OPCODE_VO_LWC2
+  LBV = 0x00,
+  LDV = 0x03,
+  LFV = 0x09,
+  LHV = 0x08,
+  LLV = 0x02,
+  LPV = 0x06,
+  LQV = 0x04,
+  LRV = 0x05,
+  LSV = 0x01,
+  LTV = 0x0B,
+  LUV = 0x07,
+  LWV = 0x0A,
+
+  // OPCODE_VO_SWC2
+  SBV = 0x00,
+  SDV = 0x03,
+  SFV = 0x09,
+  SHV = 0x08,
+  SLV = 0x02,
+  SPV = 0x06,
+  SQV = 0x04,
+  SRV = 0x05,
+  SSV = 0x01,
+  STV = 0x0B,
+  SUV = 0x07,
+  SWV = 0x0A, /* Ultra64 documentation says 0x07?  */
+              /* I'm assuming this a typo... ? */
+
+  // OPCODE_VV
+  VMOV = 0x33,
+  VRCP = 0x30,
+  VRCPH = 0x32,
+  VRCPL = 0x31,
+  VRSQ = 0x34,
+  VRSQH = 0x36,
+  VRSQL = 0x35,
+
+  // OPCODE_VVV
+  VABS = 0x13,
+  VADD = 0x10,
+  VADDC = 0x14,
+  VAND = 0x28,
+  VCH = 0x25,
+  VCL = 0x24,
+  VCR = 0x26,
+  VEQ = 0x21,
+  VGE = 0x23,
+  VLT = 0x20,
+  VMACF = 0x08,
+  VMACQ = 0x0B,
+  VMACU = 0x01,
+  VMADH = 0x0F,
+  VMADL = 0x0C,
+  VMADM = 0x0D,
+  VMADN = 0x0E,
+  VMUDH = 0x07,
+  VMUDL = 0x04,
+  VMUDM = 0x05,
+  VMUDN = 0x06,
+  VMULF = 0x00,
+  VMULQ = 0x03,
+  VMULU = 0x01,
+  VMRG = 0x27,
+  VNAND = 0x29,
+  VNE = 0x22,
+  VNOR = 0x2B,
+  VNXOR = 0x2D,
+  VOR = 0x2A,
+  VRNDN = 0x0A,
+  VRNDP = 0x02,
+  VSUB = 0x11,
+  VSUBC = 0x15,
+  VSAR = 0x1D,
+  VXOR = 0x2C,
+
+  // OPCODE_NS_VVV
+  VACCB = 0x18,
+  VADDB = 0x16,
+  VEXTN = 0x3A,
+  VEXTQ = 0x39,
+  VEXTT = 0x38,
+  VINSN = 0x3D,
+  VINSQ = 0x3C,
+  VINST = 0x3B,
+  VSAC = 0x1B,
+  VSAD = 0x1A,
+  VSUBB = 0x17,
+  VSUCB = 0x19,
+  VSUM = 0x1C,
+  VSUT = 0x12,
+
+} opcode_t;
+
+struct rsp_instruction {
+  enum rsp_opcode op;
+
+  unsigned r1;
+  unsigned r2;
+  unsigned r3;
+  unsigned e;
+};
+
+#endif
+
diff --git a/rspasm/parser.y b/rspasm/parser.y
new file mode 100644 (file)
index 0000000..40290ab
--- /dev/null
@@ -0,0 +1,281 @@
+%{
+//
+// rspasm/parser.y: RSP assembler parser.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#define YYDEBUG 0
+int yydebug = 0;
+%}
+
+// Global state: be gone.
+%define api.pure full
+%define api.prefix {rspasm}
+%lex-param { yyscan_t scanner }
+%parse-param { yyscan_t scanner }
+%locations
+
+%union {
+  char identifier[32];
+  long int constant;
+  enum rsp_opcode opcode;
+  unsigned reg;
+}
+
+%code requires {
+#include "opcodes.h"
+#include "rspasm.h"
+#include "symbols.h"
+
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void *yyscan_t;
+#endif
+}
+
+%code {
+#include "emitter.h"
+#include "lexer.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+// Raised on a parser error.
+int yyerror(YYLTYPE *yylloc, yyscan_t scanner, const char *error) {
+  //struct rspasm *rspasm = rspasmget_extra(scanner);
+
+  fprintf(stderr, "line %d: Parse error.\n", yylloc->first_line);
+  return 0;
+}
+}
+
+%token COLON
+%token COMMA
+%token CONSTANT
+%token DIVIDE
+%token DOTBYTE
+%token DOTDATA
+%token DOTDMAX
+%token DOTHALF
+%token DOTTEXT
+%token DOTWORD
+%token IDENTIFIER
+%token LEFT_BRACKET
+%token LEFT_PAREN
+%token OPCODE
+%token OPCODE_JALR //
+%token OPCODE_R //
+%token OPCODE_RRC0
+%token OPCODE_RI
+%token OPCODE_RO
+%token OPCODE_RR //
+%token OPCODE_RRI
+%token OPCODE_RRR
+%token OPCODE_RRS //
+%token OPCODE_RRT //
+%token OPCODE_RT //
+%token OPCODE_RZ2 //
+%token OPCODE_RZ2E //
+%token OPCODE_T //
+%token OPCODE_VO_LWC2 //
+%token OPCODE_VO_SWC2 //
+%token OPCODE_VV //
+%token OPCODE_VVV //
+%token OP_AND
+%token OP_BNOT
+%token OP_DIVIDE
+%token OP_LSHIFT
+%token OP_MINUS
+%token OP_MOD
+%token OP_OR
+%token OP_PLUS
+%token OP_RSHIFT
+%token OP_TIMES
+%token OP_XOR
+%token RIGHT_BRACKET
+%token RIGHT_PAREN
+%token SCALAR_REG
+%token VOPCODE
+
+%left OP_AND
+%left OP_DIVIDE
+%left OP_LSHIFT
+%left OP_MINUS
+%left OP_MOD
+%left OP_OR
+%left OP_PLUS
+%left OP_RSHIFT
+%left OP_TIMES
+%left OR_XOR
+
+%right OP_BNOT
+
+%type <constant> constexpr expr CONSTANT
+%type <identifier> IDENTIFIER
+%type <opcode> OPCODE OPCODE_RI OPCODE_RO OPCODE_RRC0 OPCODE_RRI OPCODE_RRR
+               VOPCODE
+%type <reg> SCALAR_REG
+
+%%
+
+toplevel:
+  | program;
+
+program:
+    instruction
+  | program instruction
+  ;
+
+instruction:
+    directive
+  | label
+  | scalar_instruction
+  | vector_instruction
+  ;
+
+directive:
+    DOTBYTE expr {
+      if (rspasm_emit_byte(rspasmget_extra(scanner), &yyloc, $2))
+        return EXIT_FAILURE;
+    }
+
+  | DOTDATA constexpr
+  | DOTDMAX constexpr {
+      if (rspasm_dmax_assert(rspasmget_extra(scanner), &yyloc, $2))
+        return EXIT_FAILURE;
+    }
+
+  | DOTHALF expr {
+      if (rspasm_emit_half(rspasmget_extra(scanner), &yyloc, $2))
+        return EXIT_FAILURE;
+    }
+
+  | DOTWORD expr {
+      if (rspasm_emit_word(rspasmget_extra(scanner), &yyloc, $2))
+        return EXIT_FAILURE;
+    }
+
+  | DOTDATA { ((struct rspasm *) rspasmget_extra(scanner))->in_text = false; }
+  | DOTTEXT { ((struct rspasm *) rspasmget_extra(scanner))->in_text = true; }
+  ;
+
+label:
+  IDENTIFIER COLON {
+    struct rspasm *rspasm = rspasmget_extra(scanner);
+    uint32_t addr = rspasm->in_text ? rspasm->ihead : rspasm->dhead;
+
+    if (rspasm->first_pass) {
+      if (rspasm_add_symbol(rspasm, $1, addr))
+        return EXIT_FAILURE;
+    }
+  };
+
+scalar_instruction:
+    OPCODE {
+      if (rspasm_emit_instruction(rspasmget_extra(scanner), &yyloc, $1))
+        return EXIT_FAILURE;
+    }
+
+    | OPCODE_RI SCALAR_REG COMMA expr {
+      if (rspasm_emit_instruction_ri(
+        rspasmget_extra(scanner), &yyloc, $1, $2, $4))
+        return EXIT_FAILURE;
+    }
+
+    | OPCODE_RO SCALAR_REG COMMA expr LEFT_PAREN SCALAR_REG RIGHT_PAREN {
+      if (rspasm_emit_instruction_ro(
+        rspasmget_extra(scanner), &yyloc, $1, $2, $4, $6))
+        return EXIT_FAILURE;
+    }
+
+    | OPCODE_RRC0 SCALAR_REG COMMA SCALAR_REG {
+      if (rspasm_emit_instruction_rrc0(
+        rspasmget_extra(scanner), &yyloc, $1, $2, $4))
+        return EXIT_FAILURE;
+    }
+
+    | OPCODE_RRI SCALAR_REG COMMA SCALAR_REG COMMA expr {
+      if (rspasm_emit_instruction_rri(
+        rspasmget_extra(scanner), &yyloc, $1, $2, $4, $6))
+        return EXIT_FAILURE;
+    }
+
+    | OPCODE_RRR SCALAR_REG COMMA SCALAR_REG COMMA SCALAR_REG {
+      if (rspasm_emit_instruction_rrr(
+        rspasmget_extra(scanner), &yyloc, $1, $2, $4, $6))
+        return EXIT_FAILURE;
+    }
+
+  ;
+
+vector_instruction:
+    VOPCODE {
+      if (rspasm_emit_instruction(rspasmget_extra(scanner), &yyloc, $1))
+        return EXIT_FAILURE;
+    }
+  ;
+
+constexpr:
+    LEFT_PAREN constexpr RIGHT_PAREN { $$ = $2; }
+  | CONSTANT { $$ = $1; }
+
+  | OP_BNOT constexpr { $$ = ~$2; }
+  | constexpr OP_AND constexpr { $$ = $1 & $3; }
+  | constexpr OP_OR constexpr { $$ = $1 | $3; }
+  | constexpr OR_XOR constexpr { $$ = $1 ^ $3; }
+  | constexpr OP_LSHIFT constexpr { $$ = $1 << $3; }
+  | constexpr OP_RSHIFT constexpr { $$ = $1 >> $3; }
+  | constexpr OP_TIMES constexpr { $$ = $1 * $3; }
+  | constexpr OP_DIVIDE constexpr { $$ = $1 / $3; }
+  | constexpr OP_MOD constexpr { $$ = $1 % $3; }
+  | constexpr OP_PLUS constexpr { $$ = $1 | $3; }
+  | constexpr OP_MINUS constexpr { $$ = $1 - $3; }
+
+  | OP_MINUS constexpr %prec OP_BNOT { $$ = -$2; }
+  | OP_PLUS constexpr %prec OP_BNOT { $$ = +$2; }
+  ;
+
+expr:
+    LEFT_PAREN expr RIGHT_PAREN { $$ = $2; }
+  | CONSTANT { $$ = $1; }
+
+  | IDENTIFIER {
+      const struct rspasm *rspasm = rspasmget_extra(scanner);
+
+      if (rspasm->first_pass)
+        $$ = 0;
+
+      else {
+        uint32_t val;
+
+        if (rspasm_get_symbol_address(rspasmget_extra(scanner), $1, &val)) {
+          fprintf(stderr, "Unknown symbol: %s\n", $1);
+          return EXIT_FAILURE;
+        }
+
+        $$ = val;
+      }
+    }
+
+  | OP_BNOT expr { $$ = ~$2; }
+  | expr OP_AND expr { $$ = $1 & $3; }
+  | expr OP_OR expr { $$ = $1 | $3; }
+  | expr OR_XOR expr { $$ = $1 ^ $3; }
+  | expr OP_LSHIFT expr { $$ = $1 << $3; }
+  | expr OP_RSHIFT expr { $$ = $1 >> $3; }
+  | expr OP_TIMES expr { $$ = $1 * $3; }
+  | expr OP_DIVIDE expr { $$ = $1 / $3; }
+  | expr OP_MOD expr { $$ = $1 % $3; }
+  | expr OP_PLUS expr { $$ = $1 | $3; }
+  | expr OP_MINUS expr { $$ = $1 - $3; }
+
+  | OP_MINUS expr %prec OP_BNOT { $$ = -$2; }
+  | OP_PLUS expr %prec OP_BNOT { $$ = +$2; }
+  ;
+
+%%
+
diff --git a/rspasm/rspasm.h b/rspasm/rspasm.h
new file mode 100644 (file)
index 0000000..36dd196
--- /dev/null
@@ -0,0 +1,35 @@
+//
+// rspasm/rspasm.h: RSP assembler common definitions.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#ifndef RSPASM_RSPASM_H
+#define RSPASM_RSPASM_H
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+// Forward declarations.
+struct rspasm_symbol;
+
+struct rspasm {
+  struct rspasm_symbol *symbols;
+  size_t num_symbols, max_symbols;
+
+  // Binary data.
+  size_t ihead;
+  size_t dhead;
+  uint8_t data[0x2000];
+
+  // State.
+  bool first_pass;
+  bool in_text;
+};
+
+#endif
+
diff --git a/rspasm/symbols.c b/rspasm/symbols.c
new file mode 100644 (file)
index 0000000..ffefd79
--- /dev/null
@@ -0,0 +1,144 @@
+//
+// rspasm/symbols.c: RSP assmebler symbol functionality.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#include "rspasm.h"
+#include "symbols.h"
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define RSPASM_DEFAULT_NUM_SYMBOLS 128
+
+struct rspasm_symbol {
+  char *name;
+  int32_t addr;
+};
+
+static int rspasm_symbol_binary_search(const struct rspasm_symbol *symbols,
+  const char *name, size_t mini, size_t maxi);
+
+static int rspasm_symbols_compare(const void *a, const void *b);
+
+int rspasm_add_symbol(struct rspasm *rspasm,
+  const char *name, uint32_t addr) {
+  struct rspasm_symbol *symbol;
+
+  size_t name_len;
+  char *lname;
+
+  // Allocate memory for another symbol.
+  if (rspasm->num_symbols >= rspasm->max_symbols) {
+    struct rspasm_symbol *symbols;
+
+    size_t num_symbols = rspasm->num_symbols == 0
+      ? RSPASM_DEFAULT_NUM_SYMBOLS
+      : rspasm->num_symbols * 2;
+
+    if ((symbols = malloc(sizeof(*symbols) * num_symbols)) == NULL) {
+      fprintf(stderr, "Out of memory.\n");
+      return -1;
+    }
+
+    memcpy(symbols, rspasm->symbols,
+      sizeof(*symbols) * rspasm->max_symbols);
+
+    free(rspasm->symbols);
+    rspasm->symbols = symbols;
+    rspasm->max_symbols = num_symbols;
+  }
+
+  // Allocate memory for the name, copy it.
+  name_len = strlen(name);
+
+  if ((lname = malloc(name_len + 1)) == NULL) {
+    fprintf(stderr, "Out of memory.\n");
+    return -1;
+  }
+
+  memcpy(lname, name, name_len);
+  lname[name_len] = '\0';
+
+  // Store the symbol data.
+  symbol = rspasm->symbols + (rspasm->num_symbols++);
+
+  symbol->name = lname;
+  symbol->addr = addr;
+  return 0;
+}
+
+int rspasm_do_symbols_pass(struct rspasm *rspasm) {
+  size_t i;
+
+  qsort(rspasm->symbols, rspasm->num_symbols,
+    sizeof(*rspasm->symbols), rspasm_symbols_compare);
+
+  for (i = 1; i < rspasm->num_symbols; i++) {
+    if (!strcmp(rspasm->symbols[i].name, rspasm->symbols[i - 1].name)) {
+      fprintf(stderr, "Symbol defined twice: %s\n", rspasm->symbols[i].name);
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+void rspasm_free_symbols(const struct rspasm *rspasm) {
+  if (rspasm->symbols != NULL) {
+    size_t i;
+
+    for (i = 0; i < rspasm->num_symbols; i++)
+      free(rspasm->symbols[i].name);
+  }
+
+  free(rspasm->symbols);
+}
+
+int rspasm_get_symbol_address(const struct rspasm *rspasm,
+  const char *name, uint32_t *addr) {
+  int chk_addr;
+
+  if ((chk_addr = rspasm_symbol_binary_search(
+    rspasm->symbols, name, 0, rspasm->num_symbols - 1)) < 0) {
+    fprintf(stderr, "Undefined symbol: %s\n", name);
+    return -1;
+  }
+
+  *addr = (uint32_t) chk_addr;
+  return 0;
+}
+
+int rspasm_symbol_binary_search(const struct rspasm_symbol *symbols,
+  const char *name, size_t mini, size_t maxi) {
+  size_t midi;
+  int cmp;
+
+  if (maxi < mini)
+    return -1;
+
+  midi = (mini + maxi) / 2;
+  cmp = strcmp(symbols[midi].name, name);
+
+  if (cmp > 0)
+    return rspasm_symbol_binary_search(symbols, name, mini, midi - 1);
+
+  else if (cmp < 0)
+    return rspasm_symbol_binary_search(symbols, name, midi + 1, maxi);
+
+  return (int) symbols[midi].addr;
+}
+
+int rspasm_symbols_compare(const void *a, const void *b) {
+  const struct rspasm_symbol *sa = (const struct rspasm_symbol *) a;
+  const struct rspasm_symbol *sb = (const struct rspasm_symbol *) b;
+
+  return strcmp(sa->name, sb->name);
+}
+
diff --git a/rspasm/symbols.h b/rspasm/symbols.h
new file mode 100644 (file)
index 0000000..f426095
--- /dev/null
@@ -0,0 +1,26 @@
+//
+// rspasm/symbols.h: RSP assmebler symbol functionality.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-15 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#ifndef RSPASM_SYMBOLS_H
+#define RSPASM_SYMBOLS_H
+#include "rspasm.h"
+
+int rspasm_add_symbol(struct rspasm *rspasm,
+  const char *name, uint32_t addr);
+
+int rspasm_do_symbols_pass(struct rspasm *rspasm);
+
+int rspasm_get_symbol_address(const struct rspasm *rspasm,
+  const char *name, uint32_t *addr);
+
+void rspasm_free_symbols(const struct rspasm *rspasm);
+
+#endif
+
diff --git a/threadtest/Makefile b/threadtest/Makefile
new file mode 100644 (file)
index 0000000..40ca0a8
--- /dev/null
@@ -0,0 +1,102 @@
+#
+# Makefile for n64chain ROMs.
+#
+# 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.
+#
+
+ifdef SystemRoot
+FIXPATH = $(subst /,\,$1)
+RM = del /Q
+else
+FIXPATH = $1
+RM = rm -f
+endif
+
+ROM_NAME = $(notdir $(CURDIR))
+
+AS = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-as)
+AR = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-gcc-ar)
+CC = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-gcc)
+MAKE = $(call FIXPATH,$(CURDIR)/../tools/bin/make)
+OBJCOPY = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-objcopy)
+
+CHECKSUM = $(call FIXPATH,$(CURDIR)/../tools/bin/checksum)
+RSPASM = $(call FIXPATH,$(CURDIR)/../tools/bin/rspasm)
+
+CFLAGS = -Wall -Wextra -pedantic -std=c99 -Wno-main \
+       -I../libn64/include -I../libn64 -I.
+OPTFLAGS = -Os -march=vr4300 -mabi=eabi -mgp32 -mlong32 \
+       -flto -ffat-lto-objects -ffunction-sections -fdata-sections \
+       -G4 -mno-extern-sdata -mgpopt
+
+ASMFILES = $(call FIXPATH,\
+)
+
+CFILES = $(call FIXPATH,\
+       src/main.c \
+)
+
+UCODES = $(call FIXPATH,\
+)
+
+OBJFILES = \
+       $(ASMFILES:.S=.o) \
+       $(CFILES:.c=.o)
+
+UCODEBINS = $(UCODES:.rsp=.bin)
+DEPFILES = $(OBJFILES:.o=.d)
+
+#
+# Primary targets.
+#
+all: $(ROM_NAME).z64
+
+$(ROM_NAME).z64: $(ROM_NAME).elf
+       @echo $(call FIXPATH,"Building: $(ROM_NAME)/$@")
+       @$(OBJCOPY) -O binary $< $@
+       @$(CHECKSUM) $(call FIXPATH,../libn64/header.bin) $@
+
+$(ROM_NAME).elf: libn64 $(OBJFILES)
+       @echo $(call FIXPATH,"Building: $(ROM_NAME)/$@")
+       @$(CC) $(CFLAGS) $(OPTFLAGS) -Wl,-Map=$(ROM_NAME).map -nostdlib \
+               -T$(call FIXPATH,../libn64/rom.ld) -o $@ $(OBJFILES) \
+               -L$(call FIXPATH,../libn64) -ln64
+
+#
+# Generic compilation/assembly targets.
+#
+$(call FIXPATH,src/graphics.o): $(call FIXPATH,src/graphics.S) $(call FIXPATH,src/graphics.bin)
+%.o: %.S
+       @echo $(call FIXPATH,"Assembling: $(ROM_NAME)/$<")
+       @$(CC) $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
+
+%.o: %.c
+       @echo $(call FIXPATH,"Compiling: $(ROM_NAME)/$<")
+       @$(CC) $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
+
+%.bin: %.rsp
+       @echo $(call FIXPATH,"Assembling: $(ROM_NAME)/$<")
+       @$(RSPASM) -o $@ $<
+
+.PHONY: libn64
+libn64:
+       @$(MAKE) -sC $(call FIXPATH,../libn64)
+
+#
+# Clean project target.
+#
+.PHONY: clean
+clean:
+       @echo "Cleaning $(ROM_NAME)..."
+       @$(RM) $(ROM_NAME).map $(ROM_NAME).elf $(ROM_NAME).z64 \
+               $(DEPFILES) $(OBJFILES) $(UCODEBINS)
+
+#
+# Use computed dependencies.
+#
+-include $(DEPFILES)
+
diff --git a/threadtest/src/main.c b/threadtest/src/main.c
new file mode 100644 (file)
index 0000000..677a07b
--- /dev/null
@@ -0,0 +1,90 @@
+//
+// fputest/src/main.c: FPU test cart (entry point).
+//
+// 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/fbtext.h>
+#include <os/syscall.h>
+#include <os/thread_table.h>
+#include <rcp/vi.h>
+#include <stdint.h>
+
+// These pre-defined values are suitable for NTSC.
+// TODO: Add support for PAL and PAL-M televisions.
+static vi_state_t vi_state = {
+  0x0000324E, // status
+  0x00200000, // origin
+  0x00000140, // width
+  0x00000002, // intr
+  0x00000000, // current
+  0x03E52239, // burst
+  0x0000020D, // v_sync
+  0x00000C15, // h_sync
+  0x0C150C15, // leap
+  0x006C02EC, // h_start
+  0x002501FF, // v_start
+  0x000E0204, // v_burst
+  0x00000200, // x_scale
+  0x00000400, // y_scale
+};
+
+// Higher priority thread.
+volatile unsigned threads_spawned;
+
+void thread_main(void *arg) {
+  struct libn64_fbtext_context *fbtext = (struct libn64_fbtext_context *) arg;
+  uint32_t mythreadaddr;
+  unsigned my_prio;
+
+  my_prio = (++threads_spawned);
+
+  __asm__ __volatile__("addu %0, $k1, $zero\n\t" : "=r" (mythreadaddr));
+
+#if 1
+  if (threads_spawned < 15)
+    libn64_thread_create(thread_main, fbtext, my_prio + 1);
+#endif
+
+  libn64_fbtext_puts(fbtext, "App thread! Thr=");
+  libn64_fbtext_putu32(fbtext, mythreadaddr);
+  libn64_fbtext_puts(fbtext, ",Prio=");
+  libn64_fbtext_putu32(fbtext, my_prio - 1);
+  libn64_fbtext_puts(fbtext, "\n");
+  libn64_thread_exit();
+}
+
+// Application entry point.
+void main(void *unused __attribute__((unused))) {
+  unsigned i;
+
+  // Set default thread stack addresses until we get something better.
+  // Give each thread a 4K stack starting at 1MB (except the current thread).
+  for (i = 0; i < LIBN64_THREADS_MAX - 2; i++)
+    libn64_thread_table->free_list[i]->state.regs[0x68/4] = 0x80100000 + 0x1000 * i;
+
+  // Wipe FB to black.
+  volatile uint16_t *fb = (volatile uint16_t *) 0xA0200000;
+
+  for (i = 0; i < 320 * 240; i++)
+    *(fb + i) = 0;
+
+  // Enable the VI.
+  vi_flush_state(&vi_state);
+
+  struct libn64_fbtext_context fbtext;
+  libn64_fbtext_init(&fbtext, 0x200000, LIBN64_FBTEXT_COLOR_WHITE,
+      LIBN64_FBTEXT_COLOR_BLACK, 0x140, LIBN64_FBTEXT_16BPP);
+
+#if 1
+  threads_spawned = 1;
+  libn64_thread_create(thread_main, &fbtext, 2);
+#endif
+
+  libn64_fbtext_puts(&fbtext, "Idle thread!\n");
+}
+
diff --git a/tools/build-linux64-toolchain.sh b/tools/build-linux64-toolchain.sh
new file mode 100755 (executable)
index 0000000..d9359a4
--- /dev/null
@@ -0,0 +1,193 @@
+#!/bin/bash
+set -e
+
+#
+# tools/build-linux64-toolchain.sh: Linux toolchain build script.
+#
+# 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.
+#
+
+BINUTILS="ftp://ftp.gnu.org/gnu/binutils/binutils-2.27.tar.bz2"
+GCC="ftp://ftp.gnu.org/gnu/gcc/gcc-6.1.0/gcc-6.1.0.tar.bz2"
+MAKE="ftp://ftp.gnu.org/gnu/make/make-4.2.tar.bz2"
+
+export PATH="${PATH}:${SCRIPT_DIR}/bin"
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+cd ${SCRIPT_DIR} && mkdir -p {stamps,tarballs}
+
+if [ ! -f stamps/binutils-download ]; then
+  wget "${BINUTILS}" -O "tarballs/$(basename ${BINUTILS})"
+  touch stamps/binutils-download
+fi
+
+if [ ! -f stamps/binutils-extract ]; then
+  mkdir -p binutils-{build,source}
+  tar -xf tarballs/$(basename ${BINUTILS}) -C binutils-source --strip 1
+  touch stamps/binutils-extract
+fi
+
+if [ ! -f stamps/binutils-configure ]; then
+  pushd binutils-build
+  ../binutils-source/configure \
+    --prefix="${SCRIPT_DIR}" \
+    --with-lib-path="${SCRIPT_DIR}/lib" \
+    --target=mips64-elf --with-arch=vr4300 \
+    --enable-64-bit-bfd \
+    --enable-plugins \
+    --enable-static \
+    --disable-gold \
+    --disable-multilib \
+    --disable-nls \
+    --disable-shared \
+    --disable-werror
+  popd
+
+  touch stamps/binutils-configure
+fi
+
+if [ ! -f stamps/binutils-build ]; then
+  pushd binutils-build
+  make
+  popd
+
+  touch stamps/binutils-build
+fi
+
+if [ ! -f stamps/binutils-install ]; then
+  pushd binutils-build
+  make install
+  popd
+
+  touch stamps/binutils-install
+fi
+
+if [ ! -f stamps/gcc-download ]; then
+  wget "${GCC}" -O "tarballs/$(basename ${GCC})"
+  touch stamps/gcc-download
+fi
+
+if [ ! -f stamps/gcc-extract ]; then
+  mkdir -p gcc-{build,source}
+  tar -xf tarballs/$(basename ${GCC}) -C gcc-source --strip 1
+  touch stamps/gcc-extract
+fi
+
+if [ ! -f stamps/gcc-configure ]; then
+  pushd gcc-build
+  ../gcc-source/configure \
+    --prefix="${SCRIPT_DIR}" \
+    --target=mips64-elf --with-arch=vr4300 \
+    --enable-languages=c --without-headers --with-newlib \
+    --with-gnu-as=${SCRIPT_DIR}/bin/mips64-elf-as \
+    --with-gnu-ld=${SCRIPT_DIR}/bin/mips64-elf-ld \
+    --enable-checking=release \
+    --disable-decimal-float \
+    --disable-gold \
+    --disable-libatomic \
+    --disable-libgomp \
+    --disable-libitm \
+    --disable-libquadmath \
+    --disable-libquadmath-support \
+    --disable-libsanitizer \
+    --disable-libssp \
+    --disable-libunwind-exceptions \
+    --disable-libvtv \
+    --disable-multilib \
+    --disable-nls \
+    --disable-shared \
+    --disable-threads \
+    --disable-win32-registry \
+    --enable-lto \
+    --enable-plugin \
+    --enable-static \
+    --without-included-gettext
+  popd
+
+  touch stamps/gcc-configure
+fi
+
+if [ ! -f stamps/gcc-build ]; then
+  pushd gcc-build
+  make all-gcc
+  popd
+
+  touch stamps/gcc-build
+fi
+
+if [ ! -f stamps/gcc-install ]; then
+  pushd gcc-build
+  make install-gcc
+  popd
+
+  # build-win32-toolchain.sh needs this; the cross-compiler build
+  # will look for mips64-elf-cc and we only have mips64-elf-gcc.
+  pushd "${SCRIPT_DIR}/bin"
+  ln -sfv mips64-elf-{gcc,cc}
+  popd
+
+  touch stamps/gcc-install
+fi
+
+if [ ! -f stamps/make-download ]; then
+  wget "${MAKE}" -O "tarballs/$(basename ${MAKE})"
+  touch stamps/make-download
+fi
+
+if [ ! -f stamps/make-extract ]; then
+  mkdir -p make-{build,source}
+  tar -xf tarballs/$(basename ${MAKE}) -C make-source --strip 1
+  touch stamps/make-extract
+fi
+
+if [ ! -f stamps/make-configure ]; then
+  pushd make-build
+  ../make-source/configure \
+    --prefix="${SCRIPT_DIR}" \
+    --disable-largefile \
+    --disable-nls \
+    --disable-rpath
+  popd
+
+  touch stamps/make-configure
+fi
+
+if [ ! -f stamps/make-build ]; then
+  pushd make-build
+  make
+  popd
+
+  touch stamps/make-build
+fi
+
+if [ ! -f stamps/make-install ]; then
+  pushd make-build
+  make install
+  popd
+
+  touch stamps/make-install
+fi
+
+if [ ! -f stamps/checksum-build ]; then
+  cc -Wall -Wextra -pedantic -std=c99 -static -O2 checksum.c -o bin/checksum
+
+  touch stamps/checksum-build
+fi
+
+if [ ! -f stamps/rspasm-build ]; then
+  pushd "${SCRIPT_DIR}/../rspasm"
+
+  make clean && make all
+  cp rspasm ${SCRIPT_DIR}/bin
+fi
+
+rm -rf "${SCRIPT_DIR}"/../tools/tarballs
+rm -rf "${SCRIPT_DIR}"/../tools/*-source
+rm -rf "${SCRIPT_DIR}"/../tools/*-build
+rm -rf "${SCRIPT_DIR}"/../tools/stamps
+exit 0
+
diff --git a/tools/build-win64-toolchain.sh b/tools/build-win64-toolchain.sh
new file mode 100755 (executable)
index 0000000..2279acc
--- /dev/null
@@ -0,0 +1,235 @@
+#!/bin/bash
+set -e
+
+#
+# tools/build-win64-toolchain.sh: Win64 toolchain build script.
+#
+# 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.
+#
+
+BINUTILS="ftp://ftp.gnu.org/gnu/binutils/binutils-2.27.tar.bz2"
+GCC="ftp://ftp.gnu.org/gnu/gcc/gcc-6.1.0/gcc-6.1.0.tar.bz2"
+GMP="ftp://ftp.gnu.org/gnu/gmp/gmp-6.1.1.tar.bz2"
+MAKE="ftp://ftp.gnu.org/gnu/make/make-4.2.tar.bz2"
+MPC="ftp://ftp.gnu.org/gnu/mpc/mpc-1.0.3.tar.gz"
+MPFR="ftp://ftp.gnu.org/gnu/mpfr/mpfr-3.1.4.tar.bz2"
+
+export PATH="${PATH}:${SCRIPT_DIR}/bin"
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+cd ${SCRIPT_DIR} && mkdir -p {stamps,tarballs}
+
+if [ ! -f stamps/binutils-download ]; then
+  wget "${BINUTILS}" -O "tarballs/$(basename ${BINUTILS})"
+  touch stamps/binutils-download
+fi
+
+if [ ! -f stamps/binutils-extract ]; then
+  mkdir -p binutils-{build,source}
+  tar -xf tarballs/$(basename ${BINUTILS}) -C binutils-source --strip 1
+  touch stamps/binutils-extract
+fi
+
+if [ ! -f stamps/binutils-configure ]; then
+  pushd binutils-build
+  ../binutils-source/configure \
+    --build=x86_64-linux-gnu \
+    --host=x86_64-w64-mingw32 \
+    --prefix="${SCRIPT_DIR}" \
+    --with-lib-path="${SCRIPT_DIR}/lib" \
+    --target=mips64-elf --with-arch=vr4300 \
+    --enable-64-bit-bfd \
+    --enable-plugins \
+    --enable-static \
+    --disable-gold \
+    --disable-multilib \
+    --disable-nls \
+    --disable-shared \
+    --disable-werror
+  popd
+
+  touch stamps/binutils-configure
+fi
+
+if [ ! -f stamps/binutils-build ]; then
+  pushd binutils-build
+  make
+  popd
+
+  touch stamps/binutils-build
+fi
+
+if [ ! -f stamps/binutils-install ]; then
+  pushd binutils-build
+  make install
+  popd
+
+  touch stamps/binutils-install
+fi
+
+if [ ! -f stamps/gmp-download ]; then
+  wget "${GMP}" -O "tarballs/$(basename ${GMP})"
+  touch stamps/gmp-download
+fi
+
+if [ ! -f stamps/mpfr-download ]; then
+  wget "${MPFR}" -O "tarballs/$(basename ${MPFR})"
+  touch stamps/mpfr-download
+fi
+
+if [ ! -f stamps/mpc-download ]; then
+  wget "${MPC}" -O "tarballs/$(basename ${MPC})"
+  touch stamps/mpc-download
+fi
+
+if [ ! -f stamps/gcc-download ]; then
+  wget "${GCC}" -O "tarballs/$(basename ${GCC})"
+  touch stamps/gcc-download
+fi
+
+if [ ! -f stamps/gcc-extract ]; then
+  mkdir -p gcc-{build,source}
+  tar -xf tarballs/$(basename ${GCC}) -C gcc-source --strip 1
+  touch stamps/gcc-extract
+fi
+
+if [ ! -f stamps/gmp-extract ]; then
+  mkdir -p gcc-source/gmp
+  tar -xf tarballs/$(basename ${GMP}) -C gcc-source/gmp --strip 1
+  touch stamps/gmp-extract
+fi
+
+if [ ! -f stamps/mpfr-extract ]; then
+  mkdir -p gcc-source/mpfr
+  tar -xf tarballs/$(basename ${MPFR}) -C gcc-source/mpfr --strip 1
+  touch stamps/mpfr-extract
+fi
+
+if [ ! -f stamps/mpc-extract ]; then
+  mkdir -p gcc-source/mpc
+  tar -xf tarballs/$(basename ${MPC}) -C gcc-source/mpc --strip 1
+  touch stamps/mpc-extract
+fi
+
+if [ ! -f stamps/gcc-configure ]; then
+  pushd gcc-build
+  ../gcc-source/configure \
+    --build=x86_64-linux-gnu \
+    --host=x86_64-w64-mingw32 \
+    --prefix="${SCRIPT_DIR}" \
+    --target=mips64-elf --with-arch=vr4300 \
+    --enable-languages=c --without-headers --with-newlib \
+    --with-gnu-as=${SCRIPT_DIR}/bin/mips64-elf-as.exe \
+    --with-gnu-ld=${SCRIPT_DIR}/bin/mips64-elf-ld.exe \
+    --enable-checking=release \
+    --disable-decimal-float \
+    --disable-gold \
+    --disable-libatomic \
+    --disable-libgomp \
+    --disable-libitm \
+    --disable-libquadmath \
+    --disable-libquadmath-support \
+    --disable-libsanitizer \
+    --disable-libssp \
+    --disable-libunwind-exceptions \
+    --disable-libvtv \
+    --disable-multilib \
+    --disable-nls \
+    --disable-shared \
+    --disable-threads \
+    --disable-win32-registry \
+    --enable-lto \
+    --enable-plugin-ld \
+    --enable-static \
+    --without-included-gettext
+  popd
+
+  touch stamps/gcc-configure
+fi
+
+if [ ! -f stamps/gcc-build ]; then
+  pushd gcc-build
+  make all-gcc
+  popd
+
+  touch stamps/gcc-build
+fi
+
+if [ ! -f stamps/gcc-install ]; then
+  pushd gcc-build
+  make install-gcc
+  popd
+
+  # While not necessary, this is still a good idea.
+  pushd "${SCRIPT_DIR}/bin"
+  cp mips64-elf-{gcc,cc}.exe
+  popd
+
+  touch stamps/gcc-install
+fi
+
+if [ ! -f stamps/make-download ]; then
+  wget "${MAKE}" -O "tarballs/$(basename ${MAKE})"
+  touch stamps/make-download
+fi
+
+if [ ! -f stamps/make-extract ]; then
+  mkdir -p make-{build,source}
+  tar -xf tarballs/$(basename ${MAKE}) -C make-source --strip 1
+  touch stamps/make-extract
+fi
+
+if [ ! -f stamps/make-configure ]; then
+  pushd make-build
+  ../make-source/configure \
+    --build=x86_64-linux-gnu \
+    --host=x86_64-w64-mingw32 \
+    --prefix="${SCRIPT_DIR}" \
+    --disable-largefile \
+    --disable-nls \
+    --disable-rpath
+  popd
+
+  touch stamps/make-configure
+fi
+
+if [ ! -f stamps/make-build ]; then
+  pushd make-build
+  make
+  popd
+
+  touch stamps/make-build
+fi
+
+if [ ! -f stamps/make-install ]; then
+  pushd make-build
+  make install
+  popd
+
+  touch stamps/make-install
+fi
+
+if [ ! -f stamps/checksum-build ]; then
+  x86_64-w64-mingw32-gcc -Wall -Wextra -pedantic -std=c99 -static -O2 checksum.c -o bin/checksum.exe
+
+  touch stamps/checksum-build
+fi
+
+if [ ! -f stamps/rspasm-build ]; then
+  pushd "${SCRIPT_DIR}/../rspasm"
+
+  make clean
+  CC=x86_64-w64-mingw32-gcc RSPASM_LIBS="-lws2_32" make
+  cp rspasm ${SCRIPT_DIR}/bin/rspasm.exe
+fi
+
+rm -rf "${SCRIPT_DIR}"/../tools/tarballs
+rm -rf "${SCRIPT_DIR}"/../tools/*-source
+rm -rf "${SCRIPT_DIR}"/../tools/*-build
+rm -rf "${SCRIPT_DIR}"/../tools/stamps
+exit 0
+
diff --git a/tools/checksum.c b/tools/checksum.c
new file mode 100644 (file)
index 0000000..fe69bc2
--- /dev/null
@@ -0,0 +1,271 @@
+//
+// tools/checksum.c: ROM checksum/padding tool.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014 Tyler J. Stachecki <tstache1@binghamton.edu>
+//
+// This file is more or less a direct rip of chksum64:
+// Copyright 1997 Andreas Sterbenz <stan@sbox.tu-graz.ac.at>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CHECKSUM_START    0x00001000
+#define CHECKSUM_LENGTH   0x00100000
+#define CIC_NUS6102_SEED  0xF8CA4DDC
+#define CRC_OFFSET        0x00000010
+#define HEADER_SIZE       0x00000040
+#define BUFFER_SIZE       0x00008000
+
+static inline uint32_t btol(const uint8_t *b) {
+  return
+    (b[0] << 24) |
+    (b[1] << 16) |
+    (b[2] <<  8) |
+    (b[3] <<  0);
+}
+
+static inline void ltob(uint32_t l, uint8_t *b) {
+  b[0] = l >> 24;
+  b[1] = l >> 16;
+  b[2] = l >>  8;
+  b[3] = l >>  0;
+}
+
+static inline uint32_t rol(uint32_t i, unsigned b) {
+  return (i << b) | (i >> (32 - b));
+}
+
+static int calculate_crc(const uint8_t *buffer, uint32_t crc[2]);
+
+static long get_file_length_and_rewind(FILE *f);
+static int safe_fread(uint8_t *buffer, size_t len, FILE *f);
+static int safe_fwrite(const uint8_t *buffer, size_t len, FILE *f);
+
+static int calculate_crc(const uint8_t *buffer, uint32_t crc[2]) {
+  uint8_t *crc_buffer;
+  unsigned l, offs;
+
+  uint32_t t1, t2, t3, t4, t5, t6;
+  uint32_t c1, k1, k2;
+
+  if ((crc_buffer = malloc(BUFFER_SIZE)) == NULL) {
+    perror("malloc");
+
+    return 1;
+  }
+
+  t1 = t2 = t3 = CIC_NUS6102_SEED;
+  t4 = t5 = t6 = CIC_NUS6102_SEED;
+
+  l = CHECKSUM_LENGTH;
+  offs = CHECKSUM_START;
+
+  while (1) {
+    unsigned i, n;
+
+    n = (BUFFER_SIZE < l) ? BUFFER_SIZE : l;
+    memcpy(crc_buffer, buffer + offs, n);
+    offs += n;
+
+    for (i = 0; i < n; i += 4) {
+      c1 = btol(crc_buffer + i);
+      k1 = t6 + c1;
+
+      if (k1 < t6)
+        t4++;
+
+      t6 = k1;
+      t3 ^= c1;
+      k2 = c1 & 0x1F;
+      k1 = rol(c1, k2);
+      t5 += k1;
+
+      if (c1 < t2)
+        t2 ^= k1;
+      else
+        t2 ^= t6 ^ c1;
+
+      t1 += c1 ^ t5;
+    }
+
+    l -= n;
+
+    if (!l)
+      break;
+  }
+
+  crc[0] = t6 ^ t4 ^ t3;
+  crc[1] = t5 ^ t2 ^ t1;
+
+  free(crc_buffer);
+  return 0;
+}
+
+int main(int argc, const char *argv[]) {
+  size_t min_sz = 2 * 1024 * 1024;
+
+  uint8_t *buffer, out[8];
+  uint32_t crc[2], c_crc[2];
+
+  long fsz;
+  int status;
+  FILE *f;
+
+  // Process arguments.
+  if (argc != 3) {
+    printf("Usage: %s <header> <filename>\n", argv[0]);
+    return EXIT_SUCCESS;
+  }
+
+  // Open header, read it into memory, close.
+  if ((buffer = calloc(min_sz, 1)) == NULL) {
+    perror("calloc");
+
+    return EXIT_FAILURE;
+  }
+
+  if ((f = fopen(argv[1], "rb")) == NULL) {
+    perror("fopen");
+    free(buffer);
+
+    return EXIT_FAILURE;
+  }
+
+  if (safe_fread(buffer, CHECKSUM_START, f)) {
+    fprintf(stderr, "Unable to read contents of: %s.\n", argv[1]);
+    free(buffer);
+    fclose(f);
+
+    return EXIT_FAILURE;
+  }
+
+  fclose(f);
+
+  // Open ROM contents, read it into memory, compute CRC, close.
+  if ((f = fopen(argv[2], "rb")) == NULL) {
+    perror("fopen");
+    free(buffer);
+
+    return EXIT_FAILURE;
+  }
+
+  if ((fsz = get_file_length_and_rewind(f)) < 0) {
+    fprintf(stderr, "Unable to determine file size of: %s.\n", argv[2]);
+    free(buffer);
+    fclose(f);
+
+    return EXIT_FAILURE;
+  }
+
+  if (safe_fread(buffer + CHECKSUM_START,
+    ((CHECKSUM_LENGTH - CHECKSUM_START) > fsz)
+      ? fsz : CHECKSUM_LENGTH - CHECKSUM_START, f)) {
+    fprintf(stderr, "Unable to read contents of: %s.\n", argv[2]);
+    free(buffer);
+    fclose(f);
+
+    return EXIT_FAILURE;
+  }
+
+  // Read current CRC, compute correct one.
+  c_crc[0] = btol(buffer + CRC_OFFSET);
+  c_crc[1] = btol(buffer + CRC_OFFSET + 4);
+  status = calculate_crc(buffer, crc);
+
+  fclose(f);
+
+  if (status) {
+    free(buffer);
+
+    return EXIT_FAILURE;
+  }
+
+  if ((c_crc[0] == crc[0] && c_crc[1] == crc[1]) && ((size_t) fsz >= min_sz))
+    return EXIT_SUCCESS;
+
+  // CRCs don't match; update the ROM.
+  ltob(crc[0], out);
+  ltob(crc[1], out + 4);
+
+  if ((f = fopen(argv[2], "rb+")) == NULL) {
+    perror("fopen");
+    free(buffer);
+
+    return EXIT_FAILURE;
+  }
+
+  // Ensure the file is at least min_sz in length, write the CRC.
+  if (safe_fwrite(buffer, min_sz, f)) {
+    fprintf(stderr, "Unable to write contents to: %s.\n", argv[2]);
+    free(buffer);
+    fclose(f);
+
+    return EXIT_FAILURE;
+  }
+
+  free(buffer);
+
+  if (fseek(f, CRC_OFFSET, SEEK_SET)) {
+    perror("fseek");
+    fclose(f);
+
+    return EXIT_FAILURE;
+  }
+
+  if (safe_fwrite(out, sizeof(out), f)) {
+    fprintf(stderr, "Unable to write CRC to: %s.\n", argv[2]);
+    fclose(f);
+
+    return EXIT_FAILURE;
+  }
+
+  fclose(f);
+  return 0;
+}
+
+long get_file_length_and_rewind(FILE *f) {
+  long sz;
+
+  if (fseek(f, 0, SEEK_END))
+    return -1;
+
+  if ((sz = ftell(f)) < 0)
+    return -1;
+
+  rewind(f);
+  return sz;
+}
+
+int safe_fread(uint8_t *buffer, size_t len, FILE *f) {
+  size_t i, amt;
+
+  for (i = 0; i < len; i += amt) {
+    amt = fread(buffer + i, 1, len - i, f);
+
+    if (ferror(f) || feof(f))
+      return -1;
+  }
+
+  return 0;
+}
+
+int safe_fwrite(const uint8_t *buffer, size_t len, FILE *f) {
+  size_t i, amt;
+
+  for (i = 0; i < len; i += amt) {
+    amt = fwrite(buffer + i, 1, len - i, f);
+
+    if (ferror(f))
+      return -1;
+  }
+
+  return 0;
+}
+