diff mbox

[RFC,1/1] PCI: override BIOS/firmware resource allocation

Message ID 20100908065958.GA27447@ram-laptop (mailing list archive)
State RFC, archived
Headers show

Commit Message

Ram Pai Sept. 8, 2010, 6:59 a.m. UTC
None
diff mbox

Patch

diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt
index f084af0..eec068f 100644
--- a/Documentation/kernel-parameters.txt
+++ b/Documentation/kernel-parameters.txt
@@ -1961,6 +1961,21 @@  and is between 256 and 4096 characters. It is defined in the file
 				PAGE_SIZE is used as alignment.
 				PCI-PCI bridge can be specified, if resource
 				windows need to be expanded.
+		override=[off, conflict, always, <device>]
+				off : Do not override BIOS/firmware allocations. This is the 
+					default
+				conflict : override BIOS/firmware allocations if conflicting
+					or not allocated.
+				always : override all BIOS/firmware allocations
+				<device>: Format [<domain>:]<bus>:<slot>.<func>[; ...]
+					override BIOS/firmware allocations of specified
+					devices
+
+		clear=<device>
+				<device>: Format [<domain>:]<bus>:<slot>.<func>[; ...]
+					release BIOS/firmware allocations of specified
+					devices. By default no allocations are cleared.
+
 		ecrc=		Enable/disable PCIe ECRC (transaction layer
 				end-to-end CRC checking).
 				bios: Use BIOS/firmware settings. This is the
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 7fa3cbd..5676416 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -2984,6 +2984,85 @@  void __weak pci_fixup_cardbus(struct pci_bus *bus)
 }
 EXPORT_SYMBOL(pci_fixup_cardbus);
 
+#define RESOURCE_RELEASE_PARAM_SIZE COMMAND_LINE_SIZE
+static char __initdata resource_release_param[RESOURCE_RELEASE_PARAM_SIZE];
+int pci_override=PCI_OVERRIDE_OFF;
+
+/*
+ * Return success if 'dev' is listed as one of the devices
+ * through the kernel command line
+ * pci=[override|clear]=device[;device]*
+ */
+int __init is_pci_reset_device(struct pci_dev *dev)
+{
+	int seg, bus, slot, func, count;
+	char *p;
+
+	p = resource_release_param;
+	while (*p) {
+		count = 0;
+		if (sscanf(p, "%x:%x:%x.%x%n",
+			&seg, &bus, &slot, &func, &count) != 4) {
+			seg = 0;
+			if (sscanf(p, "%x:%x.%x%n",
+					&bus, &slot, &func, &count) != 3) {
+				/* Invalid format */
+				printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: %s\n",
+					p);
+				return 0;
+			}
+		}
+		p += count;
+		if (seg == pci_domain_nr(dev->bus) &&
+			bus == dev->bus->number &&
+			slot == PCI_SLOT(dev->devfn) &&
+			func == PCI_FUNC(dev->devfn)) {
+			return 1;
+		}
+		if (*p != ';') {
+			/* End of param or invalid format */
+			return 0;
+		}
+		p++;
+	}
+	return 0;
+}
+
+
+static void __init pci_override_setup(const char *str, int override)
+{
+	if (override && !strncmp(str, "off", 3)) {
+
+		pci_override = PCI_OVERRIDE_OFF;
+		printk(KERN_INFO "pci: do not override "
+			"BIOS/uEFI allocations\n");
+
+	} else if (override && !strncmp(str, "conflict", 8)) {
+
+		pci_override = PCI_OVERRIDE_CONFLICT;
+		printk(KERN_INFO "pci: reallocate BIOS/uEFI "
+			"allocated resources on conflicts\n");
+
+	} else if (override && !strncmp(str, "always", 6)) {
+
+		pci_override =  PCI_OVERRIDE_ALWAYS;
+		printk(KERN_INFO "pci: override all BIOS/uEFI allocations\n");
+
+	} else {
+		int count=strlen(str);
+
+		pci_override = override ? PCI_OVERRIDE_DEVICE :
+				PCI_CLEAR_DEVICE;
+		if (count > RESOURCE_RELEASE_PARAM_SIZE - 1)
+			count = RESOURCE_RELEASE_PARAM_SIZE - 1;
+		strncpy(resource_release_param, str, count);
+		resource_release_param[count] = '\0';
+		printk(KERN_INFO "pci: %s BIOS/uEFI allocations for %s\n",
+			 override ? "override" : "clear", resource_release_param);
+	}
+	return;
+}
+
 static int __init pci_setup(char *str)
 {
 	while (str) {
@@ -3010,6 +3089,10 @@  static int __init pci_setup(char *str)
 				pci_hotplug_io_size = memparse(str + 9, &str);
 			} else if (!strncmp(str, "hpmemsize=", 10)) {
 				pci_hotplug_mem_size = memparse(str + 10, &str);
+			} else if (!strncmp(str, "override=", 9)) {
+				pci_override_setup(str + 9, 1);
+			} else if (!strncmp(str, "clear=", 6)) {
+				pci_override_setup(str + 6, 0);
 			} else {
 				printk(KERN_ERR "PCI: Unknown option `%s'\n",
 						str);
diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c
index 66cb8f4..e215ee9 100644
--- a/drivers/pci/setup-bus.c
+++ b/drivers/pci/setup-bus.c
@@ -838,21 +838,195 @@  static void pci_bus_dump_resources(struct pci_bus *bus)
 	}
 }
 
+static void __init
+__pci_dev_reset_resource(struct pci_dev *dev,
+			struct resource_list_x *fail_head)
+{
+	int idx, start, end;
+	struct resource *r;
+	u16 class = dev->class >> 8;
+
+	if (class == PCI_CLASS_BRIDGE_PCI) {
+		start=PCI_BRIDGE_RESOURCES;
+		end=PCI_BRIDGE_RESOURCE_END+1;
+	} else {
+		start=0;
+		end=PCI_NUM_RESOURCES;
+	}
+
+	for (idx = start; idx < end; idx++) {
+		r = &dev->resource[idx];
+
+		if (r->flags & IORESOURCE_PCI_FIXED)
+			continue;
+
+		if ( idx == PCI_ROM_RESOURCE ||
+		      r->flags & IORESOURCE_ROM_ENABLE)
+		 	continue;		
+
+		if (fail_head)
+			add_to_failed_list(fail_head, dev, r);
+
+		/* keep the old size */
+		r->end = 0;
+		r->start = 0;
+		r->flags = 0;
+	}
+}
+
+/*
+ * reset the resource requirement of the device tree under 'dev'.
+ * Capture and add each resource's original requirement to the list 'head'
+ */
+static void __init
+pci_dev_reset_resource(struct pci_dev *dev,
+			struct resource_list_x *head)
+{
+	u16 class = dev->class >> 8;
+
+	/* Don't touch classless devices or host bridges or ioapics.  */
+	if (class == PCI_CLASS_NOT_DEFINED || class == PCI_CLASS_BRIDGE_HOST)
+		return;
+
+	/* Don't touch ioapic devices already enabled by firmware */
+	if (class == PCI_CLASS_SYSTEM_PIC) {
+		u16 command;
+		pci_read_config_word(dev, PCI_COMMAND, &command);
+		if (command & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY))
+			return;
+	}
+
+	if (class == PCI_CLASS_BRIDGE_PCI) {
+		struct pci_bus *bus = dev->subordinate;
+		if (bus) {
+			struct pci_dev *dev1;
+			list_for_each_entry(dev1, &bus->devices, bus_list)
+				pci_dev_reset_resource(dev1, head);
+		}
+	}
+	__pci_dev_reset_resource(dev, head);
+	return;
+}
+
+
+/*
+ * Reset the resource requirement of all devices under 'bus' that
+ * specified by the kernel parameter 'pci=[override|clear]=<device>[;<device>]*'
+ * Capture and add each resource's original requirement to the
+ * list 'head'
+ */
+static void __init
+pci_bus_reset_resource(struct pci_bus *bus,
+			struct resource_list_x *head)
+{
+	struct pci_bus *b;
+	struct pci_dev *dev;
+
+	if (bus->self && (pci_override == PCI_OVERRIDE_ALWAYS ||
+				is_pci_reset_device(bus->self))) {
+		pci_dev_reset_resource(bus->self, head);
+		return;
+	}
+
+	list_for_each_entry(dev, &bus->devices, bus_list) {
+		if (is_pci_reset_device(dev)) {
+			pci_dev_reset_resource(dev, head);
+			continue;
+		}
+		if ((b = dev->subordinate))
+			pci_bus_reset_resource(b, head);
+	}
+	return;
+}
+
+/*
+ * Release all the resources in the list 'head'.
+ */
+static void __init
+pci_release_resources(struct resource_list_x *head)
+{
+	struct resource_list_x *list;
+	unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM |
+				  IORESOURCE_PREFETCH;
+	enum release_type rel_type = whole_subtree;
+	/*
+	 * Try to release leaf bridge's resources that doesn't fit resource of
+	 * child device under that bridge
+	 */
+	for (list = head->next; list;) {
+		struct pci_bus *bus =  list->dev->bus;
+		pci_bus_release_bridge_resources(bus, list->flags & type_mask,
+						  rel_type);
+		list = list->next;
+	}
+	/* restore size and flags */
+	for (list = head->next; list;) {
+		struct resource *res = list->res;
+
+		res->start = list->start;
+		res->end = list->end;
+		res->flags = list->flags;
+		if (list->dev->subordinate)
+			res->flags = 0;
+
+		list = list->next;
+	}
+	free_failed_list(head);
+}
+
+
 void __init
 pci_assign_unassigned_resources(void)
 {
 	struct pci_bus *bus;
+	int tried_times = 0;
+	struct resource_list_x head;
+
+	head.next = NULL;
 
-	/* Depth first, calculate sizes and alignments of all
-	   subordinate buses. */
 	list_for_each_entry(bus, &pci_root_buses, node) {
-		pci_bus_size_bridges(bus);
+		if (pci_override == PCI_OVERRIDE_DEVICE ||
+			  pci_override == PCI_OVERRIDE_ALWAYS) {
+			pci_bus_reset_resource(bus, &head);
+			pci_release_resources(&head);
+		} else if (pci_override == PCI_CLEAR_DEVICE) {
+			pci_bus_reset_resource(bus, NULL);
+		}
 	}
-	/* Depth last, allocate resources and update the hardware. */
-	list_for_each_entry(bus, &pci_root_buses, node) {
-		pci_bus_assign_resources(bus);
+
+	do { /* loop at most 2 times */
+		/* Depth first, calculate sizes and alignments of all
+		   subordinate buses. */
+		list_for_each_entry(bus, &pci_root_buses, node) {
+			pci_bus_size_bridges(bus);
+		}
+
+		/* Depth last, allocate resources and update the hardware. */
+		list_for_each_entry(bus, &pci_root_buses, node) {
+			__pci_bus_assign_resources(bus, &head);
+		}
+
+		/* happily exit the loop if all devices are happy */
+		if (!head.next)
+			goto enable_and_dump;
+
+		/* dont' care if any device complained, unless asked to care */
+		if (pci_override != PCI_OVERRIDE_CONFLICT) {
+			free_failed_list(&head);
+			goto enable_and_dump;
+		}
+
+		printk(KERN_INFO "PCI: No. %d try to assign unassigned res\n",
+				 tried_times);
+
+		pci_release_resources(&head);
+
+	} while (!tried_times++);
+
+enable_and_dump:
+	/* Depth last, update the hardware. */
+	list_for_each_entry(bus, &pci_root_buses, node)
 		pci_enable_bridges(bus);
-	}
 
 	/* dump the resource on buses */
 	list_for_each_entry(bus, &pci_root_buses, node) {
diff --git a/include/linux/pci.h b/include/linux/pci.h
index b1d1795..c001005 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1357,10 +1357,18 @@  extern u8 pci_cache_line_size;
 extern unsigned long pci_hotplug_io_size;
 extern unsigned long pci_hotplug_mem_size;
 
+extern int pci_override;
+#define  PCI_OVERRIDE_OFF 	1
+#define  PCI_OVERRIDE_CONFLICT	2
+#define  PCI_OVERRIDE_ALWAYS 	3
+#define  PCI_OVERRIDE_DEVICE 	4
+#define  PCI_CLEAR_DEVICE 	5
+
 int pcibios_add_platform_entries(struct pci_dev *dev);
 void pcibios_disable_device(struct pci_dev *dev);
 int pcibios_set_pcie_reset_state(struct pci_dev *dev,
 				 enum pcie_reset_state state);
+int is_pci_reset_device(struct pci_dev *dev);
 
 #ifdef CONFIG_PCI_MMCONFIG
 extern void __init pci_mmcfg_early_init(void);