/* * Revision 1.2: By Yacine (yacine@eyetap.org) Summer 2001 * * The driver now compiles under the Linux kernel 2.4.X * * Revision 1.1: By Taneem (taneem@eyetap.org) Tue Sep 5 20:36:44 EDT 2000 * * Fixed the bug so that there won't be a mix up in minor numbers. * * Changed the code so the ibus port number can be changed through * insmod. * e.g. insmod i_bus_base=0x278 ibus.o * * Took out the delay while writing (but the code is still there). * * Added some error checking * * Organized the code a bit * * i-bus2.x.c -- modified simple device driver for compatibility to 2.0 or 2.2 * original implementation (Erlich, Mann, Fung, etc.) written for 2.0 kernels * 2000Apr20 Modified by Behr Raz (g7behr@cdf.utoronto.ca ) to compile under * pre and post 2.2.0 KERNEL versions * * i-bus2.x.c is source code for 2.x (e.g. 2.0 or 2.2 kernels) * ibus.o is the object * /dev/ibus0 and dev/ibus1 are the devices loaded by load_i-bus.sh * echo -n -e "\377" > /dev/ibus0 * is non inverting (turns all on) * echo -n -e "\377" > /dev/ibus1 * is inverting (turns all off) */ #include #include #include #include #include /* printk() */ #include /* everything... */ #include /* error codes */ #include #include #include #include /* In 2.2.3 /usr/include/linux/versibusion.h includes * a macro for this, but 2.0.35 doesn't - so I add * it here if necessary. */ #ifndef KERNEL_VERSION #define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c)) #endif /* Conditional compilation. LINUX_VERSION_CODE is * the code (as per KERNEL_VERSION) of this version. */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0) #include /* for put_user */ #include #endif static int i_bus_major = 0; static int i_bus_base = 0; /* Check module_init */ #define DEFAULT_IBUS_BASE 0x240 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,2,0) #define copy_to_user memcpy_tofs #define copy_from_user memcpy_fromfs #endif enum i_bus_modes {I_BUS_DEFAULT=0, I_BUS_INVERT}; static int i_bus_open(struct inode *inode, struct file *filp){ int minor = (MINOR(inode->i_rdev)); if (minor > 1 && minor < 0) { printk("Error IBus: The minor number should be either 0 or 1\n"); return -EINVAL; } MOD_INC_USE_COUNT; return 0; } /* This function is called when a process closes the * device file. It doesn't have a return value in * version 2.0.x because it can't fail (you must ALWAYS * be able to close a device). In version 2.2.x it is * allowed to fail - but we won't let it. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) static int i_bus_release(struct inode *inode, struct file *filp){ #else void i_bus_release(struct inode *inode, struct file *filp){ #endif MOD_DEC_USE_COUNT; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) return 0; #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) static ssize_t i_bus_read( struct file *filp, char *buf, /* The buffer to fill with data */ size_t count, /* The length of the buffer */ loff_t *offset){ /* Our offset in the file */ #else static int i_bus_read( struct inode *inode, struct file *filp, char *buf, /* The buffer to fill with the data */ int count /* The length of the buffer,mustn't write beyond that! */ ){ #endif int retval = 1; unsigned port = i_bus_base; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) int mode = (MINOR(filp->f_dentry->d_inode->i_rdev)); #else int mode = (MINOR(inode->i_rdev)); #endif unsigned char portdata; switch(mode) { case I_BUS_DEFAULT: portdata=inb(port); break; case I_BUS_INVERT: portdata=~inb(port); break; default: retval = -EINVAL; break; } if (retval < 0) return retval; else { copy_to_user(buf, &portdata,retval); return retval; } } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) static ssize_t i_bus_write( struct file *filp, const char *buf, size_t count, loff_t *offset ){ #else int i_bus_write( struct inode *inode, struct file *filp, const char *buf, int count ){ #endif int retval = count; unsigned port = i_bus_base; unsigned char outval; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) int mode = (MINOR(filp->f_dentry->d_inode->i_rdev)); #else int mode = (MINOR(inode->i_rdev)); #endif unsigned char *kbuf, *ptr; unsigned long j; kbuf = (unsigned char *) kmalloc(count, GFP_KERNEL); if (!kbuf) return -ENOMEM; copy_from_user(kbuf, buf, count); ptr=kbuf; switch(mode) { case I_BUS_DEFAULT: while (count--) { outval = *(ptr++); outb(outval,port); #if 0 /* this adds a delay of 1 sec, but we don't need it for the first few labs.*/ j = jiffies + 1 * HZ; while(jiffies= KERNEL_VERSION(2,4,0) NULL, #endif NULL, /* i_bus_lseek */ i_bus_read, i_bus_write, NULL, /* i_bus_readdir */ NULL, /* i_bus_select */ NULL, /* i_bus_ioctl */ NULL, /* i_bus_mmap */ i_bus_open, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) NULL, /* flush */ #endif i_bus_release }; #if LINUX_VERSION_CODE > 0x20118 MODULE_PARM(i_bus_base, "i"); #endif int init_module(void){ int result; if (!i_bus_base) /* Check if i_bus_base was set through insmod */ i_bus_base = DEFAULT_IBUS_BASE; /* otherwise set it to DEFAULT_IBUS_BASE */ result = check_region(i_bus_base,32); if (result){ printk("i-bus2.x: can't get I/O address 0x%x\n",i_bus_base); return result; } request_region(i_bus_base,32,"ibus"); result = register_chrdev(i_bus_major, "ibus", &i_bus_fops); if (result < 0){ printk("i-bus2.x: can't get major number\n"); release_region(i_bus_base,32); return result; } if (i_bus_major == 0) i_bus_major = result; /* dynamic */ return 0; } void cleanup_module(void){ unregister_chrdev(i_bus_major, "ibus"); release_region(i_bus_base,32); }