diff --git a/hw/usb/core.c b/hw/usb/core.c index 975f76250a1a34b79252975b13724368577240f4..51b36126cab25b5c9854f157b09ce14c233b2e51 100644 --- a/hw/usb/core.c +++ b/hw/usb/core.c @@ -87,7 +87,7 @@ void usb_device_reset(USBDevice *dev) return; } usb_device_handle_reset(dev); - dev->remote_wakeup = 0; + dev->remote_wakeup &= ~USB_DEVICE_REMOTE_WAKEUP; dev->addr = 0; dev->state = USB_STATE_DEFAULT; } @@ -105,7 +105,8 @@ void usb_wakeup(USBEndpoint *ep, unsigned int stream) */ return; } - if (dev->remote_wakeup && dev->port && dev->port->ops->wakeup) { + if ((dev->remote_wakeup & USB_DEVICE_REMOTE_WAKEUP) + && dev->port && dev->port->ops->wakeup) { dev->port->ops->wakeup(dev->port); } if (bus->ops->wakeup_endpoint) { diff --git a/hw/usb/desc.c b/hw/usb/desc.c index 8b6eaea4079e24a7488fb4fcc7b8f36a65585381..78bbe74c713258d4080b4d8127eb0b07d0d29fd3 100644 --- a/hw/usb/desc.c +++ b/hw/usb/desc.c @@ -751,7 +751,7 @@ int usb_desc_handle_control(USBDevice *dev, USBPacket *p, if (config->bmAttributes & USB_CFG_ATT_SELFPOWER) { data[0] |= 1 << USB_DEVICE_SELF_POWERED; } - if (dev->remote_wakeup) { + if (dev->remote_wakeup & USB_DEVICE_REMOTE_WAKEUP) { data[0] |= 1 << USB_DEVICE_REMOTE_WAKEUP; } data[1] = 0x00; @@ -761,14 +761,15 @@ int usb_desc_handle_control(USBDevice *dev, USBPacket *p, } case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: if (value == USB_DEVICE_REMOTE_WAKEUP) { - dev->remote_wakeup = 0; + dev->remote_wakeup &= ~USB_DEVICE_REMOTE_WAKEUP; ret = 0; } trace_usb_clear_device_feature(dev->addr, value, ret); break; case DeviceOutRequest | USB_REQ_SET_FEATURE: + dev->remote_wakeup |= USB_DEVICE_REMOTE_WAKEUP_IS_SUPPORTED; if (value == USB_DEVICE_REMOTE_WAKEUP) { - dev->remote_wakeup = 1; + dev->remote_wakeup |= USB_DEVICE_REMOTE_WAKEUP; ret = 0; } trace_usb_set_device_feature(dev->addr, value, ret); diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c index 1c7ae97c3033442dba820db492bdd04cba7c6299..9fb89f6955c395067b6549c9b028b63372d17794 100644 --- a/hw/usb/dev-hid.c +++ b/hw/usb/dev-hid.c @@ -745,7 +745,7 @@ static int usb_ptr_post_load(void *opaque, int version_id) { USBHIDState *s = opaque; - if (s->dev.remote_wakeup) { + if (s->dev.remote_wakeup & USB_DEVICE_REMOTE_WAKEUP) { hid_pointer_activate(&s->hid); } return 0; diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c index d1b5657d722a3a9463ab78ffd60d677c6bd2f57e..693c68f445bde69836eaa9813f46a9b1e6bd8120 100644 --- a/hw/usb/hcd-uhci.c +++ b/hw/usb/hcd-uhci.c @@ -44,6 +44,8 @@ #include "hcd-uhci.h" #define FRAME_TIMER_FREQ 1000 +#define FRAME_TIMER_FREQ_LAZY 10 +#define USB_DEVICE_NEED_NORMAL_FREQ "QEMU USB Tablet" #define FRAME_MAX_LOOPS 256 @@ -111,6 +113,22 @@ static void uhci_async_cancel(UHCIAsync *async); static void uhci_queue_fill(UHCIQueue *q, UHCI_TD *td); static void uhci_resume(void *opaque); +static int64_t uhci_frame_timer_freq = FRAME_TIMER_FREQ_LAZY; + +static void uhci_set_frame_freq(int freq) +{ + if (freq <= 0) { + return; + } + + uhci_frame_timer_freq = freq; +} + +static qemu_usb_controller qemu_uhci = { + .name = "uhci", + .qemu_set_freq = uhci_set_frame_freq, +}; + static inline int32_t uhci_queue_token(UHCI_TD *td) { if ((td->token & (0xf << 15)) == 0) { @@ -353,7 +371,7 @@ static int uhci_post_load(void *opaque, int version_id) if (version_id < 2) { s->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + - (NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ); + (NANOSECONDS_PER_SECOND / uhci_frame_timer_freq); } return 0; } @@ -394,8 +412,29 @@ static void uhci_port_write(void *opaque, hwaddr addr, if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) { /* start frame processing */ trace_usb_uhci_schedule_start(); - s->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + - (NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ); + + /* + * If the frequency of frame_timer is too slow, Guest OS (Win2012) would become + * blue-screen after hotplugging some vcpus. + * If this USB device support the remote-wakeup, the UHCI controller + * will enter global suspend mode when there is no input for several seconds. + * In this case, Qemu will delete the frame_timer. Since the frame_timer has been deleted, + * there is no influence to the performance of Vms. So, we can change the frequency to 1000. + * After that the frequency will be safe when we trigger the frame_timer again. + * Excepting this, there are two ways to change the frequency: + * 1)VNC connect/disconnect;2)attach/detach USB device. + */ + if ((uhci_frame_timer_freq != FRAME_TIMER_FREQ) + && (s->ports[0].port.dev) + && (!memcmp(s->ports[0].port.dev->product_desc, + USB_DEVICE_NEED_NORMAL_FREQ, strlen(USB_DEVICE_NEED_NORMAL_FREQ))) + && (s->ports[0].port.dev->remote_wakeup & USB_DEVICE_REMOTE_WAKEUP_IS_SUPPORTED)) { + qemu_log("turn up the frequency of UHCI controller to %d\n", FRAME_TIMER_FREQ); + uhci_frame_timer_freq = FRAME_TIMER_FREQ; + } + + s->frame_time = NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ; + s->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->frame_time; timer_mod(s->frame_timer, s->expire_time); s->status &= ~UHCI_STS_HCHALTED; } else if (!(val & UHCI_CMD_RS)) { @@ -1083,7 +1122,6 @@ static void uhci_frame_timer(void *opaque) UHCIState *s = opaque; uint64_t t_now, t_last_run; int i, frames; - const uint64_t frame_t = NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ; s->completions_only = false; qemu_bh_cancel(s->bh); @@ -1099,14 +1137,14 @@ static void uhci_frame_timer(void *opaque) } /* We still store expire_time in our state, for migration */ - t_last_run = s->expire_time - frame_t; + t_last_run = s->expire_time - s->frame_time; t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); /* Process up to MAX_FRAMES_PER_TICK frames */ - frames = (t_now - t_last_run) / frame_t; + frames = (t_now - t_last_run) / s->frame_time; if (frames > s->maxframes) { int skipped = frames - s->maxframes; - s->expire_time += skipped * frame_t; + s->expire_time += skipped * s->frame_time; s->frnum = (s->frnum + skipped) & 0x7ff; frames -= skipped; } @@ -1123,7 +1161,7 @@ static void uhci_frame_timer(void *opaque) /* The spec says frnum is the frame currently being processed, and * the guest must look at frnum - 1 on interrupt, so inc frnum now */ s->frnum = (s->frnum + 1) & 0x7ff; - s->expire_time += frame_t; + s->expire_time += s->frame_time; } /* Complete the previous frame(s) */ @@ -1134,7 +1172,12 @@ static void uhci_frame_timer(void *opaque) } s->pending_int_mask = 0; - timer_mod(s->frame_timer, t_now + frame_t); + /* expire_time is calculated from last frame_time, we should calculate it + * according to new frame_time which equals to + * NANOSECONDS_PER_SECOND / uhci_frame_timer_freq */ + s->expire_time -= s->frame_time - NANOSECONDS_PER_SECOND / uhci_frame_timer_freq; + s->frame_time = NANOSECONDS_PER_SECOND / uhci_frame_timer_freq; + timer_mod(s->frame_timer, t_now + s->frame_time); } static const MemoryRegionOps uhci_ioport_ops = { @@ -1196,8 +1239,10 @@ void usb_uhci_common_realize(PCIDevice *dev, Error **errp) s->bh = qemu_bh_new(uhci_bh, s); s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, uhci_frame_timer, s); s->num_ports_vmstate = NB_PORTS; + s->frame_time = NANOSECONDS_PER_SECOND / uhci_frame_timer_freq; QTAILQ_INIT(&s->queues); + qemu_register_usb_controller(&qemu_uhci, QEMU_USB_CONTROLLER_UHCI); memory_region_init_io(&s->io_bar, OBJECT(s), &uhci_ioport_ops, s, "uhci", 0x20); diff --git a/hw/usb/hcd-uhci.h b/hw/usb/hcd-uhci.h index c85ab7868eee496323a92fd51942cb4e1429c5fc..5194d22ab41fa166db60f00d2ceacc11c198e4da 100644 --- a/hw/usb/hcd-uhci.h +++ b/hw/usb/hcd-uhci.h @@ -50,6 +50,7 @@ typedef struct UHCIState { uint16_t status; uint16_t intr; /* interrupt enable register */ uint16_t frnum; /* frame number */ + uint64_t frame_time; /* frame time in ns */ uint32_t fl_base_addr; /* frame list base address */ uint8_t sof_timing; uint8_t status2; /* bit 0 and 1 are used to generate UHCI_STS_USBINT */ diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c index d0d46dd0a4a37ad00f3ad5d3c34ee47ae6ecb71f..8f521ad586ad251925992ca230bfc6c7b81541b4 100644 --- a/hw/usb/host-libusb.c +++ b/hw/usb/host-libusb.c @@ -945,6 +945,30 @@ static void usb_host_ep_update(USBHostDevice *s) libusb_free_config_descriptor(conf); } +static unsigned int usb_get_controller_type(int speed) +{ + unsigned int type = MAX_USB_CONTROLLER_TYPES; + + switch (speed) { + case USB_SPEED_SUPER: + type = QEMU_USB_CONTROLLER_XHCI; + break; + case USB_SPEED_HIGH: + type = QEMU_USB_CONTROLLER_EHCI; + break; + case USB_SPEED_FULL: + type = QEMU_USB_CONTROLLER_UHCI; + break; + case USB_SPEED_LOW: + type = QEMU_USB_CONTROLLER_OHCI; + break; + default: + break; + } + + return type; +} + static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd) { USBDevice *udev = USB_DEVICE(s); @@ -1054,6 +1078,12 @@ static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd) } trace_usb_host_open_success(bus_num, addr); + + /* change ehci frame time freq when USB passthrough */ + qemu_log("usb host speed is %d\n", udev->speed); + qemu_timer_set_mode(QEMU_TIMER_USB_NORMAL_MODE, + usb_get_controller_type(udev->speed)); + return 0; fail: @@ -1129,6 +1159,8 @@ static int usb_host_close(USBHostDevice *s) } usb_host_auto_check(NULL); + qemu_timer_set_mode(QEMU_TIMER_USB_LAZY_MODE, + usb_get_controller_type(udev->speed)); return 0; } diff --git a/include/hw/usb.h b/include/hw/usb.h index 33668dd0a99aa61eb235e64326773605f15b04cf..fa3a176159d7a9c5c6723d9396a865fbd2297ca5 100644 --- a/include/hw/usb.h +++ b/include/hw/usb.h @@ -142,6 +142,7 @@ #define USB_DEVICE_SELF_POWERED 0 #define USB_DEVICE_REMOTE_WAKEUP 1 +#define USB_DEVICE_REMOTE_WAKEUP_IS_SUPPORTED 2 #define USB_DT_DEVICE 0x01 #define USB_DT_CONFIG 0x02 diff --git a/include/qemu/timer.h b/include/qemu/timer.h index 88ef11468944bb4a6ee924f271c8b8f41363c402..d263fad9a471f477aad6d61ce83764c5cc0e15ea 100644 --- a/include/qemu/timer.h +++ b/include/qemu/timer.h @@ -91,6 +91,34 @@ struct QEMUTimer { int scale; }; +#define QEMU_USB_NORMAL_FREQ 1000 +#define QEMU_USB_LAZY_FREQ 10 +#define MAX_USB_CONTROLLER_TYPES 4 +#define QEMU_USB_CONTROLLER_OHCI 0 +#define QEMU_USB_CONTROLLER_UHCI 1 +#define QEMU_USB_CONTROLLER_EHCI 2 +#define QEMU_USB_CONTROLLER_XHCI 3 + +typedef void (*QEMUSetFreqHandler) (int freq); + +typedef struct qemu_usb_controller { + const char *name; + QEMUSetFreqHandler qemu_set_freq; +} qemu_usb_controller; + +typedef qemu_usb_controller* qemu_usb_controller_ptr; + +enum qemu_timer_mode { + QEMU_TIMER_USB_NORMAL_MODE = 1 << 0, /* Set when VNC connect or + * with usb dev passthrough + */ + QEMU_TIMER_USB_LAZY_MODE = 1 << 1, /* Set when VNC disconnect */ +}; + +int qemu_register_usb_controller(qemu_usb_controller_ptr controller, + unsigned int type); +int qemu_timer_set_mode(enum qemu_timer_mode mode, unsigned int type); + extern QEMUTimerListGroup main_loop_tlg; /* diff --git a/ui/vnc.c b/ui/vnc.c index af02522e8416f7949a2ba8672909e5d46f801ede..bc86c20370c884ddb7c6d44cac71f7c6804d9365 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -1379,6 +1379,8 @@ void vnc_disconnect_finish(VncState *vs) g_free(vs->zrle); g_free(vs->tight); g_free(vs); + + qemu_timer_set_mode(QEMU_TIMER_USB_LAZY_MODE, QEMU_USB_CONTROLLER_UHCI); } size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error *err) @@ -3333,6 +3335,8 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, } } } + + qemu_timer_set_mode(QEMU_TIMER_USB_NORMAL_MODE, QEMU_USB_CONTROLLER_UHCI); } void vnc_start_protocol(VncState *vs) diff --git a/util/qemu-timer.c b/util/qemu-timer.c index f36c75e594a6f23a7b982567f81180605d17f73d..40e8c83722b7855219875e3d58a42bc3a08a4f84 100644 --- a/util/qemu-timer.c +++ b/util/qemu-timer.c @@ -23,6 +23,7 @@ */ #include "qemu/osdep.h" +#include "qemu/log.h" #include "qemu/main-loop.h" #include "qemu/timer.h" #include "qemu/lockable.h" @@ -75,6 +76,74 @@ struct QEMUTimerList { QemuEvent timers_done_ev; }; +typedef struct qemu_controller_timer_state { + qemu_usb_controller_ptr controller; + int refs; +} controller_timer_state; + +typedef controller_timer_state* controller_timer_state_ptr; + +static controller_timer_state uhci_timer_state = { + .controller = NULL, + .refs = 0, +}; + +static controller_timer_state_ptr \ + qemu_usb_controller_tab[MAX_USB_CONTROLLER_TYPES] = {NULL, + &uhci_timer_state, + NULL, NULL}; + +int qemu_register_usb_controller(qemu_usb_controller_ptr controller, + unsigned int type) +{ + if (type != QEMU_USB_CONTROLLER_UHCI) { + return 0; + } + + /* for companion EHCI controller will create three UHCI controllers, + * we init it only once. + */ + if (!qemu_usb_controller_tab[type]->controller) { + qemu_log("the usb controller (%d) registed frame handler\n", type); + qemu_usb_controller_tab[type]->controller = controller; + } + + return 0; +} + +int qemu_timer_set_mode(enum qemu_timer_mode mode, unsigned int type) +{ + if (type != QEMU_USB_CONTROLLER_UHCI) { + qemu_log("the usb controller (%d) no need change frame frep\n", type); + return 0; + } + + if (!qemu_usb_controller_tab[type]->controller) { + qemu_log("the usb controller (%d) not registed yet\n", type); + return 0; + } + + if (mode == QEMU_TIMER_USB_NORMAL_MODE) { + if (qemu_usb_controller_tab[type]->refs++ > 0) { + return 0; + } + qemu_usb_controller_tab[type]->controller-> + qemu_set_freq(QEMU_USB_NORMAL_FREQ); + qemu_log("Set the controller (%d) of freq %d HZ,\n", + type, QEMU_USB_NORMAL_FREQ); + } else { + if (--qemu_usb_controller_tab[type]->refs > 0) { + return 0; + } + qemu_usb_controller_tab[type]->controller-> + qemu_set_freq(QEMU_USB_LAZY_FREQ); + qemu_log("Set the controller(type:%d) of freq %d HZ,\n", + type, QEMU_USB_LAZY_FREQ); + } + + return 0; +} + /** * qemu_clock_ptr: * @type: type of clock