mirror of
https://git.proxmox.com/git/qemu
synced 2025-07-19 12:00:05 +00:00

Add a path string to USBPort. Add usb_port_location() function to set the physical location of the usb port. Update all drivers implementing usb ports to call it. Update the monitor commands to print it. Wind it up in qdev. Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
376 lines
9.0 KiB
C
376 lines
9.0 KiB
C
#include "hw.h"
|
|
#include "usb.h"
|
|
#include "qdev.h"
|
|
#include "sysemu.h"
|
|
#include "monitor.h"
|
|
|
|
static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent);
|
|
|
|
static char *usb_get_dev_path(DeviceState *dev);
|
|
static char *usbbus_get_fw_dev_path(DeviceState *dev);
|
|
|
|
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 = usbbus_get_fw_dev_path,
|
|
};
|
|
static int next_usb_bus = 0;
|
|
static QTAILQ_HEAD(, USBBus) busses = QTAILQ_HEAD_INITIALIZER(busses);
|
|
|
|
void usb_bus_new(USBBus *bus, DeviceState *host)
|
|
{
|
|
qbus_create_inplace(&bus->qbus, &usb_bus_info, host, NULL);
|
|
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_qdev_init(DeviceState *qdev, DeviceInfo *base)
|
|
{
|
|
USBDevice *dev = DO_UPCAST(USBDevice, qdev, qdev);
|
|
USBDeviceInfo *info = DO_UPCAST(USBDeviceInfo, qdev, base);
|
|
int rc;
|
|
|
|
pstrcpy(dev->product_desc, sizeof(dev->product_desc), info->product_desc);
|
|
dev->info = info;
|
|
dev->auto_attach = 1;
|
|
QLIST_INIT(&dev->strings);
|
|
rc = dev->info->init(dev);
|
|
if (rc == 0 && dev->auto_attach)
|
|
usb_device_attach(dev);
|
|
return rc;
|
|
}
|
|
|
|
static int usb_qdev_exit(DeviceState *qdev)
|
|
{
|
|
USBDevice *dev = DO_UPCAST(USBDevice, qdev, qdev);
|
|
|
|
usb_device_detach(dev);
|
|
if (dev->info->handle_destroy) {
|
|
dev->info->handle_destroy(dev);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void usb_qdev_register(USBDeviceInfo *info)
|
|
{
|
|
info->qdev.bus_info = &usb_bus_info;
|
|
info->qdev.init = usb_qdev_init;
|
|
info->qdev.unplug = qdev_simple_unplug_cb;
|
|
info->qdev.exit = usb_qdev_exit;
|
|
qdev_register(&info->qdev);
|
|
}
|
|
|
|
void usb_qdev_register_many(USBDeviceInfo *info)
|
|
{
|
|
while (info->qdev.name) {
|
|
usb_qdev_register(info);
|
|
info++;
|
|
}
|
|
}
|
|
|
|
USBDevice *usb_create(USBBus *bus, const char *name)
|
|
{
|
|
DeviceState *dev;
|
|
|
|
#if 1
|
|
/* temporary stopgap until all usb is properly qdev-ified */
|
|
if (!bus) {
|
|
bus = usb_bus_find(-1);
|
|
if (!bus)
|
|
return NULL;
|
|
fprintf(stderr, "%s: no bus specified, using \"%s\" for \"%s\"\n",
|
|
__FUNCTION__, bus->qbus.name, name);
|
|
}
|
|
#endif
|
|
|
|
dev = qdev_create(&bus->qbus, name);
|
|
return DO_UPCAST(USBDevice, qdev, dev);
|
|
}
|
|
|
|
USBDevice *usb_create_simple(USBBus *bus, const char *name)
|
|
{
|
|
USBDevice *dev = usb_create(bus, name);
|
|
if (!dev) {
|
|
hw_error("Failed to create USB device '%s'\n", name);
|
|
}
|
|
qdev_init_nofail(&dev->qdev);
|
|
return dev;
|
|
}
|
|
|
|
void usb_register_port(USBBus *bus, USBPort *port, void *opaque, int index,
|
|
USBDevice *pdev, USBPortOps *ops, int speedmask)
|
|
{
|
|
port->opaque = opaque;
|
|
port->index = index;
|
|
port->pdev = pdev;
|
|
port->opaque = opaque;
|
|
port->index = index;
|
|
port->ops = ops;
|
|
port->speedmask = speedmask;
|
|
QTAILQ_INSERT_TAIL(&bus->free, port, next);
|
|
bus->nfree++;
|
|
}
|
|
|
|
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--;
|
|
}
|
|
|
|
static void do_attach(USBDevice *dev)
|
|
{
|
|
USBBus *bus = usb_bus_from_device(dev);
|
|
USBPort *port;
|
|
|
|
if (dev->attached) {
|
|
fprintf(stderr, "Warning: tried to attach usb device %s twice\n",
|
|
dev->product_desc);
|
|
return;
|
|
}
|
|
dev->attached++;
|
|
|
|
port = QTAILQ_FIRST(&bus->free);
|
|
QTAILQ_REMOVE(&bus->free, port, next);
|
|
bus->nfree--;
|
|
|
|
usb_attach(port, dev);
|
|
|
|
QTAILQ_INSERT_TAIL(&bus->used, port, next);
|
|
bus->nused++;
|
|
}
|
|
|
|
int usb_device_attach(USBDevice *dev)
|
|
{
|
|
USBBus *bus = usb_bus_from_device(dev);
|
|
|
|
if (bus->nfree == 1) {
|
|
/* Create a new hub and chain it on. */
|
|
usb_create_simple(bus, "usb-hub");
|
|
}
|
|
do_attach(dev);
|
|
return 0;
|
|
}
|
|
|
|
int usb_device_detach(USBDevice *dev)
|
|
{
|
|
USBBus *bus = usb_bus_from_device(dev);
|
|
USBPort *port;
|
|
|
|
if (!dev->attached) {
|
|
fprintf(stderr, "Warning: tried to detach unattached usb device %s\n",
|
|
dev->product_desc);
|
|
return -1;
|
|
}
|
|
dev->attached--;
|
|
|
|
QTAILQ_FOREACH(port, &bus->used, next) {
|
|
if (port->dev == dev)
|
|
break;
|
|
}
|
|
assert(port != NULL);
|
|
|
|
QTAILQ_REMOVE(&bus->used, port, next);
|
|
bus->nused--;
|
|
|
|
usb_attach(port, NULL);
|
|
|
|
QTAILQ_INSERT_TAIL(&bus->free, port, next);
|
|
bus->nfree++;
|
|
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",
|
|
};
|
|
if (speed >= ARRAY_SIZE(txt))
|
|
return "?";
|
|
return txt[speed];
|
|
}
|
|
|
|
static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent)
|
|
{
|
|
USBDevice *dev = DO_UPCAST(USBDevice, qdev, 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 = DO_UPCAST(USBDevice, qdev, qdev);
|
|
return qemu_strdup(dev->port->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 */);
|
|
DeviceInfo *info;
|
|
USBDeviceInfo *usb;
|
|
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 (info = device_info_list; info != NULL; info = info->next) {
|
|
if (info->bus_info != &usb_bus_info)
|
|
continue;
|
|
usb = DO_UPCAST(USBDeviceInfo, qdev, info);
|
|
if (usb->usbdevice_name == NULL)
|
|
continue;
|
|
if (strcmp(usb->usbdevice_name, driver) != 0)
|
|
continue;
|
|
break;
|
|
}
|
|
if (info == NULL) {
|
|
#if 0
|
|
/* no error because some drivers are not converted (yet) */
|
|
error_report("usbdevice %s not found", driver);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
if (!usb->usbdevice_init) {
|
|
if (*params) {
|
|
error_report("usbdevice %s accepts no params", driver);
|
|
return NULL;
|
|
}
|
|
return usb_create_simple(bus, usb->qdev.name);
|
|
}
|
|
return usb->usbdevice_init(params);
|
|
}
|
|
|
|
static int usbbus_get_fw_dev_path_helper(USBDevice *d, USBBus *bus, char *p,
|
|
int len)
|
|
{
|
|
int l = 0;
|
|
USBPort *port;
|
|
|
|
QTAILQ_FOREACH(port, &bus->used, next) {
|
|
if (port->dev == d) {
|
|
if (port->pdev) {
|
|
l = usbbus_get_fw_dev_path_helper(port->pdev, bus, p, len);
|
|
}
|
|
l += snprintf(p + l, len - l, "%s@%x/", qdev_fw_name(&d->qdev),
|
|
port->index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
static char *usbbus_get_fw_dev_path(DeviceState *dev)
|
|
{
|
|
USBDevice *d = (USBDevice*)dev;
|
|
USBBus *bus = usb_bus_from_device(d);
|
|
char path[100];
|
|
int l;
|
|
|
|
assert(d->attached != 0);
|
|
|
|
l = usbbus_get_fw_dev_path_helper(d, bus, path, sizeof(path));
|
|
|
|
if (l == 0) {
|
|
abort();
|
|
}
|
|
|
|
path[l-1] = '\0';
|
|
|
|
return strdup(path);
|
|
}
|