Novell Home

Migrating 2.4 Modules to the 2.6 Kernel

Novell Cool Solutions: Feature
By Simon Nattrass

Digg This - Slashdot This

Posted: 18 May 2005
 

Abstract

In the paper "Creating SUSE Linux Device Drivers" we covered a basic introduction to device driver development for the 2.4 kernel. With the recent advance to the 2.6 kernel in products such as SUSE Linux Enterprise Server 9 and Novell Linux Desktop, this article examines the differences involved in developing to this new kernel version.

Contents
  1. Compilation
  2. helloworld.c Revisited
  3. sample_driver.c Take Two
  4. A Word (or Two) on sample_driver's Data Buffer

Compilation

Casting your mind back to the 2.4 era you will recall that compiling a kernel module was a relatively simple process:

gcc –D__KERNEL__ –DMODULE –DMODVERSIONS –O2 –Wall 
     –I/usr/src/linux/include –o module.o –c module.c

With the advent of the 2.6 kernel comes the kbuild system for kernel compilation, which although present in the 2.4 kernel has now been greatly improved. Kbuild is a well defined set of Makefiles to configure, compile and install the kernel including any loadable modules.

Additional kernel modules can be built by two principal means:

  • Configure the raw sources in /usr/src/linux and build against this

  • Use one of the standard configurations in /usr/src/linux–obj/$ARCH/$FLAVOR

Option 1 – Using the sources in /usr/src/linux

  1. Change to the /usr/src/linux directory and configure the kernel (for example, make oldconfig or make cloneconfig', see Configuring the Kernel Source in the previous paper).

  2. Create files required for compiling external modules
  3. make modules_prepare
  4. Compile the module(s) by navigating to the module source directory and typing:
  5. make –C /usr/src/linux M=$(pwd) modules
  6. Install the module(s)
  7. make –C /usr/src/linux M=$(pwd) modules_install

Option 2 – Using one of the configurations in /usr/src/linux–obj/$ARCH/$FLAVOR

If the installed kernel sources match the running kernel, then /lib/modules/$(uname –r)/build is the preferential path to use (which is a symlink to /usr/src/linux–obj/$ARCH/$FLAVOR)
  1. Install kernel–syms.$ARCH.rpm. This package is necessary for symbol version information (CONFIG_MODVERSIONS).

  2. Compile the module(s) by navigating to the module source directory and typing:
  3. make –C /lib/modules/$(uname –r)/build M=$(pwd) modules
  4. Install the module(s)
  5. make –C /lib/modules/$(uname –r)/build M=$(pwd) modules_install

Of course we're not going to get very far without a Makefile for the new module. So here goes...

obj–m := module.o

Yes, it really is that simple! obj–m specifies the object files to be built as loadable kernel modules. If the module is built from several source files then this information is passed to kbuild through the $(<module_name>–objs) variable. For example:

obj–m := foo.o
foo–objs := bar.o barfoo.o foobar.o

Once we have our Makefile we execute the following command to build the module generating our binary

make –C /lib/modules/$(uname –r)/build M=$(pwd)

Kernel 2.6 introduces a new file naming convention for module binaries which now have a .ko extension (in place of the old .o extension) distinguishing them from conventional object files.

helloworld.c Revisited

Our original 2.4 version of the Hello World! program looked like the following:

/* 
 * helloworld.c 
 */

#include <linux/module.h> 
#include <linux/kernel.h>

int init_module(void)
{
     printk("<1>Hello World!\n");
     return 0;
}

void cleanup_module(void)
{
     printk(KERN_ALERT "Goodbye World...\n");
}

The method of module initialization and shutdown has changed so that arbitrary names may be assigned to the respective functions. Of course since these names can now be anything, these functions must be registered as the initialization and clean up sections of the module. Although present in the 2.4 kernel, this etiquette was not always followed and since this is the preferred method – now is as good a time as any to change old habits.

module_init(helloworld_init);
module_exit(helloworld_finish);

So putting it all together we now have:

#include <linux/module.h> 
#include <linux/kernel.h& 

int helloworld_module(void)
{
     printk("<1>Hello World!\n");
     return 0;
}

void helloworld_finish(void)
{
     printk(KERN_ALERT "Goodbye World...\n");
}

module_init(helloworld_init);
module_exit(helloworld_finish);

With these minimal modifications let's go ahead, create our Makefile and build our new module.

Makefile

obj–m := helloworld.o

Build (executed in the same directory as our Makefile and helloworld.c).

$ make –C /lib/modules/$(uname –r)/build M=$(pwd) modules

Assuming all has compiled correctly, let's load the module into the kernel and check the output log:

Load the module

$ insmod helloworld.ko

Check the output log

$ tail /var/log/messages

But what's this – "unsupported module tainting kernel"?

...
Apr 19 10:56:25 fiji kernel: helloworld: unsupported module, tainting kernel.
Apr 19 10:56:25 fiji kernel: Hello World!
...

We've forgotten the module licensing touched upon in the previous paper! Going back to the code if we add the following:

MODULE_LICENSE("GPL");

Perform a quick rebuild, remove and reinsert into the kernel.

$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ rmmod helloworld
$ insmod helloworld.ko

After checking the log again you will notice that the module is still tainting the kernel, this is due to a new SUSE policy of supported vs unsupported kernel modules. Essentially a module can be one of three variants: supported by SUSE, supported by SUSE via a third party or unsupported. In our situation we've developed an unsupported module since SUSE knows nothing of it, and as we have just witnessed, by default the kernel flags all unsupported modules.

This flagging is controlled via the switch in /proc/sys/kernel/unsupported which can have the values:

0 – only load supported modules
1 – warn when loading unsupported modules
2 – load and don't warn

Examining /proc/sys/kernel/unsupported we see that the switch is set to 1 by default as we'd expect from the previous results. To remove our taint warning we can pass unsupported=2 as a parameter to the kernel at boot time via either the LILO or GRUB boot loader. Alternatively the module can be explicitly flagged as supported, but remember this implies a support agreement with SUSE and thus the details to enable this functionality are only available on request. See /usr/src/linux/README.SUSE for further information.

Returning to our helloworld module, we'll keep the GPL license and we can safely ignore the tainted message we see in the logs.

Now that we've got these basics under our belt let's revisit our old friend sample_driver.c.

sample_driver.c Take Two

In the previous paper we looked at sample_driver.c - a fictitious device consisting of a small buffer which could be written to and read from. Here we examine the changes required to have this example work under the 2.6 kernel.

Module versioning is now handled by the kbuild system and thus MONDVERSIONS and linux/modversions.h are no longer required.

Wrapper.h which contained some of the memory management functions previously no longer exists in the 2.6 kernel and thus this include has been removed.

 * sample_driver.c 
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
...

The initialization and shutdown functions should really now be static with additional the __init and __exit macros to tell the kernel that these functions are only used at initialization and shutdown. This results in the initialization function being freed from memory once initialization is complete, while a call the shutdown function any any time other than unloading the module will now force an error.

Memory management has been greatly simplified in our example with allocation solely handled by get_zeroed_page(). The memory mapping initialization has been removed (see later).

...

/*
 * Initialization
 */
static int __init start_module(void)
{
     // get one free page for the data pool
     data_pool = (char *) get_zeroed_page(GFP_KERNEL);
     if ( data_pool == NULL )
     {
          return -ENOMEM;
     }
...

Memory management is simplified again at clean up time, with a single free_page() function. The module creation and destruction handlers are now bound as previously discussed with module_init() and module_exit()

/*
 * Cleanup
 */
static void __exit end_module(void)
{
     // unreserve our page used for the data pool and free it 
     free_page((unsigned long) data_pool);

     printk(KERN_ALERT "goodbye World...\n");
} // end_module()

module_init(start_module);
module_exit(end_module);
...

The macros for incrementing and decrementing the module usage counter have been removed in favour of two functions try_module_get() and module_put() to lock and release the module respectively.

/*
 * Open
 */
static int sampledriver_open(struct inode *inode, struct file *file)
{
     printk(KERN_ALERT "opening sample_driver\n");
     try_module_get(THIS_MODULE);     
     
     return 0;
} // sampledriver_open()

/*
 * Release
 */
static int sampledriver_release(struct inode *inode, 
                                struct file *file)
{
     printk(KERN_ALERT "releasing sample_driver\n");
     module_put(THIS_MODULE);
     
     return 0;
} // sampledriver_release()
...

With these code changes in place, compile as described earlier (remember the simple makefile!) and run exactly as the 2.4 driver.

# insmod sample_driver.ko
# mknod /dev/sample_driver c 254 0
# cp foo.txt /dev/sample_driver
# cp /dev/sample_driver bar.txt
# less bar.txt

There we have it, a few simple changes and sample_driver.c from the previous paper is happily working in the 2.6 kernel.

A Word (or Two) on sample_driver's Data Buffer

The sample_driver.c example uses the low level function get_zeroed_page() to obtain a single page of zeroed memory. On x86 a single page is 4K, which which nicely fits with our scenario using cp which requests blocks up to 4K, repeating until zero bytes are returned (see the output in /var/log/messages).

In the previous paper a combination of __get_free_page(), virt_to_page(), mem_map_reserve() and memset() were all used to allocate memory and provide memory mapping. Ultimately the memory mapping functionality was removed to keep the example concise and simple however these calls to initialize the memory mapping erroneously remained. Thus the only allocation is of real interest which has been reduced to the single get_zeroed_page() call mentioned above.

Summary

With this second installment on driver development we've observed some of the immediate differences a developer is likely to face. As before we're only just touching upon a vast subject, but with these lessons learned we're on the right path and heading in the right direction.

References


Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

© 2014 Novell