1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2014 IBM Corp. |
4 | */ |
5 | |
6 | #include <linux/pci.h> |
7 | #include <misc/cxl.h> |
8 | #include "cxl.h" |
9 | |
10 | static int cxl_pci_probe_mode(struct pci_bus *bus) |
11 | { |
12 | return PCI_PROBE_NORMAL; |
13 | } |
14 | |
15 | static int cxl_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) |
16 | { |
17 | return -ENODEV; |
18 | } |
19 | |
20 | static void cxl_teardown_msi_irqs(struct pci_dev *pdev) |
21 | { |
22 | /* |
23 | * MSI should never be set but need still need to provide this call |
24 | * back. |
25 | */ |
26 | } |
27 | |
28 | static bool cxl_pci_enable_device_hook(struct pci_dev *dev) |
29 | { |
30 | struct pci_controller *phb; |
31 | struct cxl_afu *afu; |
32 | struct cxl_context *ctx; |
33 | |
34 | phb = pci_bus_to_host(dev->bus); |
35 | afu = (struct cxl_afu *)phb->private_data; |
36 | |
37 | if (!cxl_ops->link_ok(afu->adapter, afu)) { |
38 | dev_warn(&dev->dev, "%s: Device link is down, refusing to enable AFU\n" , __func__); |
39 | return false; |
40 | } |
41 | |
42 | dev->dev.archdata.dma_offset = PAGE_OFFSET; |
43 | |
44 | /* |
45 | * Allocate a context to do cxl things too. If we eventually do real |
46 | * DMA ops, we'll need a default context to attach them to |
47 | */ |
48 | ctx = cxl_dev_context_init(dev); |
49 | if (IS_ERR(ptr: ctx)) |
50 | return false; |
51 | dev->dev.archdata.cxl_ctx = ctx; |
52 | |
53 | return (cxl_ops->afu_check_and_enable(afu) == 0); |
54 | } |
55 | |
56 | static void cxl_pci_disable_device(struct pci_dev *dev) |
57 | { |
58 | struct cxl_context *ctx = cxl_get_context(dev); |
59 | |
60 | if (ctx) { |
61 | if (ctx->status == STARTED) { |
62 | dev_err(&dev->dev, "Default context started\n" ); |
63 | return; |
64 | } |
65 | dev->dev.archdata.cxl_ctx = NULL; |
66 | cxl_release_context(ctx); |
67 | } |
68 | } |
69 | |
70 | static void cxl_pci_reset_secondary_bus(struct pci_dev *dev) |
71 | { |
72 | /* Should we do an AFU reset here ? */ |
73 | } |
74 | |
75 | static int cxl_pcie_cfg_record(u8 bus, u8 devfn) |
76 | { |
77 | return (bus << 8) + devfn; |
78 | } |
79 | |
80 | static inline struct cxl_afu *pci_bus_to_afu(struct pci_bus *bus) |
81 | { |
82 | struct pci_controller *phb = bus ? pci_bus_to_host(bus) : NULL; |
83 | |
84 | return phb ? phb->private_data : NULL; |
85 | } |
86 | |
87 | static void cxl_afu_configured_put(struct cxl_afu *afu) |
88 | { |
89 | atomic_dec_if_positive(v: &afu->configured_state); |
90 | } |
91 | |
92 | static bool cxl_afu_configured_get(struct cxl_afu *afu) |
93 | { |
94 | return atomic_inc_unless_negative(v: &afu->configured_state); |
95 | } |
96 | |
97 | static inline int cxl_pcie_config_info(struct pci_bus *bus, unsigned int devfn, |
98 | struct cxl_afu *afu, int *_record) |
99 | { |
100 | int record; |
101 | |
102 | record = cxl_pcie_cfg_record(bus: bus->number, devfn); |
103 | if (record > afu->crs_num) |
104 | return PCIBIOS_DEVICE_NOT_FOUND; |
105 | |
106 | *_record = record; |
107 | return 0; |
108 | } |
109 | |
110 | static int cxl_pcie_read_config(struct pci_bus *bus, unsigned int devfn, |
111 | int offset, int len, u32 *val) |
112 | { |
113 | int rc, record; |
114 | struct cxl_afu *afu; |
115 | u8 val8; |
116 | u16 val16; |
117 | u32 val32; |
118 | |
119 | afu = pci_bus_to_afu(bus); |
120 | /* Grab a reader lock on afu. */ |
121 | if (afu == NULL || !cxl_afu_configured_get(afu)) |
122 | return PCIBIOS_DEVICE_NOT_FOUND; |
123 | |
124 | rc = cxl_pcie_config_info(bus, devfn, afu, record: &record); |
125 | if (rc) |
126 | goto out; |
127 | |
128 | switch (len) { |
129 | case 1: |
130 | rc = cxl_ops->afu_cr_read8(afu, record, offset, &val8); |
131 | *val = val8; |
132 | break; |
133 | case 2: |
134 | rc = cxl_ops->afu_cr_read16(afu, record, offset, &val16); |
135 | *val = val16; |
136 | break; |
137 | case 4: |
138 | rc = cxl_ops->afu_cr_read32(afu, record, offset, &val32); |
139 | *val = val32; |
140 | break; |
141 | default: |
142 | WARN_ON(1); |
143 | } |
144 | |
145 | out: |
146 | cxl_afu_configured_put(afu); |
147 | return rc ? PCIBIOS_DEVICE_NOT_FOUND : 0; |
148 | } |
149 | |
150 | static int cxl_pcie_write_config(struct pci_bus *bus, unsigned int devfn, |
151 | int offset, int len, u32 val) |
152 | { |
153 | int rc, record; |
154 | struct cxl_afu *afu; |
155 | |
156 | afu = pci_bus_to_afu(bus); |
157 | /* Grab a reader lock on afu. */ |
158 | if (afu == NULL || !cxl_afu_configured_get(afu)) |
159 | return PCIBIOS_DEVICE_NOT_FOUND; |
160 | |
161 | rc = cxl_pcie_config_info(bus, devfn, afu, record: &record); |
162 | if (rc) |
163 | goto out; |
164 | |
165 | switch (len) { |
166 | case 1: |
167 | rc = cxl_ops->afu_cr_write8(afu, record, offset, val & 0xff); |
168 | break; |
169 | case 2: |
170 | rc = cxl_ops->afu_cr_write16(afu, record, offset, val & 0xffff); |
171 | break; |
172 | case 4: |
173 | rc = cxl_ops->afu_cr_write32(afu, record, offset, val); |
174 | break; |
175 | default: |
176 | WARN_ON(1); |
177 | } |
178 | |
179 | out: |
180 | cxl_afu_configured_put(afu); |
181 | return rc ? PCIBIOS_SET_FAILED : 0; |
182 | } |
183 | |
184 | static struct pci_ops cxl_pcie_pci_ops = |
185 | { |
186 | .read = cxl_pcie_read_config, |
187 | .write = cxl_pcie_write_config, |
188 | }; |
189 | |
190 | |
191 | static struct pci_controller_ops cxl_pci_controller_ops = |
192 | { |
193 | .probe_mode = cxl_pci_probe_mode, |
194 | .enable_device_hook = cxl_pci_enable_device_hook, |
195 | .disable_device = cxl_pci_disable_device, |
196 | .release_device = cxl_pci_disable_device, |
197 | .reset_secondary_bus = cxl_pci_reset_secondary_bus, |
198 | .setup_msi_irqs = cxl_setup_msi_irqs, |
199 | .teardown_msi_irqs = cxl_teardown_msi_irqs, |
200 | }; |
201 | |
202 | int cxl_pci_vphb_add(struct cxl_afu *afu) |
203 | { |
204 | struct pci_controller *phb; |
205 | struct device_node *vphb_dn; |
206 | struct device *parent; |
207 | |
208 | /* |
209 | * If there are no AFU configuration records we won't have anything to |
210 | * expose under the vPHB, so skip creating one, returning success since |
211 | * this is still a valid case. This will also opt us out of EEH |
212 | * handling since we won't have anything special to do if there are no |
213 | * kernel drivers attached to the vPHB, and EEH handling is not yet |
214 | * supported in the peer model. |
215 | */ |
216 | if (!afu->crs_num) |
217 | return 0; |
218 | |
219 | /* The parent device is the adapter. Reuse the device node of |
220 | * the adapter. |
221 | * We don't seem to care what device node is used for the vPHB, |
222 | * but tools such as lsvpd walk up the device parents looking |
223 | * for a valid location code, so we might as well show devices |
224 | * attached to the adapter as being located on that adapter. |
225 | */ |
226 | parent = afu->adapter->dev.parent; |
227 | vphb_dn = parent->of_node; |
228 | |
229 | /* Alloc and setup PHB data structure */ |
230 | phb = pcibios_alloc_controller(vphb_dn); |
231 | if (!phb) |
232 | return -ENODEV; |
233 | |
234 | /* Setup parent in sysfs */ |
235 | phb->parent = parent; |
236 | |
237 | /* Setup the PHB using arch provided callback */ |
238 | phb->ops = &cxl_pcie_pci_ops; |
239 | phb->cfg_addr = NULL; |
240 | phb->cfg_data = NULL; |
241 | phb->private_data = afu; |
242 | phb->controller_ops = cxl_pci_controller_ops; |
243 | |
244 | /* Scan the bus */ |
245 | pcibios_scan_phb(phb); |
246 | if (phb->bus == NULL) |
247 | return -ENXIO; |
248 | |
249 | /* Set release hook on root bus */ |
250 | pci_set_host_bridge_release(to_pci_host_bridge(phb->bus->bridge), |
251 | release_fn: pcibios_free_controller_deferred, |
252 | release_data: (void *) phb); |
253 | |
254 | /* Claim resources. This might need some rework as well depending |
255 | * whether we are doing probe-only or not, like assigning unassigned |
256 | * resources etc... |
257 | */ |
258 | pcibios_claim_one_bus(phb->bus); |
259 | |
260 | /* Add probed PCI devices to the device model */ |
261 | pci_bus_add_devices(bus: phb->bus); |
262 | |
263 | afu->phb = phb; |
264 | |
265 | return 0; |
266 | } |
267 | |
268 | void cxl_pci_vphb_remove(struct cxl_afu *afu) |
269 | { |
270 | struct pci_controller *phb; |
271 | |
272 | /* If there is no configuration record we won't have one of these */ |
273 | if (!afu || !afu->phb) |
274 | return; |
275 | |
276 | phb = afu->phb; |
277 | afu->phb = NULL; |
278 | |
279 | pci_remove_root_bus(bus: phb->bus); |
280 | /* |
281 | * We don't free phb here - that's handled by |
282 | * pcibios_free_controller_deferred() |
283 | */ |
284 | } |
285 | |
286 | bool cxl_pci_is_vphb_device(struct pci_dev *dev) |
287 | { |
288 | struct pci_controller *phb; |
289 | |
290 | phb = pci_bus_to_host(dev->bus); |
291 | |
292 | return (phb->ops == &cxl_pcie_pci_ops); |
293 | } |
294 | |
295 | struct cxl_afu *cxl_pci_to_afu(struct pci_dev *dev) |
296 | { |
297 | struct pci_controller *phb; |
298 | |
299 | phb = pci_bus_to_host(dev->bus); |
300 | |
301 | return (struct cxl_afu *)phb->private_data; |
302 | } |
303 | EXPORT_SYMBOL_GPL(cxl_pci_to_afu); |
304 | |
305 | unsigned int cxl_pci_to_cfg_record(struct pci_dev *dev) |
306 | { |
307 | return cxl_pcie_cfg_record(bus: dev->bus->number, devfn: dev->devfn); |
308 | } |
309 | EXPORT_SYMBOL_GPL(cxl_pci_to_cfg_record); |
310 | |