OSI Lab6: SD Card Driver
Devices are essential components of a computer system, enabling programs to interact with the external world, such as through I/O operations. In this lab, you will study how the CPU communicates with devices, the role of device drivers, and how drivers function. In particular, you will implement an SD card driver, which will later serve as the storage medium for our file system.
Fetch lab6 from upstream repo
- fetch
lab6branch$ git fetch upstream lab6 <you should see:> ... * [new branch] lab6 -> upstream/lab6 - create a local
lab6branch$ git checkout --no-track -b lab6 upstream/lab6 <you should see:> Switched to a new branch 'lab6' - confirm you’re on local branch
lab6$ git status <you should see:> On branch lab6 nothing to commit, working tree clean - rebase your previous labs to the current branch
<you're now on branch lab6> $ git rebase lab5You need to resolve any conflict caused by the rebase. Read how to resolve conflict here.
- push branch
lab6to remote repoorigin$ git push -u origin lab6 - Make sure your merged code-base runs without errors.
Note Makefile config: MLFQ, ECALL, and VMON
- In lab6, the kernel executes in M-mode; all addresses are physical.
(It will be an interesting final project to move all drivers to U-mode.)- The SD card driver (your implementation) should be compatible with your own scheduler, system calls, and virtual memory subsystem: while developing lab6, make sure everything works together.
Section 1: Understanding SD Specifications
A. Background: Memory-mapped IO and UART (as an example)
The OS communicates with devices to perform different tasks. For example, the tty device handles user input and displays output on the screen. Underlying this functionality is the UART protocol, which egos uses to interact with input and output devices such as the keyboard and screen. The file earth/dev_uart.c shows how to read and write characters from and to a tty device over the UART protocol.
To understand uart_getc and uart_putc in earth/dev_uart.c, observe that the CPU exposes a memory-mapped I/O (MMIO) region for UART communication at 0x10000000UL (UART_BASE in library/egos.h). This address serves as the data register for transmitting and receiving bytes. For example, on input, uart_getc reads the least significant byte from the the 32-bit value from the MMIO data register. Meanwhile, the driver consults the Line Status Register (LSR), mapped at a separate MMIO address, to determine device state—specifically, whether input data is available or whether the transmit holding register is empty. For details, refer to the 16550 UART register specification.
Device tty is one simple device. Next, we will work with another device, SD card, which is the core to the lab. In this lab, you will enable egos to support SD devices.
B. SDHCI: programming interface
SDHCI stands for Secure Digital Host Controller Interface. It defines a register-level programming interface between the operating system and an SD host controller Concretely, it defines:
- (a) MMIO register layout
- (b) SD commands
- (c) DMA mechanisms (e.g.,
SDMA) - others
At a high level, the CPU interacts with the SD device by issuing commands and receiving responses. Each response reports the execution status of the corresponding command, including error conditions. In this lab, we ignore detailed error handling and focus on the command–response control flow. SD cards have multiple implementation variants across vendors and controller generations. In this lab, we target only the model emulated by QEMU and restrict our implementation to its exposed behavior.
Next, we elaborate on the details of the programming interface, SDHCI.
(a) MMIO register layout
SDHCI defines the layout of memory-mapped I/O registers used to control SD devices. Refer to this table for an excerpt of the SDHCI Host Control Registers. Read this document to identify the registers required to configure data transfers, and refer to this pdf for the status registers used to track command execution and transfer progress.
In this lab, you will implement Single Operation DMA (SDMA). Below we summarize the purpose of the relevant registers for SDMA.
| Offset | Register |
| 0x00 | SDMA System Address (Low) |
| 0x02 | SDMA System Address (High) |
| 0x04 | Block Size |
| 0x06 | Block Count |
| 0x08 | Argument (High) |
| 0x0A | Argument (Low) |
| 0x0C | Transfer Mode |
| 0x0E | Command |
Brief field descriptions:
- SDMA System Address (0x00) holds the physical memory address used by SDMA. The controller reads from or writes to this address during data transfer. The full address spans the low and high parts.
- Block Size (0x04) specifies the size of each data block in bytes. Commonly set to 512 bytes for SD cards.
- Block Count (0x06) specifies the number of blocks to transfer in multi-block operations.
- Argument (0x08) contains the 32-bit command argument passed to the SD card. For example, it encodes the block address for read/write commands.
- Transfer Mode (0x0C) configures data transfer behavior. Controls DMA enable, block count enable, read/write direction, and multi-block selection.
- Command (0x0E) encodes the command index (e.g.,
CMD17,CMD25). Writing this register triggers command execution.
(b) SD commands
In the lab, you will interact with the following commands. This is a subset of all commands.
| Command Index | Argument | Description |
| CMD12 | None(0) | Stop to read data. |
| CMD17 | Address[31:0] | Read a block. |
| CMD18 | Address[31:0] | Read multiple blocks. |
| CMD24 | Address[31:0] | Write a block. |
| CMD25 | Address[31:0] | Write multiple blocks. |
In the table, “CMD12” refers to a command with a command index of 12. Refer to SD Specifications: Physical Layer for the full set of commands.
(c) DMA
As discussed, this lab uses SDMA (a simple DMA). Refer to the SDMA workflow document to understand its sequence. Below we present a simplified version tailored to lab6. 
The registers shown in the figures correspond to those defined in the MMIO layout section.
References:
- Command specifications: SD Specifications: Physical Layer
- SDHCI: SD Specifications: SD Host Controller Specificiations
- SDMA workflow document
- Table: Host Control Registers
- Transfer reigster specs
- SDMA System Address
- Block Size
- Block Count
- Argument
- Transfer Mode
- Command
- Status register specs
- Present State Register
- Normal Interrupt Status Register
Section 2: SD card driver
In this section, you will implement an SD card driver. In particular, you will study the provided example for reading a block from the SD card, then complete the function of writing a block. Finally, you will extend the driver to support multi-block reads and writes.
A. Understanding single-block read
The primary function of an SD card driver is to transfer data between memory and the SD card. In a read or write transaction, the CPU begins by sending a command, waits the command to complete, and finish the transaction.
We begin with a single-block read (512 bytes) from the SD card. The CPU issues CMD17 and waits for the corresponding response. After processing CMD17, the card returns the response and updates the status registers to reflect command completion and transfer readiness.
Using this control flow, you will implement the SD driver logic required to read one block from the SD card. (sd_single_read() in earth/dev_sdcard.c).
Exercise 1 complete single-block read
- Read
sdhci_exec_cmd()inearth/dev_sdcard.cto understand how to issue a command (also refer to SDMA flow).- If this is your first time reading driver code, it might take you some time to read back and forth between code and the instructions.
- Turn on SD card driver by changing
DISK=SDCARDinMakefile- Then, you will see,
$ make qemu ... [FATAL] sdhci_single_read is not complete- Read and complete
sd_single_read().- After completion, you should see:
$ make qemu ... [CRITICAL] Welcome to the egos-2k+ shell! [INFO] proc 5 finished after 0 yields, turnaround time: 0.00, response time: 0.00, cputime: 0.00 ➜ /home/cs6640 %
B. Implementing single-block write
Next, implement the write path of the driver. Note that SDMA requires the buffer to be aligned to the block size; in our configuration, this means 512-byte alignment.
Exercise 2 implement single-block write
- Complete the function
sd_single_write()enabling writing data to the SD card.- To test your code, uncomment
sd_test()indisk_init()ofearth/dev_sdcard.cand run egos.
(Search[lab6-ex2]inearth/dev_sdcard.c.)- You should pass the first “single-write test” twice:
$ make qemu ... [CRITICAL] === Start SD card testing === [SUCCESS] single-write test passed! [SUCCESS] single-write test passed! [FATAL] sdhci_multi_read() is not implemented
C. Multi-block reads and writes
Reading and writing a single block at a time is inefficient. SDMA supports transferring multiple blocks in a single transaction. In this section, your task is to implement multi-block read and write operations for the SD card driver.
Multi-block reads
CMD18 performs a sequential multi-block read starting from the address specified in its argument. It enables a single transfer operation to fetch multiple contiguous blocks and write them into physical memory. The transfer terminates upon receipt of CMD12. In this lab, you should enable the Auto CMD12 feature so that the controller issues CMD12 automatically at the end of the transfer.
Exercise 3 implement multi-block read
- Implement
sdhci_multi_read()inearth/dev_sdcard.c- After completion, you should pass the second test case:
$ make qemu ... [CRITICAL] === Start SD card testing === [SUCCESS] single-write test passed! [SUCCESS] single-write test passed! [SUCCESS] multi-read test passed! [SUCCESS] multi-read test passed! [FATAL] sdhci_multi_write() is not implemented
Multi-block writes
Finally, implement multi-block writes. The CMD25 command writes multiple contiguous data blocks starting at the address specified in its argument, transferring the blocks sequentially in a single operation.
Exercise 4 implement multi-block write
- Implement
sdhci_multi_write()inearth/dev_sdcard.c- After completion, you should pass the third test:
... [CRITICAL] === Start SD card testing === [SUCCESS] single-write test passed! [SUCCESS] single-write test passed! [SUCCESS] multi-read test passed! [SUCCESS] multi-read test passed! [SUCCESS] multi-write test passed! [SUCCESS] multi-write test passed! [CRITICAL] ============================= ... ➜ /home/cs6640 %
Finally, submit your work
Submitting consists of three steps:
- Executing this checklist:
- Fill in
~/osi/egos/slack/lab6.txt. - Make sure that your code build with no warnings.
- Fill in
Push your code to GitHub:
$ cd ~/osi/egos/ $ git commit -am 'submit lab6' $ git push origin lab6 Counting objects: ... .... To github.com/NEU-CS6640-labs/egos-<YOUR_ID>.git c1c38e6..59c0c6e lab6 -> lab6Actually commit your lab (with timestamp and git commit id):
Get the git commit id of your work. A commit id is a 40-character hexadecimal string. You can obtain the commit id for the last commit by running the command
git log -1 --format=oneline.- Submit a file named
git.txtto Canvas. (there will be an assignment for this lab on Canvas.) The filegit.txtcontains two lines: the first line is your github repo url; the second line is the git commit id that you want us to grade. Here is an example:git@github.com:NEU-CS6640-labs/egos-<YOUR_ID>.git 29dfdadeadbeefe33421f242b5dd8312732fd3c9Notice: the repo address must start with
git@github.com:...(nothttps://...). You can get your repo address on GitHub repo page by clicking the green “Code” button, then choose “SSH”. - Note: You can submit as many times as you want; we will grade the last commit id submitted to Canvas. Also, you can submit any commit id in your pushed git history; again, we will grade the commit id submitted to Canvas.
Notice: if you submit multiple times, the file name (git.txt) changes togit-N.txtwhereNis an integer and represents how many times you’ve submitted. We will grade the file with the largestN.
NOTE: Ground truth is what and when you submitted to Canvas.
A non-existent repo address or a non-existent commit id in Canvas means that you have not submitted the lab, regardless of what you have pushed to GitHub—we will not grade it. So, please double check your submitted repo and commit id!
The time of your submission for the purposes of tracking lateness is the timestamp on Canvas, not the timestamp on GitHub.
This completes the lab.
Acknowledgments
Some materials are borrowed from Yunhao Zhang’s notes.