Standard Parallel Port Control Using the Custom Device Driver liftmon_snowconThe Project Trailblazer engineers could barely contain their enthusiasm. They had an interface circuit that connected I/O modules to the parallel port. They had written code using inb and outb to control their interface board. They had created, compiled, and tested a proc module. Now they needed to take a really big step: Combine all these accomplishments and develop the custom lift monitoring and snow-making control device driver, liftmon_snowcon. The engineers created the following requirements for liftmon_snowcon:
These requirements should simplify system integration using bash scripts. Scripts should work with named lift monitoring and snow-making files in the /proc directory. This device driver overcomes the limitations of port I/O and ppdev by automatically enabling the OUTPUT_ENABLE signal and maintaining the latch output value internally. This approach is more robust and less prone to errors. Code can be added to helloworld_proc_module.c to implement these liftmon_snowcon requirements. Enhancements to helloworld_proc_module to Create liftmon_snowconThe liftmon_snowcon module has module_init, module_exit, proc_read, and proc_write routines, just like helloworld_proc_module. The liftmon_snowcon init code doesn't create /proc entries in the /proc directory. Rather, it first creates the directory /proc/trailblazer. Then it creates all the liftmon_snowcon entries snowwatervalve1, snowwatervalve2, liftacmains, liftmotorcontroller, and so on in the directory /proc/trailblazer. The init function then fills each of the proc file's data, read_proc, write_proc, and owner fields. init then writes 0 to the latch. This turns off all the output modules. init then asserts the OUTPUT_ENABLE signal. init completes by writing a message to the system log. TIP You should separate your /proc file entries from other system entries by making a directory within the /proc directory, using the proc_mkdir function. This makes it easy to identify your project's /proc files. Notice that in Listing 7.5 there isn't separate proc_read and proc_write functions for each /proc file. The snow-making control files share the same write_proc routine, proc_write_snowcondata, and share the same read_proc routine, proc_read_snowcondata. The data structure liftmon_snowcon_data_t contains a bitmask to distinguish one proc file from another. The bitmask matches up with the hardware bit that is used to control the output port. For example, the snowwatervalve3 proc file's data field contains a pointer to snowwatervalve3_data. snowwatervalve3_data.mask = SNOWWATERVALVE3, which equals 0x04. 0x04 corresponds to D3, which controls the third output module (Water Valve 3). Similarly, the liftmon proc files share the same proc_read function and use data.mask to distinguish one from another. The module_exit routine, cleanup_liftmon_snowcon, latches a 0, disables the latch, removes the proc file entries and the /proc/trailblazer directory, and completes by writing a message to the system log file. The liftmon_snowcon code is shown in Listing 7.5. Listing 7.5 The liftmon_snowcon.c Device Driver/* * liftmon_snowcon v1.0 9/26/01 * www.embeddedlinuxinterfacing.com * * The original location of this code is * http://www.embeddedlinuxinterfacing.com/chapters/07/liftmon_snowcon.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 */ /* * liftmon_snowcon.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 */ /* liftmon_snowcon * liftmon_snowcon uses inb and outp port I/O system calls to control * an interface circuit connected the PC parallel printer port. The port's * control port drives the interface circuit's output latch signals and * input buffer. This module performs all bit operations for the data bus. * Bash script need only read and write to /proc entry files to determine * status or control equipment. In addition, the module's init code asserts * the OUTPUT_ENABLE signal and its exit code deasserts the OUTPUT_ENABLE * signal. * * This module creates these /proc entries: * Trailblazer directory /proc/trailblazer * Lift Monitoring * AC Mains /proc/trailblazer/liftacmains * Motor Controller /proc/trailblazer/liftmotorcontroller * Low Speed Operation /proc/trailblazer/liftslowspeed * High Speed Operation /proc/trailblazer/lifthighspeed * Operator Switch Base /proc/trailblazer/liftoperatorswitchbase * Operator Switch Top /proc/trailblazer/liftoperatorswitchtop * Snow-Making Control * Water Value 1 /proc/trailblazer/snowwatervalve1 * Water Value 2 /proc/trailblazer/snowwatervalve2 * Water Value 3 /proc/trailblazer/snowwatervalve3 * Heater /proc/trailblazer/snowheater1 */ /* gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/linux/include \ -c liftmon_snowcon.c -o liftmon_snowcon.o */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <linux/sched.h> #include <asm/uaccess.h> #include <asm/io.h> #define MODULE_VERSION "1.0" #define MODULE_NAME "liftmon_snowcon" /* Standard Parallel Port (SPP) definitions */ #define SPPDATAPORT 0x378 #define SPPSTATUSPORT (SPPDATAPORT + 1) #define SPPCONTROLPORT (SPPDATAPORT + 2) /* SPP control port bit definitions */ #define OUTPUTENABLE 0x02 #define OUTPUTLATCH 0x04 #define INPUTENABLE 0x08 #define SPPPORTREAD 0x20 /* SPP input bit definitions */ #define LIFTACMAINS 0x01 #define LIFTMOTORCONTROLLER 0x02 #define LIFTSLOWSPEED 0x04 #define LIFTHIGHSPEED 0x08 #define LIFTOPERATORSWITCHBASE 0x10 #define LIFTOPERATORSWITCHTOP 0x20 /* SPP output bit definitions */ #define SNOWWATERVALVE1 0x01 #define SNOWWATERVALVE2 0x02 #define SNOWWATERVALVE3 0x04 #define SNOWHEATER1 0x08 /* define a bitmask, each *_file uses this to determine who it is */ struct liftmon_snowcon_data_t { unsigned char mask; }; /* snowcondata is the output latch value stored internally. Control changes made by user scripts writing to /proc/trailblazer entries result in bits being either cleared or set in snowcondata. We write snowcondata to the output latch every time a control change occurs */ unsigned char snowcondata; /* this are the data structures that hold the mask. When a /proc file is read or written to, the read_proc or write_proc routine receives a pointer to this structure */ struct liftmon_snowcon_data_t liftacmains_data, liftmotorcontroller_data, liftslowspeed_data, lifthighspeed_data, liftoperatorswitchbase_data, liftoperatorswitchtop_data, snowwatervalve1_data, snowwatervalve2_data, snowwatervalve3_data, snowheater1_data; /* These are the pointers to the /proc directory entries */ static struct proc_dir_entry *tb_dir, *liftacmains_file, *liftmotorcontroller_file, *liftslowspeed_file, *lifthighspeed_file, *liftoperatorswitchbase_file, *liftoperatorswitchtop_file, *snowwatervalve1_file, *snowwatervalve2_file, *snowwatervalve3_file, *snowheater1_file; /* proc_read - proc_read_liftmon * proc_read_liftmon is the callback function that the kernel calls when * there's a read file operation on these /proc/trailblazer files: * liftacmains, lifthighspeed, liftmotorcontroller, liftoperatorswitchbase * liftoperatorswitchtop, and liftslowspeed. The file's data pointer is * passed in the data parameter. You first cast it to the * liftmon_snowcon_data_t structure. The input module rack values are * read by configuring SPP data for input and asserting the input buffer. * This places the state of the input modules on the SPP data port. Using * inb, the bit values are read then anded with the bitmask value to * to determine if the particular input module is on or off. Which * particular input module is defined by which /proc/trailblazer/ file * is read. */ static int proc_read_liftmon(char *page, char **start, off_t off, int count, int *eof, void *data) { unsigned char v; struct liftmon_snowcon_data_t *liftmon_snowcon_data = (struct liftmon_snowcon_data_t *)data; outb(OUTPUTENABLE, SPPCONTROLPORT); /* this asserts three items. * 1. SSPPORTREAD, this configures the bidirectional data port as input * 2. INPUTENABLE, this enables the input buffer to drive the data bus * 3. OUTPUTENABLE, enable the output latch, driving the output rack * if you don't assert this, the output modules will * turn off */ outb(SPPPORTREAD | INPUTENABLE | OUTPUTENABLE, SPPCONTROLPORT); /* The input buffer is now driving the bus, so do a read */ v = inb(SPPDATAPORT); /* Deassert SPPORTREAD and INPUTENABLE. * Use OUTPUTENABLE to keep output latch enabled */ outb(OUTPUTENABLE, SPPCONTROLPORT); /* mask the input value based on the mask. Each mask is different depending * which /proc/trailblazer file was read. * Electrical note: returning an inverted value because AC power to an input * module pulls outputs a low and the input buffer, 74244, doesn't invert */ if (v & liftmon_snowcon_data->mask) page[0] = '0'; else page[0] = '1'; /* return 1 which is the length of page */ return 1; } /* proc_write - proc_write_snowcondata * proc_write_snowcondata is the callback function that the kernel calls * when there's a write file operation on these /proc/trailblazer files: * snowheater1, snowwatervalve1, snowwatervalve2 and snowwatervalve3. * The file's data pointer is passed in the data parameter. You first * cast it to the liftmon_snowcon_data_t structure. The buffer parameter * points to the incoming data. If the incoming data is a 1 or a 0, * a bit in snowcondata is set or cleared. Which bit is defined by which * /proc/trailblazer file is written to. snowcondata is then written to * the output latch. */ static int proc_write_snowcondata(struct file *file, const char *buffer, unsigned long count, void *data) { struct liftmon_snowcon_data_t *liftmon_snowcon_data = (struct liftmon_snowcon_data_t *)data; /* check if the user wrote a 1 or a 0 the /proc/trailblazer file. if so, set or clear a bit in snowcondata */ if (buffer[0] == '1') snowcondata |= liftmon_snowcon_data->mask; if (buffer[0] == '0') snowcondata &= ~liftmon_snowcon_data->mask; /* Use OUTPUTENABLE to keep output latch enabled */ outb(OUTPUTENABLE, SPPCONTROLPORT); /* put snowcondata on the data bus */ outb(snowcondata , SPPDATAPORT); /* assert OUTPUTLATCH, this latches the data bus to the latch's * outputs, driving the output module rack */ outb(OUTPUTENABLE | OUTPUTLATCH, SPPCONTROLPORT); /* Use OUTPUTENABLE to keep output latch enabled */ outb(OUTPUTENABLE,SPPCONTROLPORT); return 1; } /* proc_read - proc_read_snowcondata * proc_read_snowcondata is the callback function that the kernel calls * when there's a read file operation on these /proc/trailblazer files: * snowheater1, snowwatervalve1, snowwatervalve2 and snowwatervalve3. * The file's data pointer is passed in the data parameter. You first * cast it to the liftmon_snowcon_data_t structure. Use snowcondata * anded with the bitmask value to determine if the particular output * module is on or off. Which particular output module is defined by * which /proc/trailblazer/ file is read. */ static int proc_read_snowcondata(char *page, char **start, off_t off, int count, int *eof, void *data) { struct liftmon_snowcon_data_t *liftmon_snowcon_data = (struct liftmon_snowcon_data_t *)data; /* mask the snowcondata value based on the mask. Each mask is different * depending which /proc/trailblazer file was read. */ if ( snowcondata & liftmon_snowcon_data->mask ) page[0] = '1'; else page[0] = '0'; /* return the length */ return 1; } /* init - init_liftmon_snowcon * init_liftmon_snowcon creates the /proc entry files and obtains * their pointers. For each file, the fields, data, read_proc, * write_proc and owner, are filled. init_liftmon_snowcon * initializes the output modules in the off state then * complete by writing an entry to the system log using printk. */ static int __init init_liftmon_snowcon(void) { int rv = 0; /* Create the trailblazer /proc entry */ tb_dir = proc_mkdir("trailblazer", NULL); if(tb_dir == NULL) { rv = -ENOMEM; goto out; } tb_dir->owner = THIS_MODULE; /* Create liftacmains and make it readable by all - 0444 */ liftacmains_file = create_proc_entry("liftacmains", 0444, tb_dir); if(liftacmains_file == NULL) { rv = -ENOMEM; goto no_liftacmains; } liftacmains_data.mask = LIFTACMAINS; liftacmains_file->data = &liftacmains_data; liftacmains_file->read_proc = &proc_read_liftmon; liftacmains_file->write_proc = NULL; liftacmains_file->owner = THIS_MODULE; /* Create liftmotorcontroller and make it readable by all - 0444 */ liftmotorcontroller_file = create_proc_entry("liftmotorcontroller", 0444, tb_dir); if(liftmotorcontroller_file == NULL) { rv = -ENOMEM; goto no_liftmotorcontroller; } liftmotorcontroller_data.mask = LIFTMOTORCONTROLLER; liftmotorcontroller_file->data = &liftmotorcontroller_data; liftmotorcontroller_file->read_proc = &proc_read_liftmon; liftmotorcontroller_file->write_proc = NULL; liftmotorcontroller_file->owner = THIS_MODULE; /* Create liftslowspeed and make it readable by all - 0444 */ liftslowspeed_file = create_proc_entry("liftslowspeed", 0444, tb_dir); if(liftslowspeed_file == NULL) { rv = -ENOMEM; goto no_liftslowspeed; } liftslowspeed_data.mask = LIFTSLOWSPEED; liftslowspeed_file->data = &liftslowspeed_data; liftslowspeed_file->read_proc = &proc_read_liftmon; liftslowspeed_file->write_proc = NULL; liftslowspeed_file->owner = THIS_MODULE; /* Create lifthighspeed and make it readable by all - 0444 */ lifthighspeed_file = create_proc_entry("lifthighspeed", 0444, tb_dir); if(lifthighspeed_file == NULL) { rv = -ENOMEM; goto no_lifthighspeed; } lifthighspeed_data.mask = LIFTHIGHSPEED; lifthighspeed_file->data = &lifthighspeed_data; lifthighspeed_file->read_proc = &proc_read_liftmon; lifthighspeed_file->write_proc = NULL; lifthighspeed_file->owner = THIS_MODULE; /* Create liftoperatorswitchbase and make it readable by all - 0444 */ liftoperatorswitchbase_file = create_proc_entry("liftoperatorswitchbase", 0444, tb_dir); if(liftoperatorswitchbase_file == NULL) { rv = -ENOMEM; goto no_liftoperatorswitchbase; } liftoperatorswitchbase_data.mask = LIFTOPERATORSWITCHBASE; liftoperatorswitchbase_file->data = &liftoperatorswitchbase_data; liftoperatorswitchbase_file->read_proc = &proc_read_liftmon; liftoperatorswitchbase_file->write_proc = NULL; liftoperatorswitchbase_file->owner = THIS_MODULE; /* Create liftoperatorswitchtop and make it readable by all - 0444 */ liftoperatorswitchtop_file = create_proc_entry("liftoperatorswitchtop", 0444, tb_dir); if(liftoperatorswitchtop_file == NULL) { rv = -ENOMEM; goto no_liftoperatorswitchtop; } liftoperatorswitchtop_data.mask = LIFTOPERATORSWITCHTOP; liftoperatorswitchtop_file->data = &liftoperatorswitchtop_data; liftoperatorswitchtop_file->read_proc = &proc_read_liftmon; liftoperatorswitchtop_file->write_proc = NULL; liftoperatorswitchtop_file->owner = THIS_MODULE; /* Create snowwatervalve1 and make it root writable, readable by all-0644 */ snowwatervalve1_file = create_proc_entry("snowwatervalve1", 0644, tb_dir); if(snowwatervalve1_file == NULL) { rv = -ENOMEM; goto no_snowwatervalve1; } snowwatervalve1_data.mask = SNOWWATERVALVE1; snowwatervalve1_file->data = &snowwatervalve1_data; snowwatervalve1_file->read_proc = &proc_read_snowcondata; snowwatervalve1_file->write_proc = &proc_write_snowcondata; snowwatervalve1_file->owner = THIS_MODULE; /* Create snowwatervalve2 and make it root writable, readable by all-0644 */ snowwatervalve2_file = create_proc_entry("snowwatervalve2", 0644, tb_dir); if(snowwatervalve2_file == NULL) { rv = -ENOMEM; goto no_snowwatervalve2; } snowwatervalve2_data.mask = SNOWWATERVALVE2; snowwatervalve2_file->data = &snowwatervalve2_data; snowwatervalve2_file->read_proc = &proc_read_snowcondata; snowwatervalve2_file->write_proc = &proc_write_snowcondata; snowwatervalve2_file->owner = THIS_MODULE; /* Create snowwatervalve3 and make it root writable, readable by all-0644 */ snowwatervalve3_file = create_proc_entry("snowwatervalve3", 0644, tb_dir); if(snowwatervalve3_file == NULL) { rv = -ENOMEM; goto no_snowwatervalve3; } snowwatervalve3_data.mask = SNOWWATERVALVE3; snowwatervalve3_file->data = &snowwatervalve3_data; snowwatervalve3_file->read_proc = &proc_read_snowcondata; snowwatervalve3_file->write_proc = &proc_write_snowcondata; snowwatervalve3_file->owner = THIS_MODULE; /* Create snowheater1 and make it root writable, readable by all-0644 */ snowheater1_file = create_proc_entry("snowheater1", 0644, tb_dir); if(snowheater1_file == NULL) { rv = -ENOMEM; goto no_snowheater1; } snowheater1_data.mask = SNOWHEATER1; snowheater1_file->data = &snowheater1_data; snowheater1_file->read_proc = &proc_read_snowcondata; snowheater1_file->write_proc = &proc_write_snowcondata; snowheater1_file->owner = THIS_MODULE; /* initialize snowcondata to 0, all output modules off */ snowcondata = 0; /* initialize the control port to know value 0 */ outb(0, SPPCONTROLPORT); /* put snowcondata on the data bus */ outb(snowcondata, SPPDATAPORT); /* latch it first before we turn on the output modules */ outb(OUTPUTLATCH, SPPCONTROLPORT); /* turn on the latch output to drive the output modules */ outb(OUTPUTENABLE,SPPCONTROLPORT); /* everything initialed */ printk(KERN_INFO "%s %s initialized\n", MODULE_NAME, MODULE_VERSION); return 0; /* this removes /proc entries if we have an error along the way */ no_snowheater1: remove_proc_entry("snowheater1", tb_dir); no_snowwatervalve3: remove_proc_entry("snowwatervalve3", tb_dir); no_snowwatervalve2: remove_proc_entry("snowwatervalve2", tb_dir); no_snowwatervalve1: remove_proc_entry("snowwatervalve1", tb_dir); no_liftoperatorswitchtop: remove_proc_entry("liftoperatorswitchtop", tb_dir); no_liftoperatorswitchbase: remove_proc_entry("liftoperatorswitchbase", tb_dir); no_lifthighspeed: remove_proc_entry("lifthighspeed", tb_dir); no_liftslowspeed: remove_proc_entry("liftslowspeed", tb_dir); no_liftmotorcontroller: remove_proc_entry("liftmotorcontroller", tb_dir); no_liftacmains: remove_proc_entry("liftacmains", tb_dir); out: return rv; } /* exit - cleanup_liftmon_snowcon * cleanup_liftmon_snowcon turns off the output modules and * deasserts the OUTPUT_ENABLE signal. It removes the /proc entry files * prints a message to the system log. */ static void __exit cleanup_liftmon_snowcon(void) { /* this turns off all the output modules */ outb(0, SPPCONTROLPORT); outb(0, SPPDATAPORT); outb(OUTPUTLATCH, SPPCONTROLPORT); outb(0,SPPCONTROLPORT); /* removing the /proc entries */ remove_proc_entry("liftacmains", tb_dir); remove_proc_entry("liftmotorcontroller", tb_dir); remove_proc_entry("liftslowspeed", tb_dir); remove_proc_entry("lifthighspeed", tb_dir); remove_proc_entry("liftoperatorswitchbase", tb_dir); remove_proc_entry("liftoperatorswitchtop", tb_dir); remove_proc_entry("snowwatervalve1", tb_dir); remove_proc_entry("snowwatervalve2", tb_dir); remove_proc_entry("snowwatervalve3", tb_dir); remove_proc_entry("snowheater1", tb_dir); remove_proc_entry("trailblazer", NULL); /* we're done */ printk(KERN_INFO "%s %s removed\n", MODULE_NAME, MODULE_VERSION); } module_init(init_liftmon_snowcon); module_exit(cleanup_liftmon_snowcon); MODULE_AUTHOR("Craig Hollabaugh"); MODULE_DESCRIPTION("Trailblazer Lift Monitor and Snow-making Control"); EXPORT_NO_SYMBOLS; Compiling, Inserting, and Testing liftmon_snowcon on the MZ104When it is inserted into the kernel, liftmon_snowcon creates the /proc/trailblazer directory and lift-monitoring and snow-making control entries. bash scripts can read from and write to these entries. Here are the steps to compile the liftmon_snowcon module, using tbdev1, and then insert and test the operation of liftmon_snowcon, using tbdevmz, the MZ104:
The heater output module goes off. If you check the OUTPUT_ENABLE signal, you will see that it is not asserted (greater than +3V), so the latch outputs are disabled and the output modules are also off. This result shows that the cleanup_liftmon_snowcon code functions correctly. When it is loaded, this device driver asserts OUTPUT_ENABLE. It shields software developers from interface logic specifics. It provides bash script access to individual lift monitoring and snow-making equipment through a /proc file interface. Use of this device driver eases system integration using bash scripts. |
Top |