@samitouri / QOSamiQemu / commits / 680935c9a6

hw/cxl: Add a performant (and correct) path for the non interleaved cases

The CXL address to device decoding logic is complex because of the need to correctly decode fine grained interleave. The current implementation prevents use with KVM where executed instructions may reside in that memory and gives very slow performance even in TCG. In many real cases non interleaved memory configurations are useful and for those we can use a more conventional memory region alias allowing similar performance to other memory in the system. Whether this fast path is applicable can be established once the full set of HDM decoders has been committed (in whatever order the guest decides to commit them). As such a check is performed on each commit/uncommit of HDM decoder to establish if the alias should be added or removed. Reviewed-by: Li Zhijian <lizhijian@fujitsu.com> Co-developed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Signed-off-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Gregory Price <gourry@gourry.net> Tested-by: Gregory Price <gourry@gourry.net> Signed-off-by: Alireza Sanaee <alireza.sanaee@huawei.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-Id: <20260318171918.146-4-alireza.sanaee@huawei.com>

Alireza Sanaee committed Mar 18, 2026 at 17:19 UTC 680935c9a6fff2df0665b680322b9e9d0cdb692b
5 files changed +216
hw/cxl/cxl-component-utils.c
+6
@@ -143,6 +143,12 @@ static void dumb_hdm_handler(CXLComponentState *cxl_cstate, hwaddr offset,
143 value = FIELD_DP32(value, CXL_HDM_DECODER0_CTRL, COMMITTED, 0);
144 }
145 stl_le_p((uint8_t *)cache_mem + offset, value);
146 +
147 + if (should_commit) {
148 + cfmws_update_non_interleaved(true);
149 + } else if (should_uncommit) {
150 + cfmws_update_non_interleaved(false);
151 + }
152 }
153
154 static void bi_handler(CXLComponentState *cxl_cstate, hwaddr offset,
hw/cxl/cxl-host.c
+201
@@ -264,6 +264,207 @@ static PCIDevice *cxl_cfmws_find_device(CXLFixedWindow *fw, hwaddr addr,
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;
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 {
hw/mem/cxl_type3.c
+4
@@ -427,6 +427,8 @@ static void hdm_decoder_commit(CXLType3Dev *ct3d, int which)
427 ctrl = FIELD_DP32(ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED, 1);
428
429 stl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + which * hdm_inc, ctrl);
430 +
431 + cfmws_update_non_interleaved(true);
432 }
433
434 static void hdm_decoder_uncommit(CXLType3Dev *ct3d, int which)
@@ -442,6 +444,8 @@ static void hdm_decoder_uncommit(CXLType3Dev *ct3d, int which)
444 ctrl = FIELD_DP32(ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED, 0);
445
446 stl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + which * hdm_inc, ctrl);
447 +
448 + cfmws_update_non_interleaved(false);
449 }
450
451 static int ct3d_qmp_uncor_err_to_cxl(CxlUncorErrorType qmp_err)
include/hw/cxl/cxl.h
+1
@@ -71,4 +71,5 @@ CXLComponentState *cxl_usp_to_cstate(CXLUpstreamPort *usp);
71 typedef struct CXLDownstreamPort CXLDownstreamPort;
72 DECLARE_INSTANCE_CHECKER(CXLDownstreamPort, CXL_DSP, TYPE_CXL_DSP)
73
74 +void cfmws_update_non_interleaved(bool commit);
75 #endif
include/hw/cxl/cxl_device.h
+4
@@ -685,6 +685,8 @@ typedef struct CXLSetFeatureInfo {
685 size_t data_size;
686 } CXLSetFeatureInfo;
687
688 +typedef struct CXLFixedWindow CXLFixedWindow;
689 +
690 struct CXLSanitizeInfo;
691
692 typedef struct CXLAlertConfig {
@@ -712,6 +714,8 @@ struct CXLType3Dev {
714 uint64_t sn;
715
716 /* State */
717 + MemoryRegion direct_mr[CXL_HDM_DECODER_COUNT];
718 + CXLFixedWindow *direct_mr_fw[CXL_HDM_DECODER_COUNT];
719 AddressSpace hostvmem_as;
720 AddressSpace hostpmem_as;
721 CXLComponentState cxl_cstate;