| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 | /* * Handle ring buffer link between ESP32 and FPGA * This implements the ESP32 (upstream/active/initiator/master) side. */#define MODULE "esplink"#include "common.h"#include "esplink.h"#include "fpga.h"#include "boardinfo_esp.h"struct esplink_ringbuf_ptrs {    const volatile struct esplink_ptrs_dstr *d;    volatile struct esplink_ptrs_ustr *u;};struct esplink_sem {    SemaphoreHandle_t lock;};/* * Event group indicating which ring buffers are ready for I/O */EventGroupHandle_t esplink_filled;	/* Data requested in "fill" ready */static struct {    volatile unsigned int ready;    bool shutdown;    SemaphoreHandle_t mutex;	/* Configuration mutex */    struct esplink_ringbuf_ptrs rb;    struct esplink_ringbuf_desc *desc;    struct esplink_ringbuf_head head;    struct esplink_sem sem[EL_RB_COUNT][2];    size_t need[EL_RB_COUNT][2]; /* Amount of data requested for wakeup */} elink;/* Leave at least this much space, to preserve alignment */#define RINGBUF_UNUSABLE	4#define ELQUEUE_ALL_MASK	((1UL << (EL_RB_COUNT*2)) - 1)static void esplink_stop(void){    elink.ready = 0;    /* No waking up waiters that only want online wakeups */    xEventGroupClearBits(esplink_filled, ELWAIT_ONLINE);    /* Wake up all the internal waiters or others with online = false */    xEventGroupSetBits(esplink_filled, ELQUEUE_ALL_MASK);    struct esplink_sem *s = &elink.sem[0][0];    for (size_t i = 0; i < EL_RB_COUNT*2; i++) {	xSemaphoreTake(s->lock, portMAX_DELAY);	s++;    }}/* This must be called and completed before any other esplink functions! */void esplink_init(void){    elink.mutex    = null_check(xSemaphoreCreateMutex());    esplink_filled = null_check(xEventGroupCreate());    elink.desc = xmalloc_dma(EL_RB_COUNT * sizeof *elink.desc);    elink.rb.u = xmalloc_dma(EL_RB_COUNT * sizeof *elink.rb.u);    elink.rb.d = xmalloc_dma(EL_RB_COUNT * sizeof *elink.rb.d);    struct esplink_sem *s = &elink.sem[0][0];    for (size_t i = 0; i < EL_RB_COUNT*2; i++) {	s->lock = null_check(xSemaphoreCreateBinary());	s++;    }    xSemaphoreGive(elink.mutex);}/* * This needs to be called from the FPGA service thread once before * any esplink functions can be called from any other thread. After that, * those functions are safe to call (but may fail) regardless of * shutdown and/or reinitialization of the link. * * Call this function with head == NULL to either shut the link down * or to initialize the data structures (see above) */void esplink_start(const struct esplink_head *head){    struct fpga_iov iov[4];    static unsigned int gen_count;    xSemaphoreTake(elink.mutex, portMAX_DELAY);    if (elink.ready)	esplink_stop();    if (!head)	goto shutdown;    /* At this point elink.mutex and all the ->lock mutexes are held */    elink.head       = head->rb;    elink.head.count = Min(head->rb.count, EL_RB_COUNT);    size_t desc_size = sizeof(*elink.desc) * elink.head.count;    size_t dptr_size = sizeof(*elink.rb.d) * elink.head.count;    /* Set wakeup thresholds */    for (size_t i = 0; i < elink.head.count; i++) {	elink.need[i][0] = RINGBUF_UNUSABLE + 1;	elink.need[i][1] = 1;    }    iov[0].cmd   = FPGA_CMD_RD;    iov[0].addr  = elink.head.desc;    iov[0].rdata = (void *)&elink.desc;    iov[0].len   = desc_size;    iov[1].cmd   = FPGA_CMD_RD | FPGA_CMD_ACK(EL_UIRQ_RINGBUF);    iov[1].addr  = elink.head.dstr;    iov[1].rdata = (void *)elink.rb.d;    iov[1].len   = dptr_size;    /* Write back the same pointer values -> all buffers currently empty */    iov[2].cmd   = FPGA_CMD_WR | FPGA_CMD_IRQ(EL_DIRQ_RINGBUF);    iov[2].addr  = elink.head.ustr;    iov[2].wdata = (void *)elink.rb.d; /* rb.d is correct */    iov[2].len   = dptr_size;    /* Update board_info on the FPGA */    iov[3].cmd   = FPGA_CMD_WR | FPGA_CMD_IRQ(EL_DIRQ_BOARDINFO);    iov[3].addr  = head->board_info;    iov[3].wdata = &board_info;    iov[3].len   = sizeof board_info;    fpga_iov(iov, ARRAY_SIZE(iov));    memcpy((void *)elink.rb.u, (void *)elink.rb.d, dptr_size);    elink.ready = ++gen_count;    xEventGroupClearBits(esplink_filled, ELQUEUE_ALL_MASK);    struct esplink_sem *s = &elink.sem[0][0];    for (size_t i = 0; i < EL_RB_COUNT*2; i++) {	xSemaphoreGive(s->lock);	s++;    }    xEventGroupSetBits(esplink_filled, ELWAIT_ONLINE);shutdown:    xSemaphoreGive(elink.mutex);}/* * Called from the FPGA service thread when a ring buffer * interrupt is received */void esplink_poll(void){    if (!elink.ready)	return;    xSemaphoreTake(elink.mutex, portMAX_DELAY);    if (elink.ready) {	const size_t count     = elink.head.count;	const size_t dptr_size = sizeof(*elink.rb.d) * count;	struct fpga_iov iov[1];	iov[0].cmd   = FPGA_CMD_RD | FPGA_CMD_ACK(EL_UIRQ_RINGBUF);	iov[0].addr  = elink.head.dstr;	iov[0].rdata = (void *)elink.rb.d;	iov[0].len   = dptr_size;	fpga_iov(iov, 1);	EventBits_t wakeup = 0;	EventBits_t tbit = 1;	for (size_t i = 0; i < count; i++) {	    size_t need;	    need = atomic(elink.need[i][0]);	    if (((elink.rb.d[i].tail - atomic(elink.rb.u[i].head) - 1) &		 (elink.desc[i].dstr.size-1)) >= need)		wakeup |= tbit;	    tbit <<= 1;	    need = atomic(elink.need[i][1]);	    if (((elink.rb.d[i].head - atomic(elink.rb.u[i].tail)) &		 (elink.desc[i].ustr.size-1)) >= need)		wakeup |= tbit;	    tbit <<= 1;	}	xEventGroupSetBits(esplink_filled, wakeup);    }    xSemaphoreGive(elink.mutex);}/* ------------------------------------------------------------------------- * * Functions that can be called from a non-service thread. * ------------------------------------------------------------------------- *//* * Write/read data to/from a ring buffer. Block if necessary until at least * <mintx>/<minrx> bytes have been send/received, otherwise return. * * Returns the number of bytes send/received. * * If <atomic> is set, only advance the head/tail pointer after the * whole transaction has been performed (must be no larger than half * the ring buffer size.) If <mintx>/<minrx> < <len> and the return * value is less than <len>, then the pointer will NOT have been advanced * and the transaction was aborted. A new call will restart from the * previous pointer location. * * The wakeup "need" value in esplink_wait_for() is set appropriately * for a transaction the same size, depending on if the <atomic> flag * was set or not. Thus, for an atomic transaction, if a shorter * atomic or non-atomic transaction is then desired, it may be * necessary to issue it without waiting for esplink_wait_for(). */size_t esplink_write(enum esplink_ringbuf_user ring, const void *data,		     size_t len, size_t mintx, bool atomic){    const size_t unusable = RINGBUF_UNUSABLE;    size_t tx = 0;    if (!len || ring >= EL_RB_COUNT || !elink.ready)	return tx;    mintx = Min(mintx, len);    const char *p = data;    struct esplink_sem *sem = &elink.sem[ring][0];    xSemaphoreTake(sem->lock, portMAX_DELAY);    const unsigned int ready_gen = elink.ready;    if (unlikely(!ready_gen || ring >= elink.head.count))	goto bail;    const struct esplink_ringbuf * const desc = &elink.head.desc[ring].dstr;    const size_t size = desc->size;    if (unlikely(atomic && len > (size >> 1)))	goto bail;    size_t * const hptr = (size_t *)&elink.rb.u[ring].head;    const volatile size_t * const tptr = &elink.rb.d[ring].tail;    size_t head       = *hptr;    const size_t need = (atomic ? len : 1) + unusable;    atomic(elink.need[ring][0]) = need; /* Minimum wakeup */    char * const start = desc->start;    while (elink.ready == ready_gen) {	xEventGroupClearBits(esplink_filled, ELQUEUE_DL(ring));	const size_t tail = *tptr;	size_t space      = (tail-head) & (size-1);	if (!len) {	    if (space >= need)		xEventGroupSetBits(esplink_filled, ELQUEUE_DL(ring));	    break;	}	if (space < need) {	    if (tx >= mintx)		break;	    esplink_wait_for(ELQUEUE_DL(ring), false);	    continue;	}	size_t chunk  = Min(space - unusable, len);	struct fpga_iov iov[4], *iv = iov;	while (chunk) {	    iv->cmd   = FPGA_CMD_WR;	    iv->addr  = start + head;	    iv->wdata = p;	    iv->len   = Min(chunk, size - head);	    p     += iv->len;	    tx    += iv->len;	    len   -= iv->len;	    chunk -= iv->len;	    head   = (head+iv->len) & (size-1);	    iv++;	}	if (!len || !atomic) {	    /* Commit the data to the ring buffer */	    elink.rb.u[ring].head = head;	    iv->cmd   = FPGA_CMD_WR | FPGA_CMD_IRQ(EL_DIRQ_RINGBUF);	    iv->addr  = &elink.head.dstr[ring].head;	    iv->wdata = hptr;	    iv->len   = sizeof *hptr;	    iv++;	}	/* Optimistically poll for tail pointer advance */	iv->cmd   = FPGA_CMD_RD;	iv->addr  = &elink.head.dstr[ring].tail;	iv->rdata = (void *)tptr;	iv->len   = sizeof *tptr;	iv++;	fpga_iov(iov, iv - iov);    }bail:    xSemaphoreGive(sem->lock);    return tx;}size_t esplink_read(enum esplink_ringbuf_user ring, void *data,		    size_t len, size_t minrx, bool atomic){    size_t rx = 0;    if (!len || ring >= EL_RB_COUNT || !elink.ready)	return rx;    minrx = Min(minrx, len);    char *p = data;    struct esplink_sem *sem = &elink.sem[ring][1];    xSemaphoreTake(sem->lock, portMAX_DELAY);    const unsigned int ready_gen = elink.ready;    if (unlikely(!ready_gen || ring >= elink.head.count))	goto bail;    const struct esplink_ringbuf * const desc = &elink.head.desc[ring].ustr;    const size_t size = desc->size;    if (unlikely(atomic && len > (size >> 1)))	goto bail;    size_t * const tptr = (size_t *)&elink.rb.u[ring].tail;    const volatile size_t * const hptr = &elink.rb.d[ring].head;    size_t tail       = *tptr;    const size_t need = atomic ? len : 1;    atomic(elink.need[ring][1]) = need; /* Minimum wakeup */    char * const start  = desc->start;    while (elink.ready == ready_gen) {	xEventGroupClearBits(esplink_filled, ELQUEUE_UL(ring));	const size_t head = *hptr;	size_t avail      = (head-tail) & (size-1);	if (!len) {	    if (avail >= need)		xEventGroupSetBits(esplink_filled, ELQUEUE_UL(ring));	    break;	}	if (avail < need) {	    if (rx >= minrx)		break;	    esplink_wait_for(ELQUEUE_UL(ring), false);	    continue;	}	size_t chunk  = Min(avail, len);	struct fpga_iov iov[4], *iv = iov;	while (chunk) {	    iv->cmd   = FPGA_CMD_RD;	    iv->addr  = start + tail;	    iv->rdata = p;	    iv->len   = Min(chunk, size - tail);	    p        += iv->len;	    rx       += iv->len;	    len      -= iv->len;	    chunk    -= iv->len;	    tail      = (tail+iv->len) & (size-1);	    iv++;	}	if (!len || !atomic) {	    /* Consume the data from the ring buffer */	    elink.rb.u[ring].tail = tail;	    iv->cmd   = FPGA_CMD_WR | FPGA_CMD_IRQ(EL_DIRQ_RINGBUF);	    iv->addr  = &elink.head.dstr[ring].tail;	    iv->wdata = tptr;	    iv->len   = sizeof *tptr;	    iv++;	}	/* Optimistically poll for head pointer advance */	iv->cmd   = FPGA_CMD_RD;	iv->addr  = &elink.head.dstr[ring].head;	iv->rdata = (void *)hptr;	iv->len   = sizeof *hptr;	iv++;	fpga_iov(iov, iv - iov);    }bail:    xSemaphoreGive(sem->lock);    return rx;}
 |