From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DFC20361DD0; Tue, 17 Feb 2026 13:36:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771335410; cv=none; b=EzdhCy3+vcUdKc8Tn+dXbIW2Y4gNXyesAxb1Lm7n0QC0uyPUD/ofO3MN0y+I6gEk+aqUufSTLP04bpfM5mFc4D5bxnt4v758FzkexwoomeD9++RHnIU/r/bDlTbvFrv3ZgZStftEEh/CcPjjj8VCsnj7UawjbbHLqfAbp3qLkwM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771335410; c=relaxed/simple; bh=nNxrJLRRFqdj8uT3qG8zhi++J7aS40BWQKyYmMUHDW4=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=Nb3ZWn7Pr7QlvBPYFAaz07iD03B5QFykxd678MUcIdy/oyU9aTFpwetudeJPx+nPs8GhytQWIY/ux1VO5zOBO+0yRaFGRBGfY1zBwAdDBcPK0aAb3zFUOKAWeVRcfijTarAZUzL1jWvda7zx8RQumbDljH/ufOwSXnIlegWrFt8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.99) (envelope-from ) id 1vsLFg-000000006kK-290F; Tue, 17 Feb 2026 13:36:32 +0000 Date: Tue, 17 Feb 2026 13:36:29 +0000 From: Daniel Golle To: cedric.jehasse@luminex.be Cc: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Russell King , linux-kernel@vger.kernel.org, netdev@vger.kernel.org Subject: Re: [PATCH RFC net-next v2] net: dsa: mv88e6xxx: Add partial support for TCAM entries Message-ID: References: <20260217-net-next-mv88e6xxx-tcam-v2-1-d84cf420da46@luminex.be> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20260217-net-next-mv88e6xxx-tcam-v2-1-d84cf420da46@luminex.be> Hi Cedric, I've reviewed this submission, find some comments inline below. On Tue, Feb 17, 2026 at 01:59:27PM +0100, Cedric Jehasse via B4 Relay wrote: > From: Cedric Jehasse > > This patch adds partial Ternary Content Addressable Memory (TCAM) for > the mv88e6390 and mv88e6393 family of switches. TCAM entries allow the > switch to match the first 48 or 96 bytes of a frame and take actions on > matched frames. > > This patch introduces a subset of the available TCAM functionality. > Matching on ip addresses/protocol and trapping to the cpu. > > Eg. to trap traffic with destination ip 224.0.1.129 to the cpu: > > tc qdisc add dev p1 clsact > tc filter add dev p1 ingress protocol ip flower skip_sw \ > dst_ip 224.0.1.129 action trap > > Signed-off-by: Cedric Jehasse > --- > Changes in RFC-v2: > - moved tcam_addr to chip info struct and remove tcam_support field > - added 6393-specific tcam ops that write the TCAM extension register > - rename the defines for TCAM registers to be consistent > - Link to v1: https://lore.kernel.org/r/20260213-net-next-mv88e6xxx-tcam-v1-1-051e552e2afc@luminex.be > --- > drivers/net/dsa/mv88e6xxx/Makefile | 2 + > drivers/net/dsa/mv88e6xxx/chip.c | 30 ++++ > drivers/net/dsa/mv88e6xxx/chip.h | 50 ++++++ > drivers/net/dsa/mv88e6xxx/port.c | 23 ++- > drivers/net/dsa/mv88e6xxx/port.h | 7 +- > drivers/net/dsa/mv88e6xxx/tcam.c | 310 +++++++++++++++++++++++++++++++++++ > drivers/net/dsa/mv88e6xxx/tcam.h | 38 +++++ > drivers/net/dsa/mv88e6xxx/tcflower.c | 138 ++++++++++++++++ > drivers/net/dsa/mv88e6xxx/tcflower.h | 13 ++ > 9 files changed, 609 insertions(+), 2 deletions(-) > > diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile > index dd961081d631..b0b08c6f159c 100644 > --- a/drivers/net/dsa/mv88e6xxx/Makefile > +++ b/drivers/net/dsa/mv88e6xxx/Makefile > @@ -21,6 +21,8 @@ mv88e6xxx-objs += serdes.o > mv88e6xxx-objs += smi.o > mv88e6xxx-objs += switchdev.o > mv88e6xxx-objs += trace.o > +mv88e6xxx-objs += tcflower.o > +mv88e6xxx-objs += tcam.o > > # for tracing framework to find trace.h > CFLAGS_trace.o := -I$(src) > diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c > index 09002c853b78..b95212dc2f75 100644 > --- a/drivers/net/dsa/mv88e6xxx/chip.c > +++ b/drivers/net/dsa/mv88e6xxx/chip.c > @@ -43,6 +43,8 @@ > #include "ptp.h" > #include "serdes.h" > #include "smi.h" > +#include "tcam.h" > +#include "tcflower.h" > > static void assert_reg_lock(struct mv88e6xxx_chip *chip) > { > @@ -3560,6 +3562,11 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port) > if (err) > return err; > } > + if (chip->info->ops->port_enable_tcam) { > + err = chip->info->ops->port_enable_tcam(chip, port); > + if (err) > + return err; > + } > > if (chip->info->ops->port_tag_remap) { > err = chip->info->ops->port_tag_remap(chip, port); > @@ -3938,6 +3945,14 @@ static int mv88e6xxx_mdios_register(struct mv88e6xxx_chip *chip) > return 0; > } > > +static int mv88e6xxx_tcam_setup(struct mv88e6xxx_chip *chip) > +{ > + if (!mv88e6xxx_has_tcam(chip)) > + return 0; > + > + return chip->info->ops->tcam_ops->flush_tcam(chip); > +} > + > static void mv88e6xxx_teardown(struct dsa_switch *ds) > { > struct mv88e6xxx_chip *chip = ds->priv; > @@ -4083,6 +4098,10 @@ static int mv88e6xxx_setup(struct dsa_switch *ds) > if (err) > goto unlock; > > + err = mv88e6xxx_tcam_setup(chip); > + if (err) > + goto unlock; > + > unlock: > mv88e6xxx_reg_unlock(chip); > > @@ -5134,6 +5153,7 @@ static const struct mv88e6xxx_ops mv88e6290_ops = { > .ptp_ops = &mv88e6390_ptp_ops, > .phylink_get_caps = mv88e6390_phylink_get_caps, > .pcs_ops = &mv88e6390_pcs_ops, > + .tcam_ops = &mv88e6390_tcam_ops, > }; > > static const struct mv88e6xxx_ops mv88e6320_ops = { > @@ -5525,6 +5545,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = { > .serdes_get_regs = mv88e6390_serdes_get_regs, > .phylink_get_caps = mv88e6390_phylink_get_caps, > .pcs_ops = &mv88e6390_pcs_ops, > + .tcam_ops = &mv88e6390_tcam_ops, > }; > > static const struct mv88e6xxx_ops mv88e6390x_ops = { > @@ -5621,6 +5642,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = { > .port_set_cmode = mv88e6393x_port_set_cmode, > .port_setup_message_port = mv88e6xxx_setup_message_port, > .port_set_upstream_port = mv88e6393x_port_set_upstream_port, > + .port_enable_tcam = mv88e6xxx_port_enable_tcam, > .stats_snapshot = mv88e6390_g1_stats_snapshot, > .stats_set_histogram = mv88e6390_g1_stats_set_histogram, > .stats_get_sset_count = mv88e6320_stats_get_sset_count, > @@ -5652,6 +5674,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = { > .ptp_ops = &mv88e6352_ptp_ops, > .phylink_get_caps = mv88e6393x_phylink_get_caps, > .pcs_ops = &mv88e6393x_pcs_ops, > + .tcam_ops = &mv88e6393_tcam_ops, > }; > > static const struct mv88e6xxx_info mv88e6xxx_table[] = { > @@ -6132,6 +6155,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { > .phy_base_addr = 0x0, > .global1_addr = 0x1b, > .global2_addr = 0x1c, > + .tcam_addr = 0x1f, > .age_time_coeff = 3750, > .g1_irqs = 10, > .g2_irqs = 14, > @@ -6233,6 +6257,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { > .phy_base_addr = 0x0, > .global1_addr = 0x1b, > .global2_addr = 0x1c, > + .tcam_addr = 0x1f, > .age_time_coeff = 3750, > .g1_irqs = 9, > .g2_irqs = 14, > @@ -6445,6 +6470,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { > .phy_base_addr = 0x0, > .global1_addr = 0x1b, > .global2_addr = 0x1c, > + .tcam_addr = 0x1f, > .age_time_coeff = 3750, > .g1_irqs = 9, > .g2_irqs = 14, > @@ -6497,6 +6523,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { > .phy_base_addr = 0x0, > .global1_addr = 0x1b, > .global2_addr = 0x1c, > + .tcam_addr = 0x1f, > .age_time_coeff = 3750, > .g1_irqs = 10, > .g2_irqs = 14, > @@ -6589,6 +6616,7 @@ static struct mv88e6xxx_chip *mv88e6xxx_alloc_chip(struct device *dev) > INIT_LIST_HEAD(&chip->mdios); > idr_init(&chip->policies); > INIT_LIST_HEAD(&chip->msts); > + INIT_LIST_HEAD(&chip->tcam.entries); > > return chip; > } > @@ -7184,6 +7212,8 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = { > .port_hwtstamp_get = mv88e6xxx_port_hwtstamp_get, > .port_txtstamp = mv88e6xxx_port_txtstamp, > .port_rxtstamp = mv88e6xxx_port_rxtstamp, > + .cls_flower_add = mv88e6xxx_cls_flower_add, > + .cls_flower_del = mv88e6xxx_cls_flower_del, > .get_ts_info = mv88e6xxx_get_ts_info, > .devlink_param_get = mv88e6xxx_devlink_param_get, > .devlink_param_set = mv88e6xxx_devlink_param_set, > diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h > index e073446ee7d0..1734bc4666b0 100644 > --- a/drivers/net/dsa/mv88e6xxx/chip.h > +++ b/drivers/net/dsa/mv88e6xxx/chip.h > @@ -141,6 +141,7 @@ struct mv88e6xxx_info { > unsigned int phy_base_addr; > unsigned int global1_addr; > unsigned int global2_addr; > + unsigned int tcam_addr; > unsigned int age_time_coeff; > unsigned int g1_irqs; > unsigned int g2_irqs; > @@ -210,6 +211,7 @@ struct mv88e6xxx_avb_ops; > struct mv88e6xxx_ptp_ops; > struct mv88e6xxx_pcs_ops; > struct mv88e6xxx_cc_coeffs; > +struct mv88e6xxx_tcam_ops; > > struct mv88e6xxx_irq { > u16 masked; > @@ -339,6 +341,10 @@ struct mv88e6xxx_hw_stat { > int type; > }; > > +struct mv88e6xxx_tcam { > + struct list_head entries; > +}; > + > struct mv88e6xxx_chip { > const struct mv88e6xxx_info *info; > > @@ -444,6 +450,35 @@ struct mv88e6xxx_chip { > > /* FID map */ > DECLARE_BITMAP(fid_bitmap, MV88E6XXX_N_FID); > + > + /* TCAM entries */ > + struct mv88e6xxx_tcam tcam; > +}; > + > +#define TCAM_MATCH_SIZE 96 > + > +struct mv88e6xxx_tcam_key { > + u16 spv; > + u16 spv_mask; > + > + u8 frame_data[TCAM_MATCH_SIZE]; > + u8 frame_mask[TCAM_MATCH_SIZE]; > +}; > + > +struct mv88e6xxx_tcam_action { > + u8 dpv_mode; > + u16 dpv; > +}; > + > +struct mv88e6xxx_tcam_entry { > + struct list_head list; > + unsigned long cookie; > + u32 prio; > + u8 hw_idx; > + > + struct mv88e6xxx_tcam_key key; > + struct mv88e6xxx_tcam_action action; > + > }; > > struct mv88e6xxx_bus_ops { > @@ -678,6 +713,11 @@ struct mv88e6xxx_ops { > > /* Max Frame Size */ > int (*set_max_frame_size)(struct mv88e6xxx_chip *chip, int mtu); > + > + int (*port_enable_tcam)(struct mv88e6xxx_chip *chip, int port); > + > + /* Ternary Content Addressable Memory operations */ > + const struct mv88e6xxx_tcam_ops *tcam_ops; > }; > > struct mv88e6xxx_irq_ops { > @@ -752,6 +792,11 @@ struct mv88e6xxx_pcs_ops { > > }; > > +struct mv88e6xxx_tcam_ops { > + int (*entry_add)(struct mv88e6xxx_chip *chip, struct mv88e6xxx_tcam_entry *entry, u8 idx); > + int (*flush_tcam)(struct mv88e6xxx_chip *chip); > +}; > + > static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip) > { > return chip->info->max_sid > 0 && > @@ -769,6 +814,11 @@ static inline bool mv88e6xxx_has_lag(struct mv88e6xxx_chip *chip) > return !!chip->info->global2_addr; > } > > +static inline bool mv88e6xxx_has_tcam(struct mv88e6xxx_chip *chip) > +{ > + return !!chip->info->tcam_addr; > +} > + > static inline unsigned int mv88e6xxx_num_databases(struct mv88e6xxx_chip *chip) > { > return chip->info->num_databases; > diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c > index 66b1b7277281..e9d0289f91ad 100644 > --- a/drivers/net/dsa/mv88e6xxx/port.c > +++ b/drivers/net/dsa/mv88e6xxx/port.c > @@ -1380,7 +1380,28 @@ int mv88e6xxx_port_disable_learn_limit(struct mv88e6xxx_chip *chip, int port) > > int mv88e6xxx_port_disable_pri_override(struct mv88e6xxx_chip *chip, int port) > { > - return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE, 0); > + u16 reg; > + int err; > + > + err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE, ®); > + if (err) > + return err; > + > + reg &= MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_MASK; > + return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE, reg); > +} > + > +int mv88e6xxx_port_enable_tcam(struct mv88e6xxx_chip *chip, int port) > +{ > + u16 reg; > + int err; > + > + err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE, ®); > + if (err) > + return err; > + > + reg |= MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_96_BYTE; > + return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE, reg); > } > > /* Offset 0x0E: Policy & MGMT Control Register for FAMILY 6191X 6193X 6393X */ > diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h > index c1d2f99efb1c..a2492cf4d920 100644 > --- a/drivers/net/dsa/mv88e6xxx/port.h > +++ b/drivers/net/dsa/mv88e6xxx/port.h > @@ -254,7 +254,10 @@ > #define MV88E6XXX_PORT_ATU_CTL 0x0c > > /* Offset 0x0D: Priority Override Register */ > -#define MV88E6XXX_PORT_PRI_OVERRIDE 0x0d > +#define MV88E6XXX_PORT_PRI_OVERRIDE 0x0d > +#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_MASK 0x0003 > +#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_48_BYTE 0x0001 > +#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_96_BYTE 0x0002 > > /* Offset 0x0E: Policy Control Register */ > #define MV88E6XXX_PORT_POLICY_CTL 0x0e > @@ -608,4 +611,6 @@ int mv88e6xxx_port_hidden_wait(struct mv88e6xxx_chip *chip); > int mv88e6xxx_port_hidden_read(struct mv88e6xxx_chip *chip, int block, int port, > int reg, u16 *val); > > +int mv88e6xxx_port_enable_tcam(struct mv88e6xxx_chip *chip, int port); > + > #endif /* _MV88E6XXX_PORT_H */ > diff --git a/drivers/net/dsa/mv88e6xxx/tcam.c b/drivers/net/dsa/mv88e6xxx/tcam.c > new file mode 100644 > index 000000000000..51cf26cdc77f > --- /dev/null > +++ b/drivers/net/dsa/mv88e6xxx/tcam.c > @@ -0,0 +1,310 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Marvell 88E6xxx Switch TCAM support > + * > + * Copyright (c) 2026 Luminex Network Intelligence > + */ > + > +#include "linux/list.h" > + > +#include "chip.h" > +#include "tcam.h" > + > +/* TCAM operatation register */ > +#define MV88E6XXX_TCAM_OP 0x00 > +#define MV88E6XXX_TCAM_OP_BUSY 0x8000 > +#define MV88E6XXX_TCAM_OP_OP_MASK 0x7000 > +#define MV88E6XXX_TCAM_OP_OP_FLUSH_ALL 0x1000 > +#define MV88E6XXX_TCAM_OP_OP_FLUSH 0x2000 > +#define MV88E6XXX_TCAM_OP_OP_LOAD 0x3000 > +#define MV88E6XXX_TCAM_OP_OP_GET_NEXT 0x4000 > +#define MV88E6XXX_TCAM_OP_OP_READ 0x5000 > + > +/* TCAM extension register */ > +#define MV88E6XXX_TCAM_EXTENSION 0x01 > + > +/* TCAM keys register 1 */ > +#define MV88E6XXX_TCAM_KEYS1 0x02 > +#define MV88E6XXX_TCAM_KEYS1_FT_MASK 0xC000 > +#define MV88E6XXX_TCAM_KEYS1_SPV_MASK 0x0007 > + > +/* TCAM keys register 1 */ > +#define MV88E6XXX_TCAM_KEYS2 0x03 > +#define MV88E6XXX_TCAM_KEYS2_SPV_MASK 0x00ff > + > +#define MV88E6XXX_TCAM_ING_ACT3 0x04 > +#define MV88E6XXX_TCAM_ING_ACT3_SF 0x0800 > +#define MV88E6XXX_TCAM_ING_ACT3_DPV_MASK 0x07ff > + > +#define MV88E6XXX_TCAM_ING_ACT5 0x06 > +#define MV88E6XXX_TCAM_ING_ACT5_DPV_MODE_MASK 0xc000 > + > +static int mv88e6xxx_tcam_write(struct mv88e6xxx_chip *chip, int reg, u16 val) > +{ > + return mv88e6xxx_write(chip, chip->info->tcam_addr, reg, val); > +} > + > +static int mv88e6xxx_tcam_wait(struct mv88e6xxx_chip *chip) > +{ > + int bit = __bf_shf(MV88E6XXX_TCAM_OP_BUSY); > + > + return mv88e6xxx_wait_bit(chip, chip->info->tcam_addr, > + MV88E6XXX_TCAM_OP, bit, 0); > +} > + > +static int mv88e6xxx_tcam_read_page(struct mv88e6xxx_chip *chip, u8 page, u8 entry) > + > +{ > + u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_READ | (page & 0x3) << 10 | entry; > + int rc; > + > + rc = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val); > + if (rc) > + return rc; > + > + return mv88e6xxx_tcam_wait(chip); > +} > + > +static int mv88e6xxx_tcam_load_page(struct mv88e6xxx_chip *chip, u8 page, u8 entry) > +{ > + u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_LOAD | (page & 0x3) << 10 | entry; > + int rc; > + > + rc = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val); > + if (rc) > + return rc; > + > + return mv88e6xxx_tcam_wait(chip); > +} > + > +static int mv88e6xxx_tcam_flush_entry(struct mv88e6xxx_chip *chip, u8 entry) > +{ > + u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_FLUSH | entry; > + int rc; > + > + rc = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val); > + if (rc) > + return rc; > + > + return mv88e6xxx_tcam_wait(chip); > +} > + > +static int mv88e6xxx_tcam_flush_all(struct mv88e6xxx_chip *chip) > +{ > + u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_FLUSH_ALL; > + int rc; > + > + rc = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val); > + if (rc) > + return rc; > + > + return mv88e6xxx_tcam_wait(chip); > +} > + > +struct mv88e6xxx_tcam_entry *mv88e6xxx_tcam_entry_find(struct mv88e6xxx_chip *chip, > + unsigned long cookie) > +{ > + struct mv88e6xxx_tcam_entry *entry; > + > + list_for_each_entry(entry, &chip->tcam.entries, list) > + if (entry->cookie == cookie) > + return entry; > + > + return NULL; > +} > + > +/* insert tcam entry in ordered list and move existing entries if necessary */ > +static int mv88e6xxx_tcam_insert_entry(struct mv88e6xxx_chip *chip, > + struct mv88e6xxx_tcam_entry *entry) > +{ > + struct mv88e6xxx_tcam_entry *elem; > + struct list_head *hpos; > + int rc; > + > + list_for_each_prev(hpos, &chip->tcam.entries) { > + elem = list_entry(hpos, struct mv88e6xxx_tcam_entry, list); > + if (entry->prio >= elem->prio) > + break; > + > + u8 move_idx = elem->hw_idx + 1; > + > + mv88e6xxx_reg_lock(chip); > + rc = mv88e6xxx_tcam_flush_entry(chip, move_idx); > + mv88e6xxx_reg_unlock(chip); > + if (rc) > + return rc; > + mv88e6xxx_reg_lock(chip); > + rc = chip->info->ops->tcam_ops->entry_add(chip, elem, move_idx); > + mv88e6xxx_reg_unlock(chip); > + if (rc) > + return rc; > + > + elem->hw_idx = move_idx; > + } > + > + if (list_is_head(hpos, &chip->tcam.entries)) { > + entry->hw_idx = 0; > + } else { > + elem = list_entry(hpos, struct mv88e6xxx_tcam_entry, list); > + entry->hw_idx = elem->hw_idx + 1; > + } > + list_add(&entry->list, hpos); > + return 0; > +} > + > +int mv88e6xxx_tcam_entry_add(struct mv88e6xxx_chip *chip, struct mv88e6xxx_tcam_entry *entry) > +{ > + int rc; > + > + rc = mv88e6xxx_tcam_insert_entry(chip, entry); > + if (rc) > + return rc; > + > + mv88e6xxx_reg_lock(chip); > + rc = mv88e6xxx_tcam_flush_entry(chip, entry->hw_idx); > + mv88e6xxx_reg_unlock(chip); > + if (rc) > + goto unlink_out; > + mv88e6xxx_reg_lock(chip); > + rc = chip->info->ops->tcam_ops->entry_add(chip, entry, entry->hw_idx); > + mv88e6xxx_reg_unlock(chip); > + if (rc) > + goto unlink_out; > + > + return 0; > + > +unlink_out: > + list_del(&entry->list); > + return rc; > +} > + > +int mv88e6xxx_tcam_entry_del(struct mv88e6xxx_chip *chip, struct mv88e6xxx_tcam_entry *entry) > +{ > + struct mv88e6xxx_tcam_entry *elem = entry; > + int rc; > + u8 move_idx = entry->hw_idx; > + > + mv88e6xxx_reg_lock(chip); > + rc = mv88e6xxx_tcam_flush_entry(chip, entry->hw_idx); > + mv88e6xxx_reg_unlock(chip); > + > + /* move entries that come after the deleted entry forward */ > + list_for_each_entry_continue(elem, &chip->tcam.entries, list) { > + u8 tmp_idx = elem->hw_idx; > + > + mv88e6xxx_reg_lock(chip); > + rc = chip->info->ops->tcam_ops->entry_add(chip, elem, move_idx); > + mv88e6xxx_reg_unlock(chip); > + > + elem->hw_idx = move_idx; > + move_idx = tmp_idx; > + > + /* flush the last entry after moving entries */ > + if (list_is_last(&elem->list, &chip->tcam.entries)) { > + mv88e6xxx_reg_lock(chip); > + rc = mv88e6xxx_tcam_flush_entry(chip, tmp_idx); > + mv88e6xxx_reg_unlock(chip); > + } > + } > + > + list_del(&entry->list); > + return rc; > +} > + > +static int mv88e6390_tcam_entry_add(struct mv88e6xxx_chip *chip, > + struct mv88e6xxx_tcam_entry *entry, u8 idx) > +{ > + int i; > + int err = 0; > + > + err = mv88e6xxx_tcam_read_page(chip, 2, idx); > + if (err) > + return err; > + if (entry->action.dpv_mode != 0) { > + u16 val = MV88E6XXX_TCAM_ING_ACT3_SF | > + (entry->action.dpv & MV88E6XXX_TCAM_ING_ACT3_DPV_MASK); > + > + err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_ING_ACT3, val); > + if (err) > + return err; > + val = entry->action.dpv_mode << 14; > + err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_ING_ACT5, val); > + if (err) > + return err; > + } > + err = mv88e6xxx_tcam_load_page(chip, 2, idx); > + if (err) > + return err; > + > + err = mv88e6xxx_tcam_read_page(chip, 1, idx); > + if (err) > + return err; > + > + for (i = PAGE0_MATCH_SIZE; i < PAGE0_MATCH_SIZE + PAGE1_MATCH_SIZE; i++) { > + if (entry->key.frame_mask[i]) { > + u16 val = entry->key.frame_mask[i] << 8 | entry->key.frame_data[i]; > + > + err = mv88e6xxx_tcam_write(chip, i - PAGE0_MATCH_SIZE + 2, val); > + if (err) > + return err; > + } > + } > + err = mv88e6xxx_tcam_load_page(chip, 1, idx); > + if (err) > + return err; > + > + err = mv88e6xxx_tcam_read_page(chip, 0, idx); > + if (err) > + return err; > + for (i = 0; i < PAGE0_MATCH_SIZE; i++) { > + if (entry->key.frame_mask[i]) { > + u16 val = entry->key.frame_mask[i] << 8 | entry->key.frame_data[i]; > + > + err = mv88e6xxx_tcam_write(chip, i + 6, val); > + if (err) > + return err; > + } > + } > + u16 spv_mask = entry->key.spv_mask & mv88e6xxx_port_mask(chip); > + u16 spv = entry->key.spv & mv88e6xxx_port_mask(chip); > + // frame type mask bits must be set for a valid entry Wrong comment style. Use /* ... */ also for single-line comments. > + err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_KEYS1, > + MV88E6XXX_TCAM_KEYS1_FT_MASK | (spv_mask & 0x0700) | (spv >> 8)); > + if (err) > + return err; > + > + err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_KEYS2, > + (spv_mask << 8) | (spv & 0x00ff)); > + if (err) > + return err; > + err = mv88e6xxx_tcam_load_page(chip, 0, idx); > + if (err) > + return err; > + > + entry->hw_idx = idx; > + return 0; > +} > + > +static int mv88e6393_tcam_entry_add(struct mv88e6xxx_chip *chip, > + struct mv88e6xxx_tcam_entry *entry, u8 idx) > +{ > + int err; > + > + // select block 0 port 0, then adding an entry is the same as 6390 as other > + // blocks aren't used at the moment Multiline comments also shouldn't use // style > + err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_EXTENSION, 0x00); > + if (err) > + return err; > + > + return mv88e6390_tcam_entry_add(chip, entry, idx); > +} > + > +const struct mv88e6xxx_tcam_ops mv88e6390_tcam_ops = { > + .entry_add = mv88e6390_tcam_entry_add, > + .flush_tcam = mv88e6xxx_tcam_flush_all, > +}; > + > +const struct mv88e6xxx_tcam_ops mv88e6393_tcam_ops = { > + .entry_add = mv88e6393_tcam_entry_add, > + .flush_tcam = mv88e6xxx_tcam_flush_all, > +}; > diff --git a/drivers/net/dsa/mv88e6xxx/tcam.h b/drivers/net/dsa/mv88e6xxx/tcam.h > new file mode 100644 > index 000000000000..5ef5cb8d69d1 > --- /dev/null > +++ b/drivers/net/dsa/mv88e6xxx/tcam.h > @@ -0,0 +1,38 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > + > +/* > + * Copyright (c) 2026 Luminex Network Intelligence > + */ > +#ifndef _MV88E6XXX_TCAM_H_ > +#define _MV88E6XXX_TCAM_H_ > + > +#define PAGE0_MATCH_SIZE 22 > +#define PAGE1_MATCH_SIZE 26 > + > +#define DPV_MODE_NOP 0x0 > +#define DPV_MODE_AND 0x1 > +#define DPV_MODE_OR 0x2 > +#define DPV_MODE_REPLACE 0x3 > + > +int mv88e6xxx_tcam_entry_add(struct mv88e6xxx_chip *chip, struct mv88e6xxx_tcam_entry *entry); > +int mv88e6xxx_tcam_entry_del(struct mv88e6xxx_chip *chip, struct mv88e6xxx_tcam_entry *entry); > +struct mv88e6xxx_tcam_entry *mv88e6xxx_tcam_entry_find(struct mv88e6xxx_chip *chip, > + unsigned long cookie); > +#define mv88e6xxx_tcam_match_set(key, _offset, data, mask) \ > + do { \ > + typeof(_offset) (offset) = (_offset); \ > + BUILD_BUG_ON((offset) + sizeof((data)) > TCAM_MATCH_SIZE); \ > + __mv88e6xxx_tcam_match_set(key, offset, sizeof(data), \ > + (u8 *)&(data), (u8 *)&(mask)); \ > + } while (0) > + > +static inline void __mv88e6xxx_tcam_match_set(struct mv88e6xxx_tcam_key *key, unsigned int offset, > + size_t size, u8 *data, u8 *mask) > +{ > + memcpy(&key->frame_data[offset], data, size); > + memcpy(&key->frame_mask[offset], mask, size); > +} > + > +extern const struct mv88e6xxx_tcam_ops mv88e6390_tcam_ops; > +extern const struct mv88e6xxx_tcam_ops mv88e6393_tcam_ops; > +#endif > diff --git a/drivers/net/dsa/mv88e6xxx/tcflower.c b/drivers/net/dsa/mv88e6xxx/tcflower.c > new file mode 100644 > index 000000000000..4e0cc65759ef > --- /dev/null > +++ b/drivers/net/dsa/mv88e6xxx/tcflower.c > @@ -0,0 +1,138 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Marvell 88E6xxx Switch flower support > + * > + * Copyright (c) 2026 Luminex Network Intelligence > + */ > + > +#include "chip.h" > +#include "tcflower.h" > +#include "tcam.h" > + > +static int mv88e6xx_flower_parse_key(struct mv88e6xxx_chip *chip, > + struct netlink_ext_ack *extack, > + struct flow_cls_offload *cls, > + struct mv88e6xxx_tcam_key *key) > +{ > + struct flow_rule *rule = flow_cls_offload_flow_rule(cls); > + struct flow_dissector *dissector = rule->match.dissector; > + u16 addr_type = 0; > + > + if (dissector->used_keys & > + ~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | > + BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) | > + BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) | > + BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS))) { > + NL_SET_ERR_MSG_MOD(extack, > + "Unsupported keys used"); > + dev_warn(chip->dev, "used_keys: 0x%llx", dissector->used_keys); Why issue two error messages instead of a single one? > + return -EOPNOTSUPP; > + } > + > + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) { > + struct flow_match_control match; > + > + flow_rule_match_control(rule, &match); > + addr_type = match.key->addr_type; > + > + if (flow_rule_has_control_flags(match.mask->flags, > + cls->common.extack)) > + return -EOPNOTSUPP; > + } > + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) { > + struct flow_match_basic match; > + > + flow_rule_match_basic(rule, &match); > + // ethertype Another //-style comment which should be /* ... */ > + mv88e6xxx_tcam_match_set(key, 16, match.key->n_proto, match.mask->n_proto); > + mv88e6xxx_tcam_match_set(key, 27, match.key->ip_proto, match.mask->ip_proto); > + } > + > + if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) { > + struct flow_match_ipv4_addrs match; > + > + flow_rule_match_ipv4_addrs(cls->rule, &match); > + mv88e6xxx_tcam_match_set(key, 30, match.key->src, match.mask->src); > + mv88e6xxx_tcam_match_set(key, 34, match.key->dst, match.mask->dst); > + } > + > + return 0; > +} > + > +int mv88e6xxx_cls_flower_add(struct dsa_switch *ds, int port, > + struct flow_cls_offload *cls, bool ingress) > +{ > + struct mv88e6xxx_chip *chip = ds->priv; > + struct flow_rule *rule = flow_cls_offload_flow_rule(cls); > + struct netlink_ext_ack *extack = cls->common.extack; > + unsigned long cookie = cls->cookie; > + const struct flow_action_entry *act; > + struct mv88e6xxx_tcam_key key = { 0 }; > + struct mv88e6xxx_tcam_entry *entry; > + int rc, i; > + > + if (!mv88e6xxx_has_tcam(chip)) { > + NL_SET_ERR_MSG_MOD(extack, "hardware offload not supported"); > + return -EOPNOTSUPP; > + } > + > + rc = mv88e6xx_flower_parse_key(chip, extack, cls, &key); > + if (rc) > + return rc; > + > + entry = mv88e6xxx_tcam_entry_find(chip, cookie); > + if (entry) > + return -EEXIST; > + > + entry = kzalloc(sizeof(*entry), GFP_KERNEL); > + if (!entry) > + return -ENOMEM; > + > + entry->cookie = cookie; > + entry->prio = cls->common.prio; > + entry->key = key; > + > + flow_action_for_each(i, act, &rule->action) { > + switch (act->id) { > + case FLOW_ACTION_TRAP: { > + int cpu = dsa_upstream_port(ds, port); > + > + entry->action.dpv_mode = DPV_MODE_REPLACE; > + entry->action.dpv = BIT(cpu); > + break; > + } > + default: { > + NL_SET_ERR_MSG_MOD(extack, "action not supported"); > + rc = -EOPNOTSUPP; > + goto err_free_entry; > + } > + } > + } > + > + entry->key.spv = BIT(port); > + entry->key.spv_mask = mv88e6xxx_port_mask(chip); > + > + rc = mv88e6xxx_tcam_entry_add(chip, entry); > + if (rc) > + goto err_free_entry; > + > + return 0; > + > +err_free_entry: > + kfree(entry); > + return rc; > +} > + > +int mv88e6xxx_cls_flower_del(struct dsa_switch *ds, int port, > + struct flow_cls_offload *cls, bool ingress) > +{ > + struct mv88e6xxx_chip *chip = ds->priv; > + struct mv88e6xxx_tcam_entry *entry = mv88e6xxx_tcam_entry_find(chip, cls->cookie); > + int rc = 0; > + > + if (entry) { > + rc = mv88e6xxx_tcam_entry_del(chip, entry); > + kfree(entry); > + } > + return rc; > +} > diff --git a/drivers/net/dsa/mv88e6xxx/tcflower.h b/drivers/net/dsa/mv88e6xxx/tcflower.h > new file mode 100644 > index 000000000000..96bb8da80bec > --- /dev/null > +++ b/drivers/net/dsa/mv88e6xxx/tcflower.h > @@ -0,0 +1,13 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > + > +/* > + * Copyright (c) 2026 Luminex Network Intelligence > + */ > +#ifndef _MV88E6XXX_TCFLOWER_H_ > +#define _MV88E6XXX_TCFLOWER_H_ > + > +int mv88e6xxx_cls_flower_add(struct dsa_switch *ds, int port, > + struct flow_cls_offload *cls, bool ingress); > +int mv88e6xxx_cls_flower_del(struct dsa_switch *ds, int port, > + struct flow_cls_offload *cls, bool ingress); > +#endif > > --- > base-commit: cd51b40495c09b92beea6893663b3d0ed7605e81 > change-id: 20260213-net-next-mv88e6xxx-tcam-f65be16008fb > > Best regards, > -- > Cedric Jehasse > > >