diff --git a/tests/gadget-zero/Makefile.stm32f4disco b/tests/gadget-zero/Makefile.stm32f4disco
new file mode 100644
index 00000000..23d777bb
--- /dev/null
+++ b/tests/gadget-zero/Makefile.stm32f4disco
@@ -0,0 +1,43 @@
+##
+## This file is part of the libopencm3 project.
+##
+## This library 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 library 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 library. If not, see .
+##
+
+BOARD = stm32f4disco
+PROJECT = usb-gadget0-$(BOARD)
+BUILD_DIR = bin-$(BOARD)
+
+SHARED_DIR = ../shared
+
+CFILES = main-$(BOARD).c
+CFILES += usb-gadget0.c trace.c trace_stdio.c
+
+VPATH += $(SHARED_DIR)
+
+INCLUDES += $(patsubst %,-I%, . $(SHARED_DIR))
+
+OPENCM3_DIR=../..
+
+### This section can go to an arch shared rules eventually...
+LDSCRIPT = ../../lib/stm32/f4/stm32f405x6.ld
+OPENCM3_LIB = opencm3_stm32f4
+OPENCM3_DEFS = -DSTM32F4
+FP_FLAGS ?= -mfloat-abi=hard -mfpu=fpv4-sp-d16
+ARCH_FLAGS = -mthumb -mcpu=cortex-m4 $(FP_FLAGS)
+#OOCD_INTERFACE = stlink-v2
+#OOCD_TARGET = stm32f4x
+OOCD_FILE = openocd.stm32f4disco.cfg
+
+include ../rules.mk
diff --git a/tests/gadget-zero/README.md b/tests/gadget-zero/README.md
new file mode 100644
index 00000000..9d7b8e99
--- /dev/null
+++ b/tests/gadget-zero/README.md
@@ -0,0 +1,23 @@
+This project, inspired by [usbtest](http://www.linux-usb.org/usbtest/) and
+the linux usb gadget zero driver is used for regression testing changes to the
+libopencm3 usb stack.
+
+The firmware itself is meant to be portable to any supported hardware, and then
+identical unit test code is run against all platforms. This project can and
+should be built for multiple devices.
+
+Requirements:
+pyusb for running the tests.
+openocd >= 0.9 for automated flashing of specific boards
+python3 for running the tests at the command line.
+
+You _will_ need to modify the openocd config files, as they contain specific
+serial numbers of programming hardware. You should set these up for the set of
+available boards at your disposal.
+
+Tests marked as @unittest.skip are either for functionality that is known to be
+broken, and are awaiting code fixes, or are long running performance tests
+
+An example of a successful test run:
+
+
diff --git a/tests/gadget-zero/main-stm32f4disco.c b/tests/gadget-zero/main-stm32f4disco.c
new file mode 100644
index 00000000..e554cfd0
--- /dev/null
+++ b/tests/gadget-zero/main-stm32f4disco.c
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2015 Karl Palsson
+ *
+ * This library 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 library 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 library. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include
+#include "usb-gadget0.h"
+
+#define ER_DEBUG
+#ifdef ER_DEBUG
+#define ER_DPRINTF(fmt, ...) \
+ do { printf(fmt, ## __VA_ARGS__); } while (0)
+#else
+#define ER_DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+int main(void)
+{
+ rcc_clock_setup_hse_3v3(&hse_8mhz_3v3[CLOCK_3V3_168MHZ]);
+ rcc_periph_clock_enable(RCC_GPIOA);
+ rcc_periph_clock_enable(RCC_OTGFS);
+
+ gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE,
+ GPIO9 | GPIO11 | GPIO12);
+ gpio_set_af(GPIOA, GPIO_AF10, GPIO9 | GPIO11 | GPIO12);
+
+ /* LEDS on discovery board */
+ rcc_periph_clock_enable(RCC_GPIOD);
+ gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT,
+ GPIO_PUPD_NONE, GPIO12 | GPIO13 | GPIO14 | GPIO15);
+
+ usbd_device *usbd_dev = gadget0_init(&otgfs_usb_driver, "stm32f4disco");
+
+ ER_DPRINTF("bootup complete\n");
+ while (1) {
+ usbd_poll(usbd_dev);
+ }
+
+}
+
diff --git a/tests/gadget-zero/openocd.stm32f4disco.cfg b/tests/gadget-zero/openocd.stm32f4disco.cfg
new file mode 100644
index 00000000..478fe30d
--- /dev/null
+++ b/tests/gadget-zero/openocd.stm32f4disco.cfg
@@ -0,0 +1,13 @@
+source [find interface/stlink-v2.cfg]
+set WORKAREASIZE 0x4000
+source [find target/stm32f4x.cfg]
+
+# serial of my f4 disco board.
+hla_serial "W?k\x06IgHV0H\x10?"
+
+tpiu config internal swodump.stm32f4disco.log uart off 168000000
+
+# Uncomment to reset on connect, for grabbing under WFI et al
+reset_config srst_only srst_nogate
+# reset_config srst_only srst_nogate connect_assert_srst
+
diff --git a/tests/gadget-zero/stub.py b/tests/gadget-zero/stub.py
new file mode 100644
index 00000000..de0439ee
--- /dev/null
+++ b/tests/gadget-zero/stub.py
@@ -0,0 +1,4 @@
+__author__ = 'karlp'
+
+def config_switch():
+ pass
diff --git a/tests/gadget-zero/test_gadget0.py b/tests/gadget-zero/test_gadget0.py
new file mode 100644
index 00000000..09db7596
--- /dev/null
+++ b/tests/gadget-zero/test_gadget0.py
@@ -0,0 +1,207 @@
+import array
+import datetime
+import usb.core
+import usb.util as uu
+import logging
+
+import unittest
+
+DUT_SERIAL = "stm32f4disco"
+
+class find_by_serial(object):
+ def __init__(self, serial):
+ self._serial = serial
+
+ def __call__(self, device):
+ return device.serial_number == self._serial
+
+
+class TestGadget0(unittest.TestCase):
+ # TODO - parameterize this with serial numbers so we can find
+ # gadget 0 code for different devices. (or use different PIDs?)
+ def setUp(self):
+ self.dev = usb.core.find(idVendor=0xcafe, idProduct=0xcafe, custom_match=find_by_serial(DUT_SERIAL))
+ self.assertIsNotNone(self.dev, "Couldn't find locm3 gadget0 device")
+
+ def tearDown(self):
+ uu.dispose_resources(self.dev)
+
+ def test_sanity(self):
+ self.assertEqual(2, self.dev.bNumConfigurations, "Should have 2 configs")
+
+ def test_config_switch_2(self):
+ """
+ Uses the API if you're interested in the cfg block
+ """
+ cfg = uu.find_descriptor(self.dev, bConfigurationValue=2)
+ self.assertIsNotNone(cfg, "Config 2 should exist")
+ self.dev.set_configuration(cfg)
+
+ def test_config_switch_3(self):
+ """
+ Uses the simple API
+ """
+ self.dev.set_configuration(3)
+
+ def test_fetch_config(self):
+ self.dev.set_configuration(3)
+ # FIXME - find a way to get the defines for these from pyusb
+ x = self.dev.ctrl_transfer(0x80, 0x08, 0, 0, 1)
+ self.assertEqual(3, x[0], "Should get the actual bConfigurationValue back")
+
+ def test_invalid_config(self):
+ try:
+ # FIXME - find a way to get the defines for these from pyusb
+ self.dev.ctrl_transfer(0x00, 0x09, 99)
+ self.fail("Request of invalid cfg should have failed")
+ except usb.core.USBError as e:
+ # Note, this might not be as portable as we'd like.
+ self.assertIn("Pipe", e.strerror)
+
+
+class TestConfigSourceSink(unittest.TestCase):
+ """
+ We could inherit, but it doesn't save much, and this saves me from remembering how to call super.
+ """
+
+ def setUp(self):
+ self.dev = usb.core.find(idVendor=0xcafe, idProduct=0xcafe, custom_match=find_by_serial(DUT_SERIAL))
+ self.assertIsNotNone(self.dev, "Couldn't find locm3 gadget0 device")
+
+ self.cfg = uu.find_descriptor(self.dev, bConfigurationValue=2)
+ self.assertIsNotNone(self.cfg, "Config 2 should exist")
+ self.dev.set_configuration(self.cfg);
+ self.intf = self.cfg[(0, 0)]
+ # heh, kinda gross...
+ self.ep_out = [ep for ep in self.intf if uu.endpoint_direction(ep.bEndpointAddress) == uu.ENDPOINT_OUT][0]
+ self.ep_in = [ep for ep in self.intf if uu.endpoint_direction(ep.bEndpointAddress) == uu.ENDPOINT_IN][0]
+
+ def tearDown(self):
+ uu.dispose_resources(self.dev)
+
+ def test_write_simple(self):
+ """
+ here we go, start off with just a simple write of < bMaxPacketSize and just make sure it's accepted
+ :return:
+ """
+ data = [x for x in range(int(self.ep_out.wMaxPacketSize / 2))]
+ written = self.dev.write(self.ep_out, data)
+ self.assertEqual(written, len(data), "Should have written all bytes plz")
+
+ def test_write_zlp(self):
+ written = self.ep_out.write([])
+ self.assertEqual(0, written, "should have written zero for a zero length write y0")
+
+ def test_write_batch(self):
+ """
+ Write 50 max sized packets. Should not stall. Will stall if firmware isn't consuming data properly
+ :return:
+ """
+ for i in range(50):
+ data = [x for x in range(int(self.ep_out.wMaxPacketSize))]
+ written = self.dev.write(self.ep_out, data)
+ self.assertEqual(written, len(data), "Should have written all bytes plz")
+
+ def test_write_mixed(self):
+ for i in range(int(self.ep_out.wMaxPacketSize / 4), self.ep_out.wMaxPacketSize * 10, 11):
+ data = [x & 0xff for x in range(i)]
+ written = self.ep_out.write(data)
+ self.assertEqual(written, len(data), "should have written all bytes plz")
+
+ def test_read_zeros(self):
+ self.dev.ctrl_transfer(uu.CTRL_TYPE_VENDOR | uu.CTRL_RECIPIENT_INTERFACE, 0x1, 0)
+ self.ep_in.read(self.ep_in.wMaxPacketSize) # Clear out any prior pattern data
+ # unless, you know _exactly_ how much will be written by the device, always read
+ # an integer multiple of max packet size, to avoid overflows.
+ # the returned data will have the actual length.
+ # You can't just magically read out less than the device wrote.
+ read_size = self.ep_in.wMaxPacketSize * 10
+ data = self.dev.read(self.ep_in, read_size)
+ self.assertEqual(len(data), read_size, "Should have read as much as we asked for")
+ expected = array.array('B', [0 for x in range(read_size)])
+ self.assertEqual(data, expected, "In pattern 0, all source data should be zeros: ")
+
+ def test_read_sequence(self):
+ # switching to the mod63 pattern requires resynching carefully to read out any zero frames already
+ # queued, but still make sure we start the sequence at zero.
+ self.dev.ctrl_transfer(uu.CTRL_TYPE_VENDOR | uu.CTRL_RECIPIENT_INTERFACE, 0x1, 1)
+ self.ep_in.read(self.ep_in.wMaxPacketSize) # Potentially queued zeros, or would have been safe.
+ self.dev.ctrl_transfer(uu.CTRL_TYPE_VENDOR | uu.CTRL_RECIPIENT_INTERFACE, 0x1, 1)
+ self.ep_in.read(self.ep_in.wMaxPacketSize) # definitely right pattern now, but need to restart at zero.
+ read_size = self.ep_in.wMaxPacketSize * 3
+ data = self.dev.read(self.ep_in, read_size)
+ self.assertEqual(len(data), read_size, "Should have read as much as we asked for")
+ expected = array.array('B', [x % 63 for x in range(read_size)])
+ self.assertEqual(data, expected, "In pattern 1, Should be % 63")
+
+ def test_read_write_interleaved(self):
+ for i in range(1, 20):
+ ii = self.ep_in.read(self.ep_in.wMaxPacketSize * i)
+ dd = [x & 0xff for x in range(i * 20 + 3)]
+ oo = self.ep_out.write(dd)
+ self.assertEqual(len(ii), self.ep_in.wMaxPacketSize * i, "should have read full packet")
+ self.assertEqual(oo, len(dd), "should have written full packet")
+
+ def test_control_known(self):
+ self.dev.ctrl_transfer(uu.CTRL_TYPE_VENDOR | uu.CTRL_RECIPIENT_INTERFACE, 0x1, 0)
+ self.dev.ctrl_transfer(uu.CTRL_TYPE_VENDOR | uu.CTRL_RECIPIENT_INTERFACE, 0x1, 1)
+ self.dev.ctrl_transfer(uu.CTRL_TYPE_VENDOR | uu.CTRL_RECIPIENT_INTERFACE, 0x1, 99)
+ self.dev.ctrl_transfer(uu.CTRL_TYPE_VENDOR | uu.CTRL_RECIPIENT_INTERFACE, 0x1, 0)
+
+ def test_control_unknown(self):
+ try:
+ self.dev.ctrl_transfer(uu.CTRL_TYPE_VENDOR | uu.CTRL_RECIPIENT_INTERFACE, 42, 69)
+ self.fail("Should have got a stall")
+ except usb.core.USBError as e:
+ # Note, this might not be as portable as we'd like.
+ self.assertIn("Pipe", e.strerror)
+
+
+@unittest.skip("Perf tests only on demand (comment this line!)")
+class TestConfigSourceSinkPerformance(unittest.TestCase):
+ """
+ Read/write throughput, roughly
+ """
+
+ def setUp(self):
+ self.dev = usb.core.find(idVendor=0xcafe, idProduct=0xcafe, custom_match=find_by_serial(DUT_SERIAL))
+ self.assertIsNotNone(self.dev, "Couldn't find locm3 gadget0 device")
+
+ self.cfg = uu.find_descriptor(self.dev, bConfigurationValue=2)
+ self.assertIsNotNone(self.cfg, "Config 2 should exist")
+ self.dev.set_configuration(self.cfg);
+ self.intf = self.cfg[(0, 0)]
+ # heh, kinda gross...
+ self.ep_out = [ep for ep in self.intf if uu.endpoint_direction(ep.bEndpointAddress) == uu.ENDPOINT_OUT][0]
+ self.ep_in = [ep for ep in self.intf if uu.endpoint_direction(ep.bEndpointAddress) == uu.ENDPOINT_IN][0]
+
+ def tearDown(self):
+ uu.dispose_resources(self.dev)
+
+ def tput(self, xc, te):
+ return (xc / 1024 / max(1, te.seconds + te.microseconds /
+ 1000000.0))
+
+ def test_read_perf(self):
+ # I get around 990kps here...
+ ts = datetime.datetime.now()
+ rxc = 0
+ while rxc < 5 * 1024 * 1024:
+ desired = 100 * 1024
+ data = self.ep_in.read(desired, timeout=0)
+ self.assertEqual(desired, len(data), "Should have read all bytes plz")
+ rxc += len(data)
+ te = datetime.datetime.now() - ts
+ print("read %s bytes in %s for %s kps" % (rxc, te, self.tput(rxc, te)))
+
+ def test_write_perf(self):
+ # caps out around 420kps?
+ ts = datetime.datetime.now()
+ txc = 0
+ data = [x & 0xff for x in range(100 * 1024)]
+ while txc < 5 * 1024 * 1024:
+ w = self.ep_out.write(data, timeout=0)
+ self.assertEqual(w, len(data), "Should have written all bytes plz")
+ txc += w
+ te = datetime.datetime.now() - ts
+ print("wrote %s bytes in %s for %s kps" % (txc, te, self.tput(txc, te)))
diff --git a/tests/gadget-zero/usb-gadget0.c b/tests/gadget-zero/usb-gadget0.c
new file mode 100644
index 00000000..4e7b7adf
--- /dev/null
+++ b/tests/gadget-zero/usb-gadget0.c
@@ -0,0 +1,304 @@
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2015 Karl Palsson
+ *
+ * This library 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 library 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 library. If not, see .
+ */
+
+/*
+ * This file implements linux's "Gadget zero" functionality, both the
+ * "source sink" functional interface, and the "loopback" interface.
+ * It _only_ uses usb includes, do _not_ include any target specific code here!
+ */
+#include
+#include
+#include
+#include
+
+#include "trace.h"
+#include "usb-gadget0.h"
+
+#define ER_DEBUG
+#ifdef ER_DEBUG
+#include
+#define ER_DPRINTF(fmt, ...) \
+ do { printf(fmt, ## __VA_ARGS__); } while (0)
+#else
+#define ER_DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+/*
+ * USB Vendor:Interface control requests.
+ */
+#define GZ_REQ_SET_PATTERN 1
+#define INTEL_COMPLIANCE_WRITE 0x5b
+#define INTEL_COMPLIANCE_READ 0x5c
+
+/* USB configurations */
+#define GZ_CFG_SOURCESINK 2
+#define GZ_CFG_LOOPBACK 3
+
+
+static const struct usb_device_descriptor dev = {
+ .bLength = USB_DT_DEVICE_SIZE,
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = 0x0200,
+ .bDeviceClass = USB_CLASS_VENDOR,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .bMaxPacketSize0 = 64,
+
+ /* when we're compatible with gadget 0
+ * #define DRIVER_VENDOR_NUM 0x0525
+ * #define DRIVER_PRODUCT_NUM 0xa4a0
+ */
+ .idVendor = 0xcafe,
+ .idProduct = 0xcafe,
+ .bcdDevice = 0x0001,
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .iSerialNumber = 3,
+ .bNumConfigurations = 2,
+};
+
+static const struct usb_endpoint_descriptor endp_bulk[] = {
+ {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 0x01,
+ .bmAttributes = USB_ENDPOINT_ATTR_BULK,
+ .wMaxPacketSize = 64,
+ .bInterval = 1,
+ },
+ {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 0x82,
+ .bmAttributes = USB_ENDPOINT_ATTR_BULK,
+ .wMaxPacketSize = 64,
+ .bInterval = 1,
+ }
+};
+
+static const struct usb_interface_descriptor iface_sourcesink[] = {
+ {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR,
+ .iInterface = 0,
+ .endpoint = endp_bulk,
+ }
+};
+
+static const struct usb_interface_descriptor iface_loopback[] = {
+ {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0, // still 0, as it's a different config...?
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR,
+ .iInterface = 0,
+ .endpoint = endp_bulk,
+ }
+};
+
+static const struct usb_interface ifaces_sourcesink[] = {
+ {
+ .num_altsetting = 1,
+ .altsetting = iface_sourcesink,
+ }
+};
+
+static const struct usb_interface ifaces_loopback[] = {
+ {
+ .num_altsetting = 1,
+ .altsetting = iface_loopback,
+ }
+};
+
+static const struct usb_config_descriptor config[] = {
+ {
+ .bLength = USB_DT_CONFIGURATION_SIZE,
+ .bDescriptorType = USB_DT_CONFIGURATION,
+ .wTotalLength = 0,
+ .bNumInterfaces = 1,
+ .bConfigurationValue = GZ_CFG_SOURCESINK,
+ .iConfiguration = 4, // string index
+ .bmAttributes = 0x80,
+ .bMaxPower = 0x32,
+ .interface = ifaces_sourcesink,
+ },
+ {
+ .bLength = USB_DT_CONFIGURATION_SIZE,
+ .bDescriptorType = USB_DT_CONFIGURATION,
+ .wTotalLength = 0,
+ .bNumInterfaces = 1,
+ .bConfigurationValue = GZ_CFG_LOOPBACK,
+ .iConfiguration = 5, // string index
+ .bmAttributes = 0x80,
+ .bMaxPower = 0x32,
+ .interface = ifaces_loopback,
+ }
+};
+
+static char serial[] = "0123456789.0123456789.0123456789";
+static const char * usb_strings[] = {
+ "libopencm3",
+ "Gadget-Zero",
+ serial,
+ "source and sink data",
+ "loop input to output"
+};
+
+/* Buffer to be used for control requests. */
+static uint8_t usbd_control_buffer[128];
+static usbd_device *our_dev;
+
+/* Private global for state */
+static struct {
+ uint8_t pattern;
+ int pattern_counter;
+} state = {
+ .pattern = 0,
+ .pattern_counter = 0,
+};
+
+static void gadget0_ss_out_cb(usbd_device *usbd_dev, uint8_t ep)
+{
+ (void) ep;
+ // TODO - if you're really keen, perf test this. tiva implies it matters
+ //char buf[64] __attribute__ ((aligned(4)));
+ char buf[64];
+ trace_send_blocking8(0, 'O');
+ uint16_t x = usbd_ep_read_packet(usbd_dev, ep, buf, sizeof(buf));
+ trace_send_blocking8(1, x);
+}
+
+static void gadget0_ss_in_cb(usbd_device *usbd_dev, uint8_t ep)
+{
+ (void) usbd_dev;
+ trace_send_blocking8(0, 'I');
+ uint8_t buf[64];
+ switch (state.pattern) {
+ case 0:
+ memset(buf, 0, sizeof(buf));
+ break;
+ case 1:
+ for (unsigned i = 0; i < sizeof(buf); i++) {
+ buf[i] = state.pattern_counter++ % 63;
+ }
+ break;
+ }
+
+ uint16_t x = usbd_ep_write_packet(usbd_dev, ep, buf, sizeof(buf));
+ /* As we are calling write in the callback, this should never fail */
+ trace_send_blocking8(2, x);
+ if (x != sizeof(buf)) {
+ ER_DPRINTF("failed to write?: %d\n", x);
+ }
+ //assert(x == sizeof(buf));
+}
+
+static void gadget0_rx_cb_loopback(usbd_device *usbd_dev, uint8_t ep)
+{
+ (void) usbd_dev;
+ ER_DPRINTF("loop rx %x\n", ep);
+ // TODO - unimplemented - consult linux source on proper behaviour
+}
+
+static void gadget0_tx_cb_loopback(usbd_device *usbd_dev, uint8_t ep)
+{
+ (void) usbd_dev;
+ ER_DPRINTF("loop tx %x\n", ep);
+ // TODO - unimplemented - consult linux source on proper behaviour
+}
+
+static int gadget0_control_request(usbd_device *usbd_dev,
+ struct usb_setup_data *req,
+ uint8_t **buf,
+ uint16_t *len,
+ void (**complete)(usbd_device *usbd_dev,
+ struct usb_setup_data *req))
+{
+ (void) usbd_dev;
+ (void) complete;
+ (void) buf;
+ (void) len;
+ ER_DPRINTF("ctrl breq: %x, bmRT: %x, windex :%x, wlen: %x, wval :%x\n",
+ req->bRequest, req->bmRequestType, req->wIndex, req->wLength, req->wValue);
+
+ // TODO - what do the return values mean again?
+ switch (req->bRequest) {
+ case GZ_REQ_SET_PATTERN:
+ state.pattern_counter = 0;
+ state.pattern = req->wValue;
+ return USBD_REQ_HANDLED;
+ case INTEL_COMPLIANCE_WRITE:
+ case INTEL_COMPLIANCE_READ:
+ ER_DPRINTF("unimplemented!");
+ return USBD_REQ_NOTSUPP;
+ }
+ return USBD_REQ_NEXT_CALLBACK;
+}
+
+static void gadget0_set_config(usbd_device *usbd_dev, uint16_t wValue)
+{
+ ER_DPRINTF("set cfg %d\n", wValue);
+ switch (wValue) {
+ case GZ_CFG_SOURCESINK:
+ usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64,
+ gadget0_ss_out_cb);
+ usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64,
+ gadget0_ss_in_cb);
+ usbd_register_control_callback(
+ usbd_dev,
+ USB_REQ_TYPE_VENDOR | USB_REQ_TYPE_INTERFACE,
+ USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
+ gadget0_control_request);
+ /* Prime source for IN data. */
+ gadget0_ss_in_cb(usbd_dev, 0x82);
+ break;
+ case GZ_CFG_LOOPBACK:
+ usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64,
+ gadget0_rx_cb_loopback);
+ usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64,
+ gadget0_tx_cb_loopback);
+ break;
+ default:
+ ER_DPRINTF("set configuration unknown: %d\n", wValue);
+ }
+}
+
+usbd_device* gadget0_init(const usbd_driver *driver, const char *userserial)
+{
+#ifdef ER_DEBUG
+ setbuf(stdout, NULL);
+#endif
+ if (userserial) {
+ usb_strings[2] = userserial;
+ }
+ our_dev = usbd_init(driver, &dev, config,
+ usb_strings, 5,
+ usbd_control_buffer, sizeof(usbd_control_buffer));
+
+ usbd_register_set_config_callback(our_dev, gadget0_set_config);
+
+ return our_dev;
+}
diff --git a/tests/gadget-zero/usb-gadget0.h b/tests/gadget-zero/usb-gadget0.h
new file mode 100644
index 00000000..be790cd3
--- /dev/null
+++ b/tests/gadget-zero/usb-gadget0.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2015 Karl Palsson
+ *
+ * This library 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 library 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 library. If not, see .
+ */
+
+#ifndef USB_GADGET0_H
+#define USB_GADGET0_H
+
+#include
+
+/**
+ * Start up the gadget0 framework.
+ * @param driver which usbd hardware driver to use.
+ * @param userserial if non-null, will become the serial number.
+ * You should provide this to help the test code find something particular
+ * to the hardware.
+ * @return the usbd_device created.
+*/
+usbd_device* gadget0_init(const usbd_driver *driver, const char *userserial);
+
+#endif
diff --git a/tests/rules.mk b/tests/rules.mk
new file mode 100644
index 00000000..280c80ee
--- /dev/null
+++ b/tests/rules.mk
@@ -0,0 +1,158 @@
+##
+## This file is part of the libopencm3 project.
+##
+## This library 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 library 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 library. If not, see .
+##
+
+# This version of rules.mk expects the following to be defined before
+# inclusion..
+### REQUIRED ###
+# OPENCM3_DIR - duh
+# OPENCM3_LIB - the basename, eg: opencm3_stm32f4
+# OPENCM3_DEFS - the target define eg: -DSTM32F4
+# ARCH_FLAGS - eg, -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
+# (ie, the full set of cpu arch flags, _none_ are defined in this file)
+# PROJECT - will be the basename of the output elf, eg usb-gadget0-stm32f4disco
+# CFILES - basenames only, eg main.c blah.c
+# LDSCRIPT - full path, eg ../../examples/stm32/f4/stm32f4-discovery/stm32f4-discovery.ld
+#
+### OPTIONAL ###
+# INCLUDES - fully formed -I paths, if you want extra, eg -I../shared
+# BUILD_DIR - defaults to bin, should set this if you are building multiarch
+# OPT - full -O flag, defaults to -Os
+# CSTD - defaults -std=c99
+# CXXSTD - no default.
+# OOCD_INTERFACE - eg stlink-v2
+# OOCD_TARGET - eg stm32f4x
+# both only used if you use the "make flash" target.
+# OOCD_FILE - eg my.openocd.cfg
+# This overrides interface/target above, and is used as just -f FILE
+### TODO/FIXME/notes ###
+# No support for stylecheck.
+# No support for BMP/texane/random flash methods, no plans either
+# No support for magically finding the library.
+# C++ hasn't been actually tested with this..... sorry bout that. ;)
+# Second expansion/secondary not set, add this if you need them.
+
+BUILD_DIR ?= bin
+OPT ?= -Os
+CSTD ?= -std=c99
+
+# Be silent per default, but 'make V=1' will show all compiler calls.
+# If you're insane, V=99 will print out all sorts of things.
+V?=0
+ifeq ($(V),0)
+Q := @
+NULL := 2>/dev/null
+endif
+
+# Tool paths.
+PREFIX ?= arm-none-eabi-
+CC = $(PREFIX)gcc
+LD = $(PREFIX)gcc
+OBJCOPY = $(PREFIX)objcopy
+OBJDUMP = $(PREFIX)objdump
+OOCD ?= openocd
+
+OPENCM3_INC = $(OPENCM3_DIR)/include
+
+# Inclusion of library header files
+INCLUDES += $(patsubst %,-I%, . $(OPENCM3_INC) )
+
+OBJS = $(CFILES:%.c=$(BUILD_DIR)/%.o)
+
+CPPFLAGS += -MD
+CPPFLAGS += -Wall -Wundef $(INCLUDES)
+CPPFLAGS += $(INCLUDES) $(OPENCM3_DEFS)
+
+CFLAGS += $(OPT) $(CSTD) -ggdb3
+CFLAGS += $(ARCH_FLAGS)
+CFLAGS += -fno-common
+CFLAGS += -ffunction-sections -fdata-sections
+CFLAGS += -Wextra -Wshadow -Wno-unused-variable -Wimplicit-function-declaration
+CFLAGS += -Wredundant-decls -Wstrict-prototypes -Wmissing-prototypes
+
+CXXFLAGS += $(OPT) $(CXXSTD) -ggdb3
+CXXFLAGS += $(ARCH_FLAGS)
+CXXFLAGS += -fno-common
+CXXFLAGS += -ffunction-sections -fdata-sections
+CXXFLAGS += -Wextra -Wshadow -Wredundant-decls -Weffc++
+
+LDFLAGS += -T$(LDSCRIPT) -L$(OPENCM3_DIR)/lib -nostartfiles
+LDFLAGS += $(ARCH_FLAGS)
+LDFLAGS += -specs=nano.specs
+LDFLAGS += -Wl,--gc-sections
+# OPTIONAL
+#LDFLAGS += -Wl,-Map=$(PROJECT).map
+ifeq ($(V),99)
+LDFLAGS += -Wl,--print-gc-sections
+endif
+
+LDLIBS += -l$(OPENCM3_LIB)
+# nosys is only in newer gcc-arm-embedded...
+#LDLIBS += -specs=nosys.specs
+LDLIBS += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group
+
+all: $(PROJECT).elf $(PROJECT).bin
+flash: $(PROJECT).flash
+
+$(LDSCRIPT):
+ifeq (,$(wildcard $(LDSCRIPT)))
+ $(error Unable to find specified linker script: $(LDSCRIPT))
+endif
+
+# Need a special rule to have a bin dir
+$(BUILD_DIR)/%.o: %.c
+ @printf " CC\t$<\n"
+ @mkdir -p $(dir $@)
+ $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $<
+
+$(BUILD_DIR)/%.o: %.cxx
+ @printf " CXX\t$<\n"
+ @mkdir -p $(dir $@)
+ $(Q)$(CC) $(CXXFLAGS) $(CPPFLAGS) -o $@ -c $<
+
+$(PROJECT).elf: $(OBJS) $(LDSCRIPT)
+ @printf " LD\t$@\n"
+ $(Q)$(LD) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@
+
+%.bin: %.elf
+ @printf " OBJCOPY\t$@\n"
+ $(Q)$(OBJCOPY) -O binary $< $@
+
+%.lss: %.elf
+ $(OBJDUMP) -h -S $< > $@
+
+%.list: %.elf
+ $(OBJDUMP) -S $< > $@
+
+%.flash: %.elf
+ @printf " FLASH\t$<\n"
+ifeq (,$(OOCD_FILE))
+ $(Q)$(OOCD) -f interface/$(OOCD_INTERFACE).cfg \
+ -f target/$(OOCD_TARGET).cfg \
+ -c "program $(*).elf verify reset exit" \
+ $(NULL)
+else
+ $(Q)$(OOCD) -f $(OOCD_FILE) \
+ -c "program $(*).elf verify reset exit" \
+ $(NULL)
+endif
+
+clean:
+ rm -rf $(BUILD_DIR) $(PROJECT).{elf,bin} $(PROJECT).{list,lss,map}
+
+.PHONY: all clean flash
+-include $(OBJS:.o=.d)
+
diff --git a/tests/shared/trace.c b/tests/shared/trace.c
new file mode 100644
index 00000000..9e05d9a0
--- /dev/null
+++ b/tests/shared/trace.c
@@ -0,0 +1,54 @@
+#include
+#include
+#include
+#include
+#include "trace.h"
+
+void trace_send_blocking8(int stimulus_port, char c) {
+ if (!(ITM_TER[0] & (1<
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void trace_send_blocking8(int stimulus_port, char c);
+void trace_send8(int stimulus_port, char c);
+
+void trace_send_blocking16(int stimulus_port, uint16_t val);
+void trace_send16(int stimulus_port, uint16_t val);
+
+void trace_send_blocking32(int stimulus_port, uint32_t val);
+void trace_send32(int stimulus_port, uint32_t val);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TRACE_H */
+
diff --git a/tests/shared/trace_stdio.c b/tests/shared/trace_stdio.c
new file mode 100644
index 00000000..27109428
--- /dev/null
+++ b/tests/shared/trace_stdio.c
@@ -0,0 +1,34 @@
+/*
+ * support for stdio output to a trace port
+ * Karl Palsson, 2014
+ */
+
+#include
+#include
+#include
+
+#include "trace.h"
+
+#ifndef STIMULUS_STDIO
+#define STIMULUS_STDIO 0
+#endif
+
+int _write(int file, char *ptr, int len);
+int _write(int file, char *ptr, int len)
+{
+ int i;
+
+ if (file == STDOUT_FILENO || file == STDERR_FILENO) {
+ for (i = 0; i < len; i++) {
+ if (ptr[i] == '\n') {
+ trace_send_blocking8(STIMULUS_STDIO, '\r');
+ }
+ trace_send_blocking8(STIMULUS_STDIO, ptr[i]);
+ }
+ return i;
+ }
+ errno = EIO;
+ return -1;
+}
+
+