Commit | Line | Data |
---|---|---|
0f06157e DW |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* Copyright(c) 2020 Intel Corporation. */ | |
0f06157e DW |
3 | #include <linux/io-64-nonatomic-lo-hi.h> |
4 | #include <linux/device.h> | |
5 | #include <linux/slab.h> | |
6 | #include <linux/pci.h> | |
7 | #include <cxlmem.h> | |
af9cae9f | 8 | #include <cxlpci.h> |
1ad3f701 | 9 | #include <pmu.h> |
0f06157e | 10 | |
fa89248e RR |
11 | #include "core.h" |
12 | ||
2b922a9d DW |
13 | /** |
14 | * DOC: cxl registers | |
15 | * | |
16 | * CXL device capabilities are enumerated by PCI DVSEC (Designated | |
17 | * Vendor-specific) and / or descriptors provided by platform firmware. | |
18 | * They can be defined as a set like the device and component registers | |
19 | * mandated by CXL Section 8.1.12.2 Memory Device PCIe Capabilities and | |
20 | * Extended Capabilities, or they can be individual capabilities | |
21 | * appended to bridged and endpoint devices. | |
22 | * | |
23 | * Provide common infrastructure for enumerating and mapping these | |
24 | * discrete capabilities. | |
25 | */ | |
26 | ||
0f06157e DW |
27 | /** |
28 | * cxl_probe_component_regs() - Detect CXL Component register blocks | |
29 | * @dev: Host device of the @base mapping | |
30 | * @base: Mapping containing the HDM Decoder Capability Header | |
31 | * @map: Map object describing the register block information found | |
32 | * | |
33 | * See CXL 2.0 8.2.4 Component Register Layout and Definition | |
34 | * See CXL 2.0 8.2.5.5 CXL Device Register Interface | |
35 | * | |
36 | * Probe for component register information and return it in map object. | |
37 | */ | |
38 | void cxl_probe_component_regs(struct device *dev, void __iomem *base, | |
39 | struct cxl_component_reg_map *map) | |
40 | { | |
41 | int cap, cap_count; | |
74b0fe80 | 42 | u32 cap_array; |
0f06157e DW |
43 | |
44 | *map = (struct cxl_component_reg_map) { 0 }; | |
45 | ||
46 | /* | |
47 | * CXL.cache and CXL.mem registers are at offset 0x1000 as defined in | |
48 | * CXL 2.0 8.2.4 Table 141. | |
49 | */ | |
50 | base += CXL_CM_OFFSET; | |
51 | ||
74b0fe80 | 52 | cap_array = readl(base + CXL_CM_CAP_HDR_OFFSET); |
0f06157e DW |
53 | |
54 | if (FIELD_GET(CXL_CM_CAP_HDR_ID_MASK, cap_array) != CM_CAP_HDR_CAP_ID) { | |
55 | dev_err(dev, | |
d621bc2e | 56 | "Couldn't locate the CXL.cache and CXL.mem capability array header.\n"); |
0f06157e DW |
57 | return; |
58 | } | |
59 | ||
60 | /* It's assumed that future versions will be backward compatible */ | |
61 | cap_count = FIELD_GET(CXL_CM_CAP_HDR_ARRAY_SIZE_MASK, cap_array); | |
62 | ||
63 | for (cap = 1; cap <= cap_count; cap++) { | |
64 | void __iomem *register_block; | |
af2dfef8 | 65 | struct cxl_reg_map *rmap; |
0f06157e | 66 | u16 cap_id, offset; |
af2dfef8 | 67 | u32 length, hdr; |
0f06157e DW |
68 | |
69 | hdr = readl(base + cap * 0x4); | |
70 | ||
71 | cap_id = FIELD_GET(CXL_CM_CAP_HDR_ID_MASK, hdr); | |
72 | offset = FIELD_GET(CXL_CM_CAP_PTR_MASK, hdr); | |
73 | register_block = base + offset; | |
af2dfef8 | 74 | hdr = readl(register_block); |
0f06157e | 75 | |
af2dfef8 | 76 | rmap = NULL; |
0f06157e | 77 | switch (cap_id) { |
af2dfef8 DW |
78 | case CXL_CM_CAP_CAP_ID_HDM: { |
79 | int decoder_cnt; | |
80 | ||
0f06157e DW |
81 | dev_dbg(dev, "found HDM decoder capability (0x%x)\n", |
82 | offset); | |
83 | ||
0f06157e DW |
84 | decoder_cnt = cxl_hdm_decoder_count(hdr); |
85 | length = 0x20 * decoder_cnt + 0x10; | |
af2dfef8 | 86 | rmap = &map->hdm_decoder; |
0f06157e | 87 | break; |
af2dfef8 | 88 | } |
bd09626b DW |
89 | case CXL_CM_CAP_CAP_ID_RAS: |
90 | dev_dbg(dev, "found RAS capability (0x%x)\n", | |
91 | offset); | |
92 | length = CXL_RAS_CAPABILITY_LENGTH; | |
93 | rmap = &map->ras; | |
0f06157e DW |
94 | break; |
95 | default: | |
96 | dev_dbg(dev, "Unknown CM cap ID: %d (0x%x)\n", cap_id, | |
97 | offset); | |
98 | break; | |
99 | } | |
af2dfef8 DW |
100 | |
101 | if (!rmap) | |
102 | continue; | |
103 | rmap->valid = true; | |
a1554e9c | 104 | rmap->id = cap_id; |
af2dfef8 DW |
105 | rmap->offset = CXL_CM_OFFSET + offset; |
106 | rmap->size = length; | |
0f06157e DW |
107 | } |
108 | } | |
affec782 | 109 | EXPORT_SYMBOL_NS_GPL(cxl_probe_component_regs, CXL); |
0f06157e DW |
110 | |
111 | /** | |
112 | * cxl_probe_device_regs() - Detect CXL Device register blocks | |
113 | * @dev: Host device of the @base mapping | |
114 | * @base: Mapping of CXL 2.0 8.2.8 CXL Device Register Interface | |
115 | * @map: Map object describing the register block information found | |
116 | * | |
117 | * Probe for device register information and return it in map object. | |
118 | */ | |
119 | void cxl_probe_device_regs(struct device *dev, void __iomem *base, | |
120 | struct cxl_device_reg_map *map) | |
121 | { | |
122 | int cap, cap_count; | |
123 | u64 cap_array; | |
124 | ||
125 | *map = (struct cxl_device_reg_map){ 0 }; | |
126 | ||
127 | cap_array = readq(base + CXLDEV_CAP_ARRAY_OFFSET); | |
128 | if (FIELD_GET(CXLDEV_CAP_ARRAY_ID_MASK, cap_array) != | |
129 | CXLDEV_CAP_ARRAY_CAP_ID) | |
130 | return; | |
131 | ||
132 | cap_count = FIELD_GET(CXLDEV_CAP_ARRAY_COUNT_MASK, cap_array); | |
133 | ||
134 | for (cap = 1; cap <= cap_count; cap++) { | |
af2dfef8 | 135 | struct cxl_reg_map *rmap; |
0f06157e DW |
136 | u32 offset, length; |
137 | u16 cap_id; | |
138 | ||
139 | cap_id = FIELD_GET(CXLDEV_CAP_HDR_CAP_ID_MASK, | |
140 | readl(base + cap * 0x10)); | |
141 | offset = readl(base + cap * 0x10 + 0x4); | |
142 | length = readl(base + cap * 0x10 + 0x8); | |
143 | ||
af2dfef8 | 144 | rmap = NULL; |
0f06157e DW |
145 | switch (cap_id) { |
146 | case CXLDEV_CAP_CAP_ID_DEVICE_STATUS: | |
147 | dev_dbg(dev, "found Status capability (0x%x)\n", offset); | |
af2dfef8 | 148 | rmap = &map->status; |
0f06157e DW |
149 | break; |
150 | case CXLDEV_CAP_CAP_ID_PRIMARY_MAILBOX: | |
151 | dev_dbg(dev, "found Mailbox capability (0x%x)\n", offset); | |
af2dfef8 | 152 | rmap = &map->mbox; |
0f06157e DW |
153 | break; |
154 | case CXLDEV_CAP_CAP_ID_SECONDARY_MAILBOX: | |
155 | dev_dbg(dev, "found Secondary Mailbox capability (0x%x)\n", offset); | |
156 | break; | |
157 | case CXLDEV_CAP_CAP_ID_MEMDEV: | |
158 | dev_dbg(dev, "found Memory Device capability (0x%x)\n", offset); | |
af2dfef8 | 159 | rmap = &map->memdev; |
0f06157e DW |
160 | break; |
161 | default: | |
162 | if (cap_id >= 0x8000) | |
163 | dev_dbg(dev, "Vendor cap ID: %#x offset: %#x\n", cap_id, offset); | |
164 | else | |
165 | dev_dbg(dev, "Unknown cap ID: %#x offset: %#x\n", cap_id, offset); | |
166 | break; | |
167 | } | |
af2dfef8 DW |
168 | |
169 | if (!rmap) | |
170 | continue; | |
171 | rmap->valid = true; | |
a1554e9c | 172 | rmap->id = cap_id; |
af2dfef8 DW |
173 | rmap->offset = offset; |
174 | rmap->size = length; | |
0f06157e DW |
175 | } |
176 | } | |
affec782 | 177 | EXPORT_SYMBOL_NS_GPL(cxl_probe_device_regs, CXL); |
0f06157e | 178 | |
d17d0540 DW |
179 | void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr, |
180 | resource_size_t length) | |
0f06157e DW |
181 | { |
182 | void __iomem *ret_val; | |
183 | struct resource *res; | |
184 | ||
3bb80da5 RR |
185 | if (WARN_ON_ONCE(addr == CXL_RESOURCE_NONE)) |
186 | return NULL; | |
187 | ||
0f06157e DW |
188 | res = devm_request_mem_region(dev, addr, length, dev_name(dev)); |
189 | if (!res) { | |
190 | resource_size_t end = addr + length - 1; | |
191 | ||
192 | dev_err(dev, "Failed to request region %pa-%pa\n", &addr, &end); | |
193 | return NULL; | |
194 | } | |
195 | ||
196 | ret_val = devm_ioremap(dev, addr, length); | |
197 | if (!ret_val) | |
198 | dev_err(dev, "Failed to map region %pr\n", res); | |
199 | ||
200 | return ret_val; | |
201 | } | |
202 | ||
0c0df631 | 203 | int cxl_map_component_regs(const struct cxl_register_map *map, |
57340804 RR |
204 | struct cxl_component_regs *regs, |
205 | unsigned long map_mask) | |
0f06157e | 206 | { |
dd22581f | 207 | struct device *host = map->host; |
a1554e9c | 208 | struct mapinfo { |
688baac1 | 209 | const struct cxl_reg_map *rmap; |
a1554e9c DW |
210 | void __iomem **addr; |
211 | } mapinfo[] = { | |
212 | { &map->component_map.hdm_decoder, ®s->hdm_decoder }, | |
bd09626b | 213 | { &map->component_map.ras, ®s->ras }, |
a1554e9c DW |
214 | }; |
215 | int i; | |
216 | ||
217 | for (i = 0; i < ARRAY_SIZE(mapinfo); i++) { | |
218 | struct mapinfo *mi = &mapinfo[i]; | |
d3970f00 | 219 | resource_size_t addr; |
a1554e9c | 220 | resource_size_t length; |
0f06157e | 221 | |
a1554e9c DW |
222 | if (!mi->rmap->valid) |
223 | continue; | |
224 | if (!test_bit(mi->rmap->id, &map_mask)) | |
225 | continue; | |
d3970f00 | 226 | addr = map->resource + mi->rmap->offset; |
a1554e9c | 227 | length = mi->rmap->size; |
d3970f00 | 228 | *(mi->addr) = devm_cxl_iomap_block(host, addr, length); |
a1554e9c DW |
229 | if (!*(mi->addr)) |
230 | return -ENOMEM; | |
231 | } | |
0f06157e DW |
232 | |
233 | return 0; | |
234 | } | |
affec782 | 235 | EXPORT_SYMBOL_NS_GPL(cxl_map_component_regs, CXL); |
0f06157e | 236 | |
0c0df631 | 237 | int cxl_map_device_regs(const struct cxl_register_map *map, |
57340804 | 238 | struct cxl_device_regs *regs) |
0f06157e | 239 | { |
dd22581f | 240 | struct device *host = map->host; |
6c7f4f1e | 241 | resource_size_t phys_addr = map->resource; |
1191ca10 | 242 | struct mapinfo { |
688baac1 | 243 | const struct cxl_reg_map *rmap; |
1191ca10 DW |
244 | void __iomem **addr; |
245 | } mapinfo[] = { | |
246 | { &map->device_map.status, ®s->status, }, | |
247 | { &map->device_map.mbox, ®s->mbox, }, | |
248 | { &map->device_map.memdev, ®s->memdev, }, | |
249 | }; | |
250 | int i; | |
251 | ||
252 | for (i = 0; i < ARRAY_SIZE(mapinfo); i++) { | |
253 | struct mapinfo *mi = &mapinfo[i]; | |
0f06157e | 254 | resource_size_t length; |
0f06157e | 255 | resource_size_t addr; |
0f06157e | 256 | |
1191ca10 DW |
257 | if (!mi->rmap->valid) |
258 | continue; | |
0f06157e | 259 | |
1191ca10 DW |
260 | addr = phys_addr + mi->rmap->offset; |
261 | length = mi->rmap->size; | |
dd22581f | 262 | *(mi->addr) = devm_cxl_iomap_block(host, addr, length); |
1191ca10 | 263 | if (!*(mi->addr)) |
0f06157e DW |
264 | return -ENOMEM; |
265 | } | |
266 | ||
267 | return 0; | |
268 | } | |
affec782 | 269 | EXPORT_SYMBOL_NS_GPL(cxl_map_device_regs, CXL); |
303ebc1b | 270 | |
6c7f4f1e | 271 | static bool cxl_decode_regblock(struct pci_dev *pdev, u32 reg_lo, u32 reg_hi, |
303ebc1b BW |
272 | struct cxl_register_map *map) |
273 | { | |
5c88a9cc | 274 | u8 reg_type = FIELD_GET(CXL_DVSEC_REG_LOCATOR_BLOCK_ID_MASK, reg_lo); |
6c7f4f1e DW |
275 | int bar = FIELD_GET(CXL_DVSEC_REG_LOCATOR_BIR_MASK, reg_lo); |
276 | u64 offset = ((u64)reg_hi << 32) | | |
277 | (reg_lo & CXL_DVSEC_REG_LOCATOR_BLOCK_OFF_LOW_MASK); | |
278 | ||
279 | if (offset > pci_resource_len(pdev, bar)) { | |
280 | dev_warn(&pdev->dev, | |
281 | "BAR%d: %pr: too small (offset: %pa, type: %d)\n", bar, | |
5c88a9cc | 282 | &pdev->resource[bar], &offset, reg_type); |
6c7f4f1e DW |
283 | return false; |
284 | } | |
285 | ||
5c88a9cc | 286 | map->reg_type = reg_type; |
6c7f4f1e DW |
287 | map->resource = pci_resource_start(pdev, bar) + offset; |
288 | map->max_size = pci_resource_len(pdev, bar) - offset; | |
289 | return true; | |
303ebc1b BW |
290 | } |
291 | ||
292 | /** | |
d717d7f3 | 293 | * cxl_find_regblock_instance() - Locate a register block by type / index |
303ebc1b BW |
294 | * @pdev: The CXL PCI device to enumerate. |
295 | * @type: Register Block Indicator id | |
296 | * @map: Enumeration output, clobbered on error | |
d717d7f3 JC |
297 | * @index: Index into which particular instance of a regblock wanted in the |
298 | * order found in register locator DVSEC. | |
303ebc1b BW |
299 | * |
300 | * Return: 0 if register block enumerated, negative error code otherwise | |
301 | * | |
302 | * A CXL DVSEC may point to one or more register blocks, search for them | |
d717d7f3 | 303 | * by @type and @index. |
303ebc1b | 304 | */ |
d717d7f3 JC |
305 | int cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_type type, |
306 | struct cxl_register_map *map, int index) | |
303ebc1b BW |
307 | { |
308 | u32 regloc_size, regblocks; | |
d717d7f3 | 309 | int instance = 0; |
303ebc1b BW |
310 | int regloc, i; |
311 | ||
57340804 | 312 | *map = (struct cxl_register_map) { |
dd22581f | 313 | .host = &pdev->dev, |
57340804 RR |
314 | .resource = CXL_RESOURCE_NONE, |
315 | }; | |
316 | ||
962f1e79 | 317 | regloc = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL, |
303ebc1b BW |
318 | CXL_DVSEC_REG_LOCATOR); |
319 | if (!regloc) | |
320 | return -ENXIO; | |
321 | ||
322 | pci_read_config_dword(pdev, regloc + PCI_DVSEC_HEADER1, ®loc_size); | |
323 | regloc_size = FIELD_GET(PCI_DVSEC_HEADER1_LENGTH_MASK, regloc_size); | |
324 | ||
325 | regloc += CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET; | |
326 | regblocks = (regloc_size - CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET) / 8; | |
327 | ||
328 | for (i = 0; i < regblocks; i++, regloc += 8) { | |
329 | u32 reg_lo, reg_hi; | |
330 | ||
331 | pci_read_config_dword(pdev, regloc, ®_lo); | |
332 | pci_read_config_dword(pdev, regloc + 4, ®_hi); | |
333 | ||
6c7f4f1e DW |
334 | if (!cxl_decode_regblock(pdev, reg_lo, reg_hi, map)) |
335 | continue; | |
303ebc1b | 336 | |
d717d7f3 JC |
337 | if (map->reg_type == type) { |
338 | if (index == instance) | |
339 | return 0; | |
340 | instance++; | |
341 | } | |
303ebc1b BW |
342 | } |
343 | ||
6c7f4f1e | 344 | map->resource = CXL_RESOURCE_NONE; |
303ebc1b BW |
345 | return -ENODEV; |
346 | } | |
d717d7f3 JC |
347 | EXPORT_SYMBOL_NS_GPL(cxl_find_regblock_instance, CXL); |
348 | ||
349 | /** | |
350 | * cxl_find_regblock() - Locate register blocks by type | |
351 | * @pdev: The CXL PCI device to enumerate. | |
352 | * @type: Register Block Indicator id | |
353 | * @map: Enumeration output, clobbered on error | |
354 | * | |
355 | * Return: 0 if register block enumerated, negative error code otherwise | |
356 | * | |
357 | * A CXL DVSEC may point to one or more register blocks, search for them | |
358 | * by @type. | |
359 | */ | |
360 | int cxl_find_regblock(struct pci_dev *pdev, enum cxl_regloc_type type, | |
361 | struct cxl_register_map *map) | |
362 | { | |
363 | return cxl_find_regblock_instance(pdev, type, map, 0); | |
364 | } | |
303ebc1b | 365 | EXPORT_SYMBOL_NS_GPL(cxl_find_regblock, CXL); |
d5b1a271 | 366 | |
d717d7f3 JC |
367 | /** |
368 | * cxl_count_regblock() - Count instances of a given regblock type. | |
369 | * @pdev: The CXL PCI device to enumerate. | |
370 | * @type: Register Block Indicator id | |
371 | * | |
372 | * Some regblocks may be repeated. Count how many instances. | |
373 | * | |
374 | * Return: count of matching regblocks. | |
375 | */ | |
376 | int cxl_count_regblock(struct pci_dev *pdev, enum cxl_regloc_type type) | |
377 | { | |
378 | struct cxl_register_map map; | |
379 | int rc, count = 0; | |
380 | ||
381 | while (1) { | |
382 | rc = cxl_find_regblock_instance(pdev, type, &map, count); | |
383 | if (rc) | |
384 | return count; | |
385 | count++; | |
386 | } | |
387 | } | |
388 | EXPORT_SYMBOL_NS_GPL(cxl_count_regblock, CXL); | |
389 | ||
e8db0701 | 390 | int cxl_map_pmu_regs(struct cxl_register_map *map, struct cxl_pmu_regs *regs) |
1ad3f701 | 391 | { |
e8db0701 | 392 | struct device *dev = map->host; |
1ad3f701 JC |
393 | resource_size_t phys_addr; |
394 | ||
395 | phys_addr = map->resource; | |
396 | regs->pmu = devm_cxl_iomap_block(dev, phys_addr, CXL_PMU_REGMAP_SIZE); | |
397 | if (!regs->pmu) | |
398 | return -ENOMEM; | |
399 | ||
400 | return 0; | |
401 | } | |
402 | EXPORT_SYMBOL_NS_GPL(cxl_map_pmu_regs, CXL); | |
403 | ||
d076bb8c TB |
404 | static int cxl_map_regblock(struct cxl_register_map *map) |
405 | { | |
dd22581f | 406 | struct device *host = map->host; |
d076bb8c TB |
407 | |
408 | map->base = ioremap(map->resource, map->max_size); | |
409 | if (!map->base) { | |
dd22581f | 410 | dev_err(host, "failed to map registers\n"); |
d076bb8c TB |
411 | return -ENOMEM; |
412 | } | |
413 | ||
dd22581f | 414 | dev_dbg(host, "Mapped CXL Memory Device resource %pa\n", &map->resource); |
d076bb8c TB |
415 | return 0; |
416 | } | |
417 | ||
418 | static void cxl_unmap_regblock(struct cxl_register_map *map) | |
419 | { | |
420 | iounmap(map->base); | |
421 | map->base = NULL; | |
422 | } | |
423 | ||
424 | static int cxl_probe_regs(struct cxl_register_map *map) | |
425 | { | |
426 | struct cxl_component_reg_map *comp_map; | |
427 | struct cxl_device_reg_map *dev_map; | |
dd22581f | 428 | struct device *host = map->host; |
d076bb8c TB |
429 | void __iomem *base = map->base; |
430 | ||
431 | switch (map->reg_type) { | |
432 | case CXL_REGLOC_RBI_COMPONENT: | |
433 | comp_map = &map->component_map; | |
dd22581f RR |
434 | cxl_probe_component_regs(host, base, comp_map); |
435 | dev_dbg(host, "Set up component registers\n"); | |
d076bb8c TB |
436 | break; |
437 | case CXL_REGLOC_RBI_MEMDEV: | |
438 | dev_map = &map->device_map; | |
dd22581f | 439 | cxl_probe_device_regs(host, base, dev_map); |
d076bb8c TB |
440 | if (!dev_map->status.valid || !dev_map->mbox.valid || |
441 | !dev_map->memdev.valid) { | |
dd22581f | 442 | dev_err(host, "registers not found: %s%s%s\n", |
d076bb8c TB |
443 | !dev_map->status.valid ? "status " : "", |
444 | !dev_map->mbox.valid ? "mbox " : "", | |
445 | !dev_map->memdev.valid ? "memdev " : ""); | |
446 | return -ENXIO; | |
447 | } | |
448 | ||
dd22581f | 449 | dev_dbg(host, "Probing device registers...\n"); |
d076bb8c TB |
450 | break; |
451 | default: | |
452 | break; | |
453 | } | |
454 | ||
455 | return 0; | |
456 | } | |
457 | ||
458 | int cxl_setup_regs(struct cxl_register_map *map) | |
459 | { | |
460 | int rc; | |
461 | ||
462 | rc = cxl_map_regblock(map); | |
463 | if (rc) | |
464 | return rc; | |
465 | ||
466 | rc = cxl_probe_regs(map); | |
467 | cxl_unmap_regblock(map); | |
468 | ||
469 | return rc; | |
470 | } | |
471 | EXPORT_SYMBOL_NS_GPL(cxl_setup_regs, CXL); | |
472 | ||
f05fd10d RR |
473 | u16 cxl_rcrb_to_aer(struct device *dev, resource_size_t rcrb) |
474 | { | |
475 | void __iomem *addr; | |
476 | u16 offset = 0; | |
477 | u32 cap_hdr; | |
478 | ||
479 | if (WARN_ON_ONCE(rcrb == CXL_RESOURCE_NONE)) | |
480 | return 0; | |
481 | ||
482 | if (!request_mem_region(rcrb, SZ_4K, dev_name(dev))) | |
483 | return 0; | |
484 | ||
485 | addr = ioremap(rcrb, SZ_4K); | |
486 | if (!addr) | |
487 | goto out; | |
488 | ||
489 | cap_hdr = readl(addr + offset); | |
490 | while (PCI_EXT_CAP_ID(cap_hdr) != PCI_EXT_CAP_ID_ERR) { | |
491 | offset = PCI_EXT_CAP_NEXT(cap_hdr); | |
492 | ||
493 | /* Offset 0 terminates capability list. */ | |
494 | if (!offset) | |
495 | break; | |
496 | cap_hdr = readl(addr + offset); | |
497 | } | |
498 | ||
499 | if (offset) | |
500 | dev_dbg(dev, "found AER extended capability (0x%x)\n", offset); | |
501 | ||
502 | iounmap(addr); | |
503 | out: | |
504 | release_mem_region(rcrb, SZ_4K); | |
505 | ||
506 | return offset; | |
507 | } | |
508 | ||
06193378 | 509 | resource_size_t __rcrb_to_component(struct device *dev, struct cxl_rcrb_info *ri, |
eb4663b0 | 510 | enum cxl_rcrb which) |
d5b1a271 RR |
511 | { |
512 | resource_size_t component_reg_phys; | |
06193378 | 513 | resource_size_t rcrb = ri->base; |
397cd265 | 514 | void __iomem *addr; |
d5b1a271 | 515 | u32 bar0, bar1; |
d5b1a271 RR |
516 | u16 cmd; |
517 | u32 id; | |
518 | ||
519 | if (which == CXL_RCRB_UPSTREAM) | |
520 | rcrb += SZ_4K; | |
521 | ||
522 | /* | |
523 | * RCRB's BAR[0..1] point to component block containing CXL | |
524 | * subsystem component registers. MEMBAR extraction follows | |
525 | * the PCI Base spec here, esp. 64 bit extraction and memory | |
526 | * ranges alignment (6.0, 7.5.1.2.1). | |
527 | */ | |
528 | if (!request_mem_region(rcrb, SZ_4K, "CXL RCRB")) | |
529 | return CXL_RESOURCE_NONE; | |
530 | addr = ioremap(rcrb, SZ_4K); | |
531 | if (!addr) { | |
532 | dev_err(dev, "Failed to map region %pr\n", addr); | |
533 | release_mem_region(rcrb, SZ_4K); | |
534 | return CXL_RESOURCE_NONE; | |
535 | } | |
536 | ||
537 | id = readl(addr + PCI_VENDOR_ID); | |
538 | cmd = readw(addr + PCI_COMMAND); | |
539 | bar0 = readl(addr + PCI_BASE_ADDRESS_0); | |
540 | bar1 = readl(addr + PCI_BASE_ADDRESS_1); | |
541 | iounmap(addr); | |
542 | release_mem_region(rcrb, SZ_4K); | |
543 | ||
544 | /* | |
545 | * Sanity check, see CXL 3.0 Figure 9-8 CXL Device that Does Not | |
546 | * Remap Upstream Port and Component Registers | |
547 | */ | |
548 | if (id == U32_MAX) { | |
549 | if (which == CXL_RCRB_DOWNSTREAM) | |
550 | dev_err(dev, "Failed to access Downstream Port RCRB\n"); | |
551 | return CXL_RESOURCE_NONE; | |
552 | } | |
553 | if (!(cmd & PCI_COMMAND_MEMORY)) | |
554 | return CXL_RESOURCE_NONE; | |
555 | /* The RCRB is a Memory Window, and the MEM_TYPE_1M bit is obsolete */ | |
556 | if (bar0 & (PCI_BASE_ADDRESS_MEM_TYPE_1M | PCI_BASE_ADDRESS_SPACE_IO)) | |
557 | return CXL_RESOURCE_NONE; | |
558 | ||
559 | component_reg_phys = bar0 & PCI_BASE_ADDRESS_MEM_MASK; | |
560 | if (bar0 & PCI_BASE_ADDRESS_MEM_TYPE_64) | |
561 | component_reg_phys |= ((u64)bar1) << 32; | |
562 | ||
563 | if (!component_reg_phys) | |
564 | return CXL_RESOURCE_NONE; | |
565 | ||
566 | /* MEMBAR is block size (64k) aligned. */ | |
567 | if (!IS_ALIGNED(component_reg_phys, CXL_COMPONENT_REG_BLOCK_SIZE)) | |
568 | return CXL_RESOURCE_NONE; | |
569 | ||
570 | return component_reg_phys; | |
571 | } | |
eb4663b0 RR |
572 | |
573 | resource_size_t cxl_rcd_component_reg_phys(struct device *dev, | |
574 | struct cxl_dport *dport) | |
575 | { | |
576 | if (!dport->rch) | |
577 | return CXL_RESOURCE_NONE; | |
06193378 | 578 | return __rcrb_to_component(dev, &dport->rcrb, CXL_RCRB_UPSTREAM); |
eb4663b0 RR |
579 | } |
580 | EXPORT_SYMBOL_NS_GPL(cxl_rcd_component_reg_phys, CXL); |