* [PATCH 1/6] hw/cxl: fix timer leak in cxl_destroy_cci()
2026-06-26 6:21 [PATCH 0/6] hw/cxl: fix Type-3 device reset resource leaks and convert to three-phase Junjie Cao
@ 2026-06-26 6:21 ` Junjie Cao
2026-07-21 1:03 ` Jonathan Cameron
2026-06-26 6:21 ` [PATCH 2/6] hw/cxl: destroy primary CCI before re-initialization on reset Junjie Cao
` (4 subsequent siblings)
5 siblings, 1 reply; 8+ messages in thread
From: Junjie Cao @ 2026-06-26 6:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jonathan Cameron, linux-cxl, junjie.cao, qemu-stable
cxl_init_cci() allocates a QEMUTimer via timer_new_ms() but
cxl_destroy_cci() never frees it. This leaks a timer object on every
device exit path and, more critically, on every device reset cycle since
the secondary CCIs (vdm_fm_owned_ld_mctp_cci, ld0_cci) are destroyed
and re-initialized each time ct3d_reset() runs.
Free the timer with timer_free(), which cancels any pending expiry via
timer_del() internally and tolerates a NULL pointer, then clear the
field so that a repeated timer_free() on the same CCI is a safe no-op.
(The function as a whole is not idempotent: it also calls
qemu_mutex_destroy(), which asserts on an already-destroyed mutex.
Callers must not invoke cxl_destroy_cci() twice; the .initialized guard
added in the next patch enforces that.)
Fixes: 98cbac128f1c ("hw/cxl: Support aborting background commands")
Cc: qemu-stable@nongnu.org
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
hw/cxl/cxl-mailbox-utils.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hw/cxl/cxl-mailbox-utils.c b/hw/cxl/cxl-mailbox-utils.c
index 20e0b7e476..18a455e89c 100644
--- a/hw/cxl/cxl-mailbox-utils.c
+++ b/hw/cxl/cxl-mailbox-utils.c
@@ -4770,6 +4770,8 @@ void cxl_init_cci(CXLCCI *cci, size_t payload_max)
void cxl_destroy_cci(CXLCCI *cci)
{
+ timer_free(cci->bg.timer);
+ cci->bg.timer = NULL;
qemu_mutex_destroy(&cci->bg.lock);
cci->initialized = false;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH 1/6] hw/cxl: fix timer leak in cxl_destroy_cci()
2026-06-26 6:21 ` [PATCH 1/6] hw/cxl: fix timer leak in cxl_destroy_cci() Junjie Cao
@ 2026-07-21 1:03 ` Jonathan Cameron
0 siblings, 0 replies; 8+ messages in thread
From: Jonathan Cameron @ 2026-07-21 1:03 UTC (permalink / raw)
To: Junjie Cao; +Cc: qemu-devel, linux-cxl, qemu-stable
On Fri, 26 Jun 2026 14:21:44 +0800
Junjie Cao <junjie.cao@intel.com> wrote:
> cxl_init_cci() allocates a QEMUTimer via timer_new_ms() but
> cxl_destroy_cci() never frees it. This leaks a timer object on every
> device exit path and, more critically, on every device reset cycle since
> the secondary CCIs (vdm_fm_owned_ld_mctp_cci, ld0_cci) are destroyed
> and re-initialized each time ct3d_reset() runs.
>
> Free the timer with timer_free(), which cancels any pending expiry via
> timer_del() internally and tolerates a NULL pointer, then clear the
> field so that a repeated timer_free() on the same CCI is a safe no-op.
> (The function as a whole is not idempotent: it also calls
> qemu_mutex_destroy(), which asserts on an already-destroyed mutex.
> Callers must not invoke cxl_destroy_cci() twice; the .initialized guard
> added in the next patch enforces that.)
>
> Fixes: 98cbac128f1c ("hw/cxl: Support aborting background commands")
> Cc: qemu-stable@nongnu.org
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Hi. Sorry for delay on catching up with these.
One minor thing inline - otherwise looks good to me.
Jonathan
> ---
> hw/cxl/cxl-mailbox-utils.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/hw/cxl/cxl-mailbox-utils.c b/hw/cxl/cxl-mailbox-utils.c
> index 20e0b7e476..18a455e89c 100644
> --- a/hw/cxl/cxl-mailbox-utils.c
> +++ b/hw/cxl/cxl-mailbox-utils.c
> @@ -4770,6 +4770,8 @@ void cxl_init_cci(CXLCCI *cci, size_t payload_max)
>
> void cxl_destroy_cci(CXLCCI *cci)
> {
> + timer_free(cci->bg.timer);
> + cci->bg.timer = NULL;
Can we do this in reverse of the cxl_init_cci() ordering? That would
put these after the qemu_mutex_destroy()
> qemu_mutex_destroy(&cci->bg.lock);
> cci->initialized = false;
> }
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/6] hw/cxl: destroy primary CCI before re-initialization on reset
2026-06-26 6:21 [PATCH 0/6] hw/cxl: fix Type-3 device reset resource leaks and convert to three-phase Junjie Cao
2026-06-26 6:21 ` [PATCH 1/6] hw/cxl: fix timer leak in cxl_destroy_cci() Junjie Cao
@ 2026-06-26 6:21 ` Junjie Cao
2026-06-26 6:21 ` [PATCH 3/6] hw/cxl: convert cxl-type3 to three-phase reset Junjie Cao
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Junjie Cao @ 2026-06-26 6:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jonathan Cameron, linux-cxl, junjie.cao, qemu-stable
ct3d_reset() re-initializes the primary CCI through the call chain
cxl_device_register_init_t3() -> cxl_initialize_mailbox_t3() ->
cxl_init_cci(), but never calls cxl_destroy_cci() first. Each reset
cycle therefore leaks the old timer and leaves the old mutex
undestroyed while silently overwriting the CCI state.
Per CXL r4.0, the "Mailbox Interfaces Ready" bit in the Memory Device
Status register (Table 8-212) is set after a Conventional Reset or CXL
Reset once the device has re-initialized its mailbox interfaces. The
CCI is the software abstraction of these interfaces and must be properly
torn down before re-initialization.
The secondary CCIs (vdm_fm_owned_ld_mctp_cci, ld0_cci) already follow
the correct destroy-before-reinit pattern in the same function; apply
the same discipline to the primary CCI.
Also destroy the secondary CCIs in ct3_exit() where they were
previously leaked at device removal time. Guard the primary CCI
teardown there with the same .initialized check used for the secondary
CCIs: the primary CCI is only brought up from the reset path
(cxl_device_register_init_t3()), so a device that is unrealized before
its first reset would otherwise tear down a never-initialized CCI.
Fixes: cac36a8faffc ("hw/cxl/mbox: Pull the CCI definition out of the CXLDeviceState")
Cc: qemu-stable@nongnu.org
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
hw/mem/cxl_type3.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index cba05ec57d..4ac6eaa950 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -1073,7 +1073,15 @@ static void ct3_exit(PCIDevice *pci_dev)
cxl_doe_cdat_release(cxl_cstate);
msix_uninit_exclusive_bar(pci_dev);
g_free(regs->special_ops);
- cxl_destroy_cci(&ct3d->cci);
+ if (ct3d->cci.initialized) {
+ cxl_destroy_cci(&ct3d->cci);
+ }
+ if (ct3d->vdm_fm_owned_ld_mctp_cci.initialized) {
+ cxl_destroy_cci(&ct3d->vdm_fm_owned_ld_mctp_cci);
+ }
+ if (ct3d->ld0_cci.initialized) {
+ cxl_destroy_cci(&ct3d->ld0_cci);
+ }
if (ct3d->dc.host_dc) {
cxl_destroy_dc_regions(ct3d);
address_space_destroy(&ct3d->dc.host_dc_as);
@@ -1328,6 +1336,9 @@ static void ct3d_reset(DeviceState *dev)
ct3d->flitmode);
cxl_component_register_init_common(reg_state, write_msk,
CXL2_TYPE3_DEVICE, ct3d->hdmdb);
+ if (ct3d->cci.initialized) {
+ cxl_destroy_cci(&ct3d->cci);
+ }
cxl_device_register_init_t3(ct3d, CXL_T3_MSIX_MBOX);
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 3/6] hw/cxl: convert cxl-type3 to three-phase reset
2026-06-26 6:21 [PATCH 0/6] hw/cxl: fix Type-3 device reset resource leaks and convert to three-phase Junjie Cao
2026-06-26 6:21 ` [PATCH 1/6] hw/cxl: fix timer leak in cxl_destroy_cci() Junjie Cao
2026-06-26 6:21 ` [PATCH 2/6] hw/cxl: destroy primary CCI before re-initialization on reset Junjie Cao
@ 2026-06-26 6:21 ` Junjie Cao
2026-06-26 6:21 ` [PATCH 4/6] hw/cxl: free in-flight sanitize state on reset Junjie Cao
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Junjie Cao @ 2026-06-26 6:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jonathan Cameron, linux-cxl, junjie.cao
Replace the deprecated device_class_set_legacy_reset() registration
with the three-phase resettable interface, following the pattern
already established by the CXL root port (cxl_rp_reset_hold).
Only the hold phase is needed; enter and exit are left NULL. The
parent hold phase is chained for correctness: TYPE_PCI_DEVICE installs
no hold phase today, so parent_phases.hold is currently NULL and the
chained call is a no-op, but capturing and invoking it is the correct
forward-compatible pattern and mirrors cxl_rp_reset_hold().
No functional change — the reset body is identical; only the
registration path and function signature change.
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
hw/mem/cxl_type3.c | 16 ++++++++++++----
include/hw/cxl/cxl_device.h | 2 ++
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index 4ac6eaa950..b842e71c66 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -1326,13 +1326,18 @@ MemTxResult cxl_type3_write(PCIDevice *d, hwaddr host_addr, uint64_t data,
return address_space_write(as, dpa_offset, attrs, &data, size);
}
-static void ct3d_reset(DeviceState *dev)
+static void ct3d_reset_hold(Object *obj, ResetType type)
{
- CXLType3Dev *ct3d = CXL_TYPE3(dev);
+ CXLType3Dev *ct3d = CXL_TYPE3(obj);
+ CXLType3Class *cvc = CXL_TYPE3_GET_CLASS(obj);
uint32_t *reg_state = ct3d->cxl_cstate.crb.cache_mem_registers;
uint32_t *write_msk = ct3d->cxl_cstate.crb.cache_mem_regs_write_mask;
- pcie_cap_fill_link_ep_usp(PCI_DEVICE(dev), ct3d->width, ct3d->speed,
+ if (cvc->parent_phases.hold) {
+ cvc->parent_phases.hold(obj, type);
+ }
+
+ pcie_cap_fill_link_ep_usp(PCI_DEVICE(obj), ct3d->width, ct3d->speed,
ct3d->flitmode);
cxl_component_register_init_common(reg_state, write_msk,
CXL2_TYPE3_DEVICE, ct3d->hdmdb);
@@ -2464,6 +2469,7 @@ static void ct3_class_init(ObjectClass *oc, const void *data)
DeviceClass *dc = DEVICE_CLASS(oc);
PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc);
CXLType3Class *cvc = CXL_TYPE3_CLASS(oc);
+ ResettableClass *rc = RESETTABLE_CLASS(oc);
pc->realize = ct3_realize;
pc->exit = ct3_exit;
@@ -2477,9 +2483,11 @@ static void ct3_class_init(ObjectClass *oc, const void *data)
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
dc->desc = "CXL Memory Device (Type 3)";
- device_class_set_legacy_reset(dc, ct3d_reset);
device_class_set_props(dc, ct3_props);
+ resettable_class_set_parent_phases(rc, NULL, ct3d_reset_hold, NULL,
+ &cvc->parent_phases);
+
cvc->get_lsa_size = get_lsa_size;
cvc->get_lsa = get_lsa;
cvc->set_lsa = set_lsa;
diff --git a/include/hw/cxl/cxl_device.h b/include/hw/cxl/cxl_device.h
index ba551fa5f9..b7e20e3fe4 100644
--- a/include/hw/cxl/cxl_device.h
+++ b/include/hw/cxl/cxl_device.h
@@ -805,6 +805,8 @@ struct CXLType3Class {
/* Private */
PCIDeviceClass parent_class;
+ ResettablePhases parent_phases;
+
/* public */
uint64_t (*get_lsa_size)(CXLType3Dev *ct3d);
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 4/6] hw/cxl: free in-flight sanitize state on reset
2026-06-26 6:21 [PATCH 0/6] hw/cxl: fix Type-3 device reset resource leaks and convert to three-phase Junjie Cao
` (2 preceding siblings ...)
2026-06-26 6:21 ` [PATCH 3/6] hw/cxl: convert cxl-type3 to three-phase reset Junjie Cao
@ 2026-06-26 6:21 ` Junjie Cao
2026-06-26 6:21 ` [PATCH 5/6] hw/cxl: clear event logs, scan media and interrupt policy " Junjie Cao
2026-06-26 6:21 ` [PATCH 6/6] hw/cxl: clear poison lists and feature transfer state " Junjie Cao
5 siblings, 0 replies; 8+ messages in thread
From: Junjie Cao @ 2026-06-26 6:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jonathan Cameron, linux-cxl, junjie.cao
Per CXL r4.0 Section 8.2.9.4, "Background commands do not continue to
execute across Conventional Resets." If a sanitize or media operation
is in progress when the device is reset, the background timer is already
cancelled and freed via cxl_destroy_cci(), but the per-operation state
(media_op_sanitize) is heap-allocated separately and would otherwise be
leaked.
Free it unconditionally at the end of the reset hold phase. The timer
that advances the operation lives in the CCI that was just destroyed and
re-initialized, so the operation can never complete after a reset of any
type; preserving the heap state across reset has no benefit and would
leak it the next time media_op_sanitize is assigned.
Note that Section 8.2.10.9.5.1 additionally requires a device whose
Sanitize was interrupted by reset to remain in the Media Disabled state
until a successful Sanitize completes. That latch is not modelled here
(reset re-enables media via memdev_reg_init_common()) and is left for
future work; this patch only addresses the resource leak.
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
hw/mem/cxl_type3.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index b842e71c66..a5e6df3033 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -1361,6 +1361,16 @@ static void ct3d_reset_hold(Object *obj, ResetType type)
}
cxl_initialize_t3_ld_cci(&ct3d->ld0_cci, DEVICE(ct3d), DEVICE(ct3d),
512); /* Max payload made up */
+
+ /*
+ * Free any in-flight sanitize state unconditionally. The background
+ * timer that would advance it lives in the CCI just torn down and
+ * re-initialized above, so the operation can never complete after this
+ * point regardless of the reset type; keeping the heap state would only
+ * leak it on the next allocation.
+ */
+ g_free(ct3d->media_op_sanitize);
+ ct3d->media_op_sanitize = NULL;
}
static const Property ct3_props[] = {
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 5/6] hw/cxl: clear event logs, scan media and interrupt policy on reset
2026-06-26 6:21 [PATCH 0/6] hw/cxl: fix Type-3 device reset resource leaks and convert to three-phase Junjie Cao
` (3 preceding siblings ...)
2026-06-26 6:21 ` [PATCH 4/6] hw/cxl: free in-flight sanitize state on reset Junjie Cao
@ 2026-06-26 6:21 ` Junjie Cao
2026-06-26 6:21 ` [PATCH 6/6] hw/cxl: clear poison lists and feature transfer state " Junjie Cao
5 siblings, 0 replies; 8+ messages in thread
From: Junjie Cao @ 2026-06-26 6:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jonathan Cameron, linux-cxl, junjie.cao
Event records, scan media results and event interrupt settings are
device-internal dynamic state that should not survive a device reset.
Per CXL r4.0 Section 8.2.10.9.4.6 (Get Scan Media Results), "If the
Scan Media command has not been called since the last Conventional
Reset, the device shall return the Unsupported return code." This
explicitly invalidates scan media results across reset, so clear the
scan_media_hasrun flag.
Per CXL r4.0 Section 8.2.10.2.5 (Set Event Interrupt Policy), "All
event log interrupt settings shall be reset to 00b (No Interrupts) by
the device on Conventional Reset." Clear irq_enabled for every event
log accordingly.
For the event records there is no direct spec mandate to drop the
stored records on reset; the Event Status register (Table 8-203) is
non-sticky and resets to zero per Section 9.7, and no reset flavor
requires the already-reported records to persist. Draining the queues
is therefore a reasonable modelling choice that keeps the records
consistent with the freshly-reset status register, rather than a spec
requirement. Call the existing cxl_discard_all_event_records() helper
to drain all event queues. The event log infrastructure itself
(mutexes, IRQ vectors) remains intact as it is initialized once during
device realize.
This is also where reset-type gating begins: a wakeup from suspend-to-RAM
(RESET_TYPE_WAKEUP) is not a Conventional or CXL Reset and must retain
device-internal state, so the hold phase returns early for that type
before discarding any records. This mirrors the RESET_TYPE_WAKEUP
shortcut in virtio-mem and virtio-balloon. Only the unconditional
mailbox interface re-initialization and the in-flight sanitize free
(whose backing timer was already destroyed) run on a wakeup.
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
hw/mem/cxl_type3.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index a5e6df3033..5bf0cffb72 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -1371,6 +1371,23 @@ static void ct3d_reset_hold(Object *obj, ResetType type)
*/
g_free(ct3d->media_op_sanitize);
ct3d->media_op_sanitize = NULL;
+
+ /*
+ * A wakeup from suspend-to-RAM is not a Conventional or CXL Reset. The
+ * device-internal dynamic state cleared below (event logs, scan media
+ * results, and the poison/feature-transfer state cleared in subsequent
+ * patches) must be preserved across resume, so stop here for a wakeup.
+ * virtio-mem and virtio-balloon take the same RESET_TYPE_WAKEUP shortcut.
+ */
+ if (type == RESET_TYPE_WAKEUP) {
+ return;
+ }
+
+ cxl_discard_all_event_records(&ct3d->cxl_dstate);
+ for (int i = 0; i < CXL_EVENT_TYPE_MAX; i++) {
+ ct3d->cxl_dstate.event_logs[i].irq_enabled = false;
+ }
+ ct3d->scan_media_hasrun = false;
}
static const Property ct3_props[] = {
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6/6] hw/cxl: clear poison lists and feature transfer state on reset
2026-06-26 6:21 [PATCH 0/6] hw/cxl: fix Type-3 device reset resource leaks and convert to three-phase Junjie Cao
` (4 preceding siblings ...)
2026-06-26 6:21 ` [PATCH 5/6] hw/cxl: clear event logs, scan media and interrupt policy " Junjie Cao
@ 2026-06-26 6:21 ` Junjie Cao
5 siblings, 0 replies; 8+ messages in thread
From: Junjie Cao @ 2026-06-26 6:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jonathan Cameron, linux-cxl, junjie.cao
The poison lists (active, backup, and scan-media results), their
associated overflow tracking, and the Set Feature partial-transfer
state are all device-internal dynamic state that accumulates during
the device's lifetime.
Per CXL r4.0 Table 8-309 (Identify Memory Device), the "Injects
Persistent Poison" capability bit (offset 41h, Bit[0]) controls poison
retention across reset. When cleared — the QEMU default — "a
Conventional Reset or CXL Reset shall automatically clear the injected
poison." Clear all poison lists accordingly, reusing the existing
cxl_clear_poison_list_overflowed() helper for the overflow tracking.
For the Set Feature transfer state, CXL r4.0 Section 8.2.10.6.3 (Set
Feature) requires: "If the Feature data transfer is interrupted by a
Conventional Reset or a CXL Reset, the Feature data transfer shall be
aborted by the device [...] the device shall require the Feature data
transfer to be started from the beginning." Zero set_feat_info on
reset so any partially transferred Set Feature is abandoned and must
restart from the beginning.
Both clears run after the RESET_TYPE_WAKEUP early-return added in the
previous patch, so this state is preserved across a suspend-to-RAM
wakeup.
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
hw/mem/cxl_type3.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index 5bf0cffb72..1fe4d56762 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -1326,6 +1326,16 @@ MemTxResult cxl_type3_write(PCIDevice *d, hwaddr host_addr, uint64_t data,
return address_space_write(as, dpa_offset, attrs, &data, size);
}
+static void ct3d_clear_poison_list(CXLPoisonList *list)
+{
+ CXLPoison *ent, *next;
+
+ QLIST_FOREACH_SAFE(ent, list, node, next) {
+ QLIST_REMOVE(ent, node);
+ g_free(ent);
+ }
+}
+
static void ct3d_reset_hold(Object *obj, ResetType type)
{
CXLType3Dev *ct3d = CXL_TYPE3(obj);
@@ -1388,6 +1398,14 @@ static void ct3d_reset_hold(Object *obj, ResetType type)
ct3d->cxl_dstate.event_logs[i].irq_enabled = false;
}
ct3d->scan_media_hasrun = false;
+
+ ct3d_clear_poison_list(&ct3d->poison_list);
+ ct3d_clear_poison_list(&ct3d->poison_list_bkp);
+ ct3d_clear_poison_list(&ct3d->scan_media_results);
+ ct3d->poison_list_cnt = 0;
+ cxl_clear_poison_list_overflowed(ct3d);
+
+ memset(&ct3d->set_feat_info, 0, sizeof(ct3d->set_feat_info));
}
static const Property ct3_props[] = {
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread