[PARPORT] [PATCH] Improve userspace access to ppdev with simple hardware

From: Andrew Draper (adraper@altera.com)
Date: Wed Jun 25 2003 - 08:13:04 EDT

  • Next message: juapabsan@tutopia.com: "[PARPORT] Validacion de Status de Cajon Monedero en Impresora POS Epson"

    Hi,

    The patch below speeds up userspace access to simple hardware on a
    parallel port by ~30% by reducing system call overhead. More
    explanation below.

    I am using the parallel port to drive some very simplistic hardware - the
    Altera ByteBlaster cable (see http://www.altera.com/literature/ds/dsbytemv.pdf
    (page 4) for a circuit diagram, or
    http://www.altera.com/support/software/drivers/dri-index.html for more
    general information). It's very stupid - basically just a set of buffers.

    The cable is connected to the JTAG port on a set of devices. It works by
    using the data0 pin as a clock to shift data in from data1, through the
    JTAG chain and back out of the status pin.

    I'm currently using the ppdev device to access this, but am obviously
    using a lot of ioctls to drive the clock. The patch below lets userspace
    provide a block of data to ppdev in one ioctl, this is sent out one
    access at a time.

    The new ioctl should be flexible enough for use by any hardware (eg. the
    pin on which the clock is provided is configurable). If it doesn't suit
    your hardware then please say so and I'll try and generalise it some more.

    Who should I send this to when it is ready?

        Andy

    diff -X patch-exclude.txt -ur orig/linux-2.5.73/include/linux/ppdev.h linux-2.5.73/include/linux/ppdev.h
    --- linux-2.5.73-orig/include/linux/ppdev.h Sun Jun 22 19:32:38 2003
    +++ linux-2.5.73/include/linux/ppdev.h Mon Jun 23 17:17:07 2003
    @@ -98,4 +98,25 @@
     /* only masks user-visible flags */
     #define PP_FLAGMASK (PP_FASTWRITE | PP_FASTREAD | PP_W91284PIC)
     
    +struct ppdev_bitbash
    +{
    + __u32 outlen;
    + __u32 inlen;
    + __u32 double_data;
    + __u32 double_ctrl;
    + __u32 modify_data[32];
    + __u32 modify_ctrl[32];
    + void __user *output;
    + void __user *input;
    +};
    +
    +/* Perform a number of BITBASH operations */
    +#define PPBITBASH _IOWR(PP_IOCTL, 0x9c, struct ppdev_bitbash)
    +
    +/* BITBASH control flags */
    +#define BITBASH_WDATA (1<<0)
    +#define BITBASH_WCTRL (1<<1)
    +#define BITBASH_WDOUBLE (1<<2)
    +#define BITBASH_WMODIFY (1<<3)
    +#define BITBASH_RSTATUS (1<<4)
     
    diff -X patch-exclude.txt -ur orig/linux-2.5.73/drivers/char/ppdev.c linux-2.5.73/drivers/char/ppdev.c
    --- linux-2.5.73-orig/drivers/char/ppdev.c Sun Jun 22 19:33:32 2003
    +++ linux-2.5.73/drivers/char/ppdev.c Mon Jun 23 18:16:24 2003
    @@ -41,6 +41,7 @@
      * GETPHASE gets the current IEEE1284 phase
      * GETFLAGS gets current (user-visible) flags
      * SETFLAGS sets current (user-visible) flags
    + * BITBASH performs multiple reads and writes to the registers
      * read/write read or write in current IEEE 1284 protocol
      * select wait for interrupt (in readfds)
      *
    @@ -54,6 +55,8 @@
      *
      * Added GETMODES/GETMODE/GETPHASE ioctls, Fred Barnes <frmb2@ukc.ac.uk>, 03/01/2001.
      * Added GETFLAGS/SETFLAGS ioctls, Fred Barnes, 04/2001
    + *
    + * Added BITBASH ioctl, Andrew Draper <adraper@altera.com> 07/2003
      */
     
     #include <linux/module.h>
    @@ -326,6 +329,169 @@
             }
             return IEEE1284_PH_FWD_IDLE;
     }
    +
    +/**
    + * bitbash_ioctl() - Multiple writes to parallel port data lines
    + * @pp: The ppdev device the ioctl was submitted on
    + * @arg: A pointer to the location in userspace where the argument is located
    + *
    + * Description: This function handles the ioctl PPBITBASH.
    + * The ppdev_bitbash structure passed to this ioctl points to two userspace
    + * buffers, the output buffer and the input buffer.
    + *
    + * Data in the output buffer is formatted as an action byte, optionally followed
    + * by data and control bytes. The action byte is a bitfield with the following
    + * meanings:
    + * BITBASH_WDATA - The next byte should be written to the port's data
    + * register.
    + * BITBASH_WCTRL - The next byte (or next but one if WDATA is also set)
    + * should be written to the port's control register
    + * BITBASH_WDOUBLE - The data/control bytes should be written twice each.
    + * The value written the second time is xored with a
    + * value from the ppdev_bitbash structure.
    + * BITBASH_WMODIFY - Modify the data/control bytes to be written according
    + * to the value read from the status register.
    + * BITBASH_RSTATUS - Read the status register after writing the data and
    + * control registers and store the value read in the
    + * input buffer.
    + *
    + * The WDOUBLE bit is intended for use when one of the data/control lines is
    + * supplying a clock to some piece of hardware. Set the double_data or
    + * double_ctrl member to the bit which controls the clock.
    + *
    + * The WMODIFY bit allows larger blocks of data to be used when the value
    + * written out depends on the value read in on the previous clock. Set the
    + * appropriate members of modify_data or modify_ctrl to control which status
    + * bit modifies which data/ctrl bits. WMODIFY cannot be set on the first
    + * action in the buffer.
    + */
    +static int bitbash_ioctl (struct pp_struct *pp, struct ppdev_bitbash __user * arg)
    +{
    + struct ppdev_bitbash bitbash;
    + char * outbuffer;
    + char * inbuffer;
    + struct parport *port = pp->pdev->port;
    +
    + unsigned char status = 0xFF;
    + int bytes_read = 0;
    +
    + if (copy_from_user (&bitbash, arg, sizeof (bitbash)))
    + return -EFAULT;
    +
    + outbuffer = kmalloc(min_t(size_t, bitbash.outlen, PP_BUFFER_SIZE), GFP_KERNEL);
    + if (!outbuffer) {
    + return -ENOMEM;
    + }
    +
    + inbuffer = kmalloc(min_t(size_t, bitbash.inlen, PP_BUFFER_SIZE), GFP_KERNEL);
    + if (!inbuffer) {
    + kfree(outbuffer);
    + return -ENOMEM;
    + }
    +
    + while (bitbash.outlen > 0) {
    + /* Leave 4 bytes spare so that we can read from them safely if we go past */
    + /* the end of the buffer (the values read are always discarded) */
    + ssize_t out_n = min_t(unsigned long, bitbash.outlen, PP_BUFFER_SIZE - 4);
    + ssize_t in_n = min_t(unsigned long, bitbash.inlen, PP_BUFFER_SIZE);
    +
    + const char * outend = outbuffer + out_n;
    + const char * outptr = outbuffer;
    + char * inend = inbuffer + in_n;
    + char * inptr = inbuffer;
    +
    + if (copy_from_user (outbuffer, bitbash.output, out_n)) {
    + bytes_read = -EFAULT;
    + break;
    + }
    +
    + while (outptr < outend) {
    + const char * outstart = outptr;
    + unsigned char actions = *outptr++;
    + unsigned char data = 0;
    + unsigned char ctrl = 0;
    +
    + if (actions & BITBASH_WDATA)
    + data = *outptr++;
    +
    + if (actions & BITBASH_WCTRL)
    + ctrl = *outptr++;
    +
    + if (outptr > outend || ((actions & BITBASH_RSTATUS) && inptr >= inend)) {
    + outptr = outstart;
    + break;
    + }
    +
    + if (actions & BITBASH_WMODIFY) {
    + if (status == 0xFF)
    + status = parport_read_status (port);
    +
    + data ^= bitbash.modify_data[status >> 3];
    + ctrl ^= bitbash.modify_ctrl[(status >> 3) | 32];
    + }
    +
    + if (actions & BITBASH_WDATA)
    + parport_write_data (port, data);
    + if (actions & BITBASH_WCTRL)
    + parport_write_control (port, ctrl);
    +
    + if (actions & BITBASH_WDOUBLE) {
    + if (actions & BITBASH_WDATA)
    + parport_write_data (port, data ^ bitbash.double_data);
    + if (actions & BITBASH_WCTRL)
    + parport_write_control (port, ctrl ^ bitbash.double_ctrl);
    + }
    +
    + if (actions & BITBASH_RSTATUS) {
    + status = parport_read_status (port) & 0xF8;
    + *inptr++ = status;
    + } else {
    + status = 0xFF;
    + }
    + }
    +
    + out_n = outptr - outbuffer;
    +
    + /* If incomplete data (or insufficient input buffer space) is provided
    + * we bail out instead of looping for ever */
    + if (out_n == 0)
    + break;
    +
    + bitbash.output = (char __user *)bitbash.output + out_n;
    + bitbash.outlen -= out_n;
    +
    + in_n = inptr - inbuffer;
    +
    + if (in_n > 0 && copy_to_user (bitbash.input, inbuffer, in_n)) {
    + bytes_read = -EFAULT;
    + break;
    + }
    +
    + bytes_read += in_n;
    + bitbash.input = (char __user *)bitbash.input + in_n;
    + bitbash.inlen -= in_n;
    +
    + if (bitbash.outlen == 0)
    + break;
    +
    + if (signal_pending (current)) {
    + if (!bytes_read)
    + bytes_read = -EINTR;
    + break;
    + }
    +
    + cond_resched();
    + }
    +
    + /* Copy back so that the restart works right if we've been interrupted */
    + if (copy_to_user (arg, &bitbash, sizeof (bitbash)))
    + bytes_read = -EFAULT;
    +
    + kfree(inbuffer);
    + kfree(outbuffer);
    +
    + return bytes_read;
    +}
     
     static int pp_ioctl(struct inode *inode, struct file *file,
                         unsigned int cmd, unsigned long arg)
    @@ -625,6 +791,9 @@
                             return -EFAULT;
                     }
                     return 0;
    +
    + case PPBITBASH:
    + return bitbash_ioctl(pp, (struct ppdev_bitbash *)arg);
     
             default:
                     printk (KERN_DEBUG CHRDEV "%x: What? (cmd=0x%x)\n", minor,

    -- To unsubscribe, send mail to: linux-parport-request@torque.net --
    -- with the single word "unsubscribe" in the body of the message. --



    This archive was generated by hypermail 2b29 : Wed Jun 25 2003 - 09:11:55 EDT