Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI: Dynamically map ECAM regions

Attempting to boot 32-bit ARM kernels under QEMU's 3.x virt models fails
when we have more than 512M of RAM in the model as we run out of vmalloc
space for the PCI ECAM regions. This failure will be silent when running
libvirt, as the console in that situation is a PCI device.

In this configuration, the kernel maps the whole ECAM, which QEMU sets up
for 256 buses, even when maybe only seven buses are in use. Each bus uses
1M of ECAM space, and ioremap() adds an additional guard page between
allocations. The kernel vmap allocator will align these regions to 512K,
resulting in each mapping eating 1.5M of vmalloc space. This means we need
384M of vmalloc space just to map all of these, which is very wasteful of
resources.

Fix this by only mapping the ECAM for buses we are going to be using. In
my setups, this is around seven buses in most guests, which is 10.5M of
vmalloc space - way smaller than the 384M that would otherwise be required.
This also means that the kernel can boot without forcing extra RAM into
highmem with the vmalloc= argument, or decreasing the virtual RAM available
to the guest.

Suggested-by: Arnd Bergmann <arnd@arndb.de>
Link: https://lore.kernel.org/r/E1lhCAV-0002yb-50@rmk-PC.armlinux.org.uk
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Arnd Bergmann <arnd@arndb.de>

authored by

Russell King and committed by
Bjorn Helgaas
8fe55ef2 ea4aae05

+47 -8
+46 -8
drivers/pci/ecam.c
··· 32 32 struct pci_config_window *cfg; 33 33 unsigned int bus_range, bus_range_max, bsz; 34 34 struct resource *conflict; 35 - int i, err; 35 + int err; 36 36 37 37 if (busr->start > busr->end) 38 38 return ERR_PTR(-EINVAL); ··· 50 50 cfg->busr.start = busr->start; 51 51 cfg->busr.end = busr->end; 52 52 cfg->busr.flags = IORESOURCE_BUS; 53 + cfg->bus_shift = bus_shift; 53 54 bus_range = resource_size(&cfg->busr); 54 55 bus_range_max = resource_size(cfgres) >> bus_shift; 55 56 if (bus_range > bus_range_max) { ··· 78 77 cfg->winp = kcalloc(bus_range, sizeof(*cfg->winp), GFP_KERNEL); 79 78 if (!cfg->winp) 80 79 goto err_exit_malloc; 81 - for (i = 0; i < bus_range; i++) { 82 - cfg->winp[i] = 83 - pci_remap_cfgspace(cfgres->start + i * bsz, 84 - bsz); 85 - if (!cfg->winp[i]) 86 - goto err_exit_iomap; 87 - } 88 80 } else { 89 81 cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz); 90 82 if (!cfg->win) ··· 123 129 } 124 130 EXPORT_SYMBOL_GPL(pci_ecam_free); 125 131 132 + static int pci_ecam_add_bus(struct pci_bus *bus) 133 + { 134 + struct pci_config_window *cfg = bus->sysdata; 135 + unsigned int bsz = 1 << cfg->bus_shift; 136 + unsigned int busn = bus->number; 137 + phys_addr_t start; 138 + 139 + if (!per_bus_mapping) 140 + return 0; 141 + 142 + if (busn < cfg->busr.start || busn > cfg->busr.end) 143 + return -EINVAL; 144 + 145 + busn -= cfg->busr.start; 146 + start = cfg->res.start + busn * bsz; 147 + 148 + cfg->winp[busn] = pci_remap_cfgspace(start, bsz); 149 + if (!cfg->winp[busn]) 150 + return -ENOMEM; 151 + 152 + return 0; 153 + } 154 + 155 + static void pci_ecam_remove_bus(struct pci_bus *bus) 156 + { 157 + struct pci_config_window *cfg = bus->sysdata; 158 + unsigned int busn = bus->number; 159 + 160 + if (!per_bus_mapping || busn < cfg->busr.start || busn > cfg->busr.end) 161 + return; 162 + 163 + busn -= cfg->busr.start; 164 + if (cfg->winp[busn]) { 165 + iounmap(cfg->winp[busn]); 166 + cfg->winp[busn] = NULL; 167 + } 168 + } 169 + 126 170 /* 127 171 * Function to implement the pci_ops ->map_bus method 128 172 */ ··· 199 167 /* ECAM ops */ 200 168 const struct pci_ecam_ops pci_generic_ecam_ops = { 201 169 .pci_ops = { 170 + .add_bus = pci_ecam_add_bus, 171 + .remove_bus = pci_ecam_remove_bus, 202 172 .map_bus = pci_ecam_map_bus, 203 173 .read = pci_generic_config_read, 204 174 .write = pci_generic_config_write, ··· 212 178 /* ECAM ops for 32-bit access only (non-compliant) */ 213 179 const struct pci_ecam_ops pci_32b_ops = { 214 180 .pci_ops = { 181 + .add_bus = pci_ecam_add_bus, 182 + .remove_bus = pci_ecam_remove_bus, 215 183 .map_bus = pci_ecam_map_bus, 216 184 .read = pci_generic_config_read32, 217 185 .write = pci_generic_config_write32, ··· 223 187 /* ECAM ops for 32-bit read only (non-compliant) */ 224 188 const struct pci_ecam_ops pci_32b_read_ops = { 225 189 .pci_ops = { 190 + .add_bus = pci_ecam_add_bus, 191 + .remove_bus = pci_ecam_remove_bus, 226 192 .map_bus = pci_ecam_map_bus, 227 193 .read = pci_generic_config_read32, 228 194 .write = pci_generic_config_write,
+1
include/linux/pci-ecam.h
··· 55 55 struct pci_config_window { 56 56 struct resource res; 57 57 struct resource busr; 58 + unsigned int bus_shift; 58 59 void *priv; 59 60 const struct pci_ecam_ops *ops; 60 61 union {