精简ISA总线实现高速大容量数据采集

 2021-6-22 11:15:18     作者:Emtronix    

  《精简ISA总线编程接 – Part2》介绍了英创工控主板ESM7000在DMA驱动的同步总线周期,在应用层可获得:


模式传输速率CPU负载
MemCpy DMA同步总线读8.67MB/s6.01%
MemCpy DMA同步总线写7.93MB/s5.71%


  本文在《精简ISA总线编程 – Part2》的基础上,进一步优化总线参数,总线同步时钟BCLK从30MHz提高到40MHz,同时采用mmap存储器映射方法在应用层读取ISA总线采集的数据,使应用层的读取速度达到16MB/s。


  为达到优化目标,首先需要预留1MB的DDR空间,作mmap使用。对客户来说,就是配置特别的DTB文件(imx7d-esm7000-isa-v2.dtb)。ISA读取数据的流程很简单,基本是初始化mmap指针,把需要采集的长度等参数填到struct isa_transfer结构中,然后就是调用读取函数。以下介绍核心的代码。


  Mmap指针初始化代码如下:


void* mmap_base(unsigned long mem_phys, unsigned int size)
{
    void *base = NULL;
    int fd;
    fd = open("/dev/mem", O_RDWR|O_SYNC);
    if(fd < 0) {
        printf("%s open failed %d\n", __func__, fd);
        goto cleanup;
    }
    /* mmap mem */
    base = mmap(
        NULL,                   //Any adddress in our space will do
        size,                   //Map length
        PROT_READ|PROT_WRITE,   //Enable reading & writting to mapped memory
        MAP_SHARED,             //Shared with other processes
        fd,                     //File to map
        mem_phys                //Offset to DMTimer peripheral
    );
    close(fd);
cleanup:
    return base;
}


  通过mmap驱动ISA数据的核心函数:


int mmap_dma_mem_read(int fd, struct isa_transfer *tp, void* mmapbuf, unsigned int mmapbuf_size)
{
    int rc = 0;
    unsigned int rem_len, mmap_len, pack_len;
    u_int8_t *sbuf[2], *cbuf;
    unsigned int sbuf_len, sbuf_count, sbuf_idx;
    u_int8_t dbuf[256];
    unsigned dbuf_len = sizeof(dbuf);
    struct pollfd fds[1];
    rem_len = tp->len;
    if(!rem_len) {
        rc = -EINVAL;
        goto cleanup;
    }
    // init variables
    sbuf[0] = (u_int8_t*)mmapbuf;
    sbuf_len = mmapbuf_size / 2;
    sbuf[1] = sbuf[0] + sbuf_len;
    sbuf_count = 0;
    sbuf_idx = 0;
    cbuf = sbuf[sbuf_idx];
    memset(fds, 0, sizeof(fds));
    fds[0].fd = fd;
    fds[0].events = POLLIN;
    // start dma process
    rc = ioctl(fd, ISA_IOCTL_START, (unsigned long)tp);
    if(rc < 0) {
        printf("%s start dma-mmap failed %d\n", __func__, rc);
        goto cleanup;
    }
    while(rem_len) {
        // wait data ready with timeout
        rc = poll(fds, 1, POLL_TIMEOUT);
        if(rc == -1) {
            perror("poll failed!\n");
            ioctl(fd, ISA_IOCTL_STOP, (unsigned long)tp);
            return rc;
        }
        else if(rc == 0) {
            // timeout
            continue;
        }
        // setup source buffer length
        mmap_len = sbuf_len;
        if(mmap_len > rem_len) {
            mmap_len = rem_len;
        }
        // get data from mmap buffer
        while(mmap_len) {
            pack_len = dbuf_len;
            if(pack_len > mmap_len) {
                pack_len = mmap_len;
            }
            memcpy(dbuf, sbuf[sbuf_idx] + sbuf_count, pack_len);
            sbuf_count += pack_len;
            mmap_len -= pack_len;
            if(INFINITE != rem_len)
                rem_len -= pack_len;
        }
        // switch to next mmap buffer
        sbuf_idx = (sbuf_idx + 1) % 2;
        sbuf_count = 0;
    }
    // well done
    rc = tp->len - rem_len;
cleanup:
    return rc;
}


  上面的代码中fd是打开设备/dev/em_isa的文件句柄。


  应用层调用:


#define MMAP_BASE_PHYS          0xBFF00000
#define MMAP_SIZE               0x80000             //512KB
    struct isa_transfer t;
    double elapsed, data_rate;
    unsigned int data_length;
    void *mmapbuf;
    unsigned int mmapbuf_size;
//………
        // get mmap buffer
        mmapbuf = mmap_base(MMAP_BASE_PHYS, MMAP_SIZE);
        if(!mmapbuf) {
            printf("failed to get mmap buffer\n");
            break;
        }
        printf("mmap range(0x%08x, 0x%x) => %p\n", MMAP_BASE_PHYS, MMAP_SIZE, mmapbuf);
        memset(&t, 0, sizeof(struct isa_transfer));
        t.rx_buf = (void*)MMAP_BASE_PHYS;
        t.offset = offset;
        t.len = count;
        rc = mmap_dma_mem_read(fd, &t, mmapbuf, MMAP_SIZE);
        if(rc < 0) {
            printf("mmap_dma_mem_read failed %d\n", rc);
            break;
        }
        data_length = rc;


  采用64MB数据长度,对上述代码测试应用层数据:


模式传输速率CPU负载
MemCpy DMA同步总线读16.06MB/s38.7%


  函数mmap_dma_mem_read是软件启动DMA执行同步总线读周期,其基本的总线时序与《精简ISA总线编程 – Part2》的时序图是一致的,不同之处仅仅是总线同步时钟BCLK的周期从过去的33ns缩短到25ns。客户的FPGA应尽可能靠近主板CN2布局,以保证总线信号的完整性。


精简ISA总线应用于高速大容量数据采集.png

DMA-MEM同步总线读时序


  测试程序test_isa包括了完整的ISA总线的读写测试,其中执行mmap dma-mem读操作的命令为:

  ./test_isa D1000000 4000


  命令中参数”D”表示mmap dma-mem读操作,后续的是16进制的读取字节数,0x1000000表示16MB,第二个参数是16进制的ISA总线地址偏移量,0x4000用于告诉驱动程序执行dma-mem操作,有关地址偏移量的说明可参考《精简ISA总线编程 – Part1》


  对测试程序干兴趣的客户可来邮件(suppport@emtronix.com)索取test_isa的源代码。目前在Linux平台上的开发环境是多种多样,推荐使用微软开源的Visual Studio Code作为基本的IDE环境,通过简单的配置就可把ESM7000的SDK编译工具加入环境,就可Build应用程序。再通过NFS的挂载,就可直接在ESM7000目标板上跑客户的应用程序了。