资讯动态

Linux内核runtime PM:设备级动态电源管理机制解析

发布时间:2026/10/10 5:49:20 来源:尧图企业网站定制
1. 为什么 runtime PM 是内核功耗管理里最“狡猾”的一块拼图你翻过 Linux 内核文档大概率在Documentation/power/目录下见过runtime_pm.txt这个文件——它不长不到两千行但几乎每个嵌入式驱动工程师、SoC 芯片验证人员、甚至 Android BSP 开发者都曾被它卡住超过 8 小时。它不像cpuidle那样只管 CPU 停顿也不像cpufreq那样只调频率它干的是件更精细、更隐蔽、也更容易出错的事在设备“不用”的瞬间悄无声息地关掉它的电源等它“要用了”再零延迟唤醒——整个过程对上层驱动和用户空间完全透明。这就是 runtime PM 的核心价值它不是靠系统整体休眠suspend-to-RAM来省电而是把功耗控制颗粒度从“整机级”细化到“单个外设级”。一个 USB 摄像头闲置时自动断电一块 PCIe SSD 在无 IO 时进入 D3cold一个 I2C 温度传感器每 30 秒采样一次、其余时间彻底断电——这些都不是靠应用层轮询或定时器硬控实现的而是由内核在设备模型device model层面统一调度、原子化管理的结果。我做过三款 ARM64 平台的低功耗网关产品其中一款在待机功耗测试中仅靠启用 runtime PM 就让整机静态功耗从 187mW 降到 92mW降幅超 50%。这不是靠降低主频或关闭 CPU 核心实现的而是因为两路千兆以太网 PHY 在链路断开后 200ms 内自动进入低功耗模式SPI Flash 控制器在完成固件加载后立即切断 VCCQ 供电HDMI CEC 接口控制器在未检测到遥控信号的 5 秒后主动关闭其内部 PLL 和 IO 电源域。这些动作全部由 runtime PM 子系统自动触发无需驱动额外编写电源管理逻辑也不依赖用户空间 daemon 干预。它真正实现了“设备即电源节点”的理念——每个 struct device 都自带一个 struct dev_pm_info里面藏着一个 refcount、一个 usage_count、一个 suspend_status、一个 autosuspend_delay还有四个关键回调函数指针prepare、suspend、resume、complete。这组结构体就像给每个硬件设备装上了智能电闸而 runtime PM 就是那个永不疲倦的配电房调度员。所以当你看到标题《Linux 内核功耗子系统七runtime pm功能梳理》时别把它当成又一篇泛泛而谈的文档复述。它实际是在拆解一套内核级的、基于引用计数的、异步可中断的、与设备生命周期强绑定的动态电源门控机制。它解决的不是“能不能省电”而是“如何在不破坏设备功能、不引入竞态、不拖慢响应的前提下让省电这件事自动发生”。适合谁读如果你正在调试某块摄像头模组在 standby 状态下仍耗电 12mA或者发现 USB 设备插拔后无法恢复供电又或者在用cat /sys/devices/platform/xxx/power/runtime_status查看状态时总显示suspended却收不到中断——那你不是在看一篇技术文章你是在找一把能打开电源黑盒的钥匙。2. runtime PM 的设计哲学不是“开关”而是“状态流”很多人初学 runtime PM第一反应是“不就是调用pm_runtime_suspend()和pm_runtime_resume()吗”——这是典型误区。runtime PM 的本质不是一组 API而是一套状态机 引用计数 延迟队列 异步工作流的组合体。它的设计逻辑完全围绕“设备何时可用、何时可停、停多久、谁说了算”展开而不是简单粗暴的“开/关”指令。2.1 四个核心状态及其流转条件runtime PM 定义了设备的四种运行时状态全部记录在dev-power.status中状态值含义进入条件退出条件典型场景RPM_ACTIVE设备正在被使用电源必须开启驱动调用pm_runtime_get_sync()或pm_runtime_get()成功pm_runtime_put_sync()或pm_runtime_put()导致 usage_count 归零且满足 autosuspend 条件USB 设备正在传输数据、I2C 设备正在读取寄存器RPM_RESUMING设备正从挂起状态恢复中异步pm_runtime_resume()被调用且当前为 suspended 或 suspended_asyncresume 回调执行完毕status 切换为 RPM_ACTIVEPCIe 设备从 D3cold 唤醒需重新配置 BAR 和 MSIRPM_SUSPENDED设备已挂起电源已关闭suspend 回调执行成功且无 pending 的 get 请求pm_runtime_resume()被显式调用或有新的 get 请求到达UART 设备空闲超 5 秒后自动断电TX/RX FIFO 清空RPM_SUSPENDING设备正准备挂起异步pm_runtime_suspend()被调用且当前为 activesuspend 回调执行完毕status 切换为 RPM_SUSPENDEDSDIO WiFi 模块在无网络 activity 时先禁用 IRQ再关闭 VDDIO提示RPM_SUSPENDING和RPM_RESUMING是瞬态中间状态不可长期停留。如果某个设备卡在这两个状态超过 5 秒默认 timeout内核会打印 warning 并尝试强制超时处理——这往往是驱动 suspend/resume 回调阻塞或死锁的直接证据。状态流转不是线性单向的。例如一个设备处于RPM_SUSPENDED状态时若此时有中断到来如 GPIO 触发的 wakeup event内核会自动触发 resume 流程无需驱动干预。这种“事件驱动唤醒”能力正是 runtime PM 区别于传统手动电源管理的关键。2.2 引用计数谁在用谁负责供电runtime PM 的核心调度依据是dev-power.usage_count这是一个带符号的 atomic_t 计数器。它的增减规则极其严格pm_runtime_get()原子递增 usage_count若原值 ≤ 0则触发 resume 流程异步pm_runtime_get_sync()原子递增 usage_count若原值 ≤ 0则同步等待resume 完成后再返回pm_runtime_put()原子递减 usage_count若结果为 0 且 autosuspend 已启用则启动 suspend 延迟计时pm_runtime_put_sync()原子递减 usage_count若结果为 0 且 autosuspend 已启用则同步执行suspend 流程。这个计数器的设计意图非常明确只要有一个模块驱动、子系统、甚至用户空间 ioctl持有该设备的引用设备就必须保持供电只有当所有引用都被释放且满足空闲条件才允许挂起。举个真实案例某款工业相机驱动在 open() 时调用pm_runtime_get_sync(dev)确保 sensor 和 ISP 通电在 close() 时调用pm_runtime_put_sync(dev)。但如果在 read() 过程中V4L2 子系统又调用了pm_runtime_get(dev)用于保证 DMA buffer 访问期间供电稳定那么即使 close() 执行完毕usage_count 仍为 1设备不会挂起。直到 V4L2 完成帧传输并调用pm_runtime_put(dev)计数归零autosuspend 倒计时才真正开始。注意pm_runtime_get()和pm_runtime_put()必须成对出现且不能在中断上下文调用因可能 sleep。我在调试某款音频 codec 时曾因在 IRQ handler 中误调pm_runtime_get()导致 hard lockup——因为该函数内部可能触发 workqueue 调度而中断上下文禁止 sleep。2.3 autosuspend让挂起“等一等”避免抖动直接在 usage_count 归零后立刻 suspend会导致高频设备如触摸屏、加速度计频繁启停既增加唤醒延迟又产生电源噪声。为此runtime PM 引入了autosuspend_delay机制每个设备可通过dev-power.autosuspend设置延迟毫秒数默认 -1表示禁用 autosuspend当 usage_count 归零时内核启动一个struct delayed_work延时autosuspend_delay后再执行 suspend若在此期间有新的pm_runtime_get()到达则 cancel 该 work并重置倒计时。这个 delay 不是固定值而是可动态调整的。例如Android 的PowerManagerService会根据系统负载、屏幕状态、电池电量通过 sysfs 接口/sys/devices/.../power/autosuspend实时修改各设备的 delay 值。我实测过当手机进入 Doze 模式时蓝牙 HCI 设备的 autosuspend_delay 会被设为 5000ms而一旦检测到 BLE 广播包该值立即降为 100ms确保快速响应。2.4 wakeup capability让设备自己决定“能不能被叫醒”并非所有设备都支持被外部事件唤醒。runtime PM 通过dev-power.can_wakeup和dev-power.wakeup字段管理这一能力can_wakeup是布尔标志由驱动在 probe 时设置如device_set_wakeup_capable(dev, true)wakeup是一个struct wakeup_source *代表该设备的唤醒源对象包含 name、active count、expire time 等当设备处于 suspended 状态且can_wakeup true则其 IRQ 可以触发__pm_wakeup_event()进而调用pm_runtime_resume()。这里有个关键细节wakeup capability 必须在 suspend 前就准备好。如果驱动在 suspend 回调中才调用enable_irq_wake()而此时设备电源已断GPIO 中断控制器可能无法捕获信号。正确做法是在 probe 阶段就配置好 wakeup source并在 suspend 回调中仅做最小化操作如保存寄存器、关闭时钟。我曾遇到一块 STM32H7 的 USB OTG 设备在 suspend 后无法被 host 枚举唤醒。查到最后发现驱动在usb_otg_suspend()中调用了disable_irq(otg-irq)但没调用enable_irq_wake(otg-irq)—— 导致内核认为该 IRQ 不具备 wakeup 能力直接忽略其触发。修复方案很简单在 probe 末尾添加enable_irq_wake(otg-irq)并在 suspend 中移除disable_irq()调用。3. 实操核心从驱动注册到状态观测的完整链路光懂理论不够真正落地时你得亲手把 runtime PM “接进”一个驱动里并能准确观测其行为。下面以一个典型的 I2C 温度传感器如 TMP102为例展示从初始化到验证的全流程。3.1 驱动初始化阶段声明能力、注册回调// drivers/i2c/busses/i2c-tmp102.c static int tmp102_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct tmp102_data *data; int ret; data devm_kzalloc(client-dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; i2c_set_clientdata(client, data); >static int tmp102_runtime_suspend(struct device *dev) { struct i2c_client *client to_i2c_client(dev); struct tmp102_data *data i2c_get_clientdata(client); int ret; /* 1. 禁用连续转换模式停止 ADC 采样*/ ret i2c_smbus_write_word_data(client, TMP102_REG_CONFIG, TMP102_CFG_SHUTDOWN); if (ret) return ret; /* 2. 关闭 VDD如果硬件支持——注意此操作必须在 I2C 通信完成后 */ regulator_disable(data-vdd); /* 3. 关闭时钟如果使用专用 clock*/ clk_disable_unprepare(data-clk); dev_dbg(dev, runtime suspend completed); return 0; } static int tmp102_runtime_resume(struct device *dev) { struct i2c_client *client to_i2c_client(dev); struct tmp102_data *data i2c_get_clientdata(client); int ret; /* 1. 使能时钟 */ ret clk_prepare_enable(data-clk); if (ret) return ret; /* 2. 使能 VDD */ ret regulator_enable(data-vdd); if (ret) goto err_clk_disable; /* 3. 等待电源稳定典型值 100us~1ms查阅 datasheet*/ usleep_range(100, 200); /* 4. 重写 config 寄存器恢复连续转换模式 */ ret i2c_smbus_write_word_data(client, TMP102_REG_CONFIG, TMP102_CFG_CONTINUOUS); if (ret) goto err_regulator_disable; dev_dbg(dev, runtime resume completed); return 0; err_regulator_disable: regulator_disable(data-vdd); err_clk_disable: clk_disable_unprepare(data-clk); return ret; }注意事项suspend 回调中严禁调用可能 sleep 的函数如msleep()、wait_event_timeout()因为该回调在 workqueue 上执行且可能被中断上下文触发。必须用usleep_range()或硬件 busy-wait。resume 回调中必须检查所有资源获取是否成功失败时要清理已分配资源如上面的err_regulator_disable分支否则下次 resume 可能因资源冲突失败。所有 I2C 通信必须在电源稳定后进行。TMP102 datasheet 明确要求VDD 上升至 1.8V 后需等待至少 100μs 才能访问寄存器。3.3 用户空间观测用 sysfs 和 debugfs 看清每一帧状态内核为 runtime PM 提供了丰富的观测接口无需改代码、无需 recompile即可实时诊断1基础状态查询# 查看设备当前 runtime 状态 $ cat /sys/devices/platform/80860F09:01/i2c-0/0-0048/power/runtime_status active # 查看 usage_count 和 autosuspend_delay $ cat /sys/devices/platform/80860F09:01/i2c-0/0-0048/power/usage_count 1 $ cat /sys/devices/platform/80860F09:01/i2c-0/0-0048/power/autosuspend 3000 # 强制触发 suspend用于测试 $ echo auto /sys/devices/platform/80860F09:01/i2c-0/0-0048/power/control $ cat /sys/devices/platform/80860F09:01/i2c-0/0-0048/power/runtime_status suspended2深度诊断启用 PM debug log# 开启 runtime PM 相关 debug 信息需 CONFIG_PM_DEBUGy $ echo 1 /sys/module/suspend/parameters/pm_debug_messages $ dmesg -c # 清空旧日志 $ cat /sys/devices/platform/80860F09:01/i2c-0/0-0048/power/runtime_status active # 此时 dmesg 会输出 # [ 123.456789] tmp102 0-0048: pm_runtime: resuming device # [ 123.457890] tmp102 0-0048: pm_runtime: device resumed3wakeup source 监控# 查看系统所有 wakeup source 活跃状态 $ cat /sys/power/wakeup_count 12345 # 查看具体设备的 wakeup 状态 $ cat /sys/devices/platform/80860F09:01/i2c-0/0-0048/power/wakeup enabled # 查看 wakeup event 统计需 CONFIG_PM_SLEEP_DEBUGy $ cat /d/wakeup_sources name active_count event_count wakeup_count expire_count active_since tmp102_0-0048 0 12 12 0 0实操技巧当发现设备无法 suspend 时先检查usage_count是否为 0。如果不是用lsof或fuser查看哪些进程打开了该设备的 sysfs 节点如果是 0再检查autosuspend值是否为 -1禁用或power/control是否被设为on强制常开。我曾遇到一个 casepower/control被 udev rules 错误地设为on导致所有 I2C 设备永久供电功耗居高不下。4. 常见问题与排查技巧实录那些让你熬夜的 runtime PM Bug在实际项目中runtime PM 相关问题往往表现为“设备莫名耗电”、“唤醒失败”、“suspend 卡死”等表象背后原因却千差万别。以下是我在多个芯片平台ARM64/ARM32/RISC-V上踩过的坑按发生频率排序附带定位方法和修复方案。4.1 问题速查表症状、原因、验证命令、修复要点症状可能原因快速验证命令修复要点runtime_status长期显示active即使无任何访问usage_count 0或power/control被设为on或 autosuspend_delay -1cat /sys/.../power/usage_countcat /sys/.../power/controlcat /sys/.../power/autosuspend检查驱动是否漏掉pm_runtime_put()确认 udev rules 未覆盖 controlprobe 中显式调用pm_runtime_set_autosuspend_delay()设备能 suspend但无法被中断唤醒can_wakeup未设为 true或enable_irq_wake()未调用或 wakeup source 未激活cat /sys/.../power/wakeupcat /proc/interrupts | grep xxxcat /d/wakeup_sources在 probe 中调用device_set_wakeup_capable(dev, true)和enable_irq_wake(irq)确认 IRQ 在 suspend 前未被 disablesuspend/resume 过程中 kernel panic 或 lockupsuspend/resume 回调中调用了可能 sleep 的函数或存在自旋锁死锁或并发 get/put 未加保护dmesg | grep -i lockup|panic|sleep查看 oops call trace回调中禁用所有msleep/wait_event用spin_lock_irqsave替代mutex_lock对共享变量加atomic_t保护设备 suspend 后再次访问时报 I/O error硬件 reset 未完成或寄存器未正确恢复或时钟/VDD 未稳定就访问dmesg | grep tmp102用逻辑分析仪抓 I2C bus 波形resume 回调中增加usleep_range()等待电源稳定读取 status 寄存器确认硬件 ready保存/恢复关键寄存器上下文多个子设备共用同一 parentparent suspend 失败parent 的 usage_count 未归零或子设备 suspend 顺序错误或 parent 的 PM callback 未正确 propagatecat /sys/devices/platform/xxx/power/usage_countcat /sys/devices/platform/xxx/subsystem/devices确保子设备 suspend 完成后再 suspend parentparent 的 suspend 回调中遍历所有 child 调用pm_runtime_suspend()使用pm_runtime_force_suspend()作为 fallback4.2 典型案例深度复盘USB Host Controller 的 autosuspend 失效现象某 Intel Bay Trail 平台USB 2.0 Host Controllerxhci_hcd在插入 U 盘后拔出 U 盘 5 秒runtime_status仍为activeusage_count1功耗无下降。排查过程cat /sys/bus/usb/devices/1-0:1.0/power/usage_count→ 输出1lsof /sys/bus/usb/devices/1-0:1.0/→ 无进程占用cat /sys/bus/usb/devices/1-0:1.0/power/autosuspend→ 输出-1禁用检查驱动源码drivers/usb/host/xhci-pci.c发现xhci_pci_probe()中未调用pm_runtime_set_autosuspend_delay()进一步发现xhci_pci_probe()调用了pm_runtime_enable()但xhci_plat_probe()用于 platform device却漏掉了 autosuspend 设置根因该平台 USB controller 以 platform device 方式注册而xhci_plat_probe()函数中pm_runtime_enable()被调用但pm_runtime_set_autosuspend_delay()被遗漏。导致内核使用默认值 -1autosuspend 功能实质关闭。修复补丁--- a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c -123,6 123,7 static int xhci_plat_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_set_autosuspend_delay(dev, 2000); pm_runtime_allow(dev);验证打上补丁后拔出 U 盘2 秒后runtime_status变为suspendedusage_count0平台待机功耗下降 15mW。4.3 高级调试技巧用 ftrace 抓取 runtime PM 事件流当 sysfs 信息不足以定位问题时ftrace 是终极武器。以下命令可捕获完整的 runtime PM 状态变迁# 启用 pm event trace $ echo 1 /sys/kernel/debug/tracing/events/power/rpm_callback/enable $ echo 1 /sys/kernel/debug/tracing/events/power/rpm_status/enable $ echo 1 /sys/kernel/debug/tracing/events/power/rpm_resume/enable $ echo 1 /sys/kernel/debug/tracing/events/power/rpm_suspend/enable # 开始 trace $ echo 1 /sys/kernel/debug/tracing/tracing_on # 执行你的操作如插拔设备、读取 sensor $ cat /sys/class/i2c-adapter/i2c-0/0-0048/temp1_input # 停止 trace 并查看 $ echo 0 /sys/kernel/debug/tracing/tracing_on $ cat /sys/kernel/debug/tracing/trace输出示例xhci_hcd-1010 [000] d... 12345.678901: rpm_resume: dev0000:00:14.0 xhci_hcd-1010 [000] d... 12345.678923: rpm_callback: dev0000:00:14.0 fnresume xhci_hcd-1010 [000] d... 12345.678945: rpm_status: dev0000:00:14.0 statusactive ... xhci_hcd-1010 [000] d... 12348.123456: rpm_suspend: dev0000:00:14.0 xhci_hcd-1010 [000] d... 12348.123478: rpm_callback: dev0000:00:14.0 fnsuspend xhci_hcd-1010 [000] d... 12348.123490: rpm_status: dev0000:00:14.0 statussuspended提示ftrace 输出中rpm_callback行告诉你哪个回调被调用rpm_status行告诉你状态变更结果。如果看到rpm_suspend事件但后续没有rpm_callback: fnsuspend说明 suspend 回调根本没执行——大概率是 driver 没注册回调或pm_runtime_enable()未调用。5. runtime PM 与内核其他子系统的协同关系它不是孤岛runtime PM 从不单独存在。它像一条暗河贯穿于内核设备模型、电源管理框架、中断子系统、甚至热管理模块之中。理解它与其他子系统的耦合点是写出健壮驱动的前提。5.1 与设备模型Device Model的深度绑定struct device是 runtime PM 的载体。每个 device 都内置struct dev_pm_info power而该结构体的生命周期与 device 本身完全一致。这意味着device_register()时power结构体被初始化usage_count0,statusRPM_SUSPENDEDdevice_del()时pm_runtime_disable()被隐式调用防止 dangling referencedevice_shutdown()系统关机时会遍历所有 device强制调用pm_runtime_force_suspend()。因此任何绕过 device_register() 的设备注册方式如 legacy platform device without proper struct device都会导致 runtime PM 失效。我曾在一个老项目中看到驱动直接操作ioremap()得到的寄存器地址而不注册struct device结果无论怎么调pm_runtime_*usage_count始终为 0状态永不变化。5.2 与 suspend-to-RAMS2RAM的协作逻辑当系统执行echo mem /sys/power/state进入 S2RAM 时runtime PM 并非被 bypass而是进入“强制模式”内核首先对所有 device 调用pm_runtime_force_suspend()无视usage_count和autosuspend_delay若某个 device 的runtime_suspend回调返回-EBUSY内核会打印 warning但继续向下执行S2RAM 不会因此失败resume 时内核调用pm_runtime_force_resume()同样无视当前状态。这种“force”机制保证了 S2RAM 的可靠性但也带来风险如果 driver 的force_suspend未正确处理硬件状态resume 后设备可能无法工作。因此优秀的 driver 应在runtime_suspend和force_suspend中复用同一套硬件关闭逻辑仅在force_suspend中增加对 critical state 的保存。5.3 与 thermal subsystem 的联动现代 SoC 的 thermal governor如step_wise在检测到温度过高时会主动降低设备性能。对于支持 runtime PM 的设备thermal subsystem 可以直接调用pm_runtime_put_sync()来“软关闭”设备而非粗暴地降低频率。例如当 CPU 温度 85°Cthermal governor 调用pm_runtime_put_sync(gpu_dev)让 GPU 进入 suspended 状态当温度回落至 75°C再调用pm_runtime_get_sync(gpu_dev)恢复。这种联动需要 driver 在runtime_resume()中检查 thermal state并决定是否真正恢复 full performance。我在调试某款 Mali GPU 驱动时发现它在 resume 后直接全速运行导致温度再次飙升——修复方案是在 resume 回调中读取thermal_zone_get_temp()若温度仍高则只恢复 50% 频率直到 thermal governor 显式通知。5.4 与 Regulator Framework 的配合struct regulator是电压域的抽象。runtime PM 的suspend回调中regulator_disable()是标准操作而resume回调中regulator_enable()必须在 I2C/SPI 通信之前完成。但 regulator framework 本身也有一套 runtime PM 逻辑regulator_enable()内部会调用pm_runtime_get_sync()获取其 parent如 PMIC device的供电如果 PMIC device 自身也启用了 runtime PM那么regulator_enable()可能触发 PMIC 的 resume 流程。这就形成了跨层级的 runtime PM 依赖链。如果某一级 regulator 的 parent 未正确 enable runtime PM整个链路就会卡死。我遇到过一个 caseWiFi 模块的 VDDIO 由 PMIC 的 LDO1 供电而 LDO1 的 parentPMIC I2C adapter未调用pm_runtime_enable()导致regulator_enable(wifi_vddio)永远阻塞在pm_runtime_get_sync()上。解决方案在 PMIC driver 的 probe 中不仅要pm_runtime_enable()自身还要确保其所有 child regulator 都能被正确管理。内核提供了regulator_bulk_enable()和regulator_bulk_disable()它们会自动处理父子依赖比单个 regulator 操作更安全。6. 性能与功耗的平衡术如何设定最优 autosuspend_delayautosuspend_delay 不是越小越好也不是越大越省电。它是一个需要根据硬件特性、应用场景、用户体验三维权衡的参数。设定不当轻则增加唤醒延迟重则引发功能异常。6.1 延迟设定的黄金法则传感器类设备温度、加速度、光照delay 1000 ~ 5000 ms理由采样间隔通常为 100ms~1s过短 delay 会导致频繁启停过长则浪费电。TMP102 典型采样周期 250ms设为 3000ms 较合理。通信类设备USB、PCIe、SDIOdelay 5000 ~ 30000 ms理由需容忍突发流量。USB HID 设备键盘/鼠标设为 5000msPCIe SSD 设为 15000ms避免小 IO 导致反复唤醒。显示类设备LCD、HDMIdelay 0禁用 autosuspend或 60000 ms理由用户感知敏感。HDMI CEC 设备可设为 0即pm_runtime_put_sync()后立即 suspend但 LCD panel 必须设为大值否则屏幕闪烁。存储类设备eMMC、UFSdelay 10000 ~ 60000 ms

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑