CMSC 421: Principles of Operating Systems

Project 2: Minesweeper 2: Electric Boogaloo

This project is due on Sunday, December 18, at 11:59:59 PM (Eastern standard time). You must use submit to turn in your project like so: submit cs421_jtang proj2 minesweeper2.c minesweeper2-test.c

Your module code must be named minesweeper2.c, and it will be compiled against a 4.4 Linux kernel source tree, via the Kbuild supplied below. It must not have any compilation warnings; warnings will result in grading penalties. This module code must be properly indented and have a file header comment, as described on the coding conventions page. Prior to submission, use the kernel's indentation script to reformat your code, like so:

    ~/linux/scripts/Lindent minesweeper2.c
  

In addition, you will write a unit test program, minesweeper2-test.c, and it will be compiled on Ubuntu 14.04 as follow:

  gcc ‐‐std=c99 ‐Wall ‐O2 ‐pthread ‐o minesweeper2-test minesweeper2-test.c cs421net.c ‐lm
There must not be any compilation warnings in your submission; warnings will result in grading penalties. In addition, this code must also have a file header comment and be properly indented. You will submit this test code along with your module code.

In this project, you will write a Linux driver that extends the Minesweeper game introduced in the first project. This improved version of the game adds networking support, cheat mode, and statistics tracking.

Part 1: Compile Minesweeper Module

All instructions hereforth assume you successfully completed the first project. If you have not done so, go back and finish the assignment before proceeding. You have been warned.

To begin, create a directory for your project and download the following files into that directory via wget:

http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj2/xt_cs421net.c
A Netfilter module that simulates a network device.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj2/xt_cs421net.h
Header file that declares symbols defined in xt_cs421net.c.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj2/cs421net.c
Adds networking functions to your unit test code.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj2/cs421net.h
Header file that declares symbols defined in cs421net.c.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj2/Kbuild
Read by Linux kernel's build system, defines what is being built.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj2/Makefile
Builds the kernel module and unit test program, by simply running make. Also includes a clean target to remove all built objects.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj2/proj2_start.sh
Shell script that sets up the Minesweeper networking system. After downloading this file, mark the script executable (chmod u+x proj2_start.sh).
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj2/proj2_stop.sh
Shell script that disables the Minesweeper networking system. After downloading this file, mark the script executable (chmod u+x proj2_stop.sh).
You do not need to modify any of these files, nor should you submit any of them with your work.

After downloading all of these files into a new directory, copy your files from Project 1 as follows:

  1. Copy play.sh to this directory.
  2. Copy your minesweeper.c and minesweeper-test.c to this directory. Rename them to minesweeper2.c and minesweeper2-test.c, respectively.
  3. At the top of minesweeper2.c, add the line #include "xt_cs421net.h".
  4. Add the top of minesweeper2-test.c, add the line #include "cs421net.h". At the top of main(), add a function call to cs421net_init().

Now run make to compile everything. Upon success, you should now have the kernel driver minesweeper2.ko. Run proj2_start.sh; it may ask for your password, as that it runs some sudo commands. This will set up your VM for this project. Rerun this script if you ever need to reboot your VM.

Part 2: Implement Networking Support

The first change to your code is to add networking support. Read the code in xt_cs421net.c. When this Netfilter module is installed, via the proj2_start.sh script, it will raise an interrupt every time your computer receives network packets on TCP port 4210. It is your driver's responsibility to handle those interrupts and treat the packets as if a user were writing to /dev/ms_ctl.

First add the line #include <linux/interrupt.h> to the top of minesweeper2.c. Then update minesweeper_init() to install a threaded interrupt handler for interrupt number CS421NET_IRQ. Remove that handler in minesweeper_exit(). Implement your interrupt handler as follows:

/**
 * cs421net_top() - top-half of network ISR
 * @irq: IRQ that was invoked
 * @cookie: Pointer to data that was passed into
 * request_threaded_irq() (ignored)
 *
 * If @irq is %CS421NET_IRQ, then wake up the bottom-half. Otherwise,
 * return %IRQ_NONE.
 */
static irqreturn_t cs421net_top(int irq, void *cookie);

/**
 * cs421net_bottom() - bottom-half to network ISR
 * @irq: IRQ that was invoked (ignored)
 * @cookie: Pointer that was passed into request_threaded_irq()
 * (ignored)
 *
 * Fetch the incoming packet, via cs421net_get_data(). Treat the data
 * as if it were user input, as per minesweeper_ctl_write(). Remember
 * to add appropriate spin lock calls in this function.
 *
 * Note that the incoming payload is NOT a string; you can NOT use
 * strcpy() or strlen() on it.
 *
 * Return: always %IRQ_HANDLED
 */
static irqreturn_t cs421net_bottom(int irq, void *cookie);

As that cs421net_bottom() will be manipulating your game state, be sure to guard the code with a spin lock. Because the bottom half is running in process context, not interrupt context, you do not need to use spin_lock_irqsave().

Once you are confident your ISR works, call cs421net_enable() in minesweeper_init(), and likewise disable network integration with cs421net_disable() in minesweeper_exit(). Install your module, and check /proc/interrupts to ensure your ISR was registered.

To test this enhancement, ensure you earlier ran proj2_start.sh. In a second terminal, run this command: telnet localhost 4210. Use this second terminal to send messages to your driver, just as if you were writing game commands to /dev/ms_ctl. Close the telnet session by pressing control-] (right bracket) and then entering close.

You can also test this by connecting to other students' virtual machines. Assuming that both yours and your friend's VMs are on the same network, enable port forwarding as necessary. Then telnet to the other VM; you can view IP addresses via the ifconfig command.

Part 3: Add Cheat Mode

The next new feature is to add a cheat mode to the game. In minesweeper2.c, add the line #include <linux/sched.h>. In ms_read(), if the UID of the requesting task is zero (i.e., the root user) then do not copy user_view to the caller. Instead, return a representation of the board with the mines marked. Test this by running either sudo cat /dev/ms or sudo ./play.sh.

Then add two new commands to ms_ctl_write():

  1. If the user enters aXY, then add a mine to location (X, Y). X and Y must be integers from zero through nine. Regardless if there was a mine or not already at (X, Y), unreveal the square (X, Y) and its eight surrounding squares, also removing any marked mines during the process. Decrement mines_marked as necessary.
  2. If the user enters dXY, then remove the mine at location (X, Y), if any. X and Y must be integers from zero through nine. Regardless if there was a mine or not already at (X, Y), unreveal the square (X, Y) and its eight surrounding squares, also removing any marked mines in the process. Decrement mines_marked as necessary. Do not allow the user to remove the last mine in the game; return an error in this case.
These commands may only be executed by UID 0. If a different UID sends these inputs, then return -EPERM. If these inputs came via CS421Net, ignore them.

Be sure to update the game status with the new number of mines in the game. Upon a new game, reset the number of mines in the game to 10.

Test these new commands from the command-line like so:

    $ sudo su -c 'echo a42 > /dev/ms_ctl'
    $ sudo su -c 'echo d10 > /dev/ms_ctl'

You can retrieve your own UID via the shell command id or programmatically via getuid(). Within the kernel, be aware of various blog posts describing the current_uid() call. Kernel UIDs have changed, and thus older blog posts are inaccurate. In modern kernels, you have to use the val field to get the numeric identifier; also, see the kernel code itself.

Part 4: Record Statistics

The last new game feature is to record game statistics for each game played. Whenever a game ends, either because the user revealed a mine or because all mines were marked correctly, record these three values:

In minesweeper2.c, add the lines #include <linux/gfp.h>, #include <linux/list.h>, and #include <linux/slab.h>. Declare a kernel linked list to hold the above three values. Then declare a global variable to hold a pointer to the beginning of the linked list. Use kzalloc() to dynamically create each list node. Append nodes to the linked list with the macro list_add_tail(). Because this list can be accessed by multiple threads, guard accesses to the list using a spin lock.

Next, create a global character pointer; name it stats_view. In minesweeper_init(), use vzalloc() to dynamically allocate PAGE_SIZE bytes for the pointer and set the buffer to zero. Duplicate the existing function ms_mmap() as ms_stats_mmap(); in this copy change the parameter in vmalloc_to_pfn() to stats_view. See the original Project 1 minesweeper.c for examples of how to use these functions.

Then, create a third miscellaneous device, /dev/ms_stats. Register this device during module initialization. Set its mmap callback to ms_stats_mmap(). This device has no read nor write callbacks. Set the default file mode to 0444.

Every time your linked list changes, regenerate the contents of stats_view. For each node on the list, write the node's values, space-separated, to stats_view, using newlines to separate each node. Assume that all statistics can fit within a PAGE_SIZE.

In minesweeper_exit(), free all memory that was allocated for the list. Release the memory associated with stats_view with vfree(). Finally, deregister the new miscellaneous device.

As an example, the memory-mapped contents of /dev/ms_stats could contain the following:

10 5 1 (Five mines were correctly marked and 1 incorrectly mark.)
12 0 0 (No mines were marked, correctly nor incorrectly. Also, the root user added two mines to the game.)
10 10 0 (All 10 mines were correctly marked, and thus the game was won.)

Part 5: Unit Tests and Documentation

Now that you have (in theory) a working driver, you must then write your own unit tests. Read the networking code in cs421net.h. You can use cs421net_send() to send arbitrary data to the server. Modify your minesweeper2-test.c to exercise all functionality new to this assignment. This includes a mix of inputs when writing to the device nodes and network socket, and confirming the memory map contents of /dev/ms_stats.

As a hint, your minesweeper2-test should have two modes: when it is running as the root user and when not. If its current UID is 0, then it should be able to add and delete mines; otherwise adding and deleting mines should not be permitted. The grader will run minesweeper2-test using sudo and without.

The unit tests must have comments that explain what things are being tested. As before, your goal is to test boundary conditions of your driver's interfaces. You will be graded based upon the thoroughness of the tests.

Other Hints and Notes

Extra Credit

You may earn an additional 5% credit for this assignment by recording time spent playing each game. Store the current timestamp when a game begins. When the game ends, record in the statistics list the game duration, in seconds (rounded down). Prepend each line read from /dev/ms_stats with the time. For example, a read from /dev/ms_stats could return the following:

30 10 5 1 (This game lasted 30 seconds, before the player lost.)
1 12 0 0 (This player lost almost immediately.)
600 10 10 0 (The player won the game after playing for 10 minutes.)

You may earn another 5% extra credit by augmenting the output from /dev/ms_stats. Change that memory map to return a list sorted by time, from shortest duration to longest. You may choose the sorting behavior in cases of ties.

Afterwards, update minesweeper2-test.c to test this new functionality.

If you choose to perform this extra credit, put a comment at the top of your file, alerting the grader.