Call for testers: Kernel connector update, SuperIO update.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Attached patches and changelogs for the latest connector and superio
drivers updates.

Patch against tree included here.
also attached userspace programm to communicate with superio module.

Please review, test and comment.

Little note: driver for access bus is not tested since in my Soekris
board ACB can not be detected with both old drivers and new one.

Signed-off-by: Evgeniy Polyakov <johnpol at 2ka.mipt.ru>


--- linux-2.6/drivers/Makefile~	2004-09-09 12:01:51.000000000 +0400
+++ linux-2.6/drivers/Makefile	2004-09-09 11:58:29.000000000 +0400
@@ -43,6 +43,8 @@
 obj-$(CONFIG_I2O)		+= message/
 obj-$(CONFIG_I2C)		+= i2c/
 obj-$(CONFIG_W1)		+= w1/
+obj-$(CONFIG_CONNECTOR)	+= connector/
+obj-$(CONFIG_SUPERIO)		+= superio/
 obj-$(CONFIG_PHONE)		+= telephony/
 obj-$(CONFIG_MD)		+= md/
 obj-$(CONFIG_BT)		+= bluetooth/


--- linux-2.6/drivers/Kconfig~	2004-09-09 12:01:29.000000000 +0400
+++ linux-2.6/drivers/Kconfig	2004-09-09 11:58:46.000000000 +0400
@@ -44,6 +44,10 @@
 
 source "drivers/w1/Kconfig"
 
+source "drivers/connector/Kconfig"
+
+source "drivers/superio/Kconfig"
+
 source "drivers/misc/Kconfig"
 
 source "drivers/media/Kconfig"


diff -Nru /tmp/empty/Kconfig linux-2.6/drivers/superio/Kconfig
--- /tmp/empty/Kconfig	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/Kconfig	2004-09-09 11:58:03.000000000 +0400
@@ -0,0 +1,34 @@
+menu "SuperIO subsystem support"
+
+config SC_SUPERIO
+	tristate "SuperIO subsystem support"
+	depends on CONNECTOR
+	help
+	  SuperIO subsystem support.
+
+config SC_PC8736X
+	tristate "PC8736x SuperIO"
+	depends on SC_SUPERIO
+	help
+	  Say Y here if you want to use PC8736x controller.
+
+config SC_SCX200
+	tristate "SCx200/SC1100 SuperIO"
+	depends on SC_SUPERIO
+	help
+	  Say Y here if you want to use SCx200/SC1100 controller.
+
+
+config SC_GPIO
+	tristate "SuperIO - GPIO"
+	depends on SC_SUPERIO
+	help
+	  Say Y here if you want to use GPIO pins.
+
+config SC_ACB
+	tristate "SuperIO - Access Bus"
+	depends on SC_SUPERIO
+	help
+	  Say Y here if you want to use Access Bus.
+
+endmenu
diff -Nru /tmp/empty/Kconfig~ linux-2.6/drivers/superio/Kconfig~
--- /tmp/empty/Kconfig~	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/Kconfig~	2004-09-09 11:54:40.000000000 +0400
@@ -0,0 +1,34 @@
+menu "SuperIO subsystem support"
+
+config SUPERIO
+	tristate "SuperIO subsystem support"
+	depends on CONNECTOR
+	---help---
+	  SuperIO subsystem support.
+
+config PC8736X
+	tristate "PC8736x SuperIO"
+	depends on SUPERIO
+	help
+	  Say Y here if you want to use PC8736x controller.
+
+config SCX200
+	tristate "SCx200/SC1100 SuperIO"
+	depends on SUPERIO
+	help
+	  Say Y here if you want to use SCx200/SC1100 controller.
+
+
+config SC_GPIO
+	tristate "SuperIO - GPIO"
+	depends on SUPERIO
+	help
+	  Say Y here if you want to use GPIO pins.
+
+config SC_ACB
+	tristate "SuperIO - Access Bus"
+	depends on SUPERIO
+	help
+	  Say Y here if you want to use Access Bus.
+
+endmenu
diff -Nru /tmp/empty/Makefile linux-2.6/drivers/superio/Makefile
--- /tmp/empty/Makefile	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/Makefile	2004-09-09 11:57:00.000000000 +0400
@@ -0,0 +1,11 @@
+#
+# Makefile for the SuperIO subsystem.
+#
+
+obj-$(CONFIG_SC_SUPERIO)	+= superio.o
+obj-$(CONFIG_SC_GPIO)		+= sc_gpio.o
+obj-$(CONFIG_SC_ACB)		+= sc_acb.o
+obj-$(CONFIG_SC_PC8736X)	+= pc8736x.o
+obj-$(CONFIG_SC_SCX200)		+= scx200.o
+
+superio-objs		:= sc.o chain.o sc_conn.o
diff -Nru /tmp/empty/Makefile~ linux-2.6/drivers/superio/Makefile~
--- /tmp/empty/Makefile~	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/Makefile~	2004-09-09 09:03:07.000000000 +0400
@@ -0,0 +1,15 @@
+#
+# Makefile for the Dallas's 1-wire bus.
+#
+
+obj-$(CONFIG_W1)	+= wire.o
+wire-objs		:= w1.o w1_int.o w1_family.o w1_netlink.o w1_io.o
+
+obj-$(CONFIG_W1_MATROX)		+= matrox_w1.o
+obj-$(CONFIG_W1_THERM)		+= w1_therm.o
+obj-$(CONFIG_W1_SOEKRIS)	+= superio.o
+obj-$(CONFIG_W1_SOEKRIS_GPIO)	+= sc_gpio.o
+obj-$(CONFIG_W1_SOEKRIS_GPIO_W1)+= sc_w1.o
+
+superio-objs		:= sc.o chain.o sc_conn.o
+obj-m				+= pc8736x.o pin_test.o scx200.o sc_acb.o
diff -Nru /tmp/empty/chain.c linux-2.6/drivers/superio/chain.c
--- /tmp/empty/chain.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/chain.c	2004-09-09 11:46:10.000000000 +0400
@@ -0,0 +1,53 @@
+/*
+ * 	chain.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "chain.h"
+
+struct dev_chain *chain_alloc(void *ptr)
+{
+	struct dev_chain *ch;
+
+	ch = kmalloc(sizeof(struct dev_chain), GFP_ATOMIC);
+	if (!ch)
+	{
+		printk(KERN_ERR "Failed to allocate new chain for %p.\n", ptr);
+		return NULL;
+	}
+
+	memset(ch, 0, sizeof(struct dev_chain));
+
+	ch->ptr = ptr;
+
+	return ch;
+}
+
+void chain_free(struct dev_chain *ch)
+{
+	memset(ch, 0, sizeof(struct dev_chain));
+	kfree(ch);
+}
diff -Nru /tmp/empty/chain.h linux-2.6/drivers/superio/chain.h
--- /tmp/empty/chain.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/chain.h	2004-09-09 11:46:10.000000000 +0400
@@ -0,0 +1,37 @@
+/*
+ * 	chain.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 __CHAIN_H
+#define __CHAIN_H
+
+#include <linux/list.h>
+
+struct dev_chain
+{
+	struct list_head	chain_entry;
+	void			*ptr;
+};
+
+struct dev_chain *chain_alloc(void *ptr);
+void chain_free(struct dev_chain *ch);
+
+#endif /* __CHAIN_H */
diff -Nru /tmp/empty/pc8736x.c linux-2.6/drivers/superio/pc8736x.c
--- /tmp/empty/pc8736x.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/pc8736x.c	2004-09-09 11:46:11.000000000 +0400
@@ -0,0 +1,212 @@
+/*
+ * 	pc8736x.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+
+#include "sc.h"
+#include "pc8736x.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol at 2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for PC87366 SuperIO chip.");
+
+static int pc8736x_probe(void *, unsigned long base);
+static int pc8736x_activate_one_logical(struct logical_dev *ldev);
+
+static struct sc_dev pc8736x_dev = 
+{
+	.name		"PC8736X",
+	.probe		pc8736x_probe,
+	.activate_one	pc8736x_activate_one_logical,
+	//.deactivate_one	pc8736x_deactivate_one,
+	//.read		pc8736x_read,
+	//.write	pc8736x_write,
+};
+
+static struct sc_chip_id pc8736x_sio_ids[] = 
+{
+	{"PC87360", 0xe1},
+	{"PC87363", 0xe8},
+	{"PC87364", 0xe4},
+	{"PC87365", 0xe5},
+	{"PC87366", 0xe9},
+};
+
+void pc8736x_write_reg(struct sc_dev *dev, u8 reg, u8 val)
+{
+	outb(reg, dev->base_index);
+	outb(val, dev->base_data);
+}
+
+u8 pc8736x_read_reg(struct sc_dev *dev, u8 reg)
+{
+	u8 val;
+		
+	outb(reg, dev->base_index);
+	val = inb(dev->base_data);
+
+	return val;
+}
+
+static int pc8736x_chip_index(u8 id)
+{
+	int i;
+
+	for (i=0; i<sizeof(pc8736x_sio_ids)/sizeof(pc8736x_sio_ids[0]); ++i)
+		if (pc8736x_sio_ids[i].id == id)
+			return i;
+
+	return -ENODEV;
+}
+
+static int pc8736x_probe(void *data, unsigned long base)
+{
+	unsigned long size = 2;
+	u8 id;
+	int chip_num;
+	struct sc_dev *dev = (struct sc_dev *)data;
+
+	/*
+	 * Special address to handle.
+	 */
+	if (base == 0)
+		return -ENODEV;
+	
+	dev->base_index = base;
+	dev->base_data = base + 1;
+
+	id = pc8736x_read_reg(dev, SIO_REG_SID);
+	chip_num = pc8736x_chip_index(id);
+
+	if (chip_num >= 0)
+	{
+		printk(KERN_INFO "Found %s [0x%x] at 0x%04lx-0x%04lx.\n", 
+				pc8736x_sio_ids[chip_num].name, pc8736x_sio_ids[chip_num].id, 
+				base, base+size-1);
+		return 0;
+	}
+
+	printk(KERN_INFO "Found nothing at 0x%04lx-0x%04lx.\n", base, base+size-1);
+
+	return -ENODEV;
+}
+
+static int pc8736x_activate_one_logical(struct logical_dev *ldev)
+{
+	int err;
+	struct sc_dev *dev = ldev->pdev;
+	u8 active;
+
+	pc8736x_write_reg(dev, SIO_REG_LDN, ldev->index);
+	active = pc8736x_read_reg(dev, SIO_REG_ACTIVE);
+       	if ((active & SIO_ACTIVE_EN) == 0)
+	{
+		printk(KERN_INFO "\t%16s - not activated at %x: activating... ", 
+				ldev->name, ldev->index);
+		
+		pc8736x_write_reg(dev, SIO_REG_ACTIVE, active | SIO_ACTIVE_EN);
+		active = pc8736x_read_reg(dev, SIO_REG_ACTIVE);
+		if ((active & SIO_ACTIVE_EN) == 0)
+		{
+			printk("failed.\n");
+			return -ENODEV;
+		}
+		printk("done\n");
+		/*printk("done. Resetting... ");
+		
+		pc8736x_write_reg(dev, SIO_REG_ACTIVE, active | SIO_RESET);
+		active = pc8736x_read_reg(dev, SIO_REG_ACTIVE);
+		printk("done [sio_cfg1=%x].\n", active);
+		*/
+	}
+
+	ldev->irq = pc8736x_read_reg(dev, SIO_REG_IRQ);
+	ldev->irq_type = pc8736x_read_reg(dev, SIO_REG_IRQ_TYPE);
+	ldev->base_addr = pc8736x_read_reg(dev, SIO_REG_IO_LSB);
+	ldev->base_addr |= (pc8736x_read_reg(dev, SIO_REG_IO_MSB) << 8);
+
+	{
+		int i;
+		u8 idx[] = {0x20, 0x21, 0x22, 0x60, 0x61, 0x62, 0x63};
+
+		for (i=0; i<sizeof(idx)/sizeof(idx[0]); ++i)
+			printk("%s: %s at %x: %x - %x\n", 
+					dev->name,
+					ldev->name, ldev->index,
+					idx[i], pc8736x_read_reg(dev, idx[i]));
+	}
+
+	err = ldev->activate(ldev);
+	if (err < 0)
+	{
+		printk(KERN_INFO "\t%16s - not activated: ->activate() failed with error code %d.\n", 
+				ldev->name, err);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "\t%16s - activated: 0x%04lx-0x%04lx, irq=%02x [type=%02x]\n", 
+			ldev->name, ldev->base_addr, ldev->base_addr + ldev->range,
+			ldev->irq, ldev->irq_type);
+
+	return 0;
+}
+
+static int pc8736x_init(void)
+{
+	int err;
+
+	err = sc_add_sc_dev(&pc8736x_dev);
+	if (err)
+		return err;
+
+	printk(KERN_INFO "Driver for %s SuperIO chip.\n", pc8736x_dev.name);
+	return 0;
+}
+
+static void pc8736x_fini(void)
+{
+	sc_del_sc_dev(&pc8736x_dev);
+
+	while (atomic_read(&pc8736x_dev.refcnt))
+	{
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ);
+	}
+}
+
+module_init(pc8736x_init);
+module_exit(pc8736x_fini);
+
+EXPORT_SYMBOL(pc8736x_write_reg);
+EXPORT_SYMBOL(pc8736x_read_reg);
diff -Nru /tmp/empty/pc8736x.h linux-2.6/drivers/superio/pc8736x.h
--- /tmp/empty/pc8736x.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/pc8736x.h	2004-09-09 11:46:11.000000000 +0400
@@ -0,0 +1,39 @@
+/*
+ * 	pc8736x.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 __PC8736X_H
+#define __PC8736X_H
+
+#define SIO_GPDO0		0x00
+#define SIO_GPDI0 		0x01
+#define SIO_GPEVEN0		0x02 
+#define SIO_GPEVST0		0x03 
+#define SIO_GPDO1 		0x04
+#define SIO_GPDI1 		0x05
+#define SIO_GPEVEN1		0x06 
+#define SIO_GPEVST1		0x07 
+#define SIO_GPDO2 		0x08
+#define SIO_GPDI2 		0x09
+#define SIO_GPDO3 		0x0A
+#define SIO_GPDI3 		0x0B
+
+#endif /* __PC8736X_H */
diff -Nru /tmp/empty/sc.c linux-2.6/drivers/superio/sc.c
--- /tmp/empty/sc.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/sc.c	2004-09-09 11:46:12.000000000 +0400
@@ -0,0 +1,777 @@
+/*
+ * 	sc.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+
+#include "sc.h"
+#include "sc_conn.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol at 2ka.mipt.ru>");
+MODULE_DESCRIPTION("Generic SuperIO driver.");
+
+static unsigned long base_addr[] = {0x2e, 0x4e};
+
+static spinlock_t sdev_lock = SPIN_LOCK_UNLOCKED;
+static LIST_HEAD(sdev_list);
+
+static spinlock_t ldev_lock = SPIN_LOCK_UNLOCKED;
+static LIST_HEAD(ldev_list);
+
+static int sc_activate_logical(struct sc_dev *, struct logical_dev *);
+static void sc_deactivate_logical(struct sc_dev *, struct logical_dev *);
+
+static int __devinit sc_init(void);
+static void __devexit sc_fini(void);
+
+static inline int sc_ldev_equal(struct logical_dev *l1, struct logical_dev *l2)
+{
+	int a, b;
+
+	a = b = 1;
+
+	a = (!strncmp(l1->name, l2->name, SC_NAME_LEN) && l1->index == l2->index);
+
+	if (sc_ldev_is_clone(l1) && sc_ldev_is_clone(l2))
+		b = (l1->base_addr == l2->base_addr);
+
+	return (a && b);
+}
+
+static inline int sc_ldev_equal_name(struct logical_dev *l1, struct logical_dev *l2)
+{
+	return (!strncmp(l1->name, l2->name, SC_NAME_LEN));
+}
+
+static inline int sc_sdev_equal(struct sc_dev *s1, struct sc_dev *s2)
+{
+	return (!strncmp(s1->name, s2->name, SC_NAME_LEN));
+}
+
+static void sc_del_sdev_from_ldev(struct sc_dev *sdev, struct logical_dev *ldev)
+{
+	struct sc_dev *__sdev;
+	struct dev_chain *ch, *n;
+
+	spin_lock(&ldev->chain_lock);
+
+	list_for_each_entry_safe(ch, n, &ldev->chain_list, chain_entry)
+	{
+		__sdev = ch->ptr;
+
+		if (sc_sdev_equal(__sdev, sdev))
+		{
+			list_del(&ch->chain_entry);
+			chain_free(ch);
+			atomic_dec(&__sdev->refcnt);
+			break;
+		}
+	}
+	
+	spin_unlock(&ldev->chain_lock);
+}
+
+static int __sc_add_logical_dev(struct sc_dev *dev, struct logical_dev *__ldev)
+{
+	int err;
+	struct logical_dev *ldev;
+	struct dev_chain *ch, *lch, *_ch;
+	int __found = 0;
+
+	err = 0;
+
+	list_for_each_entry(_ch, &dev->chain_list, chain_entry)
+	{
+		ldev = _ch->ptr;
+
+		if (sc_ldev_equal(ldev, __ldev))
+		{
+			printk(KERN_INFO "Logical device %s already registered in SuperIO chip %s.\n",
+					ldev->name, dev->name);
+			err++;
+			break;
+		}
+	}
+
+	if (err)
+	{
+		err = -ENODEV;
+		goto err_out;
+	}
+
+	if (!sc_ldev_is_clone(__ldev))
+	{
+		struct sc_chip_id *cid;
+
+		/*
+		 * SuperIO core is registering logical device. 
+		 * SuperIO chip knows where it must live.
+		 * If logical device being registered lives at the different location
+		 * (for example when it was registered for all devices, 
+		 * but has address(index) corresponding to only one SuperIO chip) 
+		 * then we will register new logical device with the same name 
+		 * but with the different location(index).
+		 *
+		 * It is called clone.
+		 */
+
+		for (cid = dev->ldevs; cid && strlen(cid->name); ++cid)
+		{
+			if (!strncmp(cid->name, __ldev->name, SC_NAME_LEN) && cid->id != __ldev->index)
+			{
+				struct logical_dev *clone;
+				
+				__found = 1;
+				
+				printk(KERN_INFO "Logical device %s in chip %s lives at %x, but provided address %x.\n"
+						"Registering new logical device %s in chip %s with address %x.\n",
+						__ldev->name, dev->name, cid->id, __ldev->index,
+						__ldev->name, dev->name, cid->id);
+
+				clone = sc_ldev_clone(__ldev);
+				if (!clone)
+				{
+					err = -ENOMEM;
+					continue;
+				}
+
+				/*
+				 * If logical device provided 0xFF index, than it is mean that
+				 * SuperIO chip driver must handle this situation.
+				 * It is similar to the zero base address in SuperIO ->probe() function.
+				 */
+
+				clone->index = cid->id;
+				
+				err = __sc_add_logical_dev(dev, clone);
+				if (err)
+					sc_ldev_unclone(clone);
+			}
+		}
+
+		if (__found)
+			return 0;
+	}
+
+	__ldev->pdev = dev;
+	err = sc_activate_logical(dev, __ldev);
+	if (err)
+	{
+		printk(KERN_INFO "Logical device %s is not found in SuperIO chip %s.\n",
+				__ldev->name, dev->name);
+		err = -EINVAL;
+		goto err_out;
+	}
+
+	ch = chain_alloc(dev);
+	if (!ch)
+	{
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	lch = chain_alloc(__ldev);
+	if (!lch)
+	{
+		err = -ENOMEM;
+		goto err_out_free_chain;
+	}
+
+	ch->ptr = dev;
+
+	spin_lock(&__ldev->chain_lock);
+	atomic_inc(&__ldev->refcnt);
+	list_add_tail(&ch->chain_entry, &__ldev->chain_list);
+	spin_unlock(&__ldev->chain_lock);
+
+	atomic_inc(&dev->refcnt);
+	list_add_tail(&lch->chain_entry, &dev->chain_list);
+	
+	__found = 0;
+	spin_lock(&ldev_lock);
+	list_for_each_entry(ldev, &ldev_list, ldev_entry)
+	{
+		if (sc_ldev_equal(ldev, __ldev))
+		{
+			__found = 1;
+			break;
+		}
+	}
+	
+	if (!__found)
+	{
+		list_add(&__ldev->ldev_entry, &ldev_list);
+	}
+	
+	spin_unlock(&ldev_lock);
+	
+	return 0;
+	
+	chain_free(lch);
+err_out_free_chain:
+	chain_free(ch);
+err_out:
+
+	return err;
+}
+
+int sc_add_logical_dev(struct sc_dev *sdev, struct logical_dev *__ldev)
+{
+	struct sc_dev *dev;
+	int err, found = 0;
+
+	printk(KERN_INFO "Adding logical device %s [%x] [%s].\n",
+			__ldev->name, __ldev->index, (sc_ldev_is_clone(__ldev))?"clone":"not clone");
+
+	spin_lock_init(&__ldev->chain_lock);
+	INIT_LIST_HEAD(&__ldev->chain_list);
+	
+	spin_lock_init(&__ldev->lock);
+
+	atomic_set(&__ldev->refcnt, 0);
+
+	if (sdev)
+	{
+		spin_lock(&sdev->lock);
+		spin_lock(&sdev->chain_lock);
+		err = __sc_add_logical_dev(sdev, __ldev);
+		spin_unlock(&sdev->chain_lock);
+		spin_unlock(&sdev->lock);
+
+		if (!err)
+			found = 1;
+
+		goto finish;
+	}
+
+	spin_lock(&sdev_lock);
+	list_for_each_entry(dev, &sdev_list, sdev_entry)
+	{
+		spin_lock(&dev->lock);
+		spin_lock(&dev->chain_lock);
+		err = __sc_add_logical_dev(dev, __ldev);
+		spin_unlock(&dev->chain_lock);
+		spin_unlock(&dev->lock);
+		if (!err)
+			found = 1;
+	}
+	spin_unlock(&sdev_lock);
+
+finish:
+	
+	return (found)?0:-ENODEV;
+}
+
+/*
+ * Must be called under ldev->chain_lock and ldev_lock held.
+ */
+static void __sc_del_logical_dev(struct sc_dev *dev, struct logical_dev *__ldev)
+{
+	struct dev_chain *ch, *n;
+	struct logical_dev *ldev;
+
+	spin_lock(&dev->chain_lock);
+	list_for_each_entry_safe(ch, n, &dev->chain_list, chain_entry)
+	{
+		ldev = ch->ptr;
+
+		if (sc_ldev_equal(ldev, __ldev))
+		{
+			list_del(&ch->chain_entry);
+			atomic_dec(&ldev->refcnt);
+			chain_free(ch);
+
+			sc_deactivate_logical(dev, ldev);
+
+			break;
+		}
+	}
+	spin_unlock(&dev->chain_lock);
+}
+
+void sc_del_logical_dev(struct logical_dev *ldev)
+{
+	struct sc_dev *dev;
+	struct dev_chain *ch, *n;
+	struct logical_dev *ld, *ln;
+
+	spin_lock(&ldev->lock);
+
+	spin_lock(&ldev->chain_lock);
+	list_for_each_entry_safe(ch, n, &ldev->chain_list, chain_entry)
+	{
+		dev = ch->ptr;
+
+		spin_lock(&dev->lock);
+		printk("Deactivating %s [%s/%s] from %s\n", 
+				ldev->name, 
+				(sc_ldev_is_clone(ldev))?"clone":"not clone", (sc_ldev_cloned(ldev))?"cloned":"not cloned",
+				dev->name);
+		__sc_del_logical_dev(dev, ldev);
+		
+		list_del(&ch->chain_entry);
+		atomic_dec(&dev->refcnt);
+		chain_free(ch);
+		spin_unlock(&dev->lock);
+	}
+	spin_unlock(&ldev->chain_lock);
+
+	if (sc_ldev_is_clone(ldev))
+	{
+		spin_unlock(&ldev->lock);
+		return;
+	}
+
+	spin_lock(&ldev_lock);
+	list_for_each_entry_safe(ld, ln, &ldev_list, ldev_entry)
+	{
+		printk("Processing ldev %s [%s/%s] [%x]\n", 
+				ld->name, 
+				(sc_ldev_is_clone(ld))?"clone":"not clone", (sc_ldev_cloned(ld))?"cloned":"not cloned",
+				ld->index);
+		if (sc_ldev_equal(ld, ldev))
+		{
+			list_del(&ld->ldev_entry);
+		}
+		else if (sc_ldev_cloned(ldev))
+		{
+			/*
+			 * When logical device is clonned 
+			 * clone's chunks can point to the diferent device 
+			 * than origianl logical device's chunks.
+			 * Since we do not have backlink from the original device
+			 * to it's clones we must run through the whole ldev_list.
+			 */
+			
+			if (sc_ldev_is_clone(ld) && sc_ldev_equal_name(ld, ldev))
+			{
+				list_del(&ld->ldev_entry);
+				sc_del_logical_dev(ld);
+				sc_ldev_unclone(ld);
+			}
+		}
+	}
+	spin_unlock(&ldev_lock);
+	
+	spin_unlock(&ldev->lock);
+
+	while(atomic_read(&ldev->refcnt))
+	{
+		printk(KERN_INFO "Waiting logical device %s [%x] [%s] to become free: refcnt=%d.\n",
+				ldev->name, ldev->index, 
+				(sc_ldev_is_clone(ldev))?"clone":"not clone", atomic_read(&ldev->refcnt));
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ);
+	}
+
+}
+
+static int sc_check_sc_dev(struct sc_dev *dev)
+{
+	if (!dev->activate_one)
+	{
+		printk(KERN_ERR "SuperIO device %s does not have ->activate_one() method.\n", dev->name);
+		return -EINVAL;
+	}
+
+	if (!dev->probe)
+	{
+		printk(KERN_ERR "SuperIO device %s does not have ->probe() method.\n", dev->name);
+		return -EINVAL;
+	}
+
+	if (!dev->ldevs)
+		printk(KERN_INFO "SuperIO device %s does not have logical device table.\n", dev->name);
+
+	return 0;
+}
+
+int sc_add_sc_dev(struct sc_dev *__sdev)
+{
+	int i, err;
+	struct sc_dev *sdev;
+
+	if (sc_check_sc_dev(__sdev))
+		return -EINVAL;
+
+	spin_lock_init(&__sdev->chain_lock);
+	INIT_LIST_HEAD(&__sdev->chain_list);
+	
+	spin_lock_init(&__sdev->lock);
+	
+	spin_lock(&sdev_lock);
+	list_for_each_entry(sdev, &sdev_list, sdev_entry)
+	{
+		if (sc_sdev_equal(sdev, __sdev))
+		{
+			printk(KERN_INFO "Super IO chip %s already registered.\n",
+					sdev->name);
+			spin_unlock(&sdev_lock);
+			return -EINVAL;
+		}
+	}
+
+	err = -ENODEV;
+	for (i=0; i<sizeof(base_addr)/sizeof(base_addr[0]); ++i)
+	{
+		err = __sdev->probe(__sdev, base_addr[i]);
+		if (!err)
+			break;
+	}
+
+	/*
+	 * Special case for non standard base location.
+	 */
+	if (i == sizeof(base_addr)/sizeof(base_addr[0]))
+		err = __sdev->probe(__sdev, 0);
+
+	if (!err)
+	{
+		atomic_set(&__sdev->refcnt, 0);
+		list_add_tail(&__sdev->sdev_entry, &sdev_list);
+	}
+
+	spin_unlock(&sdev_lock);
+
+	return err;
+}
+
+void sc_del_sc_dev(struct sc_dev *__sdev)
+{
+	struct dev_chain *ch, *n;
+	struct logical_dev *ldev;
+	struct sc_dev *sdev, *sn;
+	
+	spin_lock(&__sdev->lock);
+	spin_lock(&sdev_lock);
+	list_for_each_entry_safe(sdev, sn, &sdev_list, sdev_entry)
+	{
+		if (sc_sdev_equal(sdev, __sdev))
+		{
+			list_del(&sdev->sdev_entry);
+			break;
+		}
+	}
+	spin_unlock(&sdev_lock);
+
+	spin_lock(&__sdev->chain_lock);
+	list_for_each_entry_safe(ch, n, &__sdev->chain_list, chain_entry)
+	{
+		ldev = ch->ptr;
+
+		list_del(&ch->chain_entry);
+		atomic_dec(&ldev->refcnt);
+		chain_free(ch);
+
+		sc_deactivate_logical(__sdev, ldev);
+		sc_del_sdev_from_ldev(__sdev, ldev);
+	}
+	spin_unlock(&__sdev->chain_lock);
+	spin_unlock(&__sdev->lock);
+
+	while(atomic_read(&__sdev->refcnt))
+	{
+		printk(KERN_INFO "Waiting SuperIO chip %s to become free: refcnt=%d.\n",
+				__sdev->name, atomic_read(&__sdev->refcnt));
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(HZ);
+	}
+}
+
+static void sc_deactivate_logical(struct sc_dev *dev, struct logical_dev *ldev)
+{
+	printk(KERN_INFO "Deactivating logical device %s in SuperIO chip %s... ", ldev->name, dev->name);
+
+	if (dev->deactivate_one)
+		dev->deactivate_one(ldev);
+
+	printk("done.\n");
+}
+
+/*
+ * Must be called under sdev_lock held.
+ */
+static int sc_activate_logical(struct sc_dev *dev, struct logical_dev *ldev)
+{
+	int err;
+	
+	printk(KERN_INFO "Activating logical device %s [%x].\n", ldev->name, ldev->index);
+
+	ldev->pdev = dev;
+	err = dev->activate_one(ldev);
+	return err;
+}
+
+struct sc_dev *sc_get_sdev(char *name)
+{
+	struct sc_dev *sdev;
+
+	spin_lock(&sdev_lock);
+	list_for_each_entry(sdev, &sdev_list, sdev_entry)
+	{
+		if (!strcmp(name, sdev->name))
+		{
+			atomic_inc(&sdev->refcnt);
+			spin_unlock(&sdev_lock);
+			return sdev;
+		}
+	}
+	spin_unlock(&sdev_lock);
+
+	return NULL;
+}
+
+void sc_put_sdev(struct sc_dev *sdev)
+{
+	atomic_dec(&sdev->refcnt);
+}
+
+/*
+ * Get logical device which has given name and index.
+ */
+struct logical_dev *sc_get_ldev_index(char *name, u8 index)
+{
+	struct logical_dev *ldev;
+
+	spin_lock(&ldev_lock);
+	list_for_each_entry(ldev, &ldev_list, ldev_entry)
+	{
+		if (!strcmp(name, ldev->name) && ldev->index == index)
+		{
+			atomic_inc(&ldev->refcnt);
+			spin_unlock(&ldev_lock);
+			return ldev;
+		}
+	}
+	spin_unlock(&ldev_lock);
+
+	return NULL;
+}
+
+/*
+ * Get the first logical device with the given name.
+ */
+struct logical_dev *sc_get_ldev(char *name)
+{
+	struct logical_dev *ldev;
+
+	spin_lock(&ldev_lock);
+	list_for_each_entry(ldev, &ldev_list, ldev_entry)
+	{
+		if (!strcmp(name, ldev->name))
+		{
+			atomic_inc(&ldev->refcnt);
+			spin_unlock(&ldev_lock);
+			return ldev;
+		}
+	}
+	spin_unlock(&ldev_lock);
+
+	return NULL;
+}
+
+/*
+ * Get the first logical device with the given name connected to given SuperIO chip.
+ */
+struct logical_dev *sc_get_ldev_in_sdev(char *name, struct sc_dev *sdev)
+{
+	struct dev_chain *ch;
+	struct logical_dev *ldev;
+
+	spin_lock(&sdev->chain_lock);
+	list_for_each_entry(ch, &sdev->chain_list, chain_entry)
+	{
+		ldev = ch->ptr;
+
+		if (!strcmp(name, ldev->name))
+		{
+			atomic_inc(&ldev->refcnt);
+			spin_unlock(&sdev->chain_lock);
+			return ldev;
+		}
+	}
+	spin_unlock(&sdev->chain_lock);
+
+	return NULL;
+}
+
+void sc_put_ldev(struct logical_dev *ldev)
+{
+	atomic_dec(&ldev->refcnt);
+}
+
+/*
+ * Cloned logical device has the same structure as original device.
+ * Although cloned and original devices do not cross, they both point
+ * to the same block of the memory(they have pointers to the same functions), 
+ * so we will increment reference counter for original device 
+ * like cloned device has a reference to it.
+ */
+struct logical_dev *sc_ldev_clone(struct logical_dev *ldev)
+{
+	struct logical_dev *__ldev;
+	
+	__ldev = sc_ldev_alloc(ldev->name, ldev->index);
+	if (!__ldev)
+		return NULL;
+
+	memcpy(__ldev, ldev, sizeof(*__ldev));
+
+	spin_lock_init(&__ldev->chain_lock);
+	INIT_LIST_HEAD(&__ldev->chain_list);
+	spin_lock_init(&__ldev->lock);
+	
+	atomic_inc(&ldev->refcnt);
+	set_bit(LDEV_CLONED, (long *)&ldev->flags);
+
+	atomic_set(&__ldev->refcnt, 0);	
+	__ldev->orig_ldev = ldev;
+
+	return __ldev;
+}
+
+inline int sc_ldev_is_clone(struct logical_dev *ldev)
+{
+	return (ldev->orig_ldev)?1:0;
+}
+
+inline int sc_ldev_cloned(struct logical_dev *ldev)
+{
+	return (test_bit(LDEV_CLONED, (long *)&ldev->flags) && (atomic_read(&ldev->refcnt) >= 1));
+}
+
+void sc_ldev_unclone(struct logical_dev *clone)
+{
+	struct logical_dev *orig = clone->orig_ldev;
+
+	if (!sc_ldev_is_clone(clone))
+	{
+		printk(KERN_INFO "Logical device %s is not clone.\n", clone->name);
+		return;
+	}
+
+	if (atomic_dec_and_test(&orig->refcnt))
+		clear_bit(LDEV_CLONED, (long *)&orig->flags);
+
+	memset(clone, 0, sizeof(*clone));
+	kfree(clone);
+	clone = NULL;
+}
+
+struct logical_dev *sc_ldev_alloc(char *name, u8 index)
+{
+	struct logical_dev *ldev;
+
+	ldev = kmalloc(sizeof(*ldev), GFP_ATOMIC);
+	if (!ldev)
+	{
+		printk(KERN_ERR "Failed to allocate new logical device %s at address %x.\n",
+				name, index);
+		return NULL;
+	}
+
+	memset(ldev, 0, sizeof(*ldev));
+
+	snprintf(ldev->name, sizeof(ldev->name), "%s", name);
+	ldev->index = index;
+
+	return ldev;
+}
+
+void sc_ldev_free(struct logical_dev *ldev)
+{
+	if (ldev->orig_ldev)
+	{
+		struct logical_dev *orig = ldev->orig_ldev;
+		/*
+		 * It is clone.
+		 */
+		atomic_dec(&ldev->refcnt);
+		if (atomic_read(&ldev->refcnt))
+		{
+			/*
+			 * It is impossible, clone can not have clones.
+			 */
+			printk(KERN_INFO "Logical device clone %s has refcnt=%d and flags=%x.\n",
+					ldev->name, atomic_read(&ldev->refcnt), ldev->flags);
+			BUG();
+		}
+
+		spin_lock(&orig->lock);
+		
+		clear_bit(LDEV_CLONED, (long *)&orig->flags);
+		atomic_dec(&orig->refcnt);
+
+		memset(ldev, 0, sizeof(*ldev));
+		kfree(ldev);
+		
+		spin_unlock(&orig->lock);
+	}
+	else if (sc_ldev_cloned(ldev))
+	{
+		/*
+		 * It is cloned.
+		 */
+	}
+}
+
+static int __devinit sc_init(void)
+{
+	printk(KERN_INFO "Soekris SuperIO driver is starting...\n");
+	
+	return sc_register_callback();
+}
+
+static void __devexit sc_fini(void)
+{
+	sc_unregister_callback();
+	printk(KERN_INFO "Soekris SuperIO driver finished.\n");
+}
+
+module_init(sc_init);
+module_exit(sc_fini);
+
+EXPORT_SYMBOL(sc_add_logical_dev);
+EXPORT_SYMBOL(sc_del_logical_dev);
+EXPORT_SYMBOL(sc_get_ldev);
+EXPORT_SYMBOL(sc_get_ldev_in_sdev);
+EXPORT_SYMBOL(sc_put_ldev);
+EXPORT_SYMBOL(sc_add_sc_dev);
+EXPORT_SYMBOL(sc_del_sc_dev);
+EXPORT_SYMBOL(sc_get_sdev);
+EXPORT_SYMBOL(sc_put_sdev);
+EXPORT_SYMBOL(sc_ldev_alloc);
+EXPORT_SYMBOL(sc_ldev_free);
+EXPORT_SYMBOL(sc_ldev_clone);
+EXPORT_SYMBOL(sc_ldev_unclone);
+EXPORT_SYMBOL(sc_ldev_cloned);
+EXPORT_SYMBOL(sc_ldev_is_clone);
diff -Nru /tmp/empty/sc.h linux-2.6/drivers/superio/sc.h
--- /tmp/empty/sc.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/sc.h	2004-09-09 11:46:13.000000000 +0400
@@ -0,0 +1,132 @@
+/*
+ * 	sc.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 __SC_H
+#define __SC_H
+
+#include <linux/types.h>
+
+#include "chain.h"
+
+#define SC_NAME_LEN		16
+
+#define SIO_REG_SID		0x20	/* Super I/O ID */
+
+#define SIO_REG_SRID		0x27	/* Super I/O Revision */
+#define SIO_REG_IRQ		0x70	/* IRQ number */
+#define SIO_REG_IRQ_TYPE	0x71	/* IRQ type */
+
+#define SIO_REG_LDN		0x07	/* Logical Device Number */
+#define SIO_LDN_GPIO		0x07	/* General-Purpose I/O (GPIO) Ports */
+#define SIO_LDN_ACB		0x08	/* Access bus */
+
+#define SIO_REG_ACTIVE		0x30	/* Logical Device Activate Register */
+#define SIO_ACTIVE_EN		0x01	/* enabled */
+#define SIO_RESET		0x02
+
+#define SIO_REG_IO_MSB		0x60	/* I/O Port Base, bits 15-8 */
+#define SIO_REG_IO_LSB		0x61	/* I/O Port Base, bits 7-0 */
+
+#define LDEV_PRIVATE		0xff	/* Logical device has non standard dynamic address (like PCI space) */
+
+#define LDEV_CLONED		(1<<0)
+
+struct logical_dev
+{
+	struct list_head	ldev_entry;
+
+	atomic_t		refcnt;
+	spinlock_t		lock;
+
+	struct list_head	chain_list;
+	spinlock_t		chain_lock;
+
+	unsigned char		name[SC_NAME_LEN];
+	u8			index;
+	
+	unsigned long		base_addr;
+	unsigned long		range;
+
+	u32			flags;
+
+	void			*pdev;
+	void			*orig_ldev;
+
+	u8			irq;
+	u8			irq_type;
+
+	int			(*activate)(void *);
+	u8			(*read)(void *, int);
+	void			(*write)(void *, int, u8);
+	void			(*control)(void *, int, u8, u8);
+};
+
+struct sc_dev
+{
+	struct list_head	sdev_entry;
+
+	atomic_t		refcnt;
+	spinlock_t		lock;
+	
+	struct list_head	chain_list;
+	spinlock_t		chain_lock;
+
+	unsigned char		name[SC_NAME_LEN];
+
+	void			*pdev;
+	unsigned long		base_index, base_data;
+
+	struct sc_chip_id	*ldevs;
+
+	int			(*probe)(void *, unsigned long);
+	int			(*activate_one)(struct logical_dev *);
+	int			(*deactivate_one)(struct logical_dev *);
+	u8			(*read)(struct logical_dev *, unsigned long);
+	void			(*write)(struct logical_dev *, unsigned long, u8);
+};
+
+struct sc_chip_id
+{
+	unsigned char		name[SC_NAME_LEN];
+	u8			id;
+};
+
+int sc_add_logical_dev(struct sc_dev *, struct logical_dev *);
+void sc_del_logical_dev(struct logical_dev *);
+struct logical_dev *sc_get_ldev(char *);
+struct logical_dev *sc_get_ldev_in_sdev(char *, struct sc_dev *);
+void sc_put_ldev(struct logical_dev *);
+
+int sc_add_sc_dev(struct sc_dev *);
+void sc_del_sc_dev(struct sc_dev *);
+struct sc_dev *sc_get_sdev(char *);
+void sc_put_sdev(struct sc_dev *);
+
+struct logical_dev *sc_ldev_clone(struct logical_dev *ldev);
+void sc_ldev_unclone(struct logical_dev *ldev);
+inline int sc_ldev_cloned(struct logical_dev *ldev);
+inline int sc_ldev_is_clone(struct logical_dev *ldev);
+
+struct logical_dev *sc_ldev_alloc(char *name, u8 index);
+void sc_ldev_free(struct logical_dev *ldev);
+
+#endif /* __SC_H */
diff -Nru /tmp/empty/sc_acb.c linux-2.6/drivers/superio/sc_acb.c
--- /tmp/empty/sc_acb.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/sc_acb.c	2004-09-09 11:46:11.000000000 +0400
@@ -0,0 +1,166 @@
+/*
+ * 	sc_acb.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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/module.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+
+#include "sc.h"
+#include "sc_acb.h"
+
+int sc_acb_activate(void *data);
+u8 sc_acb_read(void *data, int reg);
+void sc_acb_write(void *data, int reg, u8 byte);
+void sc_acb_control(void *data, int pin, u8 mask, u8 ctl);
+
+static struct logical_dev ldev_acb = 
+{
+	.name		= "ACB", 
+	.index		= 0x08, 
+	.range		= 16, 
+	
+	.activate	= sc_acb_activate, 
+	.read		= sc_acb_read, 
+	.write		= sc_acb_write, 
+	.control	= sc_acb_control,
+
+	.flags		= 0,
+	.orig_ldev	= NULL,
+};
+
+void sc_write_reg(struct sc_dev *dev, u8 reg, u8 val)
+{
+	outb(reg, dev->base_index);
+	outb(val, dev->base_data);
+}
+
+u8 sc_read_reg(struct sc_dev *dev, u8 reg)
+{
+	u8 val;
+		
+	outb(reg, dev->base_index);
+	val = inb(dev->base_data);
+
+	return val;
+}
+
+int sc_acb_activate(void *data)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	u8 val;
+
+	sc_write_reg(ldev->pdev, SIO_REG_LDN, ldev->index);
+
+	sc_write_reg(ldev->pdev, ACBCTL2, 1);
+	wmb();
+	mdelay(100);
+	
+	val = sc_read_reg(ldev->pdev, ACBCTL2);
+	if ((val & 1) != 1)
+	{
+		printk(KERN_ERR "Can not enable %s at %x: ctl2=%x.\n", 
+				ldev->name, ldev->index,
+				val);
+		return -ENODEV;
+	}
+
+	sc_write_reg(ldev->pdev, ACBCTL2, 0x71);
+
+	val = sc_read_reg(ldev->pdev, ACBCTL2);
+       	if (val != 0x71) {
+		printk(KERN_ERR "ACBCTL2 readback failed: val=%x.\n", val);
+		return -ENXIO;
+	}
+
+	sc_write_reg(ldev->pdev, ACBCTL1, sc_read_reg(ldev->pdev, ACBCTL1) | ACBCTL1_NMINTE);
+
+	val = sc_read_reg(ldev->pdev, ACBCTL1);
+	if (val) {
+		printk(KERN_ERR "disabled, but ACBCTL1=0x%02x\n", val);
+		return -ENXIO;
+	}
+
+	sc_write_reg(ldev->pdev, ACBCTL2, sc_read_reg(ldev->pdev, ACBCTL2) | ACBCTL2_ENABLE);
+
+	sc_write_reg(ldev->pdev, ACBCTL1, sc_read_reg(ldev->pdev, ACBCTL1) | ACBCTL1_NMINTE);
+
+	val = sc_read_reg(ldev->pdev, ACBCTL1);
+	if ((val & ACBCTL1_NMINTE) != ACBCTL1_NMINTE) {
+		printk(KERN_ERR "enabled, but NMINTE won't be set, ACBCTL1=0x%02x\n", val);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+u8 sc_acb_read(void *data, int reg)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	u8 val;
+
+	val = inb(ldev->base_addr + reg);
+
+	//printk("R: %02x\n", val);
+
+	return val;
+}
+
+void sc_acb_write(void *data, int reg, u8 byte)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	//printk("W: %02x\n", val);
+	
+	outb(byte, ldev->base_addr + reg);
+}
+
+void sc_acb_control(void *data, int pin, u8 mask, u8 ctl)
+{
+}
+
+static int __devinit sc_acb_init(void)
+{
+	printk(KERN_INFO "Access Bus logical device driver is activating now.\n");
+	INIT_LIST_HEAD(&ldev_acb.ldev_entry);
+	spin_lock_init(&ldev_acb.lock);
+	return sc_add_logical_dev(NULL, &ldev_acb);
+}
+
+static void __devexit sc_acb_fini(void)
+{
+	sc_del_logical_dev(&ldev_acb);
+	printk(KERN_INFO "Access Bus logical device driver finished.\n");
+}
+
+module_init(sc_acb_init);
+module_exit(sc_acb_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol at 2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for Access Bus logical device.");
+
+EXPORT_SYMBOL(sc_acb_activate);
+EXPORT_SYMBOL(sc_acb_read);
+EXPORT_SYMBOL(sc_acb_write);
+EXPORT_SYMBOL(sc_acb_control);
diff -Nru /tmp/empty/sc_acb.h linux-2.6/drivers/superio/sc_acb.h
--- /tmp/empty/sc_acb.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/sc_acb.h	2004-09-09 11:46:11.000000000 +0400
@@ -0,0 +1,45 @@
+/*
+ * 	sc_acb.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 __SC_ACB_H
+#define __SC_ACB_H
+
+#define ACBSDA			(ldev->base_addr + 0)
+#define ACBST			(ldev->base_addr + 1)
+#define ACBST_SDAST		0x40 /* SDA Status */
+#define ACBST_BER		0x20 
+#define ACBST_NEGACK		0x10 /* Negative Acknowledge */
+#define ACBST_STASTR		0x08 /* Stall After Start */
+#define ACBST_MASTER		0x02
+#define ACBCST			(ldev->base_addr + 2)
+#define ACBCST_BB		0x02
+#define ACBCTL1			(ldev->base_addr + 3)
+#define ACBCTL1_STASTRE		0x80
+#define ACBCTL1_NMINTE		0x40
+#define	ACBCTL1_ACK		0x10
+#define	ACBCTL1_STOP		0x02
+#define	ACBCTL1_START		0x01
+#define ACBADDR			(ldev->base_addr + 4)
+#define ACBCTL2			(ldev->base_addr + 5)
+#define ACBCTL2_ENABLE		0x01
+
+#endif /* __SC_ACB_H */
diff -Nru /tmp/empty/sc_conn.c linux-2.6/drivers/superio/sc_conn.c
--- /tmp/empty/sc_conn.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/sc_conn.c	2004-09-09 11:46:12.000000000 +0400
@@ -0,0 +1,133 @@
+/*
+ * 	sc_conn.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include "sc.h"
+#include "sc_conn.h"
+#include "../connector/connector.h"
+
+#define SC_CONN_IDX		0xaabb
+#define SC_CONN_VAL		0xccdd
+
+static struct cb_id sc_conn_id = {SC_CONN_IDX, SC_CONN_VAL};
+
+static void sc_conn_callback(void *data)
+{
+	struct cn_msg *reply, *msg = (struct cn_msg *)data;
+	struct sc_conn_data *rcmd, *cmd = (struct sc_conn_data *)(msg+1);
+	struct logical_dev *ldev;
+	struct sc_dev *sdev;
+	u8 ret;
+
+	if (msg->len != sizeof(*cmd))
+	{
+		printk(KERN_ERR "Wrong additional data size %u, must be %u.\n",
+				msg->len, sizeof(*cmd));
+		return;
+	}
+#if 0
+	printk("%s: len=%u, seq=%u, ack=%u, sname=%s, lname=%s, idx=0x%x, cmd=%02x [%02x.%02x.%02x].\n",
+			__func__, 
+			msg->len, msg->seq, msg->ack,
+			cmd->sname,
+			cmd->lname, cmd->idx,
+			cmd->cmd, cmd->p0, cmd->p1, cmd->p2);
+#endif
+	sdev = sc_get_sdev(cmd->sname);
+	if (!sdev)
+	{
+		printk(KERN_ERR "%s: sdev %s does not exist.\n", __func__, cmd->sname);
+		return;
+	}
+	
+	ldev = sc_get_ldev_in_sdev(cmd->lname, sdev);
+	if (!ldev)
+	{
+		printk(KERN_ERR "%s: ldev %s does not exist in chip %s.\n", 
+				__func__, cmd->lname, cmd->sname);
+		sc_put_sdev(sdev);
+		return;
+	}
+	
+	ret = 0;
+	switch (cmd->cmd)
+	{
+		case SC_CMD_LDEV_READ:
+			ret = ldev->read(ldev, cmd->p0);
+			reply = kmalloc(sizeof(*msg) + sizeof(*cmd), GFP_ATOMIC);
+			if (reply)
+			{
+				memcpy(reply, msg, sizeof(*reply));
+
+				/*
+				 * See protocol description in connecter.c
+				 */
+				reply->ack++;
+				
+				rcmd = (struct sc_conn_data *)(reply + 1);
+				memcpy(rcmd, cmd, sizeof(*rcmd));
+				
+				rcmd->cmd = SC_CMD_LDEV_READ;
+				rcmd->p0 = cmd->p0;
+				rcmd->p1 = ret;
+
+				cn_netlink_send(reply);
+
+				kfree(reply);
+			}
+			else
+				printk(KERN_ERR "Failed to allocate %d bytes in reply to comamnd 0x%x.\n",
+						sizeof(*msg) + sizeof(*cmd), cmd->cmd);
+			break;
+		case SC_CMD_LDEV_WRITE:
+			ldev->write(ldev, cmd->p0, cmd->p1);
+			break;
+		case SC_CMD_LDEV_CONTROL:
+			ldev->control(ldev, cmd->p0, cmd->p1, cmd->p2);
+			break;
+		case SC_CMD_LDEV_ACTIVATE:
+			ldev->activate(ldev);
+			break;
+		default:
+			printk(KERN_ERR "Unsupported command 0x%x for %s in chip %s.\n",
+					cmd->cmd, ldev->name, sdev->name);
+			break;
+	}
+		
+	sc_put_ldev(ldev);
+	sc_put_sdev(sdev);
+}
+
+int sc_register_callback(void)
+{
+	return cn_add_callback(&sc_conn_id, "sc_callback", (void (*)(void *))&sc_conn_callback);
+}
+
+void sc_unregister_callback(void)
+{
+	return cn_del_callback(&sc_conn_id);
+}
+
+EXPORT_SYMBOL(sc_register_callback);
+EXPORT_SYMBOL(sc_unregister_callback);
diff -Nru /tmp/empty/sc_conn.h linux-2.6/drivers/superio/sc_conn.h
--- /tmp/empty/sc_conn.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/sc_conn.h	2004-09-09 11:46:12.000000000 +0400
@@ -0,0 +1,50 @@
+/*
+ * 	sc_conn.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 __SC_CONN_H
+#define __SC_CONN_H
+
+enum sc_conn_cmd
+{
+	SC_CMD_LDEV_READ = 0,
+	SC_CMD_LDEV_WRITE,
+	SC_CMD_LDEV_CONTROL,
+	SC_CMD_LDEV_ACTIVATE,
+
+	__SC_CMD_MAX_CMD_NUMBER
+};
+
+struct sc_conn_data
+{
+	char		sname[16];
+	char		lname[16];
+	__u32		idx;
+	
+	__u8		cmd;
+	__u8		p0, p1, p2;
+
+	__u8		data[0];
+};
+
+int sc_register_callback(void);
+void sc_unregister_callback(void);
+
+#endif /* __SC_CONN_H */
diff -Nru /tmp/empty/sc_gpio.c linux-2.6/drivers/superio/sc_gpio.c
--- /tmp/empty/sc_gpio.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/sc_gpio.c	2004-09-09 11:46:13.000000000 +0400
@@ -0,0 +1,214 @@
+/*
+ * 	sc_gpio.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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/module.h>
+#include <linux/string.h>
+
+#include <asm/io.h>
+
+#include "sc.h"
+#include "sc_gpio.h"
+#include "pc8736x.h"
+
+static struct gpio_pin gpin[SIO_GPIO_NPINS];
+
+static struct logical_dev ldev_gpio = 
+{
+	.name		= "GPIO", 
+	.index		= SIO_LDN_GPIO, 
+	.range		= 16, 
+	
+	.activate	= sc_gpio_activate, 
+	.read		= sc_gpio_read, 
+	.write		= sc_gpio_write, 
+	.control	= sc_gpio_control,
+
+	.flags		= 0,
+	.orig_ldev	= 0,
+};
+
+void sc_write_reg(struct sc_dev *dev, u8 reg, u8 val)
+{
+	outb(reg, dev->base_index);
+	outb(val, dev->base_data);
+}
+
+u8 sc_read_reg(struct sc_dev *dev, u8 reg)
+{
+	u8 val;
+		
+	outb(reg, dev->base_index);
+	val = inb(dev->base_data);
+
+	return val;
+}
+
+void sc_gpio_pin_select(void *data, int pin_number)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	int port, pin;
+	u8 val;
+
+	port 	= pin_number >> 3;
+	pin 	= pin_number - (pin_number & (~7));
+	val 	= (port << 4) | pin;
+
+	sc_write_reg(ldev->pdev, SIO_REG_LDN, SIO_LDN_GPIO);
+	sc_write_reg(ldev->pdev, SIO_GPIO_PINSEL, val);
+}
+
+int sc_gpio_activate(void *data)
+{
+	//struct logical_dev *ldev = (struct logical_dev *)data;
+	int i;
+
+	memset(gpin, 0, sizeof(gpin));
+
+	for (i=0; i<SIO_GPIO_NPINS; ++i)
+	{
+		gpin[i].flags = SIO_GPIO_CONF_PULLUP;
+		gpin[i].mask &= ~(SIO_GPIO_CONF_DEBOUNCE);
+		
+		//sc_gpio_control(ldev, i, gpin[i].mask, gpin[i].flags);
+		
+		//gpin[i].state = GPIO_PIN_HIGH;
+		//sc_gpio_write(ldev, i, gpin[i].state);
+	}
+	
+	return 0;
+}
+
+u8 sc_gpio_read(void *data, int pin_number)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	int port, pin;
+	u8 val, reg = SIO_GPDI0;
+	
+	port 	= pin_number >> 3;
+	pin 	= pin_number - (pin_number & (~7));
+
+	switch (port)
+	{
+		case 0:
+			reg = SIO_GPDI0;
+			break;
+		case 1:
+			reg = SIO_GPDI1;
+			break;
+		case 2:
+			reg = SIO_GPDI2;
+			break;
+		case 3:
+			reg = SIO_GPDI3;
+			break;
+	}
+
+	val = inb(ldev->base_addr + reg);
+
+	//printk("R: %02x [%d]\n", val, ((val>>pin)&1));
+
+	return ((val >> pin) & 0x01) ;
+}
+
+void sc_gpio_write(void *data, int pin_number, u8 byte)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	int port, pin;
+	u8 val, reg = SIO_GPDO0, rreg = SIO_GPDI0;
+
+	port 	= pin_number >> 3;
+	pin 	= pin_number - (pin_number & (~7));
+
+	switch (port)
+	{
+		case 0:
+			reg = SIO_GPDO0;
+			rreg = SIO_GPDI0;
+			break;
+		case 1:
+			reg = SIO_GPDO1;
+			rreg = SIO_GPDI1;
+			break;
+		case 2:
+			reg = SIO_GPDO2;
+			rreg = SIO_GPDI2;
+			break;
+		case 3:
+			reg = SIO_GPDO3;
+			rreg = SIO_GPDI3;
+			break;
+	}
+
+	//val = inb(ldev->base_addr + reg);
+	val = inb(ldev->base_addr + rreg);
+	
+	if (byte)
+		val |= (1 << pin);
+	else
+		val &= ~(1 << pin);
+
+	//printk("W: %02x [%d]\n", val, ((val>>pin)&1));
+	
+	outb(val, ldev->base_addr + reg);
+}
+
+void sc_gpio_control(void *data, int pin, u8 mask, u8 ctl)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	u8 cfg;
+
+	sc_gpio_pin_select(ldev, pin);
+
+	cfg = sc_read_reg(ldev->pdev, SIO_GPIO_PINCFG);
+
+	cfg &= mask;
+	cfg |= ctl;
+
+	sc_write_reg(ldev->pdev, SIO_GPIO_PINCFG, cfg);
+}
+
+static int __devinit sc_gpio_init(void)
+{
+	printk(KERN_INFO "GPIO logical device driver is activating now.\n");
+	INIT_LIST_HEAD(&ldev_gpio.ldev_entry);
+	spin_lock_init(&ldev_gpio.lock);
+	return sc_add_logical_dev(NULL, &ldev_gpio);
+}
+
+static void __devexit sc_gpio_fini(void)
+{
+	sc_del_logical_dev(&ldev_gpio);
+	printk(KERN_INFO "GPIO logical device driver finished.\n");
+}
+
+module_init(sc_gpio_init);
+module_exit(sc_gpio_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol at 2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for GPIO logical device.");
+
+EXPORT_SYMBOL(sc_gpio_activate);
+EXPORT_SYMBOL(sc_gpio_read);
+EXPORT_SYMBOL(sc_gpio_write);
+EXPORT_SYMBOL(sc_gpio_control);
+EXPORT_SYMBOL(sc_gpio_pin_select);
diff -Nru /tmp/empty/sc_gpio.h linux-2.6/drivers/superio/sc_gpio.h
--- /tmp/empty/sc_gpio.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/sc_gpio.h	2004-09-09 11:46:13.000000000 +0400
@@ -0,0 +1,53 @@
+/*
+ * 	sc_gpio.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 __SC_GPIO_H
+#define __SC_GPIO_H
+
+#define SIO_GPIO_PINSEL		0xf0
+#define SIO_GPIO_PINCFG		0xf1
+#define SIO_GPIO_PINEV		0xf2
+
+#define GPIO_PIN_LOW		0x00	/* low level (logical 0) */
+#define GPIO_PIN_HIGH		0x01	/* high level (logical 1) */
+
+#define SIO_GPIO_NPINS		29
+
+#define SIO_GPIO_CONF_OUTPUTEN	(1 << 0)
+#define SIO_GPIO_CONF_PUSHPULL	(1 << 1)
+#define SIO_GPIO_CONF_PULLUP	(1 << 2)
+#define SIO_GPIO_CONF_DEBOUNCE	(1 << 6)
+
+int sc_gpio_activate(void *);
+u8 sc_gpio_read(void *, int);
+void sc_gpio_write(void *, int, u8);
+void sc_gpio_control(void *, int, u8, u8);
+void sc_gpio_pin_select(void *, int);
+
+struct gpio_pin
+{
+	u8			state;
+	u8			flags;
+	u8			mask;
+};
+
+#endif /* __SC_GPIO_H */
diff -Nru /tmp/empty/scx200.c linux-2.6/drivers/superio/scx200.c
--- /tmp/empty/scx200.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/scx200.c	2004-09-09 11:46:14.000000000 +0400
@@ -0,0 +1,411 @@
+/*
+ * 	scx200.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pci.h>
+
+#include "sc.h"
+#include "scx200.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol at 2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for SCx200/SC1100 SuperIO chips.");
+
+static int scx200_probe(void *, unsigned long base);
+static int scx200_activate_one_logical(struct logical_dev *ldev);
+static int scx200_deactivate_one_logical(struct logical_dev *ldev);
+
+static struct sc_chip_id scx200_logical_devs[] = 
+{
+	{"RTC", 	0x00},
+	{"SWC", 	0x01},
+	{"IRCP", 	0x02},
+	{"ACB", 	0x05},
+	{"ACB", 	0x06},
+	{"SPORT", 	0x08},
+	{"GPIO",	LDEV_PRIVATE},
+	{}
+};
+
+static struct sc_dev scx200_dev = 
+{
+	.name		"SCx200",
+	.probe		scx200_probe,
+	.ldevs		scx200_logical_devs,
+	.activate_one	scx200_activate_one_logical,
+	.deactivate_one	scx200_deactivate_one_logical,
+};
+
+static struct sc_chip_id scx200_sio_ids[] = 
+{
+	{"SCx200/SC1100", 0xF5},
+};
+	
+static unsigned long private_base;
+
+static struct pci_device_id scx200_tbl[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SCx200_BRIDGE) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SC1100_BRIDGE) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci,scx200_tbl);
+
+static int scx200_pci_probe(struct pci_dev *, const struct pci_device_id *);
+
+static struct pci_driver scx200_pci_driver = {
+	.name = "scx200",
+	.id_table = scx200_tbl,
+	.probe = scx200_pci_probe,
+};
+
+void scx200_write_reg(struct sc_dev *dev, u8 reg, u8 val)
+{
+	outb(reg, dev->base_index);
+	outb(val, dev->base_data);
+}
+
+u8 scx200_read_reg(struct sc_dev *dev, u8 reg)
+{
+	u8 val;
+		
+	outb(reg, dev->base_index);
+	val = inb(dev->base_data);
+
+	return val;
+}
+
+static u8 scx200_gpio_read(void *data, int pin_number)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	
+	if (pin_number > 63 || pin_number < 0)
+		return 0;
+
+	bank = 0;
+	base = ldev->base_addr;
+	
+	if (pin_number > 31)
+	{
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+
+	val = inl(base + 0x04);
+
+	return ((val >> pin_number) & 0x01) ;
+}
+
+static void scx200_gpio_write(void *data, int pin_number, u8 byte)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	if (pin_number > 63 || pin_number < 0)
+		return;
+
+	bank = 0;
+	base = ldev->base_addr;
+	
+	if (pin_number > 31)
+	{
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+	
+	val = inl(base);
+
+	if (byte)
+		val |= (1 << pin_number);
+	else
+		val &= ~(1 << pin_number);
+	
+	outl(val, base);
+}
+
+void scx200_gpio_control(void *data, int pin_number, u8 mask, u8 ctl)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	if (pin_number > 63 || pin_number < 0)
+		return;
+
+	bank = 0;
+	base = ldev->base_addr;
+	
+	if (pin_number > 31)
+	{
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+
+	/*
+	 * Pin selection.
+	 */
+
+	val = 0;
+	val = ((bank & 0x01) << 5) | pin_number;
+	outl(val, ldev->base_addr + 0x20);
+
+	val = inl(ldev->base_addr + 0x24);
+
+	val &= 0x7f;
+	val &= mask;
+	val |= ctl;
+	
+	outl(val, ldev->base_addr + 0x24);
+}
+
+int scx200_gpio_activate(void *data)
+{
+	return 0;
+}
+
+static int scx200_ldev_index_by_name(char *name)
+{
+	int i;
+
+	for (i=0; i<sizeof(scx200_logical_devs)/sizeof(scx200_logical_devs[0]); ++i)
+		if (!strncmp(scx200_logical_devs[i].name, name, SC_NAME_LEN))
+			return i;
+
+	return -ENODEV;
+}
+
+static int scx200_chip_index(u8 id)
+{
+	int i;
+
+	for (i=0; i<sizeof(scx200_sio_ids)/sizeof(scx200_sio_ids[0]); ++i)
+		if (scx200_sio_ids[i].id == id)
+			return i;
+
+	return -ENODEV;
+}
+
+static int scx200_probe(void *data, unsigned long base)
+{
+	unsigned long size = 2;
+	u8 id;
+	int chip_num;
+	struct sc_dev *dev = (struct sc_dev *)data;
+
+	/*
+	 * Special address to handle.
+	 */
+	if (base == 0)
+	{
+		return scx200_probe(data, 0x015C);
+	}
+	
+	dev->base_index = base;
+	dev->base_data = base + 1;
+
+	id = scx200_read_reg(dev, SIO_REG_SID);
+	chip_num = scx200_chip_index(id);
+
+	if (chip_num >= 0)
+	{
+		printk(KERN_INFO "Found %s [0x%x] at 0x%04lx-0x%04lx.\n", 
+				scx200_sio_ids[chip_num].name, scx200_sio_ids[chip_num].id, 
+				base, base+size-1);
+		return 0;
+	}
+
+	printk(KERN_INFO "Found nothing at 0x%04lx-0x%04lx.\n", base, base+size-1);
+
+	return -ENODEV;
+}
+
+static int scx200_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	private_base = pci_resource_start(pdev, 0);
+	printk(KERN_INFO "%s: GPIO base 0x%lx.\n", pci_name(pdev), private_base);
+
+	if (!request_region(private_base, SCx200_GPIO_SIZE, "NatSemi SCx200 GPIO")) 
+	{
+		printk(KERN_ERR "%s: failed to request %d bytes I/O region for GPIOs.\n", 
+				pci_name(pdev), SCx200_GPIO_SIZE);
+		return -EBUSY;
+	}
+
+	pci_set_drvdata(pdev, &private_base);
+	pci_enable_device(pdev);
+
+	return 0;
+}
+
+static int scx200_deactivate_one_logical(struct logical_dev *ldev)
+{
+	if (ldev->index != LDEV_PRIVATE)
+		return -ENODEV;
+	
+	private_base -= 0x10;
+	
+	return 0;
+}
+
+static int scx200_find_private_device(struct logical_dev *ldev)
+{
+	struct sc_dev *dev = (struct sc_dev *)ldev->pdev;
+	
+	/*
+	 * SCx200/SC1100 has only GPIO in it's private space.
+	 */
+
+	if (strncmp(ldev->name, "GPIO", SC_NAME_LEN))
+	{
+		printk(KERN_ERR "Logical device %s at private space is not supported in chip %s.\n",
+				ldev->name, dev->name);
+		return -ENODEV;
+	}
+
+	ldev->base_addr = private_base;
+	private_base += 0x10;
+
+	ldev->read	= scx200_gpio_read;
+	ldev->write 	= scx200_gpio_write;
+	ldev->control 	= scx200_gpio_control;
+	ldev->activate	= scx200_gpio_activate;
+
+	return 0;
+}
+
+static int scx200_activate_one_logical(struct logical_dev *ldev)
+{
+	int err, idx;
+	struct sc_dev *dev = ldev->pdev;
+	u8 active;
+
+	idx = scx200_ldev_index_by_name(ldev->name);
+	if (idx < 0)
+	{
+		printk(KERN_INFO "Chip %s does not have logical device %s at %x.\n", 
+				dev->name, ldev->name, ldev->index);
+		return -ENODEV;
+	}
+
+	if (scx200_logical_devs[idx].id == LDEV_PRIVATE)
+	{
+		err = scx200_find_private_device(ldev);
+		if (err)
+			return err;
+
+		printk(KERN_INFO "\t%16s - found at 0x%lx.\n", ldev->name, ldev->base_addr);
+	}
+	else
+	{
+		scx200_write_reg(dev, SIO_REG_LDN, ldev->index);
+		active = scx200_read_reg(dev, SIO_REG_ACTIVE);
+		if ((active & SIO_ACTIVE_EN) == 0)
+		{
+			printk(KERN_INFO "\t%16s - not activated at %x: activating... ", 
+					ldev->name, ldev->index);
+			
+			scx200_write_reg(dev, SIO_REG_ACTIVE, active | SIO_ACTIVE_EN);
+			active = scx200_read_reg(dev, SIO_REG_ACTIVE);
+			if ((active & SIO_ACTIVE_EN) == 0)
+			{
+				printk("failed.\n");
+				return -ENODEV;
+			}
+			printk("done.\n");
+		}
+
+		ldev->base_addr = scx200_read_reg(dev, SIO_REG_IO_LSB);
+		ldev->base_addr |= (scx200_read_reg(dev, SIO_REG_IO_MSB) << 8);
+	}
+
+	err = ldev->activate(ldev);
+	if (err < 0)
+	{
+		printk(KERN_INFO "\t%16s - not activated: ->activate() failed with error code %d.\n", 
+				ldev->name, err);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "\t%16s - activated at %x: 0x%04lx-0x%04lx\n", 
+			ldev->name, ldev->index, ldev->base_addr, ldev->base_addr + ldev->range);
+		
+	return 0;
+}
+
+static int scx200_init(void)
+{
+	int err;
+
+	err = pci_module_init(&scx200_pci_driver);
+	if (err)
+	{
+		printk(KERN_ERR "Failed to register PCI driver for device %s : err=%d.\n", 
+				scx200_pci_driver.name, err);
+		return err;
+	}
+
+	err = sc_add_sc_dev(&scx200_dev);
+	if (err)
+		return err;
+
+	printk(KERN_INFO "Driver for %s SuperIO chip.\n", scx200_dev.name);
+	return 0;
+}
+
+static void scx200_fini(void)
+{
+	sc_del_sc_dev(&scx200_dev);
+
+	while (atomic_read(&scx200_dev.refcnt))
+		schedule_timeout(HZ);
+	
+	pci_unregister_driver(&scx200_pci_driver);
+	if (private_base)
+		release_region(private_base, SCx200_GPIO_SIZE);
+}
+
+module_init(scx200_init);
+module_exit(scx200_fini);
+
+EXPORT_SYMBOL(scx200_write_reg);
+EXPORT_SYMBOL(scx200_read_reg);
diff -Nru /tmp/empty/scx200.h linux-2.6/drivers/superio/scx200.h
--- /tmp/empty/scx200.h	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/superio/scx200.h	2004-09-09 11:46:14.000000000 +0400
@@ -0,0 +1,28 @@
+/*
+ * 	scx200.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 __SCX200_H
+#define __SCX200_H
+
+#define SCx200_GPIO_SIZE 	0x2c
+
+#endif /* __SCX200_H */


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-09 08:43:37.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-09 08:40:10.000000000 +0400
@@ -0,0 +1,16 @@
+obj-m		:= cn.o
+cn-objs		:= cn_queue.o connector.o
+
+#KDIR	:= /lib/modules/$(shell uname -r)/build
+KDIR	:= /usr/local/src/linux-2.6/linux-2.6.6
+PWD	:= $(shell pwd)
+
+default:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
+
+clean:
+	rm -f *.o *.ko *.mod.* .*.cmd *~
+	rm -rf .tmp_versions
+
+ucon:	ucon.c connector.h
+	gcc -W -Wall -O9 ucon.c -o ucon
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-09 11:46:04.000000000 +0400
@@ -0,0 +1,214 @@
+/*
+ * 	cn_queue.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 "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 %s to became free: refcnt=%d.\n", 
+				cbq->pdev->name, atomic_read(&cbq->cb->refcnt));
+		schedule_timeout(HZ);
+	}
+	
+	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;
+
+	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 %s to became free: refcnt=%d.\n", 
+				dev->name, atomic_read(&dev->refcnt));
+		schedule_timeout(HZ);
+	}
+	
+	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-09 11:46:04.000000000 +0400
@@ -0,0 +1,90 @@
+/*
+ * 	cn_queue.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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/cn_test.c linux-2.6/drivers/connector/cn_test.c
--- /tmp/empty/cn_test.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/connector/cn_test.c	2004-09-09 11:46:05.000000000 +0400
@@ -0,0 +1,66 @@
+/*
+ * 	cn_test.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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/moduleparam.h>
+
+#include "connector.h"
+
+static struct cb_id cn_test_id = {0x123, 0x456};
+static char cn_test_name[] = "cn_test";
+
+void cn_test_callback(void *data)
+{
+	struct cn_msg *msg = (struct cn_msg *)data;
+
+	printk("%s: idx=%x, val=%x, len=%d.\n", 
+			__func__, 
+			msg->id.idx, msg->id.val, msg->len);
+}
+
+static int cn_test_init(void)
+{
+	int err;
+
+	err = cn_add_callback(&cn_test_id, cn_test_name, cn_test_callback);
+	if (err)
+		return err;
+	cn_test_id.val++;
+	err = cn_add_callback(&cn_test_id, cn_test_name, cn_test_callback);
+	if (err)
+	{
+		cn_del_callback(&cn_test_id);
+		return err;
+	}
+
+	return 0;
+}
+
+static void cn_test_fini(void)
+{
+	cn_del_callback(&cn_test_id);
+	cn_test_id.val--;
+	cn_del_callback(&cn_test_id);
+}
+
+module_init(cn_test_init);
+module_exit(cn_test_fini);
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-09 11:46:05.000000000 +0400
@@ -0,0 +1,310 @@
+/*
+ * 	connector.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 <johnpol at 2ka.mipt.ru>");
+MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector.");
+
+static int unit = NETLINK_NFLOG;
+module_param(unit, int, 0);
+
+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)
+{
+	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;
+	
+	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;
+	}
+
+	size = NLMSG_SPACE(sizeof(*msg) + msg->len);
+
+	skb = alloc_skb(size, GFP_ATOMIC);
+	if (!skb) {
+		printk(KERN_ERR "skb_alloc() failed.\n");
+		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 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 (err < 0)
+				netlink_ack(skb, nlh, -err);
+			kfree_skb(skb);
+			break;
+		}
+		else 
+		{
+			if (nlh->nlmsg_flags & NLM_F_ACK)
+				netlink_ack(skb, nlh, 0);
+			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);
+}
+
+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;
+	}
+
+	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);
+			break;
+		}
+	}
+}
+
+static int cn_init(void)
+{
+	struct cn_dev *dev = &cdev;
+
+	dev->input = cn_input;
+	
+	dev->nls = netlink_kernel_create(unit, dev->input);
+	if (!dev->nls) {
+		printk(KERN_ERR "Failed to create new netlink socket(%u).\n",
+			NETLINK_NFLOG);
+		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 0;
+}
+
+static void cn_fini(void)
+{
+	struct cn_dev *dev = &cdev;
+
+	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-09 11:46:05.000000000 +0400
@@ -0,0 +1,58 @@
+/*
+ * 	connector.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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];
+};
+
+#ifdef __KERNEL__
+
+#include <net/sock.h>
+
+struct cn_dev
+{
+	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 *);
+
+#endif /* __KERNEL__ */
+#endif /* __CONNECTOR_H */
diff -Nru /tmp/empty/ucon.c linux-2.6/drivers/connector/ucon.c
--- /tmp/empty/ucon.c	1970-01-01 03:00:00.000000000 +0300
+++ linux-2.6/drivers/connector/ucon.c	2004-09-09 11:46:06.000000000 +0400
@@ -0,0 +1,213 @@
+/*
+ * 	ucon.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 
+ *
+ * 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 <asm/types.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#include "connector.h"
+#include "../soekris/sc_conn.h"
+
+static int need_exit;
+__u32 seq; 
+
+static int netlink_send(int s, char *data)
+{
+	struct nlmsghdr *nlh;
+	unsigned int size;
+	int err;
+	char buf[128];
+	struct cn_msg *m, *msg;
+	struct sc_conn_data *cmd;
+
+	size = NLMSG_SPACE(sizeof(struct cn_msg) + sizeof(struct sc_conn_data));
+	
+	nlh = (struct nlmsghdr *)buf;
+	nlh->nlmsg_seq = seq++;
+	nlh->nlmsg_pid = getpid();
+	nlh->nlmsg_type = NLMSG_DONE;
+	nlh->nlmsg_len = NLMSG_LENGTH(size - sizeof(*nlh));
+	nlh->nlmsg_flags = 0;
+
+	m = NLMSG_DATA(nlh);
+	msg = (struct cn_msg *)data;
+	cmd = (struct sc_conn_data *)(msg + 1);
+
+	printf("%s: len=%u, seq=%u, ack=%u, "
+		"sname=%s, lname=%s, idx=0x%x, cmd=%02x [%02x.%02x.%02x].\n",
+			__func__, 
+			msg->len, msg->seq, msg->ack,
+			cmd->sname,
+			cmd->lname, cmd->idx,
+			cmd->cmd, cmd->p0, cmd->p1, cmd->p2);
+
+	memcpy(m, data, sizeof(*m) + msg->len);
+
+	err = send(s, nlh, size, 0);
+	if (err == -1)
+		fprintf(stderr, "Failed to send: %s [%d].\n",
+				strerror(errno), errno);
+
+	return err;
+}
+
+int main(int argc, char *argv[])
+{
+	int s;
+	char buf[1024];
+	int len;
+	struct nlmsghdr *reply;
+	struct sockaddr_nl l_local;
+	struct cn_msg *data;
+	struct sc_conn_data *m;
+	FILE *out;
+	time_t tm;
+	struct pollfd pfd;
+	
+	if (argc < 2)
+		out = stdout;
+	else
+	{
+		out = fopen(argv[1], "a+");
+		if (!out)
+		{
+			fprintf(stderr, "Unable to open %s for writing: %s\n", 
+					argv[1], strerror(errno));
+			out = stdout;
+		}
+	}
+	
+	memset(buf, 0, sizeof(buf));
+	
+	s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_NFLOG);
+	if (s == -1)
+	{
+		perror("socket");
+		return -1;
+	}
+	
+	l_local.nl_family = AF_NETLINK;
+	l_local.nl_groups = 1;
+	l_local.nl_pid    = getpid();
+	
+	if (bind(s, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl)) == -1)
+	{
+		perror("bind");
+		close(s);
+		return -1;
+	}
+
+	pfd.fd = s;
+
+	while (!need_exit)
+	{
+		pfd.events = POLLIN;
+		pfd.revents = 0;
+		/*switch (poll(&pfd, 1, -1)) 
+		{
+			case 0:
+				need_exit = 1;
+				break;
+			case -1:
+				if (errno != EINTR) 
+				{
+					need_exit = 1;
+					break;
+				}
+				continue;
+		}*/
+		if (need_exit)
+			break;
+
+		data = (struct cn_msg *)buf;
+
+		data->id.idx 	= 0xaabb;
+		data->id.val 	= 0xccdd;
+		data->seq 	= seq++;
+		data->ack 	= (seq ^ 0x1234);
+		data->len 	= sizeof(*m);
+
+		m = (struct sc_conn_data *)(data+1);
+		memset(m, 0, sizeof(*m));
+
+		m->cmd = SC_CMD_LDEV_READ;
+		m->idx = 0xff;
+		sprintf(m->sname, "PC8736X");
+		sprintf(m->lname, "GPIO");
+
+		m->p0 = 21;
+
+		len = netlink_send(s, buf);
+
+		len = recv(s, buf, sizeof(buf), 0);
+		if (len == -1)
+		{
+			perror("recv buf");
+			close(s);
+			return -1;
+		}
+		reply = (struct nlmsghdr *)buf;
+		
+		switch (reply->nlmsg_type)
+		{
+			case NLMSG_ERROR:
+				fprintf(out, "Error message received.\n");
+				fflush(out);
+				break;
+			case NLMSG_DONE:
+				data = (struct cn_msg *)NLMSG_DATA(reply);
+				m = (struct sc_conn_data *)(data+1);
+				
+				time(&tm);
+				fprintf(out, "%.24s : [%x.%x] [seq=%u, ack=%u], sname=%s, lname=%s, idx=%u, cmd=%#02x [%#02x.%#02x.%#02x]\n", 
+					ctime(&tm), 
+					data->id.idx, data->id.val,
+					data->seq, data->ack, 
+					m->sname, m->lname, m->idx,
+					m->cmd,
+					m->p0, m->p1, m->p2);
+				fflush(out);
+				break;
+			default:
+				break;
+		}
+
+		sleep(1);
+	}
+
+	close(s);
+	return 0;
+}
+
--
	Evgeniy Polyakov ( s0mbre )

Crash is better than data corruption. -- Artur Grabowski
-------------- next part --------------
# do not edit -- automatically generated by arch changelog
# arch-tag: automatic-ChangeLog--johnpol at 2ka.mipt.ru-2004/connector--main--0
#

2004-09-09 07:04:41 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-6

    Summary:
      Use group 1 as start point not group 0.
    Revision:
      connector--main--0--patch-6

    Use group 1 as start point not group 0.

    modified files:
     cn_queue.c


2004-09-09 07:03:09 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-5

    Summary:
      Changed to support new protocol headers.
    Revision:
      connector--main--0--patch-5

    Changed to support new protocol headers.

    modified files:
     ucon.c


2004-09-09 07:02:27 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-4

    Summary:
      Fixed skb leaks in callback processing, added commented debug output.
    Revision:
      connector--main--0--patch-4

    Fixed skb leaks in callback processing, added commented debug output.

    modified files:
     connector.c


2004-09-09 04:28:26 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-3

    Summary:
      new header/message support.
    Revision:
      connector--main--0--patch-3

    New header/message support.

    modified files:
     ucon.c


2004-09-08 12:42:22 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-2

    Summary:
      Added cn_netlink_send(), changed packet identification.
    Revision:
      connector--main--0--patch-2

    Added cn_netlink_send() - sends provided packet through dev->nls socket.
    
    Changed packet identification - struct cb_id.

    modified files:
     cn_queue.c cn_queue.h cn_test.c connector.c connector.h

-------------- next part --------------
diff -Nru connector--main--0--patch-1/cn_queue.c connector--main--0--patch-6/cn_queue.c
--- connector--main--0--patch-1/cn_queue.c	2004-09-09 11:25:34.092287280 +0400
+++ connector--main--0--patch-6/cn_queue.c	2004-09-09 11:26:08.488058328 +0400
@@ -26,6 +26,7 @@
 #include <linux/workqueue.h>
 #include <linux/spinlock.h>
 #include <linux/slab.h>
+#include <linux/skbuff.h>
 
 #include "cn_queue.h"
 
@@ -74,6 +75,11 @@
 	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;
@@ -89,7 +95,7 @@
 	spin_lock(&dev->queue_lock);
 	list_for_each_entry_safe(__cbq, n, &dev->queue_list, callback_entry)
 	{
-		if (__cbq->cb->idx == cb->idx) 
+		if (cn_cb_equal(&__cbq->cb->id, &cb->id)) 
 		{
 			found = 1;
 			break;
@@ -102,10 +108,19 @@
 	}
 	spin_unlock(&dev->queue_lock);
 	
-	if (found)	
+	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;
 
-	return (found)?-EINVAL:0;
+	return 0;
 }
 
 void cn_queue_del_callback(struct cn_queue_dev *dev, struct cn_callback *cb)
@@ -116,7 +131,7 @@
 	spin_lock(&dev->queue_lock);
 	list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry)
 	{
-		if (cbq->cb->idx == cb->idx) 
+		if (cn_cb_equal(&cbq->cb->id, &cb->id)) 
 		{
 			list_del(&cbq->callback_entry);
 			found = 1;
@@ -133,7 +148,7 @@
 	}
 }
 
-struct cn_queue_dev *cn_queue_alloc_dev(char *name)
+struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls)
 {
 	struct cn_queue_dev *dev;
 
@@ -152,6 +167,9 @@
 	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)
 	{
diff -Nru connector--main--0--patch-1/cn_queue.h connector--main--0--patch-6/cn_queue.h
--- connector--main--0--patch-1/cn_queue.h	2004-09-09 11:25:33.794332576 +0400
+++ connector--main--0--patch-6/cn_queue.h	2004-09-09 11:26:08.489058176 +0400
@@ -22,9 +22,18 @@
 #ifndef __CN_QUEUE_H
 #define __CN_QUEUE_H
 
-#include <asm/atomic.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>
 
@@ -39,13 +48,16 @@
 	
 	struct list_head 	queue_list;
 	spinlock_t 		queue_lock;
+
+	int			netlink_groups;
+	struct sock		*nls;
 };
 
 struct cn_callback
 {
 	unsigned char		name[CN_CBQ_NAMELEN];
 	
-	u32			idx;
+	struct cb_id		id;
 	void			(* callback)(void *);
 	void			*priv;
 	
@@ -61,12 +73,18 @@
 	
 	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 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 connector--main--0--patch-1/cn_test.c connector--main--0--patch-6/cn_test.c
--- connector--main--0--patch-1/cn_test.c	2004-09-09 11:25:33.794332576 +0400
+++ connector--main--0--patch-6/cn_test.c	2004-09-09 11:26:08.489058176 +0400
@@ -25,27 +25,30 @@
 
 #include "connector.h"
 
-u32 cn_test_idx = 0x123;
-char cn_test_name[] = "cn_test";
+static struct cb_id cn_test_id = {0x123, 0x456};
+static char cn_test_name[] = "cn_test";
 
 void cn_test_callback(void *data)
 {
 	struct cn_msg *msg = (struct cn_msg *)data;
 
-	printk("%s: %x\n", __func__, msg->val);
+	printk("%s: idx=%x, val=%x, len=%d.\n", 
+			__func__, 
+			msg->id.idx, msg->id.val, msg->len);
 }
 
 static int cn_test_init(void)
 {
 	int err;
 
-	err = cn_add_callback(cn_test_idx, cn_test_name, cn_test_callback);
+	err = cn_add_callback(&cn_test_id, cn_test_name, cn_test_callback);
 	if (err)
 		return err;
-	err = cn_add_callback(cn_test_idx+1, cn_test_name, cn_test_callback);
+	cn_test_id.val++;
+	err = cn_add_callback(&cn_test_id, cn_test_name, cn_test_callback);
 	if (err)
 	{
-		cn_del_callback(cn_test_idx);
+		cn_del_callback(&cn_test_id);
 		return err;
 	}
 
@@ -54,8 +57,9 @@
 
 static void cn_test_fini(void)
 {
-	cn_del_callback(cn_test_idx);
-	cn_del_callback(cn_test_idx+1);
+	cn_del_callback(&cn_test_id);
+	cn_test_id.val--;
+	cn_del_callback(&cn_test_id);
 }
 
 module_init(cn_test_init);
diff -Nru connector--main--0--patch-1/connector.c connector--main--0--patch-6/connector.c
--- connector--main--0--patch-1/connector.c	2004-09-09 11:25:33.795332424 +0400
+++ connector--main--0--patch-6/connector.c	2004-09-09 11:26:08.506055592 +0400
@@ -28,8 +28,8 @@
 
 #include <net/sock.h>
 
-#include "connector.h"
-#include "cn_queue.h"
+#include "../connector/connector.h"
+#include "../connector/cn_queue.h"
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Evgeniy Polyakov <johnpol at 2ka.mipt.ru>");
@@ -40,18 +40,95 @@
 
 struct cn_dev cdev;
 
-static int cn_call_callback(struct cn_msg *msg, void (*destruct_data)(void *), void *data)
+/*
+ * 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)
 {
 	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;
 	
 	spin_lock(&dev->cbdev->queue_lock);
 	list_for_each_entry_safe(__cbq, n, &dev->cbdev->queue_list, callback_entry)
 	{
-		if (__cbq->cb->idx == msg->val)
+		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;
+	}
+
+	size = NLMSG_SPACE(sizeof(*msg) + msg->len);
+
+	skb = alloc_skb(size, GFP_ATOMIC);
+	if (!skb) {
+		printk(KERN_ERR "skb_alloc() failed.\n");
+		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;
@@ -69,17 +146,18 @@
 
 static int __cn_rx_skb(struct sk_buff *skb, struct nlmsghdr *nlh)
 {
-	u32 pid, uid, seq;
+	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);
-
-	printk(KERN_INFO "pid=%u, uid=%u, seq=%u.\n", 
-			pid, uid, seq);
-
+#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);
 }
 
@@ -104,7 +182,9 @@
 	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)
+		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));
@@ -114,11 +194,13 @@
 		len = NLMSG_ALIGN(nlh->nlmsg_len);
 		if (len > skb->len)
 			len = skb->len;
+
 		err = __cn_rx_skb(skb, nlh);
 		if (err)
 		{
 			if (err < 0)
 				netlink_ack(skb, nlh, -err);
+			kfree_skb(skb);
 			break;
 		}
 		else 
@@ -140,7 +222,7 @@
 		cn_rx_skb(skb);
 }
 
-int cn_add_callback(u32 idx, char *name, void (* callback)(void *))
+int cn_add_callback(struct cb_id *id, char *name, void (* callback)(void *))
 {
 	int err;
 	struct cn_dev *dev = &cdev;
@@ -157,7 +239,7 @@
 	
 	snprintf(cb->name, sizeof(cb->name), "%s", name);
 	
-	cb->idx 	= idx;
+	memcpy(&cb->id, id, sizeof(cb->id));
 	cb->callback 	= callback;
 	
 	atomic_set(&cb->refcnt, 0);
@@ -172,14 +254,14 @@
 	return 0;
 }
 
-void cn_del_callback(u32 idx)
+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 (__cbq->cb->idx == idx)
+		if (cn_cb_equal(&__cbq->cb->id, id))
 		{
 			cn_queue_del_callback(dev->cbdev, __cbq->cb);
 			break;
@@ -200,7 +282,7 @@
 		return -EIO;
 	}
 
-	dev->cbdev = cn_queue_alloc_dev("cqueue");
+	dev->cbdev = cn_queue_alloc_dev("cqueue", dev->nls);
 	if (!dev->cbdev)
 	{
 		if (dev->nls->sk_socket)
@@ -215,9 +297,9 @@
 {
 	struct cn_dev *dev = &cdev;
 
+	cn_queue_free_dev(dev->cbdev);
 	if (dev->nls->sk_socket)
 		sock_release(dev->nls->sk_socket);
-	cn_queue_free_dev(dev->cbdev);
 }
 
 module_init(cn_init);
@@ -225,3 +307,4 @@
 
 EXPORT_SYMBOL(cn_add_callback);
 EXPORT_SYMBOL(cn_del_callback);
+EXPORT_SYMBOL(cn_netlink_send);
Binary files connector--main--0--patch-1/connector.dia and connector--main--0--patch-6/connector.dia differ
diff -Nru connector--main--0--patch-1/connector.h connector--main--0--patch-6/connector.h
--- connector--main--0--patch-1/connector.h	2004-09-09 11:25:33.795332424 +0400
+++ connector--main--0--patch-6/connector.h	2004-09-09 11:26:08.506055592 +0400
@@ -22,6 +22,21 @@
 #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];
+};
+
 #ifdef __KERNEL__
 
 #include <net/sock.h>
@@ -35,16 +50,9 @@
 	struct cn_queue_dev	*cbdev;
 };
 
-int cn_add_callback(u32 idx, char *name, void (* callback)(void *));
-void cn_del_callback(u32 idx);
-
-#endif
-
-#define CONNECTOR_MAX_MSG_SIZE 	1024
-
-struct cn_msg
-{
-	__u32			val;
-};
+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 *);
 
+#endif /* __KERNEL__ */
 #endif /* __CONNECTOR_H */
diff -Nru connector--main--0--patch-1/ucon.c connector--main--0--patch-6/ucon.c
--- connector--main--0--patch-1/ucon.c	2004-09-09 11:25:33.794332576 +0400
+++ connector--main--0--patch-6/ucon.c	2004-09-09 11:26:08.497056960 +0400
@@ -38,21 +38,21 @@
 #include <time.h>
 
 #include "connector.h"
+#include "../soekris/sc_conn.h"
 
 static int need_exit;
 __u32 seq; 
 
-static int netlink_send(int s)
+static int netlink_send(int s, char *data)
 {
 	struct nlmsghdr *nlh;
-	struct cn_msg *msg;
 	unsigned int size;
-	char buf[128];
 	int err;
+	char buf[128];
+	struct cn_msg *m, *msg;
+	struct sc_conn_data *cmd;
 
-	memset(buf, 0, sizeof(buf));
-
-	size = NLMSG_SPACE(sizeof(struct cn_msg));
+	size = NLMSG_SPACE(sizeof(struct cn_msg) + sizeof(struct sc_conn_data));
 	
 	nlh = (struct nlmsghdr *)buf;
 	nlh->nlmsg_seq = seq++;
@@ -61,9 +61,19 @@
 	nlh->nlmsg_len = NLMSG_LENGTH(size - sizeof(*nlh));
 	nlh->nlmsg_flags = 0;
 
-	msg = NLMSG_DATA(nlh);
+	m = NLMSG_DATA(nlh);
+	msg = (struct cn_msg *)data;
+	cmd = (struct sc_conn_data *)(msg + 1);
+
+	printf("%s: len=%u, seq=%u, ack=%u, "
+		"sname=%s, lname=%s, idx=0x%x, cmd=%02x [%02x.%02x.%02x].\n",
+			__func__, 
+			msg->len, msg->seq, msg->ack,
+			cmd->sname,
+			cmd->lname, cmd->idx,
+			cmd->cmd, cmd->p0, cmd->p1, cmd->p2);
 
-	msg->val = nlh->nlmsg_seq;
+	memcpy(m, data, sizeof(*m) + msg->len);
 
 	err = send(s, nlh, size, 0);
 	if (err == -1)
@@ -81,6 +91,7 @@
 	struct nlmsghdr *reply;
 	struct sockaddr_nl l_local;
 	struct cn_msg *data;
+	struct sc_conn_data *m;
 	FILE *out;
 	time_t tm;
 	struct pollfd pfd;
@@ -108,7 +119,7 @@
 	}
 	
 	l_local.nl_family = AF_NETLINK;
-	l_local.nl_groups = 23;
+	l_local.nl_groups = 1;
 	l_local.nl_pid    = getpid();
 	
 	if (bind(s, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl)) == -1)
@@ -140,7 +151,25 @@
 		if (need_exit)
 			break;
 
-		len = netlink_send(s);
+		data = (struct cn_msg *)buf;
+
+		data->id.idx 	= 0xaabb;
+		data->id.val 	= 0xccdd;
+		data->seq 	= seq++;
+		data->ack 	= (seq ^ 0x1234);
+		data->len 	= sizeof(*m);
+
+		m = (struct sc_conn_data *)(data+1);
+		memset(m, 0, sizeof(*m));
+
+		m->cmd = SC_CMD_LDEV_READ;
+		m->idx = 0xff;
+		sprintf(m->sname, "PC8736X");
+		sprintf(m->lname, "GPIO");
+
+		m->p0 = 21;
+
+		len = netlink_send(s, buf);
 
 		len = recv(s, buf, sizeof(buf), 0);
 		if (len == -1)
@@ -159,14 +188,23 @@
 				break;
 			case NLMSG_DONE:
 				data = (struct cn_msg *)NLMSG_DATA(reply);
+				m = (struct sc_conn_data *)(data+1);
+				
 				time(&tm);
-				fprintf(out, "%.24s : %u\n", 
-					ctime(&tm), data->val);
+				fprintf(out, "%.24s : [%x.%x] [seq=%u, ack=%u], sname=%s, lname=%s, idx=%u, cmd=%#02x [%#02x.%#02x.%#02x]\n", 
+					ctime(&tm), 
+					data->id.idx, data->id.val,
+					data->seq, data->ack, 
+					m->sname, m->lname, m->idx,
+					m->cmd,
+					m->p0, m->p1, m->p2);
 				fflush(out);
 				break;
 			default:
 				break;
 		}
+
+		sleep(1);
 	}
 
 	close(s);
-------------- next part --------------
diff -Nru soekris--main--0--patch-56/chain.h soekris--main--0--patch-66/chain.h
--- soekris--main--0--patch-56/chain.h	2004-09-08 08:54:19.000000000 +0400
+++ soekris--main--0--patch-66/chain.h	2004-09-09 11:09:01.422196072 +0400
@@ -23,6 +23,8 @@
 #ifndef __CHAIN_H
 #define __CHAIN_H
 
+#include <linux/list.h>
+
 struct dev_chain
 {
 	struct list_head	chain_entry;
diff -Nru soekris--main--0--patch-56/Makefile soekris--main--0--patch-66/Makefile
--- soekris--main--0--patch-56/Makefile	2004-09-08 08:54:18.000000000 +0400
+++ soekris--main--0--patch-66/Makefile	2004-09-09 11:09:01.026256264 +0400
@@ -1,5 +1,5 @@
 obj-m		:= superio.o sc_gpio.o pc8736x.o sc_acb.o scx200.o
-superio-objs	:= sc.o chain.o
+superio-objs	:= sc.o chain.o sc_conn.o
 
 #KDIR	:= /lib/modules/$(shell uname -r)/build
 KDIR	:= /usr/local/src/linux-2.6/linux-2.6.6
diff -Nru soekris--main--0--patch-56/sc.c soekris--main--0--patch-66/sc.c
--- soekris--main--0--patch-56/sc.c	2004-09-08 08:54:18.000000000 +0400
+++ soekris--main--0--patch-66/sc.c	2004-09-09 11:09:01.017257632 +0400
@@ -33,6 +33,7 @@
 #include <linux/slab.h>
 
 #include "sc.h"
+#include "sc_conn.h"
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Evgeniy Polyakov <johnpol at 2ka.mipt.ru>");
@@ -215,8 +216,6 @@
 	atomic_inc(&dev->refcnt);
 	list_add_tail(&lch->chain_entry, &dev->chain_list);
 	
-	printk("%s: ldev->refcnt[%p] = %d\n", __func__, &__ldev->refcnt, atomic_read(&__ldev->refcnt));
-	
 	__found = 0;
 	spin_lock(&ldev_lock);
 	list_for_each_entry(ldev, &ldev_list, ldev_entry)
@@ -309,7 +308,6 @@
 		{
 			list_del(&ch->chain_entry);
 			atomic_dec(&ldev->refcnt);
-			printk("%s: ldev->refcnt[%p] = %d\n", __func__, &ldev->refcnt, atomic_read(&ldev->refcnt));
 			chain_free(ch);
 
 			sc_deactivate_logical(dev, ldev);
@@ -391,7 +389,6 @@
 		printk(KERN_INFO "Waiting logical device %s [%x] [%s] to become free: refcnt=%d.\n",
 				ldev->name, ldev->index, 
 				(sc_ldev_is_clone(ldev))?"clone":"not clone", atomic_read(&ldev->refcnt));
-			printk("%s: ldev->refcnt[%p] = %d\n", __func__, &ldev->refcnt, atomic_read(&ldev->refcnt));
 		set_current_state(TASK_INTERRUPTIBLE);
 		schedule_timeout(HZ);
 	}
@@ -493,7 +490,6 @@
 
 		list_del(&ch->chain_entry);
 		atomic_dec(&ldev->refcnt);
-			printk("%s: ldev->refcnt[%p] = %d\n", __func__, &ldev->refcnt, atomic_read(&ldev->refcnt));
 		chain_free(ch);
 
 		sc_deactivate_logical(__sdev, ldev);
@@ -572,7 +568,6 @@
 		if (!strcmp(name, ldev->name) && ldev->index == index)
 		{
 			atomic_inc(&ldev->refcnt);
-			printk("%s: ldev->refcnt[%p] = %d\n", __func__, &ldev->refcnt, atomic_read(&ldev->refcnt));
 			spin_unlock(&ldev_lock);
 			return ldev;
 		}
@@ -595,7 +590,6 @@
 		if (!strcmp(name, ldev->name))
 		{
 			atomic_inc(&ldev->refcnt);
-			printk("%s: ldev->refcnt[%p] = %d\n", __func__, &ldev->refcnt, atomic_read(&ldev->refcnt));
 			spin_unlock(&ldev_lock);
 			return ldev;
 		}
@@ -605,10 +599,34 @@
 	return NULL;
 }
 
+/*
+ * Get the first logical device with the given name connected to given SuperIO chip.
+ */
+struct logical_dev *sc_get_ldev_in_sdev(char *name, struct sc_dev *sdev)
+{
+	struct dev_chain *ch;
+	struct logical_dev *ldev;
+
+	spin_lock(&sdev->chain_lock);
+	list_for_each_entry(ch, &sdev->chain_list, chain_entry)
+	{
+		ldev = ch->ptr;
+
+		if (!strcmp(name, ldev->name))
+		{
+			atomic_inc(&ldev->refcnt);
+			spin_unlock(&sdev->chain_lock);
+			return ldev;
+		}
+	}
+	spin_unlock(&sdev->chain_lock);
+
+	return NULL;
+}
+
 void sc_put_ldev(struct logical_dev *ldev)
 {
 	atomic_dec(&ldev->refcnt);
-			printk("%s: ldev->refcnt[%p] = %d\n", __func__, &ldev->refcnt, atomic_read(&ldev->refcnt));
 }
 
 /*
@@ -633,11 +651,9 @@
 	spin_lock_init(&__ldev->lock);
 	
 	atomic_inc(&ldev->refcnt);
-			printk("%s: ldev->refcnt[%p] = %d\n", __func__, &ldev->refcnt, atomic_read(&ldev->refcnt));
 	set_bit(LDEV_CLONED, (long *)&ldev->flags);
 
 	atomic_set(&__ldev->refcnt, 0);	
-			printk("%s: __ldev->refcnt[%p] = %d\n", __func__, &__ldev->refcnt, atomic_read(&__ldev->refcnt));
 	__ldev->orig_ldev = ldev;
 
 	return __ldev;
@@ -732,11 +748,12 @@
 {
 	printk(KERN_INFO "Soekris SuperIO driver is starting...\n");
 	
-	return 0;
+	return sc_register_callback();
 }
 
 static void __devexit sc_fini(void)
 {
+	sc_unregister_callback();
 	printk(KERN_INFO "Soekris SuperIO driver finished.\n");
 }
 
@@ -746,6 +763,7 @@
 EXPORT_SYMBOL(sc_add_logical_dev);
 EXPORT_SYMBOL(sc_del_logical_dev);
 EXPORT_SYMBOL(sc_get_ldev);
+EXPORT_SYMBOL(sc_get_ldev_in_sdev);
 EXPORT_SYMBOL(sc_put_ldev);
 EXPORT_SYMBOL(sc_add_sc_dev);
 EXPORT_SYMBOL(sc_del_sc_dev);
diff -Nru soekris--main--0--patch-56/sc_conn.c soekris--main--0--patch-66/sc_conn.c
--- soekris--main--0--patch-56/sc_conn.c	1970-01-01 03:00:00.000000000 +0300
+++ soekris--main--0--patch-66/sc_conn.c	2004-09-09 11:09:01.026256264 +0400
@@ -0,0 +1,133 @@
+/*
+ * 	sc_conn.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include "sc.h"
+#include "sc_conn.h"
+#include "../connector/connector.h"
+
+#define SC_CONN_IDX		0xaabb
+#define SC_CONN_VAL		0xccdd
+
+static struct cb_id sc_conn_id = {SC_CONN_IDX, SC_CONN_VAL};
+
+static void sc_conn_callback(void *data)
+{
+	struct cn_msg *reply, *msg = (struct cn_msg *)data;
+	struct sc_conn_data *rcmd, *cmd = (struct sc_conn_data *)(msg+1);
+	struct logical_dev *ldev;
+	struct sc_dev *sdev;
+	u8 ret;
+
+	if (msg->len != sizeof(*cmd))
+	{
+		printk(KERN_ERR "Wrong additional data size %u, must be %u.\n",
+				msg->len, sizeof(*cmd));
+		return;
+	}
+#if 0
+	printk("%s: len=%u, seq=%u, ack=%u, sname=%s, lname=%s, idx=0x%x, cmd=%02x [%02x.%02x.%02x].\n",
+			__func__, 
+			msg->len, msg->seq, msg->ack,
+			cmd->sname,
+			cmd->lname, cmd->idx,
+			cmd->cmd, cmd->p0, cmd->p1, cmd->p2);
+#endif
+	sdev = sc_get_sdev(cmd->sname);
+	if (!sdev)
+	{
+		printk(KERN_ERR "%s: sdev %s does not exist.\n", __func__, cmd->sname);
+		return;
+	}
+	
+	ldev = sc_get_ldev_in_sdev(cmd->lname, sdev);
+	if (!ldev)
+	{
+		printk(KERN_ERR "%s: ldev %s does not exist in chip %s.\n", 
+				__func__, cmd->lname, cmd->sname);
+		sc_put_sdev(sdev);
+		return;
+	}
+	
+	ret = 0;
+	switch (cmd->cmd)
+	{
+		case SC_CMD_LDEV_READ:
+			ret = ldev->read(ldev, cmd->p0);
+			reply = kmalloc(sizeof(*msg) + sizeof(*cmd), GFP_ATOMIC);
+			if (reply)
+			{
+				memcpy(reply, msg, sizeof(*reply));
+
+				/*
+				 * See protocol description in connecter.c
+				 */
+				reply->ack++;
+				
+				rcmd = (struct sc_conn_data *)(reply + 1);
+				memcpy(rcmd, cmd, sizeof(*rcmd));
+				
+				rcmd->cmd = SC_CMD_LDEV_READ;
+				rcmd->p0 = cmd->p0;
+				rcmd->p1 = ret;
+
+				cn_netlink_send(reply);
+
+				kfree(reply);
+			}
+			else
+				printk(KERN_ERR "Failed to allocate %d bytes in reply to comamnd 0x%x.\n",
+						sizeof(*msg) + sizeof(*cmd), cmd->cmd);
+			break;
+		case SC_CMD_LDEV_WRITE:
+			ldev->write(ldev, cmd->p0, cmd->p1);
+			break;
+		case SC_CMD_LDEV_CONTROL:
+			ldev->control(ldev, cmd->p0, cmd->p1, cmd->p2);
+			break;
+		case SC_CMD_LDEV_ACTIVATE:
+			ldev->activate(ldev);
+			break;
+		default:
+			printk(KERN_ERR "Unsupported command 0x%x for %s in chip %s.\n",
+					cmd->cmd, ldev->name, sdev->name);
+			break;
+	}
+		
+	sc_put_ldev(ldev);
+	sc_put_sdev(sdev);
+}
+
+int sc_register_callback(void)
+{
+	return cn_add_callback(&sc_conn_id, "sc_callback", (void (*)(void *))&sc_conn_callback);
+}
+
+void sc_unregister_callback(void)
+{
+	return cn_del_callback(&sc_conn_id);
+}
+
+EXPORT_SYMBOL(sc_register_callback);
+EXPORT_SYMBOL(sc_unregister_callback);
diff -Nru soekris--main--0--patch-56/sc_conn.h soekris--main--0--patch-66/sc_conn.h
--- soekris--main--0--patch-56/sc_conn.h	1970-01-01 03:00:00.000000000 +0300
+++ soekris--main--0--patch-66/sc_conn.h	2004-09-09 11:09:01.026256264 +0400
@@ -0,0 +1,50 @@
+/*
+ * 	sc_conn.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
+ * 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 __SC_CONN_H
+#define __SC_CONN_H
+
+enum sc_conn_cmd
+{
+	SC_CMD_LDEV_READ = 0,
+	SC_CMD_LDEV_WRITE,
+	SC_CMD_LDEV_CONTROL,
+	SC_CMD_LDEV_ACTIVATE,
+
+	__SC_CMD_MAX_CMD_NUMBER
+};
+
+struct sc_conn_data
+{
+	char		sname[16];
+	char		lname[16];
+	__u32		idx;
+	
+	__u8		cmd;
+	__u8		p0, p1, p2;
+
+	__u8		data[0];
+};
+
+int sc_register_callback(void);
+void sc_unregister_callback(void);
+
+#endif /* __SC_CONN_H */
diff -Nru soekris--main--0--patch-56/sc.h soekris--main--0--patch-66/sc.h
--- soekris--main--0--patch-56/sc.h	2004-09-08 08:54:18.000000000 +0400
+++ soekris--main--0--patch-66/sc.h	2004-09-09 11:09:01.017257632 +0400
@@ -113,6 +113,7 @@
 int sc_add_logical_dev(struct sc_dev *, struct logical_dev *);
 void sc_del_logical_dev(struct logical_dev *);
 struct logical_dev *sc_get_ldev(char *);
+struct logical_dev *sc_get_ldev_in_sdev(char *, struct sc_dev *);
 void sc_put_ldev(struct logical_dev *);
 
 int sc_add_sc_dev(struct sc_dev *);
diff -Nru soekris--main--0--patch-56/scx200.c soekris--main--0--patch-66/scx200.c
--- soekris--main--0--patch-56/scx200.c	2004-09-08 08:54:18.000000000 +0400
+++ soekris--main--0--patch-66/scx200.c	2004-09-09 11:09:01.027256112 +0400
@@ -56,7 +56,6 @@
 	{"ACB", 	0x06},
 	{"SPORT", 	0x08},
 	{"GPIO",	LDEV_PRIVATE},
-	{"GPIO",	LDEV_PRIVATE},
 	{}
 };
 
@@ -107,6 +106,103 @@
 	return val;
 }
 
+static u8 scx200_gpio_read(void *data, int pin_number)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	
+	if (pin_number > 63 || pin_number < 0)
+		return 0;
+
+	bank = 0;
+	base = ldev->base_addr;
+	
+	if (pin_number > 31)
+	{
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+
+	val = inl(base + 0x04);
+
+	return ((val >> pin_number) & 0x01) ;
+}
+
+static void scx200_gpio_write(void *data, int pin_number, u8 byte)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	if (pin_number > 63 || pin_number < 0)
+		return;
+
+	bank = 0;
+	base = ldev->base_addr;
+	
+	if (pin_number > 31)
+	{
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+	
+	val = inl(base);
+
+	if (byte)
+		val |= (1 << pin_number);
+	else
+		val &= ~(1 << pin_number);
+	
+	outl(val, base);
+}
+
+void scx200_gpio_control(void *data, int pin_number, u8 mask, u8 ctl)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	if (pin_number > 63 || pin_number < 0)
+		return;
+
+	bank = 0;
+	base = ldev->base_addr;
+	
+	if (pin_number > 31)
+	{
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+
+	/*
+	 * Pin selection.
+	 */
+
+	val = 0;
+	val = ((bank & 0x01) << 5) | pin_number;
+	outl(val, ldev->base_addr + 0x20);
+
+	val = inl(ldev->base_addr + 0x24);
+
+	val &= 0x7f;
+	val &= mask;
+	val |= ctl;
+	
+	outl(val, ldev->base_addr + 0x24);
+}
+
+int scx200_gpio_activate(void *data)
+{
+	return 0;
+}
+
 static int scx200_ldev_index_by_name(char *name)
 {
 	int i;
@@ -209,6 +305,11 @@
 	ldev->base_addr = private_base;
 	private_base += 0x10;
 
+	ldev->read	= scx200_gpio_read;
+	ldev->write 	= scx200_gpio_write;
+	ldev->control 	= scx200_gpio_control;
+	ldev->activate	= scx200_gpio_activate;
+
 	return 0;
 }
 
-------------- next part --------------
# do not edit -- automatically generated by arch changelog
# arch-tag: automatic-ChangeLog--johnpol at 2ka.mipt.ru-2004/soekris--main--0
#

2004-09-09 07:07:21 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-66

    Summary:
      Added callback registration prototype definitions, marked first entry in enum as 0.
    Revision:
      soekris--main--0--patch-66

    Added callback registration prototype definitions, marked first entry in enum as 0.

    modified files:
     sc_conn.h


2004-09-09 07:06:43 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-65

    Summary:
      Commented some debug info, fixed leak in reply messge, added callback registration routing.
    Revision:
      soekris--main--0--patch-65

    Commented some debug info, fixed leak in reply messge, added callback registration routing.

    modified files:
     sc_conn.c


2004-09-09 07:05:41 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-64

    Summary:
      Removed unneded debug output, added callback registration.
    Revision:
      soekris--main--0--patch-64

    Removed unneded debug output, added callback registration.

    modified files:
     sc.c


2004-09-08 12:44:53 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-63

    Summary:
      Added sc_conn - initial part of migrating to "connector".
    Revision:
      soekris--main--0--patch-63

    Added sc_conn - initial part of migrating to "connector".

    new files:
     .arch-ids/sc_conn.c.id .arch-ids/sc_conn.h.id sc_conn.c
     sc_conn.h

    modified files:
     Makefile


2004-09-08 12:43:50 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-62

    Summary:
      Added sc_conn.h include.
    Revision:
      soekris--main--0--patch-62

    Added sc_conn.h include.

    modified files:
     sc.c


2004-09-08 12:43:10 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-61

    Summary:
      Fixes includes.
    Revision:
      soekris--main--0--patch-61

    Fixes includes.

    modified files:
     chain.h


2004-09-08 08:05:11 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-60

    Summary:
      Need to prototype declaration of sc_get_ldev_in_sdev().
    Revision:
      soekris--main--0--patch-60

    Need to prototype declaration of sc_get_ldev_in_sdev().

    modified files:
     sc.h


2004-09-08 08:04:08 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-59

    Summary:
      Need to export sc_get_ldev_in_sdev().
    Revision:
      soekris--main--0--patch-59

    Need to export sc_get_ldev_in_sdev().

    modified files:
     sc.c


2004-09-08 08:00:51 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-58

    Summary:
      Added sc_get_ldev_in_sdev() - searches first logical device with the given name connected to given SuperIO chip.
    Revision:
      soekris--main--0--patch-58

    Added sc_get_ldev_in_sdev() - searches first logical device with the given name connected to given SuperIO chip.

    modified files:
     sc.c


2004-09-08 07:58:20 GMT	Evgeniy Polyakov <johnpol at 2ka.mipt.ru>	patch-57

    Summary:
      Added scx200_gpio_* functions().
    Revision:
      soekris--main--0--patch-57

    Added scx200_gpio_* functions() - they must be used for GPIO operations with SCx200/SC1100 chip.
    Functions provided by the higher SuperIO layer are substituted with private ones.

    modified files:
     scx200.c
-------------- next part --------------
/*
 * 	ucon.c
 *
 * Copyright (c) 2004 Evgeniy Polyakov <johnpol at 2ka.mipt.ru>
 * 
 *
 * 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 <asm/types.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>

#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include "connector.h"
#include "../soekris/sc_conn.h"

static int need_exit;
__u32 seq; 

static int netlink_send(int s, char *data)
{
	struct nlmsghdr *nlh;
	unsigned int size;
	int err;
	char buf[128];
	struct cn_msg *m, *msg;
	struct sc_conn_data *cmd;

	size = NLMSG_SPACE(sizeof(struct cn_msg) + sizeof(struct sc_conn_data));
	
	nlh = (struct nlmsghdr *)buf;
	nlh->nlmsg_seq = seq++;
	nlh->nlmsg_pid = getpid();
	nlh->nlmsg_type = NLMSG_DONE;
	nlh->nlmsg_len = NLMSG_LENGTH(size - sizeof(*nlh));
	nlh->nlmsg_flags = 0;

	m = NLMSG_DATA(nlh);
	msg = (struct cn_msg *)data;
	cmd = (struct sc_conn_data *)(msg + 1);

	printf("%s: len=%u, seq=%u, ack=%u, "
		"sname=%s, lname=%s, idx=0x%x, cmd=%02x [%02x.%02x.%02x].\n",
			__func__, 
			msg->len, msg->seq, msg->ack,
			cmd->sname,
			cmd->lname, cmd->idx,
			cmd->cmd, cmd->p0, cmd->p1, cmd->p2);

	memcpy(m, data, sizeof(*m) + msg->len);

	err = send(s, nlh, size, 0);
	if (err == -1)
		fprintf(stderr, "Failed to send: %s [%d].\n",
				strerror(errno), errno);

	return err;
}

int main(int argc, char *argv[])
{
	int s;
	char buf[1024];
	int len;
	struct nlmsghdr *reply;
	struct sockaddr_nl l_local;
	struct cn_msg *data;
	struct sc_conn_data *m;
	FILE *out;
	time_t tm;
	struct pollfd pfd;
	
	if (argc < 2)
		out = stdout;
	else
	{
		out = fopen(argv[1], "a+");
		if (!out)
		{
			fprintf(stderr, "Unable to open %s for writing: %s\n", 
					argv[1], strerror(errno));
			out = stdout;
		}
	}
	
	memset(buf, 0, sizeof(buf));
	
	s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_NFLOG);
	if (s == -1)
	{
		perror("socket");
		return -1;
	}
	
	l_local.nl_family = AF_NETLINK;
	l_local.nl_groups = 1;
	l_local.nl_pid    = getpid();
	
	if (bind(s, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl)) == -1)
	{
		perror("bind");
		close(s);
		return -1;
	}

	pfd.fd = s;

	while (!need_exit)
	{
		pfd.events = POLLIN;
		pfd.revents = 0;
		/*switch (poll(&pfd, 1, -1)) 
		{
			case 0:
				need_exit = 1;
				break;
			case -1:
				if (errno != EINTR) 
				{
					need_exit = 1;
					break;
				}
				continue;
		}*/
		if (need_exit)
			break;

		data = (struct cn_msg *)buf;

		data->id.idx 	= 0xaabb;
		data->id.val 	= 0xccdd;
		data->seq 	= seq++;
		data->ack 	= (seq ^ 0x1234);
		data->len 	= sizeof(*m);

		m = (struct sc_conn_data *)(data+1);
		memset(m, 0, sizeof(*m));

		m->cmd = SC_CMD_LDEV_READ;
		m->idx = 0xff;
		sprintf(m->sname, "PC8736X");
		sprintf(m->lname, "GPIO");

		m->p0 = 21;

		len = netlink_send(s, buf);

		len = recv(s, buf, sizeof(buf), 0);
		if (len == -1)
		{
			perror("recv buf");
			close(s);
			return -1;
		}
		reply = (struct nlmsghdr *)buf;
		
		switch (reply->nlmsg_type)
		{
			case NLMSG_ERROR:
				fprintf(out, "Error message received.\n");
				fflush(out);
				break;
			case NLMSG_DONE:
				data = (struct cn_msg *)NLMSG_DATA(reply);
				m = (struct sc_conn_data *)(data+1);
				
				time(&tm);
				fprintf(out, "%.24s : [%x.%x] [seq=%u, ack=%u], sname=%s, lname=%s, idx=%u, cmd=%#02x [%#02x.%#02x.%#02x]\n", 
					ctime(&tm), 
					data->id.idx, data->id.val,
					data->seq, data->ack, 
					m->sname, m->lname, m->idx,
					m->cmd,
					m->p0, m->p1, m->p2);
				fflush(out);
				break;
			default:
				break;
		}

		sleep(1);
	}

	close(s);
	return 0;
}



[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux