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; }