* [PATCH 01/11] ASoC: fsl_asrc: Use guard() for spin locks
From: phucduc.bui @ 2026-06-12 13:26 UTC (permalink / raw)
To: Mark Brown
Cc: Liam Girdwood, Jaroslav Kysela, Takashi Iwai, Shengjiu Wang,
Xiubo Li, Frank Li, Fabio Estevam, Nicolin Chen, Sascha Hauer,
Pengutronix Kernel Team, linux-sound, linux-kernel,
linux-arm-kernel, imx, linuxppc-dev, bui duc phuc
In-Reply-To: <20260612132639.78086-1-phucduc.bui@gmail.com>
From: bui duc phuc <phucduc.bui@gmail.com>
Clean up the code using guard() for spin locks.
Merely code refactoring, and no behavior change.
Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>
---
sound/soc/fsl/fsl_asrc.c | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c
index 5fda9b647c70..0b28bcfa47fe 100644
--- a/sound/soc/fsl/fsl_asrc.c
+++ b/sound/soc/fsl/fsl_asrc.c
@@ -222,10 +222,9 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
enum asrc_pair_index index = ASRC_INVALID_PAIR;
struct fsl_asrc *asrc = pair->asrc;
struct device *dev = &asrc->pdev->dev;
- unsigned long lock_flags;
int i, ret = 0;
- spin_lock_irqsave(&asrc->lock, lock_flags);
+ guard(spinlock_irqsave)(&asrc->lock);
for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) {
if (asrc->pair[i] != NULL)
@@ -250,8 +249,6 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
pair->index = index;
}
- spin_unlock_irqrestore(&asrc->lock, lock_flags);
-
return ret;
}
@@ -265,19 +262,16 @@ static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair)
{
struct fsl_asrc *asrc = pair->asrc;
enum asrc_pair_index index = pair->index;
- unsigned long lock_flags;
/* Make sure the pair is disabled */
regmap_update_bits(asrc->regmap, REG_ASRCTR,
ASRCTR_ASRCEi_MASK(index), 0);
- spin_lock_irqsave(&asrc->lock, lock_flags);
+ guard(spinlock_irqsave)(&asrc->lock);
asrc->channel_avail += pair->channels;
asrc->pair[index] = NULL;
pair->error = 0;
-
- spin_unlock_irqrestore(&asrc->lock, lock_flags);
}
/**
--
2.43.0
^ permalink raw reply related
* [PATCH 00/11] ASoC: fsl: Use guard() for mutex & spin locks
From: phucduc.bui @ 2026-06-12 13:26 UTC (permalink / raw)
To: Mark Brown
Cc: Liam Girdwood, Jaroslav Kysela, Takashi Iwai, Shengjiu Wang,
Xiubo Li, Frank Li, Fabio Estevam, Nicolin Chen, Sascha Hauer,
Pengutronix Kernel Team, linux-sound, linux-kernel,
linux-arm-kernel, imx, linuxppc-dev, bui duc phuc
From: bui duc phuc <phucduc.bui@gmail.com>
Hi all,
This series converts mutex & spinlock handling in the FSL sound drivers
to use guard() helpers.
The changes are code cleanup only and should have no functional impact.
I have compile-tested all affected files except `mpc5200_dma` and
`mpc5200_psc_ac97`, as I have not yet found the correct configuration
needed to enable those drivers.
Best regards,
Phuc
bui duc phuc (11):
ASoC: fsl_asrc: Use guard() for spin locks
ASoC: fsl_audmix: Use guard() for spin locks
ASoC: fsl_easrc: Use guard() for spin locks
ASoC: fsl_esai: Use guard() for spin locks
ASoC: fsl_spdif: Use guard() for spin locks
ASoC: fsl_ssi: Use guard() for mutex locks
ASoC: fsl_xcvr: Use guard() for spin locks
ASoC: imx-audio-rpmsg: Use guard() for spin locks
ASoC: fsl_rpmsg: Use guard() for mutex & spin locks
ASoC: fsl: mpc5200_dma: Use guard() for spin locks
ASoC: fsl: mpc5200_psc_ac97: Use guard() for mutex locks
sound/soc/fsl/fsl_asrc.c | 10 +----
sound/soc/fsl/fsl_audmix.c | 11 ++---
sound/soc/fsl/fsl_easrc.c | 36 +++++------------
sound/soc/fsl/fsl_esai.c | 16 +++-----
sound/soc/fsl/fsl_spdif.c | 8 +---
sound/soc/fsl/fsl_ssi.c | 13 ++----
sound/soc/fsl/fsl_xcvr.c | 29 ++++++--------
sound/soc/fsl/imx-audio-rpmsg.c | 25 ++++++------
sound/soc/fsl/imx-pcm-rpmsg.c | 69 ++++++++++++++------------------
sound/soc/fsl/mpc5200_dma.c | 56 +++++++++++++-------------
sound/soc/fsl/mpc5200_psc_ac97.c | 34 ++++++----------
11 files changed, 121 insertions(+), 186 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH v2 2/4] ASoC: meson: aiu-encoder-i2s: prepare for multiple streams
From: Mark Brown @ 2026-06-12 13:19 UTC (permalink / raw)
To: Jerome Brunet
Cc: Valerio Setti, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Neil Armstrong, Kevin Hilman, Martin Blumenstingl, linux-kernel,
linux-sound, linux-arm-kernel, linux-amlogic
In-Reply-To: <1jik7pebk7.fsf@starbuckisacylon.baylibre.com>
[-- Attachment #1: Type: text/plain, Size: 669 bytes --]
On Thu, Jun 11, 2026 at 10:16:56AM +0200, Jerome Brunet wrote:
> On mer. 10 juin 2026 at 23:29, Valerio Setti <vsetti@baylibre.com> wrote:
>
> > aiu-encoder-i2s is going to be the interface that handles both playback
> > and capture, so this commit does all the required changes to prepare
> > for that since so far it only handled playback:
> > - probe/remove functions are added to allocate/free per stream data,
> > respectively.
Please delete unneeded context from mails when replying. Doing this
makes it much easier to find your reply in the message, helping ensure
it won't be missed by people scrolling through the irrelevant quoted
material.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH] net: macb: add TX stall timeout callback to recover from lost TSTART write
From: Andrea della Porta @ 2026-06-12 12:40 UTC (permalink / raw)
To: Théo Lebrun
Cc: Andrea della Porta, netdev, Nicolas Ferre, Claudiu Beznea,
Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-kernel, linux-arm-kernel, linux-rpi-kernel,
Lukasz Raczylo, Steffen Jaeckel
In-Reply-To: <DJ6Z39C906LE.1F3XKDDM41UJ8@bootlin.com>
Hi Theo,
On 11:45 Fri 12 Jun , Théo Lebrun wrote:
> Hello Andrea,
>
> On Fri Jun 12, 2026 at 11:01 AM CEST, Andrea della Porta wrote:
> > From: Lukasz Raczylo <lukasz@raczylo.com>
> >
> > The MACB found in the Raspberry Pi RP1 suffers from sporadic stalls on
> > the TX queue.
> > While the exact root cause is not yet fully understood, it is likely
> > related to a hardware issue where a TSTART write to the NCR register
> > is missed, preventing the transmission from being kicked off.
> >
> > Implement a timeout callback to handle TX queue stalls, triggering the
> > existing restart mechanism to recover.
> >
> > Link: https://lore.kernel.org/all/20260514215459.36109-1-lukasz@raczylo.com/
> > Fixes: dc110d1b23564 ("net: cadence: macb: Add support for Raspberry Pi RP1 ethernet controller")
> > Signed-off-by: Lukasz Raczylo <lukasz@raczylo.com>
> > Co-developed-by: Steffen Jaeckel <sjaeckel@suse.de>
> > Signed-off-by: Steffen Jaeckel <sjaeckel@suse.de>
> > Co-developed-by: Andrea della Porta <andrea.porta@suse.com>
> > Signed-off-by: Andrea della Porta <andrea.porta@suse.com>
> > ---
> > drivers/net/ethernet/cadence/macb_main.c | 11 +++++++++++
> > 1 file changed, 11 insertions(+)
> >
> > diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
> > index a12aa21244e83..615da65d5d68d 100644
> > --- a/drivers/net/ethernet/cadence/macb_main.c
> > +++ b/drivers/net/ethernet/cadence/macb_main.c
> > @@ -4522,6 +4522,16 @@ static int macb_setup_tc(struct net_device *dev, enum tc_setup_type type,
> > }
> > }
> >
> > +static void macb_tx_timeout(struct net_device *dev, unsigned int q)
> > +{
> > + struct macb *bp = netdev_priv(dev);
> > +
> > + if (net_ratelimit())
> > + netdev_err(dev, "TX stall detected, re-kicking TSTART\n");
>
> Is this standard? It looks odd.
I've found it used in other drivers, it's the closest I had found that
limit the rate for net related output. As Nicolai suggested, on timeout
a message is already printed by the core, so I will drop those two lines.
>
> > + dev->stats.tx_errors++;
>
> I am surprised by this. `tx_errors` would ideally be one per packet that
> didn't get sent. Here we increment it once per queue that stalled.
>
> I have a series to address stats issue (and use netdev_stat_ops API).
> It is a follow-up to this:
> https://lore.kernel.org/netdev/20260428-macb-drop-tx-v2-0-647f5199d8df@bootlin.com/
>
> Also this is per-device shared data and we access it without
> synchronisation.
>
> Let's drop this increment.
Agreed.
Thanks,
Andrea
>
> > + macb_tx_restart(&bp->queues[q]);
> > +}
>
> Regards,
>
> --
> Théo Lebrun, Bootlin
> Embedded Linux and Kernel engineering
> https://bootlin.com
>
^ permalink raw reply
* Re: [PATCH 04/37] drm/display: bridge-connector: store the drm_device pointer
From: Luca Ceresoli @ 2026-06-12 13:12 UTC (permalink / raw)
To: Maxime Ripard, Luca Ceresoli
Cc: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Inki Dae, Jagan Teki,
Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
linux-arm-kernel
In-Reply-To: <20260608-imposing-umber-tuatara-62daba@houat>
On Mon Jun 8, 2026 at 1:34 PM CEST, Maxime Ripard wrote:
> Hi,
>
> On Tue, May 19, 2026 at 12:37:21PM +0200, Luca Ceresoli wrote:
>> Currently the struct drm_device pointer is only needed during the initial
>> drm_bridge_connector_init() and in drm_bridge_connector_handle_hpd() which
>> gets it from the struct drm_connector.
>>
>> This will be insufficient when introducing bridge hotplugging, because:
>>
>> * some of the actions in drm_bridge_connector_init() will have to be
>> performed later on, when a bridge is hot(un)plugged
>> * the connector will be removed and re-added based on hotplug events,
>> so the drm_connector might just not exist or its content be cleared
>>
>> Store the drm_device pointer in struct drm_bridge_connector for any later
>> needs. Also convert drm_bridge_connector_handle_hpd() to use the newly
>> stored value.
>>
>> Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
>
> This is already accessible as drm_connector->dev and drm_bridge->dev. I
> think it would be great to list why this is different, and how it is
> different (ie, when it's set, unset).
With hotplug the drm_connector will be created dynamically, so it cannot be
used.
The drm_bridge->dev maybe.
But we have a pointer to the encoder, which is supposed to be always
present, so I'll try using next for v2.
Luca
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [Question] Enabling CoreSight TRBE in firmware on CIX Orion O6
From: Yunseong Kim @ 2026-06-12 13:05 UTC (permalink / raw)
To: Leo Yan, Gary Yang
Cc: Peter Chen, Fugang Duan, Guomin Chen, Hans Zhang, Joakim Zhang,
Jerry Zhu, CIX Linux Kernel Upstream Group, devicetree,
linux-arm-kernel, linux-kernel@vger.kernel.org, Yunseong Kim,
Yunseong Kim
In-Reply-To: <20260610113435.GV101133@e132581.arm.com>
Hi,
> On Wed, Jun 10, 2026 at 03:42:25PM +0800, Gary Yang wrote:
>
> [...]
>
>>> (2) Or expose the full CoreSight topology in ACPI:
>>> - Add ARMHC97C (TMC-ETR) device with MMIO base address
>>> - Add ARMHC502 (funnel) devices if applicable
>>> - Reference: ARM DEN0067 (CoreSight Architecture ACPI bindings)
>
> The CPUs on O6 support ETE + TRBE, you don't need to use ETR or funnel
> modules.
>
>> The firmware (TF-A) for the Radxa O6 is provided and maintained by Radxa. We
>> will forward your request to the Radxa firmware team and ask them to evaluate
>> enabling TRBE access from non-secure EL1/EL2 (i.e. setting MDCR_EL3.NSTBE = 1
>> in TF-A), as you suggested.
>
> The issue is caused by ACPI: the APIC table does not contain a TRBE
> interrupt, and the SSDT is missing ETE nodes (ETE node should be
> present for each CPU):
>
> Device (CPU0)
> {
> ...
>
> Device ( ETE0 ) {
> Name (_UID, Zero)
> Name (_HID , "ARMHC500")
> }
> }
>
> Thanks,
> Leo
Thanks, Gary and Leo.
I'm looking forward to adding arm64 CoreSight usage on CIX Orion O6 board.
If the board supports CoreSight, I'll make sure to write on the perf tools Wiki:
https://perf.wiki.kernel.org/index.php/Main_Page
Best regards,
Yunseong
^ permalink raw reply
* Re: [PATCH] net: macb: add TX stall timeout callback to recover from lost TSTART write
From: Andrea della Porta @ 2026-06-12 13:03 UTC (permalink / raw)
To: Nicolai Buchwitz
Cc: Andrea della Porta, netdev, Theo Lebrun, Nicolas Ferre,
Claudiu Beznea, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-kernel, linux-arm-kernel,
linux-rpi-kernel, Lukasz Raczylo, Steffen Jaeckel
In-Reply-To: <85507fd0fb42fca280aca1ee02178ca9@tipi-net.de>
Hi Nicolai,
On 14:53 Fri 12 Jun , Nicolai Buchwitz wrote:
> Hi Andrea
>
> On 12.6.2026 14:51, Andrea della Porta wrote:
>
> > [...]
>
> > >
> > > The commit message describes it as RP1 specific, but it gets applied
> > > to all
> > > other variants?
> >
> > I've seen this issue happening only on RaspberryPi 5, but AFAIK it
> > could affect also other MACB blocks connected through PCIe, so it
> > may be widespread (even though it should have probably already been
> > noticed in the past). In the orginal driver there's no timeout callback
> > defined and this is much like pretgending the issue causing the timeout
> > to happen to go away without doing anything (whatever the cause ot the
> > specific hw are). So in my opinion we can just extend that to all MACB.
> > Or maybe we should execute the restart conditionally on
> > .compatible = "raspberrypi,rp1-gem"?
>
> I just observed the issue once, but other people reported it to be happen
> more
> frequently. If we can narrow down a reproducer, it would be good to test on
> other
> blocks too (like EyeQ at Théo's).|
>
> So maybe you can imagine a good repro for this issue?
Sure, it's happening quite often during bulk dataflow, at least
on my RPi5.
It can be reproduced with the following, issued from the DUT:
iperf -c <SERVER_IP> -P 10 -t 3000 -w 4M -i 1
plus, of course, the related command on server side: iperf -s.
It usually happens a couple of times withing a few hours.
Regards,
Andrea
>
> Thanks,
> Nicolai
^ permalink raw reply
* Re: [PATCH 06/37] drm/display: bridge-connector: use a drm_bridge_connector internally, not a drm_connector
From: Luca Ceresoli @ 2026-06-12 12:57 UTC (permalink / raw)
To: Maxime Ripard, Luca Ceresoli
Cc: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Inki Dae, Jagan Teki,
Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
linux-arm-kernel
In-Reply-To: <20260608-little-lyrebird-of-competition-c25f7d@houat>
On Mon Jun 8, 2026 at 1:41 PM CEST, Maxime Ripard wrote:
> On Tue, May 19, 2026 at 12:37:23PM +0200, Luca Ceresoli wrote:
>> Currently drm_bridge_connector_init() always returns the added connector or
>> errors out. When adding bridge hotplug the bridge-connector can be
>> successfully initialized without creating a connector, which can be added
>> later when the pipeline will be complete.
>>
>> For this the internal function drm_bridge_connector_add_connector() must be
>> able to return a valid drm_bridge_connector even without any drm_connector.
>>
>> In preparation to support bridge hotplug, change its return value to be the
>> same drm_bridge_connector pointer it gets as input, or a PTR_ERR.
>>
>> No functional changes, just changing an internal API.
>>
>> Note the return value could now become an int (0 or negative error) because
>> returning the same value received as input does not carry any added
>> value. However this would be change a lot of lines, so leave such change as
>> a future cleanup.
>
> You just created that function and changed "a lot of lines" already, so
> I'm not sure that argument holds.
Do you refer to the previous patch?
My comment is more about the following patches. It means I separated
changes moving code to a subfunction from changes to the the return value
in separate patches, so that each patch is trivial to review for
correctness.
Makes sense?
Luca
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH 05/37] drm/display: bridge-connector: split code creating the connector to a subfunction
From: Luca Ceresoli @ 2026-06-12 12:56 UTC (permalink / raw)
To: Maxime Ripard, Luca Ceresoli
Cc: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Inki Dae, Jagan Teki,
Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
linux-arm-kernel
In-Reply-To: <20260608-mindful-heavy-frigatebird-be9faf@houat>
On Mon Jun 8, 2026 at 1:40 PM CEST, Maxime Ripard wrote:
> On Tue, May 19, 2026 at 12:37:22PM +0200, Luca Ceresoli wrote:
>> In preparation to introduce bridge hotplug, split out from
>> drm_bridge_connector_init() the code adding the drm_connector into a
>> dedicated function. This will be needed to be able to add (and re-add) the
>> connector from different code paths.
>
> Same story here, explaining what you need later on that calls for that
> change would be nice.
Here's a more verbose version:
Currently drm_bridge_connector_init() does two things:
* allocate and initialize the drm_bridge_connector
(which embeds a drm_connector)
* initialize and register the embedded drm_connector
For bridge hotplug we need to separate these two actions:
* the drm_connector needs to be added and removed at any time based on
hotplug events
* the drm_bridge_connector is designated to create and remove the
drm_connector, so it must be persistent for the card lifetime
As the lifetimes of drm_bridge_connector and drm_connector become
different, we need to create them in different moments.
In preparation to support that, split out from
drm_bridge_connector_init() the code adding the drm_connector into a
dedicated function. No functional changes, just moving code around for
now. A future commit will make the drm_connector be created based on
hotplug events.
Looks good?
Luca
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* [PATCH v2 4/5] ARM: dts: stm32: reorder cs_cti_trace node in stm32mp157c-ev1.dts
From: Amelie Delaunay @ 2026-06-12 12:56 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Maxime Coquelin,
Alexandre Torgue
Cc: devicetree, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
In-Reply-To: <20260612-node_reordering-v2-0-f68032ca3088@foss.st.com>
In the ST board DTS files, the &label entries must be ordered
alphanumerically.
The nodes became misordered when Coresight support was added.
Move cs_cti_trace to the right place to avoid future misordering.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
arch/arm/boot/dts/st/stm32mp157c-ev1.dts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boot/dts/st/stm32mp157c-ev1.dts b/arch/arm/boot/dts/st/stm32mp157c-ev1.dts
index 0e65a1862eb5..eaab09e1755f 100644
--- a/arch/arm/boot/dts/st/stm32mp157c-ev1.dts
+++ b/arch/arm/boot/dts/st/stm32mp157c-ev1.dts
@@ -81,15 +81,15 @@ &cec {
status = "okay";
};
-&cs_cti_trace {
+&cs_cti_cpu0 {
status = "okay";
};
-&cs_cti_cpu0 {
+&cs_cti_cpu1 {
status = "okay";
};
-&cs_cti_cpu1 {
+&cs_cti_trace {
status = "okay";
};
--
2.43.0
^ permalink raw reply related
* [PATCH v2 5/5] ARM: dts: stm32: reorder mdma1 node in stm32mp15*-scmi.dts
From: Amelie Delaunay @ 2026-06-12 12:56 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Maxime Coquelin,
Alexandre Torgue
Cc: devicetree, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
In-Reply-To: <20260612-node_reordering-v2-0-f68032ca3088@foss.st.com>
In the ST board DTS files, the &label entries must be ordered
alphanumerically.
The nodes became misordered when mlahb was replaced by m4_rproc.
Move mdma1 to the right place to avoid future misordering.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
arch/arm/boot/dts/st/stm32mp157a-dk1-scmi.dts | 8 ++++----
arch/arm/boot/dts/st/stm32mp157c-dk2-scmi.dts | 8 ++++----
arch/arm/boot/dts/st/stm32mp157c-ed1-scmi.dts | 8 ++++----
arch/arm/boot/dts/st/stm32mp157c-ev1-scmi.dts | 8 ++++----
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/arch/arm/boot/dts/st/stm32mp157a-dk1-scmi.dts b/arch/arm/boot/dts/st/stm32mp157a-dk1-scmi.dts
index 847b360f02fc..53e40e2f776b 100644
--- a/arch/arm/boot/dts/st/stm32mp157a-dk1-scmi.dts
+++ b/arch/arm/boot/dts/st/stm32mp157a-dk1-scmi.dts
@@ -51,10 +51,6 @@ &iwdg2 {
clocks = <&rcc IWDG2>, <&scmi_clk CK_SCMI_LSI>;
};
-&mdma1 {
- resets = <&scmi_reset RST_SCMI_MDMA>;
-};
-
&m4_rproc {
/delete-property/ st,syscfg-holdboot;
resets = <&scmi_reset RST_SCMI_MCU>,
@@ -62,6 +58,10 @@ &m4_rproc {
reset-names = "mcu_rst", "hold_boot";
};
+&mdma1 {
+ resets = <&scmi_reset RST_SCMI_MDMA>;
+};
+
&optee {
interrupt-parent = <&intc>;
interrupts = <GIC_PPI 15 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>;
diff --git a/arch/arm/boot/dts/st/stm32mp157c-dk2-scmi.dts b/arch/arm/boot/dts/st/stm32mp157c-dk2-scmi.dts
index 43280289759d..0790ed426ebc 100644
--- a/arch/arm/boot/dts/st/stm32mp157c-dk2-scmi.dts
+++ b/arch/arm/boot/dts/st/stm32mp157c-dk2-scmi.dts
@@ -57,10 +57,6 @@ &iwdg2 {
clocks = <&rcc IWDG2>, <&scmi_clk CK_SCMI_LSI>;
};
-&mdma1 {
- resets = <&scmi_reset RST_SCMI_MDMA>;
-};
-
&m4_rproc {
/delete-property/ st,syscfg-holdboot;
resets = <&scmi_reset RST_SCMI_MCU>,
@@ -68,6 +64,10 @@ &m4_rproc {
reset-names = "mcu_rst", "hold_boot";
};
+&mdma1 {
+ resets = <&scmi_reset RST_SCMI_MDMA>;
+};
+
&optee {
interrupt-parent = <&intc>;
interrupts = <GIC_PPI 15 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>;
diff --git a/arch/arm/boot/dts/st/stm32mp157c-ed1-scmi.dts b/arch/arm/boot/dts/st/stm32mp157c-ed1-scmi.dts
index 6f27d794d270..0a3894aff4ae 100644
--- a/arch/arm/boot/dts/st/stm32mp157c-ed1-scmi.dts
+++ b/arch/arm/boot/dts/st/stm32mp157c-ed1-scmi.dts
@@ -56,10 +56,6 @@ &iwdg2 {
clocks = <&rcc IWDG2>, <&scmi_clk CK_SCMI_LSI>;
};
-&mdma1 {
- resets = <&scmi_reset RST_SCMI_MDMA>;
-};
-
&m4_rproc {
/delete-property/ st,syscfg-holdboot;
resets = <&scmi_reset RST_SCMI_MCU>,
@@ -67,6 +63,10 @@ &m4_rproc {
reset-names = "mcu_rst", "hold_boot";
};
+&mdma1 {
+ resets = <&scmi_reset RST_SCMI_MDMA>;
+};
+
&optee {
interrupt-parent = <&intc>;
interrupts = <GIC_PPI 15 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>;
diff --git a/arch/arm/boot/dts/st/stm32mp157c-ev1-scmi.dts b/arch/arm/boot/dts/st/stm32mp157c-ev1-scmi.dts
index 6ae391bffee5..c2b6efb1cbb7 100644
--- a/arch/arm/boot/dts/st/stm32mp157c-ev1-scmi.dts
+++ b/arch/arm/boot/dts/st/stm32mp157c-ev1-scmi.dts
@@ -61,10 +61,6 @@ &m_can1 {
clocks = <&scmi_clk CK_SCMI_HSE>, <&rcc FDCAN_K>;
};
-&mdma1 {
- resets = <&scmi_reset RST_SCMI_MDMA>;
-};
-
&m4_rproc {
/delete-property/ st,syscfg-holdboot;
resets = <&scmi_reset RST_SCMI_MCU>,
@@ -72,6 +68,10 @@ &m4_rproc {
reset-names = "mcu_rst", "hold_boot";
};
+&mdma1 {
+ resets = <&scmi_reset RST_SCMI_MDMA>;
+};
+
&optee {
interrupt-parent = <&intc>;
interrupts = <GIC_PPI 15 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/5] arm64: dts: st: reorder ommanager node in stm32mp257f-ev1.dts
From: Amelie Delaunay @ 2026-06-12 12:56 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Maxime Coquelin,
Alexandre Torgue
Cc: devicetree, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
In-Reply-To: <20260612-node_reordering-v2-0-f68032ca3088@foss.st.com>
In the ST board DTS files, the &label entries must be ordered
alphanumerically.
The nodes became misordered when &ommanager and &lptimer3 were added
simultaneously. After that, <dc and &lvds used the &lptimers position
as a reference.
Move ommanager at the right place to avoid future misordering.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
arch/arm64/boot/dts/st/stm32mp257f-ev1.dts | 56 +++++++++++++++---------------
1 file changed, 28 insertions(+), 28 deletions(-)
diff --git a/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts b/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts
index 14e033f365e3..f044331b8b55 100644
--- a/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts
+++ b/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts
@@ -307,34 +307,6 @@ &i2c8 {
/delete-property/dma-names;
};
-&ommanager {
- memory-region = <&mm_ospi1>;
- memory-region-names = "ospi1";
- pinctrl-0 = <&ospi_port1_clk_pins_a
- &ospi_port1_io03_pins_a
- &ospi_port1_cs0_pins_a>;
- pinctrl-1 = <&ospi_port1_clk_sleep_pins_a
- &ospi_port1_io03_sleep_pins_a
- &ospi_port1_cs0_sleep_pins_a>;
- pinctrl-names = "default", "sleep";
- status = "okay";
-
- spi@0 {
- #address-cells = <1>;
- #size-cells = <0>;
- memory-region = <&mm_ospi1>;
- status = "okay";
-
- flash0: flash@0 {
- compatible = "jedec,spi-nor";
- reg = <0>;
- spi-rx-bus-width = <4>;
- spi-tx-bus-width = <4>;
- spi-max-frequency = <50000000>;
- };
- };
-};
-
/* use LPTIMER with tick broadcast for suspend mode */
&lptimer3 {
status = "okay";
@@ -374,6 +346,34 @@ lvds_out0: endpoint {
};
};
+&ommanager {
+ memory-region = <&mm_ospi1>;
+ memory-region-names = "ospi1";
+ pinctrl-0 = <&ospi_port1_clk_pins_a
+ &ospi_port1_io03_pins_a
+ &ospi_port1_cs0_pins_a>;
+ pinctrl-1 = <&ospi_port1_clk_sleep_pins_a
+ &ospi_port1_io03_sleep_pins_a
+ &ospi_port1_cs0_sleep_pins_a>;
+ pinctrl-names = "default", "sleep";
+ status = "okay";
+
+ spi@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ memory-region = <&mm_ospi1>;
+ status = "okay";
+
+ flash0: flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-rx-bus-width = <4>;
+ spi-tx-bus-width = <4>;
+ spi-max-frequency = <50000000>;
+ };
+ };
+};
+
&pcie_ep {
pinctrl-names = "default", "init";
pinctrl-0 = <&pcie_pins_a>;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 3/5] ARM: dts: stm32: reorder cs_cti_trace node in stm32mp15xx-dkx.dtsi
From: Amelie Delaunay @ 2026-06-12 12:56 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Maxime Coquelin,
Alexandre Torgue
Cc: devicetree, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
In-Reply-To: <20260612-node_reordering-v2-0-f68032ca3088@foss.st.com>
In the ST board DTS files, the &label entries must be ordered
alphanumerically.
The nodes became misordered when Coresight support was added.
Move cs_cti_trace to the right place to avoid future misordering.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
arch/arm/boot/dts/st/stm32mp15xx-dkx.dtsi | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boot/dts/st/stm32mp15xx-dkx.dtsi b/arch/arm/boot/dts/st/stm32mp15xx-dkx.dtsi
index 599ea07bdb19..956509cef321 100644
--- a/arch/arm/boot/dts/st/stm32mp15xx-dkx.dtsi
+++ b/arch/arm/boot/dts/st/stm32mp15xx-dkx.dtsi
@@ -155,15 +155,15 @@ &crc1 {
status = "okay";
};
-&cs_cti_trace {
+&cs_cti_cpu0 {
status = "okay";
};
-&cs_cti_cpu0 {
+&cs_cti_cpu1 {
status = "okay";
};
-&cs_cti_cpu1 {
+&cs_cti_trace {
status = "okay";
};
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/5] ARM: dts: stm32: reorder cs_cti_trace node in stm32mp135f-dk.dts
From: Amelie Delaunay @ 2026-06-12 12:56 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Maxime Coquelin,
Alexandre Torgue
Cc: devicetree, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
In-Reply-To: <20260612-node_reordering-v2-0-f68032ca3088@foss.st.com>
In the ST board DTS files, the &label entries must be ordered
alphanumerically.
The nodes became misordered when Coresight support was added.
Move cs_cti_trace to the right place to avoid future misordering.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
arch/arm/boot/dts/st/stm32mp135f-dk.dts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm/boot/dts/st/stm32mp135f-dk.dts b/arch/arm/boot/dts/st/stm32mp135f-dk.dts
index 6022e73f58af..bc3050a9bec5 100644
--- a/arch/arm/boot/dts/st/stm32mp135f-dk.dts
+++ b/arch/arm/boot/dts/st/stm32mp135f-dk.dts
@@ -190,11 +190,11 @@ &cryp {
status = "okay";
};
-&cs_cti_trace {
+&cs_cti_cpu0 {
status = "okay";
};
-&cs_cti_cpu0 {
+&cs_cti_trace {
status = "okay";
};
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/5] ARM/arm64: dts: st: fix node ordering in ST board device trees
From: Amelie Delaunay @ 2026-06-12 12:56 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Maxime Coquelin,
Alexandre Torgue
Cc: devicetree, linux-stm32, linux-arm-kernel, linux-kernel,
Amelie Delaunay
In the ST board DTS files, &label entries must be ordered
alphanumerically.
Over time, several nodes ended up out of order as a side effect of
adding new features or refactoring existing ones.
This series restores the correct alphanumeric ordering across
all ST board DTS files, with no functional change.
Signed-off-by: Amelie Delaunay <amelie.delaunay@foss.st.com>
---
Changes in v2:
- Fix typo in commit message of patch 1.
- Link to v1: https://lore.kernel.org/r/20260611-node_reordering-v1-0-7e519f2cb456@foss.st.com
---
Amelie Delaunay (5):
arm64: dts: st: reorder ommanager node in stm32mp257f-ev1.dts
ARM: dts: stm32: reorder cs_cti_trace node in stm32mp135f-dk.dts
ARM: dts: stm32: reorder cs_cti_trace node in stm32mp15xx-dkx.dtsi
ARM: dts: stm32: reorder cs_cti_trace node in stm32mp157c-ev1.dts
ARM: dts: stm32: reorder mdma1 node in stm32mp15*-scmi.dts
arch/arm/boot/dts/st/stm32mp135f-dk.dts | 4 +-
arch/arm/boot/dts/st/stm32mp157a-dk1-scmi.dts | 8 ++--
arch/arm/boot/dts/st/stm32mp157c-dk2-scmi.dts | 8 ++--
arch/arm/boot/dts/st/stm32mp157c-ed1-scmi.dts | 8 ++--
arch/arm/boot/dts/st/stm32mp157c-ev1-scmi.dts | 8 ++--
arch/arm/boot/dts/st/stm32mp157c-ev1.dts | 6 +--
arch/arm/boot/dts/st/stm32mp15xx-dkx.dtsi | 6 +--
arch/arm64/boot/dts/st/stm32mp257f-ev1.dts | 56 +++++++++++++--------------
8 files changed, 52 insertions(+), 52 deletions(-)
---
base-commit: fba4a31a7f3b6b29b01c83180f83e7ed4c398738
change-id: 20260611-node_reordering-4b9b132b007f
Best regards,
--
Amelie Delaunay <amelie.delaunay@foss.st.com>
^ permalink raw reply
* Re: [PATCH] net: macb: add TX stall timeout callback to recover from lost TSTART write
From: Nicolai Buchwitz @ 2026-06-12 12:53 UTC (permalink / raw)
To: Andrea della Porta
Cc: netdev, Theo Lebrun, Nicolas Ferre, Claudiu Beznea, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-kernel, linux-arm-kernel, linux-rpi-kernel, Lukasz Raczylo,
Steffen Jaeckel
In-Reply-To: <aiwA1dD-qXcT3hds@apocalypse>
Hi Andrea
On 12.6.2026 14:51, Andrea della Porta wrote:
> [...]
>>
>> The commit message describes it as RP1 specific, but it gets applied
>> to all
>> other variants?
>
> I've seen this issue happening only on RaspberryPi 5, but AFAIK it
> could affect also other MACB blocks connected through PCIe, so it
> may be widespread (even though it should have probably already been
> noticed in the past). In the orginal driver there's no timeout callback
> defined and this is much like pretgending the issue causing the timeout
> to happen to go away without doing anything (whatever the cause ot the
> specific hw are). So in my opinion we can just extend that to all MACB.
> Or maybe we should execute the restart conditionally on
> .compatible = "raspberrypi,rp1-gem"?
I just observed the issue once, but other people reported it to be
happen more
frequently. If we can narrow down a reproducer, it would be good to test
on other
blocks too (like EyeQ at Théo's).|
So maybe you can imagine a good repro for this issue?
Thanks,
Nicolai
^ permalink raw reply
* Re: [PATCH v3] arm64: errata: Workaround NVIDIA Olympus device store/load ordering erratum
From: Jason Gunthorpe @ 2026-06-12 12:48 UTC (permalink / raw)
To: Shanker Donthineni
Cc: Will Deacon, Catalin Marinas, Vladimir Murzin,
linux-arm-kernel@lists.infradead.org, Mark Rutland,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
Vikram Sethi, Jason Sequeira
In-Reply-To: <851c4107-3f6d-46f3-b659-212ce4f69e6e@nvidia.com>
On Thu, Jun 11, 2026 at 08:13:48PM -0500, Shanker Donthineni wrote:
> For the scalar MMIO helpers, the workaround promotes the raw writes to
> store-release on affected CPUs as v1/v2 shown below. For the memcpy-toIO
> helpers, could you please clarify the specific reason for adding a dmb despite
> the documented no-ordering contract? Is the concern that some drivers may
> be relying on ordering across memcpy_toio_*() today even though the API
> does not guarantee it, and that we should cover those cases defensively?
I think given how arm implements them today the iocopy's are actually
the _relaxed variations.. I wonder if this matters to any user?
Jason
^ permalink raw reply
* Re: [PATCH] net: macb: add TX stall timeout callback to recover from lost TSTART write
From: Andrea della Porta @ 2026-06-12 12:51 UTC (permalink / raw)
To: Nicolai Buchwitz
Cc: Andrea della Porta, netdev, Theo Lebrun, Nicolas Ferre,
Claudiu Beznea, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-kernel, linux-arm-kernel,
linux-rpi-kernel, Lukasz Raczylo, Steffen Jaeckel
In-Reply-To: <dbc25b24865a1ad92555bc826bef4267@tipi-net.de>
Hi Nicolai,
On 14:23 Fri 12 Jun , Nicolai Buchwitz wrote:
> Hi Andrea
>
> On 12.6.2026 11:01, Andrea della Porta wrote:
> > From: Lukasz Raczylo <lukasz@raczylo.com>
> >
> > The MACB found in the Raspberry Pi RP1 suffers from sporadic stalls on
> > the TX queue.
> > While the exact root cause is not yet fully understood, it is likely
> > related to a hardware issue where a TSTART write to the NCR register
> > is missed, preventing the transmission from being kicked off.
> >
> > Implement a timeout callback to handle TX queue stalls, triggering the
> > existing restart mechanism to recover.
> >
> > Link:
> > https://lore.kernel.org/all/20260514215459.36109-1-lukasz@raczylo.com/
> > Fixes: dc110d1b23564 ("net: cadence: macb: Add support for Raspberry Pi
> > RP1 ethernet controller")
> > Signed-off-by: Lukasz Raczylo <lukasz@raczylo.com>
> > Co-developed-by: Steffen Jaeckel <sjaeckel@suse.de>
> > Signed-off-by: Steffen Jaeckel <sjaeckel@suse.de>
> > Co-developed-by: Andrea della Porta <andrea.porta@suse.com>
> > Signed-off-by: Andrea della Porta <andrea.porta@suse.com>
> > ---
> > drivers/net/ethernet/cadence/macb_main.c | 11 +++++++++++
> > 1 file changed, 11 insertions(+)
> >
> > diff --git a/drivers/net/ethernet/cadence/macb_main.c
> > b/drivers/net/ethernet/cadence/macb_main.c
> > index a12aa21244e83..615da65d5d68d 100644
> > --- a/drivers/net/ethernet/cadence/macb_main.c
> > +++ b/drivers/net/ethernet/cadence/macb_main.c
> > @@ -4522,6 +4522,16 @@ static int macb_setup_tc(struct net_device *dev,
> > enum tc_setup_type type,
> > }
> > }
> >
> > +static void macb_tx_timeout(struct net_device *dev, unsigned int q)
> > +{
> > + struct macb *bp = netdev_priv(dev);
> > +
> > + if (net_ratelimit())
>
> Do we need the net_ratelimit() check (and message) here? AFAIU the watchdog
> core already prints a message for every timeout.
Correct. I'll drop those two lines.
>
> > + netdev_err(dev, "TX stall detected, re-kicking TSTART\n");
> > + dev->stats.tx_errors++;
> > + macb_tx_restart(&bp->queues[q]);
> > +}
> > +
> > static const struct net_device_ops macb_netdev_ops = {
> > .ndo_open = macb_open,
> > .ndo_stop = macb_close,
> > @@ -4540,6 +4550,7 @@ static const struct net_device_ops macb_netdev_ops
> > = {
> > .ndo_hwtstamp_set = macb_hwtstamp_set,
> > .ndo_hwtstamp_get = macb_hwtstamp_get,
> > .ndo_setup_tc = macb_setup_tc,
> > + .ndo_tx_timeout = macb_tx_timeout,
>
> The commit message describes it as RP1 specific, but it gets applied to all
> other variants?
I've seen this issue happening only on RaspberryPi 5, but AFAIK it
could affect also other MACB blocks connected through PCIe, so it
may be widespread (even though it should have probably already been
noticed in the past). In the orginal driver there's no timeout callback
defined and this is much like pretgending the issue causing the timeout
to happen to go away without doing anything (whatever the cause ot the
specific hw are). So in my opinion we can just extend that to all MACB.
Or maybe we should execute the restart conditionally on
.compatible = "raspberrypi,rp1-gem"?
Thanks,
Andrea
>
> > };
> >
> > /* Configure peripheral capabilities according to device tree
>
> Thanks
> Nicolai
^ permalink raw reply
* Re: [PATCH 15/37] drm/display: bridge-connector: allocate the connector dynamically
From: Luca Ceresoli @ 2026-06-12 12:44 UTC (permalink / raw)
To: Maxime Ripard, Luca Ceresoli
Cc: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Inki Dae, Jagan Teki,
Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
linux-arm-kernel
In-Reply-To: <20260608-rustling-infrared-turkey-e09af8@houat>
On Mon Jun 8, 2026 at 1:46 PM CEST, Maxime Ripard wrote:
> On Tue, May 19, 2026 at 12:37:32PM +0200, Luca Ceresoli wrote:
>> Currently the drm_bridge_connector has an embedded drm_connector, so their
>> allocation lifetimes are tied to each other. This is insufficient to
>> support DRM bridge hotplugging, which requires the connector to be added
>> and removed dynamically at runtime multiple times based on hotplug/unplug
>> events while the drm_bridge_connector is persistent.
>>
>> Moreover the drm_connector is exposed to user space and thus an ongoing
>> operation (e.g. an ioctl) might last for an arbitrarily long time even
>> after the hardware gets removed. This means a new connector might have to
>> be added when the previous one is still referenced by user space.
>>
>> In preparation to handle hotplug, allocate the drm-connector dynamically,
>> to allow:
>>
>> * creating and destroying a connector multiple times during a single
>> drm_bridge_connector lifetime
>> * creating a new connector even though the previous one is still in use
>> and thus still refcounted and not yet freed
>>
>> This commit does not introduce the actions in the two bullets (it will
>> happen in a later commit), it only moves to dynamic APIs for connector
>> allocation and init.
>>
>> Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
>
> I think this patch should be split in half, with the switch to using
> destroy first, and then the actual move to the dynamically allocated
> connector API.
Is it doable? drm_connector_dynamic_init() mandates a .destroy callback,
drm_connector_init() forbids it.
Luca
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* [PATCH net-next 9/9] net: sparx5: add neighbour event handling for L3 routing
From: Jens Emil Schulz Østergaard @ 2026-06-12 12:37 UTC (permalink / raw)
To: Horatiu Vultur, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Daniel Machon,
Steen Hegelund, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-arm-kernel, linux-hardening,
Jens Emil Schulz Østergaard
In-Reply-To: <20260612-sparx5_l3_routing-v1-0-fc3c10160f49@microchip.com>
Register a netevent notifier to handle NETEVENT_NEIGH_UPDATE events,
completing the L3 unicast forwarding data path:
- When ARP/NDP resolves a neighbour, update the hardware ARP table entry
with the resolved MAC address and notify all linked nexthops to
refresh their ECMP forwarding state.
- When a neighbour becomes unreachable, tear down the hardware ARP entry
and mark linked nexthops as unresolved so traffic traps to the CPU.
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
.../net/ethernet/microchip/sparx5/sparx5_router.c | 108 ++++++++++++++++++++-
1 file changed, 107 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_router.c b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
index 5f6b4288755e..4e8950d6535f 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
@@ -2525,6 +2525,104 @@ static int sparx5_rr_fib_event(struct notifier_block *nb, unsigned long event,
return NOTIFY_BAD;
}
+static void sparx5_rr_neigh_event_work(struct work_struct *work)
+{
+ struct sparx5_rr_netevent_work *net_work =
+ container_of(work, struct sparx5_rr_netevent_work, work);
+ unsigned char hwaddr[ETH_ALEN] __aligned(2);
+ struct sparx5 *sparx5 = net_work->sparx5;
+ struct neighbour *n = net_work->neigh;
+ struct sparx5_rr_neigh_key key = { };
+ struct sparx5_rr_neigh_entry *entry;
+ bool entry_connected;
+ u8 nud_state, dead;
+
+ sparx5_rr_nb2neigh_key(n, &key);
+
+ /* Frames with link-local dip are trapped, so ignore the neighbour. */
+ if (key.iaddr.version == SPARX5_IPV6 &&
+ ipv6_addr_type(&key.iaddr.ipv6) & IPV6_ADDR_LINKLOCAL)
+ goto out;
+
+ /* If n changes after this read section, we will get another neigh
+ * event, which is processed after the current one.
+ */
+ read_lock_bh(&n->lock);
+ ether_addr_copy(hwaddr, n->ha);
+ nud_state = n->nud_state;
+ dead = n->dead;
+ read_unlock_bh(&n->lock);
+
+ mutex_lock(&sparx5->router->lock);
+
+ entry_connected = nud_state & NUD_VALID && !dead;
+ entry = sparx5_rr_neigh_entry_lookup(sparx5, &key);
+ if (!entry_connected && !entry)
+ goto out_mutex;
+
+ if (!entry) {
+ entry = sparx5_rr_neigh_entry_create(sparx5, &key);
+ if (IS_ERR(entry))
+ goto out_mutex;
+ }
+
+ if (entry->connected && entry_connected &&
+ ether_addr_equal(entry->hwaddr, hwaddr))
+ goto out_mutex;
+
+ ether_addr_copy(entry->hwaddr, hwaddr);
+ sparx5_rr_neigh_entry_update(sparx5, entry, entry_connected);
+ sparx5_rr_nexthops_update_notify(sparx5, entry, entry_connected);
+ if (!entry_connected)
+ sparx5_rr_neigh_entry_put(sparx5, entry);
+
+out_mutex:
+ mutex_unlock(&sparx5->router->lock);
+out:
+ neigh_release(n);
+ kfree(net_work);
+}
+
+/* Handle neighbour update events. Used to manage neigh_entries. Called in
+ * atomic context, with rcu_read_lock().
+ */
+static int sparx5_rr_netevent_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct sparx5_rr_netevent_work *net_work;
+ struct sparx5_router *router;
+ struct sparx5_port *port;
+ struct neighbour *n;
+
+ router = container_of(nb, struct sparx5_router, netevent_nb);
+
+ switch (event) {
+ case NETEVENT_NEIGH_UPDATE:
+ n = ptr;
+
+ if (n->tbl->family != AF_INET && n->tbl->family != AF_INET6)
+ return NOTIFY_DONE;
+
+ port = sparx5_port_dev_lower_find(n->dev);
+ if (!port)
+ return NOTIFY_DONE;
+
+ net_work = kzalloc_obj(*net_work, GFP_ATOMIC);
+ if (!net_work)
+ return NOTIFY_BAD;
+
+ INIT_WORK(&net_work->work, sparx5_rr_neigh_event_work);
+ net_work->sparx5 = router->sparx5;
+ net_work->neigh = neigh_clone(n);
+ net_work->event = event;
+ sparx5_rr_schedule_work(router->sparx5, &net_work->work);
+
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_DONE;
+};
+
static void sparx5_rr_leg_base_mac_set(struct sparx5 *sparx5,
unsigned char mac[ETH_ALEN])
{
@@ -2903,10 +3001,15 @@ int sparx5_rr_router_init(struct sparx5 *sparx5)
ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, sparx5,
ANA_ACL_VCAP_S2_MISC_CTRL);
+ r->netevent_nb.notifier_call = sparx5_rr_netevent_event;
+ err = register_netevent_notifier(&r->netevent_nb);
+ if (err)
+ goto err_workqueue_destroy;
+
r->fib_nb.notifier_call = sparx5_rr_fib_event;
err = register_fib_notifier(&init_net, &r->fib_nb, NULL, NULL);
if (err)
- goto err_workqueue_destroy;
+ goto err_unreg_netevent_notifier;
r->inetaddr_nb.notifier_call = sparx5_rr_inetaddr_event;
err = register_inetaddr_notifier(&r->inetaddr_nb);
@@ -2945,6 +3048,8 @@ int sparx5_rr_router_init(struct sparx5 *sparx5)
unregister_inetaddr_notifier(&r->inetaddr_nb);
err_unreg_fib_notifier:
unregister_fib_notifier(&init_net, &r->fib_nb);
+err_unreg_netevent_notifier:
+ unregister_netevent_notifier(&r->netevent_nb);
err_workqueue_destroy:
destroy_workqueue(r->sparx5_router_owq);
err_fib_ht_destroy:
@@ -2979,6 +3084,7 @@ void sparx5_rr_router_deinit(struct sparx5 *sparx5)
unregister_inetaddr_validator_notifier(&router->inetaddr_valid_nb);
unregister_inetaddr_notifier(&router->inetaddr_nb);
unregister_fib_notifier(&init_net, &router->fib_nb);
+ unregister_netevent_notifier(&router->netevent_nb);
destroy_workqueue(router->sparx5_router_owq);
sparx5_rr_fib_flush(sparx5);
rhashtable_destroy(&router->fib_ht);
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 8/9] net: sparx5: add L3 FIB, nexthop and neighbour entry management
From: Jens Emil Schulz Østergaard @ 2026-06-12 12:37 UTC (permalink / raw)
To: Horatiu Vultur, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Daniel Machon,
Steen Hegelund, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-arm-kernel, linux-hardening,
Jens Emil Schulz Østergaard
In-Reply-To: <20260612-sparx5_l3_routing-v1-0-fc3c10160f49@microchip.com>
Add the data path for L3 unicast route offloading for IPv4 and IPv6 FIB
and nexthop groups.
* FIB entry lifecycle management. FIB entries are mapped to hardware
using LPM VCAP rules.
* Nexthop group and nexthop lifecycle management. These are mapped to
hardware using an ARP table for ECMP, and inline vcap actions for
single nexthops. Each FIB entry has exactly one nexthop group. As a
simplification each FIB has a copy of its nexthop group, and we do
not support nexthop objects.
* Neighbour (ARP/NDP) lifecycle management. These are referenced by
both nexthops and FIB entries, and are shared amongst them. When a
neighbour referenced by a nexthop is removed we set the HW mac to
zero which makes sure traffic for the nexthop is trapped. A
neighbour will also have an associated /32 or /128 route in the LPM
VCAP for traffic destined directly to the neighbour.
Neighbour entries are created as a side effect of nexthop resolution
but are not yet updated from ARP/NDP events. Neighbour event handling
to activate MAC resolution is added in the next patch.
The three lifecycles are handled in a single patch because they are
mutually entangled. In particular, sparx5_rr_fib_entry_destroy()
walks fib_entry->neigh_list and calls sparx5_rr_neigh_entry_update(),
sparx5_rr_nexthops_update_notify() and sparx5_rr_neigh_entry_put() on
each entry.
The same neigh_entry and nexthop helpers invoked by the
NETEVENT_NEIGH_UPDATE handler added in the following patch. A FIB
teardown must therefore call into the neighbour path, and a neighbour
update must call into the nexthop path, so the three CRUD layers have to
land together for the patch to compile and for the destroy paths to be
consistent.
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
.../net/ethernet/microchip/sparx5/sparx5_main.h | 3 +
.../net/ethernet/microchip/sparx5/sparx5_router.c | 1942 +++++++++++++++++++-
2 files changed, 1943 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index 5dc18b8dbed0..662d5e013047 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -529,11 +529,14 @@ struct sparx5_router {
struct notifier_block netdevice_nb;
struct notifier_block inet6addr_nb;
struct notifier_block inet6addr_valid_nb;
+ struct rhashtable neigh_ht;
+ struct rhashtable fib_ht;
struct sparx5_rr_hw_route link_local; /* Trap all link-local traffic. */
struct net_device *port_dev; /* For VCAP API. */
struct list_head fib_lpm4_list;
struct list_head fib_lpm6_list;
+ struct list_head fib_list; /* All fib entries, for teardown */
struct mutex lock; /* Global router lock for all shared data. */
struct workqueue_struct *sparx5_router_owq;
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_router.c b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
index 03923d91fdfb..5f6b4288755e 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
@@ -225,6 +225,7 @@ struct sparx5_rr_neigh_entry {
struct net_device *dev;
struct sparx5_iaddr iaddr;
} key;
+ netdevice_tracker dev_tracker;
struct rhash_head ht_node;
struct sparx5_rr_fib_entry *fib_entry;
struct list_head fib_list_node; /* Fib route for this neighbour */
@@ -282,6 +283,7 @@ struct sparx5_rr_fib_entry {
enum sparx5_rr_fib_type type;
struct rhash_head ht_node; /* Router member */
struct list_head fib_lpm_node; /* Router member */
+ struct list_head fib_node; /* Router member, all fib entries */
struct list_head neigh_list; /* Neighbours under this route */
struct sparx5_rr_hw_route hw_route;
struct sparx5_rr_nexthop_group *nh_grp;
@@ -305,6 +307,133 @@ static void sparx5_rr_schedule_work(struct sparx5 *sparx5,
queue_work(sparx5->router->sparx5_router_owq, work);
}
+static void sparx5_rr_fib_info_init(struct sparx5_rr_fib_info *fi,
+ enum sparx5_rr_l3_version version)
+{
+ fi->version = version;
+
+ switch (version) {
+ case SPARX5_IPV4:
+ fi->fen4_info.fi = NULL;
+ return;
+ case SPARX5_IPV6:
+ fi->fe6_info.nrt6 = 0;
+ fi->fe6_info.rt_arr = NULL;
+ return;
+ }
+}
+
+/* Return number of nexthops. */
+static int sparx5_rr_fib_info_nhs(struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return fib_info_num_path(fi->fen4_info.fi);
+ case SPARX5_IPV6:
+ return fi->fe6_info.nrt6;
+ default:
+ WARN_ON(1);
+ return 0;
+ }
+}
+
+static struct fib_nh_common *
+sparx5_rr_fib_info_nhc(struct sparx5_rr_fib_info *fi, int nhsel)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return fib_info_nhc(fi->fen4_info.fi, nhsel);
+ case SPARX5_IPV6:
+ return &fi->fe6_info.rt_arr[nhsel]->fib6_nh->nh_common;
+ default:
+ WARN_ON(1);
+ return NULL;
+ }
+}
+
+static bool sparx5_rr_fib_info_is_nh_obj(struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return !!fi->fen4_info.fi->nh;
+ case SPARX5_IPV6:
+ return !!fi->fe6_info.rt_arr[0]->nh;
+ default:
+ WARN_ON(1);
+ return false;
+ }
+}
+
+static u8 sparx5_rr_fib_info_type(struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return fi->fen4_info.type;
+ case SPARX5_IPV6:
+ return fi->fe6_info.rt_arr[0]->fib6_type;
+ default:
+ WARN_ON(1);
+ return RTN_UNSPEC;
+ }
+}
+
+static u32 sparx5_rr_fib_info_tb_id(struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return fi->fen4_info.tb_id;
+ case SPARX5_IPV6:
+ return fi->fe6_info.rt_arr[0]->fib6_table->tb6_id;
+ default:
+ WARN_ON(1);
+ return RT_TABLE_UNSPEC;
+ }
+}
+
+static bool sparx5_rr_fib_info_should_ignore(struct sparx5_rr_fib_info *fi)
+{
+ return fi->version == SPARX5_IPV6 &&
+ ipv6_addr_type(&fi->fe6_info.rt_arr[0]->fib6_dst.addr) &
+ (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static void sparx5_rr_rt6_release(struct fib6_info *rt)
+{
+ if (!rt->nh)
+ rt->fib6_nh->fib_nh_flags &= ~RTNH_F_OFFLOAD;
+
+ fib6_info_release(rt);
+}
+#else
+static void sparx5_rr_rt6_release(struct fib6_info *rt)
+{
+}
+#endif
+
+static void sparx5_rr_fib6_info_put(struct sparx5_rr_fib6_entry_info *fi)
+{
+ for (int i = 0; i < fi->nrt6; i++)
+ sparx5_rr_rt6_release(fi->rt_arr[i]);
+
+ kfree(fi->rt_arr);
+ fi->nrt6 = 0;
+ fi->rt_arr = NULL;
+}
+
+static void sparx5_rr_fib_info_put(struct sparx5_rr_fib_info *fi)
+{
+ if (fi->version == SPARX5_IPV4) {
+ if (fi->fen4_info.fi) {
+ fib_info_put(fi->fen4_info.fi);
+ fi->fen4_info.fi = NULL;
+ }
+ return;
+ }
+
+ sparx5_rr_fib6_info_put(&fi->fe6_info);
+}
+
static void sparx5_rr_split_mac(unsigned char mac[ETH_ALEN], u32 split,
u32 *msb, u32 *lsb)
{
@@ -315,6 +444,28 @@ static void sparx5_rr_split_mac(unsigned char mac[ETH_ALEN], u32 split,
*msb = m >> split;
}
+static int sparx5_rr_arp_tbl_grp_alloc(struct sparx5 *sparx5,
+ unsigned int nh_grp_size)
+{
+ int offset;
+
+ offset = bitmap_find_next_zero_area(sparx5->router->arp_tbl_mask,
+ sparx5->data->consts->arp_tbl_cnt,
+ 0, nh_grp_size, 0);
+ if (offset >= sparx5->data->consts->arp_tbl_cnt)
+ return -ENOMEM;
+
+ bitmap_set(sparx5->router->arp_tbl_mask, offset, nh_grp_size);
+
+ return offset;
+}
+
+static void sparx5_rr_arp_tbl_grp_free(struct sparx5 *sparx5,
+ unsigned int nh_grp_size, int offset)
+{
+ bitmap_clear(sparx5->router->arp_tbl_mask, offset, nh_grp_size);
+}
+
static int sparx5_vmid_alloc(struct sparx5 *sparx5)
{
int vmid;
@@ -334,6 +485,104 @@ static void sparx5_vmid_free(struct sparx5 *sparx5, u16 vmid)
clear_bit(vmid, sparx5->router->vmid_mask);
}
+static void sparx5_rr_nb2neigh_key(struct neighbour *n,
+ struct sparx5_rr_neigh_key *key)
+{
+ memset(key, 0, sizeof(*key));
+
+ /* The primary_key, tbl->family and dev are constant for the lifetime of
+ * the neighbour, so we can read them without n->lock.
+ */
+ if (n->tbl->family == AF_INET) {
+ key->iaddr.version = SPARX5_IPV4;
+ key->iaddr.ipv4 = *(__be32 *)n->primary_key;
+ } else {
+ key->iaddr.version = SPARX5_IPV6;
+ key->iaddr.ipv6 = *(struct in6_addr *)n->primary_key;
+ }
+
+ key->dev = n->dev;
+}
+
+static void
+sparx5_rr_neigh_entry_offload_mark(struct sparx5_rr_neigh_entry *entry,
+ bool offloaded)
+{
+ struct neighbour *n;
+
+ if (!entry->neigh_tbl)
+ return;
+
+ n = neigh_lookup(entry->neigh_tbl, &entry->key.iaddr,
+ entry->key.dev);
+ if (!n)
+ return;
+
+ write_lock_bh(&n->lock);
+ if (offloaded)
+ n->flags |= NTF_OFFLOADED;
+ else
+ n->flags &= ~NTF_OFFLOADED;
+ write_unlock_bh(&n->lock);
+
+ neigh_release(n);
+}
+
+static const struct rhashtable_params sparx5_neigh_ht_params = {
+ .key_offset = offsetof(struct sparx5_rr_neigh_entry, key),
+ .head_offset = offsetof(struct sparx5_rr_neigh_entry, ht_node),
+ .key_len = sizeof(struct sparx5_rr_neigh_key),
+ .automatic_shrinking = true,
+};
+
+static const struct rhashtable_params sparx5_rr_fib_entry_ht_params = {
+ .key_offset = offsetof(struct sparx5_rr_fib_entry, key),
+ .head_offset = offsetof(struct sparx5_rr_fib_entry, ht_node),
+ .key_len = sizeof(struct sparx5_rr_fib_key),
+ .automatic_shrinking = true,
+};
+
+static void sparx5_rr_to_fib4_key(u32 dst, int dst_len, u32 tb_id,
+ struct sparx5_rr_fib_key *key)
+{
+ memset(key, 0, sizeof(*key));
+ key->addr.version = SPARX5_IPV4;
+ key->addr.ipv4 = cpu_to_be32(dst);
+ key->prefix_len = dst_len;
+ key->tb_id = tb_id;
+}
+
+static void sparx5_rr_to_fib6_key(struct in6_addr *addr, int prefix_len,
+ u32 tb_id, struct sparx5_rr_fib_key *key)
+{
+ memset(key, 0, sizeof(*key));
+ key->addr.version = SPARX5_IPV6;
+ memcpy(&key->addr.ipv6, addr, sizeof(*addr));
+ key->prefix_len = prefix_len;
+ key->tb_id = tb_id;
+}
+
+static void sparx5_rr_fib_info_to_fib_key(struct sparx5_rr_fib_info *fi,
+ struct sparx5_rr_fib_key *key)
+{
+ struct fib_entry_notifier_info *fen_info;
+ struct fib6_info *rt;
+
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ fen_info = &fi->fen4_info;
+ sparx5_rr_to_fib4_key(fen_info->dst, fen_info->dst_len,
+ fen_info->tb_id, key);
+ return;
+ case SPARX5_IPV6:
+ rt = fi->fe6_info.rt_arr[0];
+
+ sparx5_rr_to_fib6_key(&rt->fib6_dst.addr, rt->fib6_dst.plen,
+ rt->fib6_table->tb6_id, key);
+ return;
+ }
+}
+
static void sparx5_rr_inet6_make_mask_le(int logmask, u8 *mask)
{
/* Caller must ensure 0 <= logmask <= 128 */
@@ -350,6 +599,81 @@ static void sparx5_rr_inet6_make_mask_le(int logmask, u8 *mask)
mask[15 - byte_prefix] = GENMASK(7, 7 - rem + 1);
}
+static bool sparx5_rr_fib_entry_lpm_match(struct sparx5_iaddr *addr,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ __be32 mask;
+
+ switch (addr->version) {
+ case SPARX5_IPV4:
+ mask = inet_make_mask(fib_entry->key.prefix_len);
+ return !((addr->ipv4 ^ fib_entry->key.addr.ipv4) & mask);
+ case SPARX5_IPV6:
+ return ipv6_prefix_equal(&addr->ipv6,
+ &fib_entry->key.addr.ipv6,
+ fib_entry->key.prefix_len);
+ default:
+ WARN_ON(1);
+ return false;
+ }
+}
+
+static struct list_head *sparx5_rr_fib_lpm_get(struct sparx5 *sparx5,
+ struct sparx5_iaddr *addr)
+{
+ return addr->version == SPARX5_IPV6
+ ? &sparx5->router->fib_lpm6_list
+ : &sparx5->router->fib_lpm4_list;
+}
+
+static struct sparx5_rr_fib_entry *
+sparx5_rr_fib_lpm_lookup(struct sparx5 *sparx5, struct sparx5_iaddr *addr)
+{
+ struct list_head *lpm_backend = sparx5_rr_fib_lpm_get(sparx5, addr);
+ struct sparx5_rr_fib_entry *iter;
+
+ list_for_each_entry(iter, lpm_backend, fib_lpm_node)
+ if (sparx5_rr_fib_entry_lpm_match(addr, iter))
+ return iter;
+
+ return NULL;
+}
+
+static void sparx5_rr_fib_lpm_insert(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct list_head *lpm_backend =
+ sparx5_rr_fib_lpm_get(sparx5, &fib_entry->key.addr);
+ struct sparx5_rr_fib_entry *iter, *next = NULL;
+
+ /* No need to search through local FIB entries */
+ if (fib_entry->type != SPARX5_RR_FIB_TYPE_UNICAST)
+ return;
+
+ list_for_each_entry(iter, lpm_backend, fib_lpm_node) {
+ if (fib_entry->sort_key < iter->sort_key) {
+ next = iter;
+ break;
+ }
+ }
+
+ if (!next) {
+ list_add_tail(&fib_entry->fib_lpm_node, lpm_backend);
+ return;
+ }
+
+ /* Add before next entry */
+ list_add_tail(&fib_entry->fib_lpm_node, &next->fib_lpm_node);
+}
+
+static void sparx5_rr_fib_lpm_remove(struct sparx5_rr_fib_entry *fib_entry)
+{
+ if (fib_entry->type != SPARX5_RR_FIB_TYPE_UNICAST)
+ return;
+
+ list_del(&fib_entry->fib_lpm_node);
+}
+
static int sparx5_rr_lpm_rule_xip_add(struct vcap_rule *rule,
struct sparx5_iaddr *addr, u32 prefix_len)
{
@@ -387,6 +711,668 @@ sparx5_rr_leg_find_by_dev(struct sparx5 *sparx5, struct net_device *dev)
return NULL;
}
+static struct sparx5_rr_fib_entry *
+sparx5_rr_fib_entry_lookup(struct sparx5 *sparx5, struct sparx5_rr_fib_key *key)
+{
+ return rhashtable_lookup_fast(&sparx5->router->fib_ht, key,
+ sparx5_rr_fib_entry_ht_params);
+}
+
+static int sparx5_rr_fib_entry_insert(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ return rhashtable_insert_fast(&sparx5->router->fib_ht,
+ &fib_entry->ht_node,
+ sparx5_rr_fib_entry_ht_params);
+}
+
+static void sparx5_rr_fib_entry_remove(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ rhashtable_remove_fast(&sparx5->router->fib_ht, &fib_entry->ht_node,
+ sparx5_rr_fib_entry_ht_params);
+}
+
+static struct sparx5_rr_neigh_entry *
+sparx5_rr_neigh_entry_lookup(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_key *key)
+{
+ return rhashtable_lookup_fast(&sparx5->router->neigh_ht, key,
+ sparx5_neigh_ht_params);
+}
+
+static int sparx5_rr_neigh_entry_insert(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ return rhashtable_insert_fast(&sparx5->router->neigh_ht,
+ &entry->ht_node, sparx5_neigh_ht_params);
+}
+
+static void sparx5_rr_neigh_entry_remove(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ rhashtable_remove_fast(&sparx5->router->neigh_ht, &entry->ht_node,
+ sparx5_neigh_ht_params);
+}
+
+static int sparx5_lower_dev_walk(struct net_device *lower_dev,
+ struct netdev_nested_priv *priv)
+{
+ int ret = 0;
+
+ if (sparx5_netdevice_check(lower_dev)) {
+ priv->data = (void *)netdev_priv(lower_dev);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static struct sparx5_port *
+sparx5_port_dev_lower_find_rcu(struct net_device *dev)
+{
+ struct netdev_nested_priv priv = {
+ .data = NULL,
+ };
+
+ if (sparx5_netdevice_check(dev))
+ return netdev_priv(dev);
+
+ netdev_walk_all_lower_dev_rcu(dev, sparx5_lower_dev_walk, &priv);
+
+ return priv.data;
+}
+
+static struct sparx5_port *sparx5_port_dev_lower_find(struct net_device *dev)
+{
+ struct sparx5_port *port;
+
+ rcu_read_lock();
+ port = sparx5_port_dev_lower_find_rcu(dev);
+ rcu_read_unlock();
+
+ return port;
+}
+
+static struct sparx5_rr_neigh_entry *
+sparx5_rr_neigh_entry_alloc(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_key *key,
+ struct sparx5_rr_router_leg *leg)
+{
+ struct sparx5_rr_neigh_entry *entry;
+
+ entry = kzalloc_obj(*entry);
+ if (!entry)
+ return NULL;
+
+ memcpy(&entry->key, key, sizeof(*key));
+ entry->vmid = leg->vmid;
+
+ switch (key->iaddr.version) {
+ case SPARX5_IPV4:
+ entry->neigh_tbl = &arp_tbl;
+ break;
+ case SPARX5_IPV6:
+#if IS_ENABLED(CONFIG_IPV6)
+ entry->neigh_tbl = &nd_tbl;
+ break;
+#else
+ kfree(entry);
+ return NULL;
+#endif
+ }
+
+ INIT_LIST_HEAD(&entry->nexthop_list);
+ INIT_LIST_HEAD(&entry->fib_list_node);
+
+ return entry;
+}
+
+static int sparx5_rr_neigh_entry_fib_link(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ struct sparx5_rr_fib_entry *fib_entry;
+
+ fib_entry = sparx5_rr_fib_lpm_lookup(sparx5, &entry->key.iaddr);
+ if (!fib_entry)
+ return -ENOENT;
+
+ list_add(&entry->fib_list_node, &fib_entry->neigh_list);
+ entry->fib_entry = fib_entry;
+
+ return 0;
+}
+
+static struct sparx5_rr_neigh_entry *
+sparx5_rr_neigh_entry_create(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_key *key)
+{
+ struct sparx5_rr_neigh_entry *entry;
+ struct sparx5_rr_router_leg *leg;
+ struct sparx5_port *port_below;
+ int err;
+
+ port_below = sparx5_port_dev_lower_find(key->dev);
+ if (!port_below)
+ return ERR_PTR(-EINVAL);
+
+ leg = sparx5_rr_leg_find_by_dev(sparx5, key->dev);
+ if (!leg)
+ return ERR_PTR(-EINVAL);
+
+ entry = sparx5_rr_neigh_entry_alloc(sparx5, key, leg);
+ if (!entry)
+ return ERR_PTR(-ENOMEM);
+
+ err = sparx5_rr_neigh_entry_insert(sparx5, entry);
+ if (err)
+ goto err_insert;
+
+ /* Link neigh to the fib which owns the subnet. */
+ err = sparx5_rr_neigh_entry_fib_link(sparx5, entry);
+ if (err)
+ goto err_fib_link;
+
+ netdev_hold(entry->key.dev, &entry->dev_tracker, GFP_KERNEL);
+
+ return entry;
+
+err_fib_link:
+ sparx5_rr_neigh_entry_remove(sparx5, entry);
+err_insert:
+ kfree(entry);
+ return ERR_PTR(err);
+}
+
+static int sparx5_rr_nexthop_neigh_init(struct sparx5 *sparx5,
+ struct sparx5_rr_router_leg *leg,
+ struct sparx5_rr_nexthop *nh)
+{
+ struct sparx5_rr_neigh_entry *neigh_entry;
+ struct net_device *dev = leg->dev;
+ struct sparx5_rr_neigh_key key;
+ struct neighbour *n;
+ int err = 0;
+
+ if (!nh->gateway || nh->neigh_entry || !nh->neigh_tbl)
+ return 0;
+
+ /* Look up neighbor in the global neighbor table. Takes ref to n. */
+ n = neigh_lookup(nh->neigh_tbl, &nh->gw_addr, dev);
+ if (!n) {
+ n = neigh_create(nh->neigh_tbl, &nh->gw_addr, dev);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+ /* Start arp process */
+ neigh_event_send(n, NULL);
+ }
+
+ sparx5_rr_nb2neigh_key(n, &key);
+
+ neigh_entry = sparx5_rr_neigh_entry_lookup(sparx5, &key);
+ if (!neigh_entry) {
+ neigh_entry = sparx5_rr_neigh_entry_create(sparx5, &key);
+ if (IS_ERR(neigh_entry)) {
+ err = PTR_ERR(neigh_entry);
+ goto out;
+ }
+ }
+
+ nh->neigh_entry = neigh_entry;
+ list_add_tail(&nh->neigh_list_node, &neigh_entry->nexthop_list);
+
+out:
+ neigh_release(n);
+ return err;
+}
+
+static void sparx5_rr_neigh_entry_destroy(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ WARN_ON(entry->hw_route.vrule_id_valid);
+
+ if (entry->fib_entry)
+ list_del(&entry->fib_list_node);
+
+ sparx5_rr_neigh_entry_offload_mark(entry, false);
+ sparx5_rr_neigh_entry_remove(sparx5, entry);
+ netdev_put(entry->key.dev, &entry->dev_tracker);
+
+ kfree(entry);
+}
+
+static void sparx5_rr_neigh_entry_put(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *neigh_entry)
+{
+ if (neigh_entry && list_empty(&neigh_entry->nexthop_list) &&
+ !neigh_entry->hw_route.vrule_id_valid)
+ sparx5_rr_neigh_entry_destroy(sparx5, neigh_entry);
+}
+
+static void sparx5_rr_nexthop_deinit(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop *nh)
+{
+ struct sparx5_rr_neigh_entry *neigh_entry = nh->neigh_entry;
+
+ if (neigh_entry) {
+ list_del(&nh->neigh_list_node);
+ sparx5_rr_neigh_entry_put(sparx5, neigh_entry);
+ }
+
+ nh->neigh_entry = NULL;
+}
+
+static int sparx5_rr_nexthop_init(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp,
+ struct sparx5_rr_nexthop *nh,
+ struct fib_nh_common *fnhc)
+{
+ struct sparx5_rr_router_leg *leg;
+
+ nh->ifindex = -1;
+ nh->grp = nh_grp;
+ nh->gateway = fnhc->nhc_gw_family != 0;
+ nh->trapped = false;
+ nh->neigh_entry = NULL;
+ nh->neigh_tbl = NULL;
+
+ memset(&nh->gw_addr, 0, sizeof(nh->gw_addr));
+
+ if (!nh->gateway)
+ return 0;
+
+ switch (fnhc->nhc_gw_family) {
+ case AF_INET:
+ nh->gw_addr.version = SPARX5_IPV4;
+ nh->gw_addr.ipv4 = fnhc->nhc_gw.ipv4;
+ nh->neigh_tbl = &arp_tbl;
+ break;
+ case AF_INET6:
+ nh->gw_addr.version = SPARX5_IPV6;
+ nh->gw_addr.ipv6 = fnhc->nhc_gw.ipv6;
+#if IS_ENABLED(CONFIG_IPV6)
+ nh->neigh_tbl = &nd_tbl;
+ break;
+#else
+ return -EINVAL;
+#endif
+ default:
+ WARN_ON_ONCE(1); /* BUG */
+ return 0;
+ }
+
+ /* Blackhole route nexthops have no egress device. */
+ if (!fnhc->nhc_dev)
+ return 0;
+
+ nh->ifindex = fnhc->nhc_dev->ifindex;
+
+ /* When a router leg is removed, all the nexthops with gateway IPs in a
+ * subnet governed by the leg will receive fib delete events. However,
+ * these delete events are received one by one. Therefore, this nexthop
+ * init could have been triggered by a group resize action for such an
+ * event, where the underlying leg is already removed.
+ *
+ * This is not an error. We handle this during offloading by
+ * trapping nexthops which do not have a neigh_entry. As fib deletion
+ * events are processed, we converge to the proper state.
+ */
+ leg = sparx5_rr_leg_find_by_dev(sparx5, fnhc->nhc_dev);
+ if (!leg)
+ return 0;
+
+ return sparx5_rr_nexthop_neigh_init(sparx5, leg, nh);
+}
+
+static int
+sparx5_rr_nexthop_group_info_init(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp,
+ struct sparx5_rr_fib_info *fi)
+{
+ unsigned int nhs = sparx5_rr_fib_info_nhs(fi);
+ struct sparx5_rr_nexthop_group_info *nhgi;
+ struct sparx5_rr_nexthop *nh;
+ int err, i;
+
+ nhgi = kzalloc_flex(*nhgi, nexthops, nhs);
+ if (!nhgi)
+ return -ENOMEM;
+
+ nh_grp->nhgi = nhgi;
+ nhgi->grp = nh_grp;
+ nhgi->atbl_offset_valid = false;
+ nhgi->atbl_offset = 0;
+ nhgi->count = nhs;
+
+ for (i = 0; i < nhgi->count; i++) {
+ struct fib_nh_common *fnhc;
+
+ nh = &nhgi->nexthops[i];
+ fnhc = sparx5_rr_fib_info_nhc(fi, i);
+ err = sparx5_rr_nexthop_init(sparx5, nh_grp, nh, fnhc);
+ if (err)
+ goto err_nexthop_init;
+ }
+
+ return 0;
+
+err_nexthop_init:
+ for (i--; i >= 0; i--) {
+ nh = &nhgi->nexthops[i];
+ sparx5_rr_nexthop_deinit(sparx5, nh);
+ }
+ kfree(nhgi);
+ return err;
+}
+
+static void
+sparx5_rr_nexthop_group_info_deinit(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp)
+{
+ struct sparx5_rr_nexthop_group_info *nhgi = nh_grp->nhgi;
+ struct sparx5_rr_nexthop *nh;
+ int i;
+
+ WARN_ON(!nhgi->count);
+ WARN_ON_ONCE(nhgi->atbl_offset_valid);
+
+ for (i = nhgi->count - 1; i >= 0; i--) {
+ nh = &nhgi->nexthops[i];
+
+ sparx5_rr_nexthop_deinit(sparx5, nh);
+ }
+
+ kfree(nhgi);
+}
+
+static void sparx5_rr_arp_tbl_hw_addr_apply(struct sparx5 *sparx5,
+ unsigned char mac[ETH_ALEN],
+ u16 evmid, int offset)
+{
+ u32 mac_msb, mac_lsb;
+
+ sparx5_rr_split_mac(mac, 32, &mac_msb, &mac_lsb);
+
+ spx5_rmw(ANA_L3_ARP_CFG_0_MAC_MSB_SET(mac_msb) |
+ ANA_L3_ARP_CFG_0_ARP_VMID_SET(evmid) |
+ ANA_L3_ARP_CFG_0_ARP_ENA_SET(1),
+ ANA_L3_ARP_CFG_0_ARP_ENA |
+ ANA_L3_ARP_CFG_0_ARP_VMID |
+ ANA_L3_ARP_CFG_0_MAC_MSB,
+ sparx5, ANA_L3_ARP_CFG_0(offset));
+
+ spx5_wr(mac_lsb, sparx5, ANA_L3_ARP_CFG_1(offset));
+}
+
+static void sparx5_rr_arp_tbl_hw_addr_clear(struct sparx5 *sparx5, int offset)
+{
+ spx5_rmw(ANA_L3_ARP_CFG_0_ARP_ENA_SET(0), ANA_L3_ARP_CFG_0_ARP_ENA,
+ sparx5, ANA_L3_ARP_CFG_0(offset));
+}
+
+static void
+sparx5_rr_nh_grp_arp_tbl_grp_clear(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp)
+{
+ int offset = nh_grp->nhgi->atbl_offset;
+
+ if (nh_grp->nhgi->atbl_offset_valid) {
+ for (u8 i = 0; i < nh_grp->nhgi->count; i++)
+ sparx5_rr_arp_tbl_hw_addr_clear(sparx5, offset + i);
+ sparx5_rr_arp_tbl_grp_free(sparx5, nh_grp->nhgi->count,
+ offset);
+ }
+ nh_grp->nhgi->atbl_offset_valid = false;
+}
+
+static void sparx5_rr_nexthop_group_put(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp)
+{
+ sparx5_rr_nh_grp_arp_tbl_grp_clear(sparx5, nh_grp);
+ sparx5_rr_nexthop_group_info_deinit(sparx5, nh_grp);
+ kfree(nh_grp);
+}
+
+static struct sparx5_rr_nexthop_group *
+sparx5_rr_nexthop_group_create(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct sparx5_rr_nexthop_group *nh_grp;
+ int err;
+
+ nh_grp = kzalloc_obj(*nh_grp);
+ if (!nh_grp)
+ return ERR_PTR(-ENOMEM);
+
+ err = sparx5_rr_nexthop_group_info_init(sparx5, nh_grp, &fib_entry->fi);
+ if (err)
+ goto err_group_info_init;
+
+ return nh_grp;
+
+err_group_info_init:
+ kfree(nh_grp);
+ return ERR_PTR(err);
+}
+
+static enum sparx5_rr_fib_type sparx5_rr_rtm_type2fib_type(u8 type)
+{
+ switch (type) {
+ case RTN_UNICAST:
+ return SPARX5_RR_FIB_TYPE_UNICAST;
+ case RTN_LOCAL:
+ return SPARX5_RR_FIB_TYPE_LOCAL;
+ case RTN_MULTICAST:
+ return SPARX5_RR_FIB_TYPE_MULTICAST;
+ case RTN_BLACKHOLE:
+ return SPARX5_RR_FIB_TYPE_BLACKHOLE;
+ case RTN_PROHIBIT:
+ return SPARX5_RR_FIB_TYPE_PROHIBIT;
+ case RTN_UNREACHABLE:
+ return SPARX5_RR_FIB_TYPE_UNREACHABLE;
+ default:
+ return SPARX5_RR_FIB_TYPE_INVALID;
+ }
+}
+
+static void
+sparx5_rr_fib_entry_fib4_info_set(struct sparx5_rr_fib_entry *fib_entry,
+ struct fib_entry_notifier_info *fen4_info)
+{
+ /* Prevent the fib_info from being deleted while we store the
+ * fen_info
+ */
+ fib_info_hold(fen4_info->fi);
+ memcpy(&fib_entry->fi.fen4_info, fen4_info, sizeof(*fen4_info));
+}
+
+static int
+sparx5_rr_fib_entry_fib6_info_add(struct sparx5_rr_fib_entry *fib_entry,
+ struct sparx5_rr_fib6_entry_info *fib6_info)
+{
+ struct sparx5_rr_fib6_entry_info *f6i = &fib_entry->fi.fe6_info;
+ unsigned int old_ntr6, new_ntr6;
+ struct fib6_info **rt_arr;
+
+ old_ntr6 = f6i->nrt6;
+ new_ntr6 = old_ntr6 + fib6_info->nrt6;
+
+ rt_arr = kzalloc_objs(struct fib6_info *, new_ntr6);
+ if (!rt_arr)
+ return -ENOMEM;
+
+ /* Copy existing */
+ for (int i = 0; i < old_ntr6; i++)
+ rt_arr[i] = f6i->rt_arr[i];
+
+ /* Copy new and hold fib6_info */
+ for (int i = 0; i < fib6_info->nrt6; i++) {
+ struct fib6_info *rt = fib6_info->rt_arr[i];
+
+ rt_arr[old_ntr6 + i] = rt;
+ fib6_info_hold(rt);
+ }
+
+ /* Free old fib6_info */
+ kfree(f6i->rt_arr);
+ f6i->rt_arr = rt_arr;
+ f6i->nrt6 = new_ntr6;
+
+ WARN_ON(!fib_entry->fi.fe6_info.rt_arr);
+ WARN_ON(!fib_entry->fi.fe6_info.nrt6);
+
+ return 0;
+}
+
+static int
+sparx5_rr_fib_entry_fib_info_add(struct sparx5_rr_fib_entry *fib_entry,
+ struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ /* IPv4 nexthops can not be added/removed piecemeal similar to
+ * IPv6, so this is a replace in practice.
+ */
+ sparx5_rr_fib_entry_fib4_info_set(fib_entry, &fi->fen4_info);
+ return 0;
+ case SPARX5_IPV6:
+ return sparx5_rr_fib_entry_fib6_info_add(fib_entry,
+ &fi->fe6_info);
+ default:
+ WARN_ON(1);
+ return 0;
+ }
+}
+
+static struct sparx5_rr_fib_entry *
+sparx5_rr_fib_entry_create(struct sparx5 *sparx5, struct sparx5_rr_fib_key *key,
+ struct sparx5_rr_fib_info *fi)
+{
+ struct sparx5_rr_nexthop_group *nh_grp;
+ u8 type = sparx5_rr_fib_info_type(fi);
+ struct sparx5_rr_fib_entry *fib_entry;
+ int err;
+
+ fib_entry = kzalloc_obj(*fib_entry);
+ if (!fib_entry)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(&fib_entry->key, key, sizeof(*key));
+ sparx5_rr_fib_info_init(&fib_entry->fi, fi->version);
+ fib_entry->type = sparx5_rr_rtm_type2fib_type(type);
+ fib_entry->sort_key = LPM_SORT_KEY(key->prefix_len);
+
+ err = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);
+ if (err)
+ goto err_fib_info_set;
+
+ err = sparx5_rr_fib_entry_insert(sparx5, fib_entry);
+ if (err)
+ goto err_fib_entry_insert;
+
+ nh_grp = sparx5_rr_nexthop_group_create(sparx5, fib_entry);
+ if (IS_ERR(nh_grp)) {
+ err = PTR_ERR(nh_grp);
+ goto err_nexthop_group_create;
+ }
+
+ fib_entry->nh_grp = nh_grp;
+ nh_grp->fib_entry = fib_entry;
+ INIT_LIST_HEAD(&fib_entry->neigh_list);
+
+ list_add(&fib_entry->fib_node, &sparx5->router->fib_list);
+ sparx5_rr_fib_lpm_insert(sparx5, fib_entry);
+
+ return fib_entry;
+
+err_nexthop_group_create:
+ sparx5_rr_fib_entry_remove(sparx5, fib_entry);
+err_fib_entry_insert:
+ sparx5_rr_fib_info_put(&fib_entry->fi);
+err_fib_info_set:
+ kfree(fib_entry);
+
+ return ERR_PTR(err);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static void
+sparx5_rr_fib6_entry_offload_mark(struct sparx5 *sparx5,
+ struct sparx5_rr_fib6_entry_info *fen6_info,
+ bool offload,
+ bool trap,
+ bool offload_failed)
+{
+ for (int i = 0; i < fen6_info->nrt6; i++)
+ fib6_info_hw_flags_set(&init_net, fen6_info->rt_arr[i], offload,
+ trap, offload_failed);
+}
+#else
+static void
+sparx5_rr_fib6_entry_offload_mark(struct sparx5 *sparx5,
+ struct sparx5_rr_fib6_entry_info *fen6_info,
+ bool offload,
+ bool trap,
+ bool offload_failed)
+{
+}
+#endif
+
+static void
+sparx5_rr_fib4_entry_offload_mark(struct sparx5 *sparx5,
+ struct fib_entry_notifier_info *fen4_info,
+ bool offload,
+ bool trap,
+ bool offload_failed)
+{
+ struct fib_rt_info fri;
+
+ fri.fi = fen4_info->fi;
+ fri.tb_id = fen4_info->tb_id;
+ fri.dst = cpu_to_be32(fen4_info->dst);
+ fri.dst_len = fen4_info->dst_len;
+ fri.dscp = fen4_info->dscp;
+ fri.type = fen4_info->type;
+ fri.offload = offload;
+ fri.trap = trap;
+ fri.offload_failed = offload_failed;
+
+ fib_alias_hw_flags_set(&init_net, &fri);
+}
+
+static void sparx5_rr_fib_info_offload_mark(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi,
+ bool offload, bool trap,
+ bool offload_failed)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return sparx5_rr_fib4_entry_offload_mark(sparx5,
+ &fi->fen4_info,
+ offload, trap,
+ offload_failed);
+ case SPARX5_IPV6:
+ return sparx5_rr_fib6_entry_offload_mark(sparx5,
+ &fi->fe6_info,
+ offload, trap,
+ offload_failed);
+ }
+}
+
+static void
+sparx5_rr_fib_entry_offload_mark(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ bool offload, trap, offload_failed;
+
+ offload_failed = fib_entry->offload_fail;
+ offload = !fib_entry->offload_fail;
+ trap = !fib_entry->offload_fail && fib_entry->trap;
+
+ sparx5_rr_fib_info_offload_mark(sparx5, &fib_entry->fi, offload, trap,
+ offload_failed);
+}
+
static int
sparx5_rr_lpm_arp_entry_create(struct sparx5 *sparx5,
struct sparx5_iaddr *addr,
@@ -424,6 +1410,472 @@ sparx5_rr_lpm_arp_entry_create(struct sparx5 *sparx5,
return err;
}
+static int sparx5_rr_lpm_arp_entry_mod(struct sparx5 *sparx5,
+ unsigned char mac[ETH_ALEN], u16 evmid,
+ u32 vrule_id)
+{
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ struct vcap_rule *vrule;
+ u32 mac_msb, mac_lsb;
+ int err;
+
+ sparx5_rr_split_mac(mac, 32, &mac_msb, &mac_lsb);
+
+ vrule = vcap_get_rule(vctrl, vrule_id);
+ if (IS_ERR(vrule))
+ return -EINVAL;
+
+ err = vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_MSB, mac_msb);
+ err |= vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_LSB, mac_lsb);
+ err |= vcap_rule_mod_action_u32(vrule, VCAP_AF_ARP_VMID, evmid);
+ err |= vcap_rule_mod_action_bit(vrule, VCAP_AF_ARP_ENA, VCAP_BIT_1);
+
+ err = err ? -EINVAL : vcap_mod_rule(vrule);
+
+ vcap_free_rule(vrule);
+ return err;
+}
+
+static int
+sparx5_rr_fib_entry_update_arp_entry(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry,
+ unsigned char mac[ETH_ALEN], u16 evmid)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ u32 vrule_id = fib_entry->hw_route.vrule_id;
+ struct vcap_rule *vrule;
+ u32 mac_msb, mac_lsb;
+ int err;
+
+ sparx5_rr_split_mac(mac, 32, &mac_msb, &mac_lsb);
+
+ vrule = vcap_get_rule(vctrl, vrule_id);
+ if (IS_ERR(vrule)) {
+ fib_entry->hw_route.vrule_id_valid = false;
+ return PTR_ERR(vrule);
+ }
+
+ switch (vrule->actionset) {
+ case VCAP_AFS_ARP_ENTRY:
+ err = vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_MSB,
+ mac_msb);
+ err |= vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_LSB,
+ mac_lsb);
+
+ err = err ? -EINVAL : vcap_mod_rule(vrule);
+ goto free_rule;
+ case VCAP_AFS_ARP_PTR:
+ /* Convert arp_ptr to arp_entry */
+ err = sparx5_rr_lpm_arp_entry_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len,
+ mac, evmid,
+ &fib_entry->hw_route);
+ if (err)
+ goto free_rule;
+
+ sparx5_rr_nh_grp_arp_tbl_grp_clear(sparx5, fib_entry->nh_grp);
+ err = vcap_del_rule(vctrl, pdev, vrule_id);
+ goto free_rule;
+ default:
+ err = -EINVAL;
+ WARN_ON(1); /* BUG */
+ }
+
+free_rule:
+ vcap_free_rule(vrule);
+
+ return err;
+}
+
+static int sparx5_rr_lpm_arp_ptr_create(struct sparx5 *sparx5,
+ struct sparx5_iaddr *addr,
+ u32 prefix_len, u32 arp_offset_addr,
+ u8 ecmp_size,
+ struct sparx5_rr_hw_route *hw_route)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ u32 priority = LPM_SORT_KEY(prefix_len);
+ struct vcap_rule *rule;
+ int err;
+
+ rule = vcap_alloc_rule(vctrl, pdev, VCAP_CID_PREROUTING_L0,
+ VCAP_USER_L3, priority, 0);
+ if (IS_ERR(rule))
+ return PTR_ERR(rule);
+
+ err = sparx5_rr_lpm_rule_xip_add(rule, addr, prefix_len);
+ err |= vcap_rule_add_key_u32(rule, VCAP_KF_AFFIX, 0, 0);
+ err |= vcap_rule_add_key_bit(rule, VCAP_KF_DST_FLAG, VCAP_BIT_1);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_ARP_PTR, arp_offset_addr);
+ err |= vcap_rule_add_action_bit(rule, VCAP_AF_ARP_PTR_REMAP_ENA,
+ VCAP_BIT_0);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_ECMP_CNT, ecmp_size - 1);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_RGID, 0);
+
+ err = err ? -EINVAL : vcap_val_add_rule(rule, LPM_PROTO(addr));
+ if (!err) {
+ hw_route->vrule_id_valid = true;
+ hw_route->vrule_id = rule->id;
+ }
+
+ vcap_free_rule(rule);
+ return err;
+}
+
+/* Get egress mac and vmid for nexthop. */
+static void sparx5_rr_nexthop_egress_derive(struct sparx5_rr_nexthop *nh,
+ u8 *mac, u16 *vmid)
+{
+ struct sparx5_rr_neigh_entry *nh_neigh = nh->neigh_entry;
+
+ nh->trapped = !nh_neigh || is_zero_ether_addr(nh_neigh->hwaddr);
+
+ if (nh_neigh) {
+ memcpy(mac, nh_neigh->hwaddr, ETH_ALEN);
+ *vmid = nh_neigh->vmid;
+ return;
+ }
+
+ eth_zero_addr(mac);
+ *vmid = 0;
+}
+
+static int
+sparx5_rr_fib_entry_ecmp_hw_apply(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct sparx5_rr_nexthop_group_info *nhgi = fib_entry->nh_grp->nhgi;
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ struct sparx5_rr_nexthop *nh;
+ int err, i, offset;
+ u16 vmid;
+
+ offset = sparx5_rr_arp_tbl_grp_alloc(sparx5, nhgi->count);
+
+ if (offset < 0) {
+ fib_entry->offload_fail = true;
+ return offset;
+ }
+
+ for (i = 0; i < nhgi->count; i++) {
+ nh = &nhgi->nexthops[i];
+
+ sparx5_rr_nexthop_egress_derive(nh, mac, &vmid);
+
+ sparx5_rr_arp_tbl_hw_addr_apply(sparx5, mac, vmid, offset + i);
+ }
+
+ err = sparx5_rr_lpm_arp_ptr_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len, offset,
+ nhgi->count, &fib_entry->hw_route);
+ if (err)
+ goto err_arp_ptr_create;
+
+ nhgi->atbl_offset = offset;
+ nhgi->atbl_offset_valid = true;
+ fib_entry->offload_fail = false;
+
+ return 0;
+
+err_arp_ptr_create:
+ for (i--; i >= 0; i--)
+ sparx5_rr_arp_tbl_hw_addr_clear(sparx5, offset + i);
+ sparx5_rr_arp_tbl_grp_free(sparx5, nhgi->count, offset);
+ fib_entry->offload_fail = true;
+ nhgi->atbl_offset_valid = false;
+
+ return err;
+}
+
+static int
+sparx5_rr_fib_blackhole_hw_apply(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ unsigned char mac[ETH_ALEN];
+
+ /* Hardware blackholes are implemented by:
+ *
+ * 1) Making sure traffic is not trapped with non-zero dmac.
+ * 2) Using reserved router leg vmid for egress.
+ * 3) This router leg is attached to a VLAN id > 4095.
+ * 4) The port-mask for this VLAN is all zero.
+ *
+ * The hardware VLAN table has more than 4096 entries. The specific
+ * size depends on the chip. LAN969x has 4608 and Sparx5 has 5120
+ * entries. These additional VLAN entries can be used for internal
+ * logic.
+ *
+ * The port-mask for the blackhole VLAN is zero. Therefore, frames
+ * routed to the blackhole leg will not egress on any ports.
+ */
+ eth_zero_addr(mac);
+ mac[5] = 0xff;
+
+ return sparx5_rr_lpm_arp_entry_create(sparx5, &fib_entry->key.addr,
+ fib_entry->key.prefix_len, mac,
+ SPARX5_BLACKHOLE_VMID(sparx5),
+ &fib_entry->hw_route);
+}
+
+static int sparx5_rr_fib_entry_hw_apply(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct sparx5_rr_nexthop_group_info *nhgi = fib_entry->nh_grp->nhgi;
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ struct sparx5_rr_nexthop *nh;
+ int err = 0;
+ u16 vmid;
+
+ switch (fib_entry->type) {
+ case SPARX5_RR_FIB_TYPE_UNREACHABLE:
+ fallthrough;
+ case SPARX5_RR_FIB_TYPE_PROHIBIT:
+ /* Ensure kernel can respond with correct ICMP packets. */
+ fallthrough;
+ case SPARX5_RR_FIB_TYPE_LOCAL:
+ /* Trap traffic destined for device itself, to ensure
+ * device can receive traffic even when default gateways are
+ * configured.
+ */
+ fib_entry->trap = true;
+
+ /* Trap frames with zero mac, VMID does not matter */
+ eth_zero_addr(mac);
+ err = sparx5_rr_lpm_arp_entry_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len,
+ mac, 0,
+ &fib_entry->hw_route);
+ goto out;
+
+ case SPARX5_RR_FIB_TYPE_UNICAST:
+ fib_entry->trap = false;
+
+ if (!nhgi->nexthops->gateway) {
+ /* Directly connected subnet. Trap traffic so kernel
+ * can perform ARP/NDP on our behalf.
+ */
+ eth_zero_addr(mac);
+ err = sparx5_rr_lpm_arp_entry_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len,
+ mac, 0,
+ &fib_entry->hw_route);
+ goto out;
+ }
+
+ if (nhgi->count == 1) { /* Use arp_entry */
+ nh = &nhgi->nexthops[0];
+ sparx5_rr_nexthop_egress_derive(nh, mac, &vmid);
+ err = sparx5_rr_lpm_arp_entry_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len,
+ mac, vmid,
+ &fib_entry->hw_route);
+ goto out;
+ }
+
+ /* Multiple nexthops so we use the HW arp table. */
+ err = sparx5_rr_fib_entry_ecmp_hw_apply(sparx5,
+ fib_entry);
+ goto out;
+
+ case SPARX5_RR_FIB_TYPE_BLACKHOLE:
+ fib_entry->trap = false;
+ err = sparx5_rr_fib_blackhole_hw_apply(sparx5, fib_entry);
+ goto out;
+
+ default:
+ dev_warn(sparx5->dev, "Fib entry offload, unhandled type=%d\n",
+ fib_entry->type);
+ return -EINVAL;
+ }
+
+out:
+ fib_entry->offload_fail = !!err;
+
+ return err;
+}
+
+static void sparx5_rr_nexthop_neigh_update(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop *nh,
+ bool entry_connected)
+{
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ int err, nh_offset, grp_idx;
+ u16 vmid;
+
+ if (!nh->gateway)
+ return;
+
+ vmid = nh->neigh_entry->vmid;
+
+ /* Trap traffic with zero mac */
+ eth_zero_addr(mac);
+
+ if (entry_connected)
+ ether_addr_copy(mac, nh->neigh_entry->hwaddr);
+
+ if (nh->trapped && !entry_connected)
+ return;
+
+ nh->trapped = !entry_connected;
+
+ if (nh->grp->nhgi->count == 1) {
+ err = sparx5_rr_fib_entry_update_arp_entry(sparx5,
+ nh->grp->fib_entry,
+ mac, vmid);
+ if (err)
+ dev_err(sparx5->dev,
+ "Nexthop fib entry update failed\n");
+
+ return;
+ }
+
+ if (!nh->grp->nhgi->atbl_offset_valid)
+ return;
+
+ nh_offset = (int)(ptrdiff_t)(nh - nh->grp->nhgi->nexthops);
+ grp_idx = nh->grp->nhgi->atbl_offset;
+
+ sparx5_rr_arp_tbl_hw_addr_apply(sparx5, mac, vmid, grp_idx + nh_offset);
+}
+
+static void
+sparx5_rr_nexthops_update_notify(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *neigh_entry,
+ bool entry_connected)
+{
+ struct sparx5_rr_nexthop *nh;
+
+ list_for_each_entry(nh, &neigh_entry->nexthop_list, neigh_list_node)
+ sparx5_rr_nexthop_neigh_update(sparx5, nh, entry_connected);
+}
+
+static int sparx5_rr_neigh_entry_hw_apply(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ u32 prefix_len = SPARX5_IADDR_LEN(entry->key.iaddr.version);
+
+ if (!entry->hw_route.vrule_id_valid)
+ return sparx5_rr_lpm_arp_entry_create(sparx5,
+ &entry->key.iaddr,
+ prefix_len,
+ entry->hwaddr,
+ entry->vmid,
+ &entry->hw_route);
+
+ return sparx5_rr_lpm_arp_entry_mod(sparx5, entry->hwaddr, entry->vmid,
+ entry->hw_route.vrule_id);
+}
+
+static void sparx5_rr_neigh_entry_update(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry,
+ bool adding)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ bool offloaded = adding;
+ int err;
+
+ if (!adding && !entry->connected && !entry->hw_route.vrule_id_valid)
+ return;
+
+ entry->connected = adding;
+
+ if (adding) {
+ err = sparx5_rr_neigh_entry_hw_apply(sparx5, entry);
+ if (err)
+ offloaded = false;
+ } else if (entry->hw_route.vrule_id_valid) {
+ vcap_del_rule(sparx5->vcap_ctrl, pdev,
+ entry->hw_route.vrule_id);
+ entry->hw_route.vrule_id_valid = false;
+ }
+
+ return sparx5_rr_neigh_entry_offload_mark(entry, offloaded);
+}
+
+static void sparx5_rr_fib_entry_destroy(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct sparx5_rr_neigh_entry *neigh_entry, *tmp;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+
+ list_del(&fib_entry->fib_node);
+ sparx5_rr_fib_lpm_remove(fib_entry);
+
+ list_for_each_entry_safe(neigh_entry, tmp, &fib_entry->neigh_list,
+ fib_list_node) {
+ list_del(&neigh_entry->fib_list_node);
+ neigh_entry->fib_entry = NULL;
+
+ /* Remove LPM VCAP entry for neighbour, if used */
+ sparx5_rr_neigh_entry_update(sparx5, neigh_entry, false);
+ sparx5_rr_nexthops_update_notify(sparx5, neigh_entry, false);
+ sparx5_rr_neigh_entry_put(sparx5, neigh_entry);
+ }
+
+ sparx5_rr_fib_entry_remove(sparx5, fib_entry);
+ sparx5_rr_nexthop_group_put(sparx5, fib_entry->nh_grp);
+ if (fib_entry->hw_route.vrule_id_valid)
+ vcap_del_rule(vctrl, pdev, fib_entry->hw_route.vrule_id);
+ sparx5_rr_fib_info_put(&fib_entry->fi);
+ kfree(fib_entry);
+}
+
+/* Update nexthop group based on current fib_info state. */
+static int
+sparx5_rr_entry_nexthop_group_update(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ struct sparx5_rr_nexthop_group *new_nh_grp;
+ struct sparx5_rr_nexthop_group *old_nh_grp;
+ bool old_vrule_id_valid;
+ u32 old_vrule_id;
+ int err;
+
+ old_nh_grp = fib_entry->nh_grp;
+ old_vrule_id = fib_entry->hw_route.vrule_id;
+ old_vrule_id_valid = fib_entry->hw_route.vrule_id_valid;
+
+ /* Prepare new group in SW representation */
+ new_nh_grp = sparx5_rr_nexthop_group_create(sparx5, fib_entry);
+ if (IS_ERR(new_nh_grp)) {
+ dev_warn(sparx5->dev, "Failed to create nexthop group\n");
+ return PTR_ERR(new_nh_grp);
+ }
+
+ fib_entry->nh_grp = new_nh_grp;
+ new_nh_grp->fib_entry = fib_entry;
+
+ /* Write new rule to HW */
+ err = sparx5_rr_fib_entry_hw_apply(sparx5, fib_entry);
+ if (err)
+ goto hw_apply_err;
+
+ /* Clean up old rule and start routing traffic according to new rule */
+ if (old_vrule_id_valid && fib_entry->hw_route.vrule_id != old_vrule_id)
+ vcap_del_rule(vctrl, pdev, old_vrule_id);
+
+ /* Remove old unused group */
+ sparx5_rr_nexthop_group_put(sparx5, old_nh_grp);
+
+ return 0;
+
+hw_apply_err:
+ fib_entry->nh_grp = old_nh_grp;
+ new_nh_grp->fib_entry = NULL;
+ sparx5_rr_nexthop_group_put(sparx5, new_nh_grp);
+ return err;
+}
+
static void sparx5_rr_leg_hw_init(struct sparx5 *sparx5,
struct sparx5_rr_router_leg *leg)
{
@@ -599,6 +2051,21 @@ sparx5_rr_router_leg_create(struct sparx5 *sparx5, struct net_device *dev,
return leg;
}
+static void sparx5_rr_fib4_del(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct sparx5_rr_fib_key key;
+
+ sparx5_rr_fib_info_to_fib_key(fi, &key);
+
+ fib_entry = sparx5_rr_fib_entry_lookup(sparx5, &key);
+ if (!fib_entry)
+ return;
+
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+}
+
static bool sparx5_rr_dev_real_is_vlan_aware(struct net_device *dev)
{
struct net_device *vlan_rdev;
@@ -620,6 +2087,444 @@ static bool sparx5_rr_dev_real_is_vlan_aware(struct net_device *dev)
return false;
}
+static bool sparx5_rr_fib_info_should_offload(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ u32 tb_id = sparx5_rr_fib_info_tb_id(fi);
+ u8 type = sparx5_rr_fib_info_type(fi);
+ int nhs = sparx5_rr_fib_info_nhs(fi);
+
+ if (!(type == RTN_UNICAST ||
+ type == RTN_LOCAL ||
+ type == RTN_BLACKHOLE ||
+ type == RTN_PROHIBIT ||
+ type == RTN_UNREACHABLE))
+ return false;
+
+ if (!(tb_id == RT_TABLE_MAIN ||
+ tb_id == RT_TABLE_LOCAL))
+ return false;
+
+ /* No support for nexthop objects (optimization for larger scale
+ * routing). Instead each route has a copy of it's nexthops.
+ */
+ if (sparx5_rr_fib_info_is_nh_obj(fi))
+ return false;
+
+ /* For IPv4 the nexthops of these route types have NULL egress device.
+ * However, for IPv6 the nexthops use the loopback interface, so accept
+ * early.
+ */
+ if (type == RTN_BLACKHOLE ||
+ type == RTN_PROHIBIT ||
+ type == RTN_UNREACHABLE)
+ return true;
+
+ if (nhs > SPARX5_MAX_ECMP_SIZE)
+ return false;
+
+ for (int i = 0; i < nhs; i++) {
+ struct fib_nh_common *nhc = sparx5_rr_fib_info_nhc(fi, i);
+
+ if (nhc->nhc_dev &&
+ !sparx5_rr_dev_real_is_vlan_aware(nhc->nhc_dev))
+ return false;
+
+ /* HW only supports equal weight nexthops */
+ if (nhc->nhc_weight != 1)
+ return false;
+ }
+
+ return true;
+}
+
+static int sparx5_rr_fib_replace(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ u8 fi_type = sparx5_rr_fib_info_type(fi);
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct sparx5_rr_fib_info old_fi;
+ struct sparx5_rr_fib_key key;
+ int err = 0;
+
+ if (sparx5_rr_fib_info_should_ignore(fi))
+ return 0;
+
+ sparx5_rr_fib_info_to_fib_key(fi, &key);
+
+ fib_entry = sparx5_rr_fib_entry_lookup(sparx5, &key);
+
+ if (!sparx5_rr_fib_info_should_offload(sparx5, fi)) {
+ /* A previously offloadable fib, is modified to unoffloadable
+ * state, so we must remove it.
+ */
+ if (fib_entry)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+ return 0;
+ }
+
+ if (!fib_entry) {
+ /* Holds refs to kernel fib_info */
+ fib_entry = sparx5_rr_fib_entry_create(sparx5, &key, fi);
+ if (IS_ERR(fib_entry)) {
+ dev_warn(sparx5->dev, "Failed to create fib entry\n");
+ sparx5_rr_fib_info_offload_mark(sparx5, fi, false,
+ false, true);
+ return PTR_ERR(fib_entry);
+ }
+
+ err = sparx5_rr_fib_entry_hw_apply(sparx5, fib_entry);
+ goto out_fib_mark_offload;
+ }
+
+ /* Save old fib_info, add new one, then release old. This ordering
+ * ensures fib_entry retains valid fi on allocation failure.
+ */
+ old_fi = fib_entry->fi;
+
+ /* Clear fib_entry fi */
+ sparx5_rr_fib_info_init(&fib_entry->fi, fi->version);
+
+ /* Hold and replace with new fib_info */
+ err = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);
+ if (err) {
+ fib_entry->fi = old_fi;
+ dev_err(sparx5->dev, "Failed to replace fib info\n");
+ sparx5_rr_fib_info_offload_mark(sparx5, fi, false, false, true);
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+ return err;
+ }
+
+ /* Release and allow any previous fib_info to be deleted */
+ sparx5_rr_fib_info_put(&old_fi);
+
+ fib_entry->type = sparx5_rr_rtm_type2fib_type(fi_type);
+
+ err = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);
+
+out_fib_mark_offload:
+ fib_entry->offload_fail = !!err;
+ sparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);
+ if (err)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+ return err;
+}
+
+static void sparx5_rr_fib4_event_work(struct work_struct *work)
+{
+ struct sparx5_fib_event_work *fib_work =
+ container_of(work, struct sparx5_fib_event_work, work);
+ struct sparx5 *sparx5 = fib_work->sparx5;
+ int err;
+
+ mutex_lock(&sparx5->router->lock);
+
+ switch (fib_work->event) {
+ case FIB_EVENT_ENTRY_REPLACE:
+ err = sparx5_rr_fib_replace(sparx5, &fib_work->fi);
+ if (err)
+ dev_warn(sparx5->dev, "FIB replace failed, ip=%pI4l\n",
+ &fib_work->fi.fen4_info.dst);
+
+ break;
+ case FIB_EVENT_ENTRY_DEL:
+ sparx5_rr_fib4_del(sparx5, &fib_work->fi);
+ break;
+ default:
+ /* FIB_EVENT_ENTRY_APPEND only occurs for IPv6. */
+ WARN_ON_ONCE(1); /* BUG */
+ break;
+ }
+
+ /* Release fib_info hold for workqueue. */
+ sparx5_rr_fib_info_put(&fib_work->fi);
+ mutex_unlock(&sparx5->router->lock);
+ kfree(fib_work);
+}
+
+static int sparx5_rr_fib6_append(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct sparx5_rr_fib_key key;
+ int err = 0;
+
+ if (sparx5_rr_fib_info_should_ignore(fi))
+ return 0;
+
+ sparx5_rr_fib_info_to_fib_key(fi, &key);
+
+ fib_entry = sparx5_rr_fib_entry_lookup(sparx5, &key);
+ if (!fib_entry)
+ return 0;
+
+ /* Are we adding new nexthops which can not be offloaded */
+ if (!sparx5_rr_fib_info_should_offload(sparx5, fi)) {
+ err = -EINVAL;
+ goto out_fib_mark_offload;
+ }
+
+ /* Append new rt_arr data to fen6_info rt data */
+ err = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);
+ if (err)
+ goto out_fib_mark_offload;
+
+ /* Realloc nexthop group and apply to hw. */
+ err = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);
+
+out_fib_mark_offload:
+ fib_entry->offload_fail = !!err;
+ sparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);
+ if (err)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+
+ return err;
+}
+
+static bool sparx5_rr_fib6_rt_exists(struct sparx5_rr_fib6_entry_info *f6i,
+ struct fib6_info *rt)
+{
+ for (int i = 0; i < f6i->nrt6; i++)
+ if (f6i->rt_arr[i] == rt)
+ return true;
+
+ return false;
+}
+
+static int sparx5_rr_fib6_nexthop_prune(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry,
+ struct sparx5_rr_fib6_entry_info *f6i)
+{
+ struct fib6_info **old_rt_arr = fib_entry->fi.fe6_info.rt_arr;
+ unsigned int old_nrt6, new_nrt6;
+ struct fib6_info **rt_arr;
+ int j = 0;
+
+ old_nrt6 = fib_entry->fi.fe6_info.nrt6;
+ new_nrt6 = old_nrt6 >= f6i->nrt6 ? old_nrt6 - f6i->nrt6 : 0;
+
+ rt_arr = kzalloc_objs(struct fib6_info *, new_nrt6);
+ if (!rt_arr)
+ return -ENOMEM;
+
+ for (int i = 0; i < old_nrt6; i++) {
+ struct fib6_info *fi = old_rt_arr[i];
+
+ if (sparx5_rr_fib6_rt_exists(f6i, fi)) {
+ sparx5_rr_rt6_release(fi);
+ continue;
+ }
+
+ rt_arr[j++] = fi;
+ }
+
+ /* Assume incoming f6i only contain live nexthops, and no duplicates. */
+ WARN_ON_ONCE(j != new_nrt6);
+
+ kfree(fib_entry->fi.fe6_info.rt_arr);
+ fib_entry->fi.fe6_info.nrt6 = new_nrt6;
+ fib_entry->fi.fe6_info.rt_arr = rt_arr;
+ return 0;
+}
+
+static int sparx5_rr_fib6_del(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ struct sparx5_rr_fib_entry *fib_entry;
+ int nhs = sparx5_rr_fib_info_nhs(fi);
+ struct sparx5_rr_fib_key key;
+ int err;
+
+ sparx5_rr_fib_info_to_fib_key(fi, &key);
+
+ fib_entry = sparx5_rr_fib_entry_lookup(sparx5, &key);
+ if (!fib_entry)
+ return 0;
+
+ /* Full delete. */
+ if (nhs == sparx5_rr_fib_info_nhs(&fib_entry->fi)) {
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+ return 0;
+ }
+
+ /* Partial delete. Remove fi nexthops from fib_entry. */
+ err = sparx5_rr_fib6_nexthop_prune(sparx5, fib_entry, &fi->fe6_info);
+ if (err)
+ goto err_nexthop_prune;
+
+ /* Realloc nexthop group and apply to hw. */
+ err = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);
+
+err_nexthop_prune:
+ fib_entry->offload_fail = !!err;
+ sparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);
+ if (err)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+
+ return err;
+}
+
+static void sparx5_rr_fib6_event_work(struct work_struct *work)
+{
+ struct sparx5_fib_event_work *fib_work =
+ container_of(work, struct sparx5_fib_event_work, work);
+ struct sparx5_rr_fib_info *fi = &fib_work->fi;
+ struct sparx5 *sparx5 = fib_work->sparx5;
+ int err;
+
+ mutex_lock(&sparx5->router->lock);
+
+ switch (fib_work->event) {
+ case FIB_EVENT_ENTRY_REPLACE:
+ err = sparx5_rr_fib_replace(sparx5, fi);
+ if (err)
+ dev_warn(sparx5->dev, "FIB 6 replace failed.\n");
+
+ break;
+
+ case FIB_EVENT_ENTRY_APPEND:
+ /* Netlink API for IPv6 is different from IPV4. It is
+ * possible to do partial update/deletes of nexthops on a
+ * route. In this case fi only contains the nexthops to
+ * add/remove, and must be merged with the existing nexthops
+ * on the route. Therefore, we only share fib_replace between
+ * IPv6 and IPv4 logic.
+ */
+ err = sparx5_rr_fib6_append(sparx5, fi);
+ if (err)
+ dev_warn(sparx5->dev, "FIB 6 append failed.\n");
+
+ break;
+
+ case FIB_EVENT_ENTRY_DEL:
+ err = sparx5_rr_fib6_del(sparx5, fi);
+ if (err)
+ dev_warn(sparx5->dev, "FIB 6 delete failed.\n");
+
+ break;
+
+ default:
+ WARN_ON_ONCE(1); /* BUG */
+ break;
+ }
+
+ /* Release fib6_info holds for workqueue. */
+ sparx5_rr_fib_info_put(fi);
+ mutex_unlock(&sparx5->router->lock);
+ kfree(fib_work);
+}
+
+static int sparx5_rr_fib6_work_init(struct sparx5_fib_event_work *fib_work,
+ struct fib6_entry_notifier_info *fen6_info)
+{
+ struct sparx5_rr_fib6_entry_info *fib6_info = &fib_work->fi.fe6_info;
+ struct fib6_info *rt = fen6_info->rt;
+ struct fib6_info **rt_arr;
+ struct fib6_info *iter;
+ unsigned int nrt6;
+ int i = 0;
+
+ nrt6 = fen6_info->nsiblings + 1;
+
+ rt_arr = kzalloc_objs(struct fib6_info *, nrt6, GFP_ATOMIC);
+ if (!rt_arr)
+ return -ENOMEM;
+
+ fib6_info->rt_arr = rt_arr;
+ fib6_info->nrt6 = nrt6;
+
+ rt_arr[0] = rt;
+ fib6_info_hold(rt);
+
+ if (!fen6_info->nsiblings)
+ return 0;
+
+ list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) {
+ if (i == fen6_info->nsiblings)
+ break;
+
+ rt_arr[i + 1] = iter;
+ fib6_info_hold(iter);
+ i++;
+ }
+
+ return 0;
+}
+
+/* Handle fib events, which manage fib_entries. Called in atomic context, with
+ * rcu_read_lock().
+ */
+static int sparx5_rr_fib_event(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct fib6_entry_notifier_info *fen6_info;
+ struct fib_entry_notifier_info *fen_info;
+ struct sparx5_fib_event_work *fib_work;
+ struct fib_notifier_info *info = ptr;
+ struct sparx5_router *router;
+ int err;
+
+ /* Handle IPv4 and IPv6 */
+ if (info->family != AF_INET && info->family != AF_INET6)
+ return NOTIFY_DONE;
+
+ if (event != FIB_EVENT_ENTRY_REPLACE &&
+ event != FIB_EVENT_ENTRY_DEL &&
+ event != FIB_EVENT_ENTRY_APPEND)
+ return NOTIFY_DONE;
+
+ router = container_of(nb, struct sparx5_router, fib_nb);
+
+ fib_work = kzalloc_obj(*fib_work, GFP_ATOMIC);
+ if (!fib_work)
+ return NOTIFY_BAD;
+
+ fib_work->sparx5 = router->sparx5;
+ fib_work->event = event;
+
+ switch (info->family) {
+ case AF_INET:
+ INIT_WORK(&fib_work->work, sparx5_rr_fib4_event_work);
+
+ fen_info = container_of(info, struct fib_entry_notifier_info,
+ info);
+ fib_work->fi.fen4_info = *fen_info;
+ fib_work->fi.version = SPARX5_IPV4;
+
+ /* Hold fib_info while item is queued */
+ fib_info_hold(fib_work->fi.fen4_info.fi);
+
+ sparx5_rr_schedule_work(router->sparx5, &fib_work->work);
+ break;
+ case AF_INET6:
+ INIT_WORK(&fib_work->work, sparx5_rr_fib6_event_work);
+
+ /* Copy and hold fib6_info for route and all nhs while item is
+ * queued.
+ */
+ fen6_info = container_of(info, struct fib6_entry_notifier_info,
+ info);
+ err = sparx5_rr_fib6_work_init(fib_work, fen6_info);
+ if (err)
+ goto err_fib6;
+
+ fib_work->fi.version = SPARX5_IPV6;
+
+ sparx5_rr_schedule_work(router->sparx5, &fib_work->work);
+ break;
+ default:
+ goto err_fam_unhandled;
+ }
+
+ return NOTIFY_DONE;
+
+err_fam_unhandled:
+ WARN_ON_ONCE(1); /* BUG */
+err_fib6:
+ kfree(fib_work);
+ return NOTIFY_BAD;
+}
+
static void sparx5_rr_leg_base_mac_set(struct sparx5 *sparx5,
unsigned char mac[ETH_ALEN])
{
@@ -922,16 +2827,25 @@ int sparx5_rr_router_init(struct sparx5 *sparx5)
INIT_LIST_HEAD(&r->leg_list);
INIT_LIST_HEAD(&r->fib_lpm4_list);
INIT_LIST_HEAD(&r->fib_lpm6_list);
+ INIT_LIST_HEAD(&r->fib_list);
/* Add reserved leg for blackhole routes. */
err = sparx5_rr_blackhole_leg_create(sparx5);
if (err)
goto err_free_router;
+ err = rhashtable_init(&r->neigh_ht, &sparx5_neigh_ht_params);
+ if (err)
+ goto err_blackhole_destroy;
+
+ err = rhashtable_init(&r->fib_ht, &sparx5_rr_fib_entry_ht_params);
+ if (err)
+ goto err_neigh_ht_destroy;
+
r->sparx5_router_owq = alloc_ordered_workqueue("sparx5_router_owq", 0);
if (!r->sparx5_router_owq) {
err = -ENOMEM;
- goto err_blackhole_destroy;
+ goto err_fib_ht_destroy;
}
atomic_set(&r->legs_count, 0);
@@ -989,10 +2903,15 @@ int sparx5_rr_router_init(struct sparx5 *sparx5)
ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, sparx5,
ANA_ACL_VCAP_S2_MISC_CTRL);
+ r->fib_nb.notifier_call = sparx5_rr_fib_event;
+ err = register_fib_notifier(&init_net, &r->fib_nb, NULL, NULL);
+ if (err)
+ goto err_workqueue_destroy;
+
r->inetaddr_nb.notifier_call = sparx5_rr_inetaddr_event;
err = register_inetaddr_notifier(&r->inetaddr_nb);
if (err)
- goto err_workqueue_destroy;
+ goto err_unreg_fib_notifier;
r->inetaddr_valid_nb.notifier_call = sparx5_rr_inetaddr_valid_event;
err = register_inetaddr_validator_notifier(&r->inetaddr_valid_nb);
@@ -1024,8 +2943,14 @@ int sparx5_rr_router_init(struct sparx5 *sparx5)
unregister_inetaddr_validator_notifier(&r->inetaddr_valid_nb);
err_unreg_inet_notifier:
unregister_inetaddr_notifier(&r->inetaddr_nb);
+err_unreg_fib_notifier:
+ unregister_fib_notifier(&init_net, &r->fib_nb);
err_workqueue_destroy:
destroy_workqueue(r->sparx5_router_owq);
+err_fib_ht_destroy:
+ rhashtable_destroy(&r->fib_ht);
+err_neigh_ht_destroy:
+ rhashtable_destroy(&r->neigh_ht);
err_blackhole_destroy:
sparx5_rr_router_legs_flush(sparx5);
err_free_router:
@@ -1035,6 +2960,15 @@ int sparx5_rr_router_init(struct sparx5 *sparx5)
return err;
}
+static void sparx5_rr_fib_flush(struct sparx5 *sparx5)
+{
+ struct sparx5_rr_fib_entry *fib_entry, *tmp;
+
+ list_for_each_entry_safe(fib_entry, tmp, &sparx5->router->fib_list,
+ fib_node)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+}
+
void sparx5_rr_router_deinit(struct sparx5 *sparx5)
{
struct sparx5_router *router = sparx5->router;
@@ -1044,7 +2978,11 @@ void sparx5_rr_router_deinit(struct sparx5 *sparx5)
unregister_netdevice_notifier(&router->netdevice_nb);
unregister_inetaddr_validator_notifier(&router->inetaddr_valid_nb);
unregister_inetaddr_notifier(&router->inetaddr_nb);
+ unregister_fib_notifier(&init_net, &router->fib_nb);
destroy_workqueue(router->sparx5_router_owq);
+ sparx5_rr_fib_flush(sparx5);
+ rhashtable_destroy(&router->fib_ht);
+ rhashtable_destroy(&router->neigh_ht);
sparx5_rr_router_legs_flush(sparx5);
mutex_destroy(&router->lock);
kfree(router);
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 7/9] net: sparx5: add L3 router infrastructure and leg management
From: Jens Emil Schulz Østergaard @ 2026-06-12 12:37 UTC (permalink / raw)
To: Horatiu Vultur, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Daniel Machon,
Steen Hegelund, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-arm-kernel, linux-hardening,
Jens Emil Schulz Østergaard
In-Reply-To: <20260612-sparx5_l3_routing-v1-0-fc3c10160f49@microchip.com>
Add event handling for netdevices and iaddrs to intercept VLAN uppers of
a bridge device, and their IP addresses. Add all the basic types we will
use for l3 routing management. Add lifecycle management for router legs.
The chips can only route in VLAN aware mode, and routing happens between
router legs (RLEG). To each VLAN upper of a VLAN aware bridge, we
associate a router leg. The indices to the router leg table are called
VMIDs in the datasheet.
There is a global router leg base MAC used for MAC rewrites by the chip.
Here we use the bridge MAC.
A specific leg is reserved to enable blackhole routes. The blackhole
route uses an reserved egress VLAN id for which the port mask is empty,
resulting in all frames being dropped.
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
drivers/net/ethernet/microchip/sparx5/Makefile | 2 +-
.../ethernet/microchip/sparx5/lan969x/lan969x.c | 2 +
.../net/ethernet/microchip/sparx5/sparx5_main.c | 13 +-
.../net/ethernet/microchip/sparx5/sparx5_main.h | 43 +
.../net/ethernet/microchip/sparx5/sparx5_router.c | 1051 ++++++++++++++++++++
5 files changed, 1109 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/Makefile b/drivers/net/ethernet/microchip/sparx5/Makefile
index d447f9e84d92..d917734d9f12 100644
--- a/drivers/net/ethernet/microchip/sparx5/Makefile
+++ b/drivers/net/ethernet/microchip/sparx5/Makefile
@@ -11,7 +11,7 @@ sparx5-switch-y := sparx5_main.o sparx5_packet.o \
sparx5_ptp.o sparx5_pgid.o sparx5_tc.o sparx5_qos.o \
sparx5_vcap_impl.o sparx5_vcap_ag_api.o sparx5_tc_flower.o \
sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o \
- sparx5_psfp.o sparx5_mirror.o sparx5_regs.o
+ sparx5_psfp.o sparx5_mirror.o sparx5_regs.o sparx5_router.o
sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o
sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
index f3a9c71bea36..f8d1741c264f 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
@@ -322,6 +322,8 @@ static const struct sparx5_consts lan969x_consts = {
.qres_max_prio_idx = 315,
.qres_max_colour_idx = 323,
.tod_pin = 4,
+ .vmid_cnt = 127,
+ .arp_tbl_cnt = 1024,
.vcaps = lan969x_vcaps,
.vcap_stats = &lan969x_vcap_stats,
.vcaps_cfg = lan969x_vcap_inst_cfg,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
index dad713e9ddd5..24acdbc20dc8 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
@@ -994,14 +994,22 @@ static int mchp_sparx5_probe(struct platform_device *pdev)
goto cleanup_ptp;
}
+ err = sparx5_rr_router_init(sparx5);
+ if (err) {
+ dev_err(sparx5->dev, "Router initialization failed\n");
+ goto cleanup_netdevs;
+ }
+
err = sparx5_register_notifier_blocks(sparx5);
if (err) {
dev_err(sparx5->dev, "Failed to register notifier blocks\n");
- goto cleanup_netdevs;
+ goto cleanup_router;
}
goto cleanup_config;
+cleanup_router:
+ sparx5_rr_router_deinit(sparx5);
cleanup_netdevs:
sparx5_unregister_netdevs(sparx5);
cleanup_ptp:
@@ -1031,6 +1039,7 @@ static void mchp_sparx5_remove(struct platform_device *pdev)
sparx5_unregister_notifier_blocks(sparx5);
sparx5_unregister_netdevs(sparx5);
sparx5_ptp_deinit(sparx5);
+ sparx5_rr_router_deinit(sparx5);
sparx5_frame_io_deinit(sparx5);
sparx5_stats_deinit(sparx5);
sparx5_mact_deinit(sparx5);
@@ -1067,6 +1076,8 @@ static const struct sparx5_consts sparx5_consts = {
.qres_max_prio_idx = 630,
.qres_max_colour_idx = 638,
.tod_pin = 4,
+ .vmid_cnt = 511,
+ .arp_tbl_cnt = 2048,
.vcaps = sparx5_vcaps,
.vcaps_cfg = sparx5_vcap_inst_cfg,
.vcap_stats = &sparx5_vcap_stats,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index eb57b86fbe22..5dc18b8dbed0 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -136,6 +136,10 @@ enum sparx5_feature {
#define SPARX5_MAX_PTP_ID 512
+/* Must be maximum values across all L3 enabled platforms */
+#define SPARX5_ROUTER_LEG_N_VMID 511
+#define SPARX5_ARP_TBL_SIZE 2048
+
struct sparx5;
struct sparx5_calendar_data {
@@ -320,6 +324,8 @@ struct sparx5_consts {
u32 qres_max_prio_idx; /* Maximum QRES prio index */
u32 qres_max_colour_idx; /* Maximum QRES colour index */
u32 tod_pin; /* PTP TOD pin */
+ u32 vmid_cnt; /* Number of router leg VMID's */
+ u32 arp_tbl_cnt; /* Number of ARP table entries */
const struct sparx5_vcap_inst *vcaps_cfg;
const struct vcap_info *vcaps;
const struct vcap_statistics *vcap_stats;
@@ -434,6 +440,8 @@ struct sparx5 {
/* Common root for debugfs */
struct dentry *debugfs_root;
const struct sparx5_match_data *data;
+ /* L3 Forwarding */
+ struct sparx5_router *router;
};
/* sparx5_main.c */
@@ -503,6 +511,41 @@ int sparx5_vlan_vid_add(struct sparx5_port *port, u16 vid, bool pvid,
int sparx5_vlan_vid_del(struct sparx5_port *port, u16 vid);
void sparx5_vlan_port_apply(struct sparx5 *sparx5, struct sparx5_port *port);
+/* sparx5_router.c */
+int sparx5_rr_router_init(struct sparx5 *sparx5);
+void sparx5_rr_router_deinit(struct sparx5 *sparx5);
+
+struct sparx5_rr_hw_route {
+ u32 vrule_id;
+ bool vrule_id_valid;
+};
+
+struct sparx5_router {
+ struct sparx5 *sparx5;
+ struct notifier_block fib_nb;
+ struct notifier_block netevent_nb;
+ struct notifier_block inetaddr_nb;
+ struct notifier_block inetaddr_valid_nb;
+ struct notifier_block netdevice_nb;
+ struct notifier_block inet6addr_nb;
+ struct notifier_block inet6addr_valid_nb;
+ struct sparx5_rr_hw_route link_local; /* Trap all link-local traffic. */
+ struct net_device *port_dev; /* For VCAP API. */
+
+ struct list_head fib_lpm4_list;
+ struct list_head fib_lpm6_list;
+ struct mutex lock; /* Global router lock for all shared data. */
+
+ struct workqueue_struct *sparx5_router_owq;
+
+ atomic_t legs_count;
+ struct list_head leg_list;
+ /* Track allocated router leg indices in hw */
+ DECLARE_BITMAP(vmid_mask, SPARX5_ROUTER_LEG_N_VMID);
+ /* Track allocated arp table indices in hw */
+ DECLARE_BITMAP(arp_tbl_mask, SPARX5_ARP_TBL_SIZE);
+};
+
/* sparx5_calendar.c */
int sparx5_calendar_init(struct sparx5 *sparx5);
int sparx5_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_router.c b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
new file mode 100644
index 000000000000..03923d91fdfb
--- /dev/null
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
@@ -0,0 +1,1051 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Microchip Sparx5 Switch driver
+ *
+ * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/inetdevice.h>
+#include <linux/list.h>
+#include <linux/rhashtable.h>
+#include <net/addrconf.h>
+#include <net/arp.h>
+#include <net/fib_notifier.h>
+#include <net/ip6_fib.h>
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/neighbour.h>
+#include <net/netevent.h>
+#include <net/nexthop.h>
+
+#include "sparx5_main.h"
+#include "sparx5_port.h"
+#include "sparx5_vcap_ag_api.h"
+#include "sparx5_vcap_impl.h"
+
+/* The main routing objects are
+ *
+ * 1) Router legs, which correspond to IP interfaces. They can have multiple IPs
+ * attached and are associated with a VLAN. Only routes with nexthops that
+ * egress on ports that are part of a router leg are considered for
+ * offloading.
+ *
+ * 2) Fib entries, which correspond to entries in the routing table.
+ * > ip route add 6.6.0.0/16 nexthop via 1.0.10.10
+ * will create fib entry for 6.6.0.0/16, if it is offloadable.
+ *
+ * 3) Nexthop groups and nexthops. Each fib entry has exactly one nexthop hop.
+ * To simplify the SW representation, we duplicate nexthops and nexthops
+ * groups for each fib entry, even when the same nexthop is used for many
+ * different routes.
+ *
+ * 4) Neigh entries, which correspond to directly connected neighbours.
+ * These are created mainly by ARP events. Their job is to maintain the
+ * association of L3 and L2 addresses.
+ *
+ * The neigh entries are referenced by nexthops and fib entries. They are
+ * shared so we must keep track of the objects that are referencing them.
+ *
+ * If a neighbour which is used in a nexthop group dies, we will set the mac
+ * to zero, so traffic for this nexthop is trapped.
+ *
+ *
+ * Both fib_entry and neigh_entry can trigger writes to the LPM VCAP, and own
+ * entries in HW. Fib entries own the corresponding route associated with them.
+ * Neigh entries own a /32 route, for traffic destined directly to the
+ * neighbour.
+ *
+ * We have 3 main cases for routing:
+ *
+ * 1) Routes for directly connected subnets. E.g. router has IP 1.0.10.1, and
+ * routes subnet 1.0.10.0/24.
+ *
+ * In this case, we have a fib_entry, with a non-gateway nexthop group. We
+ * install a LPM VCAP route, with action type arp entry, for 1.0.10.0/24 with
+ * a zero mac, to ensure frames destined for this subnet will be sent to the
+ * CPU and start the ARP process.
+ *
+ * When we get the arp reply, we create a neigh_entry for each neighbour and
+ * install a direct route in the LPM VCAP for this neighbour. For example,
+ * 1.0.10.11 is sent to DMAC 0x1111110001. This is how we route directly
+ * connected subnets.
+ *
+ * Moreover, the fib_entry maintains a list of all neighbours discovered in
+ * the subnet it is routing. These neighbours hold a reference back to the
+ * fib_entry.
+ *
+ * |-> neigh_entry 1.0.10.10
+ * neigh_list |-> neigh_entry 1.0.10.11
+ * |-> neigh_entry 1.0.10.12
+ * |
+ * +-------------+ | +--------------+
+ * | fib_entry |-+ | nexthop group|
+ * | 1.0.10.1/24 |----+ |
+ * +-------------+ | nexthop --------> NULL (Write zero mac in VCAP)
+ * +--------------+
+ *
+ *
+ * 2) Routes for non-connected subnets. E.g. we are routing subnet 6.6.0.0/16,
+ * but we have no IP in this subnet. We are routing via nexthops, which are
+ * directly connected. Say we have >=2 nexthops.
+ *
+ * In this case, have a fib_entry with a gateway nexthop group. Each nexthop
+ * points to a neigh_entry, corresponding to the gateway used for routing.
+ *
+ * Say we use the nexthops:
+ *
+ * - 1.0.11.10
+ * - 1.0.10.10
+ * - 1.0.9.10
+ *
+ * We install a LPM VCAP route for 6.6.0.0/16, which contains a pointer
+ * to the hw arp table, and the size of the group. The ARP table
+ * contains the Mac addresses of the nexthops. The Mac addresses are
+ * supplied by neigh_entries.
+ *
+ *
+ * +-------------+ +--------------+
+ * | fib_entry | | nexthop group|
+ * | 6.6.0.0/164 |----+ |
+ * +-------------+ | nexthop --------> neigh_entry 1.0.11.10
+ * | nexthop --------> neigh_entry 1.0.10.10
+ * | nexthop --------> neigh_entry 1.0.9.10
+ * +--------------+
+ *
+ *
+ * 3) Local routes for traffic destined to the router. If the router has an IP
+ * 1.0.10.1, then we must ensure this traffic is sent to the CPU.
+ * Therefore, we install a direct route for 1.0.10.1/32 in the LPM VCAP, with
+ * zero mac.
+ *
+ * 4) All IPv6 link-local traffic is explicitly trapped.
+ *
+ * On the hardware side, we use the VCAP LPM and ARP table for UC IPv4
+ * routing. HW picks the match found at the highest address in the VCAP LPM.
+ * To ensure the longest prefix match we make sure to order the entries
+ * according to mask length, with longer masks at higher addresses.
+ *
+ * It is possible to store ARP data, such as DMAC, directly in the VCAP LPM
+ * using ARP entry actions. We do this whenever possible, so the ARP table
+ * is only used when a route has multiple nexthops.
+ *
+ * With the above breakdown in mind, cases 1) and 3) use arp entries, and
+ * case 2) use the arp table if the number of nexthops is >1.
+ *
+ * If the DMAC written to HW is all zero, the chip will trap the frame,
+ * redirecting it to the CPU. This is how we get the kernel to perform ARP
+ * requests on our behalf.
+ *
+ * The nexthop group must be laid out at contiguous addresses in the ARP table.
+ * The VCAP LPM stores a pointer to the bottom address in the group, and the
+ * group size. We do not use the arp pointer remap table.
+ *
+ * The layout of nexthops in a nexthop group matches the layout in HW, e.g.
+ *
+ * nhgi->nexthops[0] -> arp table address n
+ * ...
+ * nhgi->nexthops[k] -> arp table address n+k
+ *
+ * where the n is the ARP table offset (atbl_offset) for the group.
+ */
+
+#define SPARX5_MAX_ECMP_SIZE 16
+#define SPARX5_RLEG_USE_GLOBAL_BASE_MAC 2
+#define SPARX5_LINK_LOCAL_PREFIX_LEN 64
+#define SPARX5_BLACKHOLE_VMID(spx5) ((spx5)->data->consts->vmid_cnt - 1)
+/* The rewriter field REW_RLEG_CTRL_RLEG_EVID will be written with the
+ * blackhole vid, but it is only 12 bits wide. However, this is only used for
+ * routing based rewrites on egress, which is not used for the blackhole.
+ * The important field is ANA_L3_RLEG_CTRL_RLEG_EVID which is 13 bits wide.
+ * Therefore, it is safe to use VIDs wider than 12 bits for the blackhole
+ * vid, frames will not be forwarded into VLAN 0s port mask.
+ */
+#define SPARX5_BLACKHOLE_VID VLAN_N_VID
+
+struct sparx5_rr_fib6_entry_info {
+ struct fib6_info **rt_arr;
+ unsigned int nrt6;
+};
+
+enum sparx5_rr_l3_version {
+ SPARX5_IPV4 = 0,
+ SPARX5_IPV6,
+};
+
+#define SPARX5_IADDR_LEN(v) ((v) == SPARX5_IPV4 ? 32 : 128)
+/* Order longer prefixes at high addresses. */
+#define LPM_SORT_KEY(plen) (SPARX5_IADDR_LEN(SPARX5_IPV6) - (plen))
+
+struct sparx5_rr_fib_info {
+ union {
+ struct fib_entry_notifier_info fen4_info;
+ struct sparx5_rr_fib6_entry_info fe6_info;
+ };
+ enum sparx5_rr_l3_version version;
+};
+
+struct sparx5_fib_event_work {
+ struct work_struct work;
+ struct sparx5_rr_fib_info fi;
+ struct sparx5 *sparx5;
+ unsigned long event;
+};
+
+struct sparx5_rr_netevent_work {
+ struct work_struct work;
+ struct sparx5 *sparx5;
+ struct neighbour *neigh;
+ unsigned long event;
+};
+
+struct sparx5_rr_router_leg {
+ struct net_device *dev;
+ netdevice_tracker dev_tracker;
+ struct sparx5 *sparx5;
+ struct list_head leg_list_node; /* Router member */
+ u16 vmid; /* Internal id */
+ u32 vid; /* VLAN id */
+};
+
+struct sparx5_iaddr {
+ union {
+ __be32 ipv4;
+ struct in6_addr ipv6;
+ }; /* Must be first */
+ enum sparx5_rr_l3_version version;
+};
+
+#define LPM_PROTO(iaddr) ((iaddr)->version ? ETH_P_IPV6 : ETH_P_IP)
+
+struct sparx5_rr_neigh_entry {
+ struct sparx5_rr_neigh_key {
+ struct net_device *dev;
+ struct sparx5_iaddr iaddr;
+ } key;
+ struct rhash_head ht_node;
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct list_head fib_list_node; /* Fib route for this neighbour */
+ struct neigh_table *neigh_tbl; /* Kernel neighbour table */
+ struct list_head nexthop_list; /* Nexthops using this neigh entry */
+ struct sparx5_rr_hw_route hw_route;
+ unsigned char hwaddr[ETH_ALEN];
+ u16 vmid;
+ bool connected;
+};
+
+struct sparx5_rr_nexthop {
+ struct sparx5_rr_neigh_entry *neigh_entry;
+ struct sparx5_rr_nexthop_group *grp;
+ struct list_head neigh_list_node; /* Neigh entry member */
+ struct list_head leg_list_node; /* Router leg member */
+ struct neigh_table *neigh_tbl; /* Kernel neighbour table */
+ struct sparx5_iaddr gw_addr;
+ int ifindex;
+ bool gateway;
+ bool trapped;
+};
+
+struct sparx5_rr_nexthop_group_info {
+ struct sparx5_rr_nexthop_group *grp;
+ u16 atbl_offset;
+ bool atbl_offset_valid;
+ u8 count; /* HW allows up to 16 nexthops */
+ struct sparx5_rr_nexthop nexthops[] __counted_by(count);
+};
+
+struct sparx5_rr_nexthop_group {
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct sparx5_rr_nexthop_group_info *nhgi;
+};
+
+enum sparx5_rr_fib_type {
+ SPARX5_RR_FIB_TYPE_INVALID = 0,
+ SPARX5_RR_FIB_TYPE_LOCAL,
+ SPARX5_RR_FIB_TYPE_UNICAST,
+ SPARX5_RR_FIB_TYPE_MULTICAST,
+ SPARX5_RR_FIB_TYPE_BLACKHOLE,
+ SPARX5_RR_FIB_TYPE_PROHIBIT,
+ SPARX5_RR_FIB_TYPE_UNREACHABLE,
+};
+
+struct sparx5_rr_fib_key {
+ struct sparx5_iaddr addr;
+ u32 prefix_len;
+ u32 tb_id; /* Routing table type: RT_TABLE_* */
+};
+
+struct sparx5_rr_fib_entry {
+ struct sparx5_rr_fib_key key;
+ enum sparx5_rr_fib_type type;
+ struct rhash_head ht_node; /* Router member */
+ struct list_head fib_lpm_node; /* Router member */
+ struct list_head neigh_list; /* Neighbours under this route */
+ struct sparx5_rr_hw_route hw_route;
+ struct sparx5_rr_nexthop_group *nh_grp;
+ struct sparx5_rr_fib_info fi;
+ u64 sort_key; /* For sw lpm lookup */
+ bool trap;
+ bool offload_fail;
+};
+
+struct sparx5_rr_inet6addr_event_work {
+ struct work_struct work;
+ struct sparx5 *sparx5;
+ struct net_device *dev;
+ netdevice_tracker dev_tracker;
+ unsigned long event;
+};
+
+static void sparx5_rr_schedule_work(struct sparx5 *sparx5,
+ struct work_struct *work)
+{
+ queue_work(sparx5->router->sparx5_router_owq, work);
+}
+
+static void sparx5_rr_split_mac(unsigned char mac[ETH_ALEN], u32 split,
+ u32 *msb, u32 *lsb)
+{
+ u32 mask = GENMASK(split - 1, 0);
+ u64 m = ether_addr_to_u64(mac);
+
+ *lsb = m & mask;
+ *msb = m >> split;
+}
+
+static int sparx5_vmid_alloc(struct sparx5 *sparx5)
+{
+ int vmid;
+
+ vmid = find_first_zero_bit(sparx5->router->vmid_mask,
+ sparx5->data->consts->vmid_cnt);
+ if (vmid >= sparx5->data->consts->vmid_cnt)
+ return -ENOMEM;
+
+ set_bit(vmid, sparx5->router->vmid_mask);
+
+ return vmid;
+}
+
+static void sparx5_vmid_free(struct sparx5 *sparx5, u16 vmid)
+{
+ clear_bit(vmid, sparx5->router->vmid_mask);
+}
+
+static void sparx5_rr_inet6_make_mask_le(int logmask, u8 *mask)
+{
+ /* Caller must ensure 0 <= logmask <= 128 */
+ int rem, byte_prefix = logmask;
+
+ rem = do_div(byte_prefix, BITS_PER_BYTE);
+
+ memset(mask, 0, 16);
+
+ for (int i = 0; i < byte_prefix; i++)
+ mask[15 - i] = 0xff;
+
+ if (rem)
+ mask[15 - byte_prefix] = GENMASK(7, 7 - rem + 1);
+}
+
+static int sparx5_rr_lpm_rule_xip_add(struct vcap_rule *rule,
+ struct sparx5_iaddr *addr, u32 prefix_len)
+{
+ struct vcap_u128_key addr_key;
+ u32 mask, iaddr;
+
+ switch (addr->version) {
+ case SPARX5_IPV4:
+ mask = ntohl(inet_make_mask(prefix_len));
+ iaddr = ntohl(addr->ipv4);
+
+ return vcap_rule_add_key_u32(rule, VCAP_KF_IP4_XIP, iaddr,
+ mask);
+ case SPARX5_IPV6:
+ sparx5_rr_inet6_make_mask_le(prefix_len, addr_key.mask);
+ vcap_netbytes_copy(addr_key.value, addr->ipv6.s6_addr, 16);
+
+ return vcap_rule_add_key_u128(rule, VCAP_KF_IP6_XIP, &addr_key);
+ default:
+ WARN_ON(1);
+ return -EINVAL;
+ }
+}
+
+static struct sparx5_rr_router_leg *
+sparx5_rr_leg_find_by_dev(struct sparx5 *sparx5, struct net_device *dev)
+{
+ struct sparx5_rr_router_leg *leg;
+
+ list_for_each_entry(leg, &sparx5->router->leg_list, leg_list_node) {
+ if (leg->dev == dev)
+ return leg;
+ }
+
+ return NULL;
+}
+
+static int
+sparx5_rr_lpm_arp_entry_create(struct sparx5 *sparx5,
+ struct sparx5_iaddr *addr,
+ u32 prefix_len, unsigned char mac[ETH_ALEN],
+ u16 evmid, struct sparx5_rr_hw_route *hw_route)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ u32 priority = LPM_SORT_KEY(prefix_len);
+ struct vcap_rule *rule;
+ u32 mac_msb, mac_lsb;
+ int err;
+
+ sparx5_rr_split_mac(mac, 32, &mac_msb, &mac_lsb);
+
+ rule = vcap_alloc_rule(vctrl, pdev, VCAP_CID_PREROUTING_L0,
+ VCAP_USER_L3, priority, 0);
+ if (IS_ERR(rule))
+ return PTR_ERR(rule);
+
+ err = sparx5_rr_lpm_rule_xip_add(rule, addr, prefix_len);
+ err |= vcap_rule_add_key_u32(rule, VCAP_KF_AFFIX, 0, 0);
+ err |= vcap_rule_add_key_bit(rule, VCAP_KF_DST_FLAG, VCAP_BIT_1);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_MAC_MSB, mac_msb);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_MAC_LSB, mac_lsb);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_ARP_VMID, evmid);
+ err |= vcap_rule_add_action_bit(rule, VCAP_AF_ARP_ENA, VCAP_BIT_1);
+
+ err = err ? -EINVAL : vcap_val_add_rule(rule, LPM_PROTO(addr));
+ if (!err) {
+ hw_route->vrule_id = rule->id;
+ hw_route->vrule_id_valid = true;
+ }
+ vcap_free_rule(rule);
+ return err;
+}
+
+static void sparx5_rr_leg_hw_init(struct sparx5 *sparx5,
+ struct sparx5_rr_router_leg *leg)
+{
+ /* Associate Router leg VMID to VLAN */
+ spx5_rmw(ANA_L3_VMID_CFG_VMID_SET(leg->vmid), ANA_L3_VMID_CFG_VMID,
+ sparx5, ANA_L3_VMID_CFG(leg->vid));
+
+ /* Enable Router leg for VLAN */
+ spx5_rmw(ANA_L3_VLAN_CFG_VLAN_RLEG_ENA_SET(1),
+ ANA_L3_VLAN_CFG_VLAN_RLEG_ENA, sparx5,
+ ANA_L3_VLAN_CFG(leg->vid));
+
+ /* Configure router leg */
+
+#if IS_ENABLED(CONFIG_IPV6)
+ spx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(1) |
+ ANA_L3_RLEG_CTRL_RLEG_EVID_SET(leg->vid) |
+ ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(1),
+ ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |
+ ANA_L3_RLEG_CTRL_RLEG_EVID |
+ ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, sparx5,
+ ANA_L3_RLEG_CTRL(leg->vmid));
+#else
+ spx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(1) |
+ ANA_L3_RLEG_CTRL_RLEG_EVID_SET(leg->vid),
+ ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |
+ ANA_L3_RLEG_CTRL_RLEG_EVID, sparx5,
+ ANA_L3_RLEG_CTRL(leg->vmid));
+#endif
+
+ /* Configure egress VLAN in rewriter */
+ spx5_rmw(REW_RLEG_CTRL_RLEG_EVID_SET(leg->vid), REW_RLEG_CTRL_RLEG_EVID,
+ sparx5, REW_RLEG_CTRL(leg->vmid));
+}
+
+static void sparx5_rr_leg_hw_deinit(struct sparx5 *sparx5,
+ struct sparx5_rr_router_leg *leg)
+{
+ /* Disable Router leg for VLAN */
+ spx5_rmw(ANA_L3_VLAN_CFG_VLAN_RLEG_ENA_SET(0),
+ ANA_L3_VLAN_CFG_VLAN_RLEG_ENA, sparx5,
+ ANA_L3_VLAN_CFG(leg->vid));
+
+ /* Disable IP UC routing on leg */
+ spx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(0) |
+ ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(0),
+ ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |
+ ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, sparx5,
+ ANA_L3_RLEG_CTRL(leg->vmid));
+}
+
+static int sparx5_rr_lpm_link_local_create(struct sparx5 *sparx5)
+{
+ struct sparx5_iaddr addr __aligned(2) = { };
+ unsigned char zero_mac[ETH_ALEN];
+
+ eth_zero_addr(zero_mac);
+
+ /* Trap traffic to fe80::/64 */
+ addr.version = SPARX5_IPV6;
+ addr.ipv6.in6_u.u6_addr8[0] = 0xfe;
+ addr.ipv6.in6_u.u6_addr8[1] = 0x80;
+
+ return sparx5_rr_lpm_arp_entry_create(sparx5, &addr,
+ SPARX5_LINK_LOCAL_PREFIX_LEN,
+ zero_mac, 0,
+ &sparx5->router->link_local);
+}
+
+static void sparx5_rr_lpm_link_local_destroy(struct sparx5 *sparx5)
+{
+ struct sparx5_rr_hw_route *llocal = &sparx5->router->link_local;
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+
+ if (!llocal->vrule_id_valid)
+ return;
+
+ vcap_del_rule(vctrl, pdev, llocal->vrule_id);
+ llocal->vrule_id_valid = false;
+}
+
+static struct sparx5_rr_router_leg *
+__sparx5_rr_leg_alloc(struct sparx5 *sparx5, struct net_device *dev, u16 vmid,
+ u16 vid)
+{
+ struct sparx5_rr_router_leg *leg;
+
+ leg = kzalloc_obj(*leg);
+ if (!leg)
+ return NULL;
+
+ INIT_LIST_HEAD(&leg->leg_list_node);
+ leg->dev = dev;
+ leg->vmid = vmid;
+ leg->vid = vid;
+ leg->sparx5 = sparx5;
+
+ return leg;
+}
+
+/* Router legs are identified by their VMID in hw */
+static struct sparx5_rr_router_leg *
+sparx5_rr_leg_alloc(struct sparx5 *sparx5, struct net_device *dev, u16 vid)
+{
+ struct sparx5_rr_router_leg *leg;
+ int next_vmid;
+
+ next_vmid = sparx5_vmid_alloc(sparx5);
+ if (next_vmid < 0)
+ return NULL;
+
+ leg = __sparx5_rr_leg_alloc(sparx5, dev, next_vmid, vid);
+ if (!leg)
+ goto err_kzalloc;
+
+ return leg;
+
+err_kzalloc:
+ sparx5_vmid_free(sparx5, next_vmid);
+
+ return NULL;
+}
+
+static void sparx5_rr_router_leg_destroy(struct sparx5_rr_router_leg *leg)
+{
+ struct sparx5 *sparx5 = leg->sparx5;
+
+ dev_dbg(sparx5->dev, "Leg destroy vid=%u vmid=%u dev=%s\n", leg->vid,
+ leg->vmid, leg->dev ? netdev_name(leg->dev) : "blackhole");
+
+ sparx5_rr_leg_hw_deinit(sparx5, leg);
+ sparx5_vmid_free(leg->sparx5, leg->vmid);
+ list_del(&leg->leg_list_node);
+
+ if (leg->dev) {
+ if (atomic_dec_return(&sparx5->router->legs_count) == 0)
+ sparx5_rr_lpm_link_local_destroy(sparx5);
+
+ netdev_put(leg->dev, &leg->dev_tracker);
+ }
+ kfree(leg);
+}
+
+static struct sparx5_rr_router_leg *
+sparx5_rr_router_leg_create(struct sparx5 *sparx5, struct net_device *dev,
+ u16 vid)
+{
+ struct sparx5_rr_router_leg *leg;
+
+ leg = sparx5_rr_leg_alloc(sparx5, dev, vid);
+ if (!leg)
+ return ERR_PTR(-ENOMEM);
+
+ /* Prevent net device from being freed while we have added it to a
+ * router leg.
+ */
+ netdev_hold(dev, &leg->dev_tracker, GFP_KERNEL);
+
+ /* While a router leg exists, add route to trap link-local traffic. */
+ if (atomic_inc_return(&sparx5->router->legs_count) == 1) {
+ if (sparx5_rr_lpm_link_local_create(sparx5))
+ dev_warn(sparx5->dev,
+ "Failed to create link-local route\n");
+ }
+
+ list_add(&leg->leg_list_node, &sparx5->router->leg_list);
+ sparx5_rr_leg_hw_init(sparx5, leg);
+
+ dev_dbg(sparx5->dev, "Leg create dev=%s vid=%u vmid=%u\n", dev->name,
+ leg->vid, leg->vmid);
+
+ return leg;
+}
+
+static bool sparx5_rr_dev_real_is_vlan_aware(struct net_device *dev)
+{
+ struct net_device *vlan_rdev;
+ /* Support l3 offloading for:
+ * 1) upper vlan interfaces for the bridge.
+ */
+ if (is_vlan_dev(dev)) {
+ if (netif_is_bridge_port(dev))
+ return false;
+
+ vlan_rdev = vlan_dev_real_dev(dev);
+ if (sparx5_netdevice_check(vlan_rdev))
+ return false;
+
+ return netif_is_bridge_master(vlan_rdev) &&
+ br_vlan_enabled(vlan_rdev);
+ }
+
+ return false;
+}
+
+static void sparx5_rr_leg_base_mac_set(struct sparx5 *sparx5,
+ unsigned char mac[ETH_ALEN])
+{
+ u8 rleg_type_sel = SPARX5_RLEG_USE_GLOBAL_BASE_MAC;
+ u32 mac_msb, mac_lsb;
+
+ sparx5_rr_split_mac(mac, 24, &mac_msb, &mac_lsb);
+
+ dev_dbg(sparx5->dev, "Router leg base MAC=%pM\n", mac);
+
+ /* The global router leg MAC must be set consistently across ANA_L3, REW
+ * and EACL.
+ */
+ spx5_wr(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,
+ ANA_L3_RLEG_CFG_0);
+
+ spx5_rmw(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |
+ ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),
+ ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB |
+ ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,
+ sparx5, ANA_L3_RLEG_CFG_1);
+
+ /* Set global Router leg MAC (REW) */
+ spx5_wr(REW_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,
+ REW_RLEG_CFG_0);
+
+ spx5_rmw(REW_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |
+ REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),
+ REW_RLEG_CFG_1_RLEG_MAC_MSB | REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,
+ sparx5, REW_RLEG_CFG_1);
+
+ /* Set global Router leg MAC (EACL) */
+ spx5_wr(EACL_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,
+ EACL_RLEG_CFG_0);
+
+ spx5_rmw(EACL_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |
+ EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),
+ EACL_RLEG_CFG_1_RLEG_MAC_MSB |
+ EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,
+ sparx5, EACL_RLEG_CFG_1);
+}
+
+static bool
+sparx5_rr_router_leg_addr_list_empty_rcu(struct sparx5_rr_router_leg *leg)
+{
+ struct inet6_dev *inet6_dev;
+ struct in_device *in_dev;
+
+ in_dev = __in_dev_get_rcu(leg->dev);
+ if (in_dev && in_dev->ifa_list)
+ return false;
+
+ inet6_dev = __in6_dev_get(leg->dev);
+ if (inet6_dev && !list_empty(&inet6_dev->addr_list))
+ return false;
+
+ return true;
+}
+
+static bool
+sparx5_rr_router_leg_addr_list_empty(struct sparx5_rr_router_leg *leg)
+{
+ bool addr_list_empty;
+
+ rcu_read_lock();
+ addr_list_empty = sparx5_rr_router_leg_addr_list_empty_rcu(leg);
+ rcu_read_unlock();
+
+ return addr_list_empty;
+}
+
+static int __sparx5_rr_inetaddr_event(struct sparx5 *sparx5,
+ struct net_device *dev,
+ unsigned long event)
+{
+ struct sparx5_rr_router_leg *leg;
+ u16 vid;
+
+ if (!sparx5_rr_dev_real_is_vlan_aware(dev))
+ return 0;
+
+ /* Our basic case: ip addr/subnet added to vlan upper of
+ * bridge dev.
+ */
+ switch (event) {
+ case NETDEV_UP:
+ leg = sparx5_rr_leg_find_by_dev(sparx5, dev);
+ if (leg)
+ return 0;
+
+ /* HW allows at most 1 leg per VLAN, but we do not need to
+ * lookup leg by vid, since the kernel does not allow multiple
+ * vlan devs with the same vid on top of a given device.
+ */
+ vid = vlan_dev_vlan_id(dev);
+
+ leg = sparx5_rr_router_leg_create(sparx5, dev, vid);
+ if (IS_ERR(leg))
+ return PTR_ERR(leg);
+ break;
+ case NETDEV_DOWN:
+ leg = sparx5_rr_leg_find_by_dev(sparx5, dev);
+ if (!leg || !sparx5_rr_router_leg_addr_list_empty(leg))
+ return 0;
+
+ sparx5_rr_router_leg_destroy(leg);
+ break;
+ }
+
+ return 0;
+}
+
+static int sparx5_rr_inetaddr_event_handle(struct sparx5 *sparx5,
+ struct net_device *dev,
+ unsigned long event)
+{
+ int err;
+
+ mutex_lock(&sparx5->router->lock);
+ err = __sparx5_rr_inetaddr_event(sparx5, dev, event);
+ mutex_unlock(&sparx5->router->lock);
+
+ return notifier_from_errno(err);
+}
+
+/* Called with RTNL. */
+static int sparx5_rr_inet6addr_valid_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct in6_validator_info *i6vi = (struct in6_validator_info *)ptr;
+ struct net_device *dev = i6vi->i6vi_dev->dev;
+ struct sparx5_router *router;
+
+ ASSERT_RTNL();
+
+ if (event != NETDEV_UP)
+ return NOTIFY_DONE;
+
+ router = container_of(nb, struct sparx5_router, inet6addr_valid_nb);
+
+ return sparx5_rr_inetaddr_event_handle(router->sparx5, dev, event);
+}
+
+static void sparx5_rr_inet6addr_event_work(struct work_struct *work)
+{
+ struct sparx5_rr_inet6addr_event_work *addr_work =
+ container_of(work, struct sparx5_rr_inet6addr_event_work, work);
+ struct sparx5_router *router = addr_work->sparx5->router;
+
+ rtnl_lock();
+ mutex_lock(&router->lock);
+
+ __sparx5_rr_inetaddr_event(addr_work->sparx5, addr_work->dev,
+ addr_work->event);
+
+ mutex_unlock(&router->lock);
+ rtnl_unlock();
+ netdev_put(addr_work->dev, &addr_work->dev_tracker);
+ kfree(addr_work);
+}
+
+/* Called in atomic context. */
+static int sparx5_rr_inet6addr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct inet6_ifaddr *if6 = (struct inet6_ifaddr *)ptr;
+ struct sparx5_rr_inet6addr_event_work *work;
+ struct net_device *dev = if6->idev->dev;
+ struct sparx5_router *router;
+
+ if (event != NETDEV_DOWN)
+ return NOTIFY_DONE;
+
+ work = kzalloc_obj(*work, GFP_ATOMIC);
+ if (!work)
+ return NOTIFY_BAD;
+
+ router = container_of(nb, struct sparx5_router, inet6addr_nb);
+ INIT_WORK(&work->work, sparx5_rr_inet6addr_event_work);
+ work->sparx5 = router->sparx5;
+ work->dev = dev;
+ work->event = event;
+ netdev_hold(dev, &work->dev_tracker, GFP_ATOMIC);
+ sparx5_rr_schedule_work(router->sparx5, &work->work);
+
+ return NOTIFY_DONE;
+}
+
+/* Handle events for ip address changes on ifs. Used to manage router legs.
+ * Called with RTNL.
+ */
+static int sparx5_rr_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+ struct net_device *dev = ifa->ifa_dev->dev;
+ struct sparx5_router *router;
+
+ ASSERT_RTNL();
+
+ if (event != NETDEV_DOWN)
+ return NOTIFY_DONE;
+
+ router = container_of(nb, struct sparx5_router, inetaddr_nb);
+
+ return sparx5_rr_inetaddr_event_handle(router->sparx5, dev, event);
+}
+
+/* Called with RTNL. */
+static int sparx5_rr_inetaddr_valid_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct in_validator_info *ivi = (struct in_validator_info *)ptr;
+ struct net_device *dev = ivi->ivi_dev->dev;
+ struct sparx5_router *router;
+
+ ASSERT_RTNL();
+
+ if (event != NETDEV_UP)
+ return NOTIFY_DONE;
+
+ router = container_of(nb, struct sparx5_router, inetaddr_valid_nb);
+
+ return sparx5_rr_inetaddr_event_handle(router->sparx5, dev, event);
+}
+
+/* Called with RTNL. */
+static int sparx5_rr_netdevice_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ struct sparx5_router *router;
+ struct sparx5 *sparx5;
+
+ ASSERT_RTNL();
+
+ router = container_of(nb, struct sparx5_router, netdevice_nb);
+ sparx5 = router->sparx5;
+
+ /* Allow single bridge. Global router leg MAC tracks bridge mac. */
+ if (!netif_is_bridge_master(dev))
+ return NOTIFY_OK;
+
+ switch (event) {
+ case NETDEV_CHANGEADDR:
+ ether_addr_copy(mac, dev->dev_addr);
+ sparx5_rr_leg_base_mac_set(sparx5, mac);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int sparx5_rr_blackhole_leg_create(struct sparx5 *sparx5)
+{
+ struct sparx5_rr_router_leg *leg;
+ u16 vmid, vid;
+
+ vmid = SPARX5_BLACKHOLE_VMID(sparx5);
+ vid = SPARX5_BLACKHOLE_VID;
+
+ leg = __sparx5_rr_leg_alloc(sparx5, NULL, vmid, vid);
+ if (!leg)
+ return -ENOMEM;
+
+ set_bit(vmid, sparx5->router->vmid_mask);
+
+ list_add(&leg->leg_list_node, &sparx5->router->leg_list);
+ sparx5_rr_leg_hw_init(sparx5, leg);
+
+ dev_dbg(sparx5->dev, "Blackhole leg create vid=%u vmid=%u\n",
+ leg->vid, leg->vmid);
+
+ return 0;
+}
+
+static void sparx5_rr_router_legs_flush(struct sparx5 *sparx5)
+{
+ struct sparx5_rr_router_leg *leg, *tmp;
+
+ list_for_each_entry_safe(leg, tmp, &sparx5->router->leg_list,
+ leg_list_node)
+ sparx5_rr_router_leg_destroy(leg);
+}
+
+int sparx5_rr_router_init(struct sparx5 *sparx5)
+{
+ struct sparx5_router *r;
+ int err;
+
+ r = kzalloc_obj(*sparx5->router);
+ if (!r)
+ return -ENOMEM;
+
+ mutex_init(&r->lock);
+ sparx5->router = r;
+ r->sparx5 = sparx5;
+
+ INIT_LIST_HEAD(&r->leg_list);
+ INIT_LIST_HEAD(&r->fib_lpm4_list);
+ INIT_LIST_HEAD(&r->fib_lpm6_list);
+
+ /* Add reserved leg for blackhole routes. */
+ err = sparx5_rr_blackhole_leg_create(sparx5);
+ if (err)
+ goto err_free_router;
+
+ r->sparx5_router_owq = alloc_ordered_workqueue("sparx5_router_owq", 0);
+ if (!r->sparx5_router_owq) {
+ err = -ENOMEM;
+ goto err_blackhole_destroy;
+ }
+
+ atomic_set(&r->legs_count, 0);
+ r->link_local.vrule_id = 0;
+ r->link_local.vrule_id_valid = false;
+ /* VCAP API requires a port net_device, to get a sparx5 reference.
+ * Fetch any valid port.
+ */
+ for (int i = 0; i < sparx5->data->consts->n_ports; i++) {
+ if (!sparx5->ports[i])
+ continue;
+
+ r->port_dev = sparx5->ports[i]->ndev;
+ if (r->port_dev)
+ break;
+ }
+ if (!r->port_dev) {
+ err = -ENXIO;
+ goto err_workqueue_destroy;
+ }
+
+ /* Enable L3 UC routing on all ports. */
+ spx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA);
+ if (is_sparx5(sparx5)) {
+ spx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA1);
+ spx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA2);
+ }
+
+ /* Enable routing and global router options */
+ spx5_rmw(ANA_L3_ROUTING_CFG_L3_ENA_MODE_SET(1) |
+ ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_SET(1),
+ ANA_L3_ROUTING_CFG_L3_ENA_MODE |
+ ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA |
+ ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA |
+ ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA |
+ ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA |
+ ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA |
+ ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA,
+ sparx5, ANA_L3_ROUTING_CFG);
+
+ /* By default, routing related frame edits are done in REW, but when
+ * combining routing with PTP, ANA_ACL must be configured to change DMAC
+ * to next-hop DMAC in order to allow other information to be stored in
+ * the IFH.
+ *
+ * This enables routing related frame edits independently of VCAP_S2
+ * action ACL_RT_MODE.
+ */
+ spx5_rmw(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_SET(1),
+ ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, sparx5,
+ ANA_ACL_VCAP_S2_MISC_CTRL);
+
+ r->inetaddr_nb.notifier_call = sparx5_rr_inetaddr_event;
+ err = register_inetaddr_notifier(&r->inetaddr_nb);
+ if (err)
+ goto err_workqueue_destroy;
+
+ r->inetaddr_valid_nb.notifier_call = sparx5_rr_inetaddr_valid_event;
+ err = register_inetaddr_validator_notifier(&r->inetaddr_valid_nb);
+ if (err)
+ goto err_unreg_inet_notifier;
+
+ r->netdevice_nb.notifier_call = sparx5_rr_netdevice_event;
+ err = register_netdevice_notifier(&r->netdevice_nb);
+ if (err)
+ goto err_unreg_inet_addr_val_notifier;
+
+ r->inet6addr_valid_nb.notifier_call = sparx5_rr_inet6addr_valid_event;
+ err = register_inet6addr_validator_notifier(&r->inet6addr_valid_nb);
+ if (err)
+ goto err_unreg_netdev_notifier;
+
+ r->inet6addr_nb.notifier_call = sparx5_rr_inet6addr_event;
+ err = register_inet6addr_notifier(&r->inet6addr_nb);
+ if (err)
+ goto err_unreg_inet6_addr_val_notifier;
+
+ return 0;
+
+err_unreg_inet6_addr_val_notifier:
+ unregister_inet6addr_validator_notifier(&r->inet6addr_valid_nb);
+err_unreg_netdev_notifier:
+ unregister_netdevice_notifier(&r->netdevice_nb);
+err_unreg_inet_addr_val_notifier:
+ unregister_inetaddr_validator_notifier(&r->inetaddr_valid_nb);
+err_unreg_inet_notifier:
+ unregister_inetaddr_notifier(&r->inetaddr_nb);
+err_workqueue_destroy:
+ destroy_workqueue(r->sparx5_router_owq);
+err_blackhole_destroy:
+ sparx5_rr_router_legs_flush(sparx5);
+err_free_router:
+ mutex_destroy(&r->lock);
+ kfree(r);
+
+ return err;
+}
+
+void sparx5_rr_router_deinit(struct sparx5 *sparx5)
+{
+ struct sparx5_router *router = sparx5->router;
+
+ unregister_inet6addr_notifier(&router->inet6addr_nb);
+ unregister_inet6addr_validator_notifier(&router->inet6addr_valid_nb);
+ unregister_netdevice_notifier(&router->netdevice_nb);
+ unregister_inetaddr_validator_notifier(&router->inetaddr_valid_nb);
+ unregister_inetaddr_notifier(&router->inetaddr_nb);
+ destroy_workqueue(router->sparx5_router_owq);
+ sparx5_rr_router_legs_flush(sparx5);
+ mutex_destroy(&router->lock);
+ kfree(router);
+}
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 6/9] net: sparx5: vcap: add lpm vcap implementation
From: Jens Emil Schulz Østergaard @ 2026-06-12 12:37 UTC (permalink / raw)
To: Horatiu Vultur, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Daniel Machon,
Steen Hegelund, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-arm-kernel, linux-hardening,
Jens Emil Schulz Østergaard
In-Reply-To: <20260612-sparx5_l3_routing-v1-0-fc3c10160f49@microchip.com>
Add the implementation for the LPM VCAP for lan969x and sparx5. The LPM
VCAP has a static keyset configuration, and a single lookup dedicated to
LPM, so this is straight forward, and many functions can use existing
functionality.
The LPM VCAP is allocated from the Super VCAP Blocks, which is shared
amongst different VCAPs. All blocks are used, so we steal a block from
IS0, which had 2 assigned. Since we statically assign blocks, this means
IS0 has half the available address space for vcap rules, compared to
before.
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
.../microchip/sparx5/lan969x/lan969x_vcap_impl.c | 12 ++
.../ethernet/microchip/sparx5/sparx5_vcap_impl.c | 159 ++++++++++++++++++++-
.../ethernet/microchip/sparx5/sparx5_vcap_impl.h | 5 +
3 files changed, 174 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c
index 543a1f2bf6bd..6e6a1f833dea 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c
@@ -82,4 +82,16 @@ const struct sparx5_vcap_inst lan969x_vcap_inst_cfg[] = {
.count = 1024,
.ingress = false,
},
+ {
+ .vtype = VCAP_TYPE_LPM,
+ .vinst = 0,
+ .map_id = 6,
+ .lookups = SPARX5_LPM_LOOKUPS,
+ .lookups_per_instance = SPARX5_LPM_LOOKUPS,
+ .first_cid = SPARX5_VCAP_CID_LPM_L0,
+ .last_cid = SPARX5_VCAP_CID_LPM_MAX,
+ .blockno = 5,
+ .blocks = 1,
+ .ingress = true,
+ },
};
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
index 95b93e46a41d..e25e759c24f6 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
@@ -54,8 +54,8 @@ const struct sparx5_vcap_inst sparx5_vcap_inst_cfg[] = {
.lookups_per_instance = SPARX5_IS0_LOOKUPS / 3,
.first_cid = SPARX5_VCAP_CID_IS0_L0,
.last_cid = SPARX5_VCAP_CID_IS0_L2 - 1,
- .blockno = 8, /* Maps block 8-9 */
- .blocks = 2,
+ .blockno = 8, /* Maps block 8 */
+ .blocks = 1,
.ingress = true,
},
{
@@ -124,6 +124,18 @@ const struct sparx5_vcap_inst sparx5_vcap_inst_cfg[] = {
.count = 12288, /* Addresses according to datasheet */
.ingress = false,
},
+ {
+ .vtype = VCAP_TYPE_LPM,
+ .vinst = 0,
+ .map_id = 6,
+ .lookups = SPARX5_LPM_LOOKUPS,
+ .lookups_per_instance = SPARX5_LPM_LOOKUPS,
+ .first_cid = SPARX5_VCAP_CID_LPM_L0,
+ .last_cid = SPARX5_VCAP_CID_LPM_MAX,
+ .blockno = 9,
+ .blocks = 1,
+ .ingress = true,
+ },
};
/* These protocols have dedicated keysets in IS0 and a TC dissector */
@@ -149,6 +161,12 @@ static u16 sparx5_vcap_es2_known_etypes[] = {
ETH_P_IPV6,
};
+static u16 sparx5_vcap_lpm_known_etypes[] = {
+ ETH_P_ALL,
+ ETH_P_IP,
+ ETH_P_IPV6,
+};
+
static void sparx5_vcap_type_err(struct sparx5 *sparx5,
struct vcap_admin *admin,
const char *fname)
@@ -195,6 +213,7 @@ static void _sparx5_vcap_range_init(struct sparx5 *sparx5,
u32 size = count - 1;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
spx5_wr(VCAP_SUPER_CFG_MV_NUM_POS_SET(0) |
@@ -386,6 +405,11 @@ static int sparx5_vcap_es2_cid_to_lookup(int cid)
return lookup;
}
+static int sparx5_vcap_lpm_cid_to_lookup(int cid)
+{
+ return 0;
+}
+
/* Add ethernet type IS0 keyset to a list */
static void
sparx5_vcap_is0_get_port_etype_keysets(struct vcap_keyset_list *keysetlist,
@@ -401,6 +425,28 @@ sparx5_vcap_is0_get_port_etype_keysets(struct vcap_keyset_list *keysetlist,
}
}
+static int sparx5_vcap_lpm_get_port_keysets(struct net_device *ndev,
+ int lookup,
+ struct vcap_keyset_list *keysetlist,
+ u16 l3_proto)
+{
+ /* LPM keysets are static. */
+ if (l3_proto == ETH_P_ALL) {
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP4);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP4);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP6);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP6);
+ } else if (l3_proto == ETH_P_IP) {
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP4);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP4);
+ } else if (l3_proto == ETH_P_IPV6) {
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP6);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP6);
+ }
+
+ return 0;
+}
+
/* Return the list of keysets for the vcap port configuration */
static int sparx5_vcap_is0_get_port_keysets(struct net_device *ndev,
int lookup,
@@ -683,6 +729,11 @@ int sparx5_vcap_get_port_keyset(struct net_device *ndev,
struct sparx5_port *port;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ lookup = sparx5_vcap_lpm_cid_to_lookup(cid);
+ err = sparx5_vcap_lpm_get_port_keysets(ndev, lookup, kslist,
+ l3_proto);
+ break;
case VCAP_TYPE_IS0:
lookup = sparx5_vcap_is0_cid_to_lookup(cid);
err = sparx5_vcap_is0_get_port_keysets(ndev, lookup, kslist,
@@ -716,6 +767,10 @@ bool sparx5_vcap_is_known_etype(struct vcap_admin *admin, u16 etype)
int size, idx;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ known_etypes = sparx5_vcap_lpm_known_etypes;
+ size = ARRAY_SIZE(sparx5_vcap_lpm_known_etypes);
+ break;
case VCAP_TYPE_IS0:
known_etypes = sparx5_vcap_is0_known_etypes;
size = ARRAY_SIZE(sparx5_vcap_is0_known_etypes);
@@ -760,6 +815,11 @@ sparx5_vcap_validate_keyset(struct net_device *ndev,
/* Get a list of currently configured keysets in the lookups */
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ lookup = sparx5_vcap_lpm_cid_to_lookup(rule->vcap_chain_id);
+ sparx5_vcap_lpm_get_port_keysets(ndev, lookup, &keysetlist,
+ l3_proto);
+ break;
case VCAP_TYPE_IS0:
lookup = sparx5_vcap_is0_cid_to_lookup(rule->vcap_chain_id);
sparx5_vcap_is0_get_port_keysets(ndev, lookup, &keysetlist,
@@ -873,6 +933,9 @@ static void sparx5_vcap_add_default_fields(struct net_device *ndev,
/* add the lookup bit */
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ /* LPM VCAP has no default fields */
+ break;
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
sparx5_vcap_ingress_add_default_fields(ndev, admin, rule);
@@ -1083,6 +1146,46 @@ static void sparx5_vcap_es2_cache_write(struct sparx5 *sparx5,
}
}
+static void sparx5_vcap_lpm_cache_write(struct sparx5 *sparx5,
+ struct vcap_admin *admin,
+ enum vcap_selection sel,
+ u32 start,
+ u32 count)
+{
+ u32 *keystr, *mskstr, *actstr;
+ int idx;
+
+ keystr = &admin->cache.keystream[start];
+ mskstr = &admin->cache.maskstream[start];
+ actstr = &admin->cache.actionstream[start];
+
+ switch (sel) {
+ case VCAP_SEL_ENTRY:
+ for (idx = 0; idx < count; ++idx) {
+ /* Avoid 'match-off' by setting value & mask */
+ spx5_wr(keystr[idx] & mskstr[idx], sparx5,
+ VCAP_SUPER_VCAP_ENTRY_DAT(idx));
+ spx5_wr(~mskstr[idx], sparx5,
+ VCAP_SUPER_VCAP_MASK_DAT(idx));
+ }
+ break;
+ case VCAP_SEL_ACTION:
+ for (idx = 0; idx < count; ++idx)
+ spx5_wr(actstr[idx], sparx5,
+ VCAP_SUPER_VCAP_ACTION_DAT(idx));
+ break;
+ case VCAP_SEL_ALL:
+ pr_err("%s:%d: cannot write all streams at once\n", __func__,
+ __LINE__);
+ break;
+ default:
+ break;
+ }
+ if (sel & VCAP_SEL_COUNTER)
+ spx5_wr(admin->cache.counter, sparx5,
+ VCAP_SUPER_VCAP_CNT_DAT(0));
+}
+
/* API callback used for writing to the VCAP cache */
static void sparx5_vcap_cache_write(struct net_device *ndev,
struct vcap_admin *admin,
@@ -1094,6 +1197,9 @@ static void sparx5_vcap_cache_write(struct net_device *ndev,
struct sparx5 *sparx5 = port->sparx5;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ sparx5_vcap_lpm_cache_write(sparx5, admin, sel, start, count);
+ break;
case VCAP_TYPE_IS0:
sparx5_vcap_is0_cache_write(sparx5, admin, sel, start, count);
break;
@@ -1273,6 +1379,41 @@ static void sparx5_vcap_es2_cache_read(struct sparx5 *sparx5,
}
}
+static void sparx5_vcap_lpm_cache_read(struct sparx5 *sparx5,
+ struct vcap_admin *admin,
+ enum vcap_selection sel,
+ u32 start,
+ u32 count)
+{
+ u32 *keystr, *mskstr, *actstr;
+ int idx;
+
+ keystr = &admin->cache.keystream[start];
+ mskstr = &admin->cache.maskstream[start];
+ actstr = &admin->cache.actionstream[start];
+
+ if (sel & VCAP_SEL_ENTRY) {
+ for (idx = 0; idx < count; ++idx) {
+ keystr[idx] = spx5_rd(sparx5,
+ VCAP_SUPER_VCAP_ENTRY_DAT(idx));
+ mskstr[idx] = ~spx5_rd(sparx5,
+ VCAP_SUPER_VCAP_MASK_DAT(idx));
+ }
+ }
+
+ if (sel & VCAP_SEL_ACTION)
+ for (idx = 0; idx < count; ++idx)
+ actstr[idx] = spx5_rd(sparx5,
+ VCAP_SUPER_VCAP_ACTION_DAT(idx));
+
+ if (sel & VCAP_SEL_COUNTER) {
+ u32 val = spx5_rd(sparx5, VCAP_SUPER_VCAP_CNT_DAT(0));
+
+ admin->cache.counter = val;
+ admin->cache.sticky = !!val;
+ }
+}
+
/* API callback used for reading from the VCAP into the VCAP cache */
static void sparx5_vcap_cache_read(struct net_device *ndev,
struct vcap_admin *admin,
@@ -1284,6 +1425,9 @@ static void sparx5_vcap_cache_read(struct net_device *ndev,
struct sparx5 *sparx5 = port->sparx5;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ sparx5_vcap_lpm_cache_read(sparx5, admin, sel, start, count);
+ break;
case VCAP_TYPE_IS0:
sparx5_vcap_is0_cache_read(sparx5, admin, sel, start, count);
break;
@@ -1379,6 +1523,7 @@ static void sparx5_vcap_update(struct net_device *ndev,
struct sparx5 *sparx5 = port->sparx5;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
sparx5_vcap_super_update(sparx5, cmd, sel, addr);
@@ -1475,6 +1620,7 @@ static void sparx5_vcap_move(struct net_device *ndev, struct vcap_admin *admin,
}
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
sparx5_vcap_super_move(sparx5, addr, cmd, mv_num_pos, mv_size);
@@ -1743,6 +1889,8 @@ void sparx5_vcap_set_port_keyset(struct net_device *ndev,
int lookup;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ break;
case VCAP_TYPE_IS0:
lookup = sparx5_vcap_is0_cid_to_lookup(cid);
if (orig)
@@ -1879,6 +2027,9 @@ static void sparx5_vcap_port_key_selection(struct sparx5 *sparx5,
case VCAP_TYPE_ES2:
sparx5_vcap_es2_port_key_selection(sparx5, admin);
break;
+ case VCAP_TYPE_LPM:
+ /* VCAP LPM key selection is static */
+ break;
default:
sparx5_vcap_type_err(sparx5, admin, __func__);
break;
@@ -1920,6 +2071,9 @@ static void sparx5_vcap_port_key_deselection(struct sparx5 *sparx5,
sparx5,
EACL_VCAP_ES2_KEY_SEL(portno, lookup));
break;
+ case VCAP_TYPE_LPM:
+ /* LPM key selection is static */
+ break;
default:
sparx5_vcap_type_err(sparx5, admin, __func__);
break;
@@ -1980,6 +2134,7 @@ static void sparx5_vcap_block_alloc(struct sparx5 *sparx5,
int idx, cores;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
/* Super VCAP block mapping and address configuration. Block 0
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h
index d0a42406bf26..94c77a60e4f8 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h
@@ -20,6 +20,7 @@
#define SPARX5_IS0_LOOKUPS 6
#define SPARX5_ES0_LOOKUPS 1
#define SPARX5_ES2_LOOKUPS 2
+#define SPARX5_LPM_LOOKUPS 1
#define SPARX5_VCAP_CID_IS0_L0 VCAP_CID_INGRESS_L0 /* IS0/CLM lookup 0 */
#define SPARX5_VCAP_CID_IS0_L1 VCAP_CID_INGRESS_L1 /* IS0/CLM lookup 1 */
@@ -30,6 +31,10 @@
#define SPARX5_VCAP_CID_IS0_MAX \
(VCAP_CID_INGRESS_L5 + VCAP_CID_LOOKUP_SIZE - 1) /* IS0/CLM Max */
+#define SPARX5_VCAP_CID_LPM_L0 VCAP_CID_PREROUTING_L0 /* LPM lookup 0 */
+#define SPARX5_VCAP_CID_LPM_MAX \
+ (VCAP_CID_PREROUTING_L0 + VCAP_CID_LOOKUP_SIZE - 1) /* LPM Max */
+
#define SPARX5_VCAP_CID_IS2_L0 VCAP_CID_INGRESS_STAGE2_L0 /* IS2 lookup 0 */
#define SPARX5_VCAP_CID_IS2_L1 VCAP_CID_INGRESS_STAGE2_L1 /* IS2 lookup 1 */
#define SPARX5_VCAP_CID_IS2_L2 VCAP_CID_INGRESS_STAGE2_L2 /* IS2 lookup 2 */
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 5/9] net: sparx5: add l3 routing registers
From: Jens Emil Schulz Østergaard @ 2026-06-12 12:37 UTC (permalink / raw)
To: Horatiu Vultur, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Daniel Machon,
Steen Hegelund, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-arm-kernel, linux-hardening,
Jens Emil Schulz Østergaard
In-Reply-To: <20260612-sparx5_l3_routing-v1-0-fc3c10160f49@microchip.com>
Add autogenerated register macros for L3 routing on lan969x and sparx5.
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
.../microchip/sparx5/lan969x/lan969x_regs.c | 20 +-
.../ethernet/microchip/sparx5/sparx5_main_regs.h | 691 ++++++++++++++++++++-
.../net/ethernet/microchip/sparx5/sparx5_regs.c | 20 +-
.../net/ethernet/microchip/sparx5/sparx5_regs.h | 20 +-
4 files changed, 739 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
index ace4ba21eec4..63a6253a1218 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip lan969x Switch driver
*
- * Copyright (c) 2024 Microchip Technology Inc.
+ * Copyright (c) 2026 Microchip Technology Inc.
*/
-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
+/* This file is autogenerated by cml-utils 2026-02-16 21:50:29 +0100.
+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2
*/
#include "lan969x.h"
@@ -77,12 +77,16 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = {
[GA_ANA_CL_COMMON] = 87040,
[GA_ANA_L2_COMMON] = 561928,
[GA_ANA_L3_COMMON] = 370752,
+ [GA_ANA_L3_VMID] = 360448,
+ [GA_ANA_L3_ARP_PTR_REMAP] = 370944,
+ [GA_ANA_L3_ARP] = 294912,
[GA_ANA_L3_VLAN_ARP_L3MC_STICKY] = 368580,
[GA_ASM_CFG] = 18304,
[GA_ASM_PFC_TIMER_CFG] = 15568,
[GA_ASM_LBK_WM_CFG] = 15596,
[GA_ASM_LBK_MISC_CFG] = 15608,
[GA_ASM_RAM_CTRL] = 15684,
+ [GA_EACL_COMMON] = 37408,
[GA_EACL_ES2_KEY_SELECT_PROFILE] = 36864,
[GA_EACL_CNT_TBL] = 30720,
[GA_EACL_POL_CFG] = 38400,
@@ -102,6 +106,7 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = {
[GA_QSYS_RAM_CTRL] = 2204,
[GA_REW_COMMON] = 98304,
[GA_REW_PORT] = 49152,
+ [GA_REW_VMID] = 96768,
[GA_REW_VOE_PORT_LM_CNT] = 90112,
[GA_REW_RAM_CTRL] = 93992,
[GA_VOP_RAM_CTRL] = 16368,
@@ -123,6 +128,8 @@ const unsigned int lan969x_gcnt[GCNT_LAST] = {
[GC_ANA_L2_ISDX_LIMIT] = 256,
[GC_ANA_L2_ISDX] = 1024,
[GC_ANA_L3_VLAN] = 4608,
+ [GC_ANA_L3_VMID] = 127,
+ [GC_ANA_L3_ARP] = 1024,
[GC_ASM_DEV_STATISTICS] = 30,
[GC_EACL_ES2_KEY_SELECT_PROFILE] = 68,
[GC_EACL_CNT_TBL] = 512,
@@ -132,6 +139,7 @@ const unsigned int lan969x_gcnt[GCNT_LAST] = {
[GC_PTP_PTP_PINS] = 8,
[GC_PTP_PHASE_DETECTOR_CTRL] = 8,
[GC_REW_PORT] = 35,
+ [GC_REW_VMID] = 127,
[GC_REW_VOE_PORT_LM_CNT] = 240,
};
@@ -188,7 +196,12 @@ const unsigned int lan969x_fsize[FSIZE_LAST] = {
[FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA] = 30,
[FW_ANA_L2_DLB_CFG_DLB_IDX] = 9,
[FW_ANA_L2_TSN_CFG_TSN_SFID] = 8,
+ [FW_ANA_L3_L3_UC_ENA_L3_UC_ENA] = 30,
+ [FW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1] = 3,
+ [FW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA] = 30,
+ [FW_ANA_L3_VMID_CFG_VMID] = 7,
[FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK] = 30,
+ [FW_ANA_L3_ARP_CFG_0_ARP_VMID] = 7,
[FW_FDMA_CH_CFG_CH_DCB_DB_CNT] = 2,
[FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL] = 7,
[FW_HSCH_SE_CFG_SE_DWRR_CNT] = 5,
@@ -214,6 +227,7 @@ const unsigned int lan969x_fsize[FSIZE_LAST] = {
[FW_QSYS_ATOP_ATOP] = 11,
[FW_QSYS_ATOP_TOT_CFG_ATOP_TOT] = 11,
[FW_REW_RTAG_ETAG_CTRL_IPE_TBL] = 6,
+ [FW_REW_RLEG_CTRL_DECAP_IRLEG] = 7,
[FW_XQS_STAT_CFG_STAT_VIEW] = 10,
[FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP] = 14,
[FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP] = 14,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
index d9ef4ef137b8..9d34750416eb 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0+
* Microchip Sparx5 Switch driver
*
- * Copyright (c) 2024 Microchip Technology Inc.
+ * Copyright (c) 2026 Microchip Technology Inc.
*/
-/* This file is autogenerated by cml-utils 2024-10-04 10:40:40 +0200.
- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
+/* This file is autogenerated by cml-utils 2026-02-16 21:50:30 +0100.
+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2
*/
#ifndef _SPARX5_MAIN_REGS_H_
@@ -711,6 +711,124 @@ extern const struct sparx5_regs *regs;
#define ANA_ACL_SWAP_IP_CTRL_IP_SWAP_IP4_TTL_ENA_GET(x)\
FIELD_GET(ANA_ACL_SWAP_IP_CTRL_IP_SWAP_IP4_TTL_ENA, x)
+/* ANA_ACL:COMMON:VCAP_S2_MISC_CTRL */
+#define ANA_ACL_VCAP_S2_MISC_CTRL \
+ __REG(TARGET_ANA_ACL, 0, 1, regs->gaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \
+ 416, 0, 1, 4)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA GENMASK(27, 24)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA GENMASK(23, 20)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA GENMASK(19, 16)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA GENMASK(15, 12)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA BIT(11)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE BIT(10)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE BIT(9)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA BIT(8)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA BIT(7)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL GENMASK(6, 5)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA BIT(4)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA BIT(3)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA BIT(2)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA BIT(1)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA BIT(0)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA, x)
+
+/* ANA_ACL:COMMON:VCAP_S2_MISC_CTRL2 */
+#define ANA_ACL_VCAP_S2_MISC_CTRL2 \
+ __REG(TARGET_ANA_ACL, 0, 1, regs->gaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \
+ 420, 0, 1, 4)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT GENMASK(12, 8)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA GENMASK(7, 4)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA GENMASK(3, 0)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA, x)
+
/* ANA_ACL:COMMON:VCAP_S2_RLEG_STAT */
#define ANA_ACL_VCAP_S2_RLEG_STAT(r) \
__REG(TARGET_ANA_ACL, 0, 1, regs->gaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \
@@ -1812,6 +1930,338 @@ extern const struct sparx5_regs *regs;
#define ANA_L3_VLAN_CTRL_VLAN_ENA_GET(x)\
FIELD_GET(ANA_L3_VLAN_CTRL_VLAN_ENA, x)
+/* ANA_L3:COMMON:L3_UC_ENA */
+#define ANA_L3_L3_UC_ENA \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, 8,\
+ 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:L3_UC_ENA1 */
+#define ANA_L3_L3_UC_ENA1 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 12, 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:L3_UC_ENA2 */
+#define ANA_L3_L3_UC_ENA2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 16, 0, 1, 4)
+
+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2 BIT(0)
+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2_SET(x)\
+ FIELD_PREP(ANA_L3_L3_UC_ENA2_L3_UC_ENA2, x)
+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2_GET(x)\
+ FIELD_GET(ANA_L3_L3_UC_ENA2_L3_UC_ENA2, x)
+
+/* ANA_L3:COMMON:ROUTING_CFG */
+#define ANA_L3_ROUTING_CFG \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 104, 0, 1, 4)
+
+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE BIT(30)
+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_L3_ENA_MODE, x)
+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_L3_ENA_MODE, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA BIT(29)
+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA BIT(28)
+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA BIT(27)
+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA, x)
+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE GENMASK(26, 25)
+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE, x)
+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR BIT(22)
+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_LEN_REDIR, x)
+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_LEN_REDIR, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR BIT(21)
+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_LEN_REDIR, x)
+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_LEN_REDIR, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA BIT(20)
+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA BIT(19)
+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA BIT(18)
+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA BIT(17)
+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA GENMASK(16, 15)
+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA GENMASK(13, 11)
+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA GENMASK(10, 8)
+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA GENMASK(7, 5)
+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA BIT(4)
+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA BIT(3)
+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA BIT(2)
+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA BIT(1)
+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA BIT(0)
+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA, x)
+
+/* ANA_L3:COMMON:ROUTING_CFG2 */
+#define ANA_L3_ROUTING_CFG2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 108, 0, 1, 4)
+
+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA BIT(4)
+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA, x)
+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA, x)
+
+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA BIT(3)
+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA, x)
+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA, x)
+
+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA BIT(1)
+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA, x)
+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA, x)
+
+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA BIT(0)
+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA, x)
+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA, x)
+
+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA BIT(2)
+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA, x)
+
+/* ANA_L3:COMMON:RLEG_CFG_0 */
+#define ANA_L3_RLEG_CFG_0 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 112, 0, 1, 4)
+
+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(31, 8)
+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB, x)
+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB, x)
+
+/* ANA_L3:COMMON:RLEG_CFG_1 */
+#define ANA_L3_RLEG_CFG_1 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 116, 0, 1, 4)
+
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB, x)
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB, x)
+
+/* ANA_L3:COMMON:CPU_QU_CFG */
+#define ANA_L3_CPU_QU_CFG \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 120, 0, 1, 4)
+
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU GENMASK(30, 28)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU GENMASK(26, 24)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU GENMASK(22, 20)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU GENMASK(18, 16)
+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU GENMASK(14, 12)
+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU GENMASK(10, 8)
+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU GENMASK(6, 4)
+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU GENMASK(2, 0)
+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU, x)
+
+/* ANA_L3:COMMON:CPU_QU_CFG2 */
+#define ANA_L3_CPU_QU_CFG2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 124, 0, 1, 4)
+
+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU GENMASK(2, 0)
+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU, x)
+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU, x)
+
+/* ANA_L3:COMMON:SIP_SECURE_ENA */
+#define ANA_L3_SIP_SECURE_ENA \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 144, 0, 1, 4)
+
+/* ANA_L3:COMMON:SIP_SECURE_ENA1 */
+#define ANA_L3_SIP_SECURE_ENA1 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 148, 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:SIP_SECURE_ENA2 */
+#define ANA_L3_SIP_SECURE_ENA2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 152, 0, 1, 4)
+
+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2 GENMASK(5, 0)
+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2_SET(x)\
+ FIELD_PREP(ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2, x)
+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2_GET(x)\
+ FIELD_GET(ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2, x)
+
+/* ANA_L3:COMMON:DIP_SECURE_ENA */
+#define ANA_L3_DIP_SECURE_ENA \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 156, 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:DIP_SECURE_ENA1 */
+#define ANA_L3_DIP_SECURE_ENA1 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 160, 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:DIP_SECURE_ENA2 */
+#define ANA_L3_DIP_SECURE_ENA2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 164, 0, 1, 4)
+
+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2 BIT(0)
+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2_SET(x)\
+ FIELD_PREP(ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2, x)
+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2_GET(x)\
+ FIELD_GET(ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2, x)
+
+/* ANA_L3:VLAN:VMID_CFG */
+#define ANA_L3_VMID_CFG(g) \
+ __REG(TARGET_ANA_L3, 0, 1, 0, g, regs->gcnt[GC_ANA_L3_VLAN], 64, 0, 0, \
+ 1, 4)
+
+#define ANA_L3_VMID_CFG_VMID\
+ GENMASK(regs->fsize[FW_ANA_L3_VMID_CFG_VMID] + 0 - 1, 0)
+#define ANA_L3_VMID_CFG_VMID_SET(x)\
+ spx5_field_prep(ANA_L3_VMID_CFG_VMID, x)
+#define ANA_L3_VMID_CFG_VMID_GET(x)\
+ spx5_field_get(ANA_L3_VMID_CFG_VMID, x)
+
/* ANA_L3:VLAN:VLAN_CFG */
#define ANA_L3_VLAN_CFG(g) \
__REG(TARGET_ANA_L3, 0, 1, 0, g, regs->gcnt[GC_ANA_L3_VLAN], 64, 8, 0, \
@@ -1871,6 +2321,13 @@ extern const struct sparx5_regs *regs;
#define ANA_L3_VLAN_CFG_VLAN_MIRROR_ENA_GET(x)\
FIELD_GET(ANA_L3_VLAN_CFG_VLAN_MIRROR_ENA, x)
+/* LAN969X ONLY */
+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS BIT(31)
+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS_SET(x)\
+ FIELD_PREP(ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS, x)
+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS_GET(x)\
+ FIELD_GET(ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS, x)
+
/* ANA_L3:VLAN:VLAN_MASK_CFG */
#define ANA_L3_VLAN_MASK_CFG(g) \
__REG(TARGET_ANA_L3, 0, 1, 0, g, regs->gcnt[GC_ANA_L3_VLAN], 64, 16, 0,\
@@ -1894,6 +2351,154 @@ extern const struct sparx5_regs *regs;
#define ANA_L3_VLAN_MASK_CFG2_VLAN_PORT_MASK2_GET(x)\
FIELD_GET(ANA_L3_VLAN_MASK_CFG2_VLAN_PORT_MASK2, x)
+/* ANA_L3:VMID:RLEG_CTRL */
+#define ANA_L3_RLEG_CTRL(g) \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_VMID], g, \
+ regs->gcnt[GC_ANA_L3_VMID], 64, 0, 0, 1, 4)
+
+#define ANA_L3_RLEG_CTRL_RLEG_EVID GENMASK(31, 19)
+#define ANA_L3_RLEG_CTRL_RLEG_EVID_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_EVID, x)
+#define ANA_L3_RLEG_CTRL_RLEG_EVID_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_EVID, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA BIT(18)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA BIT(17)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE GENMASK(15, 14)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE GENMASK(13, 12)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS BIT(9)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS BIT(8)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA BIT(7)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA BIT(6)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA BIT(5)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA BIT(4)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA BIT(3)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA BIT(2)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA BIT(1)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA BIT(0)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA, x)
+
+/* ANA_L3:ARP:ARP_CFG_0 */
+#define ANA_L3_ARP_CFG_0(g) \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_ARP], g, \
+ regs->gcnt[GC_ANA_L3_ARP], 32, 0, 0, 1, 4)
+
+#define ANA_L3_ARP_CFG_0_MAC_MSB GENMASK(31, 16)
+#define ANA_L3_ARP_CFG_0_MAC_MSB_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_MAC_MSB, x)
+#define ANA_L3_ARP_CFG_0_MAC_MSB_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_MAC_MSB, x)
+
+#define ANA_L3_ARP_CFG_0_ARP_VMID\
+ GENMASK(regs->fsize[FW_ANA_L3_ARP_CFG_0_ARP_VMID] + 7 - 1, 7)
+#define ANA_L3_ARP_CFG_0_ARP_VMID_SET(x)\
+ spx5_field_prep(ANA_L3_ARP_CFG_0_ARP_VMID, x)
+#define ANA_L3_ARP_CFG_0_ARP_VMID_GET(x)\
+ spx5_field_get(ANA_L3_ARP_CFG_0_ARP_VMID, x)
+
+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU GENMASK(6, 4)
+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU, x)
+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU, x)
+
+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA BIT(3)
+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_SIP_RPF_ENA, x)
+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_SIP_RPF_ENA, x)
+
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA BIT(2)
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA, x)
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA, x)
+
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA BIT(1)
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA, x)
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA, x)
+
+#define ANA_L3_ARP_CFG_0_ARP_ENA BIT(0)
+#define ANA_L3_ARP_CFG_0_ARP_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_ARP_ENA, x)
+#define ANA_L3_ARP_CFG_0_ARP_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_ARP_ENA, x)
+
+/* ANA_L3:ARP:ARP_CFG_1 */
+#define ANA_L3_ARP_CFG_1(g) \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_ARP], g, \
+ regs->gcnt[GC_ANA_L3_ARP], 32, 4, 0, 1, 4)
+
/* ASM:DEV_STATISTICS:RX_IN_BYTES_CNT */
#define ASM_RX_IN_BYTES_CNT(g) \
__REG(TARGET_ASM, 0, 1, 0, g, regs->gcnt[GC_ASM_DEV_STATISTICS], 512, \
@@ -4515,6 +5120,34 @@ extern const struct sparx5_regs *regs;
#define DSM_TAXI_CAL_CFG_CAL_PGM_SEL_GET(x)\
FIELD_GET(DSM_TAXI_CAL_CFG_CAL_PGM_SEL, x)
+/* EACL:COMMON:RLEG_CFG_0 */
+#define EACL_RLEG_CFG_0 \
+ __REG(TARGET_EACL, 0, 1, regs->gaddr[GA_EACL_COMMON], 0, 1, 216, 64, 0,\
+ 1, 4)
+
+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(23, 0)
+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\
+ FIELD_PREP(EACL_RLEG_CFG_0_RLEG_MAC_LSB, x)
+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\
+ FIELD_GET(EACL_RLEG_CFG_0_RLEG_MAC_LSB, x)
+
+/* EACL:COMMON:RLEG_CFG_1 */
+#define EACL_RLEG_CFG_1 \
+ __REG(TARGET_EACL, 0, 1, regs->gaddr[GA_EACL_COMMON], 0, 1, 216, 68, 0,\
+ 1, 4)
+
+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)
+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\
+ FIELD_PREP(EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\
+ FIELD_GET(EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+
+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)
+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\
+ FIELD_PREP(EACL_RLEG_CFG_1_RLEG_MAC_MSB, x)
+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\
+ FIELD_GET(EACL_RLEG_CFG_1_RLEG_MAC_MSB, x)
+
/* EACL:ES2_KEY_SELECT_PROFILE:VCAP_ES2_KEY_SEL */
#define EACL_VCAP_ES2_KEY_SEL(g, r) \
__REG(TARGET_EACL, 0, 1, regs->gaddr[GA_EACL_ES2_KEY_SELECT_PROFILE], \
@@ -7341,6 +7974,34 @@ extern const struct sparx5_regs *regs;
#define REW_ES0_CTRL_ES0_LU_ENA_GET(x)\
FIELD_GET(REW_ES0_CTRL_ES0_LU_ENA, x)
+/* REW:COMMON:RLEG_CFG_0 */
+#define REW_RLEG_CFG_0 \
+ __REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_COMMON], 0, 1, 1232, 1156, \
+ 0, 1, 4)
+
+#define REW_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(23, 0)
+#define REW_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\
+ FIELD_PREP(REW_RLEG_CFG_0_RLEG_MAC_LSB, x)
+#define REW_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\
+ FIELD_GET(REW_RLEG_CFG_0_RLEG_MAC_LSB, x)
+
+/* REW:COMMON:RLEG_CFG_1 */
+#define REW_RLEG_CFG_1 \
+ __REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_COMMON], 0, 1, 1232, 1160, \
+ 0, 1, 4)
+
+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)
+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\
+ FIELD_PREP(REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\
+ FIELD_GET(REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+
+#define REW_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)
+#define REW_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\
+ FIELD_PREP(REW_RLEG_CFG_1_RLEG_MAC_MSB, x)
+#define REW_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\
+ FIELD_GET(REW_RLEG_CFG_1_RLEG_MAC_MSB, x)
+
/* REW:PORT:PORT_VLAN_CFG */
#define REW_PORT_VLAN_CFG(g) \
__REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_PORT], g, \
@@ -7567,6 +8228,30 @@ extern const struct sparx5_regs *regs;
#define REW_PTP_GEN_STAMP_FMT_RT_FMT_GET(x)\
FIELD_GET(REW_PTP_GEN_STAMP_FMT_RT_FMT, x)
+/* REW:VMID:RLEG_CTRL */
+#define REW_RLEG_CTRL(g) \
+ __REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_VMID], g, \
+ regs->gcnt[GC_REW_VMID], 4, 0, 0, 1, 4)
+
+#define REW_RLEG_CTRL_DECAP_IRLEG\
+ GENMASK(regs->fsize[FW_REW_RLEG_CTRL_DECAP_IRLEG] + 13 - 1, 13)
+#define REW_RLEG_CTRL_DECAP_IRLEG_SET(x)\
+ spx5_field_prep(REW_RLEG_CTRL_DECAP_IRLEG, x)
+#define REW_RLEG_CTRL_DECAP_IRLEG_GET(x)\
+ spx5_field_get(REW_RLEG_CTRL_DECAP_IRLEG, x)
+
+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED BIT(12)
+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED_SET(x)\
+ FIELD_PREP(REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED, x)
+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED_GET(x)\
+ FIELD_GET(REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED, x)
+
+#define REW_RLEG_CTRL_RLEG_EVID GENMASK(11, 0)
+#define REW_RLEG_CTRL_RLEG_EVID_SET(x)\
+ FIELD_PREP(REW_RLEG_CTRL_RLEG_EVID, x)
+#define REW_RLEG_CTRL_RLEG_EVID_GET(x)\
+ FIELD_GET(REW_RLEG_CTRL_RLEG_EVID, x)
+
/* REW:RAM_CTRL:RAM_INIT */
#define REW_RAM_INIT \
__REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_RAM_CTRL], 0, 1, 4, 0, 0, 1,\
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
index 220e81b714d4..5fc11403ddf6 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
- * Copyright (c) 2024 Microchip Technology Inc.
+ * Copyright (c) 2026 Microchip Technology Inc.
*/
-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
+/* This file is autogenerated by cml-utils 2026-02-16 21:50:29 +0100.
+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2
*/
#include "sparx5_regs.h"
@@ -77,12 +77,16 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = {
[GA_ANA_CL_COMMON] = 166912,
[GA_ANA_L2_COMMON] = 566024,
[GA_ANA_L3_COMMON] = 493632,
+ [GA_ANA_L3_VMID] = 458752,
+ [GA_ANA_L3_ARP_PTR_REMAP] = 493824,
+ [GA_ANA_L3_ARP] = 327680,
[GA_ANA_L3_VLAN_ARP_L3MC_STICKY] = 491460,
[GA_ASM_CFG] = 33280,
[GA_ASM_PFC_TIMER_CFG] = 34716,
[GA_ASM_LBK_WM_CFG] = 34744,
[GA_ASM_LBK_MISC_CFG] = 34756,
[GA_ASM_RAM_CTRL] = 34832,
+ [GA_EACL_COMMON] = 118480,
[GA_EACL_ES2_KEY_SELECT_PROFILE] = 149504,
[GA_EACL_CNT_TBL] = 122880,
[GA_EACL_POL_CFG] = 150608,
@@ -102,6 +106,7 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = {
[GA_QSYS_RAM_CTRL] = 2344,
[GA_REW_COMMON] = 387264,
[GA_REW_PORT] = 360448,
+ [GA_REW_VMID] = 389120,
[GA_REW_VOE_PORT_LM_CNT] = 393216,
[GA_REW_RAM_CTRL] = 378696,
[GA_VOP_RAM_CTRL] = 279176,
@@ -123,6 +128,8 @@ const unsigned int sparx5_gcnt[GCNT_LAST] = {
[GC_ANA_L2_ISDX_LIMIT] = 1536,
[GC_ANA_L2_ISDX] = 4096,
[GC_ANA_L3_VLAN] = 5120,
+ [GC_ANA_L3_VMID] = 511,
+ [GC_ANA_L3_ARP] = 2048,
[GC_ASM_DEV_STATISTICS] = 65,
[GC_EACL_ES2_KEY_SELECT_PROFILE] = 138,
[GC_EACL_CNT_TBL] = 2048,
@@ -132,6 +139,7 @@ const unsigned int sparx5_gcnt[GCNT_LAST] = {
[GC_PTP_PTP_PINS] = 5,
[GC_PTP_PHASE_DETECTOR_CTRL] = 5,
[GC_REW_PORT] = 70,
+ [GC_REW_VMID] = 511,
[GC_REW_VOE_PORT_LM_CNT] = 520,
};
@@ -188,7 +196,12 @@ const unsigned int sparx5_fsize[FSIZE_LAST] = {
[FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA] = 32,
[FW_ANA_L2_DLB_CFG_DLB_IDX] = 13,
[FW_ANA_L2_TSN_CFG_TSN_SFID] = 10,
+ [FW_ANA_L3_L3_UC_ENA_L3_UC_ENA] = 32,
+ [FW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1] = 32,
+ [FW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA] = 32,
+ [FW_ANA_L3_VMID_CFG_VMID] = 9,
[FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK] = 32,
+ [FW_ANA_L3_ARP_CFG_0_ARP_VMID] = 9,
[FW_FDMA_CH_CFG_CH_DCB_DB_CNT] = 4,
[FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL] = 9,
[FW_HSCH_SE_CFG_SE_DWRR_CNT] = 7,
@@ -214,6 +227,7 @@ const unsigned int sparx5_fsize[FSIZE_LAST] = {
[FW_QSYS_ATOP_ATOP] = 12,
[FW_QSYS_ATOP_TOT_CFG_ATOP_TOT] = 12,
[FW_REW_RTAG_ETAG_CTRL_IPE_TBL] = 7,
+ [FW_REW_RLEG_CTRL_DECAP_IRLEG] = 9,
[FW_XQS_STAT_CFG_STAT_VIEW] = 13,
[FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP] = 15,
[FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP] = 15,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
index ea28130c2341..28dca1ed4ea7 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver
*
- * Copyright (c) 2024 Microchip Technology Inc.
+ * Copyright (c) 2026 Microchip Technology Inc.
*/
-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
+/* This file is autogenerated by cml-utils 2026-02-16 21:50:30 +0100.
+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2
*/
#ifndef _SPARX5_REGS_H_
@@ -86,12 +86,16 @@ enum sparx5_gaddr_enum {
GA_ANA_CL_COMMON,
GA_ANA_L2_COMMON,
GA_ANA_L3_COMMON,
+ GA_ANA_L3_VMID,
+ GA_ANA_L3_ARP_PTR_REMAP,
+ GA_ANA_L3_ARP,
GA_ANA_L3_VLAN_ARP_L3MC_STICKY,
GA_ASM_CFG,
GA_ASM_PFC_TIMER_CFG,
GA_ASM_LBK_WM_CFG,
GA_ASM_LBK_MISC_CFG,
GA_ASM_RAM_CTRL,
+ GA_EACL_COMMON,
GA_EACL_ES2_KEY_SELECT_PROFILE,
GA_EACL_CNT_TBL,
GA_EACL_POL_CFG,
@@ -111,6 +115,7 @@ enum sparx5_gaddr_enum {
GA_QSYS_RAM_CTRL,
GA_REW_COMMON,
GA_REW_PORT,
+ GA_REW_VMID,
GA_REW_VOE_PORT_LM_CNT,
GA_REW_RAM_CTRL,
GA_VOP_RAM_CTRL,
@@ -133,6 +138,8 @@ enum sparx5_gcnt_enum {
GC_ANA_L2_ISDX_LIMIT,
GC_ANA_L2_ISDX,
GC_ANA_L3_VLAN,
+ GC_ANA_L3_VMID,
+ GC_ANA_L3_ARP,
GC_ASM_DEV_STATISTICS,
GC_EACL_ES2_KEY_SELECT_PROFILE,
GC_EACL_CNT_TBL,
@@ -142,6 +149,7 @@ enum sparx5_gcnt_enum {
GC_PTP_PTP_PINS,
GC_PTP_PHASE_DETECTOR_CTRL,
GC_REW_PORT,
+ GC_REW_VMID,
GC_REW_VOE_PORT_LM_CNT,
GCNT_LAST,
};
@@ -201,7 +209,12 @@ enum sparx5_fsize_enum {
FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA,
FW_ANA_L2_DLB_CFG_DLB_IDX,
FW_ANA_L2_TSN_CFG_TSN_SFID,
+ FW_ANA_L3_L3_UC_ENA_L3_UC_ENA,
+ FW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1,
+ FW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA,
+ FW_ANA_L3_VMID_CFG_VMID,
FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK,
+ FW_ANA_L3_ARP_CFG_0_ARP_VMID,
FW_FDMA_CH_CFG_CH_DCB_DB_CNT,
FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL,
FW_HSCH_SE_CFG_SE_DWRR_CNT,
@@ -227,6 +240,7 @@ enum sparx5_fsize_enum {
FW_QSYS_ATOP_ATOP,
FW_QSYS_ATOP_TOT_CFG_ATOP_TOT,
FW_REW_RTAG_ETAG_CTRL_IPE_TBL,
+ FW_REW_RLEG_CTRL_DECAP_IRLEG,
FW_XQS_STAT_CFG_STAT_VIEW,
FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP,
FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP,
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 4/9] net: microchip: vcap: expose helpers in vcap api and update debugfs
From: Jens Emil Schulz Østergaard @ 2026-06-12 12:37 UTC (permalink / raw)
To: Horatiu Vultur, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Daniel Machon,
Steen Hegelund, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-arm-kernel, linux-hardening,
Jens Emil Schulz Østergaard
In-Reply-To: <20260612-sparx5_l3_routing-v1-0-fc3c10160f49@microchip.com>
Add new helpers to the vcap client api, in preparation for L3 routing
functionality:
- vcap_val_add_rule(): wraps vcap_val_rule() + vcap_add_rule().
- vcap_rule_mod_action_bit(): modify a bit-typed action on an existing
rule.
Rename VCAP_CID_PREROUTING to VCAP_CID_PREROUTING_L0 and add
VCAP_USER_L3, both needed by the upcoming LPM VCAP user.
Extend the debugfs display to handle the new IP4_XIP and IP6_XIP key
fields.
Fix a latent undefined-behaviour bug in the debugfs action-field
printer. The old mask expression (1 << width) - 1 is UB when width is
32. The bug is unreachable before this series, since no existing field
in any client hits this, but the LPM VCAP introduces VCAP_AF_MAC_LSB
which is 32 bit wide.
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
drivers/net/ethernet/microchip/vcap/vcap_api.c | 25 ++++++++++++++++++++++
drivers/net/ethernet/microchip/vcap/vcap_api.h | 4 +++-
.../net/ethernet/microchip/vcap/vcap_api_client.h | 6 ++++++
.../net/ethernet/microchip/vcap/vcap_api_debugfs.c | 13 ++++++++---
4 files changed, 44 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.c b/drivers/net/ethernet/microchip/vcap/vcap_api.c
index 30700648672f..0905e4f192a0 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api.c
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api.c
@@ -2379,6 +2379,19 @@ int vcap_add_rule(struct vcap_rule *rule)
}
EXPORT_SYMBOL_GPL(vcap_add_rule);
+/* Validate and add rule to a VCAP instance */
+int vcap_val_add_rule(struct vcap_rule *rule, u16 l3_proto)
+{
+ int err;
+
+ err = vcap_val_rule(rule, l3_proto);
+ if (err)
+ return err;
+
+ return vcap_add_rule(rule);
+}
+EXPORT_SYMBOL_GPL(vcap_val_add_rule);
+
/* Allocate a new rule with the provided arguments */
struct vcap_rule *vcap_alloc_rule(struct vcap_control *vctrl,
struct net_device *ndev, int vcap_chain_id,
@@ -3547,6 +3560,18 @@ int vcap_rule_mod_action_u32(struct vcap_rule *rule,
}
EXPORT_SYMBOL_GPL(vcap_rule_mod_action_u32);
+/* Modify a bit action with value in the rule */
+int vcap_rule_mod_action_bit(struct vcap_rule *rule,
+ enum vcap_action_field action,
+ enum vcap_bit val)
+{
+ struct vcap_client_actionfield_data data;
+
+ vcap_rule_set_action_bitsize(&data.u1, val);
+ return vcap_rule_mod_action(rule, action, VCAP_FIELD_BIT, &data);
+}
+EXPORT_SYMBOL_GPL(vcap_rule_mod_action_bit);
+
/* Drop keys in a keylist and any keys that are not supported by the keyset */
int vcap_filter_rule_keys(struct vcap_rule *rule,
enum vcap_key_field keylist[], int length,
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.h b/drivers/net/ethernet/microchip/vcap/vcap_api.h
index 6069ad95c27e..e197e7257560 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api.h
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api.h
@@ -22,7 +22,7 @@
#define VCAP_CID_INGRESS_L5 1500000 /* Ingress Stage 1 Lookup 5 */
#define VCAP_CID_PREROUTING_IPV6 3000000 /* Prerouting Stage */
-#define VCAP_CID_PREROUTING 6000000 /* Prerouting Stage */
+#define VCAP_CID_PREROUTING_L0 6000000 /* Prerouting Stage Lookup 0 */
#define VCAP_CID_INGRESS_STAGE2_L0 8000000 /* Ingress Stage 2 Lookup 0 */
#define VCAP_CID_INGRESS_STAGE2_L1 8100000 /* Ingress Stage 2 Lookup 1 */
@@ -41,7 +41,9 @@ enum vcap_user {
VCAP_USER_MRP,
VCAP_USER_CFM,
VCAP_USER_VLAN,
+ VCAP_USER_L3,
VCAP_USER_QOS,
+ /* permanent enabled users above here */
VCAP_USER_VCAP_UTIL,
VCAP_USER_TC,
VCAP_USER_TC_EXTRA,
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_client.h b/drivers/net/ethernet/microchip/vcap/vcap_api_client.h
index cdf79e17ca54..3f17e1e76b7d 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api_client.h
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api_client.h
@@ -167,6 +167,8 @@ void vcap_free_rule(struct vcap_rule *rule);
int vcap_val_rule(struct vcap_rule *rule, u16 l3_proto);
/* Add rule to a VCAP instance */
int vcap_add_rule(struct vcap_rule *rule);
+/* Validate and add rule to a VCAP instance */
+int vcap_val_add_rule(struct vcap_rule *rule, u16 l3_proto);
/* Delete rule in a VCAP instance */
int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id);
/* Make a full copy of an existing rule with a new rule id */
@@ -266,6 +268,10 @@ int vcap_rule_mod_key_u32(struct vcap_rule *rule, enum vcap_key_field key,
int vcap_rule_mod_action_u32(struct vcap_rule *rule,
enum vcap_action_field action,
u32 value);
+/* Modify a bit action with value in the rule */
+int vcap_rule_mod_action_bit(struct vcap_rule *rule,
+ enum vcap_action_field action,
+ enum vcap_bit val);
/* Get a 32 bit key field value and mask from the rule */
int vcap_rule_get_key_u32(struct vcap_rule *rule, enum vcap_key_field key,
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c
index 59bfbda29bb3..56464ece8e6b 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c
@@ -40,7 +40,8 @@ static void vcap_debugfs_show_rule_keyfield(struct vcap_control *vctrl,
value = (u8 *)(&data->u32.value);
mask = (u8 *)(&data->u32.mask);
- if (key == VCAP_KF_L3_IP4_SIP || key == VCAP_KF_L3_IP4_DIP) {
+ if (key == VCAP_KF_L3_IP4_SIP || key == VCAP_KF_L3_IP4_DIP ||
+ key == VCAP_KF_IP4_XIP) {
out->prf(out->dst, "%pI4h/%pI4h", &data->u32.value,
&data->u32.mask);
} else if (key == VCAP_KF_ETYPE ||
@@ -88,7 +89,8 @@ static void vcap_debugfs_show_rule_keyfield(struct vcap_control *vctrl,
case VCAP_FIELD_U128:
value = data->u128.value;
mask = data->u128.mask;
- if (key == VCAP_KF_L3_IP6_SIP || key == VCAP_KF_L3_IP6_DIP) {
+ if (key == VCAP_KF_L3_IP6_SIP || key == VCAP_KF_L3_IP6_DIP ||
+ key == VCAP_KF_IP6_XIP) {
u8 nvalue[16], nmask[16];
vcap_netbytes_copy(nvalue, data->u128.value,
@@ -133,7 +135,12 @@ vcap_debugfs_show_rule_actionfield(struct vcap_control *vctrl,
out->prf(out->dst, "%d", value[0]);
break;
case VCAP_FIELD_U32:
- fmsk = (1 << actionfield[action].width) - 1;
+ if (action == VCAP_AF_MAC_LSB || action == VCAP_AF_MAC_MSB) {
+ hex = true;
+ break;
+ }
+ fmsk = actionfield[action].width ?
+ GENMASK(actionfield[action].width - 1, 0) : 0;
val = *(u32 *)value;
out->prf(out->dst, "%u", val & fmsk);
break;
--
2.52.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox