[Linux-parport] [PATCH 2.6.13] parport: Add ioctl to speed up simple clocking h/w

Andrew Draper adraper at altera.com
Wed Jul 27 11:21:03 EDT 2005


This patch adds an ioctl which speeds up access to simple hardware (for example
when a clock is output on one pin and data values on other pins) by reducing
the amount of system call overhead.  The pin on which the clock is output is
configurable.  It also supports more complex modes where the value output on
a pin is controlled by the value read on a different pin.

It has been tested using the Altera ByteBlaster hardware.

Signed-off-by: Andrew Draper <adraper at altera.com>

--- linux-2.6.12.3/include/linux/ppdev.h.orig	2005-07-27 14:06:56.000000000 +0100
+++ linux-2.6.12.3/include/linux/ppdev.h	2005-07-27 14:07:41.000000000 +0100
@@ -98,4 +98,25 @@ struct ppdev_frob_struct {
 /* 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)
 
--- linux-2.6.12.3/drivers/char/ppdev.c.orig	2005-07-27 14:07:09.000000000 +0100
+++ linux-2.6.12.3/drivers/char/ppdev.c	2005-07-27 14:07:41.000000000 +0100
@@ -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 at ukc.ac.uk>, 03/01/2001.
  * Added GETFLAGS/SETFLAGS ioctls, Fred Barnes, 04/2001
+ *
+ * Added BITBASH ioctl, Andrew Draper <adraper at altera.com> 07/2005
  */
 
 #include <linux/module.h>
@@ -329,6 +332,169 @@ static enum ieee1284_phase init_phase (i
 	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 + 4, 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++; /* Safe - see above */
+
+			if (actions & BITBASH_WCTRL)
+				ctrl = *outptr++; /* Safe - see above */
+
+			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 @@ static int pp_ioctl(struct inode *inode,
 			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,
 			cmd);




More information about the Linux-parport mailing list