Submit: Your code will be accessible for grading and TA-Bot runs if it is placed in a properly named project_3 branch.
Work should be completed in pairs. Be certain to include both names in the comment block at the top of all source code files. You may modify any files in the operating system, but only changes to "kprintf.c" will be graded for this assignment.
To set up for this assignment you will first set up github authentication, then create a repository from a template. This new repository will be where you work on assignments with your partner, and how we access your code for grading.
For this course we expect that each project will have its own branch in your github repository, named according to project number (ex: project_3 for this assignment). This layout is how TA-Bot will access your code, and how we will assign final grades. If each branch is not named correctly neither of those things will happen, so please pay close attention to naming conventions specified in the assignments.
The gh command-line utility is provided by github for easy access to and management of github repositories. See the manual for more usage details.
Read the instructions below and then follow the steps listed here:
The first command will prompt you to open a web browser, login to github, and enter a code. Doing so will allow you to access github using the gh tool.
The second command will modify your ~/.gitconfig allowing you to leverage the gh tool to to seamlessly authenticate dutring git operations.
The last command is a script that will create a new repository for you in the Operating Systems github organization; it will also create a local copy of the repo in your current working directory.
Ensure you enter the script arguments correctly -- your username can be found by running whoami.
The github organization can be viewed here.
While the gcc command-line options provide a great deal of flexibility when compiling programs, things quickly become unmanageable when the number of source files exceeds what you can conveniently type in a few seconds.
The make utility can be thought of as a companion to the compiler infrastructure (preprocessor, compiler, assembler, and linker) that allows the build rules for large projects to be explicitly encoded in Makefiles. A Makefile typically consists of common definitions, (such as, which compiler to use), and a set of rules. Each rule has a target, such as the file that is to be built, and can be followed by a list of dependencies and a sequence of steps to perform in order to build that target. In addition, make has quite a few common rules built into it.
You will not have to write your own Makefiles for this course, but you will have to use and possibly modify some for all of our remaining assignments. The Makefile is always human-readable, so feel free to open them up and look around.
To build the XINU operating system, perform the following steps:
This should produce about a page of output as each source file is compiled, and the resulting object files are linked together to form the operating system, a simple set of library functions, and the boot loader. If all goes as it should, you should find the directory full of .o files from all of the source code in the other subdirectories, and most importantly, a newly compiled operating system image called "xinu.boot."
For more information on make, please see the UNIX man pages.
Your XINU image is now ready to be run on a backend machine. To
transfer it there, we have a special utility
called riscv-console. Execute riscv-console in
the compile directory where your
xinu.boot file resides. riscv-console will
connect your terminal to the first available backend machine, and you
should see a message like:
connection 'poodoo', class 'riscv', host 'morbius.mscs.mu.edu'
depending on which backend you get. This will be immediately
followed by a stream of automated commands as the embedded target
system boots, configures its network settings, and uploads
your xinu.boot kernel.
The most important thing to remember about riscv-console is that it is modal, like vim. You start out in direct connection mode, in which your terminal connects directly through special hardware to the serial console on your backend machine. To quit out of riscv-console, hit Control-Space, followed by the 'q' key.
The source tar-ball we are starting with contains only a few files for the operating system proper, in the subdirectory system. We will be adding files into this directory in every subsequent assignment.
The other files in the XINU subdirectories break down as follows:
Your task for this assignment is to write a simple synchronous serial driver for the embedded operating system, so that you can see what you are doing in all subsequent assignments.
The driver is "synchronous" because it waits for the slow I/O device to do its work, rather than using interrupts to communicate with the hardware.
The driver is "serial" because it sends characters one at a time down an RS-232 serial port interface, like the one found on most modern PC's.
The driver is a "driver" because it provides the software interface necessary for the operating system to communicate with the hardware which, in this case, is an I/O device.
This platform's serial port, or UART (Universal Asynchronous Receiver / Transmitter) is a member of the venerable 16550 family of UARTs, documented here. Of particular interest to us is section 9.2.5 of the specification, which describes the registers accessible to programmers. On this platform (the Sipeed Nezha), the UART control and status registers are memory-mapped, starting with base address 0x2500000. You can view these address definitions in include/ns16550.h
The file system/kprintf.c has the skeleton code for four I/O-related functions: kputc(), (puts a single character to the serial port,) kgetc(), (gets a single character from the serial port,) kungetc(), (puts "back" a single character, ala K&R's getch() and ungetch() functions,) and kcheckc() (checks whether a character is available.) Each function contains a "TODO" comment where you should add code. The actual kprintf() is already complete; it will begin working as soon as you complete the kputc() function upon which it relies.
[Revised 2026 Feb 05 11:18 DWB]