Add userspace test program
This commit is contained in:
parent
60800cce4b
commit
c50dd84011
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
. script_env
|
||||||
|
setup_toolchain
|
||||||
|
|
||||||
|
make -C src/userspace CROSS_COMPILE=arm-linux- clean
|
||||||
|
make -C src/userspace CROSS_COMPILE=arm-linux- all
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
#
|
||||||
|
# Makefile to build user space software to run on the Red Pitaya.
|
||||||
|
#
|
||||||
|
|
||||||
|
CC = $(CROSS_COMPILE)gcc
|
||||||
|
CFLAGS = -Wall -O2
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: testje
|
||||||
|
|
||||||
|
testje: testje.c
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
$(RM) -f testje
|
||||||
|
|
|
@ -0,0 +1,944 @@
|
||||||
|
/*
|
||||||
|
* testje.c
|
||||||
|
*
|
||||||
|
* Command-line program to test PuzzleFW firmware.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define SOC_DEVICES_DIR "/sys/devices/soc0"
|
||||||
|
#define PUZZLEFW_REGS_SIZE 0x100000
|
||||||
|
|
||||||
|
#define REG_INFO 0x0000
|
||||||
|
#define REG_IRQ_ENABLE 0x0010
|
||||||
|
#define REG_IRQ_PENDING 0x0014
|
||||||
|
#define REG_DMA_EN 0x0100
|
||||||
|
#define REG_DMA_STATUS 0x0104
|
||||||
|
#define REG_DMA_CLEAR 0x0108
|
||||||
|
#define REG_DMA_ADDR_START 0x0200
|
||||||
|
#define REG_DMA_ADDR_END 0x0204
|
||||||
|
#define REG_DMA_ADDR_LIMIT 0x0208
|
||||||
|
#define REG_DMA_ADDR_INTR 0x020c
|
||||||
|
#define REG_DMA_ADDR_PTR 0x0210
|
||||||
|
#define REG_DMA_CHANNEL_CTRL 0x0214
|
||||||
|
#define REG_DMA_INTR_CTRL 0x0218
|
||||||
|
|
||||||
|
|
||||||
|
struct puzzlefw_context {
|
||||||
|
int uio_fd;
|
||||||
|
volatile uint32_t * regs;
|
||||||
|
volatile void * dma_buf;
|
||||||
|
size_t dma_buf_size;
|
||||||
|
size_t dma_transfer_size;
|
||||||
|
char device_name[128];
|
||||||
|
char uio_path[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find PuzzleFW device in /sys filesystem.
|
||||||
|
* Initialize "device_name" attribute of context structure.
|
||||||
|
*
|
||||||
|
* Return 0 on success, -1 on error.
|
||||||
|
*/
|
||||||
|
static int puzzlefw_find_device_name(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
DIR *dirp;
|
||||||
|
struct dirent *dent;
|
||||||
|
|
||||||
|
dirp = opendir(SOC_DEVICES_DIR);
|
||||||
|
if (dirp == NULL) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ERROR: Can not access %s (%s)\n",
|
||||||
|
SOC_DEVICES_DIR, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find entry matching "*.puzzlefw" */
|
||||||
|
while (1) {
|
||||||
|
errno = 0;
|
||||||
|
dent = readdir(dirp);
|
||||||
|
if (dent == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t namelen = strlen(dent->d_name);
|
||||||
|
if (namelen > 9
|
||||||
|
&& strcmp(dent->d_name + namelen - 9, ".puzzlefw") == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dent == NULL && errno != 0) {
|
||||||
|
fprintf(stderr, "ERROR: Can not read %s (%s)\n",
|
||||||
|
SOC_DEVICES_DIR, strerror(errno));
|
||||||
|
closedir(dirp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dirp);
|
||||||
|
|
||||||
|
if (dent == NULL) {
|
||||||
|
fprintf(stderr, "ERROR: *.puzzlefw not found in %s\n",
|
||||||
|
SOC_DEVICES_DIR);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = snprintf(ctx->device_name, sizeof(ctx->device_name),
|
||||||
|
"%s", dent->d_name);
|
||||||
|
if (n >= sizeof(ctx->device_name)) {
|
||||||
|
fprintf(stderr, "ERROR: Device name exceeds maximum length\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find UIO device node for the PuzzleFW driver.
|
||||||
|
* Initialize "uio_path" and "dma_buf_size" attributes of context structure.
|
||||||
|
*
|
||||||
|
* Return 0 on success, -1 on error.
|
||||||
|
*/
|
||||||
|
static int puzzlefw_find_uio(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
DIR *dirp;
|
||||||
|
struct dirent *dent;
|
||||||
|
char path[256];
|
||||||
|
char attrbuf[64];
|
||||||
|
|
||||||
|
int n = snprintf(path, sizeof(path),
|
||||||
|
"%s/%s/uio",
|
||||||
|
SOC_DEVICES_DIR, ctx->device_name);
|
||||||
|
if (n >= sizeof(path)) {
|
||||||
|
fprintf(stderr, "ERROR: Path name exceeds maximum length\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dirp = opendir(path);
|
||||||
|
if (dirp == NULL) {
|
||||||
|
fprintf(stderr, "ERROR: Can not access %s (%s)\n",
|
||||||
|
path, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find entry matching "uio*" */
|
||||||
|
while (1) {
|
||||||
|
errno = 0;
|
||||||
|
dent = readdir(dirp);
|
||||||
|
if (dent == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(dent->d_name) > 3 && strncmp(dent->d_name, "uio", 3) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dent == NULL && errno != 0) {
|
||||||
|
fprintf(stderr, "ERROR: Can not read %s (%s)\n",
|
||||||
|
path, strerror(errno));
|
||||||
|
closedir(dirp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dirp);
|
||||||
|
|
||||||
|
if (dent == NULL) {
|
||||||
|
fprintf(stderr, "ERROR: No UIO node found in %s\n", path);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find name of /dev/uioN node. */
|
||||||
|
n = snprintf(ctx->uio_path, sizeof(ctx->uio_path),
|
||||||
|
"/dev/%s", dent->d_name);
|
||||||
|
if (n >= sizeof(ctx->uio_path)) {
|
||||||
|
fprintf(stderr, "ERROR: Device name exceeds maximum length\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read DMA buffer size. */
|
||||||
|
n = snprintf(path + strlen(path), sizeof(path) - strlen(path),
|
||||||
|
"/%s/maps/map1/size",
|
||||||
|
dent->d_name);
|
||||||
|
if (n >= sizeof(path)) {
|
||||||
|
fprintf(stderr, "ERROR: Path name exceeds maximum length\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = fopen(path, "r");
|
||||||
|
if (f == NULL) {
|
||||||
|
fprintf(stderr, "ERROR: Can not access %s (%s)\n",
|
||||||
|
path, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fgets(attrbuf, sizeof(attrbuf), f) == NULL) {
|
||||||
|
fprintf(stderr, "ERROR: Can not read %s (%s)\n",
|
||||||
|
path, strerror(errno));
|
||||||
|
fclose(f);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
ctx->dma_buf_size = 0;
|
||||||
|
ctx->dma_buf_size = strtoul(attrbuf, NULL, 16);
|
||||||
|
|
||||||
|
if (ctx->dma_buf_size == 0) {
|
||||||
|
fprintf(stderr, "ERROR: Invalid value in %s\n", path);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open PuzzleFW driver.
|
||||||
|
*
|
||||||
|
* Return 0 on success, -1 on error.
|
||||||
|
*/
|
||||||
|
int puzzlefw_open(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
ctx->uio_fd = -1;
|
||||||
|
ctx->regs = NULL;
|
||||||
|
ctx->dma_buf = NULL;
|
||||||
|
ctx->dma_transfer_size = 128;
|
||||||
|
|
||||||
|
if (puzzlefw_find_device_name(ctx) != 0) {
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (puzzlefw_find_uio(ctx) != 0) {
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((ctx->dma_buf_size % ctx->dma_transfer_size) != 0) ||
|
||||||
|
(ctx->dma_buf_size < 8192)) {
|
||||||
|
fprintf(stderr, "ERROR: Invalid DMA buffer size (%zu)\n",
|
||||||
|
ctx->dma_buf_size);
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = open(ctx->uio_path, O_RDWR | O_SYNC);
|
||||||
|
if (fd < 0) {
|
||||||
|
fprintf(stderr, "ERROR: Can not open %s (%s)\n",
|
||||||
|
ctx->uio_path, strerror(errno));
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Map FPGA user registers.
|
||||||
|
* Offset 0 tells the UIO to map the first address range,
|
||||||
|
* which corresponds to the registers.
|
||||||
|
*/
|
||||||
|
void *regs = mmap(NULL,
|
||||||
|
PUZZLEFW_REGS_SIZE,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_SHARED,
|
||||||
|
fd,
|
||||||
|
0);
|
||||||
|
if (regs == ((void*)-1)) {
|
||||||
|
fprintf(stderr, "ERROR: Can not mmap %s (%s)\n",
|
||||||
|
ctx->uio_path, strerror(errno));
|
||||||
|
goto err_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Map DMA buffer.
|
||||||
|
* Offset PAGE_SIZE tells the UIO to map the second address range,
|
||||||
|
* which corresponds to the DMA buffer.
|
||||||
|
*/
|
||||||
|
int page_size = getpagesize();
|
||||||
|
void *dma_buf = mmap(NULL,
|
||||||
|
ctx->dma_buf_size,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_SHARED,
|
||||||
|
fd,
|
||||||
|
1 * page_size);
|
||||||
|
if (dma_buf == ((void*)-1)) {
|
||||||
|
fprintf(stderr, "ERROR: Can not mmap %s (%s)\n",
|
||||||
|
ctx->uio_path, strerror(errno));
|
||||||
|
goto err_unmap_regs;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->uio_fd = fd;
|
||||||
|
ctx->regs = regs;
|
||||||
|
ctx->dma_buf = dma_buf;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_unmap_regs:
|
||||||
|
munmap(regs, PUZZLEFW_REGS_SIZE);
|
||||||
|
err_close:
|
||||||
|
close(fd);
|
||||||
|
err_out:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Close PuzzleFW driver. */
|
||||||
|
void puzzlefw_close(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
if (ctx->uio_fd < 0 || ctx->regs == NULL || ctx->dma_buf == NULL) {
|
||||||
|
fprintf(stderr, "ERROR: Driver not open\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (munmap((void*)ctx->regs, PUZZLEFW_REGS_SIZE) < 0) {
|
||||||
|
fprintf(stderr, "ERROR: Can not unmap registers (%s)\n",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (munmap((void*)ctx->dma_buf, ctx->dma_buf_size) < 0) {
|
||||||
|
fprintf(stderr, "ERROR: Can not unmap DMA buffer (%s)\n",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(ctx->uio_fd) < 0) {
|
||||||
|
fprintf(stderr, "ERROR: Can not close %s (%s)\n",
|
||||||
|
ctx->uio_path, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->uio_fd = -1;
|
||||||
|
ctx->regs = NULL;
|
||||||
|
ctx->dma_buf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Read from FPGA register. */
|
||||||
|
static inline uint32_t puzzlefw_read_reg(
|
||||||
|
struct puzzlefw_context *ctx,
|
||||||
|
unsigned long addr)
|
||||||
|
{
|
||||||
|
return ctx->regs[addr / 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Write to FPGA register. */
|
||||||
|
static inline void puzzlefw_write_reg(
|
||||||
|
struct puzzlefw_context *ctx,
|
||||||
|
unsigned long addr,
|
||||||
|
uint32_t v)
|
||||||
|
{
|
||||||
|
ctx->regs[addr / 4] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Synchronize between register access and DMA buffer access.
|
||||||
|
*
|
||||||
|
* After reading from an FPGA register that indicates a DMA write
|
||||||
|
* operation has completed, call this function before reading from
|
||||||
|
* the DMA buffer.
|
||||||
|
*
|
||||||
|
* After writing to the DMA buffer, call this function before
|
||||||
|
* writing to the FPGA register that initiates a DMA read operation.
|
||||||
|
*/
|
||||||
|
static inline void puzzlefw_sync_dma(void)
|
||||||
|
{
|
||||||
|
asm volatile ("dsb" : : : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy data from uncached memory to normal memory.
|
||||||
|
*
|
||||||
|
* This function only supports copying multiples of 8 bytes,
|
||||||
|
* with source and destination pointers both 8-byte aligned.
|
||||||
|
*
|
||||||
|
* For large data blocks, this function copies ~ 230 MiB/s
|
||||||
|
* on Zynq-7000 with ARM running at 667 MHz and DDR running at 533 MHz.
|
||||||
|
*/
|
||||||
|
void puzzlefw_copy_from_dma(void *dst, volatile void *src, size_t len)
|
||||||
|
{
|
||||||
|
if (len < 256) {
|
||||||
|
|
||||||
|
memcpy(dst, (void*)src, len);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len / 64; i++) {
|
||||||
|
asm volatile (
|
||||||
|
"vldm %0!, {d0-d7} \n"
|
||||||
|
"vstm %1!, {d0-d7} \n"
|
||||||
|
: "+r" (src), "+r" (dst)
|
||||||
|
: : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "memory"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len % 64 > 0) {
|
||||||
|
memcpy(dst, (void*)src, len % 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void show_status(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
uint32_t v;
|
||||||
|
printf("PuzzleFW registers:\n");
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_INFO);
|
||||||
|
printf(" info = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_IRQ_ENABLE);
|
||||||
|
printf(" irq_enable = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_IRQ_PENDING);
|
||||||
|
printf(" irq_pending = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_EN);
|
||||||
|
printf(" dma_en = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_STATUS);
|
||||||
|
printf(" dma_status = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_START);
|
||||||
|
printf(" dma_addr_start = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_END);
|
||||||
|
printf(" dma_addr_end = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_LIMIT);
|
||||||
|
printf(" dma_addr_limit = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_INTR);
|
||||||
|
printf(" dma_addr_intr = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_PTR);
|
||||||
|
printf(" dma_addr_ptr = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_CHANNEL_CTRL);
|
||||||
|
printf(" dma_channel_ctrl = 0x%08x\n", v);
|
||||||
|
|
||||||
|
v = puzzlefw_read_reg(ctx, REG_DMA_INTR_CTRL);
|
||||||
|
printf(" dma_intr_ctrl = 0x%08x\n", v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void show_buf(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
printf("DMA buffer:\n");
|
||||||
|
for (int i = 0; i < 260; i++) {
|
||||||
|
unsigned int p = i * 32;
|
||||||
|
uint32_t b[8];
|
||||||
|
memcpy(b, ctx->dma_buf + p, 32);
|
||||||
|
printf("%04x: %08x %08x %08x %08x %08x %08x %08x %08x\n",
|
||||||
|
p, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void gen_data(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 2080; i++) {
|
||||||
|
uint32_t v = (i & 0xffff) | (1 << (16 + i % 16));
|
||||||
|
memcpy(ctx->dma_buf + 4 * i, &v, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static void wait_irq(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
printf("Waiting for IRQ ...\n");
|
||||||
|
while (1) {
|
||||||
|
puzzlefw_write_reg(ctx, REG_IRQ_ENABLE, 1);
|
||||||
|
|
||||||
|
uint32_t v;
|
||||||
|
ssize_t k = read(ctx->uio_fd, &v, sizeof(v));
|
||||||
|
if (k != 4) {
|
||||||
|
fprintf(stderr, "ERROR: Can not read from UIO (%s)\n",
|
||||||
|
strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("got %x interrupts\n", v);
|
||||||
|
puzzlefw_write_reg(ctx, REG_TEST_IRQ, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static void blast_dma(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
printf("Starting DMA blaster ...\n");
|
||||||
|
|
||||||
|
// Disable DMA writer.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 0);
|
||||||
|
|
||||||
|
// Setup DMA buffer.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_ADDR_START, 0);
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_ADDR_END, ctx->dma_buf_size);
|
||||||
|
|
||||||
|
// Set invalid limit to keep the writer blasting.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_ADDR_LIMIT, 0xffffffff);
|
||||||
|
|
||||||
|
// Initialize and enable DMA writer.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 3);
|
||||||
|
|
||||||
|
struct timespec tp;
|
||||||
|
tp.tv_sec = 10;
|
||||||
|
tp.tv_nsec = 0;
|
||||||
|
clock_nanosleep(CLOCK_MONOTONIC, 0, &tp, NULL);
|
||||||
|
|
||||||
|
// Disable DMA writer.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 0);
|
||||||
|
|
||||||
|
printf("Stopped DMA blaster\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send all specified data to the socket.
|
||||||
|
*
|
||||||
|
* The socket must be in blocking mode.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* 0 if all data were written;
|
||||||
|
* 2 if the connection was closed;
|
||||||
|
* -1 if another error occurs.
|
||||||
|
*/
|
||||||
|
static int send_all(int conn, const void *buf, size_t len)
|
||||||
|
{
|
||||||
|
while (len > 0) {
|
||||||
|
ssize_t ret = send(conn, buf, len, MSG_NOSIGNAL);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (errno == EPIPE || errno == ECONNRESET) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "ERROR: send failed (%s)\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf += ret;
|
||||||
|
len -= ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait until the specified number of bytes are available in the DMA buffer,
|
||||||
|
* or the specified timeout expires,
|
||||||
|
* or the specified socket connection is closed.
|
||||||
|
*
|
||||||
|
* This function assumes that the DMA write channel is the only entity that
|
||||||
|
* triggers PuzzleFW interrupts. It also assumes that no other thread is
|
||||||
|
* interacting with PuzzleFW interrupts.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* ctx: Device context.
|
||||||
|
* conn: Socket file descriptor.
|
||||||
|
* read_pointer: Read pointer within the DMA buffer.
|
||||||
|
* wait_avail: Number of bytes to wait for.
|
||||||
|
* This must be a multiple of the DMA transfer size.
|
||||||
|
* timeout_ms: Timeout in milliseconds.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* 0 if the specified number of bytes is available;
|
||||||
|
* 1 if timeout occurred;
|
||||||
|
* 2 if the connection was closed;
|
||||||
|
* -1 if a system error occurred.
|
||||||
|
*
|
||||||
|
* This function may sometimes return 0 when no data is available. This can
|
||||||
|
* happen due to a stale pending interrupt.
|
||||||
|
*/
|
||||||
|
static int wait_dma_data(
|
||||||
|
struct puzzlefw_context *ctx,
|
||||||
|
int conn,
|
||||||
|
uint32_t read_pointer,
|
||||||
|
uint32_t wait_avail,
|
||||||
|
int timeout_ms)
|
||||||
|
{
|
||||||
|
assert(wait_avail > 0);
|
||||||
|
assert(wait_avail % ctx->dma_transfer_size == 0);
|
||||||
|
|
||||||
|
// Enable DMA writer interrupt and clear pending interrupts.
|
||||||
|
uint32_t addr_intr = read_pointer + wait_avail;
|
||||||
|
if (addr_intr >= ctx->dma_buf_size) {
|
||||||
|
addr_intr -= ctx->dma_buf_size;
|
||||||
|
}
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_ADDR_INTR, addr_intr);
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 3);
|
||||||
|
|
||||||
|
// Check if data are already available.
|
||||||
|
// This is necessary to prevent a race condition when data becomes
|
||||||
|
// available just before the interrupt is enabled.
|
||||||
|
uint32_t write_pointer = puzzlefw_read_reg(ctx, REG_DMA_ADDR_PTR);
|
||||||
|
uint32_t navail =
|
||||||
|
(write_pointer >= read_pointer) ?
|
||||||
|
(write_pointer - read_pointer) :
|
||||||
|
(ctx->dma_buf_size + write_pointer - read_pointer);
|
||||||
|
if (navail >= wait_avail) {
|
||||||
|
// Data already available; disable DMA writer interrupts.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable interrupts from FPGA.
|
||||||
|
puzzlefw_write_reg(ctx, REG_IRQ_ENABLE, 1);
|
||||||
|
|
||||||
|
// Sleep until interrupt or timeout or connection closed.
|
||||||
|
struct pollfd fds[2];
|
||||||
|
fds[0].fd = ctx->uio_fd;
|
||||||
|
fds[0].events = POLLIN;
|
||||||
|
fds[1].fd = conn;
|
||||||
|
fds[1].events = 0;
|
||||||
|
|
||||||
|
int ret = poll(fds, 2, timeout_ms);
|
||||||
|
if (ret < 0) {
|
||||||
|
fprintf(stderr, "ERROR: poll failed (%s)\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable DMA writer interrupt and clear pending interrupt.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 2);
|
||||||
|
|
||||||
|
if ((fds[0].revents & POLLIN) != 0) {
|
||||||
|
// Interrupt occurred.
|
||||||
|
int32_t intr_count;
|
||||||
|
read(ctx->uio_fd, &intr_count, sizeof(intr_count));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fds[1].revents & POLLHUP) != 0) {
|
||||||
|
// Connection closed.
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably timeout then.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send data from DMA to TCP socket.
|
||||||
|
*
|
||||||
|
* Keep running until the TCP connection is closed.
|
||||||
|
*/
|
||||||
|
int transmit_dma_data(struct puzzlefw_context *ctx, int conn)
|
||||||
|
{
|
||||||
|
// Maximum block size per TCP send() call.
|
||||||
|
const uint32_t send_max_block = 65536;
|
||||||
|
|
||||||
|
// When buffer is empty, sleep until this amount becomes available.
|
||||||
|
const uint32_t wait_block_size = 4096;
|
||||||
|
|
||||||
|
// Reserve this number of bytes in the buffer to avoid ambiguous pointers.
|
||||||
|
const uint32_t pointer_margin = 1024;
|
||||||
|
|
||||||
|
// When buffer is empty, sleep at most this duration.
|
||||||
|
const int timeout_ms = 10;
|
||||||
|
|
||||||
|
assert(ctx->dma_buf_size >= 2 * wait_block_size);
|
||||||
|
assert(send_max_block % ctx->dma_transfer_size == 0);
|
||||||
|
assert(wait_block_size % ctx->dma_transfer_size == 0);
|
||||||
|
assert(pointer_margin % ctx->dma_transfer_size == 0);
|
||||||
|
|
||||||
|
// Disable AXI DMA.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_EN, 0);
|
||||||
|
|
||||||
|
// Disable DMA write channel.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 0);
|
||||||
|
|
||||||
|
// Initialize DMA write buffer.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_ADDR_START, 0);
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_ADDR_END, ctx->dma_buf_size);
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_ADDR_LIMIT,
|
||||||
|
ctx->dma_buf_size - pointer_margin);
|
||||||
|
|
||||||
|
// Disable DMA writer interrupts; clear interrupt status.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 2);
|
||||||
|
|
||||||
|
// Clear AXI DMA state.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_CLEAR, 1);
|
||||||
|
|
||||||
|
// Enable AXI DMA.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_EN, 1);
|
||||||
|
|
||||||
|
// Initialize and enable DMA writer.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 3);
|
||||||
|
|
||||||
|
uint32_t read_pointer = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
// Check DMA status.
|
||||||
|
uint32_t status = puzzlefw_read_reg(ctx, REG_DMA_STATUS);
|
||||||
|
if ((status & 0x1e) != 0) {
|
||||||
|
// DMA error.
|
||||||
|
fprintf(stderr, "ERROR: DMA error, status=0x%08x\n", status);
|
||||||
|
ret = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for data in buffer.
|
||||||
|
uint32_t write_pointer = puzzlefw_read_reg(ctx, REG_DMA_ADDR_PTR);
|
||||||
|
|
||||||
|
if (write_pointer == read_pointer) {
|
||||||
|
|
||||||
|
// Wait for new data.
|
||||||
|
ret = wait_dma_data(ctx, conn,
|
||||||
|
read_pointer,
|
||||||
|
wait_block_size,
|
||||||
|
timeout_ms);
|
||||||
|
if (ret < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ret == 2) {
|
||||||
|
// Connection closed.
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Determine number of bytes available.
|
||||||
|
// If the write pointer wrapped around the end of the buffer,
|
||||||
|
// stop at the end of the buffer and process the rest in the next
|
||||||
|
// pass through the loop.
|
||||||
|
uint32_t navail =
|
||||||
|
(write_pointer >= read_pointer) ?
|
||||||
|
(write_pointer - read_pointer) :
|
||||||
|
(ctx->dma_buf_size - read_pointer);
|
||||||
|
|
||||||
|
// Make sure the CPU view of the DMA buffer is up to date
|
||||||
|
// relative to the state previously reported via registers.
|
||||||
|
puzzlefw_sync_dma();
|
||||||
|
|
||||||
|
// Transmit available data in blocks of 64 kB.
|
||||||
|
while (navail > 0) {
|
||||||
|
uint32_t block_size =
|
||||||
|
(navail < send_max_block) ? navail : send_max_block;
|
||||||
|
|
||||||
|
// Send data to socket.
|
||||||
|
ret = send_all(conn,
|
||||||
|
(void*)ctx->dma_buf + read_pointer, block_size);
|
||||||
|
if (ret != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update read pointer and update DMA writer limit.
|
||||||
|
read_pointer += block_size;
|
||||||
|
if (read_pointer > pointer_margin) {
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_ADDR_LIMIT,
|
||||||
|
read_pointer - pointer_margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
navail -= block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 2) {
|
||||||
|
// Connection closed.
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_pointer == ctx->dma_buf_size) {
|
||||||
|
read_pointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable AXI DMA.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_EN, 0);
|
||||||
|
|
||||||
|
// Disable DMA write channel.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 0);
|
||||||
|
|
||||||
|
// Disable DMA writer interrupts; clear interrupt status.
|
||||||
|
puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 2);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run TCP server.
|
||||||
|
*/
|
||||||
|
int run_server(struct puzzlefw_context *ctx)
|
||||||
|
{
|
||||||
|
const int tcp_port = 5001;
|
||||||
|
|
||||||
|
// Create server socket.
|
||||||
|
int srv_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (srv_sock < 0) {
|
||||||
|
fprintf(stderr, "ERROR: socket (%s)\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int v = 1;
|
||||||
|
setsockopt(srv_sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v));
|
||||||
|
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(tcp_port);
|
||||||
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
|
||||||
|
if (bind(srv_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||||
|
fprintf(stderr, "ERROR: bind (%s)\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(srv_sock, 1) < 0) {
|
||||||
|
fprintf(stderr, "ERROR: listen (%s)\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Waiting for connection to port %d ...\n", tcp_port);
|
||||||
|
|
||||||
|
int conn = accept(srv_sock, NULL, NULL);
|
||||||
|
if (conn < 0) {
|
||||||
|
fprintf(stderr, "ERROR: accept (%s)\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Connected, streaming data ...\n");
|
||||||
|
|
||||||
|
close(srv_sock);
|
||||||
|
|
||||||
|
int ret = transmit_dma_data(ctx, conn);
|
||||||
|
|
||||||
|
close(conn);
|
||||||
|
|
||||||
|
printf("Connection closed\n");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct puzzlefw_context ctx;
|
||||||
|
int show = 0;
|
||||||
|
int showbuf = 0;
|
||||||
|
int gendata = 0;
|
||||||
|
int dmaon = 0;
|
||||||
|
int dmaoff = 0;
|
||||||
|
int dmaclear = 0;
|
||||||
|
int blastdma = 0;
|
||||||
|
int server = 0;
|
||||||
|
|
||||||
|
if (argc == 2 && strcmp(argv[1], "show") == 0) {
|
||||||
|
show = 1;
|
||||||
|
} else if (argc == 2 && strcmp(argv[1], "showbuf") == 0) {
|
||||||
|
showbuf = 1;
|
||||||
|
} else if (argc == 2 && strcmp(argv[1], "gendata") == 0) {
|
||||||
|
gendata = 1;
|
||||||
|
} else if (argc == 2 && strcmp(argv[1], "dmaon") == 0) {
|
||||||
|
dmaon = 1;
|
||||||
|
} else if (argc == 2 && strcmp(argv[1], "dmaoff") == 0) {
|
||||||
|
dmaoff = 1;
|
||||||
|
} else if (argc == 2 && strcmp(argv[1], "dmaclear") == 0) {
|
||||||
|
dmaclear = 1;
|
||||||
|
} else if (argc == 2 && strcmp(argv[1], "blastdma") == 0) {
|
||||||
|
blastdma = 1;
|
||||||
|
} else if (argc == 2 && strcmp(argv[1], "server") == 0) {
|
||||||
|
server = 1;
|
||||||
|
} else {
|
||||||
|
printf(
|
||||||
|
"Usage:\n"
|
||||||
|
" testje show\n"
|
||||||
|
" Show current register values.\n"
|
||||||
|
"\n"
|
||||||
|
" testje showbuf\n"
|
||||||
|
" Show current register values and part of buffer.\n"
|
||||||
|
"\n"
|
||||||
|
" testje gendata\n"
|
||||||
|
" Write test pattern to DMA buffer.\n"
|
||||||
|
"\n"
|
||||||
|
" testje dmaon\n"
|
||||||
|
" Enable DMA.\n"
|
||||||
|
"\n"
|
||||||
|
" testje dmaoff\n"
|
||||||
|
" Disable DMA.\n"
|
||||||
|
"\n"
|
||||||
|
" testje dmaclear\n"
|
||||||
|
" Clear DMA errors.\n"
|
||||||
|
"\n"
|
||||||
|
" testje blastdma\n"
|
||||||
|
" Run unlimited DMA into buffer for 10 seconds.\n"
|
||||||
|
"\n"
|
||||||
|
" testje server\n"
|
||||||
|
" Open TCP port 5001 to stream DMA data.\n"
|
||||||
|
"\n");
|
||||||
|
if (argc > 1) {
|
||||||
|
fprintf(stderr, "ERROR: Invalid command\n");
|
||||||
|
exit(1);
|
||||||
|
} else {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (puzzlefw_open(&ctx) != 0) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show || showbuf) {
|
||||||
|
show_status(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showbuf) {
|
||||||
|
show_buf(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gendata) {
|
||||||
|
gen_data(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmaon) {
|
||||||
|
puzzlefw_write_reg(&ctx, REG_DMA_EN, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmaoff) {
|
||||||
|
puzzlefw_write_reg(&ctx, REG_DMA_EN, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmaclear) {
|
||||||
|
puzzlefw_write_reg(&ctx, REG_DMA_CLEAR, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blastdma) {
|
||||||
|
blast_dma(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server) {
|
||||||
|
run_server(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
puzzlefw_close(&ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end */
|
Loading…
Reference in New Issue