mirror of
https://github.com/54shady/kernel_drivers_examples.git
synced 2025-08-15 02:39:36 +00:00
USB : Standard five keys mouse in qemu
This commit is contained in:
@ -8,7 +8,7 @@ hid report descriptors在usb描述符中的关系
|
||||
|
||||

|
||||
|
||||
下面是一个三个按键的标准鼠标
|
||||
## 标准三键的鼠标
|
||||
|
||||

|
||||
|
||||
@ -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/)
|
||||
|
Reference in New Issue
Block a user