我是靠谱客的博主 俊逸奇迹,最近开发中收集的这篇文章主要介绍PCIE_MCC驱动阅读过程–HOST篇,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

原文出自: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_MODULEpci_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~146INT_A~INT_D的中断线,对应芯片数据手册的中断源175~178147是PCIE_EDMA的中断号,对应芯片数据手册的中断源17938对应的中断源是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()函数将用户态传入的attrtarget_idportpriority参数传入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篇所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(35)

评论列表共有 0 条评论

立即
投稿
返回
顶部