The Linux kernel is a moving target—many things change over time as new features are developed. The interface that we have described in this chapter is that provided by the 2.4 kernel; if your code needs to work on older releases, you will need to take various steps to make that happen.
This is the first of many “backward compatibility” sections in this book. At the end of each chapter we’ll cover the things that have changed since version 2.0 of the kernel, and what needs to be done to make your code portable.
For starters, the KERNEL_VERSION
macro was
introduced in kernel 2.1.90. The sysdep.h
header
file contains a replacement for kernels that need it.
The new resource management scheme brings in a few portability
problems if you want to write a driver that can run with kernel
versions older than 2.4. This section discusses the portability
problems you’ll encounter and how the sysdep.h
header tries to hide them.
The most apparent change brought about by the new resource management
code is the addition of request_mem_region and
related functions. Their role is limited to accessing the I/O memory
database, without performing specific operations on any hardware. What
you can do with earlier kernels, thus, is to simply not call the
functions. The sysdep.h
header easily
accomplishes that by defining the functions as macros that return
0
for kernels earlier than 2.4.
Another difference between 2.4 and earlier kernel versions is in the actual prototypes of request_region and related functions.
Kernels earlier than 2.4 declared both
request_region and
release_region as functions returning
void
(thus forcing the use of
check_region beforehand). The new implementation,
more correctly, has functions that return a pointer value so that an
error condition can be signaled (thus making
check_region pretty useless). The actual pointer
value will not generally be useful to driver code for anything other
than a test for NULL
, which means that the request
failed.
If you want to save a few lines of code in your drivers and are not concerned about backward portability, you could exploit the new function calls and avoid using check_region in your code. Actually, check_region is now implemented on top of request_region, releasing the I/O region and returning success if the request is fulfilled; the overhead is negligible because none of these functions is ever called from a time-critical code section.
If you prefer to be portable, you can stick to the call sequence we
suggested earlier in this chapter and ignore the return values of
request_region and
release_region. Anyway,
sysdep.h
declares both functions as macros
returning 0 (success), so you can both be portable and check the
return value of every function you call.
The last difference in the I/O registry between version 2.4 and
earlier versions of the kernel is in the data types used for the
start
and len
arguments. Whereas
new kernels always use unsigned long
, older kernels
used shorter types. This change has no effect on driver portability,
though.
Version 2.0 of the kernel didn’t use the CONFIG_SMP
configuration option to build for SMP systems; instead, choice was
made a global assignment in the main kernel
makefile
. Note that modules compiled for an SMP
machine will not work in a uniprocessor kernel, and vice versa, so it
is important to get this one right.
The sample code accompanying this book automatically deals with SMP in the makefiles, so the code shown earlier need not be copied in each module. However, we do not support SMP under version 2.0 of the kernel. This should not be a problem because multiprocessor support was not very robust in Linux 2.0, and everyone running SMP systems should be using 2.2 or 2.4. Version 2.0 is covered by this book because it’s still the platform of choice for small embedded systems (especially in its no-MMU implementation), but no such system has multiple processors.
The Linux 2.0 symbol export mechanism was built around a function called register_symtab. A Linux 2.0 module would build a table describing all of the symbols to be exported, then would call register_symtab from its initialization function. Only symbols that were listed in the explicit symbol table were exported to the kernel. If, instead, the function was not called at all, all global symbols were exported.
If your module doesn’t need to export any symbols, and you don’t want
to declare everything as static
, just hide global
symbols by adding the following line to
init_module. This call to
register_symtab simply overwrites the module’s
default symbol table with an empty one:
register_symtab(NULL);
This is exactly how sysdep.h
defines
EXPORT_NO_SYMBOLS
when compiling for version
2.0. This is also why EXPORT_NO_SYMBOLS
must appear
within init_module to work properly under Linux
2.0.
If you do need to export symbols from your module, you will need to create a symbol table structure describing these symbols. Filling a Linux 2.0 symbol table structure is a tricky task, but kernel developers have provided header files to simplify things. The following lines of code show how a symbol table is declared and exported using the facilities offered by the headers of Linux 2.0:
static struct symbol_table skull_syms = { #include <linux/symtab_begin.h> X(skull_fn1), X(skull_fn2), X(skull_variable), #include <linux/symtab_end.h> }; register_symtab(&skull_syms);
Writing portable code that controls symbol visibility takes an
explicit effort from the device driver programmer. This is a case
where it is not sufficient to define a few compatibility macros;
instead, portability requires a fair amount of conditional
preprocessor code, but the concepts are simple. The first step is to
identify the kernel version in use and to define some symbols
accordingly. What we chose to do in sysdep.h
is
define a macro REGISTER_SYMTAB()
that expands to
nothing on version 2.2 and later and expands to
register_symtab on version 2.0. Also,
__USE_OLD_SYMTAB__
is defined if
the old code must be used.
By making use of this code, a module that exports symbols may now do
so portably. In the sample code is a module, called
misc-modules/export.c
, that does nothing except
export one symbol. The module, covered in more detail in Section 11.3 in Chapter 11, includes the
following lines to export the symbol portably:
#ifdef __USE_OLD_SYMTAB__ static struct symbol_table export_syms = { #include <linux/symtab_begin.h> X(export_function), #include <linux/symtab_end.h> }; #else EXPORT_SYMBOL(export_function); #endif int export_init(void) { REGISTER_SYMTAB(&export_syms); return 0; }
If __USE_OLD_SYMTAB__
is set
(meaning that you are dealing with a 2.0 kernel), the
symbol_table
structure is defined as needed;
otherwise, EXPORT_SYMBOL
is used to export the
symbol directly. Then, in init_module
,
REGISTER_SYMTAB
is called; on anything but a 2.0
kernel, it will expand to nothing.
MODULE_PARM
was introduced in kernel version
2.1.18. With the 2.0 kernel, no parameters were declared explicitly;
instead, insmod was able to change the
value of any variable within the module. This method had the
disadvantage of providing user access to variables for which this mode
of access had not been intended; there was also no type checking of
parameters. MODULE_PARM
makes module parameters
much cleaner and safer, but also makes Linux 2.2 modules incompatible
with 2.0 kernels.
If 2.0 compatibility is a concern, a simple preprocessor test can be
used to define the various MODULE_
macros to do
nothing. The header file sysdep.h
in the sample
code defines these macros when needed.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.