patch-2.3.14 linux/drivers/pnp/isapnp.c
Next file: linux/drivers/pnp/isapnp_proc.c
Previous file: linux/drivers/pnp/Makefile
Back to the patch index
Back to the overall index
- Lines: 2157
- Date:
Wed Aug 18 09:59:26 1999
- Orig file:
v2.3.13/linux/drivers/pnp/isapnp.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.3.13/linux/drivers/pnp/isapnp.c linux/drivers/pnp/isapnp.c
@@ -0,0 +1,2156 @@
+/*
+ * ISA Plug & Play support
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/string.h>
+#include <linux/malloc.h>
+#include <linux/proc_fs.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+#include <linux/pci.h>
+#include <linux/vmalloc.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <linux/isapnp.h>
+
+#ifdef CONFIG_PROC_FS
+#include "isapnp_proc.c"
+#endif
+
+#if 0
+#define ISAPNP_REGION_OK
+#endif
+#if 0
+#define ISAPNP_DEBUG
+#endif
+
+struct resource *pidxr_res = NULL;
+struct resource *pnpwrp_res = NULL;
+struct resource *isapnp_rdp_res = NULL;
+
+int isapnp_disable = 0; /* Disable ISA PnP */
+int isapnp_rdp = 0; /* Read Data Port */
+int isapnp_reset = 0; /* reset all PnP cards (deactivate) */
+int isapnp_skip_pci_scan = 0; /* skip PCI resource scanning */
+int isapnp_verbose = 1; /* verbose mode */
+int isapnp_reserve_irq[16] = { [0 ... 15] = -1 }; /* reserve (don't use) some IRQ */
+int isapnp_reserve_dma[8] = { [0 ... 7] = -1 }; /* reserve (don't use) some DMA */
+int isapnp_reserve_io[16] = { [0 ... 15] = -1 }; /* reserve (don't use) some I/O region */
+int isapnp_reserve_mem[16] = { [0 ... 15] = -1 }; /* reserve (don't use) some memory region */
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Generic ISA Plug & Play support");
+MODULE_PARM(isapnp_disable, "i");
+MODULE_PARM_DESC(isapnp_disable, "ISA Plug & Play disable");
+MODULE_PARM(isapnp_rdp, "i");
+MODULE_PARM_DESC(isapnp_rdp, "ISA Plug & Play read data port");
+MODULE_PARM(isapnp_reset, "i");
+MODULE_PARM_DESC(isapnp_reset, "ISA Plug & Play reset all cards");
+MODULE_PARM(isapnp_skip_pci_scan, "i");
+MODULE_PARM_DESC(isapnp_skip_pci_scan, "ISA Plug & Play skip PCI resource scanning");
+MODULE_PARM(isapnp_verbose, "i");
+MODULE_PARM_DESC(isapnp_verbose, "ISA Plug & Play verbose mode");
+MODULE_PARM(isapnp_reserve_irq, "1-16i");
+MODULE_PARM_DESC(isapnp_reserve_irq, "ISA Plug & Play - reserve IRQ line(s)");
+MODULE_PARM(isapnp_reserve_dma, "1-8i");
+MODULE_PARM_DESC(isapnp_reserve_dma, "ISA Plug & Play - reserve DMA channel(s)");
+MODULE_PARM(isapnp_reserve_io, "1-16i");
+MODULE_PARM_DESC(isapnp_reserve_io, "ISA Plug & Play - reserve I/O region(s) - port,size");
+MODULE_PARM(isapnp_reserve_mem, "1-16i");
+MODULE_PARM_DESC(isapnp_reserve_mem, "ISA Plug & Play - reserve memory region(s) - address,size");
+
+#define _PIDXR 0x279
+#define _PNPWRP 0xa79
+
+/* short tags */
+#define _STAG_PNPVERNO 0x01
+#define _STAG_LOGDEVID 0x02
+#define _STAG_COMPATDEVID 0x03
+#define _STAG_IRQ 0x04
+#define _STAG_DMA 0x05
+#define _STAG_STARTDEP 0x06
+#define _STAG_ENDDEP 0x07
+#define _STAG_IOPORT 0x08
+#define _STAG_FIXEDIO 0x09
+#define _STAG_VENDOR 0x0e
+#define _STAG_END 0x0f
+/* long tags */
+#define _LTAG_MEMRANGE 0x81
+#define _LTAG_ANSISTR 0x82
+#define _LTAG_UNICODESTR 0x83
+#define _LTAG_VENDOR 0x84
+#define _LTAG_MEM32RANGE 0x85
+#define _LTAG_FIXEDMEM32RANGE 0x86
+
+struct pci_bus *isapnp_cards = NULL; /* ISA PnP cards */
+struct pci_dev *isapnp_devices = NULL; /* ISA PnP devices */
+static struct pci_dev *isapnp_last_device = NULL;
+static unsigned char isapnp_checksum_value;
+static DECLARE_MUTEX(isapnp_cfg_mutex);
+static int isapnp_detected = 0;
+
+/* some prototypes */
+
+static int isapnp_config_prepare(struct pci_dev *dev);
+static int isapnp_config_activate(struct pci_dev *dev);
+static int isapnp_config_deactivate(struct pci_dev *dev);
+
+static inline void write_data(unsigned char x)
+{
+ outb(x, _PNPWRP);
+}
+
+static inline void write_address(unsigned char x)
+{
+ outb(x, _PIDXR);
+ udelay(10);
+}
+
+static inline unsigned char read_data(void)
+{
+ unsigned char val = inb(isapnp_rdp);
+ return val;
+}
+
+unsigned char isapnp_read_byte(unsigned char idx)
+{
+ write_address(idx);
+ return read_data();
+}
+
+unsigned short isapnp_read_word(unsigned char idx)
+{
+ unsigned short val;
+
+ val = isapnp_read_byte(idx);
+ val = (val << 8) + isapnp_read_byte(idx+1);
+ return val;
+}
+
+unsigned int isapnp_read_dword(unsigned char idx)
+{
+ unsigned int val;
+
+ val = isapnp_read_byte(idx);
+ val = (val << 8) + isapnp_read_byte(idx+1);
+ val = (val << 8) + isapnp_read_byte(idx+2);
+ val = (val << 8) + isapnp_read_byte(idx+3);
+ return val;
+}
+
+void isapnp_write_byte(unsigned char idx, unsigned char val)
+{
+ write_address(idx);
+ write_data(val);
+}
+
+void isapnp_write_word(unsigned char idx, unsigned short val)
+{
+ isapnp_write_byte(idx, val >> 8);
+ isapnp_write_byte(idx+1, val);
+}
+
+void isapnp_write_dword(unsigned char idx, unsigned int val)
+{
+ isapnp_write_byte(idx, val >> 24);
+ isapnp_write_byte(idx+1, val >> 16);
+ isapnp_write_byte(idx+2, val >> 8);
+ isapnp_write_byte(idx+3, val);
+}
+
+static void *isapnp_alloc(long size)
+{
+ void *result;
+
+ result = kmalloc(size, GFP_KERNEL);
+ if (!result)
+ return NULL;
+ memset(result, 0, size);
+ return result;
+}
+
+static void isapnp_key(void)
+{
+ unsigned char code = 0x6a, msb;
+ int i;
+
+ mdelay(1);
+ write_address(0x00);
+ write_address(0x00);
+
+ write_address(code);
+
+ for (i = 1; i < 32; i++) {
+ msb = ((code & 0x01) ^ ((code & 0x02) >> 1)) << 7;
+ code = (code >> 1) | msb;
+ write_address(code);
+ }
+}
+
+/* place all pnp cards in wait-for-key state */
+static void isapnp_wait(void)
+{
+ isapnp_write_byte(0x02, 0x02);
+}
+
+void isapnp_wake(unsigned char csn)
+{
+ isapnp_write_byte(0x03, csn);
+}
+
+void isapnp_device(unsigned char logdev)
+{
+ isapnp_write_byte(0x07, logdev);
+}
+
+void isapnp_activate(unsigned char logdev)
+{
+ isapnp_device(logdev);
+ isapnp_write_byte(ISAPNP_CFG_ACTIVATE, 1);
+ udelay(250);
+}
+
+void isapnp_deactivate(unsigned char logdev)
+{
+ isapnp_device(logdev);
+ isapnp_write_byte(ISAPNP_CFG_ACTIVATE, 0);
+}
+
+static void __init isapnp_peek(unsigned char *data, int bytes)
+{
+ int i, j;
+ unsigned char d;
+
+ for (i = 1; i <= bytes; i++) {
+ for (j = 0; j < 10; j++) {
+ d = isapnp_read_byte(0x05);
+ if (d & 1)
+ break;
+ udelay(10);
+ }
+ if (!(d & 1)) {
+ *data++ = 0xff;
+ continue;
+ }
+ d = isapnp_read_byte(0x04); /* PRESDI */
+ isapnp_checksum_value += d;
+ if (data != NULL)
+ *data++ = d;
+ }
+}
+
+#define RDP_STEP 32 /* minimum is 4 */
+
+static int isapnp_next_rdp(void)
+{
+ int rdp = isapnp_rdp;
+ while (rdp <= 0x3ff) {
+ if (!check_region(rdp, 1)) {
+ isapnp_rdp = rdp;
+ return 0;
+ }
+ rdp += RDP_STEP;
+ }
+ return -1;
+}
+
+/* Set read port address */
+static inline void isapnp_set_rdp(void)
+{
+ isapnp_write_byte(0x00, isapnp_rdp >> 2);
+ udelay(100);
+}
+
+
+static int __init isapnp_isolate_rdp_select(void)
+{
+ isapnp_wait();
+ isapnp_key();
+
+ /* Control: reset CSN and conditionally everything else too */
+ isapnp_write_byte(0x02, isapnp_reset ? 0x05 : 0x04);
+ mdelay(2);
+
+ isapnp_wait();
+ isapnp_key();
+ isapnp_wake(0x00);
+
+ if (isapnp_next_rdp() < 0) {
+ isapnp_wait();
+ return -1;
+ }
+
+ isapnp_set_rdp();
+ udelay(1000);
+ write_address(0x01);
+ udelay(1000);
+ return 0;
+}
+
+/*
+ * Isolate (assign uniqued CSN) to all ISA PnP devices.
+ */
+
+static int __init isapnp_isolate(void)
+{
+ unsigned char checksum = 0x6a;
+ unsigned char chksum = 0x00;
+ unsigned char bit = 0x00;
+ int data;
+ int csn = 0;
+ int i;
+ int iteration = 1;
+
+ isapnp_rdp = 0x213;
+ if (isapnp_isolate_rdp_select() < 0)
+ return -1;
+
+ while (1) {
+ for (i = 1; i <= 64; i++) {
+ data = read_data() << 8;
+ udelay(250);
+ data = data | read_data();
+ udelay(250);
+ if (data == 0x55aa)
+ bit = 0x01;
+ checksum = ((((checksum ^ (checksum >> 1)) & 0x01) ^ bit) << 7) | (checksum >> 1);
+ bit = 0x00;
+ }
+ for (i = 65; i <= 72; i++) {
+ data = read_data() << 8;
+ udelay(250);
+ data = data | read_data();
+ udelay(250);
+ if (data == 0x55aa)
+ chksum |= (1 << (i - 65));
+ }
+ if (checksum != 0x00 && checksum == chksum) {
+ csn++;
+
+ isapnp_write_byte(0x06, csn);
+ udelay(250);
+ iteration++;
+ isapnp_wake(0x00);
+ write_address(0x01);
+ goto __next;
+ }
+ if (iteration == 1) {
+ isapnp_rdp += RDP_STEP;
+ if (isapnp_isolate_rdp_select() < 0)
+ return -1;
+ } else if (iteration > 1) {
+ break;
+ }
+ __next:
+ checksum = 0x6a;
+ chksum = 0x00;
+ bit = 0x00;
+ }
+ isapnp_wait();
+ return csn;
+}
+
+/*
+ * Read one tag from stream.
+ */
+
+static int __init isapnp_read_tag(unsigned char *type, unsigned short *size)
+{
+ unsigned char tag, tmp[2];
+
+ isapnp_peek(&tag, 1);
+ if (tag == 0) /* invalid tag */
+ return -1;
+ if (tag & 0x80) { /* large item */
+ *type = tag;
+ isapnp_peek(tmp, 2);
+ *size = (tmp[1] << 8) | tmp[0];
+ } else {
+ *type = (tag >> 3) & 0x0f;
+ *size = tag & 0x07;
+ }
+#if 0
+ printk("tag = 0x%x, type = 0x%x, size = %i\n", tag, *type, *size);
+#endif
+ if (type == 0) /* wrong type */
+ return -1;
+ if (*type == 0xff && *size == 0xffff) /* probably invalid data */
+ return -1;
+ return 0;
+}
+
+/*
+ * Skip specified number of bytes from stream.
+ */
+
+static void __init isapnp_skip_bytes(int count)
+{
+ isapnp_peek(NULL, count);
+}
+
+/*
+ * Parse logical device tag.
+ */
+
+static struct pci_dev * __init isapnp_parse_device(struct pci_bus *card, int size, int number)
+{
+ unsigned char tmp[6];
+ struct pci_dev *dev;
+
+ isapnp_peek(tmp, size);
+ dev = isapnp_alloc(sizeof(struct pci_dev));
+ if (!dev)
+ return NULL;
+ dev->devfn = number;
+ dev->vendor = (tmp[1] << 8) | tmp[0];
+ dev->device = (tmp[3] << 8) | tmp[2];
+ dev->regs = tmp[4];
+ dev->bus = card;
+ if (size > 5)
+ dev->regs |= tmp[5] << 8;
+ dev->prepare = isapnp_config_prepare;
+ dev->activate = isapnp_config_activate;
+ dev->deactivate = isapnp_config_deactivate;
+ return dev;
+}
+
+/*
+ * Build new resources structure
+ */
+
+static struct isapnp_resources * __init isapnp_build_resources(struct pci_dev *dev, int dependent)
+{
+ struct isapnp_resources *res, *ptr, *ptra;
+
+ res = isapnp_alloc(sizeof(struct isapnp_resources));
+ if (!res)
+ return NULL;
+ res->dev = dev;
+ ptr = (struct isapnp_resources *)dev->sysdata;
+ while (ptr && ptr->next)
+ ptr = ptr->next;
+ if (ptr && ptr->dependent && dependent) { /* add to another list */
+ ptra = ptr->alt;
+ while (ptra && ptra->alt)
+ ptra = ptra->alt;
+ if (!ptra)
+ ptr->alt = res;
+ else
+ ptra->alt = res;
+ } else {
+ if (!ptr)
+ dev->sysdata = res;
+ else
+ ptr->next = res;
+ }
+ if (dependent) {
+ res->priority = dependent & 0xff;
+ if (res->priority > ISAPNP_RES_PRIORITY_FUNCTIONAL)
+ res->priority = ISAPNP_RES_PRIORITY_INVALID;
+ res->dependent = 1;
+ } else {
+ res->priority = ISAPNP_RES_PRIORITY_PREFERRED;
+ res->dependent = 0;
+ }
+ return res;
+}
+
+/*
+ * Add IRQ resource to resources list.
+ */
+
+static void __init isapnp_add_irq_resource(struct pci_dev *dev,
+ struct isapnp_resources **res,
+ int dependent, int size)
+{
+ unsigned char tmp[3];
+ struct isapnp_irq *irq, *ptr;
+
+ isapnp_peek(tmp, size);
+ irq = isapnp_alloc(sizeof(struct isapnp_irq));
+ if (!irq)
+ return;
+ if (*res == NULL) {
+ *res = isapnp_build_resources(dev, dependent);
+ if (*res == NULL) {
+ kfree(irq);
+ return;
+ }
+ }
+ irq->map = (tmp[1] << 8) | tmp[0];
+ if (size > 2)
+ irq->flags = tmp[2];
+ else
+ irq->flags = DEVICE_IRQ_FLAG_HIGHEDGE;
+ irq->res = *res;
+ ptr = (*res)->irq;
+ while (ptr && ptr->next)
+ ptr = ptr->next;
+ if (ptr)
+ ptr->next = irq;
+ else
+ (*res)->irq = irq;
+}
+
+/*
+ * Add DMA resource to resources list.
+ */
+
+static void __init isapnp_add_dma_resource(struct pci_dev *dev,
+ struct isapnp_resources **res,
+ int dependent, int size)
+{
+ unsigned char tmp[2];
+ struct isapnp_dma *dma, *ptr;
+
+ isapnp_peek(tmp, size);
+ dma = isapnp_alloc(sizeof(struct isapnp_dma));
+ if (!dma)
+ return;
+ if (*res == NULL) {
+ *res = isapnp_build_resources(dev, dependent);
+ if (*res == NULL) {
+ kfree(dma);
+ return;
+ }
+ }
+ dma->map = tmp[0];
+ dma->type = tmp[1] & 3;
+ dma->flags = (tmp[1] >> 2) & 7;
+ dma->speed = (tmp[1] >> 6) & 3;
+ dma->res = *res;
+ ptr = (*res)->dma;
+ while (ptr && ptr->next)
+ ptr = ptr->next;
+ if (ptr)
+ ptr->next = dma;
+ else
+ (*res)->dma = dma;
+}
+
+/*
+ * Add port resource to resources list.
+ */
+
+static void __init isapnp_add_port_resource(struct pci_dev *dev,
+ struct isapnp_resources **res,
+ int dependent, int size)
+{
+ unsigned char tmp[7];
+ struct isapnp_port *port, *ptr;
+
+ isapnp_peek(tmp, size);
+ port = isapnp_alloc(sizeof(struct isapnp_port));
+ if (!port)
+ return;
+ if (*res == NULL) {
+ *res = isapnp_build_resources(dev, dependent);
+ if (*res == NULL) {
+ kfree(port);
+ return;
+ }
+ }
+ port->min = (tmp[2] << 8) | tmp[1];
+ port->max = (tmp[4] << 8) | tmp[3];
+ port->align = tmp[5];
+ port->size = tmp[6];
+ port->flags = tmp[0] ? ISAPNP_PORT_FLAG_16BITADDR : 0;
+ port->res = *res;
+ ptr = (*res)->port;
+ while (ptr && ptr->next)
+ ptr = ptr->next;
+ if (ptr)
+ ptr->next = port;
+ else
+ (*res)->port = port;
+}
+
+/*
+ * Add fixed port resource to resources list.
+ */
+
+static void __init isapnp_add_fixed_port_resource(struct pci_dev *dev,
+ struct isapnp_resources **res,
+ int dependent, int size)
+{
+ unsigned char tmp[3];
+ struct isapnp_port *port, *ptr;
+
+ isapnp_peek(tmp, size);
+ port = isapnp_alloc(sizeof(struct isapnp_port));
+ if (!port)
+ return;
+ if (*res == NULL) {
+ *res = isapnp_build_resources(dev, dependent);
+ if (*res == NULL) {
+ kfree(port);
+ return;
+ }
+ }
+ port->min = port->max = (tmp[1] << 8) | tmp[0];
+ port->size = tmp[2];
+ port->align = 0;
+ port->flags = ISAPNP_PORT_FLAG_FIXED;
+ port->res = *res;
+ ptr = (*res)->port;
+ while (ptr && ptr->next)
+ ptr = ptr->next;
+ if (ptr)
+ ptr->next = port;
+ else
+ (*res)->port = port;
+}
+
+/*
+ * Add memory resource to resources list.
+ */
+
+static void __init isapnp_add_mem_resource(struct pci_dev *dev,
+ struct isapnp_resources **res,
+ int dependent, int size)
+{
+ unsigned char tmp[9];
+ struct isapnp_mem *mem, *ptr;
+
+ isapnp_peek(tmp, size);
+ mem = isapnp_alloc(sizeof(struct isapnp_mem));
+ if (!mem)
+ return;
+ if (*res == NULL) {
+ *res = isapnp_build_resources(dev, dependent);
+ if (*res == NULL) {
+ kfree(mem);
+ return;
+ }
+ }
+ mem->min = ((tmp[2] << 8) | tmp[1]) << 8;
+ mem->max = ((tmp[4] << 8) | tmp[3]) << 8;
+ mem->align = (tmp[6] << 8) | tmp[5];
+ mem->size = ((tmp[8] << 8) | tmp[7]) << 8;
+ mem->flags = tmp[0] & 7;
+ mem->flags |= (tmp[0] >> 2) & 0x18;
+ mem->type = (tmp[0] >> 3) & 3;
+ mem->res = *res;
+ ptr = (*res)->mem;
+ while (ptr && ptr->next)
+ ptr = ptr->next;
+ if (ptr)
+ ptr->next = mem;
+ else
+ (*res)->mem = mem;
+}
+
+/*
+ * Add 32-bit memory resource to resources list.
+ */
+
+static void __init isapnp_add_mem32_resource(struct pci_dev *dev,
+ struct isapnp_resources **res,
+ int dependent, int size)
+{
+ unsigned char tmp[17];
+ struct isapnp_mem32 *mem32, *ptr;
+
+ isapnp_peek(tmp, size);
+ mem32 = isapnp_alloc(sizeof(struct isapnp_mem32));
+ if (!mem32)
+ return;
+ if (*res == NULL) {
+ *res = isapnp_build_resources(dev, dependent);
+ if (*res == NULL) {
+ kfree(mem32);
+ return;
+ }
+ }
+ memcpy(mem32->data, tmp, 17);
+ mem32->res = *res;
+ ptr = (*res)->mem32;
+ while (ptr && ptr->next)
+ ptr = ptr->next;
+ if (ptr)
+ ptr->next = mem32;
+ else
+ (*res)->mem32 = mem32;
+}
+
+/*
+ * Add 32-bit fixed memory resource to resources list.
+ */
+
+static void __init isapnp_add_fixed_mem32_resource(struct pci_dev *dev,
+ struct isapnp_resources **res,
+ int dependent, int size)
+{
+ unsigned char tmp[17];
+ struct isapnp_mem32 *mem32, *ptr;
+
+ isapnp_peek(tmp, size);
+ mem32 = isapnp_alloc(sizeof(struct isapnp_mem32));
+ if (!mem32)
+ return;
+ if (*res == NULL) {
+ *res = isapnp_build_resources(dev, dependent);
+ if (*res == NULL) {
+ kfree(mem32);
+ return;
+ }
+ }
+ memcpy(mem32->data, tmp, 17);
+ mem32->res = *res;
+ ptr = (*res)->mem32;
+ while (ptr && ptr->next)
+ ptr = ptr->next;
+ if (ptr)
+ ptr->next = mem32;
+ else
+ (*res)->mem32 = mem32;
+}
+
+/*
+ * Parse resource map for logical device.
+ */
+
+static int __init isapnp_create_device(struct pci_bus *card,
+ unsigned short size)
+{
+ int number = 0, skip = 0, dependent = 0, compat = 0;
+ unsigned char type, tmp[17];
+ struct pci_dev *dev, *prev_dev;
+ struct isapnp_resources *res = NULL;
+
+ if ((dev = isapnp_parse_device(card, size, number++)) == NULL)
+ return 1;
+ card->devices = dev;
+ if (isapnp_last_device) {
+ isapnp_last_device->next = dev;
+ isapnp_last_device = dev;
+ } else {
+ isapnp_devices = isapnp_last_device = dev;
+ }
+ while (1) {
+ if (isapnp_read_tag(&type, &size)<0)
+ return 1;
+ if (skip && type != _STAG_LOGDEVID && type != _STAG_END)
+ goto __skip;
+ switch (type) {
+ case _STAG_LOGDEVID:
+ if (size >= 5 && size <= 6) {
+ prev_dev = dev;
+ isapnp_config_prepare(dev);
+ if ((dev = isapnp_parse_device(card, size, number++)) == NULL)
+ return 1;
+ prev_dev->sibling = dev;
+ isapnp_last_device->next = dev;
+ isapnp_last_device = dev;
+ size = 0;
+ skip = 0;
+ } else {
+ skip = 1;
+ }
+ res = NULL;
+ dependent = 0;
+ compat = 0;
+ break;
+ case _STAG_COMPATDEVID:
+ if (size == 4 && compat < DEVICE_COUNT_COMPATIBLE) {
+ isapnp_peek(tmp, 4);
+ dev->vendor_compatible[compat] = (tmp[1] << 8) | tmp[0];
+ dev->device_compatible[compat] = (tmp[3] << 8) | tmp[2];
+ compat++;
+ size = 0;
+ }
+ break;
+ case _STAG_IRQ:
+ if (size < 2 || size > 3)
+ goto __skip;
+ isapnp_add_irq_resource(dev, &res, dependent, size);
+ size = 0;
+ break;
+ case _STAG_DMA:
+ if (size != 2)
+ goto __skip;
+ isapnp_add_dma_resource(dev, &res, dependent, size);
+ size = 0;
+ break;
+ case _STAG_STARTDEP:
+ if (size > 1)
+ goto __skip;
+ res = NULL;
+ dependent = 0x100 | ISAPNP_RES_PRIORITY_ACCEPTABLE;
+ if (size > 0) {
+ isapnp_peek(tmp, size);
+ dependent = 0x100 | tmp[0];
+ size = 0;
+ }
+ break;
+ case _STAG_ENDDEP:
+ if (size != 0)
+ goto __skip;
+ res = NULL;
+ dependent = 0;
+ break;
+ case _STAG_IOPORT:
+ if (size != 7)
+ goto __skip;
+ isapnp_add_port_resource(dev, &res, dependent, size);
+ size = 0;
+ break;
+ case _STAG_FIXEDIO:
+ if (size != 3)
+ goto __skip;
+ isapnp_add_fixed_port_resource(dev, &res, dependent, size);
+ size = 0;
+ break;
+ case _STAG_VENDOR:
+ break;
+ case _LTAG_MEMRANGE:
+ if (size != 9)
+ goto __skip;
+ isapnp_add_mem_resource(dev, &res, dependent, size);
+ size = 0;
+ break;
+ case _LTAG_ANSISTR:
+ if (dev->name[0] == '\0') {
+ unsigned short size1 = size > 47 ? 47 : size;
+ isapnp_peek(dev->name, size1);
+ dev->name[size1] = '\0';
+ size -= size1;
+ }
+ break;
+ case _LTAG_UNICODESTR:
+ /* silently ignore */
+ /* who use unicode for hardware identification? */
+ break;
+ case _LTAG_VENDOR:
+ break;
+ case _LTAG_MEM32RANGE:
+ if (size != 17)
+ goto __skip;
+ isapnp_add_mem32_resource(dev, &res, dependent, size);
+ size = 0;
+ break;
+ case _LTAG_FIXEDMEM32RANGE:
+ if (size != 17)
+ goto __skip;
+ isapnp_add_fixed_mem32_resource(dev, &res, dependent, size);
+ size = 0;
+ break;
+ case _STAG_END:
+ if (size > 0)
+ isapnp_skip_bytes(size);
+ return 1;
+ default:
+ printk("isapnp: unexpected or unknown tag type 0x%x for logical device %i (device %i), ignored\n", type, dev->devfn, card->number);
+ }
+ __skip:
+ if (size > 0)
+ isapnp_skip_bytes(size);
+ }
+ isapnp_config_prepare(dev);
+ return 0;
+}
+
+/*
+ * Parse resource map for ISA PnP card.
+ */
+
+static void __init isapnp_parse_resource_map(struct pci_bus *card)
+{
+ unsigned char type, tmp[17];
+ unsigned short size;
+
+ while (1) {
+ if (isapnp_read_tag(&type, &size)<0)
+ return;
+ switch (type) {
+ case _STAG_PNPVERNO:
+ if (size != 2)
+ goto __skip;
+ isapnp_peek(tmp, 2);
+ card->pnpver = tmp[0];
+ card->productver = tmp[1];
+ size = 0;
+ break;
+ case _STAG_LOGDEVID:
+ if (size >= 5 && size <= 6) {
+ if (isapnp_create_device(card, size)==1)
+ return;
+ size = 0;
+ }
+ break;
+ case _STAG_VENDOR:
+ break;
+ case _LTAG_ANSISTR:
+ if (card->name[0] == '\0') {
+ unsigned short size1 = size > 47 ? 47 : size;
+ isapnp_peek(card->name, size1);
+ card->name[size1] = '\0';
+ size -= size1;
+ }
+ break;
+ case _LTAG_UNICODESTR:
+ /* silently ignore */
+ /* who use unicode for hardware identification? */
+ break;
+ case _LTAG_VENDOR:
+ break;
+ case _STAG_END:
+ if (size > 0)
+ isapnp_skip_bytes(size);
+ return;
+ default:
+ printk("isapnp: unexpected or unknown tag type 0x%x for device %i, ignored\n", type, card->number);
+ }
+ __skip:
+ if (size > 0)
+ isapnp_skip_bytes(size);
+ }
+}
+
+/*
+ * Compute ISA PnP checksum for first eight bytes.
+ */
+
+static unsigned char __init isapnp_checksum(unsigned char *data)
+{
+ int i, j;
+ unsigned char checksum = 0x6a, bit, b;
+
+ for (i = 0; i < 8; i++) {
+ b = data[i];
+ for (j = 0; j < 8; j++) {
+ bit = 0;
+ if (b & (1 << j))
+ bit = 1;
+ checksum = ((((checksum ^ (checksum >> 1)) & 0x01) ^ bit) << 7) | (checksum >> 1);
+ }
+ }
+ return checksum;
+}
+
+/*
+ * Build device list for all present ISA PnP devices.
+ */
+
+static int __init isapnp_build_device_list(void)
+{
+ int csn;
+ unsigned char header[9], checksum;
+ struct pci_bus *card, *prev = NULL;
+
+ isapnp_wait();
+ isapnp_key();
+ for (csn = 1; csn <= 10; csn++) {
+ isapnp_wake(csn);
+ isapnp_peek(header, 9);
+ checksum = isapnp_checksum(header);
+#if 0
+ printk("vendor: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
+ header[0], header[1], header[2], header[3],
+ header[4], header[5], header[6], header[7], header[8]);
+ printk("checksum = 0x%x\n", checksum);
+#endif
+ if (checksum == 0x00 || checksum != header[8]) /* not valid CSN */
+ continue;
+ if ((card = isapnp_alloc(sizeof(struct pci_bus))) == NULL)
+ continue;
+ card->number = csn;
+ card->vendor = (header[1] << 8) | header[0];
+ card->device = (header[3] << 8) | header[2];
+ card->serial = (header[7] << 24) | (header[6] << 16) | (header[5] << 8) | header[4];
+ isapnp_checksum_value = 0x00;
+ isapnp_parse_resource_map(card);
+ if (isapnp_checksum_value != 0x00)
+ printk("isapnp: checksum for device %i is not valid (0x%x)\n", csn, isapnp_checksum_value);
+ card->checksum = isapnp_checksum_value;
+ if (!isapnp_cards)
+ isapnp_cards = card;
+ else
+ prev->next = card;
+ prev = card;
+ }
+ return 0;
+}
+
+/*
+ * Basic configuration routines.
+ */
+
+int isapnp_present(void)
+{
+ if (isapnp_devices)
+ return 1;
+ return 0;
+}
+
+int isapnp_cfg_begin(int csn, int logdev)
+{
+ if (csn < 1 || csn > 10 || logdev > 10)
+ return -EINVAL;
+ MOD_INC_USE_COUNT;
+ down(&isapnp_cfg_mutex);
+ isapnp_wait();
+ isapnp_key();
+ isapnp_wake(csn);
+#if 1 /* to avoid malfunction when the isapnptools package is used */
+ isapnp_set_rdp();
+ udelay(1000); /* delay 1000us */
+ write_address(0x01);
+ udelay(1000); /* delay 1000us */
+#endif
+ if (logdev >= 0)
+ isapnp_device(logdev);
+ return 0;
+}
+
+int isapnp_cfg_end(void)
+{
+ isapnp_wait();
+ up(&isapnp_cfg_mutex);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/*
+ * Resource manager.
+ */
+
+static struct isapnp_port *isapnp_find_port(struct pci_dev *dev, int index)
+{
+ struct isapnp_resources *res;
+ struct isapnp_port *port;
+
+ if (!dev || index < 0 || index > 7)
+ return NULL;
+ for (res = (struct isapnp_resources *)dev->sysdata; res; res = res->next) {
+ for (port = res->port; port; port = port->next) {
+ if (!index)
+ return port;
+ index--;
+ }
+ }
+ return NULL;
+}
+
+struct isapnp_irq *isapnp_find_irq(struct pci_dev *dev, int index)
+{
+ struct isapnp_resources *res, *resa;
+ struct isapnp_irq *irq;
+ int index1, index2, index3;
+
+ if (!dev || index < 0 || index > 7)
+ return NULL;
+ for (res = (struct isapnp_resources *)dev->sysdata; res; res = res->next) {
+ index3 = 0;
+ for (resa = res; resa; resa = resa->alt) {
+ index1 = index;
+ index2 = 0;
+ for (irq = resa->irq; irq; irq = irq->next) {
+ if (!index1)
+ return irq;
+ index1--;
+ index2++;
+ }
+ if (index3 < index2)
+ index3 = index2;
+ }
+ index -= index3;
+ }
+ return NULL;
+}
+
+struct isapnp_dma *isapnp_find_dma(struct pci_dev *dev, int index)
+{
+ struct isapnp_resources *res;
+ struct isapnp_dma *dma;
+
+ if (!dev || index < 0 || index > 7)
+ return NULL;
+ for (res = (struct isapnp_resources *)dev->sysdata; res; res = res->next) {
+ for (dma = res->dma; dma; dma = dma->next) {
+ if (!index)
+ return dma;
+ index--;
+ }
+ }
+ return NULL;
+}
+
+struct isapnp_mem *isapnp_find_mem(struct pci_dev *dev, int index)
+{
+ struct isapnp_resources *res;
+ struct isapnp_mem *mem;
+
+ if (!dev || index < 0 || index > 7)
+ return NULL;
+ for (res = (struct isapnp_resources *)dev->sysdata; res; res = res->next) {
+ for (mem = res->mem; mem; mem = mem->next) {
+ if (!index)
+ return mem;
+ index--;
+ }
+ }
+ return NULL;
+}
+
+struct isapnp_mem32 *isapnp_find_mem32(struct pci_dev *dev, int index)
+{
+ struct isapnp_resources *res;
+ struct isapnp_mem32 *mem32;
+
+ if (!dev || index < 0 || index > 7)
+ return NULL;
+ for (res = (struct isapnp_resources *)dev->sysdata; res; res = res->next) {
+ for (mem32 = res->mem32; mem32; mem32 = mem32->next) {
+ if (!index)
+ return mem32;
+ index--;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Device manager.
+ */
+
+struct pci_bus *isapnp_find_card(unsigned short vendor,
+ unsigned short device,
+ struct pci_bus *from)
+{
+ struct pci_bus *card;
+
+ if (from == NULL) {
+ from = isapnp_cards;
+ } else {
+ from = from->next;
+ }
+ for (card = from; card; card = card->next) {
+ if (card->vendor == vendor && card->device == device)
+ return card;
+ }
+ return NULL;
+}
+
+struct pci_dev *isapnp_find_dev(struct pci_bus *card,
+ unsigned short vendor,
+ unsigned short function,
+ struct pci_dev *from)
+{
+ struct pci_dev *dev;
+ int idx;
+
+ if (card == NULL) { /* look for a logical device from all cards */
+ if (from == NULL) {
+ from = isapnp_devices;
+ } else {
+ from = from->next;
+ }
+ for (dev = from; dev; dev = dev->next) {
+ if (dev->vendor == vendor && dev->device == function)
+ return dev;
+ for (idx = 0; idx < DEVICE_COUNT_COMPATIBLE; idx++)
+ if (dev->vendor_compatible[idx] == vendor &&
+ dev->device_compatible[idx] == function)
+ return dev;
+ }
+ } else {
+ if (from == NULL) {
+ from = card->devices;
+ } else {
+ from = from->next;
+ }
+ if (from->bus != card) /* something is wrong */
+ return NULL;
+ for (dev = from; dev; dev = dev->sibling) {
+ if (dev->vendor == vendor && dev->device == function)
+ return dev;
+ for (idx = 0; idx < DEVICE_COUNT_COMPATIBLE; idx++)
+ if (dev->vendor_compatible[idx] == vendor &&
+ dev->device_compatible[idx] == function)
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+static int isapnp_config_prepare(struct pci_dev *dev)
+{
+ struct isapnp_resources *res, *resa;
+ struct isapnp_port *port;
+ struct isapnp_irq *irq;
+ struct isapnp_dma *dma;
+ struct isapnp_mem *mem;
+ int port_count, port_count1;
+ int irq_count, irq_count1;
+ int dma_count, dma_count1;
+ int mem_count, mem_count1;
+ int idx;
+
+ if (dev == NULL)
+ return -EINVAL;
+ if (dev->active || dev->ro)
+ return -EBUSY;
+ dev->irq = dev->irq2 = DEVICE_IRQ_NOTSET;
+ dev->irq_flags = dev->irq2_flags = 0;
+ for (idx = 0; idx < DEVICE_COUNT_DMA; idx++) {
+ dev->dma[idx] = DEVICE_DMA_NOTSET;
+ dev->dma_type[idx] = DEVICE_DMA_TYPE_8AND16BIT;
+ dev->dma_flags[idx] = 0;
+ dev->dma_speed[idx] = DEVICE_DMA_SPEED_COMPATIBLE;
+ }
+ for (idx = 0; idx < DEVICE_COUNT_RESOURCE; idx++) {
+ dev->resource[idx].name = NULL;
+ dev->resource[idx].start = DEVICE_IO_NOTSET;
+ dev->resource[idx].end = 0;
+ dev->resource[idx].fixed = 0;
+ dev->resource[idx].bits = 12;
+ dev->resource[idx].hw_flags = 0;
+ dev->resource[idx].type = DEVICE_IO_TYPE_8AND16BIT;
+ }
+ port_count = irq_count = dma_count = mem_count = 0;
+ for (res = (struct isapnp_resources *)dev->sysdata; res; res = res->next) {
+ port_count1 = irq_count1 = dma_count1 = mem_count1 = 0;
+ for (resa = res; resa; resa = resa->alt) {
+ for (port = resa->port, idx = 0; port; port = port->next, idx++) {
+ if (dev->resource[port_count + idx].start == DEVICE_IO_NOTSET) {
+ dev->resource[port_count + idx].start = DEVICE_IO_AUTO;
+ dev->resource[port_count + idx].end = port->size;
+ dev->resource[port_count + idx].bits = port->flags & ISAPNP_PORT_FLAG_16BITADDR ? 16 : 12;
+ dev->resource[port_count + idx].fixed = port->flags & ISAPNP_PORT_FLAG_FIXED ? 1 : 0;
+ }
+ }
+ if (port_count1 < idx)
+ port_count1 = idx;
+ for (irq = resa->irq, idx = 0; irq; irq = irq->next, idx++) {
+ if (irq_count + idx == 0) {
+ if (dev->irq == DEVICE_IRQ_NOTSET) {
+ dev->irq = DEVICE_IRQ_AUTO;
+ dev->irq_flags = irq->flags;
+ }
+ } else if (irq_count + idx == 1) {
+ if (dev->irq2 == DEVICE_IRQ_NOTSET) {
+ dev->irq2 = DEVICE_IRQ_AUTO;
+ dev->irq2_flags = irq->flags;
+ }
+ }
+
+ }
+ if (irq_count1 < idx)
+ irq_count1 = idx;
+ for (dma = resa->dma, idx = 0; dma; dma = dma->next, idx++)
+ if (dev->dma[idx] == DEVICE_DMA_NOTSET) {
+ dev->dma[idx] = DEVICE_DMA_AUTO;
+ dev->dma_type[idx] = dma->type;
+ dev->dma_flags[idx] = dma->flags;
+ dev->dma_speed[idx] = dma->speed;
+ }
+ if (dma_count1 < idx)
+ dma_count1 = idx;
+ for (mem = resa->mem, idx = 0; mem; mem = mem->next, idx++)
+ if (dev->resource[mem_count + idx + 8].start == DEVICE_IO_AUTO) {
+ dev->resource[mem_count + idx].start = DEVICE_IO_AUTO;
+ dev->resource[mem_count + idx].end = mem->size;
+ dev->resource[mem_count + idx].bits = 24;
+ dev->resource[mem_count + idx].fixed = 0;
+ dev->resource[mem_count + idx].hw_flags = mem->flags;
+ dev->resource[mem_count + idx].type = mem->type;
+ }
+ if (mem_count1 < idx)
+ mem_count1 = idx;
+ }
+ port_count += port_count1;
+ irq_count += irq_count1;
+ dma_count += dma_count1;
+ mem_count += mem_count1;
+ }
+ return 0;
+}
+
+struct isapnp_cfgtmp {
+ struct isapnp_port *port[8];
+ struct isapnp_irq *irq[2];
+ struct isapnp_dma *dma[2];
+ struct isapnp_mem *mem[4];
+ struct pci_dev *request;
+ struct pci_dev result;
+};
+
+static int isapnp_alternative_switch(struct isapnp_cfgtmp *cfg,
+ struct isapnp_resources *from,
+ struct isapnp_resources *to)
+{
+ int tmp, tmp1;
+ struct isapnp_port *port;
+ struct isapnp_irq *irq;
+ struct isapnp_dma *dma;
+ struct isapnp_mem *mem;
+
+ if (!cfg)
+ return -EINVAL;
+ /* process port settings */
+ for (tmp = 0; tmp < 8; tmp++) {
+ if (cfg->request->resource[tmp].start != DEVICE_IO_AUTO)
+ continue; /* don't touch */
+ port = cfg->port[tmp];
+ if (!port) {
+ cfg->port[tmp] = port = isapnp_find_port(cfg->request, tmp);
+ if (!port)
+ return -EINVAL;
+ }
+ if (from && port->res == from) {
+ while (port->res != to) {
+ if (!port->res->alt)
+ return -EINVAL;
+ port = port->res->alt->port;
+ for (tmp1 = tmp; tmp1 > 0 && port; tmp1--)
+ port = port->next;
+ cfg->port[tmp] = port;
+ cfg->result.resource[tmp].start = DEVICE_IO_AUTO;
+ if (!port)
+ return -ENOENT;
+ }
+ }
+ }
+ /* process irq settings */
+ for (tmp = 0; tmp < 2; tmp++) {
+ if (tmp == 0) {
+ if (cfg->request->irq != DEVICE_IRQ_AUTO)
+ continue; /* don't touch */
+ } else {
+ if (cfg->request->irq2 != DEVICE_IRQ_AUTO)
+ continue; /* don't touch */
+ }
+ irq = cfg->irq[tmp];
+ if (!irq) {
+ cfg->irq[tmp] = irq = isapnp_find_irq(cfg->request, tmp);
+ if (!irq)
+ return -EINVAL;
+ }
+ if (from && irq->res == from) {
+ while (irq->res != to) {
+ if (!irq->res->alt)
+ return -EINVAL;
+ irq = irq->res->alt->irq;
+ for (tmp1 = tmp; tmp1 > 0 && irq; tmp1--)
+ irq = irq->next;
+ cfg->irq[tmp] = irq;
+ if (tmp == 0) {
+ cfg->result.irq = DEVICE_IRQ_AUTO;
+ } else {
+ cfg->result.irq2 = DEVICE_IRQ_AUTO;
+ }
+ if (!irq)
+ return -ENOENT;
+ }
+ }
+ }
+ /* process dma settings */
+ for (tmp = 0; tmp < 2; tmp++) {
+ if (cfg->request->dma[tmp] != DEVICE_DMA_AUTO)
+ continue; /* don't touch */
+ dma = cfg->dma[tmp];
+ if (!dma) {
+ cfg->dma[tmp] = dma = isapnp_find_dma(cfg->request, tmp);
+ if (!dma)
+ return -EINVAL;
+ }
+ if (from && dma->res == from) {
+ while (dma->res != to) {
+ if (!dma->res->alt)
+ return -EINVAL;
+ dma = dma->res->alt->dma;
+ for (tmp1 = tmp; tmp1 > 0 && dma; tmp1--)
+ dma = dma->next;
+ cfg->dma[tmp] = dma;
+ cfg->result.dma[tmp] = DEVICE_DMA_AUTO;
+ if (!dma)
+ return -ENOENT;
+ }
+ }
+ }
+ /* process memory settings */
+ for (tmp = 0; tmp < 4; tmp++) {
+ if (cfg->request->resource[tmp + 8].start != DEVICE_IO_AUTO)
+ continue; /* don't touch */
+ mem = cfg->mem[tmp];
+ if (!mem) {
+ cfg->mem[tmp] = mem = isapnp_find_mem(cfg->request, tmp);
+ if (!mem)
+ return -EINVAL;
+ }
+ if (from && mem->res == from) {
+ while (mem->res != to) {
+ if (!mem->res->alt)
+ return -EINVAL;
+ mem = mem->res->alt->mem;
+ for (tmp1 = tmp; tmp1 > 0 && mem; tmp1--)
+ mem = mem->next;
+ cfg->mem[tmp] = mem;
+ cfg->result.resource[tmp + 8].start = DEVICE_IO_AUTO;
+ if (!mem)
+ return -ENOENT;
+ }
+ }
+ }
+ return 0;
+}
+
+static int isapnp_check_port(struct isapnp_cfgtmp *cfg, int port, int size, int idx)
+{
+ int i, tmp, rport, rsize;
+ struct isapnp_port *xport;
+ struct pci_dev *dev;
+
+ if (check_region(port, size))
+ return 1;
+ for (i = 0; i < 8; i++) {
+ rport = isapnp_reserve_io[i << 1];
+ rsize = isapnp_reserve_io[(i << 1) + 1];
+ if (port >= rport && port < rport + rsize)
+ return 1;
+ if (port + size > rport && port + size < (rport + rsize) - 1)
+ return 1;
+ }
+ for (dev = isapnp_devices; dev; dev = dev->next) {
+ if (dev->active) {
+ for (tmp = 0; tmp < 8; tmp++) {
+ if (dev->resource[tmp].start != DEVICE_IO_NOTSET) {
+ rport = dev->resource[tmp].start;
+ rsize = (dev->resource[tmp].end - rport) + 1;
+ if (port >= rport && port < rport + rsize)
+ return 1;
+ if (port + size > rport && port + size < (rport + rsize) - 1)
+ return 1;
+ }
+ }
+ }
+ }
+ for (i = 0; i < 8; i++) {
+ if (i == idx)
+ continue;
+ tmp = cfg->request->resource[i].start;
+ if (tmp == DEVICE_IO_NOTSET)
+ continue;
+ if (tmp == DEVICE_IO_AUTO) { /* auto */
+ xport = cfg->port[i];
+ if (!xport)
+ return 1;
+ tmp = cfg->result.resource[i].start;
+ if (tmp == DEVICE_IO_AUTO)
+ continue;
+ if (tmp + xport->size >= port && tmp <= port + xport->size)
+ return 1;
+ continue;
+ }
+ if (port == tmp)
+ return 1;
+ xport = isapnp_find_port(cfg->request, i);
+ if (!xport)
+ return 1;
+ if (tmp + xport->size >= port && tmp <= port + xport->size)
+ return 1;
+ }
+ return 0;
+}
+
+static int isapnp_valid_port(struct isapnp_cfgtmp *cfg, int idx)
+{
+ int err;
+ unsigned long *value;
+ struct isapnp_port *port;
+
+ if (!cfg || idx < 0 || idx > 7)
+ return -EINVAL;
+ if (cfg->result.resource[idx].start != DEVICE_IO_AUTO) /* don't touch */
+ return 0;
+ __again:
+ port = cfg->port[idx];
+ if (!port)
+ return -EINVAL;
+ value = &cfg->result.resource[idx].start;
+ if (*value == DEVICE_IO_AUTO) {
+ if (!isapnp_check_port(cfg, *value = port->min, port->size, idx))
+ return 0;
+ }
+ do {
+ *value += port->align;
+ if (*value > port->max || !port->align) {
+ if (port->res && port->res->alt) {
+ if ((err = isapnp_alternative_switch(cfg, port->res, port->res->alt))<0)
+ return err;
+ goto __again;
+ }
+ return -ENOENT;
+ }
+ } while (isapnp_check_port(cfg, *value, port->size, idx));
+ return 0;
+}
+
+static void isapnp_test_handler(int irq, void *dev_id, struct pt_regs *regs)
+{
+}
+
+static int isapnp_check_interrupt(struct isapnp_cfgtmp *cfg, int irq, int idx)
+{
+ int i;
+ struct pci_dev *dev;
+
+ if (irq < 0 || irq > 15)
+ return 1;
+ for (i = 0; i < 16; i++) {
+ if (isapnp_reserve_irq[i] == irq)
+ return 1;
+ }
+ for (dev = isapnp_devices; dev; dev = dev->next) {
+ if (dev->active) {
+ if (dev->irq == irq || dev->irq2 == irq)
+ return 1;
+ }
+ }
+ if (request_irq(irq, isapnp_test_handler, SA_INTERRUPT, "isapnp", NULL))
+ return 1;
+ free_irq(irq, NULL);
+ if (idx != 0) {
+ if (cfg->result.irq != DEVICE_IRQ_AUTO &&
+ cfg->result.irq != DEVICE_IRQ_NOTSET)
+ if (cfg->result.irq == irq)
+ return 1;
+ }
+ if (idx != 1) {
+ if (cfg->result.irq2 != DEVICE_IRQ_AUTO &&
+ cfg->result.irq2 != DEVICE_IRQ_NOTSET)
+ if (cfg->result.irq2 == irq)
+ return 1;
+ }
+ return 0;
+}
+
+static int isapnp_valid_irq(struct isapnp_cfgtmp *cfg, int idx)
+{
+ /* IRQ priority: table is good for i386 */
+ static unsigned short xtab[16] = {
+ 5, 10, 11, 12, 9, 14, 15, 7, 3, 4, 13, 0, 1, 6, 8, 2
+ };
+ int err, i;
+ unsigned int *value;
+ struct isapnp_irq *irq;
+
+ if (!cfg || idx < 0 || idx > 1)
+ return -EINVAL;
+ if (idx == 0) {
+ if (cfg->result.irq != DEVICE_IRQ_AUTO) /* don't touch */
+ return 0;
+ } else {
+ if (cfg->result.irq2 != DEVICE_IRQ_AUTO) /* don't touch */
+ return 0;
+ }
+ __again:
+ irq = cfg->irq[idx];
+ if (!irq)
+ return -EINVAL;
+ if (idx == 0) {
+ value = &cfg->result.irq;
+ } else {
+ value = &cfg->result.irq2;
+ }
+ if (*value == DEVICE_IRQ_AUTO) {
+ for (i = 0; i < 16 && !(irq->map & (1<<xtab[i])); i++);
+ if (i >= 16)
+ return -ENOENT;
+ if (!isapnp_check_interrupt(cfg, *value = xtab[i], idx))
+ return 0;
+ }
+ do {
+ for (i = 0; i < 16 && xtab[i] != *value; i++);
+ for (i++; i < 16 && !(irq->map & (1<<xtab[i])); i++);
+ if (i >= 16) {
+ if (irq->res && irq->res->alt) {
+ if ((err = isapnp_alternative_switch(cfg, irq->res, irq->res->alt))<0)
+ return err;
+ goto __again;
+ }
+ return -ENOENT;
+ } else {
+ *value = xtab[i];
+ }
+ } while (isapnp_check_interrupt(cfg, *value, idx));
+ return 0;
+}
+
+static int isapnp_check_dma(struct isapnp_cfgtmp *cfg, int dma, int idx)
+{
+ int i;
+ struct pci_dev *dev;
+
+ if (dma < 0 || dma == 4 || dma > 7)
+ return 1;
+ for (i = 0; i < 8; i++) {
+ if (isapnp_reserve_dma[i] == dma)
+ return 1;
+ }
+ for (dev = isapnp_devices; dev; dev = dev->next) {
+ if (dev->active) {
+ if (dev->dma[0] == dma || dev->dma[1] == dma)
+ return 1;
+ }
+ }
+ if (request_dma(dma, "isapnp"))
+ return 1;
+ free_dma(dma);
+ for (i = 0; i < 2; i++) {
+ if (i == idx)
+ continue;
+ if (cfg->result.dma[i] == DEVICE_DMA_NOTSET ||
+ cfg->result.dma[i] == DEVICE_DMA_AUTO)
+ continue;
+ if (cfg->result.dma[i] == dma)
+ return 1;
+ }
+ return 0;
+}
+
+static int isapnp_valid_dma(struct isapnp_cfgtmp *cfg, int idx)
+{
+ int err, i;
+ unsigned char *value;
+ struct isapnp_dma *dma;
+
+ if (!cfg || idx < 0 || idx > 1)
+ return -EINVAL;
+ if (cfg->result.dma[idx] != DEVICE_DMA_AUTO) /* don't touch */
+ return 0;
+ __again:
+ dma = cfg->dma[idx];
+ if (!dma)
+ return -EINVAL;
+ value = &cfg->result.dma[idx];
+ if (*value == DEVICE_DMA_AUTO) {
+ for (i = 0; i < 8 && !(dma->map & (1<<i)); i++);
+ if (i >= 8)
+ return -ENOENT;
+ if (!isapnp_check_dma(cfg, *value = i, idx))
+ return 0;
+ }
+ do {
+ for (i = *value + 1; i < 8 && !(dma->map & (1<<i)); i++);
+ if (i >= 8) {
+ if (dma->res && dma->res->alt) {
+ if ((err = isapnp_alternative_switch(cfg, dma->res, dma->res->alt))<0)
+ return err;
+ goto __again;
+ }
+ return -ENOENT;
+ } else {
+ *value = i;
+ }
+ } while (isapnp_check_dma(cfg, *value, idx));
+ return 0;
+}
+
+static int isapnp_check_mem(struct isapnp_cfgtmp *cfg, unsigned int addr, unsigned int size, int idx)
+{
+ int i, tmp;
+ unsigned int raddr, rsize;
+ struct pci_dev *dev;
+ struct isapnp_mem *xmem;
+
+ for (i = 0; i < 8; i++) {
+ raddr = (unsigned int)isapnp_reserve_mem[i << 1];
+ rsize = (unsigned int)isapnp_reserve_mem[(i << 1) + 1];
+ if (addr >= raddr && addr < raddr + rsize)
+ return 1;
+ if (addr + size > raddr && addr + size < (raddr + rsize) - 1)
+ return 1;
+ if (__check_region(&iomem_resource, addr, size))
+ return 1;
+ }
+ for (dev = isapnp_devices; dev; dev = dev->next) {
+ if (dev->active) {
+ for (tmp = 0; tmp < 4; tmp++) {
+ if (dev->resource[tmp].start != DEVICE_IO_NOTSET) {
+ raddr = dev->resource[tmp + 8].start;
+ rsize = (dev->resource[tmp + 8].end - raddr) + 1;
+ if (addr >= raddr && addr < raddr + rsize)
+ return 1;
+ if (addr + size > raddr && addr + size < (raddr + rsize) - 1)
+ return 1;
+ }
+ }
+ }
+ }
+ for (i = 0; i < 4; i++) {
+ if (i == idx)
+ continue;
+ tmp = cfg->request->resource[i + 8].start;
+ if (tmp == DEVICE_IO_NOTSET)
+ continue;
+ if (tmp == DEVICE_IO_AUTO) { /* auto */
+ xmem = cfg->mem[i];
+ if (!xmem)
+ return 1;
+ tmp = cfg->result.resource[i + 8].start;
+ if (tmp == DEVICE_IO_AUTO)
+ continue;
+ if (tmp + xmem->size >= addr && tmp <= addr + xmem->size)
+ return 1;
+ continue;
+ }
+ if (addr == tmp)
+ return 1;
+ xmem = isapnp_find_mem(cfg->request, i);
+ if (!xmem)
+ return 1;
+ if (tmp + xmem->size >= addr && tmp <= addr + xmem->size)
+ return 1;
+ }
+ return 0;
+}
+
+static int isapnp_valid_mem(struct isapnp_cfgtmp *cfg, int idx)
+{
+ int err;
+ unsigned long *value;
+ struct isapnp_mem *mem;
+
+ if (!cfg || idx < 0 || idx > 3)
+ return -EINVAL;
+ if (cfg->result.resource[idx + 8].start != DEVICE_IO_AUTO) /* don't touch */
+ return 0;
+ __again:
+ mem = cfg->mem[idx];
+ if (!mem)
+ return -EINVAL;
+ value = &cfg->result.resource[idx].start;
+ if (*value == DEVICE_IO_AUTO) {
+ *value = mem->min;
+ if (!isapnp_check_mem(cfg, *value, mem->size, idx))
+ return 0;
+ }
+ do {
+ *value += mem->align;
+ if (*value >= 8 || !mem->align) {
+ if (mem->res && mem->res->alt) {
+ if ((err = isapnp_alternative_switch(cfg, mem->res, mem->res->alt))<0)
+ return err;
+ goto __again;
+ }
+ return -ENOENT;
+ }
+ } while (isapnp_check_mem(cfg, *value, mem->size, idx));
+ return 0;
+}
+
+static int isapnp_check_valid(struct isapnp_cfgtmp *cfg)
+{
+ int tmp;
+
+ for (tmp = 0; tmp < 8; tmp++)
+ if (cfg->result.resource[tmp].start == DEVICE_IO_AUTO)
+ return -EAGAIN;
+ if (cfg->result.irq == DEVICE_IRQ_AUTO)
+ return -EAGAIN;
+ if (cfg->result.irq2 == DEVICE_IRQ_AUTO)
+ return -EAGAIN;
+ for (tmp = 0; tmp < 2; tmp++)
+ if (cfg->result.dma[tmp] == DEVICE_DMA_AUTO)
+ return -EAGAIN;
+ for (tmp = 0; tmp < 4; tmp++)
+ if (cfg->result.resource[tmp + 1].start == DEVICE_IO_AUTO)
+ return -EAGAIN;
+ return 0;
+}
+
+static int isapnp_config_activate(struct pci_dev *dev)
+{
+ struct isapnp_cfgtmp cfg;
+ int tmp, fauto, err;
+
+ if (!dev)
+ return -EINVAL;
+ if (dev->active)
+ return -EBUSY;
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.request = dev;
+ memcpy(&cfg.result, dev, sizeof(struct pci_dev));
+ /* check if all values are set, otherwise try auto-configuration */
+ for (tmp = fauto = 0; !fauto && tmp < 8; tmp++) {
+ if (dev->resource[tmp].start == DEVICE_IO_AUTO)
+ fauto++;
+ }
+ if (dev->irq == DEVICE_IRQ_AUTO)
+ fauto++;
+ if (dev->irq2 == DEVICE_IRQ_AUTO)
+ fauto++;
+ for (tmp = 0; !fauto && tmp < 2; tmp++) {
+ if (dev->dma[tmp] == DEVICE_DMA_AUTO)
+ fauto++;
+ }
+ for (tmp = 0; !fauto && tmp < 4; tmp++) {
+ if (dev->resource[tmp + 8].start == DEVICE_IO_AUTO)
+ fauto++;
+ }
+ if (!fauto)
+ goto __skip_auto;
+ /* set variables to initial values */
+ if ((err = isapnp_alternative_switch(&cfg, NULL, NULL))<0)
+ return err;
+ /* find first valid configuration */
+ fauto = 0;
+ do {
+ for (tmp = 0; tmp < 8 && cfg.result.resource[tmp].start != DEVICE_IO_NOTSET; tmp++)
+ if ((err = isapnp_valid_port(&cfg, tmp))<0)
+ return err;
+ if (cfg.result.irq != DEVICE_IRQ_NOTSET)
+ if ((err = isapnp_valid_irq(&cfg, 0))<0)
+ return err;
+ if (cfg.result.irq2 != DEVICE_IRQ_NOTSET)
+ if ((err = isapnp_valid_irq(&cfg, 1))<0)
+ return err;
+ for (tmp = 0; tmp < 2 && tmp < cfg.result.dma[tmp] != DEVICE_DMA_NOTSET; tmp++)
+ if ((err = isapnp_valid_dma(&cfg, tmp))<0)
+ return err;
+ for (tmp = 0; tmp < 4 && tmp < cfg.result.resource[tmp + 8].start != DEVICE_IO_NOTSET; tmp++)
+ if ((err = isapnp_valid_mem(&cfg, tmp))<0)
+ return err;
+ } while (isapnp_check_valid(&cfg)<0 && fauto++ < 20);
+ if (fauto >= 20)
+ return -EAGAIN;
+ __skip_auto:
+ /* we have valid configuration, try configure hardware */
+ isapnp_cfg_begin(dev->bus->number, dev->devfn);
+ dev->active = 1;
+ dev->irq = cfg.result.irq;
+ dev->irq2 = cfg.result.irq2;
+ dev->dma[0] = cfg.result.dma[0];
+ dev->dma[1] = cfg.result.dma[1];
+ for (tmp = 0; tmp < 12; tmp++) {
+ dev->resource[tmp].start = cfg.result.resource[tmp].start;
+ if (cfg.result.resource[tmp].start != DEVICE_IO_NOTSET &&
+ cfg.result.resource[tmp].end != DEVICE_IO_AUTO)
+ dev->resource[tmp].end += cfg.result.resource[tmp].start;
+ }
+ for (tmp = 0; tmp < 8 && dev->resource[tmp].start != DEVICE_IO_NOTSET; tmp++)
+ isapnp_write_word(ISAPNP_CFG_PORT+(tmp<<1), dev->resource[tmp].start);
+ if (dev->irq != DEVICE_IRQ_NOTSET) {
+ if (dev->irq == 2)
+ dev->irq = 9;
+ isapnp_write_byte(ISAPNP_CFG_IRQ+(0<<1), dev->irq);
+ }
+ if (dev->irq2 != DEVICE_IRQ_NOTSET) {
+ if (dev->irq2 == 2)
+ dev->irq2 = 9;
+ isapnp_write_byte(ISAPNP_CFG_IRQ+(1<<1), dev->irq2);
+ }
+ for (tmp = 0; tmp < 2 && dev->dma[tmp] != DEVICE_DMA_NOTSET; tmp++)
+ isapnp_write_byte(ISAPNP_CFG_DMA+tmp, dev->dma[tmp]);
+ for (tmp = 0; tmp < 4 && dev->resource[tmp].start != DEVICE_IO_NOTSET; tmp++)
+ isapnp_write_word(ISAPNP_CFG_MEM+(tmp<<2), (dev->resource[tmp + 8].start >> 8) & 0xffff);
+ isapnp_activate(dev->devfn);
+ isapnp_cfg_end();
+ return 0;
+}
+
+static int isapnp_config_deactivate(struct pci_dev *dev)
+{
+ if (!dev || !dev->active)
+ return -EINVAL;
+ isapnp_cfg_begin(dev->bus->number, dev->devfn);
+ isapnp_deactivate(dev->devfn);
+ dev->activate = 0;
+ isapnp_cfg_end();
+ return 0;
+}
+
+/*
+ * Inititialization.
+ */
+
+#ifdef MODULE
+
+static void isapnp_free_port(struct isapnp_port *port)
+{
+ struct isapnp_port *next;
+
+ while (port) {
+ next = port->next;
+ kfree(port);
+ port = next;
+ }
+}
+
+static void isapnp_free_irq(struct isapnp_irq *irq)
+{
+ struct isapnp_irq *next;
+
+ while (irq) {
+ next = irq->next;
+ kfree(irq);
+ irq = next;
+ }
+}
+
+static void isapnp_free_dma(struct isapnp_dma *dma)
+{
+ struct isapnp_dma *next;
+
+ while (dma) {
+ next = dma->next;
+ kfree(dma);
+ dma = next;
+ }
+}
+
+static void isapnp_free_mem(struct isapnp_mem *mem)
+{
+ struct isapnp_mem *next;
+
+ while (mem) {
+ next = mem->next;
+ kfree(mem);
+ mem = next;
+ }
+}
+
+static void isapnp_free_mem32(struct isapnp_mem32 *mem32)
+{
+ struct isapnp_mem32 *next;
+
+ while (mem32) {
+ next = mem32->next;
+ kfree(mem32);
+ mem32 = next;
+ }
+}
+
+static void isapnp_free_resources(struct isapnp_resources *resources, int alt)
+{
+ struct isapnp_resources *next;
+
+ while (resources) {
+ next = alt ? resources->alt : resources->next;
+ isapnp_free_port(resources->port);
+ isapnp_free_irq(resources->irq);
+ isapnp_free_dma(resources->dma);
+ isapnp_free_mem(resources->mem);
+ isapnp_free_mem32(resources->mem32);
+ if (!alt && resources->alt)
+ isapnp_free_resources(resources->alt, 1);
+ kfree(resources);
+ resources = next;
+ }
+}
+
+static void isapnp_free_device(struct pci_dev *dev)
+{
+ struct pci_dev *next;
+
+ while (dev) {
+ next = dev->next;
+ isapnp_free_resources((struct isapnp_resources *)dev->sysdata, 0);
+ kfree(dev);
+ dev = next;
+ }
+}
+
+#endif /* MODULE */
+
+static void isapnp_free_all_resources(void)
+{
+#ifdef MODULE
+ struct pci_bus *card, *cardnext;
+#endif
+
+#ifdef ISAPNP_REGION_OK
+ release_resource(pidxr_res);
+#endif
+ release_resource(pnpwrp_res);
+ if (isapnp_rdp >= 0x203 && isapnp_rdp <= 0x3ff)
+ release_resource(isapnp_rdp_res);
+#ifdef MODULE
+ for (card = isapnp_cards; card; card = cardnext) {
+ cardnext = card->next;
+ isapnp_free_device(card->devices);
+ kfree(card);
+ }
+#ifdef CONFIG_PROC_FS
+ isapnp_proc_done();
+#endif
+#endif
+}
+
+static int __init isapnp_do_reserve_irq(int irq)
+{
+ int i;
+
+ if (irq < 0 || irq > 15)
+ return -EINVAL;
+ for (i = 0; i < 16; i++) {
+ if (isapnp_reserve_irq[i] == irq)
+ return 0;
+ }
+ for (i = 0; i < 16; i++) {
+ if (isapnp_reserve_irq[i] < 0) {
+ isapnp_reserve_irq[i] = irq;
+#ifdef ISAPNP_DEBUG
+ printk("IRQ %i is reserved now.\n", irq);
+#endif
+ return 0;
+ }
+ }
+ return -ENOMEM;
+}
+
+#ifdef CONFIG_PCI
+
+static void __init isapnp_pci_init(void)
+{
+ int devfn;
+ struct pci_dev *dev;
+
+ for (devfn = 0; devfn < 255; devfn++) {
+ dev = pci_find_slot(0, devfn);
+ if (dev != NULL)
+ break;
+ }
+ if (dev == NULL)
+ return;
+ while (dev) {
+#ifdef ISAPNP_DEBUG
+ printk("PCI: reserved IRQ: %i\n", dev->irq);
+#endif
+ if (dev->irq > 0)
+ isapnp_do_reserve_irq(dev->irq);
+ dev = dev->next;
+ }
+}
+
+#endif /* CONFIG_PCI */
+
+EXPORT_SYMBOL(isapnp_present);
+EXPORT_SYMBOL(isapnp_cfg_begin);
+EXPORT_SYMBOL(isapnp_cfg_end);
+EXPORT_SYMBOL(isapnp_read_byte);
+EXPORT_SYMBOL(isapnp_read_word);
+EXPORT_SYMBOL(isapnp_read_dword);
+EXPORT_SYMBOL(isapnp_write_byte);
+EXPORT_SYMBOL(isapnp_write_word);
+EXPORT_SYMBOL(isapnp_write_dword);
+EXPORT_SYMBOL(isapnp_wake);
+EXPORT_SYMBOL(isapnp_device);
+EXPORT_SYMBOL(isapnp_activate);
+EXPORT_SYMBOL(isapnp_deactivate);
+EXPORT_SYMBOL(isapnp_find_card);
+EXPORT_SYMBOL(isapnp_find_dev);
+
+int __init isapnp_init(void)
+{
+ int cards;
+ struct pci_bus *card;
+ struct pci_dev *dev;
+
+ if (isapnp_disable) {
+ isapnp_detected = 0;
+ printk("isapnp: ISA Plug & Play support disabled\n");
+ return 0;
+ }
+#ifdef ISAPNP_REGION_OK
+ pidxr_res=request_region(_PIDXR, 1, "isapnp index");
+ if(!pidxr_res) {
+ printk("isapnp: Index Register 0x%x already used\n", _PIDXR);
+ return -EBUSY;
+ }
+#endif
+ pnpwrp_res=request_region(_PNPWRP, 1, "isapnp write");
+ if(!pnpwrp_res) {
+ printk("isapnp: Write Data Register 0x%x already used\n", _PNPWRP);
+ return -EBUSY;
+ }
+ if (isapnp_rdp >= 0x203 && isapnp_rdp <= 0x3ff) {
+ isapnp_rdp |= 3;
+ isapnp_rdp_res=request_region(isapnp_rdp, 1, "isapnp read");
+ if(!isapnp_rdp_res) {
+ printk("isapnp: Read Data Register 0x%x already used\n", isapnp_rdp);
+ return -EBUSY;
+ }
+ isapnp_set_rdp();
+ }
+ isapnp_detected = 1;
+ if (isapnp_rdp < 0x203 || isapnp_rdp > 0x3ff) {
+ cards = isapnp_isolate();
+ if (cards < 0 ||
+ (isapnp_rdp < 0x203 || isapnp_rdp > 0x3ff)) {
+ isapnp_free_all_resources();
+ isapnp_detected = 0;
+ printk("isapnp: No Plug & Play device found\n");
+ return 0;
+ }
+ isapnp_rdp_res=request_region(isapnp_rdp, 1, "isapnp read");
+ }
+ isapnp_build_device_list();
+ cards = 0;
+ for (card = isapnp_cards; card; card = card->next)
+ cards++;
+ if (isapnp_verbose) {
+ for (card = isapnp_cards; card; card = card->next) {
+ printk( "isapnp: Card '%s'\n", card->name[0]?card->name:"Unknown");
+ if (isapnp_verbose < 2)
+ continue;
+ for (dev = card->devices; dev; dev = dev->next)
+ printk("isapnp: Device '%s'\n", dev->name[0]?card->name:"Unknown");
+ }
+ }
+ if (cards) {
+ printk("isapnp: %i Plug & Play card%s detected total\n", cards, cards>1?"s":"");
+ } else {
+ printk("isapnp: No Plug & Play card found\n");
+ }
+#ifdef CONFIG_PCI
+ if (!isapnp_skip_pci_scan)
+ isapnp_pci_init();
+#endif
+#ifdef CONFIG_PROC_FS
+ isapnp_proc_init();
+#endif
+ return 0;
+}
+
+#ifdef MODULE
+
+int init_module(void)
+{
+ return isapnp_init();
+}
+
+void cleanup_module(void)
+{
+ if (isapnp_detected)
+ isapnp_free_all_resources();
+}
+
+#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)