最近碰到棘手的问题: 以太网进行iperf测试时, 发生了SMMU (System Memory Management Unit)访问异常导致内核崩溃. 原本只是内部测试发现, 后面在试验车上也概率性的出现. 问题发生的概率还不小. 很严重. 只能先从头把一些基本概念与流程梳理清楚. 好在最后还是找到了原因并解决了. 松了口气, 才有时间把整个问题的来龙去脉细细的总结下, 算是一个SMMU相关问题的案例.

首先来看看问题的发生的背景.

问题背景

问题发生在利用iperf做网络性能测试的时候, 测试系统(采用高通8155平台, 内置一个EMAC芯片, 最高支持1Gbps速率)作为客户端:

iperf -c 172.20.2.33 -p 8989 -f m -R

这里加-R参数表示客户端作为数据接收方(奇怪的是, 测试不加-R参数就不会有问题, 这也说明只有在接收数据的过程才会出现问题), 而服务端是发送方:

iperf -s -p 8989 -f m

这么测试几十个小时就很快出现了, 抓取到的问题堆栈如下. 前面的日志是SMMU相关的寄存器状态打印, 后面是内核调用堆栈.

[53480.526297] arm-smmu 15000000.apps-smmu: FAR    = 0x00000000a2a2a000
[53480.533192] arm-smmu 15000000.apps-smmu: PAR = 0x0000000000000000
[53480.540466] arm-smmu 15000000.apps-smmu: FSR = 0x40000402 [TF W SS ]
[53480.547750] arm-smmu 15000000.apps-smmu: TTBR0 = 0x000f00035bbaa000
[53480.554990] arm-smmu 15000000.apps-smmu: TTBR1 = 0x000f000000000000
[53480.562192] arm-smmu 15000000.apps-smmu: SCTLR = 0x00c000e7 ACTLR = 0x00000000
[53480.570572] arm-smmu 15000000.apps-smmu: CBAR = 0x0001f300
[53480.576741] arm-smmu 15000000.apps-smmu: MAIR0 = 0xf404ff44 MAIR1 = 0x00000000
[53480.585071] arm-smmu 15000000.apps-smmu: Unhandled context fault: iova=0xa2a2a000, cb=14, fsr=0x40000402, fsynr0=0x7e0013, fsynr1=0x0
[53480.597668] arm-smmu 15000000.apps-smmu: soft iova-to-phys=0x0000000000000000
[53480.605440] arm-smmu 15000000.apps-smmu: SOFTWARE TABLE WALK FAILED! Looks like 15000000.apps-smmu accessed an unmapped address!
[53480.617935] arm-smmu 15000000.apps-smmu: hard iova-to-phys (ATOS) failed
[53480.625147] arm-smmu 15000000.apps-smmu: SID=0x3c0
[53480.630332] arm-smmu 15000000.apps-smmu: Unhandled arm-smmu context fault!
[53480.638310] ------------[ cut here ]------------
[53480.638324] kernel BUG at /home/jenkins/.jenkins/workspace/SourceCode/kernel/msm-4.14/drivers/iommu/arm-smmu.c:1762!
[53480.649128] [KERN Warning] ERROR/WARN forces debug_lock off!
[53480.649135] [KERN Warning] check backtrace:
[53480.649151] CPU: 0 PID: 319 Comm: irq/386-arm-smm Tainted: G S O 4.14.170+ #2
[53480.649160] Hardware name: Qualcomm Technologies, Inc. SA8155P v2 PM8150 ADP-STAR model-D55 (DT)
[53480.649171] Call trace:
[53480.649215] dump_backtrace+0x0/0x1f4
[53480.649226] show_stack+0x20/0x2c
[53480.649241] dump_stack+0xe4/0x134
[53480.649255] debug_locks_off+0x54/0x88
[53480.649268] oops_enter+0x14/0x20
[53480.649275] die+0x38/0x16c
[53480.649284] bug_handler+0x50/0x88
[53480.649293] brk_handler+0x6c/0xb4
[53480.649301] do_debug_exception+0x7c/0x114
[53480.649309] el1_dbg+0x18/0x74
[53480.649321] arm_smmu_context_fault+0x8e0/0x944
[53480.649332] irq_thread_fn+0x2c/0x70
[53480.649340] irq_thread+0xc0/0x144
[53480.649350] kthread+0x128/0x138
[53480.649357] ret_from_fork+0x10/0x18
[53480.649367] Internal error: Oops - BUG: 0 [#1] PREEMPT SMP

从堆栈来看大致可以了解到, 这是由于SMMU监测到某个模块非法的访问DMA地址后, 引起了内核崩溃. 那么, 为何有这个错误SMMU访问错误? 这个错误又是哪个模块导致的? 是在什么情况下引起的SMMU内存错误了? 这不得不从SMMU本身说起.

什么是SMMU

简单来说, SMMU(System Memory Management Unit)是ARM为外设访问系统RAM提供了一种类似于MMU的虚拟内存访问机制, 外设可以通过DMA直接访问RAM, 而无需CPU的干预. 如此, 外设可以通过一个虚拟的地址即可访问物理地址(可以不连续), 做到了不同外设之间IO地址空间的彼此独立与隔离. 因此, SMMU也通常被称为IOMMU(Input/Output MMU).

下图是从ARM SMMU Spec手册里的一张SMMU简图: SMMU为设备与RAM之间构建了一个设备虚拟地址(IOVA)与物理地址之间的映射关系, 每次执行DMA数据传输的时候, 都要通过SMMU将IOVA地址翻译成对应的物理地址.

那么对于设备驱动来说, 如何使用SMMU了? 不妨来看下SMMU相关的API.

  • arm_iommu_create_mapping: 配置设备所要使用的VA(Virtual Address, 虚拟地址)的范围
  • arm_iommu_attach_device: 将分配好的VA地址范围与设备绑定, 并开启SMMU地址转换
  • dma_map_single/dma_unmap_single: 分配/去除某个DMA地址, 这种方式是异步的, 常用于一次性传输的场景(传输完成后DMA的映射即解除了)
  • dma_alloc_coherent/dma_free_coherent: 一致性(consistent), 同步(synchronous)的DMA内存分配方法, 确保CPU与设备的数据始终是同步的, 一般用于需要常驻内存的一些数据

这里不对IOMMU的代码做深入分析了. 有关IOMMU相关的流程可以参考内核代码:

  • kernel/drivers/iommu: SMMU驱动, 用于配置SMMU, 为设备驱动提供接口
  • kernel/arch/arm64/mm: 与平台相关的SMMU的页表分配的实现

    有了这些SMMU的基础知识, 我们就来分析下最开始那个问题.

SMMU访问异常问题分析

继续来看下问题的日志. 堆栈的前面一部分是有关SMMU的状态寄存器:

  • FSR(Fault Status Register)表示SMMU错误的类型(转换/权限等), 这里的值0x40000402 [TF W SS ], 说明是一个写操作时引起的页表访问错误

  • FAR(Fault Address Register): 表示发生错误的IO虚拟地址

  • PAR(Physical Address Register): 发生错误时查找到的物理地址, 这里是全0, 说明相应的IOVA地址没有映射

  • TTBRm(Translation Table Base Address):

    • TTBR0: 保存Translation Table0的基地址
    • TTBR1: 保存Translation Table1的基地址

重点看下如下两行日志, 我们可以知道发生内存映射异常的IOVA地址是0xa2a2a000, 对应的SID是0x3c0(SID是对应设备使用SMMU映射内存时的标识),SID一般在设备树DTS的配置中指定的.

[53480.585071] arm-smmu 15000000.apps-smmu: Unhandled context fault: iova=0xa2a2a000, cb=14, fsr=0x40000402, fsynr0=0x7e0013, fsynr1=0x0
....
[53480.625147] arm-smmu 15000000.apps-smmu: SID=0x3c0

查看内核的DTS配置, iommus这个对应了设备节点SMMU的配置;可以看到发生问题的设备正是以太网:

emac_emb_smmu: emac_emb_smmu {
compatible = "qcom,emac-smmu-embedded";
iommus = <&apps_smmu 0x3C0 0x0>;
qcom,iova-mapping = <0x80000000 0x40000000>;
};

理清楚这些SMMU的日志只是第一步, 但是对于为何会发生SMMU访问异常还是毫无头绪. 这个只能通过阅读驱动源代码弄清楚以太网网卡数据的接收流程才能一步步揭开迷雾了.

对于目前的以太网网卡来说, 一般采用ring buffer(环形缓冲区)的形式来接收数据; 驱动在初始化的时候为网卡的ring buffer预分配DMA内存, 用于接收数据. 总体来收, 网卡的数据接收流程有如下三个步骤:

  • 网卡需要传数据时, 获取到当前的缓冲区对应的DMA内存地址(IOVA)后, 通过SMMU向对应的RAM地址传输数据
  • 发送完成后, 通过中断告知驱动有数据需要接收
  • CPU接收到中断后, 驱动会把DMA的映射解除, 数据交由CPU处理; 接着驱动把对应的数据发送到协议栈继续处理

那么, 问题来了, SMMU是何时收到DMA访问异常错误的了? 是在第三个步骤, 驱动解除DMA地址映射后, 有地方再次尝试使用该DMA地址导致的吗? 从驱动的逻辑来看, 每次传送完成, DMA地址与RAM地址解除映射后, 没有地方会再次尝试获取该DMA地址了(对应buffer的DMA地址已经置空). 退一步说, 如果是驱动使用的时候发生的问题, 那么异常的堆栈应该会打印出来, 但是现在只有SMMU相关的日志.

所以, 问题的源头只能是在网卡通过SMMU往对应的DMA地址发送数据的时候, 就是说如果网卡给DMA传输数据的大小超过了预分配的buffer的大小的话, SMMU会发现对应的DMA地址没有映射到物理地址, 从而报错. 解决问题的办法也很简单, 只需要把buffer大小由原来的1538修改为2048(2kb)就可以了:

-#define DWC_ETH_QOS_ETH_FRAME_LEN (ETH_FRAME_LEN + ETH_FCS_LEN + VLAN_HLEN + PADDING_ISSUE)
+#define DWC_ETH_QOS_ETH_FRAME_LEN (1<<11)

修改后再次验证, 问题不再出现. 但这里有个问题, DMA的buffer大小为何设置成2kb而不是其他如4kb了? 这个实际跟以太网网卡(EMAC)本身的设计有关, 一般以太网的一帧数据是一个MTU(一般是1500, 如果有VLAN数据, 则会多4个字节), 但为何网卡传输的一帧数据会超过设定的MTU大小, 这个目前咨询了供应商仍然没有得到答案(供应商怀疑是发送端给到的一帧数据超过了最大的MTU 1538, 这个结论仍然值得怀疑).

从高通给的一些问题案例来说, 一般SMMU都是由于需要传输的数据大小与实际的buf大小不一致导致的. 总的说来, SMMU的问题看起来十分棘手, 但只要把基本的概念与原理弄清楚, 把代码流程梳理完整, 解决这类问题并不是件十分困难的事情.

一个SMMU内存访问异常的问题的更多相关文章

  1. VC程序查错之内存访问异常

    作者:langouster 先来看下面这张图,相信很多程序员都见过类似. ---------------------------test1.exe - 应用程序错误------------------ ...

  2. C++异常机制的实现方式和开销分析 (大图,编译器会为每个函数增加EHDL结构,组成一个单向链表,非常著名的“内存访问违例”出错对话框就是该机制的一种体现)

    白杨 http://baiy.cn 在我几年前开始写<C++编码规范与指导>一文时,就已经规划着要加入这样一篇讨论 C++ 异常机制的文章了.没想到时隔几年以后才有机会把这个尾巴补完 :- ...

  3. JVM内存区域异常分析

    在Java虚拟机规范描述中,除程序计数器外,其他几个运行时区域都有可能发生OutOfMemoryError异常.接下来将对各区域分别进行分析介绍,内容包括触发各区域OutOfMemoryError异常 ...

  4. JVM内存区域与内存溢出异常

    Java虚拟机在执行java程序时会把它所管理的内存会分为若干个不同的数据区域,不同的区域在内存不足时会抛出不同的异常. >>运行时数据区域的划分 (1)程序计数器程序计数器(Progra ...

  5. JVM自动内存管理-Java内存区域与内存溢出异常

    摘要: JVM内存的划分,导致内存溢出异常的可能区域. 1. JVM运行时内存区域 JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域: 1.1 程序计数器 程序计数器是一块较小的内 ...

  6. 《深入理解Java虚拟机》-----第2章 Java内存区域与内存溢出异常

    2.1 概述 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任 ...

  7. 《深入理解java虚拟机》读书笔记——java内存区域和内存溢出异常

    几种内存溢出异常: 堆溢出 原因:创建过多对象,并且GC Roots到对象之间有可达路径. 分两种情况: Memory Leak:无用的对象没有消除引用,导致无用对象堆积.例如<Effictiv ...

  8. Java内存溢出异常(下)

    此篇是上一篇文章Java内存溢出异常(上)的续篇,没有看过的同学,可以先看一下上篇.本篇文章将介绍剩余的两个溢出异常:方法区和运行时常量池溢出. 方法区和运行时常量池溢出 这部分为什么会放在一起呢?在 ...

  9. Java自动内存管理机制学习(一):Java内存区域与内存溢出异常

    备注:本文引用自<深入理解Java虚拟机第二版> 2.1 运行时数据区域 Java虚拟机在执行Java程序的过程中把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创 ...

  10. 《深入理解java虚拟机》第二章 Java内存区域与内存溢出异常

    第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域  

随机推荐

  1. 6、SpringMVC之视图

    注意:本文环境搭建请参考5.2节 6.1.视图概述 视图的作用是渲染数据,将模型Model中的数据展示给用户: SpringMVC视图的种类很多,默认有转发视图和重定向视图: SpringMVC中的视 ...

  2. 【Vue】树状节点接口 与 级联选择框组件

    原来有一个组织机构的渲染, 我自己写的我自己看也8太明白了: https://www.cnblogs.com/mindzone/p/14888046.html 现在,有一个位置选择,使用这个级联选择器 ...

  3. 【Git】介绍与概述

    版本控制工具应该具备的功能? 协同修改 多人并行不悖的修改服务器端的同一个文件. 数据备份 不仅保存目录和文件的当前状态,还能够保存每一个提交过的历史状态. 版本管理 在保存每一个版本的文件信息的时候 ...

  4. 人形机器人操作系统(开源) —— FreeRTOS

    地址: https://www.freertos.org/zh-cn-cmn-s/index.html

  5. GTC 2024 NVIDIA推出的新一代终端智能芯片 —— Jetson Thor

    比较好奇,NVIDIA今年推出的终端芯片Jetson Thor到底是个啥样,毕竟这东西在机器人元年的2024年开始预热宣传,2025年大规模部署,注定是AI领域的一个重要角色. 看完GTC 2024的 ...

  6. VUE——语法糖

  7. TGI 多-LoRA: 部署一次,搞定 30 个模型的推理服务

    你是否已厌倦管理多个 AI 模型所带来的复杂性和高成本? 那么, 如果你可以部署一次就搞定 30 个模型推理服务会如何? 在当今的 ML 世界中,哪些希望充分发挥其数据的价值的组织可能最终会进入一个& ...

  8. Infinity颜值与实用兼备的新标签页,高效书签管理必选的浏览器扩展

    浏览器是我们互联网冲浪的必备平台,但是在使用浏览器的过程中,我们经常会遇到标签页和书签管理的问题.过多的标签页和书签会导致浏览器变得杂乱无章,不利于我们快速查找需要的内容.为了提高我们的工作和学习效率 ...

  9. 面试官:说说volatile应用和实现原理?

    volatile 是并发编程中的重要关键字,它的名气甚至是可以与 synchronized.ReentrantLock 等齐名,也是属于并发编程五杰之一. 需要注意的是 volatile 并不能保证原 ...

  10. 一文讲透CRC校验码-附赠C语言实例

    一口君最近工作用到CRC校验,顺便整理本篇文章和大家一起研究. 一.CRC概念 1. 什么是CRC? CRC(Cyclic Redundancy Checksum)是一种纠错技术,代表循环冗余校验和. ...