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 (tty.h)

  • 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 (ldisc.c)

  • 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
    • 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
    • MEM_ZERO_DEVID
      • Equivalent of MKDEVID(1,1)
    • MEM_NULL_DEVID
      • Equivalent of MKDEVID(1,0)
    • You can use MKDEVID directly or one of the other defined macros to give a value for cd_id. For a deeper explanation of how it works read dev.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
  • sata.c
    • SATA_SECTORS_PER_BLOCK
      • Use this in conjunction with block_count to figure out what sector you're reading/writing from
    • bdev_to_ata_disk(bd)
      • Takes in a block device and returns an ata_disk_t*

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() in sata.c
  • Line discipline that handles interaction with terminals
    • ldisc_init(), ldisc_wait_read(), ldisc_read(), ldisc_key_pressed(), ldisc_get_current_line_raw() in ldisc.c
  • TTY implementation
    • tty_[read,write] in tty.c
  • Memory devices (/dev/zero and /dev/null)
    • memdevs_init(), null_[read,write], zero_read() in memdevs.c
  • Modify initproc_run() in kmain.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
  • 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
  • sata.h
    • Fields for ata_disk_t
  • tty.h
    • Fields for tty_t
  • 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 calls driverstest_main()
    • Call driverstest_main() directly in initproc_run() as you did for proctest_main() during Procs
  • 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);
  • kshell.c
    • Add kshell_add_command(name, function, description);

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 to tty_read and tty_write. To use them, if you have a pointer to chardev_t, let's say cd, to invoke its operations, you can do cd->cd_ops->read(), see driverstest.c for an example.
  • What do the null_read and null_write do?
    • tty_read and tty_write correspond to the cd_ops for a tty, null_read and null_write correspond to the cd_ops for the device /dev/null. You are responsible for implementing the function null_write directly—if you were to invoke the cd_ops->write() on the character device corresponding to /dev/null, it would eventually invoke the null_write function.
  • vterminal_key_pressed vs vterminal_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 and sata.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