xref: /qemu/chardev/msmouse.c (revision b49f4755)
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  */
24 
25 #include "qemu/osdep.h"
26 #include "qemu/module.h"
27 #include "qemu/fifo8.h"
28 #include "chardev/char.h"
29 #include "chardev/char-serial.h"
30 #include "ui/console.h"
31 #include "ui/input.h"
32 #include "qom/object.h"
33 
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))
37 
38 /* Serial PnP for 6 bit devices/mice sends all ASCII chars - 0x20 */
39 #define M(c) (c - 0x20)
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'};
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('\\')};
56 
57 struct MouseChardev {
58     Chardev parent;
59 
60     QemuInputHandlerState *hs;
61     int tiocm;
62     int axis[INPUT_AXIS__MAX];
63     bool btns[INPUT_BUTTON__MAX];
64     bool btnc[INPUT_BUTTON__MAX];
65     Fifo8 outbuf;
66 };
67 typedef struct MouseChardev MouseChardev;
68 
69 #define TYPE_CHARDEV_MSMOUSE "chardev-msmouse"
70 DECLARE_INSTANCE_CHECKER(MouseChardev, MOUSE_CHARDEV,
71                          TYPE_CHARDEV_MSMOUSE)
72 
73 static void msmouse_chr_accept_input(Chardev *chr)
74 {
75     MouseChardev *mouse = MOUSE_CHARDEV(chr);
76     uint32_t len, avail;
77 
78     len = qemu_chr_be_can_write(chr);
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;
88     }
89 }
90 
91 static void msmouse_queue_event(MouseChardev *mouse)
92 {
93     unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 };
94     int dx, dy, count = 3;
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;
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 */
108     bytes[0] |= (mouse->btns[INPUT_BUTTON_LEFT]   ? 0x20 : 0x00);
109     bytes[0] |= (mouse->btns[INPUT_BUTTON_RIGHT]  ? 0x10 : 0x00);
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;
114         count++;
115     }
116 
117     if (fifo8_num_free(&mouse->outbuf) >= count) {
118         fifo8_push_all(&mouse->outbuf, bytes, count);
119     } else {
120         /* queue full -> drop event */
121     }
122 }
123 
124 static void msmouse_input_event(DeviceState *dev, QemuConsole *src,
125                                 InputEvent *evt)
126 {
127     MouseChardev *mouse = MOUSE_CHARDEV(dev);
128     InputMoveEvent *move;
129     InputBtnEvent *btn;
130 
131     /* Ignore events if serial mouse powered down. */
132     if (!MSMOUSE_PWR(mouse->tiocm)) {
133         return;
134     }
135 
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;
145         mouse->btnc[btn->button] = true;
146         break;
147 
148     default:
149         /* keep gcc happy */
150         break;
151     }
152 }
153 
154 static void msmouse_input_sync(DeviceState *dev)
155 {
156     MouseChardev *mouse = MOUSE_CHARDEV(dev);
157     Chardev *chr = CHARDEV(dev);
158 
159     /* Ignore events if serial mouse powered down. */
160     if (!MSMOUSE_PWR(mouse->tiocm)) {
161         return;
162     }
163 
164     msmouse_queue_event(mouse);
165     msmouse_chr_accept_input(chr);
166 }
167 
168 static int msmouse_chr_write(struct Chardev *s, const uint8_t *buf, int len)
169 {
170     /* Ignore writes to mouse port */
171     return len;
172 }
173 
174 static const 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 
181 static int msmouse_ioctl(Chardev *chr, int cmd, void *arg)
182 {
183     MouseChardev *mouse = MOUSE_CHARDEV(chr);
184     int c, i, j;
185     uint8_t bytes[MSMOUSE_BUF_SZ / 2];
186     int *targ = (int *)arg;
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')};
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                 /*
198                  * Power on after reset: Send ID and PnP data
199                  * No need to check fifo space as it is empty at this point.
200                  */
201                 fifo8_push_all(&mouse->outbuf, mouse_id, sizeof(mouse_id));
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);
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          */
231         fifo8_reset(&mouse->outbuf);
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 
246 static void char_msmouse_finalize(Object *obj)
247 {
248     MouseChardev *mouse = MOUSE_CHARDEV(obj);
249 
250     if (mouse->hs) {
251         qemu_input_handler_unregister(mouse->hs);
252     }
253     fifo8_destroy(&mouse->outbuf);
254 }
255 
256 static void msmouse_chr_open(Chardev *chr,
257                              ChardevBackend *backend,
258                              bool *be_opened,
259                              Error **errp)
260 {
261     MouseChardev *mouse = MOUSE_CHARDEV(chr);
262 
263     *be_opened = false;
264     mouse->hs = qemu_input_handler_register((DeviceState *)mouse,
265                                             &msmouse_handler);
266     mouse->tiocm = 0;
267     fifo8_create(&mouse->outbuf, MSMOUSE_BUF_SZ);
268 }
269 
270 static void char_msmouse_class_init(ObjectClass *oc, void *data)
271 {
272     ChardevClass *cc = CHARDEV_CLASS(oc);
273 
274     cc->open = msmouse_chr_open;
275     cc->chr_write = msmouse_chr_write;
276     cc->chr_accept_input = msmouse_chr_accept_input;
277     cc->chr_ioctl = msmouse_ioctl;
278 }
279 
280 static const TypeInfo char_msmouse_type_info = {
281     .name = TYPE_CHARDEV_MSMOUSE,
282     .parent = TYPE_CHARDEV,
283     .instance_size = sizeof(MouseChardev),
284     .instance_finalize = char_msmouse_finalize,
285     .class_init = char_msmouse_class_init,
286 };
287 
288 static void register_types(void)
289 {
290     type_register_static(&char_msmouse_type_info);
291 }
292 
293 type_init(register_types);
294