Drivers in 2.2.x: Best way to get minor number from within xxx_read()

Drew Bertola (drew@drewb.com)
Tue, 16 Nov 1999 17:51:27 -0800 (PST)


I have an ISA I/O board called tsio and based on 4 82C55s. It has 16
8-bit ports, 12 for I/O and 4 for "control words". I want to create
16 device nodes, 1 for each port, so that I can read/write to each
independently. I set the nodes up so that each device has it's minor
number the same as it's offset from the base I/O port address (0-15).

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/