CNDRV PCI 驱动阅读过程

3 篇文章 1 订阅

CNDRV PCI 驱动阅读过程


看了一下目录结构, core目录下的 cndrv_core.c文件主要就是提供一个逻辑层的封装接口, cndrv_cdev.c主要就是字符设备初始化打开关闭等相关的一些接口, cdev_ctrl.c是字符设备的读写控制等操作的封装接口; plat目录下就是平台相关的 PCI驱动、 dma操作驱动、 mbox驱动、基于PCI驱动做的一些外设驱动、平台发送固件启动等一些内容。(由于手册中没有关于寄存器部分的描述,这里关于寄存器部分的内容就不做分析)

1. PCI驱动注册

此驱动是在RC端使用的,注册成模块,然后调用core层的驱动初始化。

在这里插入图片描述

core核心部分的注册就调用了字符设备初始化的部分。

在这里插入图片描述

看一下字符设备驱动初始化的部分,也比较简单,创建了一个设备类,名字是cambricon_dev

在这里插入图片描述

这时候驱动就算是初始化完了。

注册部分总结:

注册部分是注册PCI驱动(RC端驱动)以及创建一个class类。

2. 驱动 probe 函数

接下来看一下cn_pci_driver的内容,主要关注一下probe函数,当设备接入并且匹配成功时,调用此probe函数。一开始为此设备申请一些私有数据的内存,赋值初始化相关内容后,激活PCI EP设备,预留IO和内存资源。接下来

在这里插入图片描述
在这里插入图片描述

接下来看一下各自初始化的内容。

2.1 BAR空间映射

循环从BAR0BAR5进行映射,成功后记录映射了多少个BAR空间。

在这里插入图片描述

调用的映射单个BAR空间的函数如下,获取BAR空间的起始地址和长度后进行pci_iomap()映射,方便后续驱动可以使用。

在这里插入图片描述

BAR空间映射部分总结:

将EP设备的BAR空间都进行映射并记录。

2.2 设置 DMA 掩码

在这里都是设置的32位的掩码,即DMA的总线地址大小?

在这里插入图片描述

设置 DMA 掩码射部分总结:

设置DMA总线宽度为32位。

2.3 平台初始化

info->setup实际是指向c20e_plat_init()函数,在设备ID表中的最后一项指向c20e_pci_info

在这里插入图片描述

c20e_pci_info里面就一个函数指针,指向函数c20e_plat_init()函数。

在这里插入图片描述

2.3.1 内核预留内存初始化

c20e_plat_init()函数里面的工作主要是对BAR2空间进行一个划分,DESC_MEM_PHYS_BASE是在内核里面预留的一段物理地址。

在这里插入图片描述

一开始的common_default_data()是函数指针的赋值过程:

在这里插入图片描述

从设备树里面也可以看到,预留的64MB物理内存里,有8MB用作消息的内存,剩余的56MB用作共享的内存,供自由分配。

在这里插入图片描述

平台初始化部分总结:

将寄存器虚拟地址指向BAR0的虚拟地址(BAR0应该是在preboot或者其他阶段里映射掉了),将BAR2映射到内核预留的内存上,并进行区域划分。

此平台上:BAR0 -> 16MB, BAR2 -> 64MB, BAR4 -> 64MB

2.4 使能 MSI 中断

先关闭所有中断,再根据初始化的中断模式来使能对应的中断。

在这里插入图片描述

关闭中断的都是写寄存器的过程,此处不表。

在这里插入图片描述

在这里插入图片描述

如果是初始化的MSI-X中断,需要先判断是否具有此能力。

在这里插入图片描述

这里就使能MSI中断,注册中弄断处理函数cn_pci_msi_isr()

在这里插入图片描述

然后配置硬件寄存器:

在这里插入图片描述

实际走的是INT-X中断,这里注册cn_pci_intx_isr()中断处理函数。

在这里插入图片描述

2.4.1 PCI中断处理函数

这里的读中断状态寄存器后根据中断状态去调用注册的中断处理函数,这里应该是dma_engine_isr()函数,具体的注册过程在后面会有描述。看[2.5.1 注册DMA引擎中断回调](# 2.5.1 注册DMA引擎中断回调)。

在这里插入图片描述

读中断状态寄存器:

在这里插入图片描述

清除中断状态:

在这里插入图片描述

使能MSI中断部分总结:

根据是否有MSI能力,去注册MSI中断,否则注册INT-X中断,INT-X中断里面处理的是DMA引擎注册的中断任务。

2.5 DMA 引擎初始化

接下来就是DMA引擎的初始化,除了硬件寄存器相关的参数写入,初始化了链表头avail_q用来保存可用的DMA通道信息,wait_q用于DMA通道都正在使用时进行一个等待。

在这里插入图片描述

硬件通道参数的检查与校验:

在这里插入图片描述

错误状态的清除:

在这里插入图片描述

接下来看一下DMA引擎的初始化过程,在共享内存中划分一块自己的内存空间,初始化此引擎的相关锁和传输链表头,注册一个回调函数,由设备的pci_dev->irq触发时进行回调,写硬件寄存器使能DMA中断,然后将此通道加入设备私有数据的avail_q链表,表明此通道后续可以使用。

在这里插入图片描述

申请资源的过程是在共享内存中用的,这个内存是在BAR空间映射的时候对内核的保留内存进行映射的,看[2.3.1 内核预留内存初始化](# 2.3.1 内核预留内存初始化)章节。

在这里插入图片描述

使能DMA引擎通道:

在这里插入图片描述

在[2.5.1 注册DMA引擎中断](# 2.5.1 注册DMA引擎中断回调)回调后,会将此引擎通道加入到avail_q链表中,表明后续可以使用。

在这里插入图片描述

2.5.1 注册DMA引擎中断回调

hw_dma_setup_irq()直接调用cn_pci_register_interrupt()进行初始化,PCIE_IRQ_DMA这里是1454DMA引擎通道应该就是用中断145-148这几个。

在这里插入图片描述

cn_pci_register_interrupt()这里就是直接对这个irq_desc数据的数据进行赋值,然后PCI中断里面会调用这个handler进行处理。看[2.4.1 PCI中断处理函数](# 2.4.1 PCI中断处理函数)。
在这里插入图片描述

使能中断:

在这里插入图片描述

在这里插入图片描述

2.5.2 DMA引擎中断处理函数

这里就是DMA引擎的中断处理函数了,读本通道的中断状态,如果有中断,先关闭中断,处理中断;传输链表为空表明都已经处理过了,非空则调用engine_transfer_completion()去处理传输,最后再使能本通道的DMA中断。

在这里插入图片描述

读通道状态:

在这里插入图片描述

关闭通道中断:

在这里插入图片描述

engine_transfer_completion()处理中,先将这个DMA传输描述符从transfer_list链表中删除,如果中断状态错误,清除错误状态,否则标志为正常完成,将通道置为空闲后唤醒等待队列。dma_xfer_submit()函数中会等待这个传输完成的唤醒,看[2.8.4 发起DMA传输](# 2.8.4 发起DMA传输)。

在这里插入图片描述

清除错误状态寄存器:

在这里插入图片描述

DMA引擎初始化部分总结:

初始化硬件DMA通道,注册到PCI-irq的中断回调函数中,完成中断处理回调逻辑,将通道置为可用状态。

2.6 核心层的探测

核心层这里主要就是一个字符设备的初始化,包括一开始的内存资源申请,然后加入到全局的cn_core_list链表中,执行平台初始化,再创建字符设备接口。

在这里插入图片描述

申请资源的直接使用kzalloc()函数:

在这里插入图片描述

正确赋值ID后加入到链表尾部:

在这里插入图片描述

这里就调用之前注册的回调setup函数来进行初始化,注册的过程是在c20e_plat_init()中注册的,在[2.3.1 内核预留内存初始化](# 2.3.1 内核预留内存初始化)。

在这里插入图片描述

接下来是创建字符设备的过程以及sysfs文件的创建。

在这里插入图片描述

字符设备创建时需要先申请设备号,配置kobject之后初始化字符设备的操作集,并将设备增加到系统中,最后创建一个字符的sysfs文件。

在这里插入图片描述

在这里插入图片描述

配置kobject的名字按idx进行编号:

在这里插入图片描述

这里是设置字符设备的操作集,看一下[2.6.1 字符操作集合](# 2.6.1 字符操作集合)。

在这里插入图片描述

最后的创建sysfs文件,结束此过程的初始化。

在这里插入图片描述

2.6.1 字符操作集合

字符设备的操作都是调用的plat->fops相关的操作函数,赋值的过程是在[2.3.1 内核预留内存初始化](# 2.3.1 内核预留内存初始化)时赋值的,需要看的回头看一下即可。

2.6.1.1 ioctl

这里的ioctl指向cn_pci_ioctl()函数:

在这里插入图片描述

这个ioctl支持两个命令,用于DMA传输。

在这里插入图片描述

用户态的DMA传输需要先将用户态的内存映射到散列中,再发起实际的DMA传输,实际DMA传输的过程看[2.8.4 发起DMA传输](# 2.8.4 发起DMA传输)。

在这里插入图片描述

2.6.1.2 用户态内存的映射到散列

这里看一下用户态内存的映射到散列的过程,计算用户态数据占用的页大小,申请数组,找到用户态数据所在的页并保存起来,并赋值在散列表中。

在这里插入图片描述

获取当前进程的mm内存结构,找到vm_area_struct结构体,找到具体的页,保存在pages数组中。

在这里插入图片描述

在这里插入图片描述

取消映射的过程:

在这里插入图片描述

2.6.1.2 read

这里的read指向cn_pci_read()函数,这里的pos是指向的EP端的地址:

在这里插入图片描述

cn_pci_read()同样是调用cn_dma_transfer_user()来进行用户态数据DMA传输,看[2.6.1.1 ioctl](# 2.6.1.1 ioctl)关于这部分的分析。

在这里插入图片描述

2.6.1.3 write

这里的write指向cn_pci_write()函数,,这里的pos是指向的EP端的地址:

在这里插入图片描述

在这里插入图片描述

cn_pci_write()同样是调用cn_dma_transfer_user()来进行用户态数据DMA传输,与read方向相反,看[2.6.1.1 ioctl](# 2.6.1.1 ioctl)关于这部分的分析。

2.6.1.4 llseek

这里的llseek指向cn_pci_llseek()函数:

在这里插入图片描述

根据whence操作偏移,返回新偏移。

在这里插入图片描述

2.6.1.5 mmap

这里的mmap指向cn_pci_mmap()函数:

在这里插入图片描述

这里根据vm_pgoff来选择不同的BAR空间进行映射,vm_pgoff0时选择BAR0vm_pgoff1是选择BAR2vm_pgoff2时选择BAR4进行映射,因为此芯片只有BAR0BAR2BAR4。映射的页面属性是no cached,应该是避免DMA操作这些数据时的cache一致性问题,最后将BAR空间映射到用户态空间。这个代码里面没有对要映射的长度进行判断。

在这里插入图片描述

总结:

创建字符设备,字符操作时回调PCI驱动注册时注册的函数,提供读写、ioctl、mmap等操作函数。

2.7 外设注册

在这里,注册了一个ipc通信的设备以及一个mbox设备。

在这里插入图片描述

寄存器是BAR0上映射的EP的所有寄存器。

在这里插入图片描述

这个ipc内存地址是从BAR2上划分的一块内存。

在这里插入图片描述

2.7.1 ipc

从输入的起始地址开始预留8MB的内存,用作各个通道的私有数据,增加通道信息后,打印通道信息,保存到共享内存中,最后创建平台设备。

在这里插入图片描述

接下来是将ipc_msg_channels数组里面的通道信息增加到全局变量的chennel数组中。

在这里插入图片描述

ipc_msg_channels内容如下:

在这里插入图片描述

打印通道信息如下:

在这里插入图片描述

将全局通道信息chennel拷贝到共享内存中:

在这里插入图片描述

创建平台设备:

在这里插入图片描述

指定的操作集如下:

在这里插入图片描述

总结:

设置一些不同的通道区域,保存到共享中,并生成平台设备。

2.7.2 mbox

mbox的私有数据是EP寄存器部分的数据,中断号是92

在这里插入图片描述

创建平台设备host_mbox
在这里插入图片描述

总结:

mbox设备的私有数据设置为EP部分寄存器,中断号为92

2.8 工作队列初始化

接下来是初始化一个工作队列,执行后续的任务。

在这里插入图片描述

接下来便是加载EP端的固件,包括u-bootkernelrootfs等资源,并启动EP设备(PCIe启动方式)。

在这里插入图片描述

2.8.1 加载固件

申请内存用来保存一个名为loader私有数据,调用load_file_trans()进行加载传输。

在这里插入图片描述

申请内存,保存加载固件中使用的blocks数据,
在这里插入图片描述

/lib/mlu_fw.conf文件中获取相关环境配置信息,这里读MLU_HOME_PATH这个环境变量的信息。

在这里插入图片描述

申请内存,将文件数据读入到内存中,然后读取一行数据,再从行数据中找是否存在对应的变量,获取变量的值。

在这里插入图片描述

在这里插入图片描述

返回文件的大小:

在这里插入图片描述

内核版本比较小时需要get_fs()set_fs()操作。

在这里插入图片描述

从文件数据中获取一行数据:

在这里插入图片描述

从一行中获取环境变量的值:

在这里插入图片描述

获取变量的值:

在这里插入图片描述

传输单个文件,打开之后获取文件的大小,按照1MB的大小计算要传输的次数,依次读取完成后,发起DMA传输,传输结束后将文件关闭,资源释放。

在这里插入图片描述

在这里插入图片描述

统计块数据的方式如下,按照最大1MB进行计算,并为每一块申请内存资源;在这之前,还需要找到对应的DMA目的地址。

在这里插入图片描述

对应的目的地址如下所示:

在这里插入图片描述

结束时,对上面申请的资源进行释放。

在这里插入图片描述

在这里,遍历即将要发起DMA进行传输数据,每次结束后都打印一下进度。

在这里插入图片描述

发起内核地址的DMA传输之前,要进行一下页面的映射,用户态的地址空间同理也需要进行映射。内核的看[2.8.2 连续页面映射](# 2.8.2 连续页面映射)和[2.8.3 非连续页面映射](# 2.8.3 非连续页面映射),根据地址是内核线性地址还是通过vmalloc()申请出来的非线性地址来调用不同的映射。

在这里插入图片描述

文件传输异常时,释放对应的资源。

在这里插入图片描述

2.8.2 连续页面映射

对传入的地址判断是否是内核的线性地址,申请一个散列结构,将地址所在的页赋值给散列数据,然后[2.8.4 发起DMA传输](# 2.8.4 发起DMA传输),结束后释放散列。

在这里插入图片描述

2.8.3 非连续页面映射

如果是vmalloc()申请出来的内存,物理地址可能是不连续的,所以这里要映射到散列表中,通过vmalloc_to_page()找到对应所在的物理页,设置到散列中,[2.8.4 发起DMA传输](# 2.8.4 发起DMA传输),结束后释放散列。

在这里插入图片描述

2.8.4 发起DMA传输

接下来就是DMA传输的过程,先获取可用的DMA通道,如果所有通道都被占用,那么就等待在这里,被信号打断的话就退出;然后提交DMA传输,传输结束后将通道增加回avail_q链表中,置通道可用,并唤醒在等待DMA通道的等待队列。

在这里插入图片描述

avail_qDMA可用引擎的链表,非空时从avail_q中取出一个通道来使用。

在这里插入图片描述

提交DMA传输的过程,先进行一个pci_map_sg()对散列进行初始化,然后申请内部的DMA请求并初始化请求的内容;循环处理每一个请求,初始化相关的传输描述符,发起硬件DMA传输后,等待唤醒;处理结束状态,直到循环结束后,销毁传输描述符,释放内部DMA请求资源。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

遍历sg去初始化DMA描述符的过程。

在这里插入图片描述

传输请求的申请就是一个vmalloc的调用:

在这里插入图片描述

传输相关的初始化,先初始化等待队列头,然后构造硬件DMA相关的描述符。

在这里插入图片描述

硬件DMA相关的描述符初始化和数据填充:
在这里插入图片描述

硬件DMA相关的描述符初始化:
在这里插入图片描述

硬件DMA相关的描述符数据填充:

在这里插入图片描述

将传输描述符加入到传输链表后,如果当前DMA通道引擎未开启,则启动DMA传输。

在这里插入图片描述

从传输链表中取出一个后,硬件发起DMA引擎传输:

在这里插入图片描述

写寄存器发起DMA引擎传输:
在这里插入图片描述

引擎传输结束时:

在这里插入图片描述

打印传输相关的数据:

在这里插入图片描述

打印硬件DMA描述符的数据:

在这里插入图片描述

传输描述符端点释放,取消散列的映射:

在这里插入图片描述

释放申请的请求描述符资源:

在这里插入图片描述

PCI私有数据、DMA引擎、传输请求、传输描述符(不包含硬件DMA描述符)的几个关系大概如下:

在这里插入图片描述

发起DMA传输部分总结:

DMA传输应该是本驱动的核心内容,传输过程中各种描述符的使用使代码看起来比较复杂,很多细节部分没有去细看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值