tests: usb: gadget0 compatible interface (stm32f4)
This introduces the first firmware setup specifically for automated testing. Based heavily on the linux kernel project's "USB Gadget Zero" idea, and in theory, this should be testable with <kernelsrc>/tools/usb/testusb.c but... not yet. It's tricky to set that up and poorly documented, so we've got our own tests instead. Instead, we include a set of python unit tests using pyusb. These currently only test a basic core subset of functionality, but have already been very helpful in finding latent bugs. In this first stage, we support only the stm32f4 disco board, (MB997) and FullSpeed USB devices. A generic "rules.mk" is introduced to support multi platform builds. (See below) Some basic performance tests are included, but as they take some time to run, you must manually enable them. See the README for more information NOTE! Only the source/sink functional interface is supported, loopback will require some comparision with a real gadget zero to check exactly how it's working. FOOTNOTES 1: This introduces a rules.mk file that is arguably substantially simpler[1] for re-use, and then uses this rules.mk file to support multiple target outputs from the same shared source tree. Less path requirements are imposed, and less variables need to be defined in each project's makefile. A separate bin directory is created for each project. All useful settings and configurations imported from the original library rules file. cxx support untested, but lifted from the original library rules file. [1] Than the file in the libopencm3-examples repo it is loosely based on.
This commit is contained in:
parent
f49cbee683
commit
34f00a7d5a
43
tests/gadget-zero/Makefile.stm32f4disco
Normal file
43
tests/gadget-zero/Makefile.stm32f4disco
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
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
|
23
tests/gadget-zero/README.md
Normal file
23
tests/gadget-zero/README.md
Normal file
@ -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:
|
||||
|
||||
|
59
tests/gadget-zero/main-stm32f4disco.c
Normal file
59
tests/gadget-zero/main-stm32f4disco.c
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of the libopencm3 project.
|
||||
*
|
||||
* Copyright (C) 2015 Karl Palsson <karlp@tweak.net.au>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libopencm3/cm3/nvic.h>
|
||||
#include <libopencm3/stm32/gpio.h>
|
||||
#include <libopencm3/stm32/rcc.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
|
13
tests/gadget-zero/openocd.stm32f4disco.cfg
Normal file
13
tests/gadget-zero/openocd.stm32f4disco.cfg
Normal file
@ -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
|
||||
|
4
tests/gadget-zero/stub.py
Normal file
4
tests/gadget-zero/stub.py
Normal file
@ -0,0 +1,4 @@
|
||||
__author__ = 'karlp'
|
||||
|
||||
def config_switch():
|
||||
pass
|
207
tests/gadget-zero/test_gadget0.py
Normal file
207
tests/gadget-zero/test_gadget0.py
Normal file
@ -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)))
|
304
tests/gadget-zero/usb-gadget0.c
Normal file
304
tests/gadget-zero/usb-gadget0.c
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* This file is part of the libopencm3 project.
|
||||
*
|
||||
* Copyright (C) 2015 Karl Palsson <karlp@tweak.net.au>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libopencm3/usb/usbd.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "usb-gadget0.h"
|
||||
|
||||
#define ER_DEBUG
|
||||
#ifdef ER_DEBUG
|
||||
#include <stdio.h>
|
||||
#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;
|
||||
}
|
35
tests/gadget-zero/usb-gadget0.h
Normal file
35
tests/gadget-zero/usb-gadget0.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of the libopencm3 project.
|
||||
*
|
||||
* Copyright (C) 2015 Karl Palsson <karlp@tweak.net.au>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef USB_GADGET0_H
|
||||
#define USB_GADGET0_H
|
||||
|
||||
#include <libopencm3/usb/usbd.h>
|
||||
|
||||
/**
|
||||
* 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
|
158
tests/rules.mk
Normal file
158
tests/rules.mk
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
# 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)
|
||||
|
54
tests/shared/trace.c
Normal file
54
tests/shared/trace.c
Normal file
@ -0,0 +1,54 @@
|
||||
#include <stdint.h>
|
||||
#include <libopencm3/cm3/common.h>
|
||||
#include <libopencm3/cm3/memorymap.h>
|
||||
#include <libopencm3/cm3/itm.h>
|
||||
#include "trace.h"
|
||||
|
||||
void trace_send_blocking8(int stimulus_port, char c) {
|
||||
if (!(ITM_TER[0] & (1<<stimulus_port))) {
|
||||
return;
|
||||
}
|
||||
while (!(ITM_STIM8(stimulus_port) & ITM_STIM_FIFOREADY))
|
||||
;
|
||||
ITM_STIM8(stimulus_port) = c;
|
||||
}
|
||||
|
||||
void trace_send8(int stimulus_port, char val) {
|
||||
if (!(ITM_TER[0] & (1<<stimulus_port))) {
|
||||
return;
|
||||
}
|
||||
ITM_STIM8(stimulus_port) = val;
|
||||
}
|
||||
|
||||
void trace_send_blocking16(int stimulus_port, uint16_t val) {
|
||||
if (!(ITM_TER[0] & (1<<stimulus_port))) {
|
||||
return;
|
||||
}
|
||||
while (!(ITM_STIM16(stimulus_port) & ITM_STIM_FIFOREADY))
|
||||
;
|
||||
ITM_STIM16(stimulus_port) = val;
|
||||
}
|
||||
|
||||
void trace_send16(int stimulus_port, uint16_t val) {
|
||||
if (!(ITM_TER[0] & (1<<stimulus_port))) {
|
||||
return;
|
||||
}
|
||||
ITM_STIM16(stimulus_port) = val;
|
||||
}
|
||||
|
||||
|
||||
void trace_send_blocking32(int stimulus_port, uint32_t val) {
|
||||
if (!(ITM_TER[0] & (1<<stimulus_port))) {
|
||||
return;
|
||||
}
|
||||
while (!(ITM_STIM32(stimulus_port) & ITM_STIM_FIFOREADY))
|
||||
;
|
||||
ITM_STIM32(stimulus_port) = val;
|
||||
}
|
||||
|
||||
void trace_send32(int stimulus_port, uint32_t val) {
|
||||
if (!(ITM_TER[0] & (1<<stimulus_port))) {
|
||||
return;
|
||||
}
|
||||
ITM_STIM32(stimulus_port) = val;
|
||||
}
|
32
tests/shared/trace.h
Normal file
32
tests/shared/trace.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* File: trace.h
|
||||
* Author: karlp
|
||||
*
|
||||
* Created on November 21, 2013, 4:35 PM
|
||||
*/
|
||||
|
||||
#ifndef TRACE_H
|
||||
#define TRACE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
|
34
tests/shared/trace_stdio.c
Normal file
34
tests/shared/trace_stdio.c
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* support for stdio output to a trace port
|
||||
* Karl Palsson, 2014 <karlp@remake.is>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user