libopencm3/tests/gadget-zero/usb-gadget0.c

409 lines
11 KiB
C

/*
* 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 "delay.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 GZ_REQ_PRODUCE 2
#define GZ_REQ_SET_ALIGNED 3
#define GZ_REQ_SET_UNALIGNED 4
#define INTEL_COMPLIANCE_WRITE 0x5b
#define INTEL_COMPLIANCE_READ 0x5c
/* USB configurations */
#define GZ_CFG_SOURCESINK 2
#define GZ_CFG_LOOPBACK 3
#define BULK_EP_MAXPACKET 64
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 = BULK_EP_MAXPACKET,
/* 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 = BULK_EP_MAXPACKET,
.bInterval = 1,
},
{
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x81,
.bmAttributes = USB_ENDPOINT_ATTR_BULK,
.wMaxPacketSize = BULK_EP_MAXPACKET,
.bInterval = 1,
},
{
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x2,
.bmAttributes = USB_ENDPOINT_ATTR_BULK,
.wMaxPacketSize = BULK_EP_MAXPACKET,
.bInterval = 1,
},
{
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x82,
.bmAttributes = USB_ENDPOINT_ATTR_BULK,
.wMaxPacketSize = BULK_EP_MAXPACKET,
.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 = 4,
.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[5*BULK_EP_MAXPACKET];
static usbd_device *our_dev;
/* Private global for state */
static struct {
uint8_t pattern;
int pattern_counter;
int test_unaligned; /* If 0 (default), use 16-bit aligned buffers. This should not be declared as bool */
} state = {
.pattern = 0,
.pattern_counter = 0,
.test_unaligned = 0,
};
static void gadget0_ss_out_cb(usbd_device *usbd_dev, uint8_t ep)
{
(void) ep;
uint16_t x;
/* TODO - if you're really keen, perf test this. tiva implies it matters */
/* char buf[64] __attribute__ ((aligned(4))); */
uint8_t buf[BULK_EP_MAXPACKET + 1] __attribute__ ((aligned(2)));
uint8_t *dest;
trace_send_blocking8(0, 'O');
if (state.test_unaligned) {
dest = buf + 1;
} else {
dest = buf;
}
x = usbd_ep_read_packet(usbd_dev, ep, dest, BULK_EP_MAXPACKET);
trace_send_blocking8(1, x);
}
static void gadget0_ss_in_cb(usbd_device *usbd_dev, uint8_t ep)
{
(void) usbd_dev;
uint8_t buf[BULK_EP_MAXPACKET + 1] __attribute__ ((aligned(2)));
uint8_t *src;
trace_send_blocking8(0, 'I');
if (state.test_unaligned) {
src = buf + 1;
} else {
src = buf;
}
switch (state.pattern) {
case 0:
memset(src, 0, BULK_EP_MAXPACKET);
break;
case 1:
for (unsigned i = 0; i < BULK_EP_MAXPACKET; i++) {
src[i] = state.pattern_counter++ % 63;
}
break;
}
uint16_t x = usbd_ep_write_packet(usbd_dev, ep, src, BULK_EP_MAXPACKET);
/* As we are calling write in the callback, this should never fail */
trace_send_blocking8(2, x);
if (x != BULK_EP_MAXPACKET) {
ER_DPRINTF("failed to write?: %d\n", x);
}
/*assert(x == sizeof(buf));*/
}
static void gadget0_in_cb_loopback(usbd_device *usbd_dev, uint8_t ep)
{
(void) usbd_dev;
ER_DPRINTF("loop IN %x\n", ep);
/* Nothing to do here, basically just indicates they read us. */
}
static void gadget0_out_cb_loopback(usbd_device *usbd_dev, uint8_t ep)
{
uint8_t buf[BULK_EP_MAXPACKET];
/* Copy data we received on OUT ep back to the paired IN ep */
int x = usbd_ep_read_packet(usbd_dev, ep, buf, BULK_EP_MAXPACKET);
int y = usbd_ep_write_packet(usbd_dev, 0x80 | ep, buf, x);
ER_DPRINTF("loop OUT %x got %d => %d\n", ep, x, y);
}
static enum usbd_request_return_codes gadget0_control_request(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf,
uint16_t *len,
usbd_control_complete_callback *complete)
{
(void) usbd_dev;
(void) complete;
(void) buf;
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:
/* accept correctly formed ctrl writes */
if (req->bmRequestType != (USB_REQ_TYPE_VENDOR|USB_REQ_TYPE_INTERFACE)) {
return USBD_REQ_NOTSUPP;
}
if (req->wValue || req->wIndex) {
return USBD_REQ_NOTSUPP;
}
if (req->wLength > sizeof(usbd_control_buffer)) {
return USBD_REQ_NOTSUPP;
}
/* ok, mark it as accepted. */
return USBD_REQ_HANDLED;
case INTEL_COMPLIANCE_READ:
if (req->bmRequestType != (USB_REQ_TYPE_IN|USB_REQ_TYPE_VENDOR|USB_REQ_TYPE_INTERFACE)) {
return USBD_REQ_NOTSUPP;
}
if (req->wValue || req->wIndex) {
return USBD_REQ_NOTSUPP;
}
if (req->wLength > sizeof(usbd_control_buffer)) {
return USBD_REQ_NOTSUPP;
}
/* ok, return what they left there earlier */
*len = req->wLength;
return USBD_REQ_HANDLED;
case GZ_REQ_SET_UNALIGNED:
state.test_unaligned = 1;
return USBD_REQ_HANDLED;
case GZ_REQ_SET_ALIGNED:
state.test_unaligned = 0;
return USBD_REQ_HANDLED;
case GZ_REQ_PRODUCE:
ER_DPRINTF("fake loopback of %d\n", req->wValue);
if (req->wValue > sizeof(usbd_control_buffer)) {
ER_DPRINTF("Can't write more than out control buffer! %d > %d\n",
req->wValue, sizeof(usbd_control_buffer));
return USBD_REQ_NOTSUPP;
}
/* Don't produce more than asked for! */
if (req->wValue > req->wLength) {
ER_DPRINTF("Truncating reply to match wLen\n");
*len = req->wLength;
} else {
*len = req->wValue;
}
return USBD_REQ_HANDLED;
default:
ER_DPRINTF("Unhandled request!\n");
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:
state.test_unaligned = 0;
usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, BULK_EP_MAXPACKET,
gadget0_ss_out_cb);
usbd_ep_setup(usbd_dev, 0x81, USB_ENDPOINT_ATTR_BULK, BULK_EP_MAXPACKET,
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, 0x81);
break;
case GZ_CFG_LOOPBACK:
/*
* The ordering here is important, as it defines the addresses
* locality. We want to have both out endpoints in sequentially,
* so we can test for overrunning our memory space, if that's a
* concern on the usb peripheral.
*/
usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, BULK_EP_MAXPACKET,
gadget0_out_cb_loopback);
usbd_ep_setup(usbd_dev, 0x02, USB_ENDPOINT_ATTR_BULK, BULK_EP_MAXPACKET,
gadget0_out_cb_loopback);
usbd_ep_setup(usbd_dev, 0x81, USB_ENDPOINT_ATTR_BULK, BULK_EP_MAXPACKET,
gadget0_in_cb_loopback);
usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, BULK_EP_MAXPACKET,
gadget0_in_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);
delay_setup();
return our_dev;
}
void gadget0_run(usbd_device *usbd_dev)
{
usbd_poll(usbd_dev);
/* This should be more than allowable! */
delay_us(100);
}