Re: [PATCH net v2] cxgb4: flower: fix 802.1ad VLAN TPID matching in tc flower filters
From: Harsha Mahadeva <hidden>
Date: 2026-07-10 12:03:31
On Thursday, July 07/09/26, 2026 at 15:18:16 +0530, Paolo Abeni wrote:
From: AI Reviewer <redacted> This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible. Full review at: https://netdev-ai.bots.linux.dev/sashiko/ --- cxgb4: flower: fix 802.1ad VLAN TPID matching in tc flower filters This patch adds an 802.1ad branch to cxgb4_process_flow_match() that programs a port-specific OVLAN register with ETH_P_8021AD, sets bit 0 of the RX control register, clears bit 9 of TP_INGRESS_CONFIG, and populates the filter's outer-VLAN match fields. A few questions about the new code follow inline.
Hi Paolo, Thank you for taking the time to review the patch. Please find my responses inline below.
quoted
diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_flower.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_flower.c index 3307e5042681..8c5cfa6982e7 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_flower.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_flower.c@@ -266,24 +267,48 @@ static void cxgb4_process_flow_match(struct net_device *dev, VLAN_PRIO_SHIFT); vlan_tci_mask = match.mask->vlan_id | (match.mask->vlan_priority << VLAN_PRIO_SHIFT); - fs->val.ivlan = vlan_tci; - fs->mask.ivlan = vlan_tci_mask; - - fs->val.ivlan_vld = 1; - fs->mask.ivlan_vld = 1; - - /* Chelsio adapters use ivlan_vld bit to match vlan packets[ ... ] - if (fs->val.ethtype == ETH_P_8021Q) { - fs->val.ethtype = 0; - fs->mask.ethtype = 0; + + if (match.key->vlan_tpid == cpu_to_be16(ETH_P_8021AD)) {[Medium] The gating condition consults only match.key->vlan_tpid and does not look at match.mask->vlan_tpid. In tc-flower semantics a key value is only significant to the extent the mask covers it. If a rule sets key=0x88a8 with mask=0 (don't care about TPID), this branch is still entered and the adapter registers are still reprogrammed. Should this test be gated on the mask as well, similar to how mlx5 and nfp consult the mask before honoring the key?
This programming is per port configuration rather than per-filter state. The TPID value is used to configure the port's OVLAN parsing behavior, and the corresponding hardware register uses a fixed mask of `0xffff` regardless of the mask supplied in the tc-flower rule. As a result, the intent here is to select the port's outer VLAN TPID based on the requested TPID value, rather than to honor per-rule TPID masking semantics.
quoted
+ struct adapter *adap = netdev2adap(dev); + u32 ovlan_reg, ctl_reg, val, port_id; + + if (!adap) { + netdev_err(dev, "%s: adap not found\n", __func__); + return; + }[Low] netdev2adap() expands to netdev2pinfo(dev)->adapter and is not expected to return NULL for a netdev that tc-flower can bind to. If the check ever did fire, cxgb4_process_flow_match() is void, so this early return also skips the trailing: fs->val.iport = netdev2pinfo(dev)->port_id; fs->mask.iport = ~0; and cxgb4_flow_rule_replace() would proceed to install a filter with iport=0 / iport_mask=0. Should the check either be removed, or the function refactored to propagate an error?
Yes , `netdev2adap()` is not expected to return `NULL` for a valid cxgb4 netdev. I'll remove this check in the next revision
quoted
+ + val = (0xffff << 16) | ETH_P_8021AD;[Low] The literal 0xffff has type int (signed), so 0xffff << 16 evaluates to a value that is not representable in a signed int. Per C11 6.5.7 this is undefined behavior and UBSAN's shift sanitizer flags it. Would 0xffffU << 16 (or a symbolic _V()/_M() macro) be preferable? The layout of MPS_PORT_RX_OVLAN0_A ([31:16]=mask, [15:0]=value) is also not expressed through the driver's usual _S/_V/_F/_M helpers used by neighboring registers. Could symbolic field macros be added?
I'll switch to using an unsigned value and add symbolic field macros in the next revision to match the existing register definition style.
quoted
+ port_id = netdev2pinfo(dev)->port_id; + fs->val.ovlan = vlan_tci; + fs->mask.ovlan = vlan_tci_mask; + fs->val.ovlan_vld = 1; + fs->mask.ovlan_vld = 1;[High] Does this ovlan programming survive on adapters that have VNIC_F set in the cached ingress_config? In cxgb4_filter.c, both __cxgb4_set_filter() and cxgb4_set_hash_filter() unconditionally do: if (adap->params.tp.ingress_config & VNIC_F) { fs->val.ovlan = (fs->val.pf << 13) | fs->val.vf; fs->val.ovlan_vld = fs->val.pfvf_vld; ... } which overwrites the fs->val.ovlan / fs->val.ovlan_vld set here. validate_filter() also rejects with -EOPNOTSUPP when ovlan_vld is set together with VNIC_F, or when the filter mode does not include VNIC_ID_F. On adapters where VNIC_F is enabled, does this branch actually restore matching, or does it silently lose the VID (and, per the next comment, still mutate hardware before failing validation)?
I'll address this issue by moving the register configuration to happen after validate_filter() and install succeeds, rather than at cxgb4_process_flow_match() time as it does now. That way, if validate_filter() rejects the filter including the -EOPNOTSUPP case you mentioned, the hardware registers are never touched in the first place, since we bail out before we get anywhere near programming them
quoted
+ ovlan_reg = PORT_REG(port_id, MPS_PORT_RX_OVLAN0_A); + ctl_reg = PORT_REG(port_id, MPS_PORT_RX_CTL_A); + t4_write_reg(adap, ovlan_reg, val);[High] cxgb4_process_flow_match() is called from cxgb4_flow_rule_replace() before __cxgb4_set_filter() runs validate_filter() and the mailbox/L2T install. Do these three writes need to be deferred until after validation and successful install, so that a filter rejected by validate_filter(), or one that fails allocation/mailbox/L2T setup, does not leave the port and adapter in a reconfigured state with no installed filter to justify it?
will move the register writes to run after validate_filter() and successful install, so a filter that's rejected or fails allocation doesn't leave the port/adapter reconfigured with nothing installed to justify it
Additionally, clear_filter() (cxgb4_filter.c) only tears down L2T/SMT/ CLIP and memsets the filter entry. It does not undo MPS_PORT_RX_OVLAN0, MPS_PORT_RX_CTL bit 0, or TP_INGRESS_CONFIG bit 9. Is a corresponding teardown expected on filter delete/replace so that 'tc filter add' + 'tc filter del' does not leave OVLAN matching permanently enabled and TP_INGRESS_CONFIG bit 9 permanently cleared?
Yes, this needs teardown handling. Since these are per-port registers rather than per-filter state, we can't simply undo them on every individual filter delete another 802.1ad filter on the same port may still depend on that configuration. I'll add a per-port reference count of installed 802.1ad/ovlan filters, incremented when one is installed and decremented in clear_filter() when one is removed, and only restore MPS_PORT_RX_OVLAN0, MPS_PORT_RX_CTL bit 0 and TP_INGRESS_CONFIG bit 9 to their original state once that count reaches zero
MPS_PORT_RX_OVLAN0_A is also hardcoded to slot 0 with ETH_P_8021AD as the TPID. How is a second concurrent flow rule requesting a different outer TPID intended to be represented, given that installing it would clobber slot 0?
Currently we only support ETH_P_8021AD as the outer TPID there's no intent right now to support multiple outer TPIDs concurrently on the same port, since only a single slot (MPS_PORT_RX_OVLAN0_A) is used. I'll add an explicit check so that if a second flow rule requests a different outer TPID while one is already active on the port, it's rejected instead of silently clobbering slot 0
quoted
+ val = t4_read_reg(adap, ctl_reg); + t4_write_reg(adap, ctl_reg, val | 1);[High] This RMW on MPS_PORT_RX_CTL_A is unserialized. If another context modifies other bits of the same register between the read and the write, that update is lost. Is there a lock that should be held here (win0_lock or similar)?quoted
+ t4_tp_wr_bits_indirect(adap, TP_INGRESS_CONFIG_A, 1U << 9, 0);[High, Medium, Medium] t4_tp_wr_bits_indirect() drives the TP_PIO_ADDR_A / TP_PIO_DATA_A indirect-register pair: void t4_tp_wr_bits_indirect(struct adapter *adap, unsigned int addr, unsigned int mask, unsigned int val) { t4_write_reg(adap, TP_PIO_ADDR_A, addr); val |= t4_read_reg(adap, TP_PIO_DATA_A) & ~mask; t4_write_reg(adap, TP_PIO_DATA_A, val); } Before this patch the only in-tree caller was probe-time adap_init0_tweaks(). This patch adds a runtime, user-triggerable caller. Can a concurrent TP_PIO user (t4_tp_pio_read/t4_tp_pio_write when LDST is unavailable, register-dump code, or another tc-flower install on a different netdev of the same adapter) interleave between the ADDR write and the DATA read/write and cause the wrong TP register to be read or written? What serializes the TP_PIO_ADDR_A/TP_PIO_DATA_A pair here?
I will fix above in next revision .Will take locking around the MPS_PORT_RX_CTL_A RMW and TP_PIO_ADDR_A/TP_PIO_DATA_A sequence in t4_tp_wr_bits_indirect(), so both are serialized against concurrent access
Separately, the hardware bit is toggled but adap->params.tp.ingress_config is not updated. That shadow is consulted throughout cxgb4_filter.c (validate_filter, __cxgb4_set_filter, cxgb4_set_hash_filter) and l2t.c to make VNIC_F / USE_ENC_IDX_F / ovlan-vs-pfvf decisions. Should the shadow be kept in sync with the hardware write? Also, bit 9 of TP_INGRESS_CONFIG_A is written as the bare literal 1U << 9, while neighboring bits of the same register are exposed as _S/_V/_F macros in t4_regs.h (CSUM_HAS_PSEUDO_HDR_S=10, VNIC_S=11, USE_ENC_IDX_S=13). What feature does bit 9 control, and could a symbolic name be introduced? The commit message enumerates three actions (OVLAN register, RX control enable, filter fields) but does not mention this fourth adapter-wide write.
Bit 9 is RM_OVLAN and when its unset, it stops hardware from stripping the outer VLAN tag. I will add a symbolic _S/_F macro for it (replacing the bare 1U << 9) and call out this fourth write in the commit message, and will also update adap->params.tp.ingress_config alongside the hardware write
quoted
+ } else { + fs->val.ivlan = vlan_tci; + fs->mask.ivlan = vlan_tci_mask; + fs->val.ivlan_vld = 1; + fs->mask.ivlan_vld = 1; + + /* Chelsio adapters use ivlan_vld bit to match vlan packets + * as 802.1Q. Also, when vlan tag is present in packets, + * ethtype match is used then to match on ethtype of inner + * header ie. the header following the vlan header. + * So, set the ivlan_vld based on ethtype info supplied by + * TC for vlan packets if its 802.1Q. And then reset the + * ethtype value else, hw will try to match the supplied + * ethtype value with ethtype of inner header. + */ + + if (fs->val.ethtype == ETH_P_8021Q) { + fs->val.ethtype = 0; + fs->mask.ethtype = 0; + }[High] The preserved comment states that the hardware ethtype field matches the ethtype after the outer VLAN tag (the inner header), and the 802.1Q branch resets fs->val.ethtype/fs->mask.ethtype to avoid a mismatch. The new 802.1ad branch above does not perform the equivalent reset. For a common rule such as: tc filter add ... flower \ vlan_ethtype 0x88a8 vlan_id X ... FLOW_DISSECTOR_KEY_BASIC processing earlier in the same function has already set fs->val.ethtype = 0x88a8, but the ethtype field is matched against the inner header (which for real 802.1ad traffic is 0x8100 or an L3 protocol, never 0x88a8). Should the 802.1ad branch clear fs->val.ethtype / fs->mask.ethtype the same way the 802.1Q branch does?
The preserved comment applies to a single-tagged VLAN packet. I'll rework the logic to handle ethtype reset correctly for both the single-tagged and QinQ (802.1ad) cases
quoted
} }diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h b/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h index 695916ba0405..38c585f3b1ad 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h +++ b/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h@@ -1921,6 +1921,9 @@ #define MAC_PORT_PTP_SUM_LO_A 0x990 #define MAC_PORT_PTP_SUM_HI_A 0x994 +#define MPS_PORT_RX_OVLAN0_A 0x120 +#define MPS_PORT_RX_CTL_A 0X100[Low] This isn't a bug, but the hex prefix on 0X100 is uppercase while the line above (and every neighboring macro) uses lowercase 0x. Was that intentional?
Its not intentional ,I will fix this in next revision.Thanks for flagging it
quoted
+ #define MPS_CMN_CTL_A 0x9000-- This is an AI-generated review.