Commit 1cc403eb authored by Gerd Hoffmann's avatar Gerd Hoffmann
Browse files

usb-hub: emulate per port power switching



Add support for per port power switching.
Virtual power of course ;)

Use port-power=on property to enable this.

Signed-off-by: default avatarGerd Hoffmann <kraxel@redhat.com>
Message-id: 20190524070310.4952-6-kraxel@redhat.com
parent 638ac2d8
Loading
Loading
Loading
Loading
+63 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu-common.h"
#include "qemu/timer.h"
#include "trace.h"
#include "hw/usb.h"
#include "desc.h"
@@ -41,6 +42,8 @@ typedef struct USBHubState {
    USBDevice dev;
    USBEndpoint *intr;
    uint32_t num_ports;
    bool port_power;
    QEMUTimer *port_timer;
    USBHubPort ports[MAX_PORTS];
} USBHubState;

@@ -203,6 +206,20 @@ static bool usb_hub_port_update(USBHubPort *port)
    return notify;
}

static void usb_hub_port_update_timer(void *opaque)
{
    USBHubState *s = opaque;
    bool notify = false;
    int i;

    for (i = 0; i < s->num_ports; i++) {
        notify |= usb_hub_port_update(&s->ports[i]);
    }
    if (notify) {
        usb_wakeup(s->intr, 0);
    }
}

static void usb_hub_attach(USBPort *port1)
{
    USBHubState *s = port1->opaque;
@@ -405,6 +422,11 @@ static void usb_hub_handle_control(USBDevice *dev, USBPacket *p,
                usb_wakeup(s->intr, 0);
                break;
            case PORT_POWER:
                if (s->port_power) {
                    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
                    usb_hub_port_set(port, PORT_STAT_POWER);
                    timer_mod(s->port_timer, now + 5000000); /* 5 ms */
                }
                break;
            default:
                goto fail;
@@ -445,6 +467,14 @@ static void usb_hub_handle_control(USBDevice *dev, USBPacket *p,
            case PORT_C_RESET:
                port->wPortChange &= ~PORT_STAT_C_RESET;
                break;
            case PORT_POWER:
                if (s->port_power) {
                    usb_hub_port_clear(port, PORT_STAT_POWER);
                    usb_hub_port_clear(port, PORT_STAT_CONNECTION);
                    usb_hub_port_clear(port, PORT_STAT_ENABLE);
                    usb_hub_port_clear(port, PORT_STAT_SUSPEND);
                    port->wPortChange = 0;
                }
            default:
                goto fail;
            }
@@ -457,6 +487,11 @@ static void usb_hub_handle_control(USBDevice *dev, USBPacket *p,
                   sizeof(qemu_hub_hub_descriptor));
            data[2] = s->num_ports;

            if (s->port_power) {
                data[3] &= ~0x03;
                data[3] |= 0x01;
            }

            /* fill DeviceRemovable bits */
            limit = DIV_ROUND_UP(s->num_ports + 1, 8) + 7;
            for (n = 7; n < limit; n++) {
@@ -536,6 +571,9 @@ static void usb_hub_unrealize(USBDevice *dev, Error **errp)
        usb_unregister_port(usb_bus_from_device(dev),
                            &s->ports[i].port);
    }

    timer_del(s->port_timer);
    timer_free(s->port_timer);
}

static USBPortOps usb_hub_port_ops = {
@@ -565,6 +603,8 @@ static void usb_hub_realize(USBDevice *dev, Error **errp)

    usb_desc_create_serial(dev);
    usb_desc_init(dev);
    s->port_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
                                 usb_hub_port_update_timer, s);
    s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
    for (i = 0; i < s->num_ports; i++) {
        port = &s->ports[i];
@@ -587,6 +627,24 @@ static const VMStateDescription vmstate_usb_hub_port = {
    }
};

static bool usb_hub_port_timer_needed(void *opaque)
{
    USBHubState *s = opaque;

    return s->port_power;
}

static const VMStateDescription vmstate_usb_hub_port_timer = {
    .name = "usb-hub/port-timer",
    .version_id = 1,
    .minimum_version_id = 1,
    .needed = usb_hub_port_timer_needed,
    .fields = (VMStateField[]) {
        VMSTATE_TIMER_PTR(port_timer, USBHubState),
        VMSTATE_END_OF_LIST()
    },
};

static const VMStateDescription vmstate_usb_hub = {
    .name = "usb-hub",
    .version_id = 1,
@@ -596,11 +654,16 @@ static const VMStateDescription vmstate_usb_hub = {
        VMSTATE_STRUCT_ARRAY(ports, USBHubState, MAX_PORTS, 0,
                             vmstate_usb_hub_port, USBHubPort),
        VMSTATE_END_OF_LIST()
    },
    .subsections = (const VMStateDescription * []) {
        &vmstate_usb_hub_port_timer,
        NULL
    }
};

static Property usb_hub_properties[] = {
    DEFINE_PROP_UINT32("ports", USBHubState, num_ports, 8),
    DEFINE_PROP_BOOL("port-power", USBHubState, port_power, false),
    DEFINE_PROP_END_OF_LIST(),
};