summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/ntdb/hash.c37
-rw-r--r--lib/ntdb/ntdb.c40
-rw-r--r--lib/ntdb/private.h3
-rw-r--r--lib/ntdb/test/run-04-basichash.c24
-rw-r--r--lib/ntdb/test/run-15-append.c2
-rw-r--r--lib/ntdb/test/run-64-bit-tdb.c2
-rw-r--r--lib/ntdb/traverse.c2
7 files changed, 59 insertions, 51 deletions
diff --git a/lib/ntdb/hash.c b/lib/ntdb/hash.c
index 69192a119b..ad1196ecde 100644
--- a/lib/ntdb/hash.c
+++ b/lib/ntdb/hash.c
@@ -33,7 +33,8 @@ uint32_t ntdb_hash(struct ntdb_context *ntdb, const void *ptr, size_t len)
static ntdb_bool_err key_matches(struct ntdb_context *ntdb,
const struct ntdb_used_record *rec,
ntdb_off_t off,
- const NTDB_DATA *key)
+ const NTDB_DATA *key,
+ const char **rptr)
{
ntdb_bool_err ret = false;
const char *rkey;
@@ -43,14 +44,20 @@ static ntdb_bool_err key_matches(struct ntdb_context *ntdb,
return ret;
}
- rkey = ntdb_access_read(ntdb, off + sizeof(*rec), key->dsize, false);
+ rkey = ntdb_access_read(ntdb, off + sizeof(*rec),
+ key->dsize + rec_data_length(rec), false);
if (NTDB_PTR_IS_ERR(rkey)) {
return (ntdb_bool_err)NTDB_PTR_ERR(rkey);
}
- if (memcmp(rkey, key->dptr, key->dsize) == 0)
- ret = true;
- else
- ntdb->stats.compare_wrong_keycmp++;
+ if (memcmp(rkey, key->dptr, key->dsize) == 0) {
+ if (rptr) {
+ *rptr = rkey;
+ } else {
+ ntdb_access_release(ntdb, rkey);
+ }
+ return true;
+ }
+ ntdb->stats.compare_wrong_keycmp++;
ntdb_access_release(ntdb, rkey);
return ret;
}
@@ -61,6 +68,7 @@ static ntdb_bool_err match(struct ntdb_context *ntdb,
const NTDB_DATA *key,
ntdb_off_t val,
struct ntdb_used_record *rec,
+ const char **rptr,
const ntdb_off_t **mapped)
{
ntdb_off_t off;
@@ -87,7 +95,7 @@ static ntdb_bool_err match(struct ntdb_context *ntdb,
return (ntdb_bool_err)ecode;
}
- return key_matches(ntdb, rec, off, key);
+ return key_matches(ntdb, rec, off, key, rptr);
}
static bool is_chain(ntdb_off_t val)
@@ -107,10 +115,11 @@ static ntdb_off_t hbucket_off(ntdb_off_t base, ntdb_len_t idx)
* If not found, the return value is 0.
* If found, the return value is the offset, and *rec is the record. */
ntdb_off_t find_and_lock(struct ntdb_context *ntdb,
- NTDB_DATA key,
- int ltype,
- struct hash_info *h,
- struct ntdb_used_record *rec)
+ NTDB_DATA key,
+ int ltype,
+ struct hash_info *h,
+ struct ntdb_used_record *rec,
+ const char **rptr)
{
ntdb_off_t off, val;
const ntdb_off_t *arr = NULL;
@@ -140,9 +149,9 @@ ntdb_off_t find_and_lock(struct ntdb_context *ntdb,
}
/* Directly in hash table? */
- if (!is_chain(val)) {
+ if (!likely(is_chain(val))) {
if (val) {
- berr = match(ntdb, h->h, &key, val, rec, NULL);
+ berr = match(ntdb, h->h, &key, val, rec, rptr, NULL);
if (berr < 0) {
ecode = NTDB_OFF_TO_ERR(berr);
goto fail;
@@ -195,7 +204,7 @@ ntdb_off_t find_and_lock(struct ntdb_context *ntdb,
found_empty = true;
}
} else {
- berr = match(ntdb, h->h, &key, val, rec, &arr);
+ berr = match(ntdb, h->h, &key, val, rec, rptr, &arr);
if (berr < 0) {
ecode = NTDB_OFF_TO_ERR(berr);
if (arr) {
diff --git a/lib/ntdb/ntdb.c b/lib/ntdb/ntdb.c
index 8d50ba60c1..ddbf7d6e9e 100644
--- a/lib/ntdb/ntdb.c
+++ b/lib/ntdb/ntdb.c
@@ -114,7 +114,7 @@ _PUBLIC_ enum NTDB_ERROR ntdb_store(struct ntdb_context *ntdb,
struct ntdb_used_record rec;
enum NTDB_ERROR ecode;
- off = find_and_lock(ntdb, key, F_WRLCK, &h, &rec);
+ off = find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL);
if (NTDB_OFF_IS_ERR(off)) {
return NTDB_OFF_TO_ERR(off);
}
@@ -176,7 +176,7 @@ _PUBLIC_ enum NTDB_ERROR ntdb_append(struct ntdb_context *ntdb,
NTDB_DATA new_dbuf;
enum NTDB_ERROR ecode;
- off = find_and_lock(ntdb, key, F_WRLCK, &h, &rec);
+ off = find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL);
if (NTDB_OFF_IS_ERR(off)) {
return NTDB_OFF_TO_ERR(off);
}
@@ -240,8 +240,9 @@ _PUBLIC_ enum NTDB_ERROR ntdb_fetch(struct ntdb_context *ntdb, NTDB_DATA key,
struct ntdb_used_record rec;
struct hash_info h;
enum NTDB_ERROR ecode;
+ const char *keyp;
- off = find_and_lock(ntdb, key, F_RDLCK, &h, &rec);
+ off = find_and_lock(ntdb, key, F_RDLCK, &h, &rec, &keyp);
if (NTDB_OFF_IS_ERR(off)) {
return NTDB_OFF_TO_ERR(off);
}
@@ -250,12 +251,14 @@ _PUBLIC_ enum NTDB_ERROR ntdb_fetch(struct ntdb_context *ntdb, NTDB_DATA key,
ecode = NTDB_ERR_NOEXIST;
} else {
data->dsize = rec_data_length(&rec);
- data->dptr = ntdb_alloc_read(ntdb, off + sizeof(rec) + key.dsize,
- data->dsize);
- if (NTDB_PTR_IS_ERR(data->dptr)) {
- ecode = NTDB_PTR_ERR(data->dptr);
- } else
+ data->dptr = ntdb->alloc_fn(ntdb, data->dsize, ntdb->alloc_data);
+ if (unlikely(!data->dptr)) {
+ ecode = NTDB_ERR_OOM;
+ } else {
+ memcpy(data->dptr, keyp + key.dsize, data->dsize);
ecode = NTDB_SUCCESS;
+ }
+ ntdb_access_release(ntdb, keyp);
}
ntdb_unlock_hash(ntdb, h.h, F_RDLCK);
@@ -268,7 +271,7 @@ _PUBLIC_ bool ntdb_exists(struct ntdb_context *ntdb, NTDB_DATA key)
struct ntdb_used_record rec;
struct hash_info h;
- off = find_and_lock(ntdb, key, F_RDLCK, &h, &rec);
+ off = find_and_lock(ntdb, key, F_RDLCK, &h, &rec, NULL);
if (NTDB_OFF_IS_ERR(off)) {
return false;
}
@@ -284,7 +287,7 @@ _PUBLIC_ enum NTDB_ERROR ntdb_delete(struct ntdb_context *ntdb, NTDB_DATA key)
struct hash_info h;
enum NTDB_ERROR ecode;
- off = find_and_lock(ntdb, key, F_WRLCK, &h, &rec);
+ off = find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL);
if (NTDB_OFF_IS_ERR(off)) {
return NTDB_OFF_TO_ERR(off);
}
@@ -481,8 +484,9 @@ _PUBLIC_ enum NTDB_ERROR ntdb_parse_record_(struct ntdb_context *ntdb,
struct ntdb_used_record rec;
struct hash_info h;
enum NTDB_ERROR ecode;
+ const char *keyp;
- off = find_and_lock(ntdb, key, F_RDLCK, &h, &rec);
+ off = find_and_lock(ntdb, key, F_RDLCK, &h, &rec, &keyp);
if (NTDB_OFF_IS_ERR(off)) {
return NTDB_OFF_TO_ERR(off);
}
@@ -490,17 +494,11 @@ _PUBLIC_ enum NTDB_ERROR ntdb_parse_record_(struct ntdb_context *ntdb,
if (!off) {
ecode = NTDB_ERR_NOEXIST;
} else {
- const void *dptr;
- dptr = ntdb_access_read(ntdb, off + sizeof(rec) + key.dsize,
- rec_data_length(&rec), false);
- if (NTDB_PTR_IS_ERR(dptr)) {
- ecode = NTDB_PTR_ERR(dptr);
- } else {
- NTDB_DATA d = ntdb_mkdata(dptr, rec_data_length(&rec));
+ NTDB_DATA d = ntdb_mkdata(keyp + key.dsize,
+ rec_data_length(&rec));
- ecode = parse(key, d, data);
- ntdb_access_release(ntdb, dptr);
- }
+ ecode = parse(key, d, data);
+ ntdb_access_release(ntdb, keyp);
}
ntdb_unlock_hash(ntdb, h.h, F_RDLCK);
diff --git a/lib/ntdb/private.h b/lib/ntdb/private.h
index 957d85e494..90b782d303 100644
--- a/lib/ntdb/private.h
+++ b/lib/ntdb/private.h
@@ -369,7 +369,8 @@ ntdb_off_t find_and_lock(struct ntdb_context *ntdb,
NTDB_DATA key,
int ltype,
struct hash_info *h,
- struct ntdb_used_record *rec);
+ struct ntdb_used_record *rec,
+ const char **rkey);
enum NTDB_ERROR replace_in_hash(struct ntdb_context *ntdb,
const struct hash_info *h,
diff --git a/lib/ntdb/test/run-04-basichash.c b/lib/ntdb/test/run-04-basichash.c
index 41b49239cb..264932b988 100644
--- a/lib/ntdb/test/run-04-basichash.c
+++ b/lib/ntdb/test/run-04-basichash.c
@@ -38,7 +38,7 @@ int main(int argc, char *argv[])
v = 0;
/* Should not find it. */
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == 0);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have located space in top table, bucket 0. */
@@ -75,7 +75,7 @@ int main(int argc, char *argv[])
ok1(ntdb_check(ntdb, NULL, NULL) == 0);
/* Now, this should give a successful lookup. */
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == new_off);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == new_off);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have located it in top table, bucket 0. */
@@ -97,7 +97,7 @@ int main(int argc, char *argv[])
/* Test expansion. */
v = 1;
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == 0);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have located clash in toplevel bucket 0. */
@@ -131,7 +131,7 @@ int main(int argc, char *argv[])
/* Should be able to find both. */
v = 1;
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == new_off2);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == new_off2);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have located space in chain. */
@@ -146,7 +146,7 @@ int main(int argc, char *argv[])
ok1(ntdb_unlock_hash(ntdb, h.h, F_WRLCK) == 0);
v = 0;
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == new_off);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == new_off);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have located space in chain. */
@@ -174,7 +174,7 @@ int main(int argc, char *argv[])
/* Should still be able to find other record. */
v = 1;
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == new_off2);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == new_off2);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have located space in chain. */
@@ -190,7 +190,7 @@ int main(int argc, char *argv[])
/* Now should find empty space. */
v = 0;
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == 0);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have located space in chain, bucket 0. */
@@ -201,7 +201,7 @@ int main(int argc, char *argv[])
/* Adding another record should work. */
v = 2;
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == 0);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have located space in chain, bucket 0. */
@@ -229,7 +229,7 @@ int main(int argc, char *argv[])
/* Adding another record should cause expansion. */
v = 3;
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == 0);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should not have located space in chain. */
@@ -255,7 +255,7 @@ int main(int argc, char *argv[])
ok1(ntdb_unlock_hash(ntdb, h.h, F_WRLCK) == 0);
/* Retrieve it and check. */
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == new_off);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == new_off);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have appended to chain, bucket 2. */
@@ -272,7 +272,7 @@ int main(int argc, char *argv[])
/* YA record: relocation. */
v = 4;
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == 0);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should not have located space in chain. */
@@ -298,7 +298,7 @@ int main(int argc, char *argv[])
ok1(ntdb_unlock_hash(ntdb, h.h, F_WRLCK) == 0);
/* Retrieve it and check. */
- ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec) == new_off);
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == new_off);
/* Should have created correct hash. */
ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
/* Should have appended to chain, bucket 2. */
diff --git a/lib/ntdb/test/run-15-append.c b/lib/ntdb/test/run-15-append.c
index 97fd53c241..a797944b53 100644
--- a/lib/ntdb/test/run-15-append.c
+++ b/lib/ntdb/test/run-15-append.c
@@ -12,7 +12,7 @@ static ntdb_off_t ntdb_offset(struct ntdb_context *ntdb, NTDB_DATA key)
struct ntdb_used_record urec;
struct hash_info h;
- off = find_and_lock(ntdb, key, F_RDLCK, &h, &urec);
+ off = find_and_lock(ntdb, key, F_RDLCK, &h, &urec, NULL);
if (NTDB_OFF_IS_ERR(off))
return 0;
ntdb_unlock_hash(ntdb, h.h, F_RDLCK);
diff --git a/lib/ntdb/test/run-64-bit-tdb.c b/lib/ntdb/test/run-64-bit-tdb.c
index 5afdd8747c..582deb2234 100644
--- a/lib/ntdb/test/run-64-bit-tdb.c
+++ b/lib/ntdb/test/run-64-bit-tdb.c
@@ -67,7 +67,7 @@ int main(int argc, char *argv[])
ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
/* Make sure it put it at end as we expected. */
- off = find_and_lock(ntdb, k, F_RDLCK, &h, &rec);
+ off = find_and_lock(ntdb, k, F_RDLCK, &h, &rec, NULL);
ok1(off >= ALMOST_4G);
ntdb_unlock_hash(ntdb, h.h, F_RDLCK);
diff --git a/lib/ntdb/traverse.c b/lib/ntdb/traverse.c
index d0ce3517b0..2e6763cbdd 100644
--- a/lib/ntdb/traverse.c
+++ b/lib/ntdb/traverse.c
@@ -62,7 +62,7 @@ _PUBLIC_ enum NTDB_ERROR ntdb_nextkey(struct ntdb_context *ntdb, NTDB_DATA *key)
struct ntdb_used_record rec;
ntdb_off_t off;
- off = find_and_lock(ntdb, *key, F_RDLCK, &h, &rec);
+ off = find_and_lock(ntdb, *key, F_RDLCK, &h, &rec, NULL);
ntdb->free_fn(key->dptr, ntdb->alloc_data);
if (NTDB_OFF_IS_ERR(off)) {
return NTDB_OFF_TO_ERR(off);
ref='#n781'>781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
/*
 * 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-2000, 2002 Tim Waugh <tim@cyberelk.net>
 *
 * 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/parportx device node represents an arbitrary device
 * 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
 *   SETPHASE	set the IEEE 1284 phase of a particular mode.  Not to be
 *              confused with ioctl(fd, SETPHASER, &stun). ;-)
 *   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
 *   WCTLONIRQ	on interrupt, set control lines
 *   CLRIRQ	clear (and return) interrupt count
 *   SETTIME	sets device timeout (struct timeval)
 *   GETTIME	gets device timeout (struct timeval)
 *   GETMODES	gets hardware supported modes (unsigned int)
 *   GETMODE	gets the current IEEE1284 mode
 *   GETPHASE   gets the current IEEE1284 phase
 *   GETFLAGS   gets current (user-visible) flags
 *   SETFLAGS   sets current (user-visible) flags
 * read/write	read or write in current IEEE 1284 protocol
 * select	wait for interrupt (in readfds)
 *
 * Changes:
 * Added SETTIME/GETTIME ioctl, Fred Barnes, 1999.
 *
 * Arnaldo Carvalho de Melo <acme@conectiva.com.br> 2000/08/25
 * - On error, copy_from_user and copy_to_user do not return -EFAULT,
 *   They return the positive number of bytes *not* copied due to address
 *   space errors.
 *
 * Added GETMODES/GETMODE/GETPHASE ioctls, Fred Barnes <frmb2@ukc.ac.uk>, 03/01/2001.
 * Added GETFLAGS/SETFLAGS ioctls, Fred Barnes, 04/2001
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/ppdev.h>
#include <linux/smp_lock.h>
#include <linux/uaccess.h>

#define PP_VERSION "ppdev: user-space parallel port driver"
#define CHRDEV "ppdev"

struct pp_struct {
	struct pardevice * pdev;
	wait_queue_head_t irq_wait;
	atomic_t irqc;
	unsigned int flags;
	int irqresponse;
	unsigned char irqctl;
	struct ieee1284_info state;
	struct ieee1284_info saved_state;
	long default_inactivity;
};

/* 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 1024
#define PARDEVICE_MAX 8

/* ROUND_UP macro from fs/select.c */
#define ROUND_UP(x,y) (((x)+(y)-1)/(y))

static inline void pp_enable_irq (struct pp_struct *pp)
{
	struct parport *port = pp->pdev->port;
	port->ops->enable_irq (port);
}

static ssize_t pp_read (struct file * file, char __user * buf, size_t count,
			loff_t * ppos)
{
	unsigned int minor = iminor(file->f_path.dentry->d_inode);
	struct pp_struct *pp = file->private_data;
	char * kbuffer;
	ssize_t bytes_read = 0;
	struct parport *pport;
	int mode;

	if (!(pp->flags & PP_CLAIMED)) {
		/* Don't have the port claimed */
		pr_debug(CHRDEV "%x: claim the port first\n", minor);
		return -EINVAL;
	}

	/* Trivial case. */
	if (count == 0)
		return 0;

	kbuffer = kmalloc(min_t(size_t, count, PP_BUFFER_SIZE), GFP_KERNEL);
	if (!kbuffer) {
		return -ENOMEM;
	}
	pport = pp->pdev->port;
	mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);

	parport_set_timeout (pp->pdev,
			     (file->f_flags & O_NONBLOCK) ?
			     PARPORT_INACTIVITY_O_NONBLOCK :
			     pp->default_inactivity);

	while (bytes_read == 0) {
		ssize_t need = min_t(unsigned long, count, PP_BUFFER_SIZE);

		if (mode == IEEE1284_MODE_EPP) {
			/* various specials for EPP mode */
			int flags = 0;
			size_t (*fn)(struct parport *, void *, size_t, int);

			if (pp->flags & PP_W91284PIC) {
				flags |= PARPORT_W91284PIC;
			}
			if (pp->flags & PP_FASTREAD) {
				flags |= PARPORT_EPP_FAST;
			}
			if (pport->ieee1284.mode & IEEE1284_ADDR) {
				fn = pport->ops->epp_read_addr;
			} else {
				fn = pport->ops->epp_read_data;
			}
			bytes_read = (*fn)(pport, kbuffer, need, flags);
		} else {
			bytes_read = parport_read (pport, kbuffer, need);
		}

		if (bytes_read != 0)
			break;

		if (file->f_flags & O_NONBLOCK) {
			bytes_read = -EAGAIN;
			break;
		}

		if (signal_pending (current)) {
			bytes_read = -ERESTARTSYS;
			break;
		}

		cond_resched();
	}

	parport_set_timeout (pp->pdev, pp->default_inactivity);

	if (bytes_read > 0 && copy_to_user (buf, kbuffer, bytes_read))
		bytes_read = -EFAULT;

	kfree (kbuffer);
	pp_enable_irq (pp);
	return bytes_read;
}

static ssize_t pp_write (struct file * file, const char __user * buf,
			 size_t count, loff_t * ppos)
{
	unsigned int minor = iminor(file->f_path.dentry->d_inode);
	struct pp_struct *pp = file->private_data;
	char * kbuffer;
	ssize_t bytes_written = 0;
	ssize_t wrote;
	int mode;
	struct parport *pport;

	if (!(pp->flags & PP_CLAIMED)) {
		/* Don't have the port claimed */
		pr_debug(CHRDEV "%x: claim the port first\n", minor);
		return -EINVAL;
	}

	kbuffer = kmalloc(min_t(size_t, count, PP_BUFFER_SIZE), GFP_KERNEL);
	if (!kbuffer) {
		return -ENOMEM;
	}
	pport = pp->pdev->port;
	mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);

	parport_set_timeout (pp->pdev,
			     (file->f_flags & O_NONBLOCK) ?
			     PARPORT_INACTIVITY_O_NONBLOCK :
			     pp->default_inactivity);

	while (bytes_written < count) {
		ssize_t n = min_t(unsigned long, count - bytes_written, PP_BUFFER_SIZE);

		if (copy_from_user (kbuffer, buf + bytes_written, n)) {
			bytes_written = -EFAULT;
			break;
		}

		if ((pp->flags & PP_FASTWRITE) && (mode == IEEE1284_MODE_EPP)) {
			/* do a fast EPP write */
			if (pport->ieee1284.mode & IEEE1284_ADDR) {
				wrote = pport->ops->epp_write_addr (pport,
					kbuffer, n, PARPORT_EPP_FAST);
			} else {
				wrote = pport->ops->epp_write_data (pport,
					kbuffer, n, PARPORT_EPP_FAST);
			}
		} else {
			wrote = parport_write (pp->pdev->port, kbuffer, n);
		}

		if (wrote <= 0) {
			if (!bytes_written) {
				bytes_written = wrote;
			}
			break;
		}

		bytes_written += wrote;

		if (file->f_flags & O_NONBLOCK) {
			if (!bytes_written)
				bytes_written = -EAGAIN;
			break;
		}

		if (signal_pending (current)) {
			if (!bytes_written) {
				bytes_written = -EINTR;
			}
			break;
		}

		cond_resched();
	}

	parport_set_timeout (pp->pdev, pp->default_inactivity);

	kfree (kbuffer);
	pp_enable_irq (pp);
	return bytes_written;
}

static void pp_irq (void *private)
{
	struct pp_struct *pp = private;

	if (pp->irqresponse) {
		parport_write_control (pp->pdev->port, pp->irqctl);
		pp->irqresponse = 0;
	}

	atomic_inc (&pp->irqc);
	wake_up_interruptible (&pp->irq_wait);
}

static int register_device (int minor, struct pp_struct *pp)
{
	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 "%x", minor);

	port = parport_find_number (minor);
	if (!port) {
		printk (KERN_WARNING "%s: no associated port!\n", name);
		kfree (name);
		return -ENXIO;
	}

	fl = (pp->flags & PP_EXCL) ? PARPORT_FLAG_EXCL : 0;
	pdev = parport_register_device (port, name, NULL,
					NULL, pp_irq, fl, pp);
	parport_put_port (port);

	if (!pdev) {
		printk (KERN_WARNING "%s: failed to register device!\n", name);
		kfree (name);
		return -ENXIO;
	}

	pp->pdev = pdev;
	pr_debug("%s: registered pardevice\n", name);
	return 0;
}

static enum ieee1284_phase init_phase (int mode)
{
	switch (mode & ~(IEEE1284_DEVICEID
			 | IEEE1284_ADDR)) {
	case IEEE1284_MODE_NIBBLE:
	case IEEE1284_MODE_BYTE:
		return IEEE1284_PH_REV_IDLE;
	}
	return IEEE1284_PH_FWD_IDLE;
}

static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	unsigned int minor = iminor(file->f_path.dentry->d_inode);
	struct pp_struct *pp = file->private_data;
	struct parport * port;
	void __user *argp = (void __user *)arg;

	/* First handle the cases that don't take arguments. */
	switch (cmd) {
	case PPCLAIM:
	    {
		struct ieee1284_info *info;
		int ret;

		if (pp->flags & PP_CLAIMED) {
			pr_debug(CHRDEV "%x: you've already got it!\n", minor);
			return -EINVAL;
		}

		/* Deferred device registration. */
		if (!pp->pdev) {
			int err = register_device (minor, pp);
			if (err) {
				return err;
			}
		}

		ret = parport_claim_or_block (pp->pdev);
		if (ret < 0)
			return ret;

		pp->flags |= PP_CLAIMED;

		/* For interrupt-reporting to work, we need to be
		 * informed of each interrupt. */
		pp_enable_irq (pp);

		/* We may need to fix up the state machine. */
		info = &pp->pdev->port->ieee1284;
		pp->saved_state.mode = info->mode;
		pp->saved_state.phase = info->phase;
		info->mode = pp->state.mode;
		info->phase = pp->state.phase;
		pp->default_inactivity = parport_set_timeout (pp->pdev, 0);
		parport_set_timeout (pp->pdev, pp->default_inactivity);

		return 0;
	    }
	case PPEXCL:
		if (pp->pdev) {
			pr_debug(CHRDEV "%x: too late for PPEXCL; "
				"already registered\n", minor);
			if (pp->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->flags |= PP_EXCL;
		return 0;
	case PPSETMODE:
	    {
		int mode;
		if (copy_from_user (&mode, argp, sizeof (mode)))
			return -EFAULT;
		/* FIXME: validate mode */
		pp->state.mode = mode;
		pp->state.phase = init_phase (mode);

		if (pp->flags & PP_CLAIMED) {
			pp->pdev->port->ieee1284.mode = mode;
			pp->pdev->port->ieee1284.phase = pp->state.phase;
		}

		return 0;
	    }
	case PPGETMODE:
	    {
		int mode;

		if (pp->flags & PP_CLAIMED) {
			mode = pp->pdev->port->ieee1284.mode;
		} else {
			mode = pp->state.mode;
		}
		if (copy_to_user (argp, &mode, sizeof (mode))) {
			return -EFAULT;
		}
		return 0;
	    }
	case PPSETPHASE:
	    {
		int phase;
		if (copy_from_user (&phase, argp, sizeof (phase))) {
			return -EFAULT;
		}
		/* FIXME: validate phase */
		pp->state.phase = phase;

		if (pp->flags & PP_CLAIMED) {
			pp->pdev->port->ieee1284.phase = phase;
		}

		return 0;
	    }
	case PPGETPHASE:
	    {
		int phase;

		if (pp->flags & PP_CLAIMED) {
			phase = pp->pdev->port->ieee1284.phase;
		} else {
			phase = pp->state.phase;
		}
		if (copy_to_user (argp, &phase, sizeof (phase))) {
			return -EFAULT;
		}
		return 0;
	    }
	case PPGETMODES:
	    {
		unsigned int modes;

		port = parport_find_number (minor);
		if (!port)
			return -ENODEV;

		modes = port->modes;
		if (copy_to_user (argp, &modes, sizeof (modes))) {
			return -EFAULT;
		}
		return 0;
	    }
	case PPSETFLAGS:
	    {
		int uflags;

		if (copy_from_user (&uflags, argp, sizeof (uflags))) {
			return -EFAULT;
		}
		pp->flags &= ~PP_FLAGMASK;
		pp->flags |= (uflags & PP_FLAGMASK);
		return 0;
	    }
	case PPGETFLAGS:
	    {
		int uflags;

		uflags = pp->flags & PP_FLAGMASK;
		if (copy_to_user (argp, &uflags, sizeof (uflags))) {
			return -EFAULT;
		}
		return 0;
	    }
	}	/* end switch() */

	/* Everything else requires the port to be claimed, so check
	 * that now. */
	if ((pp->flags & PP_CLAIMED) == 0) {
		pr_debug(CHRDEV "%x: claim the port first\n", minor);
		return -EINVAL;
	}

	port = pp->pdev->port;
	switch (cmd) {
		struct ieee1284_info *info;
		unsigned char reg;
		unsigned char mask;
		int mode;
		int ret;
		struct timeval par_timeout;
		long to_jiffies;

	case PPRSTATUS:
		reg = parport_read_status (port);
		if (copy_to_user (argp, &reg, sizeof (reg)))
			return -EFAULT;
		return 0;
	case PPRDATA:
		reg = parport_read_data (port);
		if (copy_to_user (argp, &reg, sizeof (reg)))
			return -EFAULT;
		return 0;
	case PPRCONTROL:
		reg = parport_read_control (port);
		if (copy_to_user (argp, &reg, sizeof (reg)))
			return -EFAULT;
		return 0;
	case PPYIELD:
		parport_yield_blocking (pp->pdev);
		return 0;

	case PPRELEASE:
		/* Save the state machine's state. */
		info = &pp->pdev->port->ieee1284;
		pp->state.mode = info->mode;
		pp->state.phase = info->phase;
		info->mode = pp->saved_state.mode;
		info->phase = pp->saved_state.phase;
		parport_release (pp->pdev);
		pp->flags &= ~PP_CLAIMED;
		return 0;

	case PPWCONTROL:
		if (copy_from_user (&reg, argp, sizeof (reg)))
			return -EFAULT;
		parport_write_control (port, reg);
		return 0;

	case PPWDATA:
		if (copy_from_user (&reg, argp, sizeof (reg)))
			return -EFAULT;
		parport_write_data (port, reg);
		return 0;

	case PPFCONTROL:
		if (copy_from_user (&mask, argp,
				    sizeof (mask)))
			return -EFAULT;
		if (copy_from_user (&reg, 1 + (unsigned char __user *) arg,
				    sizeof (reg)))
			return -EFAULT;
		parport_frob_control (port, mask, reg);
		return 0;

	case PPDATADIR:
		if (copy_from_user (&mode, argp, 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, argp, sizeof (mode)))
			return -EFAULT;
		switch ((ret = parport_negotiate (port, mode))) {
		case 0: break;
		case -1: /* handshake failed, peripheral not IEEE 1284 */
			ret = -EIO;
			break;
		case 1:  /* handshake succeeded, peripheral rejected mode */
			ret = -ENXIO;
			break;
		}
		pp_enable_irq (pp);
		return ret;

	case PPWCTLONIRQ:
		if (copy_from_user (&reg, argp, sizeof (reg)))
			return -EFAULT;

		/* Remember what to set the control lines to, for next
		 * time we get an interrupt. */
		pp->irqctl = reg;
		pp->irqresponse = 1;
		return 0;

	case PPCLRIRQ:
		ret = atomic_read (&pp->irqc);
		if (copy_to_user (argp, &ret, sizeof (ret)))
			return -EFAULT;
		atomic_sub (ret, &pp->irqc);
		return 0;

	case PPSETTIME:
		if (copy_from_user (&par_timeout, argp, sizeof(struct timeval))) {
			return -EFAULT;
		}
		/* Convert to jiffies, place in pp->pdev->timeout */
		if ((par_timeout.tv_sec < 0) || (par_timeout.tv_usec < 0)) {
			return -EINVAL;
		}
		to_jiffies = ROUND_UP(par_timeout.tv_usec, 1000000/HZ);
		to_jiffies += par_timeout.tv_sec * (long)HZ;
		if (to_jiffies <= 0) {
			return -EINVAL;
		}
		pp->pdev->timeout = to_jiffies;
		return 0;

	case PPGETTIME:
		to_jiffies = pp->pdev->timeout;
		par_timeout.tv_sec = to_jiffies / HZ;
		par_timeout.tv_usec = (to_jiffies % (long)HZ) * (1000000/HZ);
		if (copy_to_user (argp, &par_timeout, sizeof(struct timeval)))
			return -EFAULT;
		return 0;

	default:
		pr_debug(CHRDEV "%x: What? (cmd=0x%x)\n", minor, cmd);
		return -EINVAL;
	}

	/* Keep the compiler happy */
	return 0;
}

static long pp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	long ret;
	lock_kernel();
	ret = pp_do_ioctl(file, cmd, arg);
	unlock_kernel();
	return ret;
}

static int pp_open (struct inode * inode, struct file * file)
{
	unsigned int minor = iminor(inode);
	struct pp_struct *pp;

	cycle_kernel_lock();
	if (minor >= PARPORT_MAX)
		return -ENXIO;

	pp = kmalloc (sizeof (struct pp_struct), GFP_KERNEL);
	if (!pp)
		return -ENOMEM;

	pp->state.mode = IEEE1284_MODE_COMPAT;
	pp->state.phase = init_phase (pp->state.mode);
	pp->flags = 0;
	pp->irqresponse = 0;
	atomic_set (&pp->irqc, 0);
	init_waitqueue_head (&pp->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->pdev = NULL;
	file->private_data = pp;

	return 0;
}

static int pp_release (struct inode * inode, struct file * file)
{
	unsigned int minor = iminor(inode);
	struct pp_struct *pp = file->private_data;
	int compat_negot;

	compat_negot = 0;
	if (!(pp->flags & PP_CLAIMED) && pp->pdev &&
	    (pp->state.mode != IEEE1284_MODE_COMPAT)) {
	    	struct ieee1284_info *info;

		/* parport released, but not in compatibility mode */
		parport_claim_or_block (pp->pdev);
		pp->flags |= PP_CLAIMED;
		info = &pp->pdev->port->ieee1284;
		pp->saved_state.mode = info->mode;
		pp->saved_state.phase = info->phase;
		info->mode = pp->state.mode;
		info->phase = pp->state.phase;
		compat_negot = 1;
	} else if ((pp->flags & PP_CLAIMED) && pp->pdev &&
	    (pp->pdev->port->ieee1284.mode != IEEE1284_MODE_COMPAT)) {
		compat_negot = 2;
	}
	if (compat_negot) {
		parport_negotiate (pp->pdev->port, IEEE1284_MODE_COMPAT);
		pr_debug(CHRDEV "%x: negotiated back to compatibility "
			"mode because user-space forgot\n", minor);
	}

	if (pp->flags & PP_CLAIMED) {
		struct ieee1284_info *info;

		info = &pp->pdev->port->ieee1284;
		pp->state.mode = info->mode;
		pp->state.phase = info->phase;
		info->mode = pp->saved_state.mode;
		info->phase = pp->saved_state.phase;
		parport_release (pp->pdev);
		if (compat_negot != 1) {
			pr_debug(CHRDEV "%x: released pardevice "
				"because user-space forgot\n", minor);
		}
	}

	if (pp->pdev) {
		const char *name = pp->pdev->name;
		parport_unregister_device (pp->pdev);
		kfree (name);
		pp->pdev = NULL;
		pr_debug(CHRDEV "%x: unregistered pardevice\n", minor);
	}

	kfree (pp);

	return 0;
}

/* No kernel lock held - fine */
static unsigned int pp_poll (struct file * file, poll_table * wait)
{
	struct pp_struct *pp = file->private_data;
	unsigned int mask = 0;

	poll_wait (file, &pp->irq_wait, wait);
	if (atomic_read (&pp->irqc))
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

static struct class *ppdev_class;

static const struct file_operations pp_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= pp_read,
	.write		= pp_write,
	.poll		= pp_poll,
	.unlocked_ioctl	= pp_ioctl,
	.open		= pp_open,
	.release	= pp_release,
};

static void pp_attach(struct parport *port)
{
	device_create(ppdev_class, port->dev, MKDEV(PP_MAJOR, port->number),
		      NULL, "parport%d", port->number);
}

static void pp_detach(struct parport *port)
{
	device_destroy(ppdev_class, MKDEV(PP_MAJOR, port->number));
}

static struct parport_driver pp_driver = {
	.name		= CHRDEV,
	.attach		= pp_attach,
	.detach		= pp_detach,
};

static int __init ppdev_init (void)
{
	int err = 0;

	if (register_chrdev (PP_MAJOR, CHRDEV, &pp_fops)) {
		printk (KERN_WARNING CHRDEV ": unable to get major %d\n",
			PP_MAJOR);
		return -EIO;
	}
	ppdev_class = class_create(THIS_MODULE, CHRDEV);
	if (IS_ERR(ppdev_class)) {
		err = PTR_ERR(ppdev_class);
		goto out_chrdev;
	}
	if (parport_register_driver(&pp_driver)) {
		printk (KERN_WARNING CHRDEV ": unable to register with parport\n");
		goto out_class;
	}

	printk (KERN_INFO PP_VERSION "\n");
	goto out;

out_class:
	class_destroy(ppdev_class);
out_chrdev:
	unregister_chrdev(PP_MAJOR, CHRDEV);
out:
	return err;
}

static void __exit ppdev_cleanup (void)
{
	/* Clean up all parport stuff */
	parport_unregister_driver(&pp_driver);
	class_destroy(ppdev_class);
	unregister_chrdev (PP_MAJOR, CHRDEV);
}

module_init(ppdev_init);
module_exit(ppdev_cleanup);

MODULE_LICENSE("GPL");
MODULE_ALIAS_CHARDEV_MAJOR(PP_MAJOR);