From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=205.220.180.131 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793681352; b=EjdFf+zHqVm1tp2nQzOhBYKCAIYADVcbDsaKR7F1ZspEseguRuTbnTmclGnasuiGrHvI L78nwoS60jEcD60LYdyvOfwZwI8Z8DE2LpPPWFT8zPfMmbRYueJ5bioK1QZXzwAv/pvts LcqFHABGpTVVQFKyTxEj/hnIW9h33y7s966YBWEUXZ4NVOr7GI7qAHacbte5wc/VXmF23 Qw4HXHjE4MlVv/yKrOWQ2DGkcC1ZOPm9WiAjmBrGIgFybTVeZbVcve84cNtZ7iHpERG0p NBkGKodUZkjpHYi702HN5j3ZXtwJ5/FAKoga/TDOu4+DRb6wnstG7ZiRWLjUuX/Ai2Q== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793681352; h=DMARC-Filter:Received:DKIM-Signature:Received:Received: DKIM-Signature:X-Google-DKIM-Signature:X-Forwarded-Encrypted: X-Gm-Message-State:X-Gm-Gg:X-Received:X-Received:Received:From:To:Cc: Subject:Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:X-Proofpoint-ORIG-GUID: X-Proofpoint-Spam-Details-Enc:X-Proofpoint-Spam-Info: X-Proofpoint-GUID:X-Authority-Analysis:X-Proofpoint-Virus-Version: X-Proofpoint-Spam-Details; bh=A7NsQZTNCEpuI76DkH0hq9WDv34r3f+hg60tw4bENak=; b=dtUcQ+OCW6+6Rj0xEKYbRiUw4sVP8kOU4/hHfGi9UeNnK6n399jonCeZojGhBvloix8j 2TBdEL0F4fknFnZe0RG5gQdh+FPebEdddKm1/IqH8Fc30dLPpq+hKVMLMwYT7HWF8iv/G kzW6K0w2DD3idCmc3xYZ5sadgpD8wKCY/+4WlGWEfjxw2sWfCMUPOIsbyv2YhXNzV97j5 SkFZEUe2ibWpB5iCuuCocs3NjMPl97hkJE6Hb68cpDTwCefA9TEm9rAPbLeJvF4SHRjjA /1iM3SA9mdmwQaOFEuNLBLz0lHAuTmUavxRLo4uvK9/t/ZgfhowHdhESG7rwRrWpofw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass header.d=qualcomm.com header.i=@qualcomm.com header.a=rsa-sha256 header.s=qcppdkim1 header.b=TuFzJHhT; dkim=pass header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.a=rsa-sha256 header.s=google header.b=dvs4luyi; arc=none smtp.remote-ip=205.220.180.131 Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 50ED2606A33E for ; Tue, 3 Nov 2026 04:49:06 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 50ED2606A33E Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=qualcomm.com header.i=@qualcomm.com header.a=rsa-sha256 header.s=qcppdkim1 header.b=TuFzJHhT; dkim=pass (2048-bit key, unprotected) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.a=rsa-sha256 header.s=google header.b=dvs4luyi Received: from pps.filterd (m0279868.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 6A31eNHO1082286 for ; Tue, 3 Nov 2026 04:49:05 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=qcppdkim1; bh=A7NsQZTNCEp uI76DkH0hq9WDv34r3f+hg60tw4bENak=; b=TuFzJHhTWHxQxHA0pTM41uZOYLe eo5Mai/4L2Qv7ZJen1fq6gxd/Lv1rL4sIM2VqDpBtUc2HvNemGBYPH4Uc0i0oeUo op6wmlvKd7HlQbvxx3lAnb/6gNbueTPPTy2a99tRop3KOBO7HnEK5kp3TEYRoTyU vaWEtv/lCcBG55hMMnF6v9i3TYeix5fEehqLmtlPyGf40IZk1xSup+xQtCyjf/2l Dbdrd/tBsmZqIQ7kTRG0B0cGnE1yW85Csr3j7AHX/B8qy4ziy7Y2wbN5K1/plZdh +EMB0/lUwE20FJ0K7fM6idctXAIc+OLBAoEbs81PIALJHo23xCb/qsHCvtA== Received: from mail-qv1-f71.google.com (mail-qv1-f71.google.com [209.85.219.71]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4hq07r1xyj-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Tue, 03 Nov 2026 04:49:04 +0000 (GMT) Received: by mail-qv1-f71.google.com with SMTP id 6a1803df08f44-91b4bf686ceso104439286d6.3 for ; Mon, 02 Nov 2026 20:49:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1793681344; x=1794286144; 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=A7NsQZTNCEpuI76DkH0hq9WDv34r3f+hg60tw4bENak=; b=dvs4luyil8YBCx46FTeQCZm1aaqQwTzBf71FrSgT5aAkO9sEU60QsaYFrRixJ07A0h gXyFdhSWrFuogfrgv8yelVAMf98xlAVQmysp3Lg85JSYpUFhtGGpAzzKkNMvoskQtraP d8vOSl0+yK1NNTrI85LHRLIo6jGhgz4HQT2okY1oVC4LynXPb256fg11MwzVdAsNSabq x1v32PqpG68/ToeTLJlHjKanNUYtGag95qmAtba0N0HGFpJhOk8VMr3EQznsdDhCx1Gf gg6qv8bYdKx86kDBZ1vXfKNgdY6y7H/aSDomDsmM+fNz9R00Hs38EOM6Zr8ECz4TyBd9 AmFg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793681344; x=1794286144; 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=A7NsQZTNCEpuI76DkH0hq9WDv34r3f+hg60tw4bENak=; b=wCACymXG5Ex2wKlYHK7knuEC5ac/WGk9zwjlS5RBmRQ4IM72I4IS1DmQo9dAWhKway sXNmb1eBWpVjvX3tq5yEE5e/7uoZoCU1W4d19syGJ6+tX4M4L8hWpp5z7+pXEDTWPePr ricPqW0g0MksWhDVtDQRbzxg5JHNQ/p6lwXq8sYBNQ2HGDOa4gwc/4D9hkAddRkAJFhW Rz6Dq4K8RNuzctLDgK0JtJMj17h/VFtu1dAAG5pbpQd8FBEWAigqDEhaD6m9chyBes/R qszwncAhJJMQAGN8FlU5JxclvekxTpqie32YGzs78yUV5j4TMQNddsb/0w1B0zPLuHou 8F9A== X-Forwarded-Encrypted: i=1; AKwUvBxaggHmKLG5Ps6ujcvt7OS6O1X7UwYyz/vU3eIVE1Gava/+JJGItMl7m3OaVs6RakbUk055buJehPDAwq0=@vger.kernel.org X-Gm-Message-State: APuU4Z+4ygNPZQS9/NhZWKAOLU/QQUDj9AjYQOOM5nuioRwoPP6Vu5j7 6UhpSKOga+4GgoJpJj1tm9xV5IHwQXrJcslX6F9xPwi+rlkqGyz2AAkJyZduEv87en4wwkQ7vLQ imn6IDhziADoMPSIHwi1QC4w2xdZHzfwZz2VfcQ/qrWmqZB0ZWKFIr5xO1JkiDI3CkqQ= X-Gm-Gg: AYBFou0ilz3RX51QKuPWJ144COsW4X5p9EdWQUUioq+2leaYLFHaAi5lxiIH4ZB2vQA mGLSZl9kRo5u5gRiHh6ooJJTtokvTZfdV70Ph+BG+MdnT1qm9kaRq7gpekYNEZ7tYB7NDy3UAuV TcTn7XF1v8IRL9V/779X2LoYGqA44ngbecevIv3GYz1uPdrvKZPVOcwhBdfpTSADupc6YWQr9xf 1JH4D3NMCIxhz+1l080Tlh455vGFvztqGbKAMrcumNTQuMm3NFeySIeUPTSwvvLIGqxLX887I2p fDCkjDtLaEf1Bw2eeYGh920Jd7JfGf9e+0QgB7KAF0Ajizmd6ejMl0Y25OcHFy1fs3Nlm5SYZpr 1y/aLmwXRHuxRowh/i8oe7E2bwrOD4MfUQBWDi1l4spNBPHK30MpJzc0gyDN1a7ppoa2p/gmrhw 1z X-Received: by 2002:a05:620a:28c7:b0:93c:720c:94e0 with SMTP id af79cd13be357-94421736bc4mr1947323885a.30.1793681344153; Mon, 02 Nov 2026 20:49:04 -0800 (PST) X-Received: by 2002:a05:620a:28c7:b0:93c:720c:94e0 with SMTP id af79cd13be357-94421736bc4mr1947321685a.30.1793681343579; Mon, 02 Nov 2026 20:49:03 -0800 (PST) Received: from mai.box.freepro.com ([2a05:6e02:1041:c10:8b9e:7797:dd05:11cb]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4b2a879fb29sm25685445e9.4.2026.11.02.20.49.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Nov 2026 20:49:02 -0800 (PST) From: Daniel Lezcano To: rafael@kernel.org Cc: Daniel Lezcano , Heiko Stuebner , Shuah Khan , Manaf Meethalavalappu Pallikunhi , Lukasz Luba , linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-kselftest@vger.kernel.org Subject: [PATCH v5 2/6] selftests/powercap: Add powercap hierarchy creation API tests Date: Tue, 3 Nov 2026 05:48:16 +0100 Message-ID: X-Mailer: git-send-email 2.43.0 In-Reply-To: References: X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Proofpoint-ORIG-GUID: pp6WNO9O8fWb3YaNvtnl3NQ8us2aeSUZ X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYxMTAzMDAxOCBTYWx0ZWRfX2+JnJU4fT9dt EOtG9V6Uhdhi/HbYYrgwWvD89q2iPFrfwiHLG2Q/Qsva/UlyGe/F+iQfJmzxYE8IAg+pxQFah53 1eRXBbU8aWTaI6gEaslu9OqN7MwQ80a5hRKashLN1zV8Lr7Bpz0AhMU3qMbtZQAeNbuLeCs2XJc Q2TXBVcF5q4h2ffVKUAGtO9fnH1AL5Cev4vSUL+nsIoKmidoMx7gGU7ollyDsoTL0K0NQFk+ogw rVWWnL+JjzkfGTenbPlCcY857jy+lmarz+b2nR47+kZpeDdZbRGDTuVk6a+bJ3+HgYrdL84/VCx wZoKVvNUngLfN6q5uzTZiLhm+tN53yKojkLsL0aJQublPfTWYAS3b5F5pbHfGgorabn/K38uWvu zbZlQRkhotr8DpbwIAHOCyqnvJHW1Q/o/VoG2GWPTrLgB1CTNqxNpAVTWM7O6Gi3NEyuC491yUK BauuZ09H/PfmEWIFFXg== X-Proofpoint-Spam-Info: AW1haW4tMjYxMTAzMDAxOCBTYWx0ZWRfX0WfxLNutO/2M 7U5xFhGeo+bNpOg+Ufe5s6gur+KhVMx0Qg2DoISCgEA8WG7aSD5csyMVTrNCSF0GXhp7KA9E3lA dx9NaaYj1DlbyR0yduVvJyE91ZURvxo= X-Proofpoint-GUID: pp6WNO9O8fWb3YaNvtnl3NQ8us2aeSUZ X-Authority-Analysis: v=2.4 cv=NZlzRGD4 c=1 sm=1 tr=0 ts=6ae967c1 cx=c_pps a=UgVkIMxJMSkC9lv97toC5g==:117 a=xqWC_Br6kY4A:10 a=zJRp6_cAMcgA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=ZpdpYltYx_vBUK5n70dp:22 a=bC-a23v3AAAA:8 a=EUspDBNiAAAA:8 a=HObOqzEOzm0AAkIsdZgA:9 a=1HOtulTD9v-eNWfpl4qZ:22 a=FO4_E8m0qiDe52t0p3_H:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.138,FMLib:17.12.100.49 definitions=2026-11-03_01,2026-11-02_03,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 bulkscore=0 phishscore=0 priorityscore=1501 suspectscore=0 clxscore=1015 adultscore=0 lowpriorityscore=0 impostorscore=0 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2610300000 definitions=main-2611030018 The powercap hierarchy API introduces generic helpers to duplicate, instantiate and destroy a complete powercap hierarchy from a static description. Add a kselftest exercising this API. The test builds a synthetic powercap hierarchy composed of a package, three CPU clusters and twelve CPUs, registers it as a powercap control type and checks the zone count and selected sysfs attribute values. It also checks that the controller directory disappears when the module is unloaded. Exercise direct static creation, duplication, skipped clusters and their descendants, reuse after partial destruction, and repeated creation returning -EBUSY without replacing existing zone pointers. Callback checks verify the controller, name and backend-private data supplied by the helpers. The test consists of: * a kernel module implementing a synthetic powercap hierarchy and dummy callbacks; * a userspace kselftest script that loads the module, checks 16 zones and their power and constraint attributes, unloads the module with an attribute descriptor open, and checks controller removal. Use powercap-owned zone allocations so registration failures are cleaned up by the core. Build generated module files in the selftest output directory and extend the common clean target with Kbuild module cleanup. Provide a config fragment for powercap and module support. Skip module builds when the configuration prerequisites or Module.symvers are missing, and skip runtime checks when powercap or the test module is unavailable. Resolve inherited kernel output paths before invoking Kbuild so cleanup also works with a relative O= argument. Keep the statically initialized mutex in permanent module data so its lockdep key survives initialization. Use a separate init-only hierarchy description to exercise duplication and release the original node array. This provides a regression test for the powercap hierarchy helpers and their integration with the powercap core. Cc: Manaf Meethalavalappu Pallikunhi Link: https://patch.msgid.link/20260806110159.69690-3-daniel.lezcano@oss.qualcomm.com Signed-off-by: Daniel Lezcano --- Changes in v5 (since v4): - No changes. Changes in v4 (since v3): - Rebase on v7.3 and address the issues reported by Sashiko. - Add a config fragment requesting powercap, modules and module unloading; guard the Kbuild module target with CONFIG_POWERCAP. - Skip module builds when configuration prerequisites or Module.symvers are missing, avoiding unresolved-symbol failures during modpost. - Resolve inherited output paths and pass absolute O=, KBUILD_OUTPUT and module-output paths to Kbuild, including during relative-O= cleanup. - Keep the static mutex in permanent module data so its lockdep key survives init-memory reclamation; duplicate a separate init-only hierarchy description. - Return KSFT_SKIP when powercap sysfs support or the module is absent. - Update the commit description to explain these fixes. Changes in v3 (since v2): - Rebase on v7.3-rc7. - Use TEST_GEN_FILES and MO=$(OUTPUT); preserve the common clean recipe. - Use powercap-owned allocations for consistent registration cleanup. - Keep validation and counting in the main shell so failures propagate. - Add static creation, skipped branches, reuse and -EBUSY checks, and hold a sysfs descriptor open across module removal. - Document the tests and describe their actual assertions. - Fix closing-brace indentation and __initdata placement. tools/testing/selftests/Makefile | 1 + tools/testing/selftests/powercap/Kbuild | 5 + tools/testing/selftests/powercap/Makefile | 46 +++ tools/testing/selftests/powercap/config | 3 + .../selftests/powercap/powercap_hierarchy.c | 314 ++++++++++++++++++ .../selftests/powercap/powercap_hierarchy.sh | 128 +++++++ 6 files changed, 497 insertions(+) create mode 100644 tools/testing/selftests/powercap/Kbuild create mode 100644 tools/testing/selftests/powercap/Makefile create mode 100644 tools/testing/selftests/powercap/config create mode 100644 tools/testing/selftests/powercap/powercap_hierarchy.c create mode 100755 tools/testing/selftests/powercap/powercap_hierarchy.sh diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index bc4fdb6f5337..764cca6182ce 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -104,6 +104,7 @@ TARGETS += pidfd TARGETS += pid_namespace TARGETS += pipe TARGETS += power_supply +TARGETS += powercap TARGETS += powerpc TARGETS += prctl TARGETS += proc diff --git a/tools/testing/selftests/powercap/Kbuild b/tools/testing/selftests/powercap/Kbuild new file mode 100644 index 000000000000..9300e779db0d --- /dev/null +++ b/tools/testing/selftests/powercap/Kbuild @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +ifeq ($(CONFIG_POWERCAP),y) +obj-m += powercap_hierarchy.o +endif diff --git a/tools/testing/selftests/powercap/Makefile b/tools/testing/selftests/powercap/Makefile new file mode 100644 index 000000000000..39d9ec73ba74 --- /dev/null +++ b/tools/testing/selftests/powercap/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0 + +# User-space test script +TEST_PROGS := powercap_hierarchy.sh + +KERNEL_SRC := $(realpath ../../../..) +KERNEL_OUTPUT := $(or $(O),$(KBUILD_OUTPUT)) +# The selftests parent exports BUILD as /kselftest. +# Use it when clean inherits a relative O= through MAKEFLAGS. +KDIR ?= $(if $(KERNEL_OUTPUT),$(if $(BUILD),$(BUILD)/..,$(if $(filter /%,$(KERNEL_OUTPUT)),$(KERNEL_OUTPUT),$(KERNEL_SRC)/$(KERNEL_OUTPUT))),$(KERNEL_SRC)) +KERNEL_BUILD := $(abspath $(KDIR)) + +-include $(KERNEL_BUILD)/include/config/auto.conf + +ifeq ($(CONFIG_POWERCAP)$(CONFIG_MODULES)$(CONFIG_MODULE_UNLOAD),yyy) +ifneq ($(wildcard $(KERNEL_BUILD)/Module.symvers),) +TEST_GEN_FILES := powercap_hierarchy.ko +endif +endif + +.DEFAULT_GOAL := all + +include ../lib.mk + +ifeq ($(TEST_GEN_FILES),) +all: + @echo "SKIP: powercap module needs CONFIG_POWERCAP, CONFIG_MODULES, CONFIG_MODULE_UNLOAD and a full kernel build" +endif + +.PHONY: modules + +$(OUTPUT)/powercap_hierarchy.ko: modules + +modules: + $(MAKE) -C $(KERNEL_BUILD) O=$(KERNEL_BUILD) KBUILD_OUTPUT=$(KERNEL_BUILD) \ + M=$(CURDIR) MO=$(abspath $(OUTPUT)) modules + +.PHONY: clean_modules + +clean: clean_modules + +clean_modules: + @if [ -f "$(KERNEL_BUILD)/Makefile" ] && [ -d "$(OUTPUT)" ]; then \ + $(MAKE) -C $(KERNEL_BUILD) O=$(KERNEL_BUILD) KBUILD_OUTPUT=$(KERNEL_BUILD) \ + M=$(CURDIR) MO=$(abspath $(OUTPUT)) clean; \ + fi diff --git a/tools/testing/selftests/powercap/config b/tools/testing/selftests/powercap/config new file mode 100644 index 000000000000..76ae5bcb1dde --- /dev/null +++ b/tools/testing/selftests/powercap/config @@ -0,0 +1,3 @@ +CONFIG_POWERCAP=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y diff --git a/tools/testing/selftests/powercap/powercap_hierarchy.c b/tools/testing/selftests/powercap/powercap_hierarchy.c new file mode 100644 index 000000000000..e43d510d5426 --- /dev/null +++ b/tools/testing/selftests/powercap/powercap_hierarchy.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * Author: Daniel Lezcano + * + * Powercap hierarchy description test module + * + * Test coverage: + * - Duplicate an init-only description for use after module initialization. + * - Create and destroy a hierarchy directly from a static initializer. + * - Skip a cluster and its children while creating independent branches. + * - Destroy the partial hierarchy and create the complete hierarchy again. + * - Reject repeated creation with -EBUSY and preserve existing zone pointers. + * - Check callback arguments and preservation of backend-private data. + * + * The complete hierarchy remains registered for powercap_hierarchy.sh to + * check its 16 zones and exported attribute values. Module removal destroys + * it so the script can check cleanup with a sysfs descriptor still open. + */ +#include +#include + +struct pch_test_data { + int value; +}; + +static struct powercap_node pch_test_nodes[] __initdata = { + [0] = { .name = "package", + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [1] = { .name = "cluster0", .parent = &pch_test_nodes[0], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [2] = { .name = "cluster1", .parent = &pch_test_nodes[0], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [3] = { .name = "cluster2", .parent = &pch_test_nodes[0], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [4] = { .name = "cpu0", .parent = &pch_test_nodes[1], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [5] = { .name = "cpu1", .parent = &pch_test_nodes[1], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [6] = { .name = "cpu2", .parent = &pch_test_nodes[1], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [7] = { .name = "cpu3", .parent = &pch_test_nodes[1], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [8] = { .name = "cpu4", .parent = &pch_test_nodes[2], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [9] = { .name = "cpu5", .parent = &pch_test_nodes[2], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [10] = { .name = "cpu6", .parent = &pch_test_nodes[2], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [11] = { .name = "cpu7", .parent = &pch_test_nodes[2], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [12] = { .name = "cpu8", .parent = &pch_test_nodes[3], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [13] = { .name = "cpu9", .parent = &pch_test_nodes[3], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [14] = { .name = "cpu10", .parent = &pch_test_nodes[3], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, + [15] = { .name = "cpu11", .parent = &pch_test_nodes[3], + .data = &(struct pch_test_data) { .value = 0xDEADBEEF } }, +}; + +static struct powercap_hierarchy pch_test_description __initdata = { + .nodes = pch_test_nodes, + .nr_nodes = ARRAY_SIZE(pch_test_nodes), +}; + +/* A static mutex's lockdep key must outlive the module's init sections. */ +static struct powercap_hierarchy pch_test_hierarchy = + POWERCAP_HIERARCHY_INIT(pch_test_hierarchy, NULL, 0); + +static struct powercap_hierarchy *hierarchy; + +static struct powercap_control_type *pct; + +static int pch_test_get_max_power_range_uw(struct powercap_zone *pcz, + u64 *power_uw) +{ + *power_uw = 0xBADC0FFEE; + return 0; +} + +static int pch_test_get_power_uw(struct powercap_zone *pcz, + u64 *power_uw) +{ + *power_uw = 0xC0FFEE; + return 0; +} + +static const struct powercap_zone_ops pch_test_ops = { + .get_max_power_range_uw = pch_test_get_max_power_range_uw, + .get_power_uw = pch_test_get_power_uw, +}; + +static int pch_test_set_power_limit_uw(struct powercap_zone *pcz, + int cid, u64 power_uw) +{ + return 0; +} + +static int pch_test_get_power_limit_uw(struct powercap_zone *pcz, + int cid, u64 *power_uw) +{ + *power_uw = 0xDEADC0DE; + return 0; +} + +static int pch_test_set_time_window_us(struct powercap_zone *pcz, + int cid, u64 power_uw) +{ + return 0; +} + +static int pch_test_get_time_window_us(struct powercap_zone *pcz, + int cid, u64 *power_uw) +{ + *power_uw = 0xDEADC0DE; + return 0; +} + +static int pch_test_get_max_power_uw(struct powercap_zone *pcz, + int cid, u64 *power_uw) +{ + *power_uw = 0xDEADC0DE; + return 0; +} + +static const char *pch_test_get_name(struct powercap_zone *pcz, int cid) +{ + return "my constraint name"; +} + +static const struct powercap_zone_constraint_ops pch_test_constraint_ops = { + .set_power_limit_uw = pch_test_set_power_limit_uw, + .get_power_limit_uw = pch_test_get_power_limit_uw, + .set_time_window_us = pch_test_set_time_window_us, + .get_time_window_us = pch_test_get_time_window_us, + .get_max_power_uw = pch_test_get_max_power_uw, + .get_name = pch_test_get_name, +}; + +static struct powercap_zone *pch_test_create(struct powercap_control_type *pct, + const char *name, void *data, + struct powercap_zone *parent) +{ + struct pch_test_data *pcht_data = data; + struct powercap_zone *pcz; + + /* Validate the arguments and private data supplied by the helper. */ + if (!pct) { + pr_err("Invalid NULL controller type\n"); + return ERR_PTR(-EINVAL); + } + + if (!name) { + pr_err("Invalid NULL name\n"); + return ERR_PTR(-EINVAL); + } + + if (pcht_data->value != 0xDEADBEEF) { + pr_err("Invalid pcht data != 0xDEADBEEF"); + return ERR_PTR(-EINVAL); + } + + pcz = powercap_register_zone(NULL, pct, name, parent, + &pch_test_ops, 1, &pch_test_constraint_ops); + if (IS_ERR(pcz)) { + pr_err("Failed to register powercap zone '%s': %ld\n", + name, PTR_ERR(pcz)); + } + + return pcz; +} + +static void pch_test_destroy(struct powercap_control_type *pct, + struct powercap_zone *zone, + void *data) +{ + struct pch_test_data *pcht_data = data; + + /* Destruction must receive the original private data and a valid zone. */ + if (!pct) { + pr_err("Invalid NULL controller type\n"); + return; + } + + if (!zone) { + pr_err("Invalid NULL zone\n"); + return; + } + + if (pcht_data->value != 0xDEADBEEF) { + pr_err("Invalid pcht data != 0xDEADBEEF"); + return; + } + + powercap_unregister_zone(pct, zone); +} + +static struct powercap_zone * +pch_test_skip_cluster(struct powercap_control_type *pct, const char *name, + void *data, struct powercap_zone *parent) +{ + if (!strcmp(name, "cluster0")) + return NULL; + + return pch_test_create(pct, name, data, parent); +} + +static int __init pch_test_init(void) +{ + struct powercap_zone *zones[ARRAY_SIZE(pch_test_nodes)]; + size_t i; + int ret; + + /* Keep a runtime copy whose parent pointers survive init memory release. */ + hierarchy = powercap_hierarchy_dup(&pch_test_description); + if (IS_ERR(hierarchy)) { + ret = PTR_ERR(hierarchy); + pr_err("Failed to dup the hierarchy: %d\n", ret); + return ret; + } + + pct = powercap_register_control_type(NULL, "powercap-test", NULL); + if (IS_ERR(pct)) { + ret = PTR_ERR(pct); + pr_err("Failed to register control type: %d\n", ret); + goto out_free_hierarchy; + } + + /* Exercise direct creation with a statically initialized hierarchy lock. */ + pch_test_hierarchy.nodes = pch_test_nodes; + pch_test_hierarchy.nr_nodes = ARRAY_SIZE(pch_test_nodes); + ret = powercap_hierarchy_create(pct, &pch_test_hierarchy, + pch_test_create, pch_test_destroy); + if (ret) { + pr_err("Failed to create the static hierarchy: %d\n", ret); + goto out_unregister_pct; + } + + powercap_hierarchy_destroy(pct, &pch_test_hierarchy, pch_test_destroy); + pch_test_hierarchy.nodes = NULL; + pch_test_hierarchy.nr_nodes = 0; + + /* NULL skips cluster0 and its descendants, but not the other clusters. */ + ret = powercap_hierarchy_create(pct, hierarchy, + pch_test_skip_cluster, pch_test_destroy); + if (ret) + goto out_unregister_pct; + + for (i = 0; i < hierarchy->nr_nodes; i++) { + /* Node 1 is cluster0; nodes 4 through 7 are its four CPUs. */ + bool skipped = i == 1 || (i >= 4 && i <= 7); + + if (!!hierarchy->nodes[i].zone == skipped) { + pr_err("Unexpected zone state after skipping cluster0\n"); + ret = -EINVAL; + goto out_destroy_hierarchy; + } + } + + /* Partial destruction must leave the hierarchy reusable for a full tree. */ + powercap_hierarchy_destroy(pct, hierarchy, pch_test_destroy); + + ret = powercap_hierarchy_create(pct, hierarchy, pch_test_create, pch_test_destroy); + if (ret) { + pr_err("Failed to create the hierarchy: %d\n", ret); + goto out_unregister_pct; + } + + /* A second creation must return -EBUSY without replacing any zone. */ + for (i = 0; i < hierarchy->nr_nodes; i++) + zones[i] = hierarchy->nodes[i].zone; + + ret = powercap_hierarchy_create(pct, hierarchy, pch_test_create, pch_test_destroy); + if (ret != -EBUSY) { + pr_err("Expected -EBUSY when recreating the hierarchy, got %d\n", ret); + ret = -EINVAL; + goto out_destroy_hierarchy; + } + + for (i = 0; i < hierarchy->nr_nodes; i++) { + if (hierarchy->nodes[i].zone != zones[i]) { + pr_err("Recreation changed an existing zone\n"); + ret = -EINVAL; + goto out_destroy_hierarchy; + } + } + + /* Leave the full tree available for the userspace sysfs checks. */ + return 0; + +out_destroy_hierarchy: + powercap_hierarchy_destroy(pct, hierarchy, pch_test_destroy); +out_unregister_pct: + powercap_unregister_control_type(pct); +out_free_hierarchy: + powercap_hierarchy_free(hierarchy); + return ret; +} +module_init(pch_test_init); + +static void __exit pch_test_exit(void) +{ + /* Remove zones before their controller; the script checks disappearance. */ + powercap_hierarchy_destroy(pct, hierarchy, pch_test_destroy); + powercap_hierarchy_free(hierarchy); + powercap_unregister_control_type(pct); +} +module_exit(pch_test_exit); + +MODULE_DESCRIPTION("Powercap hierarchy test driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel Lezcano