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