From nobody Fri Sep 25 10:36:40 2026 Received: from mail-wr1-f53.google.com (mail-wr1-f53.google.com [209.85.221.53]) (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 C14463815FD for ; Mon, 14 Sep 2026 07:15:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789370142; cv=none; b=PYezMqQJWw8eKgBpqQ9Y1L+yT31vuztFjfSiC16aX6w3oKhrDXhNEKeXnUZkDJOkEsntolyv76vEdpKbcnXavNPz1aYEeHCewd7lCFBLi/6TE6HmaCINQMP1IX4/UC92qjLw31+1ItX1m6ZkZanhkEOExZ7vDjTbyl4piyTXP3Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789370142; c=relaxed/simple; bh=Z2lb95NyFj5VMsmWjYdqmvMmkWFzhfs0R05riKxv8DI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qtNJeCVU844AX/AEKClSqMzlkfIPHSk3Yk8gPg3PX7WNWDmjdNWjrPnpEXGnF2DDNLbnbk51UJi1avPkTBIfENVVA0/owCUWt8O7SEOtrcp/g04TV+M0aoW0q/aTZu2LaBSq/DnLUp1SN0XPCIb66ioXkPlcnX6S9fltraQO6SA= 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=eho/KnpQ; arc=none smtp.client-ip=209.85.221.53 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="eho/KnpQ" Received: by mail-wr1-f53.google.com with SMTP id ffacd0b85a97d-485850cbac3so1491469f8f.3 for ; Mon, 14 Sep 2026 00:15:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789370139; x=1789974939; 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=CYDvBl7Hf5spaenerJy9kw2Xhfgsi5OoXNIquuHbSV8=; b=eho/KnpQZE3yB4bnYaY5/s2r3ETWOcLDS2adZ7o4nWh/tRc+IzN3ru9ZNOGqQttN0/ qTYfv0wsYpwmK7nNV79YdZPdZcpFoIl0G9QKscysTcrqfG10w2Ui3InGr/pd2DtQ/mFE JJA8mTVBf+KH9W0tJhqnfxBhHpsh9PGT+R7XVhW6Bmz1BE7dPw3Q1s3GtXjI4DQPNZ97 zzDOzFRGsLSEXzRY0tzIBcvkQQLy5eLmFNeNaCkb5UddIMoO3O+R6tDaasMfNE2l9+P3 bTJkY+xZyKUACCe7XG+Rdvv6Mb9UWYfoW6lw1AlKWLtLhh4yPUW7EFDtA4YcBiXcPnCJ b3qw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789370139; x=1789974939; 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=CYDvBl7Hf5spaenerJy9kw2Xhfgsi5OoXNIquuHbSV8=; b=aPJzUEGBtKr5SGO2u7EAu1jeLtUYgq52BVqSqdR0Nfn92B+93RZdEICnOOpwM+spke hcLm/Jb0OJom2qSMf1w8vcENey2bgnfWNdpU5x8OlN9NGccvu7bBi2CqfZygdF4It18+ IjiC89vfGNkA6x/hWoxWgzV/k7GJyyM3JBHhjW5MSiDHrPbLFMdyh2q8GnJMYApZmZX7 VfKA0IxjDp7HF2xWVv86/QxydpBc/Ef/7Lvqb9QgXuz4AyBdWuS++CEfgzrwv7t8+4b9 FvPMGGtJFDPDujxphCWVj3Pqj9rzwdHzP+JTfeAL7VjAeLpTbngxQWXUzjw9pubGeR8u K4SQ== X-Forwarded-Encrypted: i=1; AKwUvBz4TYyy6r6ZIm5LXG8DjfEW9UlBPakwAQpCppoGvFhyIbOLuT8AaoFEXYNHktsFm0YQRRz+5vFTZU0G1o0=@vger.kernel.org X-Gm-Message-State: AFuF++nLYlsxNf+oCPL4GKVErllq5GDq7tUA+TJkKD4Gk9loxwxjptKN ZBk93nPz9/NQNLxYKq/JSMnl5+oVYeHMqK54XB7hduDoF70ZL4bDg6Z1 X-Gm-Gg: AYBFou0mQUlZpTRhqe+SODCOkaviYwjh1RcZrtGCG1GMK5Ao27/SSdFONuaepHufmb3 qPbQoAZjkmnHNyB8ODMuuzaugbUUxQESyzpu7A12C187Goa61mtcYVvNee1KLIYJ0AjjkgRvkMG +TK2TuJQPVauxDTw9FcHSIBqc3lt3GIn5I4Zao5ARik3yRbWR0I7RnOhml0XChkxGB1YBV3W/x8 IECMFbvzufD+A/8dsKWGqDANja/Er8s95PXSonfjEjycnOm3B/g9W+QRZEQ6CwL/ULhC04LiXSR +mniSTykkUWdolA/jTm4eSLS6+BpPBptUdv2dwELAofnFUSCZMiks3H4C7ih1mwMgJFVoTCJIKR Gcu7E5N3Q4nWuxG/6cMcJnosaIhzFAEODC30Hz2mOsCyuSYgyWrw7BwELWq+zG6QFjH1EUnoDz8 BygbRcY3QyxutOt4lYzT7GCjPfuK59L/AnxsJvqS/mGP6BoO0iTrcOihUEc0FuRJRvQBPT9u0F7 nP6dQUZZsfdlzJOnTlG2Mf6Y5FLivZgysmL9IgA X-Received: by 2002:a05:600c:a00d:b0:49c:fc6e:a3dc with SMTP id 5b1f17b1804b1-49e7a678d2amr12314265e9.27.1789370138746; Mon, 14 Sep 2026 00:15:38 -0700 (PDT) Received: from fedora-tap.advaoptical.com ([82.166.23.19]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-486eb34e4b5sm24472558f8f.22.2026.09.14.00.15.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 14 Sep 2026 00:15:37 -0700 (PDT) From: Sagi Maimon To: Richard Cochran , Vadim Fedorenko , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Andrew Lunn , Simon Horman , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Corbet , Randy Dunlap , Shuah Khan , netdev@vger.kernel.org Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, Sagi Maimon Subject: [PATCH net-next v14 1/4] ptp: ocp: unregister devlink before detach on probe error Date: Mon, 14 Sep 2026 10:15:28 +0300 Message-ID: <20260914071531.11640-2-maimon.sagi@gmail.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20260914071531.11640-1-maimon.sagi@gmail.com> References: <20260914071531.11640-1-maimon.sagi@gmail.com> 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" devlink_register() runs before the dpll setup in ptp_ocp_probe(), but the failure paths after it went straight to ptp_ocp_detach() and then devlink_free(), which opens with ASSERT_DEVLINK_NOT_REGISTERED(devlink). Any dpll failure during probe therefore freed a devlink instance that was still registered. Route those paths through a new out_devlink label that unregisters first, matching the order ptp_ocp_remove() already uses. Fixes: 09eeb3aecc6c ("ptp_ocp: implement DPLL ops") Signed-off-by: Sagi Maimon --- drivers/ptp/ptp_ocp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9e..401b04f12d2b 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -5119,12 +5119,12 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pc= i_device_id *id) if (IS_ERR(bp->dpll)) { err =3D PTR_ERR(bp->dpll); dev_err(&pdev->dev, "dpll_device_alloc failed\n"); - goto out; + goto out_devlink; } =20 err =3D dpll_device_register(bp->dpll, DPLL_TYPE_PPS, &dpll_ops, bp); if (err) - goto out; + goto out_devlink; =20 for (i =3D 0; i < OCP_SMA_NUM; i++) { bp->sma[i].dpll_pin =3D dpll_pin_get(clkid, i, THIS_MODULE, @@ -5151,6 +5151,8 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_= device_id *id) dpll_pin_put(bp->sma[i].dpll_pin, &bp->sma[i].tracker); } dpll_device_put(bp->dpll, &bp->tracker); +out_devlink: + devlink_unregister(devlink); out: ptp_ocp_detach(bp); out_disable: --=20 2.47.0 From nobody Fri Sep 25 10:36:40 2026 Received: from mail-wr2-f12.google.com (mail-wr2-f12.google.com [74.125.225.76]) (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 7667C3AFAF4 for ; Mon, 14 Sep 2026 07:15:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.76 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789370145; cv=none; b=eYd+ZpcX18412Ai1FLOa7XK86W1Yr0W+4/Pgc9wSrQsjBwsmLGOsWOT20puxALTz11NM58rs+ysS3zRiXkpaCGd1K0kZ+smiQ9LIoiMWwp4fPYxJHiENtDjxNaVIMWWBaxDLyANjoqcZ9FbX0yPUQ78X4rARxpNaEo8qu/VBGCo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789370145; c=relaxed/simple; bh=jSJaAsJ9GUyAudepjyD7M4Zr0u8277gmamjboG8joAc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mw17NK/fLTEFs+5cZQQUYXEZLvrsxZQmiMBPVoOQ6ofZTYNi+IWTMaBfe0ZLVK8imPKlsWfiZgIJ56EriBP8m8V6zycfah9OHJuTZVmvHtyEYOENPSLtOHhkJyNyatCv7FSzu69B9LDf0YLfbq08z+YGZxJ1UU6mlLcGsfbmlBY= 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=Z4c8A6cg; arc=none smtp.client-ip=74.125.225.76 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="Z4c8A6cg" Received: by mail-wr2-f12.google.com with SMTP id ffacd0b85a97d-485b1d2874aso1329969f8f.1 for ; Mon, 14 Sep 2026 00:15:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789370142; x=1789974942; 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=CK5GX+kDbjRMghqOTfEiEdFRpT8/kc5WErzxR+yMM5U=; b=Z4c8A6cg8kdb54lm2Jv2uiCZYemrhUNORG/U6kCq3AFENw/pUTxti6K40o8o8z6HvV 1vhfb82RLpLJoL8AjtVDl3LbmbVO43ZaWA5PBXmVsXtQV4rcSbFAapAMF+Y3pSw0zT+F fffToe2zywkiutCt20RNFDVv5GT6IZTcjOk4evWAeomTvnO1YR/7X/xUVX7X5oorEW4w HlOoDi8FLStsbSdvqZlLXsqnDojbdLaizQOij2PXBrZMxgsyR7oamh853Ay/o5Pd/W2T 6jmHZWKz26DuO+jUxKmQsIdm2wLQgSHzIiprmhvpX72/SDh0tuf40So6mbo+50X9ryrg S1AQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789370142; x=1789974942; 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=CK5GX+kDbjRMghqOTfEiEdFRpT8/kc5WErzxR+yMM5U=; b=GELUBUp57oC/SKdPpxYp8CYCub0KPBTfG4M/jjr72sw1W1qQtY6cy1FyHOiy7crIlF kITnGH6MBafgeywXqtatVB6cxJ3m2kQbYGleowFi7XCkJSvXF9o7Z4bsTKgWC4O2gUiZ JGg/F+xfUe8Orp6cQsO1/n610Sc0Au6cV5feOPm4Fap47rpt7uCrlw1fLIA8srbuebov nR11HcNS211k5HNKSPsbKrnzIc8eAENRbC4dr9vaqxpPLXVzmvamU52MZOl01BjGeEDW I3qCfAE4sqTh8ECkOtkPp+dkQC0KFcUz9nbMag4v+x9DvcDhZ9femUXINeg4AGLKMJSm uSQg== X-Forwarded-Encrypted: i=1; AKwUvByq6Gr0l4rdklFOqSNvUIEWjn8XYUWCOeHrFrNYa/1a4GDWF7SnwnamEtrKi2xHAQY+ggKMWNEbRFjO1vM=@vger.kernel.org X-Gm-Message-State: AFuF++kqaoWccNrqmsboE49qdjXwp41ezPRhgV+BbYmmZaj2f1RY0rTH rWDFOducOQ1Eo/0b8lYPcctufW1dO05kOGB+FNXbCswEAMXaaqSX8iTU X-Gm-Gg: AYBFou239VghxcSc6k+LeNEcmm9Yr59Yywrow8rVUfxSeMsbKhXSvs05yKiDH1qXMH3 t1Gc9MEj2uVe+EtRNQJKC63OHRr/Ul5/taikmximnQhv5yA1MHYjZ8RDncwc56LvpJ2utqHVggI 6Br7ABx2dZE3T9wTYTCbpEfEb2IUqCpxt95YWVX4WFmGQdoDVIdg6kMajJxucZ6knlTCXsqra09 laH9pjOclaCUv29Z9a0bmyhprhgl2bhvNUTLHdDxnJ1HyeBWDc6q+lrKZvoY+v9dZYTDS1dt/hQ VceacDg93mFm5+sXy5KfwGk9vrw397mCE0dy6EVm9hlcXa7Ba5DeBtYmhNczE9+lt/srM4P36Fq BA/tfq0YgnjaMOVKtocYqnufdoKPCbS1WPKlVPaaXkFl8UD/qHWkj25EkLa+p+5DMNsyTJtGcsA A8A8tg5quP9NLAZEondOcBbSklkYTc0xEAzaIMy93IPFS4N8lLdG8UTWo+tc/Xj60FLHd+gJgi5 2FHRzJbNOFiWaCBHZQl7ilYxJo23VsbvnOxEeDm X-Received: by 2002:a05:6000:71e:b0:484:3310:c4fc with SMTP id ffacd0b85a97d-48702b2dd2bmr2636915f8f.23.1789370141587; Mon, 14 Sep 2026 00:15:41 -0700 (PDT) Received: from fedora-tap.advaoptical.com ([82.166.23.19]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-486eb34e4b5sm24472558f8f.22.2026.09.14.00.15.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 14 Sep 2026 00:15:40 -0700 (PDT) From: Sagi Maimon To: Richard Cochran , Vadim Fedorenko , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Andrew Lunn , Simon Horman , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Corbet , Randy Dunlap , Shuah Khan , netdev@vger.kernel.org Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, Sagi Maimon Subject: [PATCH net-next v14 2/4] ptp: ocp: fix dpll cleanup on probe error Date: Mon, 14 Sep 2026 10:15:29 +0300 Message-ID: <20260914071531.11640-3-maimon.sagi@gmail.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20260914071531.11640-1-maimon.sagi@gmail.com> References: <20260914071531.11640-1-maimon.sagi@gmail.com> 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" Two problems on the dpll error paths in ptp_ocp_probe(). dpll_device_register() takes a second reference and stores bp as the registration private data, and only dpll_device_unregister() drops it. out_dpll dropped bp->tracker but never unregistered, so a dpll_pin_get() or dpll_pin_register() failure left the device registered and visible to userspace with a priv pointer into the storage devlink_free() releases a few lines later, as well as leaking the registration object. A failing dpll_device_register() jumped to the same label as the failing dpll_device_get(), so the reference taken by dpll_device_get() was never dropped either. Give that case its own label. Fixes: 09eeb3aecc6c ("ptp_ocp: implement DPLL ops") Signed-off-by: Sagi Maimon --- drivers/ptp/ptp_ocp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 401b04f12d2b..f4299878c27c 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -5124,7 +5124,7 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_= device_id *id) =20 err =3D dpll_device_register(bp->dpll, DPLL_TYPE_PPS, &dpll_ops, bp); if (err) - goto out_devlink; + goto out_dpll_put; =20 for (i =3D 0; i < OCP_SMA_NUM; i++) { bp->sma[i].dpll_pin =3D dpll_pin_get(clkid, i, THIS_MODULE, @@ -5150,6 +5150,12 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci= _device_id *id) dpll_pin_unregister(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops, &bp->= sma[i]); dpll_pin_put(bp->sma[i].dpll_pin, &bp->sma[i].tracker); } + /* dpll_device_register() takes a second reference and stores bp; only + * dpll_device_unregister() drops it. Without this the device stays + * visible with a priv pointer into storage devlink_free() will release. + */ + dpll_device_unregister(bp->dpll, &dpll_ops, bp); +out_dpll_put: dpll_device_put(bp->dpll, &bp->tracker); out_devlink: devlink_unregister(devlink); --=20 2.47.0 From nobody Fri Sep 25 10:36:40 2026 Received: from mail-wr2-f12.google.com (mail-wr2-f12.google.com [74.125.225.76]) (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 370C93AE1A9 for ; Mon, 14 Sep 2026 07:15:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.76 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789370149; cv=none; b=XZZoMkdiTD7CdwbELGDHj6xWg6QFHiiNpgbBeahg0Nfx8HIPNQAvQK+MlkCd3Zhl2Z0n0FlQNaF6Wg16y1CL1feulbfmRw5vRFTKrXsvLO07WJJzROsUheQ/NNBLPw2tD/EP1waW7LpwJDyVppCqAd1OdG4cqQhVhzCBaAC/myA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789370149; c=relaxed/simple; bh=daOt5lWNYyHvriR9j/9+ZGzI8yEvwgW80BmG1PGMQJY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ijNWAoEMZTuKoWJvvrkoP5qBuwmcYg7OkN+vb1mSI2Kk5VOlvni9h0+TxBSaNETXanWuidM4IGEfE8FP1sjT/YhGowb4VkD766RNdEuvtQICuZQvWU3l0ldoCOAVaOA9e2PfU0WA3D9kP9HAg0lLscY55Qv7zhYmwgjM4UWKwQc= 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=fTKeoyy7; arc=none smtp.client-ip=74.125.225.76 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="fTKeoyy7" Received: by mail-wr2-f12.google.com with SMTP id ffacd0b85a97d-4843c3ea1f6so639871f8f.0 for ; Mon, 14 Sep 2026 00:15:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789370145; x=1789974945; 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=qNz8sB3S+x4guC51FwtW9TDNSE702r9jmMXEbrHt3tI=; b=fTKeoyy7LvjngP/4yEAMjmy55uH/ih6xSn9B6Yjs7UWDqymCcfcJqpLprPDCFzL5Vf 67JO1PdKENcTdsuty0OiZcY70D739BiXHeAXyz1duS2X+305RkBOKAReBSsz6/dDEluB p7oGVx9si28pMWs8HoUctMlNIeOQ1NQg5jLdrCNB1KKuWzkxgDJkdXiKXrxsmPtqwfJj +OUMa4op8U4RCG30HpnCg5dBb4qY+EcPJV8tPXYGYLxcv81iR75LsZ/4mFxeqwdtMbh4 LGzvx304j8954v4j0LRj3HrjQI+YTHdFp6MTt9BpODBN3oKOQzvglOueDmbSScBndF/q nYhw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789370145; x=1789974945; 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=qNz8sB3S+x4guC51FwtW9TDNSE702r9jmMXEbrHt3tI=; b=izc2xOzbNQhFu8maWMHRbFyxOA1lYqSFknh23uBzj6DzGhWe27LBF8EB1Zlvweuz9j RSqNgK+6JBTPI2bGd/JIwQ7OvTvWTIXtQtpnYYprviGQ847xIFmw2ry+bYZ/ahqpdAqk a3e2YVxSyANjhc0ovCFea9CZOLEzXBl+IiKixrsxmtIZq49Rkp1OsXirTDWsERg/Kcoo KX9Y7/qozf+7tF7qmOLTfYEZVgjOGVKtR1vKmUpRaX0R7cl/Fqm8V7W1qSulDzF1dVB/ WIZpO29tE4ULCJH+8feOKIhFAfToNc+56shyyPw0tcb+Hmj9/849OxvIiJvDMKAIKecu unxA== X-Forwarded-Encrypted: i=1; AKwUvBzHOqwJA7qGP6mb12bqnJRCSLSFB0lSEJqk+uJ0U6zER42Ut/J+4cHTfOVaj8iLx2sK9m4rDj/6PY0JR3I=@vger.kernel.org X-Gm-Message-State: AFuF++mzHCOAR7hFfRLPTYKqrC8f6Lqj/MK2WY7wSzHI1sqzPquvcV9l oe2+UgsExayh0yXdOBT3RN/s9AIHW/Z2fYUeVgXTFENhDMkPR52Zdlgb X-Gm-Gg: AYBFou1TZtpL8BnPHGV6L0Vvbc1YyZHmAAlR25tJpkIJ3gbDWiWLyoGpYFujw6lqfh3 MrHnb+Dt3lqTas8z74tM/K3GBUavMc3qVWuSIH/1AWC1If1J6ORXVbPHNlfDr+Knv5AQQnIlFEl Zu6YaR1T6AULtq/990SfAGeyG+y2lqV+XH7ojkUBfaExnQkRnNwY8P4pV+s7On3/rD/OdnwsAtK 7rI3YGk/+hE+IsIqThzkSBxcwl17FLMCzp1EjiS6jTB6xa1f5nluD8SLWvBiZKpdMssSZnQ7grj 02Vr8LaQ2m00Z9gOWYRJIyW9HDtscppB8GWmFKlKmPJGQrXlryCwHpRi43UdqlfK5mPbLDNi/TY kBBycW3swJJghMfqDUF/ecOHYLbuGo8wOYeVXqGdU8Ow8VCFqvCPSWHThpIvPBvgEAn7Om4uNOf DACPGWdpqubIBsUCWUfQrY1UYvNQYTexhDm5l3aZ/zSyDeu6LMkUqnBplgjQ1ceNJMrxXdu4eIU dkRwUITBY/GfMiCQRJNf7qx2BJ4tl3j5fXrGzQV X-Received: by 2002:a05:6000:1785:b0:485:8a46:b3c4 with SMTP id ffacd0b85a97d-48702b5e64bmr1135220f8f.44.1789370144806; Mon, 14 Sep 2026 00:15:44 -0700 (PDT) Received: from fedora-tap.advaoptical.com ([82.166.23.19]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-486eb34e4b5sm24472558f8f.22.2026.09.14.00.15.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 14 Sep 2026 00:15:43 -0700 (PDT) From: Sagi Maimon To: Richard Cochran , Vadim Fedorenko , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Andrew Lunn , Simon Horman , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Corbet , Randy Dunlap , Shuah Khan , netdev@vger.kernel.org Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, Sagi Maimon Subject: [PATCH net-next v14 3/4] ptp: ocp: add TAP CPLD access for ADVA TimeCard X1 Date: Mon, 14 Sep 2026 10:15:30 +0300 Message-ID: <20260914071531.11640-4-maimon.sagi@gmail.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20260914071531.11640-1-maimon.sagi@gmail.com> References: <20260914071531.11640-1-maimon.sagi@gmail.com> 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" The ADVA TimeCard X1 carries a Lattice MachXO3 CPLD on its TMC I2C bus. Reaching it means taking that bus from the on-card MicroBlaze first. The card has two I2C controllers. Linux registers only the one at 0x00150000, normally routed to the config EEPROMs; the one at 0x00120000 belongs to the MicroBlaze and is routed to the TMC or M.2 bus as its firmware selects. A request/grant handshake re-routes the host controller onto the TMC bus, where the PCA9548 and the CPLD behind its channel 0 live. An operation takes the i2c core adapter lock for the whole grant window and uses __i2c_transfer() internally; without that, a concurrent transfer from ptp_ocp_read_eeprom(), from the nvmem attributes or from the at24 sysfs files would be issued onto the TMC bus instead of to the EEPROM. The hand-back waits for the firmware to drop the grant before the adapter lock is released, for the same reason. On top of the arbitration this adds two read-only interfaces: a root-only cpld_status attribute reporting the flags in the CPLD status register, and the Lattice device ID as the fixed devlink version cpld.id. Reading the ID claims the bus, so it must not sit on DEVLINK_CMD_INFO_GET, which carries no GENL_ADMIN_PERM; the driver reads it once from its own worker, as soon as the i2c adapter has registered, and info_get() reports the cached value. Signed-off-by: Sagi Maimon --- Documentation/ABI/testing/sysfs-timecard | 24 + Documentation/networking/devlink/index.rst | 1 + Documentation/networking/devlink/ptp_ocp.rst | 35 ++ MAINTAINERS | 1 + drivers/ptp/ptp_ocp.c | 447 ++++++++++++++++++- 5 files changed, 504 insertions(+), 4 deletions(-) create mode 100644 Documentation/networking/devlink/ptp_ocp.rst diff --git a/Documentation/ABI/testing/sysfs-timecard b/Documentation/ABI/t= esting/sysfs-timecard index 3ae41b7634ac..c440d8930384 100644 --- a/Documentation/ABI/testing/sysfs-timecard +++ b/Documentation/ABI/testing/sysfs-timecard @@ -11,6 +11,30 @@ Contact: Jonathan Lemon Description: This directory contains the attributes of the Nth timecard registered. =20 +What: /sys/class/timecard/ocpN/cpld_status +Date: July 2026 +Contact: Sagi Maimon +Description: (RO, root only) The flags set in the status register of the + TAP CPLD, space separated, or an empty line when none is + set. Only present on ADVA x1 TAP boards (PCI ID + 0xad5a:0x0410). + + =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=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + done the configuration flash was programmed + successfully and is active + busy an internal operation is in progress + failed the last ISC operation failed + =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=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + For example "busy" while programming, or "busy failed" if a + failure is latched while another operation runs. + + A read arbitrates for the shared I2C bus and reprograms the + on-card mux, so it is restricted to root. The Lattice device + ID of the CPLD is read once by the driver, shortly after + probe, and reported from that cached value as the fixed + "cpld.id" version by devlink dev info. + What: /sys/class/timecard/ocpN/available_clock_sources Date: September 2021 Contact: Jonathan Lemon diff --git a/Documentation/networking/devlink/index.rst b/Documentation/net= working/devlink/index.rst index d4a83fdcff7f..1af780c811ee 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -99,6 +99,7 @@ parameters, info versions, and other features it supports. nfp octeontx2 prestera + ptp_ocp qed sfc stmmac diff --git a/Documentation/networking/devlink/ptp_ocp.rst b/Documentation/n= etworking/devlink/ptp_ocp.rst new file mode 100644 index 000000000000..3f997cd9f3a3 --- /dev/null +++ b/Documentation/networking/devlink/ptp_ocp.rst @@ -0,0 +1,35 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +ptp_ocp devlink support +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +This document describes the devlink features implemented by the ``ptp_ocp`` +device driver. + +Info versions +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The ``ptp_ocp`` driver reports the following versions + +.. list-table:: devlink info versions implemented + :widths: 5 5 90 + + * - Name + - Type + - Description + * - ``fw`` + - running + - Version of the firmware running on the card. Reported as ``loader`` + instead when the card is running the firmware loader. + * - ``board.id`` + - fixed + - Board identifier, read from the on-card EEPROM. + * - ``cpld.id`` + - fixed + - Lattice device ID (IDCODE) of the TAP CPLD, formatted as ``0x%08x``. + Only present on ADVA TimeCard X1 boards, which are the only ones + carrying that CPLD. Reading it claims the shared I2C bus and + reprograms the on-card mux, so the driver does that once from its + own worker and reports the cached value here; the version is + omitted until that read has succeeded. diff --git a/MAINTAINERS b/MAINTAINERS index 0e04d92d1b09..113f31434617 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20479,6 +20479,7 @@ OPENCOMPUTE PTP CLOCK DRIVER M: Vadim Fedorenko L: netdev@vger.kernel.org S: Maintained +F: Documentation/networking/devlink/ptp_ocp.rst F: drivers/ptp/ptp_ocp.c =20 OPENCORES I2C BUS DRIVER diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index f4299878c27c..98cb26077181 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -20,10 +20,14 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include =20 #define PCI_DEVICE_ID_META_TIMECARD 0x0400 =20 @@ -85,6 +89,8 @@ struct ptp_ocp_adva_info { u8 signals_nr; u8 freq_in_nr; const struct ocp_attr_group *attr_groups; + /* x1: TAP CPLD present */ + bool has_cpld; }; =20 #define OCP_CTRL_ENABLE BIT(0) @@ -163,7 +169,8 @@ struct gpio_reg { u32 gpio1; u32 __pad0; u32 gpio2; - u32 __pad1; + /* adva_x1: I2C bus ownership register; reserved on other variants */ + u32 i2c_bus_ctrl; }; =20 struct irig_master_reg { @@ -416,6 +423,23 @@ struct ptp_ocp { dpll_tracker tracker; int signals_nr; int freq_in_nr; + /* adva_x1 CPLD I2C (internal use only) */ + /* serialises CPLD operations */ + struct mutex cpld_lock; + /* I2C adapter nr; -1 if absent */ + int cpld_i2c_adap_nr; + /* claimed adapter; valid under cpld_lock */ + struct i2c_adapter *cpld_adap; + /* scratch, write half then read half; valid under cpld_lock */ + u8 *cpld_buf; + /* Lattice device ID; 0 if unread */ + u32 cpld_id; + /* the one-shot ID read is finished, successfully or not */ + bool cpld_id_tried; + /* failed ID read attempts so far */ + unsigned int cpld_id_attempts; + /* x1 TAP CPLD present */ + bool has_cpld; }; =20 #define OCP_REQ_TIMESTAMP BIT(0) @@ -1273,6 +1297,7 @@ static struct ocp_resource ocp_adva_x1_resource[] =3D= { .signals_nr =3D 4, .freq_in_nr =3D 4, .attr_groups =3D adva_timecard_x1_groups, + .has_cpld =3D true, }, }, { } @@ -2159,6 +2184,7 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, str= uct devlink_info_req *req, const char *fw_image; char buf[32]; int err; + u32 id; =20 fw_image =3D bp->fw_loader ? "loader" : "fw"; sprintf(buf, "%d.%d", bp->fw_tag, bp->fw_version); @@ -2166,6 +2192,18 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, st= ruct devlink_info_req *req, if (err) return err; =20 + /* Read by the worker, not here: this command is unprivileged and + * reading the ID claims the I2C bus. 0 means unread - skip it + * rather than fail, devlink discards the whole reply on error. + */ + id =3D READ_ONCE(bp->cpld_id); + if (bp->has_cpld && id) { + sprintf(buf, "0x%08x", id); + err =3D devlink_info_version_fixed_put(req, "cpld.id", buf); + if (err) + return err; + } + if (!bp->has_eeprom_data) { ptp_ocp_read_eeprom(bp); if (!bp->has_eeprom_data) @@ -3199,6 +3237,8 @@ ptp_ocp_adva_board_init(struct ptp_ocp *bp, struct oc= p_resource *r) return err; ptp_ocp_sma_init(bp); =20 + bp->has_cpld =3D info->has_cpld; + return ptp_ocp_init_clock(bp, &info->servo); } =20 @@ -4226,6 +4266,367 @@ static const struct ocp_attr_group art_timecard_gro= ups[] =3D { { }, }; =20 +/* + * adva_x1 TAP CPLD (Lattice LCMXO3LF-2100) helpers. + * + * The card has two I2C controllers; Linux registers only 0x00150000. + * The i2c_bus_ctrl handshake re-routes what each is wired to: + * + * grant 0: 0x150000 -> EEPROMs 0x120000 -> TMC or M.2, MB's choice + * grant 1: 0x150000 -> TMC bus 0x120000 -> M.2 + * + * The PCA9548 at 0x74 and the CPLD at 0x40 behind its channel 0 are on the + * TMC bus, reachable only while the grant is held. The EEPROMs are not + * behind the adapter for that window, so a CPLD operation holds cpld_lock + * and the adapter lock to keep the EEPROM and nvmem paths off it. + */ + +#define ADVA_MUX_ADDR 0x74 +#define ADVA_CPLD_ADDR 0x40 +#define ADVA_MUX_CHANNEL 0 + +/* Longest command or reply; bounds each half of the per-claim scratch. */ +#define ADVA_CPLD_XFER_MAX 32 + +#define MBLAZE_REQUEST 0x0000aaaaU +#define MBLAZE_GRANTED 0x5555aaaaU +#define MBLAZE_RELEASE 0x55550000U +#define MBLAZE_RETRIES 200 +#define MBLAZE_RETRY_US 10000 + +/* Lattice LCMXO3LF ISC command codes */ +#define CPLD_CMD_READ_ID 0xE0000000UL +#define CPLD_CMD_READ_STATUS 0x3C000000UL +#define CPLD_ID_MAX_ATTEMPTS 10 /* one per sync_work tick */ + +/* Status register bit positions (Lattice LCMXO3LF datasheet) */ +#define CPLD_STATUS_DONE BIT(8) +#define CPLD_STATUS_BUSY BIT(12) +#define CPLD_STATUS_FAILED BIT(13) + +/* + * Issue one I2C transaction on the TMC bus: @cmd if not negative, then + * @wlen bytes of @wdata (NULL sends zeros), then @rlen bytes read back. + * The message is built in the scratch buffer from adva_x1_bus_claim(), so + * an upload costs one allocation rather than one per page. I2C_M_DMA_SAFE + * is not set: the two halves are not separately aligned, so let the core + * bounce. Caller holds the claim, hence __i2c_transfer(). + */ +static int adva_x1_i2c_xfer(struct ptp_ocp *bp, u8 addr, int cmd, + const void *wdata, u8 wlen, + void *rdata, u8 rlen) +{ + u8 *wbuf, *rbuf; + struct i2c_adapter *adap; + struct i2c_msg msgs[2]; + int nmsgs =3D 0, ret; + u16 hdr =3D cmd >=3D 0; + + lockdep_assert_held(&bp->cpld_lock); + + adap =3D bp->cpld_adap; + if (!adap || !bp->cpld_buf) + return -ENODEV; + + if (hdr + wlen > ADVA_CPLD_XFER_MAX || rlen > ADVA_CPLD_XFER_MAX) + return -EINVAL; + + wbuf =3D bp->cpld_buf; + rbuf =3D bp->cpld_buf + ADVA_CPLD_XFER_MAX; + + if (hdr + wlen) { + if (hdr) + wbuf[0] =3D cmd; + if (wdata) + memcpy(wbuf + hdr, wdata, wlen); + else + memset(wbuf + hdr, 0, wlen); + msgs[nmsgs++] =3D (struct i2c_msg){ + .addr =3D addr, + .flags =3D 0, + .len =3D hdr + wlen, + .buf =3D wbuf, + }; + } + if (rlen) { + msgs[nmsgs++] =3D (struct i2c_msg){ + .addr =3D addr, + .flags =3D I2C_M_RD, + .len =3D rlen, + .buf =3D rbuf, + }; + } + + ret =3D __i2c_transfer(adap, msgs, nmsgs); + if (ret !=3D nmsgs) + return (ret < 0) ? ret : -EIO; + + if (rdata && rlen) + memcpy(rdata, rbuf, rlen); + + return 0; +} + +/* Hand the segment back, and wait for the firmware to drop the grant befo= re + * the caller unlocks the adapter: a transfer queued behind us would + * otherwise be clocked out while the controller is still on the TMC bus, + * which is what holding the adapter lock across the grant exists to stop. + */ +static void adva_x1_mblaze_release(struct ptp_ocp *bp) +{ + int i; + + if (!bp->pps_select) + return; + + iowrite32(MBLAZE_RELEASE, &bp->pps_select->i2c_bus_ctrl); + /* Flush the posted write, as the acquire path does. */ + ioread32(&bp->pps_select->i2c_bus_ctrl); + + for (i =3D 0; i < MBLAZE_RETRIES; i++) { + if (ioread32(&bp->pps_select->i2c_bus_ctrl) !=3D MBLAZE_GRANTED) + return; + usleep_range(MBLAZE_RETRY_US, MBLAZE_RETRY_US + 1000); + } + + dev_warn(&bp->pdev->dev, "TMC bus still granted after release\n"); +} + +/* Acquire the shared I2C bus from the MicroBlaze firmware. Returns with = no + * request outstanding on failure, so the firmware is never left granting a + * segment to a host that has given up waiting for it. + */ +static int adva_x1_mblaze_acquire(struct ptp_ocp *bp) +{ + u32 val; + int i; + + if (!bp->pps_select) + return -ENODEV; + + /* Drop a request left by a caller that died mid-sequence; cpld_lock + * keeps live ones out. The read back only flushes the posted write. + */ + iowrite32(0, &bp->pps_select->i2c_bus_ctrl); + ioread32(&bp->pps_select->i2c_bus_ctrl); + + iowrite32(MBLAZE_REQUEST, &bp->pps_select->i2c_bus_ctrl); + for (i =3D 0; i < MBLAZE_RETRIES; i++) { + usleep_range(MBLAZE_RETRY_US, MBLAZE_RETRY_US + 1000); + val =3D ioread32(&bp->pps_select->i2c_bus_ctrl); + if (val =3D=3D MBLAZE_GRANTED) + return 0; + } + + adva_x1_mblaze_release(bp); + return -ETIMEDOUT; +} + +/* Route the host controller back to the EEPROMs and release the adapter. + * Safe after a failed claim: it also clears a request that was never gran= ted. + */ +static void adva_x1_bus_release(struct ptp_ocp *bp) +{ + struct i2c_adapter *adap =3D bp->cpld_adap; + + if (!adap) + return; + + adva_x1_mblaze_release(bp); + bp->cpld_adap =3D NULL; + kfree(bp->cpld_buf); + bp->cpld_buf =3D NULL; + i2c_unlock_bus(adap, I2C_LOCK_ROOT_ADAPTER); + i2c_put_adapter(adap); +} + +static int adva_x1_i2c_adap_match(struct device *dev, const void *data) +{ + return !!i2c_verify_adapter(dev); +} + +/* + * Cache the adapter the CPLD hangs off. ptp_ocp_i2c_notifier_call() miss= es + * it when the i2c controller driver is already loaded: the adapter is then + * added from inside platform_device_register(), before bp->i2c_ctrl is se= t. + * Only direct children are searched, so a mux adapter cannot match. + */ +static void adva_x1_cache_i2c_adap(struct ptp_ocp *bp) +{ + struct device *child; + + if (!bp->has_cpld || !bp->i2c_ctrl || + READ_ONCE(bp->cpld_i2c_adap_nr) >=3D 0) + return; + + child =3D device_find_child(&bp->i2c_ctrl->dev, NULL, + adva_x1_i2c_adap_match); + if (!child) + return; + + WRITE_ONCE(bp->cpld_i2c_adap_nr, i2c_verify_adapter(child)->nr); + put_device(child); +} + +/* + * Claim the TMC bus. The adapter lock is held across the handshake to ke= ep + * the EEPROM, nvmem and at24 paths off the controller while it is routed + * away from the EEPROMs; an EEPROM read blocks until the claim is dropped. + */ +static int adva_x1_bus_claim(struct ptp_ocp *bp) +{ + struct i2c_adapter *adap; + int ret; + + lockdep_assert_held(&bp->cpld_lock); + + adap =3D i2c_get_adapter(READ_ONCE(bp->cpld_i2c_adap_nr)); + if (!adap) + return -ENODEV; + + /* The number is freed before the notifier clears it, so it can + * already be another adapter's. Check this is still ours. + */ + if (!bp->i2c_ctrl || adap->dev.parent !=3D &bp->i2c_ctrl->dev) { + i2c_put_adapter(adap); + return -ENODEV; + } + + /* One scratch buffer per claim rather than per transfer: an upload + * holds the claim for the whole image. + */ + bp->cpld_buf =3D kzalloc(2 * ADVA_CPLD_XFER_MAX, GFP_KERNEL); + if (!bp->cpld_buf) { + i2c_put_adapter(adap); + return -ENOMEM; + } + + i2c_lock_bus(adap, I2C_LOCK_ROOT_ADAPTER); + bp->cpld_adap =3D adap; + + ret =3D adva_x1_mblaze_acquire(bp); + if (ret) + adva_x1_bus_release(bp); + + return ret; +} + +/* Select a mux channel, or deselect all with ch < 0 - the power-on state. + * The mux is on the TMC bus, so what it is left set to never affects the + * EEPROM paths. + */ +static int adva_x1_mux_select(struct ptp_ocp *bp, int ch) +{ + u8 val =3D (ch >=3D 0) ? BIT(ch) : 0; + + return adva_x1_i2c_xfer(bp, ADVA_MUX_ADDR, val, NULL, 0, NULL, 0); +} + +/* + * Send a 4-byte command and read back without an intermediate STOP: two + * messages in one transfer is the Lattice write -> repeated START -> read, + * so no protocol-mangling flag is needed. + */ +static int adva_x1_cpld_cmd_read(struct ptp_ocp *bp, + u32 cmd_be, u8 *out, u8 out_len) +{ + __be32 cmd =3D cpu_to_be32(cmd_be); + + return adva_x1_i2c_xfer(bp, ADVA_CPLD_ADDR, -1, &cmd, 4, out, out_len); +} + +static int adva_x1_cpld_read_status(struct ptp_ocp *bp, u32 *status) +{ + u8 buf[4]; + int ret; + + ret =3D adva_x1_cpld_cmd_read(bp, CPLD_CMD_READ_STATUS, buf, 4); + if (ret) + return ret; + *status =3D get_unaligned_be32(buf); + return 0; +} + +/* + * Read the Lattice device ID into bp->cpld_id. Done once, off the + * unprivileged devlink path, which reports the cached value. -EBUSY means + * cpld_lock is held, so the caller can retry rather than wait behind a lo= ng + * CPLD operation. + */ +static int adva_x1_cpld_read_id(struct ptp_ocp *bp) +{ + u8 data[4]; + int ret; + + if (!mutex_trylock(&bp->cpld_lock)) + return -EBUSY; + + ret =3D adva_x1_bus_claim(bp); + if (ret) + goto out; + ret =3D adva_x1_mux_select(bp, ADVA_MUX_CHANNEL); + if (ret) + goto release; + ret =3D adva_x1_cpld_cmd_read(bp, CPLD_CMD_READ_ID, data, 4); + if (!ret) + WRITE_ONCE(bp->cpld_id, get_unaligned_be32(data)); + adva_x1_mux_select(bp, -1); +release: + adva_x1_bus_release(bp); +out: + mutex_unlock(&bp->cpld_lock); + if (ret) + dev_dbg(&bp->pdev->dev, + "CPLD device ID unavailable: %d\n", ret); + + return ret; +} + +/* cpld_status - show the flags set in the TAP CPLD status register. */ +static ssize_t +cpld_status_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ptp_ocp *bp =3D dev_get_drvdata(dev); + int count =3D 0; + u32 st =3D 0; + int ret; + + /* A CPLD operation can hold cpld_lock a long time; stay killable. */ + ret =3D mutex_lock_interruptible(&bp->cpld_lock); + if (ret) + return ret; + + ret =3D adva_x1_bus_claim(bp); + if (ret) + goto out; + ret =3D adva_x1_mux_select(bp, ADVA_MUX_CHANNEL); + if (ret) + goto release; + ret =3D adva_x1_cpld_read_status(bp, &st); + adva_x1_mux_select(bp, -1); +release: + adva_x1_bus_release(bp); +out: + mutex_unlock(&bp->cpld_lock); + if (ret) + return ret; + + /* Flags: the names of the bits that are set, nothing when none is, + * rather than a value per bit in one file. + */ + if (st & CPLD_STATUS_DONE) + count +=3D sysfs_emit_at(buf, count, "done"); + if (st & CPLD_STATUS_BUSY) + count +=3D sysfs_emit_at(buf, count, "%sbusy", count ? " " : ""); + if (st & CPLD_STATUS_FAILED) + count +=3D sysfs_emit_at(buf, count, "%sfailed", + count ? " " : ""); + + return count + sysfs_emit_at(buf, count, "\n"); +} +static DEVICE_ATTR_ADMIN_RO(cpld_status); + static struct attribute *adva_timecard_attrs[] =3D { &dev_attr_serialnum.attr, &dev_attr_gnss_sync.attr, @@ -4274,6 +4675,7 @@ static struct attribute *adva_timecard_x1_attrs[] =3D= { &dev_attr_ts_window_adjust.attr, &dev_attr_utc_tai_offset.attr, &dev_attr_tod_correction.attr, + &dev_attr_cpld_status.attr, NULL, }; =20 @@ -4904,6 +5306,7 @@ ptp_ocp_detach(struct ptp_ocp *bp) clk_hw_unregister_fixed_rate(bp->i2c_clk); if (bp->n_irqs) pci_free_irq_vectors(bp->pdev); + mutex_destroy(&bp->cpld_lock); device_unregister(&bp->dev); } =20 @@ -5052,6 +5455,20 @@ ptp_ocp_sync_work(struct work_struct *work) =20 bp->sync =3D sync; =20 + /* The adapter can appear after probe, so read the ID from here once + * it has. A claim can fail transiently - the firmware may not grant + * the segment straight after power-up - so retry a bounded number of + * times. -EBUSY means cpld_lock is held and does not count. + */ + if (bp->has_cpld && !READ_ONCE(bp->cpld_id_tried) && + READ_ONCE(bp->cpld_i2c_adap_nr) >=3D 0) { + int err =3D adva_x1_cpld_read_id(bp); + + if (err !=3D -EBUSY && + (!err || ++bp->cpld_id_attempts >=3D CPLD_ID_MAX_ATTEMPTS)) + WRITE_ONCE(bp->cpld_id_tried, true); + } + queue_delayed_work(system_power_efficient_wq, &bp->sync_work, HZ); } =20 @@ -5080,6 +5497,14 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci= _device_id *id) if (err) goto out_disable; =20 + /* Before the first error path that calls ptp_ocp_detach(), so + * mutex_destroy() runs on an initialised mutex, and before + * ptp_ocp_register_resources(), so this sentinel cannot overwrite an + * adapter number the i2c notifier stores during registration. + */ + mutex_init(&bp->cpld_lock); + bp->cpld_i2c_adap_nr =3D -1; + INIT_DELAYED_WORK(&bp->sync_work, ptp_ocp_sync_work); =20 /* compat mode. @@ -5099,6 +5524,8 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_= device_id *id) if (err) goto out; =20 + adva_x1_cache_i2c_adap(bp); + bp->ptp =3D ptp_clock_register(&bp->ptp_info, &pdev->dev); if (IS_ERR(bp->ptp)) { err =3D PTR_ERR(bp->ptp); @@ -5158,6 +5585,7 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_= device_id *id) out_dpll_put: dpll_device_put(bp->dpll, &bp->tracker); out_devlink: + /* detach() destroys cpld_lock; no devlink op may still reach it */ devlink_unregister(devlink); out: ptp_ocp_detach(bp); @@ -5204,6 +5632,7 @@ ptp_ocp_i2c_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { struct device *dev, *child =3D data; + struct i2c_adapter *adap; struct ptp_ocp *bp; bool add; =20 @@ -5227,11 +5656,21 @@ ptp_ocp_i2c_notifier_call(struct notifier_block *nb, =20 found: bp =3D dev_get_drvdata(dev); - if (add) + adap =3D i2c_verify_adapter(child); + if (add) { ptp_ocp_symlink(bp, child, "i2c"); - else + /* Only the card's own controller: a mux child added on it + * reaches ptp_ocp through the same walk. Numbers are + * recycled, so the claim re-checks the parent itself. + */ + if (bp->i2c_ctrl && adap->dev.parent =3D=3D &bp->i2c_ctrl->dev) + WRITE_ONCE(bp->cpld_i2c_adap_nr, adap->nr); + } else { + /* invalidate before free, but only for the cached adapter */ + if (READ_ONCE(bp->cpld_i2c_adap_nr) =3D=3D adap->nr) + WRITE_ONCE(bp->cpld_i2c_adap_nr, -1); sysfs_remove_link(&bp->dev.kobj, "i2c"); - + } return 0; } =20 --=20 2.47.0 From nobody Fri Sep 25 10:36:40 2026 Received: from mail-wr2-f12.google.com (mail-wr2-f12.google.com [74.125.225.76]) (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 1062B3AFB1C for ; Mon, 14 Sep 2026 07:15:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.76 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789370154; cv=none; b=u3KnCXVZ8MgWWWr+RMMQ2U6P9UI67wzp+TwiFSvynR1pnqJo1qunpSSF1YclGLnVub+mLAYXPxh7mMy3zSz7GcCVumoBNnsIcJ+/o3eg+USGhDHZ8B1qNAqw7/2QPES9wzUoVaUks8QIjWqBTJqZX14MKiO0V7NzjmMM6FgfbLQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789370154; c=relaxed/simple; bh=Z3gRReMI2MXyvkvdZqOZSuEsRFPZ8FFXXeV2fQqgflA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OrHu1p5Rcp8eMt65PzNZI91ajN7OtLbi38SX27REPOghm74OufiCHWhdrzFZM++Gk0LI+ug6QKi1Q4cswY1LMYXidqRD1N4BB360liNMl6eeWh1knvTQPHL6XVHDjEQO+pVgdqm33aRZ51pNGxoJSrAZE73LgZbEvks10Mr4TeE= 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=jC9+DSwT; arc=none smtp.client-ip=74.125.225.76 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="jC9+DSwT" Received: by mail-wr2-f12.google.com with SMTP id ffacd0b85a97d-482f635552aso1257592f8f.2 for ; Mon, 14 Sep 2026 00:15:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789370149; x=1789974949; 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=YYUlTUTs+mnKKTV+ZLnSvoMgwNBVxWwasV2EoQomx3k=; b=jC9+DSwTScs1Pt0M/Vo3eNqWGBkm67u0C71inKcup/7N1F/tM/UJuyINwWywhMtGhG t6QuRarFNE3OpVRhnzf4jHVbPLscv+8nG26bPVGbMi6ddhMZ0Yyi0GaCGi1h4Z3O7WBe wRQZ7NXjWVBx076xSB5+vv2+C+Oy2zxhRdVT2e1xnN7X0ZP2tN0hrdayPVmZIx7P73/7 qfVjNwkqHnxrjcsBM++kF4NxZAtsbPrQZBLMWYE3iRW5pj19adQA7cv/ygvj45BxUZBM jA9E86ms9t/qbSxEsVwDvcKn7XaL46CMHEpmdECvPftg9Gmj5N338tHuSdIGQ4gLzSCv Qftg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789370149; x=1789974949; 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=YYUlTUTs+mnKKTV+ZLnSvoMgwNBVxWwasV2EoQomx3k=; b=qMDlfacY57IA5V/159LtDkSYGYKBKZypzErKq9WpzvYDVHE5FHc52cvxKCDYbINqLi iCMwSaOKDDt/mwciByTP4m/dN1H1LmdIBhLQ89L7M+4uHLAil5rragRiiOPn3ryRcGBE We7WbyQvzX0S8pbek5FS8m+aLkKli2bmfs0Vtcs/wq8jMtBeWWwX9sXJSp8A7BQPK8bt gq6fe7YG2C8BajVvDMJO+Yh7Bk8Qny9nGE9hFdEEdReHrHCWvFxXKdn7L4g00Fwf1kmG Sa7TgMmueYAOD+1bBO4ka8oCkVOnuBp2kIPcGmAi0fE0taTxLxow7kPuIZWSKF4btbJB 3bXw== X-Forwarded-Encrypted: i=1; AKwUvBx4mZU74fZwECmZdsc0lWRKhRn8Xz7bbtZlc0v9mBOiavMS1qAKjEqKRE1ZMgDW5ysLXBu+I8lrpmRIYK0=@vger.kernel.org X-Gm-Message-State: AFuF++luxmMINHxohX9pZPUFZ9PXc4JzLi0Rn5hivt8FmRG8tApGggdE x1+qJCYy28RZrs7iFo0JTtSvOdDPEOH5SrtjGKhfE5SqwsZXpV4qul1v X-Gm-Gg: AYBFou1AQZqDiu94TWalZg8FZFD9ostPOt9QKkHzFDGsGSCW513GStbN0bD+X/LDtnr 26L221pb4l+Z2uV7338cHWTa/MhYxryvKBy9h5blA9G3nWwaUhCjT1PkutKvdXvt4R4wVMnAIqQ CZOI2sjAWw4Gv73Yckr1CwAfg/3wOAMfafSO5mWgISjkr+ac26mZ1wsXt0X60DKUg+yTT++S8vM FP3f40UzsucVVoix7Sh2X7ATR/J/gVnpP/CVCEKOFS1/ERi/Al5p1hRQiVvLSDWWfZVUNlbgdXo GucgF6SKhpFIzgZ09NxmwkNYSmP27I9FFfPHAdNqOjeibkpehO2jIcZguq/rhUhOR4GUgfpqNlu w6aGvT75NJiSSt8a8NKzElHAD432OXIPL2vxORD7bCDbt3a+dWhTTrUrLFpiBnHH/Y5JomJFAp8 ePBUZsfSWwdqyFBefhqyMzfHZTC82O7kZakKiJeUSbXbCiFESue9SYUUiEoyBITyGQrTAgtqrfG YD67q70Ss4nO278IrsbPTqf6wq5/tVJXLrqO3og X-Received: by 2002:a05:6000:70d:b0:485:aa36:43d with SMTP id ffacd0b85a97d-48702ab777dmr1049814f8f.18.1789370149094; Mon, 14 Sep 2026 00:15:49 -0700 (PDT) Received: from fedora-tap.advaoptical.com ([82.166.23.19]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-486eb34e4b5sm24472558f8f.22.2026.09.14.00.15.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 14 Sep 2026 00:15:47 -0700 (PDT) From: Sagi Maimon To: Richard Cochran , Vadim Fedorenko , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Andrew Lunn , Simon Horman , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Corbet , Randy Dunlap , Shuah Khan , netdev@vger.kernel.org Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, Sagi Maimon Subject: [PATCH net-next v14 4/4] ptp: ocp: add TAP CPLD flashing via devlink Date: Mon, 14 Sep 2026 10:15:31 +0300 Message-ID: <20260914071531.11640-5-maimon.sagi@gmail.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20260914071531.11640-1-maimon.sagi@gmail.com> References: <20260914071531.11640-1-maimon.sagi@gmail.com> 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" The Lattice MachXO3 CPLD on the ADVA TimeCard X1 is programmed over I2C using in-system programming (ISP). Build on the TMC bus arbitration added previously and expose the update path as a devlink flash component, next to the card flash the driver already writes through devlink. The component is named fw.cpld and reported as a running version, so ->info_get() both publishes the USERCODE of the image currently in the part and lets the core accept the name: devlink dev flash pci/0000:02:00.0 file adva-cpld.bin component fw.cpld Flashing acquires the bus, erases the configuration flash, programs the image page by page and activates it with the MachXO3 REFRESH command, reporting progress with the standard devlink notifications. The CPLD is required to come back with DONE set, not busy and no error code before the update is called successful, as drivers/fpga/machxo2-spi.c does. The whole sequence runs under cpld_lock and the i2c adapter lock, so an EEPROM read blocks for as long as programming takes; the alternative is reading the TMC bus instead. Flashing is privileged: DEVLINK_CMD_FLASH_UPDATE carries GENL_ADMIN_PERM. Signed-off-by: Sagi Maimon --- Documentation/ABI/testing/sysfs-timecard | 4 + Documentation/networking/devlink/ptp_ocp.rst | 37 ++- drivers/ptp/ptp_ocp.c | 327 ++++++++++++++++++- 3 files changed, 361 insertions(+), 7 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-timecard b/Documentation/ABI/t= esting/sysfs-timecard index c440d8930384..6ddca452bc63 100644 --- a/Documentation/ABI/testing/sysfs-timecard +++ b/Documentation/ABI/testing/sysfs-timecard @@ -35,6 +35,10 @@ Description: (RO, root only) The flags set in the status= register of the probe, and reported from that cached value as the fixed "cpld.id" version by devlink dev info. =20 + New CPLD firmware is programmed with devlink dev flash, + selecting the "fw.cpld" component; see + Documentation/networking/devlink/ptp_ocp.rst. + What: /sys/class/timecard/ocpN/available_clock_sources Date: September 2021 Contact: Jonathan Lemon diff --git a/Documentation/networking/devlink/ptp_ocp.rst b/Documentation/n= etworking/devlink/ptp_ocp.rst index 3f997cd9f3a3..37e9f1ebd1a8 100644 --- a/Documentation/networking/devlink/ptp_ocp.rst +++ b/Documentation/networking/devlink/ptp_ocp.rst @@ -5,7 +5,7 @@ ptp_ocp devlink support =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 This document describes the devlink features implemented by the ``ptp_ocp`` -device driver. +device driver: the info versions it reports and its flash update support. =20 Info versions =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D @@ -33,3 +33,38 @@ The ``ptp_ocp`` driver reports the following versions reprograms the on-card mux, so the driver does that once from its own worker and reports the cached value here; the version is omitted until that read has succeeded. + * - ``fw.cpld`` + - running + - USERCODE of the image programmed into the TAP CPLD, formatted as + ``0x%08x``. Read together with ``cpld.id`` and reported the same + way. This is the component name to pass to ``devlink dev flash`` + to update the CPLD. + +Flash update +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The driver implements ``devlink dev flash`` for two separate targets, +selected with the component name. + +.. list-table:: Flash components + :widths: 20 80 + + * - Component + - Description + * - (none) + - The card's own flash, written through the SPI controller the driver + exposes. The card runs the new image after its next reset. + * - ``fw.cpld`` + - The configuration flash of the TAP CPLD on ADVA TimeCard X1 boards, + programmed over I2C with the MachXO3 in-system programming commands + and activated with a REFRESH, so the new image runs immediately. + The image is the raw configuration bitstream, a whole number of + 16-byte pages; container formats such as ``.jed`` are rejected. + +Programming the CPLD claims the shared I2C bus for the whole cycle, so +reads of the card's EEPROM block until it completes. Progress is reported +with the standard devlink status notifications. + +Example:: + + $ devlink dev flash pci/0000:02:00.0 file adva-cpld.bin component fw.c= pld diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 98cb26077181..58b90b2b282f 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -434,12 +434,16 @@ struct ptp_ocp { u8 *cpld_buf; /* Lattice device ID; 0 if unread */ u32 cpld_id; + /* USERCODE of the programmed image; valid once cpld_id is set */ + u32 cpld_usercode; /* the one-shot ID read is finished, successfully or not */ bool cpld_id_tried; /* failed ID read attempts so far */ unsigned int cpld_id_attempts; /* x1 TAP CPLD present */ bool has_cpld; + /* EN_CFG_TP issued but not yet REFRESH'd */ + bool cpld_in_config_mode; }; =20 #define OCP_REQ_TIMESTAMP BIT(0) @@ -476,6 +480,13 @@ static int ptp_ocp_adva_board_init(struct ptp_ocp *bp,= struct ocp_resource *r); static const struct ocp_sma_op ocp_adva_sma_op; static const struct ocp_sma_op ocp_adva_x1_sma_op; =20 +/* Flash component naming the CPLD image, as reported by ->info_get(). */ +#define ADVA_CPLD_COMPONENT "fw.cpld" + +static int adva_x1_cpld_flash(struct ptp_ocp *bp, struct devlink *devlink, + const struct firmware *fw, + struct netlink_ext_ack *extack); + static const struct ocp_attr_group fb_timecard_groups[]; =20 static const struct ocp_attr_group art_timecard_groups[]; @@ -2158,6 +2169,17 @@ ptp_ocp_devlink_flash_update(struct devlink *devlink, const char *msg; int err; =20 + if (params->component) { + if (!bp->has_cpld || + strcmp(params->component, ADVA_CPLD_COMPONENT)) { + NL_SET_ERR_MSG_MOD(extack, + "unsupported flash component"); + return -EOPNOTSUPP; + } + + return adva_x1_cpld_flash(bp, devlink, params->fw, extack); + } + dev =3D ptp_ocp_find_flash(bp); if (!dev) { dev_err(&bp->pdev->dev, "Can't find Flash SPI adapter\n"); @@ -2180,6 +2202,8 @@ static int ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req = *req, struct netlink_ext_ack *extack) { + enum devlink_info_version_type ver_type =3D + DEVLINK_INFO_VERSION_TYPE_COMPONENT; struct ptp_ocp *bp =3D devlink_priv(devlink); const char *fw_image; char buf[32]; @@ -2202,6 +2226,16 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, st= ruct devlink_info_req *req, err =3D devlink_info_version_fixed_put(req, "cpld.id", buf); if (err) return err; + + /* The flashable component: naming it here is what lets + * "devlink dev flash ... component fw.cpld" through, as the + * core matches the name against the versions reported here. + */ + sprintf(buf, "0x%08x", READ_ONCE(bp->cpld_usercode)); + err =3D devlink_info_version_running_put_ext(req, "fw.cpld", buf, + ver_type); + if (err) + return err; } =20 if (!bp->has_eeprom_data) { @@ -4296,13 +4330,25 @@ static const struct ocp_attr_group art_timecard_gro= ups[] =3D { =20 /* Lattice LCMXO3LF ISC command codes */ #define CPLD_CMD_READ_ID 0xE0000000UL +#define CPLD_CMD_READ_USERCODE 0xC0000000UL #define CPLD_CMD_READ_STATUS 0x3C000000UL +#define CPLD_CMD_EN_CFG_TP 0x74 /* enable config, transparent mode */ +#define CPLD_CMD_DIS_CFG 0x26 +#define CPLD_CMD_ERASE 0x0E +#define CPLD_CMD_RESET_ADDR 0x46 +#define CPLD_CMD_WRITE_PAGE 0x70 +#define CPLD_CMD_SET_DONE 0x5E +#define CPLD_CMD_REFRESH 0x79 +#define CPLD_PAGE_SIZE 16 +#define CPLD_POLL_US 10000 /* status poll interval while busy */ +#define CPLD_ERASE_MS 15000 /* config sector erase, datasheet max= */ #define CPLD_ID_MAX_ATTEMPTS 10 /* one per sync_work tick */ =20 /* Status register bit positions (Lattice LCMXO3LF datasheet) */ #define CPLD_STATUS_DONE BIT(8) #define CPLD_STATUS_BUSY BIT(12) #define CPLD_STATUS_FAILED BIT(13) +#define CPLD_STATUS_ERR GENMASK(25, 23) =20 /* * Issue one I2C transaction on the TMC bus: @cmd if not negative, then @@ -4522,6 +4568,48 @@ static int adva_x1_mux_select(struct ptp_ocp *bp, in= t ch) return adva_x1_i2c_xfer(bp, ADVA_MUX_ADDR, val, NULL, 0, NULL, 0); } =20 +/* + * Argument bytes that follow an ISC opcode. Returns NULL with @nargs set + * when the arguments are all zero: adva_x1_i2c_xfer() zeroes the buffer. + */ +static const u8 *adva_x1_cpld_args(u8 cmd, u8 *nargs) +{ + static const u8 en_cfg_tp[] =3D { 0x08, 0x00 }; + /* cfg sector only */ + static const u8 erase_cfg[] =3D { 0x04, 0x00, 0x00 }; + + switch (cmd) { + case CPLD_CMD_EN_CFG_TP: + *nargs =3D sizeof(en_cfg_tp); + return en_cfg_tp; + case CPLD_CMD_ERASE: + *nargs =3D sizeof(erase_cfg); + return erase_cfg; + case CPLD_CMD_RESET_ADDR: + case CPLD_CMD_SET_DONE: + *nargs =3D 3; + return NULL; + case CPLD_CMD_DIS_CFG: + case CPLD_CMD_REFRESH: + *nargs =3D 2; + return NULL; + default: + *nargs =3D 0; + return NULL; + } +} + +/* Send an ISC command with the fixed arguments that belong to it. */ +static int adva_x1_cpld_write(struct ptp_ocp *bp, u8 cmd) +{ + const u8 *args; + u8 nargs; + + args =3D adva_x1_cpld_args(cmd, &nargs); + + return adva_x1_i2c_xfer(bp, ADVA_CPLD_ADDR, cmd, args, nargs, NULL, 0); +} + /* * Send a 4-byte command and read back without an intermediate STOP: two * messages in one transfer is the Lattice write -> repeated START -> read, @@ -4547,14 +4635,82 @@ static int adva_x1_cpld_read_status(struct ptp_ocp = *bp, u32 *status) return 0; } =20 +/* Poll the status register until the CPLD goes idle, or @max_ms elapses. + * The deadline is on wall time, so the I2C transactions count against it, + * and the status is read once more after it expires before giving up. + */ +static int adva_x1_cpld_wait_ready(struct ptp_ocp *bp, unsigned int max_ms) +{ + u32 status =3D 0; + int err =3D 0, ret; + + ret =3D read_poll_timeout(adva_x1_cpld_read_status, err, + err || (status & CPLD_STATUS_FAILED) || + !(status & CPLD_STATUS_BUSY), + CPLD_POLL_US, max_ms * USEC_PER_MSEC, false, + bp, &status); + if (ret) + return ret; + /* Keep the transport errno so it reaches userspace as-is; -EIO is + * reserved for the CPLD itself reporting FAILED. + */ + if (err) + return err; + if (status & CPLD_STATUS_FAILED) + return -EIO; + + return 0; +} + +/* Wait for BUSY to clear, ignoring a pending cancel. An ISC command issu= ed + * while the part is still erasing or programming may not latch, so an abo= rt + * has to wait the operation out before it can leave configuration mode. + */ +static int adva_x1_cpld_wait_idle(struct ptp_ocp *bp, unsigned int max_ms) +{ + u32 status =3D 0; + int err =3D 0, ret; + + ret =3D read_poll_timeout(adva_x1_cpld_read_status, err, + err || !(status & CPLD_STATUS_BUSY), + CPLD_POLL_US, max_ms * USEC_PER_MSEC, false, + bp, &status); + if (ret) + return ret; + + return err; +} + +/* Leave transparent configuration mode. cpld_in_config_mode stays set wh= en + * DIS_CFG does not get through, so a later cleanup() tries again rather t= han + * recording an exit that never happened. + */ +static void adva_x1_cpld_exit_config(struct ptp_ocp *bp) +{ + int err; + + if (!bp->cpld_in_config_mode) + return; + + adva_x1_cpld_wait_idle(bp, CPLD_ERASE_MS); + + err =3D adva_x1_cpld_write(bp, CPLD_CMD_DIS_CFG); + if (err) + dev_warn(&bp->pdev->dev, + "CPLD left in configuration mode: %d\n", err); + else + bp->cpld_in_config_mode =3D false; +} + /* - * Read the Lattice device ID into bp->cpld_id. Done once, off the - * unprivileged devlink path, which reports the cached value. -EBUSY means - * cpld_lock is held, so the caller can retry rather than wait behind a lo= ng - * CPLD operation. + * Read the Lattice device ID and the image USERCODE under one bus claim. + * Done once, off the unprivileged devlink path, which reports the cached + * values. -EBUSY means cpld_lock is held, so the caller can retry rather + * than wait behind a long CPLD operation. */ static int adva_x1_cpld_read_id(struct ptp_ocp *bp) { + u32 id, usercode; u8 data[4]; int ret; =20 @@ -4568,8 +4724,19 @@ static int adva_x1_cpld_read_id(struct ptp_ocp *bp) if (ret) goto release; ret =3D adva_x1_cpld_cmd_read(bp, CPLD_CMD_READ_ID, data, 4); - if (!ret) - WRITE_ONCE(bp->cpld_id, get_unaligned_be32(data)); + if (ret) + goto deselect; + id =3D get_unaligned_be32(data); + + ret =3D adva_x1_cpld_cmd_read(bp, CPLD_CMD_READ_USERCODE, data, 4); + if (ret) + goto deselect; + usercode =3D get_unaligned_be32(data); + + /* cpld_id is the validity flag for both, so publish it last. */ + WRITE_ONCE(bp->cpld_usercode, usercode); + WRITE_ONCE(bp->cpld_id, id); +deselect: adva_x1_mux_select(bp, -1); release: adva_x1_bus_release(bp); @@ -4627,6 +4794,154 @@ cpld_status_show(struct device *dev, struct device_= attribute *attr, } static DEVICE_ATTR_ADMIN_RO(cpld_status); =20 +/* + * Program the CPLD configuration flash from @fw and activate it. Selected + * by the "fw.cpld" flash component; the SPI flash keeps the default path. + * Runs under cpld_lock with the i2c adapter lock held, so an EEPROM read + * blocks for as long as programming takes. + */ +static int adva_x1_cpld_flash(struct ptp_ocp *bp, struct devlink *devlink, + const struct firmware *fw, + struct netlink_ext_ack *extack) +{ + size_t offset; + int err; + u32 st; + + if (!fw->size || fw->size % CPLD_PAGE_SIZE) { + NL_SET_ERR_MSG_MOD(extack, + "image must be a whole number of 16-byte pages"); + return -EINVAL; + } + + mutex_lock(&bp->cpld_lock); + + err =3D adva_x1_bus_claim(bp); + if (err) + goto unlock; + err =3D adva_x1_mux_select(bp, ADVA_MUX_CHANNEL); + if (err) + goto release; + + /* Set before EN_CFG_TP, not after: the CPLD may have entered config + * mode even if the write errors or the wait times out, and only this + * makes the exit path send DIS_CFG. A stray DIS_CFG is harmless; + * leaving config mode enabled is not. + */ + bp->cpld_in_config_mode =3D true; + + err =3D adva_x1_cpld_write(bp, CPLD_CMD_EN_CFG_TP); + if (!err) + err =3D adva_x1_cpld_wait_ready(bp, 5000); + if (err) + goto exit_config; + + devlink_flash_update_status_notify(devlink, "Erasing", + ADVA_CPLD_COMPONENT, 0, 0); + err =3D adva_x1_cpld_write(bp, CPLD_CMD_ERASE); + if (!err) + err =3D adva_x1_cpld_wait_ready(bp, CPLD_ERASE_MS); + if (err) + goto exit_config; + + err =3D adva_x1_cpld_write(bp, CPLD_CMD_RESET_ADDR); + if (err) + goto exit_config; + + for (offset =3D 0; offset < fw->size; offset +=3D CPLD_PAGE_SIZE) { + u8 args[3 + CPLD_PAGE_SIZE] =3D { 0x00, 0x00, 0x01 }; + + memcpy(&args[3], fw->data + offset, CPLD_PAGE_SIZE); + err =3D adva_x1_i2c_xfer(bp, ADVA_CPLD_ADDR, CPLD_CMD_WRITE_PAGE, + args, sizeof(args), NULL, 0); + if (!err) + err =3D adva_x1_cpld_wait_ready(bp, 100); + if (err) + goto exit_config; + + if (!(offset % (CPLD_PAGE_SIZE * 64))) + devlink_flash_update_status_notify(devlink, + "Programming", + ADVA_CPLD_COMPONENT, + offset, fw->size); + } + devlink_flash_update_status_notify(devlink, "Programming", + ADVA_CPLD_COMPONENT, + fw->size, fw->size); + + err =3D adva_x1_cpld_write(bp, CPLD_CMD_SET_DONE); + if (!err) + err =3D adva_x1_cpld_wait_ready(bp, 1000); + if (err) + goto exit_config; + + err =3D adva_x1_cpld_read_status(bp, &st); + if (err) + goto exit_config; + if (!(st & CPLD_STATUS_DONE)) { + err =3D -EIO; + goto exit_config; + } + + devlink_flash_update_status_notify(devlink, "Activating", + ADVA_CPLD_COMPONENT, 0, 0); + err =3D adva_x1_cpld_write(bp, CPLD_CMD_REFRESH); + if (err) + goto exit_config; + + /* REFRESH reboots the CPLD out of configuration mode, so the exit + * path must not send DIS_CFG afterwards even if a check below fails. + */ + bp->cpld_in_config_mode =3D false; + + /* The new image is already running, so a segment that is not back + * yet must not be reported as a failed update: retry the reselect + * rather than sampling the mux once at a fixed delay. + */ + msleep(1500); + err =3D read_poll_timeout(adva_x1_mux_select, err, !err, CPLD_POLL_US, + 3000 * USEC_PER_MSEC, false, + bp, ADVA_MUX_CHANNEL); + if (err) + goto deselect; + + err =3D adva_x1_cpld_wait_ready(bp, 3000); + if (err) + goto deselect; + + /* Require DONE set, not busy and no error code, as machxo2-spi.c does + * after a refresh: without it a CRC or preamble error reads back as a + * successful update. + */ + err =3D adva_x1_cpld_read_status(bp, &st); + if (err) + goto deselect; + if (!(st & CPLD_STATUS_DONE) || (st & CPLD_STATUS_BUSY) || + (st & CPLD_STATUS_ERR)) { + dev_err(&bp->pdev->dev, + "CPLD refresh left status 0x%08x\n", st); + NL_SET_ERR_MSG_MOD(extack, "CPLD did not come back configured"); + err =3D -EIO; + goto deselect; + } + + /* The image on the part changed, so the cached USERCODE has too. */ + WRITE_ONCE(bp->cpld_id, 0); + WRITE_ONCE(bp->cpld_id_tried, false); + bp->cpld_id_attempts =3D 0; + +exit_config: + adva_x1_cpld_exit_config(bp); +deselect: + adva_x1_mux_select(bp, -1); +release: + adva_x1_bus_release(bp); +unlock: + mutex_unlock(&bp->cpld_lock); + + return err; +} + static struct attribute *adva_timecard_attrs[] =3D { &dev_attr_serialnum.attr, &dev_attr_gnss_sync.attr, --=20 2.47.0