mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: MD Danish Anwar <danishanwar@ti.com>
To: <netdev-bot+sashiko@kernel.org>, Danish <danishanwar@ti.com>
Cc: <andrew+netdev@lunn.ch>, <davem@davemloft.net>,
	<edumazet@google.com>, <kuba@kernel.org>, <pabeni@redhat.com>,
	<s-vadapalli@ti.com>, <rogerq@kernel.org>, <kees@kernel.org>,
	<stefan.wiehler@nokia.com>, <c-vankar@ti.com>, <horms@kernel.org>,
	<arnd@arndb.de>, <vigneshr@ti.com>, <netdev@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>, <linux-omap@vger.kernel.org>
Subject: Re: [PATCH net] net: ti: am65-cpsw-switchdev: flush dynamic FDB entries by port on delete
Date: Wed, 23 Sep 2026 14:22:25 +0530	[thread overview]
Message-ID: <01376ced-000a-415b-8e33-bc2041357a2d@ti.com> (raw)
In-Reply-To: <156f54e9-a0d2-46f4-99e1-f43f02f58673@ti.com>



On 23/09/26 1:57 pm, MD Danish Anwar wrote:
> 
> 
> On 22/09/26 1:46 pm, netdev-bot+sashiko@kernel.org wrote:
>> Thank you for your contribution! Sashiko AI review found 3 potential
>> issue(s) to consider.
>>
>> Critical: 0 · High: 1 · Medium: 2 (1) · Low: 0 (1)
>>
>> If you believe none of the issues are legitimate, please reply to each
>> issue of Medium severity or higher and explain why the AI is wrong.
>> If any of them are legitimate and you plan to respin, please let 
>> patchwork
>> know by including "pw-bot: cr" as a separate line at the end of your 
>> reply
>> (one such reply per series is enough).
>>
>> - [High] New cross-module ALE helper
>>    `cpsw_ale_del_ucast_dynamic_by_port()` is added to…
>> - [Medium] In am65_cpsw_switchdev_event_work() the `!fdb->added_by_user`
>>    gate is dropped for SWITCHDEV_FDB_DEL_TO_DEVICE, but only the `vid ==…
>> - [Medium] Dropping the `!fdb->added_by_user` gate makes every hardware-
>>    learned (dynamic) bridge FDB delete run an unbounded full ALE table…
>>
>> Pre-existing issues:
>> - [Medium] cpsw_ale_del_ucast_dynamic_by_port() performs a full-table
>>    read-modify-write walk over the ALE indirect access window…
>> - [Low] The identical stale-dynamic-FDB defect remains in the sibling
>>    legacy switchdev driver drivers/net/ethernet/ti/cpsw_switchdev.c…
>>
>> -- 
>>
>> net: ti: am65-cpsw-switchdev: flush dynamic FDB entries by port on delete
>>
>> The patch drops the added_by_user gate in the SWITCHDEV_FDB_DEL_TO_DEVICE
>> arm of am65_cpsw_switchdev_event_work() so dynamic FDB deletes reach the
>> ALE, and adds cpsw_ale_del_ucast_dynamic_by_port() to clear dynamic rows
>> for a MAC on a port regardless of vid when the bridge reports vid=0.
>>
>>> diff --git a/drivers/net/ethernet/ti/am65-cpsw-switchdev.c b/drivers/ 
>>> net/ethernet/ti/am65-cpsw-switchdev.c
>>> index 53cdac272b58..8b4640809cf5 100644
>>> --- a/drivers/net/ethernet/ti/am65-cpsw-switchdev.c
>>> +++ b/drivers/net/ethernet/ti/am65-cpsw-switchdev.c
>>> @@ -397,13 +397,23 @@ static void 
>>> am65_cpsw_switchdev_event_work(struct work_struct *work)
>>>                  fdb->addr, fdb->vid, fdb->added_by_user,
>>>                  fdb->offloaded, port_id);
>>> -        if (!fdb->added_by_user || fdb->is_local)
>>> +        if (fdb->is_local)
>>>               break;
>>>           if (memcmp(port->slave.mac_addr, (u8 *)fdb->addr, ETH_ALEN) 
>>> == 0)
>>>               port_id = HOST_PORT_NUM;
>>> -        cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
>>> -                   fdb->vid ? ALE_VLAN : 0, fdb->vid);
>>> +        if (!fdb->added_by_user && !fdb->vid)
>>> +            /* vid=0 here just means "bridge doesn't know the
>>> +             * real vid" (vlan_filtering=0) -- the dynamic entry
>>> +             * may be stored under any nonzero vid, or several.
>>> +             * Delete by (addr, port) instead of guessing a vid.
>>> +             */
>>> +            cpsw_ale_del_ucast_dynamic_by_port(cpsw->ale,
>>> +                               (u8 *)fdb->addr,
>>> +                               port_id);
>>> +        else
>>> +            cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
>>> +                       fdb->vid ? ALE_VLAN : 0, fdb->vid);
>>
>> [Severity: Medium]
>> What happens to dynamic deletes that carry a nonzero vid (bridge with
>> vlan_filtering=1)?  They now land in the else arm, and
>> cpsw_ale_del_ucast() resolves the row through cpsw_ale_match_addr(),
>> which compares only entry type, vlan_id and MAC:
>>
>> drivers/net/ethernet/ti/cpsw_ale.c:cpsw_ale_match_addr() {
>>     ...
>>         if (cpsw_ale_get_vlan_id(ale_entry) != vid)
>>             continue;
>>         cpsw_ale_get_addr(ale_entry, entry_addr);
>>         if (ether_addr_equal(entry_addr, addr))
>>             return idx;
>>     ...
>> }
>>
>> The port argument of cpsw_ale_del_ucast() is never used, and no
>> ucast_type filtering happens, so the row that gets blanked may belong to
>> a different port or be an ALE_UCAST_PERSISTANT row.
>>
>> The MAC roaming case looks reachable: br_fdb_update() emits a dynamic
>> RTM_DELNEIGH for the old port while the ALE has already re-learned the
>> MAC on the new port:
>>
>> net/bridge/br_fdb.c:br_fdb_update() {
>>     ...
>>             if (unlikely(source != READ_ONCE(fdb->dst) &&
>>                      !test_bit(BR_FDB_STICKY, &fdb->flags))) {
>>                 br_switchdev_fdb_notify(br, fdb, RTM_DELNEIGH);
>>                 WRITE_ONCE(fdb->dst, source);
>>     ...
>> }
>>
>> The ADD arm still keeps its added_by_user gate, so nothing reprograms the
>> row afterwards.  Before this patch the gate made that path unreachable
>> for dynamic entries.
>>
>> Should the vid != 0 dynamic case also go through a port-aware and
>> ucast_type-aware delete, so both arms of the new conditional agree on
>> which rows a dynamic delete may clear?
> 
> This is a valid issue and I will fix this in v2. The fix would be to 
> pass vid also to cpsw_ale_del_ucast_dynamic_by_port and if vid=0 delete 
> all entries of a mac. if vid!=0 only delete the entry matching the vid.
> 
> The else path would remain unchanged as it will be only hit for user 
> added entries.
> 
> if (!fdb->added_by_user)
>      cpsw_ale_del_ucast_dynamic_by_port(cpsw>ale, (u8 *)fdb->addr, 
> port_id, fdb->vid);
> 
> else
>      cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
>                  fdb->vid ? ALE_VLAN : 0, fdb->vid);
> 
>>
>> [Severity: Medium]
>> With the added_by_user gate gone, every hardware-learned delete now runs
>> a full ALE table walk in cpsw_ale_del_ucast_dynamic_by_port() while the
>> rtnl_lock() taken at the top of am65_cpsw_switchdev_event_work() is held.
>> The helper has no early exit, since it deliberately clears rows under all
>> vids, so each event costs ale_entries times six MMIO accesses (64 entries
>> on cpsw2g, 512 on am64-cpswxg, more on j721e-cpswxg).
>>
>> The event rate is driven by network traffic: learning, ageing, flush and
>> especially roaming, where one RTM_DELNEIGH is emitted per roam.  An
>> off-box host can keep this going by sourcing frames with the MAC of a
>> host known on another bridge port.
>>
>> Is the added rtnl hold time and MMIO load per untrusted FDB event
>> acceptable here, or would an early exit / bounded search be preferable?
> 
> The cost associated with full ALE walk is accepetable here. The ALE size 
> is small (max 512 entries on the largest supported device) and the event 
> rate driven by normal bridge learning/ageing/roaming is modest, so the 
> added MMIO load and lock hold time are acceptable.
> 
> This is acceptable and doesn't need to be addressed in v2.
> 
>>
>> [Severity: Low]
>> This isn't a bug introduced by this patch, but the legacy sibling driver
>> drivers/net/ethernet/ti/cpsw_switchdev.c still has the exact code this
>> patch replaces in its SWITCHDEV_FDB_DEL_TO_DEVICE arm:
>>
>> drivers/net/ethernet/ti/cpsw_switchdev.c:cpsw_switchdev_event_work() {
>>     ...
>>         if (!fdb->added_by_user || fdb->is_local)
>>             break;
>>     ...
>>         cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port,
>>                    fdb->vid ? ALE_VLAN : 0, fdb->vid);
>>     ...
>> }
>>
>> cpsw_new.c also sets ALE_VLAN_AWARE unconditionally in switch mode, so
>> both premises stated in the changelog seem to hold there as well.  Since
>> the new helper is declared in the shared cpsw_ale.h consumed by both
>> drivers, was leaving cpsw_switchdev.c unconverted intentional?  A note in
>> the changelog about the scope would help.
>>
> 
> This is intentional and unrelated to this patch. Can be added later if 
> needed.
> 
>>>           break;
>>>       default:
>>>           break;
>>> diff --git a/drivers/net/ethernet/ti/cpsw_ale.c b/drivers/net/ 
>>> ethernet/ti/cpsw_ale.c
>>> index e202bba49480..1a2aceda3dbe 100644
>>> --- a/drivers/net/ethernet/ti/cpsw_ale.c
>>> +++ b/drivers/net/ethernet/ti/cpsw_ale.c
>>
>> [ ... ]
>>
>>> @@ -441,6 +441,40 @@ static int cpsw_ale_find_ageable(struct cpsw_ale 
>>> *ale)
>>>       return -ENOENT;
>>>   }
>>> +int cpsw_ale_del_ucast_dynamic_by_port(struct cpsw_ale *ale, const 
>>> u8 *addr,
>>> +                       int port)
>>> +{
>>> +    u32 ale_entry[ALE_ENTRY_WORDS];
>>> +    int type, ucast_type, idx;
>>> +    u8 entry_addr[6];
>>> +    int deleted = 0;
>>> +
>>> +    for (idx = 0; idx < ale->params.ale_entries; idx++) {
>>> +        cpsw_ale_read(ale, idx, ale_entry);
>>
>> [Severity: Medium]
>> This is a pre-existing property of cpsw_ale.c rather than something the
>> patch introduces, but the new helper adds one more unlocked
>> read-modify-write walk over the shared ALE indirect access window:
>>
>> drivers/net/ethernet/ti/cpsw_ale.c:cpsw_ale_read() {
>>     writel_relaxed(idx, ale->params.ale_regs + ALE_TABLE_CONTROL);
>>     for (i = 0; i < ALE_ENTRY_WORDS; i++)
>>         ale_entry[i] = readl_relaxed(ale->params.ale_regs +
>>                          ALE_TABLE + 4 * i);
>>     ...
>> }
>>
>> There are no locks anywhere in cpsw_ale.c, and other table transactions
>> such as am65_cpsw_nuss_ndo_slave_set_rx_mode() ->
>> cpsw_ale_flush_multicast() run under netif_addr_lock_bh() rather than the
>> rtnl_lock held by the switchdev worker.  Should anything ever interleave,
>> one transaction's staged words can be committed under the other's index,
>> and cpsw_ale_flush_multicast() writes back every ADDR/VLAN_ADDR row it
>> reads, which could restore a row this helper just cleared.
>>
>> Is there an assumption documented somewhere that all ALE table accessors
>> run under rtnl?
>>
>>> +        type = cpsw_ale_get_entry_type(ale_entry);
>>> +        if (type != ALE_TYPE_ADDR && type != ALE_TYPE_VLAN_ADDR)
>>> +            continue;
>>> +        if (cpsw_ale_get_mcast(ale_entry))
>>> +            continue;
>>> +        ucast_type = cpsw_ale_get_ucast_type(ale_entry);
>>> +        if (ucast_type == ALE_UCAST_PERSISTANT ||
>>> +            ucast_type == ALE_UCAST_OUI)
>>> +            continue;
>>> +        if (cpsw_ale_get_port_num(ale_entry, ale->port_num_bits) != 
>>> port)
>>> +            continue;
>>> +        cpsw_ale_get_addr(ale_entry, entry_addr);
>>> +        if (!ether_addr_equal(entry_addr, addr))
>>> +            continue;
>>> +
>>> +        memset(ale_entry, 0, sizeof(ale_entry));
>>> +        cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
>>> +        cpsw_ale_write(ale, idx, ale_entry);
>>> +        deleted++;
>>> +    }
>>> +
>>> +    return deleted ? 0 : -ENOENT;
>>> +}
>>
>> [Severity: High]
>> Is an EXPORT_SYMBOL_GPL() missing here?  The neighbouring ALE entry
>> points used across module boundaries all have one:
>>
>> drivers/net/ethernet/ti/cpsw_ale.c
>>     EXPORT_SYMBOL_GPL(cpsw_ale_add_ucast);
>>     ...
>>     EXPORT_SYMBOL_GPL(cpsw_ale_del_ucast);
>>
>> The only caller, am65_cpsw_switchdev_event_work() in
>> am65-cpsw-switchdev.c, is linked into a different module than
>> cpsw_ale.o:
>>
>> drivers/net/ethernet/ti/Makefile
>>     ti-cpsw-ale-y += cpsw_ale.o
>>     ...
>>     obj-$(CONFIG_TI_K3_AM65_CPSW_NUSS) += ti-am65-cpsw-nuss.o ti-cpsw- 
>> sl.o ti-cpsw-ale.o
>>     ti-am65-cpsw-nuss-y := am65-cpsw-nuss.o am65-cpsw-ethtool.o
>>     ti-am65-cpsw-nuss-$(CONFIG_TI_K3_AM65_CPSW_SWITCHDEV) += am65- 
>> cpsw-switchdev.o
>>
>> With CONFIG_TI_K3_AM65_CPSW_NUSS=m and
>> CONFIG_TI_K3_AM65_CPSW_SWITCHDEV=y, does modpost report
>> "cpsw_ale_del_ucast_dynamic_by_port" [drivers/net/ethernet/ti/ti-am65- 
>> cpsw-nuss.ko]
>> undefined?  Built-in configurations would hide this.
>>
> 
> This will be fixed in v2.
> 
>>>   static void cpsw_ale_flush_mcast(struct cpsw_ale *ale, u32 *ale_entry,
>>>                    int port_mask)
>>>   {
>>
>> [ ... ]
>>
> 

pw-bot: cr


      reply	other threads:[~2026-09-23  8:53 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-18  7:59 MD Danish Anwar
2026-09-22  8:16 ` netdev-bot+sashiko
2026-09-23  8:27   ` MD Danish Anwar
2026-09-23  8:52     ` MD Danish Anwar [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=01376ced-000a-415b-8e33-bc2041357a2d@ti.com \
    --to=danishanwar@ti.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=arnd@arndb.de \
    --cc=c-vankar@ti.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=kees@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-omap@vger.kernel.org \
    --cc=netdev-bot+sashiko@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=rogerq@kernel.org \
    --cc=s-vadapalli@ti.com \
    --cc=stefan.wiehler@nokia.com \
    --cc=vigneshr@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®