From nobody Mon Sep 28 23:51:12 2026 Received: from mail-pj1-f50.google.com (mail-pj1-f50.google.com [209.85.216.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E67D235F609 for ; Fri, 14 Aug 2026 14:49:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719002; cv=none; b=gs83RBKwD+c1r3xKX0JqpftrsiyIYmj8pgg+h0X8k2fggM2kLoXFUlo9Oi1dlzLZQ6/uL4OAzZ2nPcN0YkMBdFO/vv/+RApdGyTHVrq+a+d/8HAbPvKr6yw28moFNf2GKKF19sQ0aAJEZHvhzLdOBFCPxvaN53+rFt6eSXvMaGY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719002; c=relaxed/simple; bh=/G6o9JLaKwM+MWYO5ioYvbKdLCB6G/l0+lyvvQiDPVA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OvVIfCKUOm6DVwopAvl7Q0FH52uTrIUP1jEWZjwPdHtAv3+WJBEqt9zJhxeUvSaWpocQxBn7Vymz/gnym/HlKjWSlJdj+gsYxsVwzVOFiSXqHCrHuRxfmTQN56cIgcLrsxsLi55dpwVBsy0EPiARRMBLTGAFcU44062heqzhccU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MCAJ/nBz; arc=none smtp.client-ip=209.85.216.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MCAJ/nBz" Received: by mail-pj1-f50.google.com with SMTP id 98e67ed59e1d1-384930ca5e2so1235382a91.3 for ; Fri, 14 Aug 2026 07:49:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786718998; x=1787323798; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=VA+T8Kw/D1l+kXKHMVf0JC+Q03Y5VzQaUlRgdX/Okfc=; b=MCAJ/nBz/powPMzXZqnd3jzk8g2VgSNPeMJuSbAi/ev44KSF7921TGaKJCkgArvWzV Nb/b6BZGkFRkN1yy9HsJi6Yu2dU/ksBVdEfUzM2Cl063eZHn9wmthJbRFysYMw2BW4d3 ogBPmYk8G6DJA5EwPrRpl0OlsR1u4fCRjUKFVPcYho4bA6sDgcX0C1Fy5G21lyzk2fFd cW8avnhfM7aieUwcdtEjTty5OdZiqfzq8lmbaHj5BHBzryulRS68kXyndwXQWdSzdSMS DIb85MJvy1ZMghB9rdY640yaH5xh1++jSeGWLYn/co33RWZYqy/z/183xefPcVMfmMOz NgFg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786718998; x=1787323798; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=VA+T8Kw/D1l+kXKHMVf0JC+Q03Y5VzQaUlRgdX/Okfc=; b=fAlW2+Zy4uMHxwtnbW30A0BsXdEzZSZDjLAEERFJFiXrQWKDqMOen0LjvxbvCGTylh +4RvE8UHzRzRWNIs3Q8pjbmGwWsN+JkFWNuK+eWd5nSe0rg9pqzGey8oxXhk7l/CTJvA UgD1BZOIlnuv7ZAAEnsPLU3C7kyh48rHbhpEet8IpQ6QU0Qy5OrLrzLYdxzE8oahwNmf yDT2DQ8mWTDqWkSzfKu14JcvjguoXUSMA3RS/gVKmals1+fZgNhGXMe1S4g+7+u6/m62 fRgX+5lFNTmM1lVH0gcy2QfgfZwQ2JlM34nMCssyxA3GwUKtlv2bowKBFp0TrFh5cKdN kIjw== X-Forwarded-Encrypted: i=1; AHgh+RrrpGakfalCDKcLTA1sLba6FhcRXohXvN6jLng5bIuQlcJaY+MVfu+xwISdE9gCIeTfYGa0IpSjCgEvHk0=@vger.kernel.org X-Gm-Message-State: AOJu0YwYr4n4M1wydZRFIhexT85ywoQRuoUB9ke8I9U7iYWA9ahIANhP gs5x/uSTrLrQtmMnK5ORlaimtRn2JVhM7qYu2mTdOMvuyRoGIeqIVpjr X-Gm-Gg: AR+sD137mdsfYpsqUMWlOGvfEblHH5OgijqRDbx32mCrbGKEIQtoxYtgjPPkAuSWpTN O0xQZLN7o3Z5ttEVLJKPAebVU5aOn4bGWz+m1HO0LPtEPkvx9HKs+7AELQYBt2zMo4+XZwVKOl5 cfyc9PFqP4q5jkISjunV7BXgoagy8lWQ9VQw5WohgrKJBH0rldBZfeYwVlUYi4ZsPyHGTeKNjw9 zFjCy7RAScRtIdyd76MBLNbiqvJcUdPSFqEGm9Xu3PhE7kaL7TAz3VGpFCRmZ3W7dPBWTyKJgcJ 5VbLpaLjTKnzRPFO6J+dCA3XMM+9KxSrsUJMV98ZDfPAjKV8SC6u4aE0uTtmySrfi2U40VHRpid giOfqwRlimv8FCXVwkXvU/fDWsntzAS8MYTHin1SgrXbk8x2FCinppJ+auoSeFyhRFZjuA/geUJ 4w94VCHZJIiYQZvkyByVVS5wdcT5JsuUL1QIUsErHah3NT2+B1cIypm0TugGNLD16MOmE+3SZJl ceaIiw= X-Received: by 2002:a17:90a:e7c1:b0:36a:8240:2477 with SMTP id 98e67ed59e1d1-3933ed1ba09mr6645950a91.19.1786718997674; Fri, 14 Aug 2026 07:49:57 -0700 (PDT) Received: from kernel.tail6741c6.ts.net ([116.128.244.169]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3933b626d4esm1518950a91.2.2026.08.14.07.49.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Aug 2026 07:49:57 -0700 (PDT) From: Kunwu Chan X-Google-Original-From: Kunwu Chan To: corbet@lwn.net, skhan@linuxfoundation.org, peterz@infradead.org, mingo@redhat.com, acme@kernel.org, namhyung@kernel.org, mark.rutland@arm.com, alexander.shishkin@linux.intel.com, jolsa@kernel.org, irogers@google.com, adrian.hunter@intel.com, james.clark@linaro.org, kunwu.chan@gmail.com, lianux.mm@gmail.com Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, linux-kselftest@vger.kernel.org, sj@kernel.org Subject: [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events Date: Fri, 14 Aug 2026 22:49:17 +0800 Message-ID: <20260814144927.489172-2-kunwu.chan@linux.dev> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260814144927.489172-1-kunwu.chan@linux.dev> References: <20260814144927.489172-1-kunwu.chan@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Kunwu Chan Add an in-kernel AUX owner reference and setup/release helpers for kernel-created perf events that need an AUX buffer without a userspace mmap. The setup path validates that the event is a kernel event with no parent, rejects non-power-of-two page counts and negative watermark values, allocates the perf buffer and AUX pages, records the kernel owner, and attaches the buffer through the existing mmap-serialized helper. The release path stops AUX writers, frees AUX storage, drops the event->rb reference via ring_buffer_put(), and detaches only buffers owned by this API. Keep aux_mmap_count dedicated to userspace mappings. perf_aux_output_begin() accepts a writer while either a userspace or kernel owner remains, preserving the existing teardown ordering. Co-developed-by: Lian Wang (Processmission) Signed-off-by: Lian Wang (Processmission) Signed-off-by: Kunwu Chan --- include/linux/perf_event.h | 11 +++ kernel/events/core.c | 139 ++++++++++++++++++++++++++++++++++++ kernel/events/internal.h | 1 + kernel/events/ring_buffer.c | 11 +-- 4 files changed, 158 insertions(+), 4 deletions(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 48d851fbd8ea..4070a725d21f 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1254,6 +1254,17 @@ perf_event_create_kernel_counter(struct perf_event_a= ttr *attr, perf_overflow_handler_t callback, void *context); =20 +/* + * AUX ring-buffer support for kernel-created events (no user mmap). + * perf_event_setup_aux() allocates the buffer the PMU writes into via + * perf_aux_output_begin()/perf_aux_output_end(); the paired + * perf_event_release_aux() must be called before + * perf_event_release_kernel(). + */ +extern int perf_event_setup_aux(struct perf_event *event, int nr_pages, + long watermark); +extern void perf_event_release_aux(struct perf_event *event); + extern void perf_pmu_migrate_context(struct pmu *pmu, int src_cpu, int dst_cpu); extern int perf_event_read_local(struct perf_event *event, u64 *value, diff --git a/kernel/events/core.c b/kernel/events/core.c index 4638544205f2..da6225ca144a 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7122,6 +7122,145 @@ static void perf_mmap_close(struct vm_area_struct *= vma) ring_buffer_put(rb); /* could be last */ } =20 +/* + * perf_event_setup_aux()/perf_event_release_aux() - AUX buffer support for + * kernel-created perf events (perf_event_create_kernel_counter()). + * + * perf_mmap()/perf_mmap_close() build and tear down AUX buffers for + * user-space events; kernel consumers (e.g. DAMON's ARM SPE backend) have= no + * mmap, so they need this symmetric pair to get a buffer the PMU can write + * into via perf_aux_output_begin()/perf_aux_output_end(). + * + * The buffer holds an AUX owner reference (aux_kernel_count =3D 1) so + * perf_aux_output_begin() admits writers and so it is torn down here + * rather than by perf_mmap_close(). perf_event_release_aux() also detach= es + * the event's normal ring-buffer reference before the event is released. + */ + +/** + * perf_event_setup_aux() - Allocate an AUX ring buffer for an event. + * @event: Kernel-created perf event (must not have an rb yet). + * @nr_pages: AUX buffer size in pages; power of two, >=3D 1. + * @watermark: AUX watermark; 0 selects the perf default (half the buffer). + * + * The buffer is non-overwrite streaming (RING_BUFFER_WRITABLE): the PMU + * pauses when the buffer is full until the consumer advances the tail. + * Must be called before the event is enabled. The PMU's ->setup_aux() + * callback (e.g. arm_spe_pmu_setup_aux) validates the size and maps the + * pages for hardware writes. The ring buffer is allocated with zero + * data pages, so PERF_RECORD_AUX events are not recorded; this is + * intentional for kernel consumers that drain trace data directly. + * Returns 0 on success, -errno otherwise. + */ +int perf_event_setup_aux(struct perf_event *event, int nr_pages, + long watermark) +{ + struct perf_buffer *rb; + int ret; + + if (!is_kernel_event(event) || event->parent || + !is_power_of_2(nr_pages) || watermark < 0) + return -EINVAL; + + mutex_lock(&event->mmap_mutex); + if (event->rb) { + ret =3D -EBUSY; + goto out_unlock; + } + + rb =3D rb_alloc(0, 0, event->cpu, 0); + if (!rb) { + ret =3D -ENOMEM; + goto out_unlock; + } + + /* AUX area sits right after the user (control) page. */ + ret =3D rb_alloc_aux(rb, event, 1, nr_pages, watermark, + RING_BUFFER_WRITABLE); + if (ret) + goto err_put; + + /* + * No user-space mmap exists for this buffer; record an in-kernel + * AUX owner (aux_kernel_count) instead so perf_aux_output_begin() + * admits writers and perf_event_release_aux() performs the teardown + * that perf_mmap_close() would otherwise do. + */ + refcount_set(&rb->aux_kernel_count, 1); + + /* Transfer the allocation reference to the event. */ + ring_buffer_attach(event, rb); + mutex_unlock(&event->mmap_mutex); + + return 0; + +err_put: + ring_buffer_put(rb); +out_unlock: + mutex_unlock(&event->mmap_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(perf_event_setup_aux); + +/** + * perf_event_release_aux() - Tear down an AUX buffer for an event. + * @event: Event with an AUX buffer allocated by perf_event_setup_aux(). + * + * Stops any active AUX writers, frees the AUX pages, and detaches the ring + * buffer from the event. Must be called before perf_event_release_kernel= (). + * Safe to call with a missing rb (no-op). A ring buffer not allocated by + * perf_event_setup_aux() is left attached. + * + * The caller must quiesce AUX consumers before releasing the buffer; + * active AUX references at release time indicate a violated lifetime + * contract (see the aux_refcount WARN below). + */ +void perf_event_release_aux(struct perf_event *event) +{ + struct perf_buffer *rb; + + if (!is_kernel_event(event) || event->parent) + return; + + rb =3D ring_buffer_get(event); + if (!rb) + return; + + /* Do not detach a ring buffer that this API does not own. */ + if (!rb_has_aux(rb) || !refcount_read(&rb->aux_kernel_count)) + goto out_put; + + if (refcount_dec_and_mutex_lock(&rb->aux_kernel_count, &rb->aux_mutex)) { + /* + * Stop all AUX events writing to this buffer so the pages + * can be freed; after aux_kernel_count drops to zero they + * won't start any more (see perf_aux_output_begin()). + */ + perf_pmu_output_stop(event); + + rb_free_aux(rb); + WARN_ON_ONCE(refcount_read(&rb->aux_refcount)); + mutex_unlock(&rb->aux_mutex); + } + + /* + * Detach the ring buffer from the event. This runs even if the + * refcount_dec_and_mutex_lock above lost the race to another + * concurrent release caller; the mmap_mutex serialisation and the + * event->rb =3D=3D rb check ensure only one caller performs the detach. + */ + mutex_lock(&event->mmap_mutex); + if (event->rb =3D=3D rb) { + ring_buffer_attach(event, NULL); + ring_buffer_put(rb); /* drop the event->rb reference */ + } + mutex_unlock(&event->mmap_mutex); + +out_put: + ring_buffer_put(rb); /* the temporary reference from ring_buffer_get() */ +} +EXPORT_SYMBOL_GPL(perf_event_release_aux); + static vm_fault_t perf_mmap_pfn_mkwrite(struct vm_fault *vmf) { /* The first page is the user control page, others are read-only. */ diff --git a/kernel/events/internal.h b/kernel/events/internal.h index c03c4f2eea57..7bcb60827514 100644 --- a/kernel/events/internal.h +++ b/kernel/events/internal.h @@ -48,6 +48,7 @@ struct perf_buffer { int aux_nr_pages; int aux_overwrite; refcount_t aux_mmap_count; + refcount_t aux_kernel_count; /* in-kernel AUX owner */ unsigned long aux_mmap_locked; void (*free_aux)(void *); refcount_t aux_refcount; diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index 9fe92161715e..be57007a1b79 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -395,14 +395,17 @@ void *perf_aux_output_begin(struct perf_output_handle= *handle, goto err; =20 /* - * If aux_mmap_count is zero, the aux buffer is in perf_mmap_close(), - * about to get freed, so we leave immediately. + * If no AUX owner remains, the buffer is in perf_mmap_close() or + * perf_event_release_aux(), about to get freed, so we leave + * immediately. aux_mmap_count tracks user-space mmap owners; + * aux_kernel_count tracks in-kernel owners (perf_event_setup_aux()). * - * Checking rb::aux_mmap_count and rb::refcount has to be done in + * Checking the AUX owner counts and rb::refcount has to be done in * the same order, see perf_mmap_close. Otherwise we end up freeing * aux pages in this path, which is a bug, because in_atomic(). */ - if (!refcount_read(&rb->aux_mmap_count)) + if (!refcount_read(&rb->aux_mmap_count) && + !refcount_read(&rb->aux_kernel_count)) goto err; =20 if (!refcount_inc_not_zero(&rb->aux_refcount)) --=20 2.43.0 From nobody Mon Sep 28 23:51:12 2026 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 774C8371885 for ; Fri, 14 Aug 2026 14:50:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719011; cv=none; b=danLfExfnabGoDt5PfoPan54Mx2DNiCQPDiwecBdA92/b01y0pte7kmm5/PfflBZ0dl8Yy1pCOl3WhZx1j/wCIOBIa25+DY1wiqBzKS9jF3lBn5xTjZvB+X5o2XStgd/GS1ZSv8kn2pZ//F3BZOvNV6WyQljQAqzij65ioTYVbI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719011; c=relaxed/simple; bh=Itv9jQ+cgGREh7F3gB7lH8k0MiTigkyXyaA+k+YNoBo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ekTyMtoHs37NSBhd9rz8cR5lMDFGqmDMlwIcVwifzGdH9PvRfaBWmkotB1tWCpZAQhi1SIndqhudfOreeAbSWoSJH7vDn17qIepaKiSi8CHE+3RuDRqcUhmkrW7T4PrjTW2KJrosR/0SjCQmHbPriYoPnaUXvkpGhAgdSQSxLpE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=IF/xIHUM; arc=none smtp.client-ip=209.85.215.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="IF/xIHUM" Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-cbe6295f05bso1606396a12.1 for ; Fri, 14 Aug 2026 07:50:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786719008; x=1787323808; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=smhmlaujxPVhf53l9bjkItlTOWCewUbQLVL+UUzYPD8=; b=IF/xIHUMXKzYzpKmqrU6DomAx98F2w6HdGe3QWZKn0PtcBIGjP/JuAJdLeWWpFw3FN 48aRym2PP1zusv1jComkJ87C0PXGLohUEC6DcTni+q2qbBTJB28mC0FLuU4nASmeB6H9 uqO2wmzV9Zg3o3/SbdE5nMfa54dGLHcCyYoImL0VdxvsSnxoX60DaBDbVtWp2ERZeKF/ dEsBvuZedEGkXbzM+KYuhkLRpeVjBz5tIY+GI2Cf82rduiTEhtijpgAp3Kh1fN+SylbQ XlxnQfV4U8avMlYczHrsdqux3RQ1XRNPmxtog6nAaWpHUcp1TLZICB3297Qlctl4dGXT 9esA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786719008; x=1787323808; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=smhmlaujxPVhf53l9bjkItlTOWCewUbQLVL+UUzYPD8=; b=FAJ2XYFMkxf3qLFkzw7sEZKn1ayklgPyeE07IQ3TzO8MaNU8vqCmR5EMHZL7z7MFfG +Y98UVwnXYc8l3Yavw9dMk3QpQUIROVg8eUiDsIigSPYmAkFbLi2EGKByHBCV+Lcbhy/ Z3WFz1PobiMzvOhXkmWeVpdypQGNSzi5PLMn/sS81A0Db7q4rnlteiTnPzBf7NWDg1RK iYYBA4FOirslxpUIkQ/4I8XuRh6AApLkVfQcSoxXS2Wk9xq4jHnNgIVKQpusG4dEJNp+ WKKFW8CWsfdjwl+6lXhREJCtlgk6dNu5KD67BKdX2z42vj9uhZN4QtZCeBzuPJb8SGib Da+g== X-Forwarded-Encrypted: i=1; AHgh+RqxXscMUeC3FbPvWTWIw1hZ1W1b5oN7gGoxUPmKZ3XDRJ0DKOdsVvpQDh4E17zNXGmOPc7sA/qWY4hRPqk=@vger.kernel.org X-Gm-Message-State: AOJu0Ywb3m8jfwGjZggRbW5oJn3Wx3VxHhTJfR5pFnCgnTZFkOCj9zj9 YGyk4qQupkRqLwRtdpteflMv9m44J7p44X6trmBWZ8IV3GVhKH+45Xx0 X-Gm-Gg: AR+sD10vZpIdAtBDGLems3TEnZpUMShc9T1w936URFxFMuC9GWNTvikJ3irw0H9pPKK cyXpaiFHjVEaH0DcarZogGPsl16iqSG0Ud7VOwFuugrd3IxYOShYT6g6gWJU5fdrndoyKwhqGIH lvC1PbM8GwTCSPUlTv+5nSmKnL5L36H8c4PTmpXh9jiOHGhZTcy4XUmXx7ax3EsuTjh7DETXKEk qt3GvFcyym9fVB3CiOYMOWDUiHdHy3M/iuuS7FCxYUrU+IVQPB/yHWX5h1fRLvPDfD//g2Evd+H 5cJivYMRAkNr6Qlmm0Db09rKlhPMalrJRDWWP1ugI/INbwYt5jsK5QMqAGFZ0gkLcQlui6GDUtp sJgz4eKcmWcfo+RTBc9bG5K2w27s6ojDajJ6cmIpQ6zIiGNtPu0BAzmJduRKBaM7swec6JjwtcN Air1WIQP8ZcsFv7WcXDrcAtvLXnPkAIbgXtWDpEKvRzDuUfXcetVQ1FCddWgd5lL6XgfQeAX6yO SvEUM8UHrMS X-Received: by 2002:a17:90b:1b43:b0:38e:9ca8:e99 with SMTP id 98e67ed59e1d1-3933af8b5d7mr4735892a91.5.1786719007234; Fri, 14 Aug 2026 07:50:07 -0700 (PDT) Received: from kernel.tail6741c6.ts.net ([116.128.244.169]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3933b626d4esm1518950a91.2.2026.08.14.07.49.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Aug 2026 07:50:06 -0700 (PDT) From: Kunwu Chan X-Google-Original-From: Kunwu Chan To: corbet@lwn.net, skhan@linuxfoundation.org, peterz@infradead.org, mingo@redhat.com, acme@kernel.org, namhyung@kernel.org, mark.rutland@arm.com, alexander.shishkin@linux.intel.com, jolsa@kernel.org, irogers@google.com, adrian.hunter@intel.com, james.clark@linaro.org, kunwu.chan@gmail.com, lianux.mm@gmail.com Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, linux-kselftest@vger.kernel.org, sj@kernel.org Subject: [RFC PATCH 2/5] perf/core: add AUX ring accessors for kernel consumers Date: Fri, 14 Aug 2026 22:49:18 +0800 Message-ID: <20260814144927.489172-3-kunwu.chan@linux.dev> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260814144927.489172-1-kunwu.chan@linux.dev> References: <20260814144927.489172-1-kunwu.chan@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Kunwu Chan Add kernel-consumer accessors for the published AUX producer head, the consumer tail, and copying a possibly wrapped AUX interval without exposing perf's internal page array. Use the same memory ordering as mmap consumers: order data reads after the published head with smp_rmb() and order completed data reads before advancing the tail with smp_mb(). Validate cursor distances in the absolute cursor domain before applying the ring mask, so rewound, already-consumed, and future windows are rejected even across unsigned cursor wrap. Hold an AUX reference while copying so storage cannot disappear under the consumer. Restrict the helpers to buffers owned by the kernel AUX setup API. Document the API in Documentation/userspace-api/perf_ring_buffer.rst (=C2=A73.4). Co-developed-by: Lian Wang (Processmission) Signed-off-by: Lian Wang (Processmission) Signed-off-by: Kunwu Chan --- .../userspace-api/perf_ring_buffer.rst | 56 ++++- include/linux/perf_event.h | 7 + kernel/events/ring_buffer.c | 194 ++++++++++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) diff --git a/Documentation/userspace-api/perf_ring_buffer.rst b/Documentati= on/userspace-api/perf_ring_buffer.rst index dc71544532ce..8333d2791878 100644 --- a/Documentation/userspace-api/perf_ring_buffer.rst +++ b/Documentation/userspace-api/perf_ring_buffer.rst @@ -26,6 +26,7 @@ Perf ring buffer 3.1 The relationship between AUX and regular ring buffers 3.2 AUX events 3.3 Snapshot mode + 3.4 Kernel-consumer AUX buffer access =20 =20 1. Introduction @@ -827,4 +828,57 @@ mode. | AUX Ring buffer 3 | <- aux_head +---------------------------------------+ =20 - Figure 9. Snapshot with system wide mode + Figure 9. Snapshot with system wide mode + +3.4 Kernel-consumer AUX buffer access +------------------------------------- + +The AUX ring buffer is normally consumed from user space via mmap() +on the perf event fd. Some tracing PMUs (e.g. ARM SPE) write trace +records directly to the AUX buffer without generating a +perf_event_overflow() callback for each record. A kernel consumer +therefore needs to own and drain the AUX buffer itself rather than +rely on the overflow path. + +The perf core provides five exported functions for in-kernel +consumers that do not have a user-space mmap: + + - ``perf_event_setup_aux(event, nr_pages, watermark)`` =E2=80=94 allocate + an AUX ring buffer for a kernel-created perf event. The event + must be created by ``perf_event_create_kernel_counter()``; it + must not have a parent or an existing ring buffer. ``nr_pages`` + must be a power of two and ``watermark`` must be non-negative + (0 selects half the buffer). + + - ``perf_event_release_aux(event)`` =E2=80=94 tear down the AUX buffer + allocated by ``perf_event_setup_aux()``. Must be called before + ``perf_event_release_kernel()``. Safe to call on an event that + never had an AUX buffer (no-op). A ring buffer not owned by the + kernel AUX API is left attached. + + - ``perf_event_aux_head(event)`` =E2=80=94 read the published producer + head. Returns 0 if the event has no ring buffer. + + - ``perf_event_aux_tail_set(event, tail)`` =E2=80=94 advance the consumer + tail. The new tail must be within the current ``[old_tail, + head]`` window; an out-of-range tail is rejected with ``-EINVAL``. + + - ``perf_event_aux_copy(event, from, to, buf)`` =E2=80=94 copy a possibly + wrapped AUX interval into a linear buffer. The ``from``/``to`` + cursors are absolute and must lie within ``[tail, head]``. + +The owner reference is tracked by ``aux_kernel_count`` on the +``perf_buffer``, separate from the userspace ``aux_mmap_count``. +``perf_aux_output_begin()`` admits a writer while either owner +count is non-zero, so a kernel consumer and a userspace consumer +on different events for the same PMU do not interfere. + +A userspace mmap and a kernel ``perf_event_setup_aux()`` on the +*same* event cannot coexist: the second call finds ``event->rb`` +already set and returns ``-EBUSY``. + +Memory ordering follows the same protocol as the userspace AUX +mmap consumer: ``perf_event_aux_head()`` uses ``smp_rmb()`` to pair +with the producer's data-write barrier before publishing ``aux_head``, +and ``perf_event_aux_tail_set()`` uses ``smp_mb()`` to order prior +data reads before advancing ``aux_tail``. diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 4070a725d21f..800638f70628 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1265,6 +1265,13 @@ extern int perf_event_setup_aux(struct perf_event *e= vent, int nr_pages, long watermark); extern void perf_event_release_aux(struct perf_event *event); =20 +/* AUX ring accessors for kernel consumers (no user-space mmap). */ +extern unsigned long perf_event_aux_head(struct perf_event *event); +extern int perf_event_aux_tail_set(struct perf_event *event, + unsigned long tail); +extern long perf_event_aux_copy(struct perf_event *event, unsigned long fr= om, + unsigned long to, void *buf); + extern void perf_pmu_migrate_context(struct pmu *pmu, int src_cpu, int dst_cpu); extern int perf_event_read_local(struct perf_event *event, u64 *value, diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index be57007a1b79..6608df879d7f 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -580,6 +580,200 @@ void *perf_get_aux(struct perf_output_handle *handle) } EXPORT_SYMBOL_GPL(perf_get_aux); =20 +/* + * perf_event_aux_head()/perf_event_aux_tail_set()/perf_event_aux_copy() - + * AUX ring accessors for kernel consumers (e.g. DAMON's ARM SPE backend). + * + * The write cursor rb->aux_head is maintained by the PMU driver via + * perf_aux_output_end(), which also publishes it to user_page->aux_head; + * the consumer cursor lives in user_page->aux_tail (same absolute cursor + * domain). The kernel consumer has no mmap, so these are the counterpart + * of the user-space mmap protocol. + * + * As in the user-space protocol, data visibility is the producer's duty: + * the PMU driver must make AUX data visible before calling + * perf_aux_output_end(), which then publishes the new head. Kernel + * consumers use the same read- and full-barrier ordering as mmap consumer= s. + */ + +static bool rb_has_kernel_aux(struct perf_buffer *rb) +{ + return rb_has_aux(rb) && refcount_read(&rb->aux_kernel_count); +} + +/** + * perf_event_aux_head() - Return the event's absolute AUX write cursor. + * @event: Event with an AUX buffer (perf_event_setup_aux()). + * + * Returns the current rb->aux_head, or 0 if the event has no ring buffer. + */ +unsigned long perf_event_aux_head(struct perf_event *event) +{ + struct perf_buffer *rb =3D ring_buffer_get(event); + unsigned long head =3D 0; + + if (rb && rb_has_kernel_aux(rb)) { + head =3D READ_ONCE(rb->user_page->aux_head); + /* Pairs with the producer's AUX-data write barrier. */ + smp_rmb(); + } + if (rb) + ring_buffer_put(rb); + return head; +} +EXPORT_SYMBOL_GPL(perf_event_aux_head); + +/** + * perf_event_aux_tail_set() - Advance the event's AUX consumer cursor. + * @event: Event with an AUX buffer. + * @tail: New absolute tail; must be within the current AUX window. + * + * Frees the consumed space so perf_aux_output_begin() can compute space + * again for the PMU writer. The new tail must refer to a valid position + * within the current AUX window (the last ring of produced data); an + * out-of-window tail would corrupt the free-space computation in + * perf_aux_output_begin() and let the producer overwrite unconsumed data. + * + * If a non-overwrite buffer becomes full, the producer may be stopped; + * advancing the tail alone does not resume it, and the consumer is + * responsible for re-enabling the event if needed (as user-space AUX + * consumers do). + * + * Returns 0 on success, -ENOENT if the event has no ring buffer, -EINVAL + * on an out-of-range tail. + */ +int perf_event_aux_tail_set(struct perf_event *event, unsigned long tail) +{ + struct perf_buffer *rb =3D ring_buffer_get(event); + unsigned long advance, aux_size, head, old_tail; + int ret =3D -EINVAL; + + if (!rb) + return -ENOENT; + + if (!rb_has_kernel_aux(rb)) { + ret =3D -ENOENT; + goto out; + } + + aux_size =3D (unsigned long)rb->aux_nr_pages << PAGE_SHIFT; + old_tail =3D READ_ONCE(rb->user_page->aux_tail); + /* + * Pairs with the producer's AUX-data write barrier in + * perf_aux_output_end() before it publishes aux_head. + */ + smp_rmb(); + head =3D READ_ONCE(rb->user_page->aux_head); + advance =3D tail - old_tail; + + /* Modular distances keep the check valid when a cursor wraps. */ + if (head - old_tail <=3D aux_size && advance <=3D head - old_tail) { + /* Order all prior AUX data reads before releasing the space. */ + smp_mb(); + WRITE_ONCE(rb->user_page->aux_tail, tail); + ret =3D 0; + } + +out: + ring_buffer_put(rb); + return ret; +} +EXPORT_SYMBOL_GPL(perf_event_aux_tail_set); + +/** + * perf_event_aux_copy() - Copy an AUX window into a linear buffer. + * @event: Event with an AUX buffer. + * @from: Absolute start cursor (inclusive). + * @to: Absolute end cursor (exclusive). + * @buf: Destination; must hold (to - from) bytes. + * + * Handles wrap-around within the ring. Returns the number of bytes + * copied, or -errno. The window must not exceed the ring size; this is + * enforced below, so a buggy consumer cannot silently read garbage. + */ +long perf_event_aux_copy(struct perf_event *event, unsigned long from, + unsigned long to, void *buf) +{ + struct perf_buffer *rb =3D ring_buffer_get(event); + unsigned long aux_size, available, head, len, start, tail, tocopy; + long ret; + + if (!rb) + return -ENOENT; + + if (!rb_has_kernel_aux(rb)) { + ret =3D -ENOENT; + goto out; + } + + /* + * AUX storage may be referenced from both producer and consumer + * contexts (see rb_alloc_aux): a concurrent perf_event_release_aux() + * may free it once the last writer drops the AUX reference. Keep + * it alive for the duration of the copy. + */ + if (!refcount_inc_not_zero(&rb->aux_refcount)) { + ret =3D -ENOENT; + goto out; + } + + if (!buf) { + ret =3D -EINVAL; + goto out_aux; + } + + aux_size =3D (unsigned long)rb->aux_nr_pages << PAGE_SHIFT; + tail =3D READ_ONCE(rb->user_page->aux_tail); + head =3D READ_ONCE(rb->user_page->aux_head); + /* Pairs with the producer's AUX-data write barrier. */ + smp_rmb(); + available =3D head - tail; + start =3D from - tail; + len =3D to - from; + + /* + * Validate modular distances before masking. The requested window + * must be wholly contained in the current [tail, head] interval; + * this rejects rewound, already-consumed, and future cursors while + * remaining correct across unsigned cursor wrap. + */ + if (available > aux_size || start > available || + len > available - start) { + ret =3D -EINVAL; + goto out_aux; + } + if (!len) { + ret =3D 0; + goto out_aux; + } + + from &=3D aux_size - 1; + to &=3D aux_size - 1; + ret =3D 0; + + do { + tocopy =3D PAGE_SIZE - offset_in_page(from); + if (to > from) + tocopy =3D min(tocopy, to - from); + if (!tocopy) + break; + + memcpy(buf + ret, rb->aux_pages[from >> PAGE_SHIFT] + + offset_in_page(from), tocopy); + + ret +=3D tocopy; + from +=3D tocopy; + from &=3D aux_size - 1; + } while (to !=3D from); + +out_aux: + rb_free_aux(rb); +out: + ring_buffer_put(rb); + return ret; +} +EXPORT_SYMBOL_GPL(perf_event_aux_copy); + /* * Copy out AUX data from an AUX handle. */ --=20 2.43.0 From nobody Mon Sep 28 23:51:12 2026 Received: from mail-pj1-f43.google.com (mail-pj1-f43.google.com [209.85.216.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 098173C3456 for ; Fri, 14 Aug 2026 14:50:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719022; cv=none; b=iJgm8Bbv8LSl80jwgmSfAnk7P75DZk3DVweI+hY5mJQ202KMhVS/EVoiahAMW1LVLe5CkfKfiN9scJom5z6QPZ8JEdBabAZLdJbGUH45RZcun7FIOQ7mv3EK2o0yjmU+k5HQYE0R3HhftkolqUyeeSQwdWpvQBBPo6Y07t3yaRU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719022; c=relaxed/simple; bh=xB36dqKrhzKHaDk0Hg7QocVw6mO2aInLchDyf3/cp18=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=eDAzSMIaBsXtwZ2OI8ovweKW0HQ7yK36yPtJddiGoe+1aOf0ExSO/VNrJMTX3CpTg64mG9c/DpO4Zg2IKvOtnHqZWt9KY2zBpK7wmuWZ++WPSKmaU9tYiQM9oTh6PxtqQxtYzcbH2KhlCqxCatutWR3JC78l8t7y3x1CPEltcL0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=jtzMbH+2; arc=none smtp.client-ip=209.85.216.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="jtzMbH+2" Received: by mail-pj1-f43.google.com with SMTP id 98e67ed59e1d1-384930ca5e2so1236208a91.3 for ; Fri, 14 Aug 2026 07:50:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786719017; x=1787323817; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=PcdAjl5RiQESomOWswvjyEahBuirsMGPE322dRowOMI=; b=jtzMbH+22RCeo4KP+N+ve/LWyi4rD9aqXgR5lDl6y9S2pcXJweRDr3kc/c9Yl2tHgQ oEMxTWxYy8lGjmViQ4lt6TQqIe7tF+SLoQPKeuFw/38uLrIbE9aKXQw88dB2L6LL62ny sqWE5kPRTNclHKlVwAJxl864SKuCmWm190y2OT3GFv9gapoX2Y5TkvUudv5mSYKZLbIQ 8nRwCuoOfyE9IjPBtm7/O5OUQAqWDeNjE0qmSgPbL7Tm31CATuoAmFypmkbDLxUUSV0M pgfm29of04kEfJ7kivc41ia6i1rJkJGUj5XQ2eLU8g/Tfjh3hbuU5kdY+h+AepW70LRD 4w/A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786719017; x=1787323817; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=PcdAjl5RiQESomOWswvjyEahBuirsMGPE322dRowOMI=; b=kXU50bYeJ2mOVcHGw7lrFq7c2UWtFZsVpKKynnsQbPIrcwcxI0AC5FV92BZ0PJ88X3 t6n5ipS3HXB548tv/T3zhKJu8xe0SPk5B8VyUR6+HaNXhg4f40pmE3chmhRA/Lp52Mnr 49kw1Znyv/gOPIsacAp/Niaxb2wlzjarC6bG+V2+B9ZSBj6WdhFnUDtylNYZ5gNTCsZV YnLoPUvc7GOe2VyGCYc49nG4t4NRN5Spi91cVcaYo0ehifZFy8gsyK6Pb2ABR3SESu1y oP2ATw1wg3fsaojRWUBmZYYExXDoeevAKyyVGo95PAL/Qr4smU0tpVTcWYuI9xtacZB5 1KEg== X-Forwarded-Encrypted: i=1; AHgh+Rq7lvdNvUniDzgEeF3v+ioZAwhl+MceQsZIxtRINhZ+d8tlw09TMrKs7izTR1dtnpty4IA9Rh3PA42ooZg=@vger.kernel.org X-Gm-Message-State: AOJu0Yzypygc0A2fJPbOhRUBBwhfHdVOIZglXOcaNWNMESTPa5QFPt38 jKFLm8vrayGjNBj8Rjlv3XaYEwUL1Hqz4RxmAfNmEUZ0BeC5Kx6/S5cK X-Gm-Gg: AR+sD10clkkvf/4t148Nr58IKzopgHEuIr04feM8OlRIQVdCjmuMrwxqFE+3/eLArok r6bYxsR7HNZGf/MeY0/Bur/F+gEIj/ym2L8c5vqjeArAudmcor1xikT2lTrLYLh+GHhsorFoY3B qLoRS3Kl1CXcJkh0LK3vnQqEGN9sl42bswBlpxHmabc7HFYzzl5g+BuHijhcmj2iddYl+FYWmsO SduK3+1EQLpgN0nIBJRzAL+UIRfPnFzt00hhj67U4gJ4nI8pBGoF0cuWfhfYq/Sq5sKUEGNXex4 fJCQSjaQ0hg7NBECicNEVqs9luiC5DIXicjRpX755ki1C0/sEEBP1WoPFgs/ceTKT/2rYu0B3u5 RIsZDjcrNjMjWFxWLiMYzD74cy/blj3cY5p+2z07vDlaIFlu8WmEknaLWkvIQQQ7XVst1tMU+gv X0Y7QIMeXTyBpedawc7UL+UxUb3FtjDjwarJLTaw2wmJXQ+fRR1V6xang4cAM54gJg45uR0CoFe kQfe1I= X-Received: by 2002:a17:90a:e7c1:b0:36a:8240:2477 with SMTP id 98e67ed59e1d1-3933ed1ba09mr6649223a91.19.1786719016904; Fri, 14 Aug 2026 07:50:16 -0700 (PDT) Received: from kernel.tail6741c6.ts.net ([116.128.244.169]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3933b626d4esm1518950a91.2.2026.08.14.07.50.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Aug 2026 07:50:16 -0700 (PDT) From: Kunwu Chan X-Google-Original-From: Kunwu Chan To: corbet@lwn.net, skhan@linuxfoundation.org, peterz@infradead.org, mingo@redhat.com, acme@kernel.org, namhyung@kernel.org, mark.rutland@arm.com, alexander.shishkin@linux.intel.com, jolsa@kernel.org, irogers@google.com, adrian.hunter@intel.com, james.clark@linaro.org, kunwu.chan@gmail.com, lianux.mm@gmail.com Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, linux-kselftest@vger.kernel.org, sj@kernel.org Subject: [RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API Date: Fri, 14 Aug 2026 22:49:19 +0800 Message-ID: <20260814144927.489172-4-kunwu.chan@linux.dev> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260814144927.489172-1-kunwu.chan@linux.dev> References: <20260814144927.489172-1-kunwu.chan@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Lian Wang Add a KUnit test suite (perf_aux_kernel) for the perf_event_setup_aux(), perf_event_release_aux(), perf_event_aux_head(), perf_event_aux_tail_set() and perf_event_aux_copy() functions. A self-contained dummy AUX PMU is registered in suite_init so that every test is deterministic and needs no hardware. The dummy setup_aux pre-fills all pages with a recognisable pattern (0xAB); aux_test_produce() advances the published head via perf_aux_output_begin/end, and copy tests verify the pattern. 39 test cases cover setup/release lifecycle, writer admission and blocking, head/tail/copy accessor contract validation (including wrap-around, boundary cursors, and data correctness), buffer-full behaviour, negative watermark rejection, independence of two events on the same PMU, user/kernel coexistence simulation, and concurrent release on the same event. Gated by CONFIG_PERF_AUX_KERNEL_KUNIT_TEST. Co-developed-by: Kunwu Chan Signed-off-by: Kunwu Chan Signed-off-by: Lian Wang --- kernel/events/Makefile | 1 + kernel/events/aux_kernel_test.c | 1144 +++++++++++++++++++++++++++++++ lib/Kconfig.debug | 15 + 3 files changed, 1160 insertions(+) create mode 100644 kernel/events/aux_kernel_test.c diff --git a/kernel/events/Makefile b/kernel/events/Makefile index 91a62f566743..592604db2c76 100644 --- a/kernel/events/Makefile +++ b/kernel/events/Makefile @@ -3,4 +3,5 @@ obj-y :=3D core.o ring_buffer.o callchain.o =20 obj-$(CONFIG_HAVE_HW_BREAKPOINT) +=3D hw_breakpoint.o obj-$(CONFIG_HW_BREAKPOINT_KUNIT_TEST) +=3D hw_breakpoint_test.o +obj-$(CONFIG_PERF_AUX_KERNEL_KUNIT_TEST) +=3D aux_kernel_test.o obj-$(CONFIG_UPROBES) +=3D uprobes.o diff --git a/kernel/events/aux_kernel_test.c b/kernel/events/aux_kernel_tes= t.c new file mode 100644 index 000000000000..0b5f787c5979 --- /dev/null +++ b/kernel/events/aux_kernel_test.c @@ -0,0 +1,1144 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the perf AUX kernel-consumer API. + * + * Tests the exported functions: + * - perf_event_setup_aux() / perf_event_release_aux() + * - perf_event_aux_head() / perf_event_aux_tail_set() / perf_event_aux_= copy() + * + * A self-contained dummy AUX PMU is registered in suite_init so that + * every test is deterministic and requires no hardware. The dummy + * setup_aux pre-fills all pages with a recognisable pattern (0xAB); + * aux_test_produce() advances the published head via + * perf_aux_output_begin/end, and copy tests verify the pattern. + * + * Run via debugfs: + * echo run > /sys/kernel/debug/kunit/perf_aux_kernel/run + * cat /sys/kernel/debug/kunit/perf_aux_kernel/results + */ + +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +/* ---- Dummy AUX PMU ---- */ + +static struct pmu dummy_aux_pmu; +static int dummy_aux_pmu_type =3D -1; + +static int dummy_aux_event_init(struct perf_event *event) +{ + return 0; +} + +static void dummy_aux_event_read(struct perf_event *event) { } + +static int dummy_aux_event_add(struct perf_event *event, int mode) +{ + return 0; +} + +static void dummy_aux_event_del(struct perf_event *event, int mode) { } + +static void dummy_aux_event_start(struct perf_event *event, int mode) { } + +static void dummy_aux_event_stop(struct perf_event *event, int mode) { } + +static void *dummy_aux_setup_aux(struct perf_event *event, void **pages, + int nr_pages, bool overwrite) +{ + int i; + + for (i =3D 0; i < nr_pages; i++) + memset(pages[i], 0xAB, PAGE_SIZE); + + return pages; +} + +static void dummy_aux_free_aux(void *priv) +{ + /* aux_priv is the pages array; __rb_free_aux frees it via kfree. */ +} + +static int dummy_aux_pmu_register(void) +{ + int ret; + + if (dummy_aux_pmu_type >=3D 0) + return 0; + + memset(&dummy_aux_pmu, 0, sizeof(dummy_aux_pmu)); + dummy_aux_pmu.event_init =3D dummy_aux_event_init; + dummy_aux_pmu.add =3D dummy_aux_event_add; + dummy_aux_pmu.del =3D dummy_aux_event_del; + dummy_aux_pmu.start =3D dummy_aux_event_start; + dummy_aux_pmu.stop =3D dummy_aux_event_stop; + dummy_aux_pmu.read =3D dummy_aux_event_read; + dummy_aux_pmu.setup_aux =3D dummy_aux_setup_aux; + dummy_aux_pmu.free_aux =3D dummy_aux_free_aux; + dummy_aux_pmu.capabilities =3D PERF_PMU_CAP_ITRACE; + dummy_aux_pmu.task_ctx_nr =3D perf_sw_context; + + ret =3D perf_pmu_register(&dummy_aux_pmu, "dummy_aux", -1); + if (ret) + return ret; + + dummy_aux_pmu_type =3D dummy_aux_pmu.type; + return 0; +} + +static void dummy_aux_pmu_unregister(void) +{ + if (dummy_aux_pmu_type >=3D 0) + perf_pmu_unregister(&dummy_aux_pmu); + dummy_aux_pmu_type =3D -1; +} + +static int suite_init(struct kunit_suite *suite) +{ + return dummy_aux_pmu_register(); +} + +static void suite_exit(struct kunit_suite *suite) +{ + dummy_aux_pmu_unregister(); +} + +/* ---- Helpers ---- */ + +static struct perf_event *aux_test_create_event(void) +{ + struct perf_event_attr attr =3D {}; + + attr.type =3D dummy_aux_pmu_type; + attr.size =3D sizeof(attr); + attr.disabled =3D 1; + + return perf_event_create_kernel_counter(&attr, raw_smp_processor_id(), + NULL, NULL, NULL); +} + +/* + * Advance the AUX head by @bytes via perf_aux_output_begin/end. + * The pages are pre-filled with 0xAB in dummy_aux_setup_aux, so + * the data is already present; this function only publishes it. + */ +static unsigned long aux_test_produce(struct perf_event *event, unsigned l= ong bytes) +{ + struct perf_output_handle handle; + unsigned long head, written =3D 0; + + while (written < bytes) { + unsigned long chunk =3D min_t(unsigned long, bytes - written, 1024); + + if (!perf_aux_output_begin(&handle, event)) + break; + chunk =3D min_t(unsigned long, chunk, handle.size); + perf_aux_output_end(&handle, chunk); + written +=3D chunk; + } + + head =3D perf_event_aux_head(event); + return head; +} + +/* ---- Patch-1: Setup / release tests ---- */ + +static void test_setup_rejects_non_power_of_two(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 3, 0); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + ret =3D perf_event_setup_aux(event, 0, 0); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + ret =3D perf_event_setup_aux(event, 6, 0); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + perf_event_release_kernel(event); +} + +static void test_setup_accepts_power_of_two(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int i, ret; + int pages[] =3D { 1, 2, 4, 8, 16, 32, 64 }; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + for (i =3D 0; i < ARRAY_SIZE(pages); i++) { + ret =3D perf_event_setup_aux(event, pages[i], 0); + KUNIT_EXPECT_EQ_MSG(test, 0, ret, + "setup_aux(%d pages) expected 0, got %d", + pages[i], ret); + if (ret =3D=3D 0) + perf_event_release_aux(event); + } + + perf_event_release_kernel(event); +} + +static void test_setup_rejects_double(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, -EBUSY, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_setup_parent_event_rejected(struct kunit *test) +{ + struct perf_event *parent =3D aux_test_create_event(); + struct perf_event *child =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, child); + + child->parent =3D parent; + + ret =3D perf_event_setup_aux(child, 1, 0); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + child->parent =3D NULL; + + perf_event_release_kernel(child); + perf_event_release_kernel(parent); +} + +static void test_writer_admitted_after_setup(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_writer_blocked_after_release(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + struct perf_output_handle handle; + void *addr; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + + addr =3D perf_aux_output_begin(&handle, event); + KUNIT_EXPECT_NULL(test, addr); + + perf_event_release_kernel(event); +} + +static void test_release_noop_without_rb(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + perf_event_release_aux(event); + KUNIT_EXPECT_TRUE(test, true); + + perf_event_release_kernel(event); +} + +static void test_setup_release_roundtrip(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + KUNIT_EXPECT_TRUE(test, true); + + perf_event_release_kernel(event); +} + +static void test_multi_setup_release_cycle(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int i, ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + for (i =3D 0; i < 3; i++) { + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_ASSERT_EQ_MSG(test, 0, ret, + "cycle %d: setup_aux failed", i); + perf_event_release_aux(event); + } + + perf_event_release_kernel(event); +} + +static void test_double_release_clean(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_aux(event); + KUNIT_EXPECT_TRUE(test, true); + + perf_event_release_kernel(event); +} + +/* ---- Patch-2: Accessor tests ---- */ + +static void test_head_initial_zero(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D perf_event_aux_head(event); + KUNIT_EXPECT_EQ(test, 0, head); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_head_advances_after_produce(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_full_window(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + const unsigned long buf_size =3D PAGE_SIZE; + void *buf; + unsigned long head; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, buf_size / 2); + KUNIT_EXPECT_GT(test, head, 0); + + buf =3D kunit_kmalloc(test, head, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied =3D perf_event_aux_copy(event, 0, head, buf); + KUNIT_EXPECT_EQ_MSG(test, (long)head, copied, + "copy(0, %lu) returned %ld, expected %lu", + head, copied, head); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_wrap(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + const unsigned long buf_size =3D 2 * PAGE_SIZE; + void *buf; + unsigned long head, tail; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 2, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Fill the buffer to near-full */ + head =3D aux_test_produce(event, buf_size); + KUNIT_EXPECT_GT(test, head, 0); + + /* Consume half to free space */ + tail =3D head / 2; + ret =3D perf_event_aux_tail_set(event, tail); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Produce more =E2=80=94 head now wraps past buf_size */ + head =3D aux_test_produce(event, buf_size); + KUNIT_EXPECT_GT(test, head, buf_size); + + /* Copy a window that spans the ring wrap point */ + buf =3D kunit_kmalloc(test, buf_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied =3D perf_event_aux_copy(event, tail, head, buf); + KUNIT_EXPECT_GT(test, copied, 0); + KUNIT_EXPECT_LE(test, copied, (long)buf_size); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_zero_len(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + void *buf; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + buf =3D kunit_kmalloc(test, 1, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied =3D perf_event_aux_copy(event, 0, 0, buf); + KUNIT_EXPECT_EQ(test, 0, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_rewound(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + const unsigned long buf_size =3D PAGE_SIZE; + void *buf; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + aux_test_produce(event, buf_size / 2); + + buf =3D kunit_kmalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* from < to: rewound cursor */ + copied =3D perf_event_aux_copy(event, 100, 50, buf); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_future(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + unsigned long head; + void *buf; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + buf =3D kunit_kmalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* to > head: future cursor */ + copied =3D perf_event_aux_copy(event, head, head + 64, buf); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_oversized(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + const unsigned long buf_size =3D PAGE_SIZE; + void *buf; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + aux_test_produce(event, 512); + + buf =3D kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* window > buf_size */ + copied =3D perf_event_aux_copy(event, 0, 2 * buf_size, buf); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_null_buf(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + copied =3D perf_event_aux_copy(event, 0, 0, NULL); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_no_rb_enoent(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + void *buf; + long ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + buf =3D kunit_kmalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + ret =3D perf_event_aux_copy(event, 0, 64, buf); + KUNIT_EXPECT_EQ(test, -ENOENT, ret); + + perf_event_release_kernel(event); +} + +static void test_tail_set_valid_frees_space(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + ret =3D perf_event_aux_tail_set(event, head / 2); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_rejects_future(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* tail=3D1 when head=3D0: future */ + ret =3D perf_event_aux_tail_set(event, 1); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_accepts_within_ring_window(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + const unsigned long buf_size =3D PAGE_SIZE; + unsigned long head, tail; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Produce enough data so head - tail <=3D aux_size for a large tail */ + head =3D aux_test_produce(event, buf_size / 2); + KUNIT_EXPECT_GT(test, head, 0); + + /* + * Set tail to a non-trivial value within the [0, head] window. + * This is NOT a no-op: old_tail=3D0, tail=3Dhead/4, advance=3Dhead/4. + */ + tail =3D head / 4; + ret =3D perf_event_aux_tail_set(event, tail); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_rejects_behind_ring(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + const unsigned long buf_size =3D PAGE_SIZE; + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, buf_size / 2); + KUNIT_EXPECT_GT(test, head, 0); + + /* + * tail more than one ring behind head: head - tail > aux_size. + * This should be rejected by the hardened code. + */ + ret =3D perf_event_aux_tail_set(event, head + buf_size + 1); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_no_rb_enoent(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_aux_tail_set(event, 0); + KUNIT_EXPECT_EQ(test, -ENOENT, ret); + + perf_event_release_kernel(event); +} + +static void test_head_no_rb_zero(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + unsigned long head; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + head =3D perf_event_aux_head(event); + KUNIT_EXPECT_EQ(test, 0, head); + + perf_event_release_kernel(event); +} + +/* ---- Additional boundary and correctness tests ---- */ + +static void test_setup_rejects_negative_watermark(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, -1); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + ret =3D perf_event_setup_aux(event, 1, -4096); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + perf_event_release_kernel(event); +} + +static void test_tail_set_noop(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ret =3D perf_event_aux_tail_set(event, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_consume_all(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + ret =3D perf_event_aux_tail_set(event, head); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_data_correctness(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + void *buf; + unsigned long head; + long copied; + int ret, i; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + buf =3D kunit_kmalloc(test, head, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied =3D perf_event_aux_copy(event, 0, head, buf); + KUNIT_EXPECT_EQ(test, (long)head, copied); + + for (i =3D 0; i < copied; i++) { + KUNIT_EXPECT_EQ_MSG(test, 0xAB, ((u8 *)buf)[i], + "data mismatch at offset %d", i); + if (((u8 *)buf)[i] !=3D 0xAB) + break; + } + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_multi_produce_copy_cycle(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + void *buf; + unsigned long head, tail =3D 0; + long copied; + int ret, cycle; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + buf =3D kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + for (cycle =3D 0; cycle < 3; cycle++) { + head =3D aux_test_produce(event, 256); + KUNIT_EXPECT_GT(test, head, tail); + + copied =3D perf_event_aux_copy(event, tail, head, buf); + KUNIT_EXPECT_EQ(test, (long)(head - tail), copied); + + ret =3D perf_event_aux_tail_set(event, head); + KUNIT_EXPECT_EQ(test, 0, ret); + tail =3D head; + } + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_already_consumed(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + void *buf; + unsigned long head, tail; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + tail =3D head / 2; + ret =3D perf_event_aux_tail_set(event, tail); + KUNIT_EXPECT_EQ(test, 0, ret); + + buf =3D kunit_kmalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied =3D perf_event_aux_copy(event, tail - 1, tail, buf); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_output_end_zero_size(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + struct perf_output_handle handle; + void *addr; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + addr =3D perf_aux_output_begin(&handle, event); + KUNIT_EXPECT_NOT_NULL(test, addr); + + perf_aux_output_end(&handle, 0); + KUNIT_EXPECT_EQ(test, 0, perf_event_aux_head(event)); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_buffer_full_stops_producer(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + struct perf_output_handle handle; + const unsigned long buf_size =3D PAGE_SIZE; + unsigned long head; + void *addr; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head =3D aux_test_produce(event, buf_size); + KUNIT_EXPECT_GT(test, head, 0); + + addr =3D perf_aux_output_begin(&handle, event); + if (addr) { + KUNIT_EXPECT_EQ(test, 0, handle.size); + perf_aux_output_end(&handle, 0); + } + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_setup_with_explicit_watermark(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 2, 4096); + KUNIT_EXPECT_EQ(test, 0, ret); + perf_event_release_aux(event); + + ret =3D perf_event_setup_aux(event, 4, 8192); + KUNIT_EXPECT_EQ(test, 0, ret); + perf_event_release_aux(event); + + perf_event_release_kernel(event); +} + +static void test_two_events_independent(struct kunit *test) +{ + struct perf_event *ev_a =3D aux_test_create_event(); + struct perf_event *ev_b =3D aux_test_create_event(); + unsigned long head_a, head_b; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_a); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_b); + + ret =3D perf_event_setup_aux(ev_a, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ret =3D perf_event_setup_aux(ev_b, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head_a =3D aux_test_produce(ev_a, 512); + head_b =3D perf_event_aux_head(ev_b); + + KUNIT_EXPECT_GT(test, head_a, 0); + KUNIT_EXPECT_EQ(test, 0, head_b); + + head_b =3D aux_test_produce(ev_b, 256); + KUNIT_EXPECT_GT(test, head_b, 0); + KUNIT_EXPECT_EQ(test, head_a, perf_event_aux_head(ev_a)); + + perf_event_release_aux(ev_a); + perf_event_release_aux(ev_b); + perf_event_release_kernel(ev_a); + perf_event_release_kernel(ev_b); +} + +static void test_release_one_does_not_affect_other(struct kunit *test) +{ + struct perf_event *ev_a =3D aux_test_create_event(); + struct perf_event *ev_b =3D aux_test_create_event(); + unsigned long head_b; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_a); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_b); + + ret =3D perf_event_setup_aux(ev_a, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ret =3D perf_event_setup_aux(ev_b, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head_b =3D aux_test_produce(ev_b, 512); + KUNIT_EXPECT_GT(test, head_b, 0); + + perf_event_release_aux(ev_a); + + KUNIT_EXPECT_EQ(test, head_b, perf_event_aux_head(ev_b)); + KUNIT_EXPECT_GT(test, aux_test_produce(ev_b, 256), head_b); + + perf_event_release_aux(ev_b); + perf_event_release_kernel(ev_a); + perf_event_release_kernel(ev_b); +} + +/* + * Test: kernel consumer and simulated userspace consumer coexist on + * the same PMU without interfering. + * + * Event A uses aux_kernel_count (via perf_event_setup_aux). + * Event B swaps its refcounts to aux_mmap_count=3D1 to simulate a + * userspace mmap'd consumer. Both rings on the same dummy PMU. + * + * - produce on A =E2=86=92 B's head is unaffected + * - produce on B =E2=86=92 A's head is unaffected + * - release A (kernel) =E2=86=92 B still produces + */ +static void test_user_kernel_coexistence(struct kunit *test) +{ + struct perf_event *ev_a =3D aux_test_create_event(); + struct perf_event *ev_b =3D aux_test_create_event(); + struct perf_output_handle handle_a, handle_b; + unsigned long a_head_before, b_head_before; + void *addr; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_a); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_b); + + /* Event A: kernel consumer */ + ret =3D perf_event_setup_aux(ev_a, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Event B: start with kernel setup, then simulate userspace */ + ret =3D perf_event_setup_aux(ev_b, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Simulate userspace ownership: swap refcounts on B's rb. + * perf_mmap() would set aux_mmap_count=3D1 for a userspace event. + */ + refcount_set(&ev_b->rb->aux_mmap_count, 1); + refcount_set(&ev_b->rb->aux_kernel_count, 0); + + /* Produce on A =E2=80=94 B's head stays 0 */ + addr =3D perf_aux_output_begin(&handle_a, ev_a); + KUNIT_EXPECT_NOT_NULL(test, addr); + perf_aux_output_end(&handle_a, 512); + KUNIT_EXPECT_GT(test, ev_a->rb->aux_head, 0); + KUNIT_EXPECT_EQ(test, 0, ev_b->rb->aux_head); + + /* Produce on B =E2=80=94 A's head unchanged */ + a_head_before =3D ev_a->rb->aux_head; + addr =3D perf_aux_output_begin(&handle_b, ev_b); + KUNIT_EXPECT_NOT_NULL(test, addr); + perf_aux_output_end(&handle_b, 256); + KUNIT_EXPECT_GT(test, ev_b->rb->aux_head, 0); + KUNIT_EXPECT_EQ(test, a_head_before, ev_a->rb->aux_head); + + /* Release A (kernel) =E2=80=94 B still produces */ + b_head_before =3D ev_b->rb->aux_head; + perf_event_release_aux(ev_a); + addr =3D perf_aux_output_begin(&handle_b, ev_b); + KUNIT_EXPECT_NOT_NULL(test, addr); + perf_aux_output_end(&handle_b, 128); + KUNIT_EXPECT_GT(test, ev_b->rb->aux_head, b_head_before); + + /* Cleanup B: restore kernel ownership for proper release */ + refcount_set(&ev_b->rb->aux_kernel_count, 1); + refcount_set(&ev_b->rb->aux_mmap_count, 0); + perf_event_release_aux(ev_b); + perf_event_release_kernel(ev_a); + perf_event_release_kernel(ev_b); +} + +/* + * Test: two kthreads concurrently call perf_event_release_aux() on + * the same event. Verifies that the refcount_dec_and_mutex_lock + * serialisation and the event->rb =3D=3D rb guard in the detach block + * correctly handle the race without crashing or leaking. + */ +struct concurrent_release_ctx { + struct perf_event *event; + atomic_t count; + wait_queue_head_t wq; +}; + +static int concurrent_release_thread(void *data) +{ + struct concurrent_release_ctx *ctx =3D data; + + perf_event_release_aux(ctx->event); + + if (atomic_dec_and_test(&ctx->count)) + wake_up(&ctx->wq); + return 0; +} + +static void test_concurrent_release(struct kunit *test) +{ + struct perf_event *event =3D aux_test_create_event(); + struct concurrent_release_ctx ctx; + struct task_struct *threads[2]; + int ret, i; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret =3D perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ctx.event =3D event; + atomic_set(&ctx.count, ARRAY_SIZE(threads)); + init_waitqueue_head(&ctx.wq); + + for (i =3D 0; i < ARRAY_SIZE(threads); i++) { + threads[i] =3D kthread_run(concurrent_release_thread, &ctx, + "aux_crel_%d", i); + if (IS_ERR_OR_NULL(threads[i])) { + kunit_skip(test, "failed to create kthread"); + while (--i >=3D 0) + kthread_stop(threads[i]); + perf_event_release_aux(event); + perf_event_release_kernel(event); + return; + } + } + + wait_event(ctx.wq, atomic_read(&ctx.count) =3D=3D 0); + + perf_event_release_kernel(event); +} + +static struct kunit_case aux_kernel_test_cases[] =3D { + KUNIT_CASE(test_setup_rejects_non_power_of_two), + KUNIT_CASE(test_setup_accepts_power_of_two), + KUNIT_CASE(test_setup_rejects_double), + KUNIT_CASE(test_setup_parent_event_rejected), + KUNIT_CASE(test_setup_rejects_negative_watermark), + KUNIT_CASE(test_setup_with_explicit_watermark), + KUNIT_CASE(test_writer_admitted_after_setup), + KUNIT_CASE(test_writer_blocked_after_release), + KUNIT_CASE(test_release_noop_without_rb), + KUNIT_CASE(test_setup_release_roundtrip), + KUNIT_CASE(test_multi_setup_release_cycle), + KUNIT_CASE(test_double_release_clean), + KUNIT_CASE(test_two_events_independent), + KUNIT_CASE(test_release_one_does_not_affect_other), + KUNIT_CASE(test_user_kernel_coexistence), + KUNIT_CASE(test_concurrent_release), + KUNIT_CASE(test_head_initial_zero), + KUNIT_CASE(test_head_advances_after_produce), + KUNIT_CASE(test_head_no_rb_zero), + KUNIT_CASE(test_copy_full_window), + KUNIT_CASE(test_copy_wrap), + KUNIT_CASE(test_copy_zero_len), + KUNIT_CASE(test_copy_data_correctness), + KUNIT_CASE(test_copy_rejects_rewound), + KUNIT_CASE(test_copy_rejects_future), + KUNIT_CASE(test_copy_rejects_oversized), + KUNIT_CASE(test_copy_rejects_null_buf), + KUNIT_CASE(test_copy_rejects_already_consumed), + KUNIT_CASE(test_copy_no_rb_enoent), + KUNIT_CASE(test_tail_set_valid_frees_space), + KUNIT_CASE(test_tail_set_rejects_future), + KUNIT_CASE(test_tail_set_accepts_within_ring_window), + KUNIT_CASE(test_tail_set_rejects_behind_ring), + KUNIT_CASE(test_tail_set_noop), + KUNIT_CASE(test_tail_set_consume_all), + KUNIT_CASE(test_tail_set_no_rb_enoent), + KUNIT_CASE(test_multi_produce_copy_cycle), + KUNIT_CASE(test_output_end_zero_size), + KUNIT_CASE(test_buffer_full_stops_producer), + {}, +}; + +static struct kunit_suite aux_kernel_test_suite =3D { + .name =3D "perf_aux_kernel", + .suite_init =3D suite_init, + .suite_exit =3D suite_exit, + .test_cases =3D aux_kernel_test_cases, +}; + +kunit_test_suite(aux_kernel_test_suite); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 1244dcac2294..30f4ab1dd791 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -3102,6 +3102,21 @@ config HW_BREAKPOINT_KUNIT_TEST =20 If unsure, say N. =20 +config PERF_AUX_KERNEL_KUNIT_TEST + bool "Test perf AUX kernel-consumer API" if !KUNIT_ALL_TESTS + depends on PERF_EVENTS=3Dy + depends on KUNIT=3Dy + default KUNIT_ALL_TESTS + help + Tests for the perf AUX kernel-consumer API functions: + perf_event_setup_aux(), perf_event_release_aux(), + perf_event_aux_head(), perf_event_aux_tail_set(), and + perf_event_aux_copy(). Error-path contract tests work + without hardware; happy-path tests require an AUX-capable + PMU (ARM SPE, Intel PT, etc.). + + If unsure, say N. + source "lib/crypto/tests/Kconfig" =20 config SIPHASH_KUNIT_TEST --=20 2.43.0 From nobody Mon Sep 28 23:51:12 2026 Received: from mail-pj1-f42.google.com (mail-pj1-f42.google.com [209.85.216.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 385293DA7F2 for ; Fri, 14 Aug 2026 14:50:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719038; cv=none; b=SnbwgFFZFRNfjw14vLX/Qrps2F9J8V4xTX/AZNj0Sy5/26/+7gB//Bw05phQAk5WTE8rFsgJ/o/URpsFbQum11ymNijg/YswLw40RdOCp8pg6f8nxZx52S1uqc98T1571kjvAsgxJfIbTb6Y0kbNVnzrIMMXXZekOw8f12SQVfY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719038; c=relaxed/simple; bh=rTf0zNMGMIiVFZIeMWtdeEA+9PKSpB0w/3bBQirqVDo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=l4+EhkJK/H5NKE6al7AEvEdfyBRxP8Yz0tR6cFiU0GHDy3YV9AJy+5w3XCGRx0Ff/OIA4GewzOqQ6I17mimrRYDNwTCRSSLIj0sHpHE/WU53LSGwRAh7g2wItaIxBu7hAN1KtJVygcViNZITcREZZSxtBUCfsYzMi6KnHDvEJP8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=mEECy0bN; arc=none smtp.client-ip=209.85.216.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mEECy0bN" Received: by mail-pj1-f42.google.com with SMTP id 98e67ed59e1d1-38deea72eebso1128243a91.1 for ; Fri, 14 Aug 2026 07:50:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786719026; x=1787323826; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=WByajkS4s1sS3rQD8GcfodtHv3MTnuOsaVg1gB9Zu9E=; b=mEECy0bNhGsroS+RweiGsTNoDyfdChShTFb6wfBvnDyQ//8yYp88SdFaDsMmChZGuB u3yXRpjL566FUXFWoyDFkeRp5y1j/h2i4617rMfnmU2S6igFa30iF8RErNpePwz77ill LKhUF6HZ6Hyf02p4R73V3NXe9QmYl0hGCN7OIhisRO7kJUtMlhZejCEN3Y628R3lYuIy m1njcRjhL4t+Bl/1XK7KSryE4WbvfjcEJFXtJchv4paPLnKs1iqammeQUSSRpxubwdAS 5fvOrSpxKYtev6nMMuzgKFcfd37gaaNGRzwIRObUpJWa+2ORW9fAU1UQl2ezvicebLYa mZdA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786719026; x=1787323826; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=WByajkS4s1sS3rQD8GcfodtHv3MTnuOsaVg1gB9Zu9E=; b=OugwMYyg+atM8VJLh/U2gh/c3DkwjAJx/B9W/kDn893GDxHWCMVUeu1x+TDqr+7q+c bJyn8jxQ8dYHxvQPHPotT4STEg5Y4jHel+H1iZ9Pq9IL6IIl9u4gh3N9rm8XHdgif2cd TbV+C9d1ycP31T8gXRRR++bQ0hQHDkVjrVFKmOzufYZOHG3gP3He7ZSWNXjdbiTqXjg5 rBHMct5gDaa8ZMJY3D/PJQLRijkmOkqzSTvjtU+ot5ymUYysaiZrqUST1SWK8cbfMUD9 nGjBgIvvOHzhXvE7K+/rM3ogkOMMk8HhzWKKP3mECXMtHJ4w2y0qosSseSi2KQ6VqGG4 n2lQ== X-Forwarded-Encrypted: i=1; AHgh+Ror9dP8vXthv/3LZcjw5xQBWoL8eJjKMt9aQAnHQBJ/gJ+Wjl4wjHYsyKnK20EX0YUC2GI6f7jdOTyjqlY=@vger.kernel.org X-Gm-Message-State: AOJu0YyOCr2XX7bWeDk5c7KsRb3ZrzX8iGmrZMXGggu3OEZtsJeHAvrv zeaXmrDvvSI/4UhZeMiTg9TDt+nzqzUZQQNkBGxFxwIIjTlzvGgJMY0x X-Gm-Gg: AR+sD10VjW8OkGXkgfTdBupd+qflPzrooRb4oLFqOBJjthODWXRLByiIYYjJmUzlDXK Pnfildk3P5kVLOTB4RNRidec7tDkqOFZPAA5Jdqu7szGAfU5JeJBlA+DUU9XzmhAbYIfNM3Jhgg EQdDMvZbrMg6ET5Q9Q7OWFdfPfbaGQrNHENsJC0KK3YtoxV2viRylqjcuDWwOlrWm7Jc9hEz98A RaIfWHlaHc3GSboL+UPcEnEa9k1qgDqL4eZBKERwBqD7wW5mAf33MLNe16yBtodHlxKDjeI4QCJ DhOhqqXLgb8JeQD+GDiAypGY70m8p9Ep3FmnoOkrxgTewePiKxoijRAcHy4mLQYIoCM8czkEVt0 5vX2YQBEp/aMccRD364w30aJRnlWgHvt8Y2i+KUgTEP5Fwx9zc6uwGal4R0uaSeAlTtAqWkiSJQ 1V3Ry/yAUVfPrO15F3LU9FFM0sjUYuFy8MYfxYGx7BUx/rU8tqPaZop8piBZZZL7dE/wg9qJD4h L1fu70= X-Received: by 2002:a17:90b:582c:b0:381:c500:b0d1 with SMTP id 98e67ed59e1d1-3933bad0d4cmr8086661a91.20.1786719025451; Fri, 14 Aug 2026 07:50:25 -0700 (PDT) Received: from kernel.tail6741c6.ts.net ([116.128.244.169]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3933b626d4esm1518950a91.2.2026.08.14.07.50.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Aug 2026 07:50:25 -0700 (PDT) From: Kunwu Chan X-Google-Original-From: Kunwu Chan To: corbet@lwn.net, skhan@linuxfoundation.org, peterz@infradead.org, mingo@redhat.com, acme@kernel.org, namhyung@kernel.org, mark.rutland@arm.com, alexander.shishkin@linux.intel.com, jolsa@kernel.org, irogers@google.com, adrian.hunter@intel.com, james.clark@linaro.org, kunwu.chan@gmail.com, lianux.mm@gmail.com Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, linux-kselftest@vger.kernel.org, sj@kernel.org Subject: [RFC PATCH 4/5] selftests/perf_events: add userspace AUX regression test Date: Fri, 14 Aug 2026 22:49:20 +0800 Message-ID: <20260814144927.489172-5-kunwu.chan@linux.dev> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260814144927.489172-1-kunwu.chan@linux.dev> References: <20260814144927.489172-1-kunwu.chan@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Lian Wang Add a standalone C program that opens a perf event, mmaps the metadata and data ring, sets up the AUX area via a second mmap, enables tracing, reads aux_head from the mmap page, and tears down the mapping. This verifies the kernel-side aux_kernel_count changes do not break the existing userspace AUX mmap protocol. When the AUX area mmap fails (e.g. PMU does not support it), the AUX-specific tests are skipped and only a basic software-event test runs. Co-developed-by: Kunwu Chan Signed-off-by: Kunwu Chan Signed-off-by: Lian Wang --- tools/testing/selftests/perf_events/Makefile | 1 + .../perf_events/aux_kernel_usermode.c | 546 ++++++++++++++++++ tools/testing/selftests/perf_events/config | 2 + 3 files changed, 549 insertions(+) create mode 100644 tools/testing/selftests/perf_events/aux_kernel_usermode= .c diff --git a/tools/testing/selftests/perf_events/Makefile b/tools/testing/s= elftests/perf_events/Makefile index 2e5d85770dfe..fbafbd43f097 100644 --- a/tools/testing/selftests/perf_events/Makefile +++ b/tools/testing/selftests/perf_events/Makefile @@ -3,4 +3,5 @@ CFLAGS +=3D -Wl,-no-as-needed -Wall $(KHDR_INCLUDES) LDFLAGS +=3D -lpthread =20 TEST_GEN_PROGS :=3D sigtrap_threads remove_on_exec watermark_signal mmap +TEST_GEN_PROGS +=3D aux_kernel_usermode include ../lib.mk diff --git a/tools/testing/selftests/perf_events/aux_kernel_usermode.c b/to= ols/testing/selftests/perf_events/aux_kernel_usermode.c new file mode 100644 index 000000000000..c3d2323d0879 --- /dev/null +++ b/tools/testing/selftests/perf_events/aux_kernel_usermode.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Userspace AUX regression test for the perf AUX kernel-consumer API. + * + * Verifies that the kernel-side AUX changes (aux_kernel_count, + * perf_event_setup_aux, etc.) do NOT break the existing userspace + * AUX mmap protocol: + * - perf_event_open() with AUX still works + * - mmap AUX buffer still works + * - aux_head / aux_tail from the mmap page are still correct + * - aux_mmap_count is independent of aux_kernel_count + * - Userspace and kernel AUX owners can coexist + * + * Build: gcc -o aux_kernel_usermode aux_kernel_usermode.c + * Usage: sudo ./aux_kernel_usermode [pmu_name] + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PASS(fmt, ...) printf(" [PASS] " fmt "\n", ##__VA_ARGS__) +#define FAIL(fmt, ...) do { \ + printf(" [FAIL] " fmt "\n", ##__VA_ARGS__); \ + failed =3D 1; \ +} while (0) + +static int failed; + +static long perf_event_open(struct perf_event_attr *attr, pid_t pid, + int cpu, int group_fd, unsigned long flags) +{ + return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags); +} + +/* + * Find an AUX PMU type by scanning sysfs PMU directories. + */ +static int find_aux_pmu_type(void) +{ + char path[256], type_path[261]; + FILE *f; + struct stat st; + int type; + + /* Try common AUX PMU names first */ + static const char * const known[] =3D { + "arm_spe_0", "arm_spe_1", "arm_spe_2", + "intel_pt", "intel_pt0", + NULL + }; + + for (int i =3D 0; known[i]; i++) { + snprintf(path, sizeof(path), + "/sys/bus/event_source/devices/%s", known[i]); + if (stat(path, &st) =3D=3D 0 && S_ISDIR(st.st_mode)) { + snprintf(type_path, sizeof(type_path), + "%s/type", path); + f =3D fopen(type_path, "r"); + if (f) { + if (fscanf(f, "%d", &type) =3D=3D 1) { + fclose(f); + printf(" Found AUX PMU: %s (type=3D%d)\n", + known[i], type); + return type; + } + fclose(f); + } + } + } + + return -1; +} + +/* + * Test 1: Basic perf_event_open() with software event. + * This verifies the perf core is not broken at all. + */ +static int test_basic_sw_event(void) +{ + struct perf_event_attr attr =3D {}; + int fd; + + attr.type =3D PERF_TYPE_SOFTWARE; + attr.config =3D PERF_COUNT_SW_CPU_CLOCK; + attr.size =3D sizeof(attr); + attr.disabled =3D 1; + + fd =3D perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + FAIL("basic SW event: perf_event_open failed (%s)", + strerror(errno)); + return 1; + } + + ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + usleep(10000); + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + close(fd); + PASS("basic SW event: open/enable/disable/close"); + return 0; +} + +/* + * Test 2: Userspace AUX mmap full lifecycle. + * Opens an AUX event, mmaps the metadata/data ring, sets up the AUX + * area via a second mmap, enables, reads aux_head, disables, unmaps, + * closes. This is the critical regression test. + * + * If the kernel rejects the AUX area mmap, the test is SKIP (not all + * PMUs support userspace AUX mmap). + */ +static int test_aux_mmap(int pmu_type) +{ + struct perf_event_attr attr =3D {}; + struct perf_event_mmap_page *mp; + void *aux_base; + unsigned long aux_size, aux_offset, aux_head, aux_tail; + unsigned long mmap_size; + int fd, ret; + + attr.type =3D pmu_type; + attr.size =3D sizeof(attr); + attr.disabled =3D 1; + attr.sample_period =3D 256; + + /* ARM SPE requires period mode (freq=3D0) */ + attr.freq =3D 0; + + fd =3D perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + FAIL("AUX mmap: perf_event_open failed (%s)", strerror(errno)); + return 1; + } + + /* + * Step 1: mmap the metadata page and data ring at offset 0. + * The kernel creates the ring buffer and returns the metadata page. + */ + aux_size =3D 1UL << 20; /* 1 MiB */ + mmap_size =3D aux_size + getpagesize(); + mp =3D mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (mp =3D=3D MAP_FAILED) { + FAIL("AUX mmap: metadata mmap failed (%s)", strerror(errno)); + close(fd); + return 1; + } + + /* + * Step 2: set aux_offset and aux_size in the metadata page. + * The AUX area starts after the metadata and data pages. + * The kernel reads these values when the AUX area is mmap'd. + */ + aux_offset =3D mmap_size; + mp->aux_offset =3D aux_offset; + mp->aux_size =3D aux_size; + + /* + * Step 3: mmap the AUX area at aux_offset. + * The kernel reads aux_offset from the metadata page, verifies + * the mmap offset matches, and allocates the AUX buffer. + */ + aux_base =3D mmap(NULL, aux_size, PROT_READ, MAP_SHARED, fd, + aux_offset); + if (aux_base =3D=3D MAP_FAILED) { + printf(" [SKIP] AUX mmap: AUX area mmap failed (%s)\n", + strerror(errno)); + munmap(mp, mmap_size); + close(fd); + return 0; + } + PASS("AUX mmap: metadata mmap OK, AUX area mmap OK"); + + /* Enable the event */ + ret =3D ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + if (ret < 0) { + FAIL("AUX mmap: enable failed (%s)", strerror(errno)); + munmap(aux_base, aux_size); + munmap(mp, mmap_size); + close(fd); + return 1; + } + + /* Run a small workload to generate some AUX data */ + usleep(100000); /* 100ms */ + + /* Read the AUX head from the mmap page */ + aux_head =3D __atomic_load_n(&mp->aux_head, __ATOMIC_RELAXED); + aux_tail =3D __atomic_load_n(&mp->aux_tail, __ATOMIC_RELAXED); + PASS("AUX mmap: head=3D%llu tail=3D%llu after 100ms workload", + (unsigned long long)aux_head, (unsigned long long)aux_tail); + + /* Disable the event */ + ret =3D ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + if (ret < 0) { + FAIL("AUX mmap: disable failed (%s)", strerror(errno)); + munmap(aux_base, aux_size); + munmap(mp, mmap_size); + close(fd); + return 1; + } + + /* Unmap AUX area */ + ret =3D munmap(aux_base, aux_size); + if (ret < 0) { + FAIL("AUX mmap: AUX munmap failed (%s)", strerror(errno)); + munmap(mp, mmap_size); + close(fd); + return 1; + } + + /* Unmap metadata */ + ret =3D munmap(mp, mmap_size); + if (ret < 0) { + FAIL("AUX mmap: metadata munmap failed (%s)", strerror(errno)); + close(fd); + return 1; + } + PASS("AUX mmap: full lifecycle PASS"); + close(fd); + return 0; +} + +/* + * Test 3: Verify that aux_mmap_count is independent of aux_kernel_count. + * Open two events for the same AUX PMU. The first one is a regular + * userspace event; verify it works. Then open a second one; verify + * both can have AUX buffers simultaneously. + */ +static int test_aux_mmap_independence(int pmu_type) +{ + struct perf_event_attr attr =3D {}; + struct perf_event_mmap_page *mp1, *mp2; + unsigned long aux_size; + int fd1, fd2; + + aux_size =3D 1UL << 16; /* 64 KiB */ + + attr.type =3D pmu_type; + attr.size =3D sizeof(attr); + attr.disabled =3D 1; + attr.sample_period =3D 256; + attr.freq =3D 0; + + fd1 =3D perf_event_open(&attr, 0, -1, -1, 0); + if (fd1 < 0) { + FAIL("AUX independence: first event open failed (%s)", + strerror(errno)); + return 1; + } + + mp1 =3D mmap(NULL, aux_size + getpagesize(), PROT_READ | PROT_WRITE, + MAP_SHARED, fd1, 0); + if (mp1 =3D=3D MAP_FAILED) { + FAIL("AUX independence: first mmap failed (%s)", strerror(errno)); + close(fd1); + return 1; + } + PASS("AUX independence: first event mmap OK"); + + /* Open a second event for the same PMU */ + fd2 =3D perf_event_open(&attr, 0, -1, -1, 0); + if (fd2 < 0) { + /* Some PMUs (e.g. ARM SPE) only allow one event at a time */ + printf(" [SKIP] AUX independence: second event open (%s)\n", + strerror(errno)); + munmap(mp1, aux_size + getpagesize()); + close(fd1); + return 0; + } + + mp2 =3D mmap(NULL, aux_size + getpagesize(), PROT_READ | PROT_WRITE, + MAP_SHARED, fd2, 0); + if (mp2 =3D=3D MAP_FAILED) { + FAIL("AUX independence: second mmap failed (%s)", strerror(errno)); + munmap(mp1, aux_size + getpagesize()); + close(fd1); + close(fd2); + return 1; + } + PASS("AUX independence: second event mmap OK"); + + /* Enable both */ + ioctl(fd1, PERF_EVENT_IOC_ENABLE, 0); + ioctl(fd2, PERF_EVENT_IOC_ENABLE, 0); + usleep(50000); + ioctl(fd1, PERF_EVENT_IOC_DISABLE, 0); + ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0); + + PASS("AUX independence: two events ran simultaneously"); + + /* Both should have valid AUX data */ + PASS("AUX independence: event1 head=3D%llu event2 head=3D%llu", + (unsigned long long)__atomic_load_n(&mp1->aux_head, __ATOMIC_RELAXED= ), + (unsigned long long)__atomic_load_n(&mp2->aux_head, __ATOMIC_RELAXED= )); + + munmap(mp1, aux_size + getpagesize()); + munmap(mp2, aux_size + getpagesize()); + close(fd1); + close(fd2); + PASS("AUX independence: cleanup OK"); + return 0; +} + +/* + * Test 4: Verify that the mmap control page fields are correct. + * Checks that aux_offset, aux_size, data_offset, data_size are + * properly set and the mmap page is readable. + */ +static int test_mmap_page_fields(int pmu_type) +{ + struct perf_event_attr attr =3D {}; + struct perf_event_mmap_page *mp; + unsigned long total_size; + int fd; + + total_size =3D 1UL << 18; /* 256 KiB */ + + attr.type =3D pmu_type; + attr.size =3D sizeof(attr); + attr.disabled =3D 1; + attr.sample_period =3D 256; + attr.freq =3D 0; + + fd =3D perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + FAIL("mmap fields: perf_event_open failed (%s)", strerror(errno)); + return 1; + } + + mp =3D mmap(NULL, total_size + getpagesize(), PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (mp =3D=3D MAP_FAILED) { + FAIL("mmap fields: mmap failed (%s)", strerror(errno)); + close(fd); + return 1; + } + + /* Verify mmap page fields */ + if (mp->version !=3D 1 && mp->version !=3D 0) { + FAIL("mmap fields: version=3D%u (expected 0 or 1)", mp->version); + munmap(mp, total_size + getpagesize()); + close(fd); + return 1; + } + PASS("mmap fields: version=3D%u", mp->version); + + if (mp->compat_version !=3D 0) { + FAIL("mmap fields: compat_version=3D%u (expected 0)", + mp->compat_version); + munmap(mp, total_size + getpagesize()); + close(fd); + return 1; + } + PASS("mmap fields: compat_version=3D%u", mp->compat_version); + + if (mp->aux_offset =3D=3D 0) { + printf(" [SKIP] mmap fields: aux_offset is 0 (no AUX area)\n"); + munmap(mp, total_size + getpagesize()); + close(fd); + return 0; + } + PASS("mmap fields: aux_offset=3D%llu", (unsigned long long)mp->aux_offset= ); + + if (mp->aux_size =3D=3D 0) { + printf(" [SKIP] mmap fields: aux_size is 0 (no AUX area)\n"); + munmap(mp, total_size + getpagesize()); + close(fd); + return 0; + } + PASS("mmap fields: aux_size=3D%llu", (unsigned long long)mp->aux_size); + + /* Verify the lock field is accessible */ + __atomic_store_n(&mp->lock, 1, __ATOMIC_RELAXED); + __atomic_store_n(&mp->lock, 0, __ATOMIC_RELEASE); + PASS("mmap fields: lock field accessible"); + + munmap(mp, total_size + getpagesize()); + close(fd); + PASS("mmap fields: all fields correct"); + return 0; +} + +/* + * Test 5: Verify that the aux_head and aux_tail are monotonically + * increasing (or at least well-defined) during a workload. + */ +static int test_aux_head_monotonic(int pmu_type) +{ + struct perf_event_attr attr =3D {}; + struct perf_event_mmap_page *mp; + void *aux_base; + unsigned long aux_size, aux_offset, mmap_size; + unsigned long head_before, head_after; + int fd, ret; + + aux_size =3D 1UL << 16; + mmap_size =3D aux_size + getpagesize(); + + attr.type =3D pmu_type; + attr.size =3D sizeof(attr); + attr.disabled =3D 1; + attr.sample_period =3D 256; + attr.freq =3D 0; + + fd =3D perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + /* Some PMUs may reject the default config */ + PASS("AUX head monotonic: skipped (PMU rejected config)"); + return 0; + } + + mp =3D mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (mp =3D=3D MAP_FAILED) { + FAIL("AUX head monotonic: metadata mmap failed (%s)", + strerror(errno)); + close(fd); + return 1; + } + + aux_offset =3D mmap_size; + mp->aux_offset =3D aux_offset; + mp->aux_size =3D aux_size; + + aux_base =3D mmap(NULL, aux_size, PROT_READ, MAP_SHARED, fd, + aux_offset); + if (aux_base =3D=3D MAP_FAILED) { + printf(" [SKIP] AUX head monotonic: AUX area mmap failed (%s)\n", + strerror(errno)); + munmap(mp, mmap_size); + close(fd); + return 0; + } + + ret =3D ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + if (ret < 0) { + PASS("AUX head monotonic: skipped (enable failed)"); + munmap(aux_base, aux_size); + munmap(mp, mmap_size); + close(fd); + return 0; + } + + /* Read initial head */ + head_before =3D __atomic_load_n(&mp->aux_head, __ATOMIC_RELAXED); + + usleep(100000); /* 100ms */ + + head_after =3D __atomic_load_n(&mp->aux_head, __ATOMIC_RELAXED); + + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + if (head_after >=3D head_before) { + PASS("AUX head monotonic: before=3D%lu after=3D%lu (OK)", + head_before, head_after); + } else { + /* Wrapping is OK for very long runs */ + PASS("AUX head monotonic: before=3D%lu after=3D%lu (wrapped)", + head_before, head_after); + } + + munmap(aux_base, aux_size); + munmap(mp, mmap_size); + close(fd); + return 0; +} + +int main(int argc, char *argv[]) +{ + int pmu_type; + + if (argc > 1) + (void)argv[1]; /* PMU name hint, not currently used */ + + if (geteuid() !=3D 0) { + fprintf(stderr, "Run as root\n"); + return 4; /* skip */ + } + + printf("=3D=3D=3D Perf AUX Kernel API: Userspace Regression Test =3D=3D= =3D\n\n"); + + /* Test 1: Basic SW event */ + printf("--- 1. Basic Software Event ---\n"); + test_basic_sw_event(); + + /* Find an AUX-capable PMU */ + pmu_type =3D find_aux_pmu_type(); + if (pmu_type < 0) { + printf("--- 2-5. AUX Tests ---\n"); + printf(" [SKIP] No AUX-capable PMU found\n"); + } else { + /* Test 2: AUX mmap lifecycle */ + printf("\n--- 2. AUX Mmap Lifecycle ---\n"); + test_aux_mmap(pmu_type); + + /* Test 3: AUX mmap independence */ + printf("\n--- 3. AUX Mmap Independence ---\n"); + test_aux_mmap_independence(pmu_type); + + /* Test 4: mmap page fields */ + printf("\n--- 4. Mmap Page Fields ---\n"); + test_mmap_page_fields(pmu_type); + + /* Test 5: AUX head monotonic */ + printf("\n--- 5. AUX Head Monotonic ---\n"); + test_aux_head_monotonic(pmu_type); + } + + /* Test 6: Verify /proc/sys/kernel/perf_event_paranoid */ + printf("\n--- 6. Perf Event Paranoid ---\n"); + { + FILE *f =3D fopen("/proc/sys/kernel/perf_event_paranoid", "r"); + int paranoid; + + if (f) { + if (fscanf(f, "%d", ¶noid) =3D=3D 1) { + printf(" perf_event_paranoid=3D%d\n", paranoid); + if (paranoid > 2) + FAIL("perf_event_paranoid=3D%d may block AUX", + paranoid); + else + PASS("perf_event_paranoid=3D%d (OK)", paranoid); + } + fclose(f); + } else { + printf(" [SKIP] Cannot read perf_event_paranoid\n"); + } + } + + printf("\n=3D=3D=3D Result: %s =3D=3D=3D\n", failed ? "FAIL" : "PASS"); + return failed ? 1 : 0; +} diff --git a/tools/testing/selftests/perf_events/config b/tools/testing/sel= ftests/perf_events/config index ba58ff2203e4..2508f331cfe0 100644 --- a/tools/testing/selftests/perf_events/config +++ b/tools/testing/selftests/perf_events/config @@ -1 +1,3 @@ CONFIG_PERF_EVENTS=3Dy +CONFIG_PERF_AUX_KERNEL_KUNIT_TEST=3Dy +CONFIG_KUNIT_DEBUGFS=3Dy --=20 2.43.0 From nobody Mon Sep 28 23:51:12 2026 Received: from mail-pj1-f47.google.com (mail-pj1-f47.google.com [209.85.216.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9020842BC23 for ; Fri, 14 Aug 2026 14:50:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719047; cv=none; b=sevP9h2nET0zKuQz+ekGU+hrVKg0ckQK1sdXrSty7LQKGcsjjnHmw/HOeoa8DZswyg7dR1j9IxyCfrkE++lH4c+EUzOLk3wyozKRiej3PkEuLwfHi7dpxFzA8lwMbaHcDCgqRIQ6Eyfu3zyuaWbiuWTyjVaS3hiEaTaBt6fyCaY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786719047; c=relaxed/simple; bh=5pRIn8lkRiV+E+wvtU5gOIWkTICxIC36LIjOS5Vge44=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oRAbRrSMvEMeZ5e+d9GypzAJhwLVYnAojaA+XT/sfQRUELwgBnijZa86LJsqEfD33gV3iz8regDVW4CtZKpq7WEHcqtpgEQuCUv5S1BEv4bE2tzenV5pX6mub+9YgAB8dfAgxqIBvl2eViWy/QjEUu686SCTiuI2hVRU7yCNcs8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=pnHTyrmo; arc=none smtp.client-ip=209.85.216.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="pnHTyrmo" Received: by mail-pj1-f47.google.com with SMTP id 98e67ed59e1d1-38e347638adso1341332a91.0 for ; Fri, 14 Aug 2026 07:50:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786719035; x=1787323835; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Z9yclaL5UtYBgdz29GEjHb664/9EYuacbr2CUbeMvrQ=; b=pnHTyrmoUV8rt+msCbsXWn+kP6XHLYMa8vKD1biXFSB6r4QGGSObcDTHgZiJRL6BL1 RW9cYQoc2OmUaPHlxOBn0rdf03urwH+fgtC3ajZi3nbn1J0eml5b2XT7ckai1OSuH7AP 9UdPuxI8LnawB61mJZmAYUSzdJZL+6DgppTnOiRftgBg9P/k0y7RD5dezbCHOPlm7jTG QJEH/yyXRpqkkaYLTWitJrfcEM5P1K9/xi1gwOzIeC/GIe6d3f0xzOodTAniJHQKG7ju SMpmkPULTcK2O73zlrtXQo4aeafU7Tn9GUxNXWLkIKiae5wlnnZR0w9MIQ3OgaD7OFBn fUsQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786719035; x=1787323835; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=Z9yclaL5UtYBgdz29GEjHb664/9EYuacbr2CUbeMvrQ=; b=nHebqUrjU9tSdzZf4hrI9TAdmBl7LvUXmPTf/bVvlz7ESuWpsfmlZY4VtXNn/53QsW bNk28RgvqObhEIeJBEStpuZJdnahypyIXJw5AiRWd/uCcqKTwTUY8FUBB9NWxtwftAd9 7PDKUi4BYSFUMo/kWhjLjREHOjAx0ZOymMvNRr/3i5ENirU2Y5TvzdJmPUOfJf23f0jG qCaF+fHURAAo923op+j6tz9dB+CvyvFjN8WSqvxEVZluB/MOhugvvc7tQBuIyG+PLHWr YGv4uzzh53qGNHEXtOHykrPvVJh4u9/+MBE/EfxSRLlK2RYmEpLbykpmtn0S1FMECEQw YRrw== X-Forwarded-Encrypted: i=1; AHgh+Ro9zrI0lQZUhBWesI5eQBzYcYG4UHT97NbmNbSO7Ov1BvZkrgo2cIdIMJndwqTR8aCyHyu4wc/wC3jGuVw=@vger.kernel.org X-Gm-Message-State: AOJu0YzFNo0WBEVi3HD3FRH1M9SxLGJ5U/iVipEPfCE7rRNG6o9nbgNn PnEtm3v4/OmSI0dmuxXDXip31pphwdxFzbiUzijnrwM8JMzlDMMy/FrO X-Gm-Gg: AR+sD11fOdt2Kj1ySTjdLHfzWhjkNzLO73MgW3rqzWqKkkLsQz/nkRItfbAEmKqQYnG cynRU2MnOfB6SkR3do5Mff1zh8hQEsGqLJb8ulKoMQJ57xNkmPf57ul45hUgYlWzPjMfgz2vEFl CiotIEr4/6W993UaDHKtDB+Qe3QA88+POYrDh7M1p3G5Ms25kUdPrwXKDCc7X66+nKFVvlXdm0R ux5SIIkBW3Mqs4QpZPDCv6wFWFK+OXKsOihEkz2z5AAruqaoF6kcVyhK8JPXDlg6VK+tjjyZgGt wvjUg3YrVwfup00IzVwfGB+7gqu4OD1OsE1EqLtC1P1XwWSdcPdDdtxARG3NEfxj2ZBgBJZw0lg ufIPbKd2rdkNrxbgmtw5ER+2ZWcA7kr+2FXRFf08D/hb6MLpQ6+Ees9D20K58T3VyTaVoxgx4A5 KF+nvznFXTLfqvBHc5U/s001HABy5dL8r9Lyw/PFN3P89srL4AWYJDmS0jYjKY4iJQsmBNIpiYj DHcZEo= X-Received: by 2002:a17:90b:1806:b0:38d:e0c4:c955 with SMTP id 98e67ed59e1d1-3933b8c918bmr6886917a91.15.1786719034939; Fri, 14 Aug 2026 07:50:34 -0700 (PDT) Received: from kernel.tail6741c6.ts.net ([116.128.244.169]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3933b626d4esm1518950a91.2.2026.08.14.07.50.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Aug 2026 07:50:34 -0700 (PDT) From: Kunwu Chan X-Google-Original-From: Kunwu Chan To: corbet@lwn.net, skhan@linuxfoundation.org, peterz@infradead.org, mingo@redhat.com, acme@kernel.org, namhyung@kernel.org, mark.rutland@arm.com, alexander.shishkin@linux.intel.com, jolsa@kernel.org, irogers@google.com, adrian.hunter@intel.com, james.clark@linaro.org, kunwu.chan@gmail.com, lianux.mm@gmail.com Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, linux-kselftest@vger.kernel.org, sj@kernel.org Subject: [RFC PATCH 5/5] selftests/perf_events: add AUX kernel API selftest script Date: Fri, 14 Aug 2026 22:49:21 +0800 Message-ID: <20260814144927.489172-6-kunwu.chan@linux.dev> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260814144927.489172-1-kunwu.chan@linux.dev> References: <20260814144927.489172-1-kunwu.chan@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Kunwu Chan Add a shell script that runs the perf_aux_kernel KUnit suite through debugfs, builds and runs the userspace regression program, and performs static-analysis checks on the source tree (memory barriers, refcount types, boundary validation, EXPORT_SYMBOL_GPL coverage). When an AUX-capable PMU is present, the script also runs a live perf record and checks dmesg for errors. Usage: sudo ./aux_kernel.sh [--pmu arm_spe_0] Co-developed-by: Lian Wang (Processmission) Signed-off-by: Lian Wang (Processmission) Signed-off-by: Kunwu Chan --- tools/testing/selftests/perf_events/Makefile | 1 + .../selftests/perf_events/aux_kernel.sh | 563 ++++++++++++++++++ 2 files changed, 564 insertions(+) create mode 100755 tools/testing/selftests/perf_events/aux_kernel.sh diff --git a/tools/testing/selftests/perf_events/Makefile b/tools/testing/s= elftests/perf_events/Makefile index fbafbd43f097..9b82c7ee1238 100644 --- a/tools/testing/selftests/perf_events/Makefile +++ b/tools/testing/selftests/perf_events/Makefile @@ -4,4 +4,5 @@ LDFLAGS +=3D -lpthread =20 TEST_GEN_PROGS :=3D sigtrap_threads remove_on_exec watermark_signal mmap TEST_GEN_PROGS +=3D aux_kernel_usermode +TEST_PROGS +=3D aux_kernel.sh include ../lib.mk diff --git a/tools/testing/selftests/perf_events/aux_kernel.sh b/tools/test= ing/selftests/perf_events/aux_kernel.sh new file mode 100755 index 000000000000..431f59ef02e7 --- /dev/null +++ b/tools/testing/selftests/perf_events/aux_kernel.sh @@ -0,0 +1,563 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Perf AUX Kernel-Consumer API - Comprehensive Selftest +# +# Validates the perf-core AUX kernel-consumer API added by the +# perf_aux_support patch series: +# - perf_event_setup_aux() / perf_event_release_aux() +# - perf_event_aux_head() / perf_event_aux_tail_set() / perf_event_aux_c= opy() +# +# Test layers: +# 1. Build-time checks (Kconfig, Makefile, symbol exports, source) +# 2. KUnit suite (API contract: error paths, lifecycle, validation) +# 3. Userspace AUX regression (mmap AUX still works, aux_mmap_count +# independent, userspace+kernel coexistence) +# 4. Hardware AUX live test (if ARM SPE or Intel PT is available) +# 5. DAMON integration verification +# 6. Memory ordering/barrier static analysis +# 7. Boundary validation static analysis +# 8. Concurrency safety checks +# 9. User/kernel AUX isolation checks +# +# Usage: sudo ./aux_kernel.sh [--pmu arm_spe_0|intel_pt] +# +# Requirements: +# - CONFIG_PERF_EVENTS=3Dy +# - CONFIG_PERF_AUX_KERNEL_KUNIT_TEST=3Dy and CONFIG_KUNIT_DEBUGFS=3Dy +# - Root privileges for most sections + +set -e +PASSED=3D0; FAILED=3D0; SKIPPED=3D0 +pass() { echo " [PASS] $1"; PASSED=3D$((PASSED + 1)); } +fail() { echo " [FAIL] $1"; FAILED=3D$((FAILED + 1)); } +skip() { echo " [SKIP] $*"; SKIPPED=3D$((SKIPPED + 1)); } + +RESULTS_DIR=3D"/tmp/perf_aux_kernel_test_$$" +mkdir -p "$RESULTS_DIR" +exec > >(tee "$RESULTS_DIR/output.log") 2>&1 + +SCRIPT_DIR=3D"$(cd "$(dirname "$0")" && pwd)" +ROOT=3D"$(git rev-parse --show-toplevel 2>/dev/null || true)" +if [[ -z "$ROOT" ]]; then + ROOT=3D"/usr/src/linux" +fi + +PMU_ARG=3D"${1:-}" +if [[ "$PMU_ARG" =3D=3D "--pmu" ]]; then + PMU_NAME=3D"${2:-arm_spe_0}" +elif [[ -n "$PMU_ARG" ]]; then + PMU_NAME=3D"$PMU_ARG" +else + PMU_NAME=3D"arm_spe_0" +fi + +echo "=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D" +echo " Perf AUX Kernel-Consumer API Test" +echo "=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D" +echo "Kernel tree: $ROOT" +echo "Results dir: $RESULTS_DIR" +echo "Target PMU: $PMU_NAME" +echo "Script dir: $SCRIPT_DIR" +echo "" + +# ---- Section 1: Build-time checks ---- +echo "--- 1. Build-time Configuration ---" + +# Check that the KUnit test is built +KUNIT_SUITE=3D"/sys/kernel/debug/kunit/perf_aux_kernel" +if [[ -d "$KUNIT_SUITE" ]]; then + pass "KUnit suite 'perf_aux_kernel' is registered" +else + skip "KUnit suite 'perf_aux_kernel' not registered" \ + "(CONFIG_PERF_AUX_KERNEL_KUNIT_TEST not set)" +fi + +# Check that required symbols are exported +SYMBOLS_FILE=3D"/proc/kallsyms" +if [[ -r "$SYMBOLS_FILE" ]]; then + for sym in perf_event_setup_aux perf_event_release_aux \ + perf_event_aux_head perf_event_aux_tail_set perf_event_aux_copy; do + if grep -q "$sym" "$SYMBOLS_FILE" 2>/dev/null; then + pass "Symbol exported: $sym" + else + fail "Symbol not exported: $sym (patch not applied?)" + fi + done +else + skip "Symbol check" "/proc/kallsyms not readable" +fi + +# Check that the source files exist +SRC_EVENTS=3D"$ROOT/kernel/events" +for f in core.c ring_buffer.c internal.h; do + if [[ -f "$SRC_EVENTS/$f" ]]; then + pass "Source file: kernel/events/$f" + else + fail "Source file: kernel/events/$f missing" + fi +done + +# Check that the KUnit test source exists +if [[ -f "$SRC_EVENTS/aux_kernel_test.c" ]]; then + pass "Source file: kernel/events/aux_kernel_test.c" +else + skip "Source file: kernel/events/aux_kernel_test.c not found" +fi + +# Check that aux_kernel_count is in internal.h +if grep -q 'aux_kernel_count' "$SRC_EVENTS/internal.h" 2>/dev/null; then + pass "struct perf_buffer: aux_kernel_count field present" +else + fail "struct perf_buffer: aux_kernel_count field missing" +fi + +# Check that aux_kernel_count is used in perf_aux_output_begin() +if grep -q 'aux_kernel_count' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "ring_buffer.c: aux_kernel_count guard in perf_aux_output_begin()" +else + fail "ring_buffer.c: aux_kernel_count guard missing in perf_aux_output_be= gin()" +fi + +# Check that the new API functions are in core.c / ring_buffer.c +for func in perf_event_setup_aux perf_event_release_aux; do + if grep -q "int $func\|void $func" "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "core.c: $func() defined" + else + fail "core.c: $func() not found" + fi +done +for func in perf_event_aux_head perf_event_aux_tail_set perf_event_aux_cop= y; do + if grep -q "$func" "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "ring_buffer.c: $func() defined" + else + fail "ring_buffer.c: $func() not found" + fi +done + +# Check that EXPORT_SYMBOL_GPL is present +for func in perf_event_setup_aux perf_event_release_aux \ + perf_event_aux_head perf_event_aux_tail_set perf_event_aux_copy; do + if grep -q "EXPORT_SYMBOL_GPL($func)" "$SRC_EVENTS/core.c" \ + "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "EXPORT_SYMBOL_GPL: $func" + else + fail "EXPORT_SYMBOL_GPL: $func not found" + fi +done + +# ---- Section 2: KUnit API contract tests ---- +echo "" +echo "--- 2. KUnit API Contract Tests ---" + +KUNIT_DIR=3D"/sys/kernel/debug/kunit/perf_aux_kernel" +if [[ -d "$KUNIT_DIR" ]]; then + if [[ -f "$KUNIT_DIR/run" ]]; then + echo run > "$KUNIT_DIR/run" 2>/dev/null || true + fi + RES=3D"$KUNIT_DIR/results" + if [[ -f "$RES" ]]; then + cp "$RES" "$RESULTS_DIR/kunit-results.log" + + if grep -Eq "^[[:space:]]*not ok" "$RES"; then + fail "KUnit API contract suite has failing cases" + grep -E "^[[:space:]]*not ok" "$RES" | sed 's/^/ /' + elif grep -Eq "^[[:space:]]*ok " "$RES"; then + pass "KUnit API contract suite: all cases pass" + else + fail "KUnit API contract suite produced no completed cases" + fi + echo " $(grep '# Totals:' "$RES" | tail -1)" + + # Spot-check all 38 test cases (deterministic, dummy PMU) + for c in "test_setup_rejects_non_power_of_two" \ + "test_setup_accepts_power_of_two" \ + "test_setup_rejects_double" \ + "test_setup_parent_event_rejected" \ + "test_setup_rejects_negative_watermark" \ + "test_setup_with_explicit_watermark" \ + "test_writer_admitted_after_setup" \ + "test_writer_blocked_after_release" \ + "test_release_noop_without_rb" \ + "test_setup_release_roundtrip" \ + "test_multi_setup_release_cycle" \ + "test_double_release_clean" \ + "test_two_events_independent" \ + "test_release_one_does_not_affect_other" \ + "test_user_kernel_coexistence" \ + "test_concurrent_release" \ + "test_head_initial_zero" \ + "test_head_advances_after_produce" \ + "test_head_no_rb_zero" \ + "test_copy_full_window" \ + "test_copy_wrap" \ + "test_copy_zero_len" \ + "test_copy_data_correctness" \ + "test_copy_rejects_rewound" \ + "test_copy_rejects_future" \ + "test_copy_rejects_oversized" \ + "test_copy_rejects_null_buf" \ + "test_copy_rejects_already_consumed" \ + "test_copy_no_rb_enoent" \ + "test_tail_set_valid_frees_space" \ + "test_tail_set_rejects_future" \ + "test_tail_set_accepts_within_ring_window" \ + "test_tail_set_rejects_behind_ring" \ + "test_tail_set_noop" \ + "test_tail_set_consume_all" \ + "test_tail_set_no_rb_enoent" \ + "test_multi_produce_copy_cycle" \ + "test_output_end_zero_size" \ + "test_buffer_full_stops_producer"; do + if grep -Eq "^[[:space:]]*ok .*$c" "$RES"; then + pass "KUnit case: $c" + else + fail "KUnit case: $c" + fi + done + else + skip "KUnit results" "no results file (suite did not run)" + fi +else + skip "KUnit API contract" "CONFIG_PERF_AUX_KERNEL_KUNIT_TEST" \ + "or CONFIG_KUNIT_DEBUGFS missing" +fi + +# ---- Section 3: Userspace AUX regression (C program) ---- +echo "" +echo "--- 3. Userspace AUX Regression (api-level) ---" + +# Build and run the C regression test program +UMODE_SRC=3D"$SCRIPT_DIR/aux_kernel_usermode.c" +UMODE_BIN=3D"$SCRIPT_DIR/aux_kernel_usermode" + +if [[ -f "$UMODE_SRC" ]]; then + if [[ ! -x "$UMODE_BIN" ]] || [[ "$UMODE_SRC" -nt "$UMODE_BIN" ]]; then + echo " Building aux_kernel_usermode..." + gcc -o "$UMODE_BIN" "$UMODE_SRC" -Wall -Wextra -O2 2>&1 | \ + sed 's/^/ /' || { + skip "aux_kernel_usermode" "build failed" + UMODE_BIN=3D"" + } + fi + + if [[ -n "$UMODE_BIN" && -x "$UMODE_BIN" ]]; then + if "$UMODE_BIN" "$PMU_NAME" 2>&1 | tee "$RESULTS_DIR/usermode.log" | \ + grep -c '\[PASS\]' > /dev/null; then + # Count pass/fail from the output + UMODE_PASS=3D$(grep -c '\[PASS\]' "$RESULTS_DIR/usermode.log" || true) + UMODE_FAIL=3D$(grep -c '\[FAIL\]' "$RESULTS_DIR/usermode.log" || true) + UMODE_SKIP=3D$(grep -c '\[SKIP\]' "$RESULTS_DIR/usermode.log" || true) + pass "Userspace API regression: $UMODE_PASS passed," \ + " $UMODE_FAIL failed, $UMODE_SKIP skipped" + if [[ "$UMODE_FAIL" -gt 0 ]]; then + fail "Userspace API regression: $UMODE_FAIL failures" + fi + else + fail "Userspace API regression: C program failed" + fi + else + skip "Userspace API regression" "C program not available" + fi +else + skip "Userspace API regression" "$UMODE_SRC not found" +fi + +# Check that aux_mmap_count refcount is not affected by aux_kernel_count +# (static analysis: verify the two refcounts are separate in internal.h) +if grep -q 'aux_mmap_count' "$SRC_EVENTS/internal.h" 2>/dev/null && \ + grep -q 'aux_kernel_count' "$SRC_EVENTS/internal.h" 2>/dev/null; then + pass "Refcount separation: aux_mmap_count and aux_kernel_count are distin= ct" +else + fail "Refcount separation: aux_mmap_count or aux_kernel_count missing" +fi + +# ---- Section 4: Hardware AUX live test ---- +echo "" +echo "--- 4. Hardware AUX Live Test ---" + +PMU_DIR=3D"/sys/bus/event_source/devices/$PMU_NAME" +PERF_BIN=3D$(which perf 2>/dev/null || true) +if [[ -d "$PMU_DIR" ]]; then + PMU_TYPE=3D$(cat "$PMU_DIR/type" 2>/dev/null || true) + if [[ -n "$PMU_TYPE" ]]; then + pass "AUX PMU $PMU_NAME present (type=3D$PMU_TYPE)" + + if [[ -f "$PMU_DIR/caps/aux_output" ]]; then + pass "PMU $PMU_NAME: aux_output capability present" + else + skip "PMU $PMU_NAME: aux_output capability not found" + fi + + if [[ -n "$PERF_BIN" ]]; then + DMESG_BEFORE=3D$(dmesg 2>/dev/null | wc -l) + if "$PERF_BIN" record -e "$PMU_NAME/period=3D100000/" \ + -o "$RESULTS_DIR/perf_hw.data" -- sleep 0.5 2>/dev/null; then + pass "Hardware AUX: perf record with $PMU_NAME succeeded" + else + fail "Hardware AUX: perf record with $PMU_NAME failed" + fi + + DMESG_AFTER=3D"$RESULTS_DIR/dmesg-hardware.log" + dmesg 2>/dev/null > "$DMESG_AFTER" || true + DMESG_LINES_AFTER=3D$(wc -l < "$DMESG_AFTER") + if [[ "$DMESG_LINES_AFTER" -ge "$DMESG_BEFORE" ]]; then + NEW_DMESG=3D$(tail -n "+$((DMESG_BEFORE + 1))" "$DMESG_AFTER") + else + NEW_DMESG=3D$(cat "$DMESG_AFTER") + fi + FAIL_LINES=3D$(printf '%s\n' "$NEW_DMESG" | \ + grep -Ei "perf.*(fail|warn|error).*aux|"\ +"WARNING:|BUG:|Oops:|lockdep" || true) + if [[ -n "$FAIL_LINES" ]]; then + fail "dmesg: errors during hardware AUX test" + echo "$FAIL_LINES" | sed 's/^/ /' + else + pass "dmesg: clean during hardware AUX test" + fi + fi + else + skip "Hardware AUX" "PMU type not readable" + fi +else + skip "Hardware AUX" "PMU $PMU_NAME not available" +fi + +# ---- Section 5: DAMON integration check ---- +echo "" +echo "--- 5. DAMON Integration Verification ---" + +DAMON_ADMIN=3D"/sys/kernel/mm/damon/admin" +if [[ -d "$DAMON_ADMIN" ]]; then + pass "DAMON sysfs interface available" + + if [[ -d "$DAMON_ADMIN/kdamonds/0/contexts/0/monitoring_attrs/sample/perf= _events" ]] || \ + [[ -d "/sys/kernel/debug/damon/perf_stats" ]]; then + pass "DAMON perf observe infrastructure detected" + else + skip "DAMON perf observe" "debugfs or sysfs interface not available" + fi + + DAMON_TEST_DIR=3D"$ROOT/tools/testing/selftests/damon" + if [[ -d "$DAMON_TEST_DIR" ]]; then + pass "DAMON selftest directory present" + else + skip "DAMON selftest directory" "not found at $DAMON_TEST_DIR" + fi +else + skip "DAMON integration" "DAMON admin interface not available" +fi + +# ---- Section 6: Memory ordering and concurrency checks ---- +echo "" +echo "--- 6. Memory Ordering & Concurrency ---" + +# Verify smp_rmb() in perf_event_aux_head() +if grep -q 'smp_rmb' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "perf_event_aux_head(): smp_rmb() barrier present" +else + fail "perf_event_aux_head(): smp_rmb() barrier missing" +fi + +# Verify smp_mb() in perf_event_aux_tail_set() +if grep -A30 'perf_event_aux_tail_set' "$SRC_EVENTS/ring_buffer.c" 2>/dev/= null | \ + grep -q 'smp_mb'; then + pass "perf_event_aux_tail_set(): smp_mb() barrier present" +else + fail "perf_event_aux_tail_set(): smp_mb() barrier missing" +fi + +# Verify aux_refcount protection in perf_event_aux_copy() +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null= | \ + grep -q 'aux_refcount'; then + pass "perf_event_aux_copy(): aux_refcount protection present" +else + fail "perf_event_aux_copy(): aux_refcount protection missing" +fi + +# Verify mmap_mutex serialization in setup/release +if grep -A30 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'mmap_mutex'; then + pass "perf_event_setup_aux(): mmap_mutex serialization present" +else + fail "perf_event_setup_aux(): mmap_mutex serialization missing" +fi +if grep -A30 'perf_event_release_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'mmap_mutex'; then + pass "perf_event_release_aux(): mmap_mutex serialization present" +else + fail "perf_event_release_aux(): mmap_mutex serialization missing" +fi + +# Verify aux_mutex protection in release +if grep -A20 'perf_event_release_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'aux_mutex'; then + pass "perf_event_release_aux(): aux_mutex protection present" +else + fail "perf_event_release_aux(): aux_mutex protection missing" +fi + +# ---- Section 7: Boundary validation checks ---- +echo "" +echo "--- 7. API Contract: Boundary Validation ---" + +# perf_event_setup_aux() rejects non-power-of-2 +if grep -q 'is_power_of_2' "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "perf_event_setup_aux(): is_power_of_2() check present" +else + fail "perf_event_setup_aux(): is_power_of_2() check missing" +fi + +# perf_event_setup_aux() checks is_kernel_event() +if grep -q 'is_kernel_event' "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "perf_event_setup_aux(): is_kernel_event() check present" +else + fail "perf_event_setup_aux(): is_kernel_event() check missing" +fi + +# perf_event_setup_aux() checks event->parent +if grep -A20 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'event->parent'; then + pass "perf_event_setup_aux(): event->parent check present" +else + fail "perf_event_setup_aux(): event->parent check missing" +fi + +# perf_event_aux_copy() validates window size +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null= | \ + grep -q 'available.*aux_size'; then + pass "perf_event_aux_copy(): window bounds check present" +else + fail "perf_event_aux_copy(): window bounds check missing" +fi + +# rb_has_kernel_aux() helper +if grep -q 'rb_has_kernel_aux' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; th= en + pass "ring_buffer.c: rb_has_kernel_aux() helper defined" +else + fail "ring_buffer.c: rb_has_kernel_aux() helper missing" +fi + +# perf_event_aux_tail_set() validates advance <=3D head - old_tail +if grep -A20 'perf_event_aux_tail_set' "$SRC_EVENTS/ring_buffer.c" 2>/dev/= null | \ + grep -q 'advance.*head.*old_tail'; then + pass "perf_event_aux_tail_set(): advance bounds check present" +else + fail "perf_event_aux_tail_set(): advance bounds check missing" +fi + +# perf_event_aux_copy() validates from/to in [tail, head] +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null= | \ + grep -q 'start.*available\|len.*available.*start'; then + pass "perf_event_aux_copy(): from/to bounds check present" +else + fail "perf_event_aux_copy(): from/to bounds check missing" +fi + +# ---- Section 8: Concurrency safety checks ---- +echo "" +echo "--- 8. Concurrency Safety ---" + +# Check that aux_kernel_count uses refcount_t (atomic) +if grep -q 'refcount_t.*aux_kernel_count' "$SRC_EVENTS/internal.h" 2>/dev/= null; then + pass "aux_kernel_count: refcount_t (atomic) type" +else + fail "aux_kernel_count: not refcount_t" +fi + +# Check that aux_kernel_count is used with refcount_dec_and_mutex_lock +if grep -q 'refcount_dec_and_mutex_lock.*aux_kernel_count' "$SRC_EVENTS/co= re.c" 2>/dev/null; then + pass "aux_kernel_count: refcount_dec_and_mutex_lock() in release" +else + fail "aux_kernel_count: refcount_dec_and_mutex_lock() missing in release" +fi + +# Check that aux_refcount is incremented in copy +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null= | \ + grep -q 'refcount_inc_not_zero.*aux_refcount'; then + pass "perf_event_aux_copy(): aux_refcount_inc_not_zero() present" +else + fail "perf_event_aux_copy(): aux_refcount_inc_not_zero() missing" +fi + +# Check that rb_free_aux is called at end of copy (release refcount) +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null= | \ + grep -q 'rb_free_aux'; then + pass "perf_event_aux_copy(): rb_free_aux() at exit (balanced refcount)" +else + fail "perf_event_aux_copy(): rb_free_aux() missing at exit" +fi + +# Check that is_kernel_event guard is on both setup and release +if grep -A20 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'is_kernel_event'; then + pass "setup side: is_kernel_event() guard present" +else + fail "setup side: is_kernel_event() guard missing" +fi +if grep -A20 'perf_event_release_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'is_kernel_event'; then + pass "release side: is_kernel_event() guard present" +else + fail "release side: is_kernel_event() guard missing" +fi + +# Check that event->parent guard is on both setup and release +if grep -A20 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'event->parent'; then + pass "setup side: event->parent guard present" +else + fail "setup side: event->parent guard missing" +fi +if grep -A20 'perf_event_release_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'event->parent'; then + pass "release side: event->parent guard present" +else + fail "release side: event->parent guard missing" +fi + +# ---- Section 9: User/kernel AUX isolation ---- +echo "" +echo "--- 9. User/Kernel AUX Isolation ---" + +if grep -A30 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'EBUSY'; then + pass "setup_aux: -EBUSY when event->rb exists (user+kernel exclusion)" +else + fail "setup_aux: missing -EBUSY guard" +fi + +if grep -q 'aux_mmap_count' "$SRC_EVENTS/internal.h" 2>/dev/null && \ + grep -q 'aux_kernel_count' "$SRC_EVENTS/internal.h" 2>/dev/null; then + pass "aux_mmap_count and aux_kernel_count are distinct fields" +else + fail "aux_mmap_count or aux_kernel_count missing" +fi + +if grep -B2 -A2 'aux_mmap_count' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null |= \ + grep -q 'aux_kernel_count'; then + pass "perf_aux_output_begin: checks both owner counts" +else + fail "perf_aux_output_begin: does not check both counts" +fi + +if grep -q 'aux_kernel_count' "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "release_aux: checks aux_kernel_count ownership" +else + fail "release_aux: missing aux_kernel_count check" +fi + +# ---- Summary ---- +echo "" +echo "=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D" +echo " SUMMARY: $PASSED passed, $FAILED failed, $SKIPPED skipped" +echo "=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D" +echo "Results saved to: $RESULTS_DIR" + +if [[ "$FAILED" -gt 0 ]]; then + echo "Overall: FAIL" + exit 1 +else + echo "Overall: PASS" + exit 0 +fi --=20 2.43.0