mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-06 09:13:55 -06:00
usb: the big rename
Reorganize usb source files. Create a new hw/usb/ directory and move all usb source code to that place. Also make filenames a bit more descriptive. Host adapters are prefixed with "hch-" now, usb device emulations are prefixed with "dev-". Fixup paths Makefile and include paths to make it compile. No code changes. Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
parent
cb72b75824
commit
f1ae32a1ec
27 changed files with 74 additions and 70 deletions
584
hw/usb/bus.c
Normal file
584
hw/usb/bus.c
Normal file
|
@ -0,0 +1,584 @@
|
|||
#include "hw/hw.h"
|
||||
#include "hw/usb.h"
|
||||
#include "hw/qdev.h"
|
||||
#include "sysemu.h"
|
||||
#include "monitor.h"
|
||||
#include "trace.h"
|
||||
|
||||
static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent);
|
||||
|
||||
static char *usb_get_dev_path(DeviceState *dev);
|
||||
static char *usb_get_fw_dev_path(DeviceState *qdev);
|
||||
static int usb_qdev_exit(DeviceState *qdev);
|
||||
|
||||
static struct BusInfo usb_bus_info = {
|
||||
.name = "USB",
|
||||
.size = sizeof(USBBus),
|
||||
.print_dev = usb_bus_dev_print,
|
||||
.get_dev_path = usb_get_dev_path,
|
||||
.get_fw_dev_path = usb_get_fw_dev_path,
|
||||
.props = (Property[]) {
|
||||
DEFINE_PROP_STRING("port", USBDevice, port_path),
|
||||
DEFINE_PROP_END_OF_LIST()
|
||||
},
|
||||
};
|
||||
static int next_usb_bus = 0;
|
||||
static QTAILQ_HEAD(, USBBus) busses = QTAILQ_HEAD_INITIALIZER(busses);
|
||||
|
||||
const VMStateDescription vmstate_usb_device = {
|
||||
.name = "USBDevice",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_UINT8(addr, USBDevice),
|
||||
VMSTATE_INT32(state, USBDevice),
|
||||
VMSTATE_INT32(remote_wakeup, USBDevice),
|
||||
VMSTATE_INT32(setup_state, USBDevice),
|
||||
VMSTATE_INT32(setup_len, USBDevice),
|
||||
VMSTATE_INT32(setup_index, USBDevice),
|
||||
VMSTATE_UINT8_ARRAY(setup_buf, USBDevice, 8),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
}
|
||||
};
|
||||
|
||||
void usb_bus_new(USBBus *bus, USBBusOps *ops, DeviceState *host)
|
||||
{
|
||||
qbus_create_inplace(&bus->qbus, &usb_bus_info, host, NULL);
|
||||
bus->ops = ops;
|
||||
bus->busnr = next_usb_bus++;
|
||||
bus->qbus.allow_hotplug = 1; /* Yes, we can */
|
||||
QTAILQ_INIT(&bus->free);
|
||||
QTAILQ_INIT(&bus->used);
|
||||
QTAILQ_INSERT_TAIL(&busses, bus, next);
|
||||
}
|
||||
|
||||
USBBus *usb_bus_find(int busnr)
|
||||
{
|
||||
USBBus *bus;
|
||||
|
||||
if (-1 == busnr)
|
||||
return QTAILQ_FIRST(&busses);
|
||||
QTAILQ_FOREACH(bus, &busses, next) {
|
||||
if (bus->busnr == busnr)
|
||||
return bus;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int usb_device_init(USBDevice *dev)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->init) {
|
||||
return klass->init(dev);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
USBDevice *usb_device_find_device(USBDevice *dev, uint8_t addr)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->find_device) {
|
||||
return klass->find_device(dev, addr);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void usb_device_handle_destroy(USBDevice *dev)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->handle_destroy) {
|
||||
klass->handle_destroy(dev);
|
||||
}
|
||||
}
|
||||
|
||||
void usb_device_cancel_packet(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->cancel_packet) {
|
||||
klass->cancel_packet(dev, p);
|
||||
}
|
||||
}
|
||||
|
||||
void usb_device_handle_attach(USBDevice *dev)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->handle_attach) {
|
||||
klass->handle_attach(dev);
|
||||
}
|
||||
}
|
||||
|
||||
void usb_device_handle_reset(USBDevice *dev)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->handle_reset) {
|
||||
klass->handle_reset(dev);
|
||||
}
|
||||
}
|
||||
|
||||
int usb_device_handle_control(USBDevice *dev, USBPacket *p, int request,
|
||||
int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->handle_control) {
|
||||
return klass->handle_control(dev, p, request, value, index, length,
|
||||
data);
|
||||
}
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
int usb_device_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->handle_data) {
|
||||
return klass->handle_data(dev, p);
|
||||
}
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
const char *usb_device_get_product_desc(USBDevice *dev)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
return klass->product_desc;
|
||||
}
|
||||
|
||||
const USBDesc *usb_device_get_usb_desc(USBDevice *dev)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
return klass->usb_desc;
|
||||
}
|
||||
|
||||
void usb_device_set_interface(USBDevice *dev, int interface,
|
||||
int alt_old, int alt_new)
|
||||
{
|
||||
USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
|
||||
if (klass->set_interface) {
|
||||
klass->set_interface(dev, interface, alt_old, alt_new);
|
||||
}
|
||||
}
|
||||
|
||||
static int usb_qdev_init(DeviceState *qdev)
|
||||
{
|
||||
USBDevice *dev = USB_DEVICE(qdev);
|
||||
int rc;
|
||||
|
||||
pstrcpy(dev->product_desc, sizeof(dev->product_desc),
|
||||
usb_device_get_product_desc(dev));
|
||||
dev->auto_attach = 1;
|
||||
QLIST_INIT(&dev->strings);
|
||||
usb_ep_init(dev);
|
||||
rc = usb_claim_port(dev);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
rc = usb_device_init(dev);
|
||||
if (rc != 0) {
|
||||
usb_release_port(dev);
|
||||
return rc;
|
||||
}
|
||||
if (dev->auto_attach) {
|
||||
rc = usb_device_attach(dev);
|
||||
if (rc != 0) {
|
||||
usb_qdev_exit(qdev);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_qdev_exit(DeviceState *qdev)
|
||||
{
|
||||
USBDevice *dev = USB_DEVICE(qdev);
|
||||
|
||||
if (dev->attached) {
|
||||
usb_device_detach(dev);
|
||||
}
|
||||
usb_device_handle_destroy(dev);
|
||||
if (dev->port) {
|
||||
usb_release_port(dev);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct LegacyUSBFactory
|
||||
{
|
||||
const char *name;
|
||||
const char *usbdevice_name;
|
||||
USBDevice *(*usbdevice_init)(USBBus *bus, const char *params);
|
||||
} LegacyUSBFactory;
|
||||
|
||||
static GSList *legacy_usb_factory;
|
||||
|
||||
void usb_legacy_register(const char *typename, const char *usbdevice_name,
|
||||
USBDevice *(*usbdevice_init)(USBBus *bus,
|
||||
const char *params))
|
||||
{
|
||||
if (usbdevice_name) {
|
||||
LegacyUSBFactory *f = g_malloc0(sizeof(*f));
|
||||
f->name = typename;
|
||||
f->usbdevice_name = usbdevice_name;
|
||||
f->usbdevice_init = usbdevice_init;
|
||||
legacy_usb_factory = g_slist_append(legacy_usb_factory, f);
|
||||
}
|
||||
}
|
||||
|
||||
USBDevice *usb_create(USBBus *bus, const char *name)
|
||||
{
|
||||
DeviceState *dev;
|
||||
|
||||
dev = qdev_create(&bus->qbus, name);
|
||||
return USB_DEVICE(dev);
|
||||
}
|
||||
|
||||
USBDevice *usb_create_simple(USBBus *bus, const char *name)
|
||||
{
|
||||
USBDevice *dev = usb_create(bus, name);
|
||||
int rc;
|
||||
|
||||
if (!dev) {
|
||||
error_report("Failed to create USB device '%s'", name);
|
||||
return NULL;
|
||||
}
|
||||
rc = qdev_init(&dev->qdev);
|
||||
if (rc < 0) {
|
||||
error_report("Failed to initialize USB device '%s'", name);
|
||||
return NULL;
|
||||
}
|
||||
return dev;
|
||||
}
|
||||
|
||||
static void usb_fill_port(USBPort *port, void *opaque, int index,
|
||||
USBPortOps *ops, int speedmask)
|
||||
{
|
||||
port->opaque = opaque;
|
||||
port->index = index;
|
||||
port->ops = ops;
|
||||
port->speedmask = speedmask;
|
||||
usb_port_location(port, NULL, index + 1);
|
||||
}
|
||||
|
||||
void usb_register_port(USBBus *bus, USBPort *port, void *opaque, int index,
|
||||
USBPortOps *ops, int speedmask)
|
||||
{
|
||||
usb_fill_port(port, opaque, index, ops, speedmask);
|
||||
QTAILQ_INSERT_TAIL(&bus->free, port, next);
|
||||
bus->nfree++;
|
||||
}
|
||||
|
||||
int usb_register_companion(const char *masterbus, USBPort *ports[],
|
||||
uint32_t portcount, uint32_t firstport,
|
||||
void *opaque, USBPortOps *ops, int speedmask)
|
||||
{
|
||||
USBBus *bus;
|
||||
int i;
|
||||
|
||||
QTAILQ_FOREACH(bus, &busses, next) {
|
||||
if (strcmp(bus->qbus.name, masterbus) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bus || !bus->ops->register_companion) {
|
||||
qerror_report(QERR_INVALID_PARAMETER_VALUE, "masterbus",
|
||||
"an USB masterbus");
|
||||
if (bus) {
|
||||
error_printf_unless_qmp(
|
||||
"USB bus '%s' does not allow companion controllers\n",
|
||||
masterbus);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < portcount; i++) {
|
||||
usb_fill_port(ports[i], opaque, i, ops, speedmask);
|
||||
}
|
||||
|
||||
return bus->ops->register_companion(bus, ports, portcount, firstport);
|
||||
}
|
||||
|
||||
void usb_port_location(USBPort *downstream, USBPort *upstream, int portnr)
|
||||
{
|
||||
if (upstream) {
|
||||
snprintf(downstream->path, sizeof(downstream->path), "%s.%d",
|
||||
upstream->path, portnr);
|
||||
} else {
|
||||
snprintf(downstream->path, sizeof(downstream->path), "%d", portnr);
|
||||
}
|
||||
}
|
||||
|
||||
void usb_unregister_port(USBBus *bus, USBPort *port)
|
||||
{
|
||||
if (port->dev)
|
||||
qdev_free(&port->dev->qdev);
|
||||
QTAILQ_REMOVE(&bus->free, port, next);
|
||||
bus->nfree--;
|
||||
}
|
||||
|
||||
int usb_claim_port(USBDevice *dev)
|
||||
{
|
||||
USBBus *bus = usb_bus_from_device(dev);
|
||||
USBPort *port;
|
||||
|
||||
assert(dev->port == NULL);
|
||||
|
||||
if (dev->port_path) {
|
||||
QTAILQ_FOREACH(port, &bus->free, next) {
|
||||
if (strcmp(port->path, dev->port_path) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (port == NULL) {
|
||||
error_report("Error: usb port %s (bus %s) not found (in use?)",
|
||||
dev->port_path, bus->qbus.name);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (bus->nfree == 1 && strcmp(object_get_typename(OBJECT(dev)), "usb-hub") != 0) {
|
||||
/* Create a new hub and chain it on */
|
||||
usb_create_simple(bus, "usb-hub");
|
||||
}
|
||||
if (bus->nfree == 0) {
|
||||
error_report("Error: tried to attach usb device %s to a bus "
|
||||
"with no free ports", dev->product_desc);
|
||||
return -1;
|
||||
}
|
||||
port = QTAILQ_FIRST(&bus->free);
|
||||
}
|
||||
trace_usb_port_claim(bus->busnr, port->path);
|
||||
|
||||
QTAILQ_REMOVE(&bus->free, port, next);
|
||||
bus->nfree--;
|
||||
|
||||
dev->port = port;
|
||||
port->dev = dev;
|
||||
|
||||
QTAILQ_INSERT_TAIL(&bus->used, port, next);
|
||||
bus->nused++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void usb_release_port(USBDevice *dev)
|
||||
{
|
||||
USBBus *bus = usb_bus_from_device(dev);
|
||||
USBPort *port = dev->port;
|
||||
|
||||
assert(port != NULL);
|
||||
trace_usb_port_release(bus->busnr, port->path);
|
||||
|
||||
QTAILQ_REMOVE(&bus->used, port, next);
|
||||
bus->nused--;
|
||||
|
||||
dev->port = NULL;
|
||||
port->dev = NULL;
|
||||
|
||||
QTAILQ_INSERT_TAIL(&bus->free, port, next);
|
||||
bus->nfree++;
|
||||
}
|
||||
|
||||
int usb_device_attach(USBDevice *dev)
|
||||
{
|
||||
USBBus *bus = usb_bus_from_device(dev);
|
||||
USBPort *port = dev->port;
|
||||
|
||||
assert(port != NULL);
|
||||
assert(!dev->attached);
|
||||
trace_usb_port_attach(bus->busnr, port->path);
|
||||
|
||||
if (!(port->speedmask & dev->speedmask)) {
|
||||
error_report("Warning: speed mismatch trying to attach "
|
||||
"usb device %s to bus %s",
|
||||
dev->product_desc, bus->qbus.name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev->attached++;
|
||||
usb_attach(port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int usb_device_detach(USBDevice *dev)
|
||||
{
|
||||
USBBus *bus = usb_bus_from_device(dev);
|
||||
USBPort *port = dev->port;
|
||||
|
||||
assert(port != NULL);
|
||||
assert(dev->attached);
|
||||
trace_usb_port_detach(bus->busnr, port->path);
|
||||
|
||||
usb_detach(port);
|
||||
dev->attached--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int usb_device_delete_addr(int busnr, int addr)
|
||||
{
|
||||
USBBus *bus;
|
||||
USBPort *port;
|
||||
USBDevice *dev;
|
||||
|
||||
bus = usb_bus_find(busnr);
|
||||
if (!bus)
|
||||
return -1;
|
||||
|
||||
QTAILQ_FOREACH(port, &bus->used, next) {
|
||||
if (port->dev->addr == addr)
|
||||
break;
|
||||
}
|
||||
if (!port)
|
||||
return -1;
|
||||
dev = port->dev;
|
||||
|
||||
qdev_free(&dev->qdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *usb_speed(unsigned int speed)
|
||||
{
|
||||
static const char *txt[] = {
|
||||
[ USB_SPEED_LOW ] = "1.5",
|
||||
[ USB_SPEED_FULL ] = "12",
|
||||
[ USB_SPEED_HIGH ] = "480",
|
||||
[ USB_SPEED_SUPER ] = "5000",
|
||||
};
|
||||
if (speed >= ARRAY_SIZE(txt))
|
||||
return "?";
|
||||
return txt[speed];
|
||||
}
|
||||
|
||||
static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent)
|
||||
{
|
||||
USBDevice *dev = USB_DEVICE(qdev);
|
||||
USBBus *bus = usb_bus_from_device(dev);
|
||||
|
||||
monitor_printf(mon, "%*saddr %d.%d, port %s, speed %s, name %s%s\n",
|
||||
indent, "", bus->busnr, dev->addr,
|
||||
dev->port ? dev->port->path : "-",
|
||||
usb_speed(dev->speed), dev->product_desc,
|
||||
dev->attached ? ", attached" : "");
|
||||
}
|
||||
|
||||
static char *usb_get_dev_path(DeviceState *qdev)
|
||||
{
|
||||
USBDevice *dev = USB_DEVICE(qdev);
|
||||
return g_strdup(dev->port->path);
|
||||
}
|
||||
|
||||
static char *usb_get_fw_dev_path(DeviceState *qdev)
|
||||
{
|
||||
USBDevice *dev = USB_DEVICE(qdev);
|
||||
char *fw_path, *in;
|
||||
ssize_t pos = 0, fw_len;
|
||||
long nr;
|
||||
|
||||
fw_len = 32 + strlen(dev->port->path) * 6;
|
||||
fw_path = g_malloc(fw_len);
|
||||
in = dev->port->path;
|
||||
while (fw_len - pos > 0) {
|
||||
nr = strtol(in, &in, 10);
|
||||
if (in[0] == '.') {
|
||||
/* some hub between root port and device */
|
||||
pos += snprintf(fw_path + pos, fw_len - pos, "hub@%ld/", nr);
|
||||
in++;
|
||||
} else {
|
||||
/* the device itself */
|
||||
pos += snprintf(fw_path + pos, fw_len - pos, "%s@%ld",
|
||||
qdev_fw_name(qdev), nr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return fw_path;
|
||||
}
|
||||
|
||||
void usb_info(Monitor *mon)
|
||||
{
|
||||
USBBus *bus;
|
||||
USBDevice *dev;
|
||||
USBPort *port;
|
||||
|
||||
if (QTAILQ_EMPTY(&busses)) {
|
||||
monitor_printf(mon, "USB support not enabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
QTAILQ_FOREACH(bus, &busses, next) {
|
||||
QTAILQ_FOREACH(port, &bus->used, next) {
|
||||
dev = port->dev;
|
||||
if (!dev)
|
||||
continue;
|
||||
monitor_printf(mon, " Device %d.%d, Port %s, Speed %s Mb/s, Product %s\n",
|
||||
bus->busnr, dev->addr, port->path, usb_speed(dev->speed),
|
||||
dev->product_desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* handle legacy -usbdevice cmd line option */
|
||||
USBDevice *usbdevice_create(const char *cmdline)
|
||||
{
|
||||
USBBus *bus = usb_bus_find(-1 /* any */);
|
||||
LegacyUSBFactory *f = NULL;
|
||||
GSList *i;
|
||||
char driver[32];
|
||||
const char *params;
|
||||
int len;
|
||||
|
||||
params = strchr(cmdline,':');
|
||||
if (params) {
|
||||
params++;
|
||||
len = params - cmdline;
|
||||
if (len > sizeof(driver))
|
||||
len = sizeof(driver);
|
||||
pstrcpy(driver, len, cmdline);
|
||||
} else {
|
||||
params = "";
|
||||
pstrcpy(driver, sizeof(driver), cmdline);
|
||||
}
|
||||
|
||||
for (i = legacy_usb_factory; i; i = i->next) {
|
||||
f = i->data;
|
||||
if (strcmp(f->usbdevice_name, driver) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == NULL) {
|
||||
#if 0
|
||||
/* no error because some drivers are not converted (yet) */
|
||||
error_report("usbdevice %s not found", driver);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!f->usbdevice_init) {
|
||||
if (*params) {
|
||||
error_report("usbdevice %s accepts no params", driver);
|
||||
return NULL;
|
||||
}
|
||||
return usb_create_simple(bus, f->name);
|
||||
}
|
||||
return f->usbdevice_init(bus, params);
|
||||
}
|
||||
|
||||
static void usb_device_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *k = DEVICE_CLASS(klass);
|
||||
k->bus_info = &usb_bus_info;
|
||||
k->init = usb_qdev_init;
|
||||
k->unplug = qdev_simple_unplug_cb;
|
||||
k->exit = usb_qdev_exit;
|
||||
}
|
||||
|
||||
static TypeInfo usb_device_type_info = {
|
||||
.name = TYPE_USB_DEVICE,
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(USBDevice),
|
||||
.abstract = true,
|
||||
.class_size = sizeof(USBDeviceClass),
|
||||
.class_init = usb_device_class_init,
|
||||
};
|
||||
|
||||
static void usb_register_types(void)
|
||||
{
|
||||
type_register_static(&usb_device_type_info);
|
||||
}
|
||||
|
||||
type_init(usb_register_types)
|
663
hw/usb/core.c
Normal file
663
hw/usb/core.c
Normal file
|
@ -0,0 +1,663 @@
|
|||
/*
|
||||
* QEMU USB emulation
|
||||
*
|
||||
* Copyright (c) 2005 Fabrice Bellard
|
||||
*
|
||||
* 2008 Generic packet handler rewrite by Max Krasnyansky
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "qemu-common.h"
|
||||
#include "hw/usb.h"
|
||||
#include "iov.h"
|
||||
#include "trace.h"
|
||||
|
||||
void usb_attach(USBPort *port)
|
||||
{
|
||||
USBDevice *dev = port->dev;
|
||||
|
||||
assert(dev != NULL);
|
||||
assert(dev->attached);
|
||||
assert(dev->state == USB_STATE_NOTATTACHED);
|
||||
port->ops->attach(port);
|
||||
dev->state = USB_STATE_ATTACHED;
|
||||
usb_device_handle_attach(dev);
|
||||
}
|
||||
|
||||
void usb_detach(USBPort *port)
|
||||
{
|
||||
USBDevice *dev = port->dev;
|
||||
|
||||
assert(dev != NULL);
|
||||
assert(dev->state != USB_STATE_NOTATTACHED);
|
||||
port->ops->detach(port);
|
||||
dev->state = USB_STATE_NOTATTACHED;
|
||||
}
|
||||
|
||||
void usb_port_reset(USBPort *port)
|
||||
{
|
||||
USBDevice *dev = port->dev;
|
||||
|
||||
assert(dev != NULL);
|
||||
usb_detach(port);
|
||||
usb_attach(port);
|
||||
usb_device_reset(dev);
|
||||
}
|
||||
|
||||
void usb_device_reset(USBDevice *dev)
|
||||
{
|
||||
if (dev == NULL || !dev->attached) {
|
||||
return;
|
||||
}
|
||||
dev->remote_wakeup = 0;
|
||||
dev->addr = 0;
|
||||
dev->state = USB_STATE_DEFAULT;
|
||||
usb_device_handle_reset(dev);
|
||||
}
|
||||
|
||||
void usb_wakeup(USBEndpoint *ep)
|
||||
{
|
||||
USBDevice *dev = ep->dev;
|
||||
USBBus *bus = usb_bus_from_device(dev);
|
||||
|
||||
if (dev->remote_wakeup && dev->port && dev->port->ops->wakeup) {
|
||||
dev->port->ops->wakeup(dev->port);
|
||||
}
|
||||
if (bus->ops->wakeup_endpoint) {
|
||||
bus->ops->wakeup_endpoint(bus, ep);
|
||||
}
|
||||
}
|
||||
|
||||
/**********************/
|
||||
|
||||
/* generic USB device helpers (you are not forced to use them when
|
||||
writing your USB device driver, but they help handling the
|
||||
protocol)
|
||||
*/
|
||||
|
||||
#define SETUP_STATE_IDLE 0
|
||||
#define SETUP_STATE_SETUP 1
|
||||
#define SETUP_STATE_DATA 2
|
||||
#define SETUP_STATE_ACK 3
|
||||
#define SETUP_STATE_PARAM 4
|
||||
|
||||
static int do_token_setup(USBDevice *s, USBPacket *p)
|
||||
{
|
||||
int request, value, index;
|
||||
int ret = 0;
|
||||
|
||||
if (p->iov.size != 8) {
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
|
||||
usb_packet_copy(p, s->setup_buf, p->iov.size);
|
||||
s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
|
||||
s->setup_index = 0;
|
||||
|
||||
request = (s->setup_buf[0] << 8) | s->setup_buf[1];
|
||||
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
|
||||
index = (s->setup_buf[5] << 8) | s->setup_buf[4];
|
||||
|
||||
if (s->setup_buf[0] & USB_DIR_IN) {
|
||||
ret = usb_device_handle_control(s, p, request, value, index,
|
||||
s->setup_len, s->data_buf);
|
||||
if (ret == USB_RET_ASYNC) {
|
||||
s->setup_state = SETUP_STATE_SETUP;
|
||||
return USB_RET_ASYNC;
|
||||
}
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret < s->setup_len)
|
||||
s->setup_len = ret;
|
||||
s->setup_state = SETUP_STATE_DATA;
|
||||
} else {
|
||||
if (s->setup_len > sizeof(s->data_buf)) {
|
||||
fprintf(stderr,
|
||||
"usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
|
||||
s->setup_len, sizeof(s->data_buf));
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
if (s->setup_len == 0)
|
||||
s->setup_state = SETUP_STATE_ACK;
|
||||
else
|
||||
s->setup_state = SETUP_STATE_DATA;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_token_in(USBDevice *s, USBPacket *p)
|
||||
{
|
||||
int request, value, index;
|
||||
int ret = 0;
|
||||
|
||||
assert(p->ep->nr == 0);
|
||||
|
||||
request = (s->setup_buf[0] << 8) | s->setup_buf[1];
|
||||
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
|
||||
index = (s->setup_buf[5] << 8) | s->setup_buf[4];
|
||||
|
||||
switch(s->setup_state) {
|
||||
case SETUP_STATE_ACK:
|
||||
if (!(s->setup_buf[0] & USB_DIR_IN)) {
|
||||
ret = usb_device_handle_control(s, p, request, value, index,
|
||||
s->setup_len, s->data_buf);
|
||||
if (ret == USB_RET_ASYNC) {
|
||||
return USB_RET_ASYNC;
|
||||
}
|
||||
s->setup_state = SETUP_STATE_IDLE;
|
||||
if (ret > 0)
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* return 0 byte */
|
||||
return 0;
|
||||
|
||||
case SETUP_STATE_DATA:
|
||||
if (s->setup_buf[0] & USB_DIR_IN) {
|
||||
int len = s->setup_len - s->setup_index;
|
||||
if (len > p->iov.size) {
|
||||
len = p->iov.size;
|
||||
}
|
||||
usb_packet_copy(p, s->data_buf + s->setup_index, len);
|
||||
s->setup_index += len;
|
||||
if (s->setup_index >= s->setup_len)
|
||||
s->setup_state = SETUP_STATE_ACK;
|
||||
return len;
|
||||
}
|
||||
|
||||
s->setup_state = SETUP_STATE_IDLE;
|
||||
return USB_RET_STALL;
|
||||
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
}
|
||||
|
||||
static int do_token_out(USBDevice *s, USBPacket *p)
|
||||
{
|
||||
assert(p->ep->nr == 0);
|
||||
|
||||
switch(s->setup_state) {
|
||||
case SETUP_STATE_ACK:
|
||||
if (s->setup_buf[0] & USB_DIR_IN) {
|
||||
s->setup_state = SETUP_STATE_IDLE;
|
||||
/* transfer OK */
|
||||
} else {
|
||||
/* ignore additional output */
|
||||
}
|
||||
return 0;
|
||||
|
||||
case SETUP_STATE_DATA:
|
||||
if (!(s->setup_buf[0] & USB_DIR_IN)) {
|
||||
int len = s->setup_len - s->setup_index;
|
||||
if (len > p->iov.size) {
|
||||
len = p->iov.size;
|
||||
}
|
||||
usb_packet_copy(p, s->data_buf + s->setup_index, len);
|
||||
s->setup_index += len;
|
||||
if (s->setup_index >= s->setup_len)
|
||||
s->setup_state = SETUP_STATE_ACK;
|
||||
return len;
|
||||
}
|
||||
|
||||
s->setup_state = SETUP_STATE_IDLE;
|
||||
return USB_RET_STALL;
|
||||
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
}
|
||||
|
||||
static int do_parameter(USBDevice *s, USBPacket *p)
|
||||
{
|
||||
int request, value, index;
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
s->setup_buf[i] = p->parameter >> (i*8);
|
||||
}
|
||||
|
||||
s->setup_state = SETUP_STATE_PARAM;
|
||||
s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
|
||||
s->setup_index = 0;
|
||||
|
||||
request = (s->setup_buf[0] << 8) | s->setup_buf[1];
|
||||
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
|
||||
index = (s->setup_buf[5] << 8) | s->setup_buf[4];
|
||||
|
||||
if (s->setup_len > sizeof(s->data_buf)) {
|
||||
fprintf(stderr,
|
||||
"usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
|
||||
s->setup_len, sizeof(s->data_buf));
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
|
||||
if (p->pid == USB_TOKEN_OUT) {
|
||||
usb_packet_copy(p, s->data_buf, s->setup_len);
|
||||
}
|
||||
|
||||
ret = usb_device_handle_control(s, p, request, value, index,
|
||||
s->setup_len, s->data_buf);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret < s->setup_len) {
|
||||
s->setup_len = ret;
|
||||
}
|
||||
if (p->pid == USB_TOKEN_IN) {
|
||||
usb_packet_copy(p, s->data_buf, s->setup_len);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ctrl complete function for devices which use usb_generic_handle_packet and
|
||||
may return USB_RET_ASYNC from their handle_control callback. Device code
|
||||
which does this *must* call this function instead of the normal
|
||||
usb_packet_complete to complete their async control packets. */
|
||||
void usb_generic_async_ctrl_complete(USBDevice *s, USBPacket *p)
|
||||
{
|
||||
if (p->result < 0) {
|
||||
s->setup_state = SETUP_STATE_IDLE;
|
||||
}
|
||||
|
||||
switch (s->setup_state) {
|
||||
case SETUP_STATE_SETUP:
|
||||
if (p->result < s->setup_len) {
|
||||
s->setup_len = p->result;
|
||||
}
|
||||
s->setup_state = SETUP_STATE_DATA;
|
||||
p->result = 8;
|
||||
break;
|
||||
|
||||
case SETUP_STATE_ACK:
|
||||
s->setup_state = SETUP_STATE_IDLE;
|
||||
p->result = 0;
|
||||
break;
|
||||
|
||||
case SETUP_STATE_PARAM:
|
||||
if (p->result < s->setup_len) {
|
||||
s->setup_len = p->result;
|
||||
}
|
||||
if (p->pid == USB_TOKEN_IN) {
|
||||
p->result = 0;
|
||||
usb_packet_copy(p, s->data_buf, s->setup_len);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
usb_packet_complete(s, p);
|
||||
}
|
||||
|
||||
/* XXX: fix overflow */
|
||||
int set_usb_string(uint8_t *buf, const char *str)
|
||||
{
|
||||
int len, i;
|
||||
uint8_t *q;
|
||||
|
||||
q = buf;
|
||||
len = strlen(str);
|
||||
*q++ = 2 * len + 2;
|
||||
*q++ = 3;
|
||||
for(i = 0; i < len; i++) {
|
||||
*q++ = str[i];
|
||||
*q++ = 0;
|
||||
}
|
||||
return q - buf;
|
||||
}
|
||||
|
||||
USBDevice *usb_find_device(USBPort *port, uint8_t addr)
|
||||
{
|
||||
USBDevice *dev = port->dev;
|
||||
|
||||
if (dev == NULL || !dev->attached || dev->state != USB_STATE_DEFAULT) {
|
||||
return NULL;
|
||||
}
|
||||
if (dev->addr == addr) {
|
||||
return dev;
|
||||
}
|
||||
return usb_device_find_device(dev, addr);
|
||||
}
|
||||
|
||||
static int usb_process_one(USBPacket *p)
|
||||
{
|
||||
USBDevice *dev = p->ep->dev;
|
||||
|
||||
if (p->ep->nr == 0) {
|
||||
/* control pipe */
|
||||
if (p->parameter) {
|
||||
return do_parameter(dev, p);
|
||||
}
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_SETUP:
|
||||
return do_token_setup(dev, p);
|
||||
case USB_TOKEN_IN:
|
||||
return do_token_in(dev, p);
|
||||
case USB_TOKEN_OUT:
|
||||
return do_token_out(dev, p);
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
} else {
|
||||
/* data pipe */
|
||||
return usb_device_handle_data(dev, p);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hand over a packet to a device for processing. Return value
|
||||
USB_RET_ASYNC indicates the processing isn't finished yet, the
|
||||
driver will call usb_packet_complete() when done processing it. */
|
||||
int usb_handle_packet(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (dev == NULL) {
|
||||
return USB_RET_NODEV;
|
||||
}
|
||||
assert(dev == p->ep->dev);
|
||||
assert(dev->state == USB_STATE_DEFAULT);
|
||||
assert(p->state == USB_PACKET_SETUP);
|
||||
assert(p->ep != NULL);
|
||||
|
||||
if (QTAILQ_EMPTY(&p->ep->queue) || p->ep->pipeline) {
|
||||
ret = usb_process_one(p);
|
||||
if (ret == USB_RET_ASYNC) {
|
||||
usb_packet_set_state(p, USB_PACKET_ASYNC);
|
||||
QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue);
|
||||
} else {
|
||||
p->result = ret;
|
||||
usb_packet_set_state(p, USB_PACKET_COMPLETE);
|
||||
}
|
||||
} else {
|
||||
ret = USB_RET_ASYNC;
|
||||
usb_packet_set_state(p, USB_PACKET_QUEUED);
|
||||
QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Notify the controller that an async packet is complete. This should only
|
||||
be called for packets previously deferred by returning USB_RET_ASYNC from
|
||||
handle_packet. */
|
||||
void usb_packet_complete(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBEndpoint *ep = p->ep;
|
||||
int ret;
|
||||
|
||||
assert(p->state == USB_PACKET_ASYNC);
|
||||
assert(QTAILQ_FIRST(&ep->queue) == p);
|
||||
usb_packet_set_state(p, USB_PACKET_COMPLETE);
|
||||
QTAILQ_REMOVE(&ep->queue, p, queue);
|
||||
dev->port->ops->complete(dev->port, p);
|
||||
|
||||
while (!QTAILQ_EMPTY(&ep->queue)) {
|
||||
p = QTAILQ_FIRST(&ep->queue);
|
||||
if (p->state == USB_PACKET_ASYNC) {
|
||||
break;
|
||||
}
|
||||
assert(p->state == USB_PACKET_QUEUED);
|
||||
ret = usb_process_one(p);
|
||||
if (ret == USB_RET_ASYNC) {
|
||||
usb_packet_set_state(p, USB_PACKET_ASYNC);
|
||||
break;
|
||||
}
|
||||
p->result = ret;
|
||||
usb_packet_set_state(p, USB_PACKET_COMPLETE);
|
||||
QTAILQ_REMOVE(&ep->queue, p, queue);
|
||||
dev->port->ops->complete(dev->port, p);
|
||||
}
|
||||
}
|
||||
|
||||
/* Cancel an active packet. The packed must have been deferred by
|
||||
returning USB_RET_ASYNC from handle_packet, and not yet
|
||||
completed. */
|
||||
void usb_cancel_packet(USBPacket * p)
|
||||
{
|
||||
bool callback = (p->state == USB_PACKET_ASYNC);
|
||||
assert(usb_packet_is_inflight(p));
|
||||
usb_packet_set_state(p, USB_PACKET_CANCELED);
|
||||
QTAILQ_REMOVE(&p->ep->queue, p, queue);
|
||||
if (callback) {
|
||||
usb_device_cancel_packet(p->ep->dev, p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void usb_packet_init(USBPacket *p)
|
||||
{
|
||||
qemu_iovec_init(&p->iov, 1);
|
||||
}
|
||||
|
||||
void usb_packet_set_state(USBPacket *p, USBPacketState state)
|
||||
{
|
||||
static const char *name[] = {
|
||||
[USB_PACKET_UNDEFINED] = "undef",
|
||||
[USB_PACKET_SETUP] = "setup",
|
||||
[USB_PACKET_QUEUED] = "queued",
|
||||
[USB_PACKET_ASYNC] = "async",
|
||||
[USB_PACKET_COMPLETE] = "complete",
|
||||
[USB_PACKET_CANCELED] = "canceled",
|
||||
};
|
||||
USBDevice *dev = p->ep->dev;
|
||||
USBBus *bus = usb_bus_from_device(dev);
|
||||
|
||||
trace_usb_packet_state_change(bus->busnr, dev->port->path, p->ep->nr,
|
||||
p, name[p->state], name[state]);
|
||||
p->state = state;
|
||||
}
|
||||
|
||||
void usb_packet_setup(USBPacket *p, int pid, USBEndpoint *ep)
|
||||
{
|
||||
assert(!usb_packet_is_inflight(p));
|
||||
p->pid = pid;
|
||||
p->ep = ep;
|
||||
p->result = 0;
|
||||
p->parameter = 0;
|
||||
qemu_iovec_reset(&p->iov);
|
||||
usb_packet_set_state(p, USB_PACKET_SETUP);
|
||||
}
|
||||
|
||||
void usb_packet_addbuf(USBPacket *p, void *ptr, size_t len)
|
||||
{
|
||||
qemu_iovec_add(&p->iov, ptr, len);
|
||||
}
|
||||
|
||||
void usb_packet_copy(USBPacket *p, void *ptr, size_t bytes)
|
||||
{
|
||||
assert(p->result >= 0);
|
||||
assert(p->result + bytes <= p->iov.size);
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_SETUP:
|
||||
case USB_TOKEN_OUT:
|
||||
iov_to_buf(p->iov.iov, p->iov.niov, ptr, p->result, bytes);
|
||||
break;
|
||||
case USB_TOKEN_IN:
|
||||
iov_from_buf(p->iov.iov, p->iov.niov, ptr, p->result, bytes);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "%s: invalid pid: %x\n", __func__, p->pid);
|
||||
abort();
|
||||
}
|
||||
p->result += bytes;
|
||||
}
|
||||
|
||||
void usb_packet_skip(USBPacket *p, size_t bytes)
|
||||
{
|
||||
assert(p->result >= 0);
|
||||
assert(p->result + bytes <= p->iov.size);
|
||||
if (p->pid == USB_TOKEN_IN) {
|
||||
iov_clear(p->iov.iov, p->iov.niov, p->result, bytes);
|
||||
}
|
||||
p->result += bytes;
|
||||
}
|
||||
|
||||
void usb_packet_cleanup(USBPacket *p)
|
||||
{
|
||||
assert(!usb_packet_is_inflight(p));
|
||||
qemu_iovec_destroy(&p->iov);
|
||||
}
|
||||
|
||||
void usb_ep_init(USBDevice *dev)
|
||||
{
|
||||
int ep;
|
||||
|
||||
dev->ep_ctl.nr = 0;
|
||||
dev->ep_ctl.type = USB_ENDPOINT_XFER_CONTROL;
|
||||
dev->ep_ctl.ifnum = 0;
|
||||
dev->ep_ctl.dev = dev;
|
||||
dev->ep_ctl.pipeline = false;
|
||||
QTAILQ_INIT(&dev->ep_ctl.queue);
|
||||
for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
|
||||
dev->ep_in[ep].nr = ep + 1;
|
||||
dev->ep_out[ep].nr = ep + 1;
|
||||
dev->ep_in[ep].pid = USB_TOKEN_IN;
|
||||
dev->ep_out[ep].pid = USB_TOKEN_OUT;
|
||||
dev->ep_in[ep].type = USB_ENDPOINT_XFER_INVALID;
|
||||
dev->ep_out[ep].type = USB_ENDPOINT_XFER_INVALID;
|
||||
dev->ep_in[ep].ifnum = 0;
|
||||
dev->ep_out[ep].ifnum = 0;
|
||||
dev->ep_in[ep].dev = dev;
|
||||
dev->ep_out[ep].dev = dev;
|
||||
dev->ep_in[ep].pipeline = false;
|
||||
dev->ep_out[ep].pipeline = false;
|
||||
QTAILQ_INIT(&dev->ep_in[ep].queue);
|
||||
QTAILQ_INIT(&dev->ep_out[ep].queue);
|
||||
}
|
||||
}
|
||||
|
||||
void usb_ep_dump(USBDevice *dev)
|
||||
{
|
||||
static const char *tname[] = {
|
||||
[USB_ENDPOINT_XFER_CONTROL] = "control",
|
||||
[USB_ENDPOINT_XFER_ISOC] = "isoc",
|
||||
[USB_ENDPOINT_XFER_BULK] = "bulk",
|
||||
[USB_ENDPOINT_XFER_INT] = "int",
|
||||
};
|
||||
int ifnum, ep, first;
|
||||
|
||||
fprintf(stderr, "Device \"%s\", config %d\n",
|
||||
dev->product_desc, dev->configuration);
|
||||
for (ifnum = 0; ifnum < 16; ifnum++) {
|
||||
first = 1;
|
||||
for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
|
||||
if (dev->ep_in[ep].type != USB_ENDPOINT_XFER_INVALID &&
|
||||
dev->ep_in[ep].ifnum == ifnum) {
|
||||
if (first) {
|
||||
first = 0;
|
||||
fprintf(stderr, " Interface %d, alternative %d\n",
|
||||
ifnum, dev->altsetting[ifnum]);
|
||||
}
|
||||
fprintf(stderr, " Endpoint %d, IN, %s, %d max\n", ep,
|
||||
tname[dev->ep_in[ep].type],
|
||||
dev->ep_in[ep].max_packet_size);
|
||||
}
|
||||
if (dev->ep_out[ep].type != USB_ENDPOINT_XFER_INVALID &&
|
||||
dev->ep_out[ep].ifnum == ifnum) {
|
||||
if (first) {
|
||||
first = 0;
|
||||
fprintf(stderr, " Interface %d, alternative %d\n",
|
||||
ifnum, dev->altsetting[ifnum]);
|
||||
}
|
||||
fprintf(stderr, " Endpoint %d, OUT, %s, %d max\n", ep,
|
||||
tname[dev->ep_out[ep].type],
|
||||
dev->ep_out[ep].max_packet_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "--\n");
|
||||
}
|
||||
|
||||
struct USBEndpoint *usb_ep_get(USBDevice *dev, int pid, int ep)
|
||||
{
|
||||
struct USBEndpoint *eps;
|
||||
|
||||
if (dev == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
eps = (pid == USB_TOKEN_IN) ? dev->ep_in : dev->ep_out;
|
||||
if (ep == 0) {
|
||||
return &dev->ep_ctl;
|
||||
}
|
||||
assert(pid == USB_TOKEN_IN || pid == USB_TOKEN_OUT);
|
||||
assert(ep > 0 && ep <= USB_MAX_ENDPOINTS);
|
||||
return eps + ep - 1;
|
||||
}
|
||||
|
||||
uint8_t usb_ep_get_type(USBDevice *dev, int pid, int ep)
|
||||
{
|
||||
struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
|
||||
return uep->type;
|
||||
}
|
||||
|
||||
void usb_ep_set_type(USBDevice *dev, int pid, int ep, uint8_t type)
|
||||
{
|
||||
struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
|
||||
uep->type = type;
|
||||
}
|
||||
|
||||
uint8_t usb_ep_get_ifnum(USBDevice *dev, int pid, int ep)
|
||||
{
|
||||
struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
|
||||
return uep->ifnum;
|
||||
}
|
||||
|
||||
void usb_ep_set_ifnum(USBDevice *dev, int pid, int ep, uint8_t ifnum)
|
||||
{
|
||||
struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
|
||||
uep->ifnum = ifnum;
|
||||
}
|
||||
|
||||
void usb_ep_set_max_packet_size(USBDevice *dev, int pid, int ep,
|
||||
uint16_t raw)
|
||||
{
|
||||
struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
|
||||
int size, microframes;
|
||||
|
||||
size = raw & 0x7ff;
|
||||
switch ((raw >> 11) & 3) {
|
||||
case 1:
|
||||
microframes = 2;
|
||||
break;
|
||||
case 2:
|
||||
microframes = 3;
|
||||
break;
|
||||
default:
|
||||
microframes = 1;
|
||||
break;
|
||||
}
|
||||
uep->max_packet_size = size * microframes;
|
||||
}
|
||||
|
||||
int usb_ep_get_max_packet_size(USBDevice *dev, int pid, int ep)
|
||||
{
|
||||
struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
|
||||
return uep->max_packet_size;
|
||||
}
|
||||
|
||||
void usb_ep_set_pipeline(USBDevice *dev, int pid, int ep, bool enabled)
|
||||
{
|
||||
struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
|
||||
uep->pipeline = enabled;
|
||||
}
|
601
hw/usb/desc.c
Normal file
601
hw/usb/desc.c
Normal file
|
@ -0,0 +1,601 @@
|
|||
#include "hw/usb.h"
|
||||
#include "hw/usb/desc.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static uint8_t usb_lo(uint16_t val)
|
||||
{
|
||||
return val & 0xff;
|
||||
}
|
||||
|
||||
static uint8_t usb_hi(uint16_t val)
|
||||
{
|
||||
return (val >> 8) & 0xff;
|
||||
}
|
||||
|
||||
int usb_desc_device(const USBDescID *id, const USBDescDevice *dev,
|
||||
uint8_t *dest, size_t len)
|
||||
{
|
||||
uint8_t bLength = 0x12;
|
||||
|
||||
if (len < bLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest[0x00] = bLength;
|
||||
dest[0x01] = USB_DT_DEVICE;
|
||||
|
||||
dest[0x02] = usb_lo(dev->bcdUSB);
|
||||
dest[0x03] = usb_hi(dev->bcdUSB);
|
||||
dest[0x04] = dev->bDeviceClass;
|
||||
dest[0x05] = dev->bDeviceSubClass;
|
||||
dest[0x06] = dev->bDeviceProtocol;
|
||||
dest[0x07] = dev->bMaxPacketSize0;
|
||||
|
||||
dest[0x08] = usb_lo(id->idVendor);
|
||||
dest[0x09] = usb_hi(id->idVendor);
|
||||
dest[0x0a] = usb_lo(id->idProduct);
|
||||
dest[0x0b] = usb_hi(id->idProduct);
|
||||
dest[0x0c] = usb_lo(id->bcdDevice);
|
||||
dest[0x0d] = usb_hi(id->bcdDevice);
|
||||
dest[0x0e] = id->iManufacturer;
|
||||
dest[0x0f] = id->iProduct;
|
||||
dest[0x10] = id->iSerialNumber;
|
||||
|
||||
dest[0x11] = dev->bNumConfigurations;
|
||||
|
||||
return bLength;
|
||||
}
|
||||
|
||||
int usb_desc_device_qualifier(const USBDescDevice *dev,
|
||||
uint8_t *dest, size_t len)
|
||||
{
|
||||
uint8_t bLength = 0x0a;
|
||||
|
||||
if (len < bLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest[0x00] = bLength;
|
||||
dest[0x01] = USB_DT_DEVICE_QUALIFIER;
|
||||
|
||||
dest[0x02] = usb_lo(dev->bcdUSB);
|
||||
dest[0x03] = usb_hi(dev->bcdUSB);
|
||||
dest[0x04] = dev->bDeviceClass;
|
||||
dest[0x05] = dev->bDeviceSubClass;
|
||||
dest[0x06] = dev->bDeviceProtocol;
|
||||
dest[0x07] = dev->bMaxPacketSize0;
|
||||
dest[0x08] = dev->bNumConfigurations;
|
||||
dest[0x09] = 0; /* reserved */
|
||||
|
||||
return bLength;
|
||||
}
|
||||
|
||||
int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len)
|
||||
{
|
||||
uint8_t bLength = 0x09;
|
||||
uint16_t wTotalLength = 0;
|
||||
int i, rc;
|
||||
|
||||
if (len < bLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest[0x00] = bLength;
|
||||
dest[0x01] = USB_DT_CONFIG;
|
||||
dest[0x04] = conf->bNumInterfaces;
|
||||
dest[0x05] = conf->bConfigurationValue;
|
||||
dest[0x06] = conf->iConfiguration;
|
||||
dest[0x07] = conf->bmAttributes;
|
||||
dest[0x08] = conf->bMaxPower;
|
||||
wTotalLength += bLength;
|
||||
|
||||
/* handle grouped interfaces if any*/
|
||||
for (i = 0; i < conf->nif_groups; i++) {
|
||||
rc = usb_desc_iface_group(&(conf->if_groups[i]),
|
||||
dest + wTotalLength,
|
||||
len - wTotalLength);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
wTotalLength += rc;
|
||||
}
|
||||
|
||||
/* handle normal (ungrouped / no IAD) interfaces if any */
|
||||
for (i = 0; i < conf->nif; i++) {
|
||||
rc = usb_desc_iface(conf->ifs + i, dest + wTotalLength, len - wTotalLength);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
wTotalLength += rc;
|
||||
}
|
||||
|
||||
dest[0x02] = usb_lo(wTotalLength);
|
||||
dest[0x03] = usb_hi(wTotalLength);
|
||||
return wTotalLength;
|
||||
}
|
||||
|
||||
int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest,
|
||||
size_t len)
|
||||
{
|
||||
int pos = 0;
|
||||
int i = 0;
|
||||
|
||||
/* handle interface association descriptor */
|
||||
uint8_t bLength = 0x08;
|
||||
|
||||
if (len < bLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest[0x00] = bLength;
|
||||
dest[0x01] = USB_DT_INTERFACE_ASSOC;
|
||||
dest[0x02] = iad->bFirstInterface;
|
||||
dest[0x03] = iad->bInterfaceCount;
|
||||
dest[0x04] = iad->bFunctionClass;
|
||||
dest[0x05] = iad->bFunctionSubClass;
|
||||
dest[0x06] = iad->bFunctionProtocol;
|
||||
dest[0x07] = iad->iFunction;
|
||||
pos += bLength;
|
||||
|
||||
/* handle associated interfaces in this group */
|
||||
for (i = 0; i < iad->nif; i++) {
|
||||
int rc = usb_desc_iface(&(iad->ifs[i]), dest + pos, len - pos);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
pos += rc;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len)
|
||||
{
|
||||
uint8_t bLength = 0x09;
|
||||
int i, rc, pos = 0;
|
||||
|
||||
if (len < bLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest[0x00] = bLength;
|
||||
dest[0x01] = USB_DT_INTERFACE;
|
||||
dest[0x02] = iface->bInterfaceNumber;
|
||||
dest[0x03] = iface->bAlternateSetting;
|
||||
dest[0x04] = iface->bNumEndpoints;
|
||||
dest[0x05] = iface->bInterfaceClass;
|
||||
dest[0x06] = iface->bInterfaceSubClass;
|
||||
dest[0x07] = iface->bInterfaceProtocol;
|
||||
dest[0x08] = iface->iInterface;
|
||||
pos += bLength;
|
||||
|
||||
for (i = 0; i < iface->ndesc; i++) {
|
||||
rc = usb_desc_other(iface->descs + i, dest + pos, len - pos);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
pos += rc;
|
||||
}
|
||||
|
||||
for (i = 0; i < iface->bNumEndpoints; i++) {
|
||||
rc = usb_desc_endpoint(iface->eps + i, dest + pos, len - pos);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
pos += rc;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len)
|
||||
{
|
||||
uint8_t bLength = ep->is_audio ? 0x09 : 0x07;
|
||||
uint8_t extralen = ep->extra ? ep->extra[0] : 0;
|
||||
|
||||
if (len < bLength + extralen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest[0x00] = bLength;
|
||||
dest[0x01] = USB_DT_ENDPOINT;
|
||||
dest[0x02] = ep->bEndpointAddress;
|
||||
dest[0x03] = ep->bmAttributes;
|
||||
dest[0x04] = usb_lo(ep->wMaxPacketSize);
|
||||
dest[0x05] = usb_hi(ep->wMaxPacketSize);
|
||||
dest[0x06] = ep->bInterval;
|
||||
if (ep->is_audio) {
|
||||
dest[0x07] = ep->bRefresh;
|
||||
dest[0x08] = ep->bSynchAddress;
|
||||
}
|
||||
if (ep->extra) {
|
||||
memcpy(dest + bLength, ep->extra, extralen);
|
||||
}
|
||||
|
||||
return bLength + extralen;
|
||||
}
|
||||
|
||||
int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len)
|
||||
{
|
||||
int bLength = desc->length ? desc->length : desc->data[0];
|
||||
|
||||
if (len < bLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(dest, desc->data, bLength);
|
||||
return bLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void usb_desc_ep_init(USBDevice *dev)
|
||||
{
|
||||
const USBDescIface *iface;
|
||||
int i, e, pid, ep;
|
||||
|
||||
usb_ep_init(dev);
|
||||
for (i = 0; i < dev->ninterfaces; i++) {
|
||||
iface = dev->ifaces[i];
|
||||
if (iface == NULL) {
|
||||
continue;
|
||||
}
|
||||
for (e = 0; e < iface->bNumEndpoints; e++) {
|
||||
pid = (iface->eps[e].bEndpointAddress & USB_DIR_IN) ?
|
||||
USB_TOKEN_IN : USB_TOKEN_OUT;
|
||||
ep = iface->eps[e].bEndpointAddress & 0x0f;
|
||||
usb_ep_set_type(dev, pid, ep, iface->eps[e].bmAttributes & 0x03);
|
||||
usb_ep_set_ifnum(dev, pid, ep, iface->bInterfaceNumber);
|
||||
usb_ep_set_max_packet_size(dev, pid, ep,
|
||||
iface->eps[e].wMaxPacketSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const USBDescIface *usb_desc_find_interface(USBDevice *dev,
|
||||
int nif, int alt)
|
||||
{
|
||||
const USBDescIface *iface;
|
||||
int g, i;
|
||||
|
||||
if (!dev->config) {
|
||||
return NULL;
|
||||
}
|
||||
for (g = 0; g < dev->config->nif_groups; g++) {
|
||||
for (i = 0; i < dev->config->if_groups[g].nif; i++) {
|
||||
iface = &dev->config->if_groups[g].ifs[i];
|
||||
if (iface->bInterfaceNumber == nif &&
|
||||
iface->bAlternateSetting == alt) {
|
||||
return iface;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < dev->config->nif; i++) {
|
||||
iface = &dev->config->ifs[i];
|
||||
if (iface->bInterfaceNumber == nif &&
|
||||
iface->bAlternateSetting == alt) {
|
||||
return iface;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int usb_desc_set_interface(USBDevice *dev, int index, int value)
|
||||
{
|
||||
const USBDescIface *iface;
|
||||
int old;
|
||||
|
||||
iface = usb_desc_find_interface(dev, index, value);
|
||||
if (iface == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
old = dev->altsetting[index];
|
||||
dev->altsetting[index] = value;
|
||||
dev->ifaces[index] = iface;
|
||||
usb_desc_ep_init(dev);
|
||||
|
||||
if (old != value) {
|
||||
usb_device_set_interface(dev, index, old, value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_desc_set_config(USBDevice *dev, int value)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (value == 0) {
|
||||
dev->configuration = 0;
|
||||
dev->ninterfaces = 0;
|
||||
dev->config = NULL;
|
||||
} else {
|
||||
for (i = 0; i < dev->device->bNumConfigurations; i++) {
|
||||
if (dev->device->confs[i].bConfigurationValue == value) {
|
||||
dev->configuration = value;
|
||||
dev->ninterfaces = dev->device->confs[i].bNumInterfaces;
|
||||
dev->config = dev->device->confs + i;
|
||||
assert(dev->ninterfaces <= USB_MAX_INTERFACES);
|
||||
}
|
||||
}
|
||||
if (i < dev->device->bNumConfigurations) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < dev->ninterfaces; i++) {
|
||||
usb_desc_set_interface(dev, i, 0);
|
||||
}
|
||||
for (; i < USB_MAX_INTERFACES; i++) {
|
||||
dev->altsetting[i] = 0;
|
||||
dev->ifaces[i] = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usb_desc_setdefaults(USBDevice *dev)
|
||||
{
|
||||
const USBDesc *desc = usb_device_get_usb_desc(dev);
|
||||
|
||||
assert(desc != NULL);
|
||||
switch (dev->speed) {
|
||||
case USB_SPEED_LOW:
|
||||
case USB_SPEED_FULL:
|
||||
dev->device = desc->full;
|
||||
break;
|
||||
case USB_SPEED_HIGH:
|
||||
dev->device = desc->high;
|
||||
break;
|
||||
}
|
||||
usb_desc_set_config(dev, 0);
|
||||
}
|
||||
|
||||
void usb_desc_init(USBDevice *dev)
|
||||
{
|
||||
const USBDesc *desc = usb_device_get_usb_desc(dev);
|
||||
|
||||
assert(desc != NULL);
|
||||
dev->speed = USB_SPEED_FULL;
|
||||
dev->speedmask = 0;
|
||||
if (desc->full) {
|
||||
dev->speedmask |= USB_SPEED_MASK_FULL;
|
||||
}
|
||||
if (desc->high) {
|
||||
dev->speedmask |= USB_SPEED_MASK_HIGH;
|
||||
}
|
||||
usb_desc_setdefaults(dev);
|
||||
}
|
||||
|
||||
void usb_desc_attach(USBDevice *dev)
|
||||
{
|
||||
const USBDesc *desc = usb_device_get_usb_desc(dev);
|
||||
|
||||
assert(desc != NULL);
|
||||
if (desc->high && (dev->port->speedmask & USB_SPEED_MASK_HIGH)) {
|
||||
dev->speed = USB_SPEED_HIGH;
|
||||
} else if (desc->full && (dev->port->speedmask & USB_SPEED_MASK_FULL)) {
|
||||
dev->speed = USB_SPEED_FULL;
|
||||
} else {
|
||||
fprintf(stderr, "usb: port/device speed mismatch for \"%s\"\n",
|
||||
usb_device_get_product_desc(dev));
|
||||
return;
|
||||
}
|
||||
usb_desc_setdefaults(dev);
|
||||
}
|
||||
|
||||
void usb_desc_set_string(USBDevice *dev, uint8_t index, const char *str)
|
||||
{
|
||||
USBDescString *s;
|
||||
|
||||
QLIST_FOREACH(s, &dev->strings, next) {
|
||||
if (s->index == index) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s == NULL) {
|
||||
s = g_malloc0(sizeof(*s));
|
||||
s->index = index;
|
||||
QLIST_INSERT_HEAD(&dev->strings, s, next);
|
||||
}
|
||||
g_free(s->str);
|
||||
s->str = g_strdup(str);
|
||||
}
|
||||
|
||||
const char *usb_desc_get_string(USBDevice *dev, uint8_t index)
|
||||
{
|
||||
USBDescString *s;
|
||||
|
||||
QLIST_FOREACH(s, &dev->strings, next) {
|
||||
if (s->index == index) {
|
||||
return s->str;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len)
|
||||
{
|
||||
uint8_t bLength, pos, i;
|
||||
const char *str;
|
||||
|
||||
if (len < 4) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
/* language ids */
|
||||
dest[0] = 4;
|
||||
dest[1] = USB_DT_STRING;
|
||||
dest[2] = 0x09;
|
||||
dest[3] = 0x04;
|
||||
return 4;
|
||||
}
|
||||
|
||||
str = usb_desc_get_string(dev, index);
|
||||
if (str == NULL) {
|
||||
str = usb_device_get_usb_desc(dev)->str[index];
|
||||
if (str == NULL) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bLength = strlen(str) * 2 + 2;
|
||||
dest[0] = bLength;
|
||||
dest[1] = USB_DT_STRING;
|
||||
i = 0; pos = 2;
|
||||
while (pos+1 < bLength && pos+1 < len) {
|
||||
dest[pos++] = str[i++];
|
||||
dest[pos++] = 0;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len)
|
||||
{
|
||||
const USBDesc *desc = usb_device_get_usb_desc(dev);
|
||||
const USBDescDevice *other_dev;
|
||||
uint8_t buf[256];
|
||||
uint8_t type = value >> 8;
|
||||
uint8_t index = value & 0xff;
|
||||
int ret = -1;
|
||||
|
||||
if (dev->speed == USB_SPEED_HIGH) {
|
||||
other_dev = usb_device_get_usb_desc(dev)->full;
|
||||
} else {
|
||||
other_dev = usb_device_get_usb_desc(dev)->high;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case USB_DT_DEVICE:
|
||||
ret = usb_desc_device(&desc->id, dev->device, buf, sizeof(buf));
|
||||
trace_usb_desc_device(dev->addr, len, ret);
|
||||
break;
|
||||
case USB_DT_CONFIG:
|
||||
if (index < dev->device->bNumConfigurations) {
|
||||
ret = usb_desc_config(dev->device->confs + index, buf, sizeof(buf));
|
||||
}
|
||||
trace_usb_desc_config(dev->addr, index, len, ret);
|
||||
break;
|
||||
case USB_DT_STRING:
|
||||
ret = usb_desc_string(dev, index, buf, sizeof(buf));
|
||||
trace_usb_desc_string(dev->addr, index, len, ret);
|
||||
break;
|
||||
|
||||
case USB_DT_DEVICE_QUALIFIER:
|
||||
if (other_dev != NULL) {
|
||||
ret = usb_desc_device_qualifier(other_dev, buf, sizeof(buf));
|
||||
}
|
||||
trace_usb_desc_device_qualifier(dev->addr, len, ret);
|
||||
break;
|
||||
case USB_DT_OTHER_SPEED_CONFIG:
|
||||
if (other_dev != NULL && index < other_dev->bNumConfigurations) {
|
||||
ret = usb_desc_config(other_dev->confs + index, buf, sizeof(buf));
|
||||
buf[0x01] = USB_DT_OTHER_SPEED_CONFIG;
|
||||
}
|
||||
trace_usb_desc_other_speed_config(dev->addr, index, len, ret);
|
||||
break;
|
||||
|
||||
case USB_DT_DEBUG:
|
||||
/* ignore silently */
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "%s: %d unknown type %d (len %zd)\n", __FUNCTION__,
|
||||
dev->addr, type, len);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret > 0) {
|
||||
if (ret > len) {
|
||||
ret = len;
|
||||
}
|
||||
memcpy(dest, buf, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int usb_desc_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
const USBDesc *desc = usb_device_get_usb_desc(dev);
|
||||
int ret = -1;
|
||||
|
||||
assert(desc != NULL);
|
||||
switch(request) {
|
||||
case DeviceOutRequest | USB_REQ_SET_ADDRESS:
|
||||
dev->addr = value;
|
||||
trace_usb_set_addr(dev->addr);
|
||||
ret = 0;
|
||||
break;
|
||||
|
||||
case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
|
||||
ret = usb_desc_get_descriptor(dev, value, data, length);
|
||||
break;
|
||||
|
||||
case DeviceRequest | USB_REQ_GET_CONFIGURATION:
|
||||
/*
|
||||
* 9.4.2: 0 should be returned if the device is unconfigured, otherwise
|
||||
* the non zero value of bConfigurationValue.
|
||||
*/
|
||||
data[0] = dev->config ? dev->config->bConfigurationValue : 0;
|
||||
ret = 1;
|
||||
break;
|
||||
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
|
||||
ret = usb_desc_set_config(dev, value);
|
||||
trace_usb_set_config(dev->addr, value, ret);
|
||||
break;
|
||||
|
||||
case DeviceRequest | USB_REQ_GET_STATUS: {
|
||||
const USBDescConfig *config = dev->config ?
|
||||
dev->config : &dev->device->confs[0];
|
||||
|
||||
data[0] = 0;
|
||||
/*
|
||||
* Default state: Device behavior when this request is received while
|
||||
* the device is in the Default state is not specified.
|
||||
* We return the same value that a configured device would return if
|
||||
* it used the first configuration.
|
||||
*/
|
||||
if (config->bmAttributes & 0x40) {
|
||||
data[0] |= 1 << USB_DEVICE_SELF_POWERED;
|
||||
}
|
||||
if (dev->remote_wakeup) {
|
||||
data[0] |= 1 << USB_DEVICE_REMOTE_WAKEUP;
|
||||
}
|
||||
data[1] = 0x00;
|
||||
ret = 2;
|
||||
break;
|
||||
}
|
||||
case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
|
||||
if (value == USB_DEVICE_REMOTE_WAKEUP) {
|
||||
dev->remote_wakeup = 0;
|
||||
ret = 0;
|
||||
}
|
||||
trace_usb_clear_device_feature(dev->addr, value, ret);
|
||||
break;
|
||||
case DeviceOutRequest | USB_REQ_SET_FEATURE:
|
||||
if (value == USB_DEVICE_REMOTE_WAKEUP) {
|
||||
dev->remote_wakeup = 1;
|
||||
ret = 0;
|
||||
}
|
||||
trace_usb_set_device_feature(dev->addr, value, ret);
|
||||
break;
|
||||
|
||||
case InterfaceRequest | USB_REQ_GET_INTERFACE:
|
||||
if (index < 0 || index >= dev->ninterfaces) {
|
||||
break;
|
||||
}
|
||||
data[0] = dev->altsetting[index];
|
||||
ret = 1;
|
||||
break;
|
||||
case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
|
||||
ret = usb_desc_set_interface(dev, index, value);
|
||||
trace_usb_set_interface(dev->addr, index, value, ret);
|
||||
break;
|
||||
|
||||
}
|
||||
return ret;
|
||||
}
|
117
hw/usb/desc.h
Normal file
117
hw/usb/desc.h
Normal file
|
@ -0,0 +1,117 @@
|
|||
#ifndef QEMU_HW_USB_DESC_H
|
||||
#define QEMU_HW_USB_DESC_H
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
struct USBDescID {
|
||||
uint16_t idVendor;
|
||||
uint16_t idProduct;
|
||||
uint16_t bcdDevice;
|
||||
uint8_t iManufacturer;
|
||||
uint8_t iProduct;
|
||||
uint8_t iSerialNumber;
|
||||
};
|
||||
|
||||
struct USBDescDevice {
|
||||
uint16_t bcdUSB;
|
||||
uint8_t bDeviceClass;
|
||||
uint8_t bDeviceSubClass;
|
||||
uint8_t bDeviceProtocol;
|
||||
uint8_t bMaxPacketSize0;
|
||||
uint8_t bNumConfigurations;
|
||||
|
||||
const USBDescConfig *confs;
|
||||
};
|
||||
|
||||
struct USBDescConfig {
|
||||
uint8_t bNumInterfaces;
|
||||
uint8_t bConfigurationValue;
|
||||
uint8_t iConfiguration;
|
||||
uint8_t bmAttributes;
|
||||
uint8_t bMaxPower;
|
||||
|
||||
/* grouped interfaces */
|
||||
uint8_t nif_groups;
|
||||
const USBDescIfaceAssoc *if_groups;
|
||||
|
||||
/* "normal" interfaces */
|
||||
uint8_t nif;
|
||||
const USBDescIface *ifs;
|
||||
};
|
||||
|
||||
/* conceptually an Interface Association Descriptor, and releated interfaces */
|
||||
struct USBDescIfaceAssoc {
|
||||
uint8_t bFirstInterface;
|
||||
uint8_t bInterfaceCount;
|
||||
uint8_t bFunctionClass;
|
||||
uint8_t bFunctionSubClass;
|
||||
uint8_t bFunctionProtocol;
|
||||
uint8_t iFunction;
|
||||
|
||||
uint8_t nif;
|
||||
const USBDescIface *ifs;
|
||||
};
|
||||
|
||||
struct USBDescIface {
|
||||
uint8_t bInterfaceNumber;
|
||||
uint8_t bAlternateSetting;
|
||||
uint8_t bNumEndpoints;
|
||||
uint8_t bInterfaceClass;
|
||||
uint8_t bInterfaceSubClass;
|
||||
uint8_t bInterfaceProtocol;
|
||||
uint8_t iInterface;
|
||||
|
||||
uint8_t ndesc;
|
||||
USBDescOther *descs;
|
||||
USBDescEndpoint *eps;
|
||||
};
|
||||
|
||||
struct USBDescEndpoint {
|
||||
uint8_t bEndpointAddress;
|
||||
uint8_t bmAttributes;
|
||||
uint16_t wMaxPacketSize;
|
||||
uint8_t bInterval;
|
||||
uint8_t bRefresh;
|
||||
uint8_t bSynchAddress;
|
||||
|
||||
uint8_t is_audio; /* has bRefresh + bSynchAddress */
|
||||
uint8_t *extra;
|
||||
};
|
||||
|
||||
struct USBDescOther {
|
||||
uint8_t length;
|
||||
const uint8_t *data;
|
||||
};
|
||||
|
||||
typedef const char *USBDescStrings[256];
|
||||
|
||||
struct USBDesc {
|
||||
USBDescID id;
|
||||
const USBDescDevice *full;
|
||||
const USBDescDevice *high;
|
||||
const char* const *str;
|
||||
};
|
||||
|
||||
/* generate usb packages from structs */
|
||||
int usb_desc_device(const USBDescID *id, const USBDescDevice *dev,
|
||||
uint8_t *dest, size_t len);
|
||||
int usb_desc_device_qualifier(const USBDescDevice *dev,
|
||||
uint8_t *dest, size_t len);
|
||||
int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len);
|
||||
int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest,
|
||||
size_t len);
|
||||
int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len);
|
||||
int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len);
|
||||
int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len);
|
||||
|
||||
/* control message emulation helpers */
|
||||
void usb_desc_init(USBDevice *dev);
|
||||
void usb_desc_attach(USBDevice *dev);
|
||||
void usb_desc_set_string(USBDevice *dev, uint8_t index, const char *str);
|
||||
const char *usb_desc_get_string(USBDevice *dev, uint8_t index);
|
||||
int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len);
|
||||
int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len);
|
||||
int usb_desc_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data);
|
||||
|
||||
#endif /* QEMU_HW_USB_DESC_H */
|
714
hw/usb/dev-audio.c
Normal file
714
hw/usb/dev-audio.c
Normal file
|
@ -0,0 +1,714 @@
|
|||
/*
|
||||
* QEMU USB audio device
|
||||
*
|
||||
* written by:
|
||||
* H. Peter Anvin <hpa@linux.intel.com>
|
||||
* Gerd Hoffmann <kraxel@redhat.com>
|
||||
*
|
||||
* lousely based on usb net device code which is:
|
||||
*
|
||||
* Copyright (c) 2006 Thomas Sailer
|
||||
* Copyright (c) 2008 Andrzej Zaborowski
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "hw/usb.h"
|
||||
#include "hw/usb/desc.h"
|
||||
#include "hw/hw.h"
|
||||
#include "hw/audiodev.h"
|
||||
#include "audio/audio.h"
|
||||
|
||||
#define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */
|
||||
#define USBAUDIO_PRODUCT_NUM 0x0002
|
||||
|
||||
#define DEV_CONFIG_VALUE 1 /* The one and only */
|
||||
|
||||
/* Descriptor subtypes for AC interfaces */
|
||||
#define DST_AC_HEADER 1
|
||||
#define DST_AC_INPUT_TERMINAL 2
|
||||
#define DST_AC_OUTPUT_TERMINAL 3
|
||||
#define DST_AC_FEATURE_UNIT 6
|
||||
/* Descriptor subtypes for AS interfaces */
|
||||
#define DST_AS_GENERAL 1
|
||||
#define DST_AS_FORMAT_TYPE 2
|
||||
/* Descriptor subtypes for endpoints */
|
||||
#define DST_EP_GENERAL 1
|
||||
|
||||
enum usb_audio_strings {
|
||||
STRING_NULL,
|
||||
STRING_MANUFACTURER,
|
||||
STRING_PRODUCT,
|
||||
STRING_SERIALNUMBER,
|
||||
STRING_CONFIG,
|
||||
STRING_USBAUDIO_CONTROL,
|
||||
STRING_INPUT_TERMINAL,
|
||||
STRING_FEATURE_UNIT,
|
||||
STRING_OUTPUT_TERMINAL,
|
||||
STRING_NULL_STREAM,
|
||||
STRING_REAL_STREAM,
|
||||
};
|
||||
|
||||
static const USBDescStrings usb_audio_stringtable = {
|
||||
[STRING_MANUFACTURER] = "QEMU",
|
||||
[STRING_PRODUCT] = "QEMU USB Audio",
|
||||
[STRING_SERIALNUMBER] = "1",
|
||||
[STRING_CONFIG] = "Audio Configuration",
|
||||
[STRING_USBAUDIO_CONTROL] = "Audio Device",
|
||||
[STRING_INPUT_TERMINAL] = "Audio Output Pipe",
|
||||
[STRING_FEATURE_UNIT] = "Audio Output Volume Control",
|
||||
[STRING_OUTPUT_TERMINAL] = "Audio Output Terminal",
|
||||
[STRING_NULL_STREAM] = "Audio Output - Disabled",
|
||||
[STRING_REAL_STREAM] = "Audio Output - 48 kHz Stereo",
|
||||
};
|
||||
|
||||
#define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff)
|
||||
#define U24(x) U16(x), (((x) >> 16) & 0xff)
|
||||
#define U32(x) U24(x), (((x) >> 24) & 0xff)
|
||||
|
||||
/*
|
||||
* A Basic Audio Device uses these specific values
|
||||
*/
|
||||
#define USBAUDIO_PACKET_SIZE 192
|
||||
#define USBAUDIO_SAMPLE_RATE 48000
|
||||
#define USBAUDIO_PACKET_INTERVAL 1
|
||||
|
||||
static const USBDescIface desc_iface[] = {
|
||||
{
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL,
|
||||
.bInterfaceProtocol = 0x04,
|
||||
.iInterface = STRING_USBAUDIO_CONTROL,
|
||||
.ndesc = 4,
|
||||
.descs = (USBDescOther[]) {
|
||||
{
|
||||
/* Headphone Class-Specific AC Interface Header Descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x09, /* u8 bLength */
|
||||
USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
|
||||
DST_AC_HEADER, /* u8 bDescriptorSubtype */
|
||||
U16(0x0100), /* u16 bcdADC */
|
||||
U16(0x2b), /* u16 wTotalLength */
|
||||
0x01, /* u8 bInCollection */
|
||||
0x01, /* u8 baInterfaceNr */
|
||||
}
|
||||
},{
|
||||
/* Generic Stereo Input Terminal ID1 Descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x0c, /* u8 bLength */
|
||||
USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
|
||||
DST_AC_INPUT_TERMINAL, /* u8 bDescriptorSubtype */
|
||||
0x01, /* u8 bTerminalID */
|
||||
U16(0x0101), /* u16 wTerminalType */
|
||||
0x00, /* u8 bAssocTerminal */
|
||||
0x02, /* u16 bNrChannels */
|
||||
U16(0x0003), /* u16 wChannelConfig */
|
||||
0x00, /* u8 iChannelNames */
|
||||
STRING_INPUT_TERMINAL, /* u8 iTerminal */
|
||||
}
|
||||
},{
|
||||
/* Generic Stereo Feature Unit ID2 Descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x0d, /* u8 bLength */
|
||||
USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
|
||||
DST_AC_FEATURE_UNIT, /* u8 bDescriptorSubtype */
|
||||
0x02, /* u8 bUnitID */
|
||||
0x01, /* u8 bSourceID */
|
||||
0x02, /* u8 bControlSize */
|
||||
U16(0x0001), /* u16 bmaControls(0) */
|
||||
U16(0x0002), /* u16 bmaControls(1) */
|
||||
U16(0x0002), /* u16 bmaControls(2) */
|
||||
STRING_FEATURE_UNIT, /* u8 iFeature */
|
||||
}
|
||||
},{
|
||||
/* Headphone Ouptut Terminal ID3 Descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x09, /* u8 bLength */
|
||||
USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
|
||||
DST_AC_OUTPUT_TERMINAL, /* u8 bDescriptorSubtype */
|
||||
0x03, /* u8 bUnitID */
|
||||
U16(0x0301), /* u16 wTerminalType (SPK) */
|
||||
0x00, /* u8 bAssocTerminal */
|
||||
0x02, /* u8 bSourceID */
|
||||
STRING_OUTPUT_TERMINAL, /* u8 iTerminal */
|
||||
}
|
||||
}
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
|
||||
.iInterface = STRING_NULL_STREAM,
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
|
||||
.iInterface = STRING_REAL_STREAM,
|
||||
.ndesc = 2,
|
||||
.descs = (USBDescOther[]) {
|
||||
{
|
||||
/* Headphone Class-specific AS General Interface Descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x07, /* u8 bLength */
|
||||
USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
|
||||
DST_AS_GENERAL, /* u8 bDescriptorSubtype */
|
||||
0x01, /* u8 bTerminalLink */
|
||||
0x00, /* u8 bDelay */
|
||||
0x01, 0x00, /* u16 wFormatTag */
|
||||
}
|
||||
},{
|
||||
/* Headphone Type I Format Type Descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x0b, /* u8 bLength */
|
||||
USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
|
||||
DST_AS_FORMAT_TYPE, /* u8 bDescriptorSubtype */
|
||||
0x01, /* u8 bFormatType */
|
||||
0x02, /* u8 bNrChannels */
|
||||
0x02, /* u8 bSubFrameSize */
|
||||
0x10, /* u8 bBitResolution */
|
||||
0x01, /* u8 bSamFreqType */
|
||||
U24(USBAUDIO_SAMPLE_RATE), /* u24 tSamFreq */
|
||||
}
|
||||
}
|
||||
},
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | 0x01,
|
||||
.bmAttributes = 0x0d,
|
||||
.wMaxPacketSize = USBAUDIO_PACKET_SIZE,
|
||||
.bInterval = 1,
|
||||
.is_audio = 1,
|
||||
/* Stereo Headphone Class-specific
|
||||
AS Audio Data Endpoint Descriptor */
|
||||
.extra = (uint8_t[]) {
|
||||
0x07, /* u8 bLength */
|
||||
USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */
|
||||
DST_EP_GENERAL, /* u8 bDescriptorSubtype */
|
||||
0x00, /* u8 bmAttributes */
|
||||
0x00, /* u8 bLockDelayUnits */
|
||||
U16(0x0000), /* u16 wLockDelay */
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device = {
|
||||
.bcdUSB = 0x0200,
|
||||
.bMaxPacketSize0 = 64,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 2,
|
||||
.bConfigurationValue = DEV_CONFIG_VALUE,
|
||||
.iConfiguration = STRING_CONFIG,
|
||||
.bmAttributes = 0xc0,
|
||||
.bMaxPower = 0x32,
|
||||
.nif = ARRAY_SIZE(desc_iface),
|
||||
.ifs = desc_iface,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDesc desc_audio = {
|
||||
.id = {
|
||||
.idVendor = USBAUDIO_VENDOR_NUM,
|
||||
.idProduct = USBAUDIO_PRODUCT_NUM,
|
||||
.bcdDevice = 0,
|
||||
.iManufacturer = STRING_MANUFACTURER,
|
||||
.iProduct = STRING_PRODUCT,
|
||||
.iSerialNumber = STRING_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device,
|
||||
.str = usb_audio_stringtable,
|
||||
};
|
||||
|
||||
/*
|
||||
* A USB audio device supports an arbitrary number of alternate
|
||||
* interface settings for each interface. Each corresponds to a block
|
||||
* diagram of parameterized blocks. This can thus refer to things like
|
||||
* number of channels, data rates, or in fact completely different
|
||||
* block diagrams. Alternative setting 0 is always the null block diagram,
|
||||
* which is used by a disabled device.
|
||||
*/
|
||||
enum usb_audio_altset {
|
||||
ALTSET_OFF = 0x00, /* No endpoint */
|
||||
ALTSET_ON = 0x01, /* Single endpoint */
|
||||
};
|
||||
|
||||
/*
|
||||
* Class-specific control requests
|
||||
*/
|
||||
#define CR_SET_CUR 0x01
|
||||
#define CR_GET_CUR 0x81
|
||||
#define CR_SET_MIN 0x02
|
||||
#define CR_GET_MIN 0x82
|
||||
#define CR_SET_MAX 0x03
|
||||
#define CR_GET_MAX 0x83
|
||||
#define CR_SET_RES 0x04
|
||||
#define CR_GET_RES 0x84
|
||||
#define CR_SET_MEM 0x05
|
||||
#define CR_GET_MEM 0x85
|
||||
#define CR_GET_STAT 0xff
|
||||
|
||||
/*
|
||||
* Feature Unit Control Selectors
|
||||
*/
|
||||
#define MUTE_CONTROL 0x01
|
||||
#define VOLUME_CONTROL 0x02
|
||||
#define BASS_CONTROL 0x03
|
||||
#define MID_CONTROL 0x04
|
||||
#define TREBLE_CONTROL 0x05
|
||||
#define GRAPHIC_EQUALIZER_CONTROL 0x06
|
||||
#define AUTOMATIC_GAIN_CONTROL 0x07
|
||||
#define DELAY_CONTROL 0x08
|
||||
#define BASS_BOOST_CONTROL 0x09
|
||||
#define LOUDNESS_CONTROL 0x0a
|
||||
|
||||
/*
|
||||
* buffering
|
||||
*/
|
||||
|
||||
struct streambuf {
|
||||
uint8_t *data;
|
||||
uint32_t size;
|
||||
uint32_t prod;
|
||||
uint32_t cons;
|
||||
};
|
||||
|
||||
static void streambuf_init(struct streambuf *buf, uint32_t size)
|
||||
{
|
||||
g_free(buf->data);
|
||||
buf->size = size - (size % USBAUDIO_PACKET_SIZE);
|
||||
buf->data = g_malloc(buf->size);
|
||||
buf->prod = 0;
|
||||
buf->cons = 0;
|
||||
}
|
||||
|
||||
static void streambuf_fini(struct streambuf *buf)
|
||||
{
|
||||
g_free(buf->data);
|
||||
buf->data = NULL;
|
||||
}
|
||||
|
||||
static int streambuf_put(struct streambuf *buf, USBPacket *p)
|
||||
{
|
||||
uint32_t free = buf->size - (buf->prod - buf->cons);
|
||||
|
||||
if (!free) {
|
||||
return 0;
|
||||
}
|
||||
assert(free >= USBAUDIO_PACKET_SIZE);
|
||||
usb_packet_copy(p, buf->data + (buf->prod % buf->size),
|
||||
USBAUDIO_PACKET_SIZE);
|
||||
buf->prod += USBAUDIO_PACKET_SIZE;
|
||||
return USBAUDIO_PACKET_SIZE;
|
||||
}
|
||||
|
||||
static uint8_t *streambuf_get(struct streambuf *buf)
|
||||
{
|
||||
uint32_t used = buf->prod - buf->cons;
|
||||
uint8_t *data;
|
||||
|
||||
if (!used) {
|
||||
return NULL;
|
||||
}
|
||||
assert(used >= USBAUDIO_PACKET_SIZE);
|
||||
data = buf->data + (buf->cons % buf->size);
|
||||
buf->cons += USBAUDIO_PACKET_SIZE;
|
||||
return data;
|
||||
}
|
||||
|
||||
typedef struct USBAudioState {
|
||||
/* qemu interfaces */
|
||||
USBDevice dev;
|
||||
QEMUSoundCard card;
|
||||
|
||||
/* state */
|
||||
struct {
|
||||
enum usb_audio_altset altset;
|
||||
struct audsettings as;
|
||||
SWVoiceOut *voice;
|
||||
bool mute;
|
||||
uint8_t vol[2];
|
||||
struct streambuf buf;
|
||||
} out;
|
||||
|
||||
/* properties */
|
||||
uint32_t debug;
|
||||
uint32_t buffer;
|
||||
} USBAudioState;
|
||||
|
||||
static void output_callback(void *opaque, int avail)
|
||||
{
|
||||
USBAudioState *s = opaque;
|
||||
uint8_t *data;
|
||||
|
||||
for (;;) {
|
||||
if (avail < USBAUDIO_PACKET_SIZE) {
|
||||
return;
|
||||
}
|
||||
data = streambuf_get(&s->out.buf);
|
||||
if (NULL == data) {
|
||||
return;
|
||||
}
|
||||
AUD_write(s->out.voice, data, USBAUDIO_PACKET_SIZE);
|
||||
avail -= USBAUDIO_PACKET_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
static int usb_audio_set_output_altset(USBAudioState *s, int altset)
|
||||
{
|
||||
switch (altset) {
|
||||
case ALTSET_OFF:
|
||||
streambuf_init(&s->out.buf, s->buffer);
|
||||
AUD_set_active_out(s->out.voice, false);
|
||||
break;
|
||||
case ALTSET_ON:
|
||||
AUD_set_active_out(s->out.voice, true);
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: set interface %d\n", altset);
|
||||
}
|
||||
s->out.altset = altset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: we arbitrarily map the volume control range onto -inf..+8 dB
|
||||
*/
|
||||
#define ATTRIB_ID(cs, attrib, idif) \
|
||||
(((cs) << 24) | ((attrib) << 16) | (idif))
|
||||
|
||||
static int usb_audio_get_control(USBAudioState *s, uint8_t attrib,
|
||||
uint16_t cscn, uint16_t idif,
|
||||
int length, uint8_t *data)
|
||||
{
|
||||
uint8_t cs = cscn >> 8;
|
||||
uint8_t cn = cscn - 1; /* -1 for the non-present master control */
|
||||
uint32_t aid = ATTRIB_ID(cs, attrib, idif);
|
||||
int ret = USB_RET_STALL;
|
||||
|
||||
switch (aid) {
|
||||
case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200):
|
||||
data[0] = s->out.mute;
|
||||
ret = 1;
|
||||
break;
|
||||
case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200):
|
||||
if (cn < 2) {
|
||||
uint16_t vol = (s->out.vol[cn] * 0x8800 + 127) / 255 + 0x8000;
|
||||
data[0] = vol;
|
||||
data[1] = vol >> 8;
|
||||
ret = 2;
|
||||
}
|
||||
break;
|
||||
case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200):
|
||||
if (cn < 2) {
|
||||
data[0] = 0x01;
|
||||
data[1] = 0x80;
|
||||
ret = 2;
|
||||
}
|
||||
break;
|
||||
case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200):
|
||||
if (cn < 2) {
|
||||
data[0] = 0x00;
|
||||
data[1] = 0x08;
|
||||
ret = 2;
|
||||
}
|
||||
break;
|
||||
case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200):
|
||||
if (cn < 2) {
|
||||
data[0] = 0x88;
|
||||
data[1] = 0x00;
|
||||
ret = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
|
||||
uint16_t cscn, uint16_t idif,
|
||||
int length, uint8_t *data)
|
||||
{
|
||||
uint8_t cs = cscn >> 8;
|
||||
uint8_t cn = cscn - 1; /* -1 for the non-present master control */
|
||||
uint32_t aid = ATTRIB_ID(cs, attrib, idif);
|
||||
int ret = USB_RET_STALL;
|
||||
bool set_vol = false;
|
||||
|
||||
switch (aid) {
|
||||
case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200):
|
||||
s->out.mute = data[0] & 1;
|
||||
set_vol = true;
|
||||
ret = 0;
|
||||
break;
|
||||
case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200):
|
||||
if (cn < 2) {
|
||||
uint16_t vol = data[0] + (data[1] << 8);
|
||||
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol);
|
||||
}
|
||||
|
||||
vol -= 0x8000;
|
||||
vol = (vol * 255 + 0x4400) / 0x8800;
|
||||
if (vol > 255) {
|
||||
vol = 255;
|
||||
}
|
||||
|
||||
s->out.vol[cn] = vol;
|
||||
set_vol = true;
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (set_vol) {
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n",
|
||||
s->out.mute, s->out.vol[0], s->out.vol[1]);
|
||||
}
|
||||
AUD_set_volume_out(s->out.voice, s->out.mute,
|
||||
s->out.vol[0], s->out.vol[1]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int usb_audio_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index,
|
||||
int length, uint8_t *data)
|
||||
{
|
||||
USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
|
||||
int ret = 0;
|
||||
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: control transaction: "
|
||||
"request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n",
|
||||
request, value, index, length);
|
||||
}
|
||||
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (request) {
|
||||
case ClassInterfaceRequest | CR_GET_CUR:
|
||||
case ClassInterfaceRequest | CR_GET_MIN:
|
||||
case ClassInterfaceRequest | CR_GET_MAX:
|
||||
case ClassInterfaceRequest | CR_GET_RES:
|
||||
ret = usb_audio_get_control(s, request & 0xff, value, index,
|
||||
length, data);
|
||||
if (ret < 0) {
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: fail: get control\n");
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
case ClassInterfaceOutRequest | CR_SET_CUR:
|
||||
case ClassInterfaceOutRequest | CR_SET_MIN:
|
||||
case ClassInterfaceOutRequest | CR_SET_MAX:
|
||||
case ClassInterfaceOutRequest | CR_SET_RES:
|
||||
ret = usb_audio_set_control(s, request & 0xff, value, index,
|
||||
length, data);
|
||||
if (ret < 0) {
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: fail: set control\n");
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
fail:
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: failed control transaction: "
|
||||
"request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n",
|
||||
request, value, index, length);
|
||||
}
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_audio_set_interface(USBDevice *dev, int iface,
|
||||
int old, int value)
|
||||
{
|
||||
USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
|
||||
|
||||
if (iface == 1) {
|
||||
usb_audio_set_output_altset(s, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_audio_handle_reset(USBDevice *dev)
|
||||
{
|
||||
USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
|
||||
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: reset\n");
|
||||
}
|
||||
usb_audio_set_output_altset(s, ALTSET_OFF);
|
||||
}
|
||||
|
||||
static int usb_audio_handle_dataout(USBAudioState *s, USBPacket *p)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (s->out.altset == ALTSET_OFF) {
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
|
||||
rc = streambuf_put(&s->out.buf, p);
|
||||
if (rc < p->iov.size && s->debug > 1) {
|
||||
fprintf(stderr, "usb-audio: output overrun (%zd bytes)\n",
|
||||
p->iov.size - rc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_audio_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBAudioState *s = (USBAudioState *) dev;
|
||||
int ret = 0;
|
||||
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_OUT:
|
||||
switch (p->ep->nr) {
|
||||
case 1:
|
||||
ret = usb_audio_handle_dataout(s, p);
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
if (ret == USB_RET_STALL && s->debug) {
|
||||
fprintf(stderr, "usb-audio: failed data transaction: "
|
||||
"pid 0x%x ep 0x%x len 0x%zx\n",
|
||||
p->pid, p->ep->nr, p->iov.size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_audio_handle_destroy(USBDevice *dev)
|
||||
{
|
||||
USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
|
||||
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "usb-audio: destroy\n");
|
||||
}
|
||||
|
||||
usb_audio_set_output_altset(s, ALTSET_OFF);
|
||||
AUD_close_out(&s->card, s->out.voice);
|
||||
AUD_remove_card(&s->card);
|
||||
|
||||
streambuf_fini(&s->out.buf);
|
||||
}
|
||||
|
||||
static int usb_audio_initfn(USBDevice *dev)
|
||||
{
|
||||
USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
|
||||
|
||||
usb_desc_init(dev);
|
||||
s->dev.opaque = s;
|
||||
AUD_register_card("usb-audio", &s->card);
|
||||
|
||||
s->out.altset = ALTSET_OFF;
|
||||
s->out.mute = false;
|
||||
s->out.vol[0] = 240; /* 0 dB */
|
||||
s->out.vol[1] = 240; /* 0 dB */
|
||||
s->out.as.freq = USBAUDIO_SAMPLE_RATE;
|
||||
s->out.as.nchannels = 2;
|
||||
s->out.as.fmt = AUD_FMT_S16;
|
||||
s->out.as.endianness = 0;
|
||||
streambuf_init(&s->out.buf, s->buffer);
|
||||
|
||||
s->out.voice = AUD_open_out(&s->card, s->out.voice, "usb-audio",
|
||||
s, output_callback, &s->out.as);
|
||||
AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], s->out.vol[1]);
|
||||
AUD_set_active_out(s->out.voice, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_usb_audio = {
|
||||
.name = "usb-audio",
|
||||
.unmigratable = 1,
|
||||
};
|
||||
|
||||
static Property usb_audio_properties[] = {
|
||||
DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0),
|
||||
DEFINE_PROP_UINT32("buffer", USBAudioState, buffer,
|
||||
8 * USBAUDIO_PACKET_SIZE),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void usb_audio_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *k = USB_DEVICE_CLASS(klass);
|
||||
|
||||
dc->vmsd = &vmstate_usb_audio;
|
||||
dc->props = usb_audio_properties;
|
||||
k->product_desc = "QEMU USB Audio Interface";
|
||||
k->usb_desc = &desc_audio;
|
||||
k->init = usb_audio_initfn;
|
||||
k->handle_reset = usb_audio_handle_reset;
|
||||
k->handle_control = usb_audio_handle_control;
|
||||
k->handle_data = usb_audio_handle_data;
|
||||
k->handle_destroy = usb_audio_handle_destroy;
|
||||
k->set_interface = usb_audio_set_interface;
|
||||
}
|
||||
|
||||
static TypeInfo usb_audio_info = {
|
||||
.name = "usb-audio",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBAudioState),
|
||||
.class_init = usb_audio_class_init,
|
||||
};
|
||||
|
||||
static void usb_audio_register_types(void)
|
||||
{
|
||||
type_register_static(&usb_audio_info);
|
||||
usb_legacy_register("usb-audio", "audio", NULL);
|
||||
}
|
||||
|
||||
type_init(usb_audio_register_types)
|
557
hw/usb/dev-bluetooth.c
Normal file
557
hw/usb/dev-bluetooth.c
Normal file
|
@ -0,0 +1,557 @@
|
|||
/*
|
||||
* QEMU Bluetooth HCI USB Transport Layer v1.0
|
||||
*
|
||||
* Copyright (C) 2007 OpenMoko, Inc.
|
||||
* Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* 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 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "hw/usb.h"
|
||||
#include "hw/usb/desc.h"
|
||||
#include "net.h"
|
||||
#include "hw/bt.h"
|
||||
|
||||
struct USBBtState {
|
||||
USBDevice dev;
|
||||
struct HCIInfo *hci;
|
||||
|
||||
int config;
|
||||
|
||||
#define CFIFO_LEN_MASK 255
|
||||
#define DFIFO_LEN_MASK 4095
|
||||
struct usb_hci_in_fifo_s {
|
||||
uint8_t data[(DFIFO_LEN_MASK + 1) * 2];
|
||||
struct {
|
||||
uint8_t *data;
|
||||
int len;
|
||||
} fifo[CFIFO_LEN_MASK + 1];
|
||||
int dstart, dlen, dsize, start, len;
|
||||
} evt, acl, sco;
|
||||
|
||||
struct usb_hci_out_fifo_s {
|
||||
uint8_t data[4096];
|
||||
int len;
|
||||
} outcmd, outacl, outsco;
|
||||
};
|
||||
|
||||
#define USB_EVT_EP 1
|
||||
#define USB_ACL_EP 2
|
||||
#define USB_SCO_EP 3
|
||||
|
||||
enum {
|
||||
STR_MANUFACTURER = 1,
|
||||
STR_SERIALNUMBER,
|
||||
};
|
||||
|
||||
static const USBDescStrings desc_strings = {
|
||||
[STR_MANUFACTURER] = "QEMU " QEMU_VERSION,
|
||||
[STR_SERIALNUMBER] = "1",
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_bluetooth[] = {
|
||||
{
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 3,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_EVT_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = 0x10,
|
||||
.bInterval = 0x02,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_ACL_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 0x40,
|
||||
.bInterval = 0x0a,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_ACL_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 0x40,
|
||||
.bInterval = 0x0a,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x09,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x09,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 2,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x11,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x11,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 3,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x19,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x19,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 4,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x21,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x21,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 5,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x31,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x31,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_bluetooth = {
|
||||
.bcdUSB = 0x0110,
|
||||
.bDeviceClass = 0xe0, /* Wireless */
|
||||
.bDeviceSubClass = 0x01, /* Radio Frequency */
|
||||
.bDeviceProtocol = 0x01, /* Bluetooth */
|
||||
.bMaxPacketSize0 = 64,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 2,
|
||||
.bConfigurationValue = 1,
|
||||
.bmAttributes = 0xc0,
|
||||
.bMaxPower = 0,
|
||||
.nif = ARRAY_SIZE(desc_iface_bluetooth),
|
||||
.ifs = desc_iface_bluetooth,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDesc desc_bluetooth = {
|
||||
.id = {
|
||||
.idVendor = 0x0a12,
|
||||
.idProduct = 0x0001,
|
||||
.bcdDevice = 0x1958,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = 0,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device_bluetooth,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static void usb_bt_fifo_reset(struct usb_hci_in_fifo_s *fifo)
|
||||
{
|
||||
fifo->dstart = 0;
|
||||
fifo->dlen = 0;
|
||||
fifo->dsize = DFIFO_LEN_MASK + 1;
|
||||
fifo->start = 0;
|
||||
fifo->len = 0;
|
||||
}
|
||||
|
||||
static void usb_bt_fifo_enqueue(struct usb_hci_in_fifo_s *fifo,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
int off = fifo->dstart + fifo->dlen;
|
||||
uint8_t *buf;
|
||||
|
||||
fifo->dlen += len;
|
||||
if (off <= DFIFO_LEN_MASK) {
|
||||
if (off + len > DFIFO_LEN_MASK + 1 &&
|
||||
(fifo->dsize = off + len) > (DFIFO_LEN_MASK + 1) * 2) {
|
||||
fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len);
|
||||
exit(-1);
|
||||
}
|
||||
buf = fifo->data + off;
|
||||
} else {
|
||||
if (fifo->dlen > fifo->dsize) {
|
||||
fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len);
|
||||
exit(-1);
|
||||
}
|
||||
buf = fifo->data + off - fifo->dsize;
|
||||
}
|
||||
|
||||
off = (fifo->start + fifo->len ++) & CFIFO_LEN_MASK;
|
||||
fifo->fifo[off].data = memcpy(buf, data, len);
|
||||
fifo->fifo[off].len = len;
|
||||
}
|
||||
|
||||
static inline int usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo,
|
||||
USBPacket *p)
|
||||
{
|
||||
int len;
|
||||
|
||||
if (likely(!fifo->len))
|
||||
return USB_RET_STALL;
|
||||
|
||||
len = MIN(p->iov.size, fifo->fifo[fifo->start].len);
|
||||
usb_packet_copy(p, fifo->fifo[fifo->start].data, len);
|
||||
if (len == p->iov.size) {
|
||||
fifo->fifo[fifo->start].len -= len;
|
||||
fifo->fifo[fifo->start].data += len;
|
||||
} else {
|
||||
fifo->start ++;
|
||||
fifo->start &= CFIFO_LEN_MASK;
|
||||
fifo->len --;
|
||||
}
|
||||
|
||||
fifo->dstart += len;
|
||||
fifo->dlen -= len;
|
||||
if (fifo->dstart >= fifo->dsize) {
|
||||
fifo->dstart = 0;
|
||||
fifo->dsize = DFIFO_LEN_MASK + 1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static inline void usb_bt_fifo_out_enqueue(struct USBBtState *s,
|
||||
struct usb_hci_out_fifo_s *fifo,
|
||||
void (*send)(struct HCIInfo *, const uint8_t *, int),
|
||||
int (*complete)(const uint8_t *, int),
|
||||
USBPacket *p)
|
||||
{
|
||||
usb_packet_copy(p, fifo->data + fifo->len, p->iov.size);
|
||||
fifo->len += p->iov.size;
|
||||
if (complete(fifo->data, fifo->len)) {
|
||||
send(s->hci, fifo->data, fifo->len);
|
||||
fifo->len = 0;
|
||||
}
|
||||
|
||||
/* TODO: do we need to loop? */
|
||||
}
|
||||
|
||||
static int usb_bt_hci_cmd_complete(const uint8_t *data, int len)
|
||||
{
|
||||
len -= HCI_COMMAND_HDR_SIZE;
|
||||
return len >= 0 &&
|
||||
len >= ((struct hci_command_hdr *) data)->plen;
|
||||
}
|
||||
|
||||
static int usb_bt_hci_acl_complete(const uint8_t *data, int len)
|
||||
{
|
||||
len -= HCI_ACL_HDR_SIZE;
|
||||
return len >= 0 &&
|
||||
len >= le16_to_cpu(((struct hci_acl_hdr *) data)->dlen);
|
||||
}
|
||||
|
||||
static int usb_bt_hci_sco_complete(const uint8_t *data, int len)
|
||||
{
|
||||
len -= HCI_SCO_HDR_SIZE;
|
||||
return len >= 0 &&
|
||||
len >= ((struct hci_sco_hdr *) data)->dlen;
|
||||
}
|
||||
|
||||
static void usb_bt_handle_reset(USBDevice *dev)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) dev->opaque;
|
||||
|
||||
usb_bt_fifo_reset(&s->evt);
|
||||
usb_bt_fifo_reset(&s->acl);
|
||||
usb_bt_fifo_reset(&s->sco);
|
||||
s->outcmd.len = 0;
|
||||
s->outacl.len = 0;
|
||||
s->outsco.len = 0;
|
||||
}
|
||||
|
||||
static int usb_bt_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) dev->opaque;
|
||||
int ret;
|
||||
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0) {
|
||||
switch (request) {
|
||||
case DeviceRequest | USB_REQ_GET_CONFIGURATION:
|
||||
s->config = 0;
|
||||
break;
|
||||
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
|
||||
s->config = 1;
|
||||
usb_bt_fifo_reset(&s->evt);
|
||||
usb_bt_fifo_reset(&s->acl);
|
||||
usb_bt_fifo_reset(&s->sco);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
switch (request) {
|
||||
case InterfaceRequest | USB_REQ_GET_STATUS:
|
||||
case EndpointRequest | USB_REQ_GET_STATUS:
|
||||
data[0] = 0x00;
|
||||
data[1] = 0x00;
|
||||
ret = 2;
|
||||
break;
|
||||
case InterfaceOutRequest | USB_REQ_CLEAR_FEATURE:
|
||||
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
|
||||
goto fail;
|
||||
case InterfaceOutRequest | USB_REQ_SET_FEATURE:
|
||||
case EndpointOutRequest | USB_REQ_SET_FEATURE:
|
||||
goto fail;
|
||||
break;
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE) << 8):
|
||||
if (s->config)
|
||||
usb_bt_fifo_out_enqueue(s, &s->outcmd, s->hci->cmd_send,
|
||||
usb_bt_hci_cmd_complete, p);
|
||||
break;
|
||||
default:
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int usb_bt_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) dev->opaque;
|
||||
int ret = 0;
|
||||
|
||||
if (!s->config)
|
||||
goto fail;
|
||||
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_IN:
|
||||
switch (p->ep->nr) {
|
||||
case USB_EVT_EP:
|
||||
ret = usb_bt_fifo_dequeue(&s->evt, p);
|
||||
break;
|
||||
|
||||
case USB_ACL_EP:
|
||||
ret = usb_bt_fifo_dequeue(&s->acl, p);
|
||||
break;
|
||||
|
||||
case USB_SCO_EP:
|
||||
ret = usb_bt_fifo_dequeue(&s->sco, p);
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_TOKEN_OUT:
|
||||
switch (p->ep->nr) {
|
||||
case USB_ACL_EP:
|
||||
usb_bt_fifo_out_enqueue(s, &s->outacl, s->hci->acl_send,
|
||||
usb_bt_hci_acl_complete, p);
|
||||
break;
|
||||
|
||||
case USB_SCO_EP:
|
||||
usb_bt_fifo_out_enqueue(s, &s->outsco, s->hci->sco_send,
|
||||
usb_bt_hci_sco_complete, p);
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_bt_out_hci_packet_event(void *opaque,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) opaque;
|
||||
|
||||
usb_bt_fifo_enqueue(&s->evt, data, len);
|
||||
}
|
||||
|
||||
static void usb_bt_out_hci_packet_acl(void *opaque,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) opaque;
|
||||
|
||||
usb_bt_fifo_enqueue(&s->acl, data, len);
|
||||
}
|
||||
|
||||
static void usb_bt_handle_destroy(USBDevice *dev)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) dev->opaque;
|
||||
|
||||
s->hci->opaque = NULL;
|
||||
s->hci->evt_recv = NULL;
|
||||
s->hci->acl_recv = NULL;
|
||||
}
|
||||
|
||||
static int usb_bt_initfn(USBDevice *dev)
|
||||
{
|
||||
usb_desc_init(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
USBDevice *usb_bt_init(USBBus *bus, HCIInfo *hci)
|
||||
{
|
||||
USBDevice *dev;
|
||||
struct USBBtState *s;
|
||||
|
||||
if (!hci)
|
||||
return NULL;
|
||||
dev = usb_create_simple(bus, "usb-bt-dongle");
|
||||
if (!dev) {
|
||||
return NULL;
|
||||
}
|
||||
s = DO_UPCAST(struct USBBtState, dev, dev);
|
||||
s->dev.opaque = s;
|
||||
|
||||
s->hci = hci;
|
||||
s->hci->opaque = s;
|
||||
s->hci->evt_recv = usb_bt_out_hci_packet_event;
|
||||
s->hci->acl_recv = usb_bt_out_hci_packet_acl;
|
||||
|
||||
usb_bt_handle_reset(&s->dev);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_usb_bt = {
|
||||
.name = "usb-bt",
|
||||
.unmigratable = 1,
|
||||
};
|
||||
|
||||
static void usb_bt_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->init = usb_bt_initfn;
|
||||
uc->product_desc = "QEMU BT dongle";
|
||||
uc->usb_desc = &desc_bluetooth;
|
||||
uc->handle_reset = usb_bt_handle_reset;
|
||||
uc->handle_control = usb_bt_handle_control;
|
||||
uc->handle_data = usb_bt_handle_data;
|
||||
uc->handle_destroy = usb_bt_handle_destroy;
|
||||
dc->vmsd = &vmstate_usb_bt;
|
||||
}
|
||||
|
||||
static TypeInfo bt_info = {
|
||||
.name = "usb-bt-dongle",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(struct USBBtState),
|
||||
.class_init = usb_bt_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_bt_register_types(void)
|
||||
{
|
||||
type_register_static(&bt_info);
|
||||
}
|
||||
|
||||
type_init(usb_bt_register_types)
|
638
hw/usb/dev-hid.c
Normal file
638
hw/usb/dev-hid.c
Normal file
|
@ -0,0 +1,638 @@
|
|||
/*
|
||||
* QEMU USB HID devices
|
||||
*
|
||||
* Copyright (c) 2005 Fabrice Bellard
|
||||
* Copyright (c) 2007 OpenMoko, Inc. (andrew@openedhand.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "hw/hw.h"
|
||||
#include "console.h"
|
||||
#include "hw/usb.h"
|
||||
#include "hw/usb/desc.h"
|
||||
#include "qemu-timer.h"
|
||||
#include "hw/hid.h"
|
||||
|
||||
/* HID interface requests */
|
||||
#define GET_REPORT 0xa101
|
||||
#define GET_IDLE 0xa102
|
||||
#define GET_PROTOCOL 0xa103
|
||||
#define SET_REPORT 0x2109
|
||||
#define SET_IDLE 0x210a
|
||||
#define SET_PROTOCOL 0x210b
|
||||
|
||||
/* HID descriptor types */
|
||||
#define USB_DT_HID 0x21
|
||||
#define USB_DT_REPORT 0x22
|
||||
#define USB_DT_PHY 0x23
|
||||
|
||||
typedef struct USBHIDState {
|
||||
USBDevice dev;
|
||||
USBEndpoint *intr;
|
||||
HIDState hid;
|
||||
} USBHIDState;
|
||||
|
||||
enum {
|
||||
STR_MANUFACTURER = 1,
|
||||
STR_PRODUCT_MOUSE,
|
||||
STR_PRODUCT_TABLET,
|
||||
STR_PRODUCT_KEYBOARD,
|
||||
STR_SERIALNUMBER,
|
||||
STR_CONFIG_MOUSE,
|
||||
STR_CONFIG_TABLET,
|
||||
STR_CONFIG_KEYBOARD,
|
||||
};
|
||||
|
||||
static const USBDescStrings desc_strings = {
|
||||
[STR_MANUFACTURER] = "QEMU " QEMU_VERSION,
|
||||
[STR_PRODUCT_MOUSE] = "QEMU USB Mouse",
|
||||
[STR_PRODUCT_TABLET] = "QEMU USB Tablet",
|
||||
[STR_PRODUCT_KEYBOARD] = "QEMU USB Keyboard",
|
||||
[STR_SERIALNUMBER] = "42", /* == remote wakeup works */
|
||||
[STR_CONFIG_MOUSE] = "HID Mouse",
|
||||
[STR_CONFIG_TABLET] = "HID Tablet",
|
||||
[STR_CONFIG_KEYBOARD] = "HID Keyboard",
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_mouse = {
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_HID,
|
||||
.bInterfaceSubClass = 0x01, /* boot */
|
||||
.bInterfaceProtocol = 0x02,
|
||||
.ndesc = 1,
|
||||
.descs = (USBDescOther[]) {
|
||||
{
|
||||
/* HID descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x09, /* u8 bLength */
|
||||
USB_DT_HID, /* u8 bDescriptorType */
|
||||
0x01, 0x00, /* u16 HID_class */
|
||||
0x00, /* u8 country_code */
|
||||
0x01, /* u8 num_descriptors */
|
||||
USB_DT_REPORT, /* u8 type: Report */
|
||||
52, 0, /* u16 len */
|
||||
},
|
||||
},
|
||||
},
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | 0x01,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = 4,
|
||||
.bInterval = 0x0a,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_tablet = {
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_HID,
|
||||
.bInterfaceProtocol = 0x02,
|
||||
.ndesc = 1,
|
||||
.descs = (USBDescOther[]) {
|
||||
{
|
||||
/* HID descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x09, /* u8 bLength */
|
||||
USB_DT_HID, /* u8 bDescriptorType */
|
||||
0x01, 0x00, /* u16 HID_class */
|
||||
0x00, /* u8 country_code */
|
||||
0x01, /* u8 num_descriptors */
|
||||
USB_DT_REPORT, /* u8 type: Report */
|
||||
74, 0, /* u16 len */
|
||||
},
|
||||
},
|
||||
},
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | 0x01,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = 8,
|
||||
.bInterval = 0x0a,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_keyboard = {
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_HID,
|
||||
.bInterfaceSubClass = 0x01, /* boot */
|
||||
.bInterfaceProtocol = 0x01, /* keyboard */
|
||||
.ndesc = 1,
|
||||
.descs = (USBDescOther[]) {
|
||||
{
|
||||
/* HID descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x09, /* u8 bLength */
|
||||
USB_DT_HID, /* u8 bDescriptorType */
|
||||
0x11, 0x01, /* u16 HID_class */
|
||||
0x00, /* u8 country_code */
|
||||
0x01, /* u8 num_descriptors */
|
||||
USB_DT_REPORT, /* u8 type: Report */
|
||||
0x3f, 0, /* u16 len */
|
||||
},
|
||||
},
|
||||
},
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | 0x01,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = 8,
|
||||
.bInterval = 0x0a,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_mouse = {
|
||||
.bcdUSB = 0x0100,
|
||||
.bMaxPacketSize0 = 8,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 1,
|
||||
.bConfigurationValue = 1,
|
||||
.iConfiguration = STR_CONFIG_MOUSE,
|
||||
.bmAttributes = 0xa0,
|
||||
.bMaxPower = 50,
|
||||
.nif = 1,
|
||||
.ifs = &desc_iface_mouse,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_tablet = {
|
||||
.bcdUSB = 0x0100,
|
||||
.bMaxPacketSize0 = 8,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 1,
|
||||
.bConfigurationValue = 1,
|
||||
.iConfiguration = STR_CONFIG_TABLET,
|
||||
.bmAttributes = 0xa0,
|
||||
.bMaxPower = 50,
|
||||
.nif = 1,
|
||||
.ifs = &desc_iface_tablet,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_keyboard = {
|
||||
.bcdUSB = 0x0100,
|
||||
.bMaxPacketSize0 = 8,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 1,
|
||||
.bConfigurationValue = 1,
|
||||
.iConfiguration = STR_CONFIG_KEYBOARD,
|
||||
.bmAttributes = 0xa0,
|
||||
.bMaxPower = 50,
|
||||
.nif = 1,
|
||||
.ifs = &desc_iface_keyboard,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDesc desc_mouse = {
|
||||
.id = {
|
||||
.idVendor = 0x0627,
|
||||
.idProduct = 0x0001,
|
||||
.bcdDevice = 0,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = STR_PRODUCT_MOUSE,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device_mouse,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static const USBDesc desc_tablet = {
|
||||
.id = {
|
||||
.idVendor = 0x0627,
|
||||
.idProduct = 0x0001,
|
||||
.bcdDevice = 0,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = STR_PRODUCT_TABLET,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device_tablet,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static const USBDesc desc_keyboard = {
|
||||
.id = {
|
||||
.idVendor = 0x0627,
|
||||
.idProduct = 0x0001,
|
||||
.bcdDevice = 0,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = STR_PRODUCT_KEYBOARD,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device_keyboard,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static const uint8_t qemu_mouse_hid_report_descriptor[] = {
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x02, /* Usage (Mouse) */
|
||||
0xa1, 0x01, /* Collection (Application) */
|
||||
0x09, 0x01, /* Usage (Pointer) */
|
||||
0xa1, 0x00, /* Collection (Physical) */
|
||||
0x05, 0x09, /* Usage Page (Button) */
|
||||
0x19, 0x01, /* Usage Minimum (1) */
|
||||
0x29, 0x03, /* Usage Maximum (3) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x95, 0x03, /* Report Count (3) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x81, 0x02, /* Input (Data, Variable, Absolute) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x05, /* Report Size (5) */
|
||||
0x81, 0x01, /* Input (Constant) */
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x30, /* Usage (X) */
|
||||
0x09, 0x31, /* Usage (Y) */
|
||||
0x09, 0x38, /* Usage (Wheel) */
|
||||
0x15, 0x81, /* Logical Minimum (-0x7f) */
|
||||
0x25, 0x7f, /* Logical Maximum (0x7f) */
|
||||
0x75, 0x08, /* Report Size (8) */
|
||||
0x95, 0x03, /* Report Count (3) */
|
||||
0x81, 0x06, /* Input (Data, Variable, Relative) */
|
||||
0xc0, /* End Collection */
|
||||
0xc0, /* End Collection */
|
||||
};
|
||||
|
||||
static const uint8_t qemu_tablet_hid_report_descriptor[] = {
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x01, /* Usage (Pointer) */
|
||||
0xa1, 0x01, /* Collection (Application) */
|
||||
0x09, 0x01, /* Usage (Pointer) */
|
||||
0xa1, 0x00, /* Collection (Physical) */
|
||||
0x05, 0x09, /* Usage Page (Button) */
|
||||
0x19, 0x01, /* Usage Minimum (1) */
|
||||
0x29, 0x03, /* Usage Maximum (3) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x95, 0x03, /* Report Count (3) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x81, 0x02, /* Input (Data, Variable, Absolute) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x05, /* Report Size (5) */
|
||||
0x81, 0x01, /* Input (Constant) */
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x30, /* Usage (X) */
|
||||
0x09, 0x31, /* Usage (Y) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x26, 0xff, 0x7f, /* Logical Maximum (0x7fff) */
|
||||
0x35, 0x00, /* Physical Minimum (0) */
|
||||
0x46, 0xff, 0x7f, /* Physical Maximum (0x7fff) */
|
||||
0x75, 0x10, /* Report Size (16) */
|
||||
0x95, 0x02, /* Report Count (2) */
|
||||
0x81, 0x02, /* Input (Data, Variable, Absolute) */
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x38, /* Usage (Wheel) */
|
||||
0x15, 0x81, /* Logical Minimum (-0x7f) */
|
||||
0x25, 0x7f, /* Logical Maximum (0x7f) */
|
||||
0x35, 0x00, /* Physical Minimum (same as logical) */
|
||||
0x45, 0x00, /* Physical Maximum (same as logical) */
|
||||
0x75, 0x08, /* Report Size (8) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x81, 0x06, /* Input (Data, Variable, Relative) */
|
||||
0xc0, /* End Collection */
|
||||
0xc0, /* End Collection */
|
||||
};
|
||||
|
||||
static const uint8_t qemu_keyboard_hid_report_descriptor[] = {
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x06, /* Usage (Keyboard) */
|
||||
0xa1, 0x01, /* Collection (Application) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x95, 0x08, /* Report Count (8) */
|
||||
0x05, 0x07, /* Usage Page (Key Codes) */
|
||||
0x19, 0xe0, /* Usage Minimum (224) */
|
||||
0x29, 0xe7, /* Usage Maximum (231) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x81, 0x02, /* Input (Data, Variable, Absolute) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x08, /* Report Size (8) */
|
||||
0x81, 0x01, /* Input (Constant) */
|
||||
0x95, 0x05, /* Report Count (5) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x05, 0x08, /* Usage Page (LEDs) */
|
||||
0x19, 0x01, /* Usage Minimum (1) */
|
||||
0x29, 0x05, /* Usage Maximum (5) */
|
||||
0x91, 0x02, /* Output (Data, Variable, Absolute) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x03, /* Report Size (3) */
|
||||
0x91, 0x01, /* Output (Constant) */
|
||||
0x95, 0x06, /* Report Count (6) */
|
||||
0x75, 0x08, /* Report Size (8) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0xff, /* Logical Maximum (255) */
|
||||
0x05, 0x07, /* Usage Page (Key Codes) */
|
||||
0x19, 0x00, /* Usage Minimum (0) */
|
||||
0x29, 0xff, /* Usage Maximum (255) */
|
||||
0x81, 0x00, /* Input (Data, Array) */
|
||||
0xc0, /* End Collection */
|
||||
};
|
||||
|
||||
static void usb_hid_changed(HIDState *hs)
|
||||
{
|
||||
USBHIDState *us = container_of(hs, USBHIDState, hid);
|
||||
|
||||
usb_wakeup(us->intr);
|
||||
}
|
||||
|
||||
static void usb_hid_handle_reset(USBDevice *dev)
|
||||
{
|
||||
USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev);
|
||||
|
||||
hid_reset(&us->hid);
|
||||
}
|
||||
|
||||
static int usb_hid_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev);
|
||||
HIDState *hs = &us->hid;
|
||||
int ret;
|
||||
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
switch (request) {
|
||||
/* hid specific requests */
|
||||
case InterfaceRequest | USB_REQ_GET_DESCRIPTOR:
|
||||
switch (value >> 8) {
|
||||
case 0x22:
|
||||
if (hs->kind == HID_MOUSE) {
|
||||
memcpy(data, qemu_mouse_hid_report_descriptor,
|
||||
sizeof(qemu_mouse_hid_report_descriptor));
|
||||
ret = sizeof(qemu_mouse_hid_report_descriptor);
|
||||
} else if (hs->kind == HID_TABLET) {
|
||||
memcpy(data, qemu_tablet_hid_report_descriptor,
|
||||
sizeof(qemu_tablet_hid_report_descriptor));
|
||||
ret = sizeof(qemu_tablet_hid_report_descriptor);
|
||||
} else if (hs->kind == HID_KEYBOARD) {
|
||||
memcpy(data, qemu_keyboard_hid_report_descriptor,
|
||||
sizeof(qemu_keyboard_hid_report_descriptor));
|
||||
ret = sizeof(qemu_keyboard_hid_report_descriptor);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
case GET_REPORT:
|
||||
if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) {
|
||||
ret = hid_pointer_poll(hs, data, length);
|
||||
} else if (hs->kind == HID_KEYBOARD) {
|
||||
ret = hid_keyboard_poll(hs, data, length);
|
||||
}
|
||||
break;
|
||||
case SET_REPORT:
|
||||
if (hs->kind == HID_KEYBOARD) {
|
||||
ret = hid_keyboard_write(hs, data, length);
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
case GET_PROTOCOL:
|
||||
if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) {
|
||||
goto fail;
|
||||
}
|
||||
ret = 1;
|
||||
data[0] = hs->protocol;
|
||||
break;
|
||||
case SET_PROTOCOL:
|
||||
if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) {
|
||||
goto fail;
|
||||
}
|
||||
ret = 0;
|
||||
hs->protocol = value;
|
||||
break;
|
||||
case GET_IDLE:
|
||||
ret = 1;
|
||||
data[0] = hs->idle;
|
||||
break;
|
||||
case SET_IDLE:
|
||||
hs->idle = (uint8_t) (value >> 8);
|
||||
hid_set_next_idle(hs, qemu_get_clock_ns(vm_clock));
|
||||
if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) {
|
||||
hid_pointer_activate(hs);
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int usb_hid_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev);
|
||||
HIDState *hs = &us->hid;
|
||||
uint8_t buf[p->iov.size];
|
||||
int ret = 0;
|
||||
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_IN:
|
||||
if (p->ep->nr == 1) {
|
||||
int64_t curtime = qemu_get_clock_ns(vm_clock);
|
||||
if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) {
|
||||
hid_pointer_activate(hs);
|
||||
}
|
||||
if (!hid_has_events(hs) &&
|
||||
(!hs->idle || hs->next_idle_clock - curtime > 0)) {
|
||||
return USB_RET_NAK;
|
||||
}
|
||||
hid_set_next_idle(hs, curtime);
|
||||
if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) {
|
||||
ret = hid_pointer_poll(hs, buf, p->iov.size);
|
||||
} else if (hs->kind == HID_KEYBOARD) {
|
||||
ret = hid_keyboard_poll(hs, buf, p->iov.size);
|
||||
}
|
||||
usb_packet_copy(p, buf, ret);
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
case USB_TOKEN_OUT:
|
||||
default:
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_hid_handle_destroy(USBDevice *dev)
|
||||
{
|
||||
USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev);
|
||||
|
||||
hid_free(&us->hid);
|
||||
}
|
||||
|
||||
static int usb_hid_initfn(USBDevice *dev, int kind)
|
||||
{
|
||||
USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev);
|
||||
|
||||
usb_desc_init(dev);
|
||||
us->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
|
||||
hid_init(&us->hid, kind, usb_hid_changed);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_tablet_initfn(USBDevice *dev)
|
||||
{
|
||||
return usb_hid_initfn(dev, HID_TABLET);
|
||||
}
|
||||
|
||||
static int usb_mouse_initfn(USBDevice *dev)
|
||||
{
|
||||
return usb_hid_initfn(dev, HID_MOUSE);
|
||||
}
|
||||
|
||||
static int usb_keyboard_initfn(USBDevice *dev)
|
||||
{
|
||||
return usb_hid_initfn(dev, HID_KEYBOARD);
|
||||
}
|
||||
|
||||
static int usb_ptr_post_load(void *opaque, int version_id)
|
||||
{
|
||||
USBHIDState *s = opaque;
|
||||
|
||||
if (s->dev.remote_wakeup) {
|
||||
hid_pointer_activate(&s->hid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_usb_ptr = {
|
||||
.name = "usb-ptr",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.post_load = usb_ptr_post_load,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_USB_DEVICE(dev, USBHIDState),
|
||||
VMSTATE_HID_POINTER_DEVICE(hid, USBHIDState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_usb_kbd = {
|
||||
.name = "usb-kbd",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_USB_DEVICE(dev, USBHIDState),
|
||||
VMSTATE_HID_KEYBOARD_DEVICE(hid, USBHIDState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void usb_hid_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->handle_reset = usb_hid_handle_reset;
|
||||
uc->handle_control = usb_hid_handle_control;
|
||||
uc->handle_data = usb_hid_handle_data;
|
||||
uc->handle_destroy = usb_hid_handle_destroy;
|
||||
}
|
||||
|
||||
static void usb_tablet_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
usb_hid_class_initfn(klass, data);
|
||||
uc->init = usb_tablet_initfn;
|
||||
uc->product_desc = "QEMU USB Tablet";
|
||||
uc->usb_desc = &desc_tablet;
|
||||
dc->vmsd = &vmstate_usb_ptr;
|
||||
}
|
||||
|
||||
static TypeInfo usb_tablet_info = {
|
||||
.name = "usb-tablet",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBHIDState),
|
||||
.class_init = usb_tablet_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_mouse_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
usb_hid_class_initfn(klass, data);
|
||||
uc->init = usb_mouse_initfn;
|
||||
uc->product_desc = "QEMU USB Mouse";
|
||||
uc->usb_desc = &desc_mouse;
|
||||
dc->vmsd = &vmstate_usb_ptr;
|
||||
}
|
||||
|
||||
static TypeInfo usb_mouse_info = {
|
||||
.name = "usb-mouse",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBHIDState),
|
||||
.class_init = usb_mouse_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_keyboard_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
usb_hid_class_initfn(klass, data);
|
||||
uc->init = usb_keyboard_initfn;
|
||||
uc->product_desc = "QEMU USB Keyboard";
|
||||
uc->usb_desc = &desc_keyboard;
|
||||
dc->vmsd = &vmstate_usb_kbd;
|
||||
}
|
||||
|
||||
static TypeInfo usb_keyboard_info = {
|
||||
.name = "usb-kbd",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBHIDState),
|
||||
.class_init = usb_keyboard_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_hid_register_types(void)
|
||||
{
|
||||
type_register_static(&usb_tablet_info);
|
||||
usb_legacy_register("usb-tablet", "tablet", NULL);
|
||||
type_register_static(&usb_mouse_info);
|
||||
usb_legacy_register("usb-mouse", "mouse", NULL);
|
||||
type_register_static(&usb_keyboard_info);
|
||||
usb_legacy_register("usb-kbd", "keyboard", NULL);
|
||||
}
|
||||
|
||||
type_init(usb_hid_register_types)
|
549
hw/usb/dev-hub.c
Normal file
549
hw/usb/dev-hub.c
Normal file
|
@ -0,0 +1,549 @@
|
|||
/*
|
||||
* QEMU USB HUB emulation
|
||||
*
|
||||
* Copyright (c) 2005 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "qemu-common.h"
|
||||
#include "hw/usb.h"
|
||||
#include "hw/usb/desc.h"
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#define NUM_PORTS 8
|
||||
|
||||
typedef struct USBHubPort {
|
||||
USBPort port;
|
||||
uint16_t wPortStatus;
|
||||
uint16_t wPortChange;
|
||||
} USBHubPort;
|
||||
|
||||
typedef struct USBHubState {
|
||||
USBDevice dev;
|
||||
USBEndpoint *intr;
|
||||
USBHubPort ports[NUM_PORTS];
|
||||
} USBHubState;
|
||||
|
||||
#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE)
|
||||
#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE)
|
||||
#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR)
|
||||
#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS)
|
||||
#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS)
|
||||
#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE)
|
||||
#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE)
|
||||
|
||||
#define PORT_STAT_CONNECTION 0x0001
|
||||
#define PORT_STAT_ENABLE 0x0002
|
||||
#define PORT_STAT_SUSPEND 0x0004
|
||||
#define PORT_STAT_OVERCURRENT 0x0008
|
||||
#define PORT_STAT_RESET 0x0010
|
||||
#define PORT_STAT_POWER 0x0100
|
||||
#define PORT_STAT_LOW_SPEED 0x0200
|
||||
#define PORT_STAT_HIGH_SPEED 0x0400
|
||||
#define PORT_STAT_TEST 0x0800
|
||||
#define PORT_STAT_INDICATOR 0x1000
|
||||
|
||||
#define PORT_STAT_C_CONNECTION 0x0001
|
||||
#define PORT_STAT_C_ENABLE 0x0002
|
||||
#define PORT_STAT_C_SUSPEND 0x0004
|
||||
#define PORT_STAT_C_OVERCURRENT 0x0008
|
||||
#define PORT_STAT_C_RESET 0x0010
|
||||
|
||||
#define PORT_CONNECTION 0
|
||||
#define PORT_ENABLE 1
|
||||
#define PORT_SUSPEND 2
|
||||
#define PORT_OVERCURRENT 3
|
||||
#define PORT_RESET 4
|
||||
#define PORT_POWER 8
|
||||
#define PORT_LOWSPEED 9
|
||||
#define PORT_HIGHSPEED 10
|
||||
#define PORT_C_CONNECTION 16
|
||||
#define PORT_C_ENABLE 17
|
||||
#define PORT_C_SUSPEND 18
|
||||
#define PORT_C_OVERCURRENT 19
|
||||
#define PORT_C_RESET 20
|
||||
#define PORT_TEST 21
|
||||
#define PORT_INDICATOR 22
|
||||
|
||||
/* same as Linux kernel root hubs */
|
||||
|
||||
enum {
|
||||
STR_MANUFACTURER = 1,
|
||||
STR_PRODUCT,
|
||||
STR_SERIALNUMBER,
|
||||
};
|
||||
|
||||
static const USBDescStrings desc_strings = {
|
||||
[STR_MANUFACTURER] = "QEMU " QEMU_VERSION,
|
||||
[STR_PRODUCT] = "QEMU USB Hub",
|
||||
[STR_SERIALNUMBER] = "314159",
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_hub = {
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_HUB,
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | 0x01,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = 1 + (NUM_PORTS + 7) / 8,
|
||||
.bInterval = 0xff,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_hub = {
|
||||
.bcdUSB = 0x0110,
|
||||
.bDeviceClass = USB_CLASS_HUB,
|
||||
.bMaxPacketSize0 = 8,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 1,
|
||||
.bConfigurationValue = 1,
|
||||
.bmAttributes = 0xe0,
|
||||
.nif = 1,
|
||||
.ifs = &desc_iface_hub,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDesc desc_hub = {
|
||||
.id = {
|
||||
.idVendor = 0x0409,
|
||||
.idProduct = 0x55aa,
|
||||
.bcdDevice = 0x0101,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = STR_PRODUCT,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device_hub,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static const uint8_t qemu_hub_hub_descriptor[] =
|
||||
{
|
||||
0x00, /* u8 bLength; patched in later */
|
||||
0x29, /* u8 bDescriptorType; Hub-descriptor */
|
||||
0x00, /* u8 bNbrPorts; (patched later) */
|
||||
0x0a, /* u16 wHubCharacteristics; */
|
||||
0x00, /* (per-port OC, no power switching) */
|
||||
0x01, /* u8 bPwrOn2pwrGood; 2ms */
|
||||
0x00 /* u8 bHubContrCurrent; 0 mA */
|
||||
|
||||
/* DeviceRemovable and PortPwrCtrlMask patched in later */
|
||||
};
|
||||
|
||||
static void usb_hub_attach(USBPort *port1)
|
||||
{
|
||||
USBHubState *s = port1->opaque;
|
||||
USBHubPort *port = &s->ports[port1->index];
|
||||
|
||||
port->wPortStatus |= PORT_STAT_CONNECTION;
|
||||
port->wPortChange |= PORT_STAT_C_CONNECTION;
|
||||
if (port->port.dev->speed == USB_SPEED_LOW) {
|
||||
port->wPortStatus |= PORT_STAT_LOW_SPEED;
|
||||
} else {
|
||||
port->wPortStatus &= ~PORT_STAT_LOW_SPEED;
|
||||
}
|
||||
usb_wakeup(s->intr);
|
||||
}
|
||||
|
||||
static void usb_hub_detach(USBPort *port1)
|
||||
{
|
||||
USBHubState *s = port1->opaque;
|
||||
USBHubPort *port = &s->ports[port1->index];
|
||||
|
||||
usb_wakeup(s->intr);
|
||||
|
||||
/* Let upstream know the device on this port is gone */
|
||||
s->dev.port->ops->child_detach(s->dev.port, port1->dev);
|
||||
|
||||
port->wPortStatus &= ~PORT_STAT_CONNECTION;
|
||||
port->wPortChange |= PORT_STAT_C_CONNECTION;
|
||||
if (port->wPortStatus & PORT_STAT_ENABLE) {
|
||||
port->wPortStatus &= ~PORT_STAT_ENABLE;
|
||||
port->wPortChange |= PORT_STAT_C_ENABLE;
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_hub_child_detach(USBPort *port1, USBDevice *child)
|
||||
{
|
||||
USBHubState *s = port1->opaque;
|
||||
|
||||
/* Pass along upstream */
|
||||
s->dev.port->ops->child_detach(s->dev.port, child);
|
||||
}
|
||||
|
||||
static void usb_hub_wakeup(USBPort *port1)
|
||||
{
|
||||
USBHubState *s = port1->opaque;
|
||||
USBHubPort *port = &s->ports[port1->index];
|
||||
|
||||
if (port->wPortStatus & PORT_STAT_SUSPEND) {
|
||||
port->wPortChange |= PORT_STAT_C_SUSPEND;
|
||||
usb_wakeup(s->intr);
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_hub_complete(USBPort *port, USBPacket *packet)
|
||||
{
|
||||
USBHubState *s = port->opaque;
|
||||
|
||||
/*
|
||||
* Just pass it along upstream for now.
|
||||
*
|
||||
* If we ever implement usb 2.0 split transactions this will
|
||||
* become a little more complicated ...
|
||||
*
|
||||
* Can't use usb_packet_complete() here because packet->owner is
|
||||
* cleared already, go call the ->complete() callback directly
|
||||
* instead.
|
||||
*/
|
||||
s->dev.port->ops->complete(s->dev.port, packet);
|
||||
}
|
||||
|
||||
static USBDevice *usb_hub_find_device(USBDevice *dev, uint8_t addr)
|
||||
{
|
||||
USBHubState *s = DO_UPCAST(USBHubState, dev, dev);
|
||||
USBHubPort *port;
|
||||
USBDevice *downstream;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_PORTS; i++) {
|
||||
port = &s->ports[i];
|
||||
if (!(port->wPortStatus & PORT_STAT_ENABLE)) {
|
||||
continue;
|
||||
}
|
||||
downstream = usb_find_device(&port->port, addr);
|
||||
if (downstream != NULL) {
|
||||
return downstream;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void usb_hub_handle_reset(USBDevice *dev)
|
||||
{
|
||||
USBHubState *s = DO_UPCAST(USBHubState, dev, dev);
|
||||
USBHubPort *port;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_PORTS; i++) {
|
||||
port = s->ports + i;
|
||||
port->wPortStatus = PORT_STAT_POWER;
|
||||
port->wPortChange = 0;
|
||||
if (port->port.dev && port->port.dev->attached) {
|
||||
port->wPortStatus |= PORT_STAT_CONNECTION;
|
||||
port->wPortChange |= PORT_STAT_C_CONNECTION;
|
||||
if (port->port.dev->speed == USB_SPEED_LOW) {
|
||||
port->wPortStatus |= PORT_STAT_LOW_SPEED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int usb_hub_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
USBHubState *s = (USBHubState *)dev;
|
||||
int ret;
|
||||
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch(request) {
|
||||
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
|
||||
if (value == 0 && index != 0x81) { /* clear ep halt */
|
||||
goto fail;
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
/* usb specific requests */
|
||||
case GetHubStatus:
|
||||
data[0] = 0;
|
||||
data[1] = 0;
|
||||
data[2] = 0;
|
||||
data[3] = 0;
|
||||
ret = 4;
|
||||
break;
|
||||
case GetPortStatus:
|
||||
{
|
||||
unsigned int n = index - 1;
|
||||
USBHubPort *port;
|
||||
if (n >= NUM_PORTS) {
|
||||
goto fail;
|
||||
}
|
||||
port = &s->ports[n];
|
||||
data[0] = port->wPortStatus;
|
||||
data[1] = port->wPortStatus >> 8;
|
||||
data[2] = port->wPortChange;
|
||||
data[3] = port->wPortChange >> 8;
|
||||
ret = 4;
|
||||
}
|
||||
break;
|
||||
case SetHubFeature:
|
||||
case ClearHubFeature:
|
||||
if (value == 0 || value == 1) {
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
case SetPortFeature:
|
||||
{
|
||||
unsigned int n = index - 1;
|
||||
USBHubPort *port;
|
||||
USBDevice *dev;
|
||||
if (n >= NUM_PORTS) {
|
||||
goto fail;
|
||||
}
|
||||
port = &s->ports[n];
|
||||
dev = port->port.dev;
|
||||
switch(value) {
|
||||
case PORT_SUSPEND:
|
||||
port->wPortStatus |= PORT_STAT_SUSPEND;
|
||||
break;
|
||||
case PORT_RESET:
|
||||
if (dev && dev->attached) {
|
||||
usb_device_reset(dev);
|
||||
port->wPortChange |= PORT_STAT_C_RESET;
|
||||
/* set enable bit */
|
||||
port->wPortStatus |= PORT_STAT_ENABLE;
|
||||
}
|
||||
break;
|
||||
case PORT_POWER:
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
case ClearPortFeature:
|
||||
{
|
||||
unsigned int n = index - 1;
|
||||
USBHubPort *port;
|
||||
|
||||
if (n >= NUM_PORTS) {
|
||||
goto fail;
|
||||
}
|
||||
port = &s->ports[n];
|
||||
switch(value) {
|
||||
case PORT_ENABLE:
|
||||
port->wPortStatus &= ~PORT_STAT_ENABLE;
|
||||
break;
|
||||
case PORT_C_ENABLE:
|
||||
port->wPortChange &= ~PORT_STAT_C_ENABLE;
|
||||
break;
|
||||
case PORT_SUSPEND:
|
||||
port->wPortStatus &= ~PORT_STAT_SUSPEND;
|
||||
break;
|
||||
case PORT_C_SUSPEND:
|
||||
port->wPortChange &= ~PORT_STAT_C_SUSPEND;
|
||||
break;
|
||||
case PORT_C_CONNECTION:
|
||||
port->wPortChange &= ~PORT_STAT_C_CONNECTION;
|
||||
break;
|
||||
case PORT_C_OVERCURRENT:
|
||||
port->wPortChange &= ~PORT_STAT_C_OVERCURRENT;
|
||||
break;
|
||||
case PORT_C_RESET:
|
||||
port->wPortChange &= ~PORT_STAT_C_RESET;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
case GetHubDescriptor:
|
||||
{
|
||||
unsigned int n, limit, var_hub_size = 0;
|
||||
memcpy(data, qemu_hub_hub_descriptor,
|
||||
sizeof(qemu_hub_hub_descriptor));
|
||||
data[2] = NUM_PORTS;
|
||||
|
||||
/* fill DeviceRemovable bits */
|
||||
limit = ((NUM_PORTS + 1 + 7) / 8) + 7;
|
||||
for (n = 7; n < limit; n++) {
|
||||
data[n] = 0x00;
|
||||
var_hub_size++;
|
||||
}
|
||||
|
||||
/* fill PortPwrCtrlMask bits */
|
||||
limit = limit + ((NUM_PORTS + 7) / 8);
|
||||
for (;n < limit; n++) {
|
||||
data[n] = 0xff;
|
||||
var_hub_size++;
|
||||
}
|
||||
|
||||
ret = sizeof(qemu_hub_hub_descriptor) + var_hub_size;
|
||||
data[0] = ret;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int usb_hub_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBHubState *s = (USBHubState *)dev;
|
||||
int ret;
|
||||
|
||||
switch(p->pid) {
|
||||
case USB_TOKEN_IN:
|
||||
if (p->ep->nr == 1) {
|
||||
USBHubPort *port;
|
||||
unsigned int status;
|
||||
uint8_t buf[4];
|
||||
int i, n;
|
||||
n = (NUM_PORTS + 1 + 7) / 8;
|
||||
if (p->iov.size == 1) { /* FreeBSD workaround */
|
||||
n = 1;
|
||||
} else if (n > p->iov.size) {
|
||||
return USB_RET_BABBLE;
|
||||
}
|
||||
status = 0;
|
||||
for(i = 0; i < NUM_PORTS; i++) {
|
||||
port = &s->ports[i];
|
||||
if (port->wPortChange)
|
||||
status |= (1 << (i + 1));
|
||||
}
|
||||
if (status != 0) {
|
||||
for(i = 0; i < n; i++) {
|
||||
buf[i] = status >> (8 * i);
|
||||
}
|
||||
usb_packet_copy(p, buf, n);
|
||||
ret = n;
|
||||
} else {
|
||||
ret = USB_RET_NAK; /* usb11 11.13.1 */
|
||||
}
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
case USB_TOKEN_OUT:
|
||||
default:
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_hub_handle_destroy(USBDevice *dev)
|
||||
{
|
||||
USBHubState *s = (USBHubState *)dev;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_PORTS; i++) {
|
||||
usb_unregister_port(usb_bus_from_device(dev),
|
||||
&s->ports[i].port);
|
||||
}
|
||||
}
|
||||
|
||||
static USBPortOps usb_hub_port_ops = {
|
||||
.attach = usb_hub_attach,
|
||||
.detach = usb_hub_detach,
|
||||
.child_detach = usb_hub_child_detach,
|
||||
.wakeup = usb_hub_wakeup,
|
||||
.complete = usb_hub_complete,
|
||||
};
|
||||
|
||||
static int usb_hub_initfn(USBDevice *dev)
|
||||
{
|
||||
USBHubState *s = DO_UPCAST(USBHubState, dev, dev);
|
||||
USBHubPort *port;
|
||||
int i;
|
||||
|
||||
usb_desc_init(dev);
|
||||
s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
|
||||
for (i = 0; i < NUM_PORTS; i++) {
|
||||
port = &s->ports[i];
|
||||
usb_register_port(usb_bus_from_device(dev),
|
||||
&port->port, s, i, &usb_hub_port_ops,
|
||||
USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
|
||||
usb_port_location(&port->port, dev->port, i+1);
|
||||
}
|
||||
usb_hub_handle_reset(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_usb_hub_port = {
|
||||
.name = "usb-hub-port",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_UINT16(wPortStatus, USBHubPort),
|
||||
VMSTATE_UINT16(wPortChange, USBHubPort),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_usb_hub = {
|
||||
.name = "usb-hub",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_USB_DEVICE(dev, USBHubState),
|
||||
VMSTATE_STRUCT_ARRAY(ports, USBHubState, NUM_PORTS, 0,
|
||||
vmstate_usb_hub_port, USBHubPort),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void usb_hub_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->init = usb_hub_initfn;
|
||||
uc->product_desc = "QEMU USB Hub";
|
||||
uc->usb_desc = &desc_hub;
|
||||
uc->find_device = usb_hub_find_device;
|
||||
uc->handle_reset = usb_hub_handle_reset;
|
||||
uc->handle_control = usb_hub_handle_control;
|
||||
uc->handle_data = usb_hub_handle_data;
|
||||
uc->handle_destroy = usb_hub_handle_destroy;
|
||||
dc->fw_name = "hub";
|
||||
dc->vmsd = &vmstate_usb_hub;
|
||||
}
|
||||
|
||||
static TypeInfo hub_info = {
|
||||
.name = "usb-hub",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBHubState),
|
||||
.class_init = usb_hub_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_hub_register_types(void)
|
||||
{
|
||||
type_register_static(&hub_info);
|
||||
}
|
||||
|
||||
type_init(usb_hub_register_types)
|
1423
hw/usb/dev-network.c
Normal file
1423
hw/usb/dev-network.c
Normal file
File diff suppressed because it is too large
Load diff
637
hw/usb/dev-serial.c
Normal file
637
hw/usb/dev-serial.c
Normal file
|
@ -0,0 +1,637 @@
|
|||
/*
|
||||
* FTDI FT232BM Device emulation
|
||||
*
|
||||
* Copyright (c) 2006 CodeSourcery.
|
||||
* Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org>
|
||||
* Written by Paul Brook, reused for FTDI by Samuel Thibault
|
||||
*
|
||||
* This code is licensed under the LGPL.
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qemu-error.h"
|
||||
#include "hw/usb.h"
|
||||
#include "hw/usb/desc.h"
|
||||
#include "qemu-char.h"
|
||||
|
||||
//#define DEBUG_Serial
|
||||
|
||||
#ifdef DEBUG_Serial
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { printf("usb-serial: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...) do {} while(0)
|
||||
#endif
|
||||
|
||||
#define RECV_BUF 384
|
||||
|
||||
/* Commands */
|
||||
#define FTDI_RESET 0
|
||||
#define FTDI_SET_MDM_CTRL 1
|
||||
#define FTDI_SET_FLOW_CTRL 2
|
||||
#define FTDI_SET_BAUD 3
|
||||
#define FTDI_SET_DATA 4
|
||||
#define FTDI_GET_MDM_ST 5
|
||||
#define FTDI_SET_EVENT_CHR 6
|
||||
#define FTDI_SET_ERROR_CHR 7
|
||||
#define FTDI_SET_LATENCY 9
|
||||
#define FTDI_GET_LATENCY 10
|
||||
|
||||
#define DeviceOutVendor ((USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8)
|
||||
#define DeviceInVendor ((USB_DIR_IN |USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8)
|
||||
|
||||
/* RESET */
|
||||
|
||||
#define FTDI_RESET_SIO 0
|
||||
#define FTDI_RESET_RX 1
|
||||
#define FTDI_RESET_TX 2
|
||||
|
||||
/* SET_MDM_CTRL */
|
||||
|
||||
#define FTDI_DTR 1
|
||||
#define FTDI_SET_DTR (FTDI_DTR << 8)
|
||||
#define FTDI_RTS 2
|
||||
#define FTDI_SET_RTS (FTDI_RTS << 8)
|
||||
|
||||
/* SET_FLOW_CTRL */
|
||||
|
||||
#define FTDI_RTS_CTS_HS 1
|
||||
#define FTDI_DTR_DSR_HS 2
|
||||
#define FTDI_XON_XOFF_HS 4
|
||||
|
||||
/* SET_DATA */
|
||||
|
||||
#define FTDI_PARITY (0x7 << 8)
|
||||
#define FTDI_ODD (0x1 << 8)
|
||||
#define FTDI_EVEN (0x2 << 8)
|
||||
#define FTDI_MARK (0x3 << 8)
|
||||
#define FTDI_SPACE (0x4 << 8)
|
||||
|
||||
#define FTDI_STOP (0x3 << 11)
|
||||
#define FTDI_STOP1 (0x0 << 11)
|
||||
#define FTDI_STOP15 (0x1 << 11)
|
||||
#define FTDI_STOP2 (0x2 << 11)
|
||||
|
||||
/* GET_MDM_ST */
|
||||
/* TODO: should be sent every 40ms */
|
||||
#define FTDI_CTS (1<<4) // CTS line status
|
||||
#define FTDI_DSR (1<<5) // DSR line status
|
||||
#define FTDI_RI (1<<6) // RI line status
|
||||
#define FTDI_RLSD (1<<7) // Receive Line Signal Detect
|
||||
|
||||
/* Status */
|
||||
|
||||
#define FTDI_DR (1<<0) // Data Ready
|
||||
#define FTDI_OE (1<<1) // Overrun Err
|
||||
#define FTDI_PE (1<<2) // Parity Err
|
||||
#define FTDI_FE (1<<3) // Framing Err
|
||||
#define FTDI_BI (1<<4) // Break Interrupt
|
||||
#define FTDI_THRE (1<<5) // Transmitter Holding Register
|
||||
#define FTDI_TEMT (1<<6) // Transmitter Empty
|
||||
#define FTDI_FIFO (1<<7) // Error in FIFO
|
||||
|
||||
typedef struct {
|
||||
USBDevice dev;
|
||||
uint8_t recv_buf[RECV_BUF];
|
||||
uint16_t recv_ptr;
|
||||
uint16_t recv_used;
|
||||
uint8_t event_chr;
|
||||
uint8_t error_chr;
|
||||
uint8_t event_trigger;
|
||||
QEMUSerialSetParams params;
|
||||
int latency; /* ms */
|
||||
CharDriverState *cs;
|
||||
} USBSerialState;
|
||||
|
||||
enum {
|
||||
STR_MANUFACTURER = 1,
|
||||
STR_PRODUCT_SERIAL,
|
||||
STR_PRODUCT_BRAILLE,
|
||||
STR_SERIALNUMBER,
|
||||
};
|
||||
|
||||
static const USBDescStrings desc_strings = {
|
||||
[STR_MANUFACTURER] = "QEMU " QEMU_VERSION,
|
||||
[STR_PRODUCT_SERIAL] = "QEMU USB SERIAL",
|
||||
[STR_PRODUCT_BRAILLE] = "QEMU USB BRAILLE",
|
||||
[STR_SERIALNUMBER] = "1",
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface0 = {
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xff,
|
||||
.bInterfaceSubClass = 0xff,
|
||||
.bInterfaceProtocol = 0xff,
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | 0x01,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 64,
|
||||
},{
|
||||
.bEndpointAddress = USB_DIR_OUT | 0x02,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 64,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device = {
|
||||
.bcdUSB = 0x0200,
|
||||
.bMaxPacketSize0 = 8,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 1,
|
||||
.bConfigurationValue = 1,
|
||||
.bmAttributes = 0x80,
|
||||
.bMaxPower = 50,
|
||||
.nif = 1,
|
||||
.ifs = &desc_iface0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDesc desc_serial = {
|
||||
.id = {
|
||||
.idVendor = 0x0403,
|
||||
.idProduct = 0x6001,
|
||||
.bcdDevice = 0x0400,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = STR_PRODUCT_SERIAL,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static const USBDesc desc_braille = {
|
||||
.id = {
|
||||
.idVendor = 0x0403,
|
||||
.idProduct = 0xfe72,
|
||||
.bcdDevice = 0x0400,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = STR_PRODUCT_BRAILLE,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static void usb_serial_reset(USBSerialState *s)
|
||||
{
|
||||
/* TODO: Set flow control to none */
|
||||
s->event_chr = 0x0d;
|
||||
s->event_trigger = 0;
|
||||
s->recv_ptr = 0;
|
||||
s->recv_used = 0;
|
||||
/* TODO: purge in char driver */
|
||||
}
|
||||
|
||||
static void usb_serial_handle_reset(USBDevice *dev)
|
||||
{
|
||||
USBSerialState *s = (USBSerialState *)dev;
|
||||
|
||||
DPRINTF("Reset\n");
|
||||
|
||||
usb_serial_reset(s);
|
||||
/* TODO: Reset char device, send BREAK? */
|
||||
}
|
||||
|
||||
static uint8_t usb_get_modem_lines(USBSerialState *s)
|
||||
{
|
||||
int flags;
|
||||
uint8_t ret;
|
||||
|
||||
if (qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP)
|
||||
return FTDI_CTS|FTDI_DSR|FTDI_RLSD;
|
||||
|
||||
ret = 0;
|
||||
if (flags & CHR_TIOCM_CTS)
|
||||
ret |= FTDI_CTS;
|
||||
if (flags & CHR_TIOCM_DSR)
|
||||
ret |= FTDI_DSR;
|
||||
if (flags & CHR_TIOCM_RI)
|
||||
ret |= FTDI_RI;
|
||||
if (flags & CHR_TIOCM_CAR)
|
||||
ret |= FTDI_RLSD;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int usb_serial_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
USBSerialState *s = (USBSerialState *)dev;
|
||||
int ret;
|
||||
|
||||
DPRINTF("got control %x, value %x\n",request, value);
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
switch (request) {
|
||||
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
|
||||
ret = 0;
|
||||
break;
|
||||
|
||||
/* Class specific requests. */
|
||||
case DeviceOutVendor | FTDI_RESET:
|
||||
switch (value) {
|
||||
case FTDI_RESET_SIO:
|
||||
usb_serial_reset(s);
|
||||
break;
|
||||
case FTDI_RESET_RX:
|
||||
s->recv_ptr = 0;
|
||||
s->recv_used = 0;
|
||||
/* TODO: purge from char device */
|
||||
break;
|
||||
case FTDI_RESET_TX:
|
||||
/* TODO: purge from char device */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DeviceOutVendor | FTDI_SET_MDM_CTRL:
|
||||
{
|
||||
static int flags;
|
||||
qemu_chr_fe_ioctl(s->cs,CHR_IOCTL_SERIAL_GET_TIOCM, &flags);
|
||||
if (value & FTDI_SET_RTS) {
|
||||
if (value & FTDI_RTS)
|
||||
flags |= CHR_TIOCM_RTS;
|
||||
else
|
||||
flags &= ~CHR_TIOCM_RTS;
|
||||
}
|
||||
if (value & FTDI_SET_DTR) {
|
||||
if (value & FTDI_DTR)
|
||||
flags |= CHR_TIOCM_DTR;
|
||||
else
|
||||
flags &= ~CHR_TIOCM_DTR;
|
||||
}
|
||||
qemu_chr_fe_ioctl(s->cs,CHR_IOCTL_SERIAL_SET_TIOCM, &flags);
|
||||
break;
|
||||
}
|
||||
case DeviceOutVendor | FTDI_SET_FLOW_CTRL:
|
||||
/* TODO: ioctl */
|
||||
break;
|
||||
case DeviceOutVendor | FTDI_SET_BAUD: {
|
||||
static const int subdivisors8[8] = { 0, 4, 2, 1, 3, 5, 6, 7 };
|
||||
int subdivisor8 = subdivisors8[((value & 0xc000) >> 14)
|
||||
| ((index & 1) << 2)];
|
||||
int divisor = value & 0x3fff;
|
||||
|
||||
/* chip special cases */
|
||||
if (divisor == 1 && subdivisor8 == 0)
|
||||
subdivisor8 = 4;
|
||||
if (divisor == 0 && subdivisor8 == 0)
|
||||
divisor = 1;
|
||||
|
||||
s->params.speed = (48000000 / 2) / (8 * divisor + subdivisor8);
|
||||
qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params);
|
||||
break;
|
||||
}
|
||||
case DeviceOutVendor | FTDI_SET_DATA:
|
||||
switch (value & FTDI_PARITY) {
|
||||
case 0:
|
||||
s->params.parity = 'N';
|
||||
break;
|
||||
case FTDI_ODD:
|
||||
s->params.parity = 'O';
|
||||
break;
|
||||
case FTDI_EVEN:
|
||||
s->params.parity = 'E';
|
||||
break;
|
||||
default:
|
||||
DPRINTF("unsupported parity %d\n", value & FTDI_PARITY);
|
||||
goto fail;
|
||||
}
|
||||
switch (value & FTDI_STOP) {
|
||||
case FTDI_STOP1:
|
||||
s->params.stop_bits = 1;
|
||||
break;
|
||||
case FTDI_STOP2:
|
||||
s->params.stop_bits = 2;
|
||||
break;
|
||||
default:
|
||||
DPRINTF("unsupported stop bits %d\n", value & FTDI_STOP);
|
||||
goto fail;
|
||||
}
|
||||
qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params);
|
||||
/* TODO: TX ON/OFF */
|
||||
break;
|
||||
case DeviceInVendor | FTDI_GET_MDM_ST:
|
||||
data[0] = usb_get_modem_lines(s) | 1;
|
||||
data[1] = 0;
|
||||
ret = 2;
|
||||
break;
|
||||
case DeviceOutVendor | FTDI_SET_EVENT_CHR:
|
||||
/* TODO: handle it */
|
||||
s->event_chr = value;
|
||||
break;
|
||||
case DeviceOutVendor | FTDI_SET_ERROR_CHR:
|
||||
/* TODO: handle it */
|
||||
s->error_chr = value;
|
||||
break;
|
||||
case DeviceOutVendor | FTDI_SET_LATENCY:
|
||||
s->latency = value;
|
||||
break;
|
||||
case DeviceInVendor | FTDI_GET_LATENCY:
|
||||
data[0] = s->latency;
|
||||
ret = 1;
|
||||
break;
|
||||
default:
|
||||
fail:
|
||||
DPRINTF("got unsupported/bogus control %x, value %x\n", request, value);
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int usb_serial_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBSerialState *s = (USBSerialState *)dev;
|
||||
int i, ret = 0;
|
||||
uint8_t devep = p->ep->nr;
|
||||
struct iovec *iov;
|
||||
uint8_t header[2];
|
||||
int first_len, len;
|
||||
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_OUT:
|
||||
if (devep != 2)
|
||||
goto fail;
|
||||
for (i = 0; i < p->iov.niov; i++) {
|
||||
iov = p->iov.iov + i;
|
||||
qemu_chr_fe_write(s->cs, iov->iov_base, iov->iov_len);
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_TOKEN_IN:
|
||||
if (devep != 1)
|
||||
goto fail;
|
||||
first_len = RECV_BUF - s->recv_ptr;
|
||||
len = p->iov.size;
|
||||
if (len <= 2) {
|
||||
ret = USB_RET_NAK;
|
||||
break;
|
||||
}
|
||||
header[0] = usb_get_modem_lines(s) | 1;
|
||||
/* We do not have the uart details */
|
||||
/* handle serial break */
|
||||
if (s->event_trigger && s->event_trigger & FTDI_BI) {
|
||||
s->event_trigger &= ~FTDI_BI;
|
||||
header[1] = FTDI_BI;
|
||||
usb_packet_copy(p, header, 2);
|
||||
ret = 2;
|
||||
break;
|
||||
} else {
|
||||
header[1] = 0;
|
||||
}
|
||||
len -= 2;
|
||||
if (len > s->recv_used)
|
||||
len = s->recv_used;
|
||||
if (!len) {
|
||||
ret = USB_RET_NAK;
|
||||
break;
|
||||
}
|
||||
if (first_len > len)
|
||||
first_len = len;
|
||||
usb_packet_copy(p, header, 2);
|
||||
usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len);
|
||||
if (len > first_len)
|
||||
usb_packet_copy(p, s->recv_buf, len - first_len);
|
||||
s->recv_used -= len;
|
||||
s->recv_ptr = (s->recv_ptr + len) % RECV_BUF;
|
||||
ret = len + 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF("Bad token\n");
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_serial_handle_destroy(USBDevice *dev)
|
||||
{
|
||||
USBSerialState *s = (USBSerialState *)dev;
|
||||
|
||||
qemu_chr_delete(s->cs);
|
||||
}
|
||||
|
||||
static int usb_serial_can_read(void *opaque)
|
||||
{
|
||||
USBSerialState *s = opaque;
|
||||
return RECV_BUF - s->recv_used;
|
||||
}
|
||||
|
||||
static void usb_serial_read(void *opaque, const uint8_t *buf, int size)
|
||||
{
|
||||
USBSerialState *s = opaque;
|
||||
int first_size, start;
|
||||
|
||||
/* room in the buffer? */
|
||||
if (size > (RECV_BUF - s->recv_used))
|
||||
size = RECV_BUF - s->recv_used;
|
||||
|
||||
start = s->recv_ptr + s->recv_used;
|
||||
if (start < RECV_BUF) {
|
||||
/* copy data to end of buffer */
|
||||
first_size = RECV_BUF - start;
|
||||
if (first_size > size)
|
||||
first_size = size;
|
||||
|
||||
memcpy(s->recv_buf + start, buf, first_size);
|
||||
|
||||
/* wrap around to front if needed */
|
||||
if (size > first_size)
|
||||
memcpy(s->recv_buf, buf + first_size, size - first_size);
|
||||
} else {
|
||||
start -= RECV_BUF;
|
||||
memcpy(s->recv_buf + start, buf, size);
|
||||
}
|
||||
s->recv_used += size;
|
||||
}
|
||||
|
||||
static void usb_serial_event(void *opaque, int event)
|
||||
{
|
||||
USBSerialState *s = opaque;
|
||||
|
||||
switch (event) {
|
||||
case CHR_EVENT_BREAK:
|
||||
s->event_trigger |= FTDI_BI;
|
||||
break;
|
||||
case CHR_EVENT_FOCUS:
|
||||
break;
|
||||
case CHR_EVENT_OPENED:
|
||||
usb_serial_reset(s);
|
||||
/* TODO: Reset USB port */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int usb_serial_initfn(USBDevice *dev)
|
||||
{
|
||||
USBSerialState *s = DO_UPCAST(USBSerialState, dev, dev);
|
||||
|
||||
usb_desc_init(dev);
|
||||
|
||||
if (!s->cs) {
|
||||
error_report("Property chardev is required");
|
||||
return -1;
|
||||
}
|
||||
|
||||
qemu_chr_add_handlers(s->cs, usb_serial_can_read, usb_serial_read,
|
||||
usb_serial_event, s);
|
||||
usb_serial_handle_reset(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static USBDevice *usb_serial_init(USBBus *bus, const char *filename)
|
||||
{
|
||||
USBDevice *dev;
|
||||
CharDriverState *cdrv;
|
||||
uint32_t vendorid = 0, productid = 0;
|
||||
char label[32];
|
||||
static int index;
|
||||
|
||||
while (*filename && *filename != ':') {
|
||||
const char *p;
|
||||
char *e;
|
||||
if (strstart(filename, "vendorid=", &p)) {
|
||||
vendorid = strtol(p, &e, 16);
|
||||
if (e == p || (*e && *e != ',' && *e != ':')) {
|
||||
error_report("bogus vendor ID %s", p);
|
||||
return NULL;
|
||||
}
|
||||
filename = e;
|
||||
} else if (strstart(filename, "productid=", &p)) {
|
||||
productid = strtol(p, &e, 16);
|
||||
if (e == p || (*e && *e != ',' && *e != ':')) {
|
||||
error_report("bogus product ID %s", p);
|
||||
return NULL;
|
||||
}
|
||||
filename = e;
|
||||
} else {
|
||||
error_report("unrecognized serial USB option %s", filename);
|
||||
return NULL;
|
||||
}
|
||||
while(*filename == ',')
|
||||
filename++;
|
||||
}
|
||||
if (!*filename) {
|
||||
error_report("character device specification needed");
|
||||
return NULL;
|
||||
}
|
||||
filename++;
|
||||
|
||||
snprintf(label, sizeof(label), "usbserial%d", index++);
|
||||
cdrv = qemu_chr_new(label, filename, NULL);
|
||||
if (!cdrv)
|
||||
return NULL;
|
||||
|
||||
dev = usb_create(bus, "usb-serial");
|
||||
if (!dev) {
|
||||
return NULL;
|
||||
}
|
||||
qdev_prop_set_chr(&dev->qdev, "chardev", cdrv);
|
||||
if (vendorid)
|
||||
qdev_prop_set_uint16(&dev->qdev, "vendorid", vendorid);
|
||||
if (productid)
|
||||
qdev_prop_set_uint16(&dev->qdev, "productid", productid);
|
||||
qdev_init_nofail(&dev->qdev);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static USBDevice *usb_braille_init(USBBus *bus, const char *unused)
|
||||
{
|
||||
USBDevice *dev;
|
||||
CharDriverState *cdrv;
|
||||
|
||||
cdrv = qemu_chr_new("braille", "braille", NULL);
|
||||
if (!cdrv)
|
||||
return NULL;
|
||||
|
||||
dev = usb_create(bus, "usb-braille");
|
||||
qdev_prop_set_chr(&dev->qdev, "chardev", cdrv);
|
||||
qdev_init_nofail(&dev->qdev);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_usb_serial = {
|
||||
.name = "usb-serial",
|
||||
.unmigratable = 1,
|
||||
};
|
||||
|
||||
static Property serial_properties[] = {
|
||||
DEFINE_PROP_CHR("chardev", USBSerialState, cs),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void usb_serial_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->init = usb_serial_initfn;
|
||||
uc->product_desc = "QEMU USB Serial";
|
||||
uc->usb_desc = &desc_serial;
|
||||
uc->handle_reset = usb_serial_handle_reset;
|
||||
uc->handle_control = usb_serial_handle_control;
|
||||
uc->handle_data = usb_serial_handle_data;
|
||||
uc->handle_destroy = usb_serial_handle_destroy;
|
||||
dc->vmsd = &vmstate_usb_serial;
|
||||
dc->props = serial_properties;
|
||||
}
|
||||
|
||||
static TypeInfo serial_info = {
|
||||
.name = "usb-serial",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBSerialState),
|
||||
.class_init = usb_serial_class_initfn,
|
||||
};
|
||||
|
||||
static Property braille_properties[] = {
|
||||
DEFINE_PROP_CHR("chardev", USBSerialState, cs),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void usb_braille_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->init = usb_serial_initfn;
|
||||
uc->product_desc = "QEMU USB Braille";
|
||||
uc->usb_desc = &desc_braille;
|
||||
uc->handle_reset = usb_serial_handle_reset;
|
||||
uc->handle_control = usb_serial_handle_control;
|
||||
uc->handle_data = usb_serial_handle_data;
|
||||
uc->handle_destroy = usb_serial_handle_destroy;
|
||||
dc->vmsd = &vmstate_usb_serial;
|
||||
dc->props = braille_properties;
|
||||
}
|
||||
|
||||
static TypeInfo braille_info = {
|
||||
.name = "usb-braille",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBSerialState),
|
||||
.class_init = usb_braille_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_serial_register_types(void)
|
||||
{
|
||||
type_register_static(&serial_info);
|
||||
usb_legacy_register("usb-serial", "serial", usb_serial_init);
|
||||
type_register_static(&braille_info);
|
||||
usb_legacy_register("usb-braille", "braille", usb_braille_init);
|
||||
}
|
||||
|
||||
type_init(usb_serial_register_types)
|
1365
hw/usb/dev-smartcard-reader.c
Normal file
1365
hw/usb/dev-smartcard-reader.c
Normal file
File diff suppressed because it is too large
Load diff
677
hw/usb/dev-storage.c
Normal file
677
hw/usb/dev-storage.c
Normal file
|
@ -0,0 +1,677 @@
|
|||
/*
|
||||
* USB Mass Storage Device emulation
|
||||
*
|
||||
* Copyright (c) 2006 CodeSourcery.
|
||||
* Written by Paul Brook
|
||||
*
|
||||
* This code is licensed under the LGPL.
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qemu-option.h"
|
||||
#include "qemu-config.h"
|
||||
#include "hw/usb.h"
|
||||
#include "hw/usb/desc.h"
|
||||
#include "hw/scsi.h"
|
||||
#include "console.h"
|
||||
#include "monitor.h"
|
||||
#include "sysemu.h"
|
||||
#include "blockdev.h"
|
||||
|
||||
//#define DEBUG_MSD
|
||||
|
||||
#ifdef DEBUG_MSD
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { printf("usb-msd: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...) do {} while(0)
|
||||
#endif
|
||||
|
||||
/* USB requests. */
|
||||
#define MassStorageReset 0xff
|
||||
#define GetMaxLun 0xfe
|
||||
|
||||
enum USBMSDMode {
|
||||
USB_MSDM_CBW, /* Command Block. */
|
||||
USB_MSDM_DATAOUT, /* Transfer data to device. */
|
||||
USB_MSDM_DATAIN, /* Transfer data from device. */
|
||||
USB_MSDM_CSW /* Command Status. */
|
||||
};
|
||||
|
||||
struct usb_msd_csw {
|
||||
uint32_t sig;
|
||||
uint32_t tag;
|
||||
uint32_t residue;
|
||||
uint8_t status;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
USBDevice dev;
|
||||
enum USBMSDMode mode;
|
||||
uint32_t scsi_len;
|
||||
uint8_t *scsi_buf;
|
||||
uint32_t data_len;
|
||||
uint32_t residue;
|
||||
struct usb_msd_csw csw;
|
||||
SCSIRequest *req;
|
||||
SCSIBus bus;
|
||||
BlockConf conf;
|
||||
char *serial;
|
||||
SCSIDevice *scsi_dev;
|
||||
uint32_t removable;
|
||||
/* For async completion. */
|
||||
USBPacket *packet;
|
||||
} MSDState;
|
||||
|
||||
struct usb_msd_cbw {
|
||||
uint32_t sig;
|
||||
uint32_t tag;
|
||||
uint32_t data_len;
|
||||
uint8_t flags;
|
||||
uint8_t lun;
|
||||
uint8_t cmd_len;
|
||||
uint8_t cmd[16];
|
||||
};
|
||||
|
||||
enum {
|
||||
STR_MANUFACTURER = 1,
|
||||
STR_PRODUCT,
|
||||
STR_SERIALNUMBER,
|
||||
STR_CONFIG_FULL,
|
||||
STR_CONFIG_HIGH,
|
||||
};
|
||||
|
||||
static const USBDescStrings desc_strings = {
|
||||
[STR_MANUFACTURER] = "QEMU " QEMU_VERSION,
|
||||
[STR_PRODUCT] = "QEMU USB HARDDRIVE",
|
||||
[STR_SERIALNUMBER] = "1",
|
||||
[STR_CONFIG_FULL] = "Full speed config (usb 1.1)",
|
||||
[STR_CONFIG_HIGH] = "High speed config (usb 2.0)",
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_full = {
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_MASS_STORAGE,
|
||||
.bInterfaceSubClass = 0x06, /* SCSI */
|
||||
.bInterfaceProtocol = 0x50, /* Bulk */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | 0x01,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 64,
|
||||
},{
|
||||
.bEndpointAddress = USB_DIR_OUT | 0x02,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 64,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_full = {
|
||||
.bcdUSB = 0x0200,
|
||||
.bMaxPacketSize0 = 8,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 1,
|
||||
.bConfigurationValue = 1,
|
||||
.iConfiguration = STR_CONFIG_FULL,
|
||||
.bmAttributes = 0xc0,
|
||||
.nif = 1,
|
||||
.ifs = &desc_iface_full,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_high = {
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_MASS_STORAGE,
|
||||
.bInterfaceSubClass = 0x06, /* SCSI */
|
||||
.bInterfaceProtocol = 0x50, /* Bulk */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | 0x01,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 512,
|
||||
},{
|
||||
.bEndpointAddress = USB_DIR_OUT | 0x02,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 512,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_high = {
|
||||
.bcdUSB = 0x0200,
|
||||
.bMaxPacketSize0 = 64,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 1,
|
||||
.bConfigurationValue = 1,
|
||||
.iConfiguration = STR_CONFIG_HIGH,
|
||||
.bmAttributes = 0xc0,
|
||||
.nif = 1,
|
||||
.ifs = &desc_iface_high,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDesc desc = {
|
||||
.id = {
|
||||
.idVendor = 0x46f4, /* CRC16() of "QEMU" */
|
||||
.idProduct = 0x0001,
|
||||
.bcdDevice = 0,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = STR_PRODUCT,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device_full,
|
||||
.high = &desc_device_high,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static void usb_msd_copy_data(MSDState *s, USBPacket *p)
|
||||
{
|
||||
uint32_t len;
|
||||
len = p->iov.size - p->result;
|
||||
if (len > s->scsi_len)
|
||||
len = s->scsi_len;
|
||||
usb_packet_copy(p, s->scsi_buf, len);
|
||||
s->scsi_len -= len;
|
||||
s->scsi_buf += len;
|
||||
s->data_len -= len;
|
||||
if (s->scsi_len == 0 || s->data_len == 0) {
|
||||
scsi_req_continue(s->req);
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_msd_send_status(MSDState *s, USBPacket *p)
|
||||
{
|
||||
int len;
|
||||
|
||||
DPRINTF("Command status %d tag 0x%x, len %zd\n",
|
||||
s->csw.status, s->csw.tag, p->iov.size);
|
||||
|
||||
assert(s->csw.sig == 0x53425355);
|
||||
len = MIN(sizeof(s->csw), p->iov.size);
|
||||
usb_packet_copy(p, &s->csw, len);
|
||||
memset(&s->csw, 0, sizeof(s->csw));
|
||||
}
|
||||
|
||||
static void usb_msd_transfer_data(SCSIRequest *req, uint32_t len)
|
||||
{
|
||||
MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent);
|
||||
USBPacket *p = s->packet;
|
||||
|
||||
assert((s->mode == USB_MSDM_DATAOUT) == (req->cmd.mode == SCSI_XFER_TO_DEV));
|
||||
s->scsi_len = len;
|
||||
s->scsi_buf = scsi_req_get_buf(req);
|
||||
if (p) {
|
||||
usb_msd_copy_data(s, p);
|
||||
p = s->packet;
|
||||
if (p && p->result == p->iov.size) {
|
||||
/* Set s->packet to NULL before calling usb_packet_complete
|
||||
because another request may be issued before
|
||||
usb_packet_complete returns. */
|
||||
DPRINTF("Packet complete %p\n", p);
|
||||
s->packet = NULL;
|
||||
usb_packet_complete(&s->dev, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_msd_command_complete(SCSIRequest *req, uint32_t status, size_t resid)
|
||||
{
|
||||
MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent);
|
||||
USBPacket *p = s->packet;
|
||||
|
||||
DPRINTF("Command complete %d tag 0x%x\n", status, req->tag);
|
||||
s->residue = s->data_len;
|
||||
|
||||
s->csw.sig = cpu_to_le32(0x53425355);
|
||||
s->csw.tag = cpu_to_le32(req->tag);
|
||||
s->csw.residue = s->residue;
|
||||
s->csw.status = status != 0;
|
||||
|
||||
if (s->packet) {
|
||||
if (s->data_len == 0 && s->mode == USB_MSDM_DATAOUT) {
|
||||
/* A deferred packet with no write data remaining must be
|
||||
the status read packet. */
|
||||
usb_msd_send_status(s, p);
|
||||
s->mode = USB_MSDM_CBW;
|
||||
} else {
|
||||
if (s->data_len) {
|
||||
int len = (p->iov.size - p->result);
|
||||
usb_packet_skip(p, len);
|
||||
s->data_len -= len;
|
||||
}
|
||||
if (s->data_len == 0) {
|
||||
s->mode = USB_MSDM_CSW;
|
||||
}
|
||||
}
|
||||
s->packet = NULL;
|
||||
usb_packet_complete(&s->dev, p);
|
||||
} else if (s->data_len == 0) {
|
||||
s->mode = USB_MSDM_CSW;
|
||||
}
|
||||
scsi_req_unref(req);
|
||||
s->req = NULL;
|
||||
}
|
||||
|
||||
static void usb_msd_request_cancelled(SCSIRequest *req)
|
||||
{
|
||||
MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent);
|
||||
|
||||
if (req == s->req) {
|
||||
scsi_req_unref(s->req);
|
||||
s->req = NULL;
|
||||
s->packet = NULL;
|
||||
s->scsi_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_msd_handle_reset(USBDevice *dev)
|
||||
{
|
||||
MSDState *s = (MSDState *)dev;
|
||||
|
||||
DPRINTF("Reset\n");
|
||||
if (s->req) {
|
||||
scsi_req_cancel(s->req);
|
||||
}
|
||||
assert(s->req == NULL);
|
||||
|
||||
if (s->packet) {
|
||||
USBPacket *p = s->packet;
|
||||
s->packet = NULL;
|
||||
p->result = USB_RET_STALL;
|
||||
usb_packet_complete(dev, p);
|
||||
}
|
||||
|
||||
s->mode = USB_MSDM_CBW;
|
||||
}
|
||||
|
||||
static int usb_msd_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
MSDState *s = (MSDState *)dev;
|
||||
int ret;
|
||||
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
switch (request) {
|
||||
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
|
||||
ret = 0;
|
||||
break;
|
||||
/* Class specific requests. */
|
||||
case ClassInterfaceOutRequest | MassStorageReset:
|
||||
/* Reset state ready for the next CBW. */
|
||||
s->mode = USB_MSDM_CBW;
|
||||
ret = 0;
|
||||
break;
|
||||
case ClassInterfaceRequest | GetMaxLun:
|
||||
data[0] = 0;
|
||||
ret = 1;
|
||||
break;
|
||||
default:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_msd_cancel_io(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
MSDState *s = DO_UPCAST(MSDState, dev, dev);
|
||||
|
||||
if (s->req) {
|
||||
scsi_req_cancel(s->req);
|
||||
}
|
||||
}
|
||||
|
||||
static int usb_msd_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
MSDState *s = (MSDState *)dev;
|
||||
uint32_t tag;
|
||||
int ret = 0;
|
||||
struct usb_msd_cbw cbw;
|
||||
uint8_t devep = p->ep->nr;
|
||||
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_OUT:
|
||||
if (devep != 2)
|
||||
goto fail;
|
||||
|
||||
switch (s->mode) {
|
||||
case USB_MSDM_CBW:
|
||||
if (p->iov.size != 31) {
|
||||
fprintf(stderr, "usb-msd: Bad CBW size");
|
||||
goto fail;
|
||||
}
|
||||
usb_packet_copy(p, &cbw, 31);
|
||||
if (le32_to_cpu(cbw.sig) != 0x43425355) {
|
||||
fprintf(stderr, "usb-msd: Bad signature %08x\n",
|
||||
le32_to_cpu(cbw.sig));
|
||||
goto fail;
|
||||
}
|
||||
DPRINTF("Command on LUN %d\n", cbw.lun);
|
||||
if (cbw.lun != 0) {
|
||||
fprintf(stderr, "usb-msd: Bad LUN %d\n", cbw.lun);
|
||||
goto fail;
|
||||
}
|
||||
tag = le32_to_cpu(cbw.tag);
|
||||
s->data_len = le32_to_cpu(cbw.data_len);
|
||||
if (s->data_len == 0) {
|
||||
s->mode = USB_MSDM_CSW;
|
||||
} else if (cbw.flags & 0x80) {
|
||||
s->mode = USB_MSDM_DATAIN;
|
||||
} else {
|
||||
s->mode = USB_MSDM_DATAOUT;
|
||||
}
|
||||
DPRINTF("Command tag 0x%x flags %08x len %d data %d\n",
|
||||
tag, cbw.flags, cbw.cmd_len, s->data_len);
|
||||
s->residue = 0;
|
||||
s->scsi_len = 0;
|
||||
s->req = scsi_req_new(s->scsi_dev, tag, 0, cbw.cmd, NULL);
|
||||
scsi_req_enqueue(s->req);
|
||||
if (s->req && s->req->cmd.xfer != SCSI_XFER_NONE) {
|
||||
scsi_req_continue(s->req);
|
||||
}
|
||||
ret = p->result;
|
||||
break;
|
||||
|
||||
case USB_MSDM_DATAOUT:
|
||||
DPRINTF("Data out %zd/%d\n", p->iov.size, s->data_len);
|
||||
if (p->iov.size > s->data_len) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (s->scsi_len) {
|
||||
usb_msd_copy_data(s, p);
|
||||
}
|
||||
if (s->residue) {
|
||||
int len = p->iov.size - p->result;
|
||||
if (len) {
|
||||
usb_packet_skip(p, len);
|
||||
s->data_len -= len;
|
||||
if (s->data_len == 0) {
|
||||
s->mode = USB_MSDM_CSW;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (p->result < p->iov.size) {
|
||||
DPRINTF("Deferring packet %p\n", p);
|
||||
s->packet = p;
|
||||
ret = USB_RET_ASYNC;
|
||||
} else {
|
||||
ret = p->result;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF("Unexpected write (len %zd)\n", p->iov.size);
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_TOKEN_IN:
|
||||
if (devep != 1)
|
||||
goto fail;
|
||||
|
||||
switch (s->mode) {
|
||||
case USB_MSDM_DATAOUT:
|
||||
if (s->data_len != 0 || p->iov.size < 13) {
|
||||
goto fail;
|
||||
}
|
||||
/* Waiting for SCSI write to complete. */
|
||||
s->packet = p;
|
||||
ret = USB_RET_ASYNC;
|
||||
break;
|
||||
|
||||
case USB_MSDM_CSW:
|
||||
if (p->iov.size < 13) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (s->req) {
|
||||
/* still in flight */
|
||||
s->packet = p;
|
||||
ret = USB_RET_ASYNC;
|
||||
} else {
|
||||
usb_msd_send_status(s, p);
|
||||
s->mode = USB_MSDM_CBW;
|
||||
ret = 13;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_MSDM_DATAIN:
|
||||
DPRINTF("Data in %zd/%d, scsi_len %d\n",
|
||||
p->iov.size, s->data_len, s->scsi_len);
|
||||
if (s->scsi_len) {
|
||||
usb_msd_copy_data(s, p);
|
||||
}
|
||||
if (s->residue) {
|
||||
int len = p->iov.size - p->result;
|
||||
if (len) {
|
||||
usb_packet_skip(p, len);
|
||||
s->data_len -= len;
|
||||
if (s->data_len == 0) {
|
||||
s->mode = USB_MSDM_CSW;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (p->result < p->iov.size) {
|
||||
DPRINTF("Deferring packet %p\n", p);
|
||||
s->packet = p;
|
||||
ret = USB_RET_ASYNC;
|
||||
} else {
|
||||
ret = p->result;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF("Unexpected read (len %zd)\n", p->iov.size);
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF("Bad token\n");
|
||||
fail:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_msd_password_cb(void *opaque, int err)
|
||||
{
|
||||
MSDState *s = opaque;
|
||||
|
||||
if (!err)
|
||||
err = usb_device_attach(&s->dev);
|
||||
|
||||
if (err)
|
||||
qdev_unplug(&s->dev.qdev);
|
||||
}
|
||||
|
||||
static const struct SCSIBusInfo usb_msd_scsi_info = {
|
||||
.tcq = false,
|
||||
.max_target = 0,
|
||||
.max_lun = 0,
|
||||
|
||||
.transfer_data = usb_msd_transfer_data,
|
||||
.complete = usb_msd_command_complete,
|
||||
.cancel = usb_msd_request_cancelled
|
||||
};
|
||||
|
||||
static int usb_msd_initfn(USBDevice *dev)
|
||||
{
|
||||
MSDState *s = DO_UPCAST(MSDState, dev, dev);
|
||||
BlockDriverState *bs = s->conf.bs;
|
||||
DriveInfo *dinfo;
|
||||
|
||||
if (!bs) {
|
||||
error_report("drive property not set");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hack alert: this pretends to be a block device, but it's really
|
||||
* a SCSI bus that can serve only a single device, which it
|
||||
* creates automatically. But first it needs to detach from its
|
||||
* blockdev, or else scsi_bus_legacy_add_drive() dies when it
|
||||
* attaches again.
|
||||
*
|
||||
* The hack is probably a bad idea.
|
||||
*/
|
||||
bdrv_detach_dev(bs, &s->dev.qdev);
|
||||
s->conf.bs = NULL;
|
||||
|
||||
if (!s->serial) {
|
||||
/* try to fall back to value set with legacy -drive serial=... */
|
||||
dinfo = drive_get_by_blockdev(bs);
|
||||
if (*dinfo->serial) {
|
||||
s->serial = strdup(dinfo->serial);
|
||||
}
|
||||
}
|
||||
if (s->serial) {
|
||||
usb_desc_set_string(dev, STR_SERIALNUMBER, s->serial);
|
||||
}
|
||||
|
||||
usb_desc_init(dev);
|
||||
scsi_bus_new(&s->bus, &s->dev.qdev, &usb_msd_scsi_info);
|
||||
s->scsi_dev = scsi_bus_legacy_add_drive(&s->bus, bs, 0, !!s->removable,
|
||||
s->conf.bootindex);
|
||||
if (!s->scsi_dev) {
|
||||
return -1;
|
||||
}
|
||||
s->bus.qbus.allow_hotplug = 0;
|
||||
usb_msd_handle_reset(dev);
|
||||
|
||||
if (bdrv_key_required(bs)) {
|
||||
if (cur_mon) {
|
||||
monitor_read_bdrv_key_start(cur_mon, bs, usb_msd_password_cb, s);
|
||||
s->dev.auto_attach = 0;
|
||||
} else {
|
||||
autostart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static USBDevice *usb_msd_init(USBBus *bus, const char *filename)
|
||||
{
|
||||
static int nr=0;
|
||||
char id[8];
|
||||
QemuOpts *opts;
|
||||
DriveInfo *dinfo;
|
||||
USBDevice *dev;
|
||||
const char *p1;
|
||||
char fmt[32];
|
||||
|
||||
/* parse -usbdevice disk: syntax into drive opts */
|
||||
snprintf(id, sizeof(id), "usb%d", nr++);
|
||||
opts = qemu_opts_create(qemu_find_opts("drive"), id, 0);
|
||||
|
||||
p1 = strchr(filename, ':');
|
||||
if (p1++) {
|
||||
const char *p2;
|
||||
|
||||
if (strstart(filename, "format=", &p2)) {
|
||||
int len = MIN(p1 - p2, sizeof(fmt));
|
||||
pstrcpy(fmt, len, p2);
|
||||
qemu_opt_set(opts, "format", fmt);
|
||||
} else if (*filename != ':') {
|
||||
printf("unrecognized USB mass-storage option %s\n", filename);
|
||||
return NULL;
|
||||
}
|
||||
filename = p1;
|
||||
}
|
||||
if (!*filename) {
|
||||
printf("block device specification needed\n");
|
||||
return NULL;
|
||||
}
|
||||
qemu_opt_set(opts, "file", filename);
|
||||
qemu_opt_set(opts, "if", "none");
|
||||
|
||||
/* create host drive */
|
||||
dinfo = drive_init(opts, 0);
|
||||
if (!dinfo) {
|
||||
qemu_opts_del(opts);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* create guest device */
|
||||
dev = usb_create(bus, "usb-storage");
|
||||
if (!dev) {
|
||||
return NULL;
|
||||
}
|
||||
if (qdev_prop_set_drive(&dev->qdev, "drive", dinfo->bdrv) < 0) {
|
||||
qdev_free(&dev->qdev);
|
||||
return NULL;
|
||||
}
|
||||
if (qdev_init(&dev->qdev) < 0)
|
||||
return NULL;
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_usb_msd = {
|
||||
.name = "usb-storage",
|
||||
.unmigratable = 1, /* FIXME: handle transactions which are in flight */
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_USB_DEVICE(dev, MSDState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static Property msd_properties[] = {
|
||||
DEFINE_BLOCK_PROPERTIES(MSDState, conf),
|
||||
DEFINE_PROP_STRING("serial", MSDState, serial),
|
||||
DEFINE_PROP_BIT("removable", MSDState, removable, 0, false),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void usb_msd_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->init = usb_msd_initfn;
|
||||
uc->product_desc = "QEMU USB MSD";
|
||||
uc->usb_desc = &desc;
|
||||
uc->cancel_packet = usb_msd_cancel_io;
|
||||
uc->handle_attach = usb_desc_attach;
|
||||
uc->handle_reset = usb_msd_handle_reset;
|
||||
uc->handle_control = usb_msd_handle_control;
|
||||
uc->handle_data = usb_msd_handle_data;
|
||||
dc->fw_name = "storage";
|
||||
dc->vmsd = &vmstate_usb_msd;
|
||||
dc->props = msd_properties;
|
||||
}
|
||||
|
||||
static TypeInfo msd_info = {
|
||||
.name = "usb-storage",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(MSDState),
|
||||
.class_init = usb_msd_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_msd_register_types(void)
|
||||
{
|
||||
type_register_static(&msd_info);
|
||||
usb_legacy_register("usb-storage", "disk", usb_msd_init);
|
||||
}
|
||||
|
||||
type_init(usb_msd_register_types)
|
381
hw/usb/dev-wacom.c
Normal file
381
hw/usb/dev-wacom.c
Normal file
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* Wacom PenPartner USB tablet emulation.
|
||||
*
|
||||
* Copyright (c) 2006 Openedhand Ltd.
|
||||
* Author: Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* Based on hw/usb-hid.c:
|
||||
* Copyright (c) 2005 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "hw/hw.h"
|
||||
#include "console.h"
|
||||
#include "hw/usb.h"
|
||||
#include "hw/usb/desc.h"
|
||||
|
||||
/* Interface requests */
|
||||
#define WACOM_GET_REPORT 0x2101
|
||||
#define WACOM_SET_REPORT 0x2109
|
||||
|
||||
/* HID interface requests */
|
||||
#define HID_GET_REPORT 0xa101
|
||||
#define HID_GET_IDLE 0xa102
|
||||
#define HID_GET_PROTOCOL 0xa103
|
||||
#define HID_SET_IDLE 0x210a
|
||||
#define HID_SET_PROTOCOL 0x210b
|
||||
|
||||
typedef struct USBWacomState {
|
||||
USBDevice dev;
|
||||
QEMUPutMouseEntry *eh_entry;
|
||||
int dx, dy, dz, buttons_state;
|
||||
int x, y;
|
||||
int mouse_grabbed;
|
||||
enum {
|
||||
WACOM_MODE_HID = 1,
|
||||
WACOM_MODE_WACOM = 2,
|
||||
} mode;
|
||||
uint8_t idle;
|
||||
int changed;
|
||||
} USBWacomState;
|
||||
|
||||
enum {
|
||||
STR_MANUFACTURER = 1,
|
||||
STR_PRODUCT,
|
||||
STR_SERIALNUMBER,
|
||||
};
|
||||
|
||||
static const USBDescStrings desc_strings = {
|
||||
[STR_MANUFACTURER] = "QEMU " QEMU_VERSION,
|
||||
[STR_PRODUCT] = "Wacom PenPartner",
|
||||
[STR_SERIALNUMBER] = "1",
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_wacom = {
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_HID,
|
||||
.bInterfaceSubClass = 0x01, /* boot */
|
||||
.bInterfaceProtocol = 0x02,
|
||||
.ndesc = 1,
|
||||
.descs = (USBDescOther[]) {
|
||||
{
|
||||
/* HID descriptor */
|
||||
.data = (uint8_t[]) {
|
||||
0x09, /* u8 bLength */
|
||||
0x21, /* u8 bDescriptorType */
|
||||
0x01, 0x10, /* u16 HID_class */
|
||||
0x00, /* u8 country_code */
|
||||
0x01, /* u8 num_descriptors */
|
||||
0x22, /* u8 type: Report */
|
||||
0x6e, 0, /* u16 len */
|
||||
},
|
||||
},
|
||||
},
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | 0x01,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = 8,
|
||||
.bInterval = 0x0a,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_wacom = {
|
||||
.bcdUSB = 0x0110,
|
||||
.bMaxPacketSize0 = 8,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 1,
|
||||
.bConfigurationValue = 1,
|
||||
.bmAttributes = 0x80,
|
||||
.bMaxPower = 40,
|
||||
.nif = 1,
|
||||
.ifs = &desc_iface_wacom,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDesc desc_wacom = {
|
||||
.id = {
|
||||
.idVendor = 0x056a,
|
||||
.idProduct = 0x0000,
|
||||
.bcdDevice = 0x4210,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = STR_PRODUCT,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device_wacom,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static void usb_mouse_event(void *opaque,
|
||||
int dx1, int dy1, int dz1, int buttons_state)
|
||||
{
|
||||
USBWacomState *s = opaque;
|
||||
|
||||
s->dx += dx1;
|
||||
s->dy += dy1;
|
||||
s->dz += dz1;
|
||||
s->buttons_state = buttons_state;
|
||||
s->changed = 1;
|
||||
}
|
||||
|
||||
static void usb_wacom_event(void *opaque,
|
||||
int x, int y, int dz, int buttons_state)
|
||||
{
|
||||
USBWacomState *s = opaque;
|
||||
|
||||
/* scale to Penpartner resolution */
|
||||
s->x = (x * 5040 / 0x7FFF);
|
||||
s->y = (y * 3780 / 0x7FFF);
|
||||
s->dz += dz;
|
||||
s->buttons_state = buttons_state;
|
||||
s->changed = 1;
|
||||
}
|
||||
|
||||
static inline int int_clamp(int val, int vmin, int vmax)
|
||||
{
|
||||
if (val < vmin)
|
||||
return vmin;
|
||||
else if (val > vmax)
|
||||
return vmax;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
static int usb_mouse_poll(USBWacomState *s, uint8_t *buf, int len)
|
||||
{
|
||||
int dx, dy, dz, b, l;
|
||||
|
||||
if (!s->mouse_grabbed) {
|
||||
s->eh_entry = qemu_add_mouse_event_handler(usb_mouse_event, s, 0,
|
||||
"QEMU PenPartner tablet");
|
||||
qemu_activate_mouse_event_handler(s->eh_entry);
|
||||
s->mouse_grabbed = 1;
|
||||
}
|
||||
|
||||
dx = int_clamp(s->dx, -128, 127);
|
||||
dy = int_clamp(s->dy, -128, 127);
|
||||
dz = int_clamp(s->dz, -128, 127);
|
||||
|
||||
s->dx -= dx;
|
||||
s->dy -= dy;
|
||||
s->dz -= dz;
|
||||
|
||||
b = 0;
|
||||
if (s->buttons_state & MOUSE_EVENT_LBUTTON)
|
||||
b |= 0x01;
|
||||
if (s->buttons_state & MOUSE_EVENT_RBUTTON)
|
||||
b |= 0x02;
|
||||
if (s->buttons_state & MOUSE_EVENT_MBUTTON)
|
||||
b |= 0x04;
|
||||
|
||||
buf[0] = b;
|
||||
buf[1] = dx;
|
||||
buf[2] = dy;
|
||||
l = 3;
|
||||
if (len >= 4) {
|
||||
buf[3] = dz;
|
||||
l = 4;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
static int usb_wacom_poll(USBWacomState *s, uint8_t *buf, int len)
|
||||
{
|
||||
int b;
|
||||
|
||||
if (!s->mouse_grabbed) {
|
||||
s->eh_entry = qemu_add_mouse_event_handler(usb_wacom_event, s, 1,
|
||||
"QEMU PenPartner tablet");
|
||||
qemu_activate_mouse_event_handler(s->eh_entry);
|
||||
s->mouse_grabbed = 1;
|
||||
}
|
||||
|
||||
b = 0;
|
||||
if (s->buttons_state & MOUSE_EVENT_LBUTTON)
|
||||
b |= 0x01;
|
||||
if (s->buttons_state & MOUSE_EVENT_RBUTTON)
|
||||
b |= 0x40;
|
||||
if (s->buttons_state & MOUSE_EVENT_MBUTTON)
|
||||
b |= 0x20; /* eraser */
|
||||
|
||||
if (len < 7)
|
||||
return 0;
|
||||
|
||||
buf[0] = s->mode;
|
||||
buf[5] = 0x00 | (b & 0xf0);
|
||||
buf[1] = s->x & 0xff;
|
||||
buf[2] = s->x >> 8;
|
||||
buf[3] = s->y & 0xff;
|
||||
buf[4] = s->y >> 8;
|
||||
if (b & 0x3f) {
|
||||
buf[6] = 0;
|
||||
} else {
|
||||
buf[6] = (unsigned char) -127;
|
||||
}
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
static void usb_wacom_handle_reset(USBDevice *dev)
|
||||
{
|
||||
USBWacomState *s = (USBWacomState *) dev;
|
||||
|
||||
s->dx = 0;
|
||||
s->dy = 0;
|
||||
s->dz = 0;
|
||||
s->x = 0;
|
||||
s->y = 0;
|
||||
s->buttons_state = 0;
|
||||
s->mode = WACOM_MODE_HID;
|
||||
}
|
||||
|
||||
static int usb_wacom_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
USBWacomState *s = (USBWacomState *) dev;
|
||||
int ret;
|
||||
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
switch (request) {
|
||||
case WACOM_SET_REPORT:
|
||||
if (s->mouse_grabbed) {
|
||||
qemu_remove_mouse_event_handler(s->eh_entry);
|
||||
s->mouse_grabbed = 0;
|
||||
}
|
||||
s->mode = data[0];
|
||||
ret = 0;
|
||||
break;
|
||||
case WACOM_GET_REPORT:
|
||||
data[0] = 0;
|
||||
data[1] = s->mode;
|
||||
ret = 2;
|
||||
break;
|
||||
/* USB HID requests */
|
||||
case HID_GET_REPORT:
|
||||
if (s->mode == WACOM_MODE_HID)
|
||||
ret = usb_mouse_poll(s, data, length);
|
||||
else if (s->mode == WACOM_MODE_WACOM)
|
||||
ret = usb_wacom_poll(s, data, length);
|
||||
break;
|
||||
case HID_GET_IDLE:
|
||||
ret = 1;
|
||||
data[0] = s->idle;
|
||||
break;
|
||||
case HID_SET_IDLE:
|
||||
s->idle = (uint8_t) (value >> 8);
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int usb_wacom_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBWacomState *s = (USBWacomState *) dev;
|
||||
uint8_t buf[p->iov.size];
|
||||
int ret = 0;
|
||||
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_IN:
|
||||
if (p->ep->nr == 1) {
|
||||
if (!(s->changed || s->idle))
|
||||
return USB_RET_NAK;
|
||||
s->changed = 0;
|
||||
if (s->mode == WACOM_MODE_HID)
|
||||
ret = usb_mouse_poll(s, buf, p->iov.size);
|
||||
else if (s->mode == WACOM_MODE_WACOM)
|
||||
ret = usb_wacom_poll(s, buf, p->iov.size);
|
||||
usb_packet_copy(p, buf, ret);
|
||||
break;
|
||||
}
|
||||
/* Fall through. */
|
||||
case USB_TOKEN_OUT:
|
||||
default:
|
||||
ret = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_wacom_handle_destroy(USBDevice *dev)
|
||||
{
|
||||
USBWacomState *s = (USBWacomState *) dev;
|
||||
|
||||
if (s->mouse_grabbed) {
|
||||
qemu_remove_mouse_event_handler(s->eh_entry);
|
||||
s->mouse_grabbed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int usb_wacom_initfn(USBDevice *dev)
|
||||
{
|
||||
USBWacomState *s = DO_UPCAST(USBWacomState, dev, dev);
|
||||
usb_desc_init(dev);
|
||||
s->changed = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_usb_wacom = {
|
||||
.name = "usb-wacom",
|
||||
.unmigratable = 1,
|
||||
};
|
||||
|
||||
static void usb_wacom_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->product_desc = "QEMU PenPartner Tablet";
|
||||
uc->usb_desc = &desc_wacom;
|
||||
uc->init = usb_wacom_initfn;
|
||||
uc->handle_reset = usb_wacom_handle_reset;
|
||||
uc->handle_control = usb_wacom_handle_control;
|
||||
uc->handle_data = usb_wacom_handle_data;
|
||||
uc->handle_destroy = usb_wacom_handle_destroy;
|
||||
dc->desc = "QEMU PenPartner Tablet";
|
||||
dc->vmsd = &vmstate_usb_wacom;
|
||||
}
|
||||
|
||||
static TypeInfo wacom_info = {
|
||||
.name = "usb-wacom-tablet",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBWacomState),
|
||||
.class_init = usb_wacom_class_init,
|
||||
};
|
||||
|
||||
static void usb_wacom_register_types(void)
|
||||
{
|
||||
type_register_static(&wacom_info);
|
||||
usb_legacy_register("usb-wacom-tablet", "wacom-tablet", NULL);
|
||||
}
|
||||
|
||||
type_init(usb_wacom_register_types)
|
2345
hw/usb/hcd-ehci.c
Normal file
2345
hw/usb/hcd-ehci.c
Normal file
File diff suppressed because it is too large
Load diff
1544
hw/usb/hcd-musb.c
Normal file
1544
hw/usb/hcd-musb.c
Normal file
File diff suppressed because it is too large
Load diff
1898
hw/usb/hcd-ohci.c
Normal file
1898
hw/usb/hcd-ohci.c
Normal file
File diff suppressed because it is too large
Load diff
1408
hw/usb/hcd-uhci.c
Normal file
1408
hw/usb/hcd-uhci.c
Normal file
File diff suppressed because it is too large
Load diff
2925
hw/usb/hcd-xhci.c
Normal file
2925
hw/usb/hcd-xhci.c
Normal file
File diff suppressed because it is too large
Load diff
647
hw/usb/host-bsd.c
Normal file
647
hw/usb/host-bsd.c
Normal file
|
@ -0,0 +1,647 @@
|
|||
/*
|
||||
* BSD host USB redirector
|
||||
*
|
||||
* Copyright (c) 2006 Lonnie Mendez
|
||||
* Portions of code and concepts borrowed from
|
||||
* usb-linux.c and libusb's bsd.c and are copyright their respective owners.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "monitor.h"
|
||||
#include "hw/usb.h"
|
||||
|
||||
/* usb.h declares these */
|
||||
#undef USB_SPEED_HIGH
|
||||
#undef USB_SPEED_FULL
|
||||
#undef USB_SPEED_LOW
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#ifndef __DragonFly__
|
||||
#include <dev/usb/usb.h>
|
||||
#else
|
||||
#include <bus/usb/usb.h>
|
||||
#endif
|
||||
|
||||
/* This value has maximum potential at 16.
|
||||
* You should also set hw.usb.debug to gain
|
||||
* more detailed view.
|
||||
*/
|
||||
//#define DEBUG
|
||||
#define UGEN_DEBUG_LEVEL 0
|
||||
|
||||
|
||||
typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
|
||||
int vendor_id, int product_id,
|
||||
const char *product_name, int speed);
|
||||
static int usb_host_find_device(int *pbus_num, int *paddr,
|
||||
const char *devname);
|
||||
|
||||
typedef struct USBHostDevice {
|
||||
USBDevice dev;
|
||||
int ep_fd[USB_MAX_ENDPOINTS];
|
||||
int devfd;
|
||||
char devpath[32];
|
||||
} USBHostDevice;
|
||||
|
||||
|
||||
static int ensure_ep_open(USBHostDevice *dev, int ep, int mode)
|
||||
{
|
||||
char buf[32];
|
||||
int fd;
|
||||
|
||||
/* Get the address for this endpoint */
|
||||
ep = UE_GET_ADDR(ep);
|
||||
|
||||
if (dev->ep_fd[ep] < 0) {
|
||||
#if defined(__FreeBSD__) || defined(__DragonFly__)
|
||||
snprintf(buf, sizeof(buf) - 1, "%s.%d", dev->devpath, ep);
|
||||
#else
|
||||
snprintf(buf, sizeof(buf) - 1, "%s.%02d", dev->devpath, ep);
|
||||
#endif
|
||||
/* Try to open it O_RDWR first for those devices which have in and out
|
||||
* endpoints with the same address (eg 0x02 and 0x82)
|
||||
*/
|
||||
fd = open(buf, O_RDWR);
|
||||
if (fd < 0 && errno == ENXIO)
|
||||
fd = open(buf, mode);
|
||||
if (fd < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("ensure_ep_open: failed to open device endpoint %s: %s\n",
|
||||
buf, strerror(errno));
|
||||
#endif
|
||||
}
|
||||
dev->ep_fd[ep] = fd;
|
||||
}
|
||||
|
||||
return dev->ep_fd[ep];
|
||||
}
|
||||
|
||||
static void ensure_eps_closed(USBHostDevice *dev)
|
||||
{
|
||||
int epnum = 1;
|
||||
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
while (epnum < USB_MAX_ENDPOINTS) {
|
||||
if (dev->ep_fd[epnum] >= 0) {
|
||||
close(dev->ep_fd[epnum]);
|
||||
dev->ep_fd[epnum] = -1;
|
||||
}
|
||||
epnum++;
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_host_handle_reset(USBDevice *dev)
|
||||
{
|
||||
#if 0
|
||||
USBHostDevice *s = (USBHostDevice *)dev;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* XXX:
|
||||
* -check device states against transfer requests
|
||||
* and return appropriate response
|
||||
*/
|
||||
static int usb_host_handle_control(USBDevice *dev,
|
||||
USBPacket *p,
|
||||
int request,
|
||||
int value,
|
||||
int index,
|
||||
int length,
|
||||
uint8_t *data)
|
||||
{
|
||||
USBHostDevice *s = (USBHostDevice *)dev;
|
||||
struct usb_ctl_request req;
|
||||
struct usb_alt_interface aiface;
|
||||
int ret, timeout = 50;
|
||||
|
||||
if ((request >> 8) == UT_WRITE_DEVICE &&
|
||||
(request & 0xff) == UR_SET_ADDRESS) {
|
||||
|
||||
/* specific SET_ADDRESS support */
|
||||
dev->addr = value;
|
||||
return 0;
|
||||
} else if ((request >> 8) == UT_WRITE_DEVICE &&
|
||||
(request & 0xff) == UR_SET_CONFIG) {
|
||||
|
||||
ensure_eps_closed(s); /* can't do this without all eps closed */
|
||||
|
||||
ret = ioctl(s->devfd, USB_SET_CONFIG, &value);
|
||||
if (ret < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("handle_control: failed to set configuration - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else if ((request >> 8) == UT_WRITE_INTERFACE &&
|
||||
(request & 0xff) == UR_SET_INTERFACE) {
|
||||
|
||||
aiface.uai_interface_index = index;
|
||||
aiface.uai_alt_no = value;
|
||||
|
||||
ensure_eps_closed(s); /* can't do this without all eps closed */
|
||||
ret = ioctl(s->devfd, USB_SET_ALTINTERFACE, &aiface);
|
||||
if (ret < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("handle_control: failed to set alternate interface - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
req.ucr_request.bmRequestType = request >> 8;
|
||||
req.ucr_request.bRequest = request & 0xff;
|
||||
USETW(req.ucr_request.wValue, value);
|
||||
USETW(req.ucr_request.wIndex, index);
|
||||
USETW(req.ucr_request.wLength, length);
|
||||
req.ucr_data = data;
|
||||
req.ucr_flags = USBD_SHORT_XFER_OK;
|
||||
|
||||
ret = ioctl(s->devfd, USB_SET_TIMEOUT, &timeout);
|
||||
#if defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
if (ret < 0 && errno != EINVAL) {
|
||||
#else
|
||||
if (ret < 0) {
|
||||
#endif
|
||||
#ifdef DEBUG
|
||||
printf("handle_control: setting timeout failed - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
}
|
||||
|
||||
ret = ioctl(s->devfd, USB_DO_REQUEST, &req);
|
||||
/* ugen returns EIO for usbd_do_request_ no matter what
|
||||
* happens with the transfer */
|
||||
if (ret < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("handle_control: error after request - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
return USB_RET_NAK; // STALL
|
||||
} else {
|
||||
return req.ucr_actlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
USBHostDevice *s = (USBHostDevice *)dev;
|
||||
int ret, fd, mode;
|
||||
int one = 1, shortpacket = 0, timeout = 50;
|
||||
sigset_t new_mask, old_mask;
|
||||
uint8_t devep = p->ep->nr;
|
||||
|
||||
/* protect data transfers from SIGALRM signal */
|
||||
sigemptyset(&new_mask);
|
||||
sigaddset(&new_mask, SIGALRM);
|
||||
sigprocmask(SIG_BLOCK, &new_mask, &old_mask);
|
||||
|
||||
if (p->pid == USB_TOKEN_IN) {
|
||||
devep |= 0x80;
|
||||
mode = O_RDONLY;
|
||||
shortpacket = 1;
|
||||
} else {
|
||||
mode = O_WRONLY;
|
||||
}
|
||||
|
||||
fd = ensure_ep_open(s, devep, mode);
|
||||
if (fd < 0) {
|
||||
sigprocmask(SIG_SETMASK, &old_mask, NULL);
|
||||
return USB_RET_NODEV;
|
||||
}
|
||||
|
||||
if (ioctl(fd, USB_SET_TIMEOUT, &timeout) < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("handle_data: failed to set timeout - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
}
|
||||
|
||||
if (shortpacket) {
|
||||
if (ioctl(fd, USB_SET_SHORT_XFER, &one) < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("handle_data: failed to set short xfer mode - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
sigprocmask(SIG_SETMASK, &old_mask, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (p->pid == USB_TOKEN_IN)
|
||||
ret = readv(fd, p->iov.iov, p->iov.niov);
|
||||
else
|
||||
ret = writev(fd, p->iov.iov, p->iov.niov);
|
||||
|
||||
sigprocmask(SIG_SETMASK, &old_mask, NULL);
|
||||
|
||||
if (ret < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("handle_data: error after %s data - %s\n",
|
||||
pid == USB_TOKEN_IN ? "reading" : "writing", strerror(errno));
|
||||
#endif
|
||||
switch(errno) {
|
||||
case ETIMEDOUT:
|
||||
case EINTR:
|
||||
return USB_RET_NAK;
|
||||
default:
|
||||
return USB_RET_STALL;
|
||||
}
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_host_handle_destroy(USBDevice *opaque)
|
||||
{
|
||||
USBHostDevice *s = (USBHostDevice *)opaque;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < USB_MAX_ENDPOINTS; i++)
|
||||
if (s->ep_fd[i] >= 0)
|
||||
close(s->ep_fd[i]);
|
||||
|
||||
if (s->devfd < 0)
|
||||
return;
|
||||
|
||||
close(s->devfd);
|
||||
|
||||
g_free(s);
|
||||
}
|
||||
|
||||
static int usb_host_initfn(USBDevice *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
USBDevice *usb_host_device_open(USBBus *guest_bus, const char *devname)
|
||||
{
|
||||
struct usb_device_info bus_info, dev_info;
|
||||
USBDevice *d = NULL, *ret = NULL;
|
||||
USBHostDevice *dev;
|
||||
char ctlpath[PATH_MAX + 1];
|
||||
char buspath[PATH_MAX + 1];
|
||||
int bfd, dfd, bus, address, i;
|
||||
int ugendebug = UGEN_DEBUG_LEVEL;
|
||||
|
||||
if (usb_host_find_device(&bus, &address, devname) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(buspath, PATH_MAX, "/dev/usb%d", bus);
|
||||
|
||||
bfd = open(buspath, O_RDWR);
|
||||
if (bfd < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("usb_host_device_open: failed to open usb bus - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bus_info.udi_addr = address;
|
||||
if (ioctl(bfd, USB_DEVICEINFO, &bus_info) < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("usb_host_device_open: failed to grab bus information - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
goto fail_bfd;
|
||||
}
|
||||
|
||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
|
||||
snprintf(ctlpath, PATH_MAX, "/dev/%s", bus_info.udi_devnames[0]);
|
||||
#else
|
||||
snprintf(ctlpath, PATH_MAX, "/dev/%s.00", bus_info.udi_devnames[0]);
|
||||
#endif
|
||||
|
||||
dfd = open(ctlpath, O_RDWR);
|
||||
if (dfd < 0) {
|
||||
dfd = open(ctlpath, O_RDONLY);
|
||||
if (dfd < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("usb_host_device_open: failed to open usb device %s - %s\n",
|
||||
ctlpath, strerror(errno));
|
||||
#endif
|
||||
}
|
||||
goto fail_dfd;
|
||||
}
|
||||
|
||||
if (ioctl(dfd, USB_GET_DEVICEINFO, &dev_info) < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("usb_host_device_open: failed to grab device info - %s\n",
|
||||
strerror(errno));
|
||||
#endif
|
||||
goto fail_dfd;
|
||||
}
|
||||
|
||||
d = usb_create(guest_bus, "usb-host");
|
||||
dev = DO_UPCAST(USBHostDevice, dev, d);
|
||||
|
||||
if (dev_info.udi_speed == 1) {
|
||||
dev->dev.speed = USB_SPEED_LOW - 1;
|
||||
dev->dev.speedmask = USB_SPEED_MASK_LOW;
|
||||
} else {
|
||||
dev->dev.speed = USB_SPEED_FULL - 1;
|
||||
dev->dev.speedmask = USB_SPEED_MASK_FULL;
|
||||
}
|
||||
|
||||
if (strncmp(dev_info.udi_product, "product", 7) != 0) {
|
||||
pstrcpy(dev->dev.product_desc, sizeof(dev->dev.product_desc),
|
||||
dev_info.udi_product);
|
||||
} else {
|
||||
snprintf(dev->dev.product_desc, sizeof(dev->dev.product_desc),
|
||||
"host:%s", devname);
|
||||
}
|
||||
|
||||
pstrcpy(dev->devpath, sizeof(dev->devpath), "/dev/");
|
||||
pstrcat(dev->devpath, sizeof(dev->devpath), dev_info.udi_devnames[0]);
|
||||
|
||||
/* Mark the endpoints as not yet open */
|
||||
for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
|
||||
dev->ep_fd[i] = -1;
|
||||
}
|
||||
|
||||
ioctl(dfd, USB_SETDEBUG, &ugendebug);
|
||||
|
||||
ret = (USBDevice *)dev;
|
||||
|
||||
fail_dfd:
|
||||
close(dfd);
|
||||
fail_bfd:
|
||||
close(bfd);
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_host_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->product_desc = "USB Host Device";
|
||||
uc->init = usb_host_initfn;
|
||||
uc->handle_reset = usb_host_handle_reset;
|
||||
uc->handle_control = usb_host_handle_control;
|
||||
uc->handle_data = usb_host_handle_data;
|
||||
uc->handle_destroy = usb_host_handle_destroy;
|
||||
}
|
||||
|
||||
static TypeInfo usb_host_dev_info = {
|
||||
.name = "usb-host",
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(USBHostDevice),
|
||||
.class_init = usb_host_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_host_register_types(void)
|
||||
{
|
||||
type_register_static(&usb_host_dev_info);
|
||||
}
|
||||
|
||||
type_init(usb_host_register_types)
|
||||
|
||||
static int usb_host_scan(void *opaque, USBScanFunc *func)
|
||||
{
|
||||
struct usb_device_info bus_info;
|
||||
struct usb_device_info dev_info;
|
||||
uint16_t vendor_id, product_id, class_id, speed;
|
||||
int bfd, dfd, bus, address;
|
||||
char busbuf[20], devbuf[20], product_name[256];
|
||||
int ret = 0;
|
||||
|
||||
for (bus = 0; bus < 10; bus++) {
|
||||
|
||||
snprintf(busbuf, sizeof(busbuf) - 1, "/dev/usb%d", bus);
|
||||
bfd = open(busbuf, O_RDWR);
|
||||
if (bfd < 0)
|
||||
continue;
|
||||
|
||||
for (address = 1; address < 127; address++) {
|
||||
|
||||
bus_info.udi_addr = address;
|
||||
if (ioctl(bfd, USB_DEVICEINFO, &bus_info) < 0)
|
||||
continue;
|
||||
|
||||
/* only list devices that can be used by generic layer */
|
||||
if (strncmp(bus_info.udi_devnames[0], "ugen", 4) != 0)
|
||||
continue;
|
||||
|
||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
|
||||
snprintf(devbuf, sizeof(devbuf) - 1, "/dev/%s", bus_info.udi_devnames[0]);
|
||||
#else
|
||||
snprintf(devbuf, sizeof(devbuf) - 1, "/dev/%s.00", bus_info.udi_devnames[0]);
|
||||
#endif
|
||||
|
||||
dfd = open(devbuf, O_RDONLY);
|
||||
if (dfd < 0) {
|
||||
#ifdef DEBUG
|
||||
printf("usb_host_scan: couldn't open device %s - %s\n", devbuf,
|
||||
strerror(errno));
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ioctl(dfd, USB_GET_DEVICEINFO, &dev_info) < 0)
|
||||
printf("usb_host_scan: couldn't get device information for %s - %s\n",
|
||||
devbuf, strerror(errno));
|
||||
|
||||
/* XXX: might need to fixup endianness of word values before copying over */
|
||||
|
||||
vendor_id = dev_info.udi_vendorNo;
|
||||
product_id = dev_info.udi_productNo;
|
||||
class_id = dev_info.udi_class;
|
||||
speed = dev_info.udi_speed;
|
||||
|
||||
if (strncmp(dev_info.udi_product, "product", 7) != 0)
|
||||
pstrcpy(product_name, sizeof(product_name),
|
||||
dev_info.udi_product);
|
||||
else
|
||||
product_name[0] = '\0';
|
||||
|
||||
ret = func(opaque, bus, address, class_id, vendor_id,
|
||||
product_id, product_name, speed);
|
||||
|
||||
close(dfd);
|
||||
|
||||
if (ret)
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
close(bfd);
|
||||
}
|
||||
|
||||
the_end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef struct FindDeviceState {
|
||||
int vendor_id;
|
||||
int product_id;
|
||||
int bus_num;
|
||||
int addr;
|
||||
} FindDeviceState;
|
||||
|
||||
static int usb_host_find_device_scan(void *opaque, int bus_num, int addr,
|
||||
int class_id,
|
||||
int vendor_id, int product_id,
|
||||
const char *product_name, int speed)
|
||||
{
|
||||
FindDeviceState *s = opaque;
|
||||
if (vendor_id == s->vendor_id &&
|
||||
product_id == s->product_id) {
|
||||
s->bus_num = bus_num;
|
||||
s->addr = addr;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* the syntax is :
|
||||
'bus.addr' (decimal numbers) or
|
||||
'vendor_id:product_id' (hexa numbers) */
|
||||
static int usb_host_find_device(int *pbus_num, int *paddr,
|
||||
const char *devname)
|
||||
{
|
||||
const char *p;
|
||||
int ret;
|
||||
FindDeviceState fs;
|
||||
|
||||
p = strchr(devname, '.');
|
||||
if (p) {
|
||||
*pbus_num = strtoul(devname, NULL, 0);
|
||||
*paddr = strtoul(p + 1, NULL, 0);
|
||||
return 0;
|
||||
}
|
||||
p = strchr(devname, ':');
|
||||
if (p) {
|
||||
fs.vendor_id = strtoul(devname, NULL, 16);
|
||||
fs.product_id = strtoul(p + 1, NULL, 16);
|
||||
ret = usb_host_scan(&fs, usb_host_find_device_scan);
|
||||
if (ret) {
|
||||
*pbus_num = fs.bus_num;
|
||||
*paddr = fs.addr;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**********************/
|
||||
/* USB host device info */
|
||||
|
||||
struct usb_class_info {
|
||||
int class;
|
||||
const char *class_name;
|
||||
};
|
||||
|
||||
static const struct usb_class_info usb_class_info[] = {
|
||||
{ USB_CLASS_AUDIO, "Audio"},
|
||||
{ USB_CLASS_COMM, "Communication"},
|
||||
{ USB_CLASS_HID, "HID"},
|
||||
{ USB_CLASS_HUB, "Hub" },
|
||||
{ USB_CLASS_PHYSICAL, "Physical" },
|
||||
{ USB_CLASS_PRINTER, "Printer" },
|
||||
{ USB_CLASS_MASS_STORAGE, "Storage" },
|
||||
{ USB_CLASS_CDC_DATA, "Data" },
|
||||
{ USB_CLASS_APP_SPEC, "Application Specific" },
|
||||
{ USB_CLASS_VENDOR_SPEC, "Vendor Specific" },
|
||||
{ USB_CLASS_STILL_IMAGE, "Still Image" },
|
||||
{ USB_CLASS_CSCID, "Smart Card" },
|
||||
{ USB_CLASS_CONTENT_SEC, "Content Security" },
|
||||
{ -1, NULL }
|
||||
};
|
||||
|
||||
static const char *usb_class_str(uint8_t class)
|
||||
{
|
||||
const struct usb_class_info *p;
|
||||
for (p = usb_class_info; p->class != -1; p++) {
|
||||
if (p->class == class)
|
||||
break;
|
||||
}
|
||||
return p->class_name;
|
||||
}
|
||||
|
||||
static void usb_info_device(Monitor *mon, int bus_num, int addr, int class_id,
|
||||
int vendor_id, int product_id,
|
||||
const char *product_name,
|
||||
int speed)
|
||||
{
|
||||
const char *class_str, *speed_str;
|
||||
|
||||
switch(speed) {
|
||||
case USB_SPEED_LOW:
|
||||
speed_str = "1.5";
|
||||
break;
|
||||
case USB_SPEED_FULL:
|
||||
speed_str = "12";
|
||||
break;
|
||||
case USB_SPEED_HIGH:
|
||||
speed_str = "480";
|
||||
break;
|
||||
default:
|
||||
speed_str = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
monitor_printf(mon, " Device %d.%d, speed %s Mb/s\n",
|
||||
bus_num, addr, speed_str);
|
||||
class_str = usb_class_str(class_id);
|
||||
if (class_str)
|
||||
monitor_printf(mon, " %s:", class_str);
|
||||
else
|
||||
monitor_printf(mon, " Class %02x:", class_id);
|
||||
monitor_printf(mon, " USB device %04x:%04x", vendor_id, product_id);
|
||||
if (product_name[0] != '\0')
|
||||
monitor_printf(mon, ", %s", product_name);
|
||||
monitor_printf(mon, "\n");
|
||||
}
|
||||
|
||||
static int usb_host_info_device(void *opaque,
|
||||
int bus_num, int addr,
|
||||
int class_id,
|
||||
int vendor_id, int product_id,
|
||||
const char *product_name,
|
||||
int speed)
|
||||
{
|
||||
Monitor *mon = opaque;
|
||||
|
||||
usb_info_device(mon, bus_num, addr, class_id, vendor_id, product_id,
|
||||
product_name, speed);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void usb_host_info(Monitor *mon)
|
||||
{
|
||||
usb_host_scan(mon, usb_host_info_device);
|
||||
}
|
||||
|
||||
/* XXX add this */
|
||||
int usb_host_device_close(const char *devname)
|
||||
{
|
||||
return 0;
|
||||
}
|
1913
hw/usb/host-linux.c
Normal file
1913
hw/usb/host-linux.c
Normal file
File diff suppressed because it is too large
Load diff
52
hw/usb/host-stub.c
Normal file
52
hw/usb/host-stub.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Stub host USB redirector
|
||||
*
|
||||
* Copyright (c) 2005 Fabrice Bellard
|
||||
*
|
||||
* Copyright (c) 2008 Max Krasnyansky
|
||||
* Support for host device auto connect & disconnect
|
||||
* Major rewrite to support fully async operation
|
||||
*
|
||||
* Copyright 2008 TJ <linux@tjworld.net>
|
||||
* Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition
|
||||
* to the legacy /proc/bus/usb USB device discovery and handling
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "console.h"
|
||||
#include "hw/usb.h"
|
||||
#include "monitor.h"
|
||||
|
||||
void usb_host_info(Monitor *mon)
|
||||
{
|
||||
monitor_printf(mon, "USB host devices not supported\n");
|
||||
}
|
||||
|
||||
/* XXX: modify configure to compile the right host driver */
|
||||
USBDevice *usb_host_device_open(USBBus *bus, const char *devname)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int usb_host_device_close(const char *devname)
|
||||
{
|
||||
return 0;
|
||||
}
|
63
hw/usb/libhw.c
Normal file
63
hw/usb/libhw.c
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* QEMU USB emulation, libhw bits.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "qemu-common.h"
|
||||
#include "cpu-common.h"
|
||||
#include "hw/usb.h"
|
||||
#include "dma.h"
|
||||
|
||||
int usb_packet_map(USBPacket *p, QEMUSGList *sgl)
|
||||
{
|
||||
int is_write = (p->pid == USB_TOKEN_IN);
|
||||
target_phys_addr_t len;
|
||||
void *mem;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sgl->nsg; i++) {
|
||||
len = sgl->sg[i].len;
|
||||
mem = cpu_physical_memory_map(sgl->sg[i].base, &len,
|
||||
is_write);
|
||||
if (!mem) {
|
||||
goto err;
|
||||
}
|
||||
qemu_iovec_add(&p->iov, mem, len);
|
||||
if (len != sgl->sg[i].len) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
err:
|
||||
usb_packet_unmap(p);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void usb_packet_unmap(USBPacket *p)
|
||||
{
|
||||
int is_write = (p->pid == USB_TOKEN_IN);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < p->iov.niov; i++) {
|
||||
cpu_physical_memory_unmap(p->iov.iov[i].iov_base,
|
||||
p->iov.iov[i].iov_len, is_write,
|
||||
p->iov.iov[i].iov_len);
|
||||
}
|
||||
}
|
1485
hw/usb/redirect.c
Normal file
1485
hw/usb/redirect.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue