The problem I have is that I don't want to grab the offset from
tsio_open() and store it in a static int as that would not allow
simultaneous opens of different ports.
I am now using filp->private_data to hold the minor and them accessing
it to find the offset within each tsio_read() / tsio_write(), but that
seems akward. Does anyone have a suggestion as to a better way to do
this or an explanation as to why I can't MINOR(inode->i_rdev) within
the read / write operations themselves?
If anyone has the time, I would like a review of the code for
suggestions. It's only 250 lines w/ comments, so I've included it
below.
Thanks, Drew
D->|
-----------------------------tsio.c--------------------------------
/* tsio.c (for Linux kernel version 2.2.x or higher.
*
* This is the Linux driver for the Ecosys Monitoring Systems
* (TeloSense) I/O board. The driver requires that the base I/O
* address be passed to it at load time.
*
* A node (e.g. /dev/tsio0, * /dev/tsio1, ...) must be created for
* each 8-bit port. This can be done at the * invocation of the
* module (or thereafter) by using mknod(1). The minor number for
* each node will be used to determine the offset of the port from the
* base I/O address.
*
*
*/
#define __KERNEL__
#define MODULE
#if 0
#define EXPORT_SYMTAB /* We don't export any syms (yet). */
#endif
/* Our own definitions for this device. */
#define DEVICE_NAME "tsio"
#define RANGE 16 /* This is the number of 8-bit ports on the board. */
/*
* We need to determine the offset (from io_base) for a read or write
* operation. This is determined from the minor number of the device.
* The only time we can do that is in tsio_open(), but we need to
* secure the offset for a device for tsio_read() and tsio_write().
* To do that, we place the offset into *filp->*private_data.
* `private_data' is a `void *', so we need to use a macro to help
* conceal the unclean assignment: */
#define MINOR_FROM_FILEP(F) (unsigned int)((F)->private_data)
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* For put_user, get_user, etc. */
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/kdev_t.h> /* For MINOR macro, etc. */
#include <asm/page.h>
#include <asm/io.h>
static unsigned int io_base = 0; /* Default set to generate error. */
static unsigned int tsio_major = 0; /* Set to zero (dynamic allocation. */
MODULE_PARM(io_base, "i"); /* Define at load time. */
static int tsio_request_port (unsigned int port, unsigned int range)
{
int err;
if ((err = check_region (port, range)) < 0 ) return err; /* busy */
request_region (port, range, DEVICE_NAME);
return 0;
}
static void tsio_release_port (unsigned int port, unsigned int range)
{
release_region (port, range);
}
/*************** READ *****************/
/* The offset (difference between port to be written and base port) is
* determined from the minor number of the device. We use the
* private_data component of *filp to store the offset. This is set
* in tsio_open() which has access to *inode.
*
*/
ssize_t tsio_read (struct file *filp, char *buf, size_t count, loff_t *loff)
{
unsigned byte_read;
int offset = filp->private_data;
if (verify_area (VERIFY_WRITE, buf, count) == -EFAULT)
{
return -EFAULT;
}
put_user (byte_read = inb (io_base + offset), buf);
#ifdef DEBUG
printk ("put user byte %c.\n", byte_read);
printk ("offset = %d\n", offset);
#endif
return 1;
}
/*************** WRITE *****************/
/* The offset (difference between port to be written and base port) is
* determined from the minor number of the device. We use the
* private_data component of *filp to store the offset. This is set
* in tsio_open() which has access to *inode.
*
*/
ssize_t tsio_write (struct file *filp,
const char *buf,
size_t count,
loff_t *loff)
{
unsigned char c = buf [0];
int offset = filp->private_data;
get_user (c, buf);
outb (c, io_base + offset);
#ifdef DEBUG
printk ("getting user byte %-#12x\n", c);
printk ("tsio write %-#12x\n", c);
printk ("offset = %d\n", offset);
#endif
return 1;
}
/*************** OPEN *****************/
int tsio_open (struct inode *inode, struct file *filp)
{
/*
* Here, we use the macro defined above:
*
* MINOR_FROM_FILEP(F) (unsigned int)((F)->private_data)
*
* to store the minor number in the *filp structure. This will
* allow us to retrieve the minor number (used as 'offset') from
* within tsio_read() and tsio_write(). */
MINOR_FROM_FILEP (filp) = MINOR (inode->i_rdev);
MOD_INC_USE_COUNT;
return 0;
}
/*************** CLOSE *****************/
int tsio_release (struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
return 0;
}
/*************** FOPS ******************/
struct file_operations tsio_fops = {
NULL, /* loff_t tsio_llseek(struct file *, loff_t, int) */
tsio_read, /* tsio_read - read from device. */
tsio_write, /* tsio_write - write to device. */
NULL, /* tsio_readdir - only for directories (NULL for devices). */
NULL, /* tsio_poll - check if device is readable or writable. */
NULL, /* tsio_ioctl - issue commands to device from user space. */
NULL, /* tsio_mmap - map device memory to a process's memory. */
tsio_open, /* tsio_open - open device. */
NULL, /* tsio_flush - flush device? New to 2.2.x kernel. */
tsio_release, /* tsio_release - close device.*/
NULL, /* tsio_fsync - flush device. */
NULL, /* tsio_fasync - check fasync flag. */
NULL, /* tsio_check_media_change - only used with block devices. */
NULL, /* tsio_revalidate - only meaningful for block drivers. */
};
int init_module (void)
{
int err = 0;
int result;
int i;
/* Test the io_base address parameter to be sure it is one of the
ones available to our card. */
switch (io_base)
{
case 0x310:
case 0x330:
case 0x360:
case 0x380:
case 0x3A0:
case 0x3E0: break;
default:
{
err = -EINVAL;
printk ("Error: %d.\n", err);
printk ("Unable to load TSIO module at specified io_base address.\n");
printk ("Illegal address passed to module: 0x%x.\n", io_base);
printk ("Use one of 0x310, 0x330, 0x360, or 0x3E0.\n");
return (err);
}
}
/* Request I/O port. */
printk ("Initializing TSIO board driver at io_base: 0x%x.\n", io_base);
tsio_request_port (io_base, RANGE);
result = register_chrdev (tsio_major, DEVICE_NAME, &tsio_fops);
if (result < 0)
{
printk ("TSIO: can't get major %d.\n", tsio_major);
return result;
}
if (tsio_major == 0)
{
tsio_major = result;
}
printk ("TSIO: major = %d.\n", tsio_major);
/* Initialize all ports to 0xff (turn off all I/O bits). */
for (i = 15; i >= 0; i--)
{
if ((i + 1 ) % 4)
{
outb (0xff, io_base + i);
}
else
{
outb (0x80, io_base + i);
}
}
return (0);
}
void cleanup_module (void)
{
int err = 0;
int i;
/* Turn off all I/O bits by writing 0xff to ports. */
for (i = 15; i >= 0; i--)
{
if (( i + 1) % 4 )
{
outb (0xff, io_base + i);
}
else
{
outb (0x80, io_base + i);
}
}
printk ("Removing TSIO board driver.\n");
tsio_release_port (io_base, RANGE);
if ((err = unregister_chrdev (tsio_major, DEVICE_NAME)))
{
printk ("TSIO: Can't unregister major number %d.\n", tsio_major);
}
}
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/