1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2016 Broadcom |
4 | */ |
5 | |
6 | #include <linux/device.h> |
7 | #include <linux/io.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/pci.h> |
11 | #include <linux/pci-ecam.h> |
12 | #include <linux/slab.h> |
13 | |
14 | /* |
15 | * On 64-bit systems, we do a single ioremap for the whole config space |
16 | * since we have enough virtual address range available. On 32-bit, we |
17 | * ioremap the config space for each bus individually. |
18 | */ |
19 | static const bool per_bus_mapping = !IS_ENABLED(CONFIG_64BIT); |
20 | |
21 | /* |
22 | * Create a PCI config space window |
23 | * - reserve mem region |
24 | * - alloc struct pci_config_window with space for all mappings |
25 | * - ioremap the config space |
26 | */ |
27 | struct pci_config_window *pci_ecam_create(struct device *dev, |
28 | struct resource *cfgres, struct resource *busr, |
29 | const struct pci_ecam_ops *ops) |
30 | { |
31 | unsigned int bus_shift = ops->bus_shift; |
32 | struct pci_config_window *cfg; |
33 | unsigned int bus_range, bus_range_max, bsz; |
34 | struct resource *conflict; |
35 | int err; |
36 | |
37 | if (busr->start > busr->end) |
38 | return ERR_PTR(error: -EINVAL); |
39 | |
40 | cfg = kzalloc(size: sizeof(*cfg), GFP_KERNEL); |
41 | if (!cfg) |
42 | return ERR_PTR(error: -ENOMEM); |
43 | |
44 | /* ECAM-compliant platforms need not supply ops->bus_shift */ |
45 | if (!bus_shift) |
46 | bus_shift = PCIE_ECAM_BUS_SHIFT; |
47 | |
48 | cfg->parent = dev; |
49 | cfg->ops = ops; |
50 | cfg->busr.start = busr->start; |
51 | cfg->busr.end = busr->end; |
52 | cfg->busr.flags = IORESOURCE_BUS; |
53 | cfg->bus_shift = bus_shift; |
54 | bus_range = resource_size(res: &cfg->busr); |
55 | bus_range_max = resource_size(res: cfgres) >> bus_shift; |
56 | if (bus_range > bus_range_max) { |
57 | bus_range = bus_range_max; |
58 | cfg->busr.end = busr->start + bus_range - 1; |
59 | dev_warn(dev, "ECAM area %pR can only accommodate %pR (reduced from %pR desired)\n" , |
60 | cfgres, &cfg->busr, busr); |
61 | } |
62 | bsz = 1 << bus_shift; |
63 | |
64 | cfg->res.start = cfgres->start; |
65 | cfg->res.end = cfgres->end; |
66 | cfg->res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; |
67 | cfg->res.name = "PCI ECAM" ; |
68 | |
69 | conflict = request_resource_conflict(root: &iomem_resource, new: &cfg->res); |
70 | if (conflict) { |
71 | err = -EBUSY; |
72 | dev_err(dev, "can't claim ECAM area %pR: address conflict with %s %pR\n" , |
73 | &cfg->res, conflict->name, conflict); |
74 | goto err_exit; |
75 | } |
76 | |
77 | if (per_bus_mapping) { |
78 | cfg->winp = kcalloc(n: bus_range, size: sizeof(*cfg->winp), GFP_KERNEL); |
79 | if (!cfg->winp) |
80 | goto err_exit_malloc; |
81 | } else { |
82 | cfg->win = pci_remap_cfgspace(offset: cfgres->start, size: bus_range * bsz); |
83 | if (!cfg->win) |
84 | goto err_exit_iomap; |
85 | } |
86 | |
87 | if (ops->init) { |
88 | err = ops->init(cfg); |
89 | if (err) |
90 | goto err_exit; |
91 | } |
92 | dev_info(dev, "ECAM at %pR for %pR\n" , &cfg->res, &cfg->busr); |
93 | return cfg; |
94 | |
95 | err_exit_iomap: |
96 | dev_err(dev, "ECAM ioremap failed\n" ); |
97 | err_exit_malloc: |
98 | err = -ENOMEM; |
99 | err_exit: |
100 | pci_ecam_free(cfg); |
101 | return ERR_PTR(error: err); |
102 | } |
103 | EXPORT_SYMBOL_GPL(pci_ecam_create); |
104 | |
105 | void pci_ecam_free(struct pci_config_window *cfg) |
106 | { |
107 | int i; |
108 | |
109 | if (per_bus_mapping) { |
110 | if (cfg->winp) { |
111 | for (i = 0; i < resource_size(res: &cfg->busr); i++) |
112 | if (cfg->winp[i]) |
113 | iounmap(addr: cfg->winp[i]); |
114 | kfree(objp: cfg->winp); |
115 | } |
116 | } else { |
117 | if (cfg->win) |
118 | iounmap(addr: cfg->win); |
119 | } |
120 | if (cfg->res.parent) |
121 | release_resource(new: &cfg->res); |
122 | kfree(objp: cfg); |
123 | } |
124 | EXPORT_SYMBOL_GPL(pci_ecam_free); |
125 | |
126 | static int pci_ecam_add_bus(struct pci_bus *bus) |
127 | { |
128 | struct pci_config_window *cfg = bus->sysdata; |
129 | unsigned int bsz = 1 << cfg->bus_shift; |
130 | unsigned int busn = bus->number; |
131 | phys_addr_t start; |
132 | |
133 | if (!per_bus_mapping) |
134 | return 0; |
135 | |
136 | if (busn < cfg->busr.start || busn > cfg->busr.end) |
137 | return -EINVAL; |
138 | |
139 | busn -= cfg->busr.start; |
140 | start = cfg->res.start + busn * bsz; |
141 | |
142 | cfg->winp[busn] = pci_remap_cfgspace(offset: start, size: bsz); |
143 | if (!cfg->winp[busn]) |
144 | return -ENOMEM; |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static void pci_ecam_remove_bus(struct pci_bus *bus) |
150 | { |
151 | struct pci_config_window *cfg = bus->sysdata; |
152 | unsigned int busn = bus->number; |
153 | |
154 | if (!per_bus_mapping || busn < cfg->busr.start || busn > cfg->busr.end) |
155 | return; |
156 | |
157 | busn -= cfg->busr.start; |
158 | if (cfg->winp[busn]) { |
159 | iounmap(addr: cfg->winp[busn]); |
160 | cfg->winp[busn] = NULL; |
161 | } |
162 | } |
163 | |
164 | /* |
165 | * Function to implement the pci_ops ->map_bus method |
166 | */ |
167 | void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn, |
168 | int where) |
169 | { |
170 | struct pci_config_window *cfg = bus->sysdata; |
171 | unsigned int bus_shift = cfg->ops->bus_shift; |
172 | unsigned int devfn_shift = cfg->ops->bus_shift - 8; |
173 | unsigned int busn = bus->number; |
174 | void __iomem *base; |
175 | u32 bus_offset, devfn_offset; |
176 | |
177 | if (busn < cfg->busr.start || busn > cfg->busr.end) |
178 | return NULL; |
179 | |
180 | busn -= cfg->busr.start; |
181 | if (per_bus_mapping) { |
182 | base = cfg->winp[busn]; |
183 | busn = 0; |
184 | } else |
185 | base = cfg->win; |
186 | |
187 | if (cfg->ops->bus_shift) { |
188 | bus_offset = (busn & PCIE_ECAM_BUS_MASK) << bus_shift; |
189 | devfn_offset = (devfn & PCIE_ECAM_DEVFN_MASK) << devfn_shift; |
190 | where &= PCIE_ECAM_REG_MASK; |
191 | |
192 | return base + (bus_offset | devfn_offset | where); |
193 | } |
194 | |
195 | return base + PCIE_ECAM_OFFSET(busn, devfn, where); |
196 | } |
197 | EXPORT_SYMBOL_GPL(pci_ecam_map_bus); |
198 | |
199 | /* ECAM ops */ |
200 | const struct pci_ecam_ops pci_generic_ecam_ops = { |
201 | .pci_ops = { |
202 | .add_bus = pci_ecam_add_bus, |
203 | .remove_bus = pci_ecam_remove_bus, |
204 | .map_bus = pci_ecam_map_bus, |
205 | .read = pci_generic_config_read, |
206 | .write = pci_generic_config_write, |
207 | } |
208 | }; |
209 | EXPORT_SYMBOL_GPL(pci_generic_ecam_ops); |
210 | |
211 | #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) |
212 | /* ECAM ops for 32-bit access only (non-compliant) */ |
213 | const struct pci_ecam_ops pci_32b_ops = { |
214 | .pci_ops = { |
215 | .add_bus = pci_ecam_add_bus, |
216 | .remove_bus = pci_ecam_remove_bus, |
217 | .map_bus = pci_ecam_map_bus, |
218 | .read = pci_generic_config_read32, |
219 | .write = pci_generic_config_write32, |
220 | } |
221 | }; |
222 | |
223 | /* ECAM ops for 32-bit read only (non-compliant) */ |
224 | const struct pci_ecam_ops pci_32b_read_ops = { |
225 | .pci_ops = { |
226 | .add_bus = pci_ecam_add_bus, |
227 | .remove_bus = pci_ecam_remove_bus, |
228 | .map_bus = pci_ecam_map_bus, |
229 | .read = pci_generic_config_read32, |
230 | .write = pci_generic_config_write, |
231 | } |
232 | }; |
233 | #endif |
234 | |