环境

硬件平台:某ARM SoC

软件平台:Linux

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

分析

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

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

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

//调用 complete

  1. static void spi_complete(void *arg)
  2. {
  3. complete(arg);
  4. }

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

  1. static int __spi_sync(struct spi_device *spi, struct spi_message *message,
  2. int bus_locked)
  3. {
  4. DECLARE_COMPLETION_ONSTACK(done);
  5. ...
  6. message->complete = spi_complete;
  7. message->context = &done;
  8. message->spi = spi;
  9.  
  10. if (status == 0) {
  11. ...
  12. __spi_pump_messages(master, false);
  13. ...
  14.  
  15. wait_for_completion(&done);
  16. status = message->status;
  17. }
  18. message->context = NULL;
  19. return status;
  20. }

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

  1. void spi_finalize_current_message(struct spi_master *master)
  2. {
  3. ...
  4. spin_lock_irqsave(&master->queue_lock, flags);
  5. mesg = master->cur_msg;
  6. spin_unlock_irqrestore(&master->queue_lock, flags);
  7.  
  8. ...
  9. if (mesg->complete)
  10. mesg->complete(mesg->context);
  11. }

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

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

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

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

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

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

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

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

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

__spi_sync 函数中,有如下语句:

  1. __spi_pump_messages(master, false); // 处理spi message,发起传输
  2. wait_for_completion(&done); // 等待传输完成
  3. 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。因此造成了问题。

  1. static u8 dm9051_r_reg(struct dm9051_net *dm, u16 reg_addr)
  2. {
  3. struct spi_transfer *xfer = &dm->spi_xfer1;
  4. struct spi_message *msg = &dm->spi_msg1;
  5. u8 r_cmd[2] = {reg_addr, 0x00};
  6. u8 r_data[2] = {0x00, 0x00};
  7. int ret;
  8.  
  9. xfer->tx_buf = r_cmd;
  10. xfer->rx_buf = r_data;
  11. xfer->len = 2;
  12.  
  13. ret = spi_sync(dm->spidev, msg);
  14. if (ret < 0)
  15. DM_MSG0("dm9051_r_reg: spi_sync() failed\n");
  16.  
  17. return r_data[1];
  18. }
  19.  
  20. static int dm9051_probe(struct spi_device *spi)
  21. {
  22. ...
  23. spi_message_init(&dm->spi_msg1);
  24. spi_message_add_tail(&dm->spi_xfer1, &dm->spi_msg1);
  25. ...
  26. }

解决

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

  1. static inline int
  2. spi_read(struct spi_device *spi, void *buf, size_t len)
  3. {
  4. struct spi_transfer t = {
  5. .rx_buf = buf,
  6. .len = len,
  7. };
  8. struct spi_message m;
  9.  
  10. spi_message_init(&m);
  11. spi_message_add_tail(&t, &m);
  12. return spi_sync(spi, &m);
  13. }

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

作者: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. JAVAEE_Servlet_05_ServletConfig接口

    ServletConfig接口 研究javax.servlet.ServletConfig接口 1.javax.servlet.ServletConfig是一个接口 2.Apache Tomcat服务 ...

  2. 实现服务端和客户端的实时双向数据传输-WebSocket简单了解

    WebSocket 前段时间项目中遇到了消息推送的问题,当时采用客户端轮询,每隔 5s 请求一次数据.由于轮询的效率低,非常浪费资源.后面准备把轮询调整为使用 WebSocket 来建立连接,实现推送 ...

  3. Laravel路由中不固定数量的参数如何实现?

    前言 laravel是个好框架,我也在学习和使用,并且在公司里推广,最近在读 Laravel 源码的时候,发现了一个段特别有趣的代码,大家请看: ... 这三个点是做什么用的呢?我查了 PHP 的手册 ...

  4. hdu1978 简单记忆化搜索

    题意: How many ways Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) T ...

  5. 开启Android Apk调试与备份选项的Xposed模块的编写

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80963610 在进行Android应用程序逆向分析的时候,经常需要进行Andro ...

  6. 2020年电子设计大赛F题

    挺简单前一百分得了九十多 当然主要是队友很给力 1 温度判别 MLX90614DCC,然后测温拟合吧...从五十度到三十度平均一次要测一个半小时...这是真的痛苦...然后虽然文件里面说自带测温工具, ...

  7. Day001 基本的Dos命令

    基本的Dos命令 打开cmd的方式 开始+系统+命令提示符(有时候需要右键以管理员身份运行) Win+R键,输入cmd打开控制台 按住shift键的同时鼠标右键,点击在此处打开powershell窗口 ...

  8. 接口测试原理及Postman详解

    接口测试定义 接口是前后端沟通的桥梁,是数据传输的通道,包括外部接口.内部接口.内部接口又包括:上层服务与下层服务接口,同级接口 生活中常见接口:电脑上的键盘.USB接口,电梯按钮,KFC下单 接口测 ...

  9. Codeforces Beta Round #107(Div2)

    B.Phone Numbers 思路:就是简单的结构体排序,只是这里有一个技巧,就是结构体存储的时候,直接存各种类型的电话的数量是多少就行,在读入电话的时候,既然号码是一定的,那么就直接按照格式%c读 ...

  10. MzzTxx——博客目录

    准备阶段 团队介绍 需求分析 技术规格说明书 功能规格说明书 Alpha 阶段任务分配 团队贡献分分配方案 Scrum Meeting Alpha 2021.04.21 Scrum Meeting 0 ...