1. 用户态使用设备物理地址方法

he traditional answer here is to use dma_alloc_coherent() in a kernel driver, then share that memory with userspace, typically via mmap(). If you need more than a few megs of memory, you will run into some default size limits, which can be worked around by tweaking various kernel settings. The "modern" solution is called CMA - Contiguous Memory Allocator - which was merged into mainline linux kernel 3.5 (IIRC).

参考
http://lwn.net/Articles/447405/
http://lwn.net/Articles/486301/
http://mina86.com/2012/06/10/deep-dive-into-contiguous-memory-allocator-part-i/ https://events.linuxfoundation.org/images/stories/pdf/lceu2012_nazarwicz.pdf

Sample driver: http://thread.gmane.org/gmane.linux.kernel/1263136

2. iomem冲突问题

2.1. 现象

CPLD初始化失败

[ 45.424249] (c01 1178 modprobe) cpld 100001a000000.cpld: cpld0: Failed to reserve physical memory 0 at 0x100001a000000 (size 0x10000)
[ 45.436819] (c01 1178 modprobe) cpld 100001a000000.cpld: cpld0: unable to reserve memory for device (rc -16)
[ 45.447447] (c01 1178 modprobe) cpld 100001a000000.cpld: cannot activate cpld

2.2. driver代码

注意这里的IRQ_CONFIG_FLAG_MEMRESERVE

2.3. dts


确实和bootbus冲突了.

2.4. kernel的iomem机制

kernel给driver提供了资源注册的函数:
include/linux/ioport.h

devm_request_region()
devm_request_mem_region()

主要作用是给driver在初始化时, 声明自己的资源, 一般都是driver从dts里读出其设备的地址范围, 然后调用devm_request_mem_region()来"注册".
devm_request_mem_region里面会做冲突检查, 有冲突打印错误.
该机制主要是防止driver用的资源有冲突, 手段是在driver初始化时检查.

但不调用这个devm_request_mem_region函数, 对driver的实际功能并没有影响.

体现在/proc目录下的ioports和iomem

~ # cat /proc/ioports 
00001000-ffffffff : OCTEON PCIe-0:0 IO
~ # cat /proc/iomem 
00400000-007fffff : System RAM
00800000-012e5fff : System RAM
  00800000-00f84207 : Kernel code
  00f84208-012e5fff : Kernel data
012e6000-0212ffff : System RAM
02130000-022c2fff : System RAM
  02130000-022c2daf : Kernel bss
02500000-0fcfffff : System RAM
20300000-79efffff : System RAM
80300000-efffffff : System RAM
f0001000-ffefffff : System RAM
100001000-10effffff : System RAM
100001a006000-100001a00600f : serial
1070000000800-10700000008ff : /soc@0/gpio-controller@1070000000800
1070000001000-10700000010ff : /soc@0/spi@1070000001000
1180000000800-118000000083f : serial
1180000000c00-1180000000c3f : serial
1180000001000-11800000011ff : /soc@0/i2c@1180000001000
1180000001200-11800000013ff : /soc@0/i2c@1180000001200
1180000001800-118000000183f : /soc@0/mdio@1180000001800
1180040000000-118004000000f : octeon_rng
11b00f0000000-11b0fffffffff : OCTEON PCIe-0:0 MEM
  11b00f0000000-11b00f00fffff : PCI Bus 0000:01
    11b00f0000000-11b00f000ffff : 0000:01:00.0
    11b00f0010000-11b00f00103ff : 0000:01:00.0
  11b00f0100000-11b00f010ffff : 0000:00:00.0
  11b0100000000-11b0100003fff : 0000:00:00.0
1400000000000-1400000000007 : octeon_rng

2.5. 修改

  • 硬件不用uart2的, 就在dts里删掉
  • 或者在dts里面, 避免冲突
    • uart2: reg = <0x10000 0x1a006000 0x0 0x10>
    • cpld: <2 0 0x10000 0x1A000000 0x10000> 这里改小一点

3. nand问题分析

3.1. Nand访问

对Octeon的CPU来说:
nand控制器挂在bootbus上, 和其他bootbus器件共用信号;
nand读写时序都是多周期的, data线只有8个, 而需要通过这8根线, 给nand器件发送命令字, 地址, 和数据.
在CLE阶段发CMD, 在ALE阶段发行地址, 列地址; 在WE/OE阶段发数据或读数据.

3.1.1. ONFI 接口的nand器件访问

参考: https://www.ssdfans.com/blog/2018/05/17/%E9%97%AA%E5%AD%98%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/ 闪存实战指南: 闪存接口有同步异步之分,一般来说,异步传输速率慢,同步传输速率快。异步接口没有时钟,每个数据读由一次REn信号触发,每个数据写由一次WE_n信号触发。同步接口有一个时钟信号,数据读写和时钟同步。
![](img/device_driver
杂记20220920104639.png)
异步写:
![](img/device_driver
杂记_20220920104652.png)
这张图里有5个信号:

  • CLE:Command Latch Enable,CLE有效时IOx发送命令;
  • CE_n:Chip Enable,这个信号来选通一个逻辑上的芯片——Target。为什么说是逻辑上的芯片?因为物理芯片里面封装了很多Target,每个Target是完全独立的,只是有可能共享数据信号,所以通过CE_n来选择当前数据传输的是哪个Target,业内一般把Target叫做CE;
  • WE_n:Write Enable,写使能,这个信号是用户发给闪存的,有效时意味着用户发过来的写数据可以采样了;
  • ALE:Address Latch Enable,ALE有效时IOx发送地址;
  • IOx:数据总线。

同时有很多时间参数,这里只介绍几个关键的参数:

  • tWP是WE_n低电平脉冲的宽度,tWH是WE_n高电平保持时间,合起来一个周期的时间就是tWC;
  • tDS是数据建立时间,意思就是8比特数据要都达到稳定状态,最多这么长时间;
  • tDH是数据稳定时间,这段时间里数据信号稳定,可以来采样;

这样我们来看上面的时序图,数据写入的时候,数据总线不能传输地址和命令,所以ALE和CLE无效。这个Target有数据传输,所以CE_n有效。每一个WE_n周期对应一次有效的数据传输。

再来看看的异步数据读出时序图,多了两个信号:

  • RE_n:读使能。这个信号是用户发给闪存的,每发一个读使能,闪存就在数据总线上准备好数据,等用户采样;
  • R/B_n:Ready/Busy。闪存正在进行内部读的时候,Busy_n有效,当操作完成数据准备好之后,Ready有效,用户可以来读了。

所以,图就是用户向闪存发了读命令之后,Ready信号拉高,意味着数据准备好了。接着,用户发RE_n信号去读数据,每个RE_n周期,闪存发送一个有效数据到数据总线上,供用户采样。

3.1.2. 闪存命令

nand控制器和nand存储器件通过如下命令交互:

有的命令有两个时序周期, 比如 Page Program第一个周期发80h, 第二个周期发10h

3.1.3. 闪存寻址

举例: 一个nand, 有2个LUN(Logical Unit), 每个LU有2个Plane, 每个Plane有N个Block, 每个Block有N个Page.
行地址编码: LUN Address + Block Address + Page Address
列地址: 就是Page内部的地址
擦除以Block为单位.

3.1.4. 时序

读: 先发CMD 00h, 接着发列地址, 再发行地址, 再发CMD 30h; 然后等待状态寄存器变为ready, 就可以开始读数据了.

写: 每次写一个Page, 先发CMD 80h, 再发列地址, 行地址, 然后发数据, 发完数据发CMD 10h, 然后等待状态寄存器为ready.

擦: 擦以Block为单位, 不涉及数据传输. 再CMD 60h和D0h之间发行地址即可.

3.2. 跟踪octeon_nand_wait

3.2.1. 加printk信息的结果


3.3. nand写相关函数

#并不完整
/repo/yingjieb/ms/linux-octeon-sdk3.1/drivers/mtd/nand/nand_base.c
 nand_write_page
  chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
  status = chip->waitfunc(mtd, chip);

 nand_write_oob_std
  chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
  chip->write_buf(mtd, buf, length);
  chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
  status = chip->waitfunc(mtd, chip);

/repo/yingjieb/ms/linux-octeon-sdk3.1/arch/mips/cavium-octeon/octeon-nand.c
 octeon_nand_write_oob_std
  chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
  chip->waitfunc(mtd, chip);


3.4. perf probe 相关函数

perf probe octeon_nand_wait
perf probe octeon_nand_cmdfunc
perf record -e probe:octeon_nand_wait -e probe:octeon_nand_cmdfunc -aR -g --no-children sleep 60
perf record -e probe:octeon_nand_wait -e probe:octeon_nand_cmdfunc -aR -g sleep 60
perf report > perf.report
perf script > perf.script
grep -E "^\S" perf.script | less

3.5. perf script原始记录

3.5.1. ls

全是读操作.

3.5.2. cp

cp是先读后写.
读一个特定文件时, 这个文件一般并没有被文件系统的page cache缓存, 所以读是直接读nand器件.
同时page cache也会缓存, 后续的读会快.

写是先写到page cache, 由内核线程把cache page的内容真正写入nand器件.
先是要nandcheck_wp, 看名字是检查写保护 ![](img/device_driver杂记20220920105305.png)
接着是连续的几个nand_write_page, 比如这次是连续两次write_page
![](img/device_driver
杂记_20220920105328.png)

再然后是wait, 也就是本文的重点要调查的函数.

3.5.3. 后台进程

主要是erase

3.5.4. 写nand调用栈分析

mm/page-writeback.c
    #walk the list of dirty pages of the given address space and write all of them
    write_cache_pages()
fs/ubifs/file.c
    do_writepage(struct page *page, int len)
fs/ubifs/journal.c
    ubifs_jnl_write_data()
        这之间是有锁的.
        make_reservation()
        ...
        release_head()
fs/ubifs/io.c
    ubifs_leb_write()
drivers/mtd/ubi/eba.c
    #writes data to logical eraseblock @lnum of a dynamic volume @vol
    ubi_eba_write_leb()
        #每个logical eraseblock 都有锁保护
        leb_write_lock()
        ubi_io_write_data()
            ubi_io_write()
                #drivers/mtd/nand/nand_base.c
                #[MTD Interface] NAND write with ECC
                nand_write()
                    #一个nand controller可以挂多个chip, 用spin lock来保证当前的chip被选中.
                    #用状态机来保证controller的状态是对的, 状态不对说明其他进程没有准备好状态, 就释放spin lock, 搭配wait_queue让出控制权.
                    nand_get_device(mtd, FL_WRITING);
                    ret = nand_do_write_ops(mtd, to, &ops);
                        nand_write_page()
                            #octeon有硬件ecc, 但似乎有问题, 我们没使用. 
                            #我们用的是NAND_ECC_SOFT和NAND_ECC_BCH, ecc.write_page是nand_write_page_swecc
                            #每个page写之前算ecc,nand_calculate_ecc()
                            #每个page都有oob, 里面放ecc和坏块信息
                            chip->ecc.write_page()
                            chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
                            status = chip->waitfunc(mtd, chip);
                    nand_release_device(mtd);
        leb_write_unlock()

3.6. 一次ubifs_writepage, 并没有真正写nand

3.7. 这次真正有nand操作




3.8. nand buserror

操作命令记录:

#典型错误打印
PAGEPROG failed with -4
#相关打印
recovery completed
UBIFS: mounted UBI device 1, volume 2, name "nand-persistent"
#调试记录
#uboot
setenv kernel_extra_args config_overlay=reboot=0 octeon-nand.debug=1
setenv kernel_extra_args config_overlay=reboot=0 loglevel=8 debug ignore_loglevel 
#linux
mount_ubi start
dd if=/dev/zero of=/mnt/nand-persistent/testnand bs=4M count=100;sync;mount_ubi stop
mount_ubi stop
ubidetach -p /dev/mtd
ubidetach -p "$mtddev"
ubiformat -yq "$mtddev"
mount -t ubifs ubi$ubidevnr:$volname $mountpoint
#编成ko加载
modprobe octeon-nand.ko
# perf和ftrace相关
perf probe -V cvmx_nand_page_write
perf probe -V cvmx_nand_page_write
perf probe --del probe:cvmx_nand_page_write
perf probe -x vmlinux -F | grep cvmx_nand_page_write
zcat /proc/config.gz | grep CONFIG_FRAME_POINTER


perf probe cvmx_nand_page_write
perf record -e probe:cvmx_nand_page_write -aR sleep 30

#相关代码
linux-octeon-sdk3.1/fs/ubifs/super.c
arch/mips/cavium-octeon/octeon-nand.c
arch/mips/cavium-octeon/executive/cvmx-nand.c

3.9. nand的timing mode

kernel4.9配的timing mode是4

改为0

ONFI的timingmode有6种: 见ONFI规范 mode 0是最慢的.


4. 网口中断

目前调试网口中断默认全部由core0处理
通过smpaffinity可以修改让四个核都处理网口中断下半部: echo f > smp_affinity ![](img/device_driver杂记_20220920110139.png)

4.1. core0 CPU占用率持续100%的情况下, 调试网口不通.

默认core 0处理全部eth0的中断, 当core 0 100%被用户占用时(通常时100%用户态占用), 网口不通.
目前我的解释是: 该用户态进程优先级过高, 导致中断不能被调度处理.
解决方法:
用tasetset把占CPU的进程绑定到其他核, 留0核处理网口中断.
taskset -c 1,2,3 xxxx程序

results matching ""

    No results matching ""