lp.c IEEE1284 readback patch

Carsten Gross (carsten@sol.wohnheim.uni-ulm.de)
Tue, 16 Dec 1997 00:04:59 +0100 (MET)


Hello Linux kernel developers,

I've some additions to the "IEEE1284" compatible printer port readback in
"nibble mode". This patch is against a 2.1.72 kernel and should apply
without problems to the file drivers/char/lp.c and linux/include/lp.h.

Behaviour after applying the patch and read(2)'ing the lp-device:

Flag(s) set | Data | No Data | Printer doesn't support
| available | available | readback operation
| | | (or is not present)
------------------------------------------------------------------------
O_NONBLOCK | return | return -EAGAIN | return -EIO
|data into | |
| buffer | |
| | |
no Flags set | return | block | return -EIO
|data into | |
| buffer | |

Receiving a signal during I/O returns -EINTR. I've made some change to
update the driver to the new ssize_t and size_t types. I hope, this
behaviour is the correct POSIX compatible implementation and I would be glad
to hear your comments.

Please have also a look on the added ioctl. Use this small program for a
first test after applying the kernel patch:
-------------- lp-ioctl.c -------------------
#include <stdio.h>
#include <linux/lp.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {
char buf[1024];
int fildes;
/* Insert your printer device here */
fildes=open("/dev/lp1", O_RDWR);
ioctl(fildes, LPGETDEVICEID, buf);
printf("%s\n", buf+2);
exit(0);
}
-------------------------------------------
My printer returns (reformatted, normally without CR and/or LF):
python:/home/carsten/c-src#./a.out
MANUFACTURER:Hewlett-Packard;COMMAND
SET:PJL,MLC,PCLXL,PCL,POSTSCRIPT;MODEL:HP LaserJet
6MP;CLASS:PRINTER;DESCRIPTION:Hewlett-Packard LaserJet 6MP Printer;

But a problem still remains to improve: I've a high load (about 0.2 on a
Pentium 100) while doing a blocking read(2) on the printer port. I think
this is much too much for just unlocking the parport device, doing a quick
test on the printer ports status lines and locking it again. I'm working on
this problem, but I've no solution for it yet.

Please seperate the two patches from this file and apply them to
drivers/char/lp.c and include/linux/lp.h.

Ciao

Carsten

-- 
Carsten Gross		Internet: carsten@sol.wohnheim.uni-ulm.de
Wohnheim Heilmeyersteige:  Sebastian Kneipp Weg 6, 89075 Ulm

-------------------- patch to driver/char/lp.c ------------------ --- lp.c.OLD Sun Nov 30 23:00:38 1997 +++ lp.c Mon Dec 15 15:00:48 1997 @@ -275,8 +275,8 @@ return total_bytes_written; } -static ssize_t lp_write(struct file * file, const char * buf, - size_t count, loff_t *ppos) +static ssize_t lp_write(struct file * file, const char * buf, + size_t count, loff_t *ppos) { unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); ssize_t retv; @@ -304,7 +304,44 @@ #ifdef CONFIG_PRINTER_READBACK -static int lp_read_nibble(int minor) +/* The following read funktions are an implementation of a status readback + * and device id request confirming to IEEE1284-1994. + */ + +/* Wait for Status line(s) to change in 35 ms - see IEEE1284-1994 page 24 to + * 25 for this. After this time we can create a timeout because the + * peripheral doesn't conform to IEEE1284. We want to save CPU time: we are + * waiting a maximum time of 500 us busy (this is for speed). If there is + * not the right answer in this time, we call schedule and other processes + * are able "to eat" the time up to 30ms. So the maximum load avarage can't + * get above 5% for a read even if the peripheral is really slow. (but your + * read gets very slow then - only one character every 10-30 ms. This + * should be tuneable). The Alpha platform could be a problem too. + */ +static int lp_wait_peripheral(unsigned int minor, unsigned char mask, + unsigned char result) +{ + int counter=0; + unsigned char status; + + do { + status=r_str(minor); + udelay(25); + counter++; + if (need_resched) + schedule(); + } while ( ((status & mask) != result) && (counter < 20) ); + if ( (counter == 20) && ((status & mask) != result) ) { + current->state=TASK_INTERRUPTIBLE; + current->timeout=jiffies+4; + schedule(); /* wait for 4 scheduler runs (40ms) */ + status=r_str(minor); + if ((status & mask) != result) return 1; /* timeout */ + } + return 0; /* okay right response from device */ +} + +static inline int lp_read_nibble(unsigned int minor) { unsigned char i; i=r_str(minor)>>3; @@ -313,100 +350,176 @@ return(i & 0x0f); } -static void lp_select_in_high(int minor) { - w_ctr(minor, (r_ctr(minor) | 8)); +static inline void lp_read_terminate(unsigned int minor) { + + w_ctr( minor, (r_ctr(minor) & ~2) | 8); + /* SelectIN high, AutoFeed low */ + if (lp_wait_peripheral(minor, 0x80, 0)) + /* timeout, SelectIN high, Autofeed low */ + return; + w_ctr( minor, r_ctr(minor) | 2); + /* AutoFeed high */ + lp_wait_peripheral(minor, 0x80, 0x80); + /* no timeout possible, Autofeed low, SelectIN high */ + w_ctr( minor, (r_ctr(minor) & ~2) | 8); + return; } -/* Status readback confirming to ieee1284 */ -static ssize_t lp_read(struct file * file, char * buf, - size_t count, loff_t *ppos) +static ssize_t lp_read_polled(unsigned int minor, char * buf, size_t count) { - unsigned char z=0, Byte=0, status; - char *temp; - ssize_t retval; - unsigned int counter=0; - unsigned int i; - unsigned int minor=MINOR(file->f_dentry->d_inode->i_rdev); - - /* Claim Parport or sleep until it becomes available - * (see lp_wakeup() for details) - */ - lp_parport_claim (minor); - - temp=buf; -#ifdef LP_READ_DEBUG - printk(KERN_INFO "lp%d: read mode\n", minor); -#endif + size_t i; + char * temp = buf; + unsigned char z=0; + unsigned char Byte=0; - retval = verify_area(VERIFY_WRITE, buf, count); - if (retval) - return retval; - if (parport_ieee1284_nibble_mode_ok(lp_table[minor].dev->port, 0)==0) { -#ifdef LP_READ_DEBUG - printk(KERN_INFO "lp%d: rejected IEEE1284 negotiation.\n", - minor); -#endif - lp_select_in_high(minor); - parport_release(lp_table[minor].dev); - return temp-buf; /* End of file */ - } for (i=0; i<=(count*2); i++) { w_ctr(minor, r_ctr(minor) | 2); /* AutoFeed high */ - do { - status=(r_str(minor) & 0x40); - udelay(50); - counter++; - if (need_resched) - schedule (); - } while ( (status == 0x40) && (counter < 20) ); - if ( counter == 20 ) { /* Timeout */ -#ifdef LP_READ_DEBUG - printk(KERN_DEBUG "lp_read: (Autofeed high) timeout\n"); -#endif - w_ctr(minor, r_ctr(minor) & ~2); - lp_select_in_high(minor); + if (lp_wait_peripheral(minor, 0x40, 0)) { + w_ctr(minor, r_ctr(minor) & ~2 ); + lp_read_terminate(minor); parport_release(lp_table[minor].dev); - return temp-buf; /* end the read at timeout */ + return temp-buf; /* end read at timeout */ } - counter=0; z=lp_read_nibble(minor); w_ctr(minor, r_ctr(minor) & ~2); /* AutoFeed low */ - do { - status=(r_str(minor) & 0x40); - udelay(20); - counter++; - if (need_resched) - schedule (); - } while ( (status == 0) && (counter < 20) ); - if (counter == 20) { /* Timeout */ -#ifdef LP_READ_DEBUG - printk(KERN_DEBUG "lp_read: (Autofeed low) timeout\n"); -#endif - if (signal_pending(current)) { - lp_select_in_high(minor); - parport_release(lp_table[minor].dev); - if (temp !=buf) - return temp-buf; - else - return -EINTR; - } - current->state=TASK_INTERRUPTIBLE; - current->timeout=jiffies + LP_TIME(minor); - schedule (); + if (lp_wait_peripheral(minor, 0x40, 0x40)) { + lp_read_terminate(minor); + parport_release(lp_table[minor].dev); + return temp-buf; } - counter=0; if (( i & 1) != 0) { Byte= (Byte | z<<4); - put_user(Byte, temp); + if (put_user(Byte, temp)) { + lp_read_terminate(minor); + parport_release(lp_table[minor].dev); + return -EFAULT; + } temp++; + /* Does the error line indicate end of data? */ + if ( (r_str(minor) & LP_PERRORP ) == LP_PERRORP) { + lp_read_terminate(minor); + parport_release(lp_table[minor].dev); + return temp-buf; + } } else Byte=z; } - lp_select_in_high(minor); + lp_read_terminate(minor); parport_release(lp_table[minor].dev); - return temp-buf; + return temp-buf; +} + +/* This function provides a status readback confirming to IEEE1284. + * This is done using the "nibble mode". This should work on all PC + * with peripherals confirming to this standard. Now all timeouts + * are correct (I hope so). The host timeout is 1s, this should not + * be a problem, but the peripheral timeout is 35ms, this is + * handled by the function lp_wait_peripheral. + */ +static ssize_t lp_read(struct file * file, char * buf, + size_t count, loff_t *ppos) +{ + unsigned int minor=MINOR(file->f_dentry->d_inode->i_rdev); + ssize_t retval; + char * temp; + + temp=buf; + /* Claim Parport or sleep until it becomes available + * (see lp_wakeup() for details) + */ + if (parport_claim(lp_table[minor].dev)) { + sleep_on(&lp_table[minor].lp_wait_q); + lp_table[minor].lp_wait_q = NULL; + } +#if 0 + if (LP_IRQ(minor)) return -EINVAL; /* irq mode not yet supported */ +#endif + retval=parport_ieee1284_nibble_mode_ok(lp_table[minor].dev->port, 0); +#ifdef LP_READ_DEBUG + printk(KERN_INFO "lp%d: rejected IEEE1284 negotiation.\n", + minor); +#endif + + if (retval == 0) { + parport_release(lp_table[minor].dev); + return -EIO; /* Printer doesn't support readback */ + } + /* If no printer data is available, there are 3 possibilities: + * 1. In the case of nonblocking IO: simply return -EAGAIN + * 2. We have received a signal: return -EINTR. + * 3. In the case of blocking IO "hang" and wait for data + */ + if (retval == 2) + do { /* printer has no data for reading, "not ready" */ + lp_read_terminate(minor); + /* Make parport available for other devices */ + parport_release(lp_table[minor].dev); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + if (signal_pending(current)) + return -EINTR; + current->state=TASK_INTERRUPTIBLE; + current->timeout=jiffies+2; + schedule(); + /* Claim the parport again */ + if (parport_claim(lp_table[minor].dev)) { + sleep_on(&lp_table[minor].lp_wait_q); + lp_table[minor].lp_wait_q = NULL; + } + retval=parport_ieee1284_nibble_mode_ok\ + (lp_table[minor].dev->port, 0); + } while (retval == 2); + return (lp_read_polled(minor, buf, count)); } +/* This is an ioctl requesting the device ID from the peripheral. Note that + * even non printer peripherals on /dev/lp.. could be IEEE1284 devices and + * we can read their ID using the call ioctl(fd, LPGETDEVICEID, char *buf) + * Beware, the first 2 characters are the length of the response in + * big endian, so you've to use buf+2 if you want the response string in + * "C" notation. + */ +static inline int lp_read_deviceid(unsigned int minor, unsigned long arg) +{ + char *temp=(char *)arg; + int retval, i; + +#if 0 + if (LP_IRQ(minor)) return -EINVAL; /* irq mode not yet supported */ +#endif + /* Claim Parport or sleep until it becomes available + * (see lp_wakeup() for details) + */ + if (parport_claim(lp_table[minor].dev)) { + sleep_on(&lp_table[minor].lp_wait_q); + lp_table[minor].lp_wait_q = NULL; + } + /* Request device id using the nibble mode (mode = 4) */ + i=parport_ieee1284_nibble_mode_ok(lp_table[minor].dev->port, 4); + if (i == 1) { + current->state=TASK_INTERRUPTIBLE; + current->timeout=jiffies+1; + schedule();/* HACK: wait 10ms because printer seems to + * ack wrong */ + retval=lp_read_polled(minor, temp, 0x10000); + /* more then 0x10000 bytes are not possible. + usually the response is quite small, about 200 to + 300 bytes. Reading into the buffer stops if there + is no more data from the printer or the buffer + pointed to by arg is to small, this results in + -EFAULT */ + if (retval < 0) + return retval; +#ifdef LP_READ_DEBUG + printk(KERN_DEBUG "lp: ioctl: DEVICEID retval=%d\n", retval); #endif + } else if (i == 0) { + parport_release(lp_table[minor].dev); + return -EIO; /* DeviceID not supported by printer */ + } + return 0; +} + +#endif /* CONFIG_PRINTER_READBACK */ static int lp_open(struct inode * inode, struct file * file) { @@ -546,8 +659,13 @@ else { int status = LP_F(minor); copy_to_user((int *) arg, &status, sizeof(int)); - } + } + break; +#ifdef CONFIG_PRINTER_READBACK + case LPGETDEVICEID: + retval = lp_read_deviceid(minor, arg); break; +#endif default: retval = -EINVAL; }

------------------ patch to include/linux/lp.h -------------------- --- lp..h.OLD Tue Aug 26 23:52:45 1997 +++ lp.h Mon Dec 15 11:01:54 1997 @@ -70,6 +70,7 @@ #define LPRESET 0x060c /* reset printer */ #define LPGETSTATS 0x060d /* get statistics (struct lp_stats) */ #define LPGETFLAGS 0x060e /* get status flags */ +#define LPGETDEVICEID 0x060f /* read device id confirming to IEEE1284 */ /* timeout for printk'ing a timeout, in jiffies (100ths of a second). This is also used for re-checking error conditions if LP_ABORT is