Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | // SPDX-License-Identifier: GPL-2.0 /* * mmconfig.c - Low-level direct PCI config space access via MMCONFIG * * This is an 64bit optimized version that always keeps the full mmconfig * space mapped. This allows lockless config space operation. */ #include <linux/pci.h> #include <linux/init.h> #include <linux/acpi.h> #include <linux/bitmap.h> #include <linux/rcupdate.h> #include <asm/e820/api.h> #include <asm/pci_x86.h> #define PREFIX "PCI: " static char __iomem *pci_dev_base(unsigned int seg, unsigned int bus, unsigned int devfn) { struct pci_mmcfg_region *cfg = pci_mmconfig_lookup(seg, bus); if (cfg && cfg->virt) return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12)); return NULL; } static int pci_mmcfg_read(unsigned int seg, unsigned int bus, unsigned int devfn, int reg, int len, u32 *value) { char __iomem *addr; /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) { err: *value = -1; return -EINVAL; } rcu_read_lock(); addr = pci_dev_base(seg, bus, devfn); if (!addr) { rcu_read_unlock(); goto err; } switch (len) { case 1: *value = mmio_config_readb(addr + reg); break; case 2: *value = mmio_config_readw(addr + reg); break; case 4: *value = mmio_config_readl(addr + reg); break; } rcu_read_unlock(); return 0; } static int pci_mmcfg_write(unsigned int seg, unsigned int bus, unsigned int devfn, int reg, int len, u32 value) { char __iomem *addr; /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) return -EINVAL; rcu_read_lock(); addr = pci_dev_base(seg, bus, devfn); if (!addr) { rcu_read_unlock(); return -EINVAL; } switch (len) { case 1: mmio_config_writeb(addr + reg, value); break; case 2: mmio_config_writew(addr + reg, value); break; case 4: mmio_config_writel(addr + reg, value); break; } rcu_read_unlock(); return 0; } const struct pci_raw_ops pci_mmcfg = { .read = pci_mmcfg_read, .write = pci_mmcfg_write, }; static void __iomem *mcfg_ioremap(struct pci_mmcfg_region *cfg) { void __iomem *addr; u64 start, size; int num_buses; start = cfg->address + PCI_MMCFG_BUS_OFFSET(cfg->start_bus); num_buses = cfg->end_bus - cfg->start_bus + 1; size = PCI_MMCFG_BUS_OFFSET(num_buses); addr = ioremap(start, size); if (addr) addr -= PCI_MMCFG_BUS_OFFSET(cfg->start_bus); return addr; } int __init pci_mmcfg_arch_init(void) { struct pci_mmcfg_region *cfg; list_for_each_entry(cfg, &pci_mmcfg_list, list) if (pci_mmcfg_arch_map(cfg)) { pci_mmcfg_arch_free(); return 0; } raw_pci_ext_ops = &pci_mmcfg; return 1; } void __init pci_mmcfg_arch_free(void) { struct pci_mmcfg_region *cfg; list_for_each_entry(cfg, &pci_mmcfg_list, list) pci_mmcfg_arch_unmap(cfg); } int pci_mmcfg_arch_map(struct pci_mmcfg_region *cfg) { cfg->virt = mcfg_ioremap(cfg); if (!cfg->virt) { pr_err(PREFIX "can't map MMCONFIG at %pR\n", &cfg->res); return -ENOMEM; } return 0; } void pci_mmcfg_arch_unmap(struct pci_mmcfg_region *cfg) { if (cfg && cfg->virt) { iounmap(cfg->virt + PCI_MMCFG_BUS_OFFSET(cfg->start_bus)); cfg->virt = NULL; } } |