概述
原文出自:https://blog.csdn.net/weixin_38878510/article/details/109648061
- PCIE_MCC驱动阅读过程--HOST篇
-
- hi35xx_dev_host.ko
-
- init
- probe
- irq_map_host.ko
- boot_device.ko
-
- init
- open/release/read/write/poll
- ioctl
-
- HI_GET_ALL_DEVICES
- HI_PCIE_TRANSFER_DATA
-
- init_ddr
-
- move_pf_window_in
- transfer_data
- HI_START_TARGET_DEVICE
- HI_RESET_TARGET_DEVICE
- ./booter start_device
-
-
- main函数
- u-boot传输
-
- pcit_dma_host.ko
-
- init
- open/release/read/write/poll
- ioctl
- mcc_drv_host.ko
- mcc_usrdev_host.ko
-
- init
- open
- read
- write
- ioctl
-
- HI_MCC_IOC_ATTR_INIT
- HI_MCC_IOC_CONNECT
- HI_MCC_IOC_CHECK
-
- 握手
-
- 第零步握手
- 第一步握手
- 共享内存初始化
- HI_MCC_IOC_GET_LOCAL_ID
- HI_MCC_IOC_GET_REMOTE_ID
先看一下海思提供的
pcie_mcc
代码里面的文档《主片引导从片启动方法.txt》,给出了几个模块的加载顺序,这里就先拿HOST端的加载顺序来看一下:
insmod hi35xx_dev_host.ko
insmod irq_map_host.ko
insmod boot_device.ko
./booter start_device
insmod pcit_dma_host.ko
insmod mcc_drv_host.ko
insmod mcc_usrdev_host.ko
- 1
- 2
- 3
- 4
- 5
- 6
从上面看,最底层的hi35xx_dev_host.ko
,那么就先从这个看;一般直接与应用程序交互的应该是mcc_usrdev_host.ko
。但由于海思好像把相关接口封装在了hi35xxvxxx_pciv.ko
模块和libpciv.a
库,源码不提供,所以就没有办法进行太多的分析,只能着重上面几个模块的分析。在实际看代码过程中并不是简单的从顶之下或者从底到上,都是看各个模块之间的关联关系。
hi35xx_dev_host.ko
init
模块注册部分如下,exit
部分就是init
部分的逆过程:
主要看一下init
的过程:
pci_register_driver()
注册驱动后,会在/sys/bus/pci/drivers/
目录下看到对应名字的目录,名字就是PCI_HIDEV_MODULE
(pci_hidev_driver
具体内容看下一节probe
)。
看一下hidev_host_init()
函数,就是针对于HOST端的特定函数赋值,相关函数后面用到了再进行细看。
看一下架构相关的pci_arch_init()
函数,
看一下host_pcie_controller_init()
函数,主要涉及硬件中断号的配置。0x12200000
就是海思Hi3559AV100
芯片的PCI Express控制器寄存器,包括做EP时的TYPE0以及做RC是的TYPE1的协议标准寄存器,以及内部其他的寄存器。
probe
回过头看pci_register_driver(&pci_hidev_driver)
这一行代码,pci_hidev_driver
的内容如下所示:
着重先看一下probe
过程,这个probe
被调用证明更底层的驱动或是硬件检测到了PCIe设备的接入并且驱动的pci_device_id
匹配上了。因为只有PCIe做EP时寄存器描述才有BAR的概念,所以这个probe
过程中获取的BAR资源应该都是指EP端的资源。
alloc_hi35xxdev()
函数就是一个简单的申请内存并清零的一个过程:
mk_slot_index()
函数是获取设备的总线号:
irq_map_host.ko
这个驱动就是针对上面的hi35xx_dev_host.ko
初始化之后,获取PCIe的几个硬件中断号。
对应的设备树如下:143~146
是INT_A~INT_D
的中断线,对应芯片数据手册的中断源175~178
,147
是PCIE_EDMA的中断号,对应芯片数据手册的中断源179
,38
对应的中断源是70
,数据手册上注明是SOFTWARE
,是专门用于软件的中断号?
boot_device.ko
init
模块的注册是注册一个杂项设备,设备名称是/dev/hisi_boot
,具体的操作集是boot_usrdev_fops
。
pcie_arch_init()
也是几个函数指针的赋值:
pcie_cfg_store()
:为两个全局变量申请内存,并读取对端设备的配置。
store_pci_context()
:读对端设备的相关配置信息。
open/release/read/write/poll
这几个函数都没有什么太多的内容,pool的函数也是空的,这里就不截图放下来了。
ioctl
这个驱动的ioctl()
提供4个命令:
- HI_GET_ALL_DEVICES : 获取所有设备
- HI_PCIE_TRANSFER_DATA : 传输数据
- HI_START_TARGET_DEVICE : 启动目标设备
- HI_RESET_TARGET_DEVICE : 复位目标设备
HI_GET_ALL_DEVICES
这个命令调用的函数是pcie_boot_get_devices_info()
,是一个简单的遍历过程。
HI_PCIE_TRANSFER_DATA
这个命令调用的函数是pcie_boot_transfer_data()
,pcie_arch_init()
时将g_boot_opt.init_ddr
初始化为init_ddr()
,将g_boot_opt.transfer_data
初始化为transfer_data()
.
init_ddr
init_ddr()
函数如下所示:海思u-boot的前面32KB是一些初始化DDR的代码,寄存器信息以及加密验签的信息,这里如果是要调用DDR初始化,则u-boot的前面32KB发送到对端;否则知识简单地进行寄存器的初始化。
slave_ddr_init()
函数如下:先写对端的一些寄存器(海思特有的一些初始化标记),然后将对端的片内RAM映射(Hi3559AV100
的片内RAM空间地址从0x08000000
开始的1MB
)。将u-boot的前面32K拷贝到对面的片内RAM之后,对端的芯片就可以初始化DDR了(对端的Hi3559AV100
芯片是设置的DDR启动模式,这时候再把完整的u-boot传到对面就可以跑u-boot了。)
slave_reg_write()
与slave_reg_read()
函数如下:都是判断一下当前要写的寄存器地址是否已经经过move_pf_window_in
映射进来了,如果当前要写的寄存器没有映射过,那么就将对端的寄存器映射BAR1
上,然后再直接写映射出来的地址。(芯片的ATU会将读写操作自己转换成PCIe的TLP相应的事务。)
move_pf_window_in
看一下move_pf_window_in()
函数:传入要映射的对端的的地址,再使能ATU地址转换。根据海思的手册意思,配置了ATU地址转换,那么访问对应的地址空间都会被ATU转换成对应的PCIe事务去处理。(目前的关键点是理解这个操作,彻底理解了之后阅读过程就轻松些了。)
pci_config_write()
函数如下:读写对端的配置信息。
到内核的pci驱动的pci_bus_write_config_dword()
函数:用自旋锁保护的读写操作宏。
这里的bus->ops->read/write
应该是指向drivers/pci/hipcie/pcie.c
里面的pcie_read_conf()
和pcie_write_conf()
。根据总线号来判断要写对端设备还是写当前设备。
这里我们应该是要写入到对端的设备的,走pcie_write_to_device()
:
里面的pcie_read_from_device()
函数如下:通过__to_pcie_address()
函数转换后读对应偏移的值。
to_pcie_address()
函数如下:info->base_addr
是架构相关的PCIe初始化时通过ioremap_nocache
映射的芯片PCIe控制器的配置空间。
假如是写入当前总线控制器,即ROOT BUS的,就没有了上面的to_pcie_address()
函数,取而代之的是芯片的PCIe控制器的寄存器(注意是控制器的寄存器,而不是标准的PCIe配置寄存器。这片空间在初始化时通过ioremap_nocache()
函数映射过了),pcie_write_to_dbi()
函数:
pcie_read_from_dbi()
函数:info->conf_base_addr
是
transfer_data
transfer_data()
函数如下:这里的move_pf_window_in
的序号是0,按照理解应该是映射目标地址到BAR0上,Hi3559AV100的BAR0有8MB大小,这里是用来传输数据。这里的目标地址,比如在DDR初始化时,就是DDR地址了。
HI_START_TARGET_DEVICE
这个命令调用的函数是pcie_boot_start_device
,判断总线号后调用start_device()
。
start_device()
函数就是一个写寄存器的过程,跟架构有关系,暂时就先不细看了。
HI_RESET_TARGET_DEVICE
这个命令调用的函数是pcie_boot_start_device
,判断总线号后调用reset_device()
。
reset_device()
函数就是一个写寄存器之后再将对端设备信息保存下来。
./booter start_device
main函数
先看一下main()
函数:检验参数之后,ioctl()
获取所有的设备,然后进行业务处理。
u-boot传输
u-boot的传输过程如下,特别的地方是增加了一个NEED_DDR_INIT
标记。这部分代码可以回头看boot_device.ko的ioctl部分,至于其他的uImage的传输,除了少了一个DDR初始化的过程,基本一致的。
pcit_dma_host.ko
init
模块注册部分如下,exit
部分就是init
部分的逆过程:
主要看一下init
的过程:
看DMA读写通道的初始过程:内部有两个DMA通道,调用trans_list_init()
对内部的相关链表初始化后,再初始化了读写的自旋锁和信号量。
trans_list_init()
初始化完后,最后加到free_list_head
链表后。
dma_channel
结构体如下:与内核DMA engine的不一样。
还有一个DMA中断相关的初始化:中断处理函数设置为host_dma_irq_handler()
,然后申请中断资源。
看一下中断资源申请的,清除读写中断后,申请中断资源,再使能中断。
再回头看一下中断处理函数,调用__do_pcit_dma_trans()
分别处理读写中断。
__do_pcit_dma_trans()
处理的过程大概如下所示:根据读写方向,读取读写的中断状态,传输被打断则开启新的DMA传输,完成后将DMA传输相关的描述资源移动到free链表中,如果busy链表非空则继续DMA传输。具体的start_dma_task()->__start_dma_task()
主要是一个写寄存器的过程,这里就不细看了,需要和寄存器手册对应着看就比较清晰了。
open/release/read/write/poll
pci_dma_miscdev
结构体指向的fops
操作集如下所示:
读写等操作函数基本没有什么内容,如下所示:
ioctl
相关的操作还是放在ioctl
上,实现了读写操作,用pcit_create_task()
函数来实现。
pcit_create_task()
函数里面注释主要解释了一下DMA操作时的DMA地址情况,
可以对比一下手册上关于这个的描述:
__pcit_create_task()
函数根据不同的方向使用不同的描述数据。
__do_create_task()
函数:根据不同的类型选择对应的链表头,如果业务繁忙则退出;否则从空闲链表中取出一个,开启新的DMA传输(原来没有任何数据在传输)或者放到busy链表中(原来有数据传输,等终端触发后自动发起新的DMA传输)。
mcc_drv_host.ko
这一个模块只是单纯地提供一些接口,算是承前启后的作用,后面用到就进行分析一下。
mcc_usrdev_host.ko
先看一下mcc_usrdev_host.ko
的代码,查看Makefile
,可以看到这个模块是由下面两个文件编译而成的:
mcc_usrdev_host-objs := mcc_core/hios_mcc/hios_mcc_usrdev.o mcc_core/hios_mcc/hios_mcc.o
- 1
init
先看hios_mcc_usrdev.c
,可以看到这个模块是注册了一个杂项设备。
这个杂项设备驱动提供了一些标准的字符设备的操作接口,open/close,read/write,ioctl/poll这些接口。
open
先看一下提供的的open()
函数,可以看到函数很简单,就是把file->private_data
指针置空了。
read
再看一下read()
函数,这个函数就是查看一下链表里面是否有数据,如果有数据取出第一个数据后拷贝到用户空间,并没有明显看到与其他驱动的直接交互。
write
看一下write()
函数,这里一开始的handle
取值于file->private_data
的过程就表明了在write()
之前必须要对file->private_data
赋值的过程,由于open()
的过程是置空,那么必然是在其他接口进行了赋值。再看下面,申请空间拷贝了用户空间数据后,调用了hios_mcc_sendto_user()
函数进行发送,这里就可以看到与其他文件的接口直接交互了。
ioctl
上面read/write
过程中都有对file->private_data
的取值过程,实际的赋值过程是在ioctl()
这个接口里,提供了5个命令,看代码的情况应该是先INIT,再CONNECT,再执行其他的命令。
- HI_MCC_IOC_ATTR_INIT:属性初始化
- HI_MCC_IOC_CONNECT:连接对端PCIe
- HI_MCC_IOC_CHECK:检查对端PCIe
- HI_MCC_IOC_GET_LOCAL_ID:获取本地ID
- HI_MCC_IOC_GET_REMOTE_ID:获取对端ID
HI_MCC_IOC_ATTR_INIT
ioctl()
的HI_MCC_IOC_ATTR_INIT
调用hios_mcc_handle_attr_init()
函数,这个属性初始化的过程就是一个简单的赋值过程,都赋值为一些非法的值。
HI_MCC_IOC_CONNECT
ioctl()
的HI_MCC_IOC_CONNECT
调用hios_mcc_open()
函数,里面有一个关于是否处于中断处理过程的判断,如果是处于中断处理过程中,申请内存需要GFP_ATOMIC
原子申请,否则可能会休眠导致系统异常;hios_mcc_open()
函数将用户态传入的attr
的target_id
,port
,priority
参数传入pci_vdd_open()
函数,进行下一层处理。
再看一下pci_vdd_open()
函数,一开始就对这个target_id
进行入参判断,但ioctl()
的INIT过程对这些参数赋值都是一个非法值,那么这个条件肯定不满足,那么就是应用层INIT之后会先对这个入参进行赋值,再调用CONNECT???(可能是先使用GET_REMOTE_ID之后再使用open就可以了???)。
HI_MCC_IOC_CHECK
ioctl()
的HI_MCC_IOC_CHECK
调用hios_mcc_check_remote()
函数:
调用的pci_vdd_check_remote()
函数如下所示,获取到设备结构体后,根据当前是否是RC端来进行检查。
HOST端执行host_check_slv()
进行检查,执行两步握手,设置共享内存为768K,并初始化共享内存。
握手
第零步握手
HOST 端:
使能本地读写DMA,映射对端寄存器到BAR空间。
SLAVE端:循环读取RC端写入的值,读到后检查握手位,共享内存在从片时,将共享内存地址写入寄存,置位握手位。
第一步握手
HOST端:等待从端写握手位,并读出共享内存的地址。
SLAVE端:直接返回。
共享内存初始化
将768K共享内存分成两半,一般用作发送消息,一半用来接收消息。
HI_MCC_IOC_GET_LOCAL_ID
ioctl()
的HI_MCC_IOC_GET_LOCAL_ID
调用hios_mcc_getlocalid()
函数:
调用的pci_vdd_remoteids()
函数根据当前是否是HOST端(RC),返回0,如果当前是EP端,则读芯片寄存器返回总线号。
HI_MCC_IOC_GET_REMOTE_ID
ioctl()
的HI_MCC_IOC_GET_REMOTE_ID
调用hios_mcc_getremoteids()
函数:
调用的pci_vdd_remoteids()
函数根据当前是否是HOST端(RC),返回SLAVE端(EP)的总线号,如果当前是EP端,则返回ID为0,表明对端是RC。
最后
以上就是俊逸奇迹为你收集整理的PCIE_MCC驱动阅读过程–HOST篇的全部内容,希望文章能够帮你解决PCIE_MCC驱动阅读过程–HOST篇所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复