PCIe总线传输速率和带宽测试
zhilu.zhang
zhilu.zhang
发布于 2020-12-23 / 150 阅读 / 0 评论 / 0 点赞

PCIe总线传输速率和带宽测试

一、基础知识介绍

传输速率:每秒的传输量GT/s,非每秒的位数Gbps;(传输量是有效payload,不包括额外的吞吐开销);比如 PCIe 1.x和PCIe 2.x使用8b / 10b编码方案,导致占用了20% (= 2/10)的原始信道带宽。

GT/s:Giga transation per second (千兆传输/秒),即每一秒内传输的次数。重点在于描述物理层通信协议的速率属性,可以不和链路宽度等关联。

Gbps:Giga Bits Per Second (千兆位/秒)。GT/s 与Gbps 之间不存在成比例的换算关系。

PCIe吞吐量(有效payload数据)计算方法:

  • 吞吐量 = 传输速率 x 编码方式;

    • eg. PCIe4.0协议支持16G GT/s,也就是说每一条Lane上支持每秒内传输5G个Bit;

    • 而PCIe 4.0 的物理层协议中使用的是128b/130b的编码方式,也就是说每传输128bit 有效payload数据需要发送130个bit(额外多2个bit);

    • 那么PCIe 3.0协议的每一条Lane支持16 x 128 / 130 = 15.7538462 Gbps的速率;

    • PCIe 3.0 x 2的通道:x2的有效带宽为15.7538462 x 2 = 31.5076923 Gbps = 3.94 GB/s;

到这里,还不够,我们知道PCIe协议中的数据报(TLP)还有包头、包尾数据:

由上图得知,1 + 2 + 12(or16)+ 4 +4 +1 = 24(or28)

  • 传输8byte数据,带宽使用率为8/(8+28) = 22.2%;

  • RC和EP传输(或者EP-EP),需要考虑MaxReadREq,如果MaxReadREq = 256,256 / (256 + 28 + 28) = 70%的使用率;

另外还有flow controll的带宽损耗在1%~5%左右。

二、设备驱动程序框架

测试两片通过pcie连接的海思芯片带宽,一片做RC一片做EP。介绍重点是PCIe控制器的配置、PCIe内部DMA和中断使能、带宽处理。

本设备驱动程序主要由两个部分组成:PCIe设备驱动程序和PCIe字符设备驱动程序。其中PCIe设备驱动程序用于实现PCIe内存映射的功能,而PCIe字符设备驱动程序用于控制DMA实现数据传输以测量PCIe总线带宽。

1 PCIe设备驱动程序框架

由于PCIe控制器的作用在本应用中主要用于映射RC和EP的内存空间。其驱动程序框架较为简单。

1.	/* PCIe信息结构体 */
2.	static struct pci_driver pci_dri = {
3.	    .name       = "pcie_slave",            //设备驱动程序名称,会显示在lspci -v命令中    
4.	    .id_table   = pci_ids,                //用于设备驱动程序与PCIe设备匹配
5.	    .probe      = pcie_slave_probe,     //驱动程序与设备匹配时触发
6.	    .remove     = pcie_slave_remove,    //设备卸载时触发

其中的name为驱动程序的名称,会显示在lspci -v命令中。通过本字段可以查看到PCIe设备目前与哪一个设备驱动程序相匹配。

1.	/* 用于匹配PCI总线上的设备 */
2.	static const struct pci_device_id pci_ids[] = {
3.	    {PCI_VENDOR_HI3559AV100, PCI_DEVICE_HI3559AV100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
4.	    {0, }
5.	}

该结构体为PCIe设备驱动程序与PCIe设备的匹配数据。实际上是系统将其与枚举时发现到的PCIe设备配置空间寄存器的设备ID、厂商ID等做对比。如果发现了匹配的字段,则将设备驱动程序与设备做匹配。

至于probe()函数和remove()函数与设备驱动程序的功能实现有关,两者分别在PCIe设备驱动程序与PCIe设备匹配和分离时被调用。

​ 在实现了以上的结构体和函数以后,就可以使用函数pci_register_driver()函数将PCIe设备驱动程序注册到系统中。类似的使用pci_unregister_driver()函数可以卸载对应的设备程序。

2 PCIe字符设备驱动程序框架

由于本驱动程序需要提供用户层接口。可以实现一个字符类型设备驱动程序用于在用户层提供DMA传输控制的接口。

1.	/* PCIs slave char driver structure */
2.	static const struct file_operations pcie_slave_char_fops = {
3.	    .owner          = THIS_MODULE,
4.	    .open           = pcie_slave_char_open,
5.	    .release        = pcie_slave_char_release,
6.	    .unlocked_ioctl = pcie_slave_char_ioctl,
7.	};

其中的open(),release()和unlocked_ioctl()函数均会在用户层调用响应的系统调用时被调用。本文主要介绍的是unlocked_ioctl()函数的实现。

1.	/* 由系统提供的宏定义产生ioctl控制命令 */
2.	#define IOCTL_DMA_READ_SIZE             _IO('D', 0)
3.	#define IOCTL_DMA_WRITE_SIZE            _IO('D', 1)
4.	#define IOCTL_DMA_READ                  _IO('D', 2)
5.	#define IOCTL_DMA_WRITE                 _IO('D', 3
6.	......
7.	static long pcie_slave_char_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
8.	{
9.	    long ret = RET_OK;
10.	
11.	    down(&psc.ioctl_sema);                        //锁定信号量,防止多线程同时操作
12.	    psm.diff_us = 0;
13.	    /* Show the CMD NUM */
14.	    switch(cmd)
15.	    {
16.	        case IOCTL_DMA_READ_SIZE:
17.	            psm.dma_read_size = (u32)arg;       //设定全局变量中需要传输的字节数    
18.	            break;
19.	        case IOCTL_DMA_WRITE_SIZE:
20.	            psm.dma_write_size = (u32)arg;
21.	            break;
22.	        case IOCTL_DMA_READ:
23.	            psm.dma_channel = 1;
24.	            /* 启动DMA传输 */
25.	            hi35xx_dma_start(psm.private_data, psm.dma_channel, psm.dma_read_size);
26.	            /* 阻塞住进程,由DMA中断处理程序解开 */
27.	            wait_event_timeout(psc.ioctl_wait, atomic_read(&psc.ioctl_aio), 1000);
28.	            atomic_dec(&psc.ioctl_aio);
29.	            ret = copy_to_user((u32* )arg, (void* )&psm.diff_us, sizeof(psm.diff_us));
30.	            if(ret)
31.	            {
32.	                printk("%s: copy_to_user() failed with code: 0x%x\n", __func__, (u32)ret);
33.	            }
34.	            break;
35.	        case IOCTL_DMA_WRITE:
36.	            psm.dma_channel = 0;
37.	            hi35xx_dma_start(psm.private_data, psm.dma_channel, psm.dma_write_size);
38.	            /* 阻塞住进程 */
39.	            wait_event_timeout(psc.ioctl_wait, atomic_read(&psc.ioctl_aio), 1000);
40.	            atomic_dec(&psc.ioctl_aio);
41.	            ret = copy_to_user((u32* )arg, (void* )&psm.diff_us, sizeof(psm.diff_us));
42.	            if(ret)
43.	            {
44.	                printk("%s: copy_to_user() failed with code: 0x%x\n", __func__, (u32)ret);
45.	            }
46.	            break;
47.	        default:
48.	            ret = -EINVAL;
49.	    }
50.	    up(&psc.ioctl_sema);                //释放信号量
51.	    return ret;
52.	}

三、PCIe控制器寄存器配置

1 ATU寄存器组配置

PCIe的映射包含两种地址映射。一个是PCIe从设备配置空间映射,配置了这一段映射空间以后,Hi3559A的CPU访问地址空间0x200000000到0x300000000的地址段会自动转换为从设备的配置空间访问事务。另一种是存储器的配置空间访问事务。经过测试,好像只用0x300000000到0x400000000的地址空间可以用于Outbound映射。而从设备的Inbound映射则没有什么内存空间限制。

1.	/* 配置PCIE设备的控制寄存器到主设备的本地总线地址 */
2.	static int hi35xx_atu_slave_config(void)
3.	{
4.	    int ret = RET_OK;
5.	
6.	    /* 在此处配置从片的配置空间到主片的地址 */
7.	    spin_lock(&psm.reg_lock);
8.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_VIEWPORT)       = 0x00000000;
9.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_BASE_LOW)       = 0x20100000;
10.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_BASE_HIGH)      = 0x00000000;
11.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_LIMIT)          = 0x201FFFFF;
12.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_REGION_CTRL1)   = 0x00000004;
13.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_REGION_CTRL2)   = 0x90000000;
14.	    spin_unlock(&psm.reg_lock);
15.	    return ret;
16.	}
17.	
18.	/* 配置主设备和从设备的inbound和outbound */
19.	static int hi35xx_atu_slave_memory(struct pci_dev* dev)
20.	{
21.	    int ret = RET_OK;
22.	
23.	    /*在此处进行inbount和outbount的配置 */
24.	    /* For RC outbound*/
25.	    spin_lock(&psm.reg_lock);
26.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_VIEWPORT)       = 0x00000000|1;
27.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_BASE_LOW)       = 0x36000000;
28.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_BASE_HIGH)      = 0x00000000;
29.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_LIMIT)          = 0x38FFFFFF;
30.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_TARGET_LOW)     = 0x36000000;
31.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_TARGET_HIGH)    = 0x00000000;
32.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_REGION_CTRL1)   = 0x00000000;
33.	    *(volatile u32*)(psm.pcie_config_base_v+ATU_REGION_CTRL2)   = 0x80000000;
34.	    /* For EP inbound*/
35.	    pci_write_config_dword(dev, ATU_VIEWPORT,                     0x80000000|1);
36.	    pci_write_config_dword(dev, ATU_BASE_LOW,                     0x36000000);
37.	    pci_write_config_dword(dev, ATU_BASE_HIGH,                    0x00000000);
38.	    pci_write_config_dword(dev, ATU_LIMIT,                        0x38FFFFFF);
39.	    pci_write_config_dword(dev, ATU_TARGET_LOW,                   0xEA400000);
40.	    pci_write_config_dword(dev, ATU_TARGET_HIGH,                  0x00000000);
41.	    pci_write_config_dword(dev, ATU_REGION_CTRL1,                 0x00000000);
42.	    pci_write_config_dword(dev, ATU_REGION_CTRL2,                 0x80000000);
43.	    spin_unlock(&psm.reg_lock);
44.	    return ret;
45.	}

主片的Outbound和从片的Inbound所映射的PCIe空间地址必须是相同的,才能形成通路。

2 PCIe内部DMA寄存器组配置

由于本驱动程序的目的是测量PCIe总线的带宽,因此需要使用DMA减少CPU对PCIe内存地址访问产生的带宽损耗。

2.1 DMA中断申请与/proc/interrupts文件

想要使用PCIe内部DMA的中断,除了配置中断使能、中断掩码寄存器外,还需要向内核申请中断资源。

1.	#define PCIE_EDMA_INT                   50
2.	......
3.	static int hi35xx_enable_pcie_dma_irq(void)
4.	{
5.	    int ret = RET_OK;
6.	
7.	    /* 清除中断标志 */
8.	    hi35xx_pcie_dma_irq_status(0);
9.	    hi35xx_pcie_dma_irq_status(1);
10.	    ret = request_irq(PCIE_EDMA_INT, hi35xx_pcie_dma_isr, 0, "PCIe DMA read irq", &psm);
11.	    if(ret)
12.	    {
13.	        printk("%s: requese irq failed.\n", __func__);
14.	        ret = -1;
15.	        return ret;
16.	    }
17.	    printk("%s: Requested a irq: %d\n", __func__, PCIE_EDMA_INT);
18.	    spin_lock(&psm.reg_lock);
19.	    /* 开启DMA读和写中断 */
20.	    writel(0x80000000, (void* )(psm.pcie_config_base_v + DMA_CH_INDEX));
21.	    writel(0x00000008, (void* )(psm.pcie_config_base_v + DMA_CH_CTRL));
22.	    writel(0x00000000, (void* )(psm.pcie_config_base_v + DMA_CH_INDEX));
23.	    writel(0x00000008, (void* )(psm.pcie_config_base_v + DMA_CH_CTRL));
24.	    spin_unlock(&psm.reg_lock);
25.	    return ret;
26.	}

该函数首先清除PCIe DMA中断的状态,然后向内核申请中断资源。使用函数request_irq()可以申请指定的终端号、指定中断处理函数和中断、终端标志、终端名称以及参数。需要注意的是这里的宏定义PCIE_EDMA_INT的值为50,然而实际上查看Hi3559A的手册对应的中断号为179,原因是内核对中断号进行了映射。最后开启了DMA读和写的中断。

​ 成功申请中断号以后,可以查看/proc/interrupts文件检查当前的中断状态。该文件的内容如下。

1.	           CPU0       CPU1       CPU2       CPU3       
2.	  1:          0          0          0          0     GIC-0  29 Level     arch_timer
3.	  2:    1975973    1974320    1972164    1976462     GIC-0  30 Level     arch_timer
4.	  3:         19          0          0          0     GIC-0 243 Level     ipcm
5.	  7:     589813          0          0          0     GIC-0  34 Level     hi35xx_timer4
6.	  9:       8094          0          0          0     GIC-0  38 Level     uart-pl011
7.	 12:        310          0          0          0     GIC-0  41 Level   
8.	 13:          1          0          0          0     GIC-0  42 Level   
9.	 14:          0          0          0          0     GIC-0  63 Level     pl022

文件中第一列用于标识映射后的中断号;其中CPU0、CPU1、CPU2、CPU3列分被用来标识当前的中断被各个CPU处理的次数;第六列用来标识当中断的中断控制器;第七列则是没有映射前的中断号(手册上的中断号);第八列为中断的触发条件;最后一列是驱动程序申请中断时指定的中断名称。

2.2 PCIe DMA寄存器组配置

PCIe内部DMA可以实现从PCIe总线地址到内存空间的读写,以及从PCIe总线地址到内存空间的读写。以及可以配置DMA传输完成中断用于向驱动程序通知DMA传输已经完成。本部分主要介绍PCIe内部DMA寄存器组的配置方法以及DMA中断处理程序等。

1.	/* 配置并启动DMA通道 */
2.	static int hi35xx_dma_start(struct pci_dev* dev, int dir, u32 size)
3.	{
4.	    int ret = RET_OK;
5.	
6.	    printk("%s: dir %d    size 0x%x\n", __func__, dir, size);
7.	    /* 配置PCIe的DMA控制器并设置为enable状态 */
8.	    spin_lock(&psm.reg_lock);
9.	    if(0 == dir)
10.	    {
11.	        /* write channel */
12.	        writel(0x00000001, (void* )(psm.pcie_config_base_v + DMA_WR_ENGINE_EN));
13.	        writel(0x00000000, (void* )(psm.pcie_config_base_v + DMA_CH_INDEX));
14.	        writel(0x00000008, (void* )(psm.pcie_config_base_v + DMA_CH_CTRL));
15.	        writel(0xEC000000, (void* )(psm.pcie_config_base_v + DMA_SAR_LOW));
16.	        writel(0x00000000, (void* )(psm.pcie_config_base_v + DMA_SAR_HIGH));
17.	        writel(0x36000000, (void* )(psm.pcie_config_base_v + DMA_DAR_LOW));
18.	        writel(0x00000000, (void* )(psm.pcie_config_base_v + DMA_DAR_HIGH));
19.	        writel(size      , (void* )(psm.pcie_config_base_v + DMA_TRANS_SIZE));
20.	        writel(0x00000000, (void* )(psm.pcie_config_base_v + DMA_WR_DOORBELL));
21.	    }
22.	    else
23.	    {
24.	        /* read channel */
25.	        writel(0x00000001, (void* )(psm.pcie_config_base_v + DMA_RD_ENGINE_EN));
26.	        writel(0x80000000, (void* )(psm.pcie_config_base_v + DMA_CH_INDEX));
27.	        writel(0x00000008, (void* )(psm.pcie_config_base_v + DMA_CH_CTRL));
28.	        writel(0x36000000, (void* )(psm.pcie_config_base_v + DMA_SAR_LOW));
29.	        writel(0x00000000, (void* )(psm.pcie_config_base_v + DMA_SAR_HIGH));
30.	        writel(0xEC000000, (void* )(psm.pcie_config_base_v + DMA_DAR_LOW));
31.	        writel(0x00000000, (void* )(psm.pcie_config_base_v + DMA_DAR_HIGH));
32.	        writel(size      , (void* )(psm.pcie_config_base_v + DMA_TRANS_SIZE));
33.	        writel(0x00000000, (void* )(psm.pcie_config_base_v + DMA_RD_DOORBELL));
34.	    }
35.	    spin_unlock(&psm.reg_lock);
36.	
37.	    #ifdef TIMEVAL_US 
38.	    /* 记录启动时间戳 */
39.	    do_gettimeofday(&time_start);
40.	    #endif
41.	    #ifdef TIME_JIFF
42.	    psm.diff_jiff = jiffies;
43.	    #endif
44.	    return ret;
45.	}

配置了DMA与中断相关的寄存器后,就可以使用DMA传输完成中断了。PCIe内部的DMA读通道和写通道共用一个中断线。因此原理是系统在收到PCIe内部DMA中断后,调用中断处理程序。在中断处理程序中查看读中断和写中断寄存器的状态以确定当前是哪一个通道引发的中断以及中断类型,然后清除响应的中断寄存器。

1.	/* PCIe DMA中断处理函数 */
2.	static irqreturn_t hi35xx_pcie_dma_isr(int irq, void* data)
3.	{
4.	    int int_status = 0; 
5.	    struct pcie_slave_mem* p;
6.	
7.	    p = (struct pcie_slave_mem* )data;
8.	    int_status = hi35xx_pcie_dma_irq_status(psm.dma_channel);
9.	    /* 判断中断状态 */
10.	    if(int_status == 0)
11.	    {
12.	        printk("%s: Not PCIE DMA read interrupt\n", __func__);
13.	    }
14.	    else if(int_status == 1)
15.	    {
16.	        p->int_nums++;
17.	        printk("%s: PCIE DMA complete interrupts: TIME %d\n", __func__, p->int_nums);
18.	        /* 记录时间戳 */
19.	        do_gettimeofday(&time_end);
20.	        p->diff_us = (time_end.tv_usec - time_start.tv_usec);
21.	        printk("%s: time of day diff us: %llu\n", __func__, p->diff_us);
22.	        /* 唤醒等待在此队列上的进程 */
23.	        atomic_inc(&psc.ioctl_aio);
24.	        wake_up(&psc.ioctl_wait);
25.	    }
26.	    else if(int_status == 2)
27.	    {
28.	        #ifdef TIME_JIFF
29.	        psm.diff_jiff = 0;
30.	        #endif
31.	        printk("%s: PCIE DMA has been abort.\n", __func__);
32.	        /* 唤醒等待在此队列上的进程 */
33.	        wake_up(&psc.ioctl_wait);
34.	    }
35.	    else
36.	    {
37.	        #ifdef TIME_JIFF
38.	        psm.diff_jiff = 0;
39.	        #endif
40.	        printk("%s: SomeUnknow error happened.\n", __func__);
41.	    }
42.	    return IRQ_HANDLED;
43.	}

在中断处理程序中处理完成上述功能外,还测量了当前DMA传输完成并进入中断处理程序后的时间,然后唤醒因调用ioctl命令而阻塞的应用程序。

四、应用程序框架

应用程序主要还是调用驱动程序的ioctl命令实现DMA的读和写传输,以及获取完成传输的耗时,以计算PCIe总线的带宽性能。

​应用程序的代码如下:

1.	/* This programe for test the PCIw bandwidth.
2.	 */
3.	#include <stdio.h>
4.	#include <stdlib.h>
5.	#include <unistd.h>
6.	#include <fcntl.h>
7.	#include <sys/ioctl.h>
8.	#include <sys/stat.h>
9.	#include <sys/types.h>
10.	
11.	#define RET_OK 0
12.	#define RET_ERR -1
13.	#define DEV_NAME "/dev/pcie_slave_char"
14.	#define IOCTL_DMA_READ_SIZE 0x4400
15.	#define IOCTL_DMA_WRITE_SIZE 0x4401
16.	#define IOCTL_DMA_READ 0x4402
17.	#define IOCTL_DMA_WRITE 0x4403
18.	
19.	/* return -1 means something error.
20.	 *  return 0 means write.
21.	 *  retrun 1 means read.
22.	 */
23.	static int argc_argv_handle(int argc, char **argv)
24.	{
25.	    int mode = -1;
26.	
27.	    if (argc != 2)
28.	    {
29.	        goto ERROR;
30.	    }
31.	
32.	    if (!strcmp(argv[1], "-w"))
33.	    {
34.	        mode = 0;
35.	    }
36.	    else if (!strcmp(argv[1], "-r"))
37.	    {
38.	        mode = 1;
39.	    }
40.	    else
41.	    {
42.	        goto ERROR;
43.	    }
44.	
45.	    return mode;
46.	ERROR:
47.	    mode = -1;
48.	    return mode;
49.	}
50.	
51.	/* 用于获取读或写方向的单次DMA传输时间 */
52.	static unsigned long long char_ioctl(int fd, int dir, unsigned long size)
53.	{
54.	    int ret = 0;
55.	    unsigned long long time_us = 0;
56.	
57.	    if (size > 0x3000000)
58.	    {
59.	        printf("%s: parameter size is bigger than 0x3000000, should be smaller.\n", __func__);
60.	        return -1;
61.	    }
62.	
63.	    if (0 == dir)
64.	    {
65.	        /* write situation */
66.	        ret = ioctl(fd, IOCTL_DMA_WRITE_SIZE, size);
67.	        if (ret)
68.	        {
69.	            goto ERROR;
70.	        }
71.	        ret = ioctl(fd, IOCTL_DMA_WRITE, &time_us);
72.	        if (ret)
73.	        {
74.	            goto ERROR;
75.	        }
76.	    }
77.	    else if (1 == dir)
78.	    {
79.	        /* read situation */
80.	        ret = ioctl(fd, IOCTL_DMA_READ_SIZE, size);
81.	        if (ret)
82.	        {
83.	            goto ERROR;
84.	        }
85.	        ret = ioctl(fd, IOCTL_DMA_READ, &time_us);
86.	        if (ret)
87.	        {
88.	            goto ERROR;
89.	        }
90.	    }
91.	    return time_us;
92.	ERROR:
93.	    ret = 0;
94.	    printf("%s: ioctl failed.\n");
95.	    return ret;
96.	}
97.	
98.	int main(int argc, char **argv)
99.	{
100.	    int ret = RET_OK;
101.	    int fd = -1;
102.	    int mode = -1;
103.	    unsigned int size_1 = 0x3000000;
104.	    unsigned int size_2 = 0x2000000;
105.	    unsigned long long time_1 = 0;
106.	    unsigned long long time_2 = 0;
107.	    double bandwidth = 0;
108.	
109.	    /* 区分DMA读和写通道 */
110.	    mode = argc_argv_handle(argc, argv);
111.	    if (-1 == mode)
112.	    {
113.	        printf("Input as %s -w|-r.\n", argv[0]);
114.	        ret = -1;
115.	        return ret;
116.	    }
117.	
118.	    fd = open(DEV_NAME, O_RDWR);
119.	    if (fd < 0)
120.	    {
121.	        printf("open filed %s failed with code %d\n", DEV_NAME, fd);
122.	        return -2;
123.	    }
124.	
125.	    time_1 = char_ioctl(fd, mode, size_1);
126.	    if (time_1 == 0)
127.	    {
128.	        goto ERROR;
129.	    }
130.	    time_2 = char_ioctl(fd, mode, size_2);
131.	    if (time_2 == 0)
132.	    {
133.	        goto ERROR;
134.	    }
135.	
136.	    printf("time_1 %u    time_2 %u\n", time_1, time_2);
137.	    bandwidth = 16.0 / (((double)time_1 - (double)time_2)/ 1000/ 1000);
138.	    printf("DMA bandwidth: %lf\n", bandwidth);
139.	
140.	    return ret;
141.	ERROR:
142.	    printf("Some Unknow Error Happened.\n");
143.	    ret = -1;
144.	}

应用程序首先对命令行参数进行验证,以区分对DMA读和写时PCIe总线带宽的测量。然后调用字符设备驱动程序的ioctl函数,首先对DMA的读或写通道配置DMA传输的字节数,然后启动DMA的读或写通道。调用后应用程序会被驱动程序阻塞住直到DMA的传输完成或者超时。然后通过ioctl的命令参数将DMA通道启动到DMA中断处理程序之间的时间差返回到应用空间。

​ DMA通道启动到DMA中断处理程序之间的时间差值应该等于PCIe传输时间加上中断处理程序的时间损耗。使用DMA对不同大小的内存空间进行读写发现其中的中断处理程序时间损耗较大,已经不能忽视。由于中断处理程序的时间损耗相对固定。因此可以理解为$T{PCIe传输}+T{中断处理}=T$因此可以配置两次不同大小内存空间的DMA读或者写,分别测量两次的总耗时分别为$T1$和$T_2$.则$T_2-T_1=T{PCIe传输1}-T_{PCIe传输2}$.以此排除不易测量的中断处理程序耗时。

/dav0/zhangzhilu # Is
char_app.run        dma BW.run             pcie_slave.ko          rm_oldapp.sh update.sh
/dav0/zhangzhilu # insmod pcie_slave.ko
pcie_slave_probe: Found a                  device: 355919e5
hi35xx_pcie_dma_irq_status:                dir 0
hi35xx_pcie_dma_irq_status:                dir 1
hi35xx_enable_pcie_dma_irq:                Requested a irq: 50
pcie_slave_char_init: Add a                new char driver.       MAJOR:230 MINOR:0.
pcie_slave_char_init: Pcie_                slave_char device support CMD: 0x4400.
pcie_slave_char_init: Pcie_                slave_char device support CMD: 0x4401.
pcie_slave_char_init: Pcie_                slave_char device support CMD: 0x4402.
pcie_slave_char_init: Pcie_                slave_char device support CMD: 0x4403.
/dav0/zhangzhilu # ./dma_BW.run -w
hi35xx dma start       dir 0 size                 0x3000000
hi35xx_pcie_dma_irq_status: dir 0
hi35xx_pcie_dma_isr: PCIE DMA complete interrupts:                TIME
hi35xx_pcie_dma_isr: time of day diff us: 66594
hi35xx_pcie_dma_isr: get the diff of jiffies: 58
hi35xx_pcie_dma_isr: Diff of the ms                          :58
hi35xx dma start       dir 0 size 0x2000000
hi35xx_pcie_dma_irq_status: dir 0
hi35xx_pcie_dma_isr: PCIE DMA complete interrupts:                TIME 2
hi35xx_pcie_dma_isr: time of day diff us: 47116
hi35xx_pcie_dma_isr: get the diff of jiffies: 39
hi35xx_pcie_dma_isr: Diff of the ms                          :39
time 1 66594 time          2 47116
DMA bandwidth: 821.439573
/dav0/zhangzhilu # ./dma_BW.run -r
hi35xx dma start       dir 1 size 0x3000000
hi35xx_pcie_dma_irq_status: dir 1
hi35xx_pcie_dma_isr: PCIE DMA complete interrupts:                TIME 3
hi35xx_pcie_dma_isr: time of day diff us: 68170
hi35xx_pcie_dma_isr: get the diff of jiffies: 60             :60
hi35xx_pcie_dma_isr: Diff of the ms
hi35xx dma start       dir 1 size 0x2000000
hi35xx_pcie_dma_irq_status: dir 1
hi35xx_pcie_dma_isr: PCIE DMA complete interrupts:                TIME 4
hi35xx_pcie_dma_isr: time of day diff us: 48167
hi35xx_pcie_dma_isr: get the diff of jiffies: 40
hi35xx_pcie_dma_isr: Diff of the ms                          :40
time 1 68170 -time         2 48167
DMA bandwidth: 799.880018
/dav0/zhangzhilu #

五、循环测试PCIe带宽脚本

编写了循环测试PCIe带宽的脚本。该脚本重复运行测试程序10次,检查是否存在测试过程中的异常的带宽差距较大。如下:

1.	echo "run dma_BW.run for 10 times ..."
2.	
3.	mode=0;
4.	#parameter check
5.	if [ $# -ne 1 ]
6.	then
7.	    echo "Should be $0 + -w|-r "
8.	    exit;
9.	fi
10.	
11.	if [ $1 == "-w" ]
12.	then
13.	    mode=1;
14.	elif [ $1 == "-r" ]
15.	then
16.	    mode=2;
17.	else
18.	    echo "Should be $0 + -w|-r "
19.	    exit;
20.	fi
21.	
22.	#get the bandwidth for 10 times.
23.	while_i=0;
24.	if [ $mode -eq 1 ]
25.	then
26.	    while [ $while_i -lt 10 ] 
27.	    do
28.	        echo $while_i;
29.	        let "while_i+=1";
30.	        ./dma_BW.run -w
31.	    done 
32.	elif [ $mode -eq 2 ]
33.	then
34.	    while [ $while_i -lt 10 ]
35.	    do
36.	        echo $while_i;
37.	        let "while_i+=1";
38.	        ./dma_BW.run -r
39.	    done
40.	fi

推荐文章:

https://aijishu.com/a/1060000000287490