环境

硬件平台:某ARM SoC

软件平台:Linux

问题现象:产品做开关机压力测试,发生死机。

分析

用crash工具解析两次死机dump信息,得到死机前的log如下。两次死机的backtrace略有不同,但死机原因类似:最后都是在调用 complete 的过程中访问空指针导致 kernel panic。

log 1:
[ 1.092790] c2 Unable to handle kernel NULL pointer dereference at virtual address 00000004
[ 1.092796] c2 pgd = c0004000
[ 1.092802] c0 [00000004] *pgd=00000000
[ 1.092812] c2 Internal error: Oops: 5 [#1] PREEMPT SMP ARM
...
[ 1.094412] c0 Backtrace:
[ 1.094430] c2 [<c07e971c>] (_raw_spin_lock_irqsave) from [<c006e2a8>] (complete+0x1c/0x4c)
[ 1.094446] c2 [<c006e28c>] (complete) from [<c04318d4>] (spi_complete+0x10/0x14)
[ 1.094468] c2 [<c04318c4>] (spi_complete) from [<c0432328>] (spi_finalize_current_message+0x228/0x26c)
[ 1.094479] c2 [<c0432100>] (spi_finalize_current_message) from [<c04327c8>] (spi_transfer_one_message+0x45c/0x484)
[ 1.094503] c2 [<c043236c>] (spi_transfer_one_message) from [<c0432e98>] (__spi_pump_messages+0x6a8/0x6e8)
[ 1.094531] c2 [<c04327f0>] (__spi_pump_messages) from [<c04330fc>] (__spi_sync+0x208/0x270)
[ 1.094559] c2 [<c0432ef4>] (__spi_sync) from [<c0433178>] (spi_sync+0x14/0x18)
[ 1.094586] c2 [<c0433164>] (spi_sync) from [<c0440744>] (dm9051_r_reg+0x4c/0x6c)
[ 1.094595] c2 [<c04406f8>] (dm9051_r_reg) from [<c0441e30>] (dm9051_probe+0x6e4/0x82c)
[ 1.094605] c2 [<c044174c>] (dm9051_probe) from [<c0431764>] (spi_drv_probe+0x8c/0xa8) log 2:
[ 1.271300] c3 Unable to handle kernel NULL pointer dereference at virtual address 00000004
[ 1.271305] c3 pgd = c0004000
[ 1.271312] c0 [00000004] *pgd=00000000
[ 1.271323] c3 Internal error: Oops: 5 [#1] PREEMPT SMP ARM
...
[ 1.414585] c0 Backtrace:
[ 1.414603] c3 [<c07e97f4>] (_raw_spin_lock_irqsave) from [<c006e2a8>] (complete+0x1c/0x4c)
[ 1.414618] c3 [<c006e28c>] (complete) from [<c0431fb8>] (spi_complete+0x28/0x34)
[ 1.414643] c3 [<c0431f90>] (spi_complete) from [<c0432350>] (spi_finalize_current_message+0x230/0x278)
[ 1.414666] c3 [<c0432120>] (spi_finalize_current_message) from [<c04327f4>] (spi_transfer_one_message+0x45c/0x484)
[ 1.414693] c3 [<c0432398>] (spi_transfer_one_message) from [<c0432ec4>] (__spi_pump_messages+0x6a8/0x6e8)
[ 1.414725] c3 [<c043281c>] (__spi_pump_messages) from [<c0432f1c>] (spi_pump_messages+0x18/0x1c)
[ 1.414759] c3 [<c0432f04>] (spi_pump_messages) from [<c0042abc>] (kthread_worker_fn+0xc0/0x14c)
[ 1.414771] c3 [<c00429fc>] (kthread_worker_fn) from [<c00429e8>] (kthread+0x110/0x124)
[ 1.414798] c3 [<c00428d8>] (kthread) from [<c000f208>] (ret_from_fork+0x14/0x2c)

梳理kernel spi驱动代码:drivers/spi/spi.c

//调用 complete

static void spi_complete(void *arg)
{
complete(arg);
}

//给 spi_message 的 complete 与 context 成员赋值

static int __spi_sync(struct spi_device *spi, struct spi_message *message,
int bus_locked)
{
DECLARE_COMPLETION_ONSTACK(done);
...
message->complete = spi_complete;
message->context = &done;
message->spi = spi; if (status == 0) {
...
__spi_pump_messages(master, false);
... wait_for_completion(&done);
status = message->status;
}
message->context = NULL;
return status;
}

//回调 spi_message 的 complete 函数,即 spi_complete,传入的参数是 spi_message 的 context,即 __spi_sync 函数里面定义的 completion 变量 “done”。

void spi_finalize_current_message(struct spi_master *master)
{
...
spin_lock_irqsave(&master->queue_lock, flags);
mesg = master->cur_msg;
spin_unlock_irqrestore(&master->queue_lock, flags); ...
if (mesg->complete)
mesg->complete(mesg->context);
}

因此基本可以确定是执行 mesg->complete(mesg->context); 时,传入的参数 mesg->context 为 NULL 导致问题。继续看死机前的部分log:

//cpu2 上发起一次 spi 传输并完成

[    1.271151] c2 70a00000.spi: __spi_sync
[ 1.271172] c2 spi_master spi0: spi_finalize_current_message
[ 1.271180] c2 [spi_complete]

//cpu2 上再次发起一次 spi 传输并完成

[    1.271192] c2 70a00000.spi: __spi_sync
[ 1.271212] c2 spi_master spi0: spi_finalize_current_message
[ 1.271219] c2 [spi_complete]

//cpu2 上再次发起一次 spi 传输,但在完成前,cpu1 发起了新的 spi 传输

[    1.271227] c2 70a00000.spi: __spi_sync
[ 1.271247] c2 spi_master spi0: spi_finalize_current_message
[ 1.271249] c1 70a00000.spi: __spi_sync //cpu1 发起新的传输
[ 1.271261] c2 [spi_complete]
[ 1.271281] c1 dm9051_r_reg: spi_sync() failed

//cpu3 上执行传输完成的 completion 时,传入的 mesg->context 为 NULL 导致死机

[    1.271288] c3 spi_master spi0: spi_finalize_current_message
[ 1.271293] c3 [spi_complete]
[ 1.271300] c3 Unable to handle kernel NULL pointer dereference at virtual address 00000004

__spi_sync 函数中,有如下语句:

__spi_pump_messages(master, false); // 处理spi message,发起传输
wait_for_completion(&done); // 等待传输完成
message->context = NULL; //将该 spi message 的 context 置为 NULL

也就是说,__spi_sync 等到 completion 后会将本次已传输完成的 spi message 的 context 置为NULL。

如果在cpu1上发起新的传输时,传入的 spi message 变量地址与cpu2上的是同一个,那么cpu1就有可能访问到这个 NULL context。

spi message 变量是dm9051驱动传递下来的,查看dm9051驱动,其调用spi的代码如下,可以看到它使用了全局变量 dm->spi_msg1 来构造 spi message。因此造成了问题。

static u8 dm9051_r_reg(struct dm9051_net *dm, u16 reg_addr)
{
struct spi_transfer *xfer = &dm->spi_xfer1;
struct spi_message *msg = &dm->spi_msg1;
u8 r_cmd[2] = {reg_addr, 0x00};
u8 r_data[2] = {0x00, 0x00};
int ret; xfer->tx_buf = r_cmd;
xfer->rx_buf = r_data;
xfer->len = 2; ret = spi_sync(dm->spidev, msg);
if (ret < 0)
DM_MSG0("dm9051_r_reg: spi_sync() failed\n"); return r_data[1];
} static int dm9051_probe(struct spi_device *spi)
{
...
spi_message_init(&dm->spi_msg1);
spi_message_add_tail(&dm->spi_xfer1, &dm->spi_msg1);
...
}

解决

仿照 include/linux/spi/spi.h 中 spi_read 和 spi_write 的实现,每次读写前都构造新的 spi message。

static inline int
spi_read(struct spi_device *spi, void *buf, size_t len)
{
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
};
struct spi_message m; spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}

-------------------------------------------------

作者:bigfish99

博客:https://www.cnblogs.com/bigfish0506/

公众号:大鱼嵌入式

某SPI设备驱动引起的开关机压力测试死机问题一例的更多相关文章

  1. RT Thread的SPI设备驱动框架的使用以及内部机制分析

    注释:这是19年初的博客,写得很一般,理解不到位也不全面.19年末得空时又重新看了RTThread的SPI和GPIO,这次理解得比较深刻.有时间时再整理上传. -------------------- ...

  2. 开着idea,死机了,关机重启。重启之后,重新打开idea报错java.lang.AssertionError:upexpected content storage modification

    开着idea,死机了,关机重启.重启之后,重新打开idea报错java.lang.AssertionError:upexpected content storage modification. goo ...

  3. Linux设备驱动剖析之SPI(三)

    572至574行,分配内存,注意对象的类型是struct spidev_data,看下它在drivers/spi/spidev.c中的定义: struct spidev_data { dev_t de ...

  4. spi驱动框架全面分析,从master驱动到设备驱动

    内核版本:linux2.6.32.2  硬件资源:s3c2440 参考:  韦东山SPI视频教程 内容概括:     1.I2C 驱动框架回顾     2.SPI 框架简单介绍     3.maste ...

  5. Linux设备驱动剖析之SPI(二)

    957至962行,一个SPI控制器用一个master来描述.这里使用SPI核心的spi_alloc_master函数请求分配master.它在drivers/spi/spi.c文件中定义: struc ...

  6. RT-thread 设备驱动组件之SPI设备

    本文主要介绍RT-thread中的SPI设备驱动,涉及到的文件主要有:驱动框架文件(spi_dev.c,spi_core.c,spi.h),底层硬件驱动文件(spi_hard.c,spi_hard.h ...

  7. SPI设备的驱动

    主要包括两个SPI设备步骤:register_chrdevspi_register_driver关键点1:spi_board_info可以去已经运行的板子下面找例子:/sys/bus/spi/driv ...

  8. RT-Thread 设备驱动SPI浅析及使用

    OS版本:RT-Thread 4.0.0 测试BSP:STM32F407 SPI简介 SPI总线框架其实和I2C差不多,可以说都是总线设备+从设备,但SPI设备的通信时序配置并不固定,也就是说控制特定 ...

  9. linux内核SPI总线驱动分析(一)(转)

    linux内核SPI总线驱动分析(一)(转) 下面有两个大的模块: 一个是SPI总线驱动的分析            (研究了具体实现的过程) 另一个是SPI总线驱动的编写(不用研究具体的实现过程) ...

随机推荐

  1. 2. IntelliJ Idea 常用快捷键列表

    Ctrl+E,最近的文件 Ctrl+Shift+E,最近更改的文件 Shift+Click,可以关闭文件 Ctrl+[ OR ],可以跑到大括号的开头与结尾 Ctrl+F12,可以显示当前文件的结构 ...

  2. Django+Vue+Docker搭建接口测试平台实战

    一. 开头说两句 大家好,我叫林宗霖,是一位测试工程师,也是全栈测开训练营中的一名学员. 在跟着训练营学习完Docker容器技术系列的课程后,理所应当需要通过实操来进行熟悉巩固.正好接口自动化测试平台 ...

  3. SQL 查询的执行顺序

    SELECT语句的完整语法如下 SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN & ...

  4. Think5之删除单条数据功能

    //删除单条学员信息 public function deleteStu(Request $request){ $stu_id = $request->param('id'); $result ...

  5. 给HTML5 Video 设置多语言字幕文件

    现在各种支持HTML5的浏览器都能够播放html5视频了,但是对于字幕的支持却很少,我们期待像DVD那样强大的字幕. 往往我们还不得不通过js来做,着实是一件痛苦的事情. 现在IE10率先对HTML5 ...

  6. Docker学习笔记---通俗易懂

    目录 Docker 简介 Docker安装 Docker的基本组成 安装Docker 配置阿里云镜像加速 回顾helloworld流程 工作原理 Docker的常用命令 帮助命令 镜像命令 容器命令 ...

  7. hdu4740 不错的简单搜索

    题意:      给你一个n*n的图,给你驴和老虎的初始坐标和方向,已知他们的速度相同,他们走动的时候都是走直线,如果不能走,驴往右拐,老虎往左拐,如果拐了一次还走不了就原地不动,问他们的最早相遇位置 ...

  8. hdu5105给你一个方程,让你求极值(直接暴力)

    题意:       给你一个方程f[x] = abss(a * x * x * x + b * x * x + c * x + d); 然后给你各个参数还有x(-100<x<100)的取值 ...

  9. js限制上传文件类型和大小

    <html> <head> <script type="text/javascript"> function fileChange(target ...

  10. MetInfo Password Reset Poisoning By Host Header Attack

    if we know some user's email, the we will can reset the user's email by host header attack. The atta ...