master
c 704 lines 19.8 KB
Raw
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/core/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
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
73 static int cxl_fmws_link(Object *obj, void *opaque)
74 {
75 Error **errp = opaque;
76 struct CXLFixedWindow *fw;
77 int i;
78
79 if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
80 return 0;
81 }
82 fw = CXL_FMW(obj);
83
84 for (i = 0; i < fw->num_targets; i++) {
85 Object *o;
86 bool ambig;
87
88 o = object_resolve_path_type(fw->targets[i], TYPE_PXB_CXL_DEV,
89 &ambig);
90 if (!o) {
91 error_setg(errp, "Could not resolve CXLFM target %s",
92 fw->targets[i]);
93 return -1;
94 }
95 fw->target_hbs[i] = PXB_CXL_DEV(o);
96 }
97 return 0;
98 }
99
100 void cxl_fmws_link_targets(Error **errp)
101 {
102 /* Order doesn't matter for this, so no need to build list */
103 object_child_foreach_recursive(object_get_root(), cxl_fmws_link, errp);
104 }
105
106 static bool cxl_hdm_find_target(uint32_t *cache_mem, hwaddr addr,
107 uint8_t *target, bool *interleaved)
108 {
109 int hdm_inc = R_CXL_HDM_DECODER1_BASE_LO - R_CXL_HDM_DECODER0_BASE_LO;
110 unsigned int hdm_count;
111 bool found = false;
112 int i;
113 uint32_t cap;
114
115 cap = ldl_le_p(cache_mem + R_CXL_HDM_DECODER_CAPABILITY);
116 hdm_count = cxl_decoder_count_dec(FIELD_EX32(cap,
117 CXL_HDM_DECODER_CAPABILITY,
118 DECODER_COUNT));
119 for (i = 0; i < hdm_count; i++) {
120 uint32_t ctrl, ig_enc, iw_enc, target_idx;
121 uint32_t low, high;
122 uint64_t base, size;
123
124 low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_LO + i * hdm_inc);
125 high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_HI + i * hdm_inc);
126 base = (low & 0xf0000000) | ((uint64_t)high << 32);
127 low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_LO + i * hdm_inc);
128 high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_HI + i * hdm_inc);
129 size = (low & 0xf0000000) | ((uint64_t)high << 32);
130 if (addr < base || addr >= base + size) {
131 continue;
132 }
133
134 ctrl = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + i * hdm_inc);
135 if (!FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED)) {
136 return false;
137 }
138 found = true;
139 ig_enc = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IG);
140 iw_enc = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IW);
141
142 if (interleaved) {
143 *interleaved = iw_enc != 0;
144 }
145
146 target_idx = (addr / cxl_decode_ig(ig_enc)) % (1 << iw_enc);
147
148 if (target_idx < 4) {
149 uint32_t val = ldl_le_p(cache_mem +
150 R_CXL_HDM_DECODER0_TARGET_LIST_LO +
151 i * hdm_inc);
152 *target = extract32(val, target_idx * 8, 8);
153 } else {
154 uint32_t val = ldl_le_p(cache_mem +
155 R_CXL_HDM_DECODER0_TARGET_LIST_HI +
156 i * hdm_inc);
157 *target = extract32(val, (target_idx - 4) * 8, 8);
158 }
159 break;
160 }
161
162 return found;
163 }
164
165 static PCIDevice *cxl_cfmws_find_device(CXLFixedWindow *fw, hwaddr addr,
166 bool allow_interleave)
167 {
168 CXLComponentState *hb_cstate, *usp_cstate;
169 PCIHostState *hb;
170 CXLUpstreamPort *usp;
171 int rb_index;
172 uint32_t *cache_mem;
173 uint8_t target;
174 bool target_found, interleaved;
175 PCIDevice *rp, *d;
176
177 if ((fw->num_targets > 1) && !allow_interleave) {
178 return NULL;
179 }
180
181 rb_index = (addr / cxl_decode_ig(fw->enc_int_gran)) % fw->num_targets;
182 hb = PCI_HOST_BRIDGE(fw->target_hbs[rb_index]->cxl_host_bridge);
183 if (!hb || !hb->bus || !pci_bus_is_cxl(hb->bus)) {
184 return NULL;
185 }
186
187 if (cxl_get_hb_passthrough(hb)) {
188 rp = pcie_find_port_first(hb->bus);
189 if (!rp) {
190 return NULL;
191 }
192 } else {
193 hb_cstate = cxl_get_hb_cstate(hb);
194 if (!hb_cstate) {
195 return NULL;
196 }
197
198 cache_mem = hb_cstate->crb.cache_mem_registers;
199
200 target_found = cxl_hdm_find_target(cache_mem, addr, &target,
201 &interleaved);
202 if (!target_found) {
203 return NULL;
204 }
205
206 if (interleaved && !allow_interleave) {
207 return NULL;
208 }
209
210 rp = pcie_find_port_by_pn(hb->bus, target);
211 if (!rp) {
212 return NULL;
213 }
214 }
215
216 d = pci_bridge_get_sec_bus(PCI_BRIDGE(rp))->devices[0];
217 if (!d) {
218 return NULL;
219 }
220
221 if (object_dynamic_cast(OBJECT(d), TYPE_CXL_TYPE3)) {
222 return d;
223 }
224
225 /*
226 * Could also be a switch. Note only one level of switching currently
227 * supported.
228 */
229 if (!object_dynamic_cast(OBJECT(d), TYPE_CXL_USP)) {
230 return NULL;
231 }
232 usp = CXL_USP(d);
233
234 usp_cstate = cxl_usp_to_cstate(usp);
235 if (!usp_cstate) {
236 return NULL;
237 }
238
239 cache_mem = usp_cstate->crb.cache_mem_registers;
240
241 target_found = cxl_hdm_find_target(cache_mem, addr, &target, &interleaved);
242 if (!target_found) {
243 return NULL;
244 }
245
246 if (interleaved && !allow_interleave) {
247 return NULL;
248 }
249
250 d = pcie_find_port_by_pn(&PCI_BRIDGE(d)->sec_bus, target);
251 if (!d) {
252 return NULL;
253 }
254
255 d = pci_bridge_get_sec_bus(PCI_BRIDGE(d))->devices[0];
256 if (!d) {
257 return NULL;
258 }
259
260 if (!object_dynamic_cast(OBJECT(d), TYPE_CXL_TYPE3)) {
261 return NULL;
262 }
263
264 return d;
265 }
266
267 typedef struct CXLDirectPTState {
268 CXLType3Dev *ct3d;
269 hwaddr decoder_base;
270 hwaddr decoder_size;
271 hwaddr dpa_base;
272 unsigned int hdm_decoder_idx;
273 } CXLDirectPTState;
274
275 static void cxl_fmws_direct_passthrough_setup(CXLDirectPTState *state,
276 CXLFixedWindow *fw)
277 {
278 CXLType3Dev *ct3d = state->ct3d;
279 MemoryRegion *mr = NULL;
280 uint64_t vmr_size = 0, pmr_size = 0, offset = 0;
281 MemoryRegion *direct_mr;
282 g_autofree char *direct_mr_name = NULL;
283 unsigned int idx = state->hdm_decoder_idx;
284
285 if (ct3d->hostvmem) {
286 MemoryRegion *vmr = host_memory_backend_get_memory(ct3d->hostvmem);
287
288 vmr_size = memory_region_size(vmr);
289 if (state->dpa_base < vmr_size) {
290 mr = vmr;
291 offset = state->dpa_base;
292 }
293 }
294 if (!mr && ct3d->hostpmem) {
295 MemoryRegion *pmr = host_memory_backend_get_memory(ct3d->hostpmem);
296
297 pmr_size = memory_region_size(pmr);
298 if (state->dpa_base - vmr_size < pmr_size) {
299 mr = pmr;
300 offset = state->dpa_base - vmr_size;
301 }
302 }
303 if (!mr) {
304 return;
305 }
306
307 if (ct3d->direct_mr_fw[idx]) {
308 return;
309 }
310
311 direct_mr = &ct3d->direct_mr[idx];
312 direct_mr_name = g_strdup_printf("cxl-direct-mapping-alias-%u", idx);
313 if (!direct_mr_name) {
314 return;
315 }
316
317 memory_region_init_alias(direct_mr, OBJECT(ct3d), direct_mr_name, mr,
318 offset, state->decoder_size);
319 memory_region_transaction_begin();
320 memory_region_add_subregion(&fw->mr,
321 state->decoder_base - fw->base, direct_mr);
322 memory_region_transaction_commit();
323 ct3d->direct_mr_fw[idx] = fw;
324 }
325
326 static void cxl_fmws_direct_passthrough_remove(CXLType3Dev *ct3d,
327 uint64_t decoder_base,
328 unsigned int idx)
329 {
330 CXLFixedWindow *owner_fw = ct3d->direct_mr_fw[idx];
331 MemoryRegion *direct_mr = &ct3d->direct_mr[idx];
332
333 if (!owner_fw) {
334 return;
335 }
336
337 if (!memory_region_is_mapped(direct_mr)) {
338 return;
339 }
340
341 if (cxl_cfmws_find_device(owner_fw, decoder_base, false)) {
342 return;
343 }
344
345 memory_region_transaction_begin();
346 memory_region_del_subregion(&owner_fw->mr, direct_mr);
347 object_unparent(OBJECT(direct_mr));
348 memory_region_transaction_commit();
349 ct3d->direct_mr_fw[idx] = NULL;
350 }
351
352 static int cxl_fmws_direct_passthrough(Object *obj, void *opaque)
353 {
354 CXLDirectPTState *state = opaque;
355 CXLFixedWindow *fw;
356
357 if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
358 return 0;
359 }
360
361 fw = CXL_FMW(obj);
362
363 /* Verify not interleaved */
364 if (!cxl_cfmws_find_device(fw, state->decoder_base, false)) {
365 return 0;
366 }
367
368 cxl_fmws_direct_passthrough_setup(state, fw);
369
370 return 0;
371 }
372
373 static int update_non_interleaved(Object *obj, void *opaque)
374 {
375 const int hdm_inc = R_CXL_HDM_DECODER1_BASE_LO - R_CXL_HDM_DECODER0_BASE_LO;
376 bool commit = *(bool *)opaque;
377 CXLType3Dev *ct3d;
378 uint32_t *cache_mem;
379 unsigned int hdm_count, i;
380 int interleave_ways_dec;
381 uint32_t cap;
382 uint64_t dpa_base = 0;
383
384 if (!object_dynamic_cast(obj, TYPE_CXL_TYPE3)) {
385 return 0;
386 }
387
388 ct3d = CXL_TYPE3(obj);
389 cache_mem = ct3d->cxl_cstate.crb.cache_mem_registers;
390 cap = ldl_le_p(cache_mem + R_CXL_HDM_DECODER_CAPABILITY);
391 hdm_count = cxl_decoder_count_dec(FIELD_EX32(cap,
392 CXL_HDM_DECODER_CAPABILITY,
393 DECODER_COUNT));
394 for (i = 0; i < hdm_count; i++) {
395 uint64_t decoder_base, decoder_size, skip;
396 uint32_t hdm_ctrl, low, high;
397 int iw, committed;
398
399 hdm_ctrl = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + i * hdm_inc);
400 committed = FIELD_EX32(hdm_ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED);
401
402 /*
403 * Optimization: Looking for a fully committed path; if the type 3 HDM
404 * decoder is not commmitted, it cannot lie on such a path.
405 */
406 if (commit && !committed) {
407 return 0;
408 }
409
410 low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_DPA_SKIP_LO +
411 i * hdm_inc);
412 high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_DPA_SKIP_HI +
413 i * hdm_inc);
414 skip = ((uint64_t)high << 32) | (low & 0xf0000000);
415 dpa_base += skip;
416
417 low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_LO + i * hdm_inc);
418 high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_HI + i * hdm_inc);
419 decoder_size = ((uint64_t)high << 32) | (low & 0xf0000000);
420
421 low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_LO + i * hdm_inc);
422 high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_HI + i * hdm_inc);
423 decoder_base = ((uint64_t)high << 32) | (low & 0xf0000000);
424
425 iw = FIELD_EX32(hdm_ctrl, CXL_HDM_DECODER0_CTRL, IW);
426
427 if (iw == 0) {
428 if (!commit) {
429 cxl_fmws_direct_passthrough_remove(ct3d, decoder_base, i);
430 } else {
431 CXLDirectPTState state = {
432 .ct3d = ct3d,
433 .decoder_base = decoder_base,
434 .decoder_size = decoder_size,
435 .dpa_base = dpa_base,
436 .hdm_decoder_idx = i,
437 };
438
439 object_child_foreach_recursive(object_get_root(),
440 cxl_fmws_direct_passthrough,
441 &state);
442 }
443 }
444
445 interleave_ways_dec = cxl_interleave_ways_dec(iw, &error_fatal);
446 if (interleave_ways_dec == 0) {
447 return 0;
448 }
449
450 dpa_base += decoder_size / interleave_ways_dec;
451 }
452
453 return 0;
454 }
455
456 void cfmws_update_non_interleaved(bool commit)
457 {
458 /*
459 * Walk endpoints to find both committed and uncommitted decoders,
460 * then check if they are not interleaved (but the path is fully set up).
461 */
462 object_child_foreach_recursive(object_get_root(),
463 update_non_interleaved, &commit);
464
465 return;
466 }
467
468 static MemTxResult cxl_read_cfmws(void *opaque, hwaddr addr, uint64_t *data,
469 unsigned size, MemTxAttrs attrs)
470 {
471 CXLFixedWindow *fw = opaque;
472 PCIDevice *d;
473
474 d = cxl_cfmws_find_device(fw, addr + fw->base, true);
475 if (d == NULL) {
476 *data = 0;
477 /* Reads to invalid address return poison */
478 return MEMTX_ERROR;
479 }
480
481 return cxl_type3_read(d, addr + fw->base, data, size, attrs);
482 }
483
484 static MemTxResult cxl_write_cfmws(void *opaque, hwaddr addr,
485 uint64_t data, unsigned size,
486 MemTxAttrs attrs)
487 {
488 CXLFixedWindow *fw = opaque;
489 PCIDevice *d;
490
491 d = cxl_cfmws_find_device(fw, addr + fw->base, true);
492 if (d == NULL) {
493 /* Writes to invalid address are silent */
494 return MEMTX_OK;
495 }
496
497 return cxl_type3_write(d, addr + fw->base, data, size, attrs);
498 }
499
500 const MemoryRegionOps cfmws_ops = {
501 .read_with_attrs = cxl_read_cfmws,
502 .write_with_attrs = cxl_write_cfmws,
503 .endianness = DEVICE_LITTLE_ENDIAN,
504 .valid = {
505 .min_access_size = 1,
506 .max_access_size = 8,
507 .unaligned = true,
508 },
509 .impl = {
510 .min_access_size = 1,
511 .max_access_size = 8,
512 .unaligned = true,
513 },
514 };
515
516 static void machine_get_cxl(Object *obj, Visitor *v, const char *name,
517 void *opaque, Error **errp)
518 {
519 CXLState *cxl_state = opaque;
520 bool value = cxl_state->is_enabled;
521
522 visit_type_bool(v, name, &value, errp);
523 }
524
525 static void machine_set_cxl(Object *obj, Visitor *v, const char *name,
526 void *opaque, Error **errp)
527 {
528 CXLState *cxl_state = opaque;
529 bool value;
530
531 if (!visit_type_bool(v, name, &value, errp)) {
532 return;
533 }
534 cxl_state->is_enabled = value;
535 }
536
537 static void machine_get_cfmw(Object *obj, Visitor *v, const char *name,
538 void *opaque, Error **errp)
539 {
540 CXLState *state = opaque;
541 CXLFixedMemoryWindowOptionsList **list = &state->cfmw_list;
542
543 visit_type_CXLFixedMemoryWindowOptionsList(v, name, list, errp);
544 }
545
546 static void machine_set_cfmw(Object *obj, Visitor *v, const char *name,
547 void *opaque, Error **errp)
548 {
549 CXLState *state = opaque;
550 CXLFixedMemoryWindowOptionsList *cfmw_list = NULL;
551 CXLFixedMemoryWindowOptionsList *it;
552 int index;
553
554 visit_type_CXLFixedMemoryWindowOptionsList(v, name, &cfmw_list, errp);
555 if (!cfmw_list) {
556 return;
557 }
558
559 for (it = cfmw_list, index = 0; it; it = it->next, index++) {
560 cxl_fixed_memory_window_config(it->value, index, errp);
561 }
562 state->cfmw_list = cfmw_list;
563 }
564
565 void cxl_machine_init(Object *obj, CXLState *state)
566 {
567 object_property_add(obj, "cxl", "bool", machine_get_cxl,
568 machine_set_cxl, NULL, state);
569 object_property_set_description(obj, "cxl",
570 "Set on/off to enable/disable "
571 "CXL instantiation");
572
573 object_property_add(obj, "cxl-fmw", "CXLFixedMemoryWindow",
574 machine_get_cfmw, machine_set_cfmw,
575 NULL, state);
576 object_property_set_description(obj, "cxl-fmw",
577 "CXL Fixed Memory Windows (array)");
578 }
579
580 void cxl_hook_up_pxb_registers(PCIBus *bus, CXLState *state, Error **errp)
581 {
582 /* Walk the pci busses looking for pxb busses to hook up */
583 if (bus) {
584 QLIST_FOREACH(bus, &bus->child, sibling) {
585 if (!pci_bus_is_root(bus)) {
586 continue;
587 }
588 if (pci_bus_is_cxl(bus)) {
589 if (!state->is_enabled) {
590 error_setg(errp, "CXL host bridges present, but cxl=off");
591 return;
592 }
593 pxb_cxl_hook_up_registers(state, bus, errp);
594 }
595 }
596 }
597 }
598
599 static int cxl_fmws_find(Object *obj, void *opaque)
600 {
601 GSList **list = opaque;
602
603 if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
604 return 0;
605 }
606 *list = g_slist_prepend(*list, obj);
607
608 return 0;
609 }
610
611 static GSList *cxl_fmws_get_all(void)
612 {
613 GSList *list = NULL;
614
615 object_child_foreach_recursive(object_get_root(), cxl_fmws_find, &list);
616
617 return list;
618 }
619
620 static gint cfmws_cmp(gconstpointer a, gconstpointer b, gpointer d)
621 {
622 const struct CXLFixedWindow *ap = a;
623 const struct CXLFixedWindow *bp = b;
624
625 return ap->index > bp->index;
626 }
627
628 GSList *cxl_fmws_get_all_sorted(void)
629 {
630 return g_slist_sort_with_data(cxl_fmws_get_all(), cfmws_cmp, NULL);
631 }
632
633 static int cxl_fmws_mmio_map(Object *obj, void *opaque)
634 {
635 struct CXLFixedWindow *fw;
636
637 if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
638 return 0;
639 }
640 fw = CXL_FMW(obj);
641 sysbus_mmio_map(SYS_BUS_DEVICE(fw), 0, fw->base);
642
643 return 0;
644 }
645
646 void cxl_fmws_update_mmio(void)
647 {
648 /* Ordering is not required for this */
649 object_child_foreach_recursive(object_get_root(), cxl_fmws_mmio_map, NULL);
650 }
651
652 hwaddr cxl_fmws_set_memmap(hwaddr base, hwaddr max_addr)
653 {
654 GSList *cfmws_list, *iter;
655 CXLFixedWindow *fw;
656
657 cfmws_list = cxl_fmws_get_all_sorted();
658 for (iter = cfmws_list; iter; iter = iter->next) {
659 fw = CXL_FMW(iter->data);
660 if (base + fw->size <= max_addr) {
661 fw->base = base;
662 base += fw->size;
663 }
664 }
665 g_slist_free(cfmws_list);
666
667 return base;
668 }
669
670 static void cxl_fmw_realize(DeviceState *dev, Error **errp)
671 {
672 CXLFixedWindow *fw = CXL_FMW(dev);
673
674 memory_region_init_io(&fw->mr, OBJECT(dev), &cfmws_ops, fw,
675 "cxl-fixed-memory-region", fw->size);
676 sysbus_init_mmio(SYS_BUS_DEVICE(dev), &fw->mr);
677 }
678
679 /*
680 * Note: Fixed memory windows represent fixed address decoders on the host and
681 * as such have no dynamic state to reset or migrate
682 */
683 static void cxl_fmw_class_init(ObjectClass *klass, const void *data)
684 {
685 DeviceClass *dc = DEVICE_CLASS(klass);
686
687 dc->desc = "CXL Fixed Memory Window";
688 dc->realize = cxl_fmw_realize;
689 /* Reason - created by machines as tightly coupled to machine memory map */
690 dc->user_creatable = false;
691 }
692
693 static const TypeInfo cxl_fmw_info = {
694 .name = TYPE_CXL_FMW,
695 .parent = TYPE_SYS_BUS_DEVICE,
696 .instance_size = sizeof(CXLFixedWindow),
697 .class_init = cxl_fmw_class_init,
698 };
699
700 static void cxl_host_register_types(void)
701 {
702 type_register_static(&cxl_fmw_info);
703 }
704 type_init(cxl_host_register_types)