From nobody Mon Jul 20 10:25:16 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4h3c8r4HYLz6ljr4 for ; Mon, 20 Jul 2026 10:25:16 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4h3c8r2pSFz45FM for ; Mon, 20 Jul 2026 10:25:16 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784543116; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=pHcjl5K/3JsfBWVf9+PPkneYnE0b91jtNhGG+gfNoMQ=; b=ju/L5rgRWHdGnHU4SfaJb/OC7s5lbZLuOH5xva8e2sgKbC9TND6iXyos3PUfKLJoZCE4Ug vwG7ieQmTu6Eu9EGCUavSXjRlEI8tWc7qq1Ibln7VQKkJbdvSgBbnhFnWBLC94uJn8l9x6 1rY2yLbwsLdNum6/UPepmBmZJUV5ThytmUAcfaq2C+DpLiETv0cR5+oV7GIHn0RLYx3HPG Ng0k8PMLZ79umsle/eFs+8FzOFcd20cLfyWD2R/X356x45OobQWIEGNHXvVAmPAN2AdfxH 3fbNMLunscagKPYGyc+772XU8Op2O+38tJcH6Ua0skoathK1pGnYEn56Jh7xlQ== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1784543116; a=rsa-sha256; cv=none; b=VMMMajJb55hF6BaUiKozx/HfWW2I9VKpr6jXKUcaabrB9IZRjqav6E7qa4dD6DLPNr62AI ztmNiMiQUIKrJrrhSw85cYaseVIm8kNQsyN4V01bJSBHr4pr3qa3O+aKmaG8D1P8CFput4 NUoi76hvNyT/mdSUgBwI5uDx1Hm7SmzN6PZCO1ql7I4ICDDz0tMhKurAIhLXz1xB0vlIw6 2Q+7AJOEny1ree9JUEDJgPEGt1xa1A2tslmKfyVNtKen+QtT3jX/O8rT+EU1ZYqsbyJ7C2 cU7l+3WxAVo03XM69zC80h3BKTRVlf3sxh9/DdSELMsdimL6/G5JZ+sLDrqhBw== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784543116; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=pHcjl5K/3JsfBWVf9+PPkneYnE0b91jtNhGG+gfNoMQ=; b=kW++HUBJCeDkNITy8KV72Gqr23uv4pjUeMKXhYgyu8DwgA6AR7EkYpIqqO944BfMmaU/se rJaHVbnZVN1id3tm9aWhAHZ41CbNy2KgeX6OKERtw8EHd/sfscA80Eu3ROYM13/i0t9cpS Wz/TrwDsHTX2hOq8Q0ekMEx92zT6C/QUCFsrv/ofyg8cv3u1fc5wtqvllt0FRS2Vl5prvQ GUfeI9p392elh/soV3/TqCxZM/ow+kFVFsAuX+Ga0C+yeqBb/Sn/Jv6QwEiTFmtkxF5kUz 1rR7qrag5fGXgBTxIhfvNOMQhMmu3WlDN5WMqzkqo8tfkVJVbO+CX/AGCKghRw== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4h3c8r1f6LzxmG for ; Mon, 20 Jul 2026 10:25:16 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 33323 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 20 Jul 2026 10:25:16 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Alexander Leidinger Subject: git: 86fa065f1862 - main - hwpmc: drain a process-mode PMC's runcount when a live target detaches List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: netchild X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 86fa065f1862f3b638efa1868523878d9db14ada Auto-Submitted: auto-generated Date: Mon, 20 Jul 2026 10:25:16 +0000 Message-Id: <6a5df78c.33323.733bf27@gitrepo.freebsd.org> The branch main has been updated by netchild: URL: https://cgit.FreeBSD.org/src/commit/?id=86fa065f1862f3b638efa1868523878d9db14ada commit 86fa065f1862f3b638efa1868523878d9db14ada Author: Alexander Leidinger AuthorDate: 2026-07-19 11:39:27 +0000 Commit: Alexander Leidinger CommitDate: 2026-07-20 10:24:46 +0000 hwpmc: drain a process-mode PMC's runcount when a live target detaches A process-mode PMC's runcount tracks how many CPUs currently have it loaded in hardware. It is decremented only by the context-switch-out and process-exit reclaim paths, both of which the scheduler invokes only for processes flagged P_HWPMC. Detaching a target that still has the PMC live in hardware dropped the target and cleared P_HWPMC without taking the PMC off the hardware or dropping the runcount reference, so the reference leaked. A subsequent release then spun in pmc_wait_for_pmc_idle() forever waiting for the runcount to reach zero: on an INVARIANTS kernel this panics ("waiting too long for pmc to be free"), otherwise it is an unkillable loop holding the hwpmc lock. Any process able to allocate a PMC can trigger this by attaching a counting PMC to itself and detaching it before releasing. Take the PMC off the hardware and drop the runcount reference as part of detaching, before P_HWPMC is cleared: reclaim it from the detaching thread's own CPU directly, and, when the detach removes the PMC's last target, wait for any references held by the target's other threads to drain while P_HWPMC is still set (they can no longer reload it). Reviewed by: adrian MFC after: 2 weeks Assisted-by: Claude Code (Fable 5) Differential Revision: https://reviews.freebsd.org/D58342 --- sys/dev/hwpmc/hwpmc_mod.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/sys/dev/hwpmc/hwpmc_mod.c b/sys/dev/hwpmc/hwpmc_mod.c index 086a05657cfe..efc2aefec2b9 100644 --- a/sys/dev/hwpmc/hwpmc_mod.c +++ b/sys/dev/hwpmc/hwpmc_mod.c @@ -225,6 +225,8 @@ static int pmc_detach_one_process(struct proc *p, struct pmc *pm, static void pmc_destroy_owner_descriptor(struct pmc_owner *po); static void pmc_destroy_pmc_descriptor(struct pmc *pm); static void pmc_destroy_process_descriptor(struct pmc_process *pp); +static void pmc_reclaim_pmc_from_cpu(struct pmc *pm, + struct pmc_process *pp, int cpu); static struct pmc_owner *pmc_find_owner_descriptor(struct proc *p); static int pmc_find_pmc(pmc_id_t pmcid, struct pmc **pm); static struct pmc *pmc_find_pmc_descriptor_in_process(struct pmc_owner *po, @@ -1241,6 +1243,21 @@ pmc_detach_one_process(struct proc *p, struct pmc *pm, int flags) if (pp->pp_pmcs[ri].pp_pmc != pm) return (EINVAL); + /* + * If this is a process-virtual PMC that is still loaded on the + * hardware of the CPU we are running on (the common case when a + * process detaches a PMC from itself), take it off and drop its + * runcount reference now. The reference is otherwise only + * dropped by the switch-out reclaim, which the scheduler stops + * calling once P_HWPMC is cleared below - leaking it and later + * wedging pmc_wait_for_pmc_idle() at release time. + */ + if (PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm))) { + critical_enter(); + pmc_reclaim_pmc_from_cpu(pm, pp, curthread->td_oncpu); + critical_exit(); + } + pmc_unlink_target_process(pm, pp); /* Issue a detach entry if a log file is configured */ @@ -1259,6 +1276,22 @@ pmc_detach_one_process(struct proc *p, struct pmc *pm, int flags) if (pp->pp_refcnt != 0) /* still a target of some PMC */ return (0); + /* + * This detach removed the process' last PMC and we are about to + * clear P_HWPMC. If the detached PMC was its last target and is + * still loaded on other CPUs (e.g. sibling threads of a + * multi-threaded target, or a target running on another CPU), + * drain those references first: the target is already unlinked so + * it cannot reload the PMC, and P_HWPMC is still set so those + * CPUs' switch-out reclaim still runs. Bounded by the target + * threads being scheduled out. + */ + if (PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)) && + LIST_EMPTY(&pm->pm_targets)) { + while (counter_u64_fetch(pm->pm_runcount) > 0) + pmc_force_context_switch(); + } + pmc_remove_process_descriptor(pp); if (flags & PMC_FLAG_REMOVE) @@ -1612,6 +1645,61 @@ pmc_delta(const struct pmc_classdep *pcd, pmc_value_t newvalue, return (delta); } +/* + * Take a process-virtual PMC off the hardware of 'cpu' if it is + * currently loaded there for process 'pp', accumulating its final + * count and dropping its runcount reference. This is the same reclaim + * that context switch out and process exit perform, factored out so it + * can also run when a target is detached while the PMC may still be + * live: the runcount reference is decremented by the switch-out reclaim + * only, which the scheduler gates on P_HWPMC, so a detach that clears + * P_HWPMC without draining would leak the reference and later wedge + * pmc_wait_for_pmc_idle(). Must be called in a critical section. + */ +static void +pmc_reclaim_pmc_from_cpu(struct pmc *pm, struct pmc_process *pp, int cpu) +{ + struct pmc_classdep *pcd; + struct pmc *phw_pm; + pmc_value_t newvalue, tmp; + u_int adjri, ri; + + ri = PMC_TO_ROWINDEX(pm); + pcd = pmc_ri_to_classdep(md, ri, &adjri); + + /* Only reclaim if this PMC is actually loaded on this CPU. */ + phw_pm = NULL; + (void)(*pcd->pcd_get_config)(cpu, adjri, &phw_pm); + if (phw_pm != pm) + return; + + KASSERT(counter_u64_fetch(pm->pm_runcount) > 0, + ("[pmc,%d] pm=%p runcount %ju", __LINE__, pm, + (uintmax_t)counter_u64_fetch(pm->pm_runcount))); + + if (pm->pm_pcpu_state[cpu].pps_cpustate) { + pm->pm_pcpu_state[cpu].pps_cpustate = 0; + if (pm->pm_pcpu_state[cpu].pps_stalled == 0) { + (void)pcd->pcd_stop_pmc(cpu, adjri, pm); + + if (PMC_TO_MODE(pm) == PMC_MODE_TC) { + (void)pcd->pcd_read_pmc(cpu, adjri, pm, + &newvalue); + tmp = pmc_delta(pcd, newvalue, + PMC_PCPU_SAVED(cpu, ri)); + + mtx_pool_lock_spin(pmc_mtxpool, pm); + pm->pm_gv.pm_savedvalue += tmp; + pp->pp_pmcs[ri].pp_pmcval += tmp; + mtx_pool_unlock_spin(pmc_mtxpool, pm); + } + } + } + + counter_u64_add(pm->pm_runcount, -1); + (void)pcd->pcd_config_pmc(cpu, adjri, NULL); +} + /* * Thread context switch OUT. */