RK3568 + RTL8211F 网络唤醒(WOL)功能适配全记录

目录

前言

[1. WOL 技术介绍](#1. WOL 技术介绍)

[1.1 什么是 WOL](#1.1 什么是 WOL)

[1.2 Magic Packet 格式](#1.2 Magic Packet 格式)

[1.3 WOL 触发条件](#1.3 WOL 触发条件)

[2. 硬件平台与方案概述](#2. 硬件平台与方案概述)

[2.1 硬件平台](#2.1 硬件平台)

[2.2 方案概述](#2.2 方案概述)

[3. 硬件原理分析](#3. 硬件原理分析)

[3.1 RTL8211F INTB/PMEB 引脚](#3.1 RTL8211F INTB/PMEB 引脚)

[3.2 PMEB 引脚特性](#3.2 PMEB 引脚特性)

[3.3 硬件连接示意](#3.3 硬件连接示意)

[3.4 关键时序](#3.4 关键时序)

[4. 设备树配置](#4. 设备树配置)

[4.1 电源管理配置](#4.1 电源管理配置)

[4.2 休眠/唤醒配置](#4.2 休眠/唤醒配置)

[4.3 GMAC 节点配置](#4.3 GMAC 节点配置)

[4.4 GPIO 按键节点](#4.4 GPIO 按键节点)

[5. 驱动修改](#5. 驱动修改)

[5.1 添加模拟电源按下函数](#5.1 添加模拟电源按下函数)

[5.2 修改 stmmac 网卡驱动](#5.2 修改 stmmac 网卡驱动)

[5.2.1 添加头文件](#5.2.1 添加头文件)

[5.2.2 添加 WOL 中断处理函数](#5.2.2 添加 WOL 中断处理函数)

[5.2.3 修改 stmmac_open 函数](#5.2.3 修改 stmmac_open 函数)

[5.2.4 修改 stmmac_release 函数](#5.2.4 修改 stmmac_release 函数)

[5.2.5 修改 stmmac_suspend 函数](#5.2.5 修改 stmmac_suspend 函数)

[5.2.6 修改 stmmac_resume 函数](#5.2.6 修改 stmmac_resume 函数)

[5.2.7 修改 probe/remove 函数](#5.2.7 修改 probe/remove 函数)

[5.3 修改平台文件](#5.3 修改平台文件)

[5.4 修改 stmmac.h](#5.4 修改 stmmac.h)

[5.5 修改 RTL8211F PHY 驱动](#5.5 修改 RTL8211F PHY 驱动)

[5.5.1 RTL8211F WOL 寄存器配置](#5.5.1 RTL8211F WOL 寄存器配置)

[5.5.2 实现代码](#5.5.2 实现代码)

[5.5.3 修改 RTL8211F 驱动结构](#5.5.3 修改 RTL8211F 驱动结构)

[6. 调试过程与问题解决](#6. 调试过程与问题解决)

总结


前言

在嵌入式 Linux 系统的开发中,功耗管理是一个重要的课题。网络唤醒(WOL)功能允许设备在休眠状态下通过接收特定的网络数据包被唤醒,从而在保持低功耗的同时保持网络可达性。近期在基于 RK3568 平台的项目中,需要在 Android13 SDK 环境下为 RTL8211F 网卡 PHY 芯片实现 WOL 功能,本文将完整记录整个适配过程,希望能够帮助到有类似需求的朋友。


1. WOL 技术介绍

1.1 什么是 WOL

Wake-On-LAN 是一种通过网络唤醒处于休眠或关机状态计算机的技术。它通过向目标设备发送一个称为 Magic Packet 的特殊广播帧来触发唤醒。

1.2 Magic Packet 格式

Magic Packet 的格式如下:

复制代码
6 个字节的 FF FF FF FF FF FF
+ 16 次重复的目标 MAC 地址
+ 可选的数据(4 或 6 字节的密码)

例如,目标 MAC 地址为 00:11:22:33:44:55 的 Magic Packet 结构为:

复制代码
FF FF FF FF FF FF 00 11 22 33 44 55 00 11 22 33 44 55 ... (重复 16 次)

1.3 WOL 触发条件

以 RTL8211F 为例,WOL 事件的触发需要满足以下条件:

  1. 目的地址匹配:收到的 Magic Packet 必须是广播、组播或单播到 PHY 地址的数据包

  2. 无 CRC 错误:数据包通过 CRC 校验

  3. 模式匹配 :Magic Packet 格式正确(6 个 0xFF + 16 次 MAC 地址)

2. 硬件平台与方案概述

2.1 硬件平台

组件 型号/规格
SoC Rockchip RK3568
内核版本 Linux 5.10 (Android13 SDK)
以太网 MAC Synopsys DesignWare GMAC (stmmac)
以太网 PHY Realtek RTL8211F

2.2 方案概述

RTL8211F 的 WOL 功能通过 PMEB 引脚(Power Management Event B)实现。当 PHY 检测到 Magic Packet 时,PMEB 引脚会从高电平跳变为低电平,产生一个下降沿信号。该信号连接到 RK3568 的 GPIO 引脚,触发 GPIO 中断,从而唤醒系统。

3. 硬件原理分析

3.1 RTL8211F INTB/PMEB 引脚

RTL8211F 的 Pin 31 是 INTB/PMEB 复用引脚,通过寄存器 Page 0xd40, Register 22, bit[5] 配置功能:

bit5 功能
0 INTB 模式(中断引脚,默认)
1 PMEB 模式(电源管理事件引脚)

重要:在 WOL 场景下,必须将引脚配置为 PMEB 模式。

3.2 PMEB 引脚特性

特性 描述
默认状态 高电平(通过 4.7kΩ 电阻上拉至 3.3V)
触发方式 下降沿触发(从高到低跳变)
触发后状态 保持低电平,直到软件清除 WOL 状态

3.3 硬件连接示意

重要:需要用RK3568的GPIO0组引脚做唤醒引脚。

3.4 关键时序

复制代码
正常状态:  PMEB 高电平 ──────────────────────────
                                      │
收到 Magic Packet:                     ↓
                                      │
PMEB 低电平:  ──────────────────────────── 下降沿触发中断
                                      │
软件清除后:   ────────────────────────── 恢复高电平

4. 设备树配置

4.1 电源管理配置

休眠时需要保持相关电源供电,确保 PHY 能正常工作,包含

1.PHY即RTL8211F的供电保存

2.PHY芯片的复位引脚保持

3.PHY芯片需要用独立晶振方案

4.vdd_log电源保持即:DCDC_REG1

rk3568-evb.dts中修改

复制代码
vdd_logic: DCDC_REG1 {
    regulator-state-mem {
       regulator-on-in-suspend;    // 休眠时保持逻辑电源
    };
};

vcc_3v3: SWITCH_REG1 {
    regulator-state-mem {
       regulator-on-in-suspend;    // 休眠时保持 3.3V 电源,PHY供电
    };
};

vccio_sd: LDO_REG5 {
	regulator-state-mem {
		regulator-on-in-suspend;   //PHY芯片的复位引脚所在IO组
	};
};

4.2 休眠/唤醒配置

rk3568.dtsi中修改

复制代码
rockchip_suspend: rockchip-suspend {
    rockchip,sleep-mode-config = <
        (0
-       | RKPM_SLP_ARMOFF_LOGOFF    // 注释掉,保持 CPU 核心唤醒能力
+       /*| RKPM_SLP_ARMOFF_LOGOFF*/
        | RKPM_SLP_CENTER_OFF
        | RKPM_SLP_HW_PLLS_OFF
        | RKPM_SLP_PMUALIVE_32K
-       | RKPM_SLP_OSC_DIS          // 注释掉,保持振荡器工作
+       /*| RKPM_SLP_OSC_DIS*/
        | RKPM_SLP_PMIC_LP
        | RKPM_SLP_32K_PVTM
        )
    >;
    rockchip,wakeup-config = <
        (0
        | RKPM_GPIO_WKUP_EN
+       | RKPM_CPU0_WKUP_EN         // 允许 CPU0 被 GPIO 唤醒
+       | RKPM_CPU2_WKUP_EN         // 允许 CPU2 被 GPIO 唤醒
        )
    >;
};

4.3 GMAC 节点配置

GMAC1 节点(主网卡),板级设备树文件

cpp 复制代码
&gmac1 {
        phy-mode = "rgmii";
        clock_in_out = "input";
        snps,reset-gpio = <&gpio3 RK_PB0 GPIO_ACTIVE_LOW>;
        snps,reset-active-low;
        snps,reset-delays-us = <0 20000 100000>;
        assigned-clocks = <&cru SCLK_GMAC1_RX_TX>, <&cru SCLK_GMAC1>;
        assigned-clock-parents = <&cru SCLK_GMAC1_RGMII_SPEED>, <&cru CLK_MAC1_2TOP>;
        assigned-clock-rates = <0>, <125000000>;
        wakeup-source;
        pinctrl-names = "default"; 
        pinctrl-0 = <&gmac1m1_miim
                     &gmac1m1_tx_bus2
                     &gmac1m1_rx_bus2
                     &gmac1m1_rgmii_clk
                     &gmac1m1_rgmii_bus>;
        pinctrl-1 = <&gmac1_pmeb_gpios>;                  //增加唤醒引脚 
	    wolirq-gpio = <&gpio0 RK_PB3 GPIO_ACTIVE_LOW>;    //增加唤醒引脚    

        tx_delay = <0x4f>;
        rx_delay = <0x26>;
        phy-handle = <&rgmii_phy1>;
	    phy-supply = <&vcc_3v3>;
        status = "okay";
};

Pinctrl 配置

复制代码
&pinctrl {
    gmac-wol {
     	gmac1_pmeb_gpios:  gmac1-pmeb-gpios {
             rockchip,pins = <0 RK_PB3 RK_FUNC_GPIO &pcfg_pull_none>;
   	    };
	};
};

4.4 GPIO 按键节点

用于模拟电源键唤醒,在 rk3568-evb.dtsi 中添加:

复制代码
keys: keys {
     compatible = "gpio-keys";
     status = "okay";
};

5. 驱动修改

5.1 添加模拟电源按下函数

为了在 WOL 中断触发时模拟按下电源键,需要在 gpio-keys 驱动中导出一个发送电源键事件的函数:

cpp 复制代码
// kernel-5.10/drivers/input/keyboard/gpio_keys.c

+ static struct input_dev *sinput_dev;

+ void rk_send_power_key(int state)
+ {
+     if (!sinput_dev) {
+         printk("wol_debug: rk_send_power_key ERROR - sinput_dev is NULL!\n");
+         return;
+     }

+     if (state) {
+         input_report_key(sinput_dev, KEY_POWER, 1);
+         input_sync(sinput_dev);
+         printk("wol_debug: rk_send_power_key - KEY_POWER pressed\n");
+     } else {
+         input_report_key(sinput_dev, KEY_POWER, 0);
+         input_sync(sinput_dev);
+         printk("wol_debug: rk_send_power_key - KEY_POWER released\n");
+     }
+ }
+ EXPORT_SYMBOL(rk_send_power_key);

// 在 probe 函数中保存 input_dev
+ static int gpio_keys_probe(...)
+ {
    // ...
+     ddata->input = input;
+    sinput_dev = input;
    // ...
+ }

5.2 修改 stmmac 网卡驱动

修改以下路径文件

cpp 复制代码
kernel-5.10/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c

5.2.1 添加头文件

cpp 复制代码
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/rk_keys.h>

5.2.2 添加 WOL 中断处理函数

cpp 复制代码
extern void rk_send_power_key(int state);
static irqreturn_t wol_io_isr(int irq, void *dev_id)
{
	struct net_device *dev = (struct net_device *)dev_id;
	struct stmmac_priv *priv = netdev_priv(dev);

	struct phy_device *phydev = dev ? dev->phydev : NULL;      
        int value;                            
	/* struct net_device *dev = (struct net_device *)dev_id;
	struct stmmac_priv *priv = netdev_priv(dev); */
	// 先禁用中断,防止风暴
        disable_irq_nosync(irq);
	printk("wol_debug:Enter 520 - 4  wol_io_isr0");
	// ========== 添加调试信息 ==========
    	printk("wol_debug: ===== WOL INTERRUPT TRIGGERED! irq=%d =====\n", irq);
    	if (priv->plat->wolirq_io > 0) {
        	int val = gpio_get_value(priv->plat->wolirq_io);
        	printk("wol_debug: wol_io_isr, GPIO %d value = %d\n",
               		priv->plat->wolirq_io, val);
    	}
    // ====================================
	// ========== 清除 PHY 的 WOL 状态,让 PMEB 恢复高电平 ==========
        if (phydev) {
            	printk("wol_debug: Clearing PHY WOL status\n");
            	// 清除 WOL 事件
            	phy_write(phydev, 31, 0x0d8a);
            	phy_write(phydev, 16, 0x0);  // 禁用 WOL 事件
            	// 复位 WOL
            	phy_write(phydev, 31, 0x0d8a);
            	value = phy_read(phydev, 17);
            	phy_write(phydev, 17, value | BIT(15));  // 复位
            	phy_write(phydev, 31, 0x0d8a);
            	value = phy_read(phydev, 17);
            	phy_write(phydev, 17, value & (~BIT(15)));  // 清除复位
            	// 重新启用 WOL 事件(如果需要后续再次唤醒)
            	phy_write(phydev, 31, 0x0d8a);
            	phy_write(phydev, 16, 0x1000);
            	phy_write(phydev, 31, 0xa42);
            	printk("wol_debug: PHY WOL status cleared\n");
        }
    	// ===========================================================
    
    	// 清除 GPIO 中断状态(如果有边沿触发,可能需要读寄存器)
    	// 对于 GPIO 中断,读取 GPIO 状态可以清除中断
    	if (priv->plat->wolirq_io > 0) {
        	gpio_get_value(priv->plat->wolirq_io);
    	}
	
	wake_lock_timeout(&priv->plat->wol_wake_lock, msecs_to_jiffies(8000));

	//rk_send_wakeup_key();
	//pm_wakeup_event(priv->device, 0);
	rk_send_power_key(1);
	rk_send_power_key(0);
	rk_send_power_key(1);
	printk("wol_debug:Enter wol_io_isr 1");
	return IRQ_HANDLED;
}

5.2.3 修改 stmmac_open 函数

在网卡打开时注册 WOL GPIO 中断:

cpp 复制代码
static int stmmac_open(struct net_device *dev)
{
	struct stmmac_priv *priv = netdev_priv(dev);
	int bfsize = 0;
	u32 chan;
	int ret;

	ret = pm_runtime_get_sync(priv->device);
	if (ret < 0) {
		pm_runtime_put_noidle(priv->device);
		return ret;
	}

	if (priv->hw->pcs != STMMAC_PCS_TBI &&
	    priv->hw->pcs != STMMAC_PCS_RTBI &&
	    priv->hw->xpcs == NULL) {
		ret = stmmac_init_phy(dev);
		if (ret) {
			netdev_err(priv->dev,
				   "%s: Cannot attach to PHY (error: %d)\n",
				   __func__, ret);
			goto init_phy_error;
		}
	}

	/* Extra statistics */
	memset(&priv->xstats, 0, sizeof(struct stmmac_extra_stats));
	priv->xstats.threshold = tc;

	bfsize = stmmac_set_16kib_bfsize(priv, dev->mtu);
	if (bfsize < 0)
		bfsize = 0;

	if (bfsize < BUF_SIZE_16KiB)
		bfsize = stmmac_set_bfsize(dev->mtu, priv->dma_buf_sz);

	priv->dma_buf_sz = bfsize;
	buf_sz = bfsize;

	priv->rx_copybreak = STMMAC_RX_COPYBREAK;

	if (!priv->dma_tx_size)
		priv->dma_tx_size = priv->plat->dma_tx_size ? priv->plat->dma_tx_size :
				    DMA_DEFAULT_TX_SIZE;

	if (!priv->dma_rx_size)
		priv->dma_rx_size = priv->plat->dma_rx_size ? priv->plat->dma_rx_size :
				    DMA_DEFAULT_RX_SIZE;

	/* Earlier check for TBS */
	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) {
		struct stmmac_tx_queue *tx_q = &priv->tx_queue[chan];
		int tbs_en = priv->plat->tx_queues_cfg[chan].tbs_en;

		/* Setup per-TXQ tbs flag before TX descriptor alloc */
		tx_q->tbs |= tbs_en ? STMMAC_TBS_AVAIL : 0;
	}

	ret = alloc_dma_desc_resources(priv);
	if (ret < 0) {
		netdev_err(priv->dev, "%s: DMA descriptors allocation failed\n",
			   __func__);
		goto dma_desc_error;
	}

	ret = init_dma_desc_rings(dev, GFP_KERNEL);
	if (ret < 0) {
		netdev_err(priv->dev, "%s: DMA descriptors initialization failed\n",
			   __func__);
		goto init_error;
	}

	if (priv->plat->serdes_powerup) {
		ret = priv->plat->serdes_powerup(dev, priv->plat->bsp_priv);
		if (ret < 0) {
			netdev_err(priv->dev, "%s: Serdes powerup failed\n",
				   __func__);
			goto init_error;
		}
	}

	ret = stmmac_hw_setup(dev, true);
	if (ret < 0) {
		netdev_err(priv->dev, "%s: Hw setup failed\n", __func__);
		goto init_error;
	}

    /* ==========  新增在这里添加 WOL GPIO 中断申请 ========== */
    if (priv->plat->wolirq_io > 0) {
        printk("wol_debug: stmmac_open - wolirq_io = %d\n", priv->plat->wolirq_io);
	    ret = devm_gpio_request(priv->device, priv->plat->wolirq_io, "gmac_wol_io");
        if (ret) {
            pr_err("%s: ERROR: failed to request WOL GPIO %d, err: %d\n",
                    __func__, priv->plat->wolirq_io, ret);
        } else {
			int val = gpio_get_value(priv->plat->wolirq_io);
            printk("wol_debug: stmmac_open - GPIO %d requested, initial value = %d\n",
                   priv->plat->wolirq_io, val);
			// 设置 GPIO 为输入
            gpio_direction_input(priv->plat->wolirq_io);
            priv->plat->wol_irq = gpio_to_irq(priv->plat->wolirq_io);
            // 设置中断触发方式为下降沿
            irq_set_irq_type(priv->plat->wol_irq, IRQF_TRIGGER_FALLING);
			ret = devm_request_irq(priv->device, priv->plat->wol_irq, wol_io_isr,
                                  IRQF_SHARED | IRQF_TRIGGER_FALLING, "gmac_wol_io_irq", dev);
            if (ret) {
                pr_err("%s: ERROR: request wol io irq fail: %d", __func__, ret);
                devm_gpio_free(priv->device, priv->plat->wolirq_io);
            } else {
                /* fixed first enable_irq crash issue */
                disable_irq(priv->plat->wol_irq);
                enable_irq(priv->plat->wol_irq);
                disable_irq(priv->plat->wol_irq);
            }
        }
    }
    /* ==========  添加结束 ========== */

	stmmac_init_coalesce(priv);

	phylink_start(priv->phylink);
	/* We may have called phylink_speed_down before */
	phylink_speed_up(priv->phylink);

	/* Request the IRQ lines */
	ret = request_irq(dev->irq, stmmac_interrupt,
			  IRQF_SHARED, dev->name, dev);
	if (unlikely(ret < 0)) {
		netdev_err(priv->dev,
			   "%s: ERROR: allocating the IRQ %d (error: %d)\n",
			   __func__, dev->irq, ret);
		goto irq_error;
	}

	/* Request the Wake IRQ in case of another line is used for WoL */
	if (priv->wol_irq != dev->irq) {
		ret = request_irq(priv->wol_irq, stmmac_interrupt,
				  IRQF_SHARED, dev->name, dev);
		if (unlikely(ret < 0)) {
			netdev_err(priv->dev,
				   "%s: ERROR: allocating the WoL IRQ %d (%d)\n",
				   __func__, priv->wol_irq, ret);
			goto wolirq_error;
		}
	}

	/* Request the IRQ lines */
	if (priv->lpi_irq > 0) {
		ret = request_irq(priv->lpi_irq, stmmac_interrupt, IRQF_SHARED,
				  dev->name, dev);
		if (unlikely(ret < 0)) {
			netdev_err(priv->dev,
				   "%s: ERROR: allocating the LPI IRQ %d (%d)\n",
				   __func__, priv->lpi_irq, ret);
			goto lpiirq_error;
		}
	}

	stmmac_enable_all_queues(priv);
	netif_tx_start_all_queues(priv->dev);

	return 0;

lpiirq_error:
	if (priv->wol_irq != dev->irq)
		free_irq(priv->wol_irq, dev);
wolirq_error:
	free_irq(dev->irq, dev);
irq_error:
	phylink_stop(priv->phylink);

	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
		del_timer_sync(&priv->tx_queue[chan].txtimer);

	stmmac_hw_teardown(dev);
init_error:
	free_dma_desc_resources(priv);
dma_desc_error:
	phylink_disconnect_phy(priv->phylink);
init_phy_error:
	pm_runtime_put(priv->device);
	return ret;
}

5.2.4 修改 stmmac_release 函数

在网卡关闭时释放资源:

cpp 复制代码
static int stmmac_release(struct net_device *dev)
{
	struct stmmac_priv *priv = netdev_priv(dev);
	u32 chan;

	if (device_may_wakeup(priv->device))
		phylink_speed_down(priv->phylink, false);
	/* Stop and disconnect the PHY */
	phylink_stop(priv->phylink);
	phylink_disconnect_phy(priv->phylink);

	if (priv->plat->integrated_phy_power)
		priv->plat->integrated_phy_power(priv->plat->bsp_priv, false);

	stmmac_disable_all_queues(priv);

	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
		del_timer_sync(&priv->tx_queue[chan].txtimer);

	/* Free the IRQ lines */
	free_irq(dev->irq, dev);
	if (priv->wol_irq != dev->irq)
		free_irq(priv->wol_irq, dev);
	if (priv->lpi_irq > 0)
		free_irq(priv->lpi_irq, dev);

	if (priv->eee_enabled) {
		priv->tx_path_in_lpi_mode = false;
		del_timer_sync(&priv->eee_ctrl_timer);
	}

	/* Stop TX/RX DMA and clear the descriptors */
	stmmac_stop_all_dma(priv);

	/* Release and free the Rx/Tx resources */
	free_dma_desc_resources(priv);

	/* Disable the MAC Rx/Tx */
	stmmac_mac_set(priv, priv->ioaddr, false);

	/* Powerdown Serdes if there is */
	if (priv->plat->serdes_powerdown)
		priv->plat->serdes_powerdown(dev, priv->plat->bsp_priv);

	netif_carrier_off(dev);

	stmmac_release_ptp(priv);

	pm_runtime_put(priv->device);

	/* ==========  在这里添加释放 WOL 资源 ========== */
    if (priv->plat->wol_irq > 0)
        free_irq(priv->plat->wol_irq, dev);

    if (priv->plat->wolirq_io > 0)
        gpio_free(priv->plat->wolirq_io);
    /* ==========  添加结束 ========== */

	return 0;
}

5.2.5 修改 stmmac_suspend 函数

关键:在休眠时手动调用 PHY suspend 配置 WOL,并启用唤醒中断。

cpp 复制代码
int stmmac_suspend(struct device *dev)
{
	struct net_device *ndev = dev_get_drvdata(dev);
	struct stmmac_priv *priv = netdev_priv(ndev);
	u32 chan;

	if (!ndev || !netif_running(ndev))
		return 0;

	phylink_mac_change(priv->phylink, false);

	mutex_lock(&priv->lock);

	netif_device_detach(ndev);

	stmmac_disable_all_queues(priv);

	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
		del_timer_sync(&priv->tx_queue[chan].txtimer);

	if (priv->eee_enabled) {
		priv->tx_path_in_lpi_mode = false;
		del_timer_sync(&priv->eee_ctrl_timer);
	}

	/* Stop TX/RX DMA */
	stmmac_stop_all_dma(priv);

	if (priv->plat->serdes_powerdown)
		priv->plat->serdes_powerdown(ndev, priv->plat->bsp_priv);

	/* Enable Power down mode by programming the PMT regs */
	if (device_may_wakeup(priv->device) && priv->plat->pmt) {
		stmmac_pmt(priv, priv->hw, priv->wolopts);
		priv->irq_wake = 1;
	} else {
		mutex_unlock(&priv->lock);
		rtnl_lock();
		if (device_may_wakeup(priv->device))
			phylink_speed_down(priv->phylink, false);
		if (priv->plat->integrated_phy_power)
			priv->plat->integrated_phy_power(priv->plat->bsp_priv,
							 false);
		phylink_stop(priv->phylink);
		rtnl_unlock();
		mutex_lock(&priv->lock);

		stmmac_mac_set(priv, priv->ioaddr, false);
		pinctrl_pm_select_sleep_state(priv->device);
	}
	mutex_unlock(&priv->lock);

	priv->speed = SPEED_UNKNOWN;

    // ========== 新增:手动调用 PHY suspend ==========
    if (ndev && ndev->phydev) {
        struct phy_device *phydev = ndev->phydev;
        if (phydev->drv && phydev->drv->suspend) {
            	printk("wol_debug: stmmac_suspend - Manually calling PHY suspend\n");
            	phydev->drv->suspend(phydev);
        }
    }
    // ================================================

	// ========== 添加调试信息 ==========
    if(!priv->plat->is_in_suspend){
        printk("wol_debug: stmmac_suspend - enabling WOL irq %d\n", priv->plat->wol_irq);
        if (priv->plat->wolirq_io > 0) {
           	int val = gpio_get_value(priv->plat->wolirq_io);
            	printk("wol_debug: stmmac_suspend - GPIO %d value = %d (PMEB pin level)\n", 
                   		priv->plat->wolirq_io, val);
        }
        enable_irq(priv->plat->wol_irq);
        enable_irq_wake(priv->plat->wol_irq);
        priv->plat->is_in_suspend = true;
        printk("wol_debug: stmmac_suspend - wake enabled\n");
    }
    // ====================================
	return 0;
}

5.2.6 修改 stmmac_resume 函数

cpp 复制代码
int stmmac_resume(struct device *dev)
{
	struct net_device *ndev = dev_get_drvdata(dev);
	struct stmmac_priv *priv = netdev_priv(ndev);
	int ret;

	if (!netif_running(ndev))
		return 0;

	/* Power Down bit, into the PM register, is cleared
	 * automatically as soon as a magic packet or a Wake-up frame
	 * is received. Anyway, it's better to manually clear
	 * this bit because it can generate problems while resuming
	 * from another devices (e.g. serial console).
	 */
	if (device_may_wakeup(priv->device) && priv->plat->pmt) {
		mutex_lock(&priv->lock);
		stmmac_pmt(priv, priv->hw, 0);
		mutex_unlock(&priv->lock);
		priv->irq_wake = 0;
	} else {
		pinctrl_pm_select_default_state(priv->device);
		/* reset the phy so that it's ready */
		if (priv->mii)
			stmmac_mdio_reset(priv->mii);
		if (priv->plat->integrated_phy_power)
			priv->plat->integrated_phy_power(priv->plat->bsp_priv,
							 true);
	}

	if (priv->plat->serdes_powerup) {
		ret = priv->plat->serdes_powerup(ndev,
						 priv->plat->bsp_priv);

		if (ret < 0)
			return ret;
	}

	if (!device_may_wakeup(priv->device) || !priv->plat->pmt) {
		rtnl_lock();
		phylink_start(priv->phylink);
		/* We may have called phylink_speed_down before */
		phylink_speed_up(priv->phylink);
		rtnl_unlock();
	}

	rtnl_lock();
	mutex_lock(&priv->lock);

	stmmac_reset_queues_param(priv);

	stmmac_free_tx_skbufs(priv);
	stmmac_clear_descriptors(priv);

	stmmac_hw_setup(ndev, false);
	stmmac_init_coalesce(priv);
	stmmac_set_rx_mode(ndev);

	stmmac_restore_hw_vlan_rx_fltr(priv, ndev, priv->hw);

	stmmac_enable_all_queues(priv);

	mutex_unlock(&priv->lock);
	rtnl_unlock();

	phylink_mac_change(priv->phylink, true);

	netif_device_attach(ndev);


	// ==========新增 ==========
    if(priv->plat->is_in_suspend){
        printk("wol_debug: stmmac_resume - disabling WOL irq %d\n", priv->plat->wol_irq);
        disable_irq(priv->plat->wol_irq);
        disable_irq_wake(priv->plat->wol_irq);
        priv->plat->is_in_suspend = false;
        printk("wol_debug: stmmac_resume - wake disabled\n");
	// 重新启用中断(如果之前在 ISR 中禁用了)
        enable_irq(priv->plat->wol_irq);
    }
    // ====================================
	return 0;
}

5.2.7 修改 probe/remove 函数

在驱动加载/卸载时初始化/销毁 wake_lock

cpp 复制代码
// stmmac_dvr_probe 中
wake_lock_init(&priv->plat->wol_wake_lock, WAKE_LOCK_SUSPEND, "wol_wake_lock");

// stmmac_dvr_remove 中
wake_lock_destroy(&priv->plat->wol_wake_lock);

5.3 修改平台文件

修改以下路径文件

cpp 复制代码
kernel-5.10/drivers/net/ethernet/stmicro/stmmac/stmmac_main.cstmmac_platform.c

来解析设备树中的 wolirq-gpio 属性:

cpp 复制代码
// 添加头文件
#include <linux/of_gpio.h>

// 在 stmmac_probe_config_dt 中
enum of_gpio_flags flags;

// ...
plat->wolirq_io = of_get_named_gpio_flags(np, "wolirq-gpio", 0, &flags);

5.4 修改 stmmac.h

修改以下路径文件

cpp 复制代码
kernel-5.10/include/linux/stmmac.h

来添加 WOL 相关字段到 plat_stmmacenet_data

cpp 复制代码
struct plat_stmmacenet_data {
    // ...
+   int wolirq_io;
+   bool is_in_suspend;
+   int wol_irq;
+   struct wake_lock wol_wake_lock;
};

5.5 修改 RTL8211F PHY 驱动

realtek.c 中为 RTL8211F 实现专用的 suspend/resume 函数(替代通用的 genphy_suspend):

5.5.1 RTL8211F WOL 寄存器配置

步骤 页 (Reg31) 寄存器 操作 说明
1 0x0d8a 16 写 0x0 禁用 WOL 事件
2 0x0d8a 17 BIT15 置 1 复位 WOL 状态
3 0x0d8a 17 BIT15 置 0 清除复位
4 0x0d8c 16-18 写 MAC 地址 设置 PHY 识别的 MAC 地址
5 0x0d8a 17 写 0x9fff 设置最大包长度
6 0x0d8a 16 写 0x1000 启用 Magic Packet WOL 事件
7 0x0d40 22 BIT5 置 1 INTB 切换为 PMEB 模式
8 0x0d8a 19 BIT15 置 1 禁用 RGMII 引脚(省电)

5.5.2 实现代码

修改以下路径文件

cpp 复制代码
kernel-5.10/drivers/net/phy/realtek.c
cpp 复制代码
static int rtl8211f_suspend(struct phy_device *phydev)
{
    int value;
    struct net_device *ndev = phydev->attached_dev;
    
    printk("wol_debug: ===== rtl8211f_suspend called =====\n");
    
    if (ndev != NULL) {
        // 步骤1:先清除 WOL 状态
        phy_write(phydev, 31, 0x0d8a);
        phy_write(phydev, 16, 0x0);
        phy_write(phydev, 31, 0x0d8a);
        value = phy_read(phydev, 17);
        phy_write(phydev, 17, value | BIT(15));
        printk("wol_debug: WOL cleared\n");
        
        // 步骤2:设置 MAC 地址
        phy_write(phydev, 31, 0x0d8c);
        phy_write(phydev, 16, ((u16)ndev->dev_addr[1] << 8) + ndev->dev_addr[0]);
        phy_write(phydev, 17, ((u16)ndev->dev_addr[3] << 8) + ndev->dev_addr[2]);
        phy_write(phydev, 18, ((u16)ndev->dev_addr[5] << 8) + ndev->dev_addr[4]);
        printk("wol_debug: MAC address set\n");
        
        // 步骤3:设置最大包长度
        phy_write(phydev, 31, 0x0d8a);
        phy_write(phydev, 17, 0x9fff);
        printk("wol_debug: max packet length set\n");
        
        // 步骤4:启用 WOL 事件 (Magic Packet)
        phy_write(phydev, 31, 0x0d8a);
        phy_write(phydev, 16, 0x1000);
        value = phy_read(phydev, 16);
        printk("wol_debug: WOL enable = 0x%04x (expected 0x1000)\n", value);
        
        // 步骤5:最后切换 INTB 为 PMEB 模式
        phy_write(phydev, 31, 0x0d40);
        value = phy_read(phydev, 22);
        printk("wol_debug: INTB before = 0x%04x\n", value);
        phy_write(phydev, 22, value | BIT(5));
        value = phy_read(phydev, 22);
        printk("wol_debug: INTB after = 0x%04x, BIT5=%d\n", value, (value >> 5) & 1);
        
        // 步骤6:禁用 RGMII 引脚(省电)
        phy_write(phydev, 31, 0x0d8a);
        value = phy_read(phydev, 19);
        phy_write(phydev, 19, value | BIT(15));
        printk("wol_debug: RGMII pad disabled\n");
        
        phy_write(phydev, 31, 0xa42);
    } else {
        printk("wol_debug: ndev is NULL, skipping WOL config\n");
    }
    
    printk("wol_debug: ===== rtl8211f_suspend done =====\n");
    return 0;
}

static int rtl8211f_resume(struct phy_device *phydev)
{
    int value;
    printk("wol_debug: ===== rtl8211f_resume called =====\n");
    
    // 步骤1:先恢复 RGMII 引脚
    phy_write(phydev, 31, 0x0d8a);
    value = phy_read(phydev, 19);
    phy_write(phydev, 19, value & (~BIT(15)));
    printk("wol_debug: RGMII pad restored\n");
    
    // 步骤2:PMEB → INTB(先切回中断模式)
    phy_write(phydev, 31, 0x0d40);
    value = phy_read(phydev, 22);
    phy_write(phydev, 22, value & (~BIT(5)));
    printk("wol_debug: INTB restored (PMEB -> INTB)\n");
    
    // 步骤3:清除 WOL 事件和复位
    phy_write(phydev, 31, 0x0d8a);
    phy_write(phydev, 16, 0x0);
    value = phy_read(phydev, 17);
    phy_write(phydev, 17, value & (~BIT(15)));
    printk("wol_debug: WOL cleared\n");
    
    phy_write(phydev, 31, 0xa42);
    msleep(100);
    
    printk("wol_debug: ===== rtl8211f_resume done =====\n");
    return 0;
}

5.5.3 修改 RTL8211F 驱动结构

cpp 复制代码
{
    PHY_ID_MATCH_EXACT(0x001cc916),
    .name           = "RTL8211F Gigabit Ethernet",
    .config_init    = &rtl8211f_config_init,
    .ack_interrupt  = &rtl8211f_ack_interrupt,
    .config_intr    = &rtl8211f_config_intr,
-   .suspend        = genphy_suspend,
-   .resume         = rtl821x_resume,
+   .suspend        = rtl8211f_suspend,
+   .resume         = rtl8211f_resume,
    .read_page      = rtl821x_read_page,
    .write_page     = rtl821x_write_page,
},

注意:这里需要把rtl821x_resume函数注释掉,否则编译会报错rtl821x_resume函数未使用

6. 调试过程与问题解决

1.使能网口WOL,调试没问题可以加到开机服务里去执行

bash 复制代码
echo enabled > /sys/class/net/eth0/device/power/wakeup

确认使能

bash 复制代码
cat /sys/class/net/eth0/device/power/wakeup

2.查看中断是否注册成功

bash 复制代码
cat /proc/interrupts

可以看到如下,表示注册成功了

bash 复制代码
110:          0          0          0          0  rockchip_gpio_irq  11 Edge      gmac_wol_io_irq

3.可以把内核日志缓冲成文件,万一调试错误,导致睡眠后唤不醒可以重启看日志

bash 复制代码
dmesg -w | grep -i wol_debug > /data/wol_log.txt &

4.执行以下命令进入睡眠

bash 复制代码
echo mem > /sys/power/state

会看到日志打印:

bash 复制代码
INFO:    wakeup source config[0x15]:
INFO:           Enable CPU0 interrupt as wakeup source
INFO:           Enable CPU2 interrupt as wakeup source

5.用安装在笔记本上的magic_pkt工具通过网口给RK3568安卓板发送魔术包测试,工具我已打包绑定资源,包括一些官方文档之类。


总结

本文详细介绍了在 RK3568 平台为 RTL8211F PHY 适配 WOL 功能的完整过程,有需要很多注重的细节。本文是经过实际工程验证的,希望能为做类似功能的朋友提供参考,节约时间。

相关推荐
Titan202426 分钟前
HTTPS基础知识梳理
服务器·网络·c++·网络协议·学习·http·https
用户09340777351427 分钟前
HarmonyOS WPS Open SDK 实践:registerApp 鉴权与就绪门禁
android·typescript·harmonyos
jike_202632 分钟前
会议离线录音APP:无网络记录能力对比
android·智能手机·语音识别
自然石人34 分钟前
石都随笔:深耕不张扬,平凡自有千钧力
网络·经验分享·百度·传媒·新浪微博
新时代牛马1 小时前
epoll 源码路径:从epoll_ctl 到ep_poll 的就绪唤醒
网络·数据库·网络协议
简单Janeee1 小时前
[Vue 3 从零到上线]-第九篇:更美更强——引入 UI 库与网络请求 (Axios)
网络·vue.js·ui
学习星球1 小时前
空天地一体化网络(NTN)深度解析:从Starlink D2C到3GPP NTN,卫星直连手机是如何实现的?
网络·人工智能·算法·智能手机·php
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】WebSocket & SSE 协议
网络·c++·websocket·网络协议·学习·面试
xieliyu.1 小时前
计算机网络‑IP 协议解析:核心特性总结
网络·笔记·网络协议·学习·tcp/ip·计算机网络