Subject: Asynchronous crypto layer. Reincarnation #1.

From: Evgeniy Polyakov <[email-masked]>
Date: Tue, 14 Dec 2004 19:03:56 +0300

Changed locking schema, coding style, symbol exporting.
HIFN driver is ready and is waiting the latest test.

It works several hours on a 4-way system without any problem
(test crypto_provider and crypto consumer are also in patch).

BTW, little note:
System crypts with 128 bits aes in ecb mode, since only one CPU in a
time performs
crypto operations(crypto_provider is a wrapper over synchronous crypto
layer)
numbers are not so exciting: about 250-350 sessions per second, each one
works
with 32 bytes block. Test crypto consumer injects new session each 3
milliseconds.
An interesting note about linux work_queues: when I inject new
crypto_session each 2
msecs - work_queue's threads for 2 of 4 CPUs are loaded about the same
3-10% of
the CPU time, but when I inject sessions each 3 or 1 msec - only one
queue's thread
gets all sessions callbacks and takes about 100% of the CPU time.
With 2msec delay I get about 500 sessions per second, with 1msec -
little more than 1000.

pcix$ cat /sys/class/acrypto/crypto_provider/scompleted && sleep 1
&&cat /sys/class/acrypto/crypto_provider/scompleted
7044717
7045728

Please test, review and comment.

Signed-off-by: Evgeniy Polyakov <[email-masked]>

-- 
        Evgeniy Polyakov

Crash is better than data corruption -- Arthur Grabowski
diff -Nru /tmp/empty/acrypto.h linux-2.6/drivers/acrypto/acrypto.h
--- /tmp/empty/acrypto.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/acrypto.h	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,226 @@
+/*
+ * 	acrypto.h
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __ACRYPTO_H
+#define __ACRYPTO_H
+
+#define SCACHE_NAMELEN		32
+
+struct crypto_session_initializer;
+struct crypto_data;
+typedef void (*crypto_callback_t) (struct crypto_session_initializer *,
+				   struct crypto_data *);
+
+extern void crypto_wake_lb(void);
+
+#define SESSION_COMPLETED	(1<<15)
+#define SESSION_FINISHED	(1<<14)
+#define SESSION_STARTED		(1<<13)
+#define SESSION_PROCESSED	(1<<12)
+#define SESSION_BINDED		(1<<11)
+#define SESSION_BROKEN		(1<<10)
+
+#define session_completed(s)	(s->ci.flags & SESSION_COMPLETED)
+#define complete_session(s)	do {s->ci.flags |= SESSION_COMPLETED;} while(0)
+#define uncomplete_session(s)	do {s->ci.flags &= ~SESSION_COMPLETED;} while (0)
+
+#define session_finished(s)	(s->ci.flags & SESSION_FINISHED)
+#define finish_session(s)	do {s->ci.flags |= SESSION_FINISHED;} while(0)
+#define unfinish_session(s)	do {s->ci.flags &= ~SESSION_FINISHED;} while (0)
+
+#define session_started(s)	(s->ci.flags & SESSION_STARTED)
+#define start_session(s)	do {s->ci.flags |= SESSION_STARTED;} while(0)
+#define unstart_session(s)	do {s->ci.flags &= ~SESSION_STARTED;} while (0)
+
+#define session_is_processed(s)		(s->ci.flags & SESSION_PROCESSED)
+#define start_process_session(s)	do {s->ci.flags |= SESSION_PROCESSED; s->ci.ptime = jiffies;} while(0)
+#define stop_process_session(s)		do {s->ci.flags &= ~SESSION_PROCESSED; s->ci.ptime = jiffies - s->ci.ptime; crypto_wake_lb();} while (0)
+
+#define session_binded(s)	(s->ci.flags & SESSION_BINDED)
+#define bind_session(s)		do {s->ci.flags |= SESSION_BINDED;} while(0)
+#define unbind_session(s)	do {s->ci.flags &= ~SESSION_BINDED;} while (0)
+#define sci_binded(ci)		(ci->flags & SESSION_BINDED)
+
+#define session_broken(s)	(s->ci.flags & SESSION_BROKEN)
+#define broke_session(s)	do {s->ci.flags |= SESSION_BROKEN;} while(0)
+#define unbroke_session(s)	do {s->ci.flags &= ~SESSION_BROKEN;} while (0)
+
+struct crypto_device_stat {
+	__u64 scompleted;
+	__u64 sfinished;
+	__u64 sstarted;
+	__u64 kmem_failed;
+};
+
+#ifdef __KERNEL__
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+
+#include <asm/scatterlist.h>
+
+#define DEBUG
+#ifdef DEBUG
+#define dprintk(f, a...) printk("%d " f, smp_processor_id(), ##a)
+#else
+#define dprintk(f, a...)
+#endif
+
+#define CRYPTO_MAX_PRIV_SIZE	1024
+
+#define DEVICE_BROKEN		(1<<0)
+
+#define device_broken(dev)	(dev->flags & DEVICE_BROKEN)
+#define broke_device(dev)	do {dev->flags |= DEVICE_BROKEN;} while(0)
+#define repair_device(dev)	do {dev->flags &= ~DEVICE_BROKEN;} while(0)
+
+struct crypto_capability {
+	u16 			operation;
+	u16 			type;
+	u16 			mode;
+	u16 			qlen;
+	u64 			ptime;
+	u64 			scomp;
+};
+
+struct crypto_session_initializer {
+	u16 			operation;
+	u16 			type;
+	u16 			mode;
+	u16 			priority;
+
+	u64 			id;
+	u64 			dev_id;
+
+	u32 			flags;
+
+	u32 			bdev;
+
+	u64 			ptime;
+
+	crypto_callback_t 	callback;
+};
+
+struct crypto_data {
+	int 			sg_src_num;
+	struct			scatterlist sg_src;
+	struct			scatterlist sg_dst;
+	struct			scatterlist sg_key;
+	struct			scatterlist sg_iv;
+
+	void 			*priv;
+	unsigned int 		priv_size;
+};
+
+struct crypto_device {
+	char 			name[SCACHE_NAMELEN];
+
+	spinlock_t 		session_lock;
+	struct list_head 	session_list;
+
+	u64 			sid;
+	spinlock_t 		lock;
+
+	atomic_t 		refcnt;
+
+	u32 			flags;
+
+	u32 			id;
+
+	struct list_head 	cdev_entry;
+
+	void 			(*data_ready)(struct crypto_device *);
+
+	struct device_driver 	*driver;
+	struct device 		device;
+	struct class_device 	class_device;
+	struct completion 	dev_released;
+
+	spinlock_t 		stat_lock;
+	struct crypto_device_stat stat;
+
+	struct crypto_capability *cap;
+	int 			cap_number;
+
+	void 			*priv;
+};
+
+struct crypto_route_head {
+	struct crypto_route 	*next;
+	struct crypto_route 	*prev;
+
+	__u32 			qlen;
+	spinlock_t 		lock;
+};
+
+struct crypto_route {
+	struct crypto_route 	*next;
+	struct crypto_route 	*prev;
+
+	struct crypto_route_head *list;
+	struct crypto_device 	*dev;
+
+	struct crypto_session_initializer ci;
+};
+
+struct crypto_session {
+	struct list_head 	dev_queue_entry;
+	struct list_head	main_queue_entry;
+
+	struct crypto_session_initializer ci;
+
+	struct crypto_data 	data;
+
+	spinlock_t 		lock;
+
+	struct work_struct 	work;
+
+	struct crypto_route_head route_list;
+};
+
+struct crypto_session *crypto_session_alloc(struct crypto_session_initializer *, struct crypto_data *);
+struct crypto_session *crypto_session_create(struct crypto_session_initializer *, struct crypto_data *);
+void crypto_session_add(struct crypto_session *);
+void crypto_session_dequeue_main(struct crypto_session *);
+void __crypto_session_dequeue_main(struct crypto_session *);
+void __crypto_session_dequeue_route(struct crypto_session *);
+void crypto_session_dequeue_route(struct crypto_session *);
+
+void crypto_device_get(struct crypto_device *);
+void crypto_device_put(struct crypto_device *);
+struct crypto_device *crypto_device_get_name(char *);
+
+int __crypto_device_add(struct crypto_device *);
+int crypto_device_add(struct crypto_device *);
+void __crypto_device_remove(struct crypto_device *);
+void crypto_device_remove(struct crypto_device *);
+int match_initializer(struct crypto_device *, struct crypto_session_initializer *);
+int __match_initializer(struct crypto_capability *, struct crypto_session_initializer *);
+
+void crypto_session_insert_main(struct crypto_device *dev, struct crypto_session *s);
+void crypto_session_insert(struct crypto_device *dev, struct crypto_session *s);
+
+#endif				/* __KERNEL__ */
+#endif				/* __ACRYPTO_H */
diff -Nru /tmp/empty/.arch-ids/acrypto.h.id linux-2.6/drivers/acrypto/.arch-ids/acrypto.h.id
--- /tmp/empty/.arch-ids/acrypto.h.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/acrypto.h.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Sun Oct  3 20:33:25 2004 2957.0
diff -Nru /tmp/empty/.arch-ids/consumer.c.id linux-2.6/drivers/acrypto/.arch-ids/consumer.c.id
--- /tmp/empty/.arch-ids/consumer.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/consumer.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Wed Oct  6 02:34:04 2004 4902.0
diff -Nru /tmp/empty/.arch-ids/crypto_conn.c.id linux-2.6/drivers/acrypto/.arch-ids/crypto_conn.c.id
--- /tmp/empty/.arch-ids/crypto_conn.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_conn.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Wed Oct  6 03:19:44 2004 5558.0
diff -Nru /tmp/empty/.arch-ids/crypto_conn.h.id linux-2.6/drivers/acrypto/.arch-ids/crypto_conn.h.id
--- /tmp/empty/.arch-ids/crypto_conn.h.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_conn.h.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Wed Oct  6 03:19:44 2004 5558.1
diff -Nru /tmp/empty/.arch-ids/crypto_def.h.id linux-2.6/drivers/acrypto/.arch-ids/crypto_def.h.id
--- /tmp/empty/.arch-ids/crypto_def.h.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_def.h.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Tue Oct 26 20:26:53 2004 2903.0
diff -Nru /tmp/empty/.arch-ids/crypto_dev.c.id linux-2.6/drivers/acrypto/.arch-ids/crypto_dev.c.id
--- /tmp/empty/.arch-ids/crypto_dev.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_dev.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Sun Oct  3 22:27:13 2004 4211.0
diff -Nru /tmp/empty/.arch-ids/crypto_lb.c.id linux-2.6/drivers/acrypto/.arch-ids/crypto_lb.c.id
--- /tmp/empty/.arch-ids/crypto_lb.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_lb.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Sun Oct  3 22:27:09 2004 4210.0
diff -Nru /tmp/empty/.arch-ids/crypto_lb.h.id linux-2.6/drivers/acrypto/.arch-ids/crypto_lb.h.id
--- /tmp/empty/.arch-ids/crypto_lb.h.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_lb.h.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Sun Oct  3 22:27:09 2004 4210.1
diff -Nru /tmp/empty/.arch-ids/crypto_main.c.id linux-2.6/drivers/acrypto/.arch-ids/crypto_main.c.id
--- /tmp/empty/.arch-ids/crypto_main.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_main.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Sun Oct  3 20:28:05 2004 2950.1
diff -Nru /tmp/empty/.arch-ids/crypto_route.h.id linux-2.6/drivers/acrypto/.arch-ids/crypto_route.h.id
--- /tmp/empty/.arch-ids/crypto_route.h.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_route.h.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Tue Oct 12 19:25:27 2004 2518.0
diff -Nru /tmp/empty/.arch-ids/crypto_stat.c.id linux-2.6/drivers/acrypto/.arch-ids/crypto_stat.c.id
--- /tmp/empty/.arch-ids/crypto_stat.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_stat.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Thu Oct  7 23:08:48 2004 4384.0
diff -Nru /tmp/empty/.arch-ids/crypto_stat.h.id linux-2.6/drivers/acrypto/.arch-ids/crypto_stat.h.id
--- /tmp/empty/.arch-ids/crypto_stat.h.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/crypto_stat.h.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Thu Oct  7 23:08:48 2004 4384.1
diff -Nru /tmp/empty/.arch-ids/Makefile.id linux-2.6/drivers/acrypto/.arch-ids/Makefile.id
--- /tmp/empty/.arch-ids/Makefile.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/Makefile.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Sun Oct  3 20:28:05 2004 2950.0
diff -Nru /tmp/empty/.arch-ids/provider.c.id linux-2.6/drivers/acrypto/.arch-ids/provider.c.id
--- /tmp/empty/.arch-ids/provider.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/provider.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Wed Oct  6 02:34:04 2004 4902.1
diff -Nru /tmp/empty/.arch-ids/sha1_provider.c.id linux-2.6/drivers/acrypto/.arch-ids/sha1_provider.c.id
--- /tmp/empty/.arch-ids/sha1_provider.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/sha1_provider.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Wed Oct 20 20:26:53 2004 25654.0
diff -Nru /tmp/empty/.arch-ids/simple_lb.c.id linux-2.6/drivers/acrypto/.arch-ids/simple_lb.c.id
--- /tmp/empty/.arch-ids/simple_lb.c.id	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/.arch-ids/simple_lb.c.id	2004-12-14 17:50:04.000000000 +0300
@@ -0,0 +1 @@
+Evgeniy Polyakov <[email-masked]> Mon Oct  4 00:58:07 2004 4691.0
diff -Nru /tmp/empty/consumer.c linux-2.6/drivers/acrypto/consumer.c
--- /tmp/empty/consumer.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/consumer.c	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,288 @@
+/*
+ * 	consumer.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/err.h>
+#include <linux/mm.h>
+#include <linux/completion.h>
+
+#undef DEBUG
+#include "acrypto.h"
+#include "crypto_def.h"
+#include "via-padlock/padlock.h"
+
+#undef dprintk
+#define dprintk(f, a...) do {} while(0)
+
+#define KEY_LENGTH		16
+static char ckey[KEY_LENGTH];
+static int key_length = sizeof(ckey);
+
+static void ctest_callback(struct crypto_session_initializer *ci,
+			   struct crypto_data *data);
+
+static struct crypto_session_initializer ci = {
+	.operation 	= CRYPTO_OP_ENCRYPT,
+	.type 		= CRYPTO_TYPE_AES_128,
+	.mode 		= CRYPTO_MODE_ECB,
+	.priority 	= 4,
+	.callback 	= ctest_callback,
+};
+
+static struct crypto_data cdata;
+
+#define CSESSION_MAX	1000
+static struct crypto_session *s;
+static int watermark;
+//static struct completion callback_completed;
+static struct timer_list ctimer;
+
+static void ctest_callback(struct crypto_session_initializer *ci,
+			   struct crypto_data *data)
+{
+	int i, off, size, ssize;
+	struct scatterlist *sg;
+	unsigned char *ptr;
+
+	watermark--;
+
+	printk("%s: session %llu [%llu]\n", __func__, ci->id, ci->dev_id);
+	dprintk("src=%s, len=%d.\n",
+		((char *)page_address(data->sg_src.page)) + data->sg_src.offset,
+		data->sg_src.length);
+
+	sg = &data->sg_key;
+	ptr = (unsigned char *)page_address(sg->page);
+	off = sg->offset;
+	size = sg->length;
+
+	if (size > 16) {
+		dprintk("key sg is broken, size=%d.\n", size);
+		goto err_out;
+	}
+
+	dprintk("key[%d]=", size);
+	for (i = 0; i < size; ++i)
+		dprintk("0x%02x, ", ptr[i + off]);
+	dprintk("\n");
+
+	sg = &data->sg_src;
+	ptr = (unsigned char *)page_address(sg->page);
+	off = sg->offset;
+	ssize = size = sg->length;
+	if (size > 32) {
+		dprintk("src sg is broken, size=%d.\n", size);
+		goto err_out;
+	}
+
+	dprintk("src[%d]=", size);
+	for (i = 0; i < size; ++i)
+		dprintk("0x%02x, ", ptr[i + off]);
+	dprintk("\n");
+
+	sg = &data->sg_dst;
+	ptr = (unsigned char *)page_address(sg->page);
+	off = sg->offset;
+	size = sg->length;
+	if (size > 32) {
+		dprintk("dst sg is broken, size=%d.\n", size);
+		goto err_out;
+	}
+
+	if (size == 0) {
+		dprintk("size=0, setting to %d.\n", ssize);
+		size = ssize;
+	}
+
+	dprintk("dst[%d]=", size);
+	for (i = 0; i < size; ++i)
+		dprintk("0x%02x, ", ptr[i + off]);
+	dprintk("\n");
+
+err_out:
+	//complete(&callback_completed);
+	return;
+}
+
+int ctimer_func(void *data, int size, int op)
+{
+	u8 *ptr;
+
+	if (size > PAGE_SIZE)
+		size = PAGE_SIZE;
+
+	ptr = (u8 *)page_address(cdata.sg_src.page) + cdata.sg_src.offset;
+	memcpy(ptr, data, size);
+	cdata.sg_src.length = size;
+	cdata.sg_dst.length = size;
+
+	ci.operation = op;
+	s = crypto_session_alloc(&ci, &cdata);
+	if (s)
+		watermark++;
+	else
+		dprintk("allocation failed.\n");
+
+	return (s) ? 0 : -EINVAL;
+}
+
+static int alloc_sg(struct scatterlist *sg, void *data, int size)
+{
+	sg->offset = 0;
+	sg->page = alloc_pages(GFP_KERNEL, get_order(size));
+	if (!sg->page) {
+		printk(KERN_ERR "Failed to allocate page.\n");
+		return -ENOMEM;
+	}
+
+	memset(page_address(sg->page), 0, PAGE_SIZE);
+
+	if (data)
+		memcpy(page_address(sg->page), data, size);
+
+	sg->length = size;
+
+	return 0;
+}
+
+static void ctimerf(unsigned long data)
+{
+	char str[] = "test message qwerty  asdzxc\n";
+	int size = sizeof(str), err;
+
+	printk("%s started\n", __func__);
+
+	err = ctimer_func(str, size, CRYPTO_OP_ENCRYPT);
+
+	if (!err)
+		mod_timer(&ctimer, jiffies + 3);
+	else
+		mod_timer(&ctimer, jiffies + HZ);
+
+	printk("%s finished.\n", __func__);
+}
+
+int consumer_init(void)
+{
+	int err;
+
+	cdata.sg_src_num = 1;
+
+	err = alloc_sg(&cdata.sg_src, NULL, PAGE_SIZE);
+	if (err)
+		goto err_out_return;
+
+	err = alloc_sg(&cdata.sg_dst, NULL, PAGE_SIZE);
+	if (err)
+		goto err_out_src;
+
+	err = alloc_sg(&cdata.sg_key, ckey, key_length);
+	if (err)
+		goto err_out_dst;
+
+	err = alloc_sg(&cdata.sg_iv, NULL, PAGE_SIZE);
+	if (err)
+		goto err_out_key;
+
+	cdata.priv_size = sizeof(struct aes_ctx);
+
+	init_timer(&ctimer);
+	ctimer.function = &ctimerf;
+	ctimer.expires = jiffies + HZ;
+	ctimer.data = 0;
+
+	add_timer(&ctimer);
+
+	return 0;
+#if 0
+	char str[] = "test message qwerty  asdzxc\n";
+	int size;
+
+	init_completion(&callback_completed);
+
+	size = 32;
+	err = ctimer_func(str, size, CRYPTO_OP_ENCRYPT);
+	if (err)
+		goto err_out_iv;
+
+	wait_for_completion(&callback_completed);
+	init_completion(&callback_completed);
+
+	err =
+	    ctimer_func(page_address(cdata.sg_dst.page), cdata.sg_dst.length,
+			CRYPTO_OP_DECRYPT);
+	if (err)
+		goto err_out_iv;
+
+	wait_for_completion(&callback_completed);
+
+	dprintk("dst: %s\n",
+		((char *)page_address(cdata.sg_dst.page)) +
+		cdata.sg_dst.offset);
+      err_out_iv:
+#endif
+
+	__free_pages(cdata.sg_iv.page, get_order(1));
+      err_out_key:
+	__free_pages(cdata.sg_key.page, get_order(key_length));
+      err_out_dst:
+	__free_pages(cdata.sg_dst.page, get_order(1));
+      err_out_src:
+	__free_pages(cdata.sg_src.page, get_order(1));
+      err_out_return:
+	return -ENODEV;
+}
+
+void consumer_fini(void)
+{
+	del_timer_sync(&ctimer);
+	while (watermark) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(HZ);
+
+		printk(KERN_INFO "Waiting for sessions to be freed: watermark=%d.\n",
+			watermark);
+	}
+
+	if (!cdata.sg_key.length)
+		printk("BUG: key length is 0 in %s.\n", __func__);
+
+	__free_pages(cdata.sg_iv.page, get_order(1));
+	__free_pages(cdata.sg_key.page, get_order(key_length));
+	__free_pages(cdata.sg_dst.page, get_order(1));
+	__free_pages(cdata.sg_src.page, get_order(1));
+
+	printk(KERN_INFO "Test crypto module consumer is unloaded.\n");
+}
+
+module_init(consumer_init);
+module_exit(consumer_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <[email-masked]>");
+MODULE_DESCRIPTION("Test crypto module consumer.");
Binary files /tmp/empty/.consumer.c.swp and linux-2.6/drivers/acrypto/.consumer.c.swp differ
diff -Nru /tmp/empty/crypto_conn.c linux-2.6/drivers/acrypto/crypto_conn.c
--- /tmp/empty/crypto_conn.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_conn.c	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,191 @@
+/*
+ * 	crypto_conn.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/vmalloc.h>
+
+#include "acrypto.h"
+#include "crypto_lb.h"
+
+#include "../connector/connector.h"
+
+#include "crypto_conn.h"
+
+static struct cb_id crypto_conn_id = { 0xdead, 0x0000 };
+static char crypto_conn_name[] = "crconn";
+
+static void crypto_conn_callback(void *data)
+{
+	struct cn_msg *msg, *reply;
+	struct crypto_conn_data *d, *cmd;
+	struct crypto_device *dev;
+	u32 sessions;
+
+	msg = (struct cn_msg *)data;
+	d = (struct crypto_conn_data *)msg->data;
+
+	if (msg->len < sizeof(*d)) {
+		dprintk(KERN_ERR "Wrong message to crypto connector: msg->len=%u < %u.\n",
+			msg->len, sizeof(*d));
+		return;
+	}
+
+	if (msg->len != sizeof(*d) + d->len) {
+		dprintk(KERN_ERR "Wrong message to crypto connector: msg->len=%u != %u.\n",
+			msg->len, sizeof(*d) + d->len);
+		return;
+	}
+
+	dev = crypto_device_get_name(d->name);
+	if (!dev) {
+		dprintk(KERN_INFO "Crypto device %s was not found.\n", d->name);
+		return;
+	}
+
+	switch (d->cmd) {
+	case CRYPTO_CONN_READ_SESSIONS:
+		reply = kmalloc(sizeof(*msg) + sizeof(*cmd) + sizeof(sessions), GFP_ATOMIC);
+		if (reply) {
+			memcpy(reply, msg, sizeof(*reply));
+			reply->len = sizeof(*cmd) + sizeof(sessions);
+
+			/*
+			 * See protocol description in connector.c
+			 */
+			reply->ack++;
+
+			cmd = (struct crypto_conn_data *)(reply + 1);
+			memcpy(cmd, d, sizeof(*cmd));
+			cmd->len = sizeof(sessions);
+
+			sessions = atomic_read(&dev->refcnt);
+
+			memcpy(cmd + 1, &sessions, sizeof(sessions));
+
+			cn_netlink_send(reply, 0);
+
+			kfree(reply);
+		} else
+			dprintk(KERN_ERR "Failed to allocate %d bytes in reply to comamnd 0x%x.\n",
+				sizeof(*msg) + sizeof(*cmd), d->cmd);
+		break;
+	case CRYPTO_CONN_DUMP_QUEUE:
+		reply = kmalloc(sizeof(*msg) + sizeof(*cmd) + 
+				1024 * sizeof(struct crypto_session_initializer), GFP_ATOMIC);
+		if (reply) {
+			struct crypto_session *s;
+			struct crypto_session_initializer *ptr;
+
+			memcpy(reply, msg, sizeof(*reply));
+
+			/*
+			 * See protocol description in connector.c
+			 */
+			reply->ack++;
+
+			cmd = (struct crypto_conn_data *)(reply + 1);
+			memcpy(cmd, d, sizeof(*cmd));
+
+			ptr = (struct crypto_session_initializer *)(cmd + 1);
+
+			sessions = 0;
+			spin_lock_irq(&dev->session_lock);
+			list_for_each_entry(s, &dev->session_list, dev_queue_entry) {
+				memcpy(ptr, &s->ci, sizeof(*ptr));
+				sessions++;
+				ptr++;
+
+				if (sessions >= 1024)
+					break;
+			}
+			spin_unlock_irq(&dev->session_lock);
+
+			cmd->len = sizeof(*ptr) * sessions;
+			reply->len = sizeof(*cmd) + cmd->len;
+
+			cn_netlink_send(reply, 0);
+
+			kfree(reply);
+		} else
+			dprintk(KERN_ERR "Failed to allocate %d bytes in reply to comamnd 0x%x.\n",
+				sizeof(*msg) + sizeof(*cmd), d->cmd);
+		break;
+	case CRYPTO_GET_STAT:
+		reply =
+		    kmalloc(sizeof(*msg) + sizeof(*cmd) + sizeof(struct crypto_device_stat), GFP_ATOMIC);
+		if (reply) {
+			struct crypto_device_stat *ptr;
+
+			memcpy(reply, msg, sizeof(*reply));
+			reply->len = sizeof(*cmd) + sizeof(*ptr);
+
+			/*
+			 * See protocol description in connector.c
+			 */
+			reply->ack++;
+
+			cmd = (struct crypto_conn_data *)(reply + 1);
+			memcpy(cmd, d, sizeof(*cmd));
+			cmd->len = sizeof(*ptr);
+
+			ptr = (struct crypto_device_stat *)(cmd + 1);
+			memcpy(ptr, &dev->stat, sizeof(*ptr));
+
+			cn_netlink_send(reply, 0);
+
+			kfree(reply);
+		} else
+			dprintk(KERN_ERR "Failed to allocate %d bytes in reply to comamnd 0x%x.\n",
+				sizeof(*msg) + sizeof(*cmd), d->cmd);
+		break;
+	default:
+		dprintk(KERN_ERR "Wrong operation 0x%04x for crypto connector.\n",
+			d->cmd);
+		return;
+	}
+
+	crypto_device_put(dev);
+}
+
+int crypto_conn_init(void)
+{
+	int err;
+
+	err = cn_add_callback(&crypto_conn_id, crypto_conn_name, crypto_conn_callback);
+	if (err)
+		return err;
+
+	dprintk(KERN_INFO "Crypto connector callback is registered.\n");
+
+	return 0;
+}
+
+void crypto_conn_fini(void)
+{
+	cn_del_callback(&crypto_conn_id);
+	dprintk(KERN_INFO "Crypto connector callback is unregistered.\n");
+}
diff -Nru /tmp/empty/crypto_conn.h linux-2.6/drivers/acrypto/crypto_conn.h
--- /tmp/empty/crypto_conn.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_conn.h	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,44 @@
+/*
+ * 	crypto_conn.h
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __CRYPTO_CONN_H
+#define __CRYPTO_CONN_H
+
+#include "acrypto.h"
+
+#define CRYPTO_CONN_READ_SESSIONS		0
+#define CRYPTO_CONN_DUMP_QUEUE			1
+#define CRYPTO_GET_STAT				2
+
+struct crypto_conn_data {
+	char 		name[SCACHE_NAMELEN];
+	__u16 		cmd;
+	__u16 		len;
+	__u8 		data[0];
+};
+
+#ifdef __KERNEL__
+
+int crypto_conn_init(void);
+void crypto_conn_fini(void);
+
+#endif				/* __KERNEL__ */
+#endif				/* __CRYPTO_CONN_H */
diff -Nru /tmp/empty/crypto_def.h linux-2.6/drivers/acrypto/crypto_def.h
--- /tmp/empty/crypto_def.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_def.h	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,38 @@
+/*
+ * 	crypto_def.h
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __CRYPTO_DEF_H
+#define __CRYPTO_DEF_H
+
+#define CRYPTO_OP_DECRYPT	0
+#define CRYPTO_OP_ENCRYPT	1
+#define CRYPTO_OP_HMAC		2
+
+#define CRYPTO_MODE_ECB		0
+#define CRYPTO_MODE_CBC		1
+#define CRYPTO_MODE_CFB		2
+#define CRYPTO_MODE_OFB		3
+
+#define CRYPTO_TYPE_AES_128	0
+#define CRYPTO_TYPE_AES_192	1
+#define CRYPTO_TYPE_AES_256	2
+
+#endif				/* __CRYPTO_DEF_H */
diff -Nru /tmp/empty/crypto_dev.c linux-2.6/drivers/acrypto/crypto_dev.c
--- /tmp/empty/crypto_dev.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_dev.c	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,371 @@
+/*
+ * 	crypto_dev.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+
+#include "acrypto.h"
+
+static LIST_HEAD(cdev_list);
+static spinlock_t cdev_lock = SPIN_LOCK_UNLOCKED;
+static u32 cdev_ids;
+
+struct list_head *crypto_device_list = &cdev_list;
+spinlock_t *crypto_device_lock = &cdev_lock;
+
+static int crypto_match(struct device *dev, struct device_driver *drv)
+{
+	return 1;
+}
+
+static int crypto_probe(struct device *dev)
+{
+	return -ENODEV;
+}
+
+static int crypto_remove(struct device *dev)
+{
+	return 0;
+}
+
+static void crypto_release(struct device *dev)
+{
+	struct crypto_device *d = container_of(dev, struct crypto_device, device);
+
+	complete(&d->dev_released);
+}
+
+static void crypto_class_release(struct class *class)
+{
+}
+
+static void crypto_class_release_device(struct class_device *class_dev)
+{
+}
+
+struct class crypto_class = {
+	.name 		= "acrypto",
+	.class_release 	= crypto_class_release,
+	.release 	= crypto_class_release_device
+};
+
+struct bus_type crypto_bus_type = {
+	.name 		= "acrypto",
+	.match 		= crypto_match
+};
+
+struct device_driver crypto_driver = {
+	.name 		= "crypto_driver",
+	.bus 		= &crypto_bus_type,
+	.probe 		= crypto_probe,
+	.remove 	= crypto_remove,
+};
+
+struct device crypto_dev = {
+	.parent 	= NULL,
+	.bus 		= &crypto_bus_type,
+	.bus_id		= "Asynchronous crypto",
+	.driver 	= &crypto_driver,
+	.release 	= &crypto_release
+};
+
+static ssize_t sessions_show(struct class_device *dev, char *buf)
+{
+	struct crypto_device *d = container_of(dev, struct crypto_device, class_device);
+
+	return sprintf(buf, "%d\n", atomic_read(&d->refcnt));
+}
+static ssize_t name_show(struct class_device *dev, char *buf)
+{
+	struct crypto_device *d = container_of(dev, struct crypto_device, class_device);
+
+	return sprintf(buf, "%s\n", d->name);
+}
+static ssize_t devices_show(struct class_device *dev, char *buf)
+{
+	struct crypto_device *d;
+	int off = 0;
+
+	spin_lock_irq(&cdev_lock);
+	list_for_each_entry(d, &cdev_list, cdev_entry) {
+		off += sprintf(buf + off, "%s ", d->name);
+	}
+	spin_unlock_irq(&cdev_lock);
+
+	if (!off)
+		off = sprintf(buf, "No devices registered yet.");
+
+	off += sprintf(buf + off, "\n");
+
+	return off;
+}
+
+static ssize_t kmem_failed_show(struct class_device *dev, char *buf)
+{
+	struct crypto_device *d =  container_of(dev, struct crypto_device, class_device);
+
+	return sprintf(buf, "%llu\n", d->stat.kmem_failed);
+}
+static ssize_t sstarted_show(struct class_device *dev, char *buf)
+{
+	struct crypto_device *d = container_of(dev, struct crypto_device, class_device);
+
+	return sprintf(buf, "%llu\n", d->stat.sstarted);
+}
+static ssize_t sfinished_show(struct class_device *dev, char *buf)
+{
+	struct crypto_device *d = container_of(dev, struct crypto_device, class_device);
+
+	return sprintf(buf, "%llu\n", d->stat.sfinished);
+}
+static ssize_t scompleted_show(struct class_device *dev, char *buf)
+{
+	struct crypto_device *d = container_of(dev, struct crypto_device, class_device);
+
+	return sprintf(buf, "%llu\n", d->stat.scompleted);
+}
+
+static CLASS_DEVICE_ATTR(sessions, 0444, sessions_show, NULL);
+static CLASS_DEVICE_ATTR(name, 0444, name_show, NULL);
+CLASS_DEVICE_ATTR(devices, 0444, devices_show, NULL);
+static CLASS_DEVICE_ATTR(scompleted, 0444, scompleted_show, NULL);
+static CLASS_DEVICE_ATTR(sstarted, 0444, sstarted_show, NULL);
+static CLASS_DEVICE_ATTR(sfinished, 0444, sfinished_show, NULL);
+static CLASS_DEVICE_ATTR(kmem_failed, 0444, kmem_failed_show, NULL);
+
+static int compare_device(struct crypto_device *d1, struct crypto_device *d2)
+{
+	if (!strncmp(d1->name, d2->name, sizeof(d1->name)))
+		return 1;
+
+	return 0;
+}
+
+static void create_device_attributes(struct crypto_device *dev)
+{
+	class_device_create_file(&dev->class_device, &class_device_attr_sessions);
+	class_device_create_file(&dev->class_device, &class_device_attr_name);
+	class_device_create_file(&dev->class_device, &class_device_attr_scompleted);
+	class_device_create_file(&dev->class_device, &class_device_attr_sstarted);
+	class_device_create_file(&dev->class_device, &class_device_attr_sfinished);
+	class_device_create_file(&dev->class_device, &class_device_attr_kmem_failed);
+}
+
+static void remove_device_attributes(struct crypto_device *dev)
+{
+	class_device_remove_file(&dev->class_device, &class_device_attr_sessions);
+	class_device_remove_file(&dev->class_device, &class_device_attr_name);
+	class_device_remove_file(&dev->class_device, &class_device_attr_scompleted);
+	class_device_remove_file(&dev->class_device, &class_device_attr_sstarted);
+	class_device_remove_file(&dev->class_device, &class_device_attr_sfinished);
+	class_device_remove_file(&dev->class_device, &class_device_attr_kmem_failed);
+}
+
+int __match_initializer(struct crypto_capability *cap, struct crypto_session_initializer *ci)
+{
+	if (cap->operation == ci->operation && cap->type == ci->type && 
+			cap->mode == (ci->mode & 0x1fff))
+		return 1;
+
+	return 0;
+}
+
+int match_initializer(struct crypto_device *dev, struct crypto_session_initializer *ci)
+{
+	int i;
+
+	for (i = 0; i < dev->cap_number; ++i) {
+		struct crypto_capability *cap = &dev->cap[i];
+
+		if (__match_initializer(cap, ci)) {
+			if (cap->qlen >= atomic_read(&dev->refcnt) + 1) {
+				dprintk("cap->len=%u, req=%u.\n",
+					cap->qlen, atomic_read(&dev->refcnt) + 1);
+				return 1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+void crypto_device_get(struct crypto_device *dev)
+{
+	atomic_inc(&dev->refcnt);
+}
+
+struct crypto_device *crypto_device_get_name(char *name)
+{
+	struct crypto_device *dev;
+	int found = 0;
+
+	spin_lock_irq(&cdev_lock);
+	list_for_each_entry(dev, &cdev_list, cdev_entry) {
+		if (!strcmp(dev->name, name)) {
+			found = 1;
+			crypto_device_get(dev);
+			break;
+		}
+	}
+	spin_unlock_irq(&cdev_lock);
+
+	if (!found)
+		return NULL;
+
+	return dev;
+}
+
+void crypto_device_put(struct crypto_device *dev)
+{
+	atomic_dec(&dev->refcnt);
+}
+
+int __crypto_device_add(struct crypto_device *dev)
+{
+	int err;
+
+	memset(&dev->stat, 0, sizeof(dev->stat));
+	spin_lock_init(&dev->stat_lock);
+	spin_lock_init(&dev->lock);
+	spin_lock_init(&dev->session_lock);
+	INIT_LIST_HEAD(&dev->session_list);
+	atomic_set(&dev->refcnt, 0);
+	dev->sid = 0;
+	dev->flags = 0;
+	init_completion(&dev->dev_released);
+	memcpy(&dev->device, &crypto_dev, sizeof(struct device));
+	dev->driver = &crypto_driver;
+
+	snprintf(dev->device.bus_id, sizeof(dev->device.bus_id), "%s", dev->name);
+	err = device_register(&dev->device);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto device %s: err=%d.\n",
+			dev->name, err);
+		return err;
+	}
+
+	snprintf(dev->class_device.class_id, sizeof(dev->class_device.class_id), "%s", dev->name);
+	dev->class_device.dev = &dev->device;
+	dev->class_device.class = &crypto_class;
+
+	err = class_device_register(&dev->class_device);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto class device %s: err=%d.\n",
+			dev->name, err);
+		device_unregister(&dev->device);
+		return err;
+	}
+
+	create_device_attributes(dev);
+
+	return 0;
+}
+
+void __crypto_device_remove(struct crypto_device *dev)
+{
+	remove_device_attributes(dev);
+	class_device_unregister(&dev->class_device);
+	device_unregister(&dev->device);
+}
+
+int crypto_device_add(struct crypto_device *dev)
+{
+	int err;
+
+	err = __crypto_device_add(dev);
+	if (err)
+		return err;
+
+	spin_lock_irq(&cdev_lock);
+	list_add(&dev->cdev_entry, &cdev_list);
+	dev->id = ++cdev_ids;
+	spin_unlock_irq(&cdev_lock);
+
+	dprintk(KERN_INFO "Crypto device %s was registered with ID=%x.\n",
+		dev->name, dev->id);
+
+	return 0;
+}
+
+void crypto_device_remove(struct crypto_device *dev)
+{
+	struct crypto_device *__dev, *n;
+
+	__crypto_device_remove(dev);
+
+	spin_lock_irq(&cdev_lock);
+	list_for_each_entry_safe(__dev, n, &cdev_list, cdev_entry) {
+		if (compare_device(__dev, dev)) {
+			list_del_init(&__dev->cdev_entry);
+			spin_unlock_irq(&cdev_lock);
+
+			/*
+			 * In test cases or when crypto device driver is not written correctly,
+			 * it's ->data_ready() method will not be callen anymore
+			 * after device is removed from crypto device list.
+			 *
+			 * For such cases we either should provide ->flush() call
+			 * or properly write ->data_ready() method.
+			 */
+
+			while (atomic_read(&__dev->refcnt)) {
+
+				dprintk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n",
+					__dev->name, atomic_read(&dev->refcnt));
+
+				/*
+				 * Hack zone: you need to write good ->data_ready()
+				 * and crypto device driver itself.
+				 *
+				 * Driver shoud not buzz if it has pending sessions
+				 * in it's queue and it was removed from global device list.
+				 *
+				 * Although I can workaround it here, for example by
+				 * flushing the whole queue and drop all pending sessions.
+				 */
+
+				__dev->data_ready(__dev);
+				set_current_state(TASK_UNINTERRUPTIBLE);
+				schedule_timeout(HZ);
+			}
+
+			dprintk(KERN_ERR "Crypto device %s was unregistered.\n",
+				dev->name);
+			return;
+		}
+	}
+	spin_unlock_irq(&cdev_lock);
+
+	dprintk(KERN_ERR "Crypto device %s was not registered.\n", dev->name);
+}
+
+EXPORT_SYMBOL_GPL(crypto_device_add);
+EXPORT_SYMBOL_GPL(crypto_device_remove);
+EXPORT_SYMBOL_GPL(crypto_device_get);
+EXPORT_SYMBOL_GPL(crypto_device_get_name);
+EXPORT_SYMBOL_GPL(crypto_device_put);
diff -Nru /tmp/empty/crypto_lb.c linux-2.6/drivers/acrypto/crypto_lb.c
--- /tmp/empty/crypto_lb.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_lb.c	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,631 @@
+/*
+ * 	crypto_lb.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/err.h>
+
+#include "acrypto.h"
+#include "crypto_lb.h"
+#include "crypto_stat.h"
+#include "crypto_route.h"
+
+static LIST_HEAD(crypto_lb_list);
+static spinlock_t crypto_lb_lock = SPIN_LOCK_UNLOCKED;
+static int lb_num = 0;
+static struct crypto_lb *current_lb, *default_lb;
+static struct completion thread_exited;
+static int need_exit;
+static struct workqueue_struct *crypto_lb_queue;
+static DECLARE_WAIT_QUEUE_HEAD(crypto_lb_wait_queue);
+
+extern struct list_head *crypto_device_list;
+extern spinlock_t *crypto_device_lock;
+
+extern int force_lb_remove;
+extern struct crypto_device main_crypto_device;
+
+static int lb_is_current(struct crypto_lb *l)
+{
+	return (l->crypto_device_list != NULL && l->crypto_device_lock != NULL);
+}
+
+static int lb_is_default(struct crypto_lb *l)
+{
+	return (l == default_lb);
+}
+
+static void __lb_set_current(struct crypto_lb *l)
+{
+	struct crypto_lb *c = current_lb;
+
+	if (c) {
+		l->crypto_device_list = crypto_device_list;
+		l->crypto_device_lock = crypto_device_lock;
+		current_lb = l;
+		c->crypto_device_list = NULL;
+		c->crypto_device_lock = NULL;
+	} else {
+		l->crypto_device_list = crypto_device_list;
+		l->crypto_device_lock = crypto_device_lock;
+		current_lb = l;
+	}
+}
+
+static void lb_set_current(struct crypto_lb *l)
+{
+	struct crypto_lb *c = current_lb;
+
+	if (c) {
+		spin_lock_irq(&c->lock);
+		__lb_set_current(l);
+		spin_unlock_irq(&c->lock);
+	} else
+		__lb_set_current(l);
+}
+
+static void __lb_set_default(struct crypto_lb *l)
+{
+	default_lb = l;
+}
+
+static void lb_set_default(struct crypto_lb *l)
+{
+	struct crypto_lb *c = default_lb;
+
+	if (c) {
+		spin_lock_irq(&c->lock);
+		__lb_set_default(l);
+		spin_unlock_irq(&c->lock);
+	} else
+		__lb_set_default(l);
+}
+
+static int crypto_lb_match(struct device *dev, struct device_driver *drv)
+{
+	return 1;
+}
+
+static int crypto_lb_probe(struct device *dev)
+{
+	return -ENODEV;
+}
+
+static int crypto_lb_remove(struct device *dev)
+{
+	return 0;
+}
+
+static void crypto_lb_release(struct device *dev)
+{
+	struct crypto_lb *d = container_of(dev, struct crypto_lb, device);
+
+	complete(&d->dev_released);
+}
+
+static void crypto_lb_class_release(struct class *class)
+{
+}
+
+static void crypto_lb_class_release_device(struct class_device *class_dev)
+{
+}
+
+struct class crypto_lb_class = {
+	.name 		= "crypto_lb",
+	.class_release 	= crypto_lb_class_release,
+	.release 	= crypto_lb_class_release_device
+};
+
+struct bus_type crypto_lb_bus_type = {
+	.name 		= "crypto_lb",
+	.match 		= crypto_lb_match
+};
+
+struct device_driver crypto_lb_driver = {
+	.name 		= "crypto_lb_driver",
+	.bus 		= &crypto_lb_bus_type,
+	.probe 		= crypto_lb_probe,
+	.remove 	= crypto_lb_remove,
+};
+
+struct device crypto_lb_dev = {
+	.parent 	= NULL,
+	.bus 		= &crypto_lb_bus_type,
+	.bus_id 	= "crypto load balancer",
+	.driver 	= &crypto_lb_driver,
+	.release 	= &crypto_lb_release
+};
+
+static ssize_t name_show(struct class_device *dev, char *buf)
+{
+	struct crypto_lb *lb = container_of(dev, struct crypto_lb, class_device);
+
+	return sprintf(buf, "%s\n", lb->name);
+}
+
+static ssize_t current_show(struct class_device *dev, char *buf)
+{
+	struct crypto_lb *lb;
+	int off = 0;
+
+	spin_lock_irq(&crypto_lb_lock);
+
+	list_for_each_entry(lb, &crypto_lb_list, lb_entry) {
+		if (lb_is_current(lb))
+			off += sprintf(buf + off, "[");
+		if (lb_is_default(lb))
+			off += sprintf(buf + off, "(");
+		off += sprintf(buf + off, "%s", lb->name);
+		if (lb_is_default(lb))
+			off += sprintf(buf + off, ")");
+		if (lb_is_current(lb))
+			off += sprintf(buf + off, "]");
+	}
+
+	spin_unlock_irq(&crypto_lb_lock);
+
+	if (!off)
+		off = sprintf(buf, "No load balancers regitered yet.");
+
+	off += sprintf(buf + off, "\n");
+
+	return off;
+}
+static ssize_t current_store(struct class_device *dev, const char *buf, size_t count)
+{
+	struct crypto_lb *lb;
+
+	spin_lock_irq(&crypto_lb_lock);
+
+	list_for_each_entry(lb, &crypto_lb_list, lb_entry) {
+		if (count == strlen(lb->name) && !strcmp(buf, lb->name)) {
+			lb_set_current(lb);
+			lb_set_default(lb);
+
+			dprintk(KERN_INFO "Load balancer %s is set as current and default.\n",
+				lb->name);
+
+			break;
+		}
+	}
+	spin_unlock_irq(&crypto_lb_lock);
+
+	return count;
+}
+
+static CLASS_DEVICE_ATTR(name, 0444, name_show, NULL);
+CLASS_DEVICE_ATTR(lbs, 0644, current_show, current_store);
+
+static void create_device_attributes(struct crypto_lb *lb)
+{
+	class_device_create_file(&lb->class_device, &class_device_attr_name);
+}
+
+static void remove_device_attributes(struct crypto_lb *lb)
+{
+	class_device_remove_file(&lb->class_device, &class_device_attr_name);
+}
+
+static int compare_lb(struct crypto_lb *l1, struct crypto_lb *l2)
+{
+	if (!strncmp(l1->name, l2->name, sizeof(l1->name)))
+		return 1;
+
+	return 0;
+}
+
+void crypto_lb_rehash(void)
+{
+	if (!current_lb)
+		return;
+
+	spin_lock_irq(&current_lb->lock);
+
+	current_lb->rehash(current_lb);
+
+	spin_unlock_irq(&current_lb->lock);
+
+	wake_up_interruptible(&crypto_lb_wait_queue);
+}
+
+struct crypto_device *crypto_lb_find_device(struct crypto_session_initializer *ci, struct crypto_data *data)
+{
+	struct crypto_device *dev;
+
+	if (!current_lb)
+		return NULL;
+
+	if (sci_binded(ci)) {
+		int found = 0;
+
+		spin_lock_irq(crypto_device_lock);
+
+		list_for_each_entry(dev, crypto_device_list, cdev_entry) {
+			if (dev->id == ci->bdev) {
+				found = 1;
+				break;
+			}
+		}
+
+		spin_unlock_irq(crypto_device_lock);
+
+		return (found) ? dev : NULL;
+	}
+
+	spin_lock_irq(&current_lb->lock);
+
+	current_lb->rehash(current_lb);
+
+	spin_lock(crypto_device_lock);
+
+	dev = current_lb->find_device(current_lb, ci, data);
+	if (dev)
+		crypto_device_get(dev);
+
+	spin_unlock(crypto_device_lock);
+
+	spin_unlock_irq(&current_lb->lock);
+
+	wake_up_interruptible(&crypto_lb_wait_queue);
+
+	return dev;
+}
+
+static int __crypto_lb_register(struct crypto_lb *lb)
+{
+	int err;
+
+	spin_lock_init(&lb->lock);
+
+	init_completion(&lb->dev_released);
+	memcpy(&lb->device, &crypto_lb_dev, sizeof(struct device));
+	lb->driver = &crypto_lb_driver;
+
+	snprintf(lb->device.bus_id, sizeof(lb->device.bus_id), "%s", lb->name);
+	err = device_register(&lb->device);
+	if (err) {
+		dprintk(KERN_ERR
+			"Failed to register crypto load balancer device %s: err=%d.\n",
+			lb->name, err);
+		return err;
+	}
+
+	snprintf(lb->class_device.class_id, sizeof(lb->class_device.class_id), "%s", lb->name);
+	lb->class_device.dev = &lb->device;
+	lb->class_device.class = &crypto_lb_class;
+
+	err = class_device_register(&lb->class_device);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto load balancer class device %s: err=%d.\n",
+			lb->name, err);
+		device_unregister(&lb->device);
+		return err;
+	}
+
+	create_device_attributes(lb);
+	wake_up_interruptible(&crypto_lb_wait_queue);
+
+	return 0;
+
+}
+
+static void __crypto_lb_unregister(struct crypto_lb *lb)
+{
+	wake_up_interruptible(&crypto_lb_wait_queue);
+	remove_device_attributes(lb);
+	class_device_unregister(&lb->class_device);
+	device_unregister(&lb->device);
+}
+
+int crypto_lb_register(struct crypto_lb *lb, int set_current, int set_default)
+{
+	struct crypto_lb *__lb;
+	int err;
+
+	spin_lock_irq(&crypto_lb_lock);
+
+	list_for_each_entry(__lb, &crypto_lb_list, lb_entry) {
+		if (unlikely(compare_lb(__lb, lb))) {
+			spin_unlock_irq(&crypto_lb_lock);
+
+			dprintk(KERN_ERR "Crypto load balancer %s is already registered.\n",
+				lb->name);
+			return -EINVAL;
+		}
+	}
+
+	list_add(&lb->lb_entry, &crypto_lb_list);
+
+	spin_unlock_irq(&crypto_lb_lock);
+
+	err = __crypto_lb_register(lb);
+	if (err) {
+		spin_lock_irq(&crypto_lb_lock);
+		list_del_init(&lb->lb_entry);
+		spin_unlock_irq(&crypto_lb_lock);
+
+		return err;
+	}
+
+	if (!default_lb || set_default)
+		lb_set_default(lb);
+
+	if (!current_lb || set_current)
+		lb_set_current(lb);
+
+	dprintk(KERN_INFO "Crypto load balancer %s was registered and set to be [%s.%s].\n",
+		lb->name, (lb_is_current(lb)) ? "current" : "not current",
+		(lb_is_default(lb)) ? "default" : "not default");
+
+	lb_num++;
+
+	return 0;
+}
+
+void crypto_lb_unregister(struct crypto_lb *lb)
+{
+	struct crypto_lb *__lb, *n;
+
+	if (lb_num == 1) {
+		dprintk(KERN_INFO "You are removing crypto load balancer %s which is current and default.\n"
+			"There is no other crypto load balancers. "
+			"Removing %s delayed untill new load balancer is registered.\n",
+			lb->name, (force_lb_remove) ? "is not" : "is");
+		while (lb_num == 1 && !force_lb_remove) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(HZ);
+
+			if (signal_pending(current))
+				flush_signals(current);
+		}
+	}
+
+	__crypto_lb_unregister(lb);
+
+	spin_lock_irq(&crypto_lb_lock);
+
+	list_for_each_entry_safe(__lb, n, &crypto_lb_list, lb_entry) {
+		if (compare_lb(__lb, lb)) {
+			lb_num--;
+			list_del_init(&__lb->lb_entry);
+
+			dprintk(KERN_ERR "Crypto load balancer %s was unregistered.\n",
+				lb->name);
+		} else if (lb_num) {
+			if (lb_is_default(lb))
+				lb_set_default(__lb);
+			if (lb_is_current(lb))
+				lb_set_current(default_lb);
+		}
+	}
+
+	spin_unlock_irq(&crypto_lb_lock);
+}
+
+static void crypto_lb_queue_wrapper(void *data)
+{
+	struct crypto_device *dev = &main_crypto_device;
+	struct crypto_session *s = (struct crypto_session *)data;
+
+	dprintk(KERN_INFO "%s: Calling callback for session %llu [%llu] flags=%x, "
+		"op=%04u, type=%04x, mode=%04x, priority=%04x\n", __func__,
+		s->ci.id, s->ci.dev_id, s->ci.flags, s->ci.operation,
+		s->ci.type, s->ci.mode, s->ci.priority);
+
+	spin_lock_irq(&s->lock);
+	crypto_stat_finish_inc(s);
+
+	finish_session(s);
+	unstart_session(s);
+	spin_unlock_irq(&s->lock);
+
+	s->ci.callback(&s->ci, &s->data);
+
+	if (session_finished(s)) {
+		kfree(s);
+		return;
+	} else {
+		/*
+		 * Special case: crypto consumer marks session as "not finished"
+		 * in it's callback - it means that crypto consumer wants 
+		 * this session to be processed further, 
+		 * for example crypto consumer can add new route and then
+		 * mark session as "not finished".
+		 */
+
+		uncomplete_session(s);
+		unstart_session(s);
+		crypto_session_insert_main(dev, s);
+	}
+	spin_unlock_irq(&s->lock);
+}
+
+static void crypto_lb_process_next_route(struct crypto_session *s)
+{
+	struct crypto_route *rt;
+	struct crypto_device *dev;
+
+	rt = crypto_route_dequeue(s);
+	if (rt) {
+		dev = rt->dev;
+
+		spin_lock_irq(&dev->session_lock);
+		list_del_init(&s->dev_queue_entry);
+		spin_unlock_irq(&dev->session_lock);
+
+		crypto_route_free(rt);
+
+		dev = crypto_route_get_current_device(s);
+		if (dev) {
+			dprintk(KERN_INFO "%s: processing new route to %s.\n",
+				__func__, dev->name);
+
+			memcpy(&s->ci, &rt->ci, sizeof(s->ci));
+			crypto_session_insert(dev, s);
+
+			/*
+			 * Reference to this device was already hold when
+			 * new routing was added.
+			 */
+			crypto_device_put(dev);
+		}
+	}
+}
+
+void crypto_wake_lb(void)
+{
+	wake_up_interruptible(&crypto_lb_wait_queue);
+}
+
+int crypto_lb_thread(void *data)
+{
+	struct crypto_session *s, *n;
+	struct crypto_device *dev = (struct crypto_device *)data;
+
+	daemonize("%s", dev->name);
+	allow_signal(SIGTERM);
+
+	while (!need_exit) {
+		list_for_each_entry_safe(s, n, &dev->session_list,
+					 main_queue_entry) {
+			dprintk("session %llu [%llu]: flags=%x, route_num=%d, %s,%s,%s,%s.\n",
+			     s->ci.id, s->ci.dev_id, s->ci.flags,
+			     crypto_route_queue_len(s),
+			     (session_completed(s)) ? "completed" : "not completed",
+			     (session_finished(s)) ? "finished" : "not finished",
+			     (session_started(s)) ? "started" : "not started",
+			     (session_is_processed(s)) ? "is being processed" : "is not being processed");
+
+			if (!spin_trylock(&s->lock))
+				continue;
+
+			if (session_is_processed(s))
+				goto unlock;
+			if (session_started(s))
+				goto unlock;
+
+			if (session_completed(s)) {
+				crypto_stat_ptime_inc(s);
+
+				if (crypto_route_queue_len(s) > 1) {
+					crypto_lb_process_next_route(s);
+				} else {
+					start_session(s);
+					crypto_stat_start_inc(s);
+
+					dprintk("%s: going to remove session %llu [%llu].\n",
+					     __func__, s->ci.id, s->ci.dev_id);
+
+					crypto_session_dequeue_main(s);
+					spin_unlock(&s->lock);
+
+					INIT_WORK(&s->work, &crypto_lb_queue_wrapper, s);
+					queue_work(crypto_lb_queue, &s->work);
+					continue;
+				}
+			}
+		      unlock:
+			spin_unlock(&s->lock);
+		}
+
+		interruptible_sleep_on_timeout(&crypto_lb_wait_queue, 100);
+	}
+
+	flush_workqueue(crypto_lb_queue);
+	complete_and_exit(&thread_exited, 0);
+}
+
+int crypto_lb_init(void)
+{
+	int err;
+	long pid;
+
+	err = bus_register(&crypto_lb_bus_type);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto load balancer bus: err=%d.\n", err);
+		goto err_out_exit;
+	}
+
+	err = driver_register(&crypto_lb_driver);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto load balancer driver: err=%d.\n", err);
+		goto err_out_bus_unregister;
+	}
+
+	crypto_lb_class.class_dev_attrs = &class_device_attr_lbs;
+
+	err = class_register(&crypto_lb_class);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto load balancer class: err=%d.\n", err);
+		goto err_out_driver_unregister;
+	}
+
+	crypto_lb_queue = create_workqueue("clbq");
+	if (!crypto_lb_queue) {
+		dprintk(KERN_ERR "Failed to create crypto load balaner work queue.\n");
+		goto err_out_class_unregister;
+	}
+
+	init_completion(&thread_exited);
+	pid = kernel_thread(crypto_lb_thread, &main_crypto_device, CLONE_FS | CLONE_FILES);
+	if (IS_ERR((void *)pid)) {
+		dprintk(KERN_ERR "Failed to create kernel load balancing thread.\n");
+		goto err_out_destroy_workqueue;
+	}
+
+	return 0;
+
+err_out_destroy_workqueue:
+	destroy_workqueue(crypto_lb_queue);
+err_out_class_unregister:
+	class_unregister(&crypto_lb_class);
+err_out_driver_unregister:
+	driver_unregister(&crypto_lb_driver);
+err_out_bus_unregister:
+	bus_unregister(&crypto_lb_bus_type);
+err_out_exit:
+	return err;
+}
+
+void crypto_lb_fini(void)
+{
+	need_exit = 1;
+	wait_for_completion(&thread_exited);
+	flush_workqueue(crypto_lb_queue);
+	destroy_workqueue(crypto_lb_queue);
+	class_unregister(&crypto_lb_class);
+	driver_unregister(&crypto_lb_driver);
+	bus_unregister(&crypto_lb_bus_type);
+}
+
+EXPORT_SYMBOL_GPL(crypto_lb_register);
+EXPORT_SYMBOL_GPL(crypto_lb_unregister);
+EXPORT_SYMBOL_GPL(crypto_lb_rehash);
+EXPORT_SYMBOL_GPL(crypto_lb_find_device);
+EXPORT_SYMBOL_GPL(crypto_wake_lb);
Binary files /tmp/empty/.crypto_lb.c.swn and linux-2.6/drivers/acrypto/.crypto_lb.c.swn differ
Binary files /tmp/empty/.crypto_lb.c.swo and linux-2.6/drivers/acrypto/.crypto_lb.c.swo differ
Binary files /tmp/empty/.crypto_lb.c.swp and linux-2.6/drivers/acrypto/.crypto_lb.c.swp differ
diff -Nru /tmp/empty/crypto_lb.h linux-2.6/drivers/acrypto/crypto_lb.h
--- /tmp/empty/crypto_lb.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_lb.h	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,62 @@
+/*
+ * 	crypto_lb.h
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __CRYPTO_LB_H
+#define __CRYPTO_LB_H
+
+#include "acrypto.h"
+
+#define CRYPTO_LB_NAMELEN	32
+
+struct crypto_lb {
+	struct list_head 	lb_entry;
+
+	char 			name[CRYPTO_LB_NAMELEN];
+
+	void 			(*rehash)(struct crypto_lb *);
+	struct crypto_device *	(*find_device) (struct crypto_lb *,
+						struct crypto_session_initializer *, 
+						struct crypto_data *);
+
+	spinlock_t 		lock;
+
+	spinlock_t 		*crypto_device_lock;
+	struct list_head 	*crypto_device_list;
+
+	struct device_driver 	*driver;
+	struct device 		device;
+	struct class_device 	class_device;
+	struct completion 	dev_released;
+
+};
+
+int crypto_lb_register(struct crypto_lb *lb, int set_current, int set_default);
+void crypto_lb_unregister(struct crypto_lb *);
+
+inline void crypto_lb_rehash(void);
+struct crypto_device *crypto_lb_find_device(struct crypto_session_initializer *, struct crypto_data *);
+
+void crypto_wake_lb(void);
+
+int crypto_lb_init(void);
+void crypto_lb_fini(void);
+
+#endif				/* __CRYPTO_LB_H */
diff -Nru /tmp/empty/crypto_main.c linux-2.6/drivers/acrypto/crypto_main.c
--- /tmp/empty/crypto_main.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_main.c	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,340 @@
+/*
+ * 	crypto_main.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#include "acrypto.h"
+#include "crypto_lb.h"
+#include "crypto_conn.h"
+#include "crypto_route.h"
+
+int force_lb_remove;
+module_param(force_lb_remove, int, 0);
+
+struct crypto_device main_crypto_device;
+
+extern struct bus_type crypto_bus_type;
+extern struct device_driver crypto_driver;
+extern struct class crypto_class;
+extern struct device crypto_dev;
+
+extern struct class_device_attribute class_device_attr_devices;
+extern struct class_device_attribute class_device_attr_lbs;
+
+static void dump_ci(struct crypto_session_initializer *ci)
+{
+	dprintk("%llu [%llu] op=%04u, type=%04x, mode=%04x, priority=%04x",
+		ci->id, ci->dev_id,
+		ci->operation, ci->type, ci->mode, ci->priority);
+}
+
+static void __crypto_session_insert(struct crypto_device *dev, struct crypto_session *s)
+{
+	struct crypto_session *__s;
+
+	if (unlikely(list_empty(&dev->session_list))) {
+		list_add(&s->dev_queue_entry, &dev->session_list);
+	} else {
+		int inserted = 0;
+
+		list_for_each_entry(__s, &dev->session_list, dev_queue_entry) {
+			if (__s->ci.priority < s->ci.priority) {
+				list_add_tail(&s->dev_queue_entry, &__s->dev_queue_entry);
+				inserted = 1;
+				break;
+			}
+		}
+
+		if (!inserted)
+			list_add_tail(&s->dev_queue_entry, &dev->session_list);
+	}
+
+	dump_ci(&s->ci);
+	dprintk(" added to crypto device %s [%d].\n", dev->name, atomic_read(&dev->refcnt));
+}
+
+void crypto_session_insert_main(struct crypto_device *dev, struct crypto_session *s)
+{
+	struct crypto_session *__s;
+
+	spin_lock_irq(&dev->session_lock);
+
+	crypto_device_get(dev);
+	if (unlikely(list_empty(&dev->session_list))) {
+		list_add(&s->main_queue_entry, &dev->session_list);
+	} else {
+		int inserted = 0;
+
+		list_for_each_entry(__s, &dev->session_list, main_queue_entry) {
+			if (__s->ci.priority < s->ci.priority) {
+				list_add_tail(&s->main_queue_entry,
+					      &__s->main_queue_entry);
+				inserted = 1;
+				break;
+			}
+		}
+
+		if (!inserted)
+			list_add_tail(&s->main_queue_entry, &dev->session_list);
+	}
+
+	spin_unlock_irq(&dev->session_lock);
+}
+
+void crypto_session_insert(struct crypto_device *dev, struct crypto_session *s)
+{
+	spin_lock_irq(&dev->session_lock);
+	__crypto_session_insert(dev, s);
+	spin_unlock_irq(&dev->session_lock);
+}
+
+struct crypto_session *crypto_session_create(struct crypto_session_initializer *ci, struct crypto_data *d)
+{
+	struct crypto_device *dev = &main_crypto_device;
+	struct crypto_device *ldev;
+	struct crypto_session *s;
+	int err;
+
+	if (d->priv_size > CRYPTO_MAX_PRIV_SIZE) {
+		dprintk("priv_size %u is too big, maximum allowed %u.\n",
+			d->priv_size, CRYPTO_MAX_PRIV_SIZE);
+		return NULL;
+	}
+	
+	ldev = crypto_lb_find_device(ci, d);
+	if (!ldev) {
+		dprintk("Cannot find suitable device.\n");
+		return NULL;
+	}
+	
+	s = kmalloc(sizeof(*s) + d->priv_size, GFP_ATOMIC);
+	if (!s) {
+		ldev->stat.kmem_failed++;
+		goto err_out_device_put;
+	}
+
+	memset(s, 0xAB, sizeof(*s));
+
+	crypto_route_head_init(&s->route_list);
+	INIT_LIST_HEAD(&s->dev_queue_entry);
+	INIT_LIST_HEAD(&s->main_queue_entry);
+
+	spin_lock_init(&s->lock);
+
+	memcpy(&s->ci, ci, sizeof(s->ci));
+	memcpy(&s->data, d, sizeof(s->data));
+	s->data.priv = s + 1;
+	if (d->priv_size) {
+		s->data.priv = s + 1;
+		if (d->priv)
+			memcpy(s->data.priv, d->priv, d->priv_size);
+	}
+
+	s->ci.id = dev->sid++;
+	s->ci.dev_id = ldev->sid++;
+	s->ci.flags = 0;
+
+	err = crypto_route_add_direct(ldev, s, ci);
+	if (err) {
+		dprintk("Can not add route to device %s.\n", ldev->name);
+		goto err_out_session_free;
+	}
+
+	return s;
+
+err_out_session_free:
+	kfree(s);
+err_out_device_put:
+	crypto_device_put(ldev);
+
+	return NULL;
+}
+
+void crypto_session_add(struct crypto_session *s)
+{
+	struct crypto_device *ldev;
+	struct crypto_device *dev = &main_crypto_device;
+
+	ldev = crypto_route_get_current_device(s);
+	BUG_ON(!ldev);		/* This can not happen. */
+
+	spin_lock_irq(&s->lock);
+	crypto_session_insert(ldev, s);
+	crypto_device_put(ldev);
+	crypto_session_insert_main(dev, s);
+	spin_unlock_irq(&s->lock);
+
+	if (ldev->data_ready)
+		ldev->data_ready(ldev);
+}
+
+struct crypto_session *crypto_session_alloc(struct crypto_session_initializer *ci, struct crypto_data *d)
+{
+	struct crypto_session *s;
+
+	s = crypto_session_create(ci, d);
+	if (!s)
+		return NULL;
+
+	crypto_session_add(s);
+
+	return s;
+}
+
+void crypto_session_dequeue_route(struct crypto_session *s)
+{
+	struct crypto_route *rt;
+	struct crypto_device *dev;
+
+	BUG_ON(crypto_route_queue_len(s) > 1);
+
+	while ((rt = crypto_route_dequeue(s))) {
+		dev = rt->dev;
+
+		dprintk(KERN_INFO "Removing route entry for device %s.\n", dev->name);
+
+		spin_lock_irq(&dev->session_lock);
+
+		list_del_init(&s->dev_queue_entry);
+
+		spin_unlock_irq(&dev->session_lock);
+
+		crypto_route_free(rt);
+	}
+}
+
+void __crypto_session_dequeue_main(struct crypto_session *s)
+{
+	struct crypto_device *dev = &main_crypto_device;
+
+	list_del(&s->main_queue_entry);
+	crypto_device_put(dev);
+}
+
+void crypto_session_dequeue_main(struct crypto_session *s)
+{
+	struct crypto_device *dev = &main_crypto_device;
+
+	spin_lock_irq(&dev->session_lock);
+
+	__crypto_session_dequeue_main(s);
+
+	spin_unlock_irq(&dev->session_lock);
+}
+
+int __devinit cmain_init(void)
+{
+	struct crypto_device *dev = &main_crypto_device;
+	int err;
+
+	snprintf(dev->name, sizeof(dev->name), "crypto_sessions");
+
+	err = bus_register(&crypto_bus_type);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto bus: err=%d.\n",
+			err);
+		return err;
+	}
+
+	err = driver_register(&crypto_driver);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto driver: err=%d.\n",
+			err);
+		goto err_out_bus_unregister;
+	}
+
+	err = class_register(&crypto_class);
+	if (err) {
+		dprintk(KERN_ERR "Failed to register crypto class: err=%d.\n",
+			err);
+		goto err_out_driver_unregister;
+	}
+
+	err = crypto_lb_init();
+	if (err)
+		goto err_out_class_unregister;
+
+	err = crypto_conn_init();
+	if (err)
+		goto err_out_crypto_lb_fini;
+
+	err = __crypto_device_add(dev);
+	if (err)
+		goto err_out_crypto_conn_fini;
+
+	err = class_device_create_file(&dev->class_device, &class_device_attr_devices);
+	if (err)
+		dprintk("Failed to create \"devices\" attribute: err=%d.\n",
+			err);
+	err = class_device_create_file(&dev->class_device, &class_device_attr_lbs);
+	if (err)
+		dprintk("Failed to create \"lbs\" attribute: err=%d.\n", err);
+
+	return 0;
+
+err_out_crypto_conn_fini:
+	crypto_conn_fini();
+err_out_crypto_lb_fini:
+	crypto_lb_fini();
+err_out_class_unregister:
+	class_unregister(&crypto_class);
+err_out_driver_unregister:
+	driver_unregister(&crypto_driver);
+err_out_bus_unregister:
+	bus_unregister(&crypto_bus_type);
+
+	return err;
+}
+
+void __devexit cmain_fini(void)
+{
+	struct crypto_device *dev = &main_crypto_device;
+
+	class_device_remove_file(&dev->class_device, &class_device_attr_devices);
+	class_device_remove_file(&dev->class_device, &class_device_attr_lbs);
+	__crypto_device_remove(dev);
+
+	crypto_conn_fini();
+	crypto_lb_fini();
+
+	class_unregister(&crypto_class);
+	driver_unregister(&crypto_driver);
+	bus_unregister(&crypto_bus_type);
+}
+
+module_init(cmain_init);
+module_exit(cmain_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <[email-masked]>");
+MODULE_DESCRIPTION("Asynchronous crypto layer.");
+
+EXPORT_SYMBOL(crypto_session_alloc);
+EXPORT_SYMBOL_GPL(crypto_session_create);
+EXPORT_SYMBOL_GPL(crypto_session_add);
+EXPORT_SYMBOL_GPL(crypto_session_dequeue_route);
diff -Nru /tmp/empty/crypto_provider.c linux-2.6/drivers/acrypto/crypto_provider.c
--- /tmp/empty/crypto_provider.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_provider.c	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,183 @@
+/*
+ * 	crypto_provider.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/err.h>
+#include <linux/crypto.h>
+#include <linux/mm.h>
+
+#define DEBUG
+#include "acrypto.h"
+#include "crypto_stat.h"
+#include "crypto_def.h"
+
+static void prov_data_ready(struct crypto_device *dev);
+
+static struct crypto_capability prov_caps[] = {
+	{CRYPTO_OP_ENCRYPT, CRYPTO_TYPE_AES_128, CRYPTO_MODE_ECB, 1000},
+	{CRYPTO_OP_DECRYPT, CRYPTO_TYPE_AES_128, CRYPTO_MODE_ECB, 1000},
+};
+static int prov_cap_number = sizeof(prov_caps) / sizeof(prov_caps[0]);
+
+static struct completion thread_exited;
+static DECLARE_WAIT_QUEUE_HEAD(crypto_wait_queue);
+static int need_exit;
+static struct crypto_tfm *tfm;
+
+static struct crypto_device pdev = {
+	.name = "crypto_provider",
+	.data_ready = prov_data_ready,
+	.cap = &prov_caps[0],
+};
+
+static void prov_data_ready(struct crypto_device *dev)
+{
+	wake_up(&crypto_wait_queue);
+}
+
+static int crypto_thread(void *data)
+{
+	struct crypto_device *dev = (struct crypto_device *)data;
+	u8 *key;
+	unsigned int keylen;
+	int err;
+	struct crypto_session *s, *n;
+
+	daemonize("%s", dev->name);
+	allow_signal(SIGTERM);
+
+	while (!need_exit) {
+		interruptible_sleep_on_timeout(&crypto_wait_queue, 100);
+
+		if (need_exit)
+			break;
+
+		list_for_each_entry_safe(s, n, &dev->session_list, dev_queue_entry) {
+			if (!spin_trylock(&s->lock))
+				continue;
+
+			if (session_completed(s)) {
+				goto unlock;
+			}
+			start_process_session(s);
+
+			dprintk("Begin to process session %llu [%llu] in %s.\n",
+				s->ci.id, s->ci.dev_id, pdev.name);
+
+			key = ((u8 *) page_address(s->data.sg_key.page)) + s->data.sg_key.offset;
+			keylen = s->data.sg_key.length;
+
+			err = crypto_cipher_setkey(tfm, key, keylen);
+			if (err) {
+				dprintk(KERN_ERR "Failed to set key [keylen=%d]: err=%d.\n",
+				     keylen, err);
+				broke_session(s);
+				goto out;
+			}
+
+			if (s->ci.operation == CRYPTO_OP_ENCRYPT)
+				crypto_cipher_encrypt(tfm, &s->data.sg_dst, &s->data.sg_src, s->data.sg_src.length);
+			else
+				crypto_cipher_decrypt(tfm, &s->data.sg_dst, &s->data.sg_src, s->data.sg_src.length);
+
+			s->data.sg_dst.length = s->data.sg_src.length;
+			s->data.sg_dst.offset = 0;
+
+			dprintk(KERN_INFO "Completing session %llu [%llu] in %s.\n",
+				s->ci.id, s->ci.dev_id, pdev.name);
+
+out:
+			crypto_stat_complete_inc(s);
+			crypto_session_dequeue_route(s);
+			complete_session(s);
+			stop_process_session(s);
+unlock:
+			spin_unlock(&s->lock);
+		}
+	}
+
+	complete_and_exit(&thread_exited, 0);
+}
+
+int prov_init(void)
+{
+	int err, pid;
+
+	tfm = crypto_alloc_tfm("aes", 0);
+	if (!tfm) {
+		printk(KERN_ERR "Failed to allocate SHA1 tfm.\n");
+		return -EINVAL;
+	}
+
+	init_completion(&thread_exited);
+	pid = kernel_thread(crypto_thread, &pdev, CLONE_FS | CLONE_FILES);
+	if (IS_ERR((void *)pid)) {
+		err = -EINVAL;
+		dprintk(KERN_ERR "Failed to create kernel load balancing thread.\n");
+		goto err_out_free_tfm;
+	}
+
+	pdev.cap_number = prov_cap_number;
+
+	err = crypto_device_add(&pdev);
+	if (err)
+		goto err_out_remove_thread;
+
+	dprintk(KERN_INFO "Test crypto provider module %s is loaded.\n", pdev.name);
+
+	return 0;
+
+err_out_remove_thread:
+	need_exit = 1;
+	wake_up(&crypto_wait_queue);
+	wait_for_completion(&thread_exited);
+err_out_free_tfm:
+	crypto_free_tfm(tfm);
+
+	return err;
+}
+
+void prov_fini(void)
+{
+	need_exit = 1;
+	wake_up(&crypto_wait_queue);
+	wait_for_completion(&thread_exited);
+
+	crypto_device_remove(&pdev);
+	crypto_free_tfm(tfm);
+
+	dprintk(KERN_INFO "Test crypto provider module %s is unloaded.\n", pdev.name);
+}
+
+module_init(prov_init);
+module_exit(prov_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <[email-masked]>");
+MODULE_DESCRIPTION("Test crypto module provider.");
Binary files /tmp/empty/.crypto_provider.c.swp and linux-2.6/drivers/acrypto/.crypto_provider.c.swp differ
diff -Nru /tmp/empty/crypto_route.h linux-2.6/drivers/acrypto/crypto_route.h
--- /tmp/empty/crypto_route.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_route.h	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,242 @@
+/*
+ * 	crypto_route.h
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __CRYPTO_ROUTE_H
+#define __CRYPTO_ROUTE_H
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "acrypto.h"
+
+static inline struct crypto_route *crypto_route_alloc_direct(struct crypto_device *dev,
+							     struct crypto_session_initializer *ci)
+{
+	struct crypto_route *rt;
+
+	rt = kmalloc(sizeof(*rt), GFP_ATOMIC);
+	if (!rt) {
+		crypto_device_put(dev);
+		return NULL;
+	}
+
+	memset(rt, 0, sizeof(*rt));
+	memcpy(&rt->ci, ci, sizeof(*ci));
+
+	rt->dev = dev;
+
+	return rt;
+}
+
+static inline struct crypto_route *crypto_route_alloc(struct crypto_device *dev,
+							struct crypto_session_initializer *ci)
+{
+	struct crypto_route *rt;
+
+	if (!match_initializer(dev, ci))
+		return NULL;
+
+	rt = crypto_route_alloc_direct(dev, ci);
+
+	return rt;
+}
+
+static inline void crypto_route_free(struct crypto_route *rt)
+{
+	crypto_device_put(rt->dev);
+	rt->dev = NULL;
+	kfree(rt);
+}
+
+static inline void __crypto_route_del(struct crypto_route *rt, struct crypto_route_head *list)
+{
+	struct crypto_route *next, *prev;
+
+	list->qlen--;
+	next = rt->next;
+	prev = rt->prev;
+	rt->next = rt->prev = NULL;
+	rt->list = NULL;
+	next->prev = prev;
+	prev->next = next;
+}
+
+static inline void crypto_route_del(struct crypto_route *rt)
+{
+	struct crypto_route_head *list = rt->list;
+
+	if (list) {
+		spin_lock_irq(&list->lock);
+		if (list == rt->list)
+			__crypto_route_del(rt, rt->list);
+		spin_unlock_irq(&list->lock);
+
+		crypto_route_free(rt);
+	}
+}
+
+static inline struct crypto_route *__crypto_route_dequeue(struct crypto_route_head *list)
+{
+	struct crypto_route *next, *prev, *result;
+
+	prev = (struct crypto_route *)list;
+	next = prev->next;
+	result = NULL;
+	if (next != prev) {
+		result = next;
+		next = next->next;
+		list->qlen--;
+		next->prev = prev;
+		prev->next = next;
+		result->next = result->prev = NULL;
+		result->list = NULL;
+	}
+	return result;
+}
+
+static inline struct crypto_route *crypto_route_dequeue(struct crypto_session *s)
+{
+	struct crypto_route *rt;
+
+	spin_lock_irq(&s->route_list.lock);
+
+	rt = __crypto_route_dequeue(&s->route_list);
+
+	spin_unlock_irq(&s->route_list.lock);
+
+	return rt;
+}
+
+static inline void __crypto_route_queue(struct crypto_route *rt, struct crypto_route_head *list)
+{
+	struct crypto_route *prev, *next;
+
+	rt->list = list;
+	list->qlen++;
+	next = (struct crypto_route *)list;
+	prev = next->prev;
+	rt->next = next;
+	rt->prev = prev;
+	next->prev = prev->next = rt;
+}
+
+static inline void crypto_route_queue(struct crypto_route *rt, struct crypto_session *s)
+{
+
+	spin_lock_irq(&s->route_list.lock);
+
+	__crypto_route_queue(rt, &s->route_list);
+
+	spin_unlock_irq(&s->route_list.lock);
+}
+
+static inline int crypto_route_add(struct crypto_device *dev, struct crypto_session *s, 
+						struct crypto_session_initializer *ci)
+{
+	struct crypto_route *rt;
+
+	rt = crypto_route_alloc(dev, ci);
+	if (!rt)
+		return -ENOMEM;
+
+	crypto_route_queue(rt, s);
+
+	return 0;
+}
+
+static inline int crypto_route_add_direct(struct crypto_device *dev, struct crypto_session *s,
+						struct crypto_session_initializer *ci)
+{
+	struct crypto_route *rt;
+
+	rt = crypto_route_alloc_direct(dev, ci);
+	if (!rt)
+		return -ENOMEM;
+
+	crypto_route_queue(rt, s);
+
+	return 0;
+}
+
+static inline int crypto_route_queue_len(struct crypto_session *s)
+{
+	return s->route_list.qlen;
+}
+
+static inline void crypto_route_head_init(struct crypto_route_head *list)
+{
+	spin_lock_init(&list->lock);
+	list->prev = list->next = (struct crypto_route *)list;
+	list->qlen = 0;
+}
+
+static inline struct crypto_route *__crypto_route_current(struct crypto_route_head *list)
+{
+	struct crypto_route *next, *prev, *result;
+
+	prev = (struct crypto_route *)list;
+	next = prev->next;
+	result = NULL;
+	if (next != prev)
+		result = next;
+
+	return result;
+}
+
+static inline struct crypto_route *crypto_route_current(struct crypto_session *s)
+{
+	struct crypto_route_head *list;
+	struct crypto_route *rt = NULL;
+
+	list = &s->route_list;
+
+	if (list) {
+		spin_lock_irq(&list->lock);
+
+		rt = __crypto_route_current(list);
+
+		spin_unlock_irq(&list->lock);
+	}
+
+	return rt;
+}
+
+static inline struct crypto_device *crypto_route_get_current_device(struct crypto_session *s)
+{
+	struct crypto_route *rt = NULL;
+	struct crypto_device *dev = NULL;
+	struct crypto_route_head *list = &s->route_list;
+
+	spin_lock_irq(&list->lock);
+
+	rt = __crypto_route_current(list);
+	if (rt) {
+		dev = rt->dev;
+		crypto_device_get(dev);
+	}
+
+	spin_unlock_irq(&list->lock);
+
+	return dev;
+}
+
+#endif				/* __CRYPTO_ROUTE_H */
diff -Nru /tmp/empty/crypto_stat.c linux-2.6/drivers/acrypto/crypto_stat.c
--- /tmp/empty/crypto_stat.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_stat.c	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,100 @@
+/*
+ * 	crypto_stat.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#include "acrypto.h"
+#include "crypto_route.h"
+
+void crypto_stat_start_inc(struct crypto_session *s)
+{
+	struct crypto_device *dev;
+
+	dev = crypto_route_get_current_device(s);
+	if (dev) {
+		spin_lock_irq(&dev->stat_lock);
+		dev->stat.sstarted++;
+		spin_unlock_irq(&dev->stat_lock);
+
+		crypto_device_put(dev);
+	}
+}
+
+void crypto_stat_finish_inc(struct crypto_session *s)
+{
+	struct crypto_device *dev;
+
+	dev = crypto_route_get_current_device(s);
+	if (dev) {
+		spin_lock_irq(&dev->stat_lock);
+		dev->stat.sfinished++;
+		spin_unlock_irq(&dev->stat_lock);
+
+		crypto_device_put(dev);
+	}
+}
+
+void crypto_stat_complete_inc(struct crypto_session *s)
+{
+	struct crypto_device *dev;
+
+	dev = crypto_route_get_current_device(s);
+	if (dev) {
+		spin_lock_irq(&dev->stat_lock);
+		dev->stat.scompleted++;
+		spin_unlock_irq(&dev->stat_lock);
+
+		crypto_device_put(dev);
+	}
+}
+
+void crypto_stat_ptime_inc(struct crypto_session *s)
+{
+	struct crypto_device *dev;
+
+	dev = crypto_route_get_current_device(s);
+	if (dev) {
+		int i;
+
+		spin_lock_irq(&dev->stat_lock);
+		for (i = 0; i < dev->cap_number; ++i) {
+			if (__match_initializer(&dev->cap[i], &s->ci)) {
+				dev->cap[i].ptime += s->ci.ptime;
+				dev->cap[i].scomp++;
+				break;
+			}
+		}
+		spin_unlock_irq(&dev->stat_lock);
+
+		crypto_device_put(dev);
+	}
+}
+
+EXPORT_SYMBOL(crypto_stat_start_inc);
+EXPORT_SYMBOL(crypto_stat_finish_inc);
+EXPORT_SYMBOL(crypto_stat_complete_inc);
diff -Nru /tmp/empty/crypto_stat.h linux-2.6/drivers/acrypto/crypto_stat.h
--- /tmp/empty/crypto_stat.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/crypto_stat.h	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,32 @@
+/*
+ * 	crypto_stat.h
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __CRYPTO_STAT_H
+#define __CRYPTO_STAT_H
+
+#include "acrypto.h"
+
+void crypto_stat_start_inc(struct crypto_session *s);
+void crypto_stat_finish_inc(struct crypto_session *s);
+void crypto_stat_complete_inc(struct crypto_session *s);
+void crypto_stat_ptime_inc(struct crypto_session *s);
+
+#endif				/* __CRYPTO_STAT_H */
diff -Nru /tmp/empty/Kconfig linux-2.6/drivers/acrypto/Kconfig
--- /tmp/empty/Kconfig	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/Kconfig	2004-12-14 18:04:35.000000000 +0300
@@ -0,0 +1,40 @@
+menu "Asynchronous crypto layer"
+
+config ACRYPTO
+	tristate "Asyncronous crypto layer"
+	depends on CONNECTOR
+	help
+		Say Y here if you want to use new Linux asynchronous crypto layer.
+		This support is also available as a module.  If so, the module 
+		will be called acrypto.ko.
+
+config SIMPLE_LB
+	tristate "Simple asyncronous crypto load balancer"
+	depends on ACRYPTO
+	help
+		Say Y here if you want to compile simple load balancer which takes into
+		account only crypto device's queue length.
+		This support is also available as a module.  If so, the module 
+		will be called simpl_lb.ko.
+
+config TEST_CRYPTO_PROVIDER
+	tristate "Simple crypto provider"
+	depends on CRYPTO && ACRYPTO
+	help
+		Say Y here if you want to compile test bridge between 
+		synchronous crypto layer and asyncronous one.
+		It supports only AES-128-ECB.
+		This support is also available as a module.  If so, the module 
+		will be called crypto_provider.ko.
+
+config TEST_CRYPTO_CONSUMER
+	tristate "Simple crypto consumer"
+	depends on ACRYPTO
+	help
+		Say Y here if you want to compile test crypto consumer
+		which injects crypto session one time per jiffie.
+		It supports only AES-128-ECB.
+		This support is also available as a module.  If so, the module 
+		will be called consumer.ko.
+
+endmenu
diff -Nru /tmp/empty/Makefile linux-2.6/drivers/acrypto/Makefile
--- /tmp/empty/Makefile	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/Makefile	2004-12-14 17:52:25.000000000 +0300
@@ -0,0 +1,6 @@
+obj-$(CONFIG_ACRYPTO)			+= acrypto.o
+obj-$(CONFIG_SIMPLE_LB)			+= simple_lb.o
+obj-$(CONFIG_TEST_CRYPTO_PROVIDER)	+= crypto_provider.o
+obj-$(CONFIG_TEST_CRYPTO_CONSUMER)	+= consumer.o
+
+acrypto-objs	:= crypto_main.o crypto_lb.o crypto_dev.o crypto_conn.o crypto_stat.o
diff -Nru /tmp/empty/simple_lb.c linux-2.6/drivers/acrypto/simple_lb.c
--- /tmp/empty/simple_lb.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/acrypto/simple_lb.c	2004-12-14 18:53:11.000000000 +0300
@@ -0,0 +1,91 @@
+/*
+ * 	simple_lb.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <[email-masked]>
+ * 
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "crypto_lb.h"
+
+static void simple_lb_rehash(struct crypto_lb *);
+static struct crypto_device *simple_lb_find_device(struct crypto_lb *,
+						   struct
+						   crypto_session_initializer *,
+						   struct crypto_data *);
+
+struct crypto_lb simple_lb = {
+	.name = "simple_lb",
+	.rehash = simple_lb_rehash,
+	.find_device = simple_lb_find_device
+};
+
+static void simple_lb_rehash(struct crypto_lb *lb)
+{
+}
+
+static struct crypto_device *simple_lb_find_device(struct crypto_lb *lb,
+						   struct
+						   crypto_session_initializer
+						   *ci,
+						   struct crypto_data *data)
+{
+	struct crypto_device *dev, *__dev;
+	int min = 0x7ffffff;
+
+	__dev = NULL;
+	list_for_each_entry(dev, lb->crypto_device_list, cdev_entry) {
+		if (device_broken(dev))
+			continue;
+		if (!match_initializer(dev, ci))
+			continue;
+
+		if (atomic_read(&dev->refcnt) < min) {
+			min = atomic_read(&dev->refcnt);
+			__dev = dev;
+		}
+	}
+
+	return __dev;
+}
+
+int __devinit simple_lb_init(void)
+{
+	dprintk(KERN_INFO "Registering simple crypto load balancer.\n");
+
+	return crypto_lb_register(&simple_lb, 1, 1);
+}
+
+void __devexit simple_lb_fini(void)
+{
+	dprintk(KERN_INFO "Unregistering simple crypto load balancer.\n");
+
+	crypto_lb_unregister(&simple_lb);
+}
+
+module_init(simple_lb_init);
+module_exit(simple_lb_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <[email-masked]>");
+MODULE_DESCRIPTION("Simple crypto load balancer.");
diff -Nru /tmp/empty/Kconfig linux-2.6/drivers/connector/Kconfig
--- /tmp/empty/Kconfig	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/connector/Kconfig	2004-09-26 00:12:57.000000000 +0400
@@ -0,0 +1,13 @@
+menu "Connector - unified userspace <-> kernelspace linker"
+
+config CONNECTOR
+	tristate "Connector - unified userspace <-> kernelspace linker"
+	depends on NET
+	---help---
+	  This is unified userspace <-> kernelspace connector working on top
+	  of the netlink socket protocol.
+
+	  Connector support can also be built as a module.  If so, the module
+	  will be called connector.ko.
+
+endmenu
diff -Nru /tmp/empty/Makefile linux-2.6/drivers/connector/Makefile
--- /tmp/empty/Makefile	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/connector/Makefile	2004-09-26 00:12:57.000000000 +0400
@@ -0,0 +1,2 @@
+obj-$(CONFIG_CONNECTOR)		+= cn.o
+cn-objs		:= cn_queue.o connector.o
diff -Nru /tmp/empty/cn_queue.c linux-2.6/drivers/connector/cn_queue.c
--- /tmp/empty/cn_queue.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/connector/cn_queue.c	2004-09-26 21:50:12.000000000 +0400
@@ -0,0 +1,219 @@
+/*
+ * 	cn_queue.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <[email-masked]>
+ * All rights reserved.
+ * 
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/suspend.h>
+
+#include "cn_queue.h"
+
+static void cn_queue_wrapper(void *data)
+{
+	struct cn_callback_entry *cbq = (struct cn_callback_entry *)data;
+
+	atomic_inc(&cbq->cb->refcnt);
+	cbq->cb->callback(cbq->cb->priv);
+	atomic_dec(&cbq->cb->refcnt);
+
+	cbq->destruct_data(cbq->ddata);
+}
+
+static struct cn_callback_entry *cn_queue_alloc_callback_entry(struct
+							       cn_callback *cb)
+{
+	struct cn_callback_entry *cbq;
+
+	cbq = kmalloc(sizeof(*cbq), GFP_KERNEL);
+	if (!cbq) {
+		printk(KERN_ERR "Failed to create new callback queue.\n");
+		return NULL;
+	}
+
+	memset(cbq, 0, sizeof(*cbq));
+
+	cbq->cb = cb;
+
+	INIT_WORK(&cbq->work, &cn_queue_wrapper, cbq);
+
+	return cbq;
+}
+
+static void cn_queue_free_callback(struct cn_callback_entry *cbq)
+{
+	cancel_delayed_work(&cbq->work);
+
+	while (atomic_read(&cbq->cb->refcnt)) {
+		printk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n",
+		       cbq->pdev->name, atomic_read(&cbq->cb->refcnt));
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ);
+
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+
+		if (signal_pending(current))
+			flush_signals(current);
+	}
+
+	kfree(cbq);
+}
+
+int cn_cb_equal(struct cb_id *i1, struct cb_id *i2)
+{
+	return ((i1->idx == i2->idx) && (i1->val == i2->val));
+}
+
+int cn_queue_add_callback(struct cn_queue_dev *dev, struct cn_callback *cb)
+{
+	struct cn_callback_entry *cbq, *n, *__cbq;
+	int found = 0;
+
+	cbq = cn_queue_alloc_callback_entry(cb);
+	if (!cbq)
+		return -ENOMEM;
+
+	atomic_inc(&dev->refcnt);
+	cbq->pdev = dev;
+
+	spin_lock(&dev->queue_lock);
+	list_for_each_entry_safe(__cbq, n, &dev->queue_list, callback_entry) {
+		if (cn_cb_equal(&__cbq->cb->id, &cb->id)) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		atomic_set(&cbq->cb->refcnt, 1);
+		list_add_tail(&cbq->callback_entry, &dev->queue_list);
+	}
+	spin_unlock(&dev->queue_lock);
+
+	if (found) {
+		atomic_dec(&dev->refcnt);
+		atomic_set(&cbq->cb->refcnt, 0);
+		cn_queue_free_callback(cbq);
+		return -EINVAL;
+	}
+
+	cbq->nls = dev->nls;
+	cbq->seq = 0;
+	//cbq->group = ++dev->netlink_groups;
+	cbq->group = cbq->cb->id.idx;
+
+	return 0;
+}
+
+void cn_queue_del_callback(struct cn_queue_dev *dev, struct cn_callback *cb)
+{
+	struct cn_callback_entry *cbq = NULL, *n;
+	int found = 0;
+
+	spin_lock(&dev->queue_lock);
+	list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) {
+		if (cn_cb_equal(&cbq->cb->id, &cb->id)) {
+			list_del(&cbq->callback_entry);
+			found = 1;
+			break;
+		}
+	}
+	spin_unlock(&dev->queue_lock);
+
+	if (found) {
+		atomic_dec(&cbq->cb->refcnt);
+		cn_queue_free_callback(cbq);
+		atomic_dec(&dev->refcnt);
+	}
+}
+
+struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls)
+{
+	struct cn_queue_dev *dev;
+
+	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		printk(KERN_ERR "%s: Failed to allocte new struct cn_queue_dev.\n",
+		       name);
+		return NULL;
+	}
+
+	memset(dev, 0, sizeof(*dev));
+
+	snprintf(dev->name, sizeof(dev->name), "%s", name);
+
+	atomic_set(&dev->refcnt, 0);
+	INIT_LIST_HEAD(&dev->queue_list);
+	spin_lock_init(&dev->queue_lock);
+
+	dev->nls = nls;
+	dev->netlink_groups = 0;
+
+	dev->cn_queue = create_workqueue(dev->name);
+	if (!dev->cn_queue) {
+		printk(KERN_ERR "Failed to create %s queue.\n", dev->name);
+		kfree(dev);
+		return NULL;
+	}
+
+	return dev;
+}
+
+void cn_queue_free_dev(struct cn_queue_dev *dev)
+{
+	struct cn_callback_entry *cbq, *n;
+
+	flush_workqueue(dev->cn_queue);
+	destroy_workqueue(dev->cn_queue);
+
+	spin_lock(&dev->queue_lock);
+	list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) {
+		list_del(&cbq->callback_entry);
+		atomic_dec(&cbq->cb->refcnt);
+	}
+	spin_unlock(&dev->queue_lock);
+
+	while (atomic_read(&dev->refcnt)) {
+		printk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n",
+		       dev->name, atomic_read(&dev->refcnt));
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ);
+
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+
+		if (signal_pending(current))
+			flush_signals(current);
+	}
+
+	memset(dev, 0, sizeof(*dev));
+	kfree(dev);
+	dev = NULL;
+}
+
+EXPORT_SYMBOL(cn_queue_add_callback);
+EXPORT_SYMBOL(cn_queue_del_callback);
+EXPORT_SYMBOL(cn_queue_alloc_dev);
+EXPORT_SYMBOL(cn_queue_free_dev);
diff -Nru /tmp/empty/cn_queue.h linux-2.6/drivers/connector/cn_queue.h
--- /tmp/empty/cn_queue.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/connector/cn_queue.h	2004-09-26 00:14:16.000000000 +0400
@@ -0,0 +1,90 @@
+/*
+ * 	cn_queue.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <[email-masked]>
+ * All rights reserved.
+ * 
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __CN_QUEUE_H
+#define __CN_QUEUE_H
+
+#include <asm/types.h>
+
+struct cb_id
+{
+	__u32			idx;
+	__u32			val;
+};
+
+#ifdef __KERNEL__
+
+#include <asm/atomic.h>
+
+#include <linux/list.h>
+#include <linux/workqueue.h>
+
+#define CN_CBQ_NAMELEN		32
+
+struct cn_queue_dev
+{
+	atomic_t		refcnt;
+	unsigned char		name[CN_CBQ_NAMELEN];
+
+	struct workqueue_struct	*cn_queue;
+	
+	struct list_head 	queue_list;
+	spinlock_t 		queue_lock;
+
+	int			netlink_groups;
+	struct sock		*nls;
+};
+
+struct cn_callback
+{
+	unsigned char		name[CN_CBQ_NAMELEN];
+	
+	struct cb_id		id;
+	void			(* callback)(void *);
+	void			*priv;
+	
+	atomic_t		refcnt;
+};
+
+struct cn_callback_entry
+{
+	struct list_head	callback_entry;
+	struct cn_callback	*cb;
+	struct work_struct	work;
+	struct cn_queue_dev	*pdev;
+	
+	void			(* destruct_data)(void *);
+	void			*ddata;
+
+	int			seq, group;
+	struct sock		*nls;
+};
+
+int cn_queue_add_callback(struct cn_queue_dev *dev, struct cn_callback *cb);
+void cn_queue_del_callback(struct cn_queue_dev *dev, struct cn_callback *cb);
+
+struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *);
+void cn_queue_free_dev(struct cn_queue_dev *dev);
+
+int cn_cb_equal(struct cb_id *, struct cb_id *);
+
+#endif /* __KERNEL__ */
+#endif /* __CN_QUEUE_H */
diff -Nru /tmp/empty/connector.c linux-2.6/drivers/connector/connector.c
--- /tmp/empty/connector.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/connector/connector.c	2004-09-26 21:52:02.000000000 +0400
@@ -0,0 +1,498 @@
+/*
+ * 	connector.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <[email-masked]>
+ * All rights reserved.
+ * 
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/moduleparam.h>
+
+#include <net/sock.h>
+
+#include "../connector/connector.h"
+#include "../connector/cn_queue.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <[email-masked]>");
+MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector.");
+
+static int unit = NETLINK_NFLOG;
+static u32 cn_idx = -1;
+static u32 cn_val = -1;
+
+module_param(unit, int, 0);
+module_param(cn_idx, uint, 0);
+module_param(cn_val, uint, 0);
+
+spinlock_t notify_lock = SPIN_LOCK_UNLOCKED;
+static LIST_HEAD(notify_list);
+
+static struct cn_dev cdev;
+
+/*
+ * msg->seq and msg->ack are used to determine message genealogy.
+ * When someone sends message it puts there locally unique sequence 
+ * and random acknowledge numbers.
+ * Sequence number may be copied into nlmsghdr->nlmsg_seq too.
+ *
+ * Sequence number is incremented with each message to be sent.
+ *
+ * If we expect reply to our message, 
+ * then sequence number in received message MUST be the same as in original message,
+ * and acknowledge number MUST be the same + 1.
+ *
+ * If we receive message and it's sequence number is not equal to one we are expecting, 
+ * then it is new message.
+ * If we receive message and it's sequence number is the same as one we are expecting,
+ * but it's acknowledge is not equal acknowledge number in original message + 1,
+ * then it is new message.
+ *
+ */
+void cn_netlink_send(struct cn_msg *msg, u32 __groups)
+{
+	struct cn_callback_entry *n, *__cbq;
+	unsigned int size;
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	struct cn_msg *data;
+	struct cn_dev *dev = &cdev;
+	u32 groups = 0;
+	int found = 0;
+
+	if (!__groups)
+	{
+		spin_lock(&dev->cbdev->queue_lock);
+		list_for_each_entry_safe(__cbq, n, &dev->cbdev->queue_list, callback_entry) {
+			if (cn_cb_equal(&__cbq->cb->id, &msg->id)) {
+				found = 1;
+				groups = __cbq->group;
+			}
+		}
+		spin_unlock(&dev->cbdev->queue_lock);
+
+		if (!found) {
+			printk(KERN_ERR "Failed to find multicast netlink group for callback[0x%x.0x%x]. seq=%u\n",
+			       msg->id.idx, msg->id.val, msg->seq);
+			return;
+		}
+	}
+	else
+		groups = __groups;
+
+	size = NLMSG_SPACE(sizeof(*msg) + msg->len);
+
+	skb = alloc_skb(size, GFP_ATOMIC);
+	if (!skb) {
+		printk(KERN_ERR "Failed to allocate new skb with size=%u.\n", size);
+		return;
+	}
+
+	nlh = NLMSG_PUT(skb, 0, msg->seq, NLMSG_DONE, size - sizeof(*nlh));
+
+	data = (struct cn_msg *)NLMSG_DATA(nlh);
+
+	memcpy(data, msg, sizeof(*data) + msg->len);
+#if 0
+	printk("%s: len=%u, seq=%u, ack=%u, group=%u.\n",
+	       __func__, msg->len, msg->seq, msg->ack, groups);
+#endif
+	NETLINK_CB(skb).dst_groups = groups;
+	netlink_broadcast(dev->nls, skb, 0, groups, GFP_ATOMIC);
+
+	return;
+
+      nlmsg_failure:
+	printk(KERN_ERR "Failed to send %u.%u\n", msg->seq, msg->ack);
+	kfree_skb(skb);
+	return;
+}
+
+static int cn_call_callback(struct cn_msg *msg, void (*destruct_data) (void *), void *data)
+{
+	struct cn_callback_entry *n, *__cbq;
+	struct cn_dev *dev = &cdev;
+	int found = 0;
+
+	spin_lock(&dev->cbdev->queue_lock);
+	list_for_each_entry_safe(__cbq, n, &dev->cbdev->queue_list, callback_entry) {
+		if (cn_cb_equal(&__cbq->cb->id, &msg->id)) {
+			__cbq->cb->priv = msg;
+
+			__cbq->ddata = data;
+			__cbq->destruct_data = destruct_data;
+
+			queue_work(dev->cbdev->cn_queue, &__cbq->work);
+			found = 1;
+			break;
+		}
+	}
+	spin_unlock(&dev->cbdev->queue_lock);
+
+	return found;
+}
+
+static int __cn_rx_skb(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+	u32 pid, uid, seq, group;
+	struct cn_msg *msg;
+
+	pid = NETLINK_CREDS(skb)->pid;
+	uid = NETLINK_CREDS(skb)->uid;
+	seq = nlh->nlmsg_seq;
+	group = NETLINK_CB((skb)).groups;
+	msg = (struct cn_msg *)NLMSG_DATA(nlh);
+
+	if (msg->len != nlh->nlmsg_len - sizeof(*msg) - sizeof(*nlh)) {
+		printk(KERN_ERR "skb does not have enough length: "
+				"requested msg->len=%u[%u], nlh->nlmsg_len=%u[%u], skb->len=%u[must be %u].\n", 
+				msg->len, NLMSG_SPACE(msg->len), 
+				nlh->nlmsg_len, nlh->nlmsg_len - sizeof(*nlh),
+				skb->len, msg->len + sizeof(*msg));
+		return -EINVAL;
+	}
+#if 0
+	printk(KERN_INFO "pid=%u, uid=%u, seq=%u, group=%u.\n",
+	       pid, uid, seq, group);
+#endif
+	return cn_call_callback(msg, (void (*)(void *))kfree_skb, skb);
+}
+
+static void cn_rx_skb(struct sk_buff *__skb)
+{
+	struct nlmsghdr *nlh;
+	u32 len;
+	int err;
+	struct sk_buff *skb;
+
+	skb = skb_get(__skb);
+	if (!skb) {
+		printk(KERN_ERR "Failed to reference an skb.\n");
+		return;
+	}
+#if 0
+	printk(KERN_INFO
+	       "skb: len=%u, data_len=%u, truesize=%u, proto=%u, cloned=%d, shared=%d.\n",
+	       skb->len, skb->data_len, skb->truesize, skb->protocol,
+	       skb_cloned(skb), skb_shared(skb));
+#endif
+	while (skb->len >= NLMSG_SPACE(0)) {
+		nlh = (struct nlmsghdr *)skb->data;
+		if (nlh->nlmsg_len < sizeof(struct cn_msg) ||
+		    skb->len < nlh->nlmsg_len ||
+		    nlh->nlmsg_len > CONNECTOR_MAX_MSG_SIZE) {
+			printk(KERN_INFO "nlmsg_len=%u, sizeof(*nlh)=%u\n",
+			       nlh->nlmsg_len, sizeof(*nlh));
+			break;
+		}
+
+		len = NLMSG_ALIGN(nlh->nlmsg_len);
+		if (len > skb->len)
+			len = skb->len;
+
+		err = __cn_rx_skb(skb, nlh);
+		if (err) {
+#if 0
+			if (err < 0 && (nlh->nlmsg_flags & NLM_F_ACK))
+				netlink_ack(skb, nlh, -err);
+#endif
+			kfree_skb(skb);
+			break;
+		} else {
+#if 0
+			if (nlh->nlmsg_flags & NLM_F_ACK)
+				netlink_ack(skb, nlh, 0);
+#endif
+			kfree_skb(skb);
+			break;
+		}
+		skb_pull(skb, len);
+	}
+}
+
+static void cn_input(struct sock *sk, int len)
+{
+	struct sk_buff *skb;
+
+	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL)
+		cn_rx_skb(skb);
+}
+
+static void cn_notify(struct cb_id *id, u32 notify_event)
+{
+	struct cn_ctl_entry *ent;
+
+	spin_lock(&notify_lock);
+	list_for_each_entry(ent, &notify_list, notify_entry) {
+		int i;
+		struct cn_notify_req *req;
+		struct cn_ctl_msg *ctl = ent->msg;
+		int a, b;
+
+		a = b = 0;
+		
+		req = (struct cn_notify_req *)ctl->data;
+		for (i=0; i<ctl->idx_notify_num; ++i, ++req) {
+			if (id->idx >= req->first && id->idx < req->first + req->range) {
+				a = 1;
+				break;
+			}
+		}
+		
+		for (i=0; i<ctl->val_notify_num; ++i, ++req) {
+			if (id->val >= req->first && id->val < req->first + req->range) {
+				b = 1;
+				break;
+			}
+		}
+
+		if (a && b) {
+			struct cn_msg m;
+			
+			printk(KERN_INFO "Notifying group %x with event %u about %x.%x.\n", 
+					ctl->group, notify_event, 
+					id->idx, id->val);
+
+			memset(&m, 0, sizeof(m));
+			m.ack = notify_event;
+
+			memcpy(&m.id, id, sizeof(m.id));
+			cn_netlink_send(&m, ctl->group);
+		}
+	}
+	spin_unlock(&notify_lock);
+}
+
+int cn_add_callback(struct cb_id *id, char *name, void (*callback) (void *))
+{
+	int err;
+	struct cn_dev *dev = &cdev;
+	struct cn_callback *cb;
+
+	cb = kmalloc(sizeof(*cb), GFP_KERNEL);
+	if (!cb) {
+		printk(KERN_INFO "%s: Failed to allocate new struct cn_callback.\n",
+		       dev->cbdev->name);
+		return -ENOMEM;
+	}
+
+	memset(cb, 0, sizeof(*cb));
+
+	snprintf(cb->name, sizeof(cb->name), "%s", name);
+
+	memcpy(&cb->id, id, sizeof(cb->id));
+	cb->callback = callback;
+
+	atomic_set(&cb->refcnt, 0);
+
+	err = cn_queue_add_callback(dev->cbdev, cb);
+	if (err) {
+		kfree(cb);
+		return err;
+	}
+			
+	cn_notify(id, 0);
+
+	return 0;
+}
+
+void cn_del_callback(struct cb_id *id)
+{
+	struct cn_dev *dev = &cdev;
+	struct cn_callback_entry *n, *__cbq;
+
+	list_for_each_entry_safe(__cbq, n, &dev->cbdev->queue_list, callback_entry) {
+		if (cn_cb_equal(&__cbq->cb->id, id)) {
+			cn_queue_del_callback(dev->cbdev, __cbq->cb);
+			cn_notify(id, 1);
+			break;
+		}
+	}
+}
+
+static int cn_ctl_msg_equals(struct cn_ctl_msg *m1, struct cn_ctl_msg *m2)
+{
+	int i;
+	struct cn_notify_req *req1, *req2;
+
+	if (m1->idx_notify_num != m2->idx_notify_num)
+		return 0;
+	
+	if (m1->val_notify_num != m2->val_notify_num)
+		return 0;
+	
+	if (m1->len != m2->len)
+		return 0;
+
+	if ((m1->idx_notify_num + m1->val_notify_num)*sizeof(*req1) != m1->len) {
+		printk(KERN_ERR "Notify entry[idx_num=%x, val_num=%x, len=%u] contains garbage. Removing.\n", 
+				m1->idx_notify_num, m1->val_notify_num, m1->len);
+		return 1;
+	}
+
+	req1 = (struct cn_notify_req *)m1->data;
+	req2 = (struct cn_notify_req *)m2->data;
+	
+	for (i=0; i<m1->idx_notify_num; ++i) {
+		if (memcmp(req1, req2, sizeof(*req1)))
+			return 0;
+
+		req1++;
+		req2++;
+	}
+
+	for (i=0; i<m1->val_notify_num; ++i) {
+		if (memcmp(req1, req2, sizeof(*req1)))
+			return 0;
+
+		req1++;
+		req2++;
+	}
+
+	return 1;
+}
+
+static void cn_callback(void * data)
+{
+	struct cn_msg *msg = (struct cn_msg *)data;
+	struct cn_ctl_msg *ctl;
+	struct cn_ctl_entry *ent;
+	u32 size;
+ 
+	if (msg->len < sizeof(*ctl)) {
+		printk(KERN_ERR "Wrong connector request size %u, must be >= %u.\n", 
+				msg->len, sizeof(*ctl));
+		return;
+	}
+	
+	ctl = (struct cn_ctl_msg *)msg->data;
+
+	size = sizeof(*ctl) + (ctl->idx_notify_num + ctl->val_notify_num)*sizeof(struct cn_notify_req);
+
+	if (msg->len != size) {
+		printk(KERN_ERR "Wrong connector request size %u, must be == %u.\n", 
+				msg->len, size);
+		return;
+	}
+
+	if (ctl->len + sizeof(*ctl) != msg->len) {
+		printk(KERN_ERR "Wrong message: msg->len=%u must be equal to inner_len=%u [+%u].\n", 
+				msg->len, ctl->len, sizeof(*ctl));
+		return;
+	}
+
+	/*
+	 * Remove notification.
+	 */
+	if (ctl->group == 0) {
+		struct cn_ctl_entry *n;
+		
+		spin_lock(&notify_lock);
+		list_for_each_entry_safe(ent, n, &notify_list, notify_entry) {
+			if (cn_ctl_msg_equals(ent->msg, ctl)) {
+				list_del(&ent->notify_entry);
+				kfree(ent);
+			}
+		}
+		spin_unlock(&notify_lock);
+
+		return;
+	}
+
+	size += sizeof(*ent);
+
+	ent = kmalloc(size, GFP_ATOMIC);
+	if (!ent) {
+		printk(KERN_ERR "Failed to allocate %d bytes for new notify entry.\n", size);
+		return;
+	}
+
+	memset(ent, 0, size);
+
+	ent->msg = (struct cn_ctl_msg *)(ent + 1);
+
+	memcpy(ent->msg, ctl, size - sizeof(*ent));
+
+	spin_lock(&notify_lock);
+	list_add(&ent->notify_entry, &notify_list);
+	spin_unlock(&notify_lock);
+
+	{
+		int i;
+		struct cn_notify_req *req;
+	
+		printk("Notify group %x for idx: ", ctl->group);
+
+		req = (struct cn_notify_req *)ctl->data;
+		for (i=0; i<ctl->idx_notify_num; ++i, ++req) {
+			printk("%u-%u ", req->first, req->first+req->range-1);
+		}
+		
+		printk("\nNotify group %x for val: ", ctl->group);
+
+		for (i=0; i<ctl->val_notify_num; ++i, ++req) {
+			printk("%u-%u ", req->first, req->first+req->range-1);
+		}
+		printk("\n");
+	}
+}
+
+static int cn_init(void)
+{
+	struct cn_dev *dev = &cdev;
+
+	dev->input = cn_input;
+	dev->id.idx = cn_idx;
+	dev->id.val = cn_val;
+
+	dev->nls = netlink_kernel_create(unit, dev->input);
+	if (!dev->nls) {
+		printk(KERN_ERR "Failed to create new netlink socket(%u).\n",
+		       unit);
+		return -EIO;
+	}
+
+	dev->cbdev = cn_queue_alloc_dev("cqueue", dev->nls);
+	if (!dev->cbdev) {
+		if (dev->nls->sk_socket)
+			sock_release(dev->nls->sk_socket);
+		return -EINVAL;
+	}
+
+	return cn_add_callback(&dev->id, "connector", &cn_callback);
+}
+
+static void cn_fini(void)
+{
+	struct cn_dev *dev = &cdev;
+
+	cn_del_callback(&dev->id);
+	cn_queue_free_dev(dev->cbdev);
+	if (dev->nls->sk_socket)
+		sock_release(dev->nls->sk_socket);
+}
+
+module_init(cn_init);
+module_exit(cn_fini);
+
+EXPORT_SYMBOL(cn_add_callback);
+EXPORT_SYMBOL(cn_del_callback);
+EXPORT_SYMBOL(cn_netlink_send);
diff -Nru /tmp/empty/connector.h linux-2.6/drivers/connector/connector.h
--- /tmp/empty/connector.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/connector/connector.h	2004-09-26 00:14:16.000000000 +0400
@@ -0,0 +1,81 @@
+/*
+ * 	connector.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <[email-masked]>
+ * All rights reserved.
+ * 
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __CONNECTOR_H
+#define __CONNECTOR_H
+
+#include "../connector/cn_queue.h"
+
+#define CONNECTOR_MAX_MSG_SIZE 	1024
+
+struct cn_msg
+{
+	struct cb_id 		id;
+
+	__u32			seq;
+	__u32			ack;
+
+	__u32			len;		/* Length of the following data */
+	__u8			data[0];
+};
+
+struct cn_notify_req
+{
+	__u32			first;
+	__u32			range;
+};
+
+struct cn_ctl_msg
+{
+	__u32			idx_notify_num;
+	__u32			val_notify_num;
+	__u32			group;
+	__u32			len;
+	__u8			data[0];
+};
+
+#ifdef __KERNEL__
+
+#include <net/sock.h>
+
+struct cn_ctl_entry
+{
+	struct list_head	notify_entry;
+	struct cn_ctl_msg	*msg;
+};
+
+struct cn_dev
+{
+	struct cb_id 		id;
+
+	u32			seq, groups;
+	struct sock 		*nls;
+	void 			(*input)(struct sock *sk, int len);
+
+	struct cn_queue_dev	*cbdev;
+};
+
+int cn_add_callback(struct cb_id *, char *, void (* callback)(void *));
+void cn_del_callback(struct cb_id *);
+void cn_netlink_send(struct cn_msg *, u32);
+
+#endif /* __KERNEL__ */
+#endif /* __CONNECTOR_H */
--- linux-2.6/drivers/Kconfig.orig	2004-09-26 13:34:48.000000000 +0400
+++ linux-2.6/drivers/Kconfig	2004-09-26 13:34:57.000000000 +0400
@@ -44,6 +44,8 @@
 
 source "drivers/w1/Kconfig"
 
+source "drivers/connector/Kconfig"
+
 source "drivers/misc/Kconfig"
 
 source "drivers/media/Kconfig"

--- linux-2.6/drivers/Kconfig.nocrypto	2004-10-30 09:05:52.000000000 +0400
+++ linux-2.6/drivers/Kconfig	2004-10-30 09:06:11.000000000 +0400
@@ -42,6 +42,8 @@
 
 source "drivers/i2c/Kconfig"
 
+source "drivers/acrypto/Kconfig"
+
 source "drivers/w1/Kconfig"
 
 source "drivers/connector/Kconfig"
--- linux-2.6/drivers/Makefile.orig	2004-09-25 23:47:08.000000000 +0400
+++ linux-2.6/drivers/Makefile	2004-09-26 13:34:25.000000000 +0400
@@ -44,6 +44,7 @@
 obj-$(CONFIG_I2O)		+= message/
 obj-$(CONFIG_I2C)		+= i2c/
 obj-$(CONFIG_W1)		+= w1/
+obj-$(CONFIG_CONNECTOR)	+= connector/
 obj-$(CONFIG_PHONE)		+= telephony/
 obj-$(CONFIG_MD)		+= md/
 obj-$(CONFIG_BT)		+= bluetooth/

--- linux-2.6/drivers/Makefile.nocrypto	2004-10-30 09:05:02.000000000 +0400
+++ linux-2.6/drivers/Makefile	2004-10-30 09:05:31.000000000 +0400
@@ -49,5 +49,6 @@
 obj-$(CONFIG_GAMEPORT)		+= input/gameport/
 obj-$(CONFIG_I2O)		+= message/
 obj-$(CONFIG_I2C)		+= i2c/
+obj-$(CONFIG_ACRYPTO)		+= acrypto/
 obj-$(CONFIG_W1)		+= w1/
 obj-$(CONFIG_CONNECTOR)		+= connector/