fw/e1-tracer: Additional USB configuration for "osmo-e1d compatible mode"

This adds a second USB configuration to the e1-tracer firmware.  This
configuration is closer to the USB configuration of an icE1usb and hence
paves the way for using osmo-e1d with the tracer.

The main conceptual difference between the existing "legacy"
configuration and this new "e1d compatible" configuration is to have two
USB interfaces, one for each direction of the traced E1 interface.  Each
interface has its own separate two altsettings, one for the disabled
and one for the enabled state.

Unmodified osmo-e1d will not work straight away with this, as it expects
ISO OUT and ISU Feedback endpoints, which a pure rx-only tracing device
of course doesn't have.

Related: OS#5733
Change-Id: I97062b9f12317b1b9b3855409c2380108cb921ff
diff --git a/firmware/ice40-riscv/e1-tracer/usb_desc_app.c b/firmware/ice40-riscv/e1-tracer/usb_desc_app.c
index 5109000..e9aa9d6 100644
--- a/firmware/ice40-riscv/e1-tracer/usb_desc_app.c
+++ b/firmware/ice40-riscv/e1-tracer/usb_desc_app.c
@@ -13,10 +13,9 @@
 #define num_elem(a) (sizeof(a) / sizeof(a[0]))
 
 
+/* Legacy Configuration */
 static const struct {
-	/* Configuration */
 	struct usb_conf_desc conf;
-
 	/* E1 */
 	struct {
 		struct {
@@ -30,7 +29,6 @@
 			struct usb_ep_desc ep_data_in1;
 		} __attribute__ ((packed)) on;
 	} __attribute__ ((packed)) e1;
-
 	/* DFU Runtime */
 	struct {
 		struct usb_intf_desc intf;
@@ -130,8 +128,117 @@
 	},
 };
 
+/* "icE1usb" compatible Configuration */
+static const struct {
+	struct usb_conf_desc conf;
+	/* Interface / Direction A */
+	struct {
+		struct {
+			struct usb_intf_desc intf;
+		} __attribute__ ((packed)) off;
+		struct {
+			struct usb_intf_desc intf;
+			struct usb_ep_desc ep_data_in0;
+		} __attribute__ ((packed)) on;
+	} __attribute__ ((packed)) a;
+	/* Interface / Direction B */
+	struct {
+		struct {
+			struct usb_intf_desc intf;
+		} __attribute__ ((packed)) off;
+		struct {
+			struct usb_intf_desc intf;
+			struct usb_ep_desc ep_data_in1;
+		} __attribute__ ((packed)) on;
+	} __attribute__ ((packed)) b;
+} __attribute__ ((packed)) _app_conf_desc_e1d = {
+	.conf = {
+		.bLength                = sizeof(struct usb_conf_desc),
+		.bDescriptorType        = USB_DT_CONF,
+		.wTotalLength           = sizeof(_app_conf_desc_e1d),
+		.bNumInterfaces         = 3,
+		.bConfigurationValue    = 2,
+		.iConfiguration         = 7,
+		.bmAttributes           = 0x80,
+		.bMaxPower              = 0x32, /* 100 mA */
+	},
+	.a = {
+		.off = {
+			.intf = {
+				.bLength		= sizeof(struct usb_intf_desc),
+				.bDescriptorType	= USB_DT_INTF,
+				.bInterfaceNumber	= 0,
+				.bAlternateSetting	= 0,
+				.bNumEndpoints		= 0,
+				.bInterfaceClass	= 0xff,
+				.bInterfaceSubClass	= 0xe1,
+				.bInterfaceProtocol	= 0x00,
+				.iInterface		= 8,
+			},
+		},
+		.on = {
+			.intf = {
+				.bLength		= sizeof(struct usb_intf_desc),
+				.bDescriptorType	= USB_DT_INTF,
+				.bInterfaceNumber	= 0,
+				.bAlternateSetting	= 1,
+				.bNumEndpoints		= 1,
+				.bInterfaceClass	= 0xff,
+				.bInterfaceSubClass	= 0xe1,
+				.bInterfaceProtocol	= 0x00,
+				.iInterface		= 8,
+			},
+			.ep_data_in0 = {
+				.bLength		= sizeof(struct usb_ep_desc),
+				.bDescriptorType	= USB_DT_EP,
+				.bEndpointAddress	= 0x81,
+				.bmAttributes		= 0x05,
+				.wMaxPacketSize		= 388,
+				.bInterval		= 1,
+			},
+		},
+	},
+	.b = {
+		.off = {
+			.intf = {
+				.bLength		= sizeof(struct usb_intf_desc),
+				.bDescriptorType	= USB_DT_INTF,
+				.bInterfaceNumber	= 1,
+				.bAlternateSetting	= 0,
+				.bNumEndpoints		= 0,
+				.bInterfaceClass	= 0xff,
+				.bInterfaceSubClass	= 0xe1,
+				.bInterfaceProtocol	= 0x00,
+				.iInterface		= 9,
+			},
+		},
+		.on = {
+			.intf = {
+				.bLength		= sizeof(struct usb_intf_desc),
+				.bDescriptorType	= USB_DT_INTF,
+				.bInterfaceNumber	= 1,
+				.bAlternateSetting	= 1,
+				.bNumEndpoints		= 1,
+				.bInterfaceClass	= 0xff,
+				.bInterfaceSubClass	= 0xe1,
+				.bInterfaceProtocol	= 0x00,
+				.iInterface		= 9,
+			},
+			.ep_data_in1 = {
+				.bLength		= sizeof(struct usb_ep_desc),
+				.bDescriptorType	= USB_DT_EP,
+				.bEndpointAddress	= 0x82,
+				.bmAttributes		= 0x05,
+				.wMaxPacketSize		= 388,
+				.bInterval		= 1,
+			},
+		},
+	},
+};
+
 static const struct usb_conf_desc * const _conf_desc_array[] = {
 	&_app_conf_desc.conf,
+	&_app_conf_desc_e1d.conf,
 };
 
 static const struct usb_dev_desc _dev_desc = {
diff --git a/firmware/ice40-riscv/e1-tracer/usb_e1.c b/firmware/ice40-riscv/e1-tracer/usb_e1.c
index 80522e2..f3f1538 100644
--- a/firmware/ice40-riscv/e1-tracer/usb_e1.c
+++ b/firmware/ice40-riscv/e1-tracer/usb_e1.c
@@ -2,6 +2,7 @@
  * usb_e1.c
  *
  * Copyright (C) 2019-2020  Sylvain Munaut <tnt@246tNt.com>
+ * Copyright (C) 2022  Harald Welte <laforge@osmocom.org>
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
@@ -17,7 +18,7 @@
 #include "misc.h"
 
 struct {
-	bool running;
+	bool running[2];
 	int in_bdi[2];
 } g_usb_e1;
 
@@ -35,12 +36,12 @@
 	int chan;
 	int bdi;
 
-	if (!g_usb_e1.running)
-		return;
-
 	/* EP[1-2] IN */
 	for (chan=0; chan<2; chan++)
 	{
+		if (!g_usb_e1.running[chan])
+			continue;
+
 		bdi = g_usb_e1.in_bdi[chan];
 
 		while ((usb_ep_regs[1+chan].in.bd[bdi].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA)
@@ -112,12 +113,17 @@
 
 	return NULL;
 }
+
+static const struct usb_conf_desc *last_conf;
+
 enum usb_fnd_resp
 _e1_set_conf(const struct usb_conf_desc *conf)
 {
 	const struct usb_intf_desc *intf;
 
 	printf("e1 set_conf %08x\n", conf);
+
+	last_conf = conf;
 	if (!conf)
 		return USB_FND_SUCCESS;
 
@@ -133,81 +139,118 @@
 	return USB_FND_SUCCESS;
 }
 
+static void
+disable_chan(int chan)
+{
+	/* Already stopped ? */
+	if (!g_usb_e1.running[chan])
+		return;
+
+	/* Update state */
+	g_usb_e1.running[chan] = false;
+
+	/* Stop E1 */
+	e1_stop(chan);
+
+	/* Disable end-points */
+	usb_ep_regs[chan+1].in.status = 0;
+}
+
+static void
+enable_chan(int chan)
+{
+	/* Already running ? */
+	if (g_usb_e1.running[chan])
+		return;
+
+	/* Update state */
+	g_usb_e1.running[chan] = true;
+
+	/* Reset buffer pointers */
+	g_usb_e1.in_bdi[chan] = 0;
+
+	/* Configure EP1 IN / EP2 IN */
+	usb_ep_regs[chan+1].in.status = USB_EP_TYPE_ISOC | USB_EP_BD_DUAL;	/* Type=Isochronous, dual buffered */
+
+	/* EP1 IN: Prepare two buffers */
+	usb_ep_regs[chan+1].in.bd[0].ptr = 256 + (chan * 2 + 0) * 388;
+	usb_ep_regs[chan+1].in.bd[0].csr = 0;
+
+	usb_ep_regs[chan+1].in.bd[1].ptr = 256 + (chan * 2 + 1) * 388;
+	usb_ep_regs[chan+1].in.bd[1].csr = 0;
+
+	/* Start E1 */
+	e1_start(chan);
+}
+
 enum usb_fnd_resp
 _e1_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel)
 {
-	if (base->bInterfaceNumber != 0)
-		return USB_FND_CONTINUE;
+	if (!last_conf || last_conf->bConfigurationValue == 1) {
+		/* Legacy Configuration */
 
-	if (sel->bAlternateSetting == 0)
-	{
-		/* Already stopped ? */
-		if (!g_usb_e1.running)
-			return USB_FND_SUCCESS;
+		if (base->bInterfaceNumber != 0)
+			return USB_FND_CONTINUE;
 
-		/* Update state */
-		g_usb_e1.running = false;
+		if (sel->bAlternateSetting == 0) {
+			disable_chan(0);
+			disable_chan(1);
+		} else if (sel->bAlternateSetting == 1) {
+			enable_chan(0);
+			enable_chan(1);
+		} else {
+			/* Unknown */
+			return USB_FND_ERROR;
+		}
+	} else if (last_conf && last_conf->bConfigurationValue == 2) {
+		/* e1d compatible configuration */
 
-		/* Stop E1 */
-		e1_stop(0);
-		e1_stop(1);
-
-		/* Disable end-points */
-		usb_ep_regs[1].in.status = 0;
-		usb_ep_regs[2].in.status = 0;
-	}
-	else if (sel->bAlternateSetting == 1)
-	{
-		/* Already running ? */
-		if (g_usb_e1.running)
-			return USB_FND_SUCCESS;
-
-		/* Update state */
-		g_usb_e1.running = true;
-
-		/* Reset buffer pointers */
-		g_usb_e1.in_bdi[0] = 0;
-		g_usb_e1.in_bdi[1] = 0;
-
-		/* Configure EP1 IN / EP2 IN */
-		usb_ep_regs[1].in.status = USB_EP_TYPE_ISOC | USB_EP_BD_DUAL;	/* Type=Isochronous, dual buffered */
-		usb_ep_regs[2].in.status = USB_EP_TYPE_ISOC | USB_EP_BD_DUAL;	/* Type=Isochronous, dual buffered */
-
-		/* EP1 IN: Prepare two buffers */
-		usb_ep_regs[1].in.bd[0].ptr = 256 + 0 * 388;
-		usb_ep_regs[1].in.bd[0].csr = 0;
-
-		usb_ep_regs[1].in.bd[1].ptr = 256 + 1 * 388;
-		usb_ep_regs[1].in.bd[1].csr = 0;
-
-		/* EP2 IN: Prepare two buffers */
-		usb_ep_regs[2].in.bd[0].ptr = 256 + 2 * 388;
-		usb_ep_regs[2].in.bd[0].csr = 0;
-
-		usb_ep_regs[2].in.bd[1].ptr = 256 + 3 * 388;
-		usb_ep_regs[2].in.bd[1].csr = 0;
-
-		/* Start E1 */
-		e1_start(0);
-		e1_start(1);
-	}
-	else
-	{
-		/* Unknown */
+		switch (base->bInterfaceNumber) {
+		case 0:
+		case 1:
+			switch (sel->bAlternateSetting) {
+			case 0:
+				disable_chan(base->bInterfaceNumber);
+				break;
+			case 1:
+				enable_chan(base->bInterfaceNumber);
+				break;
+			default:
+				/* Unknown */
+				return USB_FND_ERROR;
+			}
+			break;
+		default:
+			return USB_FND_CONTINUE;
+		}
+	} else {
 		return USB_FND_ERROR;
 	}
 
-
 	return USB_FND_SUCCESS;
 }
 
 enum usb_fnd_resp
 _e1_get_intf(const struct usb_intf_desc *base, uint8_t *alt)
 {
-	if (base->bInterfaceNumber != 0)
-		return USB_FND_CONTINUE;
+	if (!last_conf || last_conf->bConfigurationValue == 1) {
+		/* Legacy configuration */
+		if (base->bInterfaceNumber != 0)
+			return USB_FND_CONTINUE;
 
-	*alt = g_usb_e1.running ? 1 : 0;
+		*alt = g_usb_e1.running[0] && g_usb_e1.running[1] ? 1 : 0;
+	} else if (last_conf && last_conf->bConfigurationValue == 2) {
+		/* e1d compatible configuration */
+		switch (base->bInterfaceNumber) {
+		case 0:
+		case 1:
+			*alt = g_usb_e1.running[base->bInterfaceNumber] ? 1 : 0;
+			break;
+		default:
+			return USB_FND_CONTINUE;
+		}
+	} else
+		return USB_FND_CONTINUE;
 
 	return USB_FND_SUCCESS;
 }
diff --git a/firmware/ice40-riscv/e1-tracer/usb_str_app.txt b/firmware/ice40-riscv/e1-tracer/usb_str_app.txt
index 0482e91..45ac005 100644
--- a/firmware/ice40-riscv/e1-tracer/usb_str_app.txt
+++ b/firmware/ice40-riscv/e1-tracer/usb_str_app.txt
@@ -1,6 +1,9 @@
 0000000000000000
 osmocom
 e1-tracer
-Main
+Legacy
 E1
 DFU runtime
+osmo-e1d compatible
+E1 Direction A
+E1 Direction B