1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 2f68c0654SThomas Gleixner /* 3f68c0654SThomas Gleixner * mmconfig.c - Low-level direct PCI config space access via MMCONFIG 4f68c0654SThomas Gleixner * 5f68c0654SThomas Gleixner * This is an 64bit optimized version that always keeps the full mmconfig 6f68c0654SThomas Gleixner * space mapped. This allows lockless config space operation. 7f68c0654SThomas Gleixner */ 8f68c0654SThomas Gleixner 9f68c0654SThomas Gleixner #include <linux/pci.h> 10f68c0654SThomas Gleixner #include <linux/init.h> 11f68c0654SThomas Gleixner #include <linux/acpi.h> 12f68c0654SThomas Gleixner #include <linux/bitmap.h> 13376f70acSJiang Liu #include <linux/rcupdate.h> 1466441bd3SIngo Molnar #include <asm/e820/api.h> 1582487711SJaswinder Singh Rajput #include <asm/pci_x86.h> 16f68c0654SThomas Gleixner 178c57786aSBjorn Helgaas #define PREFIX "PCI: " 188c57786aSBjorn Helgaas 19f68c0654SThomas Gleixner static char __iomem *pci_dev_base(unsigned int seg, unsigned int bus, unsigned int devfn) 20f68c0654SThomas Gleixner { 21f6e1d8ccSBjorn Helgaas struct pci_mmcfg_region *cfg = pci_mmconfig_lookup(seg, bus); 22a0ca9909SIvan Kokshaysky 23f6e1d8ccSBjorn Helgaas if (cfg && cfg->virt) 24f6e1d8ccSBjorn Helgaas return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12)); 25f68c0654SThomas Gleixner return NULL; 26f68c0654SThomas Gleixner } 27f68c0654SThomas Gleixner 28f68c0654SThomas Gleixner static int pci_mmcfg_read(unsigned int seg, unsigned int bus, 29f68c0654SThomas Gleixner unsigned int devfn, int reg, int len, u32 *value) 30f68c0654SThomas Gleixner { 31f68c0654SThomas Gleixner char __iomem *addr; 32f68c0654SThomas Gleixner 33f68c0654SThomas Gleixner /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ 34f68c0654SThomas Gleixner if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) { 35a0ca9909SIvan Kokshaysky err: *value = -1; 36f68c0654SThomas Gleixner return -EINVAL; 37f68c0654SThomas Gleixner } 38f68c0654SThomas Gleixner 39376f70acSJiang Liu rcu_read_lock(); 40f68c0654SThomas Gleixner addr = pci_dev_base(seg, bus, devfn); 41376f70acSJiang Liu if (!addr) { 42376f70acSJiang Liu rcu_read_unlock(); 43a0ca9909SIvan Kokshaysky goto err; 44376f70acSJiang Liu } 45f68c0654SThomas Gleixner 46f68c0654SThomas Gleixner switch (len) { 47f68c0654SThomas Gleixner case 1: 48f68c0654SThomas Gleixner *value = mmio_config_readb(addr + reg); 49f68c0654SThomas Gleixner break; 50f68c0654SThomas Gleixner case 2: 51f68c0654SThomas Gleixner *value = mmio_config_readw(addr + reg); 52f68c0654SThomas Gleixner break; 53f68c0654SThomas Gleixner case 4: 54f68c0654SThomas Gleixner *value = mmio_config_readl(addr + reg); 55f68c0654SThomas Gleixner break; 56f68c0654SThomas Gleixner } 57376f70acSJiang Liu rcu_read_unlock(); 58f68c0654SThomas Gleixner 59f68c0654SThomas Gleixner return 0; 60f68c0654SThomas Gleixner } 61f68c0654SThomas Gleixner 62f68c0654SThomas Gleixner static int pci_mmcfg_write(unsigned int seg, unsigned int bus, 63f68c0654SThomas Gleixner unsigned int devfn, int reg, int len, u32 value) 64f68c0654SThomas Gleixner { 65f68c0654SThomas Gleixner char __iomem *addr; 66f68c0654SThomas Gleixner 67f68c0654SThomas Gleixner /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ 68f68c0654SThomas Gleixner if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) 69f68c0654SThomas Gleixner return -EINVAL; 70f68c0654SThomas Gleixner 71376f70acSJiang Liu rcu_read_lock(); 72f68c0654SThomas Gleixner addr = pci_dev_base(seg, bus, devfn); 73376f70acSJiang Liu if (!addr) { 74376f70acSJiang Liu rcu_read_unlock(); 75a0ca9909SIvan Kokshaysky return -EINVAL; 76376f70acSJiang Liu } 77f68c0654SThomas Gleixner 78f68c0654SThomas Gleixner switch (len) { 79f68c0654SThomas Gleixner case 1: 80f68c0654SThomas Gleixner mmio_config_writeb(addr + reg, value); 81f68c0654SThomas Gleixner break; 82f68c0654SThomas Gleixner case 2: 83f68c0654SThomas Gleixner mmio_config_writew(addr + reg, value); 84f68c0654SThomas Gleixner break; 85f68c0654SThomas Gleixner case 4: 86f68c0654SThomas Gleixner mmio_config_writel(addr + reg, value); 87f68c0654SThomas Gleixner break; 88f68c0654SThomas Gleixner } 89376f70acSJiang Liu rcu_read_unlock(); 90f68c0654SThomas Gleixner 91f68c0654SThomas Gleixner return 0; 92f68c0654SThomas Gleixner } 93f68c0654SThomas Gleixner 94c0fa4078SJiang Liu const struct pci_raw_ops pci_mmcfg = { 95f68c0654SThomas Gleixner .read = pci_mmcfg_read, 96f68c0654SThomas Gleixner .write = pci_mmcfg_write, 97f68c0654SThomas Gleixner }; 98f68c0654SThomas Gleixner 99a18e3690SGreg Kroah-Hartman static void __iomem *mcfg_ioremap(struct pci_mmcfg_region *cfg) 100f68c0654SThomas Gleixner { 101f68c0654SThomas Gleixner void __iomem *addr; 102068258bcSYinghai Lu u64 start, size; 103df5eb1d6SBjorn Helgaas int num_buses; 104f68c0654SThomas Gleixner 105d7e6b66fSBjorn Helgaas start = cfg->address + PCI_MMCFG_BUS_OFFSET(cfg->start_bus); 106d7e6b66fSBjorn Helgaas num_buses = cfg->end_bus - cfg->start_bus + 1; 107df5eb1d6SBjorn Helgaas size = PCI_MMCFG_BUS_OFFSET(num_buses); 108*4bdc0d67SChristoph Hellwig addr = ioremap(start, size); 1098c57786aSBjorn Helgaas if (addr) 110d7e6b66fSBjorn Helgaas addr -= PCI_MMCFG_BUS_OFFSET(cfg->start_bus); 111f68c0654SThomas Gleixner return addr; 112f68c0654SThomas Gleixner } 113f68c0654SThomas Gleixner 114f68c0654SThomas Gleixner int __init pci_mmcfg_arch_init(void) 115f68c0654SThomas Gleixner { 1163f0f5503SBjorn Helgaas struct pci_mmcfg_region *cfg; 117f68c0654SThomas Gleixner 1189cf0105dSJiang Liu list_for_each_entry(cfg, &pci_mmcfg_list, list) 1199cf0105dSJiang Liu if (pci_mmcfg_arch_map(cfg)) { 1200b64ad71SYinghai Lu pci_mmcfg_arch_free(); 121f68c0654SThomas Gleixner return 0; 122f68c0654SThomas Gleixner } 1239cf0105dSJiang Liu 124b6ce068aSMatthew Wilcox raw_pci_ext_ops = &pci_mmcfg; 1259cf0105dSJiang Liu 126f68c0654SThomas Gleixner return 1; 127f68c0654SThomas Gleixner } 1280b64ad71SYinghai Lu 1290b64ad71SYinghai Lu void __init pci_mmcfg_arch_free(void) 1300b64ad71SYinghai Lu { 1313f0f5503SBjorn Helgaas struct pci_mmcfg_region *cfg; 1320b64ad71SYinghai Lu 1339cf0105dSJiang Liu list_for_each_entry(cfg, &pci_mmcfg_list, list) 1349cf0105dSJiang Liu pci_mmcfg_arch_unmap(cfg); 1359cf0105dSJiang Liu } 1369cf0105dSJiang Liu 137a18e3690SGreg Kroah-Hartman int pci_mmcfg_arch_map(struct pci_mmcfg_region *cfg) 1389cf0105dSJiang Liu { 1399cf0105dSJiang Liu cfg->virt = mcfg_ioremap(cfg); 1409cf0105dSJiang Liu if (!cfg->virt) { 14124c97f04SJiang Liu pr_err(PREFIX "can't map MMCONFIG at %pR\n", &cfg->res); 1429cf0105dSJiang Liu return -ENOMEM; 1439cf0105dSJiang Liu } 1449cf0105dSJiang Liu 1459cf0105dSJiang Liu return 0; 1469cf0105dSJiang Liu } 1479cf0105dSJiang Liu 1489cf0105dSJiang Liu void pci_mmcfg_arch_unmap(struct pci_mmcfg_region *cfg) 1499cf0105dSJiang Liu { 1509cf0105dSJiang Liu if (cfg && cfg->virt) { 1513f0f5503SBjorn Helgaas iounmap(cfg->virt + PCI_MMCFG_BUS_OFFSET(cfg->start_bus)); 1523f0f5503SBjorn Helgaas cfg->virt = NULL; 1530b64ad71SYinghai Lu } 1540b64ad71SYinghai Lu } 155