[sw] Move device code into sw/device folder
This change addresses issue #27. All device code is being moved to the
`sw/device` folder. Makefiles and Meson build files had been updated to
support the new directory structure.
Documentation, and top level and DV dependencies have been updated as
well.
Manual tests:
* ci/run_sw_build.sh
* ./meson_init.sh && ninja -C build-fpga && ninja -C build-verilator
diff --git a/sw/device/lib/common.h b/sw/device/lib/common.h
new file mode 100644
index 0000000..f7f2840
--- /dev/null
+++ b/sw/device/lib/common.h
@@ -0,0 +1,44 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef SIMULATION
+#define CLK_FIXED_FREQ_HZ (500 * 1000)
+static const unsigned long UART_BAUD_RATE = 9600;
+#else
+#define CLK_FIXED_FREQ_HZ (50ULL * 1000 * 1000)
+static const unsigned long UART_BAUD_RATE = 230400;
+#endif
+
+// Flash memory base defines, _SZ are presented in bytes
+#define FLASH_MEM_BASE_ADDR 0x20000000
+#define FLASH_WORDS_PER_PAGE 256
+#define FLASH_WORD_SZ 4
+#define FLASH_PAGE_SZ FLASH_WORDS_PER_PAGE *FLASH_WORD_SZ
+#define FLASH_PAGES_PER_BANK 256
+#define FLASH_BANK_SZ FLASH_PAGES_PER_BANK *FLASH_PAGE_SZ
+
+#define REG8(add) *((volatile uint8_t *)(add))
+#define REG16(add) *((volatile uint16_t *)(add))
+#define REG32(add) *((volatile uint32_t *)(add))
+#define SETBIT(val, bit) (val | 1 << bit)
+#define CLRBIT(val, bit) (val & ~(1 << bit))
+
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+
+/* Hamming weight */
+#define BITLENGTH_1(X) ((X) - (((X) >> 1) & 0x55555555))
+#define BITLENGTH_2(X) (((X)&0x33333333) + (((X) >> 2) & 0x33333333))
+#define BITLENGTH_3(X) (((X) + ((X) >> 4)) & 0x0f0f0f0f)
+#define BITLENGTH_4(X) ((X) + ((X) >> 8))
+#define BITLENGTH_5(X) ((X) + ((X) >> 16))
+#define BITLENGTH(X) \
+ ((BITLENGTH_5(BITLENGTH_4(BITLENGTH_3(BITLENGTH_2(BITLENGTH_1(X)))))) & 0x7f)
+
+#endif
diff --git a/sw/device/lib/flash_ctrl.c b/sw/device/lib/flash_ctrl.c
new file mode 100644
index 0000000..8d8f7a1
--- /dev/null
+++ b/sw/device/lib/flash_ctrl.c
@@ -0,0 +1,157 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+#include "sw/device/lib/flash_ctrl.h"
+
+#include "flash_ctrl_regs.h" // Generated.
+
+#include "sw/device/lib/common.h"
+
+#define FLASH_CTRL0_BASE_ADDR 0x40030000
+
+typedef enum flash_op {
+ FLASH_READ = 0,
+ FLASH_PROG = 1,
+ FLASH_ERASE = 2
+} flash_op_t;
+
+typedef enum erase_type {
+ FLASH_PAGE_ERASE = 0,
+ FLASH_BANK_ERASE = 1
+} erase_type_t;
+
+/* Wait for flash command to complete and set ACK in controller */
+static inline void wait_done_and_ack(void) {
+ while ((REG32(FLASH_CTRL_OP_STATUS(0)) & (1 << FLASH_CTRL_OP_STATUS_DONE)) ==
+ 0) {
+ }
+ REG32(FLASH_CTRL_OP_STATUS(0)) = 0;
+}
+
+void flash_init_block(void) {
+ while ((REG32(FLASH_CTRL_STATUS(0)) & (1 << FLASH_CTRL_STATUS_INIT_WIP)) >
+ 0) {
+ }
+}
+
+/* Return status error and clear internal status register */
+static int get_clr_err(void) {
+ uint32_t err_status =
+ REG32(FLASH_CTRL_INTR_STATE(0)) & (0x1 << FLASH_CTRL_INTR_STATE_OP_ERROR);
+ REG32(FLASH_CTRL_INTR_STATE(0)) = err_status;
+ return err_status;
+}
+
+int flash_check_empty(void) {
+ uint32_t mask = -1u;
+ uint32_t *p = (uint32_t *)FLASH_MEM_BASE_ADDR;
+ // TODO: Update range to cover entire flash. Limited now to one bank while
+ // we debu initialization.
+ for (; p < (uint32_t *)(FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ);) {
+ mask &= *p++;
+ mask &= *p++;
+ mask &= *p++;
+ mask &= *p++;
+ mask &= *p++;
+ mask &= *p++;
+ mask &= *p++;
+ mask &= *p++;
+ if (mask != -1u) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int flash_bank_erase(bank_index_t idx) {
+ flash_cfg_bank_erase(idx, /*erase_en=*/true);
+
+ // TODO: Add timeout conditions and add error codes.
+ REG32(FLASH_CTRL_ADDR(0)) = (idx == FLASH_BANK_0)
+ ? FLASH_MEM_BASE_ADDR
+ : (FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ);
+ REG32(FLASH_CTRL_CONTROL(0)) =
+ (FLASH_ERASE << FLASH_CTRL_CONTROL_OP_OFFSET |
+ FLASH_BANK_ERASE << FLASH_CTRL_CONTROL_ERASE_SEL |
+ 0x1 << FLASH_CTRL_CONTROL_START);
+ wait_done_and_ack();
+
+ flash_cfg_bank_erase(idx, /*erase_en=*/false);
+
+ return get_clr_err();
+}
+
+int flash_page_erase(uint32_t addr) {
+ REG32(FLASH_CTRL_ADDR(0)) = addr;
+ REG32(FLASH_CTRL_CONTROL(0)) = FLASH_ERASE << FLASH_CTRL_CONTROL_OP_OFFSET |
+ FLASH_PAGE_ERASE
+ << FLASH_CTRL_CONTROL_ERASE_SEL |
+ 0x1 << FLASH_CTRL_CONTROL_START;
+ wait_done_and_ack();
+ return get_clr_err();
+}
+
+static int flash_write_internal(uint32_t addr, const uint32_t *data,
+ uint32_t size) {
+ // TODO: Do we need to select bank as part of the write?
+ // TODO: Update with address alignment requirements.
+ REG32(FLASH_CTRL_ADDR(0)) = addr;
+ REG32(FLASH_CTRL_CONTROL(0)) = (FLASH_PROG << FLASH_CTRL_CONTROL_OP_OFFSET |
+ (size - 1) << FLASH_CTRL_CONTROL_NUM_OFFSET |
+ 0x1 << FLASH_CTRL_CONTROL_START);
+ for (int i = 0; i < size; ++i) {
+ REG32(FLASH_CTRL_PROG_FIFO(0)) = data[i];
+ }
+ wait_done_and_ack();
+ return get_clr_err();
+}
+
+int flash_write(uint32_t addr, const uint32_t *data, uint32_t size) {
+ // TODO: Breakdown into FIFO chunks if needed.
+ return flash_write_internal(addr, data, size);
+}
+
+int flash_read(uint32_t addr, uint32_t size, uint32_t *data) {
+ REG32(FLASH_CTRL_ADDR(0)) = addr;
+ REG32(FLASH_CTRL_CONTROL(0)) = FLASH_READ << FLASH_CTRL_CONTROL_OP_OFFSET |
+ (size - 1) << FLASH_CTRL_CONTROL_NUM_OFFSET |
+ 0x1 << FLASH_CTRL_CONTROL_START;
+ for (uint32_t i = 0; i < size;) {
+ if (((REG32(FLASH_CTRL_STATUS(0)) >> FLASH_CTRL_STATUS_RD_EMPTY) & 0x1) ==
+ 0) {
+ *data++ = REG32(FLASH_CTRL_RD_FIFO(0));
+ i++;
+ }
+ }
+ wait_done_and_ack();
+ return get_clr_err();
+}
+
+void flash_cfg_bank_erase(bank_index_t bank, bool erase_en) {
+ REG32(FLASH_CTRL_MP_BANK_CFG(0)) =
+ (erase_en) ? SETBIT(REG32(FLASH_CTRL_MP_BANK_CFG(0)), bank)
+ : CLRBIT(REG32(FLASH_CTRL_MP_BANK_CFG(0)), bank);
+}
+
+void flash_default_region_access(bool rd_en, bool prog_en, bool erase_en) {
+ REG32(FLASH_CTRL_DEFAULT_REGION(0)) =
+ rd_en << FLASH_CTRL_DEFAULT_REGION_RD_EN |
+ prog_en << FLASH_CTRL_DEFAULT_REGION_PROG_EN |
+ erase_en << FLASH_CTRL_DEFAULT_REGION_ERASE_EN;
+}
+
+void flash_cfg_region(const mp_region_t *region_cfg) {
+ REG32(FLASH_CTRL_MP_REGION_CFG0(0) + region_cfg->num * 4) =
+ region_cfg->base << FLASH_CTRL_MP_REGION_CFG0_BASE0_OFFSET |
+ region_cfg->size << FLASH_CTRL_MP_REGION_CFG0_SIZE0_OFFSET |
+ region_cfg->rd_en << FLASH_CTRL_MP_REGION_CFG0_RD_EN0 |
+ region_cfg->prog_en << FLASH_CTRL_MP_REGION_CFG0_PROG_EN0 |
+ region_cfg->erase_en << FLASH_CTRL_MP_REGION_CFG0_ERASE_EN0 |
+ 0x1 << FLASH_CTRL_MP_REGION_CFG0_EN0;
+}
+
+void flash_write_scratch_reg(uint32_t value) {
+ REG32(FLASH_CTRL_SCRATCH(0)) = value;
+}
+
+uint32_t flash_read_scratch_reg(void) { return REG32(FLASH_CTRL_SCRATCH(0)); }
diff --git a/sw/device/lib/flash_ctrl.h b/sw/device/lib/flash_ctrl.h
new file mode 100644
index 0000000..d0ba0d9
--- /dev/null
+++ b/sw/device/lib/flash_ctrl.h
@@ -0,0 +1,100 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _F_FLASH_CTRL_H__
+#define _F_FLASH_CTRL_H__
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Flash bank IDs
+ */
+typedef enum bank_index { FLASH_BANK_0 = 0, FLASH_BANK_1 = 1 } bank_index_t;
+
+/**
+ * Memory protection configuration options.
+ */
+typedef struct mp_region {
+ /** Which region to program. */
+ uint32_t num;
+ /** Region offset. */
+ uint32_t base;
+ /** Region config size. */
+ uint32_t size;
+ /** Read enable flag. */
+ uint32_t rd_en;
+ /** Program enable flag. */
+ uint32_t prog_en;
+ /** Erase enable flag. */
+ uint32_t erase_en;
+} mp_region_t;
+
+/**
+ * Block until flash is initialized.
+ */
+void flash_init_block(void);
+
+/**
+ * Returns 1 if flash is empty, otherwise 0.
+ */
+int flash_check_empty(void);
+
+/**
+ * Erase flash bank |bank_idx|. Blocks until erase is complete.
+ *
+ * @param idx Flash bank index.
+ * @return Non zero on failure.
+ */
+int flash_bank_erase(bank_index_t idx);
+int flash_page_erase(uint32_t addr);
+
+/**
+ * Write |data| at |addr| offset with |size| in 4B words
+ *
+ * @param addr Flash address 32bit aligned.
+ * @param data Data to write.
+ * @param size Number of 4B words to write from |data| buffer.
+ * @return Non zero on failure.
+ */
+int flash_write(uint32_t addr, const uint32_t *data, uint32_t size);
+
+/**
+ * Read |size| 4B words and write result to |data|.
+ *
+ * @param addr Read start address.
+ * @param size Number of 4B words to read.
+ * @param data Output buffer.
+ * @return Non zero on failure.
+ */
+int flash_read(uint32_t addr, uint32_t size, uint32_t *data);
+
+/**
+ * Configure bank erase enable
+ */
+void flash_cfg_bank_erase(bank_index_t bank, bool erase_en);
+
+/**
+ * Set flash controller default permissions.
+ *
+ * @param rd_end Read enable.
+ * @param prog_en Write enable.
+ * @param erase_en Erase enable.
+ */
+void flash_default_region_access(bool rd_en, bool prog_en, bool erase_en);
+
+/**
+ * Configure memory protection region.
+ *
+ * @param region_cfg Region configuration.
+ */
+void flash_cfg_region(const mp_region_t *region_cfg);
+
+/** Write value to flash scratch register */
+void flash_write_scratch_reg(uint32_t value);
+
+/** Read scratch register */
+uint32_t flash_read_scratch_reg(void);
+
+#endif // _F_FLASH_CTRL_H__
diff --git a/sw/device/lib/gpio.c b/sw/device/lib/gpio.c
new file mode 100644
index 0000000..a7ddf74
--- /dev/null
+++ b/sw/device/lib/gpio.c
@@ -0,0 +1,34 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/gpio.h"
+
+#include <assert.h>
+
+#include "sw/device/lib/common.h"
+
+void gpio_init(uint32_t oe) { REG32(GPIO_DIRECT_OE(0)) = oe; }
+
+void gpio_write_bit(unsigned int bit, unsigned int val) {
+ uint32_t mask = 0;
+ uint32_t reg_val = 0;
+ volatile uint32_t *gpio_masked_out_reg = 0;
+
+ if (bit < 16) {
+ gpio_masked_out_reg = (uint32_t *)GPIO_MASKED_OUT_LOWER(0);
+ } else if (bit < 32) {
+ gpio_masked_out_reg = (uint32_t *)GPIO_MASKED_OUT_UPPER(0);
+ } else {
+ assert(1 && "bit must be < 32");
+ }
+ bit %= 16;
+
+ mask = (1 << bit);
+ reg_val = (mask << 16) | ((val & 0x01) << bit);
+ *gpio_masked_out_reg = reg_val;
+}
+
+void gpio_write_all(uint32_t val) { REG32(GPIO_DIRECT_OUT(0)) = val; }
+
+uint32_t gpio_read(void) { return REG32(GPIO_DATA_IN(0)); }
diff --git a/sw/device/lib/gpio.h b/sw/device/lib/gpio.h
new file mode 100644
index 0000000..850817c
--- /dev/null
+++ b/sw/device/lib/gpio.h
@@ -0,0 +1,25 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _GPIO_H_
+#define _GPIO_H_
+
+#include <stdint.h>
+
+#include "gpio_regs.h" // Generated.
+
+#define GPIO0_BASE_ADDR 0x40010000
+
+#define GPIO_BOOTSTRAP_BIT_MASK 0x20000
+
+/**
+ * @param oe bits to use as output
+ */
+void gpio_init(uint32_t oe);
+
+void gpio_write_bit(unsigned int bit, unsigned int val);
+void gpio_write_all(uint32_t val);
+uint32_t gpio_read(void);
+
+#endif
diff --git a/sw/device/lib/handler.c b/sw/device/lib/handler.c
new file mode 100644
index 0000000..b344415
--- /dev/null
+++ b/sw/device/lib/handler.c
@@ -0,0 +1,161 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "handler.h"
+
+#include "sw/device/lib/common.h"
+#include "sw/device/lib/uart.h"
+
+/**
+ * Default exception handler. Can be overidden.
+ */
+void handler_exception(void) __attribute__((aligned(4), interrupt, weak));
+
+/**
+ * SW IRQ handler. Can be overidden.
+ */
+void handler_irq_software(void) __attribute__((aligned(4), interrupt, weak));
+
+/**
+ * Timer IRQ handler. Can be overidden.
+ */
+void handler_irq_timer(void) __attribute__((aligned(4), interrupt, weak));
+
+/**
+ * external IRQ handler. Can be overidden.
+ */
+void handler_irq_external(void) __attribute__((aligned(4), interrupt, weak));
+
+/**
+ * Instruction access fault. Can be overriden.
+ */
+void handler_instr_acc_fault(void) __attribute__((aligned(4), interrupt, weak));
+
+/**
+ * Illegal Instruction fault. Can be overriden.
+ */
+void handler_instr_ill_fault(void) __attribute__((aligned(4), interrupt, weak));
+;
+
+/**
+ * Breakpoint handler. Can be overriden.
+ */
+void handler_bkpt(void) __attribute__((aligned(4), interrupt, weak));
+
+/**
+ * Load store unit fault. Can be overriden.
+ */
+void handler_lsu_fault(void) __attribute__((aligned(4), interrupt, weak));
+
+/**
+ * Exception call handler. Can be overriden.
+ */
+void handler_ecall(void) __attribute__((aligned(4), interrupt, weak));
+
+/**
+ * Return value of mtval
+ */
+static uint32_t get_mtval(void) {
+ uint32_t mtval;
+ asm volatile("csrr %0, mtval" : "=r"(mtval) : :);
+ return mtval;
+}
+
+/**
+ * Default Error Handling
+ * @param error message supplied by caller
+ * TODO - this will be soon by a real print formatting
+ */
+static void print_exc_msg(const char *msg) {
+ const uint32_t mtval = get_mtval();
+ uart_send_str((char *)msg);
+ uart_send_str("MTVAL value is ");
+ uart_send_uint(mtval, 32);
+ uart_send_str("\n");
+ while (1) {
+ };
+}
+
+// Below functions are default weak exception handlers meant to be overriden
+void handler_exception(void) {
+ uint32_t mcause;
+ exc_id_t exc_cause;
+
+ asm volatile("csrr %0 , mcause" : "=r"(mcause) : :);
+ exc_cause = (exc_id_t)(mcause & kIdMax);
+
+ switch (exc_cause) {
+ case kInstMisa:
+ handler_instr_acc_fault();
+ break;
+ case kInstAccFault:
+ handler_instr_acc_fault();
+ break;
+ case kInstIllegalFault:
+ handler_instr_ill_fault();
+ break;
+ case kBkpt:
+ handler_bkpt();
+ break;
+ case kLoadAccFault:
+ handler_lsu_fault();
+ break;
+ case kStrAccFault:
+ handler_lsu_fault();
+ break;
+ case kECall:
+ handler_ecall();
+ break;
+ default:
+ while (1) {
+ };
+ }
+}
+
+void handler_irq_software(void) {
+ uart_send_str("Software IRQ triggered!\n");
+ while (1) {
+ }
+}
+
+void handler_irq_timer(void) {
+ uart_send_str("Timer IRQ triggered!\n");
+ while (1) {
+ }
+}
+
+void handler_irq_external(void) {
+ uart_send_str("External IRQ triggered!\n");
+ while (1) {
+ }
+}
+
+void handler_instr_acc_fault(void) {
+ const char fault_msg[] =
+ "Instruction access fault, mtval shows fault address\n";
+ print_exc_msg(fault_msg);
+}
+
+void handler_instr_ill_fault(void) {
+ const char fault_msg[] =
+ "Illegal Instruction fault, mtval shows instruction content\n";
+ print_exc_msg(fault_msg);
+}
+
+void handler_bkpt(void) {
+ const char exc_msg[] =
+ "Breakpoint triggerd, mtval shows the breakpoint address\n";
+ print_exc_msg(exc_msg);
+}
+
+void handler_lsu_fault(void) {
+ const char exc_msg[] = "Load/Store fault, mtval shows the fault address\n";
+ print_exc_msg(exc_msg);
+}
+
+void handler_ecall(void) {
+ uart_send_str("Environment call encountered\n");
+ while (1) {
+ }
+}
diff --git a/sw/device/lib/handler.h b/sw/device/lib/handler.h
new file mode 100644
index 0000000..6af92d3
--- /dev/null
+++ b/sw/device/lib/handler.h
@@ -0,0 +1,19 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _HANDLER_H_
+#define _HANDLER_H_
+
+typedef enum exc_id {
+ kInstMisa = 0,
+ kInstAccFault = 1,
+ kInstIllegalFault = 2,
+ kBkpt = 3,
+ kLoadAccFault = 5,
+ kStrAccFault = 7,
+ kECall = 11,
+ kIdMax = 31
+} exc_id_t;
+
+#endif
diff --git a/sw/device/lib/hmac.c b/sw/device/lib/hmac.c
new file mode 100644
index 0000000..bfad441
--- /dev/null
+++ b/sw/device/lib/hmac.c
@@ -0,0 +1,76 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "hmac.h"
+
+#include "hmac_regs.h" // Generated.
+
+#include "sw/device/lib/common.h"
+
+#define HMAC0_BASE_ADDR 0x40120000
+#define HMAC_FIFO_MAX 16
+#define HMAC_FIFO_GROUP_SIZE HMAC_FIFO_MAX / 2
+
+void hmac_init(hmac_cfg_t hmac_cfg) {
+ REG32(HMAC_CFG(0)) = hmac_cfg.input_endian_swap << HMAC_CFG_ENDIAN_SWAP |
+ 1 << hmac_cfg.mode |
+ hmac_cfg.digest_endian_swap << HMAC_CFG_DIGEST_SWAP;
+ for (int i = 0; i < 8; i++) {
+ REG32(HMAC_KEY0(0) + i * sizeof(uint32_t)) = hmac_cfg.keys[i];
+ }
+ REG32(HMAC_CMD(0)) = 1 << HMAC_CMD_HASH_START;
+};
+
+static int hmac_fifo_depth(void) {
+ return (REG32(HMAC_STATUS(0)) >> HMAC_STATUS_FIFO_DEPTH_OFFSET) &
+ HMAC_STATUS_FIFO_DEPTH_MASK;
+}
+
+static int fifo_avail(void) { return HMAC_FIFO_MAX - hmac_fifo_depth(); }
+
+void hmac_update(const void *data, size_t size_in_bytes) {
+ const uint32_t *wp = (const uint32_t *)data;
+ uint32_t bytes_per_word = sizeof(uint32_t) / sizeof(uint8_t);
+ uint32_t bytes_left_over = (size_in_bytes % bytes_per_word);
+ size_t words_remaining = size_in_bytes / bytes_per_word;
+
+ // write in all words
+ while (words_remaining > 0) {
+ if (words_remaining > HMAC_FIFO_GROUP_SIZE) {
+ // wait until FIFO is at least half drained
+ while (fifo_avail() <= HMAC_FIFO_GROUP_SIZE) {
+ }
+
+ // write a whole group
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ words_remaining -= HMAC_FIFO_GROUP_SIZE;
+ } else {
+ REG32(HMAC_MSG_FIFO(0)) = *wp++;
+ words_remaining--;
+ };
+ }
+
+ const uint8_t *bp = (const uint8_t *)wp;
+ for (; bytes_left_over > 0; --bytes_left_over) {
+ REG8(HMAC_MSG_FIFO(0)) = *bp++;
+ }
+}
+
+void hmac_done(uint32_t *digest) {
+ REG32(HMAC_CMD(0)) = 1 << HMAC_CMD_HASH_PROCESS;
+ while (!((REG32(HMAC_INTR_STATE(0)) >> HMAC_INTR_STATE_HMAC_DONE) & 0x1)) {
+ }
+ REG32(HMAC_INTR_STATE(0)) = 1 << HMAC_INTR_STATE_HMAC_DONE;
+
+ for (uint32_t i = 0; i < 8; i++) {
+ *digest++ = REG32(HMAC_DIGEST0(0) + i * sizeof(uintptr_t));
+ }
+}
diff --git a/sw/device/lib/hmac.h b/sw/device/lib/hmac.h
new file mode 100644
index 0000000..e683977
--- /dev/null
+++ b/sw/device/lib/hmac.h
@@ -0,0 +1,52 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _F_LIB_HMAC_H__
+#define _F_LIB_HMAC_H__
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * Supported HMAC operations
+ */
+typedef enum hmac_ops { HMAC_OP_HMAC = 0, HMAC_OP_SHA256 = 1 } hmac_ops_t;
+
+/**
+ * HMAC Configuration options.
+ */
+typedef struct hmac_cfg {
+ /** Operational mode @see hmac_ops. */
+ hmac_ops_t mode;
+ /** Set to 1 to swap input bytes. */
+ uint32_t input_endian_swap;
+ /** Set to 1 to swap output bytes. */
+ uint32_t digest_endian_swap;
+ /** Input key used in HMAC mode. */
+ uint32_t keys[8];
+} hmac_cfg_t;
+
+/**
+ * Intialize HMAC to desired mode.
+ *
+ * @param hmac_cfg HMAC configuration settings.
+ */
+void hmac_init(hmac_cfg_t hmac_cfg);
+
+/**
+ * Write |size_in_bytes| bytes of |data| to HMAC input buffer
+ *
+ * @param data pointer to input buffer.
+ * @param size_in_bytes number of bytes to write.
+ */
+void hmac_update(const void *data, size_t size_in_bytes);
+
+/**
+ * Poll for hmac done and read out digest.
+ *
+ * @param digest pointer to output digest buffer.
+ */
+void hmac_done(uint32_t *digest);
+
+#endif // _F_LIB_HMAC_H__
diff --git a/sw/device/lib/hw_sha256.c b/sw/device/lib/hw_sha256.c
new file mode 100644
index 0000000..af67cae
--- /dev/null
+++ b/sw/device/lib/hw_sha256.c
@@ -0,0 +1,43 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/hw_sha256.h"
+
+#include "hmac.h"
+
+static const HASH_VTAB HW_SHA256_VTAB = {.init = &hw_SHA256_init,
+ .update = &hw_SHA256_update,
+ .final = &hw_SHA256_final,
+ .hash = &hw_SHA256_hash,
+ .size = SHA256_DIGEST_SIZE};
+
+static void sha256_init() {
+ hmac_cfg_t config = {.mode = HMAC_OP_SHA256,
+ .input_endian_swap = 1,
+ .digest_endian_swap = 1,
+ .keys = {0}};
+ hmac_init(config);
+}
+
+void hw_SHA256_init(HW_SHA256_CTX *ctx) {
+ // TODO: For security, need to make sure HMAC is not stuck in progress.
+ ctx->f = &HW_SHA256_VTAB;
+ sha256_init();
+}
+
+void hw_SHA256_update(HW_SHA256_CTX *ctx, const void *data, size_t len) {
+ hmac_update(data, len);
+}
+
+const uint8_t *hw_SHA256_final(HW_SHA256_CTX *ctx) {
+ hmac_done((uint32_t *)ctx->buf);
+ return ctx->buf;
+}
+
+const uint8_t *hw_SHA256_hash(const void *data, size_t len, uint8_t *digest) {
+ sha256_init();
+ hmac_update(data, len);
+ hmac_done((uint32_t *)digest);
+ return digest;
+}
diff --git a/sw/device/lib/hw_sha256.h b/sw/device/lib/hw_sha256.h
new file mode 100644
index 0000000..36f0651
--- /dev/null
+++ b/sw/device/lib/hw_sha256.h
@@ -0,0 +1,51 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _F_LIB_SHA256_H__
+#define _F_LIB_SHA256_H__
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sw/vendor/cryptoc/hash-internal.h"
+
+typedef HASH_CTX HW_SHA256_CTX;
+
+#define SHA256_DIGEST_SIZE 32
+
+/**
+ * hw_SHA256_init initializes |ctx|.
+ *
+ * @param ctx SHA256 context.
+ */
+void hw_SHA256_init(HW_SHA256_CTX *ctx);
+
+/**
+ * hw_SHA256_update adds |len| bytes from |data| to |ctx|.
+ *
+ * @param ctx SHA256 context.
+ * @param data Input buffer.
+ * @param len Number of bytes to add.
+ */
+void hw_SHA256_update(HW_SHA256_CTX *ctx, const void *data, size_t len);
+
+/**
+ * hw_SHA256_final adds the final padding to |ctx| and calculates digest.
+ *
+ * @param ctx SHA256 context.
+ *
+ * @return pointer to digest buffer held in |ctx|.
+ */
+const uint8_t *hw_SHA256_final(HW_SHA256_CTX *ctx);
+
+/**
+ * hw_SHA256_hash writes |digest| from |len| bytes of |data|.
+ *
+ * @param data Input buffer.
+ * @param len Number of bytes to add.
+ * @param digest Output buffer.
+ */
+const uint8_t *hw_SHA256_hash(const void *data, size_t len, uint8_t *digest);
+
+#endif // _F_LIB_SHA256_H__
diff --git a/sw/device/lib/irq.c b/sw/device/lib/irq.c
new file mode 100644
index 0000000..f57f5e7
--- /dev/null
+++ b/sw/device/lib/irq.c
@@ -0,0 +1,54 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/irq.h"
+
+#include "sw/device/lib/common.h"
+
+static const uint32_t IRQ_EXT_ENABLE_OFFSET = 11;
+static const uint32_t IRQ_TIMER_ENABLE_OFFSET = 7;
+static const uint32_t IRQ_SW_ENABLE_OFFSET = 3;
+
+static void irq_mie_set(uint32_t value) {
+ asm volatile("csrrs zero, mie, %0" : : "r"(value) :);
+}
+
+static void irq_mie_clr(uint32_t value) {
+ asm volatile("csrrc zero, mie, %0" : : "r"(value) :);
+}
+
+void irq_global_ctrl(bool en) {
+ if (en) {
+ asm volatile("csrsi mstatus, 0x8" : : :);
+ } else {
+ asm volatile("csrci mstatus, 0x8" : : :);
+ }
+}
+
+void irq_external_ctrl(bool en) {
+ const uint32_t value = 1 << IRQ_EXT_ENABLE_OFFSET;
+ if (en) {
+ irq_mie_set(value);
+ } else {
+ irq_mie_clr(value);
+ }
+}
+
+void irq_timer_ctrl(bool en) {
+ const uint32_t value = 1 << IRQ_TIMER_ENABLE_OFFSET;
+ if (en) {
+ irq_mie_set(value);
+ } else {
+ irq_mie_clr(value);
+ }
+}
+
+void irq_software_ctrl(bool en) {
+ const uint32_t value = 1 << IRQ_SW_ENABLE_OFFSET;
+ if (en) {
+ irq_mie_set(value);
+ } else {
+ irq_mie_clr(value);
+ }
+}
diff --git a/sw/device/lib/irq.h b/sw/device/lib/irq.h
new file mode 100644
index 0000000..a992889
--- /dev/null
+++ b/sw/device/lib/irq.h
@@ -0,0 +1,36 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _IRQ_H_
+#define _IRQ_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Update to the location of vectors as specificed in the linker file
+ */
+extern void update_mtvec(void);
+
+/**
+ * Enable / disable ibex globlal interrupts
+ */
+void irq_global_ctrl(bool en);
+
+/**
+ * Enable / disable ibex external interrupts
+ */
+void irq_external_ctrl(bool en);
+
+/**
+ * Enable / disable ibex timer interrupts
+ */
+void irq_timer_ctrl(bool en);
+
+/**
+ * Enable / disable ibex software interrupts
+ */
+void irq_software_ctrl(bool en);
+
+#endif
diff --git a/sw/device/lib/irq_vectors.S b/sw/device/lib/irq_vectors.S
new file mode 100644
index 0000000..5d16746
--- /dev/null
+++ b/sw/device/lib/irq_vectors.S
@@ -0,0 +1,36 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+.extern handler_exception
+.extern handler_irq_software
+.extern handler_irq_timer
+.extern handler_irq_external
+.extern _svectors
+
+update_mtvec:
+ .section .text
+ .global update_mtvec
+ la a0, _svectors
+ csrw mtvec, a0
+ ret
+
+exception_handlers:
+ .section .vectors
+ .global vector_handlers
+
+ // exception handler
+ .org 0x0
+ jal x0, handler_exception
+
+ // software interrupt handler
+ .org 0x0c
+ jal x0, handler_irq_software
+
+ // timer interrupt handler
+ .org 0x1c
+ jal x0, handler_irq_timer
+
+ // external interrupt handler
+ .org 0x2c
+ jal x0, handler_irq_external
diff --git a/sw/device/lib/meson.build b/sw/device/lib/meson.build
new file mode 100644
index 0000000..09e1436
--- /dev/null
+++ b/sw/device/lib/meson.build
@@ -0,0 +1,108 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# UART library (sw_lib_uart)
+sw_lib_uart = declare_dependency(
+ sources: [hw_ip_uart_reg_h],
+ link_with: static_library(
+ 'uart_ot',
+ sources: [
+ hw_ip_uart_reg_h,
+ 'uart.c',
+ ]
+ )
+)
+
+
+# Flash controller library (sw_lib_flash_ctrl)
+sw_lib_flash_ctrl = declare_dependency(
+ sources: [hw_ip_flash_ctrl_regs_h],
+ link_with: static_library(
+ 'flash_ctrl_ot',
+ sources: [
+ hw_ip_flash_ctrl_regs_h,
+ 'flash_ctrl.c',
+ ]
+ )
+)
+
+# GPIO library (sw_lib_gpio)
+sw_lib_gpio = declare_dependency(
+ sources: [hw_ip_gpio_reg_h],
+ link_with: static_library(
+ 'gpio_ot',
+ sources: [
+ hw_ip_gpio_reg_h,
+ 'gpio.c',
+ ]
+ )
+)
+
+# HMAC library (sw_lib_hmac)
+sw_lib_hmac = declare_dependency(
+ sources: [hw_ip_hmac_reg_h],
+ link_with: static_library(
+ 'hmac_ot',
+ sources: [
+ hw_ip_hmac_reg_h,
+ 'hw_sha256.c',
+ 'hmac.c',
+ ]
+ )
+)
+
+# IRQ library (sw_lib_irq)
+sw_lib_irq = declare_dependency(
+ link_with: static_library(
+ 'irq_ot',
+ sources: [
+ 'handler.c',
+ 'irq_vectors.S',
+ 'irq.c'
+ ],
+ dependencies: [
+ sw_lib_uart,
+ ]
+ )
+)
+
+# RV_TIMER library (sw_lib_rv_timer)
+sw_lib_rv_timer = declare_dependency(
+ sources: [hw_ip_rv_timer_reg_h],
+ link_with: static_library(
+ 'rv_timer_ot',
+ sources: [
+ hw_ip_rv_timer_reg_h,
+ 'rv_timer.c',
+ ]
+ )
+)
+
+# SPI device library (sw_lib_spi_device)
+sw_lib_spi_device = declare_dependency(
+ link_with: static_library(
+ 'spid_ot',
+ sources: [
+ hw_ip_spi_device_reg_h,
+ 'spi_device.c',
+ ]
+ )
+)
+
+# USB DEV library (sw_lib_usb)
+sw_lib_usb = declare_dependency(
+ sources: [hw_ip_usbdev_reg_h],
+ link_with: static_library(
+ 'usb_ot',
+ sources: [
+ hw_ip_usbdev_reg_h,
+ 'usb_controlep.c',
+ 'usb_simpleserial.c',
+ 'usbdev.c',
+ ],
+ dependencies: [
+ sw_lib_uart,
+ ]
+ )
+)
diff --git a/sw/device/lib/rv_timer.c b/sw/device/lib/rv_timer.c
new file mode 100644
index 0000000..caa8f24
--- /dev/null
+++ b/sw/device/lib/rv_timer.c
@@ -0,0 +1,45 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/rv_timer.h"
+
+#include "rv_timer_regs.h" // Generated.
+
+#include "sw/device/lib/common.h"
+
+#define RV_TIMER0_BASE_ADDR 0x40080000
+#define HART_CFG_ADDR_GAP 0x100
+
+static const uint32_t NS_IN_S = 1000 * 1000 * 1000;
+
+void rv_timer_set_us_tick(uint32_t hart) {
+ uint32_t ticks_per_us = (uint32_t)((1000 * CLK_FIXED_FREQ_HZ) / NS_IN_S) - 1;
+
+ REG32(RV_TIMER_CFG0(0) + hart * 4) =
+ (ticks_per_us & RV_TIMER_CFG0_PRESCALE_MASK) |
+ (1 << RV_TIMER_CFG0_STEP_OFFSET);
+}
+
+void rv_timer_set_cmp(uint32_t hart, uint64_t cmp) {
+ REG32(RV_TIMER_COMPARE_UPPER0_0(0) + hart * HART_CFG_ADDR_GAP) = -1;
+ REG32(RV_TIMER_COMPARE_LOWER0_0(0) + hart * HART_CFG_ADDR_GAP) =
+ (uint32_t)cmp;
+ REG32(RV_TIMER_COMPARE_UPPER0_0(0) + hart * HART_CFG_ADDR_GAP) =
+ (uint32_t)(cmp >> 32);
+}
+
+void rv_timer_ctrl(uint32_t hart, bool en) {
+ REG32(RV_TIMER_CTRL(0)) = (en) ? SETBIT(REG32(RV_TIMER_CTRL(0)), hart)
+ : CLRBIT(REG32(RV_TIMER_CTRL(0)), hart);
+}
+
+void rv_timer_intr_enable(uint32_t hart, bool en) {
+ REG32(RV_TIMER_INTR_ENABLE0(0)) =
+ (en) ? SETBIT(REG32(RV_TIMER_INTR_ENABLE0(0)), hart)
+ : CLRBIT(REG32(RV_TIMER_INTR_ENABLE0(0)), hart);
+}
+
+void rv_timer_clr_all_intrs(void) {
+ REG32(RV_TIMER_INTR_STATE0(0)) = REG32(RV_TIMER_INTR_STATE0(0));
+}
diff --git a/sw/device/lib/rv_timer.h b/sw/device/lib/rv_timer.h
new file mode 100644
index 0000000..0228d0a
--- /dev/null
+++ b/sw/device/lib/rv_timer.h
@@ -0,0 +1,53 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _F_LIB_RV_TIMER_H__
+#define _F_LIB_RV_TIMER_H__
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * Set hart timer prescaler
+ *
+ * Program hart timer prescaler to produce 1us ticks
+ *
+ * @param hart hart selection
+ */
+void rv_timer_set_us_tick(uint32_t hart);
+
+/**
+ * Set hart timer compare value
+ *
+ * Program hart timer compare value. When this value is met, an interrupt will
+ * be triggered.
+ *
+ * @param hart hart selection
+ */
+void rv_timer_set_cmp(uint32_t hart, uint64_t cmp);
+
+/**
+ * Enable hart timer to begin counting
+ *
+ * @param hart hart selection
+ * @param en 1 enables timer, 0 disables timer
+ */
+void rv_timer_ctrl(uint32_t hart, bool en);
+
+/**
+ * Set hart timer interrupt enable
+ *
+ * @param hart hart selection
+ * @param en 1 enables interrupt, 0 disables interrupt
+ */
+void rv_timer_intr_enable(uint32_t hart, bool en);
+
+/**
+ * Clear all active interrupts
+ * Interrupt state clearing is W1C (write-one-clear)
+ */
+void rv_timer_clr_all_intrs(void);
+
+#endif // _F_LIB_RV_TIMER_H__
diff --git a/sw/device/lib/spi_device.c b/sw/device/lib/spi_device.c
new file mode 100644
index 0000000..04caa34
--- /dev/null
+++ b/sw/device/lib/spi_device.c
@@ -0,0 +1,217 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/spi_device.h"
+
+#include "spi_device_regs.h" // Generated.
+
+#include "sw/device/lib/common.h"
+
+#define SPI_DEVICE0_BASE_ADDR 0x40020000
+#define SPID_SRAM_ADDR SPI_DEVICE_BUFFER(0)
+#define SPID_RXF_BASE 0x000
+#define SPID_RXF_SIZE 0x400
+#define SPID_TXF_BASE 0x600
+#define SPID_TXF_SIZE 0x200
+
+#define SPID_SRAM_SIZE (0x800)
+
+/* Note: these will correctly remove the phase bit */
+#define READ32_RXFPTR(P) \
+ REG32(SPID_SRAM_ADDR + SPID_RXF_BASE + ((P) & (SPID_RXF_SIZE - 1)))
+
+#define ACCESS32_TXFPTR(P) \
+ REG32(SPID_SRAM_ADDR + SPID_TXF_BASE + ((P) & ((SPID_TXF_SIZE - 1) & ~0x3)))
+
+uint32_t calc_depth(uint32_t wptr, uint32_t rptr, uint32_t size);
+
+void spid_init(void) {
+ /* Abort 0 */
+ REG32(SPI_DEVICE_CONTROL(0)) =
+ REG32(SPI_DEVICE_CONTROL(0)) & ~(1 << SPI_DEVICE_CONTROL_ABORT);
+
+ /* CPOL(0), CPHA(0), ORDERs(00), TIMER(63) */
+ REG32(SPI_DEVICE_CFG(0)) =
+ ((0 << SPI_DEVICE_CFG_CPOL) | (0 << SPI_DEVICE_CFG_CPHA) |
+ (0 << SPI_DEVICE_CFG_RX_ORDER) | (0 << SPI_DEVICE_CFG_TX_ORDER) |
+ ((63 & SPI_DEVICE_CFG_TIMER_V_MASK) << SPI_DEVICE_CFG_TIMER_V_OFFSET));
+
+ /* SRAM RXF/TXF ADDR. */
+ REG32(SPI_DEVICE_RXF_ADDR(0)) =
+ ((SPID_RXF_BASE & SPI_DEVICE_RXF_ADDR_BASE_MASK)
+ << SPI_DEVICE_RXF_ADDR_BASE_OFFSET) |
+ (((SPID_RXF_SIZE - 1) & SPI_DEVICE_RXF_ADDR_LIMIT_MASK)
+ << SPI_DEVICE_RXF_ADDR_LIMIT_OFFSET);
+
+ REG32(SPI_DEVICE_TXF_ADDR(0)) =
+ ((SPID_TXF_BASE & SPI_DEVICE_TXF_ADDR_BASE_MASK)
+ << SPI_DEVICE_TXF_ADDR_BASE_OFFSET) |
+ (((SPID_TXF_SIZE - 1) & SPI_DEVICE_TXF_ADDR_LIMIT_MASK)
+ << SPI_DEVICE_TXF_ADDR_LIMIT_OFFSET);
+}
+
+/**
+ * Calculation FIFO depth in bytes
+ *
+ * Assume SRAM size is fixed (constant) given by SPI_DEVICE_BUFFER_SIZE
+ *
+ * Fifo pointers are in bytes
+ */
+inline uint32_t calc_depth(uint32_t wptr, uint32_t rptr, uint32_t size) {
+ const uint32_t sram_szw = BITLENGTH(SPI_DEVICE_BUFFER_SIZE_BYTES - 1);
+ uint32_t depth;
+ uint32_t wptr_phase, rptr_phase, wptr_v, rptr_v;
+ wptr_phase = wptr >> sram_szw;
+ rptr_phase = rptr >> sram_szw;
+ wptr_v = wptr & (SPI_DEVICE_BUFFER_SIZE_BYTES - 1);
+ rptr_v = rptr & (SPI_DEVICE_BUFFER_SIZE_BYTES - 1);
+
+ if (wptr_phase == rptr_phase) {
+ depth = (wptr_v - rptr_v);
+ } else {
+ depth = size - rptr_v + wptr_v;
+ }
+
+ return depth;
+}
+
+/*
+ * Increment pointer, zero and flip phase if it gets to size
+ */
+uint32_t ptr_inc(uint32_t ptr, uint32_t inc, uint32_t size) {
+ uint32_t phase = ptr & SPI_DEVICE_BUFFER_SIZE_BYTES;
+ ptr = (ptr & (SPI_DEVICE_BUFFER_SIZE_BYTES - 1)) + inc;
+ if (ptr >= size) {
+ ptr -= size;
+ phase ^= SPI_DEVICE_BUFFER_SIZE_BYTES;
+ }
+ return ptr | phase;
+}
+
+static int word_aligned(void *p) { return (((int)p & 0x3) == 0); }
+
+uint32_t spid_send(void *data, uint32_t len_bytes) {
+ uint32_t txf_ptr, txf_wptr, txf_rptr;
+ uint32_t fifo_inuse_bytes;
+ uint32_t msg_length_bytes;
+
+ /* Check if TXF has enough space */
+ txf_ptr = REG32(SPI_DEVICE_TXF_PTR(0));
+ txf_wptr = (txf_ptr >> SPI_DEVICE_TXF_PTR_WPTR_OFFSET) &
+ SPI_DEVICE_TXF_PTR_WPTR_MASK;
+ txf_rptr = (txf_ptr >> SPI_DEVICE_TXF_PTR_RPTR_OFFSET) &
+ SPI_DEVICE_TXF_PTR_RPTR_MASK;
+
+ fifo_inuse_bytes = calc_depth(txf_wptr, txf_rptr, SPID_TXF_SIZE);
+
+ // Reserve the last 4 bytes in the fifo so it is always safe
+ // to write 32-bit words
+ if (len_bytes < SPID_TXF_SIZE - fifo_inuse_bytes - 4) {
+ // Safe to send all data
+ msg_length_bytes = len_bytes;
+ } else {
+ msg_length_bytes = SPID_TXF_SIZE - fifo_inuse_bytes - 4;
+ }
+ int tocopy = msg_length_bytes;
+
+ // Aligned case can just copy words
+ if (word_aligned(data) && word_aligned((void *)txf_wptr)) {
+ uint32_t *data_w = (uint32_t *)data;
+ while (tocopy > 0) {
+ ACCESS32_TXFPTR(txf_wptr) = *data_w++;
+ if (tocopy >= 4) {
+ txf_wptr = ptr_inc(txf_wptr, 4, SPID_TXF_SIZE);
+ tocopy -= 4;
+ } else {
+ txf_wptr = ptr_inc(txf_wptr, tocopy, SPID_TXF_SIZE);
+ tocopy = 0; // tocopy -= tocopy always gives zero
+ }
+ }
+ } else {
+ // preserve data if unaligned start
+ uint8_t *data_b = (uint8_t *)data;
+ uint32_t d = ACCESS32_TXFPTR(txf_wptr);
+ while (tocopy > 0) {
+ int shift = (txf_wptr & 0x3) * 8;
+ uint32_t mask = 0xff << shift;
+ d = (d & ~mask) | (*data_b++ << shift);
+ if ((txf_wptr & 0x3) == 0x3) {
+ ACCESS32_TXFPTR(txf_wptr) = d;
+ }
+ txf_wptr = ptr_inc(txf_wptr, 1, SPID_TXF_SIZE);
+ tocopy--;
+ }
+ }
+
+ // Write pointer, requires read pointer to be RO
+ REG32(SPI_DEVICE_TXF_PTR(0)) = txf_wptr << SPI_DEVICE_TXF_PTR_WPTR_OFFSET;
+
+ return msg_length_bytes;
+}
+
+uint32_t spid_read_nb(void *data, uint32_t len_bytes) {
+ uint32_t rxf_ptr, rxf_wptr, rxf_rptr;
+ uint32_t msg_len_bytes;
+
+ rxf_ptr = REG32(SPI_DEVICE_RXF_PTR(0));
+ rxf_wptr = (rxf_ptr >> SPI_DEVICE_RXF_PTR_WPTR_OFFSET) &
+ SPI_DEVICE_RXF_PTR_WPTR_MASK;
+ rxf_rptr = (rxf_ptr >> SPI_DEVICE_RXF_PTR_RPTR_OFFSET) &
+ SPI_DEVICE_RXF_PTR_RPTR_MASK;
+
+ msg_len_bytes = calc_depth(rxf_wptr, rxf_rptr, SPID_RXF_SIZE);
+ if (msg_len_bytes == 0) {
+ return 0;
+ }
+ /* Check there is room for the whole buffer */
+ if (msg_len_bytes > len_bytes) {
+ msg_len_bytes = len_bytes;
+ }
+
+ int tocopy = msg_len_bytes;
+ // Aligned case -- which for now it always will be
+ // if tocopy is not multiple of 4 then will read / write extra bytes
+ // so check buffer length
+ if (word_aligned(data) && ((len_bytes & 0x3) == 0) &&
+ word_aligned((void *)rxf_ptr)) {
+ uint32_t *data_w = (uint32_t *)data;
+ while (tocopy > 0) {
+ *data_w++ = READ32_RXFPTR(rxf_rptr);
+ if (tocopy >= 4) {
+ rxf_rptr = ptr_inc(rxf_rptr, 4, SPID_RXF_SIZE);
+ tocopy -= 4;
+ } else {
+ rxf_rptr = ptr_inc(rxf_rptr, tocopy, SPID_RXF_SIZE);
+ tocopy = 0; // tocopy -= tocopy always gives zero
+ }
+ }
+ } else {
+ uint8_t *data_b = (uint8_t *)data;
+ // Have to deal with only being able to do 32-bit accesses
+ int dst = 0;
+ uint32_t d = READ32_RXFPTR(rxf_rptr & ~0x3);
+ while (tocopy--) {
+ int boff = rxf_rptr & 0x3;
+ data_b[dst++] = (d >> (boff * 8)) & 0xff;
+ rxf_rptr = ptr_inc(rxf_rptr, 1, SPID_RXF_SIZE);
+ if (((rxf_rptr & 0x3) == 0) && tocopy) {
+ d = READ32_RXFPTR(rxf_rptr);
+ }
+ }
+ }
+ /* Update read pointer -- NB relies on write pointer being RO */
+ REG32(SPI_DEVICE_RXF_PTR(0)) = rxf_rptr << SPI_DEVICE_RXF_PTR_RPTR_OFFSET;
+
+ return msg_len_bytes;
+}
+
+uint32_t spid_bytes_available(void) {
+ uint32_t rxf_ptr = REG32(SPI_DEVICE_RXF_PTR(0));
+ uint32_t rxf_wptr = (rxf_ptr >> SPI_DEVICE_RXF_PTR_WPTR_OFFSET) &
+ SPI_DEVICE_RXF_PTR_WPTR_MASK;
+ uint32_t rxf_rptr = (rxf_ptr >> SPI_DEVICE_RXF_PTR_RPTR_OFFSET) &
+ SPI_DEVICE_RXF_PTR_RPTR_MASK;
+
+ return calc_depth(rxf_wptr, rxf_rptr, SPID_RXF_SIZE);
+}
diff --git a/sw/device/lib/spi_device.h b/sw/device/lib/spi_device.h
new file mode 100644
index 0000000..2bd12c9
--- /dev/null
+++ b/sw/device/lib/spi_device.h
@@ -0,0 +1,38 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _SPI_DEVICE_H_
+#define _SPI_DEVICE_H_
+
+#include <stdint.h>
+
+/**
+ * Init SPI Device
+ *
+ * Configure registers, RXF_ADDR, TXF_ADDR, CTRL.TIMER_V
+ */
+void spid_init(void);
+
+/**
+ * Send data over SPI
+ *
+ * @param data pointer to buffer of uint_8 to send
+ * @param len_bytes number of bytes to send
+ * @return number of bytes actually sent (<len if no space in the fifo)
+ */
+uint32_t spid_send(void *data, uint32_t len_bytes);
+
+/**
+ * Read the amount of the data from SRAM RX FIFO
+ *
+ * If remained data is smaller than length, it returns only up to data.
+ */
+uint32_t spid_read_nb(void *data, uint32_t len);
+
+/**
+ * Returns the number of bytes available to read on the RX buffer
+ */
+uint32_t spid_bytes_available(void);
+
+#endif /* _SPI_DEVICE_H_ */
diff --git a/sw/device/lib/srcs.mk b/sw/device/lib/srcs.mk
new file mode 100644
index 0000000..3431149
--- /dev/null
+++ b/sw/device/lib/srcs.mk
@@ -0,0 +1,9 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+GEN_HEADERS += $(LIB_LOC_DIF_SRCS:.c=_regs.h) sw/device/boot_rom/chip_info.h
+LIB_LOC_DIF_SRCS += uart.c gpio.c spi_device.c flash_ctrl.c hmac.c usbdev.c rv_timer.c
+LIB_LOC_EXT_SRCS += usb_controlep.c usb_simpleserial.c irq.c handler.c irq_vectors.S
+
+LIB_SRCS += $(addprefix $(LIB_DIR)/, $(LIB_LOC_DIF_SRCS) $(LIB_LOC_EXT_SRCS))
diff --git a/sw/device/lib/uart.c b/sw/device/lib/uart.c
new file mode 100644
index 0000000..f184916
--- /dev/null
+++ b/sw/device/lib/uart.c
@@ -0,0 +1,63 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/uart.h"
+
+#include "sw/device/lib/common.h"
+
+inline void uart_init(unsigned int baud) {
+ // nco = 2^20 * baud / fclk
+ uint64_t uart_ctrl_nco = ((uint64_t)baud << 20) / CLK_FIXED_FREQ_HZ;
+ REG32(UART_CTRL(0)) =
+ ((uart_ctrl_nco & UART_CTRL_NCO_MASK) << UART_CTRL_NCO_OFFSET) |
+ (1 << UART_CTRL_TX) | (1 << UART_CTRL_RX);
+
+ // reset RX/TX FIFOs
+ REG32(UART_FIFO_CTRL(0)) =
+ (1 << UART_FIFO_CTRL_RXRST) | (1 << UART_FIFO_CTRL_TXRST);
+
+ // disable interrupts
+ REG32(UART_INTR_ENABLE(0)) = 0;
+}
+
+static int uart_tx_rdy(void) {
+ return !(REG32(UART_STATUS(0)) & (1 << UART_STATUS_TXFULL));
+}
+
+void uart_send_char(char c) {
+ while (!uart_tx_rdy()) {
+ }
+ REG32(UART_WDATA(0)) = c;
+}
+
+int uart_tx_empty(void) {
+ return !!(REG32(UART_STATUS(0)) & (1 << UART_STATUS_TXEMPTY));
+}
+
+void uart_send_str(char *str) {
+ while (*str != '\0') {
+ uart_send_char(*str++);
+ }
+}
+
+#define hexchar(i) (((i & 0xf) > 9) ? (i & 0xf) - 10 + 'A' : (i & 0xf) + '0')
+
+void uart_send_uint(uint32_t n, int bits) {
+ for (int i = bits - 4; i >= 0; i -= 4) {
+ uart_send_char(hexchar(n >> i));
+ }
+}
+
+int uart_rx_empty(void) {
+ return !!(REG32(UART_STATUS(0)) & (1 << UART_STATUS_RXEMPTY));
+}
+
+int uart_rcv_char(char *c) {
+ if (uart_rx_empty()) {
+ return -1;
+ }
+
+ *c = REG32(UART_RDATA(0));
+ return 0;
+}
diff --git a/sw/device/lib/uart.h b/sw/device/lib/uart.h
new file mode 100644
index 0000000..8a4383f
--- /dev/null
+++ b/sw/device/lib/uart.h
@@ -0,0 +1,37 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _UART_H_
+#define _UART_H_
+
+#include <stdint.h>
+
+#define UART0_BASE_ADDR 0x40000000
+
+#include "uart_regs.h" // Generated.
+
+void uart_send_char(char c);
+
+/**
+ * Send unsigned int over UART
+ */
+void uart_send_uint(uint32_t n, int size);
+void uart_init(unsigned int baud);
+
+/**
+ * Send a NULL-terminated string over UART
+ */
+void uart_send_str(char *str);
+int uart_rx_empty(void);
+int uart_tx_empty(void);
+
+/**
+ * Receive a single character from UART
+ *
+ * @param c received character, caller-allocated
+ * @return 0 on success, -1 if no data is available
+ */
+int uart_rcv_char(char *c);
+
+#endif
diff --git a/sw/device/lib/usb_consts.h b/sw/device/lib/usb_consts.h
new file mode 100644
index 0000000..5e2ebb7
--- /dev/null
+++ b/sw/device/lib/usb_consts.h
@@ -0,0 +1,54 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _USB_CONSTS_H_
+#define _USB_CONSTS_H_
+
+// SETUP requests
+typedef enum usb_setup_req {
+ kUsbSetupReqGetStatus = 0,
+ kUsbSetupReqClearFeature = 1,
+ kUsbSetupReqSetFeature = 3,
+ kUsbSetupReqSetAddress = 5,
+ kUsbSetupReqGetDescriptor = 6,
+ kUsbSetupReqSetDescriptor = 7,
+ kUsbSetupReqGetConfiguration = 8,
+ kUsbSetupReqSetConfiguration = 9,
+ kUsbSetupReqGetInterface = 10,
+ kUsbSetupReqSetInterface = 11,
+ kUsbSetupReqSynchFrame = 12
+} usb_setup_req_t;
+
+typedef enum usb_req_type { // bmRequestType
+ kUsbReqTypeRecipientMask = 0x1f,
+ kUsbReqTypeDevice = 0,
+ kUsbReqTypeInterface = 1,
+ kUsbReqTypeEndpoint = 2,
+ kUsbReqTypeOther = 3,
+ kUsbReqTypeTypeMask = 0x60,
+ KUsbReqTypeStandard = 0,
+ KUsbReqTypeClass = 0x20,
+ KUsbReqTypeVendor = 0x40,
+ KUsbReqTypeReserved = 0x60,
+ kUsbReqTypeDirMask = 0x80,
+ kUsbReqTypeDirH2D = 0x00,
+ kUsbReqTypeDirD2H = 0x80,
+} usb_req_type_t;
+
+typedef enum usb_feature_req {
+ kUsbFeatureEndpointHalt = 0, // recipient is endpoint
+ kUsbFeatureDeviceRemoteWakeup = 1, // recipient is device
+ kUsbFeatureTestMode = 2, // recipient is device
+ kUsbFeatureBHnpEnable = 3, // recipient is device only if OTG
+ kUsbFeatureAHnpSupport = 4, // recipient is device only if OTG
+ kUsbFeatureAAltHnpSupport = 5 // recipient is device only if OTG
+} usb_feature_req_t;
+
+typedef enum usb_status {
+ kUsbStatusSelfPowered = 1, // Device status request
+ kUsbStatusRemWake = 2, // Device status request
+ kUsbStatusHalted = 1 // Endpoint status request
+} usb_status_t;
+
+#endif // _USB_CONSTS_H_
diff --git a/sw/device/lib/usb_controlep.c b/sw/device/lib/usb_controlep.c
new file mode 100644
index 0000000..5ff370a
--- /dev/null
+++ b/sw/device/lib/usb_controlep.c
@@ -0,0 +1,243 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// Get NULL from here
+#include "usb_controlep.h"
+
+#include <stddef.h>
+
+#include "sw/device/lib/common.h"
+#include "usb_consts.h"
+#include "usbdev.h"
+
+// Device descriptor
+static uint8_t dev_dscr[] = {
+ 18, // bLength
+ 1, // bDescriptorType
+ 0x00, // bcdUSB[0]
+ 0x02, // bcdUSB[1]
+ 0x00, // bDeviceClass (defined at interface level)
+ 0x00, // bDeviceSubClass
+ 0x00, // bDeviceProtocol
+ 64, // bMaxPacketSize0
+
+ 0xd1, // idVendor[0] 0x18d1 Google Inc.
+ 0x18, // idVendor[1]
+ 0x3a, // idProduct[0] lowRISC generic FS USB
+ 0x50, // idProduct[1] (allocated by Google)
+
+ 0, // bcdDevice[0]
+ 0x1, // bcdDevice[1]
+ 0, // iManufacturer
+ 0, // iProduct
+ 0, // iSerialNumber
+ 1 // bNumConfigurations
+};
+
+static ctstate_t setup_req(usb_controlep_ctx_t *ctctx, void *ctx,
+ usbbufid_t buf, int bmRequestType, int bRequest,
+ int wValue, int wIndex, int wLength) {
+ size_t len;
+ uint32_t stat;
+ int zero, type;
+ switch (bRequest) {
+ case kUsbSetupReqGetDescriptor:
+ if ((wValue & 0xff00) == 0x100) {
+ // Device descriptor
+ len = sizeof(dev_dscr);
+ if (wLength < len) {
+ len = wLength;
+ }
+ usbdev_buf_copyto_byid(ctx, buf, dev_dscr, len);
+ usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep);
+ return kCtWaitIn;
+ } else if ((wValue & 0xff00) == 0x200) {
+ // Configuration descriptor
+ len = ctctx->cfg_dscr_len;
+ if (wLength < len) {
+ len = wLength;
+ }
+ usbdev_buf_copyto_byid(ctx, buf, ctctx->cfg_dscr, len);
+ usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep);
+ return kCtWaitIn;
+ }
+ return kCtIdle; // unknown
+
+ case kUsbSetupReqSetAddress:
+ ctctx->new_dev = wValue & 0x7f;
+ // send zero length packet for status phase
+ usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep);
+ return kCtAddrStatIn;
+
+ case kUsbSetupReqSetConfiguration:
+ // only ever expect this to be 1 since there is one config descriptor
+ ctctx->usb_config = wValue;
+ // send zero length packet for status phase
+ usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep);
+ return kCtStatIn;
+
+ case kUsbSetupReqGetConfiguration:
+ len = sizeof(ctctx->usb_config);
+ if (wLength < len) {
+ len = wLength;
+ }
+ // return the value that was set
+ usbdev_buf_copyto_byid(ctx, buf, &ctctx->usb_config, len);
+ usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep);
+ return kCtWaitIn;
+
+ case kUsbSetupReqSetFeature:
+ if (wValue == kUsbFeatureEndpointHalt) {
+ usbdev_halt(ctx, wIndex, 1);
+ } else if (wValue == kUsbFeatureDeviceRemoteWakeup) {
+ usbdev_rem_wake_en(ctx, 1);
+ }
+ // send zero length packet for status phase
+ usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep);
+ return kCtStatIn;
+
+ case kUsbSetupReqClearFeature:
+ if (wValue == kUsbFeatureEndpointHalt) {
+ usbdev_halt(ctx, wIndex, 0);
+ } else if (wValue == kUsbFeatureDeviceRemoteWakeup) {
+ usbdev_rem_wake_en(ctx, 0);
+ }
+ // send zero length packet for status phase
+ usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep);
+ return kCtStatIn;
+
+ case kUsbSetupReqGetStatus:
+ len = 2;
+ type = bmRequestType & kUsbReqTypeRecipientMask;
+ if (type == kUsbReqTypeDevice) {
+ stat = (usbdev_can_rem_wake(ctx) ? kUsbStatusRemWake : 0) |
+ kUsbStatusSelfPowered;
+ } else if (type == kUsbReqTypeEndpoint) {
+ stat = usbdev_halted(ctx, wIndex) ? kUsbStatusHalted : 0;
+ } else {
+ stat = 0;
+ }
+ if (wLength < len) {
+ len = wLength;
+ }
+ // return the value that was set
+ usbdev_buf_copyto_byid(ctx, buf, &stat, len);
+ usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep);
+ return kCtWaitIn;
+
+ case kUsbSetupReqSetInterface:
+ // Don't support alternate interfaces, so just ignore
+ // send zero length packet for status phase
+ usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep);
+ return kCtStatIn;
+
+ case kUsbSetupReqGetInterface:
+ zero = 0;
+ len = 1;
+ if (wLength < len) {
+ len = wLength;
+ }
+ // Don't support interface, so return zero
+ usbdev_buf_copyto_byid(ctx, buf, &zero, len);
+ usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep);
+ return kCtWaitIn;
+
+ case kUsbSetupReqSynchFrame:
+ zero = 0;
+ len = 2;
+ if (wLength < len) {
+ len = wLength;
+ }
+ // Don't support synch_frame so return zero
+ usbdev_buf_copyto_byid(ctx, buf, &zero, len);
+ usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep);
+ return kCtWaitIn;
+ }
+ return kCtError;
+}
+
+static void ctrl_tx_done(void *ctctx_v) {
+ usb_controlep_ctx_t *ctctx = (usb_controlep_ctx_t *)ctctx_v;
+ void *ctx = ctctx->ctx;
+ TRC_C('A' + ctctx->ctrlstate);
+ switch (ctctx->ctrlstate) {
+ case kCtAddrStatIn:
+ // Now the status was sent on device 0 can switch to new device ID
+ usbdev_set_deviceid(ctx, ctctx->new_dev);
+ TRC_I(ctctx->new_dev, 8);
+ ctctx->ctrlstate = kCtIdle;
+ return;
+ case kCtStatIn:
+ ctctx->ctrlstate = kCtIdle;
+ return;
+ case kCtWaitIn:
+ ctctx->ctrlstate = kCtStatOut;
+ return;
+ default:
+ break;
+ }
+ TRC_S("USB: unexpected IN ");
+ TRC_I((ctctx->ctrlstate << 24), 32);
+}
+
+static void ctrl_rx(void *ctctx_v, usbbufid_t buf, int size, int setup) {
+ usb_controlep_ctx_t *ctctx = (usb_controlep_ctx_t *)ctctx_v;
+ void *ctx = ctctx->ctx;
+ volatile uint8_t *bp = (volatile uint8_t *)usbdev_buf_idtoaddr(ctx, buf);
+ if (size > BUF_LENGTH) {
+ size = BUF_LENGTH;
+ }
+
+ TRC_C('0' + ctctx->ctrlstate);
+ switch (ctctx->ctrlstate) {
+ case kCtIdle:
+ // Waiting to be set up
+ if (setup && (size == 8)) {
+ int bmRequestType = bp[0];
+ int bRequest = bp[1];
+ int wValue = (bp[3] << 8) | bp[2];
+ int wIndex = (bp[5] << 8) | bp[4];
+ int wLength = (bp[7] << 8) | bp[6];
+ TRC_C('0' + bRequest);
+
+ ctctx->ctrlstate = setup_req(ctctx, ctx, buf, bmRequestType, bRequest,
+ wValue, wIndex, wLength);
+ if (ctctx->ctrlstate != kCtError) {
+ return;
+ }
+ }
+ break;
+
+ case kCtStatOut:
+ // Have sent some data, waiting STATUS stage
+ if (!setup && (size == 0)) {
+ ctctx->ctrlstate = kCtIdle;
+ return;
+ }
+ // anything else is unexpected
+ break;
+
+ default:
+ // Error
+ break;
+ }
+ TRC_S("USB: unCT ");
+ TRC_I((ctctx->ctrlstate << 24) | setup << 16 | size, 32);
+ TRC_C(':');
+ for (int i = 0; i < size; i++) {
+ TRC_I(bp[i], 8);
+ TRC_C(' ');
+ }
+ usbdev_buf_free_byid(ctx, buf);
+ ctctx->ctrlstate = kCtIdle;
+}
+
+void usb_controlep_init(usb_controlep_ctx_t *ctctx, usbdev_ctx_t *ctx, int ep,
+ const uint8_t *cfg_dscr, size_t cfg_dscr_len) {
+ ctctx->ctx = ctx;
+ usbdev_endpoint_setup(ctx, ep, 1, ctctx, ctrl_tx_done, ctrl_rx, NULL);
+ ctctx->ctrlstate = kCtIdle;
+ ctctx->cfg_dscr = cfg_dscr;
+ ctctx->cfg_dscr_len = cfg_dscr_len;
+}
diff --git a/sw/device/lib/usb_controlep.h b/sw/device/lib/usb_controlep.h
new file mode 100644
index 0000000..9e9e218
--- /dev/null
+++ b/sw/device/lib/usb_controlep.h
@@ -0,0 +1,96 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef __USB_CONTROLEP_H__
+#define __USB_CONTROLEP_H__
+#include <stddef.h>
+
+#include "sw/device/lib/common.h"
+#include "usbdev.h"
+
+typedef enum ctstate {
+ kCtIdle,
+ kCtWaitIn, // Queued IN data stage, waiting ack
+ kCtStatOut, // Waiting for OUT status stage
+ kCtAddrStatIn, // Queued status stage, waiting ack afterwhich set dev_addr
+ kCtStatIn, // Queued status stage, waiting ack
+ kCtError // Something bad
+} ctstate_t;
+
+typedef struct usb_controlep_ctx {
+ usbdev_ctx_t *ctx;
+ int ep;
+ ctstate_t ctrlstate;
+ uint32_t new_dev;
+ uint8_t usb_config;
+ const uint8_t *cfg_dscr;
+ size_t cfg_dscr_len;
+} usb_controlep_ctx_t;
+
+/**
+ * Initialize control endpoint
+ *
+ * @param ctctx uninitialized context for this instance
+ * @param ctx initialized context for usbdev driver
+ * @param ep endpoint (if this is other than 0 make sure you know why)
+ * @param cfg_dscr configuration descriptor for the device
+ * @param cfg_dscr_len length of cfg_dscr
+ */
+void usb_controlep_init(usb_controlep_ctx_t *ctctx, usbdev_ctx_t *ctx, int ep,
+ const uint8_t *cfg_dscr, size_t cfg_dscr_len);
+
+/********************************************************************/
+/* Below this point are macros used to construct the USB descriptor */
+/* Use them to initialize a uint8_t array for cfg_dscr */
+
+#define USB_CFG_DSCR_LEN 9
+#define USB_CFG_DSCR_HEAD(total_len, nint) \
+ /* This is the actual configuration descriptor */ \
+ USB_CFG_DSCR_LEN, /* bLength */ \
+ 2, /* bDescriptorType */ \
+ (total_len)&0xff, /* wTotalLength[0] */ \
+ (total_len) >> 8, /* wTotalLength[1] */ \
+ (nint), /* bNumInterfaces */ \
+ 1, /* bConfigurationValu */ \
+ 0, /* iConfiguration */ \
+ 0xC0, /* bmAttributes: must-be-one, self-powered */ \
+ 50, /* bMaxPower */ /* MUST be followed \
+ by (nint) \
+ Interface + \
+ Endpoint \
+ Descriptors */
+
+// KEEP BLANK LINE ABOVE, it is in the macro!
+
+#define USB_INTERFACE_DSCR_LEN 9
+#define VEND_INTERFACE_DSCR(inum, nep, subclass, protocol) \
+ /* interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 */ \
+ USB_INTERFACE_DSCR_LEN, /* bLength */ \
+ 4, /* bDescriptorType */ \
+ (inum), /* bInterfaceNumber */ \
+ 0, /* bAlternateSetting */ \
+ (nep), /* bNumEndpoints */ \
+ 0xff, /* bInterfaceClass (Vendor Specific) */ \
+ (subclass), /* bInterfaceSubClass */ \
+ (protocol), /* bInterfaceProtocol */ \
+ 0, /* iInterface */ /* MUST be followed by \
+ (nep) Endpoint \
+ Descriptors */
+
+// KEEP BLANK LINE ABOVE, it is in the macro!
+
+#define USB_EP_DSCR_LEN 7
+#define USB_BULK_EP_DSCR(in, ep, maxsize, interval) \
+ /* endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 */ \
+ USB_EP_DSCR_LEN, /* bLength */ \
+ 5, /* bDescriptorType */ \
+ (ep) | (((in) << 7) & 0x80), /* bEndpointAddress, top bit set for IN */ \
+ 0x02, /* bmAttributes (0x02=bulk, data) */ \
+ (maxsize)&0xff, /* wMaxPacketSize[0] */ \
+ (maxsize) >> 8, /* wMaxPacketSize[1] */ \
+ (interval), /* bInterval */
+
+// KEEP BLANK LINE ABOVE, it is in the macro!
+
+#endif
diff --git a/sw/device/lib/usb_simpleserial.c b/sw/device/lib/usb_simpleserial.c
new file mode 100644
index 0000000..46229b1
--- /dev/null
+++ b/sw/device/lib/usb_simpleserial.c
@@ -0,0 +1,80 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// Get NULL from here
+#include "usb_simpleserial.h"
+
+#include <stddef.h>
+
+#include "sw/device/lib/common.h"
+#include "usbdev.h"
+
+#define MAX_GATHER 16
+
+static void ss_rx(void *ssctx_v, usbbufid_t buf, int size, int setup) {
+ usb_ss_ctx_t *ssctx = (usb_ss_ctx_t *)ssctx_v;
+ void *ctx = ssctx->ctx;
+ volatile uint8_t *bp = (volatile uint8_t *)usbdev_buf_idtoaddr(ctx, buf);
+
+ if (size > BUF_LENGTH) {
+ size = BUF_LENGTH;
+ }
+
+ while (size--) {
+ ssctx->got_byte(*bp++);
+ }
+}
+
+// Called periodically by the main loop to ensure characters don't
+// stick around too long
+static void ss_flush(void *ssctx_v) {
+ usb_ss_ctx_t *ssctx = (usb_ss_ctx_t *)ssctx_v;
+ void *ctx = ssctx->ctx;
+ volatile uint32_t *bp_w;
+ if ((ssctx->cur_buf == -1) || (ssctx->cur_cpos <= 0)) {
+ return;
+ }
+ if ((ssctx->cur_cpos & 0x3) != 0) {
+ // unwritten data to copy over
+ bp_w = usbdev_buf_idtoaddr(ctx, ssctx->cur_buf);
+ // no -1 here because cpos is in the word we are writing
+ bp_w[(ssctx->cur_cpos / 4)] = ssctx->chold.data_w;
+ }
+ usbdev_sendbuf_byid(ctx, ssctx->cur_buf, ssctx->cur_cpos, ssctx->ep);
+ ssctx->cur_buf = -1; // given it to the hardware
+}
+
+// Simple send byte will gather data for a while and send
+void usb_simpleserial_send_byte(usb_ss_ctx_t *ssctx, uint8_t c) {
+ volatile uint32_t *bp_w;
+ if (ssctx->cur_buf == -1) {
+ ssctx->cur_buf = usbdev_buf_allocate_byid(ssctx->ctx);
+ ssctx->cur_cpos = 0;
+ }
+ // Abort if completely out of buffers and allocation returned -1
+ if (ssctx->cur_buf < 0) {
+ return;
+ }
+ ssctx->chold.data_b[ssctx->cur_cpos++ & 0x3] = c;
+ if ((ssctx->cur_cpos & 0x3) == 0) {
+ // just wrote last byte in word
+ bp_w = usbdev_buf_idtoaddr(ssctx->ctx, ssctx->cur_buf);
+ // -1 here because cpos already incremented to next word
+ bp_w[(ssctx->cur_cpos / 4) - 1] = ssctx->chold.data_w;
+ if (ssctx->cur_cpos >= MAX_GATHER) {
+ usbdev_sendbuf_byid(ssctx->ctx, ssctx->cur_buf, ssctx->cur_cpos,
+ ssctx->ep);
+ ssctx->cur_buf = -1; // given it to the hardware
+ }
+ }
+}
+
+void usb_simpleserial_init(usb_ss_ctx_t *ssctx, usbdev_ctx_t *ctx, int ep,
+ void (*got_byte)(uint8_t)) {
+ usbdev_endpoint_setup(ctx, ep, 1, ssctx, NULL, ss_rx, ss_flush);
+ ssctx->ctx = ctx;
+ ssctx->ep = ep;
+ ssctx->got_byte = got_byte;
+ ssctx->cur_buf = -1;
+}
diff --git a/sw/device/lib/usb_simpleserial.h b/sw/device/lib/usb_simpleserial.h
new file mode 100644
index 0000000..dbb994a
--- /dev/null
+++ b/sw/device/lib/usb_simpleserial.h
@@ -0,0 +1,43 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef __USB_SIMPLESERIAL_H__
+#define __USB_SIMPLESERIAL_H__
+
+#include "sw/device/lib/common.h"
+#include "usbdev.h"
+
+// This is only here because caller of _init needs it
+typedef struct usb_ss_ctx {
+ void *ctx;
+ int ep;
+ int cur_buf;
+ int cur_cpos;
+ union usb_ss_b2w {
+ uint32_t data_w;
+ uint8_t data_b[4];
+ } chold;
+ void (*got_byte)(uint8_t);
+} usb_ss_ctx_t;
+
+/**
+ * Send a byte on a simpleserial endpoint
+ *
+ * @param ssctx instance context
+ * @param c byte to send
+ */
+void usb_simpleserial_send_byte(usb_ss_ctx_t *ssctx, uint8_t c);
+
+/**
+ * Initialize a simpleserial endpoint
+ *
+ * @param ssctx unintialized simpleserial instance context
+ * @param ctx initialized usbdev context
+ * @param ep endpoint number for this instance
+ * @param got_byte callback function for when a byte is received
+ */
+void usb_simpleserial_init(usb_ss_ctx_t *ssctx, usbdev_ctx_t *ctx, int ep,
+ void (*got_byte)(uint8_t));
+
+#endif
diff --git a/sw/device/lib/usbdev.c b/sw/device/lib/usbdev.c
new file mode 100644
index 0000000..eb47297
--- /dev/null
+++ b/sw/device/lib/usbdev.c
@@ -0,0 +1,225 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// Get NULL from here
+#include "usbdev.h"
+
+#include <stddef.h>
+
+#include "sw/device/lib/common.h"
+
+#define USBDEV_BASE_ADDR 0x40020000
+#include "usbdev_regs.h" // Generated.
+
+#define EXTRACT(n, f) ((n >> USBDEV_##f##_OFFSET) & USBDEV_##f##_MASK)
+
+// Free buffer pool is held on a simple stack
+// Initalize to all buffer IDs are free
+static void buf_init(usbdev_ctx_t *ctx) {
+ for (int i = 0; i < NUM_BUFS; i++) {
+ ctx->freebuf[i] = i;
+ }
+ ctx->nfree = NUM_BUFS;
+}
+
+// Allocating a buffer just pops next ID from the stack
+usbbufid_t usbdev_buf_allocate_byid(usbdev_ctx_t *ctx) {
+ if (ctx->nfree <= 0) {
+ return -1;
+ }
+ return ctx->freebuf[--ctx->nfree];
+}
+
+// Freeing a buffer just pushes the ID back on the stack
+int usbdev_buf_free_byid(usbdev_ctx_t *ctx, usbbufid_t buf) {
+ if ((ctx->nfree >= NUM_BUFS) || (buf >= NUM_BUFS)) {
+ return -1;
+ }
+ ctx->freebuf[ctx->nfree++] = buf;
+ return 0;
+}
+
+uint32_t *usbdev_buf_idtoaddr(usbdev_ctx_t *ctx, usbbufid_t buf) {
+ return (uint32_t *)(USBDEV_BUFFER() + (buf * BUF_LENGTH));
+}
+
+void usbdev_buf_copyto_byid(usbdev_ctx_t *ctx, usbbufid_t buf, const void *from,
+ size_t len_bytes) {
+ int32_t *from_word = (int32_t *)from;
+ int len_words;
+ volatile uint32_t *bp = usbdev_buf_idtoaddr(ctx, buf);
+
+ if (len_bytes > BUF_LENGTH) {
+ len_bytes = BUF_LENGTH;
+ }
+ // This will round up if len_bytes is not on a multiple of int32_t
+ // Always ok to fill the extra bytes since the buffers are aligned
+ len_words = (len_bytes + sizeof(int32_t) - 1) / sizeof(int32_t);
+ for (int i = 0; i < len_words; i++) {
+ bp[i] = from_word[i];
+ }
+}
+
+// Supply as many buffers to the receive available fifo as possible
+inline static void fill_av_fifo(usbdev_ctx_t *ctx) {
+ while (!(REG32(USBDEV_USBSTAT()) & (1 << USBDEV_USBSTAT_AV_FULL))) {
+ usbbufid_t buf = usbdev_buf_allocate_byid(ctx);
+ if (buf < 0) {
+ // no more free buffers, can't fill AV FIFO
+ break;
+ }
+ REG32(USBDEV_AVBUFFER()) = buf;
+ }
+}
+
+void usbdev_sendbuf_byid(usbdev_ctx_t *ctx, usbbufid_t buf, size_t size,
+ int endpoint) {
+ uint32_t configin = USBDEV_CONFIGIN0() + (4 * endpoint);
+
+ if ((endpoint >= NUM_ENDPOINTS) || (buf >= NUM_BUFS)) {
+ return;
+ }
+
+ if (size > BUF_LENGTH) {
+ size = BUF_LENGTH;
+ }
+
+ REG32(configin) =
+ ((buf << USBDEV_CONFIGIN0_BUFFER0_OFFSET) |
+ (size << USBDEV_CONFIGIN0_SIZE0_OFFSET) | (1 << USBDEV_CONFIGIN0_RDY0));
+}
+
+void usbdev_poll(usbdev_ctx_t *ctx) {
+ uint32_t istate = REG32(USBDEV_INTR_STATE());
+
+ // Do this first to keep things going
+ fill_av_fifo(ctx);
+
+ // Process IN completions first so we get the fact that send completed
+ // before processing a response
+ if (istate & (1 << USBDEV_INTR_STATE_PKT_SENT)) {
+ uint32_t sentep = REG32(USBDEV_IN_SENT());
+ uint32_t configin = USBDEV_CONFIGIN0();
+ TRC_C('a' + sentep);
+ for (int ep = 0; ep < NUM_ENDPOINTS; ep++) {
+ if (sentep & (1 << ep)) {
+ // Free up the buffer and optionally callback
+ int32_t cfgin = REG32(configin + (4 * ep));
+ usbdev_buf_free_byid(ctx, EXTRACT(cfgin, CONFIGIN0_BUFFER0));
+ if (ctx->tx_done_callback[ep]) {
+ ctx->tx_done_callback[ep](ctx->ep_ctx[ep]);
+ }
+ }
+ }
+ // Write one to clear all the ones we handled
+ REG32(USBDEV_IN_SENT()) = sentep;
+ // Clear the interupt
+ REG32(USBDEV_INTR_STATE()) = (1 << USBDEV_INTR_STATE_PKT_SENT);
+ }
+
+ if (istate & (1 << USBDEV_INTR_STATE_PKT_RECEIVED)) {
+ while (!(REG32(USBDEV_USBSTAT()) & (1 << USBDEV_USBSTAT_RX_EMPTY))) {
+ uint32_t rxinfo = REG32(USBDEV_RXFIFO());
+ usbbufid_t buf = EXTRACT(rxinfo, RXFIFO_BUFFER);
+ int size = EXTRACT(rxinfo, RXFIFO_SIZE);
+ int endpoint = EXTRACT(rxinfo, RXFIFO_EP);
+ int setup = (rxinfo >> USBDEV_RXFIFO_SETUP) & 1;
+
+ if (ctx->rx_callback[endpoint]) {
+ ctx->rx_callback[endpoint](ctx->ep_ctx[endpoint], buf, size, setup);
+ } else {
+ TRC_S("USB: unexpected RX ");
+ TRC_I(rxinfo, 24);
+ }
+ usbdev_buf_free_byid(ctx, buf);
+ }
+ // Clear the interupt
+ REG32(USBDEV_INTR_STATE()) = (1 << USBDEV_INTR_STATE_PKT_RECEIVED);
+ }
+ if (istate & ~((1 << USBDEV_INTR_STATE_PKT_RECEIVED) |
+ (1 << USBDEV_INTR_STATE_PKT_SENT))) {
+ TRC_C('I');
+ TRC_I(istate, 12);
+ TRC_C(' ');
+ REG32(USBDEV_INTR_STATE()) =
+ istate & ~((1 << USBDEV_INTR_STATE_PKT_RECEIVED) |
+ (1 << USBDEV_INTR_STATE_PKT_SENT));
+ }
+ // TODO - clean this up
+ // Frame ticks every 1ms, use to flush data every 16ms
+ // (faster in DPI but this seems to work ok)
+ // At reset frame count is 0, compare to 1 so no calls before SOF received
+ uint32_t usbframe = EXTRACT(REG32(USBDEV_USBSTAT()), USBSTAT_FRAME);
+ if ((usbframe & 0xf) == 1) {
+ if (ctx->flushed == 0) {
+ for (int i = 0; i < NUM_ENDPOINTS; i++) {
+ if (ctx->flush[i]) {
+ ctx->flush[i](ctx->ep_ctx[i]);
+ }
+ }
+ ctx->flushed = 1;
+ }
+ } else {
+ ctx->flushed = 0;
+ }
+ // TODO Errors? What Errors?
+}
+
+void usbdev_set_deviceid(usbdev_ctx_t *ctx, int deviceid) {
+ REG32(USBDEV_USBCTRL()) = (1 << USBDEV_USBCTRL_ENABLE) |
+ (deviceid << USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET);
+}
+
+void usbdev_halt(usbdev_ctx_t *ctx, int endpoint, int enable) {
+ uint32_t epbit = 1 << endpoint;
+ uint32_t stall = REG32(USBDEV_STALL());
+ if (enable) {
+ stall |= epbit;
+ } else {
+ stall &= ~epbit;
+ }
+ REG32(USBDEV_STALL()) = stall;
+ ctx->halted = stall;
+ // TODO future addition would be to callback the endpoint driver
+ // for now it just sees its traffic has stopped
+}
+
+// TODO got hang with this inline
+int usbdev_can_rem_wake(usbdev_ctx_t *ctx) { return ctx->can_wake; }
+
+void usbdev_endpoint_setup(usbdev_ctx_t *ctx, int ep, int enableout,
+ void *ep_ctx, void (*tx_done)(void *),
+ void (*rx)(void *, usbbufid_t, int, int),
+ void (*flush)(void *)) {
+ ctx->ep_ctx[ep] = ep_ctx;
+ ctx->tx_done_callback[ep] = tx_done;
+ ctx->rx_callback[ep] = rx;
+ ctx->flush[ep] = flush;
+ if (enableout) {
+ uint32_t rxen = REG32(USBDEV_RXENABLE());
+ rxen |= (1 << (ep + USBDEV_RXENABLE_OUT0));
+ REG32(USBDEV_RXENABLE()) = rxen;
+ }
+}
+
+void usbdev_init(usbdev_ctx_t *ctx) {
+ // setup context
+ for (int i = 0; i < NUM_ENDPOINTS; i++) {
+ usbdev_endpoint_setup(ctx, i, 0, NULL, NULL, NULL, NULL);
+ }
+ ctx->halted = 0;
+ ctx->can_wake = 0;
+ buf_init(ctx);
+
+ // All about polling...
+ REG32(USBDEV_INTR_ENABLE()) = 0;
+
+ // Provide buffers for any reception
+ fill_av_fifo(ctx);
+
+ REG32(USBDEV_RXENABLE()) =
+ ((1 << USBDEV_RXENABLE_SETUP0) | (1 << USBDEV_RXENABLE_OUT0));
+
+ REG32(USBDEV_USBCTRL()) = (1 << USBDEV_USBCTRL_ENABLE);
+}
diff --git a/sw/device/lib/usbdev.h b/sw/device/lib/usbdev.h
new file mode 100644
index 0000000..4f769f1
--- /dev/null
+++ b/sw/device/lib/usbdev.h
@@ -0,0 +1,183 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _USBDEV_H_
+#define _USBDEV_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+// Hardware parameters
+#define NUM_BUFS 32
+#define BUF_LENGTH 64
+#define NUM_ENDPOINTS 12
+
+// USB buffers are held in the SRAM in the interface, referenced by ID
+// Buffer IDs are 0 to NUM_BUFS
+// Use negative buffer ID for error
+typedef int usbbufid_t;
+typedef struct usbdev_ctx usbdev_ctx_t;
+
+// Note: this is only needed here because the caller of init needs it
+struct usbdev_ctx {
+ // TODO: base_addr goes here once header files support using it
+ int can_wake;
+ uint8_t freebuf[NUM_BUFS];
+ uint32_t halted; // bit vector per endpoint
+ int nfree;
+ int flushed;
+ usbdev_ctx_t *ep_ctx[NUM_ENDPOINTS];
+ void (*tx_done_callback[NUM_ENDPOINTS])(void *);
+ void (*rx_callback[NUM_ENDPOINTS])(void *, usbbufid_t, int, int);
+ void (*flush[NUM_ENDPOINTS])(void *);
+};
+
+/**
+ * Allocate a buffer for the caller to use
+ *
+ * @param ctx usbdev context pointer
+ * @return buffer ID or negative for out of buffer error
+ */
+usbbufid_t usbdev_buf_allocate_byid(usbdev_ctx_t *ctx);
+
+/**
+ * Free a buffer when caller no longer needs it
+ *
+ * @param ctx usbdev context pointer
+ * @param buf buffer ID being returned to free pool
+ * @return 0 or -1 if the free pool is full (shouldn't happen)
+ */
+int usbdev_buf_free_byid(usbdev_ctx_t *ctx, usbbufid_t buf);
+
+/**
+ * Get memory address for accessing data in a buffer
+ *
+ * Hardware restriction: buffer can only be written with 32-bit words
+ * Ok to cast the return value to int8_t * for reading
+ *
+ * @param ctx usbdev context pointer
+ * @param buf buffer ID to access
+ * @return pointer to access the data of @p buf
+ */
+uint32_t *usbdev_buf_idtoaddr(usbdev_ctx_t *ctx, usbbufid_t buf);
+
+/**
+ * Copy from memory into a buffer, referencing by buffer ID
+ *
+ * Implementation restriction: from must be 4-byte aligned
+ * TODO remove restriction
+ *
+ * @param ctx usbdev context pointer
+ * @param buf buffer ID to copy to
+ * @param from source address for data
+ * @param len_bytes length in bytes of data to copy
+ */
+void usbdev_buf_copyto_byid(usbdev_ctx_t *ctx, usbbufid_t buf, const void *from,
+ size_t len_bytes);
+
+/**
+ * Schedule a buffer for transmission on an endpoint
+ *
+ * Send happens on next IN request for that endpoint from the host.
+ * Once this call is made the buffer is owned by the hardware
+ *
+ * @param ctx usbdev context pointer
+ * @param buf buffer ID to send
+ * @param size length in bytes of data to send, zero is valid (used as ack)
+ * @param endpoint endpoint to send from
+ */
+void usbdev_sendbuf_byid(usbdev_ctx_t *ctx, usbbufid_t buf, size_t size,
+ int endpoint);
+
+/**
+ * Call regularly to poll the usbdev interface
+ *
+ * @param ctx usbdev context pointer
+ */
+void usbdev_poll(usbdev_ctx_t *ctx);
+
+/**
+ * Set the USB device ID
+ *
+ * Device ID must be zero at init. When the host assigns an ID
+ * with a SET_ADDRESS packet and the complete SETUP transaction is
+ * complete, this function should be called to set the new ID. Note
+ * on a reset the hardware will clear the device ID back to 0.
+ *
+ * @param usbdev context pointer
+ * @param new deviceid
+ */
+void usbdev_set_deviceid(usbdev_ctx_t *ctx, int deviceid);
+
+/**
+ * Halt or release an endpoint
+ *
+ * By default endpoints are enabled, but they can be halted but the host
+ *
+ * @param usbdev context pointer
+ * @param endpoint number
+ * @param enable set/clear
+ */
+void usbdev_halt(usbdev_ctx_t *ctx, int endpoint, int enable);
+
+/**
+ * Get halted status for an endpoint
+ *
+ * @param usbdev context pointer
+ * @return 1 if endpoint is halted else 0
+ */
+inline int usbdev_halted(usbdev_ctx_t *ctx, int endpoint) {
+ return (ctx->halted >> endpoint) & 0x1;
+}
+
+/**
+ * Enable or disable remote wake
+ *
+ * @param usbdev context pointer
+ * @param enable set/clear
+ */
+inline void usbdev_rem_wake_en(usbdev_ctx_t *ctx, int enable) {
+ ctx->can_wake = (enable) ? 1 : 0;
+}
+
+/**
+ * Get ability to wake the host
+ *
+ * @param usbdev context pointer
+ * @return 1 if remote wake is permitted else 0
+ */
+int usbdev_can_rem_wake(usbdev_ctx_t *ctx);
+
+/**
+ * Call to setup an endpoint
+ *
+ * @param ctx usbdev context pointer
+ * @param ep endpoint number
+ * @param enableout boolean, true to enable OUT transactions on the endpoint
+ * (OUT means host->device, ie receive by us)
+ * @param ep_ctx context pointer for callee
+ * @param tx_done(void *ep_ctx) callback once send has been Acked
+ * @param rx(void *ep_ctx, usbbufid_t buf, int size, int setup)
+ called when a packet is received
+ * @param flush(void *ep_ctx) called every 16ms based USB host timebase
+ */
+void usbdev_endpoint_setup(usbdev_ctx_t *ctx, int ep, int enableout,
+ void *ep_ctx, void (*tx_done)(void *),
+ void (*rx)(void *, usbbufid_t, int, int),
+ void (*flush)(void *));
+
+/**
+ * Initialize the usbdev interface
+ *
+ * @param ctx uninitialized usbdev context pointer
+ */
+void usbdev_init(usbdev_ctx_t *ctx);
+
+// Used for tracing what is going on
+#include "sw/device/lib/uart.h"
+#define TRC_S(s) uart_send_str(s)
+#define TRC_I(i, b) uart_send_uint(i, b)
+#define TRC_C(c) uart_send_char(c)
+
+#endif