Skip to content

Commit 7712e4b

Browse files
committed
USB : Standard five keys mouse in qemu
1 parent 8f04c16 commit 7712e4b

File tree

1 file changed

+190
-1
lines changed

1 file changed

+190
-1
lines changed

debug/usb/README.md

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ hid report descriptors在usb描述符中的关系
88

99
![report descriptors](./reportid.png)
1010

11-
下面是一个三个按键的标准鼠标
11+
## 标准三键的鼠标
1212

1313
![3 buttons mouse](./mouse.png)
1414

@@ -301,6 +301,195 @@ qemu代码里描述上述鼠标按键的结构时HIDPointerEvent
301301
0xc0, /* End Collection */
302302
};
303303

304+
## 标准五键(两个侧键)鼠标
305+
306+
鼠标按键用button1, button2, button3...来命名
307+
308+
qemu代码中用结构体HIDPointerEvent来描述鼠标的数据
309+
typedef struct HIDPointerEvent {
310+
int32_t xdx, ydy; /* relative iff it's a mouse, otherwise absolute */
311+
int32_t dz, buttons_state;
312+
} HIDPointerEvent;
313+
314+
其中buttons_state每一位表示按键,这里用int32_t说明可以支持32个按键(一般支持8个按键即可uint8_t)
315+
bit1对应button1, bit2对应button2,以此类推
316+
317+
一般操作系统中将对应按键映射成不同功能
318+
319+
button1:left(鼠标左键)
320+
button2:right(鼠标右键)
321+
button3:middle(滚轮按键)
322+
button4:side(侧边按键1)
323+
button5:extra(侧边按键2)
324+
325+
鼠标设备上报buttonN经过系统映射后变为对应的功能按键(left,right,middle...)
326+
327+
大部分标准5键鼠标都是按照上面的进行映射(button4,button5的映射非强制)
328+
329+
所以在qemu模拟的鼠标中可以将button4和button5和side和extra对应以达到同大部分物理鼠标效果
330+
331+
### 下面以spice-input和USB HID鼠标为例说明
332+
333+
spice捕获坐标后传递给qemu的input子系统来作为鼠标数据提供给虚拟鼠标设备
334+
335+
SpiceInput(spice-input.c)->InputCore(input.c)->USB HID Device(hid.c)
336+
337+
SpiceInput中通过button_mask来传入鼠标按键掩码值
338+
下面代将button4配置为side, button5配置为extra
339+
```c
340+
static void spice_update_buttons(QemuSpicePointer *pointer,
341+
int wheel, uint32_t button_mask)
342+
{
343+
static uint32_t bmap[INPUT_BUTTON__MAX] = {
344+
[INPUT_BUTTON_LEFT] = 0x01, /* button 1 */
345+
[INPUT_BUTTON_MIDDLE] = 0x04, /* button 3 */
346+
[INPUT_BUTTON_RIGHT] = 0x02, /* button 2 */
347+
[INPUT_BUTTON_WHEEL_UP] = 0x40, /* button 7 */
348+
[INPUT_BUTTON_WHEEL_DOWN] = 0x80, /* button 8 */
349+
[INPUT_BUTTON_SIDE] = 0x08, /* button 4 */
350+
[INPUT_BUTTON_EXTRA] = 0x10, /* button 5 */
351+
};
352+
353+
if (wheel < 0) {
354+
button_mask |= 0x40;
355+
}
356+
if (wheel > 0) {
357+
button_mask |= 0x80;
358+
}
359+
```
360+
361+
qemu中通过文件qapi/ui.json来生成头文件中对应的按键枚举
362+
```c
363+
{ 'enum' : 'InputButton',
364+
'data' : [ 'left', 'middle', 'right', 'wheel-up', 'wheel-down', 'side',
365+
'extra' ] }
366+
367+
下面是生成的对应的枚举值
368+
typedef enum InputButton {
369+
INPUT_BUTTON_LEFT = 0,
370+
INPUT_BUTTON_MIDDLE = 1,
371+
INPUT_BUTTON_RIGHT = 2,
372+
INPUT_BUTTON_WHEEL_UP = 3,
373+
INPUT_BUTTON_WHEEL_DOWN = 4,
374+
INPUT_BUTTON_SIDE = 5,
375+
INPUT_BUTTON_EXTRA = 6,
376+
INPUT_BUTTON__MAX = 7,
377+
} InputButton;
378+
```
379+
380+
SpiceInput调用InputCore的接口最终调入对应的设备模拟设备处理函数,这里假设是HID设备(hid_pointer_event)
381+
```c
382+
static void hid_pointer_event(DeviceState *dev, QemuConsole *src,
383+
InputEvent *evt)
384+
{
385+
static const int bmap[INPUT_BUTTON__MAX] = {
386+
[INPUT_BUTTON_LEFT] = 0x01,
387+
[INPUT_BUTTON_RIGHT] = 0x02,
388+
[INPUT_BUTTON_MIDDLE] = 0x04,
389+
[INPUT_BUTTON_SIDE] = 0x08,
390+
[INPUT_BUTTON_EXTRA] = 0x10,
391+
};
392+
393+
```
394+
395+
还有一个需要注意的是hid report descriptor
396+
其中下面的配置是同时上报8个按键
397+
```c
398+
static const uint8_t qemu_tablet_hid_report_descriptor[] = {
399+
0x05, 0x01, /* Usage Page (Generic Desktop) */
400+
0x09, 0x02, /* Usage (Mouse) */
401+
0xa1, 0x01, /* Collection (Application) */
402+
0x09, 0x01, /* Usage (Pointer) */
403+
0xa1, 0x00, /* Collection (Physical) */
404+
0x05, 0x09, /* Usage Page (Button) */
405+
0x19, 0x01, /* Usage Minimum (1) */
406+
0x29, 0x08, /* Usage Maximum (8) */
407+
0x15, 0x00, /* Logical Minimum (0) */
408+
0x25, 0x01, /* Logical Maximum (1) */
409+
0x95, 0x08, /* Report Count (8) */
410+
0x75, 0x01, /* Report Size (1) */
411+
0x81, 0x02, /* Input (Data, Variable, Absolute) */
412+
0x05, 0x01, /* Usage Page (Generic Desktop) */
413+
0x09, 0x30, /* Usage (X) */
414+
0x09, 0x31, /* Usage (Y) */
415+
0x15, 0x00, /* Logical Minimum (0) */
416+
0x26, 0xff, 0x7f, /* Logical Maximum (0x7fff) */
417+
0x35, 0x00, /* Physical Minimum (0) */
418+
0x46, 0xff, 0x7f, /* Physical Maximum (0x7fff) */
419+
0x75, 0x10, /* Report Size (16) */
420+
0x95, 0x02, /* Report Count (2) */
421+
0x81, 0x02, /* Input (Data, Variable, Absolute) */
422+
0x05, 0x01, /* Usage Page (Generic Desktop) */
423+
0x09, 0x38, /* Usage (Wheel) */
424+
0x15, 0x81, /* Logical Minimum (-0x7f) */
425+
0x25, 0x7f, /* Logical Maximum (0x7f) */
426+
0x35, 0x00, /* Physical Minimum (same as logical) */
427+
0x45, 0x00, /* Physical Maximum (same as logical) */
428+
0x75, 0x08, /* Report Size (8) */
429+
0x95, 0x01, /* Report Count (1) */
430+
0x81, 0x06, /* Input (Data, Variable, Relative) */
431+
0xc0, /* End Collection */
432+
0xc0, /* End Collection */
433+
};
434+
435+
/* HID描述符长度需要对应修改 */
436+
static const USBDescIface desc_iface_tablet = {
437+
...
438+
/* HID descriptor */
439+
.data = (uint8_t[]) {
440+
0x09, /* u8 bLength */
441+
USB_DT_HID, /* u8 bDescriptorType */
442+
0x01, 0x00, /* u16 HID_class */
443+
0x00, /* u8 country_code */
444+
0x01, /* u8 num_descriptors */
445+
USB_DT_REPORT, /* u8 type: Report */
446+
sizeof(qemu_tablet_hid_report_descriptor) / sizeof(uint8_t), 0, /* u16 len */
447+
},
448+
...
449+
```
450+
451+
### 调试输入数据
452+
453+
确认SpiceInput输出的数据可以在InputCore中打开调试
454+
455+
(qemu) trace-event input_event_btn on
456+
or
457+
virsh qemu-monitor-command <domain> --hmp trace-event input_event_btn on
458+
459+
确认模拟HID鼠标上报的数据,在下面函数中用gdb打印对应数据
460+
461+
USB数据包的处理函数usb_handle_packet调用流程如下
462+
```c
463+
usb_handle_packet
464+
usb_hid_handle_data
465+
hid_pointer_poll
466+
467+
int hid_pointer_poll(HIDState *hs, uint8_t *buf, int len)
468+
case HID_TABLET:
469+
if (len > l) {
470+
buf[l++] = e->buttons_state;
471+
}
472+
if (len > l) {
473+
buf[l++] = dx & 0xff;
474+
}
475+
if (len > l) {
476+
buf[l++] = dx >> 8;
477+
}
478+
if (len > l) {
479+
buf[l++] = dy & 0xff;
480+
}
481+
if (len > l) {
482+
buf[l++] = dy >> 8;
483+
}
484+
if (len > l) {
485+
buf[l++] = dz;
486+
}
487+
break;
488+
489+
(gdb) printf "0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x 0x%02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]
490+
```
491+
其中使用USBlyzer抓到的raw data区域对应的数据为上面的buf
492+
304493
## How to retrieve the report descriptors in linux
305494
306495
[Ref: get usb report descriptor](https://www.slashdev.ca/2010/05/08/get-usb-report-descriptor-with-linux/)

0 commit comments

Comments
 (0)