Friday, 10 April 2009, 10:04

Enabling Kernel-Modesetting in Debian

Two months ago, the Linux kernel (on its way to version 2.6.29) introduced the (now famous) KMS (a.k.a. kernel-modesetting), which allows for performing low-level access to the graphics card from inside the kernel. Previously, userland drivers (from the X, or any other userland program) had to communicate with the kernel or directly configure the graphics card. However, with a KMS-enabled kernel, all the low-level configurations and accesses are performed by the kernel, which provides further support for several improvements:

First, the graphics card mode (resolution, color depth, etc) are set only once (at boot), and, therefore, switching between VT and X is instantaneous with KMS. Moreover, suspend and resume take less time, since (again) the mode switch doesn't happen, and the kernel performs all this operations in a cleaner and faster way.

Then, another improvement is that the userland programs will be provided with a generic API where they can write log messages or debug information when a crash happens. This means that now, when X crashes, the user will be able to get an informative message about it. Before, the X used to crash and that was all, you get your image frozen and cannot do anything nor now anything about it. But let's go down to business and see how to enable and configure KMS to get it working on a Debian GNU/Linux.

Hardware and Kernel Configuration

Right now, only Intel graphics card are supported in KMS, but I know that there is work being done for ATI/AMD devices. However, this quick tutorial assumes that you have an Intel graphics card, a Debian GNU/Linux unstable (a.k.a. Debian GNU/Linux Sid) and a kernel 2.6.29 or newer.

So the first step is to configure the kernel. For that, you can download the latest kernel, which, at the time of writing this, it is 2.6.29.1 (stable one). Then, make sure you mark the following options:

CONFIG_AGP=y
CONFIG_AGP_INTEL=y
CONFIG_DRM=y
CONFIG_DRM_I915=y
CONFIG_DRM_I915_KMS=y
CONFIG_FB=y
CONFIG_VGA_CONSOLE=y
CONFIG_FRAMEBUFFER_CONSOLE=Y

Note: If you are using an amd64 architecture, you should make sure that the option CONFIG_AGP_AMD64=y is enabled too. After the kernel is properly configured, build it but do not boot this new kernel before installing the proper userland applications. So first let's make sure that you have the proper configuration and applications.

Driver and Applications Installation

In order to get KMS working, you need the latest versions of the X stack running on your Debian GNU/Linux unstable. Please check those versions and compare them to the list below (which is the list of working versions):

  • xserver-xorg 1:7.4+1
  • xserver-xorg-core 2:1.6.0-1
  • xserver-xorg-video-intel 2:2.6.3-1
  • libdrm-intel1 2.4.5-2
  • libdrm2 2.4.5-2
  • libgl1-mesa-dri 7.4-2
  • libgl1-mesa-glx 7.4-2
  • libglu1-mesa 7.4-2

If you are missing one of those packages or some version is older than the ones listed above, please upgrade your system or install any missing package. All the packages listed above are already in Debian GNU/Linux unstable, so you won't have much trouble with that.

Finally, you have to configure your xorg.conf so that it makes use of the UXA acceleration method, which is the newest and fastest for Intel graphics. This is enabled by adding the following line to the Section "Devices":

Option "AccelMethod" "UXA"

UXA is an improved version of EXA which makes an extensive use of the specific characteristics present in Intel graphics cards. It also allows for DRI2 (redirected rendering) and much more. Once you have completed all these steps, you just have to reboot your computer and boot the new kernel with the KMS.

Note: If you were previously using VESA or some other framebuffer driver, please note that now you don't need this anymore, so you can remove the vga=XXX parameter from the boot line. In fact, the new kernel with KMS will switch to the best resolution and color depth when booting the system, which is one of the main purposes of the KMS.

How do I know that I have KMS?

Well, when you boot your new kernel, there should be a mode switch (like it used to happen from VT to X) in the beginning of the booting process. Then, when you get to the X, please try to switch between VT and X, and, if you enabled KMS correctly, this switch should be instantaneous, meaning that changing from VT to X or vice verse is like changing from desktop to desktop on the X.

If you cannot get it working, your X session crashes or you get strange colors on the screen, please contact me or send an email directly to intel-gfx AT lists.freedesktop.org.

Keywords: Kernel, Linux
Sunday, 8 February 2009, 12:02

Developing on Linux, Part 3: Writing Kernel Modules

On this phase, already the third article about writing drivers for Linux you will write your first module and insert it into the source tree of the kernel. I admit that it sounds high-profile, but it has no actual magic, and I would say it is fairly simple. Let's gets our hands dirty!

Linux 2.6.x Module Structure

The good thing about writing modules for the Linux 2.6 kernel is that they all have a standard structure that has to be accomplished, thus guiding the programmer at any moment. The typical structure of a kernel module can be depicted from the code below:

/*
 * module_template.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

int init_module()
{
    /* do something on initialization */

    return 0;
}

void clean_module()
{
    /* do something on module unloading */
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Module description here");
MODULE_AUTHOR("Module author here");

Needless to say, normal functions can be created inside the source of the module and they shall be called from init_module or clean_module.

An important thing to remember is that, when developing drivers on the kernel level, the standard library functions are not available. That is, malloc, printf and similar are replaced by more primitive functions offered by the kernel, such as kmalloc or printk. This happens with many functions available in the standard C library.

Hello World Module

So let's write a Hello, world! for the kernel. The code is shown below:

/*
 * hello_version.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

int init_module()
{
    printk(KERN_INFO "Hello, world!\n");

    return 0;
}

void clean_module()
{
    printk(KERN_INFO "Goodbye, cruel world!\n");
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Greeting module");
MODULE_AUTHOR("Claudio M. Camacho");

Now, in order to compile it, we just need a simple Makefile, which may look like this:

obj-m := hello_version.o

KDIR := /usr/src/linux
PWD := $(shell pwd)

default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

Assuming that your module has been written in a file named hello_version.c, the above-provided Makefile will generate the necessary object (.ko file) so that it can be loaded against the kernel. Please note that the KDIR path should be set to the directory where your kernel sources are residing.

Once you type make, a hello_version.ko kernel object file will be generated. Now you can load it using insmod hello_version.ko and remove it using rmmod hello_version. If everything goes right, you should see the following messages on your console (or terminal):

~$ sudo insmod hello_version.ko
Hello, world!
~$ sudo rmmod hello_version
Goodbye, cruel world!

Accordingly, you may try the lsmod command in order to check that your kernel module has been loaded successfully.

Module Parameters

For this example, you will create a module that can accept parameters. Don't worry, the kernel architecture has been designed so that you do not have to deal with much trouble when adding parameters to your driver modules. Consider the following module code:

/*
 * test_driver.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

/* parameters */
static char *whom = "Claudio";
module_param(whom, charp, 0);

static int howmany = 1;
module_param(howmany, int, 0);

int init_module()
{
    int i = 0;

    for (i = 0; i < howmany; i++)
        printk(KERN_ALERT "(%d) Hello, %s\n", i, whom);

    return 0;
}

/* module exit */
void cleanup_module(void)
{
    printk(KERN_ALERT "Goodbye cruel %s!\n", whom);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Greeting module");
MODULE_AUTHOR("Claudio M. Camacho");

As simple as that, now you can load your module passing some parameters. As you can also see, the parameters have some default value (Claudio and 1), which will be taken in account if no parameters are specified on the command line when loading the module. In order to test the module parameters you have to load it issuing its parameters with some values:

~$ sudo insmod test_driver.ko howmany=3 whom=Antonio
Hello, Antonio
Hello, Antonio
Hello, Antonio

In this case, if you specify 3 as howmany, the loop in init_module will be run three times, thus printing three times the greeting message. Please realize that this is only a simple example, but that is the way the kernel modules work when being loaded with parameters.

Adding a Driver to the Kernel Sources

Finally, now that you have probably got the idea of the modules and how do they work for the Linux kernel, it would be nice to create an useful driver and integrate it into the kernel sources. In fact, it would be nice to see the driver in the kernel configuration interfaces, with its help, its dependencies and everything else, like any other driver in the kernel.

Before adding the driver to the kernel, let's write a third example. A more complicated driver that does something else than just greeting people. For this example, you will write a driver that creates an entry in the /rpoc directory (procfs) and shows information to the userland.

One nice example would be to count the number of seconds that have elapsed since the kernel module was loaded into the kernel memory. So, for instance, an user could issue cat /proc/elapsed and see how many seconds have elapsed from the moment the module was loaded. The module code is below:

/*
 *  proc_test_driver.c
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>

#define procfs_name "elapsed"

struct proc_dir_entry *Our_Proc_File;
struct timeval start;
struct timeval end;

int procfile_read(char *buffer,
                  char **buffer_location,
                  off_t offset, int buffer_length, int *eof, void *data)
{
    int ret;

    /* check the time */
    do_gettimeofday(&end);

    /* this is for syscalls like read(), that will continue issuing reading */
    if (offset > 0)
    {
        /* we have finished to read, return 0 */
        ret  = 0;
    } else {
        /* fill the buffer, return the buffer size */
        ret = sprintf(buffer, "%lds\n", end.tv_sec - start.tv_sec);
    }

    return ret;
}

int init_module()
{
    /* check the time */
    do_gettimeofday(&start);

    /* create the file under /proc */
    Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL);

    /* check for errors con creation */
    if (Our_Proc_File == NULL)
    {
        remove_proc_entry(procfs_name, NULL);
        printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
               procfs_name);
               return -ENOMEM;
    }

    /* configure reading on the proc file */
    Our_Proc_File->read_proc = procfile_read;
    Our_Proc_File->owner     = THIS_MODULE;
    Our_Proc_File->mode      = S_IFREG | S_IRUGO;
    Our_Proc_File->uid       = 0;
    Our_Proc_File->gid       = 0;
    Our_Proc_File->size      = 37;

    return 0;
}

void cleanup_module()
{
    remove_proc_entry(procfs_name, NULL);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("/proc test driver");
MODULE_AUTHOR("Claudio M. Camacho");

Before inserting this driver into the kernel mainline, let's analyze what it does. First, as you need to use the /proc filesystem, you need to include the proc_fs.h header. The name of the file under /proc is defined as procfs_name.

init_module checks the time (this is the time when the module was loaded). Please note that there is no gettimeofday but do_gettimeofday instead, which is a kernel-level equivalent for gettimeofday(). Then the module creates the proc file entry and configures it. The configuration is a manual tuning of the proc file structure.

The most important parts of configuring the proc file entry are the read_proc symbol and the size. The former is a pointer to the function that fills the buffer for creating a text interface to display when reading the file under /proc, and the latter is the maximum file size on the proc entry. Accordingly, any time the userland tries to read this file entry, the function pointed by read_proc will be issued, in order to fill the buffer with text, to be returned to the userland process that is reading.

In addition, it is important to remove any entry that has been created in the /proc when the module is unloaded. This is critical, since not removing it makes the system unstable and the /proc directoy would not work anymore, returning a Segmentation fault since a pointer to the kernel memory is missing.

Finally, the function procfile_read is the most difficult part in this module. This is the callback (or hook) to fill the buffer for the userland programs. As a suggestion, if you do not intend to develop a very professional driver using the /proc filesystem, it is enough if you implement a similar function in your code any time you want to let the userland read from your proc entry. The vague documentation about procfs makes it difficult to invent different approaches, and so it is advisable to use conventional callbacks seen in the examples available on the Internet.

So, at last, you have been waiting this moment, and now it is time to insert your new driver into the kernel sources. For that purpose, you will clone you kernel sources directory and apply some modifications there:

~$ cd /usr/src
~$ sudo rsync -a linux-2.6.28.4 linux-2.6.28.4-patched
~$ cd linux-2.6.28.4-patched
~$ sudo cp ~/proc_test_driver.c drivers/misc/
~$ cd drivers/misc/
~$ sudo echo "obj-$(CONFIG_PROC_TEST_DRIVER)  += proc_test_driver.o" >> Makefile

Now you should edit the Kconfig file under drivers/misc in order to add you driver to the kernel configuration interface. This will make your driver to appear in configuration interfaces such as menuconfig, xconfig, etcetera. You should add something similar to the lines below:

config PROC_TEST_DRIVER
	tristate "/proc Test Driver"
	depends on X86
	---help---
	 This driver adds support for testing a simple dummy driver on the /proc filesystem.

After this is done, you kernel is ready to compile your new driver. If you go to the kernel sources root directory and issue make menuconfig, going into the Device Drivers, Misc devices, you should see your driver and the corresponding help and everything, just like a normal driver. Furthermore, try to enable it a compile your kernel. If you build it as a module, you can then load it and unload it, testing the file /proc/elapsed as we discussed previously. On the other hand, if you make the driver built-in, then the file /proc/elapsed should be permanent.

Last, it is possible to create a patch for the vanilla kernel sources. This is quite useful, since you can add your driver to the kernel sources by sending a signed patch to Linus Torvalds. This won't be the case, but let's just create a patch and see that it actually works:

~$ cd /usr/src
~$ sudo diff -Nurp linux-2.6.28.4/ linux-2.6.28.4-patched/ > proc_test_driver.patch
~$ cd linux-2.6.28.4
~$ cat ../proc_test_driver.patch | sudo patch -p1
patching file drivers/misc/Kconfig                                       
patching file drivers/misc/Makefile                                      
patching file drivers/misc/proc_test_driver.c

Afterwards, your vanilla kernel should now contain your new driver. You can menuconfig it and compile, and you will see that your driver is actually compiled ant it works. Great! Isn't it?

References

The Linux Kernel Module Programming Guide [online]. Peter Jay Salzman, 2001.
URL: http://tldp.org/LDP/lkmpg/2.6/html/
Accessed 8th February 2009

Sunday, 1 February 2009, 11:02

Developing on Linux, Part 2: Kernel Compilation

In the first part of these articles you learned how to download the kernel sources and apply some patches. Besides you do know now how to search for a certain information in the kernel sources as well. In this part, you are going to learn how to configure a kernel, how to compile it for a given architecture and, what is more important, how to cross-compile and test it from one architecture to another.

Kernel Configuration

In order to configure the kernel, you must issue some commands to get a configuration menu where you can select which options will be enabled and which will not. For this lab exercise, you will focus on configuring a minimal kernel for an i386 computer and test it with Qemu.

So the first thing is to move into the kernel directory. Let's use a 2.6.28.2 kernel, which is quite new (as of writing this article). Please notice that you need certain packages to make the configuration menu appear, because it is generated on the fly, so you must install some packages such as ncurses-dev and the obvious gcc and make (in case you don't have them already).

~$ sudo apt-get install bzip2 gcc make ncurses-dev
~$ wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.28.2.tar.bz2
~$ tar jxvf linux-2.6.28.2.tar.bz2
~$ cd linux-2.6.28.2
~$ make menuconfig

After typing make menuconfig on the console, you should get a curses-based menu where you can configure every option on the kernel. Each option is classified inside sub-menus, organized according to the type of devices, kernel subsystems, etcetera. Notice that on each option in the kernel configuration menu there is a Help button (on the bottom right), that you can access and you will get a brief explanation about that option and what it enables.

For a minimal i386 PC, you will have to disable almost anything, but leaving the processor type option for PC-compatible and i386 architecture. So now, please try to configure your kernel for the following architecture (which you will later test with a certain image for qemu):

  • PC i386 computer
  • IDE ATA hard drive
  • ext2 file system

Kernel Compilation

Now please take your time and go through every sub-menu in the kernel (it may take you more than an hour), reading carefully every help and disabling anything does it would not be critical, such as device drivers, USB and SCSI, etcetera. Just leave the basic processor configuration, the IDE ATA driver (generic one) and the ext2 file system module on the File Systems subsection. Once you have your configuration ready, it is time to compile the kernel. This can be done in the following way:

~$ make

Testing

Now, in order to test this kernel, instead of trying it out on your own machine (which could obviously not be an i386 PC), you will download some lab files from free-electrons:

~$ wget http://free-electrons.com/labs/embedded_linux.tar.bz2
~$ mkdir labs
~$ cd labs
~$ tar jxvf ../embedded_linux.tar.bz2

That tarball contains a set of tests that you can readily use with your lab exercises and you won't have to bother so much with you PC machine. So once you have compiled your kernel, the image of that kernel will be placed in linux-2.6.28.2/arch/x86/boot/bzImage. In order to test that the kernel is working, perform the following command (using Qemu):

~$ qemu -m 32 -kernel linux-2.6.28.2/arch/x86/boot/bzImage -hda data/linux_i386.img -boot c -append "root=/dev/hda"

If you configured your kernel correctly, you will see a kernel booting on the emulator screen and it will reach a point where it complains: Warning: unable to open an initial console.. Don't worry this is normal, since the image is ready to reach this point, and that will mean that you have correctly configured and compiled your kernel for a minimal i386 installation.

Cross-Compiling

Cross-compiling is the technique of compiling a given software for a certain architecture using tools which are compiled for a different architecture. That is, it is possible to compile a kernel, for instance, for the ARM architecture on a i386 machine, thanks to the cross-compiling toolchains.

For this exercise, please go into the embedded_linux/labs/linux/lab3 directory and download the 2.6.20 version of the Linux kernel and apply the existing patch in the data/ subdirectory.

~$ cd labs/linux/lab3
~$ wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.20.tar.bz2
~$ tar jxvf linux-2.6.20.tar.bz2
~$ cd linux-2.6.20
~$ cat ../data/patch-arm-versatile-qemu-2.6.24 | patch -p1

Now it is time to install the ARM cross-compiling toolchain, which contains the necessary tools to generate the ARM kernel:

~$ sudo echo "deb http://free-electrons.com/labs/ubuntu/ ./" >> /etc/apt/sources.list
~$ sudo apt-get update
~$ sudo apt-get install buildroot-uclibc-arm-toolchain
~$ export PATH="/usr/local/uclibc-0.9.28-2/arm/bin/:$PATH"

Now that you have installed the toolchain and set up your PATH environment variable, you will have to modify the Makefile in order to proper tell the make system to use the cross-compiler, instead of the one used by default on your i386 architecture. Therefore, in the kernel's root directory's Makefile, you should set the following options:

ARCH ?= arm
CROSS_COMPILE ?= arm-linux-

Please note that you could also set those as environment variables before issuing make, which might be cleaner. Nevertheless, it is clear enough that you can also do it in the Makefile, for the sake of simplicity. You can also, before compiling, perform a make menuconfig and see which options are available, where there is a difference in the sub-menus available for x86 machines. Now let's build the kernel.

~$ make menuconfig
~$ make

Testing the Cross-Compiled Kernel

Now, in order to test if the kernel is working or not, you should create an initram file system (initramfs) with the directory rootfs provided in labs/linux/lab3/data/rootfs. Create an initramfs with that directory structure and then test with Qemu and the cross-compiled kernel image:

~$ mkdir initrd
~$ dd if=/dev/zero of=initrd.img bs=1k count=4096
~$ mkfs.ext2 -F initrd.img
~$ sudo mount -o loop initrd.img initrd/
~$ sudo rsync -a data/rootfs/ initrd/
~$ sudo umount initrd

Finally, you should have your initrd.img ready to be run with Qemu, so let's test the ARM kernel:

~$ qemu-system-arm -M versatilepb -m 16 -kernel linux-2.6.20/arch/arm/boot/zImage -append "clocksource=pit console=tty0 rw"

At this point, you should able to reach the command line (shell) on your ARM kernel, which means that you have been able to cross-compile the 2.6.20 kernel and run it on the ARM emulator. Congratulations!

 Older posts >>
© Claudio M. Camacho

Updated on Sunday, 29 August 2010 18:02