Subject: [PATCH] PCI: Borrow dev mem windows to copy ROM

If the mem bar is not used yet and size is bigger enough.

Signed-off-by: Yinghai Lu <yinghai@kernel.org>

---
 arch/x86/pci/common.c   |    3 +
 drivers/pci/rom.c       |   73 ++++++++++++++++++++++++++++++++++++++++++++++--
 drivers/pci/setup-bus.c |    8 ++++-
 3 files changed, 79 insertions(+), 5 deletions(-)

Index: linux-2.6/drivers/pci/rom.c
===================================================================
--- linux-2.6.orig/drivers/pci/rom.c
+++ linux-2.6/drivers/pci/rom.c
@@ -116,6 +116,10 @@ void __iomem *pci_map_rom(struct pci_dev
 	struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
 	loff_t start;
 	void __iomem *rom;
+	int i = -1;
+	struct resource *mem_r = NULL;
+	resource_size_t mem_r_start = 0;
+	resource_size_t mem_r_size = 0;
 
 	/*
 	 * IORESOURCE_ROM_SHADOW set on x86, x86_64 and IA64 supports legacy
@@ -135,8 +139,45 @@ void __iomem *pci_map_rom(struct pci_dev
 		} else {
 			/* assign the ROM an address if it doesn't have one */
 			if (res->parent == NULL &&
-			    pci_assign_resource(pdev,PCI_ROM_RESOURCE))
-				return NULL;
+			    pci_assign_resource(pdev,PCI_ROM_RESOURCE)) {
+				struct resource *r;
+
+				if (res->flags || !res->end ||
+				    !resource_size(res))
+					return NULL;
+
+				/* borrow MEM resource windows */
+				for (i = 0; i < PCI_ROM_RESOURCE; i++) {
+					r = &pdev->resource[i];
+					if (!r->parent ||
+					    !(r->flags & IORESOURCE_MEM) ||
+					    r->child ||
+					    resource_size(r) < resource_size(res) ||
+					    r->start >= (1ULL<<32))
+						continue;
+					/* found one */
+					mem_r = r;
+					break;
+				}
+				if (!mem_r) {
+					i = -1;
+					return NULL;
+				}
+				/* disable mem_r temperily */
+				mem_r_start = mem_r->start;
+				mem_r_size = resource_size(mem_r);
+				mem_r->start = 0xffff0000;
+				mem_r->end = 0;
+				pci_update_resource(pdev, i);
+				/* restore flags */
+				res->flags = IORESOURCE_MEM |
+					     IORESOURCE_PREFETCH |
+					     IORESOURCE_READONLY |
+					     IORESOURCE_CACHEABLE |
+					     IORESOURCE_SIZEALIGN;
+				res->end = resource_size(res) + mem_r_start - 1;
+				res->start = mem_r_start;
+			}
 			start = pci_resource_start(pdev, PCI_ROM_RESOURCE);
 			*size = pci_resource_len(pdev, PCI_ROM_RESOURCE);
 			if (*size == 0)
@@ -155,7 +196,7 @@ void __iomem *pci_map_rom(struct pci_dev
 				    IORESOURCE_ROM_SHADOW |
 				    IORESOURCE_ROM_COPY)))
 			pci_disable_rom(pdev);
-		return NULL;
+		goto restore_mem_r;
 	}
 
 	/*
@@ -164,6 +205,32 @@ void __iomem *pci_map_rom(struct pci_dev
 	 * True size is important if the ROM is going to be copied.
 	 */
 	*size = pci_get_rom_size(pdev, rom, *size);
+
+	/* copy rom and restore borrow mem io */
+	if (i >= 0) {
+		void __iomem *new_rom;
+
+		new_rom = kmalloc(*size, GFP_KERNEL);
+		if (new_rom)
+			memcpy_fromio(new_rom, rom, *size);
+		pci_unmap_rom(pdev, rom);
+		if (new_rom) {
+			res->start = (unsigned long long)new_rom;
+			res->end = res->start + *size;
+			res->flags |= IORESOURCE_ROM_COPY;
+		} else {
+			res->start = res->end = res->flags = 0;
+		}
+		rom = new_rom;
+	}
+
+restore_mem_r:
+	if (i >= 0) {
+		mem_r->start = mem_r_start;
+		mem_r->end = mem_r_size + mem_r->start - 1;
+		pci_update_resource(pdev, i);
+	}
+
 	return rom;
 }
 
Index: linux-2.6/drivers/pci/setup-bus.c
===================================================================
--- linux-2.6.orig/drivers/pci/setup-bus.c
+++ linux-2.6/drivers/pci/setup-bus.c
@@ -290,7 +290,13 @@ static void assign_requested_resources_s
 				add_to_list(fail_head, dev_res->dev, res,
 					    0 /* dont care */,
 					    0 /* dont care */);
-			reset_resource(res);
+			/* need to save the size */
+			if (idx == PCI_ROM_RESOURCE) {
+				res->flags = 0;
+				res->end -= res->start;
+				res->start = 0;
+			} else
+				reset_resource(res);
 		}
 	}
 }
Index: linux-2.6/arch/x86/pci/common.c
===================================================================
--- linux-2.6.orig/arch/x86/pci/common.c
+++ linux-2.6/arch/x86/pci/common.c
@@ -151,7 +151,8 @@ static void __devinit pcibios_fixup_devi
 			/* we deal with BIOS assigned ROM later */
 			return;
 		}
-		rom_r->start = rom_r->end = rom_r->flags = 0;
+		/* need to save the size */
+		rom_r->flags = 0;
 	}
 }
 
