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.
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:
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.
/* * 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 ", 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] = ' '; 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 ",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 ", 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.
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:
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.
3.145.179.35