Assignment/Kern 2 (Drivers)

From WoxWiki

Jump to: navigation, search
Assignment Out: Oct. 5 (Monday)
Assignment Due: Oct. 13 (Monday 11:59PM)

This is the second part of the CS169-only project kernel. The first part can be found here.

Please remember to read the course Programming Guide for good style guidelines.

Contents

Introduction

At this point we assume you have processes and threads working (especially make sure you have taken the steps described in the section about the Xen bus daemon, otherwise you are likely to get strange errors when trying to do device I/O).

Once you complete this section you should have device drivers for:

  • Terminals (including their line-disciplines)
  • Disks
  • Memory devices (/dev/zero, /dev/null)

Once again the NOT_YET_IMPLEMENTED macro will help you track down the functions you need to fill in. The code you will write for this part is in the drivers/ directory.

Device Drivers

There are two different types of devices: block devices and character devices. Both are similar but as you will see there are some differences in their implementation. The memory devices and disk devices you will be writing for are block devices while the terminals are character devices. In order to make these relationships easier to manage we use some magic:

C Polymorphism! (Wha?!)

As you look through the code, specifically the struct definitions of the different types of devices (found in the header files), you should notice that the struct for the more specific types of devices (e.g. terminals and disks, refered to as the sub-struct from here on) contain as fields the structures for the more generalized types of devices (e.g. block and byte devices, refered to as the super-struct from here on). These are not pointers, they occupy memory that is part of the sub-struct. This way given a sub-struct we can find the "parent" super-struct within it, but more importantly, given a super-struct we can find the "containing" sub-struct by using memory offsets (given a pointer to the struct of a byte device which you know is a terminal device, just subtract the size of the rest of the terminal device struct and you have a pointer to the beginning of the terminal device struct). There is a macro provided for the purpose of doing these pointer conversions called CONTAINER_OF. Look in the terminal device driver (drivers/term.c) for an example of its use.

You should also notice that onf of the fields in the device structs is a pointer to a struct of well defined function pointers. The generic device types (e.g. block and character devices) each specify that they expect certain operations to be available (e.g. read, write, this is very much like an interface in Java). This function pointer struct contains pointers to the functions which implement the expected operations so that we can perform the correct type of "read" operation (for example) without actually knowing what type of device struct we are working with. The definitions of these function pointer structs are in the *.c files of their respective types.

Terminal Device Driver

Each terminal device possesses a term_device struct. This struct contains all the state information and synchronization primitives necessary to operate a simple serial terminal. The drivers/term.c class itself is not all that interesting. The longest function is term_device_add which sets up a terminal device struct. Note that it also calls console_register_handler for us. This registers our terminal with the xenbusd system so that we will receive callbacks to our term_receive_char function when the user types into the terminal (each keystroke is an interrupt which eventually calls the term_receive_char function, telling us which character was typed).

Make sure you understand the difference between the low-level terminal driver and the higher level line discipline code which you are about to write. The device driver is responsible for the lower level calls that actually interact directly with the "hardware" (in this case it is actually interacting with the Xen bus since we aren't running on real hardware). The read/write semantics that you got from the read and write system calls when you were working on Shell are higher level and would deal with the tty, not the terminal.

TTY and Line Discipline

tty is the high-level device driver for for Weenix. This driver will sit atop the low-level driver and provide the terminal semantics which you are used to. The driver consists of two parts, the tty and the line discipline. The tty basically just passes all of the work on to the line discipline while the line discipline provides the normal features you expect from a terminal.

As you experienced when programming your shell, the read system call on a terminal blocks the calling thread until a newline from the user is encountered. In contrast to this, the code given to you in drivers/term.c manipulates only single characters at a time. In order to provide the functionality expected by normal read/write semantics your line discipline will need to buffer input and output (and you should be able to use backspace to delete from the input buffer). Linux also echoed characters which where typed into the terminal for you, you will need to do this yourself in your line discipline.

Once you have a working virtual file system (after VFS) you will access the terminals through files, specifically in the files /dev/tty0, /dev/tty1, etc. Then you can read and write to the terminals using the do_read and do_write functions. However, until them you will need to use the dev_byte_lookup function to get devices and then explicitly call the device's read/write functions in order to test your code.

Functions you will need to write in drivers/tty.c:

    int  tty_device_register (tty_device_t *tty);
    void tty_receive_char    (tty_device_t *tty, char c);
    void tty_provide_char    (tty_device_t *tty, char *c);
    int  tty_read            (byte_device_t *dev, int offset, void *buf, int count);
    int  tty_write           (byte_device_t *dev, int offset, const void *buf, int count);

Functions you will need to write in drivers/ttyldisc.c:

    int  ttyld_attach       (tty_device_t *tty);
    int  ttyld_read         (tty_device_t *tty, void *buf, int len);
    int  ttyld_write        (tty_device_t *tty, const void *buf, int len);
    void ttyld_receive_char (tty_device_t *tty, char c);
    void ttyld_provide_char (tty_device_t *tty, char *c);
    int  ttyld_echo         (tty_device_t *tty, const char *buf, int len);

Disk Device Driver

A block_device is associated with each disk device. This structure contains information about what threads are waiting to read or write from the disk, the disk's "event channel" (used to decide which interrupts we need to mask to stop receiving interrupts from this disk), any necessary synchronization primitives, and the device ID. To simplify writing the disk driver you can assume that all disk blocks are page-sized. We have defined the BLOCK_SIZE macro for you to be the same size as the size of a page of memory, you should use that instead of hard coded numbers.

Due to special way disk I/O needs to be handled on virtual machines there is some Xen specific code which needs to be run to interact with xenbusd; however, we have hidden this from you by providing a few functions in drivers/block_util.c which gives you an interface more similar to what you would have if you where working with a real disk device. See the comments for details.

Functions you need to write in drivers/disk.c:

    int  disk_read_at      (block_device_t *bdev, char *data, blocknum_t blocknum, unsigned int count);
    int  disk_write_at     (block_device_t *bdev, const char *data, blocknum_t *blocknum, unsigned int count);
    int  do_disk_operation (block_device_t *bdev, blocknum_t blocknum, char *data, int write);
    void disk_intr         (evtchn_port_t port, struct pt_regs *regs, void *data);

Memory Devices

You will also be writing some memory devices, which will not really be necessary until VFS. Still, these fit will with the other device drivers so you will be asked to implement a data sink and source.

If you have played around with a Linux/UNIX machine, you might be familiar with /dev/null and /dev/zero. These are both files that represent memory devices. Writing to /dev/null will always succeed (the data is discarded) and reading any amount from /dev/zero will always return as many 0's as you tried to read. The former is a data sink and the later is a data source. The low level drivers for both of these will be implemented in drivers/memdevs.c.

Functions you will need to write in drivers/memdevs.c:

    void memdevs_init(void);
    int  null_write (byte_device_t *dev, int offset, const void *buf, int ct);
    int  null_read  (byte_device_t *dev, int offset, void *buf, int ct);
    int  zero_read  (byte_device_t *dev, int offset, void *buf, int ct);

Testing

Once again we stress the importance of testing. Now that you have completed all of kernel go back to the original handout and look at our suggested tests. Remember to make a meeting to meet with your mentor TA and demo your code (which means showing off your test cases and explaining how they test all aspects of your kernel).

Don't forget to hand in your work after your demo.

Personal tools