* [PATCH net-next v3 1/5] net: dsa: microchip: Add fallback Micrel compatibles
2026-05-26 15:59 [PATCH net-next v3 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
@ 2026-05-26 15:59 ` Linus Walleij
2026-05-30 0:49 ` sashiko-bot
2026-05-26 15:59 ` [PATCH net-next v3 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
` (3 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Linus Walleij @ 2026-05-26 15:59 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
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.
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 [flat|nested] 10+ messages in thread* Re: [PATCH net-next v3 1/5] net: dsa: microchip: Add fallback Micrel compatibles
2026-05-26 15:59 ` [PATCH net-next v3 1/5] net: dsa: microchip: Add fallback Micrel compatibles Linus Walleij
@ 2026-05-30 0:49 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-05-30 0:49 UTC (permalink / raw)
To: Linus Walleij; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Making the KS8995 driver mutually exclusive with the KSZ SPI driver temporarily breaks support for legacy `micrel,ks8995` hardware, causing a bisection regression.
- [Medium] The Kconfig dependency `depends on !NET_DSA_MICROCHIP_KSZ_SPI` fails to prevent both drivers from being built as modules, defeating the stated purpose of avoiding probe races.
--
commit ca8ff9ab891d7b4494af8f583c533c66ec764e52
Author: Linus Walleij <linusw@kernel.org>
net: dsa: microchip: Add fallback Micrel compatibles
This commit adds fallback Micrel compatibles for KSZ SPI devices to allow them
to use the more capable KSZ driver. It also adds a Kconfig exclusion to avoid
probe races with the KS8995 driver.
> diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
> index 4ab567c5bbafc..e704ab702c189 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
[Severity: Medium]
Does this dependency successfully prevent both drivers from being built as
modules?
Because NET_DSA_MICROCHIP_KSZ_SPI is a tristate config, configuring it as 'm'
makes '!NET_DSA_MICROCHIP_KSZ_SPI' evaluate to 'm'. This satisfies the
dependency for NET_DSA_KS8995, allowing it to also be built as a module,
which could allow the probe races this patch intended to fix. To strictly
enforce mutual exclusion, would `depends on NET_DSA_MICROCHIP_KSZ_SPI = n`
be required?
I note this issue is later resolved by commit 19ced6cb5c2f1 ("net: dsa:
ks8995: Delete surplus driver") which completely removes the KS8995 driver.
[Severity: High]
Does making this driver mutually exclusive with the KSZ SPI driver
temporarily break support for legacy micrel,ks8995 hardware?
If CONFIG_NET_DSA_MICROCHIP_KSZ_SPI=y, Kconfig forces CONFIG_NET_DSA_KS8995=n.
However, at this commit, the KSZ SPI driver does not yet support the older
micrel,ks8995 switches. Could this break network functionality for those
devices during bisection?
I see support is restored later in the series by commit 2e706ae441971
("net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA"), which
ports KS8995 support to the new KSZ SPI driver.
> select NET_DSA_TAG_NONE
> help
> This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-ks8995-to-ksz8-v3-0-c530f651989f@kernel.org?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA
2026-05-26 15:59 [PATCH net-next v3 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
2026-05-26 15:59 ` [PATCH net-next v3 1/5] net: dsa: microchip: Add fallback Micrel compatibles Linus Walleij
@ 2026-05-26 15:59 ` Linus Walleij
2026-05-26 15:59 ` [PATCH net-next v3 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
` (2 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Linus Walleij @ 2026-05-26 15:59 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
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 [flat|nested] 10+ messages in thread* [PATCH net-next v3 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling
2026-05-26 15:59 [PATCH net-next v3 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
2026-05-26 15:59 ` [PATCH net-next v3 1/5] net: dsa: microchip: Add fallback Micrel compatibles Linus Walleij
2026-05-26 15:59 ` [PATCH net-next v3 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
@ 2026-05-26 15:59 ` Linus Walleij
2026-05-30 0:49 ` sashiko-bot
2026-05-26 15:59 ` [PATCH net-next v3 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
2026-05-26 15:59 ` [PATCH net-next v3 5/5] net: dsa: ks8995: Delete surplus driver Linus Walleij
4 siblings, 1 reply; 10+ messages in thread
From: Linus Walleij @ 2026-05-26 15:59 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
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 | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 142 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index ca6c7425b45f..eef6ac0e1200 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17409,6 +17409,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..5bdb18271582
--- /dev/null
+++ b/net/dsa/tag_ks8995.c
@@ -0,0 +1,132 @@
+// 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);
+ 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);
+ 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) {
+ 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)
+ 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 [flat|nested] 10+ messages in thread* Re: [PATCH net-next v3 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling
2026-05-26 15:59 ` [PATCH net-next v3 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
@ 2026-05-30 0:49 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-05-30 0:49 UTC (permalink / raw)
To: Linus Walleij; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Double free in ks8995_xmit() error path when vlan_insert_tag() fails.
- [High] Double free in ks8995_rcv() error path when skb_vlan_untag() fails.
- [High] Undefined behavior from ilog2(0) when receiving standard 802.1Q frames.
- [High] Failure to push non-802.1Q hardware-accelerated VLAN tags into the payload.
--
commit 0b9e2806259ede96c62f73af014408284f18d9b2
Author: Linus Walleij <linusw@kernel.org>
net: dsa: tag_ks8995: Add the KS8995 tag handling
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.
> diff --git a/net/dsa/tag_ks8995.c b/net/dsa/tag_ks8995.c
> new file mode 100644
> index 0000000000000..5bdb182715827
> --- /dev/null
> +++ b/net/dsa/tag_ks8995.c
[ ... ]
> +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;
> + }
[Severity: High]
Does this properly handle non-802.1Q hardware-accelerated VLAN tags like
802.1AD?
If an 802.1AD tag is present, it appears the tag is left in the hwaccel area
and the KS8995 special tag is pushed into the payload. The host NIC might
then push the 802.1AD tag onto the wire outside the KS8995 tag, which would
prevent the switch from seeing its special tag as the outermost ethertype.
> +
> + if (have_hwaccel_tag || hdr->h_vlan_proto != htons(ETH_P_8021Q)) {
> + skb = vlan_insert_tag(skb, KS8995M_STPID(portmask), tci);
> + if (!skb)
> + return NULL;
[Severity: High]
Will returning NULL here cause a double-free?
When vlan_insert_tag() fails to allocate memory, it frees the original skb
internally and returns NULL. ks8995_xmit() then propagates this NULL back to
dsa_user_xmit(). Since dsa_user_xmit() explicitly calls kfree_skb(skb) when
the tagger returns NULL, won't this result in freeing the same skb twice?
[ ... ]
> +static struct sk_buff *ks8995_rcv(struct sk_buff *skb, struct net_device *dev)
> +{
> + int portmask;
> + u16 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) {
> + netdev_err(dev, "%s: unable to untag skb\n", __func__);
> + return NULL;
> + }
[Severity: High]
Could this also lead to a double-free if skb_vlan_untag() fails?
If skb_vlan_untag() fails, it internally frees the SKB and returns NULL.
ks8995_rcv() propagates this NULL back to dsa_switch_rcv(). The DSA receive
core interprets a NULL return from a tagger as an unhandled packet and
explicitly calls kfree_skb(skb) on the original pointer.
> +
> + 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));
[Severity: High]
Does this trigger undefined behavior when receiving standard 802.1Q frames?
Standard 802.1Q frames with ethertype 0x8100 pass the earlier
(etype & KS8995M_STPID_STD) != ETH_P_8021Q check because the standard VLAN
ethertype matches the mask 0xFFF0. This yields a portmask of 0. Calling
ilog2(0) is mathematically undefined behavior and invokes __builtin_clzll(0),
which can cause hardware exceptions or return unpredictable values depending
on the architecture.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-ks8995-to-ksz8-v3-0-c530f651989f@kernel.org?part=3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
2026-05-26 15:59 [PATCH net-next v3 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
` (2 preceding siblings ...)
2026-05-26 15:59 ` [PATCH net-next v3 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
@ 2026-05-26 15:59 ` Linus Walleij
2026-05-30 0:49 ` sashiko-bot
2026-05-26 15:59 ` [PATCH net-next v3 5/5] net: dsa: ks8995: Delete surplus driver Linus Walleij
4 siblings, 1 reply; 10+ messages in thread
From: Linus Walleij @ 2026-05-26 15:59 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
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 | 170 +++++++++++++++++++++++-----
drivers/net/dsa/microchip/ksz8.h | 2 +
drivers/net/dsa/microchip/ksz8_reg.h | 7 ++
drivers/net/dsa/microchip/ksz_common.c | 58 ++++++++--
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, 230 insertions(+), 38 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 0f84b2e7d25d..18541a8efc39 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,18 @@ 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;
+
+ /* KSZ8995XA is "reset" by stopping the switch and starting it */
+ ksz_cfg(dev, REG_SW_ID_1, SW_START_SWITCH, false);
+ usleep_range(10, 100);
+ ksz_cfg(dev, REG_SW_ID_1, SW_START_SWITCH, true);
+
+ /* Often boot loaders have fun with ports, so reset them */
+ for (port = 0; port < dev->info->port_cnt; port++)
+ ksz_port_cfg(dev, port, 0x0f, BIT(4), 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 +173,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 +225,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);
}
@@ -826,6 +845,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;
@@ -924,8 +947,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;
@@ -1020,6 +1045,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;
@@ -1147,13 +1175,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)) {
@@ -1263,11 +1293,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;
@@ -1477,7 +1513,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) || ksz_is_ksz8463(dev))
+ if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev) || ksz_is_ksz8995xa(dev))
return -ENOTSUPP;
/* Discard packets with VID not enabled on the switch */
@@ -1693,14 +1729,16 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
offset = P1CR1;
ksz_port_cfg(dev, port, offset, PORT_BROADCAST_STORM, true);
- ksz8_port_queue_split(dev, port, dev->info->num_tx_queues);
+ if (!ksz_is_ksz8995xa(dev)) {
+ 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);
+ /* 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);
@@ -1709,6 +1747,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.
@@ -1756,7 +1807,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);
@@ -1958,6 +2011,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);
@@ -1976,6 +2033,7 @@ static int ksz8_setup(struct dsa_switch *ds)
struct ksz_port *p;
const u16 *regs;
int i, ret;
+ u8 val;
regs = dev->info->regs;
@@ -2041,9 +2099,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;
@@ -2051,11 +2110,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
@@ -2115,9 +2178,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],
@@ -2281,6 +2350,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)
@@ -2377,6 +2453,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,
@@ -2416,6 +2502,34 @@ 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,
+ .get_phy_flags = ksz_get_phy_flags,
+ .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_teardown = ksz_port_teardown,
+ .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 332408567b47..2a1f6dd53b9d 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 57c8abfe0147..ac27beb3054b 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -554,6 +554,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,
@@ -1485,6 +1499,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 = 4,
+ .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",
@@ -2826,6 +2855,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++) {
@@ -3049,6 +3082,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:
@@ -3315,11 +3349,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;
@@ -4662,6 +4700,10 @@ int ksz_switch_register(struct ksz_device *dev)
if (ret)
return ret;
+ /* Override ops with something simpler for this legacy chip */
+ if (ksz_is_ksz8995xa(dev))
+ dev->ds->ops = &ksz8995xa_switch_ops;
+
dev->dev_ops = dev->info->ops;
ret = dev->dev_ops->init(dev);
@@ -4744,11 +4786,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 b67038cf1bd0..661a82026e47 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -235,6 +235,7 @@ enum ksz_model {
KSZ88X3,
KSZ8864,
KSZ8895,
+ KSZ8995XA,
KSZ9477,
KSZ9896,
KSZ9897,
@@ -751,7 +752,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)
@@ -822,6 +829,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
@@ -885,6 +893,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 [flat|nested] 10+ messages in thread* Re: [PATCH net-next v3 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
2026-05-26 15:59 ` [PATCH net-next v3 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
@ 2026-05-30 0:49 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-05-30 0:49 UTC (permalink / raw)
To: Linus Walleij; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The KSZ8995XA PHY register emulation incorrectly maps and overwrites fundamental port control bits, disabling RX/TX and MAC learning.
--
commit 2e706ae441971159b4241c28e24cf8f143178ad5
Author: Linus Walleij <linusw@kernel.org>
net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
This adds support for the Microchip KSZ8995XA also known as the
Micrel KS8995XA switch to the KSZ driver.
> diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
> index 57c8abfe0147d..ac27beb3054bd 100644
> --- a/drivers/net/dsa/microchip/ksz_common.c
> +++ b/drivers/net/dsa/microchip/ksz_common.c
[ ... ]
> @@ -554,6 +554,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,
[Severity: High]
Will this register mapping inadvertently disable RX/TX and MAC learning?
The newly introduced ksz8995xa_regs array maps P_LOCAL_CTRL and P_FORCE_CTRL
to 0x0C, and P_NEG_RESTART_CTRL to 0x0D. The driver's PHY emulation
functions (ksz8_w_phy and ksz8_w_phy_bmcr) apply KSZ8795-specific bitmasks
to these offsets.
On the KSZ8995XA, offset 0x0C resolves to Port Control 12 (e.g., 0x1C for
Port 1), where BIT 4 is Receive Enable and BIT 3 is Transmit Enable. When
updating MII_ADVERTISE, the emulation applies PORT_AUTO_NEG_SYM_PAUSE (BIT 4)
and PORT_AUTO_NEG_100BTX_FD (BIT 3). Clearing these advertisement bits
appears to inadvertently disable RX and TX on the port.
Similarly, setting PORT_FORCE_FULL_DUPLEX (BIT 5) via MII_BMCR sets Learning
Disable. When phylink automatically configures the PHY on startup, these
writes could execute and completely break the switch port's network
functionality.
> + [P_REMOTE_STATUS] = 0x0E,
> + [P_SPEED_STATUS] = 0x09,
> + [P_STP_CTRL] = 0x02,
> + [S_START_CTRL] = 0x01,
> + [S_BROADCAST_CTRL] = 0x06,
> + [S_MULTICAST_CTRL] = 0x04,
> +};
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-ks8995-to-ksz8-v3-0-c530f651989f@kernel.org?part=4
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 5/5] net: dsa: ks8995: Delete surplus driver
2026-05-26 15:59 [PATCH net-next v3 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
` (3 preceding siblings ...)
2026-05-26 15:59 ` [PATCH net-next v3 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
@ 2026-05-26 15:59 ` Linus Walleij
2026-05-30 0:49 ` sashiko-bot
4 siblings, 1 reply; 10+ messages in thread
From: Linus Walleij @ 2026-05-26 15:59 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
The Microchip ksz driver now handles all switches that the
old driver was handling, but better.
Delete the old driver.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/Kconfig | 9 -
drivers/net/dsa/Makefile | 1 -
drivers/net/dsa/ks8995.c | 857 -----------------------------------------------
3 files changed, 867 deletions(-)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index e704ab702c18..7e6d98ebbd52 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -97,15 +97,6 @@ config NET_DSA_RZN1_A5PSW
This driver supports the A5PSW switch, which is embedded in Renesas
RZ/N1 SoC.
-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
- switches, managed over SPI.
-
config NET_DSA_SMSC_LAN9303
tristate
select NET_DSA_TAG_LAN9303
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 [flat|nested] 10+ messages in thread* Re: [PATCH net-next v3 5/5] net: dsa: ks8995: Delete surplus driver
2026-05-26 15:59 ` [PATCH net-next v3 5/5] net: dsa: ks8995: Delete surplus driver Linus Walleij
@ 2026-05-30 0:49 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-05-30 0:49 UTC (permalink / raw)
To: Linus Walleij; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [Medium] Silent loss of switch driver functionality on kernel upgrade due to missing Kconfig migration.
Pre-existing issues:
- [High] The replacement driver (ksz_spi) fails to probe for devices instantiated via platform data (non-DT) because it lacks driver_data in its spi_device_id table.
- [High] The replacement driver fails to explicitly enable the Port 5 PHY mode, a required initialization step performed by the old driver.
- [High] The MTU limit logic in ksz8863_change_mtu is completely inverted for packet sizes between 1523 and 1536 bytes, causing hardware packet drops.
--
commit 19ced6cb5c2f1c58320168d553069a57634b8e9d
Author: Linus Walleij <linusw@kernel.org>
net: dsa: ks8995: Delete surplus driver
The Microchip ksz driver now handles all switches that the
old driver was handling, but better.
Delete the old driver.
> diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
> index e704ab702c189..7e6d98ebbd522 100644
> --- a/drivers/net/dsa/Kconfig
> +++ b/drivers/net/dsa/Kconfig
> @@ -97,15 +97,6 @@ config NET_DSA_RZN1_A5PSW
> This driver supports the A5PSW switch, which is embedded in Renesas
> RZ/N1 SoC.
>
> -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
> - switches, managed over SPI.
> -
[Severity: Medium]
Will completely removing CONFIG_NET_DSA_KS8995 cause a silent loss of switch
driver functionality on kernel upgrades?
By not providing a Kconfig transition path, users with CONFIG_NET_DSA_KS8995
enabled will have the driver silently dropped from their configurations during
make oldconfig.
Does it make sense to leave a dummy NET_DSA_KS8995 config that selects the
new NET_DSA_MICROCHIP_KSZ_SPI driver to ensure a smooth transition?
> diff --git a/drivers/net/dsa/ks8995.c b/drivers/net/dsa/ks8995.c
> deleted file mode 100644
> index 77d8b842693c1..0000000000000
> --- a/drivers/net/dsa/ks8995.c
> +++ /dev/null
[ ... ]
> -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);
[Severity: High]
This isn't a bug introduced by this patch, but removing this driver exposes a
potential regression in the replacement driver.
The replacement generic Microchip ksz driver does not appear to configure
KS8995_GC0_P5_PHY (BIT 3 in REG 0x02) in its setup path for KSZ8995XA
switches. According to the datasheet, the hardware default for this bit
is 0 (disabled).
For hardware designs that rely on software to configure Port 5 into PHY mode
rather than hardware strapping, won't this missing initialization leave the PHY
disabled and break network connectivity for migrated users?
[ ... ]
> -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;
[ ... ]
> - 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;
> - }
[Severity: High]
This is a pre-existing issue in the replacement driver, but forcing users
off the old driver will expose them to it.
The old driver correctly overrides the 1522-byte limit to allow frames up
to 1536 bytes by setting KS8995_GC2_LEGAL (BIT 1). However,
ksz8863_change_mtu() in the ksz8.c driver appears to have the exact
opposite logic:
if (frame_size <= legalsz)
ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE;
For frame sizes between 1523 and 1536 bytes, this condition is false, leaving
ctrl2 = 0.
Doesn't this clear the bit and strictly enforce the 1522-byte limit, causing
the hardware to drop these packets instead of allowing them?
[ ... ]
> -static const struct spi_device_id ks8995_id[] = {
> - {"ks8995", ks8995},
> - {"ksz8864", ksz8864},
> - {"ksz8795", ksz8795},
> - { }
> -};
> -MODULE_DEVICE_TABLE(spi, ks8995_id);
[Severity: High]
This isn't a bug introduced by this patch, but migrating non-DeviceTree
setups to the ksz_spi replacement driver will result in probe failures.
The deleted driver successfully supported non-DT SPI instantiation because
it read the variant directly via spi_get_device_id(spi)->driver_data.
The replacement ksz_spi driver relies on device_get_match_data(), which
requires the spi_device_id table to populate the .driver_data field for
fallback matching on non-DT systems. Since ksz_spi_ids only provides string
names (e.g., { "ks8995" }) with a default 0/NULL driver_data,
device_get_match_data() will return NULL and the probe will immediately
abort with -EINVAL.
Will this stranding of non-DT users require an update to ksz_spi_ids in the
replacement driver before deleting this one?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-ks8995-to-ksz8-v3-0-c530f651989f@kernel.org?part=5
^ permalink raw reply [flat|nested] 10+ messages in thread