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

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

 



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/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux