2007-10-15 05:06:07

by Jim Cromie

[permalink] [raw]
Subject: [ patch .24-rc0 1/5 ] SuperIO locks coordinator - add the module

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