* [PATCH net-next v2 0/6] r8169: add support for phylink
@ 2026-06-11 9:43 javen
2026-06-11 9:43 ` [PATCH net-next v2 1/6] r8169: add speed in private struct javen
` (5 more replies)
0 siblings, 6 replies; 17+ messages in thread
From: javen @ 2026-06-11 9:43 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, maxime.chevallier, horms
Cc: netdev, linux-kernel, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
This series patch adds support for phylink. RTL8116af is a fiber mode
card, link status and speed can not be read from standard phy reg. So
we read link status and speed from serdes reg by pcs. We have not changed
the loading process of RTL8127atf.
Hi,
This series patch mainly add support for phylink framework and RTL8116af.
I have tried to remove tp->phydev as much as possible. But for realtek PCIe
nics, they are single chips. Some internal phy shared the same PHY id, but
the required PHY parameters differ depending on the specific MAC/Chip it is
integrated with. And we can not tell them part only using PHY registers. So
firmware here can not be moved to phy driver.
To improve code readability, I merged part of the code which use phylink
independently into rtl8169_init_phy.
And as Andrew reviewed, RTL8127atf should indeed be integrated with phylink.
However, I found that this was a patch submitted independently by Heiner,
link: https://lore.kernel.org/netdev/b012587a-2c38-4597-9af9-3ba723ba6cba@gmail.com/
So to limit the influence of this patch, I want to know if I can release a separate
patch to refactor it later?
Thanks,
BRs,
Javen
v1 link: https://lore.kernel.org/netdev/20260605103906.1445-1-javen_xu@realsil.com.cn/
Javen Xu (6):
r8169: add speed in private struct
r8169: create a virtual interrupt for linkchg
r8169: add support for phylink
r8169: add support for RTL8116af
r8169: add ltr support for RTL8116af
r8169: fix RTL8116af can not enter s0idle and c10
drivers/net/ethernet/realtek/Kconfig | 1 +
drivers/net/ethernet/realtek/r8169_main.c | 549 +++++++++++++++++-----
2 files changed, 438 insertions(+), 112 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH net-next v2 1/6] r8169: add speed in private struct
2026-06-11 9:43 [PATCH net-next v2 0/6] r8169: add support for phylink javen
@ 2026-06-11 9:43 ` javen
2026-06-11 9:43 ` [PATCH net-next v2 2/6] r8169: create a virtual interrupt for linkchg javen
` (4 subsequent siblings)
5 siblings, 0 replies; 17+ messages in thread
From: javen @ 2026-06-11 9:43 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, maxime.chevallier, horms
Cc: netdev, linux-kernel, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
This patch adds current_speed in private strcut in order to decouple
from phydev in the following patch supporting for phylink.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- repalce current_speed with speed
---
drivers/net/ethernet/realtek/r8169_main.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index ec4fc21fa21f..c60710f9bd21 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -750,6 +750,7 @@ struct rtl8169_private {
u32 irq_mask;
int irq;
struct clk *clk;
+ int speed;
struct {
DECLARE_BITMAP(flags, RTL_FLAG_MAX);
@@ -1673,16 +1674,14 @@ static void rtl8169_irq_mask_and_ack(struct rtl8169_private *tp)
rtl_pci_commit(tp);
}
-static void rtl_link_chg_patch(struct rtl8169_private *tp)
+static void rtl_link_chg_patch(struct rtl8169_private *tp, int speed)
{
- struct phy_device *phydev = tp->phydev;
-
if (tp->mac_version == RTL_GIGA_MAC_VER_34 ||
tp->mac_version == RTL_GIGA_MAC_VER_38) {
- if (phydev->speed == SPEED_1000) {
+ if (speed == SPEED_1000) {
rtl_eri_write(tp, 0x1bc, ERIAR_MASK_1111, 0x00000011);
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_1111, 0x00000005);
- } else if (phydev->speed == SPEED_100) {
+ } else if (speed == SPEED_100) {
rtl_eri_write(tp, 0x1bc, ERIAR_MASK_1111, 0x0000001f);
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_1111, 0x00000005);
} else {
@@ -1692,7 +1691,7 @@ static void rtl_link_chg_patch(struct rtl8169_private *tp)
rtl_reset_packet_filter(tp);
} else if (tp->mac_version == RTL_GIGA_MAC_VER_35 ||
tp->mac_version == RTL_GIGA_MAC_VER_36) {
- if (phydev->speed == SPEED_1000) {
+ if (speed == SPEED_1000) {
rtl_eri_write(tp, 0x1bc, ERIAR_MASK_1111, 0x00000011);
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_1111, 0x00000005);
} else {
@@ -1700,7 +1699,7 @@ static void rtl_link_chg_patch(struct rtl8169_private *tp)
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_1111, 0x0000003f);
}
} else if (tp->mac_version == RTL_GIGA_MAC_VER_37) {
- if (phydev->speed == SPEED_10) {
+ if (speed == SPEED_10) {
rtl_eri_write(tp, 0x1d0, ERIAR_MASK_0011, 0x4d02);
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_0011, 0x0060a);
} else {
@@ -2074,11 +2073,11 @@ rtl_coalesce_info(struct rtl8169_private *tp)
ci = rtl_coalesce_info_8168_8136;
/* if speed is unknown assume highest one */
- if (tp->phydev->speed == SPEED_UNKNOWN)
+ if (tp->speed == SPEED_UNKNOWN)
return ci;
for (; ci->speed; ci++) {
- if (tp->phydev->speed == ci->speed)
+ if (tp->speed == ci->speed)
return ci;
}
@@ -2236,7 +2235,7 @@ static void rtl_set_eee_txidle_timer(struct rtl8169_private *tp)
static unsigned int r8169_get_tx_lpi_timer_us(struct rtl8169_private *tp)
{
- unsigned int speed = tp->phydev->speed;
+ unsigned int speed = tp->speed;
unsigned int timer = tp->tx_lpi_timer;
if (!timer || speed == SPEED_UNKNOWN)
@@ -4968,8 +4967,9 @@ static void r8169_phylink_handler(struct net_device *ndev)
struct rtl8169_private *tp = netdev_priv(ndev);
struct device *d = tp_to_dev(tp);
+ tp->speed = tp->phydev->speed;
if (netif_carrier_ok(ndev)) {
- rtl_link_chg_patch(tp);
+ rtl_link_chg_patch(tp, tp->speed);
rtl_enable_tx_lpi(tp, tp->phydev->enable_tx_lpi);
pm_request_resume(d);
} else {
@@ -5667,6 +5667,7 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
ext_xid_str, xid);
tp->mac_version = chip->mac_version;
tp->fw_name = chip->fw_name;
+ tp->speed = SPEED_UNKNOWN;
/* Disable ASPM L1 as that cause random device stop working
* problems as well as full system hangs for some PCIe devices users.
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v2 2/6] r8169: create a virtual interrupt for linkchg
2026-06-11 9:43 [PATCH net-next v2 0/6] r8169: add support for phylink javen
2026-06-11 9:43 ` [PATCH net-next v2 1/6] r8169: add speed in private struct javen
@ 2026-06-11 9:43 ` javen
2026-06-13 22:05 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2026-06-11 9:43 ` [PATCH net-next v2 3/6] r8169: add support for phylink javen
` (3 subsequent siblings)
5 siblings, 2 replies; 17+ messages in thread
From: javen @ 2026-06-11 9:43 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, maxime.chevallier, horms
Cc: netdev, linux-kernel, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
Create a virtual interrupt for linkchg. To support phylink, we should
try to decouple most of tp->phydev, so we add virtual interrupt for mac
interrupt to inform the change of link status.
generic_handle_domain_irq() will help us to call phylib.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- new file
---
drivers/net/ethernet/realtek/r8169_main.c | 51 +++++++++++++++++++++--
1 file changed, 48 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index c60710f9bd21..560f987437b6 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -21,6 +21,8 @@
#include <linux/in.h>
#include <linux/io.h>
#include <linux/ip.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
#include <linux/tcp.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
@@ -775,6 +777,7 @@ struct rtl8169_private {
struct r8169_led_classdev *leds;
u32 ocp_base;
+ struct irq_domain *phy_irq_domain;
};
typedef void (*rtl_generic_fct)(struct rtl8169_private *tp);
@@ -4869,7 +4872,7 @@ static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
}
if (status & LinkChg)
- phy_mac_interrupt(tp->phydev);
+ generic_handle_domain_irq(tp->phy_irq_domain, 0);
rtl_irq_disable(tp);
napi_schedule(&tp->napi);
@@ -5423,11 +5426,39 @@ static int r8169_mdio_write_reg_c45(struct mii_bus *mii_bus, int addr,
return 0;
}
+static int rtl_phy_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq);
+ irq_set_chip_data(irq, d->host_data);
+
+ return 0;
+}
+
+static const struct irq_domain_ops rtl_phy_irq_domain_ops = {
+ .map = rtl_phy_irq_map,
+};
+
+static void rtl_phy_irq_cleanup(void *data)
+{
+ struct rtl8169_private *tp = data;
+ int virq;
+
+ if (tp->phy_irq_domain) {
+ virq = irq_find_mapping(tp->phy_irq_domain, 0);
+ if (virq)
+ irq_dispose_mapping(virq);
+
+ irq_domain_remove(tp->phy_irq_domain);
+ tp->phy_irq_domain = NULL;
+ }
+}
+
static int r8169_mdio_register(struct rtl8169_private *tp)
{
struct pci_dev *pdev = tp->pci_dev;
struct mii_bus *new_bus;
- int ret;
+ int ret, virq;
/* On some boards with this chip version the BIOS is buggy and misses
* to reset the PHY page selector. This results in the PHY ID read
@@ -5445,7 +5476,6 @@ static int r8169_mdio_register(struct rtl8169_private *tp)
new_bus->name = "r8169";
new_bus->priv = tp;
new_bus->parent = &pdev->dev;
- new_bus->irq[0] = PHY_MAC_INTERRUPT;
new_bus->phy_mask = GENMASK(31, 1);
snprintf(new_bus->id, MII_BUS_ID_SIZE, "r8169-%x-%x",
pci_domain_nr(pdev->bus), pci_dev_id(pdev));
@@ -5458,6 +5488,21 @@ static int r8169_mdio_register(struct rtl8169_private *tp)
new_bus->write_c45 = r8169_mdio_write_reg_c45;
}
+ tp->phy_irq_domain = irq_domain_add_linear(NULL, 1,
+ &rtl_phy_irq_domain_ops, tp);
+ if (!tp->phy_irq_domain)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&pdev->dev, rtl_phy_irq_cleanup, tp);
+ if (ret)
+ return ret;
+
+ virq = irq_create_mapping(tp->phy_irq_domain, 0);
+ if (!virq)
+ ret = -EINVAL;
+
+ new_bus->irq[0] = virq;
+
ret = devm_mdiobus_register(&pdev->dev, new_bus);
if (ret)
return ret;
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v2 3/6] r8169: add support for phylink
2026-06-11 9:43 [PATCH net-next v2 0/6] r8169: add support for phylink javen
2026-06-11 9:43 ` [PATCH net-next v2 1/6] r8169: add speed in private struct javen
2026-06-11 9:43 ` [PATCH net-next v2 2/6] r8169: create a virtual interrupt for linkchg javen
@ 2026-06-11 9:43 ` javen
2026-06-12 8:13 ` Maxime Chevallier
` (2 more replies)
2026-06-11 9:43 ` [PATCH net-next v2 4/6] r8169: add support for RTL8116af javen
` (2 subsequent siblings)
5 siblings, 3 replies; 17+ messages in thread
From: javen @ 2026-06-11 9:43 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, maxime.chevallier, horms
Cc: netdev, linux-kernel, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
Transfer old framework to phylink. Phylink can support fiber mode card
which can not get link status or link speed from standard phy registers.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- merge patch v1 3/6 and v1 4/6.
- add helper rtl_mac_enable_tx_lpi(), rtl_mac_disable_tx_lpi()
and rtl8169_get_lpi_caps()
---
drivers/net/ethernet/realtek/Kconfig | 1 +
drivers/net/ethernet/realtek/r8169_main.c | 249 ++++++++++++++++------
2 files changed, 181 insertions(+), 69 deletions(-)
diff --git a/drivers/net/ethernet/realtek/Kconfig b/drivers/net/ethernet/realtek/Kconfig
index 9b0f4f9631db..49ac72734225 100644
--- a/drivers/net/ethernet/realtek/Kconfig
+++ b/drivers/net/ethernet/realtek/Kconfig
@@ -88,6 +88,7 @@ config R8169
select CRC32
select PHYLIB
select REALTEK_PHY
+ select PHYLINK
help
Say Y here if you have a Realtek Ethernet adapter belonging to
the following families:
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 560f987437b6..615bd4107359 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -28,6 +28,7 @@
#include <linux/dma-mapping.h>
#include <linux/pm_runtime.h>
#include <linux/bitfield.h>
+#include <linux/phylink.h>
#include <linux/prefetch.h>
#include <linux/ipv6.h>
#include <linux/unaligned.h>
@@ -777,6 +778,8 @@ struct rtl8169_private {
struct r8169_led_classdev *leds;
u32 ocp_base;
+ struct phylink *phylink;
+ struct phylink_config phylink_config;
struct irq_domain *phy_irq_domain;
};
@@ -2256,7 +2259,7 @@ static int rtl8169_get_eee(struct net_device *dev, struct ethtool_keee *data)
if (!rtl_supports_eee(tp))
return -EOPNOTSUPP;
- ret = phy_ethtool_get_eee(tp->phydev, data);
+ ret = phylink_ethtool_get_eee(tp->phylink, data);
if (ret)
return ret;
@@ -2272,7 +2275,7 @@ static int rtl8169_set_eee(struct net_device *dev, struct ethtool_keee *data)
if (!rtl_supports_eee(tp))
return -EOPNOTSUPP;
- return phy_ethtool_set_eee(tp->phydev, data);
+ return phylink_ethtool_set_eee(tp->phylink, data);
}
static void rtl8169_get_ringparam(struct net_device *dev,
@@ -2303,13 +2306,8 @@ static void rtl8169_get_pauseparam(struct net_device *dev,
struct ethtool_pauseparam *data)
{
struct rtl8169_private *tp = netdev_priv(dev);
- bool tx_pause, rx_pause;
- phy_get_pause(tp->phydev, &tx_pause, &rx_pause);
-
- data->autoneg = tp->phydev->autoneg;
- data->tx_pause = tx_pause ? 1 : 0;
- data->rx_pause = rx_pause ? 1 : 0;
+ phylink_ethtool_get_pauseparam(tp->phylink, data);
}
static int rtl8169_set_pauseparam(struct net_device *dev,
@@ -2320,9 +2318,7 @@ static int rtl8169_set_pauseparam(struct net_device *dev,
if (dev->mtu > ETH_DATA_LEN)
return -EOPNOTSUPP;
- phy_set_asym_pause(tp->phydev, data->rx_pause, data->tx_pause);
-
- return 0;
+ return phylink_ethtool_set_pauseparam(tp->phylink, data);
}
static void rtl8169_get_eth_mac_stats(struct net_device *dev,
@@ -2388,6 +2384,14 @@ static void rtl8169_get_eth_ctrl_stats(struct net_device *dev,
le32_to_cpu(tp->counters->rx_unknown_opcode);
}
+static int rtl8169_get_link_ksettings(struct net_device *ndev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct rtl8169_private *tp = netdev_priv(ndev);
+
+ return phylink_ethtool_ksettings_get(tp->phylink, cmd);
+}
+
static int rtl8169_set_link_ksettings(struct net_device *ndev,
const struct ethtool_link_ksettings *cmd)
{
@@ -2418,6 +2422,13 @@ static int rtl8169_set_link_ksettings(struct net_device *ndev,
return 0;
}
+static int rtl8169_nway_reset(struct net_device *dev)
+{
+ struct rtl8169_private *tp = netdev_priv(dev);
+
+ return phylink_ethtool_nway_reset(tp->phylink);
+}
+
static const struct ethtool_ops rtl8169_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES,
@@ -2433,10 +2444,10 @@ static const struct ethtool_ops rtl8169_ethtool_ops = {
.get_sset_count = rtl8169_get_sset_count,
.get_ethtool_stats = rtl8169_get_ethtool_stats,
.get_ts_info = ethtool_op_get_ts_info,
- .nway_reset = phy_ethtool_nway_reset,
+ .nway_reset = rtl8169_nway_reset,
.get_eee = rtl8169_get_eee,
.set_eee = rtl8169_set_eee,
- .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .get_link_ksettings = rtl8169_get_link_ksettings,
.set_link_ksettings = rtl8169_set_link_ksettings,
.get_ringparam = rtl8169_get_ringparam,
.get_pause_stats = rtl8169_get_pause_stats,
@@ -2661,13 +2672,10 @@ static void rtl_jumbo_config(struct rtl8169_private *tp)
pcie_set_readrq(tp->pci_dev, readrq);
/* Chip doesn't support pause in jumbo mode */
- if (jumbo) {
- linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT,
- tp->phydev->advertising);
- linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
- tp->phydev->advertising);
- phy_start_aneg(tp->phydev);
- }
+ if (jumbo)
+ tp->phylink_config.mac_capabilities &= ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
+ else
+ tp->phylink_config.mac_capabilities |= (MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
}
DECLARE_RTL_COND(rtl_chipcmd_cond)
@@ -2782,7 +2790,7 @@ static void rtl_prepare_power_down(struct rtl8169_private *tp)
rtl_ephy_write(tp, 0x19, 0xff64);
if (device_may_wakeup(tp_to_dev(tp))) {
- phy_speed_down(tp->phydev, false);
+ phylink_speed_down(tp->phylink, false);
rtl_wol_enable_rx(tp);
}
}
@@ -4142,11 +4150,17 @@ static int rtl8169_change_mtu(struct net_device *dev, int new_mtu)
{
struct rtl8169_private *tp = netdev_priv(dev);
+ if (netif_running(dev))
+ phylink_stop(tp->phylink);
+
WRITE_ONCE(dev->mtu, new_mtu);
netdev_update_features(dev);
rtl_jumbo_config(tp);
rtl_set_eee_txidle_timer(tp);
+ if (netif_running(dev))
+ phylink_start(tp->phylink);
+
return 0;
}
@@ -4932,9 +4946,6 @@ static int rtl8169_poll(struct napi_struct *napi, int budget)
static void rtl_enable_tx_lpi(struct rtl8169_private *tp, bool enable)
{
- if (!rtl_supports_eee(tp))
- return;
-
switch (tp->mac_version) {
case RTL_GIGA_MAC_VER_34 ... RTL_GIGA_MAC_VER_52:
/* Adjust EEE LED frequency */
@@ -4965,41 +4976,15 @@ static void rtl_enable_tx_lpi(struct rtl8169_private *tp, bool enable)
}
}
-static void r8169_phylink_handler(struct net_device *ndev)
-{
- struct rtl8169_private *tp = netdev_priv(ndev);
- struct device *d = tp_to_dev(tp);
-
- tp->speed = tp->phydev->speed;
- if (netif_carrier_ok(ndev)) {
- rtl_link_chg_patch(tp, tp->speed);
- rtl_enable_tx_lpi(tp, tp->phydev->enable_tx_lpi);
- pm_request_resume(d);
- } else {
- pm_runtime_idle(d);
- }
-
- phy_print_status(tp->phydev);
-}
-
static int r8169_phy_connect(struct rtl8169_private *tp)
{
- struct phy_device *phydev = tp->phydev;
- phy_interface_t phy_mode;
int ret;
- phy_mode = tp->supports_gmii ? PHY_INTERFACE_MODE_GMII :
- PHY_INTERFACE_MODE_MII;
-
- ret = phy_connect_direct(tp->dev, phydev, r8169_phylink_handler,
- phy_mode);
- if (ret)
+ ret = phylink_connect_phy(tp->phylink, tp->phydev);
+ if (ret) {
+ netdev_err(tp->dev, "failed to connect phy\n");
return ret;
-
- if (!tp->supports_gmii)
- phy_set_max_speed(phydev, SPEED_100);
-
- phy_attached_info(phydev);
+ }
return 0;
}
@@ -5010,7 +4995,7 @@ static void rtl8169_down(struct rtl8169_private *tp)
/* Clear all task flags */
bitmap_zero(tp->wk.flags, RTL_FLAG_MAX);
- phy_stop(tp->phydev);
+ phylink_stop(tp->phylink);
/* Reset SerDes PHY to bring down fiber link */
if (tp->sfp_mode)
@@ -5042,7 +5027,7 @@ static void rtl8169_up(struct rtl8169_private *tp)
enable_work(&tp->wk.work);
rtl_reset_work(tp);
- phy_start(tp->phydev);
+ phylink_start(tp->phylink);
}
static int rtl8169_close(struct net_device *dev)
@@ -5058,7 +5043,7 @@ static int rtl8169_close(struct net_device *dev)
free_irq(tp->irq, tp);
- phy_disconnect(tp->phydev);
+ phylink_disconnect_phy(tp->phylink);
dma_free_coherent(&pdev->dev, R8169_RX_RING_BYTES, tp->RxDescArray,
tp->RxPhyAddr);
@@ -5291,6 +5276,8 @@ static void rtl_remove_one(struct pci_dev *pdev)
r8169_remove_leds(tp->leds);
unregister_netdev(tp->dev);
+ if (tp->phylink)
+ phylink_destroy(tp->phylink);
if (tp->dash_type != RTL_DASH_NONE)
rtl8168_driver_stop(tp);
@@ -5519,16 +5506,6 @@ static int r8169_mdio_register(struct rtl8169_private *tp)
return -EUNATCH;
}
- tp->phydev->mac_managed_pm = true;
- if (rtl_supports_eee(tp))
- phy_support_eee(tp->phydev);
- phy_support_asym_pause(tp->phydev);
-
- /* mimic behavior of r8125/r8126 vendor drivers */
- if (tp->mac_version == RTL_GIGA_MAC_VER_61)
- phy_disable_eee_mode(tp->phydev,
- ETHTOOL_LINK_MODE_2500baseT_Full_BIT);
-
/* PHY will be woken up in rtl_open() */
phy_suspend(tp->phydev);
@@ -5644,6 +5621,132 @@ static bool rtl_aspm_is_safe(struct rtl8169_private *tp)
return false;
}
+static void rtl_mac_link_down(struct phylink_config *config, unsigned int mode,
+ phy_interface_t interface)
+{
+ struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
+
+ tp->speed = SPEED_UNKNOWN;
+ pm_runtime_idle(tp_to_dev(tp));
+}
+
+static void rtl_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 rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
+
+ struct device *d = tp_to_dev(tp);
+
+ tp->speed = speed;
+ rtl_link_chg_patch(tp, speed);
+
+ pm_request_resume(d);
+}
+
+static struct phylink_pcs *rtl_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ return NULL;
+}
+
+static void rtl_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+}
+
+static void rtl_mac_disable_tx_lpi(struct phylink_config *config)
+{
+ struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
+
+ rtl_enable_tx_lpi(tp, false);
+}
+
+static int rtl_mac_enable_tx_lpi(struct phylink_config *config, u32 timer, bool tx_clk_stop)
+{
+ struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
+
+ if (!rtl_supports_eee(tp))
+ return -EOPNOTSUPP;
+
+ rtl_enable_tx_lpi(tp, true);
+
+ return 0;
+}
+
+static const struct phylink_mac_ops rtl_phylink_mac_ops = {
+ .mac_select_pcs = rtl_mac_select_pcs,
+ .mac_config = rtl_mac_config,
+ .mac_link_down = rtl_mac_link_down,
+ .mac_link_up = rtl_mac_link_up,
+ .mac_disable_tx_lpi = rtl_mac_disable_tx_lpi,
+ .mac_enable_tx_lpi = rtl_mac_enable_tx_lpi,
+};
+
+static unsigned long rtl8169_get_lpi_caps(struct rtl8169_private *tp)
+{
+ unsigned long caps = 0;
+
+ if (!rtl_supports_eee(tp))
+ return 0;
+
+ caps |= MAC_100FD | MAC_1000FD;
+
+ /* mimic behavior of r8125/r8126 vendor drivers
+ * RTL_GIGA_MAC_VER_61 doesn't support 2.5G eee
+ */
+ if (tp->mac_version >= RTL_GIGA_MAC_VER_63)
+ caps |= MAC_2500FD;
+ if (tp->mac_version >= RTL_GIGA_MAC_VER_70)
+ caps |= MAC_5000FD;
+ if (tp->mac_version == RTL_GIGA_MAC_VER_80)
+ caps |= MAC_10000FD;
+
+ return caps;
+}
+
+static int rtl_init_phylink(struct rtl8169_private *tp)
+{
+ struct phylink *pl;
+ phy_interface_t phy_mode;
+
+ tp->phylink_config.dev = &tp->dev->dev;
+ tp->phylink_config.type = PHYLINK_NETDEV;
+ tp->phylink_config.mac_managed_pm = true;
+ tp->phylink_config.lpi_capabilities = rtl8169_get_lpi_caps(tp);
+ tp->phylink_config.mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE;
+
+ if (tp->sfp_mode) {
+ phy_mode = PHY_INTERFACE_MODE_INTERNAL;
+ tp->phylink_config.mac_capabilities |= MAC_10000FD;
+ } else {
+ tp->phylink_config.mac_capabilities |= MAC_10 | MAC_100;
+ phy_mode = PHY_INTERFACE_MODE_INTERNAL;
+
+ if (tp->mac_version == RTL_GIGA_MAC_VER_80)
+ tp->phylink_config.mac_capabilities |= MAC_1000FD | MAC_2500FD |
+ MAC_5000FD | MAC_10000FD;
+ else if (tp->mac_version == RTL_GIGA_MAC_VER_70)
+ tp->phylink_config.mac_capabilities |= MAC_1000FD |
+ MAC_2500FD | MAC_5000FD;
+ else if (tp->mac_version >= RTL_GIGA_MAC_VER_61)
+ tp->phylink_config.mac_capabilities |= MAC_1000FD | MAC_2500FD;
+ else
+ if (tp->supports_gmii)
+ tp->phylink_config.mac_capabilities |= MAC_1000FD;
+ }
+
+ __set_bit(phy_mode, tp->phylink_config.supported_interfaces);
+ pl = phylink_create(&tp->phylink_config, tp_to_dev(tp)->fwnode,
+ phy_mode, &rtl_phylink_mac_ops);
+ if (IS_ERR(pl))
+ return PTR_ERR(pl);
+
+ tp->phylink = pl;
+
+ return 0;
+}
+
static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
const struct rtl_chip_info *chip;
@@ -5834,13 +5937,21 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
pci_set_drvdata(pdev, tp);
- rc = r8169_mdio_register(tp);
+ rc = rtl_init_phylink(tp);
if (rc)
return rc;
+ rc = r8169_mdio_register(tp);
+ if (rc) {
+ phylink_destroy(tp->phylink);
+ return rc;
+ }
+
rc = register_netdev(dev);
- if (rc)
+ if (rc) {
+ phylink_destroy(tp->phylink);
return rc;
+ }
if (IS_ENABLED(CONFIG_R8169_LEDS)) {
if (rtl_is_8125(tp))
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v2 4/6] r8169: add support for RTL8116af
2026-06-11 9:43 [PATCH net-next v2 0/6] r8169: add support for phylink javen
` (2 preceding siblings ...)
2026-06-11 9:43 ` [PATCH net-next v2 3/6] r8169: add support for phylink javen
@ 2026-06-11 9:43 ` javen
2026-06-13 22:06 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2026-06-11 9:43 ` [PATCH net-next v2 5/6] r8169: add ltr " javen
2026-06-11 9:43 ` [PATCH net-next v2 6/6] r8169: fix RTL8116af can not enter s0idle and c10 javen
5 siblings, 2 replies; 17+ messages in thread
From: javen @ 2026-06-11 9:43 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, maxime.chevallier, horms
Cc: netdev, linux-kernel, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
RTL8116af is sfp mode. Phylink uses pcs to get the link status and speed
from its serdes reg, instead of standard phy reg. Also, RTL8116af doesn't
have internal phy, so we add some checks to ensure that tp->phydev is
not empty when we need it.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- replace some magic numbers with macro
---
drivers/net/ethernet/realtek/r8169_main.c | 180 ++++++++++++++++++----
1 file changed, 148 insertions(+), 32 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 615bd4107359..a73c0215b240 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -99,6 +99,18 @@
#define JUMBO_9K (9 * SZ_1K - VLAN_ETH_HLEN - ETH_FCS_LEN)
#define JUMBO_16K (SZ_16K - VLAN_ETH_HLEN - ETH_FCS_LEN)
+#define OCP_SDS_ADDR_REG 0xEB10
+#define OCP_SDS_CMD_REG 0xEB0E
+#define OCP_SDS_DATA_REG 0xEB14
+#define SDS_CMD_READ 0x0001
+#define RTL_SDS_C22_BASE 0x40
+#define RTL_PKG_DETECT 0xdc00
+#define RTL_PKG_DETECT_MASK 0x0078
+#define RTL_PKG_DETECT_8116AF 0x0030
+#define RTL_INT_HW_ID 0xd006
+#define RTL_INT_HW_ID_MASK 0x00ff
+#define RTL_INT_HW_ID_8116AF 0x0000
+
static const struct rtl_chip_info {
u32 mask;
u32 val;
@@ -731,6 +743,12 @@ enum rtl_dash_type {
RTL_DASH_25_BP,
};
+enum rtl_sfp_mode {
+ RTL_SFP_NONE,
+ RTL_SFP_8168_AF,
+ RTL_SFP_8127_ATF,
+};
+
struct rtl8169_private {
void __iomem *mmio_addr; /* memory map physical address */
struct pci_dev *pci_dev;
@@ -739,6 +757,7 @@ struct rtl8169_private {
struct napi_struct napi;
enum mac_version mac_version;
enum rtl_dash_type dash_type;
+ enum rtl_sfp_mode sfp_mode;
u32 cur_rx; /* Index into the Rx descriptor buffer of next Rx pkt. */
u32 cur_tx; /* Index into the Tx descriptor buffer of next Rx pkt. */
u32 dirty_tx;
@@ -766,7 +785,6 @@ struct rtl8169_private {
unsigned supports_gmii:1;
unsigned aspm_manageable:1;
unsigned dash_enabled:1;
- bool sfp_mode:1;
dma_addr_t counters_phys_addr;
struct rtl8169_counters *counters;
struct rtl8169_tc_offsets tc_offset;
@@ -780,6 +798,7 @@ struct rtl8169_private {
u32 ocp_base;
struct phylink *phylink;
struct phylink_config phylink_config;
+ struct phylink_pcs pcs;
struct irq_domain *phy_irq_domain;
};
@@ -1136,7 +1155,7 @@ static int r8168_phy_ocp_read(struct rtl8169_private *tp, u32 reg)
return 0;
/* Return dummy MII_PHYSID2 in SFP mode to match SFP PHY driver */
- if (tp->sfp_mode && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
+ if (tp->sfp_mode == RTL_SFP_8127_ATF && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
return PHY_ID_RTL_DUMMY_SFP & 0xffff;
RTL_W32(tp, GPHY_OCP, reg << 15);
@@ -1290,6 +1309,15 @@ static void mac_mcu_write(struct rtl8169_private *tp, int reg, int value)
r8168_mac_ocp_write(tp, tp->ocp_base + reg, value);
}
+static bool rtl_is_8116af(struct rtl8169_private *tp)
+{
+ return tp->mac_version == RTL_GIGA_MAC_VER_52 &&
+ (r8168_mac_ocp_read(tp, RTL_PKG_DETECT) & RTL_PKG_DETECT_MASK) ==
+ RTL_PKG_DETECT_8116AF &&
+ (r8168_mac_ocp_read(tp, RTL_INT_HW_ID) & RTL_INT_HW_ID_MASK) ==
+ RTL_INT_HW_ID_8116AF;
+}
+
static int mac_mcu_read(struct rtl8169_private *tp, int reg)
{
return r8168_mac_ocp_read(tp, tp->ocp_base + reg);
@@ -1585,6 +1613,20 @@ static bool rtl_dash_is_enabled(struct rtl8169_private *tp)
}
}
+static enum rtl_sfp_mode rtl_get_sfp_mode(struct rtl8169_private *tp)
+{
+ if (rtl_is_8125(tp)) {
+ u16 data = r8168_mac_ocp_read(tp, RTL_INT_HW_ID);
+
+ if ((data & 0xff) == 0x07)
+ return RTL_SFP_8127_ATF;
+ } else if (rtl_is_8116af(tp)) {
+ return RTL_SFP_8168_AF;
+ }
+
+ return RTL_SFP_NONE;
+}
+
static enum rtl_dash_type rtl_get_dash_type(struct rtl8169_private *tp)
{
switch (tp->mac_version) {
@@ -2400,8 +2442,8 @@ static int rtl8169_set_link_ksettings(struct net_device *ndev,
int duplex = cmd->base.duplex;
int speed = cmd->base.speed;
- if (!tp->sfp_mode)
- return phy_ethtool_ksettings_set(phydev, cmd);
+ if (tp->sfp_mode != RTL_SFP_8127_ATF)
+ return phylink_ethtool_ksettings_set(tp->phylink, cmd);
if (cmd->base.autoneg != AUTONEG_DISABLE)
return -EINVAL;
@@ -2512,9 +2554,10 @@ void r8169_apply_firmware(struct rtl8169_private *tp)
tp->ocp_base = OCP_STD_PHY_BASE;
/* PHY soft reset may still be in progress */
- phy_read_poll_timeout(tp->phydev, MII_BMCR, val,
- !(val & BMCR_RESET),
- 50000, 600000, true);
+ if (tp->phydev)
+ phy_read_poll_timeout(tp->phydev, MII_BMCR, val,
+ !(val & BMCR_RESET),
+ 50000, 600000, true);
}
}
@@ -2551,6 +2594,8 @@ static void rtl_schedule_task(struct rtl8169_private *tp, enum rtl_flag flag)
static void rtl8169_init_phy(struct rtl8169_private *tp)
{
+ phy_init_hw(tp->phydev);
+ phy_resume(tp->phydev);
r8169_hw_phy_config(tp, tp->phydev, tp->mac_version);
if (tp->mac_version <= RTL_GIGA_MAC_VER_06) {
@@ -2565,7 +2610,7 @@ static void rtl8169_init_phy(struct rtl8169_private *tp)
tp->pci_dev->subsystem_device == 0xe000)
phy_write_paged(tp->phydev, 0x0001, 0x10, 0xf01b);
- if (tp->sfp_mode)
+ if (tp->sfp_mode == RTL_SFP_8127_ATF)
rtl_sfp_init(tp);
/* We may have called phy_speed_down before */
@@ -3703,12 +3748,14 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
rtl_pcie_state_l2l3_disable(tp);
- rg_saw_cnt = phy_read_paged(tp->phydev, 0x0c42, 0x13) & 0x3fff;
- if (rg_saw_cnt > 0) {
- u16 sw_cnt_1ms_ini;
+ if (tp->phydev) {
+ rg_saw_cnt = phy_read_paged(tp->phydev, 0x0c42, 0x13) & 0x3fff;
+ if (rg_saw_cnt > 0) {
+ u16 sw_cnt_1ms_ini;
- sw_cnt_1ms_ini = (16000000 / rg_saw_cnt) & 0x0fff;
- r8168_mac_ocp_modify(tp, 0xd412, 0x0fff, sw_cnt_1ms_ini);
+ sw_cnt_1ms_ini = (16000000 / rg_saw_cnt) & 0x0fff;
+ r8168_mac_ocp_modify(tp, 0xd412, 0x0fff, sw_cnt_1ms_ini);
+ }
}
r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
@@ -4998,7 +5045,7 @@ static void rtl8169_down(struct rtl8169_private *tp)
phylink_stop(tp->phylink);
/* Reset SerDes PHY to bring down fiber link */
- if (tp->sfp_mode)
+ if (tp->sfp_mode == RTL_SFP_8127_ATF)
rtl_sfp_reset(tp);
rtl8169_update_counters(tp);
@@ -5020,9 +5067,9 @@ static void rtl8169_up(struct rtl8169_private *tp)
rtl8168_driver_start(tp);
pci_set_master(tp->pci_dev);
- phy_init_hw(tp->phydev);
- phy_resume(tp->phydev);
- rtl8169_init_phy(tp);
+ if (tp->phydev)
+ rtl8169_init_phy(tp);
+
napi_enable(&tp->napi);
enable_work(&tp->wk.work);
rtl_reset_work(tp);
@@ -5100,9 +5147,11 @@ static int rtl_open(struct net_device *dev)
if (retval < 0)
goto err_release_fw_2;
- retval = r8169_phy_connect(tp);
- if (retval)
- goto err_free_irq;
+ if (tp->phydev) {
+ retval = r8169_phy_connect(tp);
+ if (retval)
+ goto err_free_irq;
+ }
rtl8169_up(tp);
rtl8169_init_counter_offsets(tp);
@@ -5647,6 +5696,10 @@ static void rtl_mac_link_up(struct phylink_config *config, struct phy_device *ph
static struct phylink_pcs *rtl_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
+ struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
+
+ if (interface == PHY_INTERFACE_MODE_1000BASEX || interface == PHY_INTERFACE_MODE_SGMII)
+ return &tp->pcs;
return NULL;
}
@@ -5655,6 +5708,55 @@ static void rtl_mac_config(struct phylink_config *config, unsigned int mode,
{
}
+static u16 rtl8169_sds_read(struct rtl8169_private *tp, u16 sds_reg)
+{
+ r8168_mac_ocp_write(tp, OCP_SDS_ADDR_REG, sds_reg);
+ r8168_mac_ocp_write(tp, OCP_SDS_CMD_REG, SDS_CMD_READ);
+ return r8168_mac_ocp_read(tp, OCP_SDS_DATA_REG);
+}
+
+static void rtl8169_pcs_get_state(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct rtl8169_private *tp = container_of(pcs, struct rtl8169_private, pcs);
+ u16 bmsr, lpa;
+
+ bmsr = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_BMSR);
+ lpa = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_LPA);
+
+ state->link = !!(bmsr & BMSR_LSTATUS);
+ state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE);
+ if (state->link) {
+ state->speed = SPEED_1000;
+ state->duplex = DUPLEX_FULL;
+ } else {
+ state->speed = SPEED_UNKNOWN;
+ state->duplex = DUPLEX_UNKNOWN;
+ }
+
+ if (lpa & LPA_1000XPAUSE)
+ state->pause |= MLO_PAUSE_RX | MLO_PAUSE_TX;
+}
+
+static int rtl8169_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ return 0;
+}
+
+static int rtl8169_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+ const struct phylink_link_state *state)
+{
+ return 0;
+}
+
+static void rtl8169_pcs_an_restart(struct phylink_pcs *pcs)
+{
+}
+
static void rtl_mac_disable_tx_lpi(struct phylink_config *config)
{
struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
@@ -5705,6 +5807,13 @@ static unsigned long rtl8169_get_lpi_caps(struct rtl8169_private *tp)
return caps;
}
+static const struct phylink_pcs_ops r8169_pcs_ops = {
+ .pcs_validate = rtl8169_pcs_validate,
+ .pcs_get_state = rtl8169_pcs_get_state,
+ .pcs_config = rtl8169_pcs_config,
+ .pcs_an_restart = rtl8169_pcs_an_restart,
+};
+
static int rtl_init_phylink(struct rtl8169_private *tp)
{
struct phylink *pl;
@@ -5716,10 +5825,19 @@ static int rtl_init_phylink(struct rtl8169_private *tp)
tp->phylink_config.lpi_capabilities = rtl8169_get_lpi_caps(tp);
tp->phylink_config.mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE;
- if (tp->sfp_mode) {
+ switch (tp->sfp_mode) {
+ case RTL_SFP_8168_AF:
+ tp->pcs.ops = &r8169_pcs_ops;
+ tp->pcs.poll = true;
+ tp->phylink_config.default_an_inband = true;
+ phy_mode = PHY_INTERFACE_MODE_1000BASEX;
+ tp->phylink_config.mac_capabilities |= MAC_1000FD;
+ break;
+ case RTL_SFP_8127_ATF:
phy_mode = PHY_INTERFACE_MODE_INTERNAL;
tp->phylink_config.mac_capabilities |= MAC_10000FD;
- } else {
+ break;
+ default:
tp->phylink_config.mac_capabilities |= MAC_10 | MAC_100;
phy_mode = PHY_INTERFACE_MODE_INTERNAL;
@@ -5734,6 +5852,7 @@ static int rtl_init_phylink(struct rtl8169_private *tp)
else
if (tp->supports_gmii)
tp->phylink_config.mac_capabilities |= MAC_1000FD;
+ break;
}
__set_bit(phy_mode, tp->phylink_config.supported_interfaces);
@@ -5828,12 +5947,7 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
}
tp->aspm_manageable = !rc;
- if (rtl_is_8125(tp)) {
- u16 data = r8168_mac_ocp_read(tp, 0xd006);
-
- if ((data & 0xff) == 0x07)
- tp->sfp_mode = true;
- }
+ tp->sfp_mode = rtl_get_sfp_mode(tp);
tp->dash_type = rtl_get_dash_type(tp);
tp->dash_enabled = rtl_dash_is_enabled(tp);
@@ -5941,10 +6055,12 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (rc)
return rc;
- rc = r8169_mdio_register(tp);
- if (rc) {
- phylink_destroy(tp->phylink);
- return rc;
+ if (tp->sfp_mode != RTL_SFP_8168_AF) {
+ rc = r8169_mdio_register(tp);
+ if (rc) {
+ phylink_destroy(tp->phylink);
+ return rc;
+ }
}
rc = register_netdev(dev);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v2 5/6] r8169: add ltr support for RTL8116af
2026-06-11 9:43 [PATCH net-next v2 0/6] r8169: add support for phylink javen
` (3 preceding siblings ...)
2026-06-11 9:43 ` [PATCH net-next v2 4/6] r8169: add support for RTL8116af javen
@ 2026-06-11 9:43 ` javen
2026-06-13 22:06 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2026-06-11 9:43 ` [PATCH net-next v2 6/6] r8169: fix RTL8116af can not enter s0idle and c10 javen
5 siblings, 2 replies; 17+ messages in thread
From: javen @ 2026-06-11 9:43 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, maxime.chevallier, horms
Cc: netdev, linux-kernel, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
This patch adds ltr support for RTL8116af, enables RTL8116af enter l1.2
state. This makes sense for the system to enter c10 state.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- no changes
---
drivers/net/ethernet/realtek/r8169_main.c | 31 +++++++++++++++++++----
1 file changed, 26 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index a73c0215b240..a49e7a8d8f4d 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -349,11 +349,13 @@ enum rtl_registers {
ALDPS_LTR = 0xe0a2,
LTR_OBFF_LOCK = 0xe032,
LTR_SNOOP = 0xe034,
+ SEND_LTR_MSG = 0xe038,
#define ALDPS_LTR_EN BIT(0)
#define LTR_OBFF_LOCK_EN BIT(0)
#define LINK_SPEED_CHANGE_EN BIT(14)
#define LTR_SNOOP_EN GENMASK(15, 14)
+#define LTR_MSG_EN BIT(0)
};
enum rtl8168_8101_registers {
@@ -3153,8 +3155,22 @@ static void rtl_enable_ltr(struct rtl8169_private *tp)
r8168_mac_ocp_write(tp, 0xcdf2, 0x9003);
r8168_mac_ocp_modify(tp, LTR_OBFF_LOCK, 0x0000, LINK_SPEED_CHANGE_EN);
break;
- case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
case RTL_GIGA_MAC_VER_52:
+ r8168_mac_ocp_write(tp, 0xcdd0, 0x9003);
+ r8168_mac_ocp_modify(tp, LTR_SNOOP, 0x0000, LTR_SNOOP_EN);
+ r8168_mac_ocp_write(tp, 0xe02c, 0x1880);
+ r8168_mac_ocp_write(tp, 0xe02e, 0x4880);
+ r8168_mac_ocp_modify(tp, ALDPS_LTR, 0x0000, ALDPS_LTR_EN);
+ r8168_mac_ocp_write(tp, 0xcdd8, 0x9003);
+ r8168_mac_ocp_write(tp, 0xcdda, 0x9003);
+ r8168_mac_ocp_write(tp, 0xcddc, 0x9003);
+ r8168_mac_ocp_write(tp, 0xcdd2, 0x883c);
+ r8168_mac_ocp_write(tp, 0xcdd4, 0x8c12);
+ r8168_mac_ocp_write(tp, 0xcdd6, 0x9003);
+ r8168_mac_ocp_write(tp, 0xe0a6, 0x9003);
+ r8168_mac_ocp_write(tp, 0xe0a8, 0x9003);
+ break;
+ case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
r8168_mac_ocp_modify(tp, ALDPS_LTR, 0x0000, ALDPS_LTR_EN);
RTL_W8(tp, COMBO_LTR_EXTEND, RTL_R8(tp, COMBO_LTR_EXTEND) | COMBO_LTR_EXTEND_EN);
fallthrough;
@@ -3174,6 +3190,7 @@ static void rtl_enable_ltr(struct rtl8169_private *tp)
}
/* chip can trigger LTR */
r8168_mac_ocp_modify(tp, LTR_OBFF_LOCK, 0x0003, LTR_OBFF_LOCK_EN);
+ r8168_mac_ocp_modify(tp, SEND_LTR_MSG, 0x0000, LTR_MSG_EN);
}
static void rtl_hw_aspm_clkreq_enable(struct rtl8169_private *tp, bool enable)
@@ -3207,6 +3224,7 @@ static void rtl_hw_aspm_clkreq_enable(struct rtl8169_private *tp, bool enable)
rtl_enable_ltr(tp);
switch (tp->mac_version) {
case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
+ case RTL_GIGA_MAC_VER_52:
case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_LAST:
/* reset ephy tx/rx disable timer */
r8168_mac_ocp_modify(tp, 0xe094, 0xff00, 0);
@@ -3219,6 +3237,7 @@ static void rtl_hw_aspm_clkreq_enable(struct rtl8169_private *tp, bool enable)
} else {
switch (tp->mac_version) {
case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
+ case RTL_GIGA_MAC_VER_52:
case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_LAST:
r8168_mac_ocp_modify(tp, 0xe092, 0x00ff, 0);
break;
@@ -3732,7 +3751,9 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
rtl_eri_set_bits(tp, 0xd4, 0x0010);
- rtl_eri_write(tp, 0x5f0, ERIAR_MASK_0011, 0x4f87);
+ rtl_eri_write(tp, 0x5f0, ERIAR_MASK_0011, 0x4000);
+
+ r8168_mac_ocp_write(tp, 0xe098, 0xc302);
rtl_disable_rxdvgate(tp);
@@ -3759,9 +3780,9 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
}
r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
- r8168_mac_ocp_write(tp, 0xea80, 0x0003);
- r8168_mac_ocp_modify(tp, 0xe052, 0x0000, 0x0009);
- r8168_mac_ocp_modify(tp, 0xd420, 0x0fff, 0x047f);
+ r8168_mac_ocp_write(tp, 0xea80, 0x0000);
+ r8168_mac_ocp_modify(tp, 0xe052, 0x0009, 0x0000);
+ r8168_mac_ocp_modify(tp, 0xd420, 0x0fff, 0x045f);
r8168_mac_ocp_write(tp, 0xe63e, 0x0001);
r8168_mac_ocp_write(tp, 0xe63e, 0x0000);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v2 6/6] r8169: fix RTL8116af can not enter s0idle and c10
2026-06-11 9:43 [PATCH net-next v2 0/6] r8169: add support for phylink javen
` (4 preceding siblings ...)
2026-06-11 9:43 ` [PATCH net-next v2 5/6] r8169: add ltr " javen
@ 2026-06-11 9:43 ` javen
2026-06-13 22:06 ` Jakub Kicinski
5 siblings, 1 reply; 17+ messages in thread
From: javen @ 2026-06-11 9:43 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, maxime.chevallier, horms
Cc: netdev, linux-kernel, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
RTL8116AF is a multi-function device. Functions 2 to 7 are hidden from
the PCI core and return an all-ones response when their vendor ID is read,
so they are not enumerated as normal PCI functions.
However, these hidden functions can still affect platform power
management. If they are left in D0 or keep ASPM disabled, the platform may
fail to enter the low-power s0ix state and the CPU package may fail to
enter Package C10.
Put functions 2 to 7 into D3hot and enable ASPM on their PCIe link control
register. Since these functions are hidden, access their configuration
space through pci_bus_read_config_dword() / pci_bus_write_config_dword()
using the same slot and the target function numbers.
Ignore functions that return a PCI error response when reading their
configuration space.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- no changes
---
drivers/net/ethernet/realtek/r8169_main.c | 31 +++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index a49e7a8d8f4d..bc00cdc3add6 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -356,6 +356,9 @@ enum rtl_registers {
#define LINK_SPEED_CHANGE_EN BIT(14)
#define LTR_SNOOP_EN GENMASK(15, 14)
#define LTR_MSG_EN BIT(0)
+#define RTL8116AF_FUNC_PM_CSR 0x80
+#define RTL8116AF_FUNC_EXP_LNKCTL 0x44
+#define RTL_PM_D3HOT GENMASK(1, 0)
};
enum rtl8168_8101_registers {
@@ -3731,6 +3734,33 @@ static void rtl_hw_start_8168ep_3(struct rtl8169_private *tp)
r8168_mac_ocp_modify(tp, 0xe860, 0x0000, 0x0080);
}
+static void rtl_disable_hidden_function(struct pci_dev *pdev)
+{
+ unsigned int slot = PCI_SLOT(pdev->devfn);
+ struct pci_bus *bus = pdev->bus;
+ unsigned int devfn;
+ int func;
+ int ret;
+ u32 val;
+
+ for (func = 2; func < 8; func++) {
+ devfn = PCI_DEVFN(slot, func);
+
+ ret = pci_bus_read_config_dword(bus, devfn, RTL8116AF_FUNC_PM_CSR, &val);
+ if (!ret && !PCI_POSSIBLE_ERROR(val)) {
+ val &= ~(PCI_PM_CTRL_STATE_MASK | PCI_PM_CTRL_PME_ENABLE);
+ val |= (RTL_PM_D3HOT | PCI_PM_CTRL_PME_ENABLE);
+ pci_bus_write_config_dword(bus, devfn, RTL8116AF_FUNC_PM_CSR, val);
+ }
+
+ ret = pci_bus_read_config_dword(bus, devfn, RTL8116AF_FUNC_EXP_LNKCTL, &val);
+ if (!ret && !PCI_POSSIBLE_ERROR(val)) {
+ val |= PCI_EXP_LNKCTL_ASPMC;
+ pci_bus_write_config_dword(bus, devfn, RTL8116AF_FUNC_EXP_LNKCTL, val);
+ }
+ }
+}
+
static void rtl_hw_start_8117(struct rtl8169_private *tp)
{
static const struct ephy_info e_info_8117[] = {
@@ -3789,6 +3819,7 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
r8168_mac_ocp_write(tp, 0xc094, 0x0000);
r8168_mac_ocp_write(tp, 0xc09e, 0x0000);
+ rtl_disable_hidden_function(tp->pci_dev);
/* firmware is for MAC only */
r8169_apply_firmware(tp);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 3/6] r8169: add support for phylink
2026-06-11 9:43 ` [PATCH net-next v2 3/6] r8169: add support for phylink javen
@ 2026-06-12 8:13 ` Maxime Chevallier
2026-06-13 22:05 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2 siblings, 0 replies; 17+ messages in thread
From: Maxime Chevallier @ 2026-06-12 8:13 UTC (permalink / raw)
To: javen, hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel
Hi,
On 6/11/26 11:43, javen wrote:
> From: Javen Xu <javen_xu@realsil.com.cn>
>
> Transfer old framework to phylink. Phylink can support fiber mode card
> which can not get link status or link speed from standard phy registers.
>
> Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
> ---
> Changes in v2:
> - merge patch v1 3/6 and v1 4/6.
> - add helper rtl_mac_enable_tx_lpi(), rtl_mac_disable_tx_lpi()
> and rtl8169_get_lpi_caps()
> ---
> drivers/net/ethernet/realtek/Kconfig | 1 +
> drivers/net/ethernet/realtek/r8169_main.c | 249 ++++++++++++++++------
> 2 files changed, 181 insertions(+), 69 deletions(-)
>
> diff --git a/drivers/net/ethernet/realtek/Kconfig b/drivers/net/ethernet/realtek/Kconfig
> index 9b0f4f9631db..49ac72734225 100644
> --- a/drivers/net/ethernet/realtek/Kconfig
> +++ b/drivers/net/ethernet/realtek/Kconfig
> @@ -88,6 +88,7 @@ config R8169
> select CRC32
> select PHYLIB
> select REALTEK_PHY
> + select PHYLINK
> help
> Say Y here if you have a Realtek Ethernet adapter belonging to
> the following families:
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index 560f987437b6..615bd4107359 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
> @@ -28,6 +28,7 @@
> #include <linux/dma-mapping.h>
> #include <linux/pm_runtime.h>
> #include <linux/bitfield.h>
> +#include <linux/phylink.h>
> #include <linux/prefetch.h>
> #include <linux/ipv6.h>
> #include <linux/unaligned.h>
> @@ -777,6 +778,8 @@ struct rtl8169_private {
> struct r8169_led_classdev *leds;
>
> u32 ocp_base;
> + struct phylink *phylink;
> + struct phylink_config phylink_config;
> struct irq_domain *phy_irq_domain;
> };
>
> @@ -2256,7 +2259,7 @@ static int rtl8169_get_eee(struct net_device *dev, struct ethtool_keee *data)
> if (!rtl_supports_eee(tp))
> return -EOPNOTSUPP;
>
> - ret = phy_ethtool_get_eee(tp->phydev, data);
> + ret = phylink_ethtool_get_eee(tp->phylink, data);
> if (ret)
> return ret;
>
> @@ -2272,7 +2275,7 @@ static int rtl8169_set_eee(struct net_device *dev, struct ethtool_keee *data)
> if (!rtl_supports_eee(tp))
> return -EOPNOTSUPP;
>
> - return phy_ethtool_set_eee(tp->phydev, data);
> + return phylink_ethtool_set_eee(tp->phylink, data);
> }
>
> static void rtl8169_get_ringparam(struct net_device *dev,
> @@ -2303,13 +2306,8 @@ static void rtl8169_get_pauseparam(struct net_device *dev,
> struct ethtool_pauseparam *data)
> {
> struct rtl8169_private *tp = netdev_priv(dev);
> - bool tx_pause, rx_pause;
>
> - phy_get_pause(tp->phydev, &tx_pause, &rx_pause);
> -
> - data->autoneg = tp->phydev->autoneg;
> - data->tx_pause = tx_pause ? 1 : 0;
> - data->rx_pause = rx_pause ? 1 : 0;
> + phylink_ethtool_get_pauseparam(tp->phylink, data);
> }
>
> static int rtl8169_set_pauseparam(struct net_device *dev,
> @@ -2320,9 +2318,7 @@ static int rtl8169_set_pauseparam(struct net_device *dev,
> if (dev->mtu > ETH_DATA_LEN)
> return -EOPNOTSUPP;
>
> - phy_set_asym_pause(tp->phydev, data->rx_pause, data->tx_pause);
> -
> - return 0;
> + return phylink_ethtool_set_pauseparam(tp->phylink, data);
> }
>
> static void rtl8169_get_eth_mac_stats(struct net_device *dev,
> @@ -2388,6 +2384,14 @@ static void rtl8169_get_eth_ctrl_stats(struct net_device *dev,
> le32_to_cpu(tp->counters->rx_unknown_opcode);
> }
>
> +static int rtl8169_get_link_ksettings(struct net_device *ndev,
> + struct ethtool_link_ksettings *cmd)
> +{
> + struct rtl8169_private *tp = netdev_priv(ndev);
> +
> + return phylink_ethtool_ksettings_get(tp->phylink, cmd);
> +}
> +
> static int rtl8169_set_link_ksettings(struct net_device *ndev,
> const struct ethtool_link_ksettings *cmd)
> {
> @@ -2418,6 +2422,13 @@ static int rtl8169_set_link_ksettings(struct net_device *ndev,
> return 0;
> }
>
> +static int rtl8169_nway_reset(struct net_device *dev)
> +{
> + struct rtl8169_private *tp = netdev_priv(dev);
> +
> + return phylink_ethtool_nway_reset(tp->phylink);
> +}
> +
> static const struct ethtool_ops rtl8169_ethtool_ops = {
> .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
> ETHTOOL_COALESCE_MAX_FRAMES,
> @@ -2433,10 +2444,10 @@ static const struct ethtool_ops rtl8169_ethtool_ops = {
> .get_sset_count = rtl8169_get_sset_count,
> .get_ethtool_stats = rtl8169_get_ethtool_stats,
> .get_ts_info = ethtool_op_get_ts_info,
> - .nway_reset = phy_ethtool_nway_reset,
> + .nway_reset = rtl8169_nway_reset,
> .get_eee = rtl8169_get_eee,
> .set_eee = rtl8169_set_eee,
> - .get_link_ksettings = phy_ethtool_get_link_ksettings,
> + .get_link_ksettings = rtl8169_get_link_ksettings,
> .set_link_ksettings = rtl8169_set_link_ksettings,
> .get_ringparam = rtl8169_get_ringparam,
> .get_pause_stats = rtl8169_get_pause_stats,
> @@ -2661,13 +2672,10 @@ static void rtl_jumbo_config(struct rtl8169_private *tp)
> pcie_set_readrq(tp->pci_dev, readrq);
>
> /* Chip doesn't support pause in jumbo mode */
> - if (jumbo) {
> - linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT,
> - tp->phydev->advertising);
> - linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
> - tp->phydev->advertising);
> - phy_start_aneg(tp->phydev);
> - }
> + if (jumbo)
> + tp->phylink_config.mac_capabilities &= ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
> + else
> + tp->phylink_config.mac_capabilities |= (MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
I'm wondering if this actually works. These capabilities are used at init, it's
not really something that's supposed to be changed dynamically.
I'm wondering what's the best course of action to address this. Either rejecting
pause settings in set_pauseparams when jumbo is used, but that leaves the pause
bits in the advertisement.
Have you checked if the above code you wrote works correctly w.r.t pause advertising
when you switch back and forth between jumbo/non jumbo ?
> }
>
> DECLARE_RTL_COND(rtl_chipcmd_cond)
> @@ -2782,7 +2790,7 @@ static void rtl_prepare_power_down(struct rtl8169_private *tp)
> rtl_ephy_write(tp, 0x19, 0xff64);
>
> if (device_may_wakeup(tp_to_dev(tp))) {
> - phy_speed_down(tp->phydev, false);
> + phylink_speed_down(tp->phylink, false);
> rtl_wol_enable_rx(tp);
> }
> }
> @@ -4142,11 +4150,17 @@ static int rtl8169_change_mtu(struct net_device *dev, int new_mtu)
> {
> struct rtl8169_private *tp = netdev_priv(dev);
>
> + if (netif_running(dev))
> + phylink_stop(tp->phylink);
> +
> WRITE_ONCE(dev->mtu, new_mtu);
> netdev_update_features(dev);
> rtl_jumbo_config(tp);
> rtl_set_eee_txidle_timer(tp);
>
> + if (netif_running(dev))
> + phylink_start(tp->phylink);
> +
> return 0;
> }
>
> @@ -4932,9 +4946,6 @@ static int rtl8169_poll(struct napi_struct *napi, int budget)
>
> static void rtl_enable_tx_lpi(struct rtl8169_private *tp, bool enable)
> {
> - if (!rtl_supports_eee(tp))
> - return;
> -
> switch (tp->mac_version) {
> case RTL_GIGA_MAC_VER_34 ... RTL_GIGA_MAC_VER_52:
> /* Adjust EEE LED frequency */
> @@ -4965,41 +4976,15 @@ static void rtl_enable_tx_lpi(struct rtl8169_private *tp, bool enable)
> }
> }
>
> -static void r8169_phylink_handler(struct net_device *ndev)
> -{
> - struct rtl8169_private *tp = netdev_priv(ndev);
> - struct device *d = tp_to_dev(tp);
> -
> - tp->speed = tp->phydev->speed;
> - if (netif_carrier_ok(ndev)) {
> - rtl_link_chg_patch(tp, tp->speed);
> - rtl_enable_tx_lpi(tp, tp->phydev->enable_tx_lpi);
> - pm_request_resume(d);
> - } else {
> - pm_runtime_idle(d);
> - }
> -
> - phy_print_status(tp->phydev);
> -}
> -
> static int r8169_phy_connect(struct rtl8169_private *tp)
> {
> - struct phy_device *phydev = tp->phydev;
> - phy_interface_t phy_mode;
> int ret;
>
> - phy_mode = tp->supports_gmii ? PHY_INTERFACE_MODE_GMII :
> - PHY_INTERFACE_MODE_MII;
> -
> - ret = phy_connect_direct(tp->dev, phydev, r8169_phylink_handler,
> - phy_mode);
> - if (ret)
> + ret = phylink_connect_phy(tp->phylink, tp->phydev);
> + if (ret) {
> + netdev_err(tp->dev, "failed to connect phy\n");
> return ret;
> -
> - if (!tp->supports_gmii)
> - phy_set_max_speed(phydev, SPEED_100);
> -
> - phy_attached_info(phydev);
> + }
>
> return 0;
> }
> @@ -5010,7 +4995,7 @@ static void rtl8169_down(struct rtl8169_private *tp)
> /* Clear all task flags */
> bitmap_zero(tp->wk.flags, RTL_FLAG_MAX);
>
> - phy_stop(tp->phydev);
> + phylink_stop(tp->phylink);
>
> /* Reset SerDes PHY to bring down fiber link */
> if (tp->sfp_mode)
> @@ -5042,7 +5027,7 @@ static void rtl8169_up(struct rtl8169_private *tp)
> enable_work(&tp->wk.work);
> rtl_reset_work(tp);
>
> - phy_start(tp->phydev);
> + phylink_start(tp->phylink);
> }
>
> static int rtl8169_close(struct net_device *dev)
> @@ -5058,7 +5043,7 @@ static int rtl8169_close(struct net_device *dev)
>
> free_irq(tp->irq, tp);
>
> - phy_disconnect(tp->phydev);
> + phylink_disconnect_phy(tp->phylink);
>
> dma_free_coherent(&pdev->dev, R8169_RX_RING_BYTES, tp->RxDescArray,
> tp->RxPhyAddr);
> @@ -5291,6 +5276,8 @@ static void rtl_remove_one(struct pci_dev *pdev)
> r8169_remove_leds(tp->leds);
>
> unregister_netdev(tp->dev);
> + if (tp->phylink)
> + phylink_destroy(tp->phylink);
>
> if (tp->dash_type != RTL_DASH_NONE)
> rtl8168_driver_stop(tp);
> @@ -5519,16 +5506,6 @@ static int r8169_mdio_register(struct rtl8169_private *tp)
> return -EUNATCH;
> }
>
> - tp->phydev->mac_managed_pm = true;
> - if (rtl_supports_eee(tp))
> - phy_support_eee(tp->phydev);
> - phy_support_asym_pause(tp->phydev);
> -
> - /* mimic behavior of r8125/r8126 vendor drivers */
> - if (tp->mac_version == RTL_GIGA_MAC_VER_61)
> - phy_disable_eee_mode(tp->phydev,
> - ETHTOOL_LINK_MODE_2500baseT_Full_BIT);
> -
> /* PHY will be woken up in rtl_open() */
> phy_suspend(tp->phydev);
>
> @@ -5644,6 +5621,132 @@ static bool rtl_aspm_is_safe(struct rtl8169_private *tp)
> return false;
> }
>
> +static void rtl_mac_link_down(struct phylink_config *config, unsigned int mode,
> + phy_interface_t interface)
> +{
> + struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
> +
> + tp->speed = SPEED_UNKNOWN;
> + pm_runtime_idle(tp_to_dev(tp));
> +}
> +
> +static void rtl_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 rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
> +
> + struct device *d = tp_to_dev(tp);
> +
> + tp->speed = speed;
> + rtl_link_chg_patch(tp, speed);
> +
> + pm_request_resume(d);
> +}
> +
> +static struct phylink_pcs *rtl_mac_select_pcs(struct phylink_config *config,
> + phy_interface_t interface)
> +{
> + return NULL;
> +}
> +
> +static void rtl_mac_config(struct phylink_config *config, unsigned int mode,
> + const struct phylink_link_state *state)
> +{
> +}
> +
> +static void rtl_mac_disable_tx_lpi(struct phylink_config *config)
> +{
> + struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
> +
> + rtl_enable_tx_lpi(tp, false);
> +}
> +
> +static int rtl_mac_enable_tx_lpi(struct phylink_config *config, u32 timer, bool tx_clk_stop)
> +{
> + struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
> +
> + if (!rtl_supports_eee(tp))
> + return -EOPNOTSUPP;
> +
> + rtl_enable_tx_lpi(tp, true);
> +
> + return 0;
> +}
> +
> +static const struct phylink_mac_ops rtl_phylink_mac_ops = {
> + .mac_select_pcs = rtl_mac_select_pcs,
> + .mac_config = rtl_mac_config,
> + .mac_link_down = rtl_mac_link_down,
> + .mac_link_up = rtl_mac_link_up,
> + .mac_disable_tx_lpi = rtl_mac_disable_tx_lpi,
> + .mac_enable_tx_lpi = rtl_mac_enable_tx_lpi,
> +};
> +
> +static unsigned long rtl8169_get_lpi_caps(struct rtl8169_private *tp)
> +{
> + unsigned long caps = 0;
> +
> + if (!rtl_supports_eee(tp))
> + return 0;
> +
> + caps |= MAC_100FD | MAC_1000FD;
> +
> + /* mimic behavior of r8125/r8126 vendor drivers
> + * RTL_GIGA_MAC_VER_61 doesn't support 2.5G eee
> + */
> + if (tp->mac_version >= RTL_GIGA_MAC_VER_63)
> + caps |= MAC_2500FD;
> + if (tp->mac_version >= RTL_GIGA_MAC_VER_70)
> + caps |= MAC_5000FD;
> + if (tp->mac_version == RTL_GIGA_MAC_VER_80)
> + caps |= MAC_10000FD;
> +
> + return caps;
> +}
> +
> +static int rtl_init_phylink(struct rtl8169_private *tp)
> +{
> + struct phylink *pl;
> + phy_interface_t phy_mode;
reverse xmas tree here :)
> +
> + tp->phylink_config.dev = &tp->dev->dev;
> + tp->phylink_config.type = PHYLINK_NETDEV;
> + tp->phylink_config.mac_managed_pm = true;
> + tp->phylink_config.lpi_capabilities = rtl8169_get_lpi_caps(tp);
> + tp->phylink_config.mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE;
> +
> + if (tp->sfp_mode) {
> + phy_mode = PHY_INTERFACE_MODE_INTERNAL;
> + tp->phylink_config.mac_capabilities |= MAC_10000FD;
> + } else {
> + tp->phylink_config.mac_capabilities |= MAC_10 | MAC_100;
> + phy_mode = PHY_INTERFACE_MODE_INTERNAL;
> +
> + if (tp->mac_version == RTL_GIGA_MAC_VER_80)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD | MAC_2500FD |
> + MAC_5000FD | MAC_10000FD;
> + else if (tp->mac_version == RTL_GIGA_MAC_VER_70)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD |
> + MAC_2500FD | MAC_5000FD;
> + else if (tp->mac_version >= RTL_GIGA_MAC_VER_61)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD | MAC_2500FD;
> + else
> + if (tp->supports_gmii)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD;
> + }
> +
> + __set_bit(phy_mode, tp->phylink_config.supported_interfaces);
So a few lines above in r8169_phy_connect() there's this removal :
> - phy_mode = tp->supports_gmii ? PHY_INTERFACE_MODE_GMII :
> - PHY_INTERFACE_MODE_MII;
> -
Has it always actually been an internal interface for all realtek devices ?
Maxime
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 2/6] r8169: create a virtual interrupt for linkchg
2026-06-11 9:43 ` [PATCH net-next v2 2/6] r8169: create a virtual interrupt for linkchg javen
@ 2026-06-13 22:05 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
1 sibling, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:05 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: create a virtual interrupt for linkchg
Creates a virtual interrupt for linkchg. To support phylink, we should try
to decouple most of tp->phydev, so we add virtual interrupt for mac
interrupt to inform the change of link status.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index c60710f9bd21f..560f987437b63 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
[ ... ]
> @@ -4869,7 +4872,7 @@ static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
> }
>
> if (status & LinkChg)
> - phy_mac_interrupt(tp->phydev);
> + generic_handle_domain_irq(tp->phy_irq_domain, 0);
[Severity: High]
Does this actually work as intended without the PHY driver implementing the
interrupt callbacks?
Because the Realtek PHY drivers don't implement .config_intr and
.handle_interrupt, phy_drv_supports_irq() will return false, causing
phy_probe() to force phydev->irq to PHY_POLL.
Will this cause the virtual interrupt to be unhandled by phylib, meaning
link state detection degrades from an immediate event-driven notification to
1-second polling?
> rtl_irq_disable(tp);
> napi_schedule(&tp->napi);
[ ... ]
> @@ -5458,6 +5488,21 @@ static int r8169_mdio_register(struct rtl8169_private *tp)
[ ... ]
> + virq = irq_create_mapping(tp->phy_irq_domain, 0);
> + if (!virq)
> + ret = -EINVAL;
> +
> + new_bus->irq[0] = virq;
> +
> ret = devm_mdiobus_register(&pdev->dev, new_bus);
[Severity: Medium]
If irq_create_mapping() fails, ret is set to -EINVAL, but does this
assignment get immediately overwritten by the return value of
devm_mdiobus_register()?
This would cause the IRQ mapping failure to be silently ignored, leaving
the bus with an invalid IRQ of 0 without aborting the driver initialization.
> if (ret)
> return ret;
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 3/6] r8169: add support for phylink
2026-06-11 9:43 ` [PATCH net-next v2 3/6] r8169: add support for phylink javen
2026-06-12 8:13 ` Maxime Chevallier
@ 2026-06-13 22:05 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2 siblings, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:05 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: add support for phylink
Transfer old framework to phylink. Phylink can support fiber mode card
which can not get link status or link speed from standard phy registers.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index 560f987437b63..615bd4107359a 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
[ ... ]
> @@ -2661,13 +2672,10 @@ static void rtl_jumbo_config(struct rtl8169_private *tp)
> pcie_set_readrq(tp->pci_dev, readrq);
>
> /* Chip doesn't support pause in jumbo mode */
> - if (jumbo) {
> - linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT,
> - tp->phydev->advertising);
> - linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
> - tp->phydev->advertising);
> - phy_start_aneg(tp->phydev);
> - }
> + if (jumbo)
> + tp->phylink_config.mac_capabilities &= ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
> + else
> + tp->phylink_config.mac_capabilities |= (MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
> }
[Severity: High]
Does phylink re-read mac_capabilities on start?
It looks like dynamic bitwise modifications to tp->phylink_config.mac_capabilities
made here are ignored by phylink. Since phylink does not update the pl->supported
link mode mask during phylink_start(), won't the PHY continue to incorrectly
advertise pause capabilities to the link partner when jumbo frames are enabled?
[ ... ]
> @@ -4142,11 +4150,17 @@ static int rtl8169_change_mtu(struct net_device *dev, int new_mtu)
> {
> struct rtl8169_private *tp = netdev_priv(dev);
>
> + if (netif_running(dev))
> + phylink_stop(tp->phylink);
> +
> WRITE_ONCE(dev->mtu, new_mtu);
> netdev_update_features(dev);
> rtl_jumbo_config(tp);
> rtl_set_eee_txidle_timer(tp);
>
> + if (netif_running(dev))
> + phylink_start(tp->phylink);
> +
> return 0;
> }
[ ... ]
> +static int rtl_init_phylink(struct rtl8169_private *tp)
> +{
> + struct phylink *pl;
> + phy_interface_t phy_mode;
> +
> + tp->phylink_config.dev = &tp->dev->dev;
> + tp->phylink_config.type = PHYLINK_NETDEV;
> + tp->phylink_config.mac_managed_pm = true;
> + tp->phylink_config.lpi_capabilities = rtl8169_get_lpi_caps(tp);
> + tp->phylink_config.mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE;
> +
> + if (tp->sfp_mode) {
> + phy_mode = PHY_INTERFACE_MODE_INTERNAL;
> + tp->phylink_config.mac_capabilities |= MAC_10000FD;
> + } else {
> + tp->phylink_config.mac_capabilities |= MAC_10 | MAC_100;
> + phy_mode = PHY_INTERFACE_MODE_INTERNAL;
[Severity: High]
Does hardcoding phy_mode to PHY_INTERFACE_MODE_INTERNAL here break older
adapters that use discrete PHYs over GMII or MII?
Prior to this patch, the driver dynamically checked tp->supports_gmii to set
either PHY_INTERFACE_MODE_GMII or PHY_INTERFACE_MODE_MII. Forcing INTERNAL
might tell the PHY driver that the PHY is integrated into the MAC, which could
cause discrete PHY drivers to skip necessary initializations, fail validation
checks, or misconfigure interface delays.
> +
> + if (tp->mac_version == RTL_GIGA_MAC_VER_80)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD | MAC_2500FD |
> + MAC_5000FD | MAC_10000FD;
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 4/6] r8169: add support for RTL8116af
2026-06-11 9:43 ` [PATCH net-next v2 4/6] r8169: add support for RTL8116af javen
@ 2026-06-13 22:06 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
1 sibling, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:06 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: add support for RTL8116af
This commit adds SFP mode support for the RTL8116af. It introduces checks to
ensure the PHY device exists before using it and implements phylink PCS
operations to get link status and speed from the SERDES registers.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
[ ... ]
> @@ -5655,6 +5708,55 @@ static void rtl_mac_config(struct phylink_config *config, unsigned int mode,
> {
> }
>
> +static u16 rtl8169_sds_read(struct rtl8169_private *tp, u16 sds_reg)
> +{
> + r8168_mac_ocp_write(tp, OCP_SDS_ADDR_REG, sds_reg);
> + r8168_mac_ocp_write(tp, OCP_SDS_CMD_REG, SDS_CMD_READ);
> + return r8168_mac_ocp_read(tp, OCP_SDS_DATA_REG);
> +}
> +
[Severity: High]
Can concurrent accesses to the OCP registers corrupt the hardware state?
rtl8169_sds_read() calls r8168_mac_ocp_write() and r8168_mac_ocp_read()
sequentially. These helpers internally acquire and release tp->mac_ocp_lock
for each individual 32-bit operation, but the 3-step sequence itself is not
atomic.
Since rtl8169_sds_read() is called from rtl8169_pcs_get_state(), which can be
executed concurrently by different contexts (such as phylink_resolve() under
state_mutex and phylink_ethtool_ksettings_get() under rtnl_lock), can their
executions interleave? This could cause one thread's address or command write
to be clobbered by another, leading to incorrect register reads.
> +static void rtl8169_pcs_get_state(struct phylink_pcs *pcs,
> + unsigned int neg_mode,
> + struct phylink_link_state *state)
> +{
> + struct rtl8169_private *tp = container_of(pcs, struct rtl8169_private, pcs);
> + u16 bmsr, lpa;
> +
> + bmsr = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_BMSR);
> + lpa = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_LPA);
> +
> + state->link = !!(bmsr & BMSR_LSTATUS);
> + state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE);
> + if (state->link) {
> + state->speed = SPEED_1000;
> + state->duplex = DUPLEX_FULL;
> + } else {
> + state->speed = SPEED_UNKNOWN;
> + state->duplex = DUPLEX_UNKNOWN;
> + }
> +
> + if (lpa & LPA_1000XPAUSE)
> + state->pause |= MLO_PAUSE_RX | MLO_PAUSE_TX;
> +}
[Severity: Medium]
Does this manual parsing of MII_LPA miss asymmetric pause capabilities and
fail to populate state->lp_advertising?
Phylink requires lp_advertising to be populated for in-band autonegotiation
so it can correctly report partner capabilities and resolve pause. Should
this driver use phylink_mii_c22_pcs_decode_state() to decode the link state
instead of manually checking for LPA_1000XPAUSE?
> +
> +static int rtl8169_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
> + phy_interface_t interface,
> + const unsigned long *advertising,
> + bool permit_pause_to_mac)
> +{
> + return 0;
> +}
> +
> +static int rtl8169_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
> + const struct phylink_link_state *state)
> +{
> + return 0;
> +}
> +
> +static void rtl8169_pcs_an_restart(struct phylink_pcs *pcs)
> +{
> +}
[Severity: Medium]
Will these empty stubs cause user configurations to be silently ignored?
When userspace uses ethtool to change the local advertisement (for example,
to enable or disable pause capabilities), the driver appears to ignore the
request without updating the SerDes advertisement registers or restarting
autonegotiation.
[ ... ]
> @@ -5941,10 +6055,12 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
> if (rc)
> return rc;
>
> - rc = r8169_mdio_register(tp);
> - if (rc) {
> - phylink_destroy(tp->phylink);
> - return rc;
> + if (tp->sfp_mode != RTL_SFP_8168_AF) {
> + rc = r8169_mdio_register(tp);
> + if (rc) {
> + phylink_destroy(tp->phylink);
> + return rc;
> + }
> }
>
> rc = register_netdev(dev);
[Severity: Critical]
If r8169_mdio_register() is skipped for RTL_SFP_8168_AF, will tp->phy_irq_domain
remain NULL?
The driver unconditionally unmasks the LinkChg interrupt in tp->irq_mask.
When the SFP link state changes, the hardware asserts the LinkChg interrupt,
which causes the hardirq handler rtl8169_interrupt() to execute:
if (status & LinkChg)
generic_handle_domain_irq(tp->phy_irq_domain, 0);
Passing a NULL irq_domain to generic_handle_domain_irq() falls back to the
system's irq_default_domain. This maps hwirq 0 to an unrelated system
interrupt (such as the system timer on x86 architectures). Can this cause
spurious execution of an unrelated interrupt handler and lead to system
lockups or crashes?
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 5/6] r8169: add ltr support for RTL8116af
2026-06-11 9:43 ` [PATCH net-next v2 5/6] r8169: add ltr " javen
@ 2026-06-13 22:06 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
1 sibling, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:06 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: add ltr support for RTL8116af
This patch adds ltr support for RTL8116af, enables RTL8116af enter l1.2
state. This makes sense for the system to enter c10 state.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index a73c0215b2407..a49e7a8d8f4da 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
[ ... ]
> @@ -3153,8 +3155,22 @@ static void rtl_enable_ltr(struct rtl8169_private *tp)
> r8168_mac_ocp_write(tp, 0xcdf2, 0x9003);
> r8168_mac_ocp_modify(tp, LTR_OBFF_LOCK, 0x0000, LINK_SPEED_CHANGE_EN);
> break;
> - case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
> case RTL_GIGA_MAC_VER_52:
> + r8168_mac_ocp_write(tp, 0xcdd0, 0x9003);
> + r8168_mac_ocp_modify(tp, LTR_SNOOP, 0x0000, LTR_SNOOP_EN);
> + r8168_mac_ocp_write(tp, 0xe02c, 0x1880);
> + r8168_mac_ocp_write(tp, 0xe02e, 0x4880);
[Severity: High]
Does this completely redefine the LTR enable sequence for all devices using
RTL_GIGA_MAC_VER_52? Since older devices like RTL8117 also use this MAC
version, could this cause regressions for them by replacing the previous
fallthrough behavior? Should there be a check to ensure this only runs for
the newly added RTL8116af?
> + r8168_mac_ocp_modify(tp, ALDPS_LTR, 0x0000, ALDPS_LTR_EN);
> + r8168_mac_ocp_write(tp, 0xcdd8, 0x9003);
> + r8168_mac_ocp_write(tp, 0xcdda, 0x9003);
> + r8168_mac_ocp_write(tp, 0xcddc, 0x9003);
> + r8168_mac_ocp_write(tp, 0xcdd2, 0x883c);
> + r8168_mac_ocp_write(tp, 0xcdd4, 0x8c12);
> + r8168_mac_ocp_write(tp, 0xcdd6, 0x9003);
> + r8168_mac_ocp_write(tp, 0xe0a6, 0x9003);
> + r8168_mac_ocp_write(tp, 0xe0a8, 0x9003);
> + break;
> + case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
> r8168_mac_ocp_modify(tp, ALDPS_LTR, 0x0000, ALDPS_LTR_EN);
> RTL_W8(tp, COMBO_LTR_EXTEND, RTL_R8(tp, COMBO_LTR_EXTEND) | COMBO_LTR_EXTEND_EN);
> fallthrough;
> @@ -3174,6 +3190,7 @@ static void rtl_enable_ltr(struct rtl8169_private *tp)
> }
> /* chip can trigger LTR */
> r8168_mac_ocp_modify(tp, LTR_OBFF_LOCK, 0x0003, LTR_OBFF_LOCK_EN);
> + r8168_mac_ocp_modify(tp, SEND_LTR_MSG, 0x0000, LTR_MSG_EN);
[Severity: High]
This unconditionally modifies SEND_LTR_MSG at the end of rtl_enable_ltr(),
which is called for multiple MAC versions. Was it intended to apply globally
to all chips, or should this change also be restricted to RTL8116af?
> }
>
> static void rtl_hw_aspm_clkreq_enable(struct rtl8169_private *tp, bool enable)
[ ... ]
> @@ -3732,7 +3751,9 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
>
> rtl_eri_set_bits(tp, 0xd4, 0x0010);
>
> - rtl_eri_write(tp, 0x5f0, ERIAR_MASK_0011, 0x4f87);
> + rtl_eri_write(tp, 0x5f0, ERIAR_MASK_0011, 0x4000);
> +
> + r8168_mac_ocp_write(tp, 0xe098, 0xc302);
[Severity: High]
Since older devices also use rtl_hw_start_8117(), will applying these register
changes unconditionally to all RTL_GIGA_MAC_VER_52 devices affect the hardware
initialization of existing devices? Could we add a check to restrict these
tweaks specifically to RTL8116af?
>
> rtl_disable_rxdvgate(tp);
>
> @@ -3759,9 +3780,9 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
> }
>
> r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
> - r8168_mac_ocp_write(tp, 0xea80, 0x0003);
> - r8168_mac_ocp_modify(tp, 0xe052, 0x0000, 0x0009);
> - r8168_mac_ocp_modify(tp, 0xd420, 0x0fff, 0x047f);
> + r8168_mac_ocp_write(tp, 0xea80, 0x0000);
> + r8168_mac_ocp_modify(tp, 0xe052, 0x0009, 0x0000);
> + r8168_mac_ocp_modify(tp, 0xd420, 0x0fff, 0x045f);
[Severity: High]
Like the changes above in rtl_hw_start_8117(), these magic register updates
will be applied to RTL8117 as well. Can this break link stability or ASPM
for existing devices by replacing their expected register values?
>
> r8168_mac_ocp_write(tp, 0xe63e, 0x0001);
> r8168_mac_ocp_write(tp, 0xe63e, 0x0000);
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 6/6] r8169: fix RTL8116af can not enter s0idle and c10
2026-06-11 9:43 ` [PATCH net-next v2 6/6] r8169: fix RTL8116af can not enter s0idle and c10 javen
@ 2026-06-13 22:06 ` Jakub Kicinski
0 siblings, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:06 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: fix RTL8116af can not enter s0idle and c10
RTL8116AF is a multi-function device where functions 2 to 7 are hidden from
the PCI core. This commit puts these hidden functions into D3hot and enables
ASPM on their PCIe link control register by writing to their configuration
spaces.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index a49e7a8d8f4da..bc00cdc3add65 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
> @@ -356,6 +356,9 @@ enum rtl_registers {
> #define LINK_SPEED_CHANGE_EN BIT(14)
> #define LTR_SNOOP_EN GENMASK(15, 14)
> #define LTR_MSG_EN BIT(0)
> +#define RTL8116AF_FUNC_PM_CSR 0x80
> +#define RTL8116AF_FUNC_EXP_LNKCTL 0x44
[Severity: High]
Are these offsets swapped?
In typical Realtek PCIe configuration space, the PM capability is at 0x40
(making PM_CSR offset 0x44) and the PCIe capability is at 0x70 (making Link
Control offset 0x80).
Because these definitions might be swapped, the code later reads 0x80 (Link
Control) and applies PCI_PM_CTRL_PME_ENABLE, and reads 0x44 (PM_CSR) and
applies PCI_EXP_LNKCTL_ASPMC. Could this cross-wiring fail to set PME_ENABLE
in the PM_CSR register as intended?
> +#define RTL_PM_D3HOT GENMASK(1, 0)
> };
>
[ ... ]
> @@ -3731,6 +3734,33 @@ static void rtl_hw_start_8168ep_3(struct rtl8169_private *tp)
> r8168_mac_ocp_modify(tp, 0xe860, 0x0000, 0x0080);
> }
>
> +static void rtl_disable_hidden_function(struct pci_dev *pdev)
> +{
> + unsigned int slot = PCI_SLOT(pdev->devfn);
> + struct pci_bus *bus = pdev->bus;
> + unsigned int devfn;
> + int func;
> + int ret;
> + u32 val;
> +
> + for (func = 2; func < 8; func++) {
> + devfn = PCI_DEVFN(slot, func);
> +
> + ret = pci_bus_read_config_dword(bus, devfn, RTL8116AF_FUNC_PM_CSR, &val);
> + if (!ret && !PCI_POSSIBLE_ERROR(val)) {
[Severity: High]
Is it possible to verify that the function is the intended hidden device
before proceeding?
The commit message notes that the hidden functions return an all-ones
response when their vendor ID is read. However, this code reads offset 0x80
and checks that it did not return 0xFFFFFFFF. If a legitimate, OS-enumerated
function exists at any of these indices (e.g., a secondary NIC, Wi-Fi, or SD
card reader), the read will succeed with valid register data and the driver
will overwrite its registers at 0x80 and 0x44.
Should the loop read the Vendor ID (offset 0x00) and verify it returns
0xFFFFFFFF to ensure the function is truly hidden before mutating its
configuration space?
> + val &= ~(PCI_PM_CTRL_STATE_MASK | PCI_PM_CTRL_PME_ENABLE);
> + val |= (RTL_PM_D3HOT | PCI_PM_CTRL_PME_ENABLE);
> + pci_bus_write_config_dword(bus, devfn, RTL8116AF_FUNC_PM_CSR, val);
[Severity: High]
Can these 32-bit read-modify-write operations on natively 16-bit registers
unintentionally clear adjacent Write-1-to-Clear (W1C) status bits?
Reading a 32-bit value from offset 0x80 fetches both the Link Control (lower
16 bits) and Link Status (upper 16 bits) registers. Link Status contains W1C
bits. Writing the unmodified 32-bit value back writes 1s to any W1C bits
that were set, clearing them.
The same applies to the 16-bit PM_CSR register, where writing back the 32-bit
value might clear the W1C PME_Status bit (bit 15) if it was set, and
needlessly overwrite the adjacent 8-bit PM_Data and PM_Bridge_Ext registers.
Would it be safer to use pci_bus_read_config_word() and
pci_bus_write_config_word(), and explicitly mask out the PME_Status bit when
writing PM_CSR?
> + }
> +
> + ret = pci_bus_read_config_dword(bus, devfn, RTL8116AF_FUNC_EXP_LNKCTL, &val);
> + if (!ret && !PCI_POSSIBLE_ERROR(val)) {
> + val |= PCI_EXP_LNKCTL_ASPMC;
> + pci_bus_write_config_dword(bus, devfn, RTL8116AF_FUNC_EXP_LNKCTL, val);
> + }
> + }
> +}
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 2/6] r8169: create a virtual interrupt for linkchg
2026-06-11 9:43 ` [PATCH net-next v2 2/6] r8169: create a virtual interrupt for linkchg javen
2026-06-13 22:05 ` Jakub Kicinski
@ 2026-06-13 22:06 ` Jakub Kicinski
1 sibling, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:06 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: create a virtual interrupt for linkchg
This commit creates a virtual interrupt domain in r8169_mdio_register() so
the MAC link change interrupt can be dispatched into phylib via
generic_handle_domain_irq() instead of calling phy_mac_interrupt() on
tp->phydev directly.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index c60710f9bd21..560f987437b6 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
[ ... ]
> @@ -5458,6 +5488,21 @@ static int r8169_mdio_register(struct rtl8169_private *tp)
> new_bus->write_c45 = r8169_mdio_write_reg_c45;
> }
>
> + tp->phy_irq_domain = irq_domain_add_linear(NULL, 1,
> + &rtl_phy_irq_domain_ops, tp);
> + if (!tp->phy_irq_domain)
> + return -ENOMEM;
> +
> + ret = devm_add_action_or_reset(&pdev->dev, rtl_phy_irq_cleanup, tp);
> + if (ret)
> + return ret;
> +
> + virq = irq_create_mapping(tp->phy_irq_domain, 0);
> + if (!virq)
> + ret = -EINVAL;
> +
> + new_bus->irq[0] = virq;
> +
> ret = devm_mdiobus_register(&pdev->dev, new_bus);
> if (ret)
> return ret;
[High]
Should the failure of irq_create_mapping() actually abort registration
here?
virq = irq_create_mapping(tp->phy_irq_domain, 0);
if (!virq)
ret = -EINVAL;
new_bus->irq[0] = virq;
ret = devm_mdiobus_register(&pdev->dev, new_bus);
if (ret)
return ret;
When irq_create_mapping() returns 0, ret is set to -EINVAL but the
function continues on. The next assignment to ret from
devm_mdiobus_register() overwrites that -EINVAL, so the mapping failure
is silently dropped.
A couple of follow-on consequences seem possible:
The mdiobus is registered with new_bus->irq[0] == 0. phy_interrupt_is_valid()
only special-cases PHY_POLL (-1) and PHY_MAC_INTERRUPT (-2), so 0 looks
like a normal IRQ number and phylib will try to request_threaded_irq(0,
...) when the PHY starts.
In rtl8169_interrupt(), generic_handle_domain_irq(tp->phy_irq_domain, 0)
will be called against a domain with no hwirq 0 mapping, so LinkChg
events would not reach the PHY state machine.
Would something like this be more appropriate?
virq = irq_create_mapping(tp->phy_irq_domain, 0);
if (!virq)
return -EINVAL;
new_bus->irq[0] = virq;
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 3/6] r8169: add support for phylink
2026-06-11 9:43 ` [PATCH net-next v2 3/6] r8169: add support for phylink javen
2026-06-12 8:13 ` Maxime Chevallier
2026-06-13 22:05 ` Jakub Kicinski
@ 2026-06-13 22:06 ` Jakub Kicinski
2 siblings, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:06 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: add support for phylink
This commit transfers the r8169 driver from the phylib framework to
phylink, in order to support fiber-mode cards whose link status/speed
cannot be read from standard PHY registers.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index 560f987437b6..615bd4107359 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
[ ... ]
> @@ -2661,13 +2672,10 @@ static void rtl_jumbo_config(struct rtl8169_private *tp)
> pcie_set_readrq(tp->pci_dev, readrq);
>
> /* Chip doesn't support pause in jumbo mode */
> - if (jumbo) {
> - linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT,
> - tp->phydev->advertising);
> - linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
> - tp->phydev->advertising);
> - phy_start_aneg(tp->phydev);
> - }
> + if (jumbo)
> + tp->phylink_config.mac_capabilities &= ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
> + else
> + tp->phylink_config.mac_capabilities |= (MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
> }
[High]
Does this still enforce the "Chip doesn't support pause in jumbo mode"
constraint that the comment describes?
Looking at phylink_create() in drivers/net/phy/phylink.c, pl->supported is
initialized once via linkmode_fill() + phylink_validate() and is not
re-derived from config->mac_capabilities afterwards. Pause negotiation is
driven by pl->link_config.pause and the PHY's advertising bits, neither of
which is updated when rtl_jumbo_config() toggles mac_capabilities.
In rtl8169_change_mtu() the phylink_stop()/phylink_start() cycle re-runs
phylink_mac_initial_config() against the cached pl->link_config, so
Pause/Asym_Pause remain advertised after MTU is raised above ETH_DATA_LEN.
Compared to the old code which actively cleared Pause/Asym_Pause from
tp->phydev->advertising and called phy_start_aneg() to force renegotiation,
should this branch instead be updating something that is actually consumed
on the next link-up, so that PAUSE is no longer advertised in jumbo mode?
[ ... ]
> @@ -5010,7 +4995,7 @@ static void rtl8169_down(struct rtl8169_private *tp)
> /* Clear all task flags */
> bitmap_zero(tp->wk.flags, RTL_FLAG_MAX);
>
> - phy_stop(tp->phydev);
> + phylink_stop(tp->phylink);
[Medium]
Is the phylink conversion complete here?
After this commit the PHY is owned by phylink (connected via
phylink_connect_phy() / phylink_disconnect_phy()), but
net_device_ops.ndo_eth_ioctl still points at phy_do_ioctl_running, so
SIOCGMIIREG / SIOCSMIIREG / SIOCGMIIPHY continue to go directly to the PHY
and bypass phylink.
phylink exports phylink_mii_ioctl() for exactly this case. Mixing direct
phydev ioctl access with phylink-managed link state can let phylink's
cached state (pl->link_config, pl->phy_state) drift out of sync with the
actual PHY register state.
Should ndo_eth_ioctl be switched to phylink_mii_ioctl() as part of this
conversion?
[ ... ]
> @@ -5644,6 +5621,132 @@ static bool rtl_aspm_is_safe(struct rtl8169_private *tp)
> return false;
> }
>
[ ... ]
> +static int rtl_init_phylink(struct rtl8169_private *tp)
> +{
> + struct phylink *pl;
> + phy_interface_t phy_mode;
> +
> + tp->phylink_config.dev = &tp->dev->dev;
> + tp->phylink_config.type = PHYLINK_NETDEV;
> + tp->phylink_config.mac_managed_pm = true;
> + tp->phylink_config.lpi_capabilities = rtl8169_get_lpi_caps(tp);
> + tp->phylink_config.mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE;
> +
> + if (tp->sfp_mode) {
> + phy_mode = PHY_INTERFACE_MODE_INTERNAL;
> + tp->phylink_config.mac_capabilities |= MAC_10000FD;
> + } else {
> + tp->phylink_config.mac_capabilities |= MAC_10 | MAC_100;
> + phy_mode = PHY_INTERFACE_MODE_INTERNAL;
> +
> + if (tp->mac_version == RTL_GIGA_MAC_VER_80)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD | MAC_2500FD |
> + MAC_5000FD | MAC_10000FD;
> + else if (tp->mac_version == RTL_GIGA_MAC_VER_70)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD |
> + MAC_2500FD | MAC_5000FD;
> + else if (tp->mac_version >= RTL_GIGA_MAC_VER_61)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD | MAC_2500FD;
> + else
> + if (tp->supports_gmii)
> + tp->phylink_config.mac_capabilities |= MAC_1000FD;
> + }
> +
> + __set_bit(phy_mode, tp->phylink_config.supported_interfaces);
> + pl = phylink_create(&tp->phylink_config, tp_to_dev(tp)->fwnode,
> + phy_mode, &rtl_phylink_mac_ops);
[Medium]
Is PHY_INTERFACE_MODE_INTERNAL the intended interface mode here?
Both the sfp_mode branch and the copper branch set phy_mode to
PHY_INTERFACE_MODE_INTERNAL, which makes the if/else look like a
copy-paste. The previous r8169_phy_connect() picked
PHY_INTERFACE_MODE_GMII when supports_gmii was set and
PHY_INTERFACE_MODE_MII otherwise:
phy_mode = tp->supports_gmii ? PHY_INTERFACE_MODE_GMII :
PHY_INTERFACE_MODE_MII;
PHY_INTERFACE_MODE_INTERNAL is conventionally used for PHYs integrated on
the same die as the MAC, whereas r8169's MAC-to-PHY link is logically
GMII/MII.
Phylink also derives interface-allowed link capabilities through
phy_caps_from_interface() in phylink_get_capabilities(), so the chosen
interface mode shapes which speeds and duplex modes are validated, and may
affect PHY-side EEE/clock-stop handling and rate matching.
Could the commit message explain why INTERNAL was selected for both arms,
or should the copper branch keep the GMII/MII selection?
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 4/6] r8169: add support for RTL8116af
2026-06-11 9:43 ` [PATCH net-next v2 4/6] r8169: add support for RTL8116af javen
2026-06-13 22:06 ` Jakub Kicinski
@ 2026-06-13 22:06 ` Jakub Kicinski
1 sibling, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:06 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: add support for RTL8116af
This change adds support for RTL8116af, a SerDes/SFP variant of the
RTL_GIGA_MAC_VER_52 family that has no internal PHY. It introduces a
phylink_pcs implementation that reads link state directly from the
SerDes registers via OCP and skips r8169_mdio_register() so that no
phy_device is bound, with NULL guards added in several callers.
A few questions about the change below.
[Low]
The commit message says:
Phylink uses pcs to get the link status and speed from its serdes
reg, instead of standard phy reg.
Looking at rtl8169_pcs_get_state(), only link / an_complete / lpa
appear to come from the SerDes registers. The speed and duplex are
hardcoded:
if (state->link) {
state->speed = SPEED_1000;
state->duplex = DUPLEX_FULL;
}
Is that mismatch with the commit message intentional, given that for
1000BASE-X 1000FD is the only valid mode anyway?
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index 615bd4107359..a73c0215b240 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
[ ... ]
> @@ -3703,12 +3748,14 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
>
> rtl_pcie_state_l2l3_disable(tp);
>
> - rg_saw_cnt = phy_read_paged(tp->phydev, 0x0c42, 0x13) & 0x3fff;
> - if (rg_saw_cnt > 0) {
> - u16 sw_cnt_1ms_ini;
> + if (tp->phydev) {
> + rg_saw_cnt = phy_read_paged(tp->phydev, 0x0c42, 0x13) & 0x3fff;
> + if (rg_saw_cnt > 0) {
> + u16 sw_cnt_1ms_ini;
>
> - sw_cnt_1ms_ini = (16000000 / rg_saw_cnt) & 0x0fff;
> - r8168_mac_ocp_modify(tp, 0xd412, 0x0fff, sw_cnt_1ms_ini);
> + sw_cnt_1ms_ini = (16000000 / rg_saw_cnt) & 0x0fff;
> + r8168_mac_ocp_modify(tp, 0xd412, 0x0fff, sw_cnt_1ms_ini);
> + }
> }
[Medium]
rtl_hw_start_8117() is shared between RTL8117 and the new RTL8116af
since both are RTL_GIGA_MAC_VER_52. With this change the calibration
of MAC register 0xd412 from PHY register 0x0c42:0x13 is silently
skipped for 8116af, leaving 0xd412 at its reset value rather than a
calibrated 1ms count.
Does 8116af actually not need 0xd412 configured, or does it need an
alternative calibration source? The commit log does not mention either
case.
[ ... ]
> @@ -5647,6 +5696,10 @@ static void rtl_mac_link_up(struct phylink_config *config, struct phy_device *ph
> static struct phylink_pcs *rtl_mac_select_pcs(struct phylink_config *config,
> phy_interface_t interface)
> {
> + struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
> +
> + if (interface == PHY_INTERFACE_MODE_1000BASEX || interface == PHY_INTERFACE_MODE_SGMII)
> + return &tp->pcs;
> return NULL;
> }
>
> @@ -5655,6 +5708,55 @@ static void rtl_mac_config(struct phylink_config *config, unsigned int mode,
> {
> }
>
> +static u16 rtl8169_sds_read(struct rtl8169_private *tp, u16 sds_reg)
> +{
> + r8168_mac_ocp_write(tp, OCP_SDS_ADDR_REG, sds_reg);
> + r8168_mac_ocp_write(tp, OCP_SDS_CMD_REG, SDS_CMD_READ);
> + return r8168_mac_ocp_read(tp, OCP_SDS_DATA_REG);
> +}
[Medium]
The three-step SDS sequence (write addr, write cmd, read data) is
implemented with three separate r8168_mac_ocp_*() calls, each of which
takes and releases tp->mac_ocp_lock independently.
rtl8169_pcs_get_state() can run from phylink_resolve() (workqueue) and
also from the ethtool/mii ioctl paths, e.g.:
phylink_ethtool_ksettings_get()
-> phylink_mac_pcs_get_state()
-> pcs->ops->pcs_get_state()
These contexts share no common lock. Can two SDS transactions
interleave between the address/cmd writes and the data read, so the
engine sees an addr from one caller and a cmd from another and returns
data for the wrong register?
The existing r8168_mac_ocp_modify() uses a single
raw_spin_lock_irqsave around __r8168_mac_ocp_read/write to keep its
RMW atomic. Should this SDS helper do the same across the three
accesses?
[Medium]
After writing SDS_CMD_READ (0x0001) to OCP_SDS_CMD_REG, the data
register is read immediately without polling for completion. Other
indirect accessors in this driver follow a kick-then-poll pattern, for
example r8168_phy_ocp_read():
RTL_W32(tp, GPHY_OCP, reg << 15);
return rtl_loop_wait_high(tp, &rtl_ocp_gphy_cond, 25, 10) ?
(RTL_R32(tp, GPHY_OCP) & 0xffff) : -ETIMEDOUT;
and rtl_eri_read, rtl_efuse_read, rtl_ephy_read, rtl_csi_read and
r8169_mdio_read_reg all wait on a completion bit before reading the
data register.
Is the SerDes engine guaranteed to complete synchronously here, or can
the read of OCP_SDS_DATA_REG return stale data from the previous
transaction, leading to wrong BMSR / LPA values being reported back to
phylink?
> +
> +static void rtl8169_pcs_get_state(struct phylink_pcs *pcs,
> + unsigned int neg_mode,
> + struct phylink_link_state *state)
> +{
> + struct rtl8169_private *tp = container_of(pcs, struct rtl8169_private, pcs);
> + u16 bmsr, lpa;
> +
> + bmsr = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_BMSR);
> + lpa = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_LPA);
> +
> + state->link = !!(bmsr & BMSR_LSTATUS);
> + state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE);
> + if (state->link) {
> + state->speed = SPEED_1000;
> + state->duplex = DUPLEX_FULL;
> + } else {
> + state->speed = SPEED_UNKNOWN;
> + state->duplex = DUPLEX_UNKNOWN;
> + }
> +
> + if (lpa & LPA_1000XPAUSE)
> + state->pause |= MLO_PAUSE_RX | MLO_PAUSE_TX;
> +}
[Low]
The 1000BASE-X LPA encodes pause as two bits, LPA_1000XPAUSE and
LPA_1000XPAUSE_ASYM, which together describe four states (none, sym,
asym TX, asym RX). Treating any peer that sets LPA_1000XPAUSE as full
bidirectional pause looks like it ignores the asymmetric cases.
Could this use phylink_mii_c22_pcs_decode_state() (or equivalent
decoding logic) so both LPA pause bits are interpreted correctly?
> +
> +static int rtl8169_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
> + phy_interface_t interface,
> + const unsigned long *advertising,
> + bool permit_pause_to_mac)
> +{
> + return 0;
> +}
> +
> +static int rtl8169_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
> + const struct phylink_link_state *state)
> +{
> + return 0;
> +}
> +
> +static void rtl8169_pcs_an_restart(struct phylink_pcs *pcs)
> +{
> +}
[Medium]
pcs_config and pcs_an_restart are both empty stubs. phylink calls
pcs_an_restart from phylink_ethtool_nway_reset(), so an
ethtool -r ethN on an 8116af interface will return success but will
not actually trigger AN restart in hardware. Similarly, pcs_config is
called whenever the inband advertisement changes, so changes via
ethtool -s ... advertise ... are silently dropped.
There does not appear to be any other code path that programs the
SerDes for 1000BASE-X mode or AN advertisement, so the chip seems to
rely on the bootloader leaving the PCS pre-configured.
Should pcs_an_restart at least write BMCR_ANRESTART to the SerDes
BMCR, and pcs_config program the advertisement?
[ ... ]
> @@ -5828,12 +5947,7 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
> }
> tp->aspm_manageable = !rc;
>
> - if (rtl_is_8125(tp)) {
> - u16 data = r8168_mac_ocp_read(tp, 0xd006);
> -
> - if ((data & 0xff) == 0x07)
> - tp->sfp_mode = true;
> - }
> + tp->sfp_mode = rtl_get_sfp_mode(tp);
[Low]
After detection as RTL8116af, chip still resolves to the
RTL_GIGA_MAC_VER_52 entry in rtl_chip_infos[] whose name is
"RTL8168fp/RTL8117". The probe banner
netdev_info(dev, "%s, %pM, %sXID %x, IRQ %d\n",
chip->name, ...);
therefore identifies an 8116af device as RTL8168fp/RTL8117. Should
chip->name be overridden when rtl_get_sfp_mode() returns
RTL_SFP_8168_AF so that dmesg reports the actual variant?
[ ... ]
> @@ -5941,10 +6055,12 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
> if (rc)
> return rc;
>
> - rc = r8169_mdio_register(tp);
> - if (rc) {
> - phylink_destroy(tp->phylink);
> - return rc;
> + if (tp->sfp_mode != RTL_SFP_8168_AF) {
> + rc = r8169_mdio_register(tp);
> + if (rc) {
> + phylink_destroy(tp->phylink);
> + return rc;
> + }
> }
[High]
r8169_mdio_register() is the only function that initializes
tp->phy_irq_domain via irq_domain_add_linear(). Skipping it for
RTL_SFP_8168_AF leaves tp->phy_irq_domain as NULL.
rtl_set_irq_mask() is unchanged, so the LinkChg bit remains set in
tp->irq_mask. On a SerDes link transition, rtl8169_interrupt() still
runs:
generic_handle_domain_irq(tp->phy_irq_domain, 0);
with tp->phy_irq_domain == NULL. Inside __irq_resolve_mapping() this
falls back to irq_default_domain, which on some architectures will
look up hwirq 0 in an unrelated domain, while on others
handle_irq_desc(NULL) just returns -EINVAL silently.
Since pcs.poll = true is already set for 8168_AF, link state is
polled via phylink anyway. Should LinkChg be masked out of
tp->irq_mask for 8168_AF, or should the generic_handle_domain_irq()
call be guarded by tp->phy_irq_domain (or tp->phydev)?
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 5/6] r8169: add ltr support for RTL8116af
2026-06-11 9:43 ` [PATCH net-next v2 5/6] r8169: add ltr " javen
2026-06-13 22:06 ` Jakub Kicinski
@ 2026-06-13 22:06 ` Jakub Kicinski
1 sibling, 0 replies; 17+ messages in thread
From: Jakub Kicinski @ 2026-06-13 22:06 UTC (permalink / raw)
To: javen_xu
Cc: Jakub Kicinski, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, pabeni, maxime.chevallier, horms, netdev, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
r8169: add ltr support for RTL8116af
This patch adds LTR programming for RTL8116af so the link can enter L1.2,
which is needed for the system to reach C10. The change extends
rtl_enable_ltr() with a new case for RTL_GIGA_MAC_VER_52, adds a global
SEND_LTR_MSG write at the end of rtl_enable_ltr(), and modifies several
unrelated registers in rtl_hw_start_8117().
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index a73c0215b240..a49e7a8d8f4d 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
> @@ -349,11 +349,13 @@ enum rtl_registers {
> ALDPS_LTR = 0xe0a2,
> LTR_OBFF_LOCK = 0xe032,
> LTR_SNOOP = 0xe034,
> + SEND_LTR_MSG = 0xe038,
>
> #define ALDPS_LTR_EN BIT(0)
> #define LTR_OBFF_LOCK_EN BIT(0)
> #define LINK_SPEED_CHANGE_EN BIT(14)
> #define LTR_SNOOP_EN GENMASK(15, 14)
> +#define LTR_MSG_EN BIT(0)
> };
>
> enum rtl8168_8101_registers {
> @@ -3153,8 +3155,22 @@ static void rtl_enable_ltr(struct rtl8169_private *tp)
> r8168_mac_ocp_write(tp, 0xcdf2, 0x9003);
> r8168_mac_ocp_modify(tp, LTR_OBFF_LOCK, 0x0000, LINK_SPEED_CHANGE_EN);
> break;
> - case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
> case RTL_GIGA_MAC_VER_52:
> + r8168_mac_ocp_write(tp, 0xcdd0, 0x9003);
> + r8168_mac_ocp_modify(tp, LTR_SNOOP, 0x0000, LTR_SNOOP_EN);
> + r8168_mac_ocp_write(tp, 0xe02c, 0x1880);
> + r8168_mac_ocp_write(tp, 0xe02e, 0x4880);
> + r8168_mac_ocp_modify(tp, ALDPS_LTR, 0x0000, ALDPS_LTR_EN);
> + r8168_mac_ocp_write(tp, 0xcdd8, 0x9003);
> + r8168_mac_ocp_write(tp, 0xcdda, 0x9003);
> + r8168_mac_ocp_write(tp, 0xcddc, 0x9003);
> + r8168_mac_ocp_write(tp, 0xcdd2, 0x883c);
> + r8168_mac_ocp_write(tp, 0xcdd4, 0x8c12);
> + r8168_mac_ocp_write(tp, 0xcdd6, 0x9003);
> + r8168_mac_ocp_write(tp, 0xe0a6, 0x9003);
> + r8168_mac_ocp_write(tp, 0xe0a8, 0x9003);
> + break;
[High]
The new case is selected purely on tp->mac_version == RTL_GIGA_MAC_VER_52,
but per the chip_info table that mac_version covers both the existing
RTL8168fp/RTL8117 and the new RTL8116af. Should this case use
rtl_is_8116af() to distinguish the two, the way other 8116af-specific
paths in the driver already do?
Two behavioural deltas show up for RTL8117 with this change:
- the previous (VER_46..VER_48 | VER_52) fallthrough caused VER_52
to execute
RTL_W8(tp, COMBO_LTR_EXTEND,
RTL_R8(tp, COMBO_LTR_EXTEND) | COMBO_LTR_EXTEND_EN);
which is silently dropped for VER_52 here.
- new writes to OCP 0xe0a6 and 0xe0a8 are now performed on every
VER_52 device, including pre-existing RTL8117 hardware that the
old code never poked.
Is the intent that the new sequence supersedes the old RTL8117 LTR
programming for all VER_52 chips, or only for RTL8116af? If the former,
could the commit message explain why the existing RTL8117 sequence was
wrong, and should this be a separate patch?
> + case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
> r8168_mac_ocp_modify(tp, ALDPS_LTR, 0x0000, ALDPS_LTR_EN);
> RTL_W8(tp, COMBO_LTR_EXTEND, RTL_R8(tp, COMBO_LTR_EXTEND) | COMBO_LTR_EXTEND_EN);
> fallthrough;
> @@ -3174,6 +3190,7 @@ static void rtl_enable_ltr(struct rtl8169_private *tp)
> }
> /* chip can trigger LTR */
> r8168_mac_ocp_modify(tp, LTR_OBFF_LOCK, 0x0003, LTR_OBFF_LOCK_EN);
> + r8168_mac_ocp_modify(tp, SEND_LTR_MSG, 0x0000, LTR_MSG_EN);
> }
[Medium]
This new write sits after the switch, so it runs for every case that
does not hit the default return: VER_46..VER_48, VER_51, VER_52,
VER_61..VER_66, VER_70 and VER_80. The commit message describes the
change as RTL8116af-specific, but the resulting behaviour pokes
SEND_LTR_MSG bit 0 on every modern Realtek MAC handled by this driver.
Does OCP 0xe038 bit 0 have identical "send LTR message enable"
semantics across the entire MAC_VER_46..LAST range, or has this only
been validated on RTL8116af? If the former, could that be stated in
the commit message; if the latter, should the write be gated on the
chip family it was tested on?
[ ... ]
> @@ -3732,7 +3751,9 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
>
> rtl_eri_set_bits(tp, 0xd4, 0x0010);
>
> - rtl_eri_write(tp, 0x5f0, ERIAR_MASK_0011, 0x4f87);
> + rtl_eri_write(tp, 0x5f0, ERIAR_MASK_0011, 0x4000);
> +
> + r8168_mac_ocp_write(tp, 0xe098, 0xc302);
>
> rtl_disable_rxdvgate(tp);
[High]
rtl_hw_start_8117() is dispatched for the entire VER_52 family
without any rtl_is_8116af() gating, so these changes affect existing
RTL8168fp/RTL8117 hardware and not just RTL8116af. None of the
modified registers (0x5f0, 0xe098, 0xea80, 0xe052, 0xd420) appear to
be LTR registers, yet the commit subject and body only discuss LTR
support for RTL8116af.
Could the commit message explain what each of these register changes
does and confirm they have been tested on existing RTL8117 hardware?
Should they be split into separate patches with their own
justifications?
>
> @@ -3759,9 +3780,9 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
> }
>
> r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
> - r8168_mac_ocp_write(tp, 0xea80, 0x0003);
> - r8168_mac_ocp_modify(tp, 0xe052, 0x0000, 0x0009);
> - r8168_mac_ocp_modify(tp, 0xd420, 0x0fff, 0x045f);
> + r8168_mac_ocp_write(tp, 0xea80, 0x0000);
> + r8168_mac_ocp_modify(tp, 0xe052, 0x0009, 0x0000);
> + r8168_mac_ocp_modify(tp, 0xd420, 0x0fff, 0x045f);
[High]
The 0xe052 modify call has its mask and set arguments swapped:
- r8168_mac_ocp_modify(tp, 0xe052, 0x0000, 0x0009);
+ r8168_mac_ocp_modify(tp, 0xe052, 0x0009, 0x0000);
r8168_mac_ocp_modify() applies (data & ~mask) | set, so the old call
set bits 0 and 3 of 0xe052 while the new call clears them. Is this
semantic inversion intentional, and if so could the commit message
say so explicitly? On what hardware was the new behaviour validated,
given that VER_52 covers both RTL8168fp/RTL8117 and the new
RTL8116af?
>
> r8168_mac_ocp_write(tp, 0xe63e, 0x0001);
> r8168_mac_ocp_write(tp, 0xe63e, 0x0000);
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-06-13 22:06 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-11 9:43 [PATCH net-next v2 0/6] r8169: add support for phylink javen
2026-06-11 9:43 ` [PATCH net-next v2 1/6] r8169: add speed in private struct javen
2026-06-11 9:43 ` [PATCH net-next v2 2/6] r8169: create a virtual interrupt for linkchg javen
2026-06-13 22:05 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2026-06-11 9:43 ` [PATCH net-next v2 3/6] r8169: add support for phylink javen
2026-06-12 8:13 ` Maxime Chevallier
2026-06-13 22:05 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2026-06-11 9:43 ` [PATCH net-next v2 4/6] r8169: add support for RTL8116af javen
2026-06-13 22:06 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2026-06-11 9:43 ` [PATCH net-next v2 5/6] r8169: add ltr " javen
2026-06-13 22:06 ` Jakub Kicinski
2026-06-13 22:06 ` Jakub Kicinski
2026-06-11 9:43 ` [PATCH net-next v2 6/6] r8169: fix RTL8116af can not enter s0idle and c10 javen
2026-06-13 22:06 ` Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox