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