"); // A
MODULE_LICENSE("GPL");
static int my_read
(struct file *filp, char *buf, int length, int *offset);
static int my_open (void);
static int my_close (void); // B
int init_module (void);
void cleanup_module (void);
struct file_operations fops = // C
{ .read = my_read,
.open = my_open,
.release = my_close
};
static int my_open (void) // D
{ return 0;
}
static int my_close (void) // E
{ return 0;
}
static int my_read // F
(struct file *filp, char *buf, int length, int *offset)
{ return 0;
}
int init_module (void) // G
{ major = register_chrdev (MAJOR_DEVICE_NUMBER, DEVICE_NAME, &fops);
if (major < 0)
return major;
else
return 0;
}
void cleanup_module (void) // H
{ unregister_chrdev (major, DEVICE_NAME);
}
Section explainations
A. When a driver module is installed, the module should contain a special header that specifies the license and author of the module. Otherwise, Linux may not install the module when the time comes. These two macros create this header with the given strings. The string used for license is examined by Linux when the module is installed for a recognizable value. The string “GPL” can be used for demonstration and experimentation.
B. This section is simply the prototypes of all module functions. This isn’t normally necessary in a C/C++ program, but is required for a kernel module.
C. The special struct file_operations section is where the table of member routines is specified for each Linux system call associated with the hardware device. This syntax may be new to many C++ programmers -- it simply assigns function names to members of the structure. Those that aren’t defined default to NULL. To see what system call functions are available to be defined, look in fs.h. Release refers to the close() function – the other two are obvious.
D. This function is invoked in response to a Linux open() call for /dev/my_device
E. This function is invoked in response to a Linux close() call.
F. This function is invoked in response to a Linux read() call. The parameters are defined below;
file *filp, // a pointer to a special structure containing the state of the device
char *buf, // a pointer to the user’s array when data is to be inserted
int length, // the number of bytes requested by the caller
int *offset // the current position within the file.
(In the example below, the first and last parameters are simply ignored.)
G. The function init_module() is invoked by Linux when the module is installed. This is where the driver is associated or bound to the /dev/my_device; by passing the register_chrdrv() the major device number, the name of the driver module, and the list of driver functions to be associated with system I/O calls. If the registration fails, the return code will be an error.
H. This function is invoked when the device driver is removed
The device driver given below is a complete example of a read-only device. In this case, the hardware device defined in step #1 above really doesn’t exist. This driver simply ‘generates’ characters when the device is opened. Normally, the device driver would access the physical registers of the hardware device to input chars. Let’s look over the code and then explain each section in detail:
// Demonstration device driver: hello.c
#include
#include
#include
#include
#define DEVICE_NAME "hello" // I
#define MAJOR_DEVICE_NUMBER 60
#define BUF_LEN 128
static char msg[BUF_LEN]; // J
static char *msg_ptr;
static int device_open = 0;
static int major;
static int count = 0;
MODULE_AUTHOR("Scott Cannon ");
MODULE_LICENSE("GPL");
static int my_read (
struct file *filp, char *buf, int length, int *offset);
static int my_open (void);
static int my_close (void);
int init_module (void);
void cleanup_module (void);
struct file_operations fops =
{ .read = my_read,
.open = my_open,
.release = my_close
};
static int my_open (void)
{ if (device_open)
return -EBUSY; // K
device_open = 1;
sprintf (msg, "Hello from the device on open # %d!\n", count++);
msg_ptr = msg; // L
return 0;
}
static int my_close (void)
{ device_open = 0;
return 0;
}
static int my_read
(struct file *filp, char *buf, int length, int *offset)
{ int bytes_read = 0;
if (*msg_ptr == 0) return 0; // M
while (length && *msg_ptr)
{ put_user (*(msg_ptr++), buf++); // N
length--;
bytes_read++;
}
return bytes_read; // P
}
int init_module (void)
{ major = register_chrdev (MAJOR_DEVICE_NUMBER, DEVICE_NAME, &fops);
if (major < 0)
return major;
else
return 0;
}
void cleanup_module (void)
{ unregister_chrdev (major, DEVICE_NAME);
}
Section explainations
I. The module name need not be the same as the hardware device name used in the mknod command. Typically, this is the name of the source file that contains the driver (sans .c extension).
J. Different driver functions usually communicate with each other via global variables. To retain the values of these variables when the driver is not active, we qualify declarations with the static keyword.
K. If the device has already been opened, return a pre-defined error code for failure.
L. Here is where the driver ‘generates’ characters for the device. It simply copies a fixed literal string into an array that the read() function will access.
M. The variable msg_ptr points to the next char to be read. If it is pointing to the end of the string, read() returns a failure or zero bytes read.
N This loop copies bytes from the driver array into the user’s array. Since these two arrays are in completely different environments (user space vs. kernel space), we can’t simply use an assignment statement. A special kernel function put_user() is available to do the job.
P. By definition, read() should return the number of chars successfully read.
3. Compiling the device driver
Since the device driver isn’t linked to any libraries, you should skip the linking stage of compilation. In other words, the .o file is all the kernel needs.
You might ask how the driver has access to kernel functions and variables if it isn’t linked into the kernel. Linux ‘exports’ all available symbols when it is compiled in much the same way as the –g debug option under gcc/g++ provides symbol access to a debugger. In this way, the driver can be dynamically added into the Linux kernel without recompiling the entire kernel. The driver can also be dynamically removed from the kernel.
Compilation is normally done with all warnings enabled, since a bug can be catastrophic. You also want to reference the .h files used when your kernel was compiled, rather than the standard .h file. Here is a simple makefile for the above device driver;
TARGET := hello
WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypes
INCLUDE := -isystem /lib/modules/`uname -r`/build/include
CFLAGS := -O2 -DMODULE -D__KERNEL__ $(WARN) $(INCLUDE)
CC := gcc
$(TARGET).o : $(TARGET).c
To use this makefile, use the following command line;
$ make –f hello.mak
You will see several warnings. Check them over before you ignore them.
This is done with the insmod command (as superuser);
$ insmod –r –N hello.o
(You can examine a list of all modules currently installed using the lsmod command. If the above was successful, you will see the module listed. To remove a module, use the rmmod command.)
5. Using the device driver
The device can now be treated as a Linux file. For example;
$ cat /dev/my_device
Hello from the device on open #0!
$ cat /dev/my_device
Hello from the device on open #1!
Similarly in a user program;
char word1[32], word2[32], word3[32];
ifstream infile;
infile.open(“/dev/my_device”);
infile >> word1 >> word2 >> word3;
cout << word1 << endl;
cout << word2 << endl;
cout << word3 << endl;
Produces the following output;
Hello
from
the
Be sure to un-register the driver when you are through with it. Also, delete the /dev/my_device file since you don’t want some future program to attempt to open it – since my_open() is no longer there.