01 - adds superio_locks module

User-drivers specify the sio-port characteristics they can support
device-ids, sio-port-addrs, enter & exit sequences, etc in a struct superio_search (in __devinit, preferably). superio_find() then searches existing slots/shared-reservations for a matching sio-port, and returns it if found. Otherwize it probes port-addrs, specified by find() user, and makes and returns a new reservation.

superio_find() finds and reserves the slot, returned as ptr or null
   superio_release()    relinguishes the slot (ref-counted)

Once theyve got the reservation in struct superio * gate (as named in patches 2-5) they *may* use

   superio_lock(gate)
   superio_enter/exit(gate)
   superio_inb/w(gate, regaddr),
   superio_outb/w(gate, regaddr, val)

or they can do it themselves with inb/outb, by using gate->sioaddr, etc.

The API names (superio_find etc) were chosen to fit the idiom used in hwmon/*.c, patches 2-5 remove the per-user-driver
copies of the superio_*() fns.

I added the module to /drivers/hwmon, mostly cuz thats where
Ive used it - perhaps drivers/isa is better ?


Signed-off-by:  Jim Cromie <[EMAIL PROTECTED]>
---
hwmon-superio-module
drivers/hwmon/Kconfig         |    9 +
drivers/hwmon/Makefile | 1 drivers/hwmon/superio_locks.c | 235 ++++++++++++++++++++++++++++++++++++++++++
include/linux/superio-locks.h |  112 ++++++++++++++++++++
4 files changed, 357 insertions(+)

diff -ruNp -X dontdiff -X exclude-diffs 
hwmon-fan-push-offset/drivers/hwmon/Kconfig 
hwmon-superio.old/drivers/hwmon/Kconfig
--- hwmon-fan-push-offset/drivers/hwmon/Kconfig 2007-10-14 13:00:24.000000000 
-0600
+++ hwmon-superio.old/drivers/hwmon/Kconfig     2007-10-14 17:22:23.000000000 
-0600
@@ -750,4 +765,13 @@ config HWMON_DEBUG_CHIP
          a problem with I2C support and want to see more of what is going
          on.

+config SUPERIO_LOCKS
+       tristate "Super-IO port sharing"
+       default n
+       help
+         This module provides a shared reservation for use by drivers
+         which need to share access to a multi-function device via
+         its superio port, and which register that port.
+
endif # HWMON
+
diff -ruNp -X dontdiff -X exclude-diffs 
hwmon-fan-push-offset/drivers/hwmon/Makefile 
hwmon-superio.old/drivers/hwmon/Makefile
--- hwmon-fan-push-offset/drivers/hwmon/Makefile        2007-10-14 
13:00:24.000000000 -0600
+++ hwmon-superio.old/drivers/hwmon/Makefile    2007-10-14 17:22:23.000000000 
-0600
@@ -72,3 +72,4 @@ ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
EXTRA_CFLAGS += -DDEBUG
endif

+obj-$(CONFIG_SUPERIO_LOCKS)    += superio_locks.o
diff -ruNp -X dontdiff -X exclude-diffs 
hwmon-fan-push-offset/drivers/hwmon/superio_locks.c 
hwmon-superio.old/drivers/hwmon/superio_locks.c
--- hwmon-fan-push-offset/drivers/hwmon/superio_locks.c 1969-12-31 
17:00:00.000000000 -0700
+++ hwmon-superio.old/drivers/hwmon/superio_locks.c     2007-10-14 
20:27:49.000000000 -0600
@@ -0,0 +1,235 @@
+
+#define DRVNAME "superio_locks"
+#define DEBUG 1
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/ioport.h>
+#include <linux/superio-locks.h>
+
+MODULE_AUTHOR("Jim Cromie <[EMAIL PROTECTED]");
+MODULE_LICENSE("GPL");
+
+/*
+ * This module allows multiple driver modules to coordinate their use
+ * of a Super-IO port to control the multiple logical devices behind
+ * it.  Drivers will superio_find() their port, and 2 such modules
+ * will share a slot containing the lock.
+ */
+static int max_locks = 3;      /* 3 is enough for 90% uses */
+module_param(max_locks, int, 0);
+MODULE_PARM_DESC(max_locks,
+                " Number of sio-lock clients to serve (default=3)");
+
+static int paranoid;
+module_param(paranoid, int, 0);
+MODULE_PARM_DESC(paranoid,
+                " when true, fails if superio-port region is claimed");
+
+static struct superio *sio_locks;
+static struct mutex reservation_lock;
+
+#define SIO_DEVID_ADDR_STD 0x20
+#define SIO_LDN_ADDR_STD 0x07
+
+struct superio* superio_probe_reserve(const struct superio_search * const 
where,
+                                     int cmd_addr, int want_devid)
+{
+       struct superio *gate;
+       u16 devid_addr = where->devid_addr;
+       u16 mydevid;
+       int slot, rc, i;
+
+       if (!devid_addr)
+               devid_addr = SIO_DEVID_ADDR_STD;
+
+
+       /* send superio-enter sequence for devices which need them */
+       for (i = 0; i < SEQ_SZ && where->enter_seq[i]; i++)
+               outb(where->enter_seq[i], cmd_addr);
+
+       outb(devid_addr, cmd_addr);
+
+       /* sanity check that cmd-reg remembers the val just written
+       rc = inb(cmd_addr);
+       if (rc != (devid_addr & 0xFF)) {
+               pr_debug("cmd-reg absent at %x, got %x\n", cmd_addr, rc);
+               return NULL;
+       }
+       */
+       /* Read the device-id register(s), using cmd written above */
+       if (!where->devid_word) {
+               mydevid = inb(cmd_addr+1);
+       } else {
+               /* want 16 bit devid, so get it */
+               mydevid = inb(cmd_addr+1) << 8;
+               outb(devid_addr+1, cmd_addr);
+               mydevid |= inb(cmd_addr+1);
+       }
+
+       /* test for the desired device id value */
+       mydevid &= ~ where->devid_mask;
+       if (mydevid != want_devid) {
+               pr_debug("got devid %x, want %x\n", mydevid, want_devid);
+               return NULL;
+       }
+       /* find 1st unused slot */
+       for (slot = 0; slot < max_locks; slot++)
+               if (!sio_locks[slot].users) {
+                       gate = &sio_locks[slot];
+                       break;
+               }
+       if (slot >= max_locks) {
+               printk(KERN_ERR DRVNAME
+                      ": No superio-locks left. increase max_locks\n");
+               return NULL;
+       }
+       /* grab the io-port region */
+       if (!request_region(cmd_addr, 2, DRVNAME)) {
+               printk(KERN_ERR DRVNAME
+                      ": superio port region already claimed\n");
+               if (paranoid)
+                       return NULL;
+       }
+
+       pr_debug("allocating slot %d, addr %x for device %x\n",
+                slot, cmd_addr, want_devid);
+
+       gate->sioaddr = cmd_addr;
+       gate->devid = want_devid;
+       gate->users = 1;
+
+       return gate;
+}
+/*
+ * superio_get() checks whether the desired SuperIO device has been
+ * reserved, and shares that reservation.  Otherwize it calls
+ * superio_probe_reserve to make a new reservation.
+ */
+static struct superio* superio_get(const struct superio_search * const where,
+                                  int cmd_addr, int want_devid)
+{
+       int slot;
+       struct superio *gate;
+
+       /* share any already allocated lock for this cmd_addr, device-id */
+       for (slot = 0; slot < max_locks; slot++) {
+               gate = &sio_locks[slot];
+
+               if (gate->users && cmd_addr == gate->sioaddr
+                   && want_devid == gate->devid) {
+
+                       if (gate->users >= 255) {
+                               pr_info("too many drivers sharing port %x\n",
+                                       gate->sioaddr);
+                               return NULL;
+                       }
+                       gate->users++;
+                       pr_debug("sharing port:%x dev:%x users:%d\n",
+                               gate->sioaddr, gate->devid, gate->users);
+                       return gate;
+               }
+       }
+       /* no existing reservation found */
+       return superio_probe_reserve(where, cmd_addr, want_devid);
+}
+
+/**
+ * superio_find: find a superio-port, share a reservation on it.
+ * @search-criterion : device-id addrs, values that driver supports.
+ * Description: searches for a superio-port with one of the specified
+ * device-ids at one of the command-addresses.  If port is found,
+ * creates an sio-locks reservation, or shares the one previously
+ * created (by another user-driver)
+ */
+struct superio* superio_find(const struct superio_search * const where)
+{
+       int ci, di;
+       struct superio* gate = NULL;
+
+       mutex_lock(&reservation_lock);
+
+       for (ci = 0; ci < ARRAY_SIZE(where->cmdreg_addrs)
+              && where->cmdreg_addrs[ci]; ci++) {
+ + for (di = 0; di < ARRAY_SIZE(where->device_ids)
+                      && where->device_ids[di]; di++) {
+                       
+                       gate = superio_get(where, where->cmdreg_addrs[ci],
+                                          where->device_ids[di]);
+                       if (!gate) {
+                               pr_debug("no devid:%x at port:%x\n",
+                                       where->device_ids[di],
+                                       where->cmdreg_addrs[ci]);
+                       } else
+                               goto OUT;
+               }
+       }
+OUT:
+       if (gate)
+               pr_info("found devid:%x port:%x users:%d\n",
+                       gate->devid, gate->sioaddr, gate->users);
+
+       mutex_unlock(&reservation_lock);
+       return gate;
+}
+EXPORT_SYMBOL_GPL(superio_find);
+
+/**
+ * superio_release:
+ * @gate : sio-lock reservation
+ * Description: releases the sio-lock reservation taken previously.
+ */
+void superio_release(struct superio* const gate)
+{
+       mutex_lock(&reservation_lock);
+
+       if (gate < &sio_locks[0] || gate >= &sio_locks[max_locks]) {
+               printk(KERN_ERR
+                      " superio: attempt to release corrupted superio-lock"
+                      " %p vs %p\n", gate, &sio_locks);
+               mutex_unlock(&reservation_lock);
+               return;
+       }
+       if (!(--gate->users)) {
+               release_region(gate->sioaddr, 2);
+               pr_debug("releasing last user of superio-port %x\n",
+                       gate->sioaddr);
+       } else
+               pr_debug("released superio-port %x user, now have %d\n",
+                        gate->sioaddr, gate->users);
+
+
+       mutex_unlock(&reservation_lock);
+       return;
+}
+EXPORT_SYMBOL_GPL(superio_release);
+
+static int superio_locks_init_module(void)
+{
+       int i;
+
+       pr_debug("initializing with %d reservation slots\n", max_locks);
+       sio_locks = kzalloc(max_locks*sizeof(struct superio), GFP_KERNEL);
+       if (!sio_locks) {
+               printk(KERN_ERR "superio: no memory\n");
+               return -ENOMEM;
+       }
+       for (i = 0; i < max_locks; i++)
+               mutex_init(&sio_locks[i].lock);
+
+       mutex_init(&reservation_lock);
+       return 0;
+}
+
+static void superio_locks_cleanup_module(void)
+{
+       pr_debug("releasing %d superio reservation slots\n", max_locks);
+       kfree(sio_locks);
+}
+
+module_init(superio_locks_init_module);
+module_exit(superio_locks_cleanup_module);
diff -ruNp -X dontdiff -X exclude-diffs 
hwmon-fan-push-offset/include/linux/superio-locks.h 
hwmon-superio.old/include/linux/superio-locks.h
--- hwmon-fan-push-offset/include/linux/superio-locks.h 1969-12-31 
17:00:00.000000000 -0700
+++ hwmon-superio.old/include/linux/superio-locks.h     2007-10-14 
17:22:23.000000000 -0600
@@ -0,0 +1,112 @@
+#include <linux/mutex.h>
+#include <asm/io.h>
+
+#define SEQ_SZ 8
+
+/* Super-IO ports are found in low-pin-count hardware (typically ISA,
+ * any others ?).  They usually provide access to many functional
+ * units, so many drivers must share the superio port.  This struct
+ * provides a lock that allows the drivers to coordinate access to
+ * that port.
+ */
+struct superio {
+       struct mutex lock;      /* lock shared amongst user drivers */
+       u16 sioaddr;            /* port's tested cmd-address */
+       u16 devid;              /* devid found by the registering driver */
+       u16 users;              /* count client drivers */
+       u8  enter_seq[SEQ_SZ];  /* activate the superio-port */
+       u8  exit_seq[SEQ_SZ];   /* idle the superio-port */
+       u8  devid_word;         /* some devices have 2 byte device id */
+ u8 ldn_addr; /* logical device select reg */ +};
+
+struct superio_search {
+       u16 cmdreg_addrs[4];    /* maybe default to 0x2e, 0x4e */
+       u16 device_ids[8];      /* 0 ends scan early */
+       u16 devid_addr;         /* LSB of device-id. default addr: 0x20 */
+       u16 devid_mask;         /* dont-care bits */
+       u8  enter_seq[SEQ_SZ];  /* activate the superio-port */
+       u8  exit_seq[SEQ_SZ];   /* idle the superio-port */
+       u8  devid_word;         /* some devices have 2 byte device id */
+ u8 ldn_addr; /* logical device select reg */ +};
+
+struct superio* superio_find(const struct superio_search * const where);
+void superio_release(struct superio* const gate);
+
+/* superio_inb(), superio_outb() are also former client funcs */
+
+static inline int superio_inb(struct superio * const sio_port, u8 reg)
+{
+       outb(reg, sio_port->sioaddr);
+       return inb(sio_port->sioaddr+1);
+}
+
+static inline void superio_outb(struct superio * const sio_port, u8 reg, u8 
val)
+{
+       outb(reg, sio_port->sioaddr);
+       outb(val, sio_port->sioaddr+1);
+}
+
+static inline int superio_inw(struct superio * const sio_port, u8 reg)
+{
+       int val;
+
+       outb(reg++, sio_port->sioaddr);
+       val = inb(sio_port->sioaddr) << 8;
+       outb(reg, sio_port->sioaddr);
+       val |= inb(sio_port->sioaddr+1);
+
+       return val;
+}
+
+/* superio_enter() and superio_exit() were formerly implemented in the
+ * client drivers, and managed the superio port by sending idling &
+ * activation sequences, which differ per device.  However, the
+ * protection was incomplete, since any driver would activate the port
+ * before using it, thus defeating the idling done by another driver
+ * to 'lock' the port.  We therefore claim 'eminent domain', and
+ * re-implement them here to properly manage the mutex.
+ */
+
+static inline void superio_enter(struct superio * const gate)
+{
+       int i;
+       mutex_lock(&gate->lock);
+       for (i = 0; i < SEQ_SZ && gate->enter_seq[i]; i++)
+               outb(gate->enter_seq[i], gate->sioaddr);
+}
+
+static inline void superio_exit(struct superio * const gate)
+{
+       int i;
+       for (i = 0; i < SEQ_SZ && gate->enter_seq[i]; i++)
+               outb(gate->exit_seq[i], gate->sioaddr);
+       mutex_unlock(&gate->lock);
+}
+
+/* maybe dont need these 2 */
+
+static inline void superio_lock(struct superio * const sio_port)
+{
+       mutex_lock(&sio_port->lock);
+}
+
+static inline void superio_unlock(struct superio * const sio_port)
+{
+       mutex_unlock(&sio_port->lock);
+}
+
+
+static inline u16 superio_devid(struct superio * const gate)
+{
+       return gate->devid;
+}
+
+#define LPC_LDN        0x07    /* Logical device select register */
+
+static inline void superio_select(struct superio * const gate, int ld)
+{
+       superio_outb(gate, (gate->ldn_addr) ? gate->ldn_addr : LPC_LDN, ld);
+}
+



-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to