From c6e73db5fafa151b81a969a74c3138488a4310fa Mon Sep 17 00:00:00 2001
From: Alexey Makhalov <amakhalov@vmware.com>
Date: Tue, 29 Sep 2015 15:55:49 -0700
Subject: [PATCH] pci/probe.c: Hardcodded pci probe.

PCI probing takes a long time to scan resources for all devices
on PCI bus. Idea of this patch is to hardcode known resources
for known devices. In VMware hypervisor we do not have much
virtual PCI devices.

is_known_device() has list of known devices and available
resources (BARs) for them.
It returns: 0 if BAR not available for the given device
            1 if available and sz parameter will have BAR size

guess_bar_count() returns maximum number of BARs for the given
class to do not probe for all 6 bars

has_rom() returns 0 if given class does not have any ROM BARs,
which allows to skip ROM probing

Added pci=scan_all cmdline parameter verifies hardcodded pci
values at runtime.
---
 drivers/pci/pci.c   |   2 +
 drivers/pci/pci.h   |   2 +
 drivers/pci/probe.c | 377 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 377 insertions(+), 4 deletions(-)


diff -ur linux-4.19.1/drivers/pci/pci.c linux-4.19.1_new/drivers/pci/pci.c
--- linux-4.19.1/drivers/pci/pci.c	2018-11-04 13:50:54.000000000 +0000
+++ linux-4.19.1_new/drivers/pci/pci.c	2018-11-08 01:59:21.813106907 +0000
@@ -6116,6 +6116,8 @@
 				pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS);
 			} else if (!strncmp(str, "disable_acs_redir=", 18)) {
 				disable_acs_redir_param = str + 18;
+			} else if (!strncmp(str, "scan_all", 8)) {
+				pci_scan_all();
 			} else {
 				printk(KERN_ERR "PCI: Unknown option `%s'\n",
 						str);
diff -ur linux-4.19.1/drivers/pci/pci.h linux-4.19.1_new/drivers/pci/pci.h
--- linux-4.19.1/drivers/pci/pci.h	2018-11-04 13:50:54.000000000 +0000
+++ linux-4.19.1_new/drivers/pci/pci.h	2018-11-08 01:57:51.917110565 +0000
@@ -147,6 +147,8 @@
 static inline void pci_no_msi(void) { }
 #endif
 
+void pci_scan_all(void);
+
 static inline void pci_msi_set_enable(struct pci_dev *dev, int enable)
 {
 	u16 control;
diff -ur linux-4.19.1/drivers/pci/probe.c linux-4.19.1_new/drivers/pci/probe.c
--- linux-4.19.1/drivers/pci/probe.c	2018-11-04 13:50:54.000000000 +0000
+++ linux-4.19.1_new/drivers/pci/probe.c	2018-11-08 01:57:51.917110565 +0000
@@ -168,6 +168,346 @@
 
 #define PCI_COMMAND_DECODE_ENABLE	(PCI_COMMAND_MEMORY | PCI_COMMAND_IO)
 
+static int pci_scan_and_verify = 0;
+
+void pci_scan_all(void)
+{
+	pci_scan_and_verify = 1;
+}
+
+/* shortcut version of __pci_read_base where we know the sizes already */
+int __pci_read_base_shortcut(struct pci_dev *dev, enum pci_bar_type type,
+		    struct resource *res, unsigned int pos, u32 sz_in, u32 sz2_in)
+{
+	u32 l = 0, sz = 0;
+	u64 l64, sz64, mask64;
+	struct pci_bus_region region, inverted_region;
+
+	res->name = pci_name(dev);
+
+	pci_read_config_dword(dev, pos, &l);
+
+	sz = sz_in;
+
+	/*
+	 * All bits set in sz means the device isn't working properly.
+	 * If the BAR isn't implemented, all bits must be 0.  If it's a
+	 * memory BAR or a ROM, bit 0 must be clear; if it's an io BAR, bit
+	 * 1 must be clear.
+	 * Here we set the size and is not 0xffffffff
+	 */
+
+	/*
+	 * I don't know how l can have all bits set.  Copied from old code.
+	 * Maybe it fixes a bug on some ancient platform.
+	 */
+	if (l == 0xffffffff)
+		l = 0;
+
+	if (type == pci_bar_unknown) {
+		res->flags = decode_bar(dev, l);
+		res->flags |= IORESOURCE_SIZEALIGN;
+		if (res->flags & IORESOURCE_IO) {
+			l64 = l & PCI_BASE_ADDRESS_IO_MASK;
+			sz64 = sz & PCI_BASE_ADDRESS_IO_MASK;
+			mask64 = PCI_BASE_ADDRESS_IO_MASK & (u32)IO_SPACE_LIMIT;
+		} else {
+			l64 = l & PCI_BASE_ADDRESS_MEM_MASK;
+			sz64 = sz & PCI_BASE_ADDRESS_MEM_MASK;
+			mask64 = (u32)PCI_BASE_ADDRESS_MEM_MASK;
+		}
+	} else {
+		if (l & PCI_ROM_ADDRESS_ENABLE)
+			res->flags |= IORESOURCE_ROM_ENABLE;
+		l64 = l & PCI_ROM_ADDRESS_MASK;
+		sz64 = sz & PCI_ROM_ADDRESS_MASK;
+		mask64 = PCI_ROM_ADDRESS_MASK;
+	}
+
+	if (res->flags & IORESOURCE_MEM_64) {
+		pci_read_config_dword(dev, pos + 4, &l);
+		sz = sz2_in;
+
+		l64 |= ((u64)l << 32);
+		sz64 |= ((u64)sz << 32);
+		mask64 |= ((u64)~0 << 32);
+	}
+
+	if (!sz64)
+		goto fail;
+
+	sz64 = pci_size(l64, sz64, mask64);
+	if (!sz64) {
+		dev_info(&dev->dev, FW_BUG "reg 0x%x: invalid BAR (can't size)\n",
+			 pos);
+		goto fail;
+	}
+
+	if (res->flags & IORESOURCE_MEM_64) {
+		if ((sizeof(pci_bus_addr_t) < 8 || sizeof(resource_size_t) < 8) &&
+		    sz64 > 0x100000000ULL) {
+			res->flags |= IORESOURCE_UNSET | IORESOURCE_DISABLED;
+			res->start = 0;
+			res->end = 0;
+			dev_err(&dev->dev, "reg 0x%x: can't handle BAR larger than 4GB (size %#010llx)\n",
+				pos, (unsigned long long)sz64);
+			goto out;
+		}
+
+		if ((sizeof(pci_bus_addr_t) < 8) && l) {
+			/* Above 32-bit boundary; try to reallocate */
+			res->flags |= IORESOURCE_UNSET;
+			res->start = 0;
+			res->end = sz64;
+			dev_info(&dev->dev, "reg 0x%x: can't handle BAR above 4GB (bus address %#010llx)\n",
+				 pos, (unsigned long long)l64);
+			goto out;
+		}
+	}
+
+	region.start = l64;
+	region.end = l64 + sz64;
+
+	pcibios_bus_to_resource(dev->bus, res, &region);
+	pcibios_resource_to_bus(dev->bus, &inverted_region, res);
+
+	/*
+	 * If "A" is a BAR value (a bus address), "bus_to_resource(A)" is
+	 * the corresponding resource address (the physical address used by
+	 * the CPU.  Converting that resource address back to a bus address
+	 * should yield the original BAR value:
+	 *
+	 *     resource_to_bus(bus_to_resource(A)) == A
+	 *
+	 * If it doesn't, CPU accesses to "bus_to_resource(A)" will not
+	 * be claimed by the device.
+	 */
+	if (inverted_region.start != region.start) {
+		res->flags |= IORESOURCE_UNSET;
+		res->start = 0;
+		res->end = region.end - region.start;
+		dev_info(&dev->dev, "reg 0x%x: initial BAR value %#010llx invalid\n",
+			 pos, (unsigned long long)region.start);
+	}
+
+	goto out;
+
+
+fail:
+	res->flags = 0;
+out:
+	if (res->flags)
+		dev_printk(KERN_DEBUG, &dev->dev, "reg 0x%x: %pR\n", pos, res);
+
+	return (res->flags & IORESOURCE_MEM_64) ? 1 : 0;
+}
+
+static int guess_bar_count(int class)
+{
+        if (class == 0x088000)
+            return 2;
+        if (class == 0x068000)
+            return 0;
+        if (class == 0x060100)
+            return 0;
+        if (class == 0x060000)
+            return 0;
+        if (class == 0x030000)
+            return 3;
+        if (class == 0x020000 || class == 0x010000)
+            return 4;
+        if (class == 0x00ff00)
+            return 1;
+        return 6;
+}
+
+static int has_rom(int class, int rom)
+{
+        if (class == 0x088000 ||
+	    class == 0x060100 ||
+	    class == 0x060000 ||
+	    class == 0x00ff00)
+          return 0;
+        return rom;
+}
+
+static int is_known_device(struct pci_dev *dev, int pos, int *sz)
+{
+	switch (dev->vendor) {
+		/* Intel Corporation */
+		case 0x8086:
+		switch (dev->device) {
+			/* IDE interface: 82371AB/EB/MB PIIX4 IDE */
+			case 0x7111:
+			switch (pos) {
+				case 0x20:
+					*sz = 0xfffffff1;
+					return 1;
+				case 0x10:
+				case 0x14:
+				case 0x18:
+				case 0x1c:
+				case 0x24:
+				case 0x30:
+					*sz = 0; /* Not implemented */
+					return 1;
+			}
+			break;
+
+			/* Bridge: 82371AB/EB/MB PIIX4 ACPI */
+			case 0x7113:
+			switch (pos) {
+				case 0x10:
+				case 0x14:
+				case 0x18:
+				case 0x1c:
+				case 0x20:
+				case 0x24:
+				case 0x30:
+					*sz = 0; /* Not implemented */
+					return 1;
+			}
+			break;
+		}
+		break;
+
+		/* VMware, Inc */
+		case 0x15ad:
+		switch (dev->device) {
+			/* VMware SVGA II Adapter */
+			case 0x0405:
+			switch (pos) {
+				case 0x10:
+					*sz = 0xfffffff1;
+					return 1;
+				case 0x14:
+					/* variable mem size */
+					*sz = 0xffffffff;
+					return 0;
+				case 0x18:
+					*sz = 0xff800000;
+					return 1;
+				case 0x1c:
+				case 0x20:
+				case 0x24:
+					*sz = 0; /* Not implemented */
+					return 1;
+				case 0x30:
+					*sz = 0xffff8000;
+					return 1;
+			}
+			break;
+
+			/* VMware Virtual Machine Communication Interface */
+			case 0x0740:
+			switch (pos) {
+				case 0x10:
+					*sz = 0xffffffc1;
+					return 1;
+				case 0x14:
+					*sz = 0xffffe004;
+					return 1;
+				case 0x1c:
+				case 0x20:
+				case 0x24:
+				case 0x30:
+					*sz = 0; /* Not implemented */
+					return 1;
+			}
+			break;
+
+			/* VMware PCI bridge */
+			case 0x0790:
+			/* VMware PCI Express Root Port */
+			case 0x07a0:
+			switch (pos) {
+				case 0x10:
+				case 0x14:
+				case 0x38:
+					*sz = 0; /* Not implemented */
+					return 1;
+			}
+			break;
+
+			/* VMware VMXNET3 Ethernet Controller */
+			case 0x07b0:
+			switch (pos) {
+				case 0x10:
+					*sz = 0xfffff000;
+					return 1;
+				case 0x14:
+					*sz = 0xfffff000;
+					return 1;
+				case 0x18:
+					*sz = 0xffffe000;
+					return 1;
+				case 0x1c:
+					*sz = 0xfffffff1;
+					return 1;
+				case 0x20:
+				case 0x24:
+					*sz = 0; /* Not implemented */
+					return 1;
+				case 0x30:
+					*sz = 0xffff0000;
+					return 1;
+			}
+			break;
+
+			/* VMware PVSCSI SCSI Controller */
+			case 0x07c0:
+			switch (pos) {
+				case 0x10:
+					*sz = 0xfffffff9;
+					return 1;
+				case 0x14:
+					*sz = 0xffff8000;
+					return 1;
+				case 0x1c:
+				case 0x20:
+				case 0x24:
+					*sz = 0; /* Not implemented */
+					return 1;
+				case 0x30:
+					*sz = 0xffff0000;
+					return 1;
+			}
+			break;
+		}
+		break;
+
+		/* SCSI storage controller */
+		case 0x1000:
+		switch (dev->device) {
+			/* LSI Logic */
+			case 0x0030:
+			switch (pos) {
+				case 0x10:
+					*sz = 0xff01;
+					return 1;
+				case 0x14:
+					*sz = 0xfffe0004;
+					return 1;
+				case 0x1c:
+					*sz = 0xfffe0004;
+					return 1;
+				case 0x18:
+				case 0x20:
+				case 0x24:
+					*sz = 0; /* Not implemented */
+					return 1;
+				case 0x30:
+					*sz = 0xffffc000;
+					return 1;
+			}
+			break;
+		}
+		break;
+	}
+	*sz = 0xffffffff;
+        return 0;
+}
+
 /**
  * pci_read_base - Read a PCI BAR
  * @dev: the PCI device
@@ -180,13 +520,20 @@
 int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type,
 		    struct resource *res, unsigned int pos)
 {
-	u32 l = 0, sz = 0, mask;
+	u32 l = 0, sz = 0, known_sz, mask;
 	u64 l64, sz64, mask64;
 	u16 orig_cmd;
 	struct pci_bus_region region, inverted_region;
 
 	mask = type ? PCI_ROM_ADDRESS_MASK : ~0;
 
+	if (is_known_device(dev, pos, &known_sz) && !pci_scan_and_verify)
+		return __pci_read_base_shortcut(dev, type, res, pos, known_sz, 0);
+
+	res->name = pci_name(dev);
+
+	printk("Starting probe for %s %x:%x:%x\n", res->name, dev->vendor, dev->device, pos);
+
 	/* No printks while decoding is disabled! */
 	if (!dev->mmio_always_on) {
 		pci_read_config_word(dev, PCI_COMMAND, &orig_cmd);
@@ -196,7 +543,6 @@
 		}
 	}
 
-	res->name = pci_name(dev);
 
 	pci_read_config_dword(dev, pos, &l);
 	pci_write_config_dword(dev, pos, l | mask);
@@ -212,6 +558,11 @@
 	if (sz == 0xffffffff)
 		sz = 0;
 
+	if (pci_scan_and_verify && known_sz != 0xffffffff && known_sz != sz)
+		dev_err(&dev->dev,
+			"wrong known bar size: %x != %x\n",
+			known_sz, sz);
+
 	/*
 	 * I don't know how l can have all bits set.  Copied from old code.
 	 * Maybe it fixes a bug on some ancient platform.
@@ -316,8 +667,19 @@
 fail:
 	res->flags = 0;
 out:
-	if (res->flags)
+	if (res->flags) {
 		pci_printk(KERN_DEBUG, dev, "reg 0x%x: %pR\n", pos, res);
+		if (pci_scan_and_verify) {
+			int bar = (pos - PCI_BASE_ADDRESS_0) >> 2;
+			int maxbar = guess_bar_count(dev->class);
+			if (bar < 6 && bar >= maxbar)
+				dev_err(&dev->dev,
+					"wrong guess_bar_count value %d\n",
+					maxbar);
+			if (pos == PCI_ROM_ADDRESS && !has_rom(dev->class, 1))
+				dev_err(&dev->dev, "wrong has_rom value\n");
+		}
+	}
 
 	return (res->flags & IORESOURCE_MEM_64) ? 1 : 0;
 }
@@ -1646,7 +2008,13 @@
 		if (class == PCI_CLASS_BRIDGE_PCI)
 			goto bad;
 		pci_read_irq(dev);
-		pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
+
+		if (pci_scan_and_verify)
+			pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
+		else
+			pci_read_bases(dev,
+				guess_bar_count(dev->class),
+				has_rom(dev->class, PCI_ROM_ADDRESS));
 
 		pci_subsystem_ids(dev, &dev->subsystem_vendor, &dev->subsystem_device);