rockbox/utils/hwstub/stub/main.c
James Buren 018372bf39 usb: implement macro for initializing USB strings
This uses the new unicode string literal feature that is available
now to greatly simplify the initialization of these special string
types. This makes them much more readable at a quick glance.

Change-Id: Iad8b49aa763486608e3bb7e83fb8abfb48ce0a7b
2021-03-25 13:26:03 -05:00

791 lines
23 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2013 by Amaury Pouly
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "stddef.h"
#include "config.h"
#include "protocol.h"
#include "logf.h"
#include "usb_ch9.h"
#include "usb_drv.h"
#include "memory.h"
#include "target.h"
#include "system.h"
extern unsigned char oc_codestart[];
extern unsigned char oc_codeend[];
extern unsigned char oc_stackstart[];
extern unsigned char oc_stackend[];
extern unsigned char oc_bufferstart[];
extern unsigned char oc_bufferend[];
#define oc_codesize ((size_t)(oc_codeend - oc_codestart))
#define oc_stacksize ((size_t)(oc_stackend - oc_stackstart))
#define oc_buffersize ((size_t)(oc_bufferend - oc_bufferstart))
static bool g_exit = false;
/**
*
* USB stack
*
*/
static struct usb_device_descriptor device_descriptor=
{
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = USB_CLASS_PER_INTERFACE,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 64,
.idVendor = HWSTUB_USB_VID,
.idProduct = HWSTUB_USB_PID,
.bcdDevice = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 0,
.bNumConfigurations = 1
};
#define USB_MAX_CURRENT 200
static struct usb_config_descriptor config_descriptor =
{
.bLength = sizeof(struct usb_config_descriptor),
.bDescriptorType = USB_DT_CONFIG,
.wTotalLength = 0, /* will be filled in later */
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
.bMaxPower = (USB_MAX_CURRENT + 1) / 2, /* In 2mA units */
};
#define USB_HWSTUB_INTF 0
static struct usb_interface_descriptor interface_descriptor =
{
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = USB_HWSTUB_INTF,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = HWSTUB_CLASS,
.bInterfaceSubClass = HWSTUB_SUBCLASS,
.bInterfaceProtocol = HWSTUB_PROTOCOL,
.iInterface = 3
};
static const struct usb_string_descriptor usb_string_iManufacturer =
USB_STRING_INITIALIZER(u"Rockbox.org");
static const struct usb_string_descriptor usb_string_iProduct =
USB_STRING_INITIALIZER(u"Rockbox hardware stub");
static const struct usb_string_descriptor usb_string_iInterface =
USB_STRING_INITIALIZER(u"HWStub");
/* this is stringid #0: languages supported */
static const struct usb_string_descriptor lang_descriptor =
USB_STRING_INITIALIZER(u"\x0409"); /* LANGID US English */
static struct hwstub_version_desc_t version_descriptor =
{
sizeof(struct hwstub_version_desc_t),
HWSTUB_DT_VERSION,
HWSTUB_VERSION_MAJOR,
HWSTUB_VERSION_MINOR,
HWSTUB_VERSION_REV
};
static struct hwstub_layout_desc_t layout_descriptor =
{
sizeof(struct hwstub_layout_desc_t),
HWSTUB_DT_LAYOUT,
0, 0, 0, 0, 0, 0
};
#define USB_NUM_STRINGS 5
static const struct usb_string_descriptor* const usb_strings[USB_NUM_STRINGS] =
{
&lang_descriptor,
&usb_string_iManufacturer,
&usb_string_iProduct,
&usb_string_iInterface
};
uint8_t *usb_buffer = oc_bufferstart;
uint32_t usb_buffer_size = 0;
static void fill_layout_info(void)
{
layout_descriptor.dCodeStart = (uint32_t)oc_codestart;
layout_descriptor.dCodeSize = oc_codesize;
layout_descriptor.dStackStart = (uint32_t)oc_stackstart;
layout_descriptor.dStackSize = oc_stacksize;
layout_descriptor.dBufferStart = (uint32_t)oc_bufferstart;
layout_descriptor.dBufferSize = oc_buffersize;
}
static void handle_std_dev_desc(struct usb_ctrlrequest *req)
{
int size;
void* ptr = NULL;
unsigned index = req->wValue & 0xff;
switch(req->wValue >> 8)
{
case USB_DT_DEVICE:
ptr = &device_descriptor;
size = sizeof(struct usb_device_descriptor);
break;
case USB_DT_OTHER_SPEED_CONFIG:
case USB_DT_CONFIG:
{
/* int max_packet_size; */
/* config desc */
if((req->wValue >> 8) == USB_DT_CONFIG)
{
/* max_packet_size = (usb_drv_port_speed() ? 512 : 64); */
config_descriptor.bDescriptorType = USB_DT_CONFIG;
}
else
{
/* max_packet_size = (usb_drv_port_speed() ? 64 : 512); */
config_descriptor.bDescriptorType = USB_DT_OTHER_SPEED_CONFIG;
}
size = sizeof(struct usb_config_descriptor);
/* interface desc */
memcpy(usb_buffer + size, (void *)&interface_descriptor,
sizeof(interface_descriptor));
size += sizeof(interface_descriptor);
/* hwstub version */
memcpy(usb_buffer + size, (void *)&version_descriptor,
sizeof(version_descriptor));
size += sizeof(version_descriptor);
/* hwstub layout */
fill_layout_info();
memcpy(usb_buffer + size, (void *)&layout_descriptor,
sizeof(layout_descriptor));
size += sizeof(layout_descriptor);
/* hwstub target */
fill_layout_info();
memcpy(usb_buffer + size, (void *)&target_descriptor,
sizeof(target_descriptor));
size += sizeof(target_descriptor);
/* target specific descriptors */
target_get_config_desc(usb_buffer + size, &size);
/* fix config descriptor */
config_descriptor.wTotalLength = size;
memcpy(usb_buffer, (void *)&config_descriptor,
sizeof(config_descriptor));
ptr = usb_buffer;
break;
}
case USB_DT_STRING:
if(index < USB_NUM_STRINGS)
{
size = usb_strings[index]->bLength;
ptr = (void *)usb_strings[index];
}
else
usb_drv_stall(EP_CONTROL, true, true);
break;
default:
break;
}
if(ptr)
{
int length = MIN(size, req->wLength);
if(ptr != usb_buffer)
memcpy(usb_buffer, ptr, length);
usb_drv_send(EP_CONTROL, usb_buffer, length);
usb_drv_recv(EP_CONTROL, NULL, 0);
}
else
usb_drv_stall(EP_CONTROL, true, true);
}
static void handle_std_dev_req(struct usb_ctrlrequest *req)
{
switch(req->bRequest)
{
case USB_REQ_GET_CONFIGURATION:
usb_buffer[0] = 1;
usb_drv_send(EP_CONTROL, usb_buffer, 1);
usb_drv_recv(EP_CONTROL, NULL, 0);
break;
case USB_REQ_SET_CONFIGURATION:
usb_drv_send(EP_CONTROL, NULL, 0);
break;
case USB_REQ_GET_DESCRIPTOR:
handle_std_dev_desc(req);
break;
case USB_REQ_SET_ADDRESS:
usb_drv_send(EP_CONTROL, NULL, 0);
usb_drv_set_address(req->wValue);
break;
case USB_REQ_GET_STATUS:
usb_buffer[0] = 0;
usb_buffer[1] = 0;
usb_drv_send(EP_CONTROL, usb_buffer, 2);
usb_drv_recv(EP_CONTROL, NULL, 0);
break;
default:
usb_drv_stall(EP_CONTROL, true, true);
}
}
static void handle_std_intf_desc(struct usb_ctrlrequest *req)
{
int size;
void* ptr = NULL;
switch(req->wValue >> 8)
{
case HWSTUB_DT_VERSION:
ptr = &version_descriptor;
size = sizeof(version_descriptor);
break;
case HWSTUB_DT_LAYOUT:
ptr = &layout_descriptor;
size = sizeof(layout_descriptor);
break;
case HWSTUB_DT_TARGET:
ptr = &target_descriptor;
size = sizeof(target_descriptor);
break;
default:
target_get_desc(req->wValue >> 8, &ptr);
if(ptr != 0)
size = ((struct usb_descriptor_header *)ptr)->bLength;
break;
}
if(ptr)
{
int length = MIN(size, req->wLength);
if(ptr != usb_buffer)
memcpy(usb_buffer, ptr, length);
usb_drv_send(EP_CONTROL, usb_buffer, length);
usb_drv_recv(EP_CONTROL, NULL, 0);
}
else
usb_drv_stall(EP_CONTROL, true, true);
}
static void handle_std_intf_req(struct usb_ctrlrequest *req)
{
unsigned intf = req->wIndex & 0xff;
if(intf != USB_HWSTUB_INTF)
return usb_drv_stall(EP_CONTROL, true, true);
switch(req->bRequest)
{
case USB_REQ_GET_DESCRIPTOR:
handle_std_intf_desc(req);
break;
default:
usb_drv_stall(EP_CONTROL, true, true);
}
}
static void handle_std_req(struct usb_ctrlrequest *req)
{
switch(req->bRequestType & USB_RECIP_MASK)
{
case USB_RECIP_DEVICE:
return handle_std_dev_req(req);
case USB_RECIP_INTERFACE:
return handle_std_intf_req(req);
default:
usb_drv_stall(EP_CONTROL, true, true);
}
}
static void handle_get_log(struct usb_ctrlrequest *req)
{
enable_logf(false);
int length = logf_readback(usb_buffer, MIN(req->wLength, usb_buffer_size));
usb_drv_send(EP_CONTROL, usb_buffer, length);
usb_drv_recv(EP_CONTROL, NULL, 0);
enable_logf(true);
}
/* default implementation, relying on the compiler to produce correct code,
* targets should reimplement this... */
uint8_t __attribute__((weak)) target_read8(const void *addr)
{
return *(volatile uint8_t *)addr;
}
uint16_t __attribute__((weak)) target_read16(const void *addr)
{
return *(volatile uint16_t *)addr;
}
uint32_t __attribute__((weak)) target_read32(const void *addr)
{
return *(volatile uint32_t *)addr;
}
void __attribute__((weak)) target_write8(void *addr, uint8_t val)
{
*(volatile uint8_t *)addr = val;
}
void __attribute__((weak)) target_write16(void *addr, uint16_t val)
{
*(volatile uint16_t *)addr = val;
}
void __attribute__((weak)) target_write32(void *addr, uint32_t val)
{
*(volatile uint32_t *)addr = val;
}
static bool read_atomic(void *dst, void *src, size_t sz)
{
switch(sz)
{
case 1: *(uint8_t *)dst = target_read8(src); return true;
case 2: *(uint16_t *)dst = target_read16(src); return true;
case 4: *(uint32_t *)dst = target_read32(src); return true;
default: return false;
}
}
static void *last_read_addr = 0;
static uint16_t last_read_id = 0xffff;
static size_t last_read_max_size = 0;
static void handle_read(struct usb_ctrlrequest *req)
{
uint16_t id = req->wValue;
if(req->bRequest == HWSTUB_READ)
{
int size = usb_drv_recv(EP_CONTROL, usb_buffer, req->wLength);
if(size != sizeof(struct hwstub_read_req_t))
return usb_drv_stall(EP_CONTROL, true, true);
asm volatile("nop" : : : "memory");
struct hwstub_read_req_t *read = (void *)usb_buffer;
last_read_addr = (void *)read->dAddress;
last_read_max_size = usb_buffer_size;
last_read_id = id;
usb_drv_send(EP_CONTROL, NULL, 0);
}
else
{
/* NOTE: READ2 is also called after a coprocessor operation */
if(id != last_read_id)
return usb_drv_stall(EP_CONTROL, true, true);
size_t len = MIN(req->wLength, last_read_max_size);
int ret = set_exception_jmp();
if(ret == 0)
{
if(req->bRequest == HWSTUB_READ2_ATOMIC)
{
if(!read_atomic(usb_buffer, last_read_addr, len))
return usb_drv_stall(EP_CONTROL, true, true);
}
else
{
memcpy(usb_buffer, last_read_addr, len);
asm volatile("nop" : : : "memory");
}
}
else
{
logf("trapped exception %d in read [0x%x,0x%x]\n", ret, last_read_addr,
last_read_addr + len);
return usb_drv_stall(EP_CONTROL, true, true);
}
usb_drv_send(EP_CONTROL, usb_buffer, len);
usb_drv_recv(EP_CONTROL, NULL, 0);
}
}
static bool write_atomic(void *dst, void *src, size_t sz)
{
switch(sz)
{
case 1: target_write8(dst, *(uint8_t *)src); return true;
case 2: target_write16(dst, *(uint16_t *)src); return true;
case 4: target_write32(dst, *(uint32_t *)src); return true;
default: return false;
}
}
static void handle_write(struct usb_ctrlrequest *req)
{
int size = usb_drv_recv(EP_CONTROL, usb_buffer, req->wLength);
asm volatile("nop" : : : "memory");
struct hwstub_write_req_t *write = (void *)usb_buffer;
int sz_hdr = sizeof(struct hwstub_write_req_t);
if(size < sz_hdr)
return usb_drv_stall(EP_CONTROL, true, true);
int ret = set_exception_jmp();
if(ret == 0)
{
if(req->bRequest == HWSTUB_WRITE_ATOMIC)
{
if(!write_atomic((void *)write->dAddress,
usb_buffer + sz_hdr, size - sz_hdr))
return usb_drv_stall(EP_CONTROL, true, true);
}
else
{
memcpy((void *)write->dAddress,
usb_buffer + sz_hdr, size - sz_hdr);
}
}
else
{
logf("trapped exception %d in write [0x%x,0x%x]\n", ret, write->dAddress,
write->dAddress + size - sz_hdr);
return usb_drv_stall(EP_CONTROL, true, true);
}
usb_drv_send(EP_CONTROL, NULL, 0);
}
static bool do_call(uint32_t addr)
{
/* trap exceptions */
int ret = set_exception_jmp();
if(ret == 0)
{
#if defined(CPU_ARM)
/* in case of call, respond after return */
asm volatile("blx %0\n" : : "r"(addr) : "memory");
return true;
#elif defined(CPU_MIPS)
asm volatile("jalr %0\nnop\n" : : "r"(addr) : "memory");
return true;
#else
#warning call is unsupported on this platform
return false;
#endif
}
else
{
logf("trapped exception %d in call\n", ret);
return false;
}
}
static void do_jump(uint32_t addr)
{
#if defined(CPU_ARM)
asm volatile("bx %0\n" : : "r" (addr) : "memory");
#elif defined(CPU_MIPS)
asm volatile("jr %0\nnop\n" : : "r" (addr) : "memory");
#else
#warning jump is unsupported on this platform
#define NO_JUMP
#endif
}
static void handle_exec(struct usb_ctrlrequest *req)
{
int size = usb_drv_recv(EP_CONTROL, usb_buffer, req->wLength);
asm volatile("nop" : : : "memory");
struct hwstub_exec_req_t *exec = (void *)usb_buffer;
if(size != sizeof(struct hwstub_exec_req_t))
return usb_drv_stall(EP_CONTROL, true, true);
uint32_t addr = exec->dAddress;
#if defined(CPU_ARM)
if(exec->bmFlags & HWSTUB_EXEC_THUMB)
addr |= 1;
else
addr &= ~1;
#endif
#ifdef CONFIG_FLUSH_CACHES
target_flush_caches();
#endif
if(exec->bmFlags & HWSTUB_EXEC_CALL)
{
if(do_call(addr))
usb_drv_send(EP_CONTROL, NULL, 0);
else
usb_drv_stall(EP_CONTROL, true, true);
}
else
{
#ifndef NO_JUMP
/* in case of jump, respond immediately and disconnect usb */
usb_drv_send(EP_CONTROL, NULL, 0);
usb_drv_exit();
do_jump(addr);
#else
usb_drv_stall(EP_CONTROL, true, true);
#endif
}
}
#ifdef CPU_MIPS
static uint32_t rw_cp0_inst_buffer[3];
typedef uint32_t (*read_cp0_inst_buffer_fn_t)(void);
typedef void (*write_cp0_inst_buffer_fn_t)(uint32_t);
uint32_t mips_read_cp0(unsigned reg, unsigned sel)
{
/* ok this is tricky because the coprocessor read instruction encoding
* contains the register and select, so we need to generate the instruction
* on the fly, we generate a "function like" buffer with three instructions:
* mfc0 v0, reg, sel
* jr ra
* nop
*/
rw_cp0_inst_buffer[0] = 0x40000000 | /*v0*/2 << 16 | (sel & 0x7) | (reg & 0x1f) << 11;
rw_cp0_inst_buffer[1] = /*ra*/31 << 21 | 0x8; /* jr ra */
rw_cp0_inst_buffer[2] = 0; /* nop */
#ifdef CONFIG_FLUSH_CACHES
target_flush_caches();
#endif
read_cp0_inst_buffer_fn_t fn = (read_cp0_inst_buffer_fn_t)rw_cp0_inst_buffer;
return fn();
}
void mips_write_cp0(unsigned reg, unsigned sel, uint32_t val)
{
/* ok this is tricky because the coprocessor write instruction encoding
* contains the register and select, so we need to generate the instruction
* on the fly, we generate a "function like" buffer with three instructions:
* mtc0 a0, reg, sel
* jr ra
* nop
*/
rw_cp0_inst_buffer[0] = 0x40800000 | /*a0*/4 << 16 | (sel & 0x7) | (reg & 0x1f) << 11;
rw_cp0_inst_buffer[1] = /*ra*/31 << 21 | 0x8; /* jr ra */
rw_cp0_inst_buffer[2] = 0; /* nop */
#ifdef CONFIG_FLUSH_CACHES
target_flush_caches();
#endif
write_cp0_inst_buffer_fn_t fn = (write_cp0_inst_buffer_fn_t)rw_cp0_inst_buffer;
fn(val);
}
#endif
/* coprocessor read: return <0 on error (-2 for dull dump), or size to return
* to host otherwise */
int cop_read(uint8_t args[HWSTUB_COP_ARGS], void *out_data, size_t out_max_sz)
{
/* virtually all targets do register-based operation, so 32-bit */
if(out_max_sz < 4)
{
logf("cop read failed: output buffer is too small\n");
return -1;
}
#ifdef CPU_MIPS
if(args[HWSTUB_COP_MIPS_COP] != 0)
{
logf("cop read failed: only mips cp0 is supported\n");
return -2;
}
*(uint32_t *)out_data = mips_read_cp0(args[HWSTUB_COP_MIPS_REG], args[HWSTUB_COP_MIPS_SEL]);
return 4;
#else
(void) args;
(void) out_data;
(void) out_max_sz;
logf("cop read failed: unsupported cpu\n");
return -1;
#endif
}
/* coprocessor write: return <0 on error (-2 for dull dump), or 0 on success */
int cop_write(uint8_t args[HWSTUB_COP_ARGS], const void *in_data, size_t in_sz)
{
/* virtually all targets do register-based operation, so 32-bit */
if(in_sz != 4)
{
logf("cop read failed: input buffer has wrong size\n");
return -1;
}
#ifdef CPU_MIPS
if(args[HWSTUB_COP_MIPS_COP] != 0)
{
logf("cop read failed: only mips cp0 is supported\n");
return -2;
}
mips_write_cp0(args[HWSTUB_COP_MIPS_REG], args[HWSTUB_COP_MIPS_SEL], *(uint32_t *)in_data);
return 0;
#else
(void) args;
(void) in_data;
(void) in_sz;
logf("cop write failed: unsupported cpu\n");
return -1;
#endif
}
/* return size to return to host or <0 on error */
int do_cop_op(struct hwstub_cop_req_t *cop, void *in_data, size_t in_sz,
void *out_data, size_t out_max_sz)
{
int ret = -2; /* -2 means full debug dump */
/* handle operations */
if(cop->bOp == HWSTUB_COP_READ)
{
/* read cannot have extra data */
if(in_sz > 0)
goto Lerr;
ret = cop_read(cop->bArgs, out_data, out_max_sz);
}
else if(cop->bOp == HWSTUB_COP_WRITE)
{
ret = cop_write(cop->bArgs, in_data, in_sz);
}
Lerr:
if(ret == -2)
{
/* debug output */
logf("invalid cop op: %d, ", cop->bOp);
for(int i = 0; i < HWSTUB_COP_ARGS; i++)
logf("%c0x%x", i == 0 ? '[' : ',', cop->bArgs[i]);
logf("] in:%d\n", in_sz);
}
return ret;
}
static void handle_cop(struct usb_ctrlrequest *req)
{
int size = usb_drv_recv(EP_CONTROL, usb_buffer, req->wLength);
int hdr_sz = sizeof(struct hwstub_cop_req_t);
asm volatile("nop" : : : "memory");
struct hwstub_cop_req_t *cop = (void *)usb_buffer;
/* request should at least contain the header */
if(size < hdr_sz)
return usb_drv_stall(EP_CONTROL, true, true);
/* perform coprocessor operation: put output buffer after the input one,
* limit output buffer size to maximum buffer size */
uint8_t *in_buf = usb_buffer + hdr_sz;
size_t in_sz = req->wLength - hdr_sz;
uint8_t *out_buf = in_buf + in_sz;
size_t out_max_sz = usb_buffer_size - req->wLength;
int ret = do_cop_op(cop, in_buf, in_sz, out_buf, out_max_sz);
/* STALL on error */
if(ret < 0)
return usb_drv_stall(EP_CONTROL, true, true);
/* acknowledge */
usb_drv_send(EP_CONTROL, NULL, 0);
/* if there is a read stage, prepare everything for the READ2 */
if(ret > 0)
{
last_read_id = req->wValue;
last_read_addr = out_buf;
last_read_max_size = ret;
}
}
static void handle_class_intf_req(struct usb_ctrlrequest *req)
{
unsigned intf = req->wIndex & 0xff;
if(intf != USB_HWSTUB_INTF)
return usb_drv_stall(EP_CONTROL, true, true);
switch(req->bRequest)
{
case HWSTUB_GET_LOG:
return handle_get_log(req);
case HWSTUB_READ:
case HWSTUB_READ2:
case HWSTUB_READ2_ATOMIC:
return handle_read(req);
case HWSTUB_WRITE:
case HWSTUB_WRITE_ATOMIC:
return handle_write(req);
case HWSTUB_EXEC:
return handle_exec(req);
case HWSTUB_COPROCESSOR_OP:
return handle_cop(req);
default:
usb_drv_stall(EP_CONTROL, true, true);
}
}
static void handle_class_req(struct usb_ctrlrequest *req)
{
switch(req->bRequestType & USB_RECIP_MASK)
{
case USB_RECIP_INTERFACE:
return handle_class_intf_req(req);
case USB_RECIP_DEVICE:
//return handle_class_dev_req(req);
default:
usb_drv_stall(EP_CONTROL, true, true);
}
}
/**
*
* Main
*
*/
void main(uint32_t arg)
{
usb_buffer_size = oc_buffersize;
logf("hwstub %d.%d.%d\n", HWSTUB_VERSION_MAJOR, HWSTUB_VERSION_MINOR,
HWSTUB_VERSION_REV);
logf("argument: 0x%08x\n", arg);
target_init();
usb_drv_init();
while(!g_exit)
{
struct usb_ctrlrequest req;
usb_drv_recv_setup(&req);
switch(req.bRequestType & USB_TYPE_MASK)
{
case USB_TYPE_STANDARD:
handle_std_req(&req);
break;
case USB_TYPE_CLASS:
handle_class_req(&req);
break;
default:
usb_drv_stall(EP_CONTROL, true, true);
}
}
usb_drv_exit();
}