diff mbox

Re: [PATCH] add event queueing to USB HID

Message ID 19751.1291.434672.479354@mariner.uk.xensource.com
State New
Headers show

Commit Message

Ian Jackson Jan. 7, 2011, 12:20 p.m. UTC
Gerd Hoffmann writes ("Re: [PATCH] add event queueing to USB HID"):
> On 12/23/10 15:57, Paolo Bonzini wrote:
> > @@ -68,7 +77,7 @@ typedef struct USBHIDState {
> >       int protocol;
> >       uint8_t idle;
> >       int64_t next_idle_clock;
> > -    int changed;
> > +    int have_data, changed;
> 
> What is the difference between have_data and changed?
> Do you need both?  And can't you just compare head and tail of the ring 
> instead?

I think you are right.  In the version of this code in the qemu-xen
tree, I moved "changed" from USBHIDState to USBKeyboardState.  The
USBPointerState struct doesn't contain either "changed" or
"have_data".

> I think it makes sense to do the same for the keyboard, which might even 
> simplify the code overall as both mouse and keyboard will work in a 
> simliar way then.

That would be a reasonable approach although I didn't do it in my
changes in qemu-xen.  The queue isn't directly reuseable between the
two kinds of device because the queue entries don't have the same
type, so the amount of code sharing is still going to be limited
(barring heavy use of macros, which I didn't think would be popular).

In my version there is no usb_hid_changed function any more.

> > +    /* When the buffer is empty, return the last event.
> 
> Why can this happen in the first place?  Shouldn't the device NAK polls 
> when it has no events to deliver?

The actual USB HID protocol is based around the idea of a "report"
which is available all of the time.  IIRC the device is only supposed
to start NAKing polls when "nothing has changed" if the host has done
a SET IDLE 0 to turn off idle event reporting.

Paulo: For reference, here is a diff of the relevant functionality.
It's against qemu 0.10.0 (ish) but it may be a better starting point
than what you used.

Ian.

Comments

Ian Jackson Jan. 7, 2011, 12:26 p.m. UTC | #1
I wrote:
> Paulo: For reference, here is a diff of the relevant functionality.
> It's against qemu 0.10.0 (ish) but it may be a better starting point
> than what you used.

I just had a go at seeing how that applies to current upstream master
and it doesn't look like it would be hard to fix up.

Ian.
diff mbox

Patch

diff --git a/hw/usb-hid.c b/hw/usb-hid.c
index c850a91..4d889d6 100644
--- a/hw/usb-hid.c
+++ b/hw/usb-hid.c
@@ -43,30 +43,41 @@ 
 #define USB_TABLET    2
 #define USB_KEYBOARD  3
 
-typedef struct USBMouseState {
-    int dx, dy, dz, buttons_state;
-    int x, y;
-    int mouse_grabbed;
+typedef struct USBPointerEvent {
+    int xdx, ydy; /* relative iff it's a mouse, otherwise absolute */
+    int dz, buttons_state;
+} USBPointerEvent;
+
+#define QUEUELENSHIFT 4 /* 16 events should be enough for a triple-click */
+#define QUEUELEN (1u<<QUEUELENSHIFT)
+#define QUEUEINDEXMASK (QUEUELEN-1u)
+#define QUEUE_INCR(v) ((v)++, (v) &= QUEUEINDEXMASK)
+
+typedef struct USBPointerState {
+    USBPointerEvent queue[QUEUELEN];
+    unsigned head, tail; /* indices into circular queue */
+    
+    int mouse_grabbed, xyrel;
     QEMUPutMouseEntry *eh_entry;
-} USBMouseState;
+} USBPointerState;
 
 typedef struct USBKeyboardState {
     uint16_t modifiers;
     uint8_t leds;
     uint8_t key[16];
     int keys;
+    int changed;
 } USBKeyboardState;
 
 typedef struct USBHIDState {
     USBDevice dev;
     union {
-        USBMouseState ptr;
+        USBPointerState ptr;
         USBKeyboardState kbd;
     };
     int kind;
     int protocol;
     uint8_t idle;
-    int changed;
     void *datain_opaque;
     void (*datain)(void *);
 } USBHIDState;
@@ -404,40 +415,64 @@  static const uint8_t usb_hid_usage_keys[0x100] = {
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 };
 
-static void usb_hid_changed(USBHIDState *hs)
+static void usb_notify_datain_cb(USBHIDState *hs)
 {
-    hs->changed = 1;
-
     if (hs->datain)
         hs->datain(hs->datain_opaque);
 }
 
-static void usb_mouse_event(void *opaque,
-                            int dx1, int dy1, int dz1, int buttons_state)
-{
-    USBHIDState *hs = opaque;
-    USBMouseState *s = &hs->ptr;
-
-    s->dx += dx1;
-    s->dy += dy1;
-    s->dz += dz1;
-    s->buttons_state = buttons_state;
-
-    usb_hid_changed(hs);
+static void usb_pointer_event_clear(USBPointerEvent *e, int buttons) {
+    e->xdx = e->ydy = e->dz = 0;
+    e->buttons_state = buttons;
 }
 
-static void usb_tablet_event(void *opaque,
-			     int x, int y, int dz, int buttons_state)
-{
-    USBHIDState *hs = opaque;
-    USBMouseState *s = &hs->ptr;
+static void usb_pointer_event_combine(USBPointerEvent *e, int xyrel,
+				      int x1, int y1, int z1) {
+    if (xyrel) {
+	e->xdx += x1;
+	e->ydy += y1;
+    } else {
+	e->xdx = x1;
+	e->ydy = y1;
+    }
+    e->dz += z1;
+}
 
-    s->x = x;
-    s->y = y;
-    s->dz += dz;
-    s->buttons_state = buttons_state;
+static void usb_pointer_event(void *hs_v, int x1, int y1, int z1,
+			      int buttons_state) {
+    /* We combine events where feasible to keep the queue small.
+     * We shouldn't combine anything with the first event with
+     * a particular button state, as that would change the
+     * location of the button state change. */
+    USBHIDState *hs= hs_v;
+    USBPointerState *s = &hs->ptr;
+    unsigned use_slot= (s->tail-1) & QUEUEINDEXMASK;
+    unsigned previous_slot= (use_slot-1) & QUEUEINDEXMASK;
+
+    if (s->tail == s->head) {
+        use_slot= s->tail;
+        QUEUE_INCR(s->tail);
+        usb_pointer_event_clear(&s->queue[use_slot], buttons_state);
+    } else if (use_slot == s->head ||
+	s->queue[use_slot].buttons_state != buttons_state ||
+	s->queue[previous_slot].buttons_state != buttons_state) {
+	/* can't or shouldn't combine this event with previous one */
+	use_slot= s->tail;
+	QUEUE_INCR(s->tail);
+	if (use_slot == s->head) {
+	    /* queue full, oh well, discard something */
+	    s->head++;  s->head &= QUEUEINDEXMASK;
+	    /* but we preserve the relative motions */
+	    usb_pointer_event_combine(&s->queue[s->head], s->xyrel,
+				      s->queue[use_slot].xdx,
+				      s->queue[use_slot].ydy,
+				      s->queue[use_slot].dz);
+	}
+	usb_pointer_event_clear(&s->queue[use_slot], buttons_state);
+    }
+    usb_pointer_event_combine(&s->queue[use_slot],s->xyrel, x1,y1,z1);
 
-    usb_hid_changed(hs);
+    usb_notify_datain_cb(hs);
 }
 
 static void usb_keyboard_event(void *opaque, int keycode)
@@ -451,6 +486,9 @@  static void usb_keyboard_event(void *opaque, int keycode)
     hid_code = usb_hid_usage_keys[key | ((s->modifiers >> 1) & (1 << 7))];
     s->modifiers &= ~(1 << 8);
 
+    s->changed = 1;
+    usb_notify_datain_cb(hs);
+
     switch (hid_code) {
     case 0x00:
         return;
@@ -475,7 +513,6 @@  static void usb_keyboard_event(void *opaque, int keycode)
             if (s->key[i] == hid_code) {
                 s->key[i] = s->key[-- s->keys];
                 s->key[s->keys] = 0x00;
-                usb_hid_changed(hs);
                 break;
             }
         if (i < 0)
@@ -490,8 +527,6 @@  static void usb_keyboard_event(void *opaque, int keycode)
         } else
             return;
     }
-
-    usb_hid_changed(hs);
 }
 
 static inline int int_clamp(int val, int vmin, int vmax)
@@ -504,85 +539,100 @@  static inline int int_clamp(int val, int vmin, int vmax)
         return val;
 }
 
-static int usb_mouse_poll(USBHIDState *hs, uint8_t *buf, int len)
+static int usb_suppress_report(USBHIDState *hs, int unchanged) {
+    /* TODO: Implement finite idle delays.  */
+    return !hs->idle && unchanged; /* SET_IDLE 0 means do not report */
+}
+
+static int usb_pointer_poll(USBHIDState *hs, uint8_t *buf, int len)
 {
     int dx, dy, dz, b, l;
-    USBMouseState *s = &hs->ptr;
+    USBPointerState *s = &hs->ptr;
+    USBPointerEvent *e;
 
     if (!s->mouse_grabbed) {
-	s->eh_entry = qemu_add_mouse_event_handler(usb_mouse_event, hs,
-                                                  0, "QEMU USB Mouse");
+	s->eh_entry = qemu_add_mouse_event_handler(usb_pointer_event, hs,
+                                          !s->xyrel, "QEMU USB Pointer");
 	s->mouse_grabbed = 1;
     }
 
-    dx = int_clamp(s->dx, -127, 127);
-    dy = int_clamp(s->dy, -127, 127);
-    dz = int_clamp(s->dz, -127, 127);
-
-    s->dx -= dx;
-    s->dy -= dy;
-    s->dz -= dz;
-
-    /* Appears we have to invert the wheel direction */
-    dz = 0 - dz;
+    if (usb_suppress_report(hs, s->head == s->tail))
+	return USB_RET_NAK;
 
-    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;
+    if (s->head == s->tail)
+        /* use the last report */
+        s->head = (s->head - 1) & QUEUEINDEXMASK;
 
-    l = 0;
-    if (len > l)
-        buf[l ++] = b;
-    if (len > l)
-        buf[l ++] = dx;
-    if (len > l)
-        buf[l ++] = dy;
-    if (len > l)
-        buf[l ++] = dz;
-    return l;
-}
+    e = &s->queue[s->head];
 
-static int usb_tablet_poll(USBHIDState *hs, uint8_t *buf, int len)
-{
-    int dz, b, l;
-    USBMouseState *s = &hs->ptr;
+    dz = int_clamp(e->dz, -128, 127);
 
-    if (!s->mouse_grabbed) {
-	s->eh_entry = qemu_add_mouse_event_handler(usb_tablet_event, hs,
-                                                  1, "QEMU USB Tablet");
-	s->mouse_grabbed = 1;
+    e->dz -= dz;
+    if (s->xyrel) {
+        dx = int_clamp(e->xdx, -128, 127);
+        dy = int_clamp(e->ydy, -128, 127);
+	e->xdx -= dx;
+	e->ydy -= dy;
+    } else {
+        dx = e->xdx;
+        dy = e->ydy;
     }
-
-    dz = int_clamp(s->dz, -127, 127);
-    s->dz -= dz;
-
     /* Appears we have to invert the wheel direction */
     dz = 0 - dz;
+
+    if (!(e->dz ||
+	  (s->xyrel && (e->xdx || e->ydy)))) {
+	/* that deals with this event */
+	QUEUE_INCR(s->head);
+    }
+
     b = 0;
-    if (s->buttons_state & MOUSE_EVENT_LBUTTON)
+    if (e->buttons_state & MOUSE_EVENT_LBUTTON)
         b |= 0x01;
-    if (s->buttons_state & MOUSE_EVENT_RBUTTON)
+    if (e->buttons_state & MOUSE_EVENT_RBUTTON)
         b |= 0x02;
-    if (s->buttons_state & MOUSE_EVENT_MBUTTON)
+    if (e->buttons_state & MOUSE_EVENT_MBUTTON)
         b |= 0x04;
 
-    buf[0] = b;
-    buf[1] = s->x & 0xff;
-    buf[2] = s->x >> 8;
-    buf[3] = s->y & 0xff;
-    buf[4] = s->y >> 8;
-    buf[5] = dz;
-    l = 6;
+    switch (hs->kind) {
+    case USB_MOUSE:
+	l = 0;
+	if (len > l)
+	    buf[l ++] = b;
+	if (len > l)
+	    buf[l ++] = dx;
+	if (len > l)
+	    buf[l ++] = dy;
+	if (len > l)
+	    buf[l ++] = dz;
+	break;
+
+    case USB_TABLET:
+	buf[0] = b;
+	buf[1] = dx & 0xff;
+	buf[2] = dx >> 8;
+	buf[3] = dy & 0xff;
+	buf[4] = dy >> 8;
+	buf[5] = dz;
+	l = 6;
+	break;
+
+    default:
+	abort();
+    }
 
     return l;
 }
 
-static int usb_keyboard_poll(USBKeyboardState *s, uint8_t *buf, int len)
+static int usb_keyboard_poll(USBHIDState *hs, uint8_t *buf, int len)
 {
+    USBKeyboardState *s= &hs->kbd;
+
+    if (usb_suppress_report(hs, !s->changed))
+	return USB_RET_NAK;
+
+    s->changed= 0;
+
     if (len < 2)
         return 0;
 
@@ -609,16 +659,11 @@  static int usb_keyboard_write(USBKeyboardState *s, uint8_t *buf, int len)
     return 0;
 }
 
-static void usb_mouse_handle_reset(USBDevice *dev)
+static void usb_pointer_handle_reset(USBDevice *dev)
 {
     USBHIDState *s = (USBHIDState *)dev;
 
-    s->ptr.dx = 0;
-    s->ptr.dy = 0;
-    s->ptr.dz = 0;
-    s->ptr.x = 0;
-    s->ptr.y = 0;
-    s->ptr.buttons_state = 0;
+    s->ptr.head= s->ptr.tail = 0;
     s->protocol = 1;
 }
 
@@ -764,12 +809,10 @@  static int usb_hid_handle_control(USBDevice *dev, int request, int value,
         }
         break;
     case GET_REPORT:
-	if (s->kind == USB_MOUSE)
-            ret = usb_mouse_poll(s, data, length);
-	else if (s->kind == USB_TABLET)
-            ret = usb_tablet_poll(s, data, length);
+	if (s->kind == USB_MOUSE || s->kind == USB_TABLET)
+            ret = usb_pointer_poll(s, data, length);
         else if (s->kind == USB_KEYBOARD)
-            ret = usb_keyboard_poll(&s->kbd, data, length);
+            ret = usb_keyboard_poll(s, data, length);
         break;
     case SET_REPORT:
         if (s->kind == USB_KEYBOARD)
@@ -813,16 +856,10 @@  static int usb_hid_handle_data(USBDevice *dev, USBPacket *p)
     switch(p->pid) {
     case USB_TOKEN_IN:
         if (p->devep == 1) {
-            /* TODO: Implement finite idle delays.  */
-            if (!(s->changed || s->idle))
-                return USB_RET_NAK;
-            s->changed = 0;
-            if (s->kind == USB_MOUSE)
-                ret = usb_mouse_poll(s, p->data, p->len);
-            else if (s->kind == USB_TABLET)
-                ret = usb_tablet_poll(s, p->data, p->len);
+            if (s->kind == USB_MOUSE || s->kind == USB_TABLET)
+                ret = usb_pointer_poll(s, p->data, p->len);
             else if (s->kind == USB_KEYBOARD)
-                ret = usb_keyboard_poll(&s->kbd, p->data, p->len);
+                ret = usb_keyboard_poll(s, p->data, p->len);
         } else {
             goto fail;
         }
@@ -846,7 +883,7 @@  static void usb_hid_handle_destroy(USBDevice *dev)
     qemu_free(s);
 }
 
-USBDevice *usb_tablet_init(void)
+static USBDevice *usb_pointer_init(int kind, int xyrel, const char *devname)
 {
     USBHIDState *s;
 
@@ -854,38 +891,31 @@  USBDevice *usb_tablet_init(void)
     s->dev.speed = USB_SPEED_FULL;
     s->dev.handle_packet = usb_generic_handle_packet;
 
-    s->dev.handle_reset = usb_mouse_handle_reset;
+    s->dev.handle_reset = usb_pointer_handle_reset;
     s->dev.handle_control = usb_hid_handle_control;
     s->dev.handle_data = usb_hid_handle_data;
     s->dev.handle_destroy = usb_hid_handle_destroy;
-    s->kind = USB_TABLET;
-    /* Force poll routine to be run and grab input the first time.  */
-    s->changed = 1;
 
-    pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Tablet");
+    s->kind = kind;
+    s->ptr.xyrel = xyrel;
+    usb_pointer_handle_reset((USBDevice*)s);
 
+    /* Force poll routine to be run and grab input the first time.  */
+    usb_pointer_event_clear(&s->ptr.queue[0], 0);
+    s->ptr.tail = 1;
+
+    pstrcpy(s->dev.devname, sizeof(s->dev.devname), devname);
     return (USBDevice *)s;
 }
 
-USBDevice *usb_mouse_init(void)
+USBDevice *usb_tablet_init(void)
 {
-    USBHIDState *s;
-
-    s = qemu_mallocz(sizeof(USBHIDState));
-    s->dev.speed = USB_SPEED_FULL;
-    s->dev.handle_packet = usb_generic_handle_packet;
-
-    s->dev.handle_reset = usb_mouse_handle_reset;
-    s->dev.handle_control = usb_hid_handle_control;
-    s->dev.handle_data = usb_hid_handle_data;
-    s->dev.handle_destroy = usb_hid_handle_destroy;
-    s->kind = USB_MOUSE;
-    /* Force poll routine to be run and grab input the first time.  */
-    s->changed = 1;
-
-    pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Mouse");
+    return usb_pointer_init(USB_TABLET, 0, "QEMU USB Tablet");
+}
 
-    return (USBDevice *)s;
+USBDevice *usb_mouse_init(void)
+{
+    return usb_pointer_init(USB_MOUSE, 1, "QEMU USB Mouse");
 }
 
 USBDevice *usb_keyboard_init(void)
@@ -901,6 +931,7 @@  USBDevice *usb_keyboard_init(void)
     s->dev.handle_data = usb_hid_handle_data;
     s->dev.handle_destroy = usb_hid_handle_destroy;
     s->kind = USB_KEYBOARD;
+    s->kbd.changed= 0;
 
     pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Keyboard");