Runtime PM 处理不当导致的 external abort on non-linefetch 案例分享
硬件平台:某ARM SoC
软件平台:Linux
1 Runtime PM 简介
在介绍 Runtime PM 之前,不妨先看看传统的电源管理。传统的电源管理机制,称之为 System PM(System Suspend & Resume),当整个系统要进入睡眠时,依次调用各驱动模块的 suspend 函数,是一种粗粒度的电源管理,执行路径相对也比较单一。
Runtime PM,直译过来就是运行时电源管理。每个设备(包括芯片内部件)各自处理好自身的电源管理工作,在不需要工作的时候尽量进入低功耗状态,在需要工作时又重新起来。这样即使整个系统没有进入睡眠的情况下,设备自身也可以根据实际工作情况决定是否要进入低功耗状态,达到尽量省电的目的。
落实到代码上,当需要设备工作时,通过调用 pm_runtime_get_sync 让设备 runtime resume;当工作完成后,通过调用 pm_runtime_put 让设备 runtime suspend,伪代码如下:
senddata()
{
pm_runtime_get_sync()
do something ...
pm_runtime_put()
}
recvdata()
{
pm_runtime_get_sync()
do something ...
pm_runtime_put()
}
pm_runtime_get_sync 和 pm_runtime_put 会维护一个引用计数,pm_runtime_get_sync 会增加引用计数,pm_runtime_put 会减少引用计数,当引用计数为0时,才会真正让设备进入低功耗。
Runtime PM 的概念是比较直观的,对于某个设备来说,就是谁需要我工作,就 get 我,否则就 put 我。但是提供的函数接口有点多,本文的重点不在这里,就不一一介绍了,常用的如下:
- pm_runtime_get_sync //请求
- pm_runtime_put //释放
- pm_runtime_use_autosuspend //启用auto-suspend
- pm_runtime_set_autosuspend_delay //设置多久之后auto-suspend
- pm_runtime_put_autosuspend //带auto-suspend的释放
- pm_runtime_mark_last_busy //重置auto-suspend时间计数
Runtime PM 调用的时机,需要设备驱动仔细地处理,不然可能引发功耗问题或者系统异常。
- 如果控制的粒度太细,比如封装一个寄存器读写接口,每次去读写这个设备的寄存器时,都先 get 再 put,那未免代价太高了;
- 如果控制的粒度太粗,比如设备驱动起来后就一直 get,直到系统 suspend 才 put,那就和传统的电源管理差不多了,失去了 Runtime PM 的意义。
- 如果 get / put 接口没有成对调用,比如 get 的次数大于 put 的次数,那设备就进不了低功耗。
- 如果 put 的时机不太合适,导致设备下电后仍然有代码访问设备,那么就可能出现异常。
2 问题案例 Kernel Panic:external abort on non-linefetch
external abort on non-linefetch,常见的原因是:读写芯片内某个部件的寄存器时,该部件的 power 和 clock 还没有开启。
案例一,通过用户空间 spidev_test 程序测试 SPI 时报错。
[ 86.901554] c2 Unhandled fault: external abort on non-linefetch (0x1008) at 0xe999a008
[ 86.909373] c2 pgd = 6fa82014
[ 86.912315] c2 [e999a008] *pgd=a80e1811, *pte=70a00653, *ppte=70a00453
[ 86.918813] c2 Internal error: : 1008 [#1] PREEMPT SMP ARM
[ 86.942798] c2 CPU: 2 PID: 2653 Comm: spidev_test Tainted: G O 4.14.133+ #10
[ 86.950923] c2 Hardware name: Generic DT based system
[ 86.955945] c2 task: 434d36b9 task.stack: a6b02495
[ 86.960713] c2 PC is at foo_spi_chipselect+0x8c/0xdc
[ 86.965721] c2 LR is at 0xe999a000
[ 86.969095] c2 pc : [<c0671184>] lr : [<e999a000>] psr: a00f0013
[ 86.975590] c2 sp : c3a07db8 ip : 00000000 fp : c3a07dd4
[ 86.981039] c2 r10: 00000036 r9 : 00000003 r8 : 00000196
[ 86.986489] c2 r7 : 00000196 r6 : e999a008 r5 : c3a07db8 r4 : c067113c
[ 86.993241] c2 r3 : c11adb28 r2 : e9898030 r1 : 00000030 r0 : e9898000
[ 86.999993] c2 Flags: NzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none
[ 87.007349] c2 Control: 10c5383d Table: 8531806a DAC: 00000051
[ 87.013319] c2 Process spidev_test (pid: 2653, stack limit = 0xd6c1587c)
...
[ 87.184082] c2 [<c0671184>] (foo_spi_chipselect) from [<c066b4c0>] (spi_set_cs+0x8c/0x90)
[ 87.192294] c2 [<c066b4c0>] (spi_set_cs) from [<c066df88>] (spi_setup+0x128/0x1dc)
[ 87.199814] c2 [<c066df88>] (spi_setup) from [<c066fa84>] (spidev_ioctl+0x26c/0x84c)
[ 87.207521] c2 [<c066fa84>] (spidev_ioctl) from [<c02cda60>] (vfs_ioctl+0x28/0x44)
[ 87.215048] c2 [<c02cda60>] (vfs_ioctl) from [<c02ce370>] (do_vfs_ioctl+0x7a8/0x900)
[ 87.222748] c2 [<c02ce370>] (do_vfs_ioctl) from [<c02ce524>] (SyS_ioctl+0x5c/0x84)
[ 87.230276] c2 [<c02ce524>] (SyS_ioctl) from [<c0108760>] (ret_fast_syscall+0x0/0x28)
从函数调用栈可以看出,spidev_test 程序通过 IOCTL 与 SPI driver 交互时,在 SPI driver 的 foo_spi_chipselect 函数中发生了错误。foo_spi_chipselect 函数的内容如下,可以看到它读写了 SPI Controller 的寄存器,但是操作之前并没有调用 pm_runtime_get 让 controller resume。
static void foo_spi_chipselect(struct spi_device *sdev, bool cs)
{
struct spi_controller *sctlr = sdev->controller;
struct foo_spi *ss = spi_controller_get_devdata(sctlr);
u32 val; val = readl_relaxed(ss->base + FOO_SPI_CTL0);
/* The SPI controller will pull down CS pin if cs is 0 */
if (!cs) {
val &= ~FOO_SPI_CS0_VALID;
writel_relaxed(val, ss->base + FOO_SPI_CTL0);
} else {
val |= FOO_SPI_CSN_MASK;
writel_relaxed(val, ss->base + FOO_SPI_CTL0);
}
}
我们可以改成如下代码解决问题。
static void foo_spi_chipselect(struct spi_device *sdev, bool cs)
{
struct spi_controller *sctlr = sdev->controller;
struct foo_spi *ss = spi_controller_get_devdata(sctlr);
u32 val; + pm_runtime_get_sync(ss->dev);
val = readl_relaxed(ss->base + FOO_SPI_CTL0);
/* The SPI controller will pull down CS pin if cs is 0 */
if (!cs) {
val &= ~FOO_SPI_CS0_VALID;
writel_relaxed(val, ss->base + FOO_SPI_CTL0);
} else {
val |= FOO_SPI_CSN_MASK;
writel_relaxed(val, ss->base + FOO_SPI_CTL0);
}
+ pm_runtime_mark_last_busy(ss->dev);
+ pm_runtime_put_autosuspend(ss->dev);
}
但是较新的(2019年10月份以后) kernel spi 代码,已经在 spi core 代码中修复了此问题,无需改动芯片厂商的 controller 驱动。
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index f9502db..19007e0 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -3091,7 +3091,20 @@ int spi_setup(struct spi_device *spi)
if (spi->controller->setup)
status = spi->controller->setup(spi); - spi_set_cs(spi, false);
+ if (spi->controller->auto_runtime_pm && spi->controller->set_cs) {
+ status = pm_runtime_get_sync(spi->controller->dev.parent);
+ if (status < 0) {
+ pm_runtime_put_noidle(spi->controller->dev.parent);
+ dev_err(&spi->controller->dev, "Failed to power device: %d\n",
+ status);
+ return status;
+ }
+ spi_set_cs(spi, false);
+ pm_runtime_mark_last_busy(spi->controller->dev.parent);
+ pm_runtime_put_autosuspend(spi->controller->dev.parent);
+ } else {
+ spi_set_cs(spi, false);
+ } if (spi->rt && !spi->controller->rt) {
spi->controller->rt = true;
详情可参考 https://lore.kernel.org/linux-arm-kernel/1572426234-30019-1-git-send-email-luhua.xu@mediatek.com/
案例二 USB做Host时反复开关机测试出现异常
[ 11.616956] c0 Unhandled fault: external abort on non-linefetch (0x1008) at 0xd02d4001
[ 11.624774] c0 pgd = c0004000
[ 11.627708] [d02d4001] *pgd=8f69a811, *pte=20200653, *ppte=20200453
[ 11.633944] c0 Internal error: : 1008 [#1] PREEMPT SMP ARM
[ 11.639390] Modules linked in:
[ 11.642424] c0 CPU: 0 PID: 161 Comm: kworker/0:3 Not tainted 4.4.83 #1
[ 11.648909] c0 Hardware name: Generic DT based system
[ 11.653940] Workqueue: events musb_deassert_reset
[ 11.658601] c0 task: ce854780 task.stack: ce8c6000
[ 11.663364] c0 PC is at musb_default_readb+0x54/0x9c
原因和案例一类似,musb_deassert_reset 在 USB controller shutdown 的状态下访问了 USB controller 的寄存器。
static void musb_deassert_reset(struct work_struct *work)
{
struct musb *musb;
unsigned long flags; musb = container_of(work, struct musb, deassert_reset_work.work); + pm_runtime_get_sync(musb->controller); spin_lock_irqsave(&musb->lock, flags); if (musb->port1_status & USB_PORT_STAT_RESET)
musb_port_reset(musb, false); spin_unlock_irqrestore(&musb->lock, flags); + pm_runtime_put(musb->controller);
}
按以上修改后,上述错误路径不复现,但是出现了新的错误路径,说明修改得并不彻底。
[ 13.364606] c0 Unhandled fault: external abort on non-linefetch (0x1008) at 0xd02d4001
[ 13.372418] c0 pgd = c0004000
[ 13.375359] [d02d4001] *pgd=8f69a811, *pte=20200653, *ppte=20200453
[ 13.381595] c0 Internal error: : 1008 [#1] PREEMPT SMP ARM
[ 13.387042] Modules linked in:
[ 13.390075] c0 CPU: 0 PID: 4 Comm: kworker/0:0 Not tainted 4.4.83 #1
[ 13.396388] c0 Hardware name: Generic DT based system
[ 13.401417] Workqueue: usb_hub_wq hub_event
[ 13.405562] c0 task: cf471380 task.stack: cf490000
[ 13.410326] c0 PC is at musb_default_readb+0x54/0x9c
梳理 musb 代码(drivers/usb/musb/*),发现 musb_gadget 代码有针对 runtime PM 的处理,musb_host 代码则没有针对 runtime PM 的处理。最后的处理方案是在 USB controller 驱动中完善 runtime PM 处理,没有修改 musb 公共代码。具体修改细节与该厂商 USB controller 的驱动实现逻辑有关,没有普遍的借鉴意义,就没有必要贴出了。
-------------------------------------------------------
作者:bigfish99
博客:https://www.cnblogs.com/bigfish0506/
公众号:大鱼嵌入式
Runtime PM 处理不当导致的 external abort on non-linefetch 案例分享的更多相关文章
- Linux Runtime PM介绍【转】
转自:http://blog.csdn.net/wlwl0071986/article/details/42677403 一.Runtime PM引言 1. 背景 (1)display的需求 (2)系 ...
- linux驱动程序之电源管理之Run-time PM 详解(4)
Run-time PM. 每个device或者bus都会向run-time PM core注册3个callback struct dev_pm_ops { ... int (*runtime_su ...
- Runtime.exec使用错误导致延迟.md
这篇文章是纪录了一个bug解决的过程,可是我还是没有可以真正地找出bug的缘由.希望大牛可以详解. 问题的发现 当接触的系统越来越大的时候,对于系统的性能越来越高的时候,找到表面问题的真正原因就慢慢地 ...
- linux runtime pm在深入了解的机制
一:runtime机构简介 何为runtime机制?也就是系统在非睡眠状态,设备在空暇时能够进入runtime suspend状态同一时候不依赖系统wake_lock机制.非空暇时运行runtime ...
- Drupal V7.3.1 框架处理不当导致SQL注入
这个漏洞本是2014年时候被人发现的,本着学习的目的,我来做个详细的分析.漏洞虽然很早了,新版的Drupal甚至已经改变了框架的组织方式.但是丝毫不影响对于漏洞的分析.这是一个经典的使用PDO,但是处 ...
- 开源jar包bug导致的CPU消耗200%问题分析案例
mapdb是什么 mapdb是一个快速.易用的嵌入式java数据库,主要提供map和set形式的数据存储,使用起来就像是在操作java本身的map,set, mapdb可以提供内存级别和磁盘级别的缓存 ...
- ie8浏览器 图片本身问题导致 无法显示图片--- 诡异现象的排查分享
引子: 前段时间 做新版2.0 首页 的时候, 总感觉 新版首页 线上 精彩回顾下的 2张图片颜色怪怪的,当时以为是图片压缩太厉害导致的,由于实在太忙就没太在意!以下 是来自线上 截图: 红色方 ...
- 案例分享 | dubbo 2.7.12 bug导致线上故障
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star.搜索关注微信公众号"捉虫大师",后端技术分享,架构设计.性能优化.源码阅读. ...
- INITIAL参数设置导致TRUNCATE TABLE不能降低高水位线案例
在一个数据库使用下面SQL找出了一批需要降低高水位线的表,其中有几个表没有数据,于是我打算用TRUNCATE来降低高水位线HWM SELECT a.owner, a.segment_na ...
随机推荐
- Java JFR 民间指南 - 事件详解 - jdk.ObjectAllocationSample
对象分配采样:jdk.ObjectAllocationSample 引入版本:Java 16 相关 ISSUE:Introduce JFR Event Throttling and new jdk.O ...
- 抛弃vuex ,拥抱ts,手撸泛型Store<T>!
前段时间学习了下vue3 和ts ,就尝试下做了个项目,结果发现vuex和ts几乎无法结合,越写越别扭,开始怀疑用ts就是自己给自己挖坑,然后加了几个vue相关的群,去抱怨了几句,得到大佬指点:你可以 ...
- 易酷CMS2.5本地文件包含漏洞复现
易酷CMS是一款影片播放CMS.该CMS2.5版本存在本地文件包含漏洞.我们可以利用这个漏洞,让其包含日志文件,然后再利用报错信息将一句话木马写入日志中.然后利用文件包含漏洞包含该日志文件,再用菜刀连 ...
- Wordpress主题编辑器漏洞复现
Wordpress是全球流行的博客网站,全球有上百万人使用它来搭建博客.他使用PHP脚本和Mysql数据库来搭建网站. 那么,如果当我们在渗透测试过程中获得到了别人Wordpress的账号和密码之后, ...
- UVA11464偶数矩阵
题意: 给你一个n*n的01矩阵,你的你的任务是吧尽量少的0变成1,使得每个元素的上下左右之和均为偶数(如果有的话),比如 0 0 0 0 1 0 1 0 0 ---&g ...
- 每天一道面试题LeetCode 26--删除排序数组中的重复项(python实现)
题目1:给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. ...
- liunx中文件夹不能删除怎么操作
1.运行rm -rf 文件名称 2.不能删除对应文件并且提示"rm: cannot remove './.user.ini': Operation not permitted" 操 ...
- Java中如何保证线程顺序执行
只要了解过多线程,我们就知道线程开始的顺序跟执行的顺序是不一样的.如果只是创建三个线程然后执行,最后的执行顺序是不可预期的.这是因为在创建完线程之后,线程执行的开始时间取决于CPU何时分配时间片,线程 ...
- ALPHA任务拆解
项目 内容 这个作业属于哪个课程 BUAA2020软件工程 这个作业的要求在哪里 作业要求 我们在这个课程的目标是 学会团队合作,共同开发一个完整的项目 这个作业在哪个具体方面帮助我们实现目标 团队任 ...
- Redis泛泛而谈(详细2W字)
本文适合于刚接触redis的,文章内容比较基础,大佬请绕道. 一.NoSQL入门和概述 Ⅰ-入门概述 1.为什么用NoSQL 1)单机MySQL的美好年代 在90年代,一个网站的访问量一般都不大,用单 ...