]>
Commit | Line | Data |
---|---|---|
aa71cf80 AJ |
1 | /* |
2 | * QEMU Microsoft serial mouse emulation | |
3 | * | |
4 | * Copyright (c) 2008 Lubomir Rintel | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | * THE SOFTWARE. | |
23 | */ | |
0b8fa32f | 24 | |
9c058332 | 25 | #include "qemu/osdep.h" |
0b8fa32f | 26 | #include "qemu/module.h" |
e0cf7f23 | 27 | #include "qemu/fifo8.h" |
8228e353 | 28 | #include "chardev/char.h" |
a39fe105 | 29 | #include "chardev/char-serial.h" |
28ecbaee | 30 | #include "ui/console.h" |
96d7c072 | 31 | #include "ui/input.h" |
db1015e9 | 32 | #include "qom/object.h" |
aa71cf80 | 33 | |
a39fe105 AM |
34 | #define MSMOUSE_LO6(n) ((n) & 0x3f) |
35 | #define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6) | |
36 | #define MSMOUSE_PWR(cm) (cm & (CHR_TIOCM_RTS | CHR_TIOCM_DTR)) | |
aa71cf80 | 37 | |
50d03d48 AM |
38 | /* Serial PnP for 6 bit devices/mice sends all ASCII chars - 0x20 */ |
39 | #define M(c) (c - 0x20) | |
e0cf7f23 AM |
40 | /* Serial fifo size. */ |
41 | #define MSMOUSE_BUF_SZ 64 | |
42 | ||
43 | /* Mouse ID: Send "M3" cause we behave like a 3 button logitech mouse. */ | |
44 | const uint8_t mouse_id[] = {'M', '3'}; | |
50d03d48 AM |
45 | /* |
46 | * PnP start "(", PnP version (1.0), vendor ID, product ID, '\\', | |
47 | * serial ID (omitted), '\\', MS class name, '\\', driver ID (omitted), '\\', | |
48 | * product description, checksum, ")" | |
49 | * Missing parts are inserted later. | |
50 | */ | |
51 | const uint8_t pnp_data[] = {M('('), 1, '$', M('Q'), M('M'), M('U'), | |
52 | M('0'), M('0'), M('0'), M('1'), | |
53 | M('\\'), M('\\'), | |
54 | M('M'), M('O'), M('U'), M('S'), M('E'), | |
55 | M('\\'), M('\\')}; | |
e0cf7f23 | 56 | |
db1015e9 | 57 | struct MouseChardev { |
0ec7b3e7 | 58 | Chardev parent; |
41ac54b2 | 59 | |
96d7c072 | 60 | QemuInputHandlerState *hs; |
a39fe105 | 61 | int tiocm; |
96d7c072 GH |
62 | int axis[INPUT_AXIS__MAX]; |
63 | bool btns[INPUT_BUTTON__MAX]; | |
d7b7f526 | 64 | bool btnc[INPUT_BUTTON__MAX]; |
e0cf7f23 | 65 | Fifo8 outbuf; |
db1015e9 EH |
66 | }; |
67 | typedef struct MouseChardev MouseChardev; | |
cde8dcbc | 68 | |
777357d7 | 69 | #define TYPE_CHARDEV_MSMOUSE "chardev-msmouse" |
8110fa1d EH |
70 | DECLARE_INSTANCE_CHECKER(MouseChardev, MOUSE_CHARDEV, |
71 | TYPE_CHARDEV_MSMOUSE) | |
777357d7 | 72 | |
0ec7b3e7 | 73 | static void msmouse_chr_accept_input(Chardev *chr) |
57a4e3b9 | 74 | { |
777357d7 | 75 | MouseChardev *mouse = MOUSE_CHARDEV(chr); |
e0cf7f23 | 76 | uint32_t len, avail; |
57a4e3b9 GH |
77 | |
78 | len = qemu_chr_be_can_write(chr); | |
e0cf7f23 AM |
79 | avail = fifo8_num_used(&mouse->outbuf); |
80 | while (len > 0 && avail > 0) { | |
81 | const uint8_t *buf; | |
82 | uint32_t size; | |
83 | ||
84 | buf = fifo8_pop_buf(&mouse->outbuf, MIN(len, avail), &size); | |
85 | qemu_chr_be_write(chr, buf, size); | |
86 | len = qemu_chr_be_can_write(chr); | |
87 | avail -= size; | |
57a4e3b9 GH |
88 | } |
89 | } | |
90 | ||
0ec7b3e7 | 91 | static void msmouse_queue_event(MouseChardev *mouse) |
aa71cf80 | 92 | { |
aa71cf80 | 93 | unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 }; |
d7b7f526 | 94 | int dx, dy, count = 3; |
96d7c072 GH |
95 | |
96 | dx = mouse->axis[INPUT_AXIS_X]; | |
97 | mouse->axis[INPUT_AXIS_X] = 0; | |
98 | ||
99 | dy = mouse->axis[INPUT_AXIS_Y]; | |
100 | mouse->axis[INPUT_AXIS_Y] = 0; | |
aa71cf80 AJ |
101 | |
102 | /* Movement deltas */ | |
103 | bytes[0] |= (MSMOUSE_HI2(dy) << 2) | MSMOUSE_HI2(dx); | |
104 | bytes[1] |= MSMOUSE_LO6(dx); | |
105 | bytes[2] |= MSMOUSE_LO6(dy); | |
106 | ||
107 | /* Buttons */ | |
96d7c072 GH |
108 | bytes[0] |= (mouse->btns[INPUT_BUTTON_LEFT] ? 0x20 : 0x00); |
109 | bytes[0] |= (mouse->btns[INPUT_BUTTON_RIGHT] ? 0x10 : 0x00); | |
d7b7f526 GH |
110 | if (mouse->btns[INPUT_BUTTON_MIDDLE] || |
111 | mouse->btnc[INPUT_BUTTON_MIDDLE]) { | |
112 | bytes[3] |= (mouse->btns[INPUT_BUTTON_MIDDLE] ? 0x20 : 0x00); | |
113 | mouse->btnc[INPUT_BUTTON_MIDDLE] = false; | |
e0cf7f23 | 114 | count++; |
d7b7f526 GH |
115 | } |
116 | ||
e0cf7f23 AM |
117 | if (fifo8_num_free(&mouse->outbuf) >= count) { |
118 | fifo8_push_all(&mouse->outbuf, bytes, count); | |
57a4e3b9 GH |
119 | } else { |
120 | /* queue full -> drop event */ | |
121 | } | |
96d7c072 | 122 | } |
57a4e3b9 | 123 | |
96d7c072 GH |
124 | static void msmouse_input_event(DeviceState *dev, QemuConsole *src, |
125 | InputEvent *evt) | |
126 | { | |
777357d7 | 127 | MouseChardev *mouse = MOUSE_CHARDEV(dev); |
96d7c072 GH |
128 | InputMoveEvent *move; |
129 | InputBtnEvent *btn; | |
130 | ||
a39fe105 AM |
131 | /* Ignore events if serial mouse powered down. */ |
132 | if (!MSMOUSE_PWR(mouse->tiocm)) { | |
133 | return; | |
134 | } | |
135 | ||
96d7c072 GH |
136 | switch (evt->type) { |
137 | case INPUT_EVENT_KIND_REL: | |
138 | move = evt->u.rel.data; | |
139 | mouse->axis[move->axis] += move->value; | |
140 | break; | |
141 | ||
142 | case INPUT_EVENT_KIND_BTN: | |
143 | btn = evt->u.btn.data; | |
144 | mouse->btns[btn->button] = btn->down; | |
d7b7f526 | 145 | mouse->btnc[btn->button] = true; |
96d7c072 GH |
146 | break; |
147 | ||
148 | default: | |
149 | /* keep gcc happy */ | |
150 | break; | |
151 | } | |
152 | } | |
153 | ||
154 | static void msmouse_input_sync(DeviceState *dev) | |
155 | { | |
777357d7 MAL |
156 | MouseChardev *mouse = MOUSE_CHARDEV(dev); |
157 | Chardev *chr = CHARDEV(dev); | |
96d7c072 | 158 | |
a39fe105 AM |
159 | /* Ignore events if serial mouse powered down. */ |
160 | if (!MSMOUSE_PWR(mouse->tiocm)) { | |
161 | return; | |
162 | } | |
163 | ||
96d7c072 | 164 | msmouse_queue_event(mouse); |
41ac54b2 | 165 | msmouse_chr_accept_input(chr); |
aa71cf80 AJ |
166 | } |
167 | ||
0ec7b3e7 | 168 | static int msmouse_chr_write(struct Chardev *s, const uint8_t *buf, int len) |
aa71cf80 AJ |
169 | { |
170 | /* Ignore writes to mouse port */ | |
171 | return len; | |
172 | } | |
173 | ||
50d03d48 AM |
174 | static QemuInputHandler msmouse_handler = { |
175 | .name = "QEMU Microsoft Mouse", | |
176 | .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL, | |
177 | .event = msmouse_input_event, | |
178 | .sync = msmouse_input_sync, | |
179 | }; | |
180 | ||
a39fe105 AM |
181 | static int msmouse_ioctl(Chardev *chr, int cmd, void *arg) |
182 | { | |
183 | MouseChardev *mouse = MOUSE_CHARDEV(chr); | |
50d03d48 AM |
184 | int c, i, j; |
185 | uint8_t bytes[MSMOUSE_BUF_SZ / 2]; | |
a39fe105 | 186 | int *targ = (int *)arg; |
50d03d48 AM |
187 | const uint8_t hexchr[16] = {M('0'), M('1'), M('2'), M('3'), M('4'), M('5'), |
188 | M('6'), M('7'), M('8'), M('9'), M('A'), M('B'), | |
189 | M('C'), M('D'), M('E'), M('F')}; | |
a39fe105 AM |
190 | |
191 | switch (cmd) { | |
192 | case CHR_IOCTL_SERIAL_SET_TIOCM: | |
193 | c = mouse->tiocm; | |
194 | mouse->tiocm = *(int *)arg; | |
195 | if (MSMOUSE_PWR(mouse->tiocm)) { | |
196 | if (!MSMOUSE_PWR(c)) { | |
197 | /* | |
50d03d48 AM |
198 | * Power on after reset: Send ID and PnP data |
199 | * No need to check fifo space as it is empty at this point. | |
a39fe105 | 200 | */ |
e0cf7f23 | 201 | fifo8_push_all(&mouse->outbuf, mouse_id, sizeof(mouse_id)); |
50d03d48 AM |
202 | /* Add PnP data: */ |
203 | fifo8_push_all(&mouse->outbuf, pnp_data, sizeof(pnp_data)); | |
204 | /* | |
205 | * Add device description from qemu handler name. | |
206 | * Make sure this all fits into the queue beforehand! | |
207 | */ | |
208 | c = M(')'); | |
209 | for (i = 0; msmouse_handler.name[i]; i++) { | |
210 | bytes[i] = M(msmouse_handler.name[i]); | |
211 | c += bytes[i]; | |
212 | } | |
213 | /* Calc more of checksum */ | |
214 | for (j = 0; j < sizeof(pnp_data); j++) { | |
215 | c += pnp_data[j]; | |
216 | } | |
217 | c &= 0xff; | |
218 | bytes[i++] = hexchr[c >> 4]; | |
219 | bytes[i++] = hexchr[c & 0x0f]; | |
220 | bytes[i++] = M(')'); | |
221 | fifo8_push_all(&mouse->outbuf, bytes, i); | |
a39fe105 AM |
222 | /* Start sending data to serial. */ |
223 | msmouse_chr_accept_input(chr); | |
224 | } | |
225 | break; | |
226 | } | |
227 | /* | |
228 | * Reset mouse buffers on power down. | |
229 | * Mouse won't send anything without power. | |
230 | */ | |
e0cf7f23 | 231 | fifo8_reset(&mouse->outbuf); |
a39fe105 AM |
232 | memset(mouse->axis, 0, sizeof(mouse->axis)); |
233 | memset(mouse->btns, false, sizeof(mouse->btns)); | |
234 | memset(mouse->btnc, false, sizeof(mouse->btns)); | |
235 | break; | |
236 | case CHR_IOCTL_SERIAL_GET_TIOCM: | |
237 | /* Remember line control status. */ | |
238 | *targ = mouse->tiocm; | |
239 | break; | |
240 | default: | |
241 | return -ENOTSUP; | |
242 | } | |
243 | return 0; | |
244 | } | |
245 | ||
8955e891 | 246 | static void char_msmouse_finalize(Object *obj) |
aa71cf80 | 247 | { |
8955e891 | 248 | MouseChardev *mouse = MOUSE_CHARDEV(obj); |
cde8dcbc | 249 | |
fc0c1285 MD |
250 | if (mouse->hs) { |
251 | qemu_input_handler_unregister(mouse->hs); | |
252 | } | |
e0cf7f23 | 253 | fifo8_destroy(&mouse->outbuf); |
aa71cf80 AJ |
254 | } |
255 | ||
777357d7 MAL |
256 | static void msmouse_chr_open(Chardev *chr, |
257 | ChardevBackend *backend, | |
258 | bool *be_opened, | |
259 | Error **errp) | |
aa71cf80 | 260 | { |
777357d7 | 261 | MouseChardev *mouse = MOUSE_CHARDEV(chr); |
aa71cf80 | 262 | |
82878dac | 263 | *be_opened = false; |
96d7c072 GH |
264 | mouse->hs = qemu_input_handler_register((DeviceState *)mouse, |
265 | &msmouse_handler); | |
a39fe105 | 266 | mouse->tiocm = 0; |
e0cf7f23 | 267 | fifo8_create(&mouse->outbuf, MSMOUSE_BUF_SZ); |
777357d7 | 268 | } |
cde8dcbc | 269 | |
777357d7 MAL |
270 | static void char_msmouse_class_init(ObjectClass *oc, void *data) |
271 | { | |
272 | ChardevClass *cc = CHARDEV_CLASS(oc); | |
aa71cf80 | 273 | |
777357d7 MAL |
274 | cc->open = msmouse_chr_open; |
275 | cc->chr_write = msmouse_chr_write; | |
276 | cc->chr_accept_input = msmouse_chr_accept_input; | |
a39fe105 | 277 | cc->chr_ioctl = msmouse_ioctl; |
aa71cf80 | 278 | } |
5ab8211b | 279 | |
777357d7 MAL |
280 | static const TypeInfo char_msmouse_type_info = { |
281 | .name = TYPE_CHARDEV_MSMOUSE, | |
282 | .parent = TYPE_CHARDEV, | |
283 | .instance_size = sizeof(MouseChardev), | |
8955e891 | 284 | .instance_finalize = char_msmouse_finalize, |
777357d7 MAL |
285 | .class_init = char_msmouse_class_init, |
286 | }; | |
287 | ||
5ab8211b AL |
288 | static void register_types(void) |
289 | { | |
777357d7 | 290 | type_register_static(&char_msmouse_type_info); |
5ab8211b AL |
291 | } |
292 | ||
293 | type_init(register_types); |