Accessing the Device From User Space

This is the final step of the guided part of this exercise. We will now develop a simple user space application that accesses the virtual device through the driver we just implemented.

Connecting via SSH from the Host to the VM

In this part you may need to edit files within the VM, and possibly transfer files between the host and the VM. You will notice that Qemu's virtual serial output (the console you get in the terminal after starting the VM) is not very stable when you type long commands (> 1 line of terminal), and that text editors also struggle to display things correctly in the VM. To get access to a stable console, it is better to rely on an SSH connection from the host to the VM.

With the simple virtual network we are using for that exercise (the -nic user option of Qemu), the host and the guest don't see each other directly, but we can use the following trick: Qemu's networking can be used to forward the SSH port of the VM on a given port p on the host. Once this is done, by connecting via SSH from the host locally on p, we end up in the VM.

To forward the VM's SSH port (22) to a port on the host, i.e. 1022, change the -nic option of Qemu in your [VM launch script]((05-installing-alpine.html#creating-a-vm-boot-script) of Qemu to the following:

-nic user,hostfwd=tcp::1022-:22

Launch the VM, and wait for it to boot. Then, from the host, connect via SSH to the local port 1022 in a new terminal:

ssh root@localhost -p 1022
root@localhost's password: 
Welcome to Alpine!

You are now in the VM. You can also use scp to transfer files between the host and the VM, and vice versa. These transfers need to be initiated from the host. For example to transfer a file from the host to the VM:

scp -P 1022 /path/to/local-file-on-the-host.txt root@localhost:/path/to/destination/on/the/vm

And to transfer a file from the VM to the host:

scp -P 1022 root@localhost:/path/to/source-file-on-the-vm.txt /path/to/destination/on/the/host

Note that for ssh we indicate the port with p, while for scp it is done with P, which is not particularly intuitive.

Creating the Virtual File for the Device

Before we can write the user space app that will connect to the device through the driver, we need to create the virtual file /dev/my_rng_driver mentioned in the previous step. To do so, type the following command within the VM:

mknod /dev/my_rng_driver c 250 0

The major number, here 250, must match the one you defined within the driver in the initialisation function. After invoking mknod the virtual file should be present in /dev:

ls -l /dev/my_rng_driver 
crw-r--r--    1 root     root      250,   0 Dec 20 22:32 /dev/my_rng_driver

You will need to repeat that operation each time the VM reboots. To avoid so you can configure Alpine to automatically create the virtual file each time the VM boots by creating a file (in the VM) /etc/init.d/init-my-rng-virtual-file and placing the following in it:

#!/sbin/openrc-run

mknod /dev/my_rng_driver c 250 0

Then giving that file execution permissions:

chmod +x /etc/init.d/init-my-rng-virtual-file

Installing a C Toolchain and a Text Editor in the VM

We will next write the user space application. You can either write it on the host and transfer the source file to the VM, or write it directly within the VM In both cases the application's source file will need to be compiled in the VM. You can install the text editors vim and nano, as well as the build toolchain (C compiler, etc.), with the Alpine package manager. To do so, run the following command inside the VM:

apk add build-base vim nano

You can now use vim or nano to edit files, and use gcc to compile C programs in the VM.

Writing the User Space Application

The source code of the user space application follows. We start by including a few headers for printing to the standard output, accessing files, and performing ioctl commands.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

Next we have two constants that are the ioctl numbers that were allocated for the 2 functions offered by the driver. To find them look in the VM's kernel log.

#define RAND_IOCTL	0x80047101
#define SEED_IOCTL	0x40047101

Finally, we have the main function that contains our test code:

int main() {
    int fd = open("/dev/my_rng_driver", O_RDWR);
    if (fd < 0) {
        perror("Failed to open the device file");
        return -1;
    }

    unsigned int seed = 0x0;
    unsigned int random_number = 0;

    for(int i=0; i<2; i++) {

        // seed the generator
        if(ioctl(fd, SEED_IOCTL, &seed)) {
            perror("ioctl seed");
            return -1;
        }

        // get 5 random numbers
        for (int j=0; j<5; j++) {
            if(ioctl(fd, RAND_IOCTL, &random_number)) {
                perror("ioctl rand");
                return -1;
            }

            printf("Round %d number %d: %u\n", i, j, random_number);
        }
    }

    close(fd);
    return 0;
}

This code starts by opening the virtual file representing the driver, /dev/my_rng_driver. It then follows similar steps to our in-kernel test we ran earlier: we seed the RNG, and generate 5 random numbers. We do that twice in a row to confirm that with the same seed, the device will return the same sequence of random numbers. Notice how ioctl is called with as parameter:

  • The virtual file descriptor fd
  • The ioctl code we want to invoke (RAND_IOCTL or SEED_IOCTL)
  • The address of a variable that will be filled with the random number generated (for RAND_IOCTL), or the address of a variable holding the seed we want to use (for SEED_IOCTL).

You can compile that code within the VM, assuming you write it in a file named my-app.c as follows:

gcc my-app.c -o my-app

When launching the program, you should see a series of 2 similar random number sequences:

./my-app
Round 0 number 0: 1804289383
Round 0 number 1: 846930886
Round 0 number 2: 1681692777
Round 0 number 3: 1714636915
Round 0 number 4: 1957747793
Round 1 number 0: 1804289383
Round 1 number 1: 846930886
Round 1 number 2: 1681692777
Round 1 number 3: 1714636915
Round 1 number 4: 1957747793

That's it! We have reached the end of the guided part of this exercise. Now the next step is to enhance the device/driver. There are various ways to achieve that, and it is up to you to choose an avenue of improvement. A few suggestions are given in the next and last step of this guide.