Selaa lähdekoodia

projects/riscv_usb: Import DFU firmware

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Sylvain Munaut 5 vuotta sitten
vanhempi
commit
a3065f1629

+ 24 - 2
projects/riscv_usb/fw/Makefile

@@ -30,20 +30,39 @@ SOURCES_common=\
 	usb_ctrl_std.c \
 	utils.c
 
+HEADERS_dfu=\
+	usb_dfu.h \
+	usb_dfu_proto.h \
+	usb_str_dfu.gen.h
+
+SOURCES_dfu=\
+	fw_dfu.c \
+	usb_dfu.c \
+	usb_desc_dfu.c
+
 HEADERS_app=\
+	usb_dfu_rt.h \
+	usb_dfu_proto.h \
 	usb_str_app.gen.h
 
 SOURCES_app=\
 	fw_app.c \
+	usb_dfu_rt.c \
 	usb_desc_app.c
 
 
-all: boot_app.hex fw_app.bin
+all: boot_dfu.hex boot_app.hex fw_dfu.bin fw_app.bin
+
 
+boot_dfu.elf: lnk-boot.lds boot.S
+	$(CC) $(CFLAGS) -Wl,-Bstatic,-T,lnk-boot.lds,--strip-debug -DFLASH_APP_ADDR=0x00060000 -o $@ boot.S
 
 boot_app.elf: lnk-boot.lds boot.S
 	$(CC) $(CFLAGS) -Wl,-Bstatic,-T,lnk-boot.lds,--strip-debug -DFLASH_APP_ADDR=0x000a0000 -o $@ boot.S
 
+fw_dfu.elf: lnk-app.lds $(HEADERS_dfu) $(SOURCES_dfu) $(HEADERS_common) $(SOURCES_common)
+	$(CC) $(CFLAGS) -Wl,-Bstatic,-T,lnk-app.lds,--strip-debug -o $@ $(SOURCES_common) $(SOURCES_dfu)
+
 fw_app.elf: lnk-app.lds $(HEADERS_app) $(SOURCES_app) $(HEADERS_common) $(SOURCES_common)
 	$(CC) $(CFLAGS) -Wl,-Bstatic,-T,lnk-app.lds,--strip-debug -o $@ $(SOURCES_common) $(SOURCES_app)
 
@@ -58,6 +77,9 @@ usb_str_%.gen.h: usb_str_%.txt
 	./usb_gen_strings.py $< $@ $(BOARD)
 
 
+prog_dfu: fw_dfu.bin
+	$(ICEPROG) -o 384k $<
+
 prog_app: fw_app.bin
 	$(ICEPROG) -o 640k $<
 
@@ -65,4 +87,4 @@ prog_app: fw_app.bin
 clean:
 	rm -f *.bin *.hex *.elf *.o *.gen.h
 
-.PHONY: prog_app clean
+.PHONY: prog_dfu prog_app clean

+ 22 - 0
projects/riscv_usb/fw/fw_app.c

@@ -30,6 +30,7 @@
 #include "mini-printf.h"
 #include "spi.h"
 #include "usb.h"
+#include "usb_dfu_rt.h"
 #include "utils.h"
 
 
@@ -56,6 +57,23 @@ serial_no_init()
 		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();
+}
+
 void main()
 {
 	int cmd = 0;
@@ -77,6 +95,7 @@ void main()
 	/* Enable USB directly */
 	serial_no_init();
 	usb_init(&app_stack_desc);
+	usb_dfu_rt_init();
 
 	/* Main loop */
 	while (1)
@@ -100,6 +119,9 @@ void main()
 			case 'p':
 				usb_debug_print();
 				break;
+			case 'b':
+				boot_dfu();
+				break;
 			case 'c':
 				usb_connect();
 				break;

+ 140 - 0
projects/riscv_usb/fw/fw_dfu.c

@@ -0,0 +1,140 @@
+/*
+ * fw_dfu.c
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "console.h"
+#include "led.h"
+#include "mini-printf.h"
+#include "spi.h"
+#include "usb.h"
+#include "usb_dfu.h"
+#include "utils.h"
+
+
+extern const struct usb_stack_descriptors dfu_stack_desc;
+
+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*)dfu_stack_desc.str[1];
+	for (i=0; i<16; i++)
+		desc[2 + (i << 1)] = id[i];
+}
+
+static void
+boot_app(void)
+{
+	/* Force re-enumeration */
+	usb_disconnect();
+
+	/* Boot firmware */
+	volatile uint32_t *boot = (void*)0x80000000;
+	*boot = (1 << 2) | (2 << 0);
+}
+
+void
+usb_dfu_cb_reboot(void)
+{
+	boot_app();
+}
+
+void main()
+{
+	int cmd = 0;
+
+	/* Init console IO */
+	console_init();
+	puts("Booting DFU image..\n");
+
+	/* LED */
+	led_init();
+	led_color(72, 64, 0);
+	led_blink(true, 150, 150);
+	led_breathe(true, 50, 100);
+	led_state(true);
+
+	/* SPI */
+	spi_init();
+
+	/* Enable USB directly */
+	serial_no_init();
+	usb_init(&dfu_stack_desc);
+	usb_dfu_init();
+	usb_connect();
+
+	/* 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 'p':
+				usb_debug_print();
+				break;
+			case 'c':
+				usb_connect();
+				break;
+			case 'd':
+				usb_disconnect();
+				break;
+			case 'b':
+				boot_app();
+				break;
+			default:
+				break;
+			}
+		}
+
+		/* USB poll */
+		usb_poll();
+	}
+}

+ 117 - 0
projects/riscv_usb/fw/usb_desc_dfu.c

@@ -0,0 +1,117 @@
+/*
+ * usb_desc_dfu.c
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "usb_proto.h"
+#include "usb.h"
+
+#define NULL ((void*)0)
+#define num_elem(a) (sizeof(a) / sizeof(a[0]))
+
+
+static const struct {
+	struct usb_conf_desc conf;
+	struct usb_intf_desc if_fpga;
+	struct usb_dfu_desc dfu_fpga;
+	struct usb_intf_desc if_riscv;
+	struct usb_dfu_desc dfu_riscv;
+} __attribute__ ((packed)) _dfu_conf_desc = {
+	.conf = {
+		.bLength                = sizeof(struct usb_conf_desc),
+		.bDescriptorType        = USB_DT_CONF,
+		.wTotalLength           = sizeof(_dfu_conf_desc),
+		.bNumInterfaces         = 1,
+		.bConfigurationValue    = 1,
+		.iConfiguration         = 4,
+		.bmAttributes           = 0x80,
+		.bMaxPower              = 0x32, /* 100 mA */
+	},
+	.if_fpga = {
+		.bLength		= sizeof(struct usb_intf_desc),
+		.bDescriptorType	= USB_DT_INTF,
+		.bInterfaceNumber	= 0,
+		.bAlternateSetting	= 0,
+		.bNumEndpoints		= 0,
+		.bInterfaceClass	= 0xfe,
+		.bInterfaceSubClass	= 0x01,
+		.bInterfaceProtocol	= 0x02,
+		.iInterface		= 5,
+	},
+	.dfu_fpga = {
+		.bLength		= sizeof(struct usb_dfu_desc),
+		.bDescriptorType	= USB_DT_DFU,
+		.bmAttributes		= 0x0d,
+		.wDetachTimeOut		= 1000,
+		.wTransferSize		= 4096,
+		.bcdDFUVersion		= 0x0101,
+	},
+	.if_riscv = {
+		.bLength		= sizeof(struct usb_intf_desc),
+		.bDescriptorType	= USB_DT_INTF,
+		.bInterfaceNumber	= 0,
+		.bAlternateSetting	= 1,
+		.bNumEndpoints		= 0,
+		.bInterfaceClass	= 0xfe,
+		.bInterfaceSubClass	= 0x01,
+		.bInterfaceProtocol	= 0x02,
+		.iInterface		= 6,
+	},
+	.dfu_riscv = {
+		.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[] = {
+	&_dfu_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		= 0x6146,
+	.bcdDevice		= 0x0004,	/* v0.4 */
+	.iManufacturer		= 2,
+	.iProduct		= 3,
+	.iSerialNumber		= 1,
+	.bNumConfigurations	= num_elem(_conf_desc_array),
+};
+
+#include "usb_str_dfu.gen.h"
+
+const struct usb_stack_descriptors dfu_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),
+};

+ 377 - 0
projects/riscv_usb/fw/usb_dfu.c

@@ -0,0 +1,377 @@
+/*
+ * usb_dfu.c
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "spi.h"
+#include "usb.h"
+#include "usb_dfu.h"
+#include "usb_dfu_proto.h"
+
+
+#define DFU_POLL_MS		250
+
+
+static const uint32_t dfu_valid_req[_DFU_MAX_STATE] = {
+	/* appIDLE */
+	(1 << USB_REQ_DFU_DETACH) |
+	(1 << USB_REQ_DFU_GETSTATUS) |
+	(1 << USB_REQ_DFU_GETSTATE) |
+	0,
+
+	/* appDETACH */
+	(1 << USB_REQ_DFU_GETSTATUS) |
+	(1 << USB_REQ_DFU_GETSTATE) |
+	0,
+
+	/* dfuIDLE */
+	(1 << USB_REQ_DFU_DETACH) |		/* Non-std */
+	(1 << USB_REQ_DFU_DNLOAD) |
+	(1 << USB_REQ_DFU_UPLOAD) |
+	(1 << USB_REQ_DFU_GETSTATUS) |
+	(1 << USB_REQ_DFU_GETSTATE) |
+	(1 << USB_REQ_DFU_ABORT) |
+	0,
+
+	/* dfuDNLOAD_SYNC */
+	(1 << USB_REQ_DFU_DNLOAD) |
+	(1 << USB_REQ_DFU_GETSTATUS) |
+	(1 << USB_REQ_DFU_GETSTATE) |
+	(1 << USB_REQ_DFU_ABORT) |
+	0,
+
+	/* dfuDNBUSY */
+	0,
+
+	/* dfuDNLOAD_IDLE */
+	(1 << USB_REQ_DFU_DNLOAD) |
+	(1 << USB_REQ_DFU_GETSTATUS) |
+	(1 << USB_REQ_DFU_GETSTATE) |
+	(1 << USB_REQ_DFU_ABORT) |
+	0,
+
+	/* dfuMANIFEST_SYNC */
+	(1 << USB_REQ_DFU_GETSTATUS) |
+	(1 << USB_REQ_DFU_GETSTATE) |
+	(1 << USB_REQ_DFU_ABORT) |
+	0,
+
+	/* dfuMANIFEST */
+	0,
+
+	/* dfuMANIFEST_WAIT_RESET */
+	0,
+
+	/* dfuUPLOAD_IDLE */
+	(1 << USB_REQ_DFU_UPLOAD) |
+	(1 << USB_REQ_DFU_GETSTATUS) |
+	(1 << USB_REQ_DFU_GETSTATE) |
+	(1 << USB_REQ_DFU_ABORT) |
+	0,
+
+	/* dfuERROR */
+	(1 << USB_REQ_DFU_GETSTATUS) |
+	(1 << USB_REQ_DFU_CLRSTATUS) |
+	(1 << USB_REQ_DFU_GETSTATE) |
+	0,
+};
+
+static struct {
+	enum dfu_state state;
+	enum dfu_status status;
+
+	uint8_t tick;
+	uint8_t intf;	// Selected interface number
+	uint8_t alt;	// Selected alt settings
+
+	uint8_t buf[4096] __attribute__((aligned(4)));
+
+	struct {
+		uint32_t addr_prog;
+		uint32_t addr_erase;
+		uint32_t addr_end;
+
+		int op_ofs;
+		int op_len;
+
+		enum {
+			FL_IDLE = 0,
+			FL_ERASE,
+			FL_PROGRAM,
+		} op;
+	} flash;
+} g_dfu;
+
+static const struct {
+	uint32_t start;
+	uint32_t end;
+} dfu_zones[2] = {
+	{ 0x00080000, 0x000a0000 },	/* iCE40 bitstream */
+	{ 0x000a0000, 0x000c0000 },	/* RISC-V firmware */
+};
+
+
+static void
+_dfu_tick(void)
+{
+	/* Rate limit to once every 10 ms */
+	if (g_dfu.tick++ < 10)
+		return;
+
+	g_dfu.tick = 0;
+
+	/* Anything to do ? Is flash ready ? */
+	if ((g_dfu.flash.op == FL_IDLE) || (flash_read_sr() & 1))
+		return;
+
+	/* Erase */
+	if (g_dfu.flash.op == FL_ERASE) {
+		/* Done ? */
+		if (g_dfu.flash.addr_erase >= (g_dfu.flash.addr_prog + g_dfu.flash.op_len)) {
+			/* Yes, move to programming */
+			g_dfu.flash.op = FL_PROGRAM;
+		} else{
+			/* No, issue the next command */
+			flash_write_enable();
+			flash_sector_erase(g_dfu.flash.addr_erase);
+			g_dfu.flash.addr_erase += 4096;
+		}
+	}
+
+	/* Programming */
+	if ((g_dfu.flash.op == FL_PROGRAM) && (g_dfu.state == dfuDNLOAD_SYNC)) {
+		/* Done ? */
+		if (g_dfu.flash.op_ofs == g_dfu.flash.op_len) {
+			/* Yes ! */
+			g_dfu.flash.op = FL_IDLE;
+			g_dfu.state = dfuDNLOAD_IDLE;
+			g_dfu.flash.addr_prog += g_dfu.flash.op_len;
+		} else {
+			/* Max len */
+			unsigned l = g_dfu.flash.op_len - g_dfu.flash.op_ofs;
+			unsigned pl = 256 - ((g_dfu.flash.addr_prog + g_dfu.flash.op_ofs) & 0xff);
+			if (l > pl)
+				l = pl;
+
+			/* Write page */
+			flash_write_enable();
+			flash_page_program(&g_dfu.buf[g_dfu.flash.op_ofs], g_dfu.flash.addr_prog + g_dfu.flash.op_ofs, l);
+
+			/* Next page */
+			g_dfu.flash.op_ofs += l;
+		}
+	}
+}
+
+static void
+_dfu_bus_reset(void)
+{
+	if (g_dfu.state != appDETACH)
+		usb_dfu_cb_reboot();
+}
+
+static void
+_dfu_state_chg(enum usb_dev_state state)
+{
+	if (state == USB_DS_CONFIGURED)
+		g_dfu.state = dfuIDLE;
+}
+
+static bool
+_dfu_detach_done_cb(struct usb_xfer *xfer)
+{
+	usb_dfu_cb_reboot();
+	return true;
+}
+
+static bool
+_dfu_dnload_done_cb(struct usb_xfer *xfer)
+{
+	/* State update */
+	g_dfu.state = dfuDNLOAD_SYNC;
+
+	return true;
+}
+
+static enum usb_fnd_resp
+_dfu_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	uint8_t state;
+
+	/* If this a class request for DFU interface ? */
+	if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF))
+		return USB_FND_CONTINUE;
+
+	if (req->wIndex != g_dfu.intf)
+		return USB_FND_CONTINUE;
+
+	/* Check if this request is allowed in this state */
+	if ((dfu_valid_req[g_dfu.state] & (1 << req->bRequest)) == 0)
+		goto error;
+
+	/* Handle request */
+	switch (req->wRequestAndType)
+	{
+	case USB_RT_DFU_DETACH:
+		/* In theory this should be in runtime mode only but we support
+		 * it as a request to reboot to user mode when in DFU mode */
+		xfer->cb_done = _dfu_detach_done_cb;
+		break;
+
+	case USB_RT_DFU_DNLOAD:
+		/* Check for last block */
+		if (req->wLength) {
+			/* Check length doesn't overflow */
+			if ((g_dfu.flash.addr_erase + req->wLength) > g_dfu.flash.addr_end)
+				goto error;
+
+			/* Setup buffer for data */
+			xfer->len     = req->wLength;
+			xfer->data    = g_dfu.buf;
+			xfer->cb_done = _dfu_dnload_done_cb;
+
+			/* Prepare flash */
+			g_dfu.flash.op_ofs = 0;
+			g_dfu.flash.op_len = req->wLength;
+			g_dfu.flash.op     = FL_ERASE;
+		} else {
+			/* Last xfer */
+			g_dfu.state = dfuIDLE;
+		}
+		break;
+
+	case USB_RT_DFU_UPLOAD:
+		/* Not supported */
+		goto error;
+
+	case USB_RT_DFU_GETSTATUS:
+		/* Update state */
+		if (g_dfu.state == dfuDNLOAD_SYNC) {
+			if (g_dfu.flash.op == FL_IDLE) {
+				g_dfu.state = state = dfuDNLOAD_IDLE;
+			} else {
+				state = dfuDNBUSY;
+			}
+		} else if (g_dfu.state == dfuMANIFEST_SYNC) {
+			g_dfu.state = state = dfuIDLE;
+		} else {
+			state = g_dfu.state;
+		}
+
+		/* Return data */
+		xfer->data[0] = g_dfu.status;
+		xfer->data[1] = (DFU_POLL_MS >>  0) & 0xff;
+		xfer->data[2] = (DFU_POLL_MS >>  8) & 0xff;
+		xfer->data[3] = (DFU_POLL_MS >> 16) & 0xff;
+		xfer->data[4] = state;
+		xfer->data[5] = 0;
+		break;
+
+	case USB_RT_DFU_CLRSTATUS:
+		/* Clear error */
+		g_dfu.state = dfuIDLE;
+		g_dfu.status = OK;
+		break;
+
+	case USB_RT_DFU_GETSTATE:
+		/* Return state */
+		xfer->data[0] = g_dfu.state;
+		break;
+
+	case USB_RT_DFU_ABORT:
+		/* Go to IDLE */
+		g_dfu.state = dfuIDLE;
+		break;
+
+	default:
+		goto error;
+	}
+
+	return USB_FND_SUCCESS;
+
+error:
+	g_dfu.state  = dfuERROR;
+	g_dfu.status = errUNKNOWN;
+	return USB_FND_ERROR;
+}
+
+static enum usb_fnd_resp
+_dfu_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel)
+{
+	if ((sel->bInterfaceClass != 0xfe) ||
+	    (sel->bInterfaceSubClass != 0x01) ||
+	    (sel->bInterfaceProtocol != 0x02))
+		return USB_FND_CONTINUE;
+
+	g_dfu.state = dfuIDLE;
+	g_dfu.intf  = sel->bInterfaceNumber;
+	g_dfu.alt   = sel->bAlternateSetting;
+
+	g_dfu.flash.addr_prog  = dfu_zones[g_dfu.alt].start;
+	g_dfu.flash.addr_erase = dfu_zones[g_dfu.alt].start;
+	g_dfu.flash.addr_end   = dfu_zones[g_dfu.alt].end;
+
+	return USB_FND_SUCCESS;
+}
+
+static enum usb_fnd_resp
+_dfu_get_intf(const struct usb_intf_desc *base, uint8_t *alt)
+{
+	if ((base->bInterfaceClass != 0xfe) ||
+	    (base->bInterfaceSubClass != 0x01) ||
+	    (base->bInterfaceProtocol != 0x02))
+		return USB_FND_CONTINUE;
+
+	*alt = g_dfu.alt;
+
+	return USB_FND_SUCCESS;
+}
+
+
+static struct usb_fn_drv _dfu_drv = {
+	.sof		= _dfu_tick,
+	.bus_reset      = _dfu_bus_reset,
+	.state_chg	= _dfu_state_chg,
+	.ctrl_req	= _dfu_ctrl_req,
+	.set_intf	= _dfu_set_intf,
+	.get_intf	= _dfu_get_intf,
+};
+
+
+void __attribute__((weak))
+usb_dfu_cb_reboot(void)
+{
+	/* Nothing */
+}
+
+void
+usb_dfu_init(void)
+{
+	memset(&g_dfu, 0x00, sizeof(g_dfu));
+
+	g_dfu.state = appDETACH;
+
+	usb_register_function_driver(&_dfu_drv);
+}

+ 27 - 0
projects/riscv_usb/fw/usb_dfu.h

@@ -0,0 +1,27 @@
+/*
+ * usb_dfu.h
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+void usb_dfu_cb_reboot(void);
+void usb_dfu_init(void);

+ 76 - 0
projects/riscv_usb/fw/usb_dfu_proto.h

@@ -0,0 +1,76 @@
+/*
+ * usb_dfu_proto.h
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#define USB_REQ_DFU_DETACH	(0)
+#define USB_REQ_DFU_DNLOAD	(1)
+#define USB_REQ_DFU_UPLOAD	(2)
+#define USB_REQ_DFU_GETSTATUS	(3)
+#define USB_REQ_DFU_CLRSTATUS	(4)
+#define USB_REQ_DFU_GETSTATE	(5)
+#define USB_REQ_DFU_ABORT	(6)
+
+#define USB_RT_DFU_DETACH	((0 << 8) | 0x21)
+#define USB_RT_DFU_DNLOAD	((1 << 8) | 0x21)
+#define USB_RT_DFU_UPLOAD	((2 << 8) | 0xa1)
+#define USB_RT_DFU_GETSTATUS	((3 << 8) | 0xa1)
+#define USB_RT_DFU_CLRSTATUS	((4 << 8) | 0x21)
+#define USB_RT_DFU_GETSTATE	((5 << 8) | 0xa1)
+#define USB_RT_DFU_ABORT	((6 << 8) | 0x21)
+
+
+enum dfu_state {
+	appIDLE = 0,
+	appDETACH,
+	dfuIDLE,
+	dfuDNLOAD_SYNC,
+	dfuDNBUSY,
+	dfuDNLOAD_IDLE,
+	dfuMANIFEST_SYNC,
+	dfuMANIFEST,
+	dfuMANIFEST_WAIT_RESET,
+	dfuUPLOAD_IDLE,
+	dfuERROR,
+	_DFU_MAX_STATE
+};
+
+enum dfu_status {
+	OK = 0,
+	errTARGET,
+	errFILE,
+	errWRITE,
+	errERASE,
+	errCHECK_ERASED,
+	errPROG,
+	errVERIFY,
+	errADDRESS,
+	errNOTDONE,
+	errFIRMWARE,
+	errVENDOR,
+	errUSBR,
+	errPOR,
+	errUNKNOWN,
+	errSTALLEDPKT,
+	_DFU_MAX_STATUS
+};

+ 114 - 0
projects/riscv_usb/fw/usb_dfu_rt.c

@@ -0,0 +1,114 @@
+/*
+ * usb_dfu_rt.c
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "usb.h"
+#include "usb_dfu_rt.h"
+#include "usb_dfu_proto.h"
+
+
+#define DFU_POLL_MS		250
+
+static int g_dfu_rt_intf;
+
+static bool
+_dfu_detach_done_cb(struct usb_xfer *xfer)
+{
+        usb_dfu_rt_cb_reboot();
+        return true;
+}
+
+static enum usb_fnd_resp
+_dfu_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	/* If this a class request for DFU interface ? */
+	if ((USB_REQ_TYPE(req) | USB_REQ_RCPT(req)) != (USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF))
+		return USB_FND_CONTINUE;
+
+	if (req->wIndex != g_dfu_rt_intf)
+		return USB_FND_CONTINUE;
+
+	/* Handle request */
+	switch (req->wRequestAndType)
+	{
+	case USB_RT_DFU_DETACH:
+		xfer->cb_done = _dfu_detach_done_cb;
+		break;
+
+	case USB_RT_DFU_GETSTATUS:
+		/* Return data */
+		xfer->data[0] = OK;
+		xfer->data[1] = (DFU_POLL_MS >>  0) & 0xff;
+		xfer->data[2] = (DFU_POLL_MS >>  8) & 0xff;
+		xfer->data[3] = (DFU_POLL_MS >> 16) & 0xff;
+		xfer->data[4] = appIDLE;
+		xfer->data[5] = 0;
+		break;
+
+	case USB_RT_DFU_GETSTATE:
+		/* Return state */
+		xfer->data[0] = appIDLE;
+		break;
+
+	default:
+		return USB_FND_ERROR;
+	}
+
+	return USB_FND_SUCCESS;
+}
+
+static enum usb_fnd_resp
+_dfu_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel)
+{
+	if ((sel->bInterfaceClass != 0xfe) ||
+	    (sel->bInterfaceSubClass != 0x01) ||
+	    (sel->bInterfaceProtocol != 0x01))
+		return USB_FND_CONTINUE;
+
+	g_dfu_rt_intf = base->bInterfaceNumber;
+
+	return USB_FND_SUCCESS;
+}
+
+
+static struct usb_fn_drv _dfu_rt_drv = {
+	.ctrl_req	= _dfu_ctrl_req,
+	.set_intf	= _dfu_set_intf,
+};
+
+
+void __attribute__((weak))
+usb_dfu_rt_cb_reboot(void)
+{
+	/* Nothing */
+}
+
+void
+usb_dfu_rt_init(void)
+{
+	usb_register_function_driver(&_dfu_rt_drv);
+	g_dfu_rt_intf = -1;
+}

+ 27 - 0
projects/riscv_usb/fw/usb_dfu_rt.h

@@ -0,0 +1,27 @@
+/*
+ * usb_dfu_rt.h
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+void usb_dfu_rt_cb_reboot(void);
+void usb_dfu_rt_init(void);

+ 6 - 0
projects/riscv_usb/fw/usb_str_dfu.txt

@@ -0,0 +1,6 @@
+0000000000000000
+osmocom
+!{"bitsy": "iCEBreaker-bitsy (DFU)", "": "iCE40 USB Device (DFU)"}
+DFU
+iCE40 bitstream
+RISC-V firmware