1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2004 Matthew Wilcox <matthew@wil.cx> 4 * Copyright (C) 2004 Intel Corp. 5 */ 6 7 /* 8 * mmconfig.c - Low-level direct PCI config space access via MMCONFIG 9 */ 10 11 #include <linux/pci.h> 12 #include <linux/init.h> 13 #include <linux/rcupdate.h> 14 #include <asm/e820/api.h> 15 #include <asm/pci_x86.h> 16 17 /* Assume systems with more busses have correct MCFG */ 18 #define mmcfg_virt_addr ((void __iomem *) fix_to_virt(FIX_PCIE_MCFG)) 19 20 /* The base address of the last MMCONFIG device accessed */ 21 static u32 mmcfg_last_accessed_device; 22 static int mmcfg_last_accessed_cpu; 23 24 /* 25 * Functions for accessing PCI configuration space with MMCONFIG accesses 26 */ 27 static u32 get_base_addr(unsigned int seg, int bus, unsigned devfn) 28 { 29 struct pci_mmcfg_region *cfg = pci_mmconfig_lookup(seg, bus); 30 31 if (cfg) 32 return cfg->address; 33 return 0; 34 } 35 36 /* 37 * This is always called under pci_config_lock 38 */ 39 static void pci_exp_set_dev_base(unsigned int base, int bus, int devfn) 40 { 41 u32 dev_base = base | PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12); 42 int cpu = smp_processor_id(); 43 if (dev_base != mmcfg_last_accessed_device || 44 cpu != mmcfg_last_accessed_cpu) { 45 mmcfg_last_accessed_device = dev_base; 46 mmcfg_last_accessed_cpu = cpu; 47 set_fixmap_nocache(FIX_PCIE_MCFG, dev_base); 48 } 49 } 50 51 static int pci_mmcfg_read(unsigned int seg, unsigned int bus, 52 unsigned int devfn, int reg, int len, u32 *value) 53 { 54 unsigned long flags; 55 u32 base; 56 57 if ((bus > 255) || (devfn > 255) || (reg > 4095)) { 58 err: *value = -1; 59 return -EINVAL; 60 } 61 62 rcu_read_lock(); 63 base = get_base_addr(seg, bus, devfn); 64 if (!base) { 65 rcu_read_unlock(); 66 goto err; 67 } 68 69 raw_spin_lock_irqsave(&pci_config_lock, flags); 70 71 pci_exp_set_dev_base(base, bus, devfn); 72 73 switch (len) { 74 case 1: 75 *value = mmio_config_readb(mmcfg_virt_addr + reg); 76 break; 77 case 2: 78 *value = mmio_config_readw(mmcfg_virt_addr + reg); 79 break; 80 case 4: 81 *value = mmio_config_readl(mmcfg_virt_addr + reg); 82 break; 83 } 84 raw_spin_unlock_irqrestore(&pci_config_lock, flags); 85 rcu_read_unlock(); 86 87 return 0; 88 } 89 90 static int pci_mmcfg_write(unsigned int seg, unsigned int bus, 91 unsigned int devfn, int reg, int len, u32 value) 92 { 93 unsigned long flags; 94 u32 base; 95 96 if ((bus > 255) || (devfn > 255) || (reg > 4095)) 97 return -EINVAL; 98 99 rcu_read_lock(); 100 base = get_base_addr(seg, bus, devfn); 101 if (!base) { 102 rcu_read_unlock(); 103 return -EINVAL; 104 } 105 106 raw_spin_lock_irqsave(&pci_config_lock, flags); 107 108 pci_exp_set_dev_base(base, bus, devfn); 109 110 switch (len) { 111 case 1: 112 mmio_config_writeb(mmcfg_virt_addr + reg, value); 113 break; 114 case 2: 115 mmio_config_writew(mmcfg_virt_addr + reg, value); 116 break; 117 case 4: 118 mmio_config_writel(mmcfg_virt_addr + reg, value); 119 break; 120 } 121 raw_spin_unlock_irqrestore(&pci_config_lock, flags); 122 rcu_read_unlock(); 123 124 return 0; 125 } 126 127 const struct pci_raw_ops pci_mmcfg = { 128 .read = pci_mmcfg_read, 129 .write = pci_mmcfg_write, 130 }; 131 132 int __init pci_mmcfg_arch_init(void) 133 { 134 printk(KERN_INFO "PCI: Using ECAM for extended config space\n"); 135 raw_pci_ext_ops = &pci_mmcfg; 136 return 1; 137 } 138 139 void __init pci_mmcfg_arch_free(void) 140 { 141 } 142 143 int pci_mmcfg_arch_map(struct pci_mmcfg_region *cfg) 144 { 145 return 0; 146 } 147 148 void pci_mmcfg_arch_unmap(struct pci_mmcfg_region *cfg) 149 { 150 unsigned long flags; 151 152 /* Invalidate the cached mmcfg map entry. */ 153 raw_spin_lock_irqsave(&pci_config_lock, flags); 154 mmcfg_last_accessed_device = 0; 155 raw_spin_unlock_irqrestore(&pci_config_lock, flags); 156 } 157
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.