USB : Standard five keys mouse in qemu

This commit is contained in:
zeroway
2022-10-26 16:07:13 +08:00
parent 8f04c16e81
commit 7712e4b2d1

View File

@ -8,7 +8,7 @@ hid report descriptors在usb描述符中的关系
![report descriptors](./reportid.png)
下面是一个三个按键的标准鼠标
## 标准三键的鼠标
![3 buttons mouse](./mouse.png)
@ -301,6 +301,195 @@ qemu代码里描述上述鼠标按键的结构时HIDPointerEvent
0xc0, /* End Collection */
};
## 标准五键(两个侧键)鼠标
鼠标按键用button1, button2, button3...来命名
qemu代码中用结构体HIDPointerEvent来描述鼠标的数据
typedef struct HIDPointerEvent {
int32_t xdx, ydy; /* relative iff it's a mouse, otherwise absolute */
int32_t dz, buttons_state;
} HIDPointerEvent;
其中buttons_state每一位表示按键,这里用int32_t说明可以支持32个按键(一般支持8个按键即可uint8_t)
bit1对应button1, bit2对应button2,以此类推
一般操作系统中将对应按键映射成不同功能
button1:left(鼠标左键)
button2:right(鼠标右键)
button3:middle(滚轮按键)
button4:side(侧边按键1)
button5:extra(侧边按键2)
鼠标设备上报buttonN经过系统映射后变为对应的功能按键(left,right,middle...)
大部分标准5键鼠标都是按照上面的进行映射(button4,button5的映射非强制)
所以在qemu模拟的鼠标中可以将button4和button5和side和extra对应以达到同大部分物理鼠标效果
### 下面以spice-input和USB HID鼠标为例说明
spice捕获坐标后传递给qemu的input子系统来作为鼠标数据提供给虚拟鼠标设备
SpiceInput(spice-input.c)->InputCore(input.c)->USB HID Device(hid.c)
SpiceInput中通过button_mask来传入鼠标按键掩码值
下面代将button4配置为side, button5配置为extra
```c
static void spice_update_buttons(QemuSpicePointer *pointer,
int wheel, uint32_t button_mask)
{
static uint32_t bmap[INPUT_BUTTON__MAX] = {
[INPUT_BUTTON_LEFT] = 0x01, /* button 1 */
[INPUT_BUTTON_MIDDLE] = 0x04, /* button 3 */
[INPUT_BUTTON_RIGHT] = 0x02, /* button 2 */
[INPUT_BUTTON_WHEEL_UP] = 0x40, /* button 7 */
[INPUT_BUTTON_WHEEL_DOWN] = 0x80, /* button 8 */
[INPUT_BUTTON_SIDE] = 0x08, /* button 4 */
[INPUT_BUTTON_EXTRA] = 0x10, /* button 5 */
};
if (wheel < 0) {
button_mask |= 0x40;
}
if (wheel > 0) {
button_mask |= 0x80;
}
```
qemu中通过文件qapi/ui.json来生成头文件中对应的按键枚举
```c
{ 'enum' : 'InputButton',
'data' : [ 'left', 'middle', 'right', 'wheel-up', 'wheel-down', 'side',
'extra' ] }
下面是生成的对应的枚举值
typedef enum InputButton {
INPUT_BUTTON_LEFT = 0,
INPUT_BUTTON_MIDDLE = 1,
INPUT_BUTTON_RIGHT = 2,
INPUT_BUTTON_WHEEL_UP = 3,
INPUT_BUTTON_WHEEL_DOWN = 4,
INPUT_BUTTON_SIDE = 5,
INPUT_BUTTON_EXTRA = 6,
INPUT_BUTTON__MAX = 7,
} InputButton;
```
SpiceInput调用InputCore的接口最终调入对应的设备模拟设备处理函数,这里假设是HID设备(hid_pointer_event)
```c
static void hid_pointer_event(DeviceState *dev, QemuConsole *src,
InputEvent *evt)
{
static const int bmap[INPUT_BUTTON__MAX] = {
[INPUT_BUTTON_LEFT] = 0x01,
[INPUT_BUTTON_RIGHT] = 0x02,
[INPUT_BUTTON_MIDDLE] = 0x04,
[INPUT_BUTTON_SIDE] = 0x08,
[INPUT_BUTTON_EXTRA] = 0x10,
};
```
还有一个需要注意的是hid report descriptor
其中下面的配置是同时上报8个按键
```c
static const uint8_t qemu_tablet_hid_report_descriptor[] = {
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x02, /* Usage (Mouse) */
0xa1, 0x01, /* Collection (Application) */
0x09, 0x01, /* Usage (Pointer) */
0xa1, 0x00, /* Collection (Physical) */
0x05, 0x09, /* Usage Page (Button) */
0x19, 0x01, /* Usage Minimum (1) */
0x29, 0x08, /* Usage Maximum (8) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x95, 0x08, /* Report Count (8) */
0x75, 0x01, /* Report Size (1) */
0x81, 0x02, /* Input (Data, Variable, Absolute) */
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x30, /* Usage (X) */
0x09, 0x31, /* Usage (Y) */
0x15, 0x00, /* Logical Minimum (0) */
0x26, 0xff, 0x7f, /* Logical Maximum (0x7fff) */
0x35, 0x00, /* Physical Minimum (0) */
0x46, 0xff, 0x7f, /* Physical Maximum (0x7fff) */
0x75, 0x10, /* Report Size (16) */
0x95, 0x02, /* Report Count (2) */
0x81, 0x02, /* Input (Data, Variable, Absolute) */
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x38, /* Usage (Wheel) */
0x15, 0x81, /* Logical Minimum (-0x7f) */
0x25, 0x7f, /* Logical Maximum (0x7f) */
0x35, 0x00, /* Physical Minimum (same as logical) */
0x45, 0x00, /* Physical Maximum (same as logical) */
0x75, 0x08, /* Report Size (8) */
0x95, 0x01, /* Report Count (1) */
0x81, 0x06, /* Input (Data, Variable, Relative) */
0xc0, /* End Collection */
0xc0, /* End Collection */
};
/* HID描述符长度需要对应修改 */
static const USBDescIface desc_iface_tablet = {
...
/* HID descriptor */
.data = (uint8_t[]) {
0x09, /* u8 bLength */
USB_DT_HID, /* u8 bDescriptorType */
0x01, 0x00, /* u16 HID_class */
0x00, /* u8 country_code */
0x01, /* u8 num_descriptors */
USB_DT_REPORT, /* u8 type: Report */
sizeof(qemu_tablet_hid_report_descriptor) / sizeof(uint8_t), 0, /* u16 len */
},
...
```
### 调试输入数据
确认SpiceInput输出的数据可以在InputCore中打开调试
(qemu) trace-event input_event_btn on
or
virsh qemu-monitor-command <domain> --hmp trace-event input_event_btn on
确认模拟HID鼠标上报的数据,在下面函数中用gdb打印对应数据
USB数据包的处理函数usb_handle_packet调用流程如下
```c
usb_handle_packet
usb_hid_handle_data
hid_pointer_poll
int hid_pointer_poll(HIDState *hs, uint8_t *buf, int len)
case HID_TABLET:
if (len > l) {
buf[l++] = e->buttons_state;
}
if (len > l) {
buf[l++] = dx & 0xff;
}
if (len > l) {
buf[l++] = dx >> 8;
}
if (len > l) {
buf[l++] = dy & 0xff;
}
if (len > l) {
buf[l++] = dy >> 8;
}
if (len > l) {
buf[l++] = dz;
}
break;
(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]
```
其中使用USBlyzer抓到的raw data区域对应的数据为上面的buf
## How to retrieve the report descriptors in linux
[Ref: get usb report descriptor](https://www.slashdev.ca/2010/05/08/get-usb-report-descriptor-with-linux/)