diff --git a/firmware/ice40-riscv/e1-tracer/.gitignore b/firmware/ice40-riscv/e1-tracer/.gitignore new file mode 100644 index 0000000..5d5607c --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/.gitignore @@ -0,0 +1,5 @@ +*.elf +*.hex +*.bin +*.o +*.gen.h diff --git a/firmware/ice40-riscv/e1-tracer/Makefile b/firmware/ice40-riscv/e1-tracer/Makefile new file mode 100644 index 0000000..b190478 --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/Makefile @@ -0,0 +1,82 @@ +#ice1usb +#ice1usb-proto-icebreaker +#ice1usb-proto-bitsty +#e1-tracer +BOARD ?= e1-tracer +CROSS ?= riscv-none-embed- +CC = $(CROSS)gcc +OBJCOPY = $(CROSS)objcopy +ICEPROG = iceprog +DFU_UTIL = dfu-util + +BOARD_DEFINE=BOARD_$(shell echo $(BOARD) | tr a-z\- A-Z_) +CFLAGS=-Wall -Os -march=rv32i -mabi=ilp32 -ffreestanding -flto -nostartfiles -fomit-frame-pointer -Wl,--gc-section --specs=nano.specs -D$(BOARD_DEFINE) -I. -I../common + +NO2USB_FW_VERSION=0 +include ../../../gateware/cores/no2usb/fw/fw.mk +CFLAGS += $(INC_no2usb) + +LNK=../common/lnk-app.lds + +HEADERS_common := $(addprefix ../common/, \ + console.h \ + dma.h \ + led.h \ + mini-printf.h \ + spi.h \ + utils.h \ +) + +SOURCES_common := $(addprefix ../common/, \ + ../common/start.S \ + console.c \ + dma.c \ + led.c \ + mini-printf.c \ + spi.c \ + utils.c \ +) + +HEADERS_common += $(HEADERS_no2usb) +SOURCES_common += $(SOURCES_no2usb) + +HEADERS_app=\ + config.h \ + e1.h \ + misc.h \ + usb_str_app.gen.h \ + $(NULL) + +SOURCES_app=\ + e1.c \ + fw_app.c \ + misc.c \ + usb_desc_app.c \ + usb_e1.c \ + $(NULL) + + +all: fw_app.bin + + +fw_app.elf: $(LNK) $(HEADERS_app) $(SOURCES_app) $(HEADERS_common) $(SOURCES_common) + $(CC) $(CFLAGS) -Wl,-Bstatic,-T,$(LNK),--strip-debug -o $@ $(SOURCES_common) $(SOURCES_app) + + +%.hex: %.bin + ../common/bin2hex.py $< $@ + +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +prog: fw_app.bin + $(ICEPROG) -o 640k $< + +dfuprog: fw_app.bin + $(DFU_UTIL) -R -a 1 -D $< + + +clean: + rm -f *.bin *.hex *.elf *.o *.gen.h + +.PHONY: prog dfuprog clean diff --git a/firmware/ice40-riscv/e1-tracer/config.h b/firmware/ice40-riscv/e1-tracer/config.h new file mode 100644 index 0000000..d292a4f --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/config.h @@ -0,0 +1,19 @@ +/* + * config.h + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#define SPI_FLASH_BASE 0x80000000 +#define UART_BASE 0x81000000 +#define LED_BASE 0x82000000 +#define USB_CORE_BASE 0x83000000 +#define USB_DATA_BASE 0x84000000 +#define E1_DATA_BASE 0x85000000 +#define DMA_BASE 0x86000000 +#define E1_CORE_BASE 0x87000000 +#define MISC_BASE 0x88000000 +#define SPI_LIU_BASE 0x89000000 diff --git a/firmware/ice40-riscv/e1-tracer/e1.c b/firmware/ice40-riscv/e1-tracer/e1.c new file mode 100644 index 0000000..989d2c8 --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/e1.c @@ -0,0 +1,513 @@ +/* + * e1.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include + +#include "config.h" +#include "console.h" +#include "e1.h" + +#include "dma.h" +#include "led.h" // FIXME + + +// Hardware +// -------- + +struct e1_chan { + uint32_t csr; + uint32_t bd; + uint32_t _rsvd[2]; +} __attribute__((packed,aligned(4))); + +struct e1_core { + struct e1_chan rx[2]; +} __attribute__((packed,aligned(4))); + +#define E1_RX_CR_ENABLE (1 << 0) +#define E1_RX_CR_MODE_TRSP (0 << 1) +#define E1_RX_CR_MODE_BYTE (1 << 1) +#define E1_RX_CR_MODE_BFA (2 << 1) +#define E1_RX_CR_MODE_MFA (3 << 1) +#define E1_RX_CR_OVFL_CLR (1 << 12) +#define E1_RX_SR_ENABLED (1 << 0) +#define E1_RX_SR_ALIGNED (1 << 1) +#define E1_RX_SR_BD_IN_EMPTY (1 << 8) +#define E1_RX_SR_BD_IN_FULL (1 << 9) +#define E1_RX_SR_BD_OUT_EMPTY (1 << 10) +#define E1_RX_SR_BD_OUT_FULL (1 << 11) +#define E1_RX_SR_OVFL (1 << 12) + +#define E1_BD_VALID (1 << 15) +#define E1_BD_CRC1 (1 << 14) +#define E1_BD_CRC0 (1 << 13) +#define E1_BD_ADDR(x) ((x) & 0x7f) +#define E1_BD_ADDR_MSK 0x7f +#define E1_BD_ADDR_SHFT 0 + + +static volatile struct e1_core * const e1_regs = (void *)(E1_CORE_BASE); +static volatile uint8_t * const e1_data = (void *)(E1_DATA_BASE); + + +volatile uint8_t * +e1_data_ptr(int mf, int frame, int ts) +{ + return &e1_data[(mf << 9) | (frame << 5) | ts]; +} + +unsigned int +e1_data_ofs(int mf, int frame, int ts) +{ + return (mf << 9) | (frame << 5) | ts; +} + + +// FIFOs +// ----- +/* Note: FIFO works at 'frame' level (i.e. 32 bytes) */ + +struct e1_fifo { + /* Buffer zone associated with the FIFO */ + unsigned int base; + unsigned int mask; + + /* Pointers / Levels */ + unsigned int wptr[2]; /* 0=committed 1=allocated */ + unsigned int rptr[2]; /* 0=discared 1=peeked */ +}; + + /* Utils */ +static void +e1f_reset(struct e1_fifo *fifo, unsigned int base, unsigned int len) +{ + memset(fifo, 0x00, sizeof(struct e1_fifo)); + fifo->base = base; + fifo->mask = len - 1; +} + +static unsigned int +e1f_allocd_frames(struct e1_fifo *fifo) +{ + /* Number of frames that are allocated (i.e. where we can't write to) */ + return (fifo->wptr[1] - fifo->rptr[0]) & fifo->mask; +} + +static unsigned int +e1f_valid_frames(struct e1_fifo *fifo) +{ + /* Number of valid frames */ + return (fifo->wptr[0] - fifo->rptr[0]) & fifo->mask; +} + +static unsigned int +e1f_unseen_frames(struct e1_fifo *fifo) +{ + /* Number of valid frames that haven't been peeked yet */ + return (fifo->wptr[0] - fifo->rptr[1]) & fifo->mask; +} + +static unsigned int +e1f_free_frames(struct e1_fifo *fifo) +{ + /* Number of frames that aren't allocated */ + return (fifo->rptr[0] - fifo->wptr[1] - 1) & fifo->mask; +} + +static unsigned int +e1f_ofs_to_dma(unsigned int ofs) +{ + /* DMA address are 32-bits word address. Offsets are 32 byte address */ + return (ofs << 3); +} + +static unsigned int +e1f_ofs_to_mf(unsigned int ofs) +{ + /* E1 Buffer Descriptors are always multiframe aligned */ + return (ofs >> 4); +} + + + /* Debug */ +static void +e1f_debug(struct e1_fifo *fifo, const char *name) +{ + unsigned int la, lv, lu, lf; + + la = e1f_allocd_frames(fifo); + lv = e1f_valid_frames(fifo); + lu = e1f_unseen_frames(fifo); + lf = e1f_free_frames(fifo); + + printf("%s: R: %u / %u | W: %u / %u | A:%u V:%u U:%u F:%u\n", + name, + fifo->rptr[0], fifo->rptr[1], fifo->wptr[0], fifo->wptr[1], + la, lv, lu, lf + ); +} + + /* Frame level read/write */ +static unsigned int +e1f_frame_write(struct e1_fifo *fifo, unsigned int *ofs, unsigned int max_frames) +{ + unsigned int lf, le; + + lf = e1f_free_frames(fifo); + le = fifo->mask - fifo->wptr[0] + 1; + + if (max_frames > le) + max_frames = le; + if (max_frames > lf) + max_frames = lf; + + *ofs = fifo->base + fifo->wptr[0]; + fifo->wptr[1] = fifo->wptr[0] = (fifo->wptr[0] + max_frames) & fifo->mask; + + return max_frames; +} + +static unsigned int +e1f_frame_read(struct e1_fifo *fifo, unsigned int *ofs, int max_frames) +{ + unsigned int lu, le; + + lu = e1f_unseen_frames(fifo); + le = fifo->mask - fifo->rptr[1] + 1; + + if (max_frames > le) + max_frames = le; + if (max_frames > lu) + max_frames = lu; + + *ofs = fifo->base + fifo->rptr[1]; + fifo->rptr[0] = fifo->rptr[1] = (fifo->rptr[1] + max_frames) & fifo->mask; + + return max_frames; +} + + + /* MultiFrame level split read/write */ +static bool +e1f_multiframe_write_prepare(struct e1_fifo *fifo, unsigned int *ofs) +{ + unsigned int lf; + + lf = e1f_free_frames(fifo); + if (lf < 16) + return false; + + *ofs = fifo->base + fifo->wptr[1]; + fifo->wptr[1] = (fifo->wptr[1] + 16) & fifo->mask; + + return true; +} + +static void +e1f_multiframe_write_commit(struct e1_fifo *fifo) +{ + fifo->wptr[0] = (fifo->wptr[0] + 16) & fifo->mask; +} + +static bool +e1f_multiframe_read_peek(struct e1_fifo *fifo, unsigned int *ofs) +{ + unsigned int lu; + + lu = e1f_unseen_frames(fifo); + if (lu < 16) + return false; + + *ofs = fifo->base + fifo->rptr[1]; + fifo->rptr[1] = (fifo->rptr[1] + 16) & fifo->mask; + + return true; +} + +static void +e1f_multiframe_read_discard(struct e1_fifo *fifo) +{ + fifo->rptr[0] = (fifo->rptr[0] + 16) & fifo->mask; +} + +static void +e1f_multiframe_empty(struct e1_fifo *fifo) +{ + fifo->rptr[0] = fifo->rptr[1] = (fifo->wptr[0] & ~15); +} + + + +// Main logic +// ---------- + +enum e1_pipe_state { + IDLE = 0, + BOOT = 1, + RUN = 2, + RECOVER = 3, +}; + +static struct { + struct { + uint32_t cr; + struct e1_fifo fifo; + short in_flight; + enum e1_pipe_state state; + uint8_t flags; + } rx[2]; + uint32_t error; +} g_e1; + + +void +e1_init(void) +{ + /* Global state init */ + memset(&g_e1, 0x00, sizeof(g_e1)); +} + +void +e1_start(void) +{ + /* Reset FIFOs */ +#ifdef BIGBUF + e1f_reset(&g_e1.rx[0].fifo, 0, 1024); + e1f_reset(&g_e1.rx[1].fifo, 1024, 1024); +#else + e1f_reset(&g_e1.rx[0].fifo, 0, 128); + e1f_reset(&g_e1.rx[1].fifo, 128, 128); +#endif + + /* Enable Rx0 */ + g_e1.rx[0].cr = E1_RX_CR_OVFL_CLR | + E1_RX_CR_MODE_MFA | + E1_RX_CR_ENABLE; + e1_regs->rx[0].csr = g_e1.rx[0].cr; + + /* Enable Rx1 */ + g_e1.rx[1].cr = E1_RX_CR_OVFL_CLR | + E1_RX_CR_MODE_MFA | + E1_RX_CR_ENABLE; + e1_regs->rx[1].csr = g_e1.rx[1].cr; + + /* State */ + g_e1.rx[0].state = BOOT; + g_e1.rx[0].in_flight = 0; + g_e1.rx[0].flags = 0; + + g_e1.rx[1].state = BOOT; + g_e1.rx[1].in_flight = 0; + g_e1.rx[1].flags = 0; +} + +void +e1_stop() +{ + /* Disable RX0 */ + g_e1.rx[0].cr = 0; + e1_regs->rx[0].csr = g_e1.rx[0].cr; + + /* Disable RX1 */ + g_e1.rx[1].cr = 0; + e1_regs->rx[1].csr = g_e1.rx[1].cr; + + /* State */ + g_e1.rx[0].state = IDLE; + g_e1.rx[1].state = IDLE; +} + + +#include "dma.h" + +unsigned int +e1_rx_need_data(int chan, unsigned int usb_addr, unsigned int max_frames, unsigned int *pos) +{ + unsigned int ofs; + int tot_frames = 0; + int n_frames; + + while (max_frames) { + /* Get some data from the FIFO */ + n_frames = e1f_frame_read(&g_e1.rx[chan].fifo, &ofs, max_frames); + if (!n_frames) + break; + + /* Give pos */ + if (pos) { + *pos = ofs & g_e1.rx[chan].fifo.mask; + pos = NULL; + } + + /* Copy from FIFO to USB */ + dma_exec(e1f_ofs_to_dma(ofs), usb_addr, n_frames * (32 / 4), false, NULL, NULL); + + /* Prepare Next */ + usb_addr += n_frames * (32 / 4); + max_frames -= n_frames; + tot_frames += n_frames; + + /* Wait for DMA completion */ + while (dma_poll()); + } + + return tot_frames; +} + +unsigned int +e1_rx_level(int chan) +{ + return e1f_valid_frames(&g_e1.rx[chan].fifo); +} + +uint8_t +e1_get_pending_flags(int chan) +{ + uint8_t f = g_e1.rx[chan].flags; + g_e1.rx[chan].flags = 0; + return f; +} + + +#define ERR_TIME 1000 + +void +e1_poll(void) +{ + uint32_t bd; + unsigned int ofs; + int chan; + bool error = false; + + /* HACK: LED link status */ + if ((g_e1.rx[0].state == IDLE) && (g_e1.rx[1].state == IDLE)) + { + /* Static dim red */ + led_color(32, 0, 0); + led_blink(false, 0, 0); + } else { + uint32_t csr[2]; + + csr[0] = e1_regs->rx[0].csr; + csr[1] = e1_regs->rx[1].csr; + + if (!((csr[0] & csr[1]) & E1_RX_SR_ALIGNED)) + error = true; + + /* Color is current SYNC status */ + led_color( + error ? 1 : 0, + csr[0] & E1_RX_SR_ALIGNED ? 48 : 0, + csr[1] & E1_RX_SR_ALIGNED ? 112 : 0 + ); + } + + /* Active ? */ + if ((g_e1.rx[0].state == IDLE) && (g_e1.rx[1].state == IDLE)) + return; + + /* Recover any done RX BD */ + for (chan=0; chan<2; chan++) + { + while ( (bd = e1_regs->rx[chan].bd) & E1_BD_VALID ) { + /* FIXME: CRC status ? */ + e1f_multiframe_write_commit(&g_e1.rx[chan].fifo); + if ((bd & (E1_BD_CRC0 | E1_BD_CRC1)) != (E1_BD_CRC0 | E1_BD_CRC1)) { + printf("b: %03x\n", bd); + g_e1.rx[chan].flags |= 4; + error = true; + } + g_e1.rx[chan].in_flight--; + } + } + + /* Handle RX */ + for (chan=0; chan<2; chan++) + { + /* Misalign ? */ + if (g_e1.rx[chan].state == RUN) { + if (!(e1_regs->rx[chan].csr & E1_RX_SR_ALIGNED)) { + printf("[!] E1 rx misalign\n"); + g_e1.rx[chan].state = RECOVER; + g_e1.rx[chan].flags |= 1; + error = true; + } + } + + /* Overflow ? */ + if (g_e1.rx[chan].state == RUN) { + if (e1_regs->rx[chan].csr & E1_RX_SR_OVFL) { + printf("[!] E1 overflow %d\n", g_e1.rx[chan].in_flight); + g_e1.rx[chan].state = RECOVER; + g_e1.rx[chan].flags |= 2; + } + } + + /* Recover ready ? */ + if (g_e1.rx[chan].state == RECOVER) { + if (g_e1.rx[chan].in_flight != 0) + continue; + e1f_multiframe_empty(&g_e1.rx[chan].fifo); + } + + /* Fill new RX BD */ + while (g_e1.rx[chan].in_flight < 4) { + if (!e1f_multiframe_write_prepare(&g_e1.rx[chan].fifo, &ofs)) + break; + e1_regs->rx[chan].bd = e1f_ofs_to_mf(ofs); + g_e1.rx[chan].in_flight++; + } + + /* Clear overflow if needed */ + if (g_e1.rx[chan].state != RUN) { + e1_regs->rx[chan].csr = g_e1.rx[chan].cr | E1_RX_CR_OVFL_CLR; + g_e1.rx[chan].state = RUN; + } + } + + /* Error tracking */ + if (error) { + if (!g_e1.error) { + printf("Error LED\n"); + led_blink(true, 150, 150); + } + g_e1.error = usb_get_tick() + ERR_TIME; + } else if (g_e1.error && (g_e1.error < usb_get_tick())) { + g_e1.error = 0; + led_blink(false, 0, 0); + printf("No error\n"); + } + +} + +void +e1_debug_print(bool data) +{ + volatile uint8_t *p; + + puts("E1\n"); + printf("CSR: Rx0 %04x / Rx1 %04x\n", e1_regs->rx[0].csr, e1_regs->rx[1].csr); + printf("InF: Rx0 %d / Rx1 %d\n", g_e1.rx[0].in_flight, g_e1.rx[1].in_flight); + printf("Sta: Rx0 %d / Rx1 %d\n", g_e1.rx[0].state, g_e1.rx[1].state); + + e1f_debug(&g_e1.rx[0].fifo, "Rx0 FIFO"); + e1f_debug(&g_e1.rx[1].fifo, "Rx1 FIFO"); + + if (data) { + puts("\nE1 Data\n"); + for (int f=0; f<16; f++) { + p = e1_data_ptr(0, f, 0); + for (int ts=0; ts<32; ts++) + printf(" %02x", p[ts]); + printf("\n"); + } + } +} diff --git a/firmware/ice40-riscv/e1-tracer/e1.h b/firmware/ice40-riscv/e1-tracer/e1.h new file mode 100644 index 0000000..b99c682 --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/e1.h @@ -0,0 +1,19 @@ +/* + * e1.h + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +void e1_init(); +void e1_start(); +void e1_stop(); + +void e1_poll(void); + +void e1_debug_print(bool data); + +volatile uint8_t *e1_data_ptr(int mf, int frame, int ts); +unsigned int e1_data_ofs(int mf, int frame, int ts); diff --git a/firmware/ice40-riscv/e1-tracer/fw_app.c b/firmware/ice40-riscv/e1-tracer/fw_app.c new file mode 100644 index 0000000..80f88bf --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/fw_app.c @@ -0,0 +1,224 @@ +/* + * fw_app.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include +#include + +#include "console.h" +#include "e1.h" +#include "led.h" +#include "misc.h" +#include "mini-printf.h" +#include "spi.h" +#include "utils.h" + + +extern const struct usb_stack_descriptors app_stack_desc; + +void usb_e1_init(void); +void usb_e1_run(void); + + +static void +serial_no_init() +{ + uint8_t buf[8]; + char *id, *desc; + int i; + + flash_manuf_id(buf); + printf("Flash Manufacturer : %s\n", hexstr(buf, 3, true)); + + flash_unique_id(buf); + printf("Flash Unique ID : %s\n", hexstr(buf, 8, true)); + + /* Overwrite descriptor string */ + /* In theory in rodata ... but nothing is ro here */ + id = hexstr(buf, 8, false); + desc = (char*)app_stack_desc.str[1]; + for (i=0; i<16; i++) + desc[2 + (i << 1)] = id[i]; +} + +static void +boot_dfu(void) +{ + /* Force re-enumeration */ + usb_disconnect(); + + /* Boot firmware */ + volatile uint32_t *boot = (void*)0x80000000; + *boot = (1 << 2) | (1 << 0); +} + +void +usb_dfu_rt_cb_reboot(void) +{ + boot_dfu(); +} + + + +static uint8_t +liu_read_reg(unsigned chan, uint8_t reg) +{ + uint8_t cmd = reg | 0x20; + uint8_t rv; + struct spi_xfer_chunk xfer[2] = { + { .data = (void*)&cmd, .len = 1, .read = false, .write = true, }, + { .data = (void*)&rv, .len = 1, .read = true, .write = false, }, + }; + spi_xfer(SPI_CS_LIU(chan), xfer, 2); + return rv; +} + +static void +liu_write_reg(unsigned chan, uint8_t reg, uint8_t val) +{ + uint8_t cmd[2] = { reg, val }; + struct spi_xfer_chunk xfer[2] = { + { .data = (void*)cmd, .len = 2, .read = false, .write = true, }, + }; + spi_xfer(SPI_CS_LIU(chan), xfer, 1); +} + + + +#define USB_RT_LIU_REG_WRITE ((1 << 8) | 0x42) +#define USB_RT_LIU_REG_READ ((2 << 8) | 0xc2) + + +static bool +_liu_reg_write_done_cb(struct usb_xfer *xfer) +{ + struct usb_ctrl_req *req = xfer->cb_ctx; + unsigned chan = req->wIndex - 0x81; + + liu_write_reg(chan, req->wValue, xfer->data[0]); + + return true; +} + +static enum usb_fnd_resp +_liu_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer) +{ + unsigned chan; + + /* If this a vendor request for the E1 endpoints */ + if (USB_REQ_RCPT(req) != USB_REQ_RCPT_EP) + return USB_FND_CONTINUE; + + if (req->wIndex < 0x81 || req->wIndex > 0x82) + return USB_FND_CONTINUE; + + if (USB_REQ_TYPE(req) != USB_REQ_TYPE_VENDOR) + return USB_FND_CONTINUE; + + chan = req->wIndex - 0x81; + + /* */ + switch (req->wRequestAndType) + { + case USB_RT_LIU_REG_WRITE: + xfer->len = 1; + xfer->cb_done = _liu_reg_write_done_cb; + xfer->cb_ctx = req; + break; + + case USB_RT_LIU_REG_READ: + xfer->len = 1; + xfer->data[0] = liu_read_reg(chan, req->wValue); + break; + + default: + goto error; + } + + return USB_FND_SUCCESS; + +error: + return USB_FND_ERROR; +} + +static struct usb_fn_drv _liu_drv = { + .ctrl_req = _liu_ctrl_req, +}; + + + +void main() +{ + int cmd = 0; + + /* Init console IO */ + console_init(); + puts("Booting App image..\n"); + + /* LED */ + led_init(); + led_state(true); + + /* SPI */ + spi_init(); + + puts("XXX\n"); + + /* Enable USB directly */ + serial_no_init(); + usb_init(&app_stack_desc); + usb_dfu_rt_init(); + usb_register_function_driver(&_liu_drv); + usb_e1_init(); + + usb_connect(); + e1_init(); + + /* Main loop */ + while (1) + { + /* Prompt ? */ + if (cmd >= 0) + printf("Command> "); + + /* Poll for command */ + cmd = getchar_nowait(); + + if (cmd >= 0) { + if (cmd > 32 && cmd < 127) { + putchar(cmd); + putchar('\r'); + putchar('\n'); + } + + switch (cmd) + { + case 'u': + usb_debug_print(); + break; + case 'e': + e1_debug_print(false); + break; + case 'E': + e1_debug_print(true); + break; + default: + break; + } + } + + /* USB poll */ + usb_poll(); + + /* E1 poll */ + e1_poll(); + usb_e1_run(); + } +} diff --git a/firmware/ice40-riscv/e1-tracer/misc.c b/firmware/ice40-riscv/e1-tracer/misc.c new file mode 100644 index 0000000..6b100bd --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/misc.c @@ -0,0 +1,35 @@ +/* + * misc.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#include "config.h" +#include "misc.h" + + +struct misc { + uint32_t warmboot; + uint32_t _rsvd0[3];; + struct { + uint16_t tx; + uint16_t rx; + } e1_tick[2]; + uint32_t _rsvd1; + uint32_t time; +} __attribute__((packed,aligned(4))); + +static volatile struct misc * const misc_regs = (void*)(MISC_BASE); + + +void +e1_tick_read(uint16_t *ticks) +{ + uint32_t v = misc_regs->e1_tick; + ticks[0] = (v ) & 0xffff; + ticks[1] = (v >> 16) & 0xffff; +} diff --git a/firmware/ice40-riscv/e1-tracer/misc.h b/firmware/ice40-riscv/e1-tracer/misc.h new file mode 100644 index 0000000..31e8f80 --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/misc.h @@ -0,0 +1,15 @@ +/* + * misc.h + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +void vio_set(unsigned value); + +void e1_tick_read(uint16_t *ticks); diff --git a/firmware/ice40-riscv/e1-tracer/usb_desc_app.c b/firmware/ice40-riscv/e1-tracer/usb_desc_app.c new file mode 100644 index 0000000..176ec12 --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/usb_desc_app.c @@ -0,0 +1,253 @@ +/* + * usb_desc_app.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#define NULL ((void*)0) +#define num_elem(a) (sizeof(a) / sizeof(a[0])) + + +static const struct { + /* Configuration */ + struct usb_conf_desc conf; + + /* E1 */ + struct { + struct { + struct usb_intf_desc intf; + struct usb_ep_desc ep_data_in0; + struct usb_ep_desc ep_data_in1; + } __attribute__ ((packed)) off; + struct { + struct usb_intf_desc intf; + struct usb_ep_desc ep_data_in0; + struct usb_ep_desc ep_data_in1; + } __attribute__ ((packed)) on; + } __attribute__ ((packed)) e1; + + /* CDC */ +#if 0 + struct { + struct usb_intf_desc intf_ctl; + struct usb_cs_intf_hdr_desc cs_intf_hdr; + struct usb_cs_intf_acm_desc cs_intf_acm; + struct usb_cs_intf_union_desc cs_intf_union; + uint8_t cs_intf_union_slave; + struct usb_ep_desc ep_ctl; + struct usb_intf_desc intf_data; + struct usb_ep_desc ep_data_out; + struct usb_ep_desc ep_data_in; + } __attribute__ ((packed)) cdc; +#endif + + /* DFU Runtime */ + struct { + struct usb_intf_desc intf; + struct usb_dfu_desc func; + } __attribute__ ((packed)) dfu; +} __attribute__ ((packed)) _app_conf_desc = { + .conf = { + .bLength = sizeof(struct usb_conf_desc), + .bDescriptorType = USB_DT_CONF, + .wTotalLength = sizeof(_app_conf_desc), +#if 0 + .bNumInterfaces = 4, +#else + .bNumInterfaces = 2, +#endif + .bConfigurationValue = 1, + .iConfiguration = 4, + .bmAttributes = 0x80, + .bMaxPower = 0x32, /* 100 mA */ + }, + .e1 = { + .off = { + .intf = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xe1, + .bInterfaceProtocol = 0x00, + .iInterface = 5, + }, + .ep_data_in0 = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x81, + .bmAttributes = 0x05, + .wMaxPacketSize = 0, + .bInterval = 1, + }, + .ep_data_in1 = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x82, + .bmAttributes = 0x05, + .wMaxPacketSize = 0, + .bInterval = 1, + }, + }, + .on = { + .intf = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, + .bInterfaceNumber = 0, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xe1, + .bInterfaceProtocol = 0x00, + .iInterface = 5, + }, + .ep_data_in0 = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x81, + .bmAttributes = 0x05, + .wMaxPacketSize = 388, + .bInterval = 1, + }, + .ep_data_in1 = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x82, + .bmAttributes = 0x05, + .wMaxPacketSize = 388, + .bInterval = 1, + }, + }, + }, +#if 0 + .cdc = { + .intf_ctl = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = 0x02, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 6, + }, + .cs_intf_hdr = { + .bLength = sizeof(struct usb_cs_intf_hdr_desc), + .bDescriptorType = USB_DT_CS_INTF, + .bDescriptorsubtype = 0x00, + .bcdCDC = 0x0110, + }, + .cs_intf_acm = { + .bLength = sizeof(struct usb_cs_intf_acm_desc), + .bDescriptorType = USB_DT_CS_INTF, + .bDescriptorsubtype = 0x02, + .bmCapabilities = 0x00, + }, + .cs_intf_union = { + .bLength = sizeof(struct usb_cs_intf_union_desc) + 1, + .bDescriptorType = USB_DT_CS_INTF, + .bDescriptorsubtype = 0x06, + .bMasterInterface = 1, + }, + .cs_intf_union_slave = 2, + .ep_ctl = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x84, + .bmAttributes = 0x03, + .wMaxPacketSize = 64, + .bInterval = 0xff, + }, + .intf_data = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, + .bInterfaceNumber = 2, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0x0a, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .iInterface = 7, + }, + .ep_data_out = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x05, + .bmAttributes = 0x02, + .wMaxPacketSize = 64, + .bInterval = 0x00, + }, + .ep_data_in = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x85, + .bmAttributes = 0x02, + .wMaxPacketSize = 64, + .bInterval = 0x01, + }, + }, +#endif + .dfu = { + .intf = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, +#if 0 + .bInterfaceNumber = 3, +#else + .bInterfaceNumber = 1, +#endif + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x01, + .iInterface = 8, + }, + .func = { + .bLength = sizeof(struct usb_dfu_desc), + .bDescriptorType = USB_DT_DFU, + .bmAttributes = 0x0d, + .wDetachTimeOut = 1000, + .wTransferSize = 4096, + .bcdDFUVersion = 0x0101, + }, + }, +}; + +static const struct usb_conf_desc * const _conf_desc_array[] = { + &_app_conf_desc.conf, +}; + +static const struct usb_dev_desc _dev_desc = { + .bLength = sizeof(struct usb_dev_desc), + .bDescriptorType = USB_DT_DEV, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x1d50, + .idProduct = 0x6151, + .bcdDevice = 2, + .iManufacturer = 2, + .iProduct = 3, + .iSerialNumber = 1, + .bNumConfigurations = num_elem(_conf_desc_array), +}; + +#include "usb_str_app.gen.h" + +const struct usb_stack_descriptors app_stack_desc = { + .dev = &_dev_desc, + .conf = _conf_desc_array, + .n_conf = num_elem(_conf_desc_array), + .str = _str_desc_array, + .n_str = num_elem(_str_desc_array), +}; diff --git a/firmware/ice40-riscv/e1-tracer/usb_e1.c b/firmware/ice40-riscv/e1-tracer/usb_e1.c new file mode 100644 index 0000000..421305b --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/usb_e1.c @@ -0,0 +1,230 @@ +/* + * usb_e1.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include +#include + +#include "console.h" +#include "e1.h" +#include "misc.h" + +struct { + bool running; + int in_bdi[2]; +} g_usb_e1; + + +/* Hack */ +unsigned int e1_rx_need_data(int chan, unsigned int usb_addr, unsigned int max_len, unsigned int *pos); +unsigned int e1_rx_level(int chan); +uint8_t e1_get_pending_flags(int chan); +/* ---- */ + +bool +usb_ep_boot(const struct usb_intf_desc *intf, uint8_t ep_addr, bool dual_bd); + + +void +usb_e1_run(void) +{ + int chan; + int bdi; + + if (!g_usb_e1.running) + return; + + /* EP[1-2] IN */ + for (chan=0; chan<2; chan++) + { + bdi = g_usb_e1.in_bdi[chan]; + + while ((usb_ep_regs[1+chan].in.bd[bdi].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) + { + uint32_t ptr = usb_ep_regs[1+chan].in.bd[bdi].ptr; + uint32_t hdr; + unsigned int pos; + + /* Error check */ + if ((usb_ep_regs[1+chan].in.bd[bdi].csr & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_ERR) + printf("Err EP%d IN\n", 1+chan); + + /* Get some data from E1 */ + int n = e1_rx_level(chan); + +// if (n > 64) +// n = 12; +// else if (n > 32) +// n = 10; +// else if (n > 8) +// n = 8; + if (n > 12) + n = 12; + else if (!n) + break; + + n = e1_rx_need_data(chan, (ptr >> 2) + 1, n, &pos); + + /* Write header */ + /* [31:12] (reserved) */ + /* [11:10] CRC results (first new multiframe present in packet) */ + /* [ 9: 8] CRC results (second new multiframe present in packet) */ + /* [ 7: 5] Multiframe sequence number (first frame of packet) */ + /* [ 4: 0] Position in multi-frame (first frame of packet) */ + hdr = (pos & 0xff) | (e1_get_pending_flags(chan) << 24); + usb_data_write(ptr, &hdr, 4); + usb_ep_regs[1+chan].in.bd[bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN((n * 32) + 4); + + /* Next BDI */ + bdi ^= 1; + g_usb_e1.in_bdi[chan] = bdi; + } + } +} + +static const struct usb_intf_desc * +_find_intf(const struct usb_conf_desc *conf, uint8_t idx) +{ + const struct usb_intf_desc *intf = NULL; + const void *sod, *eod; + + if (!conf) + return NULL; + + sod = conf; + eod = sod + conf->wTotalLength; + + while (1) { + sod = usb_desc_find(sod, eod, USB_DT_INTF); + if (!sod) + break; + + intf = (void*)sod; + if (intf->bInterfaceNumber == idx) + return intf; + + sod = usb_desc_next(sod); + } + + return NULL; +} +enum usb_fnd_resp +_e1_set_conf(const struct usb_conf_desc *conf) +{ + const struct usb_intf_desc *intf; + + printf("e1 set_conf %08x\n", conf); + if (!conf) + return USB_FND_SUCCESS; + + intf = _find_intf(conf, 0); + if (!intf) + return USB_FND_ERROR; + + printf("e1 set_conf %08x\n", intf); + + usb_ep_boot(intf, 0x81, true); + usb_ep_boot(intf, 0x82, true); + + return USB_FND_SUCCESS; +} + +enum usb_fnd_resp +_e1_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel) +{ + if (base->bInterfaceNumber != 0) + return USB_FND_CONTINUE; + + if (sel->bAlternateSetting == 0) + { + /* Already stopped ? */ + if (!g_usb_e1.running) + return USB_FND_SUCCESS; + + /* Update state */ + g_usb_e1.running = false; + + /* Stop E1 */ + e1_stop(); + + /* Disable end-points */ + usb_ep_regs[1].in.status = 0; + usb_ep_regs[2].in.status = 0; + } + else if (sel->bAlternateSetting == 1) + { + /* Already running ? */ + if (g_usb_e1.running) + return USB_FND_SUCCESS; + + /* Update state */ + g_usb_e1.running = true; + + /* Reset buffer pointers */ + g_usb_e1.in_bdi[0] = 0; + g_usb_e1.in_bdi[1] = 0; + + /* Configure EP1 IN / EP2 IN */ + usb_ep_regs[1].in.status = USB_EP_TYPE_ISOC | USB_EP_BD_DUAL; /* Type=Isochronous, dual buffered */ + usb_ep_regs[2].in.status = USB_EP_TYPE_ISOC | USB_EP_BD_DUAL; /* Type=Isochronous, dual buffered */ + + /* EP1 IN: Prepare two buffers */ + usb_ep_regs[1].in.bd[0].ptr = 256 + 0 * 388; + usb_ep_regs[1].in.bd[0].csr = 0; + + usb_ep_regs[1].in.bd[1].ptr = 256 + 1 * 388; + usb_ep_regs[1].in.bd[1].csr = 0; + + /* EP2 IN: Prepare two buffers */ + usb_ep_regs[2].in.bd[0].ptr = 256 + 2 * 388; + usb_ep_regs[2].in.bd[0].csr = 0; + + usb_ep_regs[2].in.bd[1].ptr = 256 + 3 * 388; + usb_ep_regs[2].in.bd[1].csr = 0; + + /* Start E1 */ + e1_start(); + } + else + { + /* Unknown */ + return USB_FND_ERROR; + } + + + return USB_FND_SUCCESS; +} + +enum usb_fnd_resp +_e1_get_intf(const struct usb_intf_desc *base, uint8_t *alt) +{ + if (base->bInterfaceNumber != 0) + return USB_FND_CONTINUE; + + *alt = g_usb_e1.running ? 1 : 0; + + return USB_FND_SUCCESS; +} + +static struct usb_fn_drv _e1_drv = { + .set_conf = _e1_set_conf, + .set_intf = _e1_set_intf, + .get_intf = _e1_get_intf, +}; + +void +usb_e1_init(void) +{ + /* Clear state */ + memset(&g_usb_e1, 0x00, sizeof(g_usb_e1)); + + /* Install driver */ + usb_register_function_driver(&_e1_drv); +} diff --git a/firmware/ice40-riscv/e1-tracer/usb_str_app.txt b/firmware/ice40-riscv/e1-tracer/usb_str_app.txt new file mode 100644 index 0000000..7f325e6 --- /dev/null +++ b/firmware/ice40-riscv/e1-tracer/usb_str_app.txt @@ -0,0 +1,8 @@ +0000000000000000 +osmocom +e1-tracer +Main +E1 +Console (control) +Console (data) +DFU runtime