
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>

#include <sys/timeb.h>




// 函数：从设备读取数据到本地内存（缓冲区）
// 功能：实现设备到主机的数据传输
// 参数：
//   dev_fd : 设备实例句柄
//   addr   : 设备中的源地址
//   buffer : 缓冲区基指针
//   size   : 数据大小
// 返回值：
//   成功：0；失败：-1
int dev_read (int dev_fd, uint64_t addr, void *buffer, uint64_t size) {
    if ( addr != lseek(dev_fd, addr, SEEK_SET) )                                 // 调整文件偏移到指定地址
        return -1;                                                               // 偏移失败
    if ( size != read(dev_fd, buffer, size) )                                    // 从设备读取数据到缓冲区
        return -1;                                                               // 读取失败
    return 0;
}


// 函数：从本地内存（缓冲区）写数据到设备
// 功能：实现主机到设备的数据传输
// 参数：
//   dev_fd : 设备实例句柄
//   addr   : 设备中的目标地址
//   buffer : 缓冲区基指针
//   size   : 数据大小
// 返回值：
//   成功：0；失败：-1
int dev_write (int dev_fd, uint64_t addr, void *buffer, uint64_t size) {
    if ( addr != lseek(dev_fd, addr, SEEK_SET) )                               
        return -1;                                                            
    if ( size != write(dev_fd, buffer, size) )                                 
        return -1;                                                          
    return 0;
}



// 函数：获取毫秒级别的时间
// 功能：返回当前时间的毫秒数
static uint64_t get_millisecond () {
    struct timeb tb;
    ftime(&tb);
    return (uint64_t)tb.millitm + (uint64_t)tb.time * 1000UL;
    // tb.time is the number of seconds since 00:00:00 January 1, 1970 UTC time;
    // tb.millitm is the number of milliseconds in a second
}



// 函数：解析无符号整数
// 功能：从字符串中解析出十六进制或十进制的无符号64位整数
// 参数：
//   string : 输入的字符串
//   pvalue : 解析后的值的指针
// 返回值：
//   成功解析的项数，失败则为0
int parse_uint (char *string, uint64_t *pvalue) {
    if ( string[0] == '0'  &&  string[1] == 'x' )                // 十六进制格式"0xXXXXXXXX"
        return sscanf( &(string[2]), "%lx", pvalue);
    else                                                         // 十进制格式
        return sscanf(   string    , "%lu", pvalue);
}




#define  DMA_MAX_SIZE   0x10000000UL  // DMA操作的最大尺寸定义 256kb



char USAGE[] =
    "Usage: \n"
    "\n"
    "  写入设备（主机到设备）:\n"
    "    %s <文件名> to <设备名> <设备内地址> <大小>\n"
    "  示例:\n"
    "    %s data.bin to /dev/xdma0_h2c_0 0x100000 0x10000\n"
    "\n"
    "  读取设备（设备到主机）:\n"
    "    %s <文件名> from <设备名> <设备内地址> <大小>\n"
    "  示例:\n"
    "    %s data.bin from /dev/xdma0_c2h_0 0x100000 0x10000\n";



int main (int argc, char *argv[]) {
    int   ret = -1;
    
    uint64_t millisecond;
    char usage_string [1024];
    
    char *dev_name, *file_name;
    char direction;
    uint64_t address, size;
    
    int   dev_fd = -1;
    FILE *file_p = NULL;
    void *buffer = NULL;
    
    sprintf(usage_string, USAGE, argv[0], argv[0], argv[0], argv[0] );
    
    if (argc < 6) {                                                // not enough argument
        puts(usage_string);
        return -1;
    }
    
    
    file_name = argv[1];
    direction = argv[2][0];
    dev_name  = argv[3];
    
    if ( 0 == parse_uint(argv[4], &address) ) {                    // parse the address in the device
        puts(usage_string);
        return -1;
    }
    
    if ( 0 == parse_uint(argv[5], &size ) ) {                      // parse the size in the device
        puts(usage_string);
        return -1;
    }
    
    // print information:
    if        (direction == 't') {                                 // to (write device, host-to-device)
        printf("from : %s\n" , file_name);
        printf("to   : %s   addr=0x%lx\n" , dev_name, address);
        printf("size : 0x%lx\n\n" , size);
    } else if (direction == 'f') {                                 // from (read device, device-to-host)
        printf("from : %s   addr=0x%lx\n" , dev_name, address);
        printf("to   : %s\n" , file_name);
        printf("size : 0x%lx\n\n" , size);
    } else {
        puts(usage_string);
        return -1;
    }
    
    
    if (size > DMA_MAX_SIZE  ||  size == 0) {
        printf("*** ERROR: DMA size must larger than 0 and NOT be larger than %lu\n", DMA_MAX_SIZE);
        return -1;
    }
    
    
    buffer = malloc(size);                                                   // allocate local memory (buffer)
    if (buffer == NULL) {
        printf("*** ERROR: failed to allocate buffer\n");
        goto close_and_clear;
    }
    
    dev_fd = open(dev_name, O_RDWR);                                         // open device
    if (dev_fd < 0) {
        printf("*** ERROR: failed to open device %s\n", dev_name);
        goto close_and_clear;
    }
    
    file_p = fopen(file_name, direction == 't' ? "rb" : "wb");               // open file for read/write
    if (file_p == NULL) {
        printf("*** ERROR: failed to open file %s\n", file_name);
        goto close_and_clear;
    }
    
    millisecond = get_millisecond();                                         // start time
    
    if (direction == 't') {
        if ( size != fread(buffer, 1, size, file_p) ) {                      // local file -> local buffer
            printf("*** ERROR: failed to read %s\n", file_name);
            goto close_and_clear;
        }
        if ( dev_write(dev_fd, address, buffer, size) ) {                    // local buffer -> device
            printf("*** ERROR: failed to write %s\n", dev_name);
            goto close_and_clear;
        }
    } else {
        if ( dev_read(dev_fd, address, buffer, size) ) {                     // device -> local buffer
            printf("*** ERROR: failed to read %s\n", dev_name);
            goto close_and_clear;
        }
        if ( size != fwrite(buffer, 1, size, file_p) ) {                     // local buffer -> local file
            printf("*** ERROR: failed to write %s\n", file_name);
            goto close_and_clear;
        }
    }
    
    millisecond = get_millisecond() - millisecond;                           // get time consumption
    millisecond = (millisecond > 0) ? millisecond : 1;                       // avoid divide-by-zero
    
    printf("time=%lu ms     data rate=%.1lf KBps\n", millisecond, (double)size / millisecond );
    
    ret = 0;
    
close_and_clear:
    
    if (buffer != NULL)
        free(buffer);
    
    if (dev_fd >= 0)
        close(dev_fd);
    
    if (file_p != NULL)
        fclose(file_p);
    
    return ret;
}


int main(int argc, char *argv[]) {
    int ret = -1; // 初始化返回值为-1，表示程序可能运行失败

    uint64_t millisecond; // 用于存储操作所花费的毫秒数
    char usage_string[1024]; // 存储程序使用说明的字符串

    char *dev_name, *file_name; // 分别指向设备名和文件名的指针
    char direction; // 存储传输方向的字符，'t' 表示写入设备，'f' 表示读取设备
    uint64_t address, size; // 存储设备地址和传输数据大小

    int dev_fd = -1; // 设备文件描述符，初始设置为-1表示未打开设备
    FILE *file_p = NULL; // 文件指针，初始为NULL
    void *buffer = NULL; // 数据缓冲区指针，初始为NULL

    // 根据程序名和命令行参数构造使用说明字符串
    sprintf(usage_string, USAGE, argv[0], argv[0], argv[0], argv[0]);

    // 检查命令行参数数量是否足够
    if (argc < 6) {
        puts(usage_string); // 打印使用说明
        return -1; // 参数不足，退出程序
    }

    // 解析命令行参数
    file_name = argv[1];
    direction = argv[2][0];
    dev_name = argv[3];

    // 解析设备地址和数据大小，如果解析失败则打印使用说明并退出
    if (parse_uint(argv[4], &address) == 0) {
        puts(usage_string);
        return -1;
    }
    if (parse_uint(argv[5], &size) == 0) {
        puts(usage_string);
        return -1;
    }

    // 根据传输方向打印操作信息
    if (direction == 't') {
        printf("从: %s\n至: %s 地址=0x%lx\n大小: 0x%lx\n", file_name, dev_name, address, size);
    } else if (direction == 'f') {
        printf("从: %s 地址=0x%lx\n至: %s\n大小: 0x%lx\n", dev_name, address, file_name, size);
    } else {
        puts(usage_string);
        return -1; // 无效的方向参数
    }

    // 检查数据大小是否合法
    if (size > DMA_MAX_SIZE || size == 0) {
        printf("*** 错误: DMA大小必须大于0且不超过 %lu\n", DMA_MAX_SIZE);
        return -1;
    }

    // 分配缓冲区
    buffer = malloc(size);
    if (buffer == NULL) {
        printf("*** 错误: 分配缓冲区失败\n");
        goto close_and_clear; // 跳转到资源释放部分
    }

    // 打开设备
    dev_fd = open(dev_name, O_RDWR);
    if (dev_fd < 0) {
        printf("*** 错误: 打开设备 %s 失败\n", dev_name);
        goto close_and_clear;
    }

    // 打开文件
    file_p = (direction == 't') ? fopen(file_name, "rb") : fopen(file_name, "wb");
    if (file_p == NULL) {
        printf("*** 错误: 打开文件 %s 失败\n", file_name);
        goto close_and_clear;
    }

    // 记录操作开始时间
    millisecond = get_millisecond();

    // 根据传输方向进行读写操作
    if (direction == 't') { // 写入设备
        if (fread(buffer, 1, size, file_p) != size) {
            printf("*** 错误: 读取文件 %s 失败\n", file_name);
            goto close_and_clear;
        }
        if (dev_write(dev_fd, address, buffer, size)) {
            printf("*** 错误: 写入设备 %s 失败\n", dev_name);
            goto close_and_clear;
        }
    } else { // 读取设备
        if (dev_read(dev_fd, address, buffer, size)) {
            printf("*** 错误: 读取设备 %s 失败\n", dev_name);
            goto close_and_clear;
        }
        if (fwrite(buffer, 1, size, file_p) != size) {
            printf("*** 错误: 写入文件 %s 失败\n", file_name);
            goto close_and_clear;
        }
    }

    // 计算并打印操作耗时和数据传输速率
    millisecond = get_millisecond() - millisecond;
    millisecond = (millisecond > 0) ? millisecond : 1; // 避免除以零错误
    printf("耗时=%lu 毫秒 数据速率=%.1lf KB/s\n", millisecond, (double)size / millisecond / 1024);

    ret = 0; // 如果一切正常，设置返回值为0，表示成功

close_and_clear: // 清理资源标签

    // 释放缓冲区
    if (buffer != NULL)
        free(buffer);

    // 关闭设备文件
    if (dev_fd >= 0)
        close(dev_fd);

    // 关闭文件
    if (file_p != NULL)
        fclose(file_p);

    return ret; // 返回程序执行结果
}
