mirror of https://gitee.com/openkylin/qemu.git
husb: Make control transactions asynchronous (Max Krasnyansky)
USB is 99.8% async now :). 0.2% is the three control requests that we need to execute synchronously. We could off-load that to a thread or something but it's not worth the pain since those requests are performed only during device initialization (ie when device is connected to the VM). The change is a bit bigger than I wanted due to the fact that generic handle_packet()/handle_control() interface was not designed for async transactions. So I ended up adding custom handle_packet() code to usb-linux. We can make that generic if/when some other component needs it. Signed-off-by: Max Krasnyansky <maxk@kernel.org> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@5204 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
parent
9d0efc88e4
commit
446ab1284e
460
usb-linux.c
460
usb-linux.c
|
@ -25,28 +25,20 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qemu-timer.h"
|
||||
#include "hw/usb.h"
|
||||
#include "console.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <dirent.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/usbdevice_fs.h>
|
||||
#include <linux/version.h>
|
||||
#include <signal.h>
|
||||
|
||||
/* We redefine it to avoid version problems */
|
||||
struct usb_ctrltransfer {
|
||||
uint8_t bRequestType;
|
||||
uint8_t bRequest;
|
||||
uint16_t wValue;
|
||||
uint16_t wIndex;
|
||||
uint16_t wLength;
|
||||
uint32_t timeout;
|
||||
void *data;
|
||||
};
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usbdevice_fs.h>
|
||||
#include <linux/version.h>
|
||||
#include "hw/usb.h"
|
||||
|
||||
typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
|
||||
int vendor_id, int product_id,
|
||||
|
@ -54,7 +46,6 @@ typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
|
|||
static int usb_host_find_device(int *pbus_num, int *paddr,
|
||||
char *product_name, int product_name_size,
|
||||
const char *devname);
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#ifdef DEBUG
|
||||
|
@ -67,14 +58,32 @@ static int usb_host_find_device(int *pbus_num, int *paddr,
|
|||
#define PRODUCT_NAME_SZ 32
|
||||
#define MAX_ENDPOINTS 16
|
||||
|
||||
struct sigaction sigact;
|
||||
|
||||
/* endpoint association data */
|
||||
struct endp_data {
|
||||
uint8_t type;
|
||||
uint8_t halted;
|
||||
};
|
||||
|
||||
enum {
|
||||
CTRL_STATE_IDLE = 0,
|
||||
CTRL_STATE_SETUP,
|
||||
CTRL_STATE_DATA,
|
||||
CTRL_STATE_ACK
|
||||
};
|
||||
|
||||
/*
|
||||
* Control transfer state.
|
||||
* Note that 'buffer' _must_ follow 'req' field because
|
||||
* we need contigious buffer when we submit control URB.
|
||||
*/
|
||||
struct ctrl_struct {
|
||||
uint16_t len;
|
||||
uint16_t offset;
|
||||
uint8_t state;
|
||||
struct usb_ctrlrequest req;
|
||||
uint8_t buffer[1024];
|
||||
};
|
||||
|
||||
typedef struct USBHostDevice {
|
||||
USBDevice dev;
|
||||
int fd;
|
||||
|
@ -82,8 +91,10 @@ typedef struct USBHostDevice {
|
|||
uint8_t descr[1024];
|
||||
int descr_len;
|
||||
int configuration;
|
||||
int ninterfaces;
|
||||
int closing;
|
||||
|
||||
struct ctrl_struct ctrl;
|
||||
struct endp_data endp_table[MAX_ENDPOINTS];
|
||||
|
||||
/* Host side address */
|
||||
|
@ -172,6 +183,26 @@ static void async_free(AsyncURB *aurb)
|
|||
qemu_free(aurb);
|
||||
}
|
||||
|
||||
static void async_complete_ctrl(USBHostDevice *s, USBPacket *p)
|
||||
{
|
||||
switch(s->ctrl.state) {
|
||||
case CTRL_STATE_SETUP:
|
||||
if (p->len < s->ctrl.len)
|
||||
s->ctrl.len = p->len;
|
||||
s->ctrl.state = CTRL_STATE_DATA;
|
||||
p->len = 8;
|
||||
break;
|
||||
|
||||
case CTRL_STATE_ACK:
|
||||
s->ctrl.state = CTRL_STATE_IDLE;
|
||||
p->len = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void async_complete(void *opaque)
|
||||
{
|
||||
USBHostDevice *s = opaque;
|
||||
|
@ -204,6 +235,8 @@ static void async_complete(void *opaque)
|
|||
switch (aurb->urb.status) {
|
||||
case 0:
|
||||
p->len = aurb->urb.actual_length;
|
||||
if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL)
|
||||
async_complete_ctrl(s, p);
|
||||
break;
|
||||
|
||||
case -EPIPE:
|
||||
|
@ -237,7 +270,7 @@ static void async_cancel(USBPacket *unused, void *opaque)
|
|||
}
|
||||
}
|
||||
|
||||
static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
|
||||
static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration)
|
||||
{
|
||||
int dev_descr_len, config_descr_len;
|
||||
int interface, nb_interfaces, nb_configurations;
|
||||
|
@ -246,6 +279,8 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
|
|||
if (configuration == 0) /* address state - ignore */
|
||||
return 1;
|
||||
|
||||
dprintf("husb: claiming interfaces. config %d\n", configuration);
|
||||
|
||||
i = 0;
|
||||
dev_descr_len = dev->descr[0];
|
||||
if (dev_descr_len > dev->descr_len)
|
||||
|
@ -265,8 +300,10 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
|
|||
|
||||
printf("husb: config #%d need %d\n", dev->descr[i + 5], configuration);
|
||||
|
||||
if (configuration < 0 || configuration == dev->descr[i + 5])
|
||||
if (configuration < 0 || configuration == dev->descr[i + 5]) {
|
||||
configuration = dev->descr[i + 5];
|
||||
break;
|
||||
}
|
||||
|
||||
i += config_descr_len;
|
||||
}
|
||||
|
@ -310,6 +347,25 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
|
|||
printf("husb: %d interfaces claimed for configuration %d\n",
|
||||
nb_interfaces, configuration);
|
||||
|
||||
dev->ninterfaces = nb_interfaces;
|
||||
dev->configuration = configuration;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int usb_host_release_interfaces(USBHostDevice *s)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
dprintf("husb: releasing interfaces\n");
|
||||
|
||||
for (i = 0; i < s->ninterfaces; i++) {
|
||||
ret = ioctl(s->fd, USBDEVFS_RELEASEINTERFACE, &i);
|
||||
if (ret < 0) {
|
||||
perror("husb: failed to release interface");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -320,7 +376,8 @@ static void usb_host_handle_reset(USBDevice *dev)
|
|||
dprintf("husb: reset device %u.%u\n", s->bus_num, s->addr);
|
||||
|
||||
ioctl(s->fd, USBDEVFS_RESET);
|
||||
usb_host_update_interfaces(s, s->configuration);
|
||||
|
||||
usb_host_claim_interfaces(s, s->configuration);
|
||||
}
|
||||
|
||||
static void usb_host_handle_destroy(USBDevice *dev)
|
||||
|
@ -343,73 +400,10 @@ static void usb_host_handle_destroy(USBDevice *dev)
|
|||
|
||||
static int usb_linux_update_endp_table(USBHostDevice *s);
|
||||
|
||||
static int usb_host_handle_control(USBDevice *dev,
|
||||
int request,
|
||||
int value,
|
||||
int index,
|
||||
int length,
|
||||
uint8_t *data)
|
||||
static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
|
||||
{
|
||||
USBHostDevice *s = (USBHostDevice *)dev;
|
||||
struct usb_ctrltransfer ct;
|
||||
struct usbdevfs_setinterface si;
|
||||
int intf_update_required = 0;
|
||||
int ret;
|
||||
|
||||
if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) {
|
||||
/* specific SET_ADDRESS support */
|
||||
dev->addr = value;
|
||||
return 0;
|
||||
} else if (request == ((USB_RECIP_INTERFACE << 8) |
|
||||
USB_REQ_SET_INTERFACE)) {
|
||||
/* set alternate setting for the interface */
|
||||
si.interface = index;
|
||||
si.altsetting = value;
|
||||
ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si);
|
||||
usb_linux_update_endp_table(s);
|
||||
} else if (request == (DeviceOutRequest | USB_REQ_SET_CONFIGURATION)) {
|
||||
dprintf("husb: ctrl set config %d\n", value & 0xff);
|
||||
if (s->configuration != (value & 0xff)) {
|
||||
s->configuration = (value & 0xff);
|
||||
intf_update_required = 1;
|
||||
}
|
||||
goto do_request;
|
||||
} else {
|
||||
do_request:
|
||||
ct.bRequestType = request >> 8;
|
||||
ct.bRequest = request;
|
||||
ct.wValue = value;
|
||||
ct.wIndex = index;
|
||||
ct.wLength = length;
|
||||
ct.timeout = 50;
|
||||
ct.data = data;
|
||||
ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
|
||||
|
||||
dprintf("husb: ctrl req 0x%x val 0x%x index %u len %u ret %d\n",
|
||||
ct.bRequest, ct.wValue, ct.wIndex, ct.wLength, ret);
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
switch(errno) {
|
||||
case ETIMEDOUT:
|
||||
return USB_RET_NAK;
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
} else {
|
||||
if (intf_update_required) {
|
||||
dprintf("husb: updating interfaces\n");
|
||||
usb_host_update_interfaces(s, value & 0xff);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBHostDevice *s = (USBHostDevice *) dev;
|
||||
AsyncURB *aurb;
|
||||
struct usbdevfs_urb *urb;
|
||||
AsyncURB *aurb;
|
||||
int ret;
|
||||
|
||||
aurb = async_alloc();
|
||||
|
@ -474,12 +468,292 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
|
|||
return USB_RET_ASYNC;
|
||||
}
|
||||
|
||||
static int ctrl_error(void)
|
||||
{
|
||||
if (errno == ETIMEDOUT)
|
||||
return USB_RET_NAK;
|
||||
else
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
|
||||
static int usb_host_set_address(USBHostDevice *s, int addr)
|
||||
{
|
||||
dprintf("husb: ctrl set addr %u\n", addr);
|
||||
s->dev.addr = addr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_host_set_config(USBHostDevice *s, int config)
|
||||
{
|
||||
usb_host_release_interfaces(s);
|
||||
|
||||
int ret = ioctl(s->fd, USBDEVFS_SETCONFIGURATION, &config);
|
||||
|
||||
dprintf("husb: ctrl set config %d ret %d errno %d\n", config, ret, errno);
|
||||
|
||||
if (ret < 0)
|
||||
return ctrl_error();
|
||||
|
||||
usb_host_claim_interfaces(s, config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_host_set_interface(USBHostDevice *s, int iface, int alt)
|
||||
{
|
||||
struct usbdevfs_setinterface si;
|
||||
int ret;
|
||||
|
||||
si.interface = iface;
|
||||
si.altsetting = alt;
|
||||
ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si);
|
||||
|
||||
dprintf("husb: ctrl set iface %d altset %d ret %d errno %d\n",
|
||||
iface, alt, ret, errno);
|
||||
|
||||
if (ret < 0)
|
||||
return ctrl_error();
|
||||
|
||||
usb_linux_update_endp_table(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_host_handle_control(USBHostDevice *s, USBPacket *p)
|
||||
{
|
||||
struct usbdevfs_urb *urb;
|
||||
AsyncURB *aurb;
|
||||
int ret, value, index;
|
||||
|
||||
/*
|
||||
* Process certain standard device requests.
|
||||
* These are infrequent and are processed synchronously.
|
||||
*/
|
||||
value = le16_to_cpu(s->ctrl.req.wValue);
|
||||
index = le16_to_cpu(s->ctrl.req.wIndex);
|
||||
|
||||
dprintf("husb: ctrl type 0x%x req 0x%x val 0x%x index %u len %u\n",
|
||||
s->ctrl.req.bRequestType, s->ctrl.req.bRequest, value, index,
|
||||
s->ctrl.len);
|
||||
|
||||
if (s->ctrl.req.bRequestType == 0) {
|
||||
switch (s->ctrl.req.bRequest) {
|
||||
case USB_REQ_SET_ADDRESS:
|
||||
return usb_host_set_address(s, value);
|
||||
|
||||
case USB_REQ_SET_CONFIGURATION:
|
||||
return usb_host_set_config(s, value & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
if (s->ctrl.req.bRequestType == 1 &&
|
||||
s->ctrl.req.bRequest == USB_REQ_SET_INTERFACE)
|
||||
return usb_host_set_interface(s, index, value);
|
||||
|
||||
/* The rest are asynchronous */
|
||||
|
||||
aurb = async_alloc();
|
||||
if (!aurb) {
|
||||
dprintf("husb: async malloc failed\n");
|
||||
return USB_RET_NAK;
|
||||
}
|
||||
aurb->hdev = s;
|
||||
aurb->packet = p;
|
||||
|
||||
/*
|
||||
* Setup ctrl transfer.
|
||||
*
|
||||
* s->ctrl is layed out such that data buffer immediately follows
|
||||
* 'req' struct which is exactly what usbdevfs expects.
|
||||
*/
|
||||
urb = &aurb->urb;
|
||||
|
||||
urb->type = USBDEVFS_URB_TYPE_CONTROL;
|
||||
urb->endpoint = p->devep;
|
||||
|
||||
urb->buffer = &s->ctrl.req;
|
||||
urb->buffer_length = 8 + s->ctrl.len;
|
||||
|
||||
urb->usercontext = s;
|
||||
|
||||
ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
|
||||
|
||||
dprintf("husb: submit ctrl. len %u aurb %p\n", urb->buffer_length, aurb);
|
||||
|
||||
if (ret < 0) {
|
||||
dprintf("husb: submit failed. errno %d\n", errno);
|
||||
async_free(aurb);
|
||||
|
||||
switch(errno) {
|
||||
case ETIMEDOUT:
|
||||
return USB_RET_NAK;
|
||||
case EPIPE:
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
}
|
||||
|
||||
usb_defer_packet(p, async_cancel, aurb);
|
||||
return USB_RET_ASYNC;
|
||||
}
|
||||
|
||||
static int do_token_setup(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBHostDevice *s = (USBHostDevice *) dev;
|
||||
int ret = 0;
|
||||
|
||||
if (p->len != 8)
|
||||
return USB_RET_STALL;
|
||||
|
||||
memcpy(&s->ctrl.req, p->data, 8);
|
||||
s->ctrl.len = le16_to_cpu(s->ctrl.req.wLength);
|
||||
s->ctrl.offset = 0;
|
||||
s->ctrl.state = CTRL_STATE_SETUP;
|
||||
|
||||
if (s->ctrl.req.bRequestType & USB_DIR_IN) {
|
||||
ret = usb_host_handle_control(s, p);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret < s->ctrl.len)
|
||||
s->ctrl.len = ret;
|
||||
s->ctrl.state = CTRL_STATE_DATA;
|
||||
} else {
|
||||
if (s->ctrl.len == 0)
|
||||
s->ctrl.state = CTRL_STATE_ACK;
|
||||
else
|
||||
s->ctrl.state = CTRL_STATE_DATA;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_token_in(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBHostDevice *s = (USBHostDevice *) dev;
|
||||
int ret = 0;
|
||||
|
||||
if (p->devep != 0)
|
||||
return usb_host_handle_data(s, p);
|
||||
|
||||
switch(s->ctrl.state) {
|
||||
case CTRL_STATE_ACK:
|
||||
if (!(s->ctrl.req.bRequestType & USB_DIR_IN)) {
|
||||
ret = usb_host_handle_control(s, p);
|
||||
if (ret == USB_RET_ASYNC)
|
||||
return USB_RET_ASYNC;
|
||||
|
||||
s->ctrl.state = CTRL_STATE_IDLE;
|
||||
return ret > 0 ? 0 : ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case CTRL_STATE_DATA:
|
||||
if (s->ctrl.req.bRequestType & USB_DIR_IN) {
|
||||
int len = s->ctrl.len - s->ctrl.offset;
|
||||
if (len > p->len)
|
||||
len = p->len;
|
||||
memcpy(p->data, s->ctrl.buffer + s->ctrl.offset, len);
|
||||
s->ctrl.offset += len;
|
||||
if (s->ctrl.offset >= s->ctrl.len)
|
||||
s->ctrl.state = CTRL_STATE_ACK;
|
||||
return len;
|
||||
}
|
||||
|
||||
s->ctrl.state = CTRL_STATE_IDLE;
|
||||
return USB_RET_STALL;
|
||||
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
}
|
||||
|
||||
static int do_token_out(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBHostDevice *s = (USBHostDevice *) dev;
|
||||
|
||||
if (p->devep != 0)
|
||||
return usb_host_handle_data(s, p);
|
||||
|
||||
switch(s->ctrl.state) {
|
||||
case CTRL_STATE_ACK:
|
||||
if (s->ctrl.req.bRequestType & USB_DIR_IN) {
|
||||
s->ctrl.state = CTRL_STATE_IDLE;
|
||||
/* transfer OK */
|
||||
} else {
|
||||
/* ignore additional output */
|
||||
}
|
||||
return 0;
|
||||
|
||||
case CTRL_STATE_DATA:
|
||||
if (!(s->ctrl.req.bRequestType & USB_DIR_IN)) {
|
||||
int len = s->ctrl.len - s->ctrl.offset;
|
||||
if (len > p->len)
|
||||
len = p->len;
|
||||
memcpy(s->ctrl.buffer + s->ctrl.offset, p->data, len);
|
||||
s->ctrl.offset += len;
|
||||
if (s->ctrl.offset >= s->ctrl.len)
|
||||
s->ctrl.state = CTRL_STATE_ACK;
|
||||
return len;
|
||||
}
|
||||
|
||||
s->ctrl.state = CTRL_STATE_IDLE;
|
||||
return USB_RET_STALL;
|
||||
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Packet handler.
|
||||
* Called by the HC (host controller).
|
||||
*
|
||||
* Returns length of the transaction or one of the USB_RET_XXX codes.
|
||||
*/
|
||||
int usb_host_handle_packet(USBDevice *s, USBPacket *p)
|
||||
{
|
||||
switch(p->pid) {
|
||||
case USB_MSG_ATTACH:
|
||||
s->state = USB_STATE_ATTACHED;
|
||||
return 0;
|
||||
|
||||
case USB_MSG_DETACH:
|
||||
s->state = USB_STATE_NOTATTACHED;
|
||||
return 0;
|
||||
|
||||
case USB_MSG_RESET:
|
||||
s->remote_wakeup = 0;
|
||||
s->addr = 0;
|
||||
s->state = USB_STATE_DEFAULT;
|
||||
s->handle_reset(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Rest of the PIDs must match our address */
|
||||
if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr)
|
||||
return USB_RET_NODEV;
|
||||
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_SETUP:
|
||||
return do_token_setup(s, p);
|
||||
|
||||
case USB_TOKEN_IN:
|
||||
return do_token_in(s, p);
|
||||
|
||||
case USB_TOKEN_OUT:
|
||||
return do_token_out(s, p);
|
||||
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
}
|
||||
|
||||
/* returns 1 on problem encountered or 0 for success */
|
||||
static int usb_linux_update_endp_table(USBHostDevice *s)
|
||||
{
|
||||
uint8_t *descriptors;
|
||||
uint8_t devep, type, configuration, alt_interface;
|
||||
struct usb_ctrltransfer ct;
|
||||
struct usbdevfs_ctrltransfer ct;
|
||||
int interface, ret, length, i;
|
||||
|
||||
ct.bRequestType = USB_DIR_IN;
|
||||
|
@ -624,10 +898,14 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p
|
|||
#endif
|
||||
|
||||
dev->fd = fd;
|
||||
dev->configuration = 1;
|
||||
|
||||
/* XXX - do something about initial configuration */
|
||||
if (!usb_host_update_interfaces(dev, -1))
|
||||
/*
|
||||
* Initial configuration is -1 which makes us claim first
|
||||
* available config. We used to start with 1, which does not
|
||||
* always work. I've seen devices where first config starts
|
||||
* with 2.
|
||||
*/
|
||||
if (!usb_host_claim_interfaces(dev, -1))
|
||||
goto fail;
|
||||
|
||||
ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci);
|
||||
|
@ -646,11 +924,9 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p
|
|||
dev->dev.speed = USB_SPEED_LOW;
|
||||
else
|
||||
dev->dev.speed = USB_SPEED_HIGH;
|
||||
dev->dev.handle_packet = usb_generic_handle_packet;
|
||||
|
||||
dev->dev.handle_packet = usb_host_handle_packet;
|
||||
dev->dev.handle_reset = usb_host_handle_reset;
|
||||
dev->dev.handle_control = usb_host_handle_control;
|
||||
dev->dev.handle_data = usb_host_handle_data;
|
||||
dev->dev.handle_destroy = usb_host_handle_destroy;
|
||||
|
||||
if (!prod_name || prod_name[0] == '\0')
|
||||
|
@ -1055,6 +1331,8 @@ void usb_host_info(void)
|
|||
|
||||
#else
|
||||
|
||||
#include "hw/usb.h"
|
||||
|
||||
void usb_host_info(void)
|
||||
{
|
||||
term_printf("USB host devices not supported\n");
|
||||
|
|
Loading…
Reference in New Issue