From nobody Sat Sep 26 07:15:19 2026 Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BF6E74B5CB6 for ; Thu, 3 Sep 2026 14:29:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788445777; cv=none; b=rbxUN0enGVsJlMiWRtJAbhyGLyNephhTujtStgiu5ek+2bHMIoANqL7AJm5cfkAOmqlh2/yc9ysJjiqNOFybJmm33AQ8Sdd9pUY1dpkJqPTI/DgHhxYA+xo6PZfaoPMglgEpbwCcXwsD59UUWXnHrtCDsCKY4q28Pb8ihgpgN2Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788445777; c=relaxed/simple; bh=1h8UY7C7DRfSBJpx+Kqk2kMtTanSJ0uIlMXF2pMqnmI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=gPWu1OACaOvZNaiGHrgNuL7W1Etd/4jHZLthfl5T8w1wmY1N/vRYCmAsP+NRIaLEHoZdMuKfvawj5kDDq7W3CNMOPbsFtu6GS6z50WlMRF0dBcWgEUmF+RG/yzNwww1pFqKrl2JSVhyIxQa9zqJ4pJlNoO7k3jZeamqQMLtclXc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=E28mpCZO; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=jW5rfLb1; arc=none smtp.client-ip=205.220.168.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject 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) header.d=qualcomm.com header.i=@qualcomm.com header.b="E28mpCZO"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="jW5rfLb1" Received: from pps.filterd (m0279867.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 683DAAmF1704864 for ; Thu, 3 Sep 2026 14:29:25 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=qcppdkim1; bh= YrYZPm63HO7KH0MIwA5KVAUAv2xcSfYhYKeQJlbSewA=; b=E28mpCZOYSVLIZUg feqGJZzVkbTIloQAxjTgVzF46iC2DKqWJgxBJv9nWtuyWk3iR1SrX/Sdr3eFo6wX r6mkjtEVsqQF/0fLb+hSDt0gZIMB1v6rOWe3gkWDUxsLA/BPl6ch6+Ri4GbDSsfZ ulRDNbIWlhBnJUIX0SqfrWEyQ2mQW6FE3T5GN9dZHZXzLatGPGDgLlu+VmNhWLAE 150PEADxA1wMOfEMF6LCr9GTy+n0rrmLSRvrMmhaKJWe72nZ/brYO8RdD87kZFjP RxGwQ9cm9rzhZCmdNjkxF1SPjzZKz6TKovh7BKugQYcfSmbLiuwRH/ZfUEKU0qTD wvOb2w== Received: from mail-qk1-f199.google.com (mail-qk1-f199.google.com [209.85.222.199]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4gf9b18cf7-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Thu, 03 Sep 2026 14:29:24 +0000 (GMT) Received: by mail-qk1-f199.google.com with SMTP id af79cd13be357-93771aa0379so663225185a.1 for ; Thu, 03 Sep 2026 07:29:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1788445764; x=1789050564; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=YrYZPm63HO7KH0MIwA5KVAUAv2xcSfYhYKeQJlbSewA=; b=jW5rfLb1GTiGMJ/ggH6WVGUnYFguEj4py/ZCsNSkq7I/A/krbQILsAT3BjuVY4p/pJ HcJwUmFv3PhNrOlfql/MmnGr+1O05KkO3DacivO61+OInUn8t3zNsp8JJT4FGUxxXUtx 9R9yNUeEiogEyb2km8TwKkJzJycYlU+pqM7CYdzMzio9riq4Ij2GCHJ2O+4xANAHsmYf JoxLFqzy6l9/+8mtsGMSB1o5x2E0NEKRkvkoISeKx2L/s6e1ILFzBwhqxzJeds44tlYR 7CKiR0VlJKcvH7hRWel8F8wMezh+uUDewZ1Icb8KoUHd2n05E01YTSeb8Cd+3FmpQdJG sL4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788445764; x=1789050564; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=YrYZPm63HO7KH0MIwA5KVAUAv2xcSfYhYKeQJlbSewA=; b=X6xj2bsThfoARuvlcA5dCMkGnW/4xFBtWij8QCNeQZLkOhm4CMlNZWTjhyBJVpnk7e uWhjIALbsdAdefzGhdonwnyrOmhszfD4L42lxHIf5DLGg9Hj6Zr7XJmAzykTzsYu7erS xjWXfLe3ZEhSLQEYc02ksuxSNZUyKrIc3EZVUtkuPH5vAK4IvCCL4yIJJNYNaUzsncya 3rh+C8CM4NM1wBHq/ktjJiTT1U8wYa0bD4yBc0vegPsskVUMK/BntNdNA3KShhnz1eTD 7Jh1vXgEDwSDqfPNEB7LBfWgefA1Fu6kH/z8JkwNFOXXD2d/xtbRY3AaS0opVUUJppYw LwTg== X-Forwarded-Encrypted: i=1; AKwUvBwtk4Bi2aAlvrxLh6cF1OV22CM4R7L5Et1Xr+ufQF0kC4QEyCVD80aJITku/kVBQkfPOjMQ7xNiqYwls1o=@vger.kernel.org X-Gm-Message-State: AFuF++n1XSf1SEUPtp9Nj4U7M208znU3xCuljp0lVmtWP0y65j+YST7Q SwDaLKgmKmdqwuW6R19YUJI5i1rS4KjuW6FR7ZweqfOJRAotjxJ6quv11Ry5PMRNz8yITiEZDBX h/LkiakztaLediRVOZXeG01r0hdIMgDIakZKwMJki8jGtFF0lDQdVu91NzlPV+in7w3yxzunf/T I= X-Gm-Gg: AYBFou0PcsRymWKQPVNXG1tM57QHu3a388iqeKbzP2WxTphs3WSeRCnS4BAcDkpovkC l0pMjjyVi27Fnom/PlhRFXg/0h2Kva5z67X0A835lppQjgG4eTRdnRIDkyKsgfoQKMq49jlC5aE w42l30beF6xnpUFDieqq0x66W1Xc9ejoKKGTdhDWvJVkWpHeLcOhCaJ4ZMJ3foVne9d009RHA1E IXRzPdEzTLkcv54fs5XalFpVVsjm0HZO84YH5oUtX3t4YEYPJzLEAJAf1eG1avhaRXl0u5FC2xc WOAGhxSg788I0ZIFM6oXNquLUtz7a9T5rGWnFi/abDir8n4gOypH0VBN97529bh25VelHS1zm4S MHXjyfKCMzYAep6hZD7tIBOD3D0cb X-Received: by 2002:a05:620a:4114:b0:939:57ca:7e0a with SMTP id af79cd13be357-93960cabe26mr1473852185a.7.1788445763662; Thu, 03 Sep 2026 07:29:23 -0700 (PDT) X-Received: by 2002:a05:620a:4114:b0:939:57ca:7e0a with SMTP id af79cd13be357-93960cabe26mr1473842785a.7.1788445762889; Thu, 03 Sep 2026 07:29:22 -0700 (PDT) Received: from brgl-qcom.local ([2a01:cb1d:dc:7e00:1c7d:412b:f9d3:3bfc]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49cee5f9114sm72799415e9.5.2026.09.03.07.29.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 03 Sep 2026 07:29:21 -0700 (PDT) From: Bartosz Golaszewski Date: Thu, 03 Sep 2026 16:29:08 +0200 Subject: [PATCH 1/2] power: sequencing: don't call .post_enable() if pwrseq_unit_enable() failed 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 Message-Id: <20260903-pwrseq-kunit-v1-1-1f893d2cabc2@oss.qualcomm.com> References: <20260903-pwrseq-kunit-v1-0-1f893d2cabc2@oss.qualcomm.com> In-Reply-To: <20260903-pwrseq-kunit-v1-0-1f893d2cabc2@oss.qualcomm.com> To: Bartosz Golaszewski , Manivannan Sadhasivam , Chen-Yu Tsai , Krzysztof Kozlowski Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, Bartosz Golaszewski , stable@vger.kernel.org X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=908; i=bartosz.golaszewski@oss.qualcomm.com; h=from:subject:message-id; bh=1h8UY7C7DRfSBJpx+Kqk2kMtTanSJ0uIlMXF2pMqnmI=; b=owEBbQKS/ZANAwAKAQWdLsv/NoTDAcsmYgBqmYQ8f67iDxjYYuF9u6KuV9DjWQ8r4SVQDMArr QvqpcuuORWJAjMEAAEKAB0WIQSR5RMt5bVGHXuiZfwFnS7L/zaEwwUCapmEPAAKCRAFnS7L/zaE wz1QD/48X4FfbNFqvDxZCWx2jhXOClH8pEnGIil8YRnF0id/RgcwhJyI2nRkXz1UWuq+tHyUUUJ nIZsbzcL4LptvIbWt20lI+Qwp1S/QqZVhjq5nIFYTop1lLX+tYrKZxCIOFyPObOMx8rEOV+BhG+ cHL6R93C6o8c1VW84qHCvz5CLVBwsWDFhJi2cMBm2vqQmPcosm2ntjl3yDHwjNe5PiUmv9mPxUA bqY61zXsbS17XFMG+JnBxnoLMmg8TEW8q2O1IWoVcs1LpWViq59sc1UaR5l8BZ/cUTPTGMX6Tjv 2jVqqRaUYurrMpkbdX2U6+T0FM+q5oMzfOfc3tEQoub+GOusp/7U6DrmvjPC1wJSwnv3e9Rdtrt mhVn2EPAaayGyO3yHOIxHIcF6KEWyoXjDmtMWKpctHO+qC/23wEbMaSURvsqsThpD5s4qtTjuLu QdLT0WiuR9RmMwbwYP2GwxATeRKup8P9JJicTeOfOtM/nXx6dHS1nl8xM7JJNa3g5YiRZSdqFMn Kw8K3aWGuayYa3meYob3Nfx+CX2Yn6n9SQu4nnE2OwcGCgocLWVs2M5CM+qyn4YnWj7wpZOaoIz XeyYsJfbucjy0/l6i1V7//swCUnHokK7kUxJJKbB+wtaRLCl9wgRsd2yHYpbWwAYNhtE9kQwpE4 Pu9efPOytK2abdw== X-Developer-Key: i=bartosz.golaszewski@oss.qualcomm.com; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 X-Proofpoint-ORIG-GUID: L2HnXZCxBM8-s82W8Fj23dQuEoQV_OaG X-Proofpoint-Spam-Info: AW1haW4tMjYwOTAzMDEyNiBTYWx0ZWRfXwX4gnfhGdEWR tuUbrmynNlq2VIDG9NBKWwk915uDrP1NHZ0tzW1WnWzjoX+HTzdvX9c2+2W9Yo/4hIGPVYFPO13 rNqgIMRZt/nondretcSM1N3zqg0fEM8= X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTAzMDEyNiBTYWx0ZWRfX7ZfFmnTtFE1F eu9Ws6DJrH+L7vqh3TLy7QFPIfK3Y1lLjlzeC4iV0S57win45M2oDySMZvzUlyM6RAEcylYuyS+ yClQK7NaFszaXcYyC3LIbq3flh2Gp5tCK2vqzx8Ul7wEd74Ifov1m+aeMGBXE/VxZrZtr4T7hkl sMEp8qxrMHsmfiE3GH/W9qDOOmXipLFGzV90ZHeB0Q/o6oj6s4h9GWupxyhrya//kkQ4fpO2ucf JPAnHYtnEr8pY2RLdSKAZslR9FiIZBpimz2ChlP8QijbFhQfe9As34RJeRSElnZKJok65bpGfW4 BhOpX+eCimWJeg2x4n3aM8AtRodum/TFDKBM56OhMAX5Kzc/b7FjIDHIJTReGIXG21O+QKnCbU3 JahU7a7NVNanecuTiSTvz0QYeAJSuZToRuYptANX1qm4o0pTHDEqM8CFypK++Owq4yuUCLoSw5g N0Pu/ghCKg3BOb9wgQw== X-Authority-Analysis: v=2.4 cv=NNTlPU6g c=1 sm=1 tr=0 ts=6a998444 cx=c_pps a=HLyN3IcIa5EE8TELMZ618Q==:117 a=xqWC_Br6kY4A:10 a=IkcTkHD0fZMA:10 a=VdqzKS8jKosA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=eoimf2acIAo5FJnRuUoq:22 a=VwQbUJbxAAAA:8 a=EUspDBNiAAAA:8 a=Trbc2l1I1fXLbfWOQTAA:9 a=QEXdDO2ut3YA:10 a=bTQJ7kPSJx9SKPbeHEYW:22 X-Proofpoint-GUID: L2HnXZCxBM8-s82W8Fj23dQuEoQV_OaG X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-09-03_04,2026-09-03_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 phishscore=0 lowpriorityscore=0 spamscore=0 bulkscore=0 adultscore=0 malwarescore=0 clxscore=1015 suspectscore=0 impostorscore=0 priorityscore=1501 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2609030126 If the call to pwrseq_unit_enable() failed in pwrseq_enable(), bail out instead of calling target->post_enable() which assumes the target was successfully enabled. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/cor= e.c index 721e888b658d1f254513e126bb72f2d0487a8383..76c39600f690d9f19a21f4a8951= 2ee24942e3b14 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -912,6 +912,8 @@ int pwrseq_enable(struct pwrseq_desc *desc) if (!ret) desc->powered_on =3D true; } + if (ret) + return ret; =20 if (target->post_enable) { ret =3D target->post_enable(pwrseq); --=20 2.47.3 From nobody Sat Sep 26 07:15:19 2026 Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CB70D4B66F7 for ; Thu, 3 Sep 2026 14:29:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.180.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788445782; cv=none; b=PQ6x3tdMy9yj1FVMOStxjUBTHoe0erPZHOdbsPRp1H0t9Ab2SqsSxtCrKWbu0Om9pynR3EWYNmIECGsUxvH3V/FM/QM6L6bU3Kivv+/LBkKxTh6aBwbj97Z2Mmu0Ipo7Z62e00ABGfho3WfvkqhyGkM0qwkHrZV47bwQ7SIIgaw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788445782; c=relaxed/simple; bh=87I7IBOfoO1/OUKB8jLS15WMIS9h1aPeWI6Zuv25XGQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=SkwUm7AnDWc0sWfZcq2BhihY4l77wTQTLGqNjh8diaFR5Z7jxD2shW367KXcBVq77NeIcuC5hIoaF7yqrkXoep4R5CdjKD28Oe+j5eVZp+QRlH3phoVcZeC4Ya7qJ2z8Fr8uB3A2XkgMhhz/5OQe3G2CWmIiImmrWNWaPk2uPYA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=NmAw6K1A; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=glHAGOyY; arc=none smtp.client-ip=205.220.180.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject 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) header.d=qualcomm.com header.i=@qualcomm.com header.b="NmAw6K1A"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="glHAGOyY" Received: from pps.filterd (m0279870.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 683DAhSE707756 for ; Thu, 3 Sep 2026 14:29:28 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=qcppdkim1; bh= 5IXu4DZ5j3g12muqhUXwDxqYZMQhzBEXoYPVjup8Kq0=; b=NmAw6K1AONiALum7 ZOh43+3ve5GlS71ktbLwK0uhPkPFpmLuwNtPKw58txum8MV+yt/4JKTuySMpb+jS Q6bEQ8F3pGjSz6sz7VLPrLixyAFa8SjmvkPzP3iIPoqH2sdUAZfwIWaomDTaIEhO 8Vd5tEMq8z5nZhcOODoGnu4Mxam70q99G4aG2nCJOKmhPqMZiGWhVtAEHOqgRsGL 8TMOlbW0f6gwfQQGmDeO6Ld/So2SsEIG3lhLJu2veOy4y04aN+sVHnHgRlT9mebu dsMqEK197LS0NFiBeHYDSvFk30oCiLvKxXAXVjzikkdAue8AfA0Qb6oe7+TtAk/a mwkzcQ== Received: from mail-qk1-f199.google.com (mail-qk1-f199.google.com [209.85.222.199]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4gf4ekhq8q-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Thu, 03 Sep 2026 14:29:28 +0000 (GMT) Received: by mail-qk1-f199.google.com with SMTP id af79cd13be357-939665a1ae6so472981185a.0 for ; Thu, 03 Sep 2026 07:29:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1788445767; x=1789050567; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=5IXu4DZ5j3g12muqhUXwDxqYZMQhzBEXoYPVjup8Kq0=; b=glHAGOyYTOYrAxbnGF0PntKv8hqOCmzd595vC6H3J/JqbaNibdh1yHc8r1mNlPocW6 0wvM92v/BI86VyCwryKe7IA7DIxntKxEmcRSmdk4AXVSOU1LjkJen4YcMAqWpbENbspG AuITYdGASFyYtp2dCk4PCqxWv64OCeSrzOTljTZlJw1y1okeK+taWQi/CsWG1I5HhKo5 9b4hla9cJFn4Is0zVl81EKws/UT0KR2hZpzQHQ6RNlevuL71CftO5lRYMD88mFHhiGUx /oU6l+kIXPmQrkRIs0jwrptUWi5+fI6ERKzeqvX3d5aSl4afdEcmoTfMPERsYzpbwxuU poWg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788445767; x=1789050567; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=5IXu4DZ5j3g12muqhUXwDxqYZMQhzBEXoYPVjup8Kq0=; b=UrV2EXhqt/4yXiwdwcYXytSJGRui31fKtVlBzgstFIzj+9OvnODhLEKhH0OHlSL2Yr WpOBiilOk7qnEqxQ7gN4OtgF6k2b7mL8HSVUPvY880zhJIYjklWL1zx4U1hPv5QEfPnH pE1xRD4ElTGyVytcV2miygnePHueRdjzreDVrmn0FLSxW7+Dc6fYmJmmTFBgKYgcs6Yn pezuQDHvuKuNqoaxTUkdauLj1vHbldf3Z7h9hJqCorJHECAzfOYl5dIqDymH7zr3o53z e/isEvXJkI0DbWwi00uMEmjUfhvjVPn+sLosuEu4M/zYMKSJtHXIPhvmCTFMRrdLJkn7 psxw== X-Forwarded-Encrypted: i=1; AKwUvByyscIL0DtxIy3DJkNatNl3A1B6w7DZz4Z6R2gk6cm5dJT69NtDY+0bPYrccFyvBtXWmXlYUkkQJNEyk6U=@vger.kernel.org X-Gm-Message-State: AFuF++lz9mjoAQ9l6Vz33Lz3hk1T5CB85URYtGpX1Mh0Bd8CPV1pY3vi P4CcYHTGXbn1ACHr+nDeoRgV0V18KBCztonD1pCJGYQRdpnn4+/qCd/azEH5JnLQlRlMKmtZR0U Wi3aM7uEpF+qaAYasVqs5bFlvnVh2E2/STM88xLrCeRFXX0MwhOwcbMvumKwwEDcbURbR26mvJA 0= X-Gm-Gg: AYBFou1sqb+/IVx3g9IZyTb0aidklLw+RMiJ64vzostUBhosPwzPU6B3XsZ9dafrdcm RML3lZL1hD+TdefCjsKjNjxgBNmdBqGXj+9hkhn1UIFCEuzd5GcxHUOLkSGX5euLGKnVa5eVV3M 6bIi5asz2iiHuQXSEhEOV0lyU0VNRo4lF5+cwcSH/7AQbnn0cmR/am/Lovm0StGfZYtrPbAYkjZ hhdaZElqU0RI73L2JNhbrtrH8CAYnuIBoG8Hzz1qOIpL/+d55qCB4I4W9EPeaAFUr5N8KCH/XkN XOcgZKBTCbmq0CxLkDVEYUfwG3bcqW9+d1uZmMAsojLtl06+GdQ5Ygh5LUgqkDb5MVuwJl/zyoj JwEYrr1WfW9vpT2MBQKJac7P0HFAz X-Received: by 2002:a05:620a:2b8a:b0:930:f129:1a45 with SMTP id af79cd13be357-93960f5b352mr1559215385a.30.1788445766320; Thu, 03 Sep 2026 07:29:26 -0700 (PDT) X-Received: by 2002:a05:620a:2b8a:b0:930:f129:1a45 with SMTP id af79cd13be357-93960f5b352mr1559193685a.30.1788445764697; Thu, 03 Sep 2026 07:29:24 -0700 (PDT) Received: from brgl-qcom.local ([2a01:cb1d:dc:7e00:1c7d:412b:f9d3:3bfc]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49cee5f9114sm72799415e9.5.2026.09.03.07.29.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 03 Sep 2026 07:29:23 -0700 (PDT) From: Bartosz Golaszewski Date: Thu, 03 Sep 2026 16:29:09 +0200 Subject: [PATCH 2/2] power: sequencing: add kunit test cases 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 Message-Id: <20260903-pwrseq-kunit-v1-2-1f893d2cabc2@oss.qualcomm.com> References: <20260903-pwrseq-kunit-v1-0-1f893d2cabc2@oss.qualcomm.com> In-Reply-To: <20260903-pwrseq-kunit-v1-0-1f893d2cabc2@oss.qualcomm.com> To: Bartosz Golaszewski , Manivannan Sadhasivam , Chen-Yu Tsai , Krzysztof Kozlowski Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, Bartosz Golaszewski X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=45947; i=bartosz.golaszewski@oss.qualcomm.com; h=from:subject:message-id; bh=87I7IBOfoO1/OUKB8jLS15WMIS9h1aPeWI6Zuv25XGQ=; b=owEBbQKS/ZANAwAKAQWdLsv/NoTDAcsmYgBqmYQ+eXYa0+IwgnAlH39FBADL0T+gzsTDJnqQd ulA9ZPjbnqJAjMEAAEKAB0WIQSR5RMt5bVGHXuiZfwFnS7L/zaEwwUCapmEPgAKCRAFnS7L/zaE wyGlEACrYgD3aM4eD3Ql8ivzPvND7mdFhbwnIIJvKnKUYbPXVZ3tYSY6s5ZYaPRfvV3RktDp+7r U/KJfzhL85Ews2v2ey9PYYfiEmfRnOCfidxA39cocT4cGe0KD7rOTiyvSJTY3oct/Chjn72zH0f RlALsPhkpgzmDifwe+q2qYeVZB7TKeWbHY2ADDtt1F1j8hjGo1N3lsTt3ts/s4YjsL5BmyuBZZl 6omVh9Uyc8HTiHf+PusrOi4F/Yw825hQedyU2gv4XoG5wUKY+hYbCaZq/Zjm2czuFPgXLtiwky7 jScVHqbRo1ldsKm3u8wrtfxbFJb81pweHlT0sscuNzQ1TIeqyklAfQjZY8EgCbRCTNBTaCYwpaw JM5a4GYjJKU3KcSY+20eTgDq7T03nuEEkDMXh2jwgpQTtgnVBxwOWHRmVCq2e3WDJSEq5emAbRn tMTyEjCjMVcYMwJH1YGsTORxo2Rq+BMxE0R6JdgVzKgmxk3eEISRBHnsv3PJ78VM2kO7FK8dyEs QbbFKmiUYd8CWafq0WVBzWBkex2g3k0A7XulrboGK+K/lFt3y9mDRofnbj/rwhuxJSEDy9wanjl 48MNevWLnaUJ8jZiccYjDT4FseAh58h6iIYg+GeoDrW6ffa4qxJekAi3RSvoERVehzZSYu7rZ/w /YnwRbWmo8HpUvQ== X-Developer-Key: i=bartosz.golaszewski@oss.qualcomm.com; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 X-Proofpoint-ORIG-GUID: aDORvYKnlaeh_JjOo3Vxl735J_skIxDN X-Proofpoint-GUID: aDORvYKnlaeh_JjOo3Vxl735J_skIxDN X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTAzMDEyNiBTYWx0ZWRfX07cRaZgpvrNu El0hO0HrLS0qLECajdFJ94sr00+VwhDPDHNja8697PtiDPq4UtcBCGAVBbG0JDjSGxX4McLROtV 7K8r/uSz1uodIZHhThFIPV14nOrrTkh5hxUJURqp9/JDSHu2GJ7MB7yebi0anIsMTNahdzPRexm bj8wXHovyWryeYBBZDf1vNNM0OKXKOvUcWSWlROn6r3MPb6K+Z+MSavySShg35/9I5x4UiR28bX lNOj4o8IkhVykeWWDp0+44SfClo42IH5Kdpo+MuwnxwjypgU8NA4MAG3jyc9ccM86GsKXUP6QF2 BDtW55nEbC7sRutRBlVb2r6iu4kFnvxIQjqYZGx2rl9JDqgOCge1/ZbQCfTzX7APspYbPttqF9z DKy9KSUf1gDUfb2fhFsYG/7XGoONBzyqWd3eH2q7BMaf7mIpgrkjR4v+dNwu9p2pVZunUHkxiAW q/Kfu0Rx/TtIIfKc1sg== X-Proofpoint-Spam-Info: AW1haW4tMjYwOTAzMDEyNiBTYWx0ZWRfX78VrKY1Kpbv/ M0/avu5YOqst1L9HLc9ImSVoh4JUejSZKohwoHnJFXTewynMoJ9wyPPx6HTEpEMnXgeUci07dFX 9HYhKqOf+tiqGcbHEal32+A9Yn+CeOA= X-Authority-Analysis: v=2.4 cv=PKc/P/qC c=1 sm=1 tr=0 ts=6a998448 cx=c_pps a=HLyN3IcIa5EE8TELMZ618Q==:117 a=xqWC_Br6kY4A:10 a=IkcTkHD0fZMA:10 a=VdqzKS8jKosA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=gowsoOTTUOVcmtlkKump:22 a=EUspDBNiAAAA:8 a=Ap85WKxUwIOrhNPdkRgA:9 a=QEXdDO2ut3YA:10 a=bTQJ7kPSJx9SKPbeHEYW:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-09-03_04,2026-09-03_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 lowpriorityscore=0 adultscore=0 bulkscore=0 clxscore=1015 phishscore=0 suspectscore=0 priorityscore=1501 spamscore=0 impostorscore=0 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2609030126 Add a set of kunit test-cases for the power sequencing subsystem. Verify several use-cases such as detecting circural dependencies, tracking of the enable-count, shared dependencies, swnode lookup, missing target on matching provider, etc. Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/Kconfig | 10 + drivers/power/sequencing/Makefile | 1 + drivers/power/sequencing/pwrseq-kunit.c | 1497 +++++++++++++++++++++++++++= ++++ 3 files changed, 1508 insertions(+) diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kc= onfig index 1c5f5820f5b76479b380df79971ca5e245e62c6b..a09f18b41feafd97833dffe82e1= 03288ffb6ce1b 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -46,4 +46,14 @@ config POWER_SEQUENCING_PCIE_M2 connectors. This driver handles the power sequencing for the M.2 connectors exposing multiple interfaces like PCIe, SATA, UART, etc... =20 +config POWER_SEQUENCING_KUNIT + tristate "Build power sequencing KUnit test cases" + depends on KUNIT + default KUNIT_ALL_TESTS + help + Say Y here to build the module containing KUnit test cases for the + power sequencing subsystem. + + If unsure, say N. + endif diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/M= akefile index 0911d461829897c5018e26dbe475b28f6fb6914c..cde06a5115d0c57b02b1a7d66cc= 641b691c5f5e3 100644 --- a/drivers/power/sequencing/Makefile +++ b/drivers/power/sequencing/Makefile @@ -6,3 +6,4 @@ pwrseq-core-y :=3D core.o obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) +=3D pwrseq-qcom-wcn.o obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) +=3D pwrseq-thead-gpu.o obj-$(CONFIG_POWER_SEQUENCING_PCIE_M2) +=3D pwrseq-pcie-m2.o +obj-$(CONFIG_POWER_SEQUENCING_KUNIT) +=3D pwrseq-kunit.o diff --git a/drivers/power/sequencing/pwrseq-kunit.c b/drivers/power/sequen= cing/pwrseq-kunit.c new file mode 100644 index 0000000000000000000000000000000000000000..c2899f3b16e6450eab7178e58d7= 5e9ddc005d5e5 --- /dev/null +++ b/drivers/power/sequencing/pwrseq-kunit.c @@ -0,0 +1,1497 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define PWRSEQ_TEST_PARENT "pwrseq-test-parent" +#define PWRSEQ_TEST_CONSUMER "pwrseq-test-consumer" + +#define PWRSEQ_SWNODE_TEST_PROVIDER "pwrseq-swnode-test-provider" +#define PWRSEQ_SWNODE_TEST_PROVIDER_2 "pwrseq-swnode-test-provider-2" +#define PWRSEQ_PROBE_ORDER_TEST_CONSUMER "pwrseq-probe-order-test-consumer" +#define PWRSEQ_PROBE_DEFER_TEST_CONSUMER "pwrseq-probe-defer-test-consumer" + +static const struct software_node pwrseq_test_provider_swnode =3D { + .name =3D "pwrseq-test-provider", +}; + +KUNIT_DEFINE_ACTION_WRAPPER(pwrseq_device_unregister_wrapper, + pwrseq_device_unregister, + struct pwrseq_device *); + +static struct pwrseq_device * +kunit_pwrseq_device_register(struct kunit *test, + const struct pwrseq_config *config) +{ + struct pwrseq_device *pwrseq; + int ret; + + pwrseq =3D pwrseq_device_register(config); + if (IS_ERR(pwrseq)) + return pwrseq; + + ret =3D kunit_add_action_or_reset(test, pwrseq_device_unregister_wrapper, + pwrseq); + if (ret) + return ERR_PTR(ret); + + return pwrseq; +} + +KUNIT_DEFINE_ACTION_WRAPPER(pwrseq_put_wrapper, pwrseq_put, + struct pwrseq_desc *); + +static struct pwrseq_desc * +kunit_pwrseq_get(struct kunit *test, struct device *dev, const char *targe= t) +{ + struct pwrseq_desc *desc; + int ret; + + desc =3D pwrseq_get(dev, target); + if (IS_ERR(desc)) + return desc; + + ret =3D kunit_add_action_or_reset(test, pwrseq_put_wrapper, desc); + if (ret) + return ERR_PTR(ret); + + return desc; +} + +struct pwrseq_test_ctx { + const char *consumer_name; + int enable_count_a; + int enable_count_b; + int disable_count_a; + int disable_count_b; + int enable_calls_a; + int post_enable_calls; + bool enable_error_a; + bool enable_error_b; + bool post_enable_error; +}; + +static int pwrseq_test_no_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + return PWRSEQ_NO_MATCH; +} + +static int pwrseq_test_match_by_name(struct pwrseq_device *pwrseq, + struct device *dev) +{ + struct pwrseq_test_ctx *ctx =3D pwrseq_device_get_drvdata(pwrseq); + + return strcmp(dev_name(dev), ctx->consumer_name) =3D=3D 0 ? + PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH; +} + +static int pwrseq_test_match_always(struct pwrseq_device *pwrseq, + struct device *dev) +{ + return PWRSEQ_MATCH_OK; +} + +static int pwrseq_test_enable_a(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx =3D pwrseq_device_get_drvdata(pwrseq); + + ctx->enable_calls_a++; + + if (ctx->enable_error_a) + return -EIO; + + ctx->enable_count_a++; + + return 0; +} + +static int pwrseq_test_disable_a(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx =3D pwrseq_device_get_drvdata(pwrseq); + + ctx->disable_count_a++; + + return 0; +} + +static int pwrseq_test_enable_b(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx =3D pwrseq_device_get_drvdata(pwrseq); + + if (ctx->enable_error_b) + return -EIO; + + ctx->enable_count_b++; + + return 0; +} + +static int pwrseq_test_disable_b(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx =3D pwrseq_device_get_drvdata(pwrseq); + + ctx->disable_count_b++; + + return 0; +} + +static int pwrseq_test_post_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx =3D pwrseq_device_get_drvdata(pwrseq); + + ctx->post_enable_calls++; + + return ctx->post_enable_error ? -EIO : 0; +} + +static int pwrseq_test_parent_probe(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver pwrseq_test_parent_driver =3D { + .probe =3D pwrseq_test_parent_probe, + .driver =3D { + .name =3D PWRSEQ_TEST_PARENT, + }, +}; + +static int pwrseq_test_parent_init(struct kunit *test) +{ + static const struct platform_device_info pdevinfo =3D { + .name =3D PWRSEQ_TEST_PARENT, + .id =3D PLATFORM_DEVID_NONE, + }; + + struct platform_device *pdev; + bool bound; + int ret; + + ret =3D kunit_platform_driver_register(test, &pwrseq_test_parent_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + pdev =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound =3D device_is_bound(&pdev->dev); + + KUNIT_ASSERT_TRUE(test, bound); + + test->priv =3D pdev; + + return 0; +} + +/* + * Test that registering a power sequencer whose unit dependency graph + * contains a two-node cycle (A -> B -> A) is rejected with -EINVAL. + */ +static void pwrseq_circular_deps(struct kunit *test) +{ + const struct pwrseq_unit_data *unit_a_deps[2] =3D { }; + const struct pwrseq_unit_data *unit_b_deps[2] =3D { }; + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct pwrseq_unit_data unit_a, unit_b; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + unit_a =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .deps =3D unit_a_deps, + }; + + unit_b =3D (struct pwrseq_unit_data){ + .name =3D "unit-b", + .deps =3D unit_b_deps, + }; + + unit_a_deps[0] =3D &unit_b; + unit_b_deps[0] =3D &unit_a; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit_a, + }; + + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .match =3D pwrseq_test_no_match, + .targets =3D targets, + }; + + kunit_warning_suppress(test) { + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } + + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); +} + +/* + * Test that a longer chain cycle (A -> B -> C -> D -> A) is also rejected + * with -EINVAL. + */ +static void pwrseq_circular_deps_chain(struct kunit *test) +{ + struct pwrseq_unit_data unit_a, unit_b, unit_c, unit_d; + const struct pwrseq_unit_data *unit_a_deps[2] =3D { }; + const struct pwrseq_unit_data *unit_b_deps[2] =3D { }; + const struct pwrseq_unit_data *unit_c_deps[2] =3D { }; + const struct pwrseq_unit_data *unit_d_deps[2] =3D { }; + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + unit_a =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .deps =3D unit_a_deps, + }; + unit_b =3D (struct pwrseq_unit_data){ + .name =3D "unit-b", + .deps =3D unit_b_deps, + }; + unit_c =3D (struct pwrseq_unit_data){ + .name =3D "unit-c", + .deps =3D unit_c_deps, + }; + unit_d =3D (struct pwrseq_unit_data){ + .name =3D "unit-d", + .deps =3D unit_d_deps, + }; + + unit_a_deps[0] =3D &unit_b; + unit_b_deps[0] =3D &unit_c; + unit_c_deps[0] =3D &unit_d; + unit_d_deps[0] =3D &unit_a; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit_a, + }; + + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .match =3D pwrseq_test_no_match, + .targets =3D targets, + }; + + kunit_warning_suppress(test) { + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } + + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); +} + +/* + * Test that a valid acyclic dependency graph (A -> B) registers successfu= lly. + */ +static void pwrseq_register_valid(struct kunit *test) +{ + const struct pwrseq_unit_data *unit_a_deps[2] =3D { }; + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct pwrseq_unit_data unit_a, unit_b; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + unit_b =3D (struct pwrseq_unit_data){ + .name =3D "unit-b", + }; + + unit_a_deps[0] =3D &unit_b; + unit_a =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .deps =3D unit_a_deps, + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit_a, + }; + + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .match =3D pwrseq_test_no_match, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); +} + +/* + * Test that registration is rejected with -EINVAL when .parent, .match or + * .targets is missing, or when .targets is a non-NULL but empty array. + */ +static void pwrseq_register_invalid_args(struct kunit *test) +{ + const struct pwrseq_target_data *empty_targets[1] =3D { }; + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + unit =3D (struct pwrseq_unit_data){ .name =3D "unit-a" }; + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit, + }; + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .match =3D pwrseq_test_no_match, + .targets =3D targets, + }; + + config.parent =3D NULL; + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); + config.parent =3D &pdev->dev; + + config.match =3D NULL; + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); + config.match =3D pwrseq_test_no_match; + + config.targets =3D NULL; + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); + + config.targets =3D empty_targets; + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); +} + +/* + * Test that a target without a unit is rejected with -EINVAL. + */ +static void pwrseq_register_target_without_unit(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D NULL, + }; + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .match =3D pwrseq_test_no_match, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); +} + +/* + * Test that enabling and disabling a single-unit target increments and + * decrements enable_count correctly and fires the enable/disable callbacks + * exactly once. + */ +static void pwrseq_enable_disable(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_test_ctx *ctx; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_desc *desc; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .enable =3D pwrseq_test_enable_a, + .disable =3D pwrseq_test_disable_a, + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit, + }; + + targets[0] =3D ⌖ + + ctx->consumer_name =3D PWRSEQ_TEST_CONSUMER; + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .drvdata =3D ctx, + .match =3D pwrseq_test_match_by_name, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + }; + + pdev =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc =3D kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret =3D pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); + + ret =3D pwrseq_disable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); +} + +/* + * Test that two consumers sharing a common dependency unit cause it to be + * enabled only once and disabled only once, while enable_count tracks each + * consumer correctly. + */ +static void pwrseq_shared_deps(struct kunit *test) +{ + const struct pwrseq_unit_data *unit_a_deps[2] =3D { }; + const struct pwrseq_unit_data *unit_b_deps[2] =3D { }; + const struct pwrseq_target_data *targets[3] =3D { }; + struct pwrseq_unit_data dep_unit, unit_a, unit_b; + struct pwrseq_target_data target_a, target_b; + struct platform_device *parent =3D test->priv; + struct platform_device *pdev_a, *pdev_b; + struct platform_device_info pdevinfo; + struct pwrseq_desc *desc_a, *desc_b; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dep_unit =3D (struct pwrseq_unit_data){ + .name =3D "dep", + .enable =3D pwrseq_test_enable_a, + .disable =3D pwrseq_test_disable_a, + }; + + unit_a_deps[0] =3D &dep_unit; + unit_a =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .deps =3D unit_a_deps, + .enable =3D pwrseq_test_enable_b, + .disable =3D pwrseq_test_disable_b, + }; + + unit_b_deps[0] =3D &dep_unit; + unit_b =3D (struct pwrseq_unit_data){ + .name =3D "unit-b", + .deps =3D unit_b_deps, + }; + + target_a =3D (struct pwrseq_target_data){ + .name =3D "target-a", + .unit =3D &unit_a, + }; + target_b =3D (struct pwrseq_target_data){ + .name =3D "target-b", + .unit =3D &unit_b, + }; + + targets[0] =3D &target_a; + targets[1] =3D &target_b; + + ctx->consumer_name =3D PWRSEQ_TEST_CONSUMER "-a"; + + config =3D (struct pwrseq_config){ + .parent =3D &parent->dev, + .drvdata =3D ctx, + .match =3D pwrseq_test_match_by_name, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER "-a", + .id =3D PLATFORM_DEVID_NONE, + }; + pdev_a =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev_a); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER "-b", + .id =3D PLATFORM_DEVID_NONE, + }; + pdev_b =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev_b); + + /* + * match_by_name() only matches one consumer name at a time, so + * reconfigure ctx->consumer_name between the two pwrseq_get() calls + * below to acquire descriptors for both consumer-a and consumer-b. + */ + desc_a =3D kunit_pwrseq_get(test, &pdev_a->dev, "target-a"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_a); + + ctx->consumer_name =3D PWRSEQ_TEST_CONSUMER "-b"; + desc_b =3D kunit_pwrseq_get(test, &pdev_b->dev, "target-b"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_b); + + ret =3D pwrseq_enable(desc_a); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 1); + + ret =3D pwrseq_enable(desc_b); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + + ret =3D pwrseq_disable(desc_a); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_b, 1); + + ret =3D pwrseq_disable(desc_b); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); +} + +/* + * Test that calling pwrseq_enable() twice on the same descriptor has no + * effect. + */ +static void pwrseq_enable_idempotent(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_unit_data unit; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + struct pwrseq_desc *desc; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .enable =3D pwrseq_test_enable_a, + .disable =3D pwrseq_test_disable_a, + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit, + }; + + targets[0] =3D ⌖ + + ctx->consumer_name =3D PWRSEQ_TEST_CONSUMER; + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .drvdata =3D ctx, + .match =3D pwrseq_test_match_by_name, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + }; + + pdev =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc =3D kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret =3D pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + + /* Second power_on on same descriptor must be a no-op. */ + ret =3D pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + + ret =3D pwrseq_disable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); +} + +/* + * Test that when a dependency unit's enable() callback fails, the error + * propagates to the caller, the top unit's enable callback is never reach= ed, + * and target->post_enable() is never invoked. + */ +static void pwrseq_enable_enable_error(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] =3D { }; + const struct pwrseq_unit_data *top_deps[2] =3D { }; + struct pwrseq_unit_data dep_unit, top_unit; + struct platform_device *pdev =3D test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + struct pwrseq_desc *desc; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dep_unit =3D (struct pwrseq_unit_data){ + .name =3D "dep", + .enable =3D pwrseq_test_enable_a, + }; + + top_deps[0] =3D &dep_unit; + top_unit =3D (struct pwrseq_unit_data){ + .name =3D "top", + .deps =3D top_deps, + .enable =3D pwrseq_test_enable_b, + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &top_unit, + .post_enable =3D pwrseq_test_post_enable, + }; + + targets[0] =3D ⌖ + + ctx->consumer_name =3D PWRSEQ_TEST_CONSUMER; + ctx->enable_error_a =3D true; + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .drvdata =3D ctx, + .match =3D pwrseq_test_match_by_name, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + }; + + pdev =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc =3D kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret =3D pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, -EIO); + /* + * Dependency's enable() was attempted but failed, so it never + * incremented. + */ + KUNIT_EXPECT_EQ(test, ctx->enable_calls_a, 1); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 0); + /* Target unit's enable() was never reached. */ + KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 0); + /* Target's .post_enable() must not run on an unpowered sequencer. */ + KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 0); +} + +/* + * Test that when the top unit's own enable() callback fails after its + * dependency's enable() already succeeded, the dependency is rolled back. + */ +static void pwrseq_enable_rollback_on_top_failure(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] =3D { }; + const struct pwrseq_unit_data *top_deps[2] =3D { }; + struct pwrseq_unit_data dep_unit, top_unit; + struct platform_device *pdev =3D test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + struct pwrseq_desc *desc; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dep_unit =3D (struct pwrseq_unit_data){ + .name =3D "dep", + .enable =3D pwrseq_test_enable_a, + .disable =3D pwrseq_test_disable_a, + }; + + top_deps[0] =3D &dep_unit; + top_unit =3D (struct pwrseq_unit_data){ + .name =3D "top", + .deps =3D top_deps, + .enable =3D pwrseq_test_enable_b, + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &top_unit, + }; + + targets[0] =3D ⌖ + + ctx->consumer_name =3D PWRSEQ_TEST_CONSUMER; + ctx->enable_error_b =3D true; + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .drvdata =3D ctx, + .match =3D pwrseq_test_match_by_name, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + }; + + pdev =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc =3D kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret =3D pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, -EIO); + /* + * Dependency was enabled, then rolled back once top's own enable + * failed. + */ + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); + /* + * Target unit's own enable never incremented since it returned an + * error. + */ + KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 0); +} + +/* + * Test target->post_enable() on both the success and failure paths: it mu= st + * run exactly once per pwrseq_enable() call that actually powers the targ= et + * unit on, and a failing post_enable() must roll the unit back and clear + * powered_on so a subsequent pwrseq_enable() is called again. + */ +static void pwrseq_enable_post_enable(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + struct pwrseq_desc *desc; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .enable =3D pwrseq_test_enable_a, + .disable =3D pwrseq_test_disable_a, + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit, + .post_enable =3D pwrseq_test_post_enable, + }; + + targets[0] =3D ⌖ + + ctx->consumer_name =3D PWRSEQ_TEST_CONSUMER; + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .drvdata =3D ctx, + .match =3D pwrseq_test_match_by_name, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + }; + + pdev =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc =3D kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + /* Success path: post_enable() runs once and reports success. */ + ret =3D pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 1); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + + ret =3D pwrseq_disable(desc); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* Failure path: post_enable() fails, unit is rolled back. */ + ctx->post_enable_error =3D true; + ret =3D pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, -EIO); + KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 2); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 2); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 2); + + /* powered_on must have been cleared. */ + ret =3D pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, -EIO); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 3); +} + +/* + * Test that pwrseq_get() returns -ENOENT when the matched provider does n= ot + * have the requested target name. + */ +static void pwrseq_get_target_not_found(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_desc *desc; + + unit =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "real-target", + .unit =3D &unit, + }; + + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .match =3D pwrseq_test_match_always, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + }; + + pdev =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc =3D kunit_pwrseq_get(test, &pdev->dev, "nonexistent-target"); + KUNIT_EXPECT_TRUE(test, IS_ERR(desc)); + KUNIT_EXPECT_EQ(test, PTR_ERR(desc), -ENOENT); +} + +/* + * Test that pwrseq_put() on a descriptor that is still powered on disables + * it as part of tear-down, instead of leaking the power-on state. + */ +static void pwrseq_put_disables_powered_desc(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device *pdev =3D test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_test_ctx *ctx; + struct pwrseq_config config; + struct pwrseq_desc *desc; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .enable =3D pwrseq_test_enable_a, + .disable =3D pwrseq_test_disable_a, + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit, + }; + + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .drvdata =3D ctx, + .match =3D pwrseq_test_match_always, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + }; + + pdev =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc =3D kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret =3D pwrseq_enable(desc); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); + + /* + * Run the deferred pwrseq_put() action now instead of at test + * teardown, so its effect can be asserted below. + */ + kunit_release_action(test, pwrseq_put_wrapper, desc); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); +} + +/* + * Test that pwrseq_device_unregister() on a target with an active user + * triggers "REMOVING POWER SEQUENCER WITH ACTIVE USERS", and that both + * pwrseq_enable() and pwrseq_disable() start returning -ENODEV for + * previously acquired descriptors afterwards. + */ +static void pwrseq_unregister_with_active_user(struct kunit *test) +{ + struct platform_device *pdev =3D test->priv, *cons_a, *cons_b; + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device_info pdevinfo; + struct pwrseq_desc *desc_a, *desc_b; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_test_ctx *ctx; + struct pwrseq_config config; + bool bound; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit =3D (struct pwrseq_unit_data){ + .name =3D "unit-a", + .enable =3D pwrseq_test_enable_a, + .disable =3D pwrseq_test_disable_a, + }; + + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit, + }; + + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .drvdata =3D ctx, + .match =3D pwrseq_test_match_always, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D 0, + }; + cons_a =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_a); + + wait_for_device_probe(); + scoped_guard(device, &cons_a->dev) + bound =3D device_is_bound(&cons_a->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdevinfo.id =3D 1; + cons_b =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_b); + + wait_for_device_probe(); + scoped_guard(device, &cons_b->dev) + bound =3D device_is_bound(&cons_b->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + desc_a =3D kunit_pwrseq_get(test, &cons_a->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_a); + desc_b =3D kunit_pwrseq_get(test, &cons_b->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_b); + + ret =3D pwrseq_enable(desc_a); + KUNIT_ASSERT_EQ(test, ret, 0); + + kunit_warning_suppress(test) { + kunit_release_action(test, pwrseq_device_unregister_wrapper, pwrseq); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } + + ret =3D pwrseq_enable(desc_b); + KUNIT_EXPECT_EQ(test, ret, -ENODEV); + + ret =3D pwrseq_disable(desc_a); + KUNIT_EXPECT_EQ(test, ret, -ENODEV); +} + +/* + * Test that pwrseq_to_device() returns the pwrseq provider's own device, + * and NULL for a NULL descriptor. + */ +static void pwrseq_to_device_test(struct kunit *test) +{ + struct platform_device *parent =3D test->priv, *cons; + const struct pwrseq_target_data *targets[2] =3D { }; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_desc *desc; + struct device *dev; + + unit =3D (struct pwrseq_unit_data){ .name =3D "unit-a" }; + target =3D (struct pwrseq_target_data){ + .name =3D "test-target", + .unit =3D &unit, + }; + targets[0] =3D ⌖ + + config =3D (struct pwrseq_config){ + .parent =3D &parent->dev, + .match =3D pwrseq_test_match_always, + .targets =3D targets, + }; + + pwrseq =3D kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + }; + + cons =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + desc =3D kunit_pwrseq_get(test, &cons->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + dev =3D pwrseq_to_device(desc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + KUNIT_EXPECT_PTR_EQ(test, dev->parent, &parent->dev); + KUNIT_EXPECT_TRUE(test, device_is_registered(dev)); + + KUNIT_EXPECT_PTR_EQ(test, pwrseq_to_device(NULL), NULL); +} + +static struct kunit_case pwrseq_tests[] =3D { + KUNIT_CASE(pwrseq_circular_deps), + KUNIT_CASE(pwrseq_circular_deps_chain), + KUNIT_CASE(pwrseq_register_valid), + KUNIT_CASE(pwrseq_register_invalid_args), + KUNIT_CASE(pwrseq_register_target_without_unit), + KUNIT_CASE(pwrseq_enable_disable), + KUNIT_CASE(pwrseq_shared_deps), + KUNIT_CASE(pwrseq_enable_idempotent), + KUNIT_CASE(pwrseq_enable_enable_error), + KUNIT_CASE(pwrseq_enable_rollback_on_top_failure), + KUNIT_CASE(pwrseq_enable_post_enable), + KUNIT_CASE(pwrseq_get_target_not_found), + KUNIT_CASE(pwrseq_put_disables_powered_desc), + KUNIT_CASE(pwrseq_unregister_with_active_user), + KUNIT_CASE(pwrseq_to_device_test), + { } +}; + +static struct kunit_suite pwrseq_test_suite =3D { + .name =3D "pwrseq", + .init =3D pwrseq_test_parent_init, + .test_cases =3D pwrseq_tests, +}; + +/* + * Match a consumer to this test provider by resolving its "pwrseq-provide= r" + * software-node reference and comparing it to the provider's own fwnode. + */ +static int pwrseq_test_match_by_swnode_ref(struct pwrseq_device *pwrseq, + struct device *dev) +{ + const struct software_node *provider_swnode; + struct fwnode_handle *provider_fwnode, *ref; + bool match; + + if (!dev_fwnode(dev)) + return PWRSEQ_NO_MATCH; + + ref =3D fwnode_find_reference(dev_fwnode(dev), "pwrseq-provider", 0); + if (IS_ERR_OR_NULL(ref)) + return PWRSEQ_NO_MATCH; + + provider_swnode =3D pwrseq_device_get_drvdata(pwrseq); + provider_fwnode =3D software_node_fwnode(provider_swnode); + match =3D (ref =3D=3D provider_fwnode); + fwnode_handle_put(ref); + + return match ? PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH; +} + +static const struct pwrseq_unit_data pwrseq_swnode_test_unit =3D { + .name =3D "test-target", +}; + +static const struct pwrseq_target_data pwrseq_swnode_test_target =3D { + .name =3D "test-target", + .unit =3D &pwrseq_swnode_test_unit, +}; + +static const struct pwrseq_target_data *pwrseq_swnode_test_targets[] =3D { + &pwrseq_swnode_test_target, + NULL, +}; + +struct pwrseq_swnode_provider_pdata { + const struct software_node *provider_swnode; +}; + +static int pwrseq_swnode_provider_probe(struct platform_device *pdev) +{ + const struct pwrseq_swnode_provider_pdata *pdata =3D dev_get_platdata(&pd= ev->dev); + struct pwrseq_config config; + + config =3D (struct pwrseq_config){ + .parent =3D &pdev->dev, + .drvdata =3D (void *)pdata->provider_swnode, + .match =3D pwrseq_test_match_by_swnode_ref, + .targets =3D pwrseq_swnode_test_targets, + }; + + return PTR_ERR_OR_ZERO(devm_pwrseq_device_register(&pdev->dev, &config)); +} + +static struct platform_driver pwrseq_swnode_provider_driver =3D { + .probe =3D pwrseq_swnode_provider_probe, + .driver =3D { + .name =3D PWRSEQ_SWNODE_TEST_PROVIDER, + }, +}; + +static struct platform_driver pwrseq_swnode_provider_driver_2 =3D { + .probe =3D pwrseq_swnode_provider_probe, + .driver =3D { + .name =3D PWRSEQ_SWNODE_TEST_PROVIDER_2, + }, +}; + +struct pwrseq_probe_order_pdata { + unsigned int probe_count; + int pwrseq_err; +}; + +static const struct pwrseq_probe_order_pdata pwrseq_probe_order_pdata_temp= late; + +static int pwrseq_probe_order_consumer_probe(struct platform_device *pdev) +{ + struct pwrseq_probe_order_pdata *pdata =3D dev_get_platdata(&pdev->dev); + struct pwrseq_desc *desc; + + pdata->probe_count++; + + desc =3D devm_pwrseq_get(&pdev->dev, "test-target"); + pdata->pwrseq_err =3D PTR_ERR_OR_ZERO(desc); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + return 0; +} + +static struct platform_driver pwrseq_probe_order_consumer_driver =3D { + .probe =3D pwrseq_probe_order_consumer_probe, + .driver =3D { + .name =3D PWRSEQ_PROBE_ORDER_TEST_CONSUMER, + }, +}; + +static struct platform_driver pwrseq_probe_defer_consumer_driver =3D { + .probe =3D pwrseq_probe_order_consumer_probe, + .driver =3D { + .name =3D PWRSEQ_PROBE_DEFER_TEST_CONSUMER, + }, +}; + +/* + * Verify that driver core orders the probe of a pwrseq consumer after its + * provider. The consumer references the provider through a software node = and + * is registered first and we rely on devlink for ordering. + */ +static void pwrseq_swnode_probe_order(struct kunit *test) +{ + struct property_entry properties[2] =3D { }; + struct pwrseq_probe_order_pdata *pdata; + struct platform_device_info pdevinfo; + struct platform_device *prvd, *cons; + struct fwnode_handle *fwnode; + bool bound =3D false; + int ret; + + ret =3D kunit_platform_driver_register(test, &pwrseq_swnode_provider_driv= er); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret =3D kunit_platform_driver_register(test, &pwrseq_probe_order_consumer= _driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode =3D kunit_software_node_register(test, &pwrseq_test_provider_swnod= e); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + properties[0] =3D PROPERTY_ENTRY_REF("pwrseq-provider", + &pwrseq_test_provider_swnode); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_PROBE_ORDER_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + .data =3D &pwrseq_probe_order_pdata_template, + .size_data =3D sizeof(pwrseq_probe_order_pdata_template), + .properties =3D properties, + }; + + cons =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound =3D device_is_bound(&cons->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata =3D dev_get_platdata(&cons->dev); + KUNIT_ASSERT_EQ(test, pdata->probe_count, 0); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_SWNODE_TEST_PROVIDER, + .id =3D PLATFORM_DEVID_NONE, + .swnode =3D &pwrseq_test_provider_swnode, + .data =3D &(const struct pwrseq_swnode_provider_pdata){ + .provider_swnode =3D &pwrseq_test_provider_swnode, + }, + .size_data =3D sizeof(struct pwrseq_swnode_provider_pdata), + }; + + prvd =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + wait_for_device_probe(); + + scoped_guard(device, &prvd->dev) + bound =3D device_is_bound(&prvd->dev); + KUNIT_ASSERT_TRUE(test, bound); + + scoped_guard(device, &cons->dev) + bound =3D device_is_bound(&cons->dev); + KUNIT_ASSERT_TRUE(test, bound); + + pdata =3D dev_get_platdata(&cons->dev); + KUNIT_EXPECT_EQ(test, pdata->probe_count, 1); + KUNIT_EXPECT_EQ(test, pdata->pwrseq_err, 0); +} + +/* + * Verify that a pwrseq consumer referencing a provider whose software nod= e is + * not registered yet, defers its probe instead of failing. + * + * The provider software node is deliberately left unregistered when the + * consumer is added. fw_devlink cannot resolve the reference, so it creat= es no + * supplier link and does not order the consumer - the consumer's probe() = runs + * and calls pwrseq_get(), which finds no matching provider and returns + * -EPROBE_DEFER. Once the provider software node and device appear, the + * deferred consumer probes again and binds. + */ +static void pwrseq_swnode_probe_defer_on_unregistered(struct kunit *test) +{ + struct property_entry properties[2] =3D { }; + struct pwrseq_probe_order_pdata *pdata; + struct platform_device_info pdevinfo; + struct platform_device *prvd, *cons; + struct fwnode_handle *fwnode; + bool bound =3D false; + int ret; + + ret =3D kunit_platform_driver_register(test, &pwrseq_swnode_provider_driv= er_2); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret =3D kunit_platform_driver_register(test, &pwrseq_probe_defer_consumer= _driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + properties[0] =3D PROPERTY_ENTRY_REF("pwrseq-provider", + &pwrseq_test_provider_swnode); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_PROBE_DEFER_TEST_CONSUMER, + .id =3D PLATFORM_DEVID_NONE, + .data =3D &pwrseq_probe_order_pdata_template, + .size_data =3D sizeof(pwrseq_probe_order_pdata_template), + .properties =3D properties, + }; + + cons =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound =3D device_is_bound(&cons->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata =3D dev_get_platdata(&cons->dev); + KUNIT_ASSERT_GT(test, pdata->probe_count, 0); + KUNIT_ASSERT_EQ(test, pdata->pwrseq_err, -EPROBE_DEFER); + + fwnode =3D kunit_software_node_register(test, &pwrseq_test_provider_swnod= e); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + pdevinfo =3D (struct platform_device_info){ + .name =3D PWRSEQ_SWNODE_TEST_PROVIDER_2, + .id =3D PLATFORM_DEVID_NONE, + .swnode =3D &pwrseq_test_provider_swnode, + .data =3D &(const struct pwrseq_swnode_provider_pdata){ + .provider_swnode =3D &pwrseq_test_provider_swnode, + }, + .size_data =3D sizeof(struct pwrseq_swnode_provider_pdata), + }; + + prvd =3D kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + wait_for_device_probe(); + + scoped_guard(device, &prvd->dev) + bound =3D device_is_bound(&prvd->dev); + KUNIT_ASSERT_TRUE(test, bound); + + scoped_guard(device, &cons->dev) + bound =3D device_is_bound(&cons->dev); + KUNIT_ASSERT_TRUE(test, bound); + + pdata =3D dev_get_platdata(&cons->dev); + KUNIT_EXPECT_EQ(test, pdata->pwrseq_err, 0); +} + +static int pwrseq_swnode_test_init(struct kunit *test) +{ + /* + * A device link teardown from an earlier test case, or from this + * suite's previous module load, may still be queued on device_link_mq. + * Flush it so software_node_register() below doesn't spuriously see + * the about-to-be-reused node name as still taken. + */ + device_link_wait_removal(); + + return 0; +} + +static struct kunit_case pwrseq_swnode_probe_order_tests[] =3D { + KUNIT_CASE(pwrseq_swnode_probe_order), + KUNIT_CASE(pwrseq_swnode_probe_defer_on_unregistered), + { } +}; + +static struct kunit_suite pwrseq_swnode_probe_order_test_suite =3D { + .name =3D "pwrseq-swnode-probe-order", + .init =3D pwrseq_swnode_test_init, + .test_cases =3D pwrseq_swnode_probe_order_tests, +}; + +kunit_test_suites(&pwrseq_test_suite, + &pwrseq_swnode_probe_order_test_suite); + +MODULE_DESCRIPTION("KUnit test cases for the power sequencing subsystem"); +MODULE_AUTHOR("Bartosz Golaszewski "= ); +MODULE_LICENSE("GPL"); --=20 2.47.3