After twiddling parallel port bits using port I/O and ppdev, the Project Trailblazer engineers decided that the potential failures due to drawbacks were unacceptable. Port I/O and ppdev approaches were error prone and risky. They needed a custom device driver for their interface circuit.
Armed with their interface circuit, port I/O, and ppdev knowledge, the Project Trailblazer engineers developed a few requirements for their first device driver:
- The driver should load at startup.
- When loaded, the driver should enable the latch output to drive the output modules.
- The driver should shield software developers from interface logic specifics.
- The driver should offer control of individual pieces of snow-making equipment without requiring the developer to perform bit manipulation.
- The driver should offer monitoring of individual lift signals without requiring the developer to perform bit manipulation.
The engineers started learning about device driver development by reading. Rubini and Corbet's Linux Device Drivers
(published by O'Reilly) offers a complete discussion of driver
development. It covers kernel version 2.4 and new kernel additions.
Unfortunately, after reading this book, the engineers still didn't know
where to start. Then they found the "Linux Kernel Module Programming
Guide,"3 which steps through kernel module development, character device files, the /proc
filesystem, communications with device files, I/O controls, blocking
processes, scheduling tasks, and interrupt handlers. The engineers
learned the following about device drivers:
- Device drivers are accessed through file operations such as read and write.
- Device drivers run in the kernel space.
- Device drivers can be written as loadable modules.
- Device drivers running in the kernel do not have access to glibc.
- Device drivers that have memory problems (for example, array out of bounds) could crash the kernel.
- Traditionally, accessing device drivers is performed through file operations in the /dev directory.
- Device files in the /dev directory require static or dynamic assignment of major numbers, the general class of the device.
- Static assignment of major numbers creates the potential for device drivers to clash with each other.
- Dynamic assignment of major numbers introduces a small inconvenience in locating the device driver.
- Device drivers can exist in the /proc directory.
- Many Linux processes make available information via files in the /proc directory.
- /proc directory device files are created on-the-fly and do not clash with other device files and can be found easily by filename.
The "Linux Kernel Module Programming Guide"3 is out of date, and its examples are difficult to compile. The engineers found the "Linux Kernel Procfs Guide,"4
which applies to kernel 2.4 and is up-to-date. They decided to create
the lift monitoring and snow-making control device driver, which would
be a loadable kernel module that would use the /proc file
system. The engineers planned to be extra careful when writing the
device driver, to make sure that array-out-of-bounds conditions never
occur. They knew they could not rely on glibc functions. They planned to develop their kernel module device driver around the "Linux Kernel Procfs Guide" program procfs_example.c. They began by writing a helloworld device driver module for the /proc directory. After they were confident in their skills at creating device drivers, they would modify helloworld and create the lift monitoring and snow-making control device driver.
Understanding helloworld_proc_module
Most helloworld programs print "Hello World" and terminate. Kernel module versions of helloworld
print "Hello World" to the console or to the system log file when
loaded and may print "Goodbye World" when they are unloaded. The helloworld_proc_module
will print loading and unloading messages, and it will also store a
character string that can be read and written with file operations. This
character string offers data persistence across the life of the module.
helloworld_proc_module will demonstrate the process of creating the /proc
directory file, transferring data from userland to the module,
transferring data from the module back to userland, and removing the /proc directory file. Here are the basic steps involved in the execution of helloworld_proc_module:
-
The module is loaded from a script or by hand, with insmod helloworld_proc_module.o.
-
The kernel calls the module's init function. The source code uses a macro define called module_init that declares which function is init. In this case, the statement is module_init(init_helloworld).
-
The module's init function executes and creates a /proc directory file entry called helloworld_file, fills its fields, and then prints a message to the system log. The helloworld_file /proc entry structure contains four fields that the kernel requires for read and write file operations:
- read_proc� The read_proc field should contain a pointer to the file's callback function that executes for a read file operation. For example, if a user ran the cat /proc/helloworld_proc_module command at the bash prompt, the kernel would ultimately call the file's read_proc function to handle this file read operation.
- write_proc� The write_proc field should contain a pointer to the file's callback function that executes for a write file operation. If a user ran the echo test > /proc/helloworld_proc_module command at the bash prompt, the kernel would call the file's write_proc function to handle this file write operation.
- data� The data field contains a pointer to the file's data. During module initialization, a data structure is created and populated. When a read or write callback occurs, using read_proc or write_proc, this data pointer is passed as a parameter. This way, the file's read_proc and write_proc functions can find their associated data.
- owner� Because the /proc file entry is used in a module, the owner field should be set to THIS_MODULE.
-
The module is unloaded by a script or by hand, with rmmod helloworld_proc_module.
-
The kernel calls the module's exit function. The source code uses a macro definition called module_exit that declares which function is exit. In this case, the statement is module_exit(cleanup_helloworld);.
-
The exit routine removes the /proc file entry and prints a message to the system log file.
This is quite a bit different from the standard helloworld.c program that you're used to seeing. This helloworld_proc_module program seems complicated, but after you get it running and you understand how to set the helloworld_file fields and how the data pointer is passed to the read and write callback functions, it's pretty simple. Listing 7.4 shows the complete helloworld_proc_module.c program.
Listing 7.4 The helloworld_proc_module.c Program
/* * helloworld_proc_module v1.0 9/25/01 * www.embeddedlinuxinterfacing.com * * The original location of this code is * http://www.embeddedlinuxinterfacing.com/chapters/07/ * helloworld_proc_module.c * * Copyright (C) 2001 by Craig Hollabaugh * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * helloworld_proc_module.c is based on procfs_example.c by Erik Mouw. * For more information, please see The Linux Kernel Procfs Guide, * http://kernelnewbies.org/documents/kdoc/procfs-guide/lkprocfsguide.html */ /* helloworld_proc_module * helloworld_proc_module demonstrates the use of a /proc directory entry. * The init function, init_helloworld, creates /proc/helloworld and * populates its data, read_proc, write_proc and owner fields. The exit * function, cleanup_helloworld, removes the /proc/helloworld entry. * The proc_read function, proc_read_helloworld, is called whenever * a file read operation occurs on /proc/helloworld. The * proc_write function, proc_write_helloworld, is called whenever a file * file write operation occurs on /proc/helloworld. * * To demonstrate read and write operations, this module uses data * structure called helloworld_data containing a char field called value. * Read and write operations on /proc/helloworld manipulate * helloworld_data->value. The init function sets value = 'Default'. */ /* gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/linux/include \ -c helloworld_proc_module.c -o helloworld_proc_module.o arm-linux-gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/arm-linux/include \ -c helloworld_proc_module.c \ -o /tftpboot/arm-rootfs/helloworld_proc_module.o */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #define MODULE_VERSION "1.0" #define MODULE_NAME "helloworld proc module" /* this is how long our data->value char array can be */ #define HW_LEN 8 struct helloworld_data_t { char value[HW_LEN + 1]; }; static struct proc_dir_entry *helloworld_file; struct helloworld_data_t helloworld_data; /* proc_read - proc_read_helloworld * proc_read_helloworld is the callback function that the kernel calls when * there's a read file operation on the /proc file (for example, * cat /proc/helloworld). The file's data pointer (&helloworld_data) is * passed in the data parameter. You first cast it to the helloworld_data_t * structure. This proc_read function then uses the sprintf function to * create a string that is pointed to by the page pointer. The function then * returns the length of page. Because helloworld_data->value is set to * "Default", the command cat /proc/helloworld should return * helloworld Default */ static int proc_read_helloworld(char *page, char **start, off_t off, int count, int *eof, void *data) { int len; /* cast the void pointer of data to helloworld_data_t*/ struct helloworld_data_t *helloworld_data=(struct helloworld_data_t *)data; /* use sprintf to fill the page array with a string */ len = sprintf(page, "helloworld %s\n", helloworld_data->value); return len; } /* proc_write - proc_write_helloworld * proc_write_helloworld is the callback function that the kernel calls * when there's a write file operation on the /proc file, (for example, * echo test > /proc/helloworld). The file's data pointer * (&helloworld_data) is passed in the data parameter. You first cast it to * the helloworld_data_t structure. The buffer parameter points to the * incoming data. You use the copy_from_user function to copy the buffer * contents to the data->value field. Before you do that, though, you check * the buffer length, which is stored in count to ensure that you don't * overrun the length of data->value. This function then returns the length * of the data copied. */ static int proc_write_helloworld(struct file *file, const char *buffer, unsigned long count, void *data) { int len; /* cast the void pointer of data to helloworld_data_t*/ struct helloworld_data_t *helloworld_data=(struct helloworld_data_t *)data; /* do a range checking, don't overflow buffers in kernel modules */ if(count > HW_LEN) len = HW_LEN; else len = count; /* use the copy_from_user function to copy buffer data to * to our helloworld_data->value */ if(copy_from_user(helloworld_data->value, buffer, len)) { return -EFAULT; } /* zero terminate helloworld_data->value */ helloworld_data->value[len] = '\0'; return len; } /* init - init_helloworld * init_helloworld creates the /proc/helloworld entry file and obtains its * pointer called helloworld_file. The helloworld_file fields, data, * read_proc, write_proc and owner, are filled. init_helloworld completes * by writing an entry to the system log using printk. */ static int __init init_helloworld(void) { int rv = 0; /* Create the proc entry and make it readable and writable by all - 0666 */ helloworld_file = create_proc_entry("helloworld", 0666, NULL); if(helloworld_file == NULL) { return -ENOMEM; } /* set the default value of our data to Sam. This way a read operation on * /proc/helloworld will return something. */ strcpy(helloworld_data.value, "Default"); /* Set helloworld_file fields */ helloworld_file->data = &helloworld_data; helloworld_file->read_proc = &proc_read_helloworld; helloworld_file->write_proc = &proc_write_helloworld; helloworld_file->owner = THIS_MODULE; /* everything initialize */ printk(KERN_INFO "%s %s initialized\n",MODULE_NAME, MODULE_VERSION); return 0; } /* exit - cleanup_helloworld * cleanup_helloworld removes the /proc file entry helloworld and * prints a message to the system log file. */ static void __exit cleanup_helloworld(void) { remove_proc_entry("helloworld", NULL); printk(KERN_INFO "%s %s removed\n", MODULE_NAME, MODULE_VERSION); } /* here are the compiler macros for module operation */ module_init(init_helloworld); module_exit(cleanup_helloworld); MODULE_AUTHOR("Craig Hollabaugh"); MODULE_DESCRIPTION("helloworld proc module"); EXPORT_NO_SYMBOLS;
TIP
You should use the helloworld_proc_module.c source file
as a skeleton for interfacing projects. It compiles, loads, and
executes correctly on the x86, ARM, and PowerPC target boards. You can
simply add hardware initialization code to the init function, interfacing code to the proc_read and proc_write functions, change the names of the /proc entries, and recompile.
Compiling, Inserting, and Testing helloworld_proc_module on the MediaEngine
When it is inserted into the kernel, the helloworld_proc_module creates a /proc directory entry that bash scripts can read from and write to. Here are the steps to compile the module using tbdev1, and then insert and test the operation of the helloworld_proc_module using tbdevarm, the MediaEngine:
-
Compile helloworld_proc_module.c by using this command:
root@tbdev1[526]: arm-linux-gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/arm-linux/include -c helloworld_proc_module.c -o /tftpboot/arm-rootfs/helloworld_proc_module.o root@tbdev1[527]: ls -sh /tftpboot/arm-rootfs/helloworld_proc_module.o 4.0k /tftpboot/arm-rootfs/helloworld_proc_module.o
-
Boot tbdevarm to gain access using minicom:
root@tbdev1[528]: minicom
Here's the tbdevarm prompt from minicom:bash-2.04#
-
Check for the helloworld_proc_module object file and to see what modules are loaded:
bash-2.04# ls / bin dev etc helloworld_proc_module.o lib proc sbin tmp usr bash-2.04# cat /proc/modules bash-2.04#
-
Insert helloworld_proc_module.o, list the current modules and check the /proc directory:
bash-2.04# insmod helloworld_proc_module.o helloworld proc module 1.0 initialized bash-2.04# cat /proc/modules helloworld_proc_module 1056 0 (unused) bash-2.04# ls /proc 1 bus helloworld locks stat 15 cmdline ide meminfo swaps 2 cpuinfo interrupts misc sys 28 devices iomem modules sysvipc 3 dma ioports mounts tty 4 driver kcore net uptime 5 execdomains kmsg partitions version 6 filesystems ksyms self 7 fs loadavg slabinfo
-
Check the read_proc function, proc_read_helloworld, by performing a read file operation on the /proc file entry, helloworld:
bash-2.04# cat /proc/helloworld helloworld Default
-
Check the write_proc function, proc_write_helloworld, by performing a write file operation on the /proc file entry, helloworld:
bash-2.04# echo 1234 > /proc/helloworld bash-2.04# cat /proc/helloworld helloworld 1234 bash-2.04#
The string 1234 was successfully copied from the proc_write_helloworld function to the data->value field. (You verified that with a read file operation using cat /proc/helloworld.) -
Remove the module, check the module list in /proc/modules and /proc directory:
bash-2.04# rmmod helloworld_proc_module helloworld proc module 1.0 removed bash-2.04# cat /proc/modules bash-2.04# ls /proc/ 1 bus ide meminfo swaps 15 cmdline interrupts misc sys 2 cpuinfo iomem modules sysvipc 3 devices ioports mounts tty 36 dma kcore net uptime 4 driver kmsg partitions version 5 execdomains ksyms self 6 filesystems loadavg slabinfo 7 fs locks stat bash-2.04#
You have just compiled, inserted, and tested helloworld_proc_module. This module dynamically creates a /proc directory entry called helloworld. You can read and write to helloworld,
and it stores an eight-character value. This seems simple, but this
powerful module is the skeleton that the Project Trailblazer engineers
needed to create their lift monitoring and snow-making control device
driver.
No comments:
Post a Comment