* [PATCH] net: xscale: add missing MODULE_DEVICE_TABLE()
From: Pengpeng Hou @ 2026-07-04 12:27 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Richard Cochran
Cc: Pengpeng Hou, netdev, linux-kernel
The driver has an OF match table wired to .of_match_table, but does
not export the table with MODULE_DEVICE_TABLE().
Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias
information is generated for OF based module autoloading.
This is a source-level fix. It does not claim dynamic hardware
reproduction; the evidence is the driver-owned match table, its use by
the platform driver, and the missing module alias publication.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/xscale/ptp_ixp46x.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/xscale/ptp_ixp46x.c b/drivers/net/ethernet/xscale/ptp_ixp46x.c
index 558c4f8d23f7..c805d1df5d4e 100644
--- a/drivers/net/ethernet/xscale/ptp_ixp46x.c
+++ b/drivers/net/ethernet/xscale/ptp_ixp46x.c
@@ -307,6 +307,7 @@ static const struct of_device_id ptp_ixp_match[] = {
},
{ },
};
+MODULE_DEVICE_TABLE(of, ptp_ixp_match);
static struct platform_driver ptp_ixp_driver = {
.driver = {
^ permalink raw reply related
* Re: [PATCH net] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Michael S. Tsirkin @ 2026-07-04 12:28 UTC (permalink / raw)
To: Simon Schippers
Cc: Willem de Bruijn, Jason Wang, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, Simon Horman,
Jonathan Corbet, Shuah Khan, Andrew Lunn, Tim Gebauer,
Brett Sheffield, linux-doc, linux-kernel
In-Reply-To: <20260704112058.95421-1-simon.schippers@tu-dortmund.de>
On Sat, Jul 04, 2026 at 01:20:58PM +0200, Simon Schippers wrote:
> Commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop
> when a qdisc is present") did not show a relevant performance regression
> in my testing but on Brett Sheffield's librecast testbed it shows a
> significant performance drop. The regression can be pinpointed when
> multiple iperf3 TCP threads are sending. For 8 threads the performance
> dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is the reason why this
> patch makes the qdisc backpressure behavior opt-in.
>
> One option to accomplish the opt-in would be to set the default qdisc to
> noqueue at init. However this may also break userspace as users might
> have chosen a custom qdisc even though most of the qdiscs did nothing
> for tun/tap in the past due to missing backpressure...
>
> This is the reason why in this patch, the flag IFF_BACKPRESSURE is
> introduced instead which is required to enable the backpressure logic.
> This means the stopping logic in tun_net_xmit() and the waking logic in
> __tun_wake_queue() are skipped if the flag is disabled.
>
> In tun_set_iff(), netif_tx_wake_all_queues() is replaced with looping
> over all tfiles in which the netdev queues are woken and cons_cnt is
> reset while the consumer_lock and producer_lock are held. This is to
> ensure that tun_net_xmit() can not stop the queue concurrently, avoiding
> a possible stall.
>
> The documentation in tuntap.rst is updated accordingly.
>
> Fixes: 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present")
> Reported-by: Brett Sheffield <brett@librecast.net>
> Closes: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/T/#u
> Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
I don't object to this approach. At the same time - a new UAPI outside
the merge window? Is this acceptable to net maintainers?
> ---
> Documentation/networking/tuntap.rst | 17 +++++++++++++++++
> drivers/net/tun.c | 29 +++++++++++++++++++++++------
> include/uapi/linux/if_tun.h | 1 +
> tools/include/uapi/linux/if_tun.h | 1 +
> 4 files changed, 42 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst
> index 4d7087f727be..599264825dd2 100644
> --- a/Documentation/networking/tuntap.rst
> +++ b/Documentation/networking/tuntap.rst
> @@ -206,6 +206,23 @@ enable is true we enable it, otherwise we disable it::
> return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
> }
>
> +3.4 qdisc backpressure
> +----------------------
> +
> +Starting with Linux 7.2, IFF_BACKPRESSURE can be set to enable qdisc
> +backpressure. Without it, TX drops occur when the internal ring buffer is
> +full. With it, the kernel stops the TX queue instead, letting the qdisc
> +hold packets. Drops only occur as a rare race. This can benefit protocols
> +like TCP that react to drops. Backpressure requires a qdisc to be
> +attached and has no effect with noqueue.
> +
> +The TUN/TAP ring buffer size can be reduced alongside this flag to
> +further shift buffering into the qdisc and reduce bufferbloat, but comes
> +at possible performance cost.
> +
> +When running multiple network streams in parallel, the flag may reduce
> +performance due to the extra overhead of the backpressure mechanism.
> +
> Universal TUN/TAP device driver Frequently Asked Question
> =========================================================
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index ffbe6f13fb1f..3bf8a73a0816 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev,
> #define TUN_FASYNC IFF_ATTACH_QUEUE
>
> #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
> - IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS)
> + IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \
> + IFF_BACKPRESSURE)
>
> #define GOODCOPY_LEN 128
>
> @@ -1077,7 +1078,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
>
> spin_lock(&tfile->tx_ring.producer_lock);
> ret = __ptr_ring_produce(&tfile->tx_ring, skb);
> - if (!qdisc_txq_has_no_queue(queue) &&
> + if ((tun->flags & IFF_BACKPRESSURE) &&
> + !qdisc_txq_has_no_queue(queue) &&
> __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
> netif_tx_stop_queue(queue);
> /* Paired with smp_mb() in __tun_wake_queue() */
> @@ -2151,8 +2153,12 @@ static ssize_t tun_put_user(struct tun_struct *tun,
> static void __tun_wake_queue(struct tun_struct *tun,
> struct tun_file *tfile, int consumed)
> {
> - struct netdev_queue *txq = netdev_get_tx_queue(tun->dev,
> - tfile->queue_index);
> + struct netdev_queue *txq;
> +
> + if (!(tun->flags & IFF_BACKPRESSURE))
> + return;
> +
> + txq = netdev_get_tx_queue(tun->dev, tfile->queue_index);
>
> /* Paired with smp_mb__after_atomic() in tun_net_xmit() */
> smp_mb();
> @@ -2893,8 +2899,19 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
> /* Make sure persistent devices do not get stuck in
> * xoff state.
> */
> - if (netif_running(tun->dev))
> - netif_tx_wake_all_queues(tun->dev);
> + if (netif_running(tun->dev)) {
> + for (int i = 0; i < tun->numqueues; i++) {
> + struct tun_file *i_tfile;
> +
> + i_tfile = rtnl_dereference(tun->tfiles[i]);
> + spin_lock_bh(&i_tfile->tx_ring.consumer_lock);
> + spin_lock(&i_tfile->tx_ring.producer_lock);
> + netif_wake_subqueue(tun->dev, i_tfile->queue_index);
> + i_tfile->cons_cnt = 0;
> + spin_unlock(&i_tfile->tx_ring.producer_lock);
> + spin_unlock_bh(&i_tfile->tx_ring.consumer_lock);
> + }
> + }
>
> strscpy(ifr->ifr_name, tun->dev->name);
> return 0;
> diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
> index 79d53c7a1ebd..73a77141315c 100644
> --- a/include/uapi/linux/if_tun.h
> +++ b/include/uapi/linux/if_tun.h
> @@ -69,6 +69,7 @@
> #define IFF_NAPI_FRAGS 0x0020
> /* Used in TUNSETIFF to bring up tun/tap without carrier */
> #define IFF_NO_CARRIER 0x0040
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h
> index 2ec07de1d73b..97b670f5bc0a 100644
> --- a/tools/include/uapi/linux/if_tun.h
> +++ b/tools/include/uapi/linux/if_tun.h
> @@ -67,6 +67,7 @@
> #define IFF_TAP 0x0002
> #define IFF_NAPI 0x0010
> #define IFF_NAPI_FRAGS 0x0020
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> --
> 2.43.0
^ permalink raw reply
* Re: [PATCH net] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Michael S. Tsirkin @ 2026-07-04 12:52 UTC (permalink / raw)
To: Simon Schippers
Cc: Willem de Bruijn, Jason Wang, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, Simon Horman,
Jonathan Corbet, Shuah Khan, Andrew Lunn, Tim Gebauer,
Brett Sheffield, linux-doc, linux-kernel
In-Reply-To: <20260704112058.95421-1-simon.schippers@tu-dortmund.de>
On Sat, Jul 04, 2026 at 01:20:58PM +0200, Simon Schippers wrote:
> Commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop
> when a qdisc is present") did not show a relevant performance regression
> in my testing but on Brett Sheffield's librecast testbed it shows a
> significant performance drop. The regression can be pinpointed when
> multiple iperf3 TCP threads are sending. For 8 threads the performance
> dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is the reason why this
> patch makes the qdisc backpressure behavior opt-in.
>
> One option to accomplish the opt-in would be to set the default qdisc to
> noqueue at init. However this may also break userspace as users might
> have chosen a custom qdisc even though most of the qdiscs did nothing
> for tun/tap in the past due to missing backpressure...
>
> This is the reason why in this patch, the flag IFF_BACKPRESSURE is
> introduced instead which is required to enable the backpressure logic.
> This means the stopping logic in tun_net_xmit() and the waking logic in
> __tun_wake_queue() are skipped if the flag is disabled.
>
> In tun_set_iff(), netif_tx_wake_all_queues() is replaced with looping
> over all tfiles in which the netdev queues are woken and cons_cnt is
> reset while the consumer_lock and producer_lock are held. This is to
> ensure that tun_net_xmit() can not stop the queue concurrently, avoiding
> a possible stall.
>
> The documentation in tuntap.rst is updated accordingly.
>
> Fixes: 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present")
> Reported-by: Brett Sheffield <brett@librecast.net>
> Closes: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/T/#u
> Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
the patch itself makes sense
Acked-by: Michael S. Tsirkin <mst@redhat.com>
The issue is it would ideally be in next, but we need it now
to fix the regression introduced by 1d6e569b7d0c.
> ---
> Documentation/networking/tuntap.rst | 17 +++++++++++++++++
> drivers/net/tun.c | 29 +++++++++++++++++++++++------
> include/uapi/linux/if_tun.h | 1 +
> tools/include/uapi/linux/if_tun.h | 1 +
> 4 files changed, 42 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst
> index 4d7087f727be..599264825dd2 100644
> --- a/Documentation/networking/tuntap.rst
> +++ b/Documentation/networking/tuntap.rst
> @@ -206,6 +206,23 @@ enable is true we enable it, otherwise we disable it::
> return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
> }
>
> +3.4 qdisc backpressure
> +----------------------
> +
> +Starting with Linux 7.2, IFF_BACKPRESSURE can be set to enable qdisc
> +backpressure. Without it, TX drops occur when the internal ring buffer is
> +full. With it, the kernel stops the TX queue instead, letting the qdisc
> +hold packets. Drops only occur as a rare race. This can benefit protocols
> +like TCP that react to drops. Backpressure requires a qdisc to be
> +attached and has no effect with noqueue.
> +
> +The TUN/TAP ring buffer size can be reduced alongside this flag to
> +further shift buffering into the qdisc and reduce bufferbloat, but comes
> +at possible performance cost.
> +
> +When running multiple network streams in parallel, the flag may reduce
> +performance due to the extra overhead of the backpressure mechanism.
> +
> Universal TUN/TAP device driver Frequently Asked Question
> =========================================================
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index ffbe6f13fb1f..3bf8a73a0816 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev,
> #define TUN_FASYNC IFF_ATTACH_QUEUE
>
> #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
> - IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS)
> + IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \
> + IFF_BACKPRESSURE)
>
> #define GOODCOPY_LEN 128
>
> @@ -1077,7 +1078,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
>
> spin_lock(&tfile->tx_ring.producer_lock);
> ret = __ptr_ring_produce(&tfile->tx_ring, skb);
> - if (!qdisc_txq_has_no_queue(queue) &&
> + if ((tun->flags & IFF_BACKPRESSURE) &&
> + !qdisc_txq_has_no_queue(queue) &&
> __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
> netif_tx_stop_queue(queue);
> /* Paired with smp_mb() in __tun_wake_queue() */
> @@ -2151,8 +2153,12 @@ static ssize_t tun_put_user(struct tun_struct *tun,
> static void __tun_wake_queue(struct tun_struct *tun,
> struct tun_file *tfile, int consumed)
> {
> - struct netdev_queue *txq = netdev_get_tx_queue(tun->dev,
> - tfile->queue_index);
> + struct netdev_queue *txq;
> +
> + if (!(tun->flags & IFF_BACKPRESSURE))
> + return;
> +
> + txq = netdev_get_tx_queue(tun->dev, tfile->queue_index);
>
> /* Paired with smp_mb__after_atomic() in tun_net_xmit() */
> smp_mb();
> @@ -2893,8 +2899,19 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
> /* Make sure persistent devices do not get stuck in
> * xoff state.
> */
> - if (netif_running(tun->dev))
> - netif_tx_wake_all_queues(tun->dev);
> + if (netif_running(tun->dev)) {
> + for (int i = 0; i < tun->numqueues; i++) {
> + struct tun_file *i_tfile;
> +
> + i_tfile = rtnl_dereference(tun->tfiles[i]);
> + spin_lock_bh(&i_tfile->tx_ring.consumer_lock);
> + spin_lock(&i_tfile->tx_ring.producer_lock);
> + netif_wake_subqueue(tun->dev, i_tfile->queue_index);
> + i_tfile->cons_cnt = 0;
> + spin_unlock(&i_tfile->tx_ring.producer_lock);
> + spin_unlock_bh(&i_tfile->tx_ring.consumer_lock);
> + }
> + }
>
> strscpy(ifr->ifr_name, tun->dev->name);
> return 0;
> diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
> index 79d53c7a1ebd..73a77141315c 100644
> --- a/include/uapi/linux/if_tun.h
> +++ b/include/uapi/linux/if_tun.h
> @@ -69,6 +69,7 @@
> #define IFF_NAPI_FRAGS 0x0020
> /* Used in TUNSETIFF to bring up tun/tap without carrier */
> #define IFF_NO_CARRIER 0x0040
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h
> index 2ec07de1d73b..97b670f5bc0a 100644
> --- a/tools/include/uapi/linux/if_tun.h
> +++ b/tools/include/uapi/linux/if_tun.h
> @@ -67,6 +67,7 @@
> #define IFF_TAP 0x0002
> #define IFF_NAPI 0x0010
> #define IFF_NAPI_FRAGS 0x0020
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> --
> 2.43.0
^ permalink raw reply
* Re: [RFC] xdp: add device context to bpf_xdp_link_attach_failed tracepoint
From: Leon Hwang @ 2026-07-04 13:28 UTC (permalink / raw)
To: Masashi Honma
Cc: netdev, bpf, linux-trace-kernel, ast, daniel, kuba, hawk, andrii,
rostedt, mhiramat, edumazet, pabeni, linux-kernel
In-Reply-To: <CAFk-A4n9U0r07tn6XHHqmOZ++-bmM4bSd-SwdpEU-88gjKts7Q@mail.gmail.com>
Pls do not top-post.
See
https://docs.kernel.org/process/submitting-patches.html#use-trimmed-interleaved-replies-in-email-discussions.
On 2026/7/4 13:50, Masashi Honma wrote:
> Thank you Leon. I think this is a much better direction than my RFC, so I'll
> drop my tracepoint proposal and support your approach instead.
>
> Since a user-space dependency on the existing tracepoint would make it hard
> for you to retire it, I'll make sure the Cilium PR doesn't rely on the
> tracepoint either.
Probably, you can get the 'extack->_msg' by tracing dev_xdp_attach using
kprobe+kretprobe or kprobe.session, if the extack is not NULL.
>
> Is there anything I can help with on the new approach?
You are welcome to review the series in the future.
Thanks,
Leon
[...]
^ permalink raw reply
* [PATCH net] net: airoha: fix ETS channel derivation in airoha_tc_setup_qdisc_ets()
From: Lorenzo Bianconi @ 2026-07-04 14:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev
Derive the hardware QoS channel from opt->parent instead of opt->handle
in airoha_tc_setup_qdisc_ets(). The ETS qdisc handle is either
user-specified or auto-allocated by qdisc_alloc_handle() and bears no
relation to the HTB leaf classid that identifies the hardware channel.
HTB derives the channel from TC_H_MIN(opt->classid), and ETS is always
attached as a child of an HTB leaf, so its opt->parent matches that
classid. Using opt->handle instead can cause two ETS qdiscs on different
HTB leaves to collide on the same hardware channel, corrupting scheduler
configuration and stats.
Fixes: 20bf7d07c956 ("net: airoha: Add sched ETS offload support")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 59001fd4b6f7..fac2aaefffff 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2504,8 +2504,7 @@ static int airoha_tc_setup_qdisc_ets(struct net_device *dev,
if (opt->parent == TC_H_ROOT)
return -EINVAL;
- channel = TC_H_MAJ(opt->handle) >> 16;
- channel = channel % AIROHA_NUM_QOS_CHANNELS;
+ channel = TC_H_MIN(opt->parent) % AIROHA_NUM_QOS_CHANNELS;
switch (opt->command) {
case TC_ETS_REPLACE:
---
base-commit: d6456743424721a837e1509b912f362caaeecd97
change-id: 20260704-airoha-ets-handle-fix-6c166de3d396
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* Re: [PATCH 1/1] net: usb: aqc111: fix set_mac_address return value for bonding
From: Andrew Lunn @ 2026-07-04 14:32 UTC (permalink / raw)
To: Hanson Wang; +Cc: netdev, linux-usb, oneukum
In-Reply-To: <20260704084258.3513069-1-hanson.wang@ugreen.com>
On Sat, Jul 04, 2026 at 04:42:58PM +0800, Hanson Wang wrote:
> Hi Andrew,
>
> Thanks for the review. You are right that this is not bonding-specific -
> any caller of ndo_set_mac_address() (including netif_set_mac_address()
> and "ip link set address") expects 0 on success and treats any non-zero
> return value as failure.
>
> I audited all drivers under drivers/net/usb/ that implement a custom
> ndo_set_mac_address callback:
>
> aqc111.c - BUG: returns usb_control_msg byte count (6) [this patch]
> ax88179_178a.c - OK: returns 0 after ax_write_cmd()
> asix_common.c - OK: returns 0 (async write to hardware)
> ch397.c - OK: returns 0
> dm9601.c - OK: returns 0 (async write)
> lan78xx.c - OK: returns 0
> mcs7830.c - OK: returns 0
> qmi_wwan.c - OK: returns 0
> r8152.c/r8157.c - OK: returns 0 on success (via usb_autopm_get_interface)
> rtl8150.c - OK: returns 0
> sr9700.c - OK: returns 0 (async write)
> sr9800.c - OK: returns 0 (async write)
Thanks for reviewing these drivers.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply
* [PATCH] net: alacritech: add missing MODULE_DEVICE_TABLE()
From: Pengpeng Hou @ 2026-07-04 15:20 UTC (permalink / raw)
To: Lino Sanfilippo, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: Pengpeng Hou, netdev, linux-kernel
The driver has a match table for the pci bus wired into its driver
structure, but the table is not exported with MODULE_DEVICE_TABLE().
Add the missing MODULE_DEVICE_TABLE() entry so module alias information
is generated for automatic module loading.
This is a source-level fix. It does not claim dynamic hardware
reproduction; the evidence is the driver-owned match table, its use by
the driver registration structure, and the missing module alias
publication.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/alacritech/slicoss.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/alacritech/slicoss.c b/drivers/net/ethernet/alacritech/slicoss.c
index c1949ca060ca..14b74c09233e 100644
--- a/drivers/net/ethernet/alacritech/slicoss.c
+++ b/drivers/net/ethernet/alacritech/slicoss.c
@@ -34,6 +34,7 @@ static const struct pci_device_id slic_id_tbl[] = {
PCI_DEVICE_ID_ALACRITECH_OASIS) },
{ 0 }
};
+MODULE_DEVICE_TABLE(pci, slic_id_tbl);
static const char slic_stats_strings[][ETH_GSTRING_LEN] = {
"rx_packets",
--
2.53.0
^ permalink raw reply related
* [PATCH] net: davicom: dm9051: add missing MODULE_DEVICE_TABLE()
From: Pengpeng Hou @ 2026-07-04 15:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Pengpeng Hou, netdev, linux-kernel
The driver has a match table for the spi bus wired into its driver
structure, but the table is not exported with MODULE_DEVICE_TABLE().
Add the missing MODULE_DEVICE_TABLE() entry so module alias information
is generated for automatic module loading.
This is a source-level fix. It does not claim dynamic hardware
reproduction; the evidence is the driver-owned match table, its use by
the driver registration structure, and the missing module alias
publication.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/davicom/dm9051.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/davicom/dm9051.c b/drivers/net/ethernet/davicom/dm9051.c
index 59ea48d4c9de..194494ac5582 100644
--- a/drivers/net/ethernet/davicom/dm9051.c
+++ b/drivers/net/ethernet/davicom/dm9051.c
@@ -1241,6 +1241,7 @@ static const struct spi_device_id dm9051_id_table[] = {
{ "dm9051", 0 },
{}
};
+MODULE_DEVICE_TABLE(spi, dm9051_id_table);
static struct spi_driver dm9051_driver = {
.driver = {
--
2.53.0
^ permalink raw reply related
* Re: [RFC PATCH bpf-next v1 0/7] xdp: RX checksum metadata hint and checksum assertion over redirect
From: Jesper Dangaard Brouer @ 2026-07-04 16:41 UTC (permalink / raw)
To: Lorenzo Bianconi, Vladimir Vdovin
Cc: sdf, kuba, andrii, ast, daniel, john.fastabend, martin.lau,
sdf.kernel, bpf, netdev, David S. Miller, Alexander Lobakin,
Eric Dumazet, kernel-team
In-Reply-To: <akZ7QPfn83OUx5Vm@lore-desk>
On 02/07/2026 16.52, Lorenzo Bianconi wrote:
[...]
>>
>> What's left that is genuinely separate is the "assertion" half -- a non-dev-bound
>> bpf_xdp_assert_rx_csum() that preserves the HW verdict across a
>> cpumap/redirect: it sets a flag on the xdp_buff that rides into the
>> xdp_frame and becomes skb->ip_summed = CHECKSUM_UNNECESSARY in
>> __xdp_build_skb_from_frame().
>>
>> Should I resend that as a small standalone series (v2, assert-only)?
>> It also looks like a PoC that you and Jakub discussed on v3 [1].
>
> I think we should address the 'CHECKSUM_COMPLETE' use case for it, let's work
> on the problem later, even Jesper is interested in it ;)
>
Yes, I'm very interested in this use-case of transferring HW offload
info when XDP_REDIRECT'ing into CPUMAP or veth. In my patchset[1] I've
added rx_hash, rx_vlan_tag, and rx_timestamp, but not csum as it was not
available. My short term use-case is: RX-hash as setting it earlier
allows the GRO engine to hash flows (see dev_gro_receive[2]), plus it
carries info on packet protocol type TCP/UDP IPv4/IPv6.
- [1]
https://lore.kernel.org/all/175146824674.1421237.18351246421763677468.stgit@firesoul/#r
- [2] https://elixir.bootlin.com/linux/v7.1.2/source/net/core/gro.c#L477
The long term[3] vision behind the patchset is that xdp_frame becomes
the "mini-SKB" that carries enough offload info to avoid allocating SKBs
in the driver. The ICE driver is very close to this vision. See [4]
where it calls xdp_build_skb_from_buff() and later fills out SKB fields
with HW offloads (ice_process_skb_fields).
What I'm asking is that we generalize this idea of transferring or
storing HW offloads, that are relevant to the SKB. My patchset makes
info avail to both xdp_buff and xdp_frame by storing data[5] in the
packet top headroom (extending xdp_frame). And like your patch[6] use
xdp_buff_flags to tell with is active. You patch only needed a single
flag as you only handle CHECKSUM_UNNECESSARY, but Lorenzo's patch also
have CHECKSUM_COMPLETE, for which we need to store a csum.
I cannot remember if it was Kuba or Eric that suggested (at netdevconf)
that instead of using a fixed struct, that I should use the same scheme
as SKB extensions. We could call it SKB core-extensions, to signal the
possibility to move existing SKB member into this area.
--Jesper
- [3]
https://lore.kernel.org/all/fbb026f9-54cf-49ba-b0dc-0df0f54c6961@kernel.org/
- [4]
https://elixir.bootlin.com/linux/v7.1.2/source/drivers/net/ethernet/intel/ice/ice_txrx.c#L1044-L1060
- [5]
https://lore.kernel.org/all/175146829944.1421237.13943404585579626611.stgit@firesoul/
- [6] https://lore.kernel.org/all/20260630191510.81402-1-deliran@verdict.gg/
^ permalink raw reply
* [PATCH net v2] bnge/bng_re: fix ring ID widths
From: Vikas Gupta @ 2026-07-04 16:47 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, andrew+netdev, horms
Cc: netdev, linux-kernel, linux-rdma, leonro, jgg, bhargava.marreddy,
rahul-rg.gupta, vsrama-krishna.nemani, rajashekar.hudumula,
ajit.khaparde, Vikas Gupta, Siva Reddy Kallam, Dharmender Garg,
Yendapally Reddy Dhananjaya Reddy
Firmware requires more than 16 bits to address TX ring IDs for its
internal QP management. Widen the associated HSI ring ID fields to
32 bits. The values firmware assigns remain within 24 bits, bounded
by the hardware doorbell XID field.
RX, completion, and NQ ring IDs are unaffected and remain 16-bit.
Fixes: 42d1c54d6248 ("bnge/bng_re: Add a new HSI")
Signed-off-by: Vikas Gupta <vikas.gupta@broadcom.com>
Reviewed-by: Siva Reddy Kallam <siva.kallam@broadcom.com>
Reviewed-by: Dharmender Garg <dharmender.garg@broadcom.com>
Reviewed-by: Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com>
---
V2:
Sashiko review:
- Updated commit message only, no code change.
- Sashiko's concern about XID overflow is valid in theory but is
handled by firmware, which guarantees TX ring IDs stay within the
24-bit hardware doorbell XID field.
- Backward compatibility with older firmware is not a concern.
drivers/infiniband/hw/bng_re/bng_dev.c | 6 +--
drivers/net/ethernet/broadcom/bnge/bnge.h | 1 +
.../ethernet/broadcom/bnge/bnge_hwrm_lib.c | 8 +--
.../ethernet/broadcom/bnge/bnge_hwrm_lib.h | 2 +-
.../net/ethernet/broadcom/bnge/bnge_netdev.c | 50 +++++++++----------
.../net/ethernet/broadcom/bnge/bnge_netdev.h | 4 +-
.../net/ethernet/broadcom/bnge/bnge_rmem.h | 2 +-
include/linux/bnge/hsi.h | 7 ++-
8 files changed, 39 insertions(+), 41 deletions(-)
diff --git a/drivers/infiniband/hw/bng_re/bng_dev.c b/drivers/infiniband/hw/bng_re/bng_dev.c
index 71a7ca2196ad..311c8bc93160 100644
--- a/drivers/infiniband/hw/bng_re/bng_dev.c
+++ b/drivers/infiniband/hw/bng_re/bng_dev.c
@@ -113,7 +113,7 @@ static void bng_re_fill_fw_msg(struct bnge_fw_msg *fw_msg, void *msg,
}
static int bng_re_net_ring_free(struct bng_re_dev *rdev,
- u16 fw_ring_id, int type)
+ u32 fw_ring_id, int type)
{
struct bnge_auxr_dev *aux_dev = rdev->aux_dev;
struct hwrm_ring_free_input req = {};
@@ -123,7 +123,7 @@ static int bng_re_net_ring_free(struct bng_re_dev *rdev,
bng_re_init_hwrm_hdr((void *)&req, HWRM_RING_FREE);
req.ring_type = type;
- req.ring_id = cpu_to_le16(fw_ring_id);
+ req.ring_id = cpu_to_le32(fw_ring_id);
bng_re_fill_fw_msg(&fw_msg, (void *)&req, sizeof(req), (void *)&resp,
sizeof(resp), BNGE_DFLT_HWRM_CMD_TIMEOUT);
rc = bnge_send_msg(aux_dev, &fw_msg);
@@ -161,7 +161,7 @@ static int bng_re_net_ring_alloc(struct bng_re_dev *rdev,
sizeof(resp), BNGE_DFLT_HWRM_CMD_TIMEOUT);
rc = bnge_send_msg(aux_dev, &fw_msg);
if (!rc)
- *fw_ring_id = le16_to_cpu(resp.ring_id);
+ *fw_ring_id = (u16)le32_to_cpu(resp.ring_id);
return rc;
}
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge.h b/drivers/net/ethernet/broadcom/bnge/bnge.h
index f21cff651fd4..4479ccd071f5 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge.h
@@ -36,6 +36,7 @@ struct bnge_pf_info {
};
#define INVALID_HW_RING_ID ((u16)-1)
+#define INVALID_HW_RING_ID_32BIT (U32_MAX)
enum {
BNGE_FW_CAP_SHORT_CMD = BIT_ULL(0),
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
index 1c9cfec1b633..651c5e783516 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
@@ -1283,7 +1283,7 @@ int bnge_hwrm_stat_ctx_alloc(struct bnge_net *bn)
int hwrm_ring_free_send_msg(struct bnge_net *bn,
struct bnge_ring_struct *ring,
- u32 ring_type, int cmpl_ring_id)
+ u32 ring_type, u32 cmpl_ring_id)
{
struct hwrm_ring_free_input *req;
struct bnge_dev *bd = bn->bd;
@@ -1295,7 +1295,7 @@ int hwrm_ring_free_send_msg(struct bnge_net *bn,
req->cmpl_ring = cpu_to_le16(cmpl_ring_id);
req->ring_type = ring_type;
- req->ring_id = cpu_to_le16(ring->fw_ring_id);
+ req->ring_id = cpu_to_le32(ring->fw_ring_id);
bnge_hwrm_req_hold(bd, req);
rc = bnge_hwrm_req_send(bd, req);
@@ -1317,7 +1317,7 @@ int hwrm_ring_alloc_send_msg(struct bnge_net *bn,
struct hwrm_ring_alloc_output *resp;
struct hwrm_ring_alloc_input *req;
struct bnge_dev *bd = bn->bd;
- u16 ring_id, flags = 0;
+ u32 ring_id, flags = 0;
int rc;
rc = bnge_hwrm_req_init(bd, req, HWRM_RING_ALLOC);
@@ -1401,7 +1401,7 @@ int hwrm_ring_alloc_send_msg(struct bnge_net *bn,
resp = bnge_hwrm_req_hold(bd, req);
rc = bnge_hwrm_req_send(bd, req);
- ring_id = le16_to_cpu(resp->ring_id);
+ ring_id = le32_to_cpu(resp->ring_id);
bnge_hwrm_req_drop(bd, req);
exit:
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h
index 3501de7a89b9..bf452e390d5b 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h
@@ -50,7 +50,7 @@ int bnge_hwrm_cfa_l2_set_rx_mask(struct bnge_dev *bd,
void bnge_hwrm_stat_ctx_free(struct bnge_net *bn);
int bnge_hwrm_stat_ctx_alloc(struct bnge_net *bn);
int hwrm_ring_free_send_msg(struct bnge_net *bn, struct bnge_ring_struct *ring,
- u32 ring_type, int cmpl_ring_id);
+ u32 ring_type, u32 cmpl_ring_id);
int hwrm_ring_alloc_send_msg(struct bnge_net *bn,
struct bnge_ring_struct *ring,
u32 ring_type, u32 map_index);
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
index 70768193004c..6f7ef506d4e1 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
@@ -1327,12 +1327,12 @@ static int bnge_alloc_core(struct bnge_net *bn)
return rc;
}
-u16 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr)
+u32 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr)
{
return rxr->rx_cpr->ring_struct.fw_ring_id;
}
-u16 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr)
+u32 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr)
{
return txr->tx_cpr->ring_struct.fw_ring_id;
}
@@ -1375,12 +1375,12 @@ static void bnge_init_nq_tree(struct bnge_net *bn)
struct bnge_nq_ring_info *nqr = &bn->bnapi[i]->nq_ring;
struct bnge_ring_struct *ring = &nqr->ring_struct;
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
for (j = 0; j < nqr->cp_ring_count; j++) {
struct bnge_cp_ring_info *cpr = &nqr->cp_ring_arr[j];
ring = &cpr->ring_struct;
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
}
}
}
@@ -1637,7 +1637,7 @@ static void bnge_init_one_rx_ring_rxbd(struct bnge_net *bn,
ring = &rxr->rx_ring_struct;
bnge_init_rxbd_pages(ring, type);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
}
static void bnge_init_one_agg_ring_rxbd(struct bnge_net *bn,
@@ -1647,7 +1647,7 @@ static void bnge_init_one_agg_ring_rxbd(struct bnge_net *bn,
u32 type;
ring = &rxr->rx_agg_ring_struct;
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
if (bnge_is_agg_reqd(bn->bd)) {
type = ((u32)BNGE_RX_PAGE_SIZE << RX_BD_LEN_SHIFT) |
RX_BD_TYPE_RX_AGG_BD | RX_BD_FLAGS_SOP;
@@ -1708,7 +1708,7 @@ static void bnge_init_tx_rings(struct bnge_net *bn)
struct bnge_tx_ring_info *txr = &bn->tx_ring[i];
struct bnge_ring_struct *ring = &txr->tx_ring_struct;
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
netif_queue_set_napi(bn->netdev, i, NETDEV_QUEUE_TYPE_TX,
&txr->bnapi->napi);
@@ -1867,7 +1867,7 @@ static int bnge_hwrm_rx_agg_ring_alloc(struct bnge_net *bn,
ring->fw_ring_id);
bnge_db_write(bn->bd, &rxr->rx_agg_db, rxr->rx_agg_prod);
bnge_db_write(bn->bd, &rxr->rx_db, rxr->rx_prod);
- bn->grp_info[grp_idx].agg_fw_ring_id = ring->fw_ring_id;
+ bn->grp_info[grp_idx].agg_fw_ring_id = (u16)ring->fw_ring_id;
return 0;
}
@@ -1886,7 +1886,7 @@ static int bnge_hwrm_rx_ring_alloc(struct bnge_net *bn,
return rc;
bnge_set_db(bn, &rxr->rx_db, type, map_idx, ring->fw_ring_id);
- bn->grp_info[map_idx].rx_fw_ring_id = ring->fw_ring_id;
+ bn->grp_info[map_idx].rx_fw_ring_id = (u16)ring->fw_ring_id;
return 0;
}
@@ -1916,7 +1916,7 @@ static int bnge_hwrm_ring_alloc(struct bnge_net *bn)
bnge_set_db(bn, &nqr->nq_db, type, map_idx, ring->fw_ring_id);
bnge_db_nq(bn, &nqr->nq_db, nqr->nq_raw_cons);
enable_irq(vector);
- bn->grp_info[i].nq_fw_ring_id = ring->fw_ring_id;
+ bn->grp_info[i].nq_fw_ring_id = (u16)ring->fw_ring_id;
if (!i) {
rc = bnge_hwrm_set_async_event_cr(bd, ring->fw_ring_id);
@@ -1986,15 +1986,13 @@ void bnge_fill_hw_rss_tbl(struct bnge_net *bn, struct bnge_vnic_info *vnic)
tbl_size = bnge_get_rxfh_indir_size(bd);
for (i = 0; i < tbl_size; i++) {
- u16 ring_id, j;
+ u32 j;
j = bd->rss_indir_tbl[i];
rxr = &bn->rx_ring[j];
- ring_id = rxr->rx_ring_struct.fw_ring_id;
- *ring_tbl++ = cpu_to_le16(ring_id);
- ring_id = bnge_cp_ring_for_rx(rxr);
- *ring_tbl++ = cpu_to_le16(ring_id);
+ *ring_tbl++ = cpu_to_le16(rxr->rx_ring_struct.fw_ring_id);
+ *ring_tbl++ = cpu_to_le16(bnge_cp_ring_for_rx(rxr));
}
}
@@ -2285,7 +2283,7 @@ static void bnge_disable_int(struct bnge_net *bn)
nqr = &bnapi->nq_ring;
ring = &nqr->ring_struct;
- if (ring->fw_ring_id != INVALID_HW_RING_ID)
+ if (ring->fw_ring_id != INVALID_HW_RING_ID_32BIT)
bnge_db_nq(bn, &nqr->nq_db, nqr->nq_raw_cons);
}
}
@@ -2401,7 +2399,7 @@ static void bnge_hwrm_rx_ring_free(struct bnge_net *bn,
u32 grp_idx = rxr->bnapi->index;
u32 cmpl_ring_id;
- if (ring->fw_ring_id == INVALID_HW_RING_ID)
+ if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT)
return;
cmpl_ring_id = bnge_cp_ring_for_rx(rxr);
@@ -2409,7 +2407,7 @@ static void bnge_hwrm_rx_ring_free(struct bnge_net *bn,
RING_FREE_REQ_RING_TYPE_RX,
close_path ? cmpl_ring_id :
INVALID_HW_RING_ID);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
bn->grp_info[grp_idx].rx_fw_ring_id = INVALID_HW_RING_ID;
}
@@ -2421,14 +2419,14 @@ static void bnge_hwrm_rx_agg_ring_free(struct bnge_net *bn,
u32 grp_idx = rxr->bnapi->index;
u32 cmpl_ring_id;
- if (ring->fw_ring_id == INVALID_HW_RING_ID)
+ if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT)
return;
cmpl_ring_id = bnge_cp_ring_for_rx(rxr);
hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_RX_AGG,
close_path ? cmpl_ring_id :
INVALID_HW_RING_ID);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
bn->grp_info[grp_idx].agg_fw_ring_id = INVALID_HW_RING_ID;
}
@@ -2439,14 +2437,14 @@ static void bnge_hwrm_tx_ring_free(struct bnge_net *bn,
struct bnge_ring_struct *ring = &txr->tx_ring_struct;
u32 cmpl_ring_id;
- if (ring->fw_ring_id == INVALID_HW_RING_ID)
+ if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT)
return;
cmpl_ring_id = close_path ? bnge_cp_ring_for_tx(txr) :
INVALID_HW_RING_ID;
hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_TX,
cmpl_ring_id);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
}
static void bnge_hwrm_cp_ring_free(struct bnge_net *bn,
@@ -2455,12 +2453,12 @@ static void bnge_hwrm_cp_ring_free(struct bnge_net *bn,
struct bnge_ring_struct *ring;
ring = &cpr->ring_struct;
- if (ring->fw_ring_id == INVALID_HW_RING_ID)
+ if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT)
return;
hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_L2_CMPL,
INVALID_HW_RING_ID);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
}
static void bnge_hwrm_ring_free(struct bnge_net *bn, bool close_path)
@@ -2496,11 +2494,11 @@ static void bnge_hwrm_ring_free(struct bnge_net *bn, bool close_path)
bnge_hwrm_cp_ring_free(bn, &nqr->cp_ring_arr[j]);
ring = &nqr->ring_struct;
- if (ring->fw_ring_id != INVALID_HW_RING_ID) {
+ if (ring->fw_ring_id != INVALID_HW_RING_ID_32BIT) {
hwrm_ring_free_send_msg(bn, ring,
RING_FREE_REQ_RING_TYPE_NQ,
INVALID_HW_RING_ID);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
bn->grp_info[i].nq_fw_ring_id = INVALID_HW_RING_ID;
}
}
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h
index f4636b5b0cf3..d177919c2e11 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h
@@ -630,8 +630,8 @@ struct bnge_l2_filter {
refcount_t refcnt;
};
-u16 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr);
-u16 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr);
+u32 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr);
+u32 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr);
void bnge_fill_hw_rss_tbl(struct bnge_net *bn, struct bnge_vnic_info *vnic);
int bnge_alloc_rx_data(struct bnge_net *bn, struct bnge_rx_ring_info *rxr,
u16 prod, gfp_t gfp);
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
index 341c7f81ed09..bb0c79a1ee60 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
@@ -184,7 +184,7 @@ struct bnge_ctx_mem_info {
struct bnge_ring_struct {
struct bnge_ring_mem_info ring_mem;
- u16 fw_ring_id;
+ u32 fw_ring_id;
union {
u16 grp_idx;
u16 map_idx; /* Used by NQs */
diff --git a/include/linux/bnge/hsi.h b/include/linux/bnge/hsi.h
index 8ea13d5407ee..1f7bd96415a5 100644
--- a/include/linux/bnge/hsi.h
+++ b/include/linux/bnge/hsi.h
@@ -8317,8 +8317,7 @@ struct hwrm_ring_alloc_output {
__le16 req_type;
__le16 seq_id;
__le16 resp_len;
- __le16 ring_id;
- __le16 logical_ring_id;
+ __le32 ring_id;
u8 push_buffer_index;
#define RING_ALLOC_RESP_PUSH_BUFFER_INDEX_PING_BUFFER 0x0UL
#define RING_ALLOC_RESP_PUSH_BUFFER_INDEX_PONG_BUFFER 0x1UL
@@ -8345,10 +8344,10 @@ struct hwrm_ring_free_input {
u8 flags;
#define RING_FREE_REQ_FLAGS_VIRTIO_RING_VALID 0x1UL
#define RING_FREE_REQ_FLAGS_LAST RING_FREE_REQ_FLAGS_VIRTIO_RING_VALID
- __le16 ring_id;
+ __le16 unused_1;
__le32 prod_idx;
__le32 opaque;
- __le32 unused_1;
+ __le32 ring_id;
};
/* hwrm_ring_free_output (size:128b/16B) */
--
2.47.1
^ permalink raw reply related
* [PATCH net] ipv4: fib: free fib_alias with kfree_rcu() on insert error path
From: Weiming Shi @ 2026-07-04 17:14 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, edumazet, kuba, pabeni, davem, horms, xmei5,
linux-kernel, Weiming Shi
fib_table_insert() publishes new_fa into the leaf's fa_list with
fib_insert_alias() before calling the fib entry notifiers. When a
notifier fails, the error path removes new_fa with fib_remove_alias()
(hlist_del_rcu) and frees it right away with kmem_cache_free().
fib_table_lookup() walks that list under rcu_read_lock() only, so a
concurrent lookup that already reached new_fa keeps reading it after the
free:
BUG: KASAN: slab-use-after-free in fib_table_lookup (net/ipv4/fib_trie.c:1601)
Read of size 1 at addr ffff88810676d4eb by task exploit/297
Call Trace:
fib_table_lookup (net/ipv4/fib_trie.c:1601)
ip_route_output_key_hash_rcu (net/ipv4/route.c:2814)
ip_route_output_key_hash (net/ipv4/route.c:2705)
__ip4_datagram_connect (net/ipv4/datagram.c:49)
udp_connect (net/ipv4/udp.c:2144)
__sys_connect (net/socket.c:2167)
__x64_sys_connect (net/socket.c:2173)
do_syscall_64
entry_SYSCALL_64_after_hwframe
which belongs to the cache ip_fib_alias of size 56
Triggering the error path needs CAP_NET_ADMIN and a registered fib
notifier that can reject a route; a netdevsim device whose IPv4 FIB
resource is exhausted is enough.
Free new_fa with alias_free_mem_rcu(), as fib_table_delete() already
does for a fib_alias removed from the trie.
Fixes: a6c76c17df02 ("ipv4: Notify route after insertion to the routing table")
Reported-by: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
net/ipv4/fib_trie.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c
index 1308213791f1..2dc87e2156d3 100644
--- a/net/ipv4/fib_trie.c
+++ b/net/ipv4/fib_trie.c
@@ -1385,7 +1385,7 @@ int fib_table_insert(struct net *net, struct fib_table *tb,
out_remove_new_fa:
fib_remove_alias(t, tp, l, new_fa);
out_free_new_fa:
- kmem_cache_free(fn_alias_kmem, new_fa);
+ alias_free_mem_rcu(new_fa);
out:
fib_release_info(fi);
err:
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net v2] bnge/bng_re: fix ring ID widths
From: Andrew Lunn @ 2026-07-04 18:34 UTC (permalink / raw)
To: Vikas Gupta, gg
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, horms, netdev,
linux-kernel, linux-rdma, leonro, jgg, bhargava.marreddy,
rahul-rg.gupta, vsrama-krishna.nemani, rajashekar.hudumula,
ajit.khaparde, Siva Reddy Kallam, Dharmender Garg,
Yendapally Reddy Dhananjaya Reddy
In-Reply-To: <20260704164747.1995227-1-vikas.gupta@broadcom.com>
> - Backward compatibility with older firmware is not a concern.
Could you expand on that please.
Andrew
^ permalink raw reply
* Re: [PATCH net-next v4 2/2] selftests: net: add FOU multicast encapsulation resubmit test
From: Willem de Bruijn @ 2026-07-04 19:08 UTC (permalink / raw)
To: Anton Danilov, netdev
Cc: Willem de Bruijn, David S . Miller, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
In-Reply-To: <a196b53a2829da66c982f88e82805afc498ea13b.1782945956.git.littlesmilingcloud@gmail.com>
Anton Danilov wrote:
> Add a selftest to verify that FOU-encapsulated packets addressed to a
> multicast destination are correctly resubmitted to the inner protocol
> handler (GRE) via the UDP multicast delivery path.
>
> The test creates two network namespaces connected by a veth pair with
> a FOU/GRETAP tunnel using a multicast remote address (239.0.0.1).
> Ping is sent through the tunnel and received packets are counted on
> the receiver's tunnel interface.
>
> A static neighbor entry is configured on the sender because ARP
> replies from the receiver cannot traverse the unidirectional multicast
> tunnel back to the sender.
>
> The early demux optimization (net.ipv4.ip_early_demux) is disabled on
> the receiver to force packets through __udp4_lib_mcast_deliver(),
> which is the code path being tested.
>
> Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
> Assisted-by: Claude:claude-opus-4-6
> ---
> tools/testing/selftests/net/Makefile | 1 +
> .../testing/selftests/net/fou_mcast_encap.sh | 112 ++++++++++++++++++
> 2 files changed, 113 insertions(+)
> create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
>
> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
> index 708d960ae07d..7e9ae937cffa 100644
> --- a/tools/testing/selftests/net/Makefile
> +++ b/tools/testing/selftests/net/Makefile
> @@ -39,6 +39,7 @@ TEST_PROGS := \
> fib_rule_tests.sh \
> fib_tests.sh \
> fin_ack_lat.sh \
> + fou_mcast_encap.sh \
> fq_band_pktlimit.sh \
> gre_gso.sh \
> gre_ipv6_lladdr.sh \
> diff --git a/tools/testing/selftests/net/fou_mcast_encap.sh b/tools/testing/selftests/net/fou_mcast_encap.sh
> new file mode 100755
> index 000000000000..8db9633f4c28
> --- /dev/null
> +++ b/tools/testing/selftests/net/fou_mcast_encap.sh
> @@ -0,0 +1,112 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Test that UDP encapsulation (FOU) correctly handles packet resubmit
> +# when packets are delivered via the multicast UDP delivery path.
> +#
> +# When a FOU-encapsulated packet arrives with a multicast destination IP,
> +# __udp4_lib_mcast_deliver() must resubmit it to the inner protocol
> +# handler (e.g., GRE) rather than consuming it. This test verifies that
> +# by creating a FOU/GRETAP tunnel with a multicast remote address and
> +# sending ping through it.
> +#
> +# The early demux optimization can mask this issue by routing packets via
> +# the unicast path (udp_unicast_rcv_skb), so we disable it to force
> +# packets through __udp4_lib_mcast_deliver().
> +
> +source lib.sh
> +
> +NSENDER=""
> +NRECV=""
> +
> +cleanup() {
> + cleanup_all_ns
> +}
> +
> +trap cleanup EXIT
> +
> +setup() {
> + setup_ns NSENDER NRECV
> +
> + ip link add veth_s type veth peer name veth_r
> + ip link set veth_s netns "$NSENDER"
> + ip link set veth_r netns "$NRECV"
Sashiko points out that names in the root namespace may collide with
other devices. Unexpected here, but it's just as easy to immediately
assign the new veth nodes to the netns. See for instance sctp_vrf.sh.
> +
> + ip -n "$NSENDER" addr add 10.0.0.1/24 dev veth_s
More importantly, have you also tested IPv6? Can you expand the
test with IPv6.
Again, sashiko points to an important issue: ip_protocol_deliver_rcu
and ip6_protocol_deliver_rcu are not equivalent. From a quick scan it
appears that the latter does not resubmit on negative return code.
^ permalink raw reply
* [PATCH net-next v7 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij, Nicolai Buchwitz,
Krzysztof Kozlowski
This series breaks with the dated attempt to polish the old
KS8995 driver, and instead implement support for the KS8995XA
in the KSZ driver, and after that delete the old KS8995 driver.
The hardware clearly has the same ancestry, the KSZ8995XA is
just a rebrand of the much older Micrel KX8995XA switch.
The old drivers referce to "KS8995" was actually KS8995XA only,
it never supported the sibling devices KS8995E or KS8995MA.
This is reflected in this patch set.
Add new compatibles, add special code paths for the KSZ8995XA
and add a new tagger for the special front tag found in the
KSZ8995XA.
The patches were tested with the Actiontec MI424WR rev D (which
has the KS8995XA) and OpenWrt as userspace.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Changes in v7:
- Rebased on v7.2-rc1
- Rebased on top of Bastiens cleanups.
- Rebased and reworked on top of my patch changing the SKB ownership
model in the taggers.
- Drop bogus reset routine, keep per-port reset but use the right
defines.
- Properly implement the 2way queue split support (low/hi prio)
- Drop bogus surplus ops assignment.
- Link to v6: https://patch.msgid.link/20260608-ks8995-to-ksz8-v6-0-d91eb43edcc5@kernel.org
Changes in v6:
- Provide a prompt along with the transition symbol NET_DSA_KS8995
so that oldconfig works.
- Clarify in commit message that the platform data probe path is
unused in the kernel and it is fine to delete this mechanism.
- Link to v5: https://patch.msgid.link/20260604-ks8995-to-ksz8-v5-0-98baeb45e665@kernel.org
Changes in v5:
- Rebase on net-next again to be sure we apply.
- Fix the codepath where DCB was set up per-port, damaging KS8995XA
port registers.
- Fix up the transitional symbol NET_DSA_KS8995 to depend on
SPI and select both NET_DSA_MICROCHIP_KSZ_COMMON and
NET_DSA_MICROCHIP_KSZ_SPI.
- Link to v4: https://patch.msgid.link/20260603-ks8995-to-ksz8-v4-0-e15149ef21e7@kernel.org
Changes in v4:
- Create a stub symbol for NET_DSA_KS8995 to phase over users to
the new driver.
- Link to v3: https://patch.msgid.link/20260526-ks8995-to-ksz8-v3-0-c530f651989f@kernel.org
Changes in v3:
- Rebase on net-next again, moving target!
- Fix a netdev_info() print in the tagger to be netdev_debug()
- Add net/dsa/tag_ks8995.c to MAINTAINERS
- Link to v2: https://patch.msgid.link/20260522-ks8995-to-ksz8-v2-0-5712c0dc9e75@kernel.org
Changes in v2:
- Avoid trying to configure TOS priority settings (DCB) on the KSZ8995XA.
It does have some support for this, but let's add that later.
- Pick up Krzysztof's ACK on the new DT bindings.
- Reset the KSZ8995XA by disabling and enabling the switch like the old
driver does.
- Move mutually exclusive Kconfig over to the patch introducing the
old Micrel compatibles to the Microchip ksz_spi driver.
(this complaint from Sashiko.)
- Use __be16 instead of u16 when casting into the SKB in the tagger
(this complaint from Sashiko.)
- Do not modify the hdr->h_vlan_TCI if no hardware accelerated tag
is detected, it would get nullified. Use whatever the Linux network stack
assigned to TCI there.
(this complaint from Sashiko.)
- Avoid dereference of NULL in debug prints in the tagger.
(this complaint from Sashiko.)
- Ignored comment: Sashiko complains about ilog2(0) being potentially
undefined but that is clearly defined as 0 in the Linux kernel.
- Link to v1: https://patch.msgid.link/20260516-ks8995-to-ksz8-v1-0-70d0ef4aa5f4@kernel.org
---
Linus Walleij (5):
net: dsa: microchip: Add fallback Micrel compatibles
dt-bindings: net: dsa: microchip: Add KSZ8995XA
net: dsa: tag_ks8995: Add the KS8995 tag handling
net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
net: dsa: ks8995: Delete surplus driver
.../devicetree/bindings/net/dsa/microchip,ksz.yaml | 1 +
MAINTAINERS | 1 +
drivers/net/dsa/Kconfig | 9 +-
drivers/net/dsa/Makefile | 1 -
drivers/net/dsa/ks8995.c | 857 ---------------------
drivers/net/dsa/microchip/Kconfig | 1 +
drivers/net/dsa/microchip/ksz8.c | 187 ++++-
drivers/net/dsa/microchip/ksz8.h | 2 +
drivers/net/dsa/microchip/ksz8_reg.h | 7 +
drivers/net/dsa/microchip/ksz_common.c | 54 +-
drivers/net/dsa/microchip/ksz_common.h | 11 +-
drivers/net/dsa/microchip/ksz_spi.c | 33 +-
include/linux/platform_data/microchip-ksz.h | 1 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +
net/dsa/Makefile | 1 +
net/dsa/tag_ks8995.c | 137 ++++
17 files changed, 408 insertions(+), 903 deletions(-)
---
base-commit: b73bc9ca3686b78b642fb35dcc1fdf874ecb74a1
change-id: 20260509-ks8995-to-ksz8-15f3f9c8271f
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply
* [PATCH net-next v7 1/5] net: dsa: microchip: Add fallback Micrel compatibles
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij, Nicolai Buchwitz
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
Because of forking paths when Micrel was acquired by Microchip,
two devices also exist with the micrel,* prefix bindings.
Add these to the KSZ SPI driver so users can use the more capable
driver.
Make the KS8995 driver mutually exclusive with this driver
to avoid probe races.
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/Kconfig | 1 +
drivers/net/dsa/microchip/ksz_spi.c | 15 +++++++++++++++
2 files changed, 16 insertions(+)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 4ab567c5bbaf..e704ab702c18 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -100,6 +100,7 @@ config NET_DSA_RZN1_A5PSW
config NET_DSA_KS8995
tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
depends on SPI
+ depends on !NET_DSA_MICROCHIP_KSZ_SPI
select NET_DSA_TAG_NONE
help
This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
index 373e9054947c..77aecac32466 100644
--- a/drivers/net/dsa/microchip/ksz_spi.c
+++ b/drivers/net/dsa/microchip/ksz_spi.c
@@ -224,6 +224,21 @@ static void ksz_spi_shutdown(struct spi_device *spi)
}
static const struct of_device_id ksz_dt_ids[] = {
+ /*
+ * Legacy Micrel bindings. In 2015 Microchip acquired
+ * Micrel which is the originator of the KSZ series, and
+ * devices branded for Micrel already existed, as well as
+ * some device tree bindings. These two products are identical
+ * to the same Microchip products.
+ */
+ {
+ .compatible = "micrel,ksz8864",
+ .data = &ksz_switch_chips[KSZ8864]
+ },
+ {
+ .compatible = "micrel,ksz8795",
+ .data = &ksz_switch_chips[KSZ8795]
+ },
{
.compatible = "microchip,ksz8463",
.data = &ksz_switch_chips[KSZ8463]
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij,
Krzysztof Kozlowski
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
The KSZ8995XA is just like the KSZ8795 and KSZ8864 a Micrel
product. It was renamed from KS8995XA to KSZ8995XA at some point,
but it has the same properties as the KS8995XA.
Be careful to use the full product name in this new compatible:
there is also KSZ8995MA and KSZ8995E which are not compatible
with the KS8995XA.
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml b/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
index 8d4a3a9a33fc..4ed13870ed3a 100644
--- a/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
@@ -23,6 +23,7 @@ properties:
- microchip,ksz8864 # 4-port version of KSZ8895 family switch
- microchip,ksz8873
- microchip,ksz8895 # 5-port version of KSZ8895 family switch
+ - microchip,ksz8995xa
- microchip,ksz9477
- microchip,ksz9897
- microchip,ksz9896
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
The KS8995 100Mbit switch can do proper DSA per-port tagging
with the proper set-up. This adds the code to handle ingress
and egress KS8995 tags.
The tag is a modified 0x8100 ethertype tag where a bit in the
last nybble is set for each target port.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
MAINTAINERS | 1 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +++
net/dsa/Makefile | 1 +
net/dsa/tag_ks8995.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 147 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 52f1a55eca99..6bd1baec7b44 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17598,6 +17598,7 @@ F: Documentation/devicetree/bindings/net/dsa/microchip,lan937x.yaml
F: drivers/net/dsa/microchip/*
F: include/linux/dsa/ksz_common.h
F: include/linux/platform_data/microchip-ksz.h
+F: net/dsa/tag_ks8995.c
F: net/dsa/tag_ksz.c
MICROCHIP LAN743X ETHERNET DRIVER
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc10..abd159527782 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -59,6 +59,7 @@ struct tc_action;
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
#define DSA_TAG_PROTO_NETC_VALUE 33
+#define DSA_TAG_PROTO_KS8995_VALUE 34
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -95,6 +96,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
+ DSA_TAG_PROTO_KS8995 = DSA_TAG_PROTO_KS8995_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index d5e725b90d78..bbdf324addac 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -119,6 +119,12 @@ config NET_DSA_TAG_MXL_GSW1XX
Say Y or M if you want to enable support for tagging frames for
MaxLinear GSW1xx switches.
+config NET_DSA_TAG_KS8995
+ tristate "Tag driver for Micrel KS8995 switch"
+ help
+ Say Y if you want to enable support for tagging frames for the
+ Micrel KS8995 switch.
+
config NET_DSA_TAG_KSZ
tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index b8c2667cd14a..a9c2a0569e9e 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_NET_DSA_TAG_BRCM_COMMON) += tag_brcm.o
obj-$(CONFIG_NET_DSA_TAG_DSA_COMMON) += tag_dsa.o
obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o
obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o
+obj-$(CONFIG_NET_DSA_TAG_KS8995) += tag_ks8995.o
obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
diff --git a/net/dsa/tag_ks8995.c b/net/dsa/tag_ks8995.c
new file mode 100644
index 000000000000..5e81ce1ea91a
--- /dev/null
+++ b/net/dsa/tag_ks8995.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Linus Walleij <linusw@kernel.org>
+ */
+#include <linux/etherdevice.h>
+#include <linux/log2.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "tag.h"
+
+/* The Micrel KS8995XA / Microchip KSZ8995XA Special Tag Packet ID (STPID)
+ * pushes its tag in a modified VLAN (802.1Q) tag.
+ * -----------------------------------------------------------
+ * | MAC DA | MAC SA | 2 bytes tag | 2 bytes TCI | EtherType |
+ * -----------------------------------------------------------
+ * The tag is: 0x8100 |= BIT(port), ports 0,1,2,3
+ */
+
+#define KS8995_NAME "ks8995"
+
+#define KS8995M_STPID_STD GENMASK(15, 4)
+#define KS8995M_STPID_PORTMASK GENMASK(3, 0)
+#define KS8995M_STPID(portmask) htons(ETH_P_8021Q | FIELD_PREP(KS8995M_STPID_PORTMASK, portmask))
+
+static struct sk_buff *ks8995_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct vlan_ethhdr *hdr = vlan_eth_hdr(skb);
+ bool have_hwaccel_tag = false;
+ u16 tci = 0, portmask;
+
+ /* Prepare the special KS8995 tags */
+ portmask = dsa_xmit_port_mask(skb, dev);
+
+ if (skb_vlan_tag_present(skb) && skb->vlan_proto == htons(ETH_P_8021Q)) {
+ tci = skb_vlan_tag_get(skb);
+ __vlan_hwaccel_clear_tag(skb);
+ have_hwaccel_tag = true;
+ }
+
+ if (have_hwaccel_tag || hdr->h_vlan_proto != htons(ETH_P_8021Q)) {
+ skb = vlan_insert_tag(skb, KS8995M_STPID(portmask), tci);
+ /* vlan_insert_tag() drops the skb on failure */
+ if (!skb)
+ return NULL;
+ hdr = vlan_eth_hdr(skb);
+ netdev_dbg(dev, "%s: inserted VLAN TAG %04x TCI %04x\n",
+ __func__, hdr->h_vlan_proto, hdr->h_vlan_TCI);
+ } else {
+ /* VLAN tag already exists in skb head, modify it in place */
+ hdr = vlan_eth_hdr(skb);
+ hdr->h_vlan_proto = KS8995M_STPID(portmask);
+ netdev_dbg(dev, "%s: modified VLAN TAG %04x\n",
+ __func__, hdr->h_vlan_proto);
+ }
+
+ return skb;
+}
+
+static struct sk_buff *ks8995_rcv(struct sk_buff *skb, struct net_device *dev)
+{
+ int portmask;
+ u16 etype;
+
+ /* We are expecting all received packets to have a mangled VLAN
+ * TPID, so drop anything else. Because of the non-standard TPID,
+ * don't even bother looking for a tag in the hwaccel area.
+ *
+ * We have to inspect the ethertype directly because skb->protocol
+ * will contain garbage.
+ */
+ etype = ntohs(*(__be16 *)dsa_etype_header_pos_rx(skb));
+ if ((etype & KS8995M_STPID_STD) != ETH_P_8021Q) {
+ netdev_dbg(dev, "%s: dropped ethertype 0x%04x\n",
+ __func__, etype);
+ kfree_skb(skb);
+ return NULL;
+ }
+ netdev_dbg(dev, "%s: received ethertype %04x\n",
+ __func__, etype);
+
+ /* Move the custom DSA+VLAN tag into the hwaccel area and strip
+ * it from the skb head
+ */
+ skb = skb_vlan_untag(skb);
+ if (!skb) {
+ /* skb_vlan_untag drops the skb on failure */
+ netdev_err(dev, "%s: unable to untag skb\n", __func__);
+ return NULL;
+ }
+
+ portmask = FIELD_GET(KS8995M_STPID_PORTMASK, etype);
+ netdev_dbg(dev, "%s: etype %04x portmask %04x (%d)\n",
+ __func__, etype, portmask, ilog2(portmask));
+ skb->dev = dsa_conduit_find_user(dev, 0, ilog2(portmask));
+ if (!skb->dev) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* Preserve the VLAN tag if it contains a non-zero VID which is not
+ * identical to 0x001, or PCP, and restore its TPID to the standard
+ * value.
+ *
+ * If this is just an ordinary inbound package the datasheet claims
+ * it will "replace null VID with ingress port VID", which means
+ * VID set to 1: 0x8101 0001 for port 0 or 0x8102 0001 for port 1.
+ * So in the DSA driver we will set the default port VID to 0 so
+ * we can properly detect non-VLAN frames.
+ */
+ if (!skb->vlan_tci) {
+ netdev_dbg(dev, "%s: clear VLAN tag from frame\n", __func__);
+ __vlan_hwaccel_clear_tag(skb);
+ } else {
+ skb->vlan_proto = htons(ETH_P_8021Q);
+ netdev_dbg(dev, "%s: vlan_tci = 0x%04x VLAN frame\n",
+ __func__, skb->vlan_tci);
+ }
+
+ dsa_default_offload_fwd_mark(skb);
+
+ return skb;
+}
+
+static const struct dsa_device_ops ks8995_netdev_ops = {
+ .name = KS8995_NAME,
+ .proto = DSA_TAG_PROTO_KS8995,
+ .xmit = ks8995_xmit,
+ .rcv = ks8995_rcv,
+ .needed_headroom = VLAN_HLEN,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for Micrel KS8995 family of switches");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KS8995, KS8995_NAME);
+
+module_dsa_tag_driver(ks8995_netdev_ops);
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
This adds support for the Microchip KSZ8995XA also known as the
Micrel KS8995XA switch to the KSZ driver.
Notice: there are also KSZ8995E and KSZ8995MA. These are BOTH
different from the KSZ8995XA.
The helper macros are named ksz_is_ksz8995xa() to make it
possible to add E and MA support in the future.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/microchip/Kconfig | 1 +
drivers/net/dsa/microchip/ksz8.c | 187 +++++++++++++++++++++++-----
drivers/net/dsa/microchip/ksz8.h | 2 +
drivers/net/dsa/microchip/ksz8_reg.h | 7 ++
drivers/net/dsa/microchip/ksz_common.c | 54 ++++++--
drivers/net/dsa/microchip/ksz_common.h | 11 +-
drivers/net/dsa/microchip/ksz_spi.c | 18 ++-
include/linux/platform_data/microchip-ksz.h | 1 +
8 files changed, 239 insertions(+), 42 deletions(-)
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig
index c71d3fd5dfeb..75c9b2114afd 100644
--- a/drivers/net/dsa/microchip/Kconfig
+++ b/drivers/net/dsa/microchip/Kconfig
@@ -2,6 +2,7 @@
menuconfig NET_DSA_MICROCHIP_KSZ_COMMON
tristate "Microchip KSZ8XXX/KSZ9XXX/LAN937X series switch support"
depends on NET_DSA
+ select NET_DSA_TAG_KS8995
select NET_DSA_TAG_KSZ
select NET_DSA_TAG_NONE
select NET_IEEE8021Q_HELPERS
diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
index 586916570a84..03a5981a6315 100644
--- a/drivers/net/dsa/microchip/ksz8.c
+++ b/drivers/net/dsa/microchip/ksz8.c
@@ -3,6 +3,7 @@
* Microchip KSZ8XXX series switch driver
*
* It supports the following switches:
+ * - KSZ8995XA (the oldest ancestor)
* - KSZ8463
* - KSZ8863, KSZ8873 aka KSZ88X3
* - KSZ8895, KSZ8864 aka KSZ8895 family
@@ -137,7 +138,17 @@ static int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 dat
static int ksz8_reset_switch(struct ksz_device *dev)
{
- if (ksz_is_ksz88x3(dev)) {
+ if (ksz_is_ksz8995xa(dev)) {
+ unsigned int port;
+
+ /* The KSZ8995XA switch itself cannot be reset by software, but
+ * often boot loaders have fun with ports, so reset them. This
+ * reset bit is self-clearing.
+ */
+ for (port = 0; port < dev->info->port_cnt; port++)
+ ksz_port_cfg(dev, port, REG_PORT_STATUS_3,
+ PORT_PHY_SOFT_RESET, true);
+ } else if (ksz_is_ksz88x3(dev)) {
/* reset switch */
ksz_cfg(dev, KSZ8863_REG_SW_RESET,
KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true);
@@ -161,8 +172,14 @@ static int ksz8_reset_switch(struct ksz_device *dev)
static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size)
{
u8 ctrl2 = 0;
+ unsigned int legalsz;
+
+ if (ksz_is_ksz8995xa(dev))
+ legalsz = KSZ8995XA_LEGAL_PACKET_SIZE;
+ else
+ legalsz = KSZ8_LEGAL_PACKET_SIZE;
- if (frame_size <= KSZ8_LEGAL_PACKET_SIZE)
+ if (frame_size <= legalsz)
ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE;
else if (frame_size > KSZ8863_NORMAL_PACKET_SIZE)
ctrl2 |= KSZ8863_HUGE_PACKET_ENABLE;
@@ -207,6 +224,7 @@ static int ksz8_change_mtu(struct dsa_switch *ds, int port, int mtu)
case KSZ88X3_CHIP_ID:
case KSZ8864_CHIP_ID:
case KSZ8895_CHIP_ID:
+ case KSZ8995XA_CHIP_ID:
return ksz8863_change_mtu(dev, frame_size);
}
@@ -242,6 +260,10 @@ static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN;
reg_4q = P1CR1;
reg_2q = P1CR1 + 1;
+ } else if (ksz_is_ksz8995xa(dev)) {
+ /* This switch has no 4way split support */
+ mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
+ reg_2q = REG_PORT_CTRL_0;
} else {
mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN;
mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
@@ -860,6 +882,10 @@ static int ksz8_r_phy_ctrl(struct ksz_device *dev, int port, u16 *val)
if (reg_val & PORT_MDIX_STATUS)
*val |= KSZ886X_CTRL_MDIX_STAT;
+ /* KSZ8995XA has no fancy features in register 0xA */
+ if (ksz_is_ksz8995xa(dev))
+ return 0;
+
ret = ksz_pread8(dev, port, REG_PORT_LINK_MD_CTRL, ®_val);
if (ret < 0)
return ret;
@@ -958,8 +984,10 @@ static int ksz8_r_phy_bmcr(struct ksz_device *dev, u16 port, u16 *val)
if (ctrl & PORT_FORCE_FULL_DUPLEX)
*val |= BMCR_FULLDPLX;
- if (speed & PORT_HP_MDIX)
- *val |= KSZ886X_BMCR_HP_MDIX;
+ if (!ksz_is_ksz8995xa(dev)) {
+ if (speed & PORT_HP_MDIX)
+ *val |= KSZ886X_BMCR_HP_MDIX;
+ }
if (restart & PORT_FORCE_MDIX)
*val |= KSZ886X_BMCR_FORCE_MDI;
@@ -1054,6 +1082,9 @@ static int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
data |= LPA_LPACK;
break;
case PHY_REG_LINK_MD:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
ret = ksz_pread8(dev, p, REG_PORT_LINK_MD_CTRL, &val1);
if (ret)
return ret;
@@ -1197,13 +1228,15 @@ static int ksz8_w_phy_bmcr(struct ksz_device *dev, u16 port, u16 val)
if (val & BMCR_RESET)
return 0;
- speed = 0;
- if (val & KSZ886X_BMCR_HP_MDIX)
- speed |= PORT_HP_MDIX;
+ if (!ksz_is_ksz8995xa(dev)) {
+ speed = 0;
+ if (val & KSZ886X_BMCR_HP_MDIX)
+ speed |= PORT_HP_MDIX;
- ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed);
- if (ret)
- return ret;
+ ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed);
+ if (ret)
+ return ret;
+ }
ctrl = 0;
if (ksz_is_ksz88x3(dev)) {
@@ -1313,11 +1346,17 @@ static int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
}
break;
case PHY_REG_LINK_MD:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
if (val & PHY_START_CABLE_DIAG)
ksz_port_cfg(dev, p, REG_PORT_LINK_MD_CTRL, PORT_START_CABLE_DIAG, true);
break;
case PHY_REG_PHY_CTRL:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
ret = ksz8_w_phy_ctrl(dev, p, val);
if (ret)
return ret;
@@ -1562,7 +1601,7 @@ static int ksz8_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag,
{
struct ksz_device *dev = ds->priv;
- if (ksz_is_ksz88x3(dev))
+ if (ksz_is_ksz88x3(dev) || ksz_is_ksz8995xa(dev))
return -ENOTSUPP;
/* Discard packets with VID not enabled on the switch */
@@ -1780,12 +1819,14 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
ksz8_port_queue_split(dev, port, dev->info->num_tx_queues);
- /* replace priority */
- offset = P_802_1P_CTRL;
- if (ksz_is_ksz8463(dev))
- offset = P1CR2;
- ksz_port_cfg(dev, port, offset,
- masks[PORT_802_1P_REMAPPING], false);
+ if (!ksz_is_ksz8995xa(dev)) {
+ /* replace priority */
+ offset = P_802_1P_CTRL;
+ if (ksz_is_ksz8463(dev))
+ offset = P1CR2;
+ ksz_port_cfg(dev, port, offset,
+ masks[PORT_802_1P_REMAPPING], false);
+ }
if (cpu_port)
member = dsa_user_ports(ds);
@@ -1794,6 +1835,19 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
ksz8_cfg_port_member(dev, port, member);
+ if (ksz_is_ksz8995xa(dev)) {
+ /*
+ * The KSZ8995XA has a special tag format in the front of the frame
+ * that need to be inserted by the CPU and then removed by each
+ * port. PORT_REMOVE_TAG simply means "remove tags coming from the
+ * CPU port" it does not affect ingress packets.
+ */
+ if (cpu_port)
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_INSERT_TAG, true);
+ else
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_REMOVE_TAG, true);
+ }
+
/* Disable all WoL options by default. Otherwise
* ksz_switch_macaddr_get/put logic will not work properly.
* CPU port 4 has no WoL functionality.
@@ -1810,7 +1864,12 @@ static int ksz8_dsa_port_setup(struct dsa_switch *ds, int port)
return 0;
ksz8_port_setup(dev, port, false);
- return ksz_dcb_init_port(dev, port);
+
+ /* TODO: Revisit this and attempt to enable DCB on the KS8995XA. */
+ if (!ksz_is_ksz8995xa(dev))
+ return ksz_dcb_init_port(dev, port);
+
+ return 0;
}
static void ksz88x3_config_rmii_clk(struct ksz_device *dev)
@@ -1841,7 +1900,9 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds)
masks = dev->info->masks;
regs = dev->info->regs;
- ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true);
+ /* KSZ8995XA uses a tag in the header instead of the tail */
+ if (!ksz_is_ksz8995xa(dev))
+ ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true);
ksz8_port_setup(dev, dev->cpu_port, true);
@@ -2043,6 +2104,10 @@ static int ksz8_enable_stp_addr(struct ksz_device *dev)
{
struct alu_struct alu;
+ /* KSZ8995XA lacks STP */
+ if (ksz_is_ksz8995xa(dev))
+ return 0;
+
/* Setup STP address for STP operation. */
memset(&alu, 0, sizeof(alu));
ether_addr_copy(alu.mac, eth_stp_addr);
@@ -2061,13 +2126,17 @@ static int ksz8_setup(struct dsa_switch *ds)
struct ksz_port *p;
const u16 *regs;
int i, ret;
+ u8 val;
regs = dev->info->regs;
- dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
- dev->info->num_vlans, GFP_KERNEL);
- if (!dev->vlan_cache)
- return -ENOMEM;
+ /* KSZ8995XA has no SW controlled VLAN handling */
+ if (!ksz_is_ksz8995xa(dev)) {
+ dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
+ dev->info->num_vlans, GFP_KERNEL);
+ if (!dev->vlan_cache)
+ return -ENOMEM;
+ }
ret = ksz8_reset_switch(dev);
if (ret) {
@@ -2126,9 +2195,10 @@ static int ksz8_setup(struct dsa_switch *ds)
* Make sure unicast VLAN boundary is set as default and
* enable no excessive collision drop.
*/
- ret = ksz_rmw8(dev, REG_SW_CTRL_2,
- UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP,
- UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP);
+ val = NO_EXC_COLLISION_DROP;
+ if (!ksz_is_ksz8995xa(dev))
+ val |= UNICAST_VLAN_BOUNDARY;
+ ret = ksz_rmw8(dev, REG_SW_CTRL_2, val, val);
if (ret)
return ret;
@@ -2136,11 +2206,15 @@ static int ksz8_setup(struct dsa_switch *ds)
ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);
- if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev))
+ if (ksz_is_ksz8995xa(dev))
+ ksz_cfg(dev, REG_SW_CTRL_9, SW_SPECIAL_TAG, true);
+ else if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev))
ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true);
- for (i = 0; i < (dev->info->num_vlans / 4); i++)
- ksz8_r_vlan_entries(dev, i);
+ if (!ksz_is_ksz8995xa(dev)) {
+ for (i = 0; i < (dev->info->num_vlans / 4); i++)
+ ksz8_r_vlan_entries(dev, i);
+ }
/* Make sure PME (WoL) is not enabled. If requested, it will
* be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs
@@ -2204,9 +2278,15 @@ static int ksz8_setup(struct dsa_switch *ds)
goto out_ptp_clock_unregister;
}
- ret = ksz_dcb_init(dev);
- if (ret)
- goto out_ptp_clock_unregister;
+ /* TODO: the KSZ8995XA does have TOS priority control registers albeit
+ * 7 instead of 15 and in a different location. Revisit this and attempt
+ * to enable DCB on the KS8995XA.
+ */
+ if (!ksz_is_ksz8995xa(dev)) {
+ ret = ksz_dcb_init(dev);
+ if (ret)
+ goto out_ptp_clock_unregister;
+ }
/* start switch */
regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL],
@@ -2388,6 +2468,13 @@ static int ksz8_switch_init(struct ksz_device *dev)
return 0;
}
+static enum dsa_tag_protocol ksz8995xa_get_tag_protocol(struct dsa_switch *ds,
+ int port,
+ enum dsa_tag_protocol mp)
+{
+ return DSA_TAG_PROTO_KS8995;
+}
+
static enum dsa_tag_protocol ksz8463_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol mp)
@@ -2484,6 +2571,16 @@ const struct phylink_mac_ops ksz8_phylink_mac_ops = {
.mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi,
};
+/*
+ * The KS(Z)8995XA has no indirect access, meaning no MIB counters,
+ * no FDB access, and no VLAN handling.
+ */
+const struct ksz_dev_ops ksz8995xa_dev_ops = {
+ .get_port_addr = ksz8_get_port_addr,
+ .cfg_port_member = ksz8_cfg_port_member,
+ .init = ksz8_switch_init,
+};
+
const struct ksz_dev_ops ksz8463_dev_ops = {
.get_port_addr = ksz8463_get_port_addr,
.cfg_port_member = ksz8_cfg_port_member,
@@ -2523,6 +2620,32 @@ const struct ksz_dev_ops ksz88xx_dev_ops = {
.pme_pwrite8 = ksz8_pme_pwrite8,
};
+/*
+ * Restricted operations for KSZ8995XA, so many things are not supported
+ * by this old switch that we need diet DSA operations.
+ */
+const struct dsa_switch_ops ksz8995xa_switch_ops = {
+ .get_tag_protocol = ksz8995xa_get_tag_protocol,
+ .setup = ksz8_setup,
+ .teardown = ksz_teardown,
+ .phy_read = ksz8_phy_read16,
+ .phy_write = ksz8_phy_write16,
+ .phylink_get_caps = ksz8_phylink_get_caps,
+ .port_setup = ksz8_dsa_port_setup,
+ .port_bridge_join = ksz_port_bridge_join,
+ .port_bridge_leave = ksz_port_bridge_leave,
+ .port_set_mac_address = ksz_port_set_mac_address,
+ .port_stp_state_set = ksz_port_stp_state_set,
+ .port_pre_bridge_flags = ksz_port_pre_bridge_flags,
+ .port_bridge_flags = ksz_port_bridge_flags,
+ .port_fast_age = ksz8_flush_dyn_mac_table,
+ .port_mirror_add = ksz8_port_mirror_add,
+ .port_mirror_del = ksz8_port_mirror_del,
+ .port_change_mtu = ksz8_change_mtu,
+ .port_max_mtu = ksz_max_mtu,
+ /* TODO: add .port_get/set_apptrust() when we implement priority */
+};
+
const struct dsa_switch_ops ksz8463_switch_ops = {
.get_tag_protocol = ksz8463_get_tag_protocol,
.connect_tag_protocol = ksz8463_connect_tag_protocol,
diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h
index bc371cc26c6f..9b37c56c9617 100644
--- a/drivers/net/dsa/microchip/ksz8.h
+++ b/drivers/net/dsa/microchip/ksz8.h
@@ -12,11 +12,13 @@
#include <net/dsa.h>
#include "ksz_common.h"
+extern const struct ksz_dev_ops ksz8995xa_dev_ops;
extern const struct ksz_dev_ops ksz8463_dev_ops;
extern const struct ksz_dev_ops ksz87xx_dev_ops;
extern const struct ksz_dev_ops ksz88xx_dev_ops;
extern const struct phylink_mac_ops ksz88x3_phylink_mac_ops;
extern const struct phylink_mac_ops ksz8_phylink_mac_ops;
+extern const struct dsa_switch_ops ksz8995xa_switch_ops;
extern const struct dsa_switch_ops ksz8463_switch_ops;
extern const struct dsa_switch_ops ksz87xx_switch_ops;
extern const struct dsa_switch_ops ksz88xx_switch_ops;
diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h
index 981ab441d9b7..c25897ccdc39 100644
--- a/drivers/net/dsa/microchip/ksz8_reg.h
+++ b/drivers/net/dsa/microchip/ksz8_reg.h
@@ -30,6 +30,11 @@
#define KSZ88X3_REG_FVID_AND_HOST_MODE 0xC6
#define KSZ88X3_PORT3_RMII_CLK_INTERNAL BIT(3)
+#define REG_SW_ID_0 0x00
+#define REG_SW_ID_1 0x01
+
+#define SW_START_SWITCH BIT(0)
+
#define REG_SW_CTRL_0 0x02
#define SW_NEW_BACKOFF BIT(7)
@@ -95,6 +100,8 @@
#define SW_LED_LINK_ACT_DUPLEX 2
#define SW_LED_LINK_DUPLEX 3
+#define SW_SPECIAL_TAG BIT(0) /* KSZ8995XA only */
+
#define REG_SW_CTRL_10 0x0C
#define SW_PASS_PAUSE BIT(0)
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index d1726778bb48..e9f85449517f 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -553,6 +553,20 @@ static const u8 ksz8895_shifts[] = {
[DYNAMIC_MAC_SRC_PORT] = 24,
};
+static const u16 ksz8995xa_regs[] = {
+ [REG_SW_MAC_ADDR] = 0x68,
+ [P_FORCE_CTRL] = 0x0C,
+ [P_LINK_STATUS] = 0x0E,
+ [P_LOCAL_CTRL] = 0x0C,
+ [P_NEG_RESTART_CTRL] = 0x0D,
+ [P_REMOTE_STATUS] = 0x0E,
+ [P_SPEED_STATUS] = 0x09,
+ [P_STP_CTRL] = 0x02,
+ [S_START_CTRL] = 0x01,
+ [S_BROADCAST_CTRL] = 0x06,
+ [S_MULTICAST_CTRL] = 0x04,
+};
+
static const u16 ksz9477_regs[] = {
[REG_SW_MAC_ADDR] = 0x0302,
[P_STP_CTRL] = 0x0B04,
@@ -1484,6 +1498,21 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, true, true, false},
},
+ [KSZ8995XA] = {
+ .chip_id = KSZ8995XA_CHIP_ID, /* Also known as KS8995XA */
+ .dev_name = "KSZ8995XA",
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 5, /* total cpu and user ports */
+ .num_tx_queues = 2, /* low/hi priority queues, no more */
+ .num_ipms = 4,
+ .ops = &ksz8995xa_dev_ops,
+ .switch_ops = &ksz8995xa_switch_ops,
+ .phylink_mac_ops = &ksz88x3_phylink_mac_ops,
+ .regs = ksz8995xa_regs,
+ .supports_mii = {true, true, true, true, true},
+ .internal_phy = {true, true, true, true, false},
+ },
+
[KSZ9477] = {
.chip_id = KSZ9477_CHIP_ID,
.dev_name = "KSZ9477",
@@ -2811,6 +2840,10 @@ void ksz_init_mib_timer(struct ksz_device *dev)
{
int i;
+ /* KSZ8995XA lacks MiB features */
+ if (ksz_is_ksz8995xa(dev))
+ return;
+
INIT_DELAYED_WORK(&dev->mib_read, ksz_mib_read_work);
for (i = 0; i < dev->info->port_cnt; i++) {
@@ -2997,6 +3030,7 @@ int ksz_max_mtu(struct dsa_switch *ds, int port)
case KSZ88X3_CHIP_ID:
case KSZ8864_CHIP_ID:
case KSZ8895_CHIP_ID:
+ case KSZ8995XA_CHIP_ID:
return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN;
case KSZ8563_CHIP_ID:
case KSZ8567_CHIP_ID:
@@ -3213,11 +3247,15 @@ static int ksz_switch_detect(struct ksz_device *dev)
return -ENODEV;
break;
case KSZ8895_FAMILY_ID:
- if (id2 == KSZ8895_CHIP_ID_95 ||
- id2 == KSZ8895_CHIP_ID_95R)
+ if (id2 == KSZ8895_CHIP_ID_95XA) {
+ dev->chip_id = KSZ8995XA_CHIP_ID;
+ break;
+ } else if (id2 == KSZ8895_CHIP_ID_95 ||
+ id2 == KSZ8895_CHIP_ID_95R) {
dev->chip_id = KSZ8895_CHIP_ID;
- else
+ } else {
return -ENODEV;
+ }
ret = ksz_read8(dev, REG_KSZ8864_CHIP_ID, &id4);
if (ret)
return ret;
@@ -4530,11 +4568,13 @@ int ksz_switch_register(struct ksz_device *dev)
if (ret)
return ret;
- /* Read MIB counters every 30 seconds to avoid overflow. */
- dev->mib_read_interval = msecs_to_jiffies(5000);
+ if (!ksz_is_ksz8995xa(dev)) {
+ /* Read MIB counters every 30 seconds to avoid overflow. */
+ dev->mib_read_interval = msecs_to_jiffies(5000);
- /* Start the MIB timer. */
- schedule_delayed_work(&dev->mib_read, 0);
+ /* Start the MIB timer. */
+ schedule_delayed_work(&dev->mib_read, 0);
+ }
return ret;
}
diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h
index b4a5673ba365..61fd5d059026 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -239,6 +239,7 @@ enum ksz_model {
KSZ88X3,
KSZ8864,
KSZ8895,
+ KSZ8995XA,
KSZ9477,
KSZ9896,
KSZ9897,
@@ -714,7 +715,13 @@ static inline bool ksz_is_ksz88x3(struct ksz_device *dev)
static inline bool ksz_is_8895_family(struct ksz_device *dev)
{
return dev->chip_id == KSZ8895_CHIP_ID ||
- dev->chip_id == KSZ8864_CHIP_ID;
+ dev->chip_id == KSZ8864_CHIP_ID ||
+ dev->chip_id == KSZ8995XA_CHIP_ID;
+}
+
+static inline bool ksz_is_ksz8995xa(struct ksz_device *dev)
+{
+ return dev->chip_id == KSZ8995XA_CHIP_ID;
}
static inline bool is_ksz8(struct ksz_device *dev)
@@ -785,6 +792,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define KSZ87_CHIP_ID_94 0x6
#define KSZ87_CHIP_ID_95 0x9
#define KSZ88_CHIP_ID_63 0x3
+#define KSZ8895_CHIP_ID_95XA 0x0
#define KSZ8895_CHIP_ID_95 0x4
#define KSZ8895_CHIP_ID_95R 0x6
@@ -848,6 +856,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define KSZ8863_HUGE_PACKET_SIZE 1916
#define KSZ8863_NORMAL_PACKET_SIZE 1536
#define KSZ8_LEGAL_PACKET_SIZE 1518
+#define KSZ8995XA_LEGAL_PACKET_SIZE 1522
#define KSZ9477_MAX_FRAME_SIZE 9000
#define KSZ8873_REG_GLOBAL_CTRL_12 0x0e
diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
index 77aecac32466..2c55014246e5 100644
--- a/drivers/net/dsa/microchip/ksz_spi.c
+++ b/drivers/net/dsa/microchip/ksz_spi.c
@@ -161,7 +161,8 @@ static int ksz_spi_probe(struct spi_device *spi)
chip->chip_id == KSZ8794_CHIP_ID ||
chip->chip_id == KSZ8765_CHIP_ID)
regmap_config = ksz8795_regmap_config;
- else if (chip->chip_id == KSZ8895_CHIP_ID ||
+ else if (chip->chip_id == KSZ8995XA_CHIP_ID ||
+ chip->chip_id == KSZ8895_CHIP_ID ||
chip->chip_id == KSZ8864_CHIP_ID)
regmap_config = ksz8863_regmap_config;
else
@@ -185,7 +186,10 @@ static int ksz_spi_probe(struct spi_device *spi)
dev->pdata = spi->dev.platform_data;
/* setup spi */
- spi->mode = SPI_MODE_3;
+ if (chip->chip_id == KSZ8995XA_CHIP_ID)
+ spi->mode = SPI_MODE_0;
+ else
+ spi->mode = SPI_MODE_3;
ret = spi_setup(spi);
if (ret)
return ret;
@@ -239,6 +243,10 @@ static const struct of_device_id ksz_dt_ids[] = {
.compatible = "micrel,ksz8795",
.data = &ksz_switch_chips[KSZ8795]
},
+ {
+ .compatible = "micrel,ks8995",
+ .data = &ksz_switch_chips[KSZ8995XA]
+ },
{
.compatible = "microchip,ksz8463",
.data = &ksz_switch_chips[KSZ8463]
@@ -271,6 +279,10 @@ static const struct of_device_id ksz_dt_ids[] = {
.compatible = "microchip,ksz8895",
.data = &ksz_switch_chips[KSZ8895]
},
+ {
+ .compatible = "microchip,ksz8995xa",
+ .data = &ksz_switch_chips[KSZ8995XA]
+ },
{
.compatible = "microchip,ksz9477",
.data = &ksz_switch_chips[KSZ9477]
@@ -332,6 +344,7 @@ static const struct of_device_id ksz_dt_ids[] = {
MODULE_DEVICE_TABLE(of, ksz_dt_ids);
static const struct spi_device_id ksz_spi_ids[] = {
+ { "ks8995" },
{ "ksz8463" },
{ "ksz8765" },
{ "ksz8794" },
@@ -340,6 +353,7 @@ static const struct spi_device_id ksz_spi_ids[] = {
{ "ksz8864" },
{ "ksz8873" },
{ "ksz8895" },
+ { "ksz8995" },
{ "ksz9477" },
{ "ksz9896" },
{ "ksz9897" },
diff --git a/include/linux/platform_data/microchip-ksz.h b/include/linux/platform_data/microchip-ksz.h
index 028781ad4059..d8eddd21c3c7 100644
--- a/include/linux/platform_data/microchip-ksz.h
+++ b/include/linux/platform_data/microchip-ksz.h
@@ -31,6 +31,7 @@ enum ksz_chip_id {
KSZ88X3_CHIP_ID = 0x8830,
KSZ8864_CHIP_ID = 0x8864,
KSZ8895_CHIP_ID = 0x8895,
+ KSZ8995XA_CHIP_ID = 0x8995,
KSZ9477_CHIP_ID = 0x00947700,
KSZ9896_CHIP_ID = 0x00989600,
KSZ9897_CHIP_ID = 0x00989700,
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 5/5] net: dsa: ks8995: Delete surplus driver
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij, Nicolai Buchwitz
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
The Microchip ksz driver now handles all switches that the
old driver was handling, but better.
Delete the old driver, but leave a stub behind in Kconfig
so new users will be automatically phased over to the new
symbol when migrating their configs.
The old driver supports platform data (board file)
instantiation, but nothing in the kernel tree makes use
of this legacy mechanism so it is fine to delete.
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/Kconfig | 10 +-
drivers/net/dsa/Makefile | 1 -
drivers/net/dsa/ks8995.c | 857 -----------------------------------------------
3 files changed, 6 insertions(+), 862 deletions(-)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index e704ab702c18..360bacc52c3a 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -100,11 +100,13 @@ config NET_DSA_RZN1_A5PSW
config NET_DSA_KS8995
tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
depends on SPI
- depends on !NET_DSA_MICROCHIP_KSZ_SPI
- select NET_DSA_TAG_NONE
+ select NET_DSA_MICROCHIP_KSZ_COMMON
+ select NET_DSA_MICROCHIP_KSZ_SPI
help
- This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
- switches, managed over SPI.
+ This is a transitional option for users who had NET_DSA_KS8995
+ enabled. It automatically enables the new NET_DSA_MICROCHIP_KSZ_SPI
+ driver which supersedes it. This option will be removed in a future
+ kernel release.
config NET_DSA_SMSC_LAN9303
tristate
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index d2975badffc0..6ae16e1835f8 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -2,7 +2,6 @@
obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm-sf2.o
bcm-sf2-objs := bcm_sf2.o bcm_sf2_cfp.o
obj-$(CONFIG_NET_DSA_LOOP) += dsa_loop.o
-obj-$(CONFIG_NET_DSA_KS8995) += ks8995.o
obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o
diff --git a/drivers/net/dsa/ks8995.c b/drivers/net/dsa/ks8995.c
deleted file mode 100644
index 77d8b842693c..000000000000
--- a/drivers/net/dsa/ks8995.c
+++ /dev/null
@@ -1,857 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
- *
- * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
- * Copyright (C) 2025 Linus Walleij <linus.walleij@linaro.org>
- *
- * This file was based on: drivers/spi/at25.c
- * Copyright (C) 2006 David Brownell
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/bits.h>
-#include <linux/if_bridge.h>
-#include <linux/if_vlan.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/delay.h>
-#include <linux/device.h>
-#include <linux/gpio/consumer.h>
-#include <linux/of.h>
-#include <linux/spi/spi.h>
-#include <net/dsa.h>
-
-#define DRV_VERSION "0.1.1"
-#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver"
-
-/* ------------------------------------------------------------------------ */
-
-#define KS8995_REG_ID0 0x00 /* Chip ID0 */
-#define KS8995_REG_ID1 0x01 /* Chip ID1 */
-
-#define KS8995_REG_GC0 0x02 /* Global Control 0 */
-
-#define KS8995_GC0_P5_PHY BIT(3) /* Port 5 PHY enabled */
-
-#define KS8995_REG_GC1 0x03 /* Global Control 1 */
-#define KS8995_REG_GC2 0x04 /* Global Control 2 */
-
-#define KS8995_GC2_HUGE BIT(2) /* Huge packet support */
-#define KS8995_GC2_LEGAL BIT(1) /* Legal size override */
-
-#define KS8995_REG_GC3 0x05 /* Global Control 3 */
-#define KS8995_REG_GC4 0x06 /* Global Control 4 */
-
-#define KS8995_GC4_10BT BIT(4) /* Force switch to 10Mbit */
-#define KS8995_GC4_MII_FLOW BIT(5) /* MII full-duplex flow control enable */
-#define KS8995_GC4_MII_HD BIT(6) /* MII half-duplex mode enable */
-
-#define KS8995_REG_GC5 0x07 /* Global Control 5 */
-#define KS8995_REG_GC6 0x08 /* Global Control 6 */
-#define KS8995_REG_GC7 0x09 /* Global Control 7 */
-#define KS8995_REG_GC8 0x0a /* Global Control 8 */
-#define KS8995_REG_GC9 0x0b /* Global Control 9 */
-
-#define KS8995_GC9_SPECIAL BIT(0) /* Special tagging mode (DSA) */
-
-/* In DSA the ports 1-4 are numbered 0-3 and the CPU port is port 4 */
-#define KS8995_REG_PC(p, r) (0x10 + (0x10 * (p)) + (r)) /* Port Control */
-#define KS8995_REG_PS(p, r) (0x1e + (0x10 * (p)) + (r)) /* Port Status */
-
-#define KS8995_REG_PC0 0x00 /* Port Control 0 */
-#define KS8995_REG_PC1 0x01 /* Port Control 1 */
-#define KS8995_REG_PC2 0x02 /* Port Control 2 */
-#define KS8995_REG_PC3 0x03 /* Port Control 3 */
-#define KS8995_REG_PC4 0x04 /* Port Control 4 */
-#define KS8995_REG_PC5 0x05 /* Port Control 5 */
-#define KS8995_REG_PC6 0x06 /* Port Control 6 */
-#define KS8995_REG_PC7 0x07 /* Port Control 7 */
-#define KS8995_REG_PC8 0x08 /* Port Control 8 */
-#define KS8995_REG_PC9 0x09 /* Port Control 9 */
-#define KS8995_REG_PC10 0x0a /* Port Control 10 */
-#define KS8995_REG_PC11 0x0b /* Port Control 11 */
-#define KS8995_REG_PC12 0x0c /* Port Control 12 */
-#define KS8995_REG_PC13 0x0d /* Port Control 13 */
-
-#define KS8995_PC0_TAG_INS BIT(2) /* Enable tag insertion on port */
-#define KS8995_PC0_TAG_REM BIT(1) /* Enable tag removal on port */
-#define KS8995_PC0_PRIO_EN BIT(0) /* Enable priority handling */
-
-#define KS8995_PC2_TXEN BIT(2) /* Enable TX on port */
-#define KS8995_PC2_RXEN BIT(1) /* Enable RX on port */
-#define KS8995_PC2_LEARN_DIS BIT(0) /* Disable learning on port */
-
-#define KS8995_PC13_TXDIS BIT(6) /* Disable transmitter */
-#define KS8995_PC13_PWDN BIT(3) /* Power down */
-
-#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */
-#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */
-#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */
-#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */
-#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */
-#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */
-#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */
-#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */
-
-#define KS8995_REG_MAC0 0x68 /* MAC address 0 */
-#define KS8995_REG_MAC1 0x69 /* MAC address 1 */
-#define KS8995_REG_MAC2 0x6a /* MAC address 2 */
-#define KS8995_REG_MAC3 0x6b /* MAC address 3 */
-#define KS8995_REG_MAC4 0x6c /* MAC address 4 */
-#define KS8995_REG_MAC5 0x6d /* MAC address 5 */
-
-#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */
-#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */
-#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */
-#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */
-#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */
-#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */
-#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */
-#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */
-#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */
-#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */
-
-#define KSZ8864_REG_ID1 0xfe /* Chip ID in bit 7 */
-
-#define KS8995_REGS_SIZE 0x80
-#define KSZ8864_REGS_SIZE 0x100
-#define KSZ8795_REGS_SIZE 0x100
-
-#define ID1_CHIPID_M 0xf
-#define ID1_CHIPID_S 4
-#define ID1_REVISION_M 0x7
-#define ID1_REVISION_S 1
-#define ID1_START_SW 1 /* start the switch */
-
-#define FAMILY_KS8995 0x95
-#define FAMILY_KSZ8795 0x87
-#define CHIPID_M 0
-#define KS8995_CHIP_ID 0x00
-#define KSZ8864_CHIP_ID 0x01
-#define KSZ8795_CHIP_ID 0x09
-
-#define KS8995_CMD_WRITE 0x02U
-#define KS8995_CMD_READ 0x03U
-
-#define KS8995_CPU_PORT 4
-#define KS8995_NUM_PORTS 5 /* 5 ports including the CPU port */
-#define KS8995_RESET_DELAY 10 /* usec */
-
-enum ks8995_chip_variant {
- ks8995,
- ksz8864,
- ksz8795,
- max_variant
-};
-
-struct ks8995_chip_params {
- char *name;
- int family_id;
- int chip_id;
- int regs_size;
- int addr_width;
- int addr_shift;
-};
-
-static const struct ks8995_chip_params ks8995_chip[] = {
- [ks8995] = {
- .name = "KS8995MA",
- .family_id = FAMILY_KS8995,
- .chip_id = KS8995_CHIP_ID,
- .regs_size = KS8995_REGS_SIZE,
- .addr_width = 8,
- .addr_shift = 0,
- },
- [ksz8864] = {
- .name = "KSZ8864RMN",
- .family_id = FAMILY_KS8995,
- .chip_id = KSZ8864_CHIP_ID,
- .regs_size = KSZ8864_REGS_SIZE,
- .addr_width = 8,
- .addr_shift = 0,
- },
- [ksz8795] = {
- .name = "KSZ8795CLX",
- .family_id = FAMILY_KSZ8795,
- .chip_id = KSZ8795_CHIP_ID,
- .regs_size = KSZ8795_REGS_SIZE,
- .addr_width = 12,
- .addr_shift = 1,
- },
-};
-
-struct ks8995_switch {
- struct spi_device *spi;
- struct device *dev;
- struct dsa_switch *ds;
- struct mutex lock;
- struct gpio_desc *reset_gpio;
- struct bin_attribute regs_attr;
- const struct ks8995_chip_params *chip;
- int revision_id;
- unsigned int max_mtu[KS8995_NUM_PORTS];
-};
-
-static const struct spi_device_id ks8995_id[] = {
- {"ks8995", ks8995},
- {"ksz8864", ksz8864},
- {"ksz8795", ksz8795},
- { }
-};
-MODULE_DEVICE_TABLE(spi, ks8995_id);
-
-static const struct of_device_id ks8995_spi_of_match[] = {
- { .compatible = "micrel,ks8995" },
- { .compatible = "micrel,ksz8864" },
- { .compatible = "micrel,ksz8795" },
- { },
-};
-MODULE_DEVICE_TABLE(of, ks8995_spi_of_match);
-
-static inline u8 get_chip_id(u8 val)
-{
- return (val >> ID1_CHIPID_S) & ID1_CHIPID_M;
-}
-
-static inline u8 get_chip_rev(u8 val)
-{
- return (val >> ID1_REVISION_S) & ID1_REVISION_M;
-}
-
-/* create_spi_cmd - create a chip specific SPI command header
- * @ks: pointer to switch instance
- * @cmd: SPI command for switch
- * @address: register address for command
- *
- * Different chip families use different bit pattern to address the switches
- * registers:
- *
- * KS8995: 8bit command + 8bit address
- * KSZ8795: 3bit command + 12bit address + 1bit TR (?)
- */
-static inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd,
- unsigned address)
-{
- u16 result = cmd;
-
- /* make room for address (incl. address shift) */
- result <<= ks->chip->addr_width + ks->chip->addr_shift;
- /* add address */
- result |= address << ks->chip->addr_shift;
- /* SPI protocol needs big endian */
- return cpu_to_be16(result);
-}
-/* ------------------------------------------------------------------------ */
-static int ks8995_read(struct ks8995_switch *ks, char *buf,
- unsigned offset, size_t count)
-{
- __be16 cmd;
- struct spi_transfer t[2];
- struct spi_message m;
- int err;
-
- cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset);
- spi_message_init(&m);
-
- memset(&t, 0, sizeof(t));
-
- t[0].tx_buf = &cmd;
- t[0].len = sizeof(cmd);
- spi_message_add_tail(&t[0], &m);
-
- t[1].rx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
- mutex_lock(&ks->lock);
- err = spi_sync(ks->spi, &m);
- mutex_unlock(&ks->lock);
-
- return err ? err : count;
-}
-
-static int ks8995_write(struct ks8995_switch *ks, char *buf,
- unsigned offset, size_t count)
-{
- __be16 cmd;
- struct spi_transfer t[2];
- struct spi_message m;
- int err;
-
- cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset);
- spi_message_init(&m);
-
- memset(&t, 0, sizeof(t));
-
- t[0].tx_buf = &cmd;
- t[0].len = sizeof(cmd);
- spi_message_add_tail(&t[0], &m);
-
- t[1].tx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
- mutex_lock(&ks->lock);
- err = spi_sync(ks->spi, &m);
- mutex_unlock(&ks->lock);
-
- return err ? err : count;
-}
-
-static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf)
-{
- return ks8995_read(ks, buf, addr, 1) != 1;
-}
-
-static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val)
-{
- char buf = val;
-
- return ks8995_write(ks, &buf, addr, 1) != 1;
-}
-
-/* ------------------------------------------------------------------------ */
-
-static int ks8995_stop(struct ks8995_switch *ks)
-{
- return ks8995_write_reg(ks, KS8995_REG_ID1, 0);
-}
-
-static int ks8995_start(struct ks8995_switch *ks)
-{
- return ks8995_write_reg(ks, KS8995_REG_ID1, 1);
-}
-
-static int ks8995_reset(struct ks8995_switch *ks)
-{
- int err;
-
- err = ks8995_stop(ks);
- if (err)
- return err;
-
- udelay(KS8995_RESET_DELAY);
-
- return ks8995_start(ks);
-}
-
-/* ks8995_get_revision - get chip revision
- * @ks: pointer to switch instance
- *
- * Verify chip family and id and get chip revision.
- */
-static int ks8995_get_revision(struct ks8995_switch *ks)
-{
- int err;
- u8 id0, id1, ksz8864_id;
-
- /* read family id */
- err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- /* verify family id */
- if (id0 != ks->chip->family_id) {
- dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n",
- ks->chip->family_id, id0);
- err = -ENODEV;
- goto err_out;
- }
-
- switch (ks->chip->family_id) {
- case FAMILY_KS8995:
- /* try reading chip id at CHIP ID1 */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- /* verify chip id */
- if ((get_chip_id(id1) == CHIPID_M) &&
- (get_chip_id(id1) == ks->chip->chip_id)) {
- /* KS8995MA */
- ks->revision_id = get_chip_rev(id1);
- } else if (get_chip_id(id1) != CHIPID_M) {
- /* KSZ8864RMN */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- if ((ksz8864_id & 0x80) &&
- (ks->chip->chip_id == KSZ8864_CHIP_ID)) {
- ks->revision_id = get_chip_rev(id1);
- }
-
- } else {
- dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n",
- id1);
- err = -ENODEV;
- }
- break;
- case FAMILY_KSZ8795:
- /* try reading chip id at CHIP ID1 */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- if (get_chip_id(id1) == ks->chip->chip_id) {
- ks->revision_id = get_chip_rev(id1);
- } else {
- dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n",
- id1);
- err = -ENODEV;
- }
- break;
- default:
- dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0);
- err = -ENODEV;
- break;
- }
-err_out:
- return err;
-}
-
-static int ks8995_check_config(struct ks8995_switch *ks)
-{
- int ret;
- u8 val;
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC0, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC0\n");
- return ret;
- }
-
- dev_dbg(ks->dev, "port 5 PHY %senabled\n",
- (val & KS8995_GC0_P5_PHY) ? "" : "not ");
-
- val |= KS8995_GC0_P5_PHY;
- ret = ks8995_write_reg(ks, KS8995_REG_GC0, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC0\n");
-
- dev_dbg(ks->dev, "set KS8995_REG_GC0 to 0x%02x\n", val);
-
- return 0;
-}
-
-static void
-ks8995_mac_config(struct phylink_config *config, unsigned int mode,
- const struct phylink_link_state *state)
-{
-}
-
-static void
-ks8995_mac_link_up(struct phylink_config *config, struct phy_device *phydev,
- unsigned int mode, phy_interface_t interface,
- int speed, int duplex, bool tx_pause, bool rx_pause)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct ks8995_switch *ks = dp->ds->priv;
- int port = dp->index;
- int ret;
- u8 val;
-
- /* Allow forcing the mode on the fixed CPU port, no autonegotiation.
- * We assume autonegotiation works on the PHY-facing ports.
- */
- if (port != KS8995_CPU_PORT)
- return;
-
- dev_dbg(ks->dev, "MAC link up on CPU port (%d)\n", port);
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC4, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC4\n");
- return;
- }
-
- /* Conjure port config */
- switch (speed) {
- case SPEED_10:
- dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
- val |= KS8995_GC4_10BT;
- break;
- case SPEED_100:
- default:
- dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
- val &= ~KS8995_GC4_10BT;
- break;
- }
-
- if (duplex == DUPLEX_HALF) {
- dev_dbg(ks->dev, "set switch MII to half duplex\n");
- val |= KS8995_GC4_MII_HD;
- } else {
- dev_dbg(ks->dev, "set switch MII to full duplex\n");
- val &= ~KS8995_GC4_MII_HD;
- }
-
- dev_dbg(ks->dev, "set KS8995_REG_GC4 to %02x\n", val);
-
- /* Enable the CPU port */
- ret = ks8995_write_reg(ks, KS8995_REG_GC4, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC4\n");
-}
-
-static void
-ks8995_mac_link_down(struct phylink_config *config, unsigned int mode,
- phy_interface_t interface)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct ks8995_switch *ks = dp->ds->priv;
- int port = dp->index;
-
- if (port != KS8995_CPU_PORT)
- return;
-
- dev_dbg(ks->dev, "MAC link down on CPU port (%d)\n", port);
-
- /* Disable the CPU port */
-}
-
-static const struct phylink_mac_ops ks8995_phylink_mac_ops = {
- .mac_config = ks8995_mac_config,
- .mac_link_up = ks8995_mac_link_up,
- .mac_link_down = ks8995_mac_link_down,
-};
-
-static enum
-dsa_tag_protocol ks8995_get_tag_protocol(struct dsa_switch *ds,
- int port,
- enum dsa_tag_protocol mp)
-{
- /* This switch actually uses the 6 byte KS8995 protocol */
- return DSA_TAG_PROTO_NONE;
-}
-
-static int ks8995_setup(struct dsa_switch *ds)
-{
- return 0;
-}
-
-static int ks8995_port_enable(struct dsa_switch *ds, int port,
- struct phy_device *phy)
-{
- struct ks8995_switch *ks = ds->priv;
-
- dev_dbg(ks->dev, "enable port %d\n", port);
-
- return 0;
-}
-
-static void ks8995_port_disable(struct dsa_switch *ds, int port)
-{
- struct ks8995_switch *ks = ds->priv;
-
- dev_dbg(ks->dev, "disable port %d\n", port);
-}
-
-static int ks8995_port_pre_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- /* We support enabling/disabling learning */
- if (flags.mask & ~(BR_LEARNING))
- return -EINVAL;
-
- return 0;
-}
-
-static int ks8995_port_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- struct ks8995_switch *ks = ds->priv;
- int ret;
- u8 val;
-
- if (flags.mask & BR_LEARNING) {
- ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
- return ret;
- }
-
- if (flags.val & BR_LEARNING)
- val &= ~KS8995_PC2_LEARN_DIS;
- else
- val |= KS8995_PC2_LEARN_DIS;
-
- ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
- if (ret) {
- dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
- return ret;
- }
- }
-
- return 0;
-}
-
-static void ks8995_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
-{
- struct ks8995_switch *ks = ds->priv;
- int ret;
- u8 val;
-
- ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
- return;
- }
-
- /* Set the bits for the different STP states in accordance with
- * the datasheet, pages 36-37 "Spanning tree support".
- */
- switch (state) {
- case BR_STATE_DISABLED:
- case BR_STATE_BLOCKING:
- case BR_STATE_LISTENING:
- val &= ~KS8995_PC2_TXEN;
- val &= ~KS8995_PC2_RXEN;
- val |= KS8995_PC2_LEARN_DIS;
- break;
- case BR_STATE_LEARNING:
- val &= ~KS8995_PC2_TXEN;
- val &= ~KS8995_PC2_RXEN;
- val &= ~KS8995_PC2_LEARN_DIS;
- break;
- case BR_STATE_FORWARDING:
- val |= KS8995_PC2_TXEN;
- val |= KS8995_PC2_RXEN;
- val &= ~KS8995_PC2_LEARN_DIS;
- break;
- default:
- dev_err(ks->dev, "unknown bridge state requested\n");
- return;
- }
-
- ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
- if (ret) {
- dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
- return;
- }
-
- dev_dbg(ks->dev, "set KS8995_REG_PC2 for port %d to %02x\n", port, val);
-}
-
-static void ks8995_phylink_get_caps(struct dsa_switch *dsa, int port,
- struct phylink_config *config)
-{
- unsigned long *interfaces = config->supported_interfaces;
-
- if (port == KS8995_CPU_PORT)
- __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
-
- if (port <= 3) {
- /* Internal PHYs */
- __set_bit(PHY_INTERFACE_MODE_INTERNAL, interfaces);
- /* phylib default */
- __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
- }
-
- config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100;
-}
-
-/* Huge packet support up to 1916 byte packages "inclusive"
- * which means that tags are included. If the bit is not set
- * it is 1536 bytes "inclusive". We present the length without
- * tags or ethernet headers. The setting affects all ports.
- */
-static int ks8995_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
-{
- struct ks8995_switch *ks = ds->priv;
- unsigned int max_mtu;
- int ret;
- u8 val;
- int i;
-
- ks->max_mtu[port] = new_mtu;
-
- /* Roof out the MTU for the entire switch to the greatest
- * common denominator: the biggest set for any one port will
- * be the biggest MTU for the switch.
- */
- max_mtu = ETH_DATA_LEN;
- for (i = 0; i < KS8995_NUM_PORTS; i++) {
- if (ks->max_mtu[i] > max_mtu)
- max_mtu = ks->max_mtu[i];
- }
-
- /* Translate to layer 2 size.
- * Add ethernet and (possible) VLAN headers, and checksum to the size.
- * For ETH_DATA_LEN (1500 bytes) this will add up to 1522 bytes.
- */
- max_mtu += VLAN_ETH_HLEN;
- max_mtu += ETH_FCS_LEN;
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC2, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC2\n");
- return ret;
- }
-
- if (max_mtu <= 1522) {
- val &= ~KS8995_GC2_HUGE;
- val &= ~KS8995_GC2_LEGAL;
- } else if (max_mtu > 1522 && max_mtu <= 1536) {
- /* This accepts packets up to 1536 bytes */
- val &= ~KS8995_GC2_HUGE;
- val |= KS8995_GC2_LEGAL;
- } else {
- /* This accepts packets up to 1916 bytes */
- val |= KS8995_GC2_HUGE;
- val |= KS8995_GC2_LEGAL;
- }
-
- dev_dbg(ks->dev, "new max MTU %d bytes (inclusive)\n", max_mtu);
-
- ret = ks8995_write_reg(ks, KS8995_REG_GC2, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC2\n");
-
- return ret;
-}
-
-static int ks8995_get_max_mtu(struct dsa_switch *ds, int port)
-{
- return 1916 - ETH_HLEN - ETH_FCS_LEN;
-}
-
-static const struct dsa_switch_ops ks8995_ds_ops = {
- .get_tag_protocol = ks8995_get_tag_protocol,
- .setup = ks8995_setup,
- .port_pre_bridge_flags = ks8995_port_pre_bridge_flags,
- .port_bridge_flags = ks8995_port_bridge_flags,
- .port_enable = ks8995_port_enable,
- .port_disable = ks8995_port_disable,
- .port_stp_state_set = ks8995_port_stp_state_set,
- .port_change_mtu = ks8995_change_mtu,
- .port_max_mtu = ks8995_get_max_mtu,
- .phylink_get_caps = ks8995_phylink_get_caps,
-};
-
-/* ------------------------------------------------------------------------ */
-static int ks8995_probe(struct spi_device *spi)
-{
- struct ks8995_switch *ks;
- int err;
- int variant = spi_get_device_id(spi)->driver_data;
-
- if (variant >= max_variant) {
- dev_err(&spi->dev, "bad chip variant %d\n", variant);
- return -ENODEV;
- }
-
- ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL);
- if (!ks)
- return -ENOMEM;
-
- mutex_init(&ks->lock);
- ks->spi = spi;
- ks->dev = &spi->dev;
- ks->chip = &ks8995_chip[variant];
-
- ks->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
- GPIOD_OUT_HIGH);
- err = PTR_ERR_OR_ZERO(ks->reset_gpio);
- if (err) {
- dev_err(&spi->dev,
- "failed to get reset gpio: %d\n", err);
- return err;
- }
-
- err = gpiod_set_consumer_name(ks->reset_gpio, "switch-reset");
- if (err)
- return err;
-
- if (ks->reset_gpio) {
- /*
- * If a reset line was obtained, wait for 100us after
- * de-asserting RESET before accessing any registers, see
- * the KS8995MA datasheet, page 44.
- */
- gpiod_set_value_cansleep(ks->reset_gpio, 0);
- udelay(100);
- }
-
- spi_set_drvdata(spi, ks);
-
- spi->mode = SPI_MODE_0;
- spi->bits_per_word = 8;
- err = spi_setup(spi);
- if (err) {
- dev_err(&spi->dev, "spi_setup failed, err=%d\n", err);
- return err;
- }
-
- err = ks8995_get_revision(ks);
- if (err)
- return err;
-
- err = ks8995_reset(ks);
- if (err)
- return err;
-
- dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
- ks->chip->name, ks->chip->chip_id, ks->revision_id);
-
- err = ks8995_check_config(ks);
- if (err)
- return err;
-
- ks->ds = devm_kzalloc(&spi->dev, sizeof(*ks->ds), GFP_KERNEL);
- if (!ks->ds)
- return -ENOMEM;
-
- ks->ds->dev = &spi->dev;
- ks->ds->num_ports = KS8995_NUM_PORTS;
- ks->ds->ops = &ks8995_ds_ops;
- ks->ds->phylink_mac_ops = &ks8995_phylink_mac_ops;
- ks->ds->priv = ks;
-
- err = dsa_register_switch(ks->ds);
- if (err)
- return dev_err_probe(&spi->dev, err,
- "unable to register DSA switch\n");
-
- return 0;
-}
-
-static void ks8995_remove(struct spi_device *spi)
-{
- struct ks8995_switch *ks = spi_get_drvdata(spi);
-
- dsa_unregister_switch(ks->ds);
- /* assert reset */
- gpiod_set_value_cansleep(ks->reset_gpio, 1);
-}
-
-/* ------------------------------------------------------------------------ */
-static struct spi_driver ks8995_driver = {
- .driver = {
- .name = "spi-ks8995",
- .of_match_table = ks8995_spi_of_match,
- },
- .probe = ks8995_probe,
- .remove = ks8995_remove,
- .id_table = ks8995_id,
-};
-
-module_spi_driver(ks8995_driver);
-
-MODULE_DESCRIPTION(DRV_DESC);
-MODULE_VERSION(DRV_VERSION);
-MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>");
-MODULE_LICENSE("GPL v2");
--
2.54.0
^ permalink raw reply related
* [PATCH net 0/3] ipv4/ipv6: Fix UAF and memory leak in IGMP/MLD
From: Eric Dumazet @ 2026-07-04 19:43 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
This series addresses two potential UAF vulnerabilities
and one memory leak in the IPv4 IGMP and IPv6 MLD subsystems.
The first two patches fix a UAF where the packet receive path races with
device teardown. If the device refcount has already hit 0 (but the memory
is still held by RCU), incoming IGMP/MLD packets trying to schedule delayed
work or timers would call refcount_inc() on the 0 refcount, triggering a
warning and eventually leading to a UAF when the work runs after the device
has been freed. This is fixed by introducing safe hold helpers using
refcount_inc_not_zero().
The third patch fixes a memory leak in IPv4 IGMP timer modification. When
a timer is deleted and not re-armed, the code dropped the group refcount
using refcount_dec(). However, if the group was concurrently removed from
the list, this decrement could drop the refcount to 0 without triggering
the cleanup/free path, leaking the group structure. This is fixed by using
ip_ma_put() instead, and deferring the put until after the lock is released.
Eric Dumazet (3):
ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
ipv6: mcast: Fix potential UAF in MLD delayed work
ipv4: igmp: Fix potential memory leak in igmp_mod_timer()
include/linux/inetdevice.h | 5 +++++
include/net/addrconf.h | 5 +++++
net/ipv4/igmp.c | 21 +++++++++++++++------
net/ipv6/mcast.c | 38 ++++++++++++++++++++++++++++----------
4 files changed, 53 insertions(+), 16 deletions(-)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply
* [PATCH net 1/3] ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
From: Eric Dumazet @ 2026-07-04 19:43 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet, Zero Day Initiative
In-Reply-To: <20260704194346.4065071-1-edumazet@google.com>
A race condition exists between device teardown (inetdev_destroy) and
incoming IGMP query processing (igmp_rcv), leading to a Use-After-Free
in the IGMP timer callback.
During device destruction, inetdev_destroy() drops the primary reference
to in_device, which can drop its refcount to 0. The actual freeing of
in_device memory is deferred via RCU (using call_rcu()).
Concurrently, igmp_rcv() runs under RCU read lock and obtains the
in_device pointer. Because the memory is RCU-protected, CPU-0 can safely
dereference in_device even if its refcount has hit 0.
However, if CPU-0 calls igmp_gq_start_timer() and re-arms the timer, it
attempts to acquire a reference using in_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the in_device memory is still scheduled to be freed after the RCU
grace period (as the free callback does not check the refcount again),
the device is freed while the timer is still armed. When the timer
expires, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not arm the timer.
A similar issue in IPv6 MLD is fixed in a subsequent patch.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Zero Day Initiative <zdi-disclosures@trendmicro.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/linux/inetdevice.h | 5 +++++
net/ipv4/igmp.c | 14 +++++++++-----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h
index dccbeb25f70141982160c776f3cc727296def2b7..6032eea2539a60d0476d85667bda3bfcad8f1425 100644
--- a/include/linux/inetdevice.h
+++ b/include/linux/inetdevice.h
@@ -293,6 +293,11 @@ static inline void in_dev_put(struct in_device *idev)
#define __in_dev_put(idev) refcount_dec(&(idev)->refcnt)
#define in_dev_hold(idev) refcount_inc(&(idev)->refcnt)
+static inline bool in_dev_hold_safe(struct in_device *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
#endif /* __KERNEL__ */
static __inline__ __be32 inet_make_mask(int logmask)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index b6337a47c1418589bf8ef31e00fd98b6187f5271..f5f9763895641bf86bfcf9fd7fd7b06012fa4ece 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -248,16 +248,20 @@ static void igmp_gq_start_timer(struct in_device *in_dev)
return;
in_dev->mr_gq_running = 1;
- if (!mod_timer(&in_dev->mr_gq_timer, exp))
- in_dev_hold(in_dev);
+ if (in_dev_hold_safe(in_dev)) {
+ if (mod_timer(&in_dev->mr_gq_timer, exp))
+ in_dev_put(in_dev);
+ }
}
static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
{
- int tv = get_random_u32_below(delay);
+ if (in_dev_hold_safe(in_dev)) {
+ int tv = get_random_u32_below(delay);
- if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2))
- in_dev_hold(in_dev);
+ if (mod_timer(&in_dev->mr_ifc_timer, jiffies + tv + 2))
+ in_dev_put(in_dev);
+ }
}
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH net 2/3] ipv6: mcast: Fix potential UAF in MLD delayed work
From: Eric Dumazet @ 2026-07-04 19:43 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260704194346.4065071-1-edumazet@google.com>
A race condition exists between device teardown and incoming MLD query
processing, leading to a Use-After-Free in the MLD delayed work.
During device destruction, the primary reference to inet6_dev is dropped,
which can drop its refcount to 0. The actual freeing of inet6_dev memory
is deferred via RCU.
Concurrently, the packet receive path runs under RCU read lock and obtains
the inet6_dev pointer. Because the memory is RCU-protected, CPU-0 can
safely dereference inet6_dev even if its refcount has hit 0.
However, if CPU-0 calls igmp6_event_query() and schedules delayed work, it
attempts to acquire a reference using in6_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the inet6_dev memory is still scheduled to be freed after the RCU
grace period, the device is freed while the work is still scheduled.
When the work runs, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in6_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not schedule the work.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/net/addrconf.h | 5 +++++
net/ipv6/mcast.c | 38 ++++++++++++++++++++++++++++----------
2 files changed, 33 insertions(+), 10 deletions(-)
diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index 539bbbe54b14e8108ff7304d7a08bc605655cc31..8ced27a8229b6e0580f934be2223676cc123307b 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -446,6 +446,11 @@ static inline void in6_dev_hold(struct inet6_dev *idev)
refcount_inc(&idev->refcnt);
}
+static inline bool in6_dev_hold_safe(struct inet6_dev *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
/* called with rcu_read_lock held */
static inline bool ip6_ignore_linkdown(const struct net_device *dev)
{
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 04b811b3be978e36b0fd4ecd8312313d73ed2ed9..7c3f739cf7638452f311c64331c89253361b34f9 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -1083,8 +1083,10 @@ static void mld_gq_start_work(struct inet6_dev *idev)
mc_assert_locked(idev);
idev->mc_gq_running = 1;
- if (!mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_gq_stop_work(struct inet6_dev *idev)
@@ -1102,8 +1104,10 @@ static void mld_ifc_start_work(struct inet6_dev *idev, unsigned long delay)
mc_assert_locked(idev);
- if (!mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_ifc_stop_work(struct inet6_dev *idev)
@@ -1121,8 +1125,10 @@ static void mld_dad_start_work(struct inet6_dev *idev, unsigned long delay)
mc_assert_locked(idev);
- if (!mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_dad_stop_work(struct inet6_dev *idev)
@@ -1395,6 +1401,7 @@ static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld,
void igmp6_event_query(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
+ bool put = false;
if (!idev || idev->dead)
goto out;
@@ -1402,11 +1409,16 @@ void igmp6_event_query(struct sk_buff *skb)
spin_lock_bh(&idev->mc_query_lock);
if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS) {
__skb_queue_tail(&idev->mc_query_queue, skb);
- if (!mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
+ put = true;
+ }
skb = NULL;
}
spin_unlock_bh(&idev->mc_query_lock);
+
+ if (put)
+ in6_dev_put(idev);
out:
kfree_skb(skb);
}
@@ -1570,6 +1582,7 @@ static void mld_query_work(struct work_struct *work)
void igmp6_event_report(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
+ bool put = false;
if (!idev || idev->dead)
goto out;
@@ -1577,11 +1590,16 @@ void igmp6_event_report(struct sk_buff *skb)
spin_lock_bh(&idev->mc_report_lock);
if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS) {
__skb_queue_tail(&idev->mc_report_queue, skb);
- if (!mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
+ put = true;
+ }
skb = NULL;
}
spin_unlock_bh(&idev->mc_report_lock);
+
+ if (put)
+ in6_dev_put(idev);
out:
kfree_skb(skb);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH net 3/3] ipv4: igmp: Fix potential memory leak in igmp_mod_timer()
From: Eric Dumazet @ 2026-07-04 19:43 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260704194346.4065071-1-edumazet@google.com>
When a timer is deleted and not re-armed in igmp_mod_timer(), the code
currently decrements the reference counter of the multicast list entry
@im using refcount_dec(&im->refcnt).
However, igmp_mod_timer() can be called from the RCU reader path (e.g., in
igmp_heard_query() via for_each_pmc_rcu()). If the group im was
concurrently removed from the list by ip_mc_dec_group(), its reference count
might have already been decremented to 1.
In this case, timer_delete() succeeds, and refcount_dec() decrements
the refcount from 1 to 0. Since refcount_dec() does not free the object
when it hits 0 (unlike ip_ma_put()), the im structure is leaked.
Fix this by using ip_ma_put(im) instead of refcount_dec(&im->refcnt).
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
net/ipv4/igmp.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index f5f9763895641bf86bfcf9fd7fd7b06012fa4ece..2170b33ba147ce4990e3ee71ba4868e8696b00cb 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -266,6 +266,8 @@ static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
{
+ bool put = false;
+
spin_lock_bh(&im->lock);
im->unsolicit_count = 0;
if (timer_delete(&im->timer)) {
@@ -275,10 +277,13 @@ static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
spin_unlock_bh(&im->lock);
return;
}
- refcount_dec(&im->refcnt);
+ put = true;
}
igmp_start_timer(im, max_delay);
spin_unlock_bh(&im->lock);
+
+ if (put)
+ ip_ma_put(im);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH ipsec] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full
From: Xiang Mei @ 2026-07-04 21:03 UTC (permalink / raw)
To: steffen.klassert, herbert, davem
Cc: nakam, edumazet, kuba, pabeni, horms, netdev, bestswngs,
Xiang Mei
The depth check in xfrm6_input_addr() is off by one:
if (1 + sp->len == XFRM_MAX_DEPTH)
goto drop;
...
sp->xvec[sp->len++] = x;
xfrm_input() can leave sp->len == XFRM_MAX_DEPTH, and the transport-mode
receive path re-enters IPv6 input via xfrm_trans_reinject() with that
secpath preserved. If the inner packet carries a destination-options HAO
option or a type-2 routing header, xfrm6_input_addr() is called with
sp->len == XFRM_MAX_DEPTH; the check (1 + 6 == 6) is false, so
sp->xvec[sp->len++] writes one slot past the 6-element xvec[]. The write
stays within the sec_path allocation (invisible to KASAN); UBSAN_BOUNDS
flags it and panics under panic_on_warn.
Use "sp->len >= XFRM_MAX_DEPTH", matching xfrm_input(). This also
restores one chain level the old check rejected at sp->len == 5.
UBSAN: array-index-out-of-bounds in net/ipv6/xfrm6_input.c:309:10
index 6 is out of range for type 'xfrm_state *[6]'
Fixes: 9473e1f631de ("[XFRM] MIPv6: Fix to input RO state correctly.")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
net/ipv6/xfrm6_input.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index 89d0443b5307..07edef258984 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -247,7 +247,7 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
goto drop;
}
- if (1 + sp->len == XFRM_MAX_DEPTH) {
+ if (sp->len >= XFRM_MAX_DEPTH) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINBUFFERERROR);
goto drop;
}
--
2.43.0
^ permalink raw reply related
* [PATCH] net: pcs: xpcs-plat: fix runtime PM initialization
From: Coia Prant @ 2026-07-04 21:48 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, Heiner Kallweit, Russell King, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Serge Semin,
linux-kernel, Coia Prant, stable
The driver calls `pm_runtime_set_active()` before runtime PM is enabled,
and before the clock is prepared and enabled.
This causes the clock to be unprepared/disabled later in the suspend
callback even though it was never prepared/enabled, resulting in warnings:
clk_csr already disabled
clk_csr already unprepared
Fix this by setting the initial runtime PM status to SUSPENDED instead
of ACTIVE.
The clock will be properly enabled when the device is first resumed
via runtime PM (e.g., during MDIO access).
Fixes: f6bb3e9d98c2 ("net: pcs: xpcs: Add Synopsys DW xPCS platform device driver")
Cc: stable@vger.kernel.org
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
drivers/net/pcs/pcs-xpcs-plat.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/pcs/pcs-xpcs-plat.c b/drivers/net/pcs/pcs-xpcs-plat.c
index f4b1b8246ce96..fb80773379df5 100644
--- a/drivers/net/pcs/pcs-xpcs-plat.c
+++ b/drivers/net/pcs/pcs-xpcs-plat.c
@@ -285,7 +285,7 @@ static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs)
return dev_err_probe(dev, PTR_ERR(pxpcs->cclk),
"Failed to get CSR clock\n");
- pm_runtime_set_active(dev);
+ pm_runtime_set_suspended(dev);
ret = devm_pm_runtime_enable(dev);
if (ret) {
dev_err(dev, "Failed to enable runtime-PM\n");
--
2.47.3
^ 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