Drivers Help Guide - brown-cs1690/handout GitHub Wiki
Theme Song: Shut Up and Drive - Rihanna
Resources
What is Drivers?
- Drivers are pieces of software that allow the operating system to communicate with hardware devices. For example, your computer has audio drivers, which might take high level OS commands and process them into low level hardware commands that your speakers can understand and execute.
- For this project, you will be coding portions of the drivers for a TTY, SATA hard disk drive, and some special memory devices.
Devices
Character Devices
- Devices that read/write one byte at a time (tty, keyboard, etc.)
Block Devices
- Devices that store lots of data, often in blocks, and read/write at this block level (hard drives, cds, etc.)
Memory Devices
/dev/null
is a data sink that discards all data written to it/dev/zero
is a data source that returns as many null bytes as requested
TTY
- TTY stands for TeleTYpewriter. The name comes from a mechanical device that was used to send and receive typed messages, but now it generally refers to any Unix terminal.
tty.h
)
tty (- This struct represents our tty and contains some important members, including the virtual terminal where the text is printed to, and a line discipline to handle incoming characters.
- ttys are character devices and employ their own versions of read and write operations (
tty_cdev_ops
).
Line Discipline
- The line discipline is the layer between the character device interface (
tty.c
) and the lower level driver (keyboard.c
). - It processes and stores incoming characters
ldisc.c
)
ldisc (- This struct represents the line discipline and is implemented using a circular buffer, with three pointers to divide the buffer into, raw, cooked, and empty.
- When all three pointers are equal, the buffer could be full or empty, so we have a flag to differentiate between the two states.
- The buffer is only flushed when one of
ETX
,EOT
, or\n
is received. - We flush the buffer by cooking any raw characters, then signaling any thread that is waiting on the ldisc
- Tips:
- When the buffer is full, all incoming characters should be ignored
- To prevent the scenario where a full buffer cannot be flushed, we leave one byte open for one of
ETX
,EOT
, or\n
. This allows us to flush a "full" buffer.
Macros
There are several macros that you should be aware of that you will utilize in different files:
ldisc.c
ldisc_to_tty(ldisc)
- Takes in a line discipline and returns a
tty_t*
- Defined in
ldisc.c
- Takes in a line discipline and returns a
LDISC_BUFFER_SIZE
- The number of characters that can be stored in the line discipline
- Defined in
ldisc.h
memdevs.c
MKDEVID(major, minor)
- Takes in a major number and a minor number, and returns a
devid_t
- Takes in a major number and a minor number, and returns a
MEM_ZERO_DEVID
- Equivalent of
MKDEVID(1,1)
- Equivalent of
MEM_NULL_DEVID
- Equivalent of
MKDEVID(1,0)
- Equivalent of
- You can use
MKDEVID
directly or one of the other defined macros to give a value forcd_id
. For a deeper explanation of how it works readdev.h
(though an understanding of this is not necessary)
tty.c
cd_to_tty(cd)
- Takes in a chardev and returns a
tty_t*
- Defined in
tty.h
- Takes in a chardev and returns a
sata.c
SATA_SECTORS_PER_BLOCK
- Use this in conjunction with
block_count
to figure out what sector you're reading/writing from
- Use this in conjunction with
bdev_to_ata_disk(bd)
- Takes in a block device and returns an
ata_disk_t*
- Takes in a block device and returns an
To-Dos and To-Donts
What is completed for you?
- Virtual terminal implementation
- Block devices (reads and writes in terms of blocks of bytes)
- Character devices (reads and writes in terms of singular bytes)
- Keyboard interaction
- PCIe (peripheral component interconnect express)
- The screen display (code that actually writes things to the screen)
- You will be writing code that essentially call functions that do work for you, but you'll also be writing code that deals with the line discipline and how different inputs are handled (such as backspaces, enter, etc.). The characters actually appearing on your screen visually is handled for you!
What do you have to complete?
- The SATA block device that handles reads and writes to disks
sata_[read,write]_block()
insata.c
- Line discipline that handles interaction with terminals
ldisc_init()
,ldisc_wait_read()
,ldisc_read()
,ldisc_key_pressed()
,ldisc_get_current_line_raw()
inldisc.c
- TTY implementation
tty_[read,write]
intty.c
- Memory devices (
/dev/zero
and/dev/null
)memdevs_init()
,null_[read,write]
,zero_read()
inmemdevs.c
- Modify
initproc_run()
inkmain.c
to have your terminals appear
Files you'll be using during the project
You will be directly modifying the following files:
ldisc.c
- Functions that deal with managing your line discipline
memdevs.c
- Special memory devices,
/dev/null
and/dev/zero
- Special memory devices,
sata.c
- Functions for reading from and writing to the disk
tty.c
- Functions for reading from and writing to TTY
It will be helpful to refer to the following files:
ldisc.h
- Detailed comments describing line discipline and fields for
ldisc_t
- Detailed comments describing line discipline and fields for
sata.h
- Fields for
ata_disk_t
- Fields for
tty.h
- Fields for
tty_t
- Fields for
vterminal.c/vterminal.h
- Not necessary to look through for the purposes of Weenix, but if you're curious about how the kshell works visually you can look through these files
commands.c/commands.h/kshell.c
- See how kshell commands are added so you can make your own!
Testing
- Once you're ready to start testing your kshell, you can run the drivers tests in one of two ways
- Adding a
driverstest
command in your kshell that callsdriverstest_main()
- Call
driverstest_main()
directly ininitproc_run()
as you did forproctest_main()
during Procs
- Adding a
- A list of cases to test for can be found on the Drivers handout
- Make sure all of your kshell commands work correctly and see if you can break it
Adding KShell Commands
If you want to add your own kshell commands it's important to know how to do so. Whenever you add a new kshell command you should modify these files:
commands.c
- Add a function with what you want your command to do
commands.h
- Add
KSHELL_CMD(name);
- Add
kshell.c
- Add
kshell_add_command(name, function, description);
- Add
KShell Command Example
commands.c
long kshell_green(kshell_t *ksh, size_t argc, char **argv) {
tty_t* tty = cd_to_tty(ksh->ksh_cd);
tty->tty_vterminal.attr = (vtattr_t) { 0, VTCOLOR_GREEN, VTC_DEFAULT_BACKGROUND };
vterminal_make_active(&tty->tty_vterminal);
return 0;
}
commands.h
KSHELL_CMD(green);
kshell.c
kshell_add_command("green", kshell_green, "changes color of terminal to green");
The command should turn the text of your kshell to green when you use it! If you finish drivers early don't be afraid to look around the different kshell files and try adding your own functionality (though none of it will count for extra credit)
Debugging
- As always, it may be helpful to use tools from the debugging handout such as GDB, debugging statements, KASSERTs, etc.
- Comment out drivers tests systematically and see where it could be going wrong
- It may be helpful to draw out what your line discipline buffer looks like as you're modifying it and compare that to the actual output (using GDB)
FAQs
- How are special characters handled?
- Enter on blank line: The kshell prompt should print again
- Backspace: delete the character and emit (or no-op if there's no character to delete)
- ETX: remove the uncooked part and have a '\n' written on the terminal
- How do the
chardev_ops
relate to the functions we write?- You are technically writing the functions for
chardev_ops
! As an example, for a tty, they correspond totty_read
andtty_write
. To use them, if you have a pointer tochardev_t
, let's saycd
, to invoke its operations, you can docd->cd_ops->read()
, seedriverstest.c
for an example.
- You are technically writing the functions for
- What do the
null_read
andnull_write
do?tty_read
andtty_write
correspond to thecd_ops
for a tty,null_read
andnull_write
correspond to thecd_ops
for the device/dev/null
. You are responsible for implementing the functionnull_write
directly—if you were to invoke thecd_ops->write()
on the character device corresponding to/dev/null
, it would eventually invoke thenull_write
function.
vterminal_key_pressed
vsvterminal_write
?vterminal_key_pressed
will get the raw part of the buffer and display that to the vterminal — you should call this function only for non-special characters.vterminal_write
is used for displaying the special characters, namely,\n
and\b
. While\n
should be placed in the line discipline buffer,\b
should not.
Getting Started
- Double check that Drivers = 1 is set in
Config.mk
- Read. Read through all of the documentation we give you. It will save you a lot of time if you spend some time reading through things and understanding how Drivers works. You won't get a 100% understanding on your first read, but it's good to have some baseline understanding of what you're implementing
- You can start with any of the files. You may find
memdevs.c
andsata.c
the easiest files to write code for.tty.c
also shouldn't be too demanding.ldisc.c
will be the trickiest as you will writing functions that deal with the line discipline's buffer and user input - Try messing around with the Linux command line to get a feel for how your kshell will work