[RFC] acpi _CRS analyzation for PCMCIA resource management

Dominik Brodowski linux at dominikbrodowski.de
Sun Mar 28 16:49:54 BST 2004


One of the biggest challanges of 16-bit PCMCIA is to determine which 
resources (IO-ports and IO-memory) are available for PCMCIA cards. And as
Russell and others explained to me lately, even on so-called 
"legacy-free PCs", not all such resources are detectable, so whatever 
mentioned in /proc/ioports and /proc/iomem is only a guess...

The ACPI standard defines that in the ACPI tables all resources must be
noted if they cannot be detected by a different standard, namely PCI or
PCMCIA. The former is done well in Linux, the latter is what we're trying to
_do_ correctly. ACPI now says that there's always a "resource producer" and
a "resource consumer". When not all resources are "consumed", they seem to
be available for users -- like PCMCIA 16-bit cards.

So, this patch decodes all _CRS (current resource settings) found in the
ACPI tables. In the first run, it "adds" all "produced" resources to an
internal database; in the second run, it "subtracts" all "consumed"
resources. Then some (too few?) safety checks are done, and the database is
dumped to the user. Later on, the database should be pushed into
rsrc_mgr.c's database, but somehow something fails badly if I try to do
so... so it's "dumping only" right now, despite what the Kconfig text says.

Unfortunately, BIOS programmers tend to get some things wrong in the ACPI 
tables, so the resources this module thinks are free should not be taken for
guarantueed. However, it seems to be better than the approach most distro's
do: just send out a /etc/pcmcia/config.opts which looks like it works on
most notebooks...

	Dominik

diff -ruN linux-original/drivers/pcmcia/acpi_rsrcmgr.c linux/drivers/pcmcia/acpi_rsrcmgr.c
--- linux-original/drivers/pcmcia/acpi_rsrcmgr.c	1970-01-01 01:00:00.000000000 +0100
+++ linux/drivers/pcmcia/acpi_rsrcmgr.c	2004-03-28 13:43:46.859997784 +0200
@@ -0,0 +1,405 @@
+/*
+ * drivers/pcmcia/acpi_rsrcmgr.c
+ *
+ * (C) Copyright 2004 Dominik Brodowski
+ *
+ * Some parts of this module are based on:
+ * - drivers/pci/hotplug/shpchprm_acpi.c, PHP Resource Manager for 
+ *    ACPI platform, (C) 2003-2004 Intel Corporation
+ * - drivers/pcmcia/rsrc_mgr.c, Resource management routines,
+ *    (C) 1999 David A. Hinds
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/acpi.h>
+
+#include <acpi/acpi.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/actypes.h>
+
+
+#ifdef ACPI_RSRCMGR_DEBUG
+#define dprintk(msg...) printk(msg)
+#else
+#define dprintk(msg...) do { } while(0)
+#endif
+ 
+
+/* error count */
+static unsigned int __initdata err_count = 0;
+
+
+struct resource_db {
+	u32			base, num;
+	struct resource_db	*next;
+};
+
+
+/* Memory resource database */
+static struct resource_db __initdata mem_db = {
+	.next	= &mem_db,
+};
+
+/* IO port resource database */
+static struct resource_db __initdata io_db = {
+	.next	= &io_db,
+};
+
+
+static void __init free_db(void) {
+	struct resource_db *p, *q;
+
+	for (p = mem_db.next; p != &mem_db; p = q) {
+		q = p->next;
+		kfree(p);
+	}
+	for (p = io_db.next; p != &io_db; p = q) {
+		q = p->next;
+		kfree(p);
+	}
+}
+
+
+static int __init add_interval(struct resource_db *map, u32 base, u32 num)
+{
+	struct resource_db *p, *q;
+
+	for (p = map; ; p = p->next) {
+		if ((p != map) && (p->base+p->num-1 >= base))
+			return -1;
+		if ((p->next == map) || (p->next->base > base+num-1))
+			break;
+	}
+	q = kmalloc(sizeof(struct resource_db), GFP_KERNEL);
+	if (!q) {
+		printk("no mem for resource_db\n");
+		err_count++;
+		return -ENOMEM;
+	}
+
+	q->base = base; 
+	q->num = num;
+	q->next = p->next; 
+	p->next = q;
+	return 0;
+}
+
+
+static int __init sub_interval(struct resource_db *map, u32 base, u32 num)
+{
+	struct resource_db *p, *q;
+
+	for (p = map; ; p = q) {
+		q = p->next;
+		if (q == map)
+			break;
+		if ((q->base+q->num > base) && (base+num > q->base)) {
+			if (q->base >= base) {
+				if (q->base+q->num <= base+num) {
+					p->next = q->next;
+					kfree(q);
+					q = p;
+				} else {
+					q->num = q->base + q->num - base - num;
+					q->base = base + num;
+				}
+			} else if (q->base+q->num <= base+num) {
+				q->num = base - q->base;
+			} else {
+				p = kmalloc(sizeof(struct resource_db), GFP_KERNEL);
+				if (!p) {
+					printk("no mem for resource_db\n");
+					err_count++;
+					return -ENOMEM;
+				}
+				p->base = base+num;
+				p->num = q->base+q->num - p->base;
+				q->num = base - q->base;
+				p->next = q->next ; q->next = p;
+			}
+		}
+	}
+	return 0;
+}
+
+
+static void dump_intervals(struct resource_db *t)
+{
+	struct resource_db *n = t->next;
+	if (t == n)
+		return;
+	while (n) {
+		if (n->num < 0xf) {
+			n = n->next;
+			continue;
+		}
+
+		if (t==&io_db)
+			printk("0x%04x-0x%04x\n", n->base, (n->base + n->num - 1));
+		else
+			printk("0x%08x-0x%08x\n", n->base, (n->base + n->num - 1));
+		n = n->next;
+		if (n == t)
+			return;
+	}
+}
+
+
+static void __init pcmcia_acpi_parse_io(union acpi_resource_data *data)
+{
+	struct acpi_resource_io *dataio = (struct acpi_resource_io *) data;
+
+	dprintk("IO: 0x%04x - 0x%04x\n", dataio->min_base_address, (dataio->max_base_address + dataio->range_length - 1));
+	sub_interval(&io_db, dataio->min_base_address, (dataio->max_base_address - dataio->min_base_address + dataio->range_length));
+	return;
+}
+
+static void __init pcmcia_acpi_parse_fixed_io(union acpi_resource_data *data)
+{
+	struct acpi_resource_fixed_io *dataio = (struct acpi_resource_fixed_io *) data;
+
+	dprintk("FixedIO: 0x%04x - 0x%04x\n", dataio->base_address, (dataio->base_address + dataio->range_length - 1));
+	sub_interval(&io_db, dataio->base_address, dataio->range_length);
+	return;
+}
+
+static void __init pcmcia_acpi_parse_mem(union acpi_resource_data *data)
+{
+	struct acpi_resource_mem32 *dataio = (struct acpi_resource_mem32 *) data;
+
+	dprintk("Mem: 0x%08x - 0x%08x\n", dataio->min_base_address, (dataio->max_base_address + dataio->range_length - 1));
+	sub_interval(&mem_db, dataio->min_base_address, (dataio->max_base_address - dataio->min_base_address + dataio->range_length));
+	return;
+}
+
+static void __init pcmcia_acpi_parse_address_consumer(union acpi_resource_data *data)
+{
+	struct acpi_resource_address32 *data32 = (struct acpi_resource_address32 *) data;
+	u32 length;
+
+	if (data32->producer_consumer != ACPI_CONSUMER)
+		return;
+
+	length = data32->max_address_range - data32->min_address_range + 1;
+	if (length < data32->address_length)
+		length = data32->address_length;
+
+	switch (data32->resource_type) {
+	case ACPI_MEMORY_RANGE: 
+		dprintk("-AdrMem: 0x%08x - 0x%08x\n", data32->min_address_range, (data32->min_address_range + length -1));
+		sub_interval(&mem_db, data32->min_address_range, length);
+		break;
+	case ACPI_IO_RANGE:
+		dprintk("-AdrIO: 0x%04x - 0x%04x\n", data32->min_address_range, (data32->min_address_range + length -1));
+		sub_interval(&io_db, data32->min_address_range, length);
+		break;
+	default:
+		return;
+	}
+	return;
+}
+
+static void __init pcmcia_acpi_parse_address_producer(union acpi_resource_data *data)
+{
+	struct acpi_resource_address32 *data32 = (struct acpi_resource_address32 *) data;
+	u32 length;
+
+	if (data32->producer_consumer == ACPI_CONSUMER)
+		return;
+
+	if (data32->decode == ACPI_SUB_DECODE) {
+		printk(KERN_WARNING "unknown producer\n");
+		err_count++;
+		return;
+	}
+
+	length = data32->max_address_range - data32->min_address_range + 1;
+	if (length < data32->address_length)
+		length = data32->address_length;
+
+	switch (data32->resource_type) {
+	case ACPI_MEMORY_RANGE: 
+		dprintk("+AdrMem: 0x%08x - 0x%08x\n", data32->min_address_range, (data32->min_address_range + length - 1));
+		add_interval(&mem_db, data32->min_address_range, length);
+		break;
+	case ACPI_IO_RANGE:
+		dprintk("+AdrIO: 0x%04x - 0x%04x\n", data32->min_address_range, (data32->min_address_range + length - 1));
+		add_interval(&io_db, data32->min_address_range, length);
+		break;
+	default:
+		return;
+	}
+	return;
+}
+
+static void __init pcmcia_acpi_parse_fixed_mem(union acpi_resource_data *data)
+{
+	struct acpi_resource_fixed_mem32 *dataio = (struct acpi_resource_fixed_mem32 *) data;
+
+	dprintk("FixedMem: 0x%08x - 0x%08x\n", dataio->range_base_address, (dataio->range_base_address + dataio->range_length - 1));
+	sub_interval(&mem_db, dataio->range_base_address, dataio->range_length);
+	return;
+}
+
+static acpi_status  __init pcmcia_acpi_walk_one (acpi_handle handle, u32 empty, void *empty2, void **empty3)
+{
+	struct acpi_buffer		crs_buffer;
+	acpi_status			status;
+	struct acpi_resource		*r;
+	unsigned int done = 0;
+	int *run = (int *) empty2;
+
+	if (err_count)
+		return 0;
+
+	printk(".");
+
+	memset(&crs_buffer, 0, sizeof(struct acpi_buffer));
+
+	status =  acpi_get_current_resources(handle, &crs_buffer);
+	if (status != AE_BUFFER_OVERFLOW)
+		return 0;
+
+        crs_buffer.pointer = kmalloc (crs_buffer.length, GFP_KERNEL);
+        if (!crs_buffer.pointer) {
+		printk("no mem\n");
+		err_count++;
+                return 0;
+        }
+
+        status = acpi_get_current_resources (handle, &crs_buffer);
+        if (ACPI_FAILURE(status)) {
+		printk("error\n");
+		err_count++;
+                kfree(crs_buffer.pointer);
+                return 0;
+	}
+
+	r = (struct acpi_resource *) crs_buffer.pointer;
+	while (!done) {
+		if (err_count){
+			kfree(crs_buffer.pointer);
+			printk("aborting additional parsing\n");
+			return 0;
+		}
+		if (r->id == ACPI_RSTYPE_END_TAG) {
+			done = 1;
+			break; 
+		}
+		if (!*run) {
+			switch (r->id) {
+			case ACPI_RSTYPE_ADDRESS16:
+			case ACPI_RSTYPE_ADDRESS32:
+				pcmcia_acpi_parse_address_producer(&r->data);
+				break; 
+			default:
+				break;
+			}
+		} else {
+			switch (r->id) {
+			case ACPI_RSTYPE_IO:
+				pcmcia_acpi_parse_io (&r->data);
+				break; 
+			case ACPI_RSTYPE_FIXED_IO:
+				pcmcia_acpi_parse_fixed_io (&r->data);
+				break; 
+				
+			case ACPI_RSTYPE_MEM24:
+			case ACPI_RSTYPE_MEM32:
+				pcmcia_acpi_parse_mem(&r->data);
+				break; 
+			case ACPI_RSTYPE_FIXED_MEM32:
+				pcmcia_acpi_parse_fixed_mem(&r->data);
+				break;
+				
+			case ACPI_RSTYPE_ADDRESS16:
+			case ACPI_RSTYPE_ADDRESS32:
+				pcmcia_acpi_parse_address_consumer(&r->data);
+				break; 
+			default:
+				break;
+			}
+		}
+
+		r = (struct acpi_resource *) ((char *)r + r->length);
+	}
+
+	kfree(crs_buffer.pointer);
+
+	return 0;
+}
+
+ 
+static int __init pcmcia_acpi_init(void)
+{
+	acpi_status status;
+	int r = 0;
+
+	printk(KERN_INFO "pcmcia: probing the ACPI namespace for _CRS which make resources available: \n");
+
+	status = acpi_walk_namespace (ACPI_TYPE_ANY, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, 
+				     pcmcia_acpi_walk_one, &r, NULL);
+	if (ACPI_FAILURE(status)) {
+		printk("failure\n");
+		free_db();
+		return status;
+	}
+
+	/* to be safe, remove the following resources on x86: */
+
+	/* IO: 0x0000 to 0x0100 is reserved for motherboard devices */
+	sub_interval(&io_db, 0x0000, 0x0100);
+
+	/* Mem: 0x00000000 to 0x000bffff */
+	sub_interval(&mem_db, 0x00000000, 0x000bffff);
+
+	if (err_count) {
+		printk(KERN_INFO "failure\n");
+		free_db();
+		return -EIO;
+	}
+
+	printk("\n" KERN_INFO "pcmcia: probing the ACPI namespace for _CRS which use resources: \n");
+	r = 1;
+
+	status = acpi_walk_namespace (ACPI_TYPE_ANY, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, 
+				     pcmcia_acpi_walk_one, &r, NULL);
+
+	if (ACPI_FAILURE(status)) {
+		printk("failure\n");
+		free_db();
+		return status;
+	}
+
+	if (err_count) {
+		printk(KERN_INFO "failure\n");
+		free_db();
+		return -EIO;
+	}
+	printk("\n");
+
+	printk("Available I/O:\n");
+	dump_intervals(&io_db);
+
+	printk("Available MEM:\n");
+	dump_intervals(&mem_db);
+
+	free_db();
+
+	return -ENODEV;
+}
+ 
+ 
+static void __exit pcmcia_acpi_exit (void)
+{
+	return;
+}
+ 
+ 
+module_init(pcmcia_acpi_init);
+module_exit(pcmcia_acpi_exit);
+ 
+MODULE_LICENSE("GPL");
+
diff -ruN linux-original/drivers/pcmcia/Kconfig linux/drivers/pcmcia/Kconfig
--- linux-original/drivers/pcmcia/Kconfig	2004-03-28 13:44:00.060990928 +0200
+++ linux/drivers/pcmcia/Kconfig	2004-03-28 12:52:23.000000000 +0200
@@ -70,6 +70,39 @@
 
 	   If unsure, say Y.
 
+config PCMCIA_ACPI_RESOURCES
+	tristate "Determine free resources by looking at ACPI tables (EXPERIMENTAL)"
+	depends on PCMCIA && ACPI && X86 && EXPERIMENTAL
+	default n
+	---help---
+
+	  If unsure, say N.	
+
 config CARDBUS
 	bool "32-bit CardBus support"	
 	depends on PCCARD && PCI
diff -ruN linux-original/drivers/pcmcia/Makefile linux/drivers/pcmcia/Makefile
--- linux-original/drivers/pcmcia/Makefile	2004-03-28 13:44:00.061990776 +0200
+++ linux/drivers/pcmcia/Makefile	2004-03-28 12:52:40.000000000 +0200
@@ -13,6 +13,8 @@
 pcmcia-y					+= ds.o bulkmem.o
 obj-$(CONFIG_PCMCIA)				+= pcmcia.o
 
+obj-$(CONFIG_PCMCIA_ACPI_RESOURCES)		+= acpi_rsrcmgr.o
+
 
 # socket drivers
 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : http://lists.infradead.org/pipermail/linux-pcmcia/attachments/20040328/4fc57348/attachment.bin


More information about the linux-pcmcia mailing list