/* * portio.c --- Linux port I/O device driver. * * Author: William Lavender * * Purpose: This module allows access to a restricted range of * I/O ports from user mode programs. The range of ports * allowed is set at the time the module is loaded by the * insmod command. * * This version of the driver is for Linux 2.2. * * An example insmod command looks like * * insmod ./portio.o ports='0x344-0x347;0x300-0x301' * * This command line results in the following configuration: * * device 0: base_addr = 0x344, length = 4 (0x344-0x347) * device 1: base_addr = 0x300, length = 2 (0x300-0x301) * *------------------------------------------------------------------------- * * Copyright 1999-2000, 2002 Illinois Institute of Technology * * See the file "LICENSE" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * */ #include #include #include #include #include #include #include #include #include "portio.h" #ifdef PORTIO_ENABLE_DEBUG # define PIO_DEBUG(x) if (debug) printk x #else # define PIO_DEBUG(x) #endif static portio_permission_map permission_map; static int *portio_permission_array = permission_map.map; #define portio_access_permitted(a) \ ( portio_permission_array[ (a) - PORTIO_MIN_ADDRESS ] ) #define PORTIO_NAME "portio" static unsigned int portio_major = PORTIO_MAJOR; static char portio_version[] = "2.0"; /* There two command line arguments for insmod, namely, ports and debug. */ static char *ports; static int debug = 0; MODULE_PARM(ports, "s"); MODULE_PARM(debug, "i"); static int portio_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg ) { unsigned char cmd_number; int cmd_size, status; __u8 byte_value; __u16 word_value; portio_message message; cmd_number = _IOC_NR(cmd); cmd_size = _IOC_SIZE(cmd); switch( cmd_number ) { case PORTIO_OUTB_CMD: status = copy_from_user( &message, (void *) arg, cmd_size ); if ( status != 0 ) return -EFAULT; if ( (message.address < PORTIO_MIN_ADDRESS) || (message.address > PORTIO_MAX_ADDRESS) ) { return -EPERM; } if ( portio_access_permitted( message.address ) == 0 ) return -EPERM; byte_value = message.arg.byte; PIO_DEBUG(("Portio: outb_p( %#02x, %#x )\n", byte_value, message.address)); #ifndef PORTIO_DISABLED outb_p( byte_value, message.address ); #endif break; case PORTIO_OUTW_CMD: status = copy_from_user( &message, (void *) arg, cmd_size ); if ( status != 0 ) return -EFAULT; if ( (message.address < PORTIO_MIN_ADDRESS) || (message.address > PORTIO_MAX_ADDRESS) ) { return -EPERM; } if ( portio_access_permitted( message.address ) == 0 ) return -EPERM; word_value = message.arg.word; PIO_DEBUG(("Portio: outw_p( %#04x, %#x )\n", word_value, message.address)); #ifndef PORTIO_DISABLED outw_p( word_value, message.address ); #endif break; case PORTIO_INB_CMD: status = copy_from_user( &message, (void *) arg, cmd_size ); if ( status != 0 ) return -EFAULT; if ( (message.address < PORTIO_MIN_ADDRESS) || (message.address > PORTIO_MAX_ADDRESS) ) { return -EPERM; } if ( portio_access_permitted( message.address ) == 0 ) return -EPERM; #ifndef PORTIO_DISABLED byte_value = inb_p( message.address ); #else byte_value = 0xb5; #endif PIO_DEBUG(("Portio: inb_p( %#x ) = %#02x\n", message.address, byte_value)); message.arg.byte = byte_value; if ( cmd_size > 0 ) { status = copy_to_user( (void *) arg, &message, cmd_size ); if ( status != 0 ) return -EFAULT; } break; case PORTIO_INW_CMD: status = copy_from_user( &message, (void *) arg, cmd_size ); if ( status != 0 ) return -EFAULT; if ( (message.address < PORTIO_MIN_ADDRESS) || (message.address > PORTIO_MAX_ADDRESS) ) { return -EPERM; } if ( portio_access_permitted( message.address ) == 0 ) return -EPERM; #ifndef PORTIO_DISABLED word_value = inw_p( message.address ); #else word_value = 0xeab5; #endif PIO_DEBUG(("Portio: inw_p( %#x ) = %#04x\n", message.address, word_value)); message.arg.word = word_value; if ( cmd_size > 0 ) { status = copy_to_user( (void *) arg, &message, cmd_size ); if ( status != 0 ) return -EFAULT; } break; case PORTIO_GETMAP_CMD: PIO_DEBUG(("Portio: getting permission map.\n")); if ( cmd_size > 0 ) { status = copy_to_user( (void *) arg, &permission_map, cmd_size ); if ( status != 0 ) return -EFAULT; } break; case PORTIO_DEBUG_CMD: status = copy_from_user( &debug, (void *) arg, cmd_size ); if ( status != 0 ) return -EFAULT; printk("Portio: changing debug state to %d\n", debug); break; default: printk("Portio: invalid command %#x seen.\n", cmd_number); return -EINVAL; } return 0; } static int portio_open( struct inode *inode, struct file *file ) { MOD_INC_USE_COUNT; return 0; } static int portio_release( struct inode *inode, struct file *file ) { MOD_DEC_USE_COUNT; return 0; } /*******************************************************/ static struct file_operations portio_ops = { #if ( LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) ) NULL, /* owner */ #endif NULL, /* lseek */ NULL, /* read */ NULL, /* write */ NULL, /* readdir */ NULL, /* poll */ portio_ioctl, /* ioctl */ NULL, /* mmap */ portio_open, /* open */ NULL, /* flush */ portio_release, /* release */ NULL, /* fsync */ NULL, /* fasync */ NULL, /* media_change */ NULL /* revalidate */ }; /*******************************************************/ static int process_command_line_arguments( void ) { char *ptr, *start_addr_ptr, *end_addr_ptr, *semicolon_ptr; __u16 start_address, end_address; int i, start_index, end_index; /* Only need to parse the 'ports' argument, the operating * system will handle parsing the debug argument on its own. */ if ( ports == NULL ) { PIO_DEBUG(( "Portio: no devices specified.\n" )); } else { PIO_DEBUG(( "Portio: ports = '%s'\n", ports )); ptr = ports; /* Skip a leading '@' character for backward compatibility * with version 0.3 and before of the driver. */ if ( *ptr == '@' ) ptr++; /* Parse the 'ports' string. */ while ( ptr != NULL ) { start_addr_ptr = ptr; semicolon_ptr = strchr( start_addr_ptr, ';' ); if ( semicolon_ptr == NULL ) { ptr = NULL; } else { ptr = semicolon_ptr + 1; *semicolon_ptr = '\0'; } end_addr_ptr = strchr( start_addr_ptr, '-' ); if ( end_addr_ptr == NULL ) { printk( "Portio: badly formed argument '%s'\n", start_addr_ptr ); return -EINVAL; } *end_addr_ptr = '\0'; end_addr_ptr++; start_address = simple_strtoul(start_addr_ptr, NULL, 0); end_address = simple_strtoul(end_addr_ptr, NULL, 0); if ( end_address < start_address ) { printk( "Portio: end address less than start address for port range (%#x-%#x)\n", start_address, end_address ); return -EINVAL; } if ( (start_address < PORTIO_MIN_ADDRESS) || (start_address > PORTIO_MAX_ADDRESS) || (end_address < PORTIO_MIN_ADDRESS) || (end_address > PORTIO_MAX_ADDRESS) ) { printk( "Portio: port range (%#x-%#x) is outside the permitted range (%#x-%#x)\n", start_address, end_address, PORTIO_MIN_ADDRESS, PORTIO_MAX_ADDRESS ); return -EINVAL; } start_index = start_address - PORTIO_MIN_ADDRESS; end_index = end_address - PORTIO_MIN_ADDRESS; for ( i = start_index; i <= end_index; i++ ) { portio_permission_array[i] = 1; } } } return 0; } static int portio_scan_addresses( int (*fptr) ( __u16, __u16 ) ) { __u16 start_address, end_address, length; int i, status, in_requested_range; start_address = 0; end_address = 0; in_requested_range = 0; for ( i = 0; i < PORTIO_NUM_PORTS; i++ ) { if ( in_requested_range == 0 ) { if ( portio_permission_array[i] != 0 ) { in_requested_range = 1; start_address = i + PORTIO_MIN_ADDRESS; } } else { if ( portio_permission_array[i] == 0 ) { in_requested_range = 0; end_address = i - 1 + PORTIO_MIN_ADDRESS; } else if ( i == (PORTIO_NUM_PORTS - 1) ) { in_requested_range = 0; end_address = PORTIO_MAX_ADDRESS; } if ( in_requested_range == 0 ) { length = end_address - start_address + 1; status = (*fptr)( start_address, length ); if ( status < 0 ) return status; start_address = 0; end_address = 0; } } } return 0; } static int portio_check_region( __u16 start_address, __u16 length ) { int status; status = check_region( start_address, length ); if ( status < 0 ) { printk( "Portio: port range (%#x-%#x) is already in use.\n", start_address, start_address + length - 1 ); return status; } return 0; } static int portio_request_region( __u16 start_address, __u16 length ) { request_region( start_address, length, PORTIO_NAME ); printk( "Portio: port range (%#x-%#x) allocated.\n", start_address, start_address + length - 1 ); return 0; } static int portio_release_region( __u16 start_address, __u16 length ) { release_region( start_address, length ); return 0; } int init_module( void ) { int i, status; PIO_DEBUG(("Portio module initializing.\n")); for ( i = 0; i < PORTIO_NUM_PORTS; i++ ) { portio_permission_array[i] = 0; } status = process_command_line_arguments(); if ( status < 0 ) return status; /* Check to see if any of the requested ioports are already taken. */ status = portio_scan_addresses( portio_check_region ); if ( status < 0 ) return status; /* Register this module as handling a character device. */ status = register_chrdev( portio_major, PORTIO_NAME, &portio_ops ); if ( status < 0 ) { printk( "Portio: Unable to request major %d ( reason = %d )\n", portio_major, status ); return -EIO; } printk( "Portio version %s driver installed (major = %d), debug = %d\n", portio_version, portio_major, debug ); /* Request I/O port regions. */ status = portio_scan_addresses( portio_request_region ); if ( status < 0 ) return status; return 0; } void cleanup_module( void ) { PIO_DEBUG(("Portio module shutting down.\n")); /* Release I/O port regions. */ (void) portio_scan_addresses( portio_release_region ); unregister_chrdev( portio_major, PORTIO_NAME ); return; }