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; +} + +