***************************************************************************** * MAKING AN I/O DRIVER FOR A LED DEVICE * * * * * * Copyright (c) 2002 Daniel P. Bovet, Marco Cesati, and Vincenzo * * Garofalo. Permission is granted to copy, distribute and/or modify this * * document under the terms of the GNU Free Documentation License, * * Version 1.1,published by the Free Software Foundation; with no * * Invariant Sections, with no Front-Cover Texts, and with no Back-Cover * * Texts. A copy of the license is included in the file named LICENSE. * * * * (version 2.0) * ***************************************************************************** The objective is to illustrate how to make a rudimentary non interrupt-driven I/O driver for a simple device connected to the parallel port of the computer. The new driver is a character device driver closely inspired to the driver included in the drivers/char/lp.c file. The I/O device that we shall baptize as "lp_led" has already been described in the slides. The project consists of three parts: a) create a new device file called lp_led and assign an unused major number to the new device b) define open(), close(), read(), and write() file operations for the new device and register the new character device c) test the I/O driver by running a test program that writes into and reads from the lp_led I/O device ***************************************************************************** STEP 0: set the proper EXTRAVERSION value in Makefile ***************************************************************************** replace: EXTRAVERSION = with: EXTRAVERSION = kh ***************************************************************************** STEP 1: modify the linux/arch/i386/config.in file ***************************************************************************** add the following lines in the 'Kernel hacking' main menu: # patch added to support the lp_led experimental device tristate 'I/O driver for lp_led' CONFIG_LP_LED ***************************************************************************** STEP 2: Add a new kernel hacking configuration option ***************************************************************************** add in linux/Documentation/Configure.help the following lines right after CONFIG_MAGIC_SYSRQ: Support for lp_led device CONFIG_LP_LED If you say Y here, you will have an I/O driver capable of handling the lp_led device connected to the parallel port. **************************************************************************** STEP 3: create a new directory linux/new_iodrivers that will contain the new lp_led.o object file of the I/O driver routines ***************************************************************************** mkdir new_iodrivers ***************************************************************************** STEP 4: modify the main linux/Makefile ***************************************************************************** a) replace CORE_FILES =kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o with: CORE_FILES =kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o new_iodrivers/new_iodrivers.o b) replace: SUBDIRS =kernel drivers mm fs net ipc lib with: SUBDIRS =kernel drivers mm fs net ipc lib new_iodrivers ***************************************************************************** STEP 5: Reserve a major number for the new lp_led I/O device ***************************************************************************** Finding a stable free major number is not easy. Do not use high numbers such as 254 or similar because they may be used before our device is registered, when performing dynamic assignment of free major numbers. We use 42 described as follows in the linux/Documentation/devices.txt file: 42 Demo/sample use This number is intended for use in sample code, as well as a general "example" device number. It should never be used for a device driver that is being distributed; either obtain an official number or use the local/experimental range. The sudden addition or removal of a driver with this number should not cause ill effects to the system (bugs excepted.) We thus add the following line in the include/linux/major.h file: #define LP_LED_MAJOR 42 ***************************************************************************** STEP 6: Define a new lp_led char device file with major number 42 in the /dev directory ***************************************************************************** issue as superuser the following command: mknod /dev/lp_led c 42 0 ***************************************************************************** STEP 7: Create the Makefile for the linux/new_iodrivers directory ***************************************************************************** # Makefile for linux/new_iodrivers # # Note: dependencies are done automatically by "make dep", which also removes # any old Makefile for linux/new_iodrivers # # Note: dependencies are done automatically by "make dep", which also removes # any old dependency. DON'T put your own dependencies ere unless it's something # special (ie not a .c file). # # Note2: the CFLAGS definition is now in the main Makefile... # O_TARGET := new_iodrivers.o obj-m := obj-y := ifeq ($(CONFIG_LP_LED),y) obj-y += lp_led.o else ifeq ($(CONFIG_LP_LED),m) obj-m += lp_led.o endif endif include $(TOPDIR)/Rules.make ***************************************************************************** STEP 8: Insert a initialization function for lp_led ***************************************************************************** a) add in drivers/char/mem.c, right after: #if defined(CONFIG_S390_TAPE) && defined(CONFIG_S390_TAPE_CHAR) extern void tapechar_init(void); #endif the following lines: #ifdef CONFIG_LP_LED extern int lp_led_init(void); #endif b) add in drivers/char/mem.c, right after: #ifdef CONFIG_FTAPE ftape_init(); #endif the following lines: #ifdef CONFIG_LP_LED lp_led_init(); #endif ***************************************************************************** STEP 9: Add the lp_led.c file in the new_iodrivers directory ***************************************************************************** /* lp_led.c */ /* */ /* This file includes all the routines needed to drive the */ /* lp_led I/O device */ #include /* THIS_MODULE, EXPORT_NO_SYMBOLS */ #include /* LP_LED_MAJOR */ #include /* inb(), outb() */ /* Addresses of the parallel port */ #define PData 0x378 #define PStatus 0x379 #define PControl 0x37a static int cc; static int lp_led_open(struct inode *inode, struct file *file) { outb(0x00, PData); /* switch off the data byte (green leds) */ outb(0x04, PControl); /* switch on the power on bit (yellow led) */ return 0; } static int lp_led_close(struct inode *inode, struct file *file) { outb(0xff, PData); /* switch on the data byte (green leds) */ return 0; } static ssize_t lp_led_read(struct file *file, char *buf, size_t count, loff_t *ppos) { if (count == 1) { *buf = inb(PStatus); *buf = *buf & 0x78; /* clear bits 7, 2, 1, 0 */ *buf = *buf >> 3; /* shift bits 6-3 into 3-0 */ cc = 1; } else { printk("KERNEL-NOTICE: when reading from lp_led count must be set to 1\n"); cc = -1; } return cc; } static ssize_t lp_led_write(struct file *file, char *buf, size_t count, loff_t *ppos) { if (count == 1) { outb((*buf), PData); cc = 1; } else { printk("KERNEL-NOTICE: when writing into lp_led count must be set to 1\n"); cc = -1; } return cc; } /* this struct must appear in the code AFTER the definition of the file operations */ static struct file_operations lp_led_fops = { owner: THIS_MODULE, read: lp_led_read, write: lp_led_write, open: lp_led_open, release: lp_led_close, }; int lp_led_init(void) { cc = register_chrdev(LP_LED_MAJOR, "lp_led_card", &lp_led_fops); if (cc < 0) printk("KERNEL-NOTICE: lp_led init error %d\n", cc); return cc; } #ifdef MODULE int init_module(void) { EXPORT_NO_SYMBOLS; printk("KERNEL-NOTICE: loading the lp_led module\n"); cc = lp_led_init(); if (cc < 0) printk("KERNEL-NOTICE: lp_led init_module error %d\n", cc); return cc; } int cleanup_module(void) { printk("KERNEL-NOTICE: unloading the lp_led module\n"); cc = unregister_chrdev(LP_LED_MAJOR, "lp_led_card"); if (cc < 0) printk("KERNEL-NOTICE: lp_led cleanup_module error %d\n", cc); return cc; } #endif MODULE_AUTHOR("D.P. Bovet, M. Cesati, and V. Garofalo"); MODULE_DESCRIPTION("LKHC driver for lp_led character device"); MODULE_LICENSE("GPL"); ***************************************************************************** STEP 9: Test the I/O driver module: ***************************************************************************** a) run make menuconfig and set to 'm' the CONFIG_LP_LED kernel hackers option b) run make dep, recompile the kernel and copy the arch/i386/boot/bzImage in /boot/vmlinuz-2.4.18kh or on a floppy c) run make modules and make modules_install d) run lilo and reboot the system and select linux-2.4.18kh as the system to boot e) login as root and insert the module by issuing the command: insmod /usr/src/linux/new_iodrivers/lp_led.o or: modprobe lp_led (if insmod works but modprobe doesn't, this might be due to the fact that you are using outdated libraries for handling modules) f) test whether the module has been loaded correctly by typing: lsmod g) run the Tlp_led test program described next ***************************************************************************** STEP 10: Test the I/O driver by running the following program: ***************************************************************************** /* Tlp_led.c */ /* */ /* This test program for the lp_led I/O driver must run with */ /* root privilege (you might also consider changing the */ /* access rights of /dev/lp_led) */ /* You are supposed to connect an lp_led circuit to the */ /* parallel port in order to get something meaningful out of */ /* this program */ #include #include #include #include #include #include #include int main (void) { char c; int cc, fd, i; unsigned char buf; fd = open("/dev/lp_led",O_RDWR); if (fd < 0) { printf("could not open /dev/lp_led\n"); exit(-1); } while (c!='q') { printf("\nSelect an option:\n"); printf("display a byte on the green leds: w\n"); printf("read value of microswitches: r\n"); printf("demo: d\n"); printf("quit: q\n"); c=getchar(); switch(c) { case 'w': printf("\n\n\ndigit a number in the range 0-255: "); scanf("%d", &buf); cc = write(fd, &buf, 1); if (cc < 0) { printf("write error\n"); break; } printf("--> hexadecimal representation of leds= %x\n\n\n", buf); c = getchar(); break; case 'r': cc = read(fd, &buf, 1); if (cc < 0) { printf("read error\n"); break; } printf ("\nchar read from lp_led = %x\n", buf); c = getchar(); break; case 'd': buf = 1; for (i=0; i<8; i++) { cc = write(fd, &buf, 1); sleep(1); buf = buf<<1; } sleep(2); buf = 255; cc = write(fd, &buf, 1); sleep(2); buf = 128; for (i=0; i<8; i++) { cc = write(fd, &buf, 1); sleep(1); buf = buf>>1; } sleep(2); buf = 255; cc = write(fd, &buf, 1); break; case 'q': cc = close(fd); } } return 0; }