diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1999-09-28 22:25:29 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1999-09-28 22:25:29 +0000 |
commit | 0ae8dceaebe3659ee0c3352c08125f403e77ebca (patch) | |
tree | 5085c389f09da78182b899d19fe1068b619a69dd /drivers/char/ppdev.c | |
parent | 273767781288c35c9d679e908672b9996cda4c34 (diff) |
Merge with 2.3.10.
Diffstat (limited to 'drivers/char/ppdev.c')
-rw-r--r-- | drivers/char/ppdev.c | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c new file mode 100644 index 000000000..881e521ad --- /dev/null +++ b/drivers/char/ppdev.c @@ -0,0 +1,548 @@ +/* + * linux/drivers/char/ppdev.c + * + * This is the code behind /dev/parport* -- it allows a user-space + * application to use the parport subsystem. + * + * Copyright (C) 1998-9 Tim Waugh <tim@cyberelk.demon.co.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * A /dev/parportxy device node represents an arbitrary device ('y') + * on port 'x'. The following operations are possible: + * + * open do nothing, set up default IEEE 1284 protocol to be COMPAT + * close release port and unregister device (if necessary) + * ioctl + * EXCL register device exclusively (may fail) + * CLAIM (register device first time) parport_claim_or_block + * RELEASE parport_release + * SETMODE set the IEEE 1284 protocol to use for read/write + * DATADIR data_forward / data_reverse + * WDATA write_data + * RDATA read_data + * WCONTROL write_control + * RCONTROL read_control + * FCONTROL frob_control + * RSTATUS read_status + * NEGOT parport_negotiate + * YIELD parport_yield_blocking + * read/write read or write in current IEEE 1284 protocol + * select wait for interrupt (in readfds) + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/ioctl.h> +#include <linux/parport.h> +#include <linux/ctype.h> +#include <linux/poll.h> +#include <asm/uaccess.h> +#include "ppdev.h" + +#define PP_VERSION "ppdev: user-space parallel port driver" +#define CHRDEV "ppdev" + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +/* The device minor encodes the parport number and (arbitrary) + * pardevice number as (port << 4) | dev. */ +#define PP_PORT(minor) ((minor >> 4) & 0xf) +#define PP_DEV(minor) ((minor) & 0xf) + +struct pp_struct { + struct pardevice * pdev; + wait_queue_head_t irq_wait; + int mode; + unsigned int flags; +}; + +/* pp_struct.flags bitfields */ +#define PP_CLAIMED (1<<0) +#define PP_EXCL (1<<1) + +/* Other constants */ +#define PP_INTERRUPT_TIMEOUT (10 * HZ) /* 10s */ +#define PP_BUFFER_SIZE 256 +#define PARDEVICE_MAX 8 + +static struct pp_struct pp_table[PARPORT_MAX][PARDEVICE_MAX]; + +static loff_t pp_lseek (struct file * file, long long offset, int origin) +{ + return -ESPIPE; +} + +/* This looks a bit like parport_read. The difference is that we don't + * determine the mode to use from the port data, but rather from the + * mode the driver told us to use. */ +static ssize_t do_read (struct pp_struct *pp, void *buf, size_t len) +{ + size_t (*fn) (struct parport *, void *, size_t, int); + struct parport *port = pp->pdev->port; + + switch (pp->mode) { + case IEEE1284_MODE_COMPAT: + /* This is a write-only mode. */ + return -EIO; + + case IEEE1284_MODE_NIBBLE: + fn = port->ops->nibble_read_data; + break; + + case IEEE1284_MODE_BYTE: + fn = port->ops->byte_read_data; + break; + + case IEEE1284_MODE_EPP: + fn = port->ops->epp_read_data; + break; + + case IEEE1284_MODE_ECP: + case IEEE1284_MODE_ECPRLE: + fn = port->ops->ecp_read_data; + break; + + case IEEE1284_MODE_ECPSWE: + fn = parport_ieee1284_ecp_read_data; + break; + + default: + printk (KERN_DEBUG "%s: unknown mode 0x%02x\n", + pp->pdev->name, pp->mode); + return -EINVAL; + } + + return (*fn) (port, buf, len, 0); +} + +/* This looks a bit like parport_write. The difference is that we don't + * determine the mode to use from the port data, but rather from the + * mode the driver told us to use. */ +static ssize_t do_write (struct pp_struct *pp, const void *buf, size_t len) +{ + size_t (*fn) (struct parport *, const void *, size_t, int); + struct parport *port = pp->pdev->port; + + switch (pp->mode) { + case IEEE1284_MODE_NIBBLE: + case IEEE1284_MODE_BYTE: + /* Read-only modes. */ + return -EIO; + + case IEEE1284_MODE_COMPAT: + fn = port->ops->compat_write_data; + break; + + case IEEE1284_MODE_EPP: + fn = port->ops->epp_write_data; + break; + + case IEEE1284_MODE_ECP: + case IEEE1284_MODE_ECPRLE: + fn = port->ops->ecp_write_data; + break; + + case IEEE1284_MODE_ECPSWE: + fn = parport_ieee1284_ecp_write_data; + break; + + default: + printk (KERN_DEBUG "%s: unknown mode 0x%02x\n", + pp->pdev->name, pp->mode); + return -EINVAL; + } + + return (*fn) (port, buf, len, 0); +} + +static ssize_t pp_read (struct file * file, char * buf, size_t count, + loff_t * ppos) +{ + unsigned int minor = MINOR (file->f_dentry->d_inode->i_rdev); + unsigned int portnum = PP_PORT (minor); + unsigned int dev = PP_DEV (minor); + char * kbuffer; + ssize_t bytes_read = 0; + ssize_t got = 0; + + if (!(pp_table[portnum][dev].flags & PP_CLAIMED)) { + /* Don't have the port claimed */ + printk (KERN_DEBUG CHRDEV "%02x: claim the port first\n", + minor); + return -EPERM; + } + + kbuffer = kmalloc (min (count, PP_BUFFER_SIZE), GFP_KERNEL); + if (!kbuffer) + return -ENOMEM; + + while (bytes_read < count) { + ssize_t need = min(count - bytes_read, PP_BUFFER_SIZE); + + got = do_read (&pp_table[portnum][dev], kbuffer, need); + + if (got < 0) { + if (!bytes_read) + bytes_read = got; + + break; + } + + if (copy_to_user (kbuffer, buf + bytes_read, got)) { + bytes_read = -EFAULT; + break; + } + + bytes_read += got; + + if (signal_pending (current)) { + if (!bytes_read) + bytes_read = -EINTR; + break; + } + + if (current->need_resched) + schedule (); + } + + kfree (kbuffer); + return bytes_read; +} + +static ssize_t pp_write (struct file * file, const char * buf, size_t count, + loff_t * ppos) +{ + unsigned int minor = MINOR (file->f_dentry->d_inode->i_rdev); + unsigned int portnum = PP_PORT (minor); + unsigned int dev = PP_DEV (minor); + char * kbuffer; + ssize_t bytes_written = 0; + ssize_t wrote; + + if (!(pp_table[portnum][dev].flags & PP_CLAIMED)) { + /* Don't have the port claimed */ + printk (KERN_DEBUG CHRDEV "%02x: claim the port first\n", + minor); + return -EPERM; + } + + kbuffer = kmalloc (min (count, PP_BUFFER_SIZE), GFP_KERNEL); + if (!kbuffer) + return -ENOMEM; + + while (bytes_written < count) { + ssize_t n = min(count - bytes_written, PP_BUFFER_SIZE); + + if (copy_from_user (kbuffer, buf + bytes_written, n)) { + bytes_written = -EFAULT; + break; + } + + wrote = do_write (&pp_table[portnum][dev], kbuffer, n); + + if (wrote < 0) { + if (!bytes_written) + bytes_written = wrote; + break; + } + + bytes_written += wrote; + + if (signal_pending (current)) { + if (!bytes_written) + bytes_written = -EINTR; + break; + } + + if (current->need_resched) + schedule (); + } + + kfree (kbuffer); + return bytes_written; +} + +static void pp_irq (int irq, void * private, struct pt_regs * unused) +{ + struct pp_struct * pp = (struct pp_struct *) private; + wake_up_interruptible (&pp->irq_wait); +} + +static int register_device (int minor) +{ + unsigned int portnum = PP_PORT (minor); + unsigned int dev = PP_DEV (minor); + struct parport * port; + struct pardevice * pdev = NULL; + char *name; + int fl; + + name = kmalloc (strlen (CHRDEV) + 3, GFP_KERNEL); + if (name == NULL) + return -ENOMEM; + + sprintf (name, CHRDEV "%02x", minor); + port = parport_enumerate (); /* FIXME: use attach/detach */ + + while (port && port->number != portnum) + port = port->next; + + if (!port) { + printk (KERN_WARNING "%s: no associated port!\n", name); + kfree (name); + return -ENXIO; + } + + fl = (pp_table[portnum][dev].flags & PP_EXCL) ? PARPORT_FLAG_EXCL : 0; + pdev = parport_register_device (port, name, NULL, NULL, pp_irq, fl, + &pp_table[portnum][dev]); + + if (!pdev) { + printk (KERN_WARNING "%s: failed to register device!\n", name); + kfree (name); + return -ENXIO; + } + + pp_table[portnum][dev].pdev = pdev; + printk (KERN_DEBUG "%s: registered pardevice\n", name); + return 0; +} + +static int pp_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + unsigned int minor = MINOR(inode->i_rdev); + unsigned int portnum = PP_PORT (minor); + unsigned int dev = PP_DEV (minor); + struct parport * port; + + /* First handle the cases that don't take arguments. */ + if (cmd == PPCLAIM) { + if (pp_table[portnum][dev].flags & PP_CLAIMED) { + printk (KERN_DEBUG CHRDEV + "%02x: you've already got it!\n", minor); + return -EINVAL; + } + + /* Deferred device registration. */ + if (!pp_table[portnum][dev].pdev) { + int err = register_device (minor); + if (err) + return err; + } + + parport_claim_or_block (pp_table[portnum][dev].pdev); + pp_table[portnum][dev].flags |= PP_CLAIMED; + return 0; + } + + port = pp_table[portnum][dev].pdev->port; + if (cmd == PPEXCL) { + if (pp_table[portnum][dev].pdev) { + printk (KERN_DEBUG CHRDEV "%02x: too late for PPEXCL; " + "already registered\n", minor); + if (pp_table[portnum][dev].flags & PP_EXCL) + /* But it's not really an error. */ + return 0; + /* There's no chance of making the driver happy. */ + return -EINVAL; + } + + /* Just remember to register the device exclusively + * when we finally do the registration. */ + pp_table[portnum][dev].flags |= PP_EXCL; + return 0; + } + + /* Everything else requires the port to be claimed, so check + * that now. */ + if ((pp_table[portnum][dev].flags & PP_CLAIMED) == 0) { + printk (KERN_DEBUG CHRDEV "%02x: claim the port first\n", + minor); + return -EPERM; + } + + switch (cmd) { + unsigned char reg; + unsigned char mask; + int mode; + + case PPRSTATUS: + reg = parport_read_status (port); + return copy_to_user ((unsigned char *) arg, ®, + sizeof (reg)); + + case PPRDATA: + reg = parport_read_data (port); + return copy_to_user ((unsigned char *) arg, ®, + sizeof (reg)); + + case PPRCONTROL: + reg = parport_read_control (port); + return copy_to_user ((unsigned char *) arg, ®, + sizeof (reg)); + + case PPYIELD: + parport_yield_blocking (pp_table[portnum][dev].pdev); + return 0; + + case PPRELEASE: + parport_release (pp_table[portnum][dev].pdev); + pp_table[portnum][dev].flags &= ~PP_CLAIMED; + return 0; + + case PPSETMODE: + if (copy_from_user (&mode, (int *) arg, sizeof (mode))) + return -EFAULT; + /* FIXME: validate mode */ + pp_table[portnum][dev].mode = mode; + return 0; + + case PPWCONTROL: + if (copy_from_user (®, (unsigned char *) arg, sizeof (reg))) + return -EFAULT; + parport_write_control (port, reg); + return 0; + + case PPWDATA: + if (copy_from_user (®, (unsigned char *) arg, sizeof (reg))) + return -EFAULT; + parport_write_data (port, reg); + return 0; + + case PPFCONTROL: + if (copy_from_user (&mask, (unsigned char *) arg, + sizeof (mask))) + return -EFAULT; + if (copy_from_user (®, 1 + (unsigned char *) arg, + sizeof (reg))) + return -EFAULT; + parport_frob_control (port, mask, reg); + return 0; + + case PPDATADIR: + if (copy_from_user (&mode, (int *) arg, sizeof (mode))) + return -EFAULT; + if (mode) + port->ops->data_reverse (port); + else + port->ops->data_forward (port); + return 0; + + case PPNEGOT: + if (copy_from_user (&mode, (int *) arg, sizeof (mode))) + return -EFAULT; + /* FIXME: validate mode */ + return parport_negotiate (port, mode); + + default: + printk (KERN_DEBUG CHRDEV "%02x: What? (cmd=0x%x\n", minor, + cmd); + return -EINVAL; + } + + /* Keep the compiler happy */ + return 0; +} + +static int pp_open (struct inode * inode, struct file * file) +{ + unsigned int minor = MINOR (inode->i_rdev); + unsigned int portnum = PP_PORT (minor); + unsigned int dev = PP_DEV (minor); + + if (portnum >= PARPORT_MAX) + return -ENXIO; + + if (pp_table[portnum][dev].pdev) + return -EBUSY; + + pp_table[portnum][dev].mode = IEEE1284_MODE_COMPAT; + pp_table[portnum][dev].flags = 0; + init_waitqueue_head (&pp_table[portnum][dev].irq_wait); + + /* Defer the actual device registration until the first claim. + * That way, we know whether or not the driver wants to have + * exclusive access to the port (PPEXCL). + */ + pp_table[portnum][dev].pdev = NULL; + + MOD_INC_USE_COUNT; + return 0; +} + +static int pp_release (struct inode * inode, struct file * file) +{ + unsigned int minor = MINOR (inode->i_rdev); + unsigned int portnum = PP_PORT (minor); + unsigned int dev = PP_DEV (minor); + + if (pp_table[portnum][dev].flags & PP_CLAIMED) { + parport_release (pp_table[portnum][dev].pdev); + printk (KERN_DEBUG CHRDEV "%02x: released pardevice because " + "user-space forgot\n", minor); + } + + if (pp_table[portnum][dev].pdev) { + kfree (pp_table[portnum][dev].pdev->name); + parport_unregister_device (pp_table[portnum][dev].pdev); + pp_table[portnum][dev].pdev = NULL; + printk (KERN_DEBUG CHRDEV "%02x: unregistered pardevice\n", + minor); + } + + MOD_DEC_USE_COUNT; + return 0; +} + +#if 0 +static unsigned int pp_poll (struct file * file, poll_table * wait) +{ + unsigned int minor = MINOR (file->f_dentry->d_inode->i_rdev); + poll_wait (file, &pp_table[minor].irq_wait, wait); + return 0; /* FIXME! Return value is wrong here */ +} +#endif + +static struct file_operations pp_fops = { + pp_lseek, + pp_read, + pp_write, + NULL, /* pp_readdir */ + NULL, /* pp_poll */ + pp_ioctl, + NULL, /* pp_mmap */ + pp_open, + NULL, /* pp_flush */ + pp_release +}; + +#ifdef MODULE +#define pp_init init_module +#endif + +int pp_init (void) +{ + if (register_chrdev (PP_MAJOR, CHRDEV, &pp_fops)) { + printk (KERN_WARNING CHRDEV ": unable to get major %d\n", + PP_MAJOR); + return -EIO; + } + + printk (KERN_INFO PP_VERSION "\n"); + return 0; +} + +#ifdef MODULE +void cleanup_module (void) +{ + /* Clean up all parport stuff */ + unregister_chrdev (PP_MAJOR, CHRDEV); +} +#endif /* MODULE */ |