1 /*
2 * CXL host parameter parsing routines
3 *
4 * Copyright (c) 2022 Huawei
5 * Modeled loosely on the NUMA options handling in hw/core/numa.c
6 */
7
8 #include "qemu/osdep.h"
9 #include "qemu/units.h"
10 #include "qemu/bitmap.h"
11 #include "qemu/error-report.h"
12 #include "qapi/error.h"
13 #include "system/qtest.h"
14 #include "hw/boards.h"
15
16 #include "qapi/qapi-visit-machine.h"
17 #include "hw/cxl/cxl.h"
18 #include "hw/cxl/cxl_host.h"
19 #include "hw/pci/pci_bus.h"
20 #include "hw/pci/pci_bridge.h"
21 #include "hw/pci/pci_host.h"
22 #include "hw/pci/pcie_port.h"
23 #include "hw/pci-bridge/pci_expander_bridge.h"
24
cxl_fixed_memory_window_config(CXLFixedMemoryWindowOptions * object,int index,Error ** errp)25 static void cxl_fixed_memory_window_config(CXLFixedMemoryWindowOptions *object,
26 int index, Error **errp)
27 {
28 ERRP_GUARD();
29 DeviceState *dev = qdev_new(TYPE_CXL_FMW);
30 CXLFixedWindow *fw = CXL_FMW(dev);
31 strList *target;
32 int i;
33
34 fw->index = index;
35
36 for (target = object->targets; target; target = target->next) {
37 fw->num_targets++;
38 }
39
40 fw->enc_int_ways = cxl_interleave_ways_enc(fw->num_targets, errp);
41 if (*errp) {
42 return;
43 }
44
45 if (object->size % (256 * MiB)) {
46 error_setg(errp,
47 "Size of a CXL fixed memory window must be a multiple of 256MiB");
48 return;
49 }
50 fw->size = object->size;
51
52 if (object->has_interleave_granularity) {
53 fw->enc_int_gran =
54 cxl_interleave_granularity_enc(object->interleave_granularity,
55 errp);
56 if (*errp) {
57 return;
58 }
59 } else {
60 /* Default to 256 byte interleave */
61 fw->enc_int_gran = 0;
62 }
63
64 fw->targets = g_malloc0_n(fw->num_targets, sizeof(*fw->targets));
65 for (i = 0, target = object->targets; target; i++, target = target->next) {
66 /* This link cannot be resolved yet, so stash the name for now */
67 fw->targets[i] = g_strdup(target->value);
68 }
69
70 sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp);
71 }
72
cxl_fmws_link(Object * obj,void * opaque)73 static int cxl_fmws_link(Object *obj, void *opaque)
74 {
75 struct CXLFixedWindow *fw;
76 int i;
77
78 if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
79 return 0;
80 }
81 fw = CXL_FMW(obj);
82
83 for (i = 0; i < fw->num_targets; i++) {
84 Object *o;
85 bool ambig;
86
87 o = object_resolve_path_type(fw->targets[i], TYPE_PXB_CXL_DEV,
88 &ambig);
89 if (!o) {
90 error_setg(&error_fatal, "Could not resolve CXLFM target %s",
91 fw->targets[i]);
92 return 1;
93 }
94 fw->target_hbs[i] = PXB_CXL_DEV(o);
95 }
96 return 0;
97 }
98
cxl_fmws_link_targets(Error ** errp)99 void cxl_fmws_link_targets(Error **errp)
100 {
101 /* Order doesn't matter for this, so no need to build list */
102 object_child_foreach_recursive(object_get_root(), cxl_fmws_link, NULL);
103 }
104
cxl_hdm_find_target(uint32_t * cache_mem,hwaddr addr,uint8_t * target)105 static bool cxl_hdm_find_target(uint32_t *cache_mem, hwaddr addr,
106 uint8_t *target)
107 {
108 int hdm_inc = R_CXL_HDM_DECODER1_BASE_LO - R_CXL_HDM_DECODER0_BASE_LO;
109 unsigned int hdm_count;
110 bool found = false;
111 int i;
112 uint32_t cap;
113
114 cap = ldl_le_p(cache_mem + R_CXL_HDM_DECODER_CAPABILITY);
115 hdm_count = cxl_decoder_count_dec(FIELD_EX32(cap,
116 CXL_HDM_DECODER_CAPABILITY,
117 DECODER_COUNT));
118 for (i = 0; i < hdm_count; i++) {
119 uint32_t ctrl, ig_enc, iw_enc, target_idx;
120 uint32_t low, high;
121 uint64_t base, size;
122
123 low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_LO + i * hdm_inc);
124 high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_HI + i * hdm_inc);
125 base = (low & 0xf0000000) | ((uint64_t)high << 32);
126 low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_LO + i * hdm_inc);
127 high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_HI + i * hdm_inc);
128 size = (low & 0xf0000000) | ((uint64_t)high << 32);
129 if (addr < base || addr >= base + size) {
130 continue;
131 }
132
133 ctrl = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + i * hdm_inc);
134 if (!FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED)) {
135 return false;
136 }
137 found = true;
138 ig_enc = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IG);
139 iw_enc = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IW);
140 target_idx = (addr / cxl_decode_ig(ig_enc)) % (1 << iw_enc);
141
142 if (target_idx < 4) {
143 uint32_t val = ldl_le_p(cache_mem +
144 R_CXL_HDM_DECODER0_TARGET_LIST_LO +
145 i * hdm_inc);
146 *target = extract32(val, target_idx * 8, 8);
147 } else {
148 uint32_t val = ldl_le_p(cache_mem +
149 R_CXL_HDM_DECODER0_TARGET_LIST_HI +
150 i * hdm_inc);
151 *target = extract32(val, (target_idx - 4) * 8, 8);
152 }
153 break;
154 }
155
156 return found;
157 }
158
cxl_cfmws_find_device(CXLFixedWindow * fw,hwaddr addr)159 static PCIDevice *cxl_cfmws_find_device(CXLFixedWindow *fw, hwaddr addr)
160 {
161 CXLComponentState *hb_cstate, *usp_cstate;
162 PCIHostState *hb;
163 CXLUpstreamPort *usp;
164 int rb_index;
165 uint32_t *cache_mem;
166 uint8_t target;
167 bool target_found;
168 PCIDevice *rp, *d;
169
170 /* Address is relative to memory region. Convert to HPA */
171 addr += fw->base;
172
173 rb_index = (addr / cxl_decode_ig(fw->enc_int_gran)) % fw->num_targets;
174 hb = PCI_HOST_BRIDGE(fw->target_hbs[rb_index]->cxl_host_bridge);
175 if (!hb || !hb->bus || !pci_bus_is_cxl(hb->bus)) {
176 return NULL;
177 }
178
179 if (cxl_get_hb_passthrough(hb)) {
180 rp = pcie_find_port_first(hb->bus);
181 if (!rp) {
182 return NULL;
183 }
184 } else {
185 hb_cstate = cxl_get_hb_cstate(hb);
186 if (!hb_cstate) {
187 return NULL;
188 }
189
190 cache_mem = hb_cstate->crb.cache_mem_registers;
191
192 target_found = cxl_hdm_find_target(cache_mem, addr, &target);
193 if (!target_found) {
194 return NULL;
195 }
196
197 rp = pcie_find_port_by_pn(hb->bus, target);
198 if (!rp) {
199 return NULL;
200 }
201 }
202
203 d = pci_bridge_get_sec_bus(PCI_BRIDGE(rp))->devices[0];
204 if (!d) {
205 return NULL;
206 }
207
208 if (object_dynamic_cast(OBJECT(d), TYPE_CXL_TYPE3)) {
209 return d;
210 }
211
212 /*
213 * Could also be a switch. Note only one level of switching currently
214 * supported.
215 */
216 if (!object_dynamic_cast(OBJECT(d), TYPE_CXL_USP)) {
217 return NULL;
218 }
219 usp = CXL_USP(d);
220
221 usp_cstate = cxl_usp_to_cstate(usp);
222 if (!usp_cstate) {
223 return NULL;
224 }
225
226 cache_mem = usp_cstate->crb.cache_mem_registers;
227
228 target_found = cxl_hdm_find_target(cache_mem, addr, &target);
229 if (!target_found) {
230 return NULL;
231 }
232
233 d = pcie_find_port_by_pn(&PCI_BRIDGE(d)->sec_bus, target);
234 if (!d) {
235 return NULL;
236 }
237
238 d = pci_bridge_get_sec_bus(PCI_BRIDGE(d))->devices[0];
239 if (!d) {
240 return NULL;
241 }
242
243 if (!object_dynamic_cast(OBJECT(d), TYPE_CXL_TYPE3)) {
244 return NULL;
245 }
246
247 return d;
248 }
249
cxl_read_cfmws(void * opaque,hwaddr addr,uint64_t * data,unsigned size,MemTxAttrs attrs)250 static MemTxResult cxl_read_cfmws(void *opaque, hwaddr addr, uint64_t *data,
251 unsigned size, MemTxAttrs attrs)
252 {
253 CXLFixedWindow *fw = opaque;
254 PCIDevice *d;
255
256 d = cxl_cfmws_find_device(fw, addr);
257 if (d == NULL) {
258 *data = 0;
259 /* Reads to invalid address return poison */
260 return MEMTX_ERROR;
261 }
262
263 return cxl_type3_read(d, addr + fw->base, data, size, attrs);
264 }
265
cxl_write_cfmws(void * opaque,hwaddr addr,uint64_t data,unsigned size,MemTxAttrs attrs)266 static MemTxResult cxl_write_cfmws(void *opaque, hwaddr addr,
267 uint64_t data, unsigned size,
268 MemTxAttrs attrs)
269 {
270 CXLFixedWindow *fw = opaque;
271 PCIDevice *d;
272
273 d = cxl_cfmws_find_device(fw, addr);
274 if (d == NULL) {
275 /* Writes to invalid address are silent */
276 return MEMTX_OK;
277 }
278
279 return cxl_type3_write(d, addr + fw->base, data, size, attrs);
280 }
281
282 const MemoryRegionOps cfmws_ops = {
283 .read_with_attrs = cxl_read_cfmws,
284 .write_with_attrs = cxl_write_cfmws,
285 .endianness = DEVICE_LITTLE_ENDIAN,
286 .valid = {
287 .min_access_size = 1,
288 .max_access_size = 8,
289 .unaligned = true,
290 },
291 .impl = {
292 .min_access_size = 1,
293 .max_access_size = 8,
294 .unaligned = true,
295 },
296 };
297
machine_get_cxl(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)298 static void machine_get_cxl(Object *obj, Visitor *v, const char *name,
299 void *opaque, Error **errp)
300 {
301 CXLState *cxl_state = opaque;
302 bool value = cxl_state->is_enabled;
303
304 visit_type_bool(v, name, &value, errp);
305 }
306
machine_set_cxl(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)307 static void machine_set_cxl(Object *obj, Visitor *v, const char *name,
308 void *opaque, Error **errp)
309 {
310 CXLState *cxl_state = opaque;
311 bool value;
312
313 if (!visit_type_bool(v, name, &value, errp)) {
314 return;
315 }
316 cxl_state->is_enabled = value;
317 }
318
machine_get_cfmw(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)319 static void machine_get_cfmw(Object *obj, Visitor *v, const char *name,
320 void *opaque, Error **errp)
321 {
322 CXLState *state = opaque;
323 CXLFixedMemoryWindowOptionsList **list = &state->cfmw_list;
324
325 visit_type_CXLFixedMemoryWindowOptionsList(v, name, list, errp);
326 }
327
machine_set_cfmw(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)328 static void machine_set_cfmw(Object *obj, Visitor *v, const char *name,
329 void *opaque, Error **errp)
330 {
331 CXLState *state = opaque;
332 CXLFixedMemoryWindowOptionsList *cfmw_list = NULL;
333 CXLFixedMemoryWindowOptionsList *it;
334 int index;
335
336 visit_type_CXLFixedMemoryWindowOptionsList(v, name, &cfmw_list, errp);
337 if (!cfmw_list) {
338 return;
339 }
340
341 for (it = cfmw_list, index = 0; it; it = it->next, index++) {
342 cxl_fixed_memory_window_config(it->value, index, errp);
343 }
344 state->cfmw_list = cfmw_list;
345 }
346
cxl_machine_init(Object * obj,CXLState * state)347 void cxl_machine_init(Object *obj, CXLState *state)
348 {
349 object_property_add(obj, "cxl", "bool", machine_get_cxl,
350 machine_set_cxl, NULL, state);
351 object_property_set_description(obj, "cxl",
352 "Set on/off to enable/disable "
353 "CXL instantiation");
354
355 object_property_add(obj, "cxl-fmw", "CXLFixedMemoryWindow",
356 machine_get_cfmw, machine_set_cfmw,
357 NULL, state);
358 object_property_set_description(obj, "cxl-fmw",
359 "CXL Fixed Memory Windows (array)");
360 }
361
cxl_hook_up_pxb_registers(PCIBus * bus,CXLState * state,Error ** errp)362 void cxl_hook_up_pxb_registers(PCIBus *bus, CXLState *state, Error **errp)
363 {
364 /* Walk the pci busses looking for pxb busses to hook up */
365 if (bus) {
366 QLIST_FOREACH(bus, &bus->child, sibling) {
367 if (!pci_bus_is_root(bus)) {
368 continue;
369 }
370 if (pci_bus_is_cxl(bus)) {
371 if (!state->is_enabled) {
372 error_setg(errp, "CXL host bridges present, but cxl=off");
373 return;
374 }
375 pxb_cxl_hook_up_registers(state, bus, errp);
376 }
377 }
378 }
379 }
380
cxl_fmws_find(Object * obj,void * opaque)381 static int cxl_fmws_find(Object *obj, void *opaque)
382 {
383 GSList **list = opaque;
384
385 if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
386 return 0;
387 }
388 *list = g_slist_prepend(*list, obj);
389
390 return 0;
391 }
392
cxl_fmws_get_all(void)393 static GSList *cxl_fmws_get_all(void)
394 {
395 GSList *list = NULL;
396
397 object_child_foreach_recursive(object_get_root(), cxl_fmws_find, &list);
398
399 return list;
400 }
401
cfmws_cmp(gconstpointer a,gconstpointer b,gpointer d)402 static gint cfmws_cmp(gconstpointer a, gconstpointer b, gpointer d)
403 {
404 const struct CXLFixedWindow *ap = a;
405 const struct CXLFixedWindow *bp = b;
406
407 return ap->index > bp->index;
408 }
409
cxl_fmws_get_all_sorted(void)410 GSList *cxl_fmws_get_all_sorted(void)
411 {
412 return g_slist_sort_with_data(cxl_fmws_get_all(), cfmws_cmp, NULL);
413 }
414
cxl_fmws_mmio_map(Object * obj,void * opaque)415 static int cxl_fmws_mmio_map(Object *obj, void *opaque)
416 {
417 struct CXLFixedWindow *fw;
418
419 if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
420 return 0;
421 }
422 fw = CXL_FMW(obj);
423 sysbus_mmio_map(SYS_BUS_DEVICE(fw), 0, fw->base);
424
425 return 0;
426 }
427
cxl_fmws_update_mmio(void)428 void cxl_fmws_update_mmio(void)
429 {
430 /* Ordering is not required for this */
431 object_child_foreach_recursive(object_get_root(), cxl_fmws_mmio_map, NULL);
432 }
433
cxl_fmws_set_memmap(hwaddr base,hwaddr max_addr)434 hwaddr cxl_fmws_set_memmap(hwaddr base, hwaddr max_addr)
435 {
436 GSList *cfmws_list, *iter;
437 CXLFixedWindow *fw;
438
439 cfmws_list = cxl_fmws_get_all_sorted();
440 for (iter = cfmws_list; iter; iter = iter->next) {
441 fw = CXL_FMW(iter->data);
442 if (base + fw->size <= max_addr) {
443 fw->base = base;
444 base += fw->size;
445 }
446 }
447 g_slist_free(cfmws_list);
448
449 return base;
450 }
451
cxl_fmw_realize(DeviceState * dev,Error ** errp)452 static void cxl_fmw_realize(DeviceState *dev, Error **errp)
453 {
454 CXLFixedWindow *fw = CXL_FMW(dev);
455
456 memory_region_init_io(&fw->mr, OBJECT(dev), &cfmws_ops, fw,
457 "cxl-fixed-memory-region", fw->size);
458 sysbus_init_mmio(SYS_BUS_DEVICE(dev), &fw->mr);
459 }
460
461 /*
462 * Note: Fixed memory windows represent fixed address decoders on the host and
463 * as such have no dynamic state to reset or migrate
464 */
cxl_fmw_class_init(ObjectClass * klass,const void * data)465 static void cxl_fmw_class_init(ObjectClass *klass, const void *data)
466 {
467 DeviceClass *dc = DEVICE_CLASS(klass);
468
469 dc->desc = "CXL Fixed Memory Window";
470 dc->realize = cxl_fmw_realize;
471 /* Reason - created by machines as tightly coupled to machine memory map */
472 dc->user_creatable = false;
473 }
474
475 static const TypeInfo cxl_fmw_info = {
476 .name = TYPE_CXL_FMW,
477 .parent = TYPE_SYS_BUS_DEVICE,
478 .instance_size = sizeof(CXLFixedWindow),
479 .class_init = cxl_fmw_class_init,
480 };
481
cxl_host_register_types(void)482 static void cxl_host_register_types(void)
483 {
484 type_register_static(&cxl_fmw_info);
485 }
486 type_init(cxl_host_register_types)
487