rk3399 ubuntu 休眠唤醒功能

背景

为了降低功耗,需要考虑使用系统的休眠唤醒功能。

休眠方式

rk3399 休眠方式有两种,mem,freeze.

内存待机 (mem)

在 mem 模式下,系统将所有设备状态保存到内存中,然后进入低功耗状态。唤醒后,系统从内存中恢复设备状态。内存待机模式通常称为 S3 睡眠模式。

冻结 (freeze)

在 freeze 模式下,所有用户空间任务被冻结,所有设备进入低功耗状态,但 CPU 仍保持活跃。冻结模式更适用于在短时间内节省电能。
我们使用 mem模式进入休眠,指令如下:

root@firefly:/sys/power# cat state
freeze mem
root@firefly:/sys/power# echo mem > state

USB 唤醒

内核配置

&rockchip_suspend {
    status = "okay";
    rockchip,sleep-debug-en = <1>;
    rockchip,sleep-mode-config = <
        (0
        | RKPM_SLP_ARMPD
        | RKPM_SLP_PERILPPD
        | RKPM_SLP_DDR_RET
        | RKPM_SLP_PLLPD
        | RKPM_SLP_CENTER_PD
        | RKPM_SLP_AP_PWROFF
        )
    >;
    rockchip,wakeup-config = <
        (0
        | RKPM_GPIO_WKUP_EN
        | RKPM_USB_WKUP_EN
        | RKPM_USB_LINESTATE_WKUP_EN
        | RKPM_PWM_WKUP_EN
        )
    >;
    rockchip,pwm-regulator-config = <
        (0
        | PWM2_REGULATOR_EN
        )
    >;
    rockchip,power-ctrl =
        <&gpio1 17 GPIO_ACTIVE_LOW>,
        <&gpio1 14 GPIO_ACTIVE_HIGH>;
};

进入休眠

root@firefly:/sys/power# cat state
freeze mem
root@firefly:/sys/power# echo mem > state
INFO:    sleep mode config[0xde]:
INFO:           AP_PWROFF
INFO:           SLP_ARMPD
INFO:           SLP_PLLPD
INFO:           DDR_RET
INFO:           SLP_CENTER_PD
INFO:    wakeup source config[0x4884]:
INFO:           GPIO interrupt can wakeup system
INFO:           USBDEV detect can wakeup system
INFO:           PWM interrupt can wakeup system
INFO:    PWM CONFIG[0x4]:
INFO:           PWM: PWM2D_REGULATOR_EN
INFO:    APIOS info[0x0]:
INFO:           not config
INFO:    GPIO POWER INFO:
INFO:           GPIO1_C1
INFO:           GPIO1_B6
INFO:    PMU_MODE_CONG: 0x1466bf41
INFO:    RK3399 the wake up information:
INFO:    wake up status: 0x80
INFO:           USBDEV detect wakeup

插拔 TypeC USB 唤醒

插拔 type C 线可以唤醒系统,唤醒后日志如下:

INFO:    RK3399 the wake up information:
INFO:    wake up status: 0x80
INFO:           USBDEV detect wakeup

功耗

工作状态:12v 0.4A 4.8W
休眠状态:12v 0.17A 2.03W

以太网唤醒

唤醒流程

image.png

以上流程图是realtek技术人员提供的资料,根据这个流程图可以知晓整个休眠唤醒的过程,以及涉及的PHY寄存器配置。

测试脚本

下面是基于RK提供的RTL8211E 测试脚本修改 的RTL8211F 测试脚本,寄存器配置与上述流程图基本吻合。

#!/bin/bash

#***********************************************************************
#                       8211F配制
#        可以在不休眠情况下验证功能,如下几点需要手动修改:
#        1、寄存器路径可能不同: busybox find /sys/ -name phy_reg
#        2、网关可能不同:  ifconfig 自己确认修改
#        3、有可能MAC地址随机,统一固定成001122334455, magic tool也设置一样
#
#***************************************************************************


#######################################
#Into sleep mode initial WOL
#Reg31:0007
#       30:006E
#       21:1100
#       22:3322
#       23:5544
#       31:0007
#       30:006D
#       22:9FFF
#       21:1000
#       25:0001
#       31:0000
######################################
#MAC address 001122334455

function suspend(){
# set INTB/PMEB pin
echo 31 0x0d40 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 22 0x20 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

# set mac 4e:78:eb:7b:3a:e2
echo 31 0x0d8c > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 16 0x784e > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 17 0x7beb > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 18 0xe23a > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
cat /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers

echo "############################################################################"

# set max packet length
echo 31 0x0d8a > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 17 0x9fff > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

# enable wol event
echo 31 0x0d8a > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 16 0x1000 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
#echo 16 0xffff > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

# disable rgmii pad
echo 31 0x0d8a > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 19 0x8002 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 31 0xa42 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5
}

####################
#after wake
#Reg31:0007
#       30:006D
#       21:0000
#       22:9FFF
#       25:0000
#       31:0000
###################


function resume(){

# disable wol event
echo 31 0x0d8a > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 16 0x0 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

# reset wol
echo 31 0x0d8a > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 17 0x1fff > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

# enable rgmii pad
echo 31 0x0d8a > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 19 0x2 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

# set INTB pin
echo 31 0x0d40 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 22 0x0 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5

echo 31 0xa42 > /sys/devices/platform/fe300000.ethernet/mdio_bus/stmmac-0/stmmac-0:00/phy_registers
sleep 0.5
}


case "$1" in
  start)
    echo -n "suspend test:"
    suspend
    echo "done"
    ;;
  stop)
    echo -n "resume test:"
    resume
    echo "done"

    ;;
  *)echo "usage: $0 start | stop "
    ;;
esac
exit 0

以上脚本用于测试RTL8211F芯片的唤醒功能,可以在不进休眠的情况下验证magic packet的唤醒功能。
执行 start 后,通过wakeonlan 工具发送magic packet, 然后测量中断引脚(gpio3_16)是否被拉低,如果被拉低说明脚本执行成功,phy芯片支持休眠唤醒,剩下的就是适配RK3399的驱动和dts.

内核适配

DTS

dts 中需要配置以下内容:

  1. 中断引脚gpio wolirq-gpio,根据原理图可知是 gpio3_16
  2. pinctrl 配置 gmac_pmeb_gpios,其实也是中断引脚的初始化配置
  3. phy_reset的复位时间调整为下拉50ms,上拉100ms,这个参数非常重要!!!默认值太小会导致唤醒后以太网复位失败,只能手动down,up eth0恢复。
  4. 休眠模式下使能 vcc3v3_s3电源,确保phy芯片及中断引脚电源域(VCC3V3_LAN) 保持供电,否则phy芯片无法接收魔术包,也就无法唤醒了。至于如何确定保留哪个电源域,需要将原理图以及rk808 pmic的dts配置对应起来。

image.png

下面就是根据以上需求配置后的DTS.

--- a/arch/arm64/boot/dts/rockchip/rk3399-keenon-linux.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3399-keenon-linux.dts
@@ -428,11 +428,13 @@
     clock_in_out = "input";
     snps,reset-gpio = <&gpio3 15 GPIO_ACTIVE_LOW>;
     snps,reset-active-low;
-    snps,reset-delays-us = <0 10000 50000>;
+    /* delay time for phy reset cannot be too short, which may cause phy reset fail */
+    snps,reset-delays-us = <0 50000 100000>;
     assigned-clocks = <&cru SCLK_RMII_SRC>;
     assigned-clock-parents = <&clkin_gmac>;
+    wolirq-gpio = <&gpio3 16 GPIO_ACTIVE_LOW>;
     pinctrl-names = "default";
-    pinctrl-0 = <&rgmii_pins>;
+    pinctrl-0 = <&rgmii_pins &gmac_pmeb_gpios>;
     tx_delay = <0x2d>;
     rx_delay = <0x2b>;
     status = "okay";
@@ -671,7 +673,7 @@
                 regulator-boot-on;
                 regulator-name = "vcc3v3_s3";
                 regulator-state-mem {
-                    regulator-off-in-suspend;
+                    regulator-on-in-suspend;
                 };
             };
 
@@ -912,6 +914,12 @@
             rockchip,pins = <1 2 RK_FUNC_GPIO &pcfg_pull_up>;
         };
     };
+
+    gmac-wol {
+        gmac_pmeb_gpios:  gmac-pmeb-gpios {
+            rockchip,pins = <3 16 RK_FUNC_GPIO &pcfg_pull_none>;
+        };
+    };
 };
 
 &pwm0 {
@@ -1128,3 +1136,35 @@
 &target {
     temperature = <100000>;
 };
+
+&rockchip_suspend {
+    /* disable rockchip_suspend which conflict with RTL8211F WOL feature */
+    status = "disabled";
+    rockchip,sleep-debug-en = <1>;
+    rockchip,sleep-mode-config = <
+        (0
+        | RKPM_SLP_ARMPD
+        | RKPM_SLP_PERILPPD
+        | RKPM_SLP_DDR_RET
+        | RKPM_SLP_PLLPD
+        | RKPM_SLP_CENTER_PD
+        | RKPM_SLP_AP_PWROFF
+        )
+    >;
+    rockchip,wakeup-config = <
+        (0
+        | RKPM_GPIO_WKUP_EN
+        | RKPM_USB_WKUP_EN
+        | RKPM_USB_LINESTATE_WKUP_EN
+        | RKPM_PWM_WKUP_EN
+        )
+    >;
+    rockchip,pwm-regulator-config = <
+        (0
+        | PWM2_REGULATOR_EN
+        )
+    >;
+    rockchip,power-ctrl =
+        <&gpio1 17 GPIO_ACTIVE_LOW>,
+        <&gpio1 14 GPIO_ACTIVE_HIGH>;
+};

说明:最后禁用的 rockchip_suspend也很关闭,保留是为了以后支持其它唤醒方式,禁掉是为了放在其它唤醒方式与网络唤醒功能产生冲突。经测试,在启用**rockchip_suspend**功能的情况下,以太网唤醒会失败!

phy & gmac 驱动

以下改动绝大部分源自RK提供的patch,其功能是添加 RTL8211E/RTL8211F phy芯片的网络唤醒功能,包括进入休眠以及退出休眠时的操作,并添加中断引脚的配置和处理函数,确保中断触发后能够唤醒系统。

--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -54,6 +54,7 @@
 #include "dwmac-rk-tool.h"
 #include <linux/reset.h>
 #include <linux/of_mdio.h>
+#include <linux/gpio.h>
 
 #define    STMMAC_ALIGN(x)        __ALIGN_KERNEL(x, SMP_CACHE_BYTES)
 
@@ -1793,6 +1794,16 @@ static int stmmac_hw_setup(struct net_device *dev, bool init_ptp)
     return 0;
 }
 
+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);
+
+    printk(KERN_ERR "keenon: enter wol_io_isr\n");
+    wake_lock_timeout(&priv->plat->wol_wake_lock, msecs_to_jiffies(8000));
+    return IRQ_HANDLED;
+}
+
 /**
  *  stmmac_open - open entry point of the driver
  *  @dev : pointer to the device structure.
@@ -1877,6 +1888,26 @@ static int stmmac_open(struct net_device *dev)
         }
     }
 
+    if (priv->plat->wolirq_io > 0) {
+        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);
+            goto lpiirq_error;
+        }
+
+        priv->plat->wol_irq = gpio_to_irq(priv->plat->wolirq_io);
+        ret = devm_request_irq(priv->device, priv->plat->wol_irq, wol_io_isr,
+            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);
+            goto lpiirq_error;
+        }
+        disable_irq(priv->plat->wol_irq);
+        enable_irq_wake(priv->plat->wol_irq);
+    }
+
     napi_enable(&priv->napi);
     netif_start_queue(dev);
 
@@ -1938,6 +1969,12 @@ static int stmmac_release(struct net_device *dev)
     if (priv->lpi_irq > 0)
         free_irq(priv->lpi_irq, dev);
 
+    if (priv->plat->wol_irq > 0)
+        devm_free_irq(priv->device, priv->plat->wol_irq, dev);
+
+    if (priv->plat->wolirq_io > 0)
+        devm_gpio_free(priv->device, priv->plat->wolirq_io);
+
     /* Stop TX/RX DMA and clear the descriptors */
     priv->hw->dma->stop_tx(priv->ioaddr);
     priv->hw->dma->stop_rx(priv->ioaddr);
@@ -3053,6 +3090,8 @@ int stmmac_dvr_probe(struct device *device,
     INIT_DELAYED_WORK(&priv->scan_dwork, stmmac_scan_delayline_dwork);
 #endif
 
+    wake_lock_init(&priv->plat->wol_wake_lock, WAKE_LOCK_SUSPEND, "wol_wake_lock");
+
     return ret;
 
 error_netdev_register:
@@ -3119,6 +3158,8 @@ int stmmac_suspend(struct device *dev)
     struct net_device *ndev = dev_get_drvdata(dev);
     struct stmmac_priv *priv = netdev_priv(ndev);
 
+    enable_irq(priv->plat->wol_irq);
+
     if (!ndev || !netif_running(ndev))
         return 0;
 
@@ -3166,6 +3207,8 @@ int stmmac_resume(struct device *dev)
     struct net_device *ndev = dev_get_drvdata(dev);
     struct stmmac_priv *priv = netdev_priv(ndev);
 
+    disable_irq(priv->plat->wol_irq);
+
     if (!netif_running(ndev))
         return 0;
 
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
@@ -29,6 +29,7 @@
 #include <linux/of_net.h>
 #include <linux/of_device.h>
 #include <linux/of_mdio.h>
+#include <linux/of_gpio.h>
 
 #include "stmmac.h"
 #include "stmmac_platform.h"
@@ -109,6 +110,7 @@ stmmac_probe_config_dt(struct platform_device *pdev, const char **mac)
     struct device_node *np = pdev->dev.of_node;
     struct plat_stmmacenet_data *plat;
     struct stmmac_dma_cfg *dma_cfg;
+    enum of_gpio_flags flags;
 
     plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL);
     if (!plat)
@@ -117,6 +119,8 @@ stmmac_probe_config_dt(struct platform_device *pdev, const char **mac)
     *mac = of_get_mac_address(np);
     plat->interface = of_get_phy_mode(np);
 
+    plat->wolirq_io = of_get_named_gpio_flags(np, "wolirq-gpio", 0, &flags);
+
     /* Get max speed of operation from device tree */
     if (of_property_read_u32(np, "max-speed", &plat->max_speed))
         plat->max_speed = -1;
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1232,8 +1232,70 @@ static int gen10g_config_init(struct phy_device *phydev)
 int genphy_suspend(struct phy_device *phydev)
 {
     int value;
+    struct net_device * ndev = phydev->attached_dev;
 
     mutex_lock(&phydev->lock);
+//#if RTL8211E
+#if 0
+    phy_write(phydev, 31, 0x07);
+    phy_write(phydev, 30, 0x6e);
+    phy_write(phydev, 21, ((u16)ndev->dev_addr[1] << 8) + ndev->dev_addr[0]);
+    phy_write(phydev, 22, ((u16)ndev->dev_addr[3] << 8) + ndev->dev_addr[2]);
+    phy_write(phydev, 23, ((u16)ndev->dev_addr[5] << 8) + ndev->dev_addr[4]);
+
+    phy_write(phydev, 31, 0x07);
+    phy_write(phydev, 30, 0x6d);
+    phy_write(phydev, 22, 0x1fff);
+    value = phy_read(phydev, 22);
+
+    phy_write(phydev, 31, 0x07);
+    phy_write(phydev, 30, 0x6d);
+    phy_write(phydev, 21, 0x1000);
+    value = phy_read(phydev, 21);
+
+    phy_write(phydev, 31, 0x07);
+    phy_write(phydev, 30, 0x6d);
+    value =  phy_read(phydev, 25);
+    phy_write(phydev, 25, value | 0x1);
+
+    phy_write(phydev, 31, 0x0);
+#endif
+
+//#if RTL8211F
+#if 1
+    //set INTB pin
+    phy_write(phydev, 31, 0x0d40);
+    value = phy_read(phydev, 22);
+    phy_write(phydev, 22, value | BIT(5));
+
+    //set MAC address
+    if(ndev) {
+        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]);
+    } 
+
+    //set max packet length
+    phy_write(phydev, 31, 0x0d8a);
+    phy_write(phydev, 17, 0x9fff);
+
+    //enable wol event
+    phy_write(phydev, 31, 0x0d8a);
+    phy_write(phydev, 16, 0x1000);
+
+    //disable rgmii pad
+    phy_write(phydev, 31, 0x0d8a);
+    value = phy_read(phydev, 19);
+    phy_write(phydev, 19, value | BIT(15));
+
+    phy_write(phydev, 31, 0xa42);
+#endif
+    mutex_unlock(&phydev->lock);
+
+    return 0;
+/*
+    mutex_lock(&phydev->lock);
 
     value = phy_read(phydev, MII_BMCR);
     phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
@@ -1241,6 +1303,7 @@ int genphy_suspend(struct phy_device *phydev)
     mutex_unlock(&phydev->lock);
 
     return 0;
+*/
 }
 EXPORT_SYMBOL(genphy_suspend);
 
@@ -1253,6 +1316,59 @@ int genphy_resume(struct phy_device *phydev)
 {
     int value;
 
+    if (phydev->suspended) {
+        mutex_lock(&phydev->lock);
+//#if RTL8211E
+#if 0
+        phy_write(phydev, 31, 0x07);
+        phy_write(phydev, 30, 0x6d);
+        phy_write(phydev, 21, 0x0);
+        value = phy_read(phydev, 21);
+
+        phy_write(phydev, 31, 0x07);
+        phy_write(phydev, 30, 0x6d);
+        value =  phy_read(phydev, 22);
+        phy_write(phydev, 22, value | BIT(15));
+        value = phy_read(phydev, 22);
+
+        phy_write(phydev, 31, 0x07);
+        phy_write(phydev, 30, 0x6d);
+        value =  phy_read(phydev, 25);
+        phy_write(phydev, 25, value & (~(0x1)));
+
+        phy_write(phydev, 31, 0x0);
+#endif
+
+//#if RTL8211F
+#if 1
+        //disable wol event
+        phy_write(phydev, 31, 0x0d8a);
+        phy_write(phydev, 16, 0x0);
+
+        //reset wol
+        phy_write(phydev, 31, 0x0d8a);
+        value = phy_read(phydev, 17);
+        phy_write(phydev, 17, value & (~BIT(15)));
+
+        //enable rgmii pad
+        phy_write(phydev, 31, 0x0d8a);
+        value = phy_read(phydev, 19);
+        phy_write(phydev, 19, value & (~BIT(15)));
+
+        //set INTB pin
+        phy_write(phydev, 31, 0x0d40);
+        value = phy_read(phydev, 22);
+        phy_write(phydev, 22, value & (~BIT(5)));
+
+        phy_write(phydev, 31, 0xa42);
+#endif
+        mutex_unlock(&phydev->lock);
+
+        msleep(100);
+
+        return 0;
+    }
+
     mutex_lock(&phydev->lock);
 
     value = phy_read(phydev, MII_BMCR);
diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h
--- a/include/linux/stmmac.h
+++ b/include/linux/stmmac.h
@@ -27,6 +27,7 @@
 #define __STMMAC_PLATFORM_DATA
 
 #include <linux/platform_device.h>
+#include <linux/wakelock.h>
 
 #define STMMAC_RX_COE_NONE    0
 #define STMMAC_RX_COE_TYPE1    1
@@ -123,5 +124,9 @@ struct plat_stmmacenet_data {
     void (*exit)(struct platform_device *pdev, void *priv);
     void (*get_eth_addr)(void *priv, unsigned char *addr);
     void *bsp_priv;
+    int wolirq_io;
+    int wolirq_io_level;
+    int wol_irq;
+    struct wake_lock wol_wake_lock;
 };
 #endif

RK提供的patch无法直接实现休眠唤醒功能,其中包括编译错误问题,休眠时kernel crash导致休眠失败问题等。这些问题经过排查分析已经解决了,部分问题的说明见下文。以上就是最终实现网络唤醒的代码。

唤醒工具

根据系统平台的不同,休眠后的唤醒工具可以分为以下3类。

ubuntu

如果是Ubuntu系统,可以使用 wakeonlan指令进行唤醒。

sudo wakeonlan -i 192.168.64.255 4e:78:eb:7b:3a:e2

注意:

  1. 需要使用广播IP地址 192.168.64.255,否则唤醒会失败
  2. udp包的端口号没有特殊要求,默认的9或者自定义的5555都可以唤醒
  3. mac地址必须是phy芯片对应的网口

Android

对于Android系统, 可以使用apk,比如 Wake On Lan_v1.35_apkpure.com.apk发送唤醒包,或者app内部实现wol数据包的组装和发送。
pm-add-device.png

windows

对于windows系统,可以使用RK提供的 Magic_Package (Wake On Lan).rar, 或者下载 wakeuppro软件。
wakeuppro.png

调试方法

通常情况下,系统进入休眠后没有任何串口日志,哪怕异常也看不出,为了调试,可以在休眠前执行以下语句,将日志打印到串口。

echo 7 4 1 7 > /proc/sys/kernel/printk
echo N > /sys/module/printk/parameters/console_suspend
echo 1 > /sys/power/pm_print_times
echo mem > /sys/power/state

# 合一
echo 7 4 1 7 > /proc/sys/kernel/printk && echo N > /sys/module/printk/parameters/console_suspend && echo 1 > /sys/power/pm_print_times

网络唤醒失败问题排查

进入休眠时异常

root@firefly:~# echo 7 4 1 7 > /proc/sys/kernel/printk
root@firefly:~# echo N > /sys/module/printk/parameters/console_suspend
root@firefly:~# echo 1 > /sys/power/pm_print_times
root@firefly:~# cat /sys/power/pm_print_times
1
root@firefly:~# echo mem > /sys/power/state
[  241.184074] PM: suspend entry 2024-04-15 11:47:06.000579232 UTC
[  241.184597] PM: Syncing filesystems ... done.
[  241.194766] Freezing user space processes ... (elapsed 0.004 seconds) done.
[  241.199524] Freezing remaining freezable tasks ... (elapsed 0.001 seconds) done.
[  241.203165] calling  1-1.4+ @ 6, parent: 1-1, cb: usb_dev_suspend
[  241.203283] calling  6-1+ @ 48, parent: usb6, cb: usb_dev_suspend
[  241.204271] call 1-1.4+ returned 0 after 13 usecs
[  241.204727] calling  3-1+ @ 6, parent: usb3, cb: usb_dev_suspend
[  241.205310] calling  input1+ @ 1317, parent: gpio-keys, cb: input_dev_suspend
[  241.205384] calling  5-1+ @ 151, parent: usb5, cb: usb_dev_suspend
[  241.206485] call input1+ returned 0 after 1 usecs
[  241.207033] calling  fe310000.dwmmc+ @ 1317, parent: platform, cb: pm_generic_suspend
[  241.207721] call fe310000.dwmmc+ returned 0 after 1 usecs
[  241.208355] calling  usb4+ @ 13725, parent: fe3a0000.usb, cb: usb_dev_suspend
[  241.208573] calling  1-1+ @ 153, parent: usb1, cb: usb_dev_suspend
[  241.210830] call 1-1+ returned 0 after 2200 usecs
[  241.247858] call 3-1+ returned 0 after 41593 usecs
[  241.258065] call usb4+ returned 0 after 47391 usecs
[  241.258648] calling  fe3a0000.usb+ @ 1317, parent: platform, cb: pm_generic_suspend
[  241.259369] call fe3a0000.usb+ returned 0 after 12 usecs
[  241.259965] calling  usb3+ @ 6, parent: fe380000.usb, cb: usb_dev_suspend
[  241.262173] call usb3+ returned 0 after 1576 usecs
[  241.262910] calling  fe380000.usb+ @ 1317, parent: platform, cb: pm_generic_suspend
[  241.273979] call fe380000.usb+ returned 0 after 10094 usecs
[  241.301011] call 5-1+ returned 0 after 93377 usecs
[  241.301601] calling  usb5+ @ 13725, parent: xhci-hcd.2.auto, cb: usb_dev_suspend
[  241.302654] call usb5+ returned 0 after 321 usecs
[  241.309006] call 6-1+ returned 0 after 103237 usecs
[  241.309521] calling  usb6+ @ 153, parent: xhci-hcd.2.auto, cb: usb_dev_suspend
[  241.310194] call usb6+ returned 0 after 27 usecs
[  241.310912] calling  xhci-hcd.2.auto+ @ 1317, parent: fe900000.dwc3, cb: platform_pm_suspend
[  241.311681] call xhci-hcd.2.auto+ returned 0 after 18 usecs
[  241.312190] calling  fe900000.dwc3+ @ 1317, parent: usb1, cb: platform_pm_suspend
[  241.312860] call fe900000.dwc3+ returned 0 after 8 usecs
[  241.313333] calling  usb1+ @ 1317, parent: platform, cb: pm_generic_suspend
[  241.313965] call usb1+ returned 0 after 15 usecs
[  241.314400] calling  xhci-hcd.1.auto+ @ 1317, parent: fe800000.dwc3, cb: platform_pm_suspend
[  241.315145] call xhci-hcd.1.auto+ returned 0 after 0 usecs
[  241.315640] calling  fe800000.dwc3+ @ 1317, parent: usb0, cb: platform_pm_suspend
[  241.316535] android_work: sent uevent USB_STATE=DISCONNECTED
[  241.317082] call fe800000.dwc3+ returned 0 after 761 usecs
[  241.317569] calling  usb0+ @ 1317, parent: platform, cb: pm_generic_suspend
[  241.318188] call usb0+ returned 0 after 1 usecs
[  241.318635] calling  ff9a0000.gpu+ @ 1317, parent: platform, cb: pm_generic_suspend
[  241.318689] calling  mmc1:0001+ @ 153, parent: mmc1, cb: mmc_bus_suspend
[  241.320126] call ff9a0000.gpu+ returned 0 after 218 usecs
[  241.320612] calling  ff7c0000.phy+ @ 1317, parent: platform, cb: pm_generic_suspend
[  241.321273] call mmc1:0001+ returned 0 after 2519 usecs
[  241.321749] call ff7c0000.phy+ returned 0 after 0 usecs
[  241.322225] calling  ff770000.syscon:usb2-phy@e450+ @ 1317, parent: ff770000.syscon, cb: pm_generic_suspend
[  241.323086] call ff770000.syscon:usb2-phy@e450+ returned 0 after 4 usecs
[  241.323687] calling  snd-soc-dummy+ @ 1317, parent: platform, cb: platform_pm_suspend
[  241.324379] call snd-soc-dummy+ returned 0 after 0 usecs
[  241.324870] calling  led_ctl+ @ 1317, parent: leds, cb: led_suspend
[  241.325425] call led_ctl+ returned 0 after 7 usecs
[  241.325875] calling  mmc1::+ @ 1317, parent: fe330000.sdhci, cb: led_suspend
[  241.326492] call mmc1::+ returned 0 after 0 usecs
[  241.326936] calling  cpufreq-dt+ @ 1317, parent: platform, cb: platform_pm_suspend
[  241.327599] call cpufreq-dt+ returned 0 after 0 usecs
[  241.328066] calling  input0+ @ 1317, parent: 4-0022, cb: input_dev_suspend
[  241.328669] call input0+ returned 0 after 1 usecs
[  241.329101] calling  4-0022+ @ 1317, parent: i2c-4, cb: fusb30x_pm_suspend
[  241.329710] call 4-0022+ returned 0 after 6 usecs
[  241.330142] calling  rk808-rtc+ @ 1317, parent: 0-001b, cb: platform_pm_suspend
[  241.330781] call rk808-rtc+ returned 0 after 0 usecs
[  241.331258] calling  rk808-regulator+ @ 1317, parent: 0-001b, cb: platform_pm_suspend
[  241.331950] call rk808-regulator+ returned 0 after 0 usecs
[  241.332436] calling  rk808-clkout+ @ 1317, parent: 0-001b, cb: platform_pm_suspend
[  241.333103] call rk808-clkout+ returned 0 after 0 usecs
[  241.333576] calling  0-001b+ @ 1317, parent: i2c-0, cb: rk808_suspend
[  241.334149] call 0-001b+ returned 0 after 1 usecs
[  241.334579] calling  rtc0+ @ 1317, parent: 0-0051, cb: rtc_suspend
[  241.335697] call rtc0+ returned 0 after 544 usecs
[  241.336195] calling  0-0051+ @ 1317, parent: i2c-0, cb: hym8563_suspend
[  241.336776] call 0-0051+ returned 0 after 0 usecs
[  241.337269] calling  stmmac-0:01+ @ 1317, parent: stmmac-0, cb: mdio_bus_suspend
[  241.337347] calling  usb2+ @ 153, parent: fe3e0000.usb, cb: usb_dev_suspend
[  241.337448] calling  usb1+ @ 48, parent: fe3c0000.usb, cb: usb_dev_suspend
[  241.337764] call usb1+ returned 0 after 306 usecs
[  241.339729] Unable to handle kernel NULL pointer dereference at virtual address 00000318
[  241.340442] pgd = ffffffc0eb940000
[  241.340743] [00000318] *pgd=00000000eb968003, *pud=00000000eb968003, *pmd=00000000eefdf003, *pte=0000000000000000
[  241.341667] Internal error: Oops: 96000007 [#1] SMP
[  241.342095] Modules linked in:
[  241.342374] CPU: 3 PID: 1317 Comm: bash Not tainted 4.4.194 #36
[  241.342893] Hardware name: Rockchip RK3399 Keenon Board (Linux Opensource) (DT)
[  241.343531] task: ffffffc0edfe4380 task.stack: ffffffc0eba70000
[  241.344052] PC is at genphy_suspend+0x68/0x180
[  241.344445] LR is at genphy_suspend+0x68/0x180
[  241.344835] pc : [<ffffff80085d8fd4>] lr : [<ffffff80085d8fd4>] pstate: 20000145
[  241.345480] sp : ffffffc0eba739a0
[  241.345773] x29: ffffffc0eba739a0 x28: ffffff8009237cf8
[  241.346247] x27: ffffffc0f12f00f0 x26: ffffff8008581c84
[  241.346720] x25: ffffff8009237000 x24: ffffff800951d688
[  241.347194] x23: ffffff8008f31183 x22: 000000382c8e9eb5
[  241.347669] x21: ffffff80085da2b4 x20: 0000000000000000
[  241.348142] x19: ffffffc0f12f0000 x18: ffffff8089365897
[  241.348616] x17: 0000000000000000 x16: 0000000000000000
[  241.349089] x15: 0000000000000000 x14: 0000000000052764
[  241.349562] x13: 000000000000000a x12: 0000000000000030
[  241.350037] x11: 00000000fffffffe x10: ffffff800936589f
[  241.350510] x9 : 0000000005f5e0ff x8 : ffffff800835b114
[  241.350984] x7 : ffffff800920e688 x6 : 0000000000069d0e
[  241.351459] x5 : 000000015dc9eef7 x4 : ffffff80085e0f38
[  241.351932] x3 : 00000000ffff264a x2 : 00000000ffff1a92
[  241.352405] x1 : 0000000000000000 x0 : 0000000000000000
[  241.352881]
[  241.352881] PC: 0xffffff80085d8f54:
...
[  241.466657]
[  241.466793] Process bash (pid: 1317, stack limit = 0xffffffc0eba70000)
[  241.467365] Stack: (0xffffffc0eba739a0 to 0xffffffc0eba74000)
...
[  241.510453] [<ffffff80085d8fd4>] genphy_suspend+0x68/0x180
[  241.510936] [<ffffff80085d8dd4>] phy_suspend+0x68/0x78
[  241.511390] [<ffffff80085da300>] mdio_bus_suspend+0x4c/0x60
[  241.511883] [<ffffff8008581378>] dpm_run_callback+0xa4/0x178
[  241.512381] [<ffffff8008581b18>] __device_suspend+0x190/0x2fc
[  241.512887] [<ffffff80085839d4>] dpm_suspend+0x268/0x2e8
[  241.513355] [<ffffff8008583ee4>] dpm_suspend_start+0x70/0x74
[  241.513856] [<ffffff80080ea8c8>] suspend_devices_and_enter+0x68/0x258
[  241.514421] [<ffffff80080eaeb8>] pm_suspend+0x400/0x548
[  241.514881] [<ffffff80080e9314>] state_store+0xb8/0xe0
[  241.515336] [<ffffff80083ba2a4>] kobj_attr_store+0x14/0x24
[  241.515821] [<ffffff8008216c80>] sysfs_kf_write+0x54/0x74
[  241.516296] [<ffffff8008215cdc>] kernfs_fop_write+0x120/0x17c
[  241.516803] [<ffffff80081ab8e8>] __vfs_write+0x48/0xe8
[  241.517256] [<ffffff80081ac1b4>] vfs_write+0xa8/0x15c
[  241.517702] [<ffffff80081acb54>] SyS_write+0x5c/0xb0
[  241.518141] [<ffffff8008082f70>] el0_svc_naked+0x24/0x28
[  241.518609] Code: 5281b183 f9400660 528003e2 940006c3 (f9418e80)
[  241.519145] ---[ end trace 89b1acabdee346a2 ]---

前期测试时发现休眠失败,经排查,发现是驱动执行到 genphy_suspend时出现空指针异常,进一步确认是配置mac地址时,变量ndev为空,因此后面加了一个空指针判断就好了。

+    //set MAC address
+    if(ndev) {
+        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]);
+    } 

中断信号无法拉低到0v

这个问题也是很神奇,测试发现发送magic packet后,中断引脚确实被拉低了,但是只是从3.3v拉低到2v,而不是0v,导致中断触发失败。最终发现是这块板子的问题,换了一块就好了,怀疑是这块板子哪里短路或者损坏了。

有中断触发但无法唤醒

当中断信号问题解决后,发现还是无法唤醒,检查中断计数 cat /proc/interrupts是有新增的。gmac_wol_io_irq计数值有增加,说明正常触发了中断,但就是无法唤醒。

root@firefly:/# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5
 14:          0          0          0          0          0          0     GICv3  29 Edge      arch_timer
 15:     154130     126946     108949     115420      75167      80830     GICv3  30 Edge      arch_timer
 17:      23495      23295      22069      21663      23800      22967     GICv3 113 Level     rk_timer
 20:          0          0          0          0          0          0     GICv3  37 Level     ff6d0000.dma-controller
 21:          0          0          0          0          0          0     GICv3  38 Level     ff6d0000.dma-controller
 22:          0          0          0          0          0          0     GICv3  39 Level     ff6e0000.dma-controller
 23:          0          0          0          0          0          0     GICv3  40 Level     ff6e0000.dma-controller
 24:         27          0          0          0          0          0     GICv3  44 Level     eth0
 25:         45          0          0          0          0          0     GICv3  96 Level     dw-mci
 26:          0          0          0          0          0          0     GICv3  97 Level     dw-mci
 27:      15728          0          0          0          0          0     GICv3  43 Level     mmc1
 28:       5901          0          0          0          0          0     GICv3  58 Level     ehci_hcd:usb5
 29:          0          0          0          0          0          0     GICv3  60 Level     ohci_hcd:usb6
 30:        498          0          0          0          0          0     GICv3  62 Level     ehci_hcd:usb1
 31:          0          0          0          0          0          0     GICv3  64 Level     ohci_hcd:usb2
 32:          0          0          0          0          0          0     GICv3  94 Level     ff100000.saradc
 33:       6252          0          0          0          0          0     GICv3  89 Level     ff3c0000.i2c
 35:        239          0          0          0          0          0     GICv3 132 Level     serial
 36:          0          0          0          0          0          0     GICv3 129 Level     rockchip_thermal
 38:        693          0          0          0          0          0     GICv3  88 Level     ff3d0000.i2c
 40:          0          0          0          0          0          0     GICv3 148 Level     ff660000.rkvdec
 42:          0          0          0          0          0          0     GICv3  87 Level     rga
 44:         12          0          0          0          0          0     GICv3  51 Level     ff9a0000.gpu
 45:          1          0          0          0          0          0     GICv3  52 Level     ff9a0000.gpu
 46:          1          0          0          0          0          0     GICv3  53 Level     ff9a0000.gpu
 57:          0          0          0          0          0          0     gpio0   5 Edge      GPIO Key Power
 86:         10          0          0          0          0          0     gpio1   2 Level     fusb302
105:          0          0          0          0          0          0     gpio1  21 Level     rk808
164:          3          1          0          0          0          0     gpio3  16 Edge      gmac_wol_io_irq
212:          7          0          0          0          0          0     GICv3  63 Level     rockchip_usb2phy
213:       2882          0          0          0          0          0     GICv3 137 Level     dwc3
214:        232          0          0          0          0          0     GICv3 142 Level     xhci-hcd:usb3
224:          4          0          0          0          0          0     GICv3  59 Level     rockchip_usb2phy
225:          3          0          0          0          0          0     GICv3 135 Level     rockchip_usb2phy_bvalid
226:          3          0          0          0          0          0     GICv3 138 Level     rockchip_usb2phy
IPI0:    107703      97644      97952      92577      76656      83385       Rescheduling interrupts
IPI1:        23         55         35         44         54         58       Function call interrupts
IPI2:         0          0          0          0          0          0       CPU stop interrupts
IPI3:         0          0          0          0          0          0       CPU stop (for crash dump) interrupts
IPI4:       939       1047        913        986       1374       1455       Timer broadcast interrupts
IPI5:         8          6          4          3          0          1       IRQ work interrupts
IPI6:         0          0          0          0          0          0       CPU wake-up interrupts

最终尝试将 rockchip_suspend禁掉后就好了,至于为什么需要禁掉,目前RK没有给出明确答复,留意下就好。怀疑是两套唤醒机制会有冲突。

唤醒后以太网无法使用

好不容易把唤醒功能实现了,结果发现eth0无法正常使用,ping不通192.168.64.10, 而且会出现NETDEV WATCHDOG

[ 6034.474982] rk_gmac-dwmac fe300000.ethernet eth0: Link is Up - 100Mbps/Full - flow control off
[ 6042.778740] NETDEV WATCHDOG: eth0 (rk_gmac-dwmac): transmit queue 0 timed out
[ 6042.779391] ------------[ cut here ]------------
[ 6042.779795] WARNING: at net/sched/sch_generic.c:306
[ 6042.780221] Modules linked in:
[ 6042.780494]
[ 6042.780631] CPU: 4 PID: 0 Comm: swapper/4 Not tainted 4.4.194 #52
[ 6042.781163] Hardware name: Rockchip RK3399 Keenon Board (Linux Opensource) (DT)
[ 6042.781800] task: ffffffc0f1c5b600 task.stack: ffffffc0f1c74000
[ 6042.782326] PC is at dev_watchdog+0x1e4/0x234
[ 6042.782709] LR is at dev_watchdog+0x1e4/0x234
[ 6042.783090] pc : [<ffffff8008a84f18>] lr : [<ffffff8008a84f18>] pstate: 20000145
[ 6042.783734] sp : ffffffc0f6f2dde0
[ 6042.784024] x29: ffffffc0f6f2dde0 x28: ffffff800932a000
[ 6042.784496] x27: ffffffc0f6f2ded8 x26: 00000000ffffffff
[ 6042.784967] x25: 0000000000000004 x24: ffffffc0f1294880
[ 6042.785438] x23: 0000000000000001 x22: ffffff800932a000
[ 6042.785910] x21: ffffffc0f12c03e0 x20: 0000000000000000
[ 6042.786382] x19: ffffffc0f12c0000 x18: ffffff8089365897
[ 6042.786852] x17: 0000000000000000 x16: 0000000000000000
[ 6042.787323] x15: 0000000000000000 x14: 00000000000be1f4
[ 6042.787795] x13: 000000000000000a x12: 0000000000000030
[ 6042.788266] x11: 00000000fffffffe x10: ffffff800936589f
[ 6042.788738] x9 : 0000000005f5e0ff x8 : ffffff800835b114
[ 6042.789210] x7 : ffffff800920e688 x6 : 0000000000000051
[ 6042.789681] x5 : 0000000000000010 x4 : 0000000000000000
[ 6042.790152] x3 : 0000000000000000 x2 : 0000000000000040
[ 6042.790622] x1 : 0000000000000006 x0 : 0000000000000041

经RK指导,怀疑是唤醒后phy没有正常复位,但是测量信号发现是有进行复位的,不过复位时间很短,后来经过调整复位的上下拉时间解决。

     snps,reset-gpio = <&gpio3 15 GPIO_ACTIVE_LOW>;
     snps,reset-active-low;
-    snps,reset-delays-us = <0 10000 50000>;
+    /* delay time for phy reset cannot be too short, which may cause phy reset fail */
+    snps,reset-delays-us = <0 50000 100000>;

下面这是修改时间后的PHY_RESET信号。

phy_reset_0_50_100.png

小结

  1. 网络唤醒流程
    1. 使用 echo mem > /sys/power/state进入休眠状态
    2. 休眠时phy将中断引脚拉高,使能唤醒功能
    3. 发送 magic packet给phy芯片,phy芯片接收到后将中断引脚拉低
    4. 中断引脚下降沿触发中断,进行唤醒操作
    5. 唤醒后对phy进行复位,并禁止中断,配置phy寄存器
  2. 以太网唤醒功能依赖于PHY芯片,RK3399 两个网口,只有eth0可以用于网络唤醒
  3. 注意休眠时保证phy芯片及中断引脚gpio的电源保持上电
  4. 注意rockchip_suspend 与 网络唤醒不能同时使用
  5. 注意mac地址配置时需要判空处理,否则会出现crash
  6. 注意示波器测量信号时防止引脚短接引起短路,损坏电路

参考