]>
Commit | Line | Data |
---|---|---|
e7151f83 AL |
1 | /* |
2 | * xen paravirt framebuffer backend | |
3 | * | |
4 | * Copyright IBM, Corp. 2005-2006 | |
5 | * Copyright Red Hat, Inc. 2006-2008 | |
6 | * | |
7 | * Authors: | |
8 | * Anthony Liguori <[email protected]>, | |
9 | * Markus Armbruster <[email protected]>, | |
10 | * Daniel P. Berrange <[email protected]>, | |
11 | * Pat Campbell <[email protected]>, | |
12 | * Gerd Hoffmann <[email protected]> | |
13 | * | |
14 | * This program is free software; you can redistribute it and/or modify | |
15 | * it under the terms of the GNU General Public License as published by | |
16 | * the Free Software Foundation; under version 2 of the License. | |
17 | * | |
18 | * This program is distributed in the hope that it will be useful, | |
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | * GNU General Public License for more details. | |
22 | * | |
23 | * You should have received a copy of the GNU General Public License along | |
8167ee88 | 24 | * with this program; if not, see <http://www.gnu.org/licenses/>. |
e7151f83 AL |
25 | */ |
26 | ||
21cbfe5f | 27 | #include "qemu/osdep.h" |
f0353b0d | 28 | #include "qemu/units.h" |
e7151f83 | 29 | |
83c9f4ca | 30 | #include "hw/hw.h" |
a1ccbafd | 31 | #include "ui/input.h" |
28ecbaee | 32 | #include "ui/console.h" |
2d0ed5e6 | 33 | #include "hw/xen/xen-legacy-backend.h" |
e7151f83 | 34 | |
a3434a2d AP |
35 | #include "hw/xen/interface/io/fbif.h" |
36 | #include "hw/xen/interface/io/kbdif.h" | |
37 | #include "hw/xen/interface/io/protocols.h" | |
b41f6719 | 38 | |
91043dad DK |
39 | #include "trace.h" |
40 | ||
e7151f83 AL |
41 | #ifndef BTN_LEFT |
42 | #define BTN_LEFT 0x110 /* from <linux/input.h> */ | |
43 | #endif | |
44 | ||
45 | /* -------------------------------------------------------------------- */ | |
46 | ||
47 | struct common { | |
2d0ed5e6 | 48 | struct XenLegacyDevice xendev; /* must be first */ |
e7151f83 | 49 | void *page; |
e7151f83 AL |
50 | }; |
51 | ||
52 | struct XenInput { | |
53 | struct common c; | |
54 | int abs_pointer_wanted; /* Whether guest supports absolute pointer */ | |
34975e53 | 55 | int raw_pointer_wanted; /* Whether guest supports raw (unscaled) pointer */ |
a1ccbafd OS |
56 | QemuInputHandlerState *qkbd; |
57 | QemuInputHandlerState *qmou; | |
58 | int axis[INPUT_AXIS__MAX]; | |
59 | int wheel; | |
e7151f83 AL |
60 | }; |
61 | ||
62 | #define UP_QUEUE 8 | |
63 | ||
64 | struct XenFB { | |
65 | struct common c; | |
9f2130f5 | 66 | QemuConsole *con; |
e7151f83 AL |
67 | size_t fb_len; |
68 | int row_stride; | |
69 | int depth; | |
70 | int width; | |
71 | int height; | |
72 | int offset; | |
73 | void *pixels; | |
74 | int fbpages; | |
75 | int feature_update; | |
e7151f83 | 76 | int bug_trigger; |
e7151f83 AL |
77 | int do_resize; |
78 | ||
79 | struct { | |
80 | int x,y,w,h; | |
81 | } up_rects[UP_QUEUE]; | |
82 | int up_count; | |
83 | int up_fullscreen; | |
84 | }; | |
9f2130f5 | 85 | static const GraphicHwOps xenfb_ops; |
e7151f83 AL |
86 | |
87 | /* -------------------------------------------------------------------- */ | |
88 | ||
89 | static int common_bind(struct common *c) | |
90 | { | |
9ed257d1 IC |
91 | uint64_t val; |
92 | xen_pfn_t mfn; | |
e7151f83 | 93 | |
9ed257d1 | 94 | if (xenstore_read_fe_uint64(&c->xendev, "page-ref", &val) == -1) |
c22e91b1 | 95 | return -1; |
9ed257d1 IC |
96 | mfn = (xen_pfn_t)val; |
97 | assert(val == mfn); | |
643f5932 | 98 | |
e7151f83 | 99 | if (xenstore_read_fe_int(&c->xendev, "event-channel", &c->xendev.remote_port) == -1) |
c22e91b1 | 100 | return -1; |
e7151f83 | 101 | |
e0cb42ae IC |
102 | c->page = xenforeignmemory_map(xen_fmem, c->xendev.dom, |
103 | PROT_READ | PROT_WRITE, 1, &mfn, NULL); | |
e7151f83 | 104 | if (c->page == NULL) |
c22e91b1 | 105 | return -1; |
e7151f83 AL |
106 | |
107 | xen_be_bind_evtchn(&c->xendev); | |
96c77dba | 108 | xen_pv_printf(&c->xendev, 1, |
c22e91b1 EC |
109 | "ring mfn %"PRI_xen_pfn", remote-port %d, local-port %d\n", |
110 | mfn, c->xendev.remote_port, c->xendev.local_port); | |
e7151f83 AL |
111 | |
112 | return 0; | |
113 | } | |
114 | ||
115 | static void common_unbind(struct common *c) | |
116 | { | |
65807f4b | 117 | xen_pv_unbind_evtchn(&c->xendev); |
e7151f83 | 118 | if (c->page) { |
e0cb42ae | 119 | xenforeignmemory_unmap(xen_fmem, c->page, 1); |
e7151f83 AL |
120 | c->page = NULL; |
121 | } | |
122 | } | |
123 | ||
124 | /* -------------------------------------------------------------------- */ | |
e7151f83 AL |
125 | /* Send an event to the keyboard frontend driver */ |
126 | static int xenfb_kbd_event(struct XenInput *xenfb, | |
127 | union xenkbd_in_event *event) | |
128 | { | |
129 | struct xenkbd_page *page = xenfb->c.page; | |
130 | uint32_t prod; | |
131 | ||
132 | if (xenfb->c.xendev.be_state != XenbusStateConnected) | |
133 | return 0; | |
134 | if (!page) | |
135 | return 0; | |
136 | ||
137 | prod = page->in_prod; | |
138 | if (prod - page->in_cons == XENKBD_IN_RING_LEN) { | |
139 | errno = EAGAIN; | |
140 | return -1; | |
141 | } | |
142 | ||
143 | xen_mb(); /* ensure ring space available */ | |
144 | XENKBD_IN_RING_REF(page, prod) = *event; | |
145 | xen_wmb(); /* ensure ring contents visible */ | |
146 | page->in_prod = prod + 1; | |
ba18fa2a | 147 | return xen_pv_send_notify(&xenfb->c.xendev); |
e7151f83 AL |
148 | } |
149 | ||
150 | /* Send a keyboard (or mouse button) event */ | |
151 | static int xenfb_send_key(struct XenInput *xenfb, bool down, int keycode) | |
152 | { | |
153 | union xenkbd_in_event event; | |
154 | ||
155 | memset(&event, 0, XENKBD_IN_EVENT_SIZE); | |
156 | event.type = XENKBD_TYPE_KEY; | |
157 | event.key.pressed = down ? 1 : 0; | |
158 | event.key.keycode = keycode; | |
159 | ||
160 | return xenfb_kbd_event(xenfb, &event); | |
161 | } | |
162 | ||
163 | /* Send a relative mouse movement event */ | |
164 | static int xenfb_send_motion(struct XenInput *xenfb, | |
165 | int rel_x, int rel_y, int rel_z) | |
166 | { | |
167 | union xenkbd_in_event event; | |
168 | ||
169 | memset(&event, 0, XENKBD_IN_EVENT_SIZE); | |
170 | event.type = XENKBD_TYPE_MOTION; | |
171 | event.motion.rel_x = rel_x; | |
172 | event.motion.rel_y = rel_y; | |
e7151f83 | 173 | event.motion.rel_z = rel_z; |
e7151f83 AL |
174 | |
175 | return xenfb_kbd_event(xenfb, &event); | |
176 | } | |
177 | ||
178 | /* Send an absolute mouse movement event */ | |
179 | static int xenfb_send_position(struct XenInput *xenfb, | |
180 | int abs_x, int abs_y, int z) | |
181 | { | |
182 | union xenkbd_in_event event; | |
183 | ||
184 | memset(&event, 0, XENKBD_IN_EVENT_SIZE); | |
185 | event.type = XENKBD_TYPE_POS; | |
186 | event.pos.abs_x = abs_x; | |
187 | event.pos.abs_y = abs_y; | |
e7151f83 | 188 | event.pos.rel_z = z; |
e7151f83 AL |
189 | |
190 | return xenfb_kbd_event(xenfb, &event); | |
191 | } | |
192 | ||
193 | /* | |
194 | * Send a key event from the client to the guest OS | |
a1ccbafd | 195 | * QEMU gives us a QCode. |
e7151f83 AL |
196 | * We have to turn this into a Linux Input layer keycode. |
197 | * | |
e7151f83 AL |
198 | * Wish we could just send scancodes straight to the guest which |
199 | * already has code for dealing with this... | |
200 | */ | |
a1ccbafd OS |
201 | static void xenfb_key_event(DeviceState *dev, QemuConsole *src, |
202 | InputEvent *evt) | |
e7151f83 | 203 | { |
a1ccbafd OS |
204 | struct XenInput *xenfb = (struct XenInput *)dev; |
205 | InputKeyEvent *key = evt->u.key.data; | |
206 | int qcode = qemu_input_key_value_to_qcode(key->key); | |
207 | int lnx; | |
208 | ||
209 | if (qcode < qemu_input_map_qcode_to_linux_len) { | |
210 | lnx = qemu_input_map_qcode_to_linux[qcode]; | |
211 | ||
212 | if (lnx) { | |
213 | trace_xenfb_key_event(xenfb, lnx, key->down); | |
214 | xenfb_send_key(xenfb, key->down, lnx); | |
215 | } | |
e7151f83 | 216 | } |
e7151f83 AL |
217 | } |
218 | ||
219 | /* | |
220 | * Send a mouse event from the client to the guest OS | |
221 | * | |
222 | * The QEMU mouse can be in either relative, or absolute mode. | |
223 | * Movement is sent separately from button state, which has to | |
224 | * be encoded as virtual key events. We also don't actually get | |
225 | * given any button up/down events, so have to track changes in | |
226 | * the button state. | |
227 | */ | |
a1ccbafd OS |
228 | static void xenfb_mouse_event(DeviceState *dev, QemuConsole *src, |
229 | InputEvent *evt) | |
e7151f83 | 230 | { |
a1ccbafd OS |
231 | struct XenInput *xenfb = (struct XenInput *)dev; |
232 | InputBtnEvent *btn; | |
233 | InputMoveEvent *move; | |
234 | QemuConsole *con; | |
9f2130f5 | 235 | DisplaySurface *surface; |
a1ccbafd OS |
236 | int scale; |
237 | ||
238 | switch (evt->type) { | |
239 | case INPUT_EVENT_KIND_BTN: | |
240 | btn = evt->u.btn.data; | |
241 | switch (btn->button) { | |
242 | case INPUT_BUTTON_LEFT: | |
243 | xenfb_send_key(xenfb, btn->down, BTN_LEFT); | |
244 | break; | |
245 | case INPUT_BUTTON_RIGHT: | |
246 | xenfb_send_key(xenfb, btn->down, BTN_LEFT + 1); | |
247 | break; | |
248 | case INPUT_BUTTON_MIDDLE: | |
249 | xenfb_send_key(xenfb, btn->down, BTN_LEFT + 2); | |
250 | break; | |
251 | case INPUT_BUTTON_WHEEL_UP: | |
252 | if (btn->down) { | |
253 | xenfb->wheel--; | |
254 | } | |
255 | break; | |
256 | case INPUT_BUTTON_WHEEL_DOWN: | |
257 | if (btn->down) { | |
258 | xenfb->wheel++; | |
259 | } | |
260 | break; | |
261 | default: | |
262 | break; | |
263 | } | |
264 | break; | |
265 | ||
266 | case INPUT_EVENT_KIND_ABS: | |
267 | move = evt->u.abs.data; | |
34975e53 OS |
268 | if (xenfb->raw_pointer_wanted) { |
269 | xenfb->axis[move->axis] = move->value; | |
270 | } else { | |
271 | con = qemu_console_lookup_by_index(0); | |
272 | if (!con) { | |
273 | xen_pv_printf(&xenfb->c.xendev, 0, "No QEMU console available"); | |
274 | return; | |
275 | } | |
276 | surface = qemu_console_surface(con); | |
277 | switch (move->axis) { | |
278 | case INPUT_AXIS_X: | |
279 | scale = surface_width(surface) - 1; | |
280 | break; | |
281 | case INPUT_AXIS_Y: | |
282 | scale = surface_height(surface) - 1; | |
283 | break; | |
284 | default: | |
285 | scale = 0x8000; | |
286 | break; | |
287 | } | |
288 | xenfb->axis[move->axis] = move->value * scale / 0x7fff; | |
a1ccbafd | 289 | } |
a1ccbafd OS |
290 | break; |
291 | ||
292 | case INPUT_EVENT_KIND_REL: | |
293 | move = evt->u.rel.data; | |
294 | xenfb->axis[move->axis] += move->value; | |
295 | break; | |
296 | ||
297 | default: | |
298 | break; | |
9f2130f5 | 299 | } |
a1ccbafd | 300 | } |
9f2130f5 | 301 | |
a1ccbafd OS |
302 | static void xenfb_mouse_sync(DeviceState *dev) |
303 | { | |
304 | struct XenInput *xenfb = (struct XenInput *)dev; | |
e7151f83 | 305 | |
a1ccbafd OS |
306 | trace_xenfb_mouse_event(xenfb, xenfb->axis[INPUT_AXIS_X], |
307 | xenfb->axis[INPUT_AXIS_Y], | |
308 | xenfb->wheel, 0, | |
91043dad | 309 | xenfb->abs_pointer_wanted); |
a1ccbafd OS |
310 | if (xenfb->abs_pointer_wanted) { |
311 | xenfb_send_position(xenfb, xenfb->axis[INPUT_AXIS_X], | |
312 | xenfb->axis[INPUT_AXIS_Y], | |
313 | xenfb->wheel); | |
314 | } else { | |
315 | xenfb_send_motion(xenfb, xenfb->axis[INPUT_AXIS_X], | |
316 | xenfb->axis[INPUT_AXIS_Y], | |
317 | xenfb->wheel); | |
318 | xenfb->axis[INPUT_AXIS_X] = 0; | |
319 | xenfb->axis[INPUT_AXIS_Y] = 0; | |
e7151f83 | 320 | } |
a1ccbafd | 321 | xenfb->wheel = 0; |
e7151f83 AL |
322 | } |
323 | ||
a1ccbafd OS |
324 | static QemuInputHandler xenfb_keyboard = { |
325 | .name = "Xen PV Keyboard", | |
326 | .mask = INPUT_EVENT_MASK_KEY, | |
327 | .event = xenfb_key_event, | |
328 | }; | |
329 | ||
330 | static QemuInputHandler xenfb_abs_mouse = { | |
331 | .name = "Xen PV Mouse", | |
332 | .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS, | |
333 | .event = xenfb_mouse_event, | |
334 | .sync = xenfb_mouse_sync, | |
335 | }; | |
336 | ||
337 | static QemuInputHandler xenfb_rel_mouse = { | |
338 | .name = "Xen PV Mouse", | |
339 | .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL, | |
340 | .event = xenfb_mouse_event, | |
341 | .sync = xenfb_mouse_sync, | |
342 | }; | |
343 | ||
2d0ed5e6 | 344 | static int input_init(struct XenLegacyDevice *xendev) |
e7151f83 | 345 | { |
e7151f83 | 346 | xenstore_write_be_int(xendev, "feature-abs-pointer", 1); |
34975e53 | 347 | xenstore_write_be_int(xendev, "feature-raw-pointer", 1); |
e7151f83 AL |
348 | return 0; |
349 | } | |
350 | ||
2d0ed5e6 | 351 | static int input_initialise(struct XenLegacyDevice *xendev) |
e7151f83 AL |
352 | { |
353 | struct XenInput *in = container_of(xendev, struct XenInput, c.xendev); | |
354 | int rc; | |
355 | ||
e7151f83 AL |
356 | rc = common_bind(&in->c); |
357 | if (rc != 0) | |
358 | return rc; | |
359 | ||
6d646730 JH |
360 | return 0; |
361 | } | |
362 | ||
2d0ed5e6 | 363 | static void input_connected(struct XenLegacyDevice *xendev) |
6d646730 JH |
364 | { |
365 | struct XenInput *in = container_of(xendev, struct XenInput, c.xendev); | |
366 | ||
367 | if (xenstore_read_fe_int(xendev, "request-abs-pointer", | |
368 | &in->abs_pointer_wanted) == -1) { | |
369 | in->abs_pointer_wanted = 0; | |
370 | } | |
34975e53 OS |
371 | if (xenstore_read_fe_int(xendev, "request-raw-pointer", |
372 | &in->raw_pointer_wanted) == -1) { | |
373 | in->raw_pointer_wanted = 0; | |
374 | } | |
375 | if (in->raw_pointer_wanted && in->abs_pointer_wanted == 0) { | |
376 | xen_pv_printf(xendev, 0, "raw pointer set without abs pointer"); | |
377 | } | |
6d646730 | 378 | |
a1ccbafd OS |
379 | if (in->qkbd) { |
380 | qemu_input_handler_unregister(in->qkbd); | |
381 | } | |
382 | if (in->qmou) { | |
383 | qemu_input_handler_unregister(in->qmou); | |
6d646730 | 384 | } |
91043dad | 385 | trace_xenfb_input_connected(xendev, in->abs_pointer_wanted); |
a1ccbafd OS |
386 | |
387 | in->qkbd = qemu_input_handler_register((DeviceState *)in, &xenfb_keyboard); | |
388 | in->qmou = qemu_input_handler_register((DeviceState *)in, | |
389 | in->abs_pointer_wanted ? &xenfb_abs_mouse : &xenfb_rel_mouse); | |
d12c5b7d OS |
390 | |
391 | if (in->raw_pointer_wanted) { | |
392 | qemu_input_handler_activate(in->qkbd); | |
393 | qemu_input_handler_activate(in->qmou); | |
394 | } | |
e7151f83 AL |
395 | } |
396 | ||
2d0ed5e6 | 397 | static void input_disconnect(struct XenLegacyDevice *xendev) |
e7151f83 AL |
398 | { |
399 | struct XenInput *in = container_of(xendev, struct XenInput, c.xendev); | |
400 | ||
a1ccbafd OS |
401 | if (in->qkbd) { |
402 | qemu_input_handler_unregister(in->qkbd); | |
403 | in->qkbd = NULL; | |
404 | } | |
405 | if (in->qmou) { | |
406 | qemu_input_handler_unregister(in->qmou); | |
407 | in->qmou = NULL; | |
e7151f83 | 408 | } |
e7151f83 AL |
409 | common_unbind(&in->c); |
410 | } | |
411 | ||
2d0ed5e6 | 412 | static void input_event(struct XenLegacyDevice *xendev) |
e7151f83 AL |
413 | { |
414 | struct XenInput *xenfb = container_of(xendev, struct XenInput, c.xendev); | |
415 | struct xenkbd_page *page = xenfb->c.page; | |
416 | ||
417 | /* We don't understand any keyboard events, so just ignore them. */ | |
418 | if (page->out_prod == page->out_cons) | |
419 | return; | |
420 | page->out_cons = page->out_prod; | |
ba18fa2a | 421 | xen_pv_send_notify(&xenfb->c.xendev); |
e7151f83 AL |
422 | } |
423 | ||
424 | /* -------------------------------------------------------------------- */ | |
425 | ||
643f5932 | 426 | static void xenfb_copy_mfns(int mode, int count, xen_pfn_t *dst, void *src) |
e7151f83 AL |
427 | { |
428 | uint32_t *src32 = src; | |
429 | uint64_t *src64 = src; | |
430 | int i; | |
431 | ||
432 | for (i = 0; i < count; i++) | |
433 | dst[i] = (mode == 32) ? src32[i] : src64[i]; | |
434 | } | |
435 | ||
436 | static int xenfb_map_fb(struct XenFB *xenfb) | |
437 | { | |
438 | struct xenfb_page *page = xenfb->c.page; | |
439 | char *protocol = xenfb->c.xendev.protocol; | |
440 | int n_fbdirs; | |
643f5932 SS |
441 | xen_pfn_t *pgmfns = NULL; |
442 | xen_pfn_t *fbmfns = NULL; | |
e7151f83 AL |
443 | void *map, *pd; |
444 | int mode, ret = -1; | |
445 | ||
446 | /* default to native */ | |
447 | pd = page->pd; | |
448 | mode = sizeof(unsigned long) * 8; | |
449 | ||
450 | if (!protocol) { | |
451 | /* | |
452 | * Undefined protocol, some guesswork needed. | |
453 | * | |
454 | * Old frontends which don't set the protocol use | |
455 | * one page directory only, thus pd[1] must be zero. | |
456 | * pd[1] of the 32bit struct layout and the lower | |
457 | * 32 bits of pd[0] of the 64bit struct layout have | |
458 | * the same location, so we can check that ... | |
459 | */ | |
460 | uint32_t *ptr32 = NULL; | |
461 | uint32_t *ptr64 = NULL; | |
462 | #if defined(__i386__) | |
463 | ptr32 = (void*)page->pd; | |
464 | ptr64 = ((void*)page->pd) + 4; | |
465 | #elif defined(__x86_64__) | |
466 | ptr32 = ((void*)page->pd) - 4; | |
467 | ptr64 = (void*)page->pd; | |
468 | #endif | |
469 | if (ptr32) { | |
470 | if (ptr32[1] == 0) { | |
471 | mode = 32; | |
472 | pd = ptr32; | |
473 | } else { | |
474 | mode = 64; | |
475 | pd = ptr64; | |
476 | } | |
477 | } | |
478 | #if defined(__x86_64__) | |
479 | } else if (strcmp(protocol, XEN_IO_PROTO_ABI_X86_32) == 0) { | |
480 | /* 64bit dom0, 32bit domU */ | |
481 | mode = 32; | |
482 | pd = ((void*)page->pd) - 4; | |
483 | #elif defined(__i386__) | |
484 | } else if (strcmp(protocol, XEN_IO_PROTO_ABI_X86_64) == 0) { | |
485 | /* 32bit dom0, 64bit domU */ | |
486 | mode = 64; | |
487 | pd = ((void*)page->pd) + 4; | |
488 | #endif | |
489 | } | |
490 | ||
491 | if (xenfb->pixels) { | |
492 | munmap(xenfb->pixels, xenfb->fbpages * XC_PAGE_SIZE); | |
493 | xenfb->pixels = NULL; | |
494 | } | |
495 | ||
d0448de7 | 496 | xenfb->fbpages = DIV_ROUND_UP(xenfb->fb_len, XC_PAGE_SIZE); |
e7151f83 | 497 | n_fbdirs = xenfb->fbpages * mode / 8; |
d0448de7 | 498 | n_fbdirs = DIV_ROUND_UP(n_fbdirs, XC_PAGE_SIZE); |
e7151f83 | 499 | |
643f5932 SS |
500 | pgmfns = g_malloc0(sizeof(xen_pfn_t) * n_fbdirs); |
501 | fbmfns = g_malloc0(sizeof(xen_pfn_t) * xenfb->fbpages); | |
e7151f83 AL |
502 | |
503 | xenfb_copy_mfns(mode, n_fbdirs, pgmfns, pd); | |
e0cb42ae IC |
504 | map = xenforeignmemory_map(xen_fmem, xenfb->c.xendev.dom, |
505 | PROT_READ, n_fbdirs, pgmfns, NULL); | |
e7151f83 AL |
506 | if (map == NULL) |
507 | goto out; | |
508 | xenfb_copy_mfns(mode, xenfb->fbpages, fbmfns, map); | |
e0cb42ae | 509 | xenforeignmemory_unmap(xen_fmem, map, n_fbdirs); |
e7151f83 | 510 | |
e0cb42ae IC |
511 | xenfb->pixels = xenforeignmemory_map(xen_fmem, xenfb->c.xendev.dom, |
512 | PROT_READ, xenfb->fbpages, fbmfns, NULL); | |
e7151f83 AL |
513 | if (xenfb->pixels == NULL) |
514 | goto out; | |
515 | ||
516 | ret = 0; /* all is fine */ | |
517 | ||
518 | out: | |
7267c094 AL |
519 | g_free(pgmfns); |
520 | g_free(fbmfns); | |
e7151f83 AL |
521 | return ret; |
522 | } | |
523 | ||
524 | static int xenfb_configure_fb(struct XenFB *xenfb, size_t fb_len_lim, | |
c22e91b1 EC |
525 | int width, int height, int depth, |
526 | size_t fb_len, int offset, int row_stride) | |
e7151f83 | 527 | { |
f18793b0 SH |
528 | size_t mfn_sz = sizeof_field(struct xenfb_page, pd[0]); |
529 | size_t pd_len = sizeof_field(struct xenfb_page, pd) / mfn_sz; | |
e7151f83 AL |
530 | size_t fb_pages = pd_len * XC_PAGE_SIZE / mfn_sz; |
531 | size_t fb_len_max = fb_pages * XC_PAGE_SIZE; | |
532 | int max_width, max_height; | |
533 | ||
534 | if (fb_len_lim > fb_len_max) { | |
96c77dba | 535 | xen_pv_printf(&xenfb->c.xendev, 0, |
c22e91b1 EC |
536 | "fb size limit %zu exceeds %zu, corrected\n", |
537 | fb_len_lim, fb_len_max); | |
538 | fb_len_lim = fb_len_max; | |
e7151f83 AL |
539 | } |
540 | if (fb_len_lim && fb_len > fb_len_lim) { | |
96c77dba | 541 | xen_pv_printf(&xenfb->c.xendev, 0, |
c22e91b1 EC |
542 | "frontend fb size %zu limited to %zu\n", |
543 | fb_len, fb_len_lim); | |
544 | fb_len = fb_len_lim; | |
e7151f83 AL |
545 | } |
546 | if (depth != 8 && depth != 16 && depth != 24 && depth != 32) { | |
96c77dba | 547 | xen_pv_printf(&xenfb->c.xendev, 0, |
c22e91b1 EC |
548 | "can't handle frontend fb depth %d\n", |
549 | depth); | |
550 | return -1; | |
e7151f83 AL |
551 | } |
552 | if (row_stride <= 0 || row_stride > fb_len) { | |
96c77dba | 553 | xen_pv_printf(&xenfb->c.xendev, 0, "invalid frontend stride %d\n", |
c22e91b1 EC |
554 | row_stride); |
555 | return -1; | |
e7151f83 AL |
556 | } |
557 | max_width = row_stride / (depth / 8); | |
558 | if (width < 0 || width > max_width) { | |
96c77dba | 559 | xen_pv_printf(&xenfb->c.xendev, 0, |
c22e91b1 EC |
560 | "invalid frontend width %d limited to %d\n", |
561 | width, max_width); | |
562 | width = max_width; | |
e7151f83 AL |
563 | } |
564 | if (offset < 0 || offset >= fb_len) { | |
96c77dba | 565 | xen_pv_printf(&xenfb->c.xendev, 0, |
c22e91b1 EC |
566 | "invalid frontend offset %d (max %zu)\n", |
567 | offset, fb_len - 1); | |
568 | return -1; | |
e7151f83 AL |
569 | } |
570 | max_height = (fb_len - offset) / row_stride; | |
571 | if (height < 0 || height > max_height) { | |
96c77dba | 572 | xen_pv_printf(&xenfb->c.xendev, 0, |
c22e91b1 EC |
573 | "invalid frontend height %d limited to %d\n", |
574 | height, max_height); | |
575 | height = max_height; | |
e7151f83 AL |
576 | } |
577 | xenfb->fb_len = fb_len; | |
578 | xenfb->row_stride = row_stride; | |
579 | xenfb->depth = depth; | |
580 | xenfb->width = width; | |
581 | xenfb->height = height; | |
582 | xenfb->offset = offset; | |
583 | xenfb->up_fullscreen = 1; | |
584 | xenfb->do_resize = 1; | |
96c77dba | 585 | xen_pv_printf(&xenfb->c.xendev, 1, |
b9730c5b | 586 | "framebuffer %dx%dx%d offset %d stride %d\n", |
c22e91b1 | 587 | width, height, depth, offset, row_stride); |
e7151f83 AL |
588 | return 0; |
589 | } | |
590 | ||
591 | /* A convenient function for munging pixels between different depths */ | |
592 | #define BLT(SRC_T,DST_T,RSB,GSB,BSB,RDB,GDB,BDB) \ | |
593 | for (line = y ; line < (y+h) ; line++) { \ | |
594 | SRC_T *src = (SRC_T *)(xenfb->pixels \ | |
595 | + xenfb->offset \ | |
596 | + (line * xenfb->row_stride) \ | |
597 | + (x * xenfb->depth / 8)); \ | |
598 | DST_T *dst = (DST_T *)(data \ | |
599 | + (line * linesize) \ | |
600 | + (x * bpp / 8)); \ | |
601 | int col; \ | |
602 | const int RSS = 32 - (RSB + GSB + BSB); \ | |
603 | const int GSS = 32 - (GSB + BSB); \ | |
604 | const int BSS = 32 - (BSB); \ | |
605 | const uint32_t RSM = (~0U) << (32 - RSB); \ | |
606 | const uint32_t GSM = (~0U) << (32 - GSB); \ | |
607 | const uint32_t BSM = (~0U) << (32 - BSB); \ | |
608 | const int RDS = 32 - (RDB + GDB + BDB); \ | |
609 | const int GDS = 32 - (GDB + BDB); \ | |
610 | const int BDS = 32 - (BDB); \ | |
611 | const uint32_t RDM = (~0U) << (32 - RDB); \ | |
612 | const uint32_t GDM = (~0U) << (32 - GDB); \ | |
613 | const uint32_t BDM = (~0U) << (32 - BDB); \ | |
614 | for (col = x ; col < (x+w) ; col++) { \ | |
615 | uint32_t spix = *src; \ | |
616 | *dst = (((spix << RSS) & RSM & RDM) >> RDS) | \ | |
617 | (((spix << GSS) & GSM & GDM) >> GDS) | \ | |
618 | (((spix << BSS) & BSM & BDM) >> BDS); \ | |
619 | src = (SRC_T *) ((unsigned long) src + xenfb->depth / 8); \ | |
620 | dst = (DST_T *) ((unsigned long) dst + bpp / 8); \ | |
621 | } \ | |
622 | } | |
623 | ||
624 | ||
625 | /* | |
626 | * This copies data from the guest framebuffer region, into QEMU's | |
627 | * displaysurface. qemu uses 16 or 32 bpp. In case the pv framebuffer | |
628 | * uses something else we must convert and copy, otherwise we can | |
629 | * supply the buffer directly and no thing here. | |
630 | */ | |
631 | static void xenfb_guest_copy(struct XenFB *xenfb, int x, int y, int w, int h) | |
632 | { | |
9f2130f5 | 633 | DisplaySurface *surface = qemu_console_surface(xenfb->con); |
e7151f83 | 634 | int line, oops = 0; |
c78f7137 GH |
635 | int bpp = surface_bits_per_pixel(surface); |
636 | int linesize = surface_stride(surface); | |
637 | uint8_t *data = surface_data(surface); | |
e7151f83 | 638 | |
c78f7137 | 639 | if (!is_buffer_shared(surface)) { |
e7151f83 AL |
640 | switch (xenfb->depth) { |
641 | case 8: | |
642 | if (bpp == 16) { | |
643 | BLT(uint8_t, uint16_t, 3, 3, 2, 5, 6, 5); | |
644 | } else if (bpp == 32) { | |
645 | BLT(uint8_t, uint32_t, 3, 3, 2, 8, 8, 8); | |
646 | } else { | |
647 | oops = 1; | |
648 | } | |
649 | break; | |
650 | case 24: | |
651 | if (bpp == 16) { | |
652 | BLT(uint32_t, uint16_t, 8, 8, 8, 5, 6, 5); | |
653 | } else if (bpp == 32) { | |
654 | BLT(uint32_t, uint32_t, 8, 8, 8, 8, 8, 8); | |
655 | } else { | |
656 | oops = 1; | |
657 | } | |
658 | break; | |
659 | default: | |
660 | oops = 1; | |
661 | } | |
662 | } | |
663 | if (oops) /* should not happen */ | |
96c77dba | 664 | xen_pv_printf(&xenfb->c.xendev, 0, "%s: oops: convert %d -> %d bpp?\n", |
a89f364a | 665 | __func__, xenfb->depth, bpp); |
e7151f83 | 666 | |
9f2130f5 | 667 | dpy_gfx_update(xenfb->con, x, y, w, h); |
e7151f83 AL |
668 | } |
669 | ||
dea1b0bd | 670 | #ifdef XENFB_TYPE_REFRESH_PERIOD |
e7151f83 AL |
671 | static int xenfb_queue_full(struct XenFB *xenfb) |
672 | { | |
673 | struct xenfb_page *page = xenfb->c.page; | |
674 | uint32_t cons, prod; | |
675 | ||
676 | if (!page) | |
677 | return 1; | |
678 | ||
679 | prod = page->in_prod; | |
680 | cons = page->in_cons; | |
681 | return prod - cons == XENFB_IN_RING_LEN; | |
682 | } | |
683 | ||
684 | static void xenfb_send_event(struct XenFB *xenfb, union xenfb_in_event *event) | |
685 | { | |
686 | uint32_t prod; | |
687 | struct xenfb_page *page = xenfb->c.page; | |
688 | ||
689 | prod = page->in_prod; | |
690 | /* caller ensures !xenfb_queue_full() */ | |
691 | xen_mb(); /* ensure ring space available */ | |
692 | XENFB_IN_RING_REF(page, prod) = *event; | |
693 | xen_wmb(); /* ensure ring contents visible */ | |
694 | page->in_prod = prod + 1; | |
695 | ||
ba18fa2a | 696 | xen_pv_send_notify(&xenfb->c.xendev); |
e7151f83 AL |
697 | } |
698 | ||
699 | static void xenfb_send_refresh_period(struct XenFB *xenfb, int period) | |
700 | { | |
701 | union xenfb_in_event event; | |
702 | ||
703 | memset(&event, 0, sizeof(event)); | |
704 | event.type = XENFB_TYPE_REFRESH_PERIOD; | |
705 | event.refresh_period.period = period; | |
706 | xenfb_send_event(xenfb, &event); | |
707 | } | |
708 | #endif | |
709 | ||
710 | /* | |
711 | * Periodic update of display. | |
712 | * Also transmit the refresh interval to the frontend. | |
713 | * | |
714 | * Never ever do any qemu display operations | |
715 | * (resize, screen update) outside this function. | |
716 | * Our screen might be inactive. When asked for | |
717 | * an update we know it is active. | |
718 | */ | |
719 | static void xenfb_update(void *opaque) | |
720 | { | |
721 | struct XenFB *xenfb = opaque; | |
da229ef3 | 722 | DisplaySurface *surface; |
e7151f83 AL |
723 | int i; |
724 | ||
725 | if (xenfb->c.xendev.be_state != XenbusStateConnected) | |
726 | return; | |
727 | ||
dea1b0bd | 728 | if (!xenfb->feature_update) { |
c22e91b1 EC |
729 | /* we don't get update notifications, thus use the |
730 | * sledge hammer approach ... */ | |
731 | xenfb->up_fullscreen = 1; | |
e7151f83 AL |
732 | } |
733 | ||
734 | /* resize if needed */ | |
735 | if (xenfb->do_resize) { | |
30f1e661 GH |
736 | pixman_format_code_t format; |
737 | ||
e7151f83 AL |
738 | xenfb->do_resize = 0; |
739 | switch (xenfb->depth) { | |
740 | case 16: | |
741 | case 32: | |
742 | /* console.c supported depth -> buffer can be used directly */ | |
30f1e661 | 743 | format = qemu_default_pixman_format(xenfb->depth, true); |
da229ef3 | 744 | surface = qemu_create_displaysurface_from |
30f1e661 GH |
745 | (xenfb->width, xenfb->height, format, |
746 | xenfb->row_stride, xenfb->pixels + xenfb->offset); | |
e7151f83 AL |
747 | break; |
748 | default: | |
749 | /* we must convert stuff */ | |
da229ef3 | 750 | surface = qemu_create_displaysurface(xenfb->width, xenfb->height); |
e7151f83 AL |
751 | break; |
752 | } | |
9f2130f5 | 753 | dpy_gfx_replace_surface(xenfb->con, surface); |
96c77dba | 754 | xen_pv_printf(&xenfb->c.xendev, 1, |
b9730c5b | 755 | "update: resizing: %dx%d @ %d bpp%s\n", |
e7151f83 | 756 | xenfb->width, xenfb->height, xenfb->depth, |
c78f7137 | 757 | is_buffer_shared(surface) ? " (shared)" : ""); |
e7151f83 AL |
758 | xenfb->up_fullscreen = 1; |
759 | } | |
760 | ||
761 | /* run queued updates */ | |
762 | if (xenfb->up_fullscreen) { | |
96c77dba | 763 | xen_pv_printf(&xenfb->c.xendev, 3, "update: fullscreen\n"); |
c22e91b1 | 764 | xenfb_guest_copy(xenfb, 0, 0, xenfb->width, xenfb->height); |
e7151f83 | 765 | } else if (xenfb->up_count) { |
96c77dba | 766 | xen_pv_printf(&xenfb->c.xendev, 3, "update: %d rects\n", |
c22e91b1 EC |
767 | xenfb->up_count); |
768 | for (i = 0; i < xenfb->up_count; i++) | |
769 | xenfb_guest_copy(xenfb, | |
770 | xenfb->up_rects[i].x, | |
771 | xenfb->up_rects[i].y, | |
772 | xenfb->up_rects[i].w, | |
773 | xenfb->up_rects[i].h); | |
e7151f83 | 774 | } else { |
96c77dba | 775 | xen_pv_printf(&xenfb->c.xendev, 3, "update: nothing\n"); |
e7151f83 AL |
776 | } |
777 | xenfb->up_count = 0; | |
778 | xenfb->up_fullscreen = 0; | |
779 | } | |
780 | ||
dea1b0bd GH |
781 | static void xenfb_update_interval(void *opaque, uint64_t interval) |
782 | { | |
783 | struct XenFB *xenfb = opaque; | |
784 | ||
785 | if (xenfb->feature_update) { | |
786 | #ifdef XENFB_TYPE_REFRESH_PERIOD | |
787 | if (xenfb_queue_full(xenfb)) { | |
788 | return; | |
789 | } | |
790 | xenfb_send_refresh_period(xenfb, interval); | |
791 | #endif | |
792 | } | |
793 | } | |
794 | ||
e7151f83 AL |
795 | /* QEMU display state changed, so refresh the framebuffer copy */ |
796 | static void xenfb_invalidate(void *opaque) | |
797 | { | |
798 | struct XenFB *xenfb = opaque; | |
799 | xenfb->up_fullscreen = 1; | |
800 | } | |
801 | ||
802 | static void xenfb_handle_events(struct XenFB *xenfb) | |
803 | { | |
7ea11bf3 | 804 | uint32_t prod, cons, out_cons; |
e7151f83 AL |
805 | struct xenfb_page *page = xenfb->c.page; |
806 | ||
807 | prod = page->out_prod; | |
7ea11bf3 | 808 | out_cons = page->out_cons; |
4df26e88 | 809 | if (prod - out_cons > XENFB_OUT_RING_LEN) { |
ac0487e1 SS |
810 | return; |
811 | } | |
e7151f83 | 812 | xen_rmb(); /* ensure we see ring contents up to prod */ |
7ea11bf3 | 813 | for (cons = out_cons; cons != prod; cons++) { |
e7151f83 | 814 | union xenfb_out_event *event = &XENFB_OUT_RING_REF(page, cons); |
7ea11bf3 | 815 | uint8_t type = event->type; |
e7151f83 AL |
816 | int x, y, w, h; |
817 | ||
7ea11bf3 | 818 | switch (type) { |
e7151f83 AL |
819 | case XENFB_TYPE_UPDATE: |
820 | if (xenfb->up_count == UP_QUEUE) | |
821 | xenfb->up_fullscreen = 1; | |
822 | if (xenfb->up_fullscreen) | |
823 | break; | |
824 | x = MAX(event->update.x, 0); | |
825 | y = MAX(event->update.y, 0); | |
826 | w = MIN(event->update.width, xenfb->width - x); | |
827 | h = MIN(event->update.height, xenfb->height - y); | |
828 | if (w < 0 || h < 0) { | |
96c77dba | 829 | xen_pv_printf(&xenfb->c.xendev, 1, "bogus update ignored\n"); |
e7151f83 AL |
830 | break; |
831 | } | |
832 | if (x != event->update.x || | |
833 | y != event->update.y || | |
834 | w != event->update.width || | |
835 | h != event->update.height) { | |
96c77dba | 836 | xen_pv_printf(&xenfb->c.xendev, 1, "bogus update clipped\n"); |
e7151f83 AL |
837 | } |
838 | if (w == xenfb->width && h > xenfb->height / 2) { | |
839 | /* scroll detector: updated more than 50% of the lines, | |
840 | * don't bother keeping track of the rectangles then */ | |
841 | xenfb->up_fullscreen = 1; | |
842 | } else { | |
843 | xenfb->up_rects[xenfb->up_count].x = x; | |
844 | xenfb->up_rects[xenfb->up_count].y = y; | |
845 | xenfb->up_rects[xenfb->up_count].w = w; | |
846 | xenfb->up_rects[xenfb->up_count].h = h; | |
847 | xenfb->up_count++; | |
848 | } | |
849 | break; | |
850 | #ifdef XENFB_TYPE_RESIZE | |
851 | case XENFB_TYPE_RESIZE: | |
852 | if (xenfb_configure_fb(xenfb, xenfb->fb_len, | |
853 | event->resize.width, | |
854 | event->resize.height, | |
855 | event->resize.depth, | |
856 | xenfb->fb_len, | |
857 | event->resize.offset, | |
858 | event->resize.stride) < 0) | |
859 | break; | |
860 | xenfb_invalidate(xenfb); | |
861 | break; | |
862 | #endif | |
863 | } | |
864 | } | |
865 | xen_mb(); /* ensure we're done with ring contents */ | |
866 | page->out_cons = cons; | |
867 | } | |
868 | ||
2d0ed5e6 | 869 | static int fb_init(struct XenLegacyDevice *xendev) |
e7151f83 | 870 | { |
e7151f83 AL |
871 | #ifdef XENFB_TYPE_RESIZE |
872 | xenstore_write_be_int(xendev, "feature-resize", 1); | |
873 | #endif | |
874 | return 0; | |
875 | } | |
876 | ||
2d0ed5e6 | 877 | static int fb_initialise(struct XenLegacyDevice *xendev) |
e7151f83 AL |
878 | { |
879 | struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev); | |
880 | struct xenfb_page *fb_page; | |
881 | int videoram; | |
882 | int rc; | |
883 | ||
884 | if (xenstore_read_fe_int(xendev, "videoram", &videoram) == -1) | |
885 | videoram = 0; | |
886 | ||
887 | rc = common_bind(&fb->c); | |
888 | if (rc != 0) | |
889 | return rc; | |
890 | ||
891 | fb_page = fb->c.page; | |
f0353b0d | 892 | rc = xenfb_configure_fb(fb, videoram * MiB, |
e7151f83 AL |
893 | fb_page->width, fb_page->height, fb_page->depth, |
894 | fb_page->mem_length, 0, fb_page->line_length); | |
895 | if (rc != 0) | |
896 | return rc; | |
897 | ||
898 | rc = xenfb_map_fb(fb); | |
899 | if (rc != 0) | |
900 | return rc; | |
901 | ||
9f2130f5 | 902 | fb->con = graphic_console_init(NULL, 0, &xenfb_ops, fb); |
e7151f83 AL |
903 | |
904 | if (xenstore_read_fe_int(xendev, "feature-update", &fb->feature_update) == -1) | |
905 | fb->feature_update = 0; | |
906 | if (fb->feature_update) | |
907 | xenstore_write_be_int(xendev, "request-update", 1); | |
908 | ||
96c77dba | 909 | xen_pv_printf(xendev, 1, "feature-update=%d, videoram=%d\n", |
e7151f83 AL |
910 | fb->feature_update, videoram); |
911 | return 0; | |
912 | } | |
913 | ||
2d0ed5e6 | 914 | static void fb_disconnect(struct XenLegacyDevice *xendev) |
e7151f83 AL |
915 | { |
916 | struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev); | |
917 | ||
918 | /* | |
919 | * FIXME: qemu can't un-init gfx display (yet?). | |
920 | * Replacing the framebuffer with anonymous shared memory | |
921 | * instead. This releases the guest pages and keeps qemu happy. | |
922 | */ | |
e0cb42ae | 923 | xenforeignmemory_unmap(xen_fmem, fb->pixels, fb->fbpages); |
e7151f83 AL |
924 | fb->pixels = mmap(fb->pixels, fb->fbpages * XC_PAGE_SIZE, |
925 | PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, | |
926 | -1, 0); | |
0193c62c | 927 | if (fb->pixels == MAP_FAILED) { |
96c77dba | 928 | xen_pv_printf(xendev, 0, |
0193c62c SS |
929 | "Couldn't replace the framebuffer with anonymous memory errno=%d\n", |
930 | errno); | |
931 | } | |
e7151f83 AL |
932 | common_unbind(&fb->c); |
933 | fb->feature_update = 0; | |
934 | fb->bug_trigger = 0; | |
935 | } | |
936 | ||
2d0ed5e6 PD |
937 | static void fb_frontend_changed(struct XenLegacyDevice *xendev, |
938 | const char *node) | |
e7151f83 AL |
939 | { |
940 | struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev); | |
941 | ||
942 | /* | |
943 | * Set state to Connected *again* once the frontend switched | |
944 | * to connected. We must trigger the watch a second time to | |
945 | * workaround a frontend bug. | |
946 | */ | |
947 | if (fb->bug_trigger == 0 && strcmp(node, "state") == 0 && | |
948 | xendev->fe_state == XenbusStateConnected && | |
949 | xendev->be_state == XenbusStateConnected) { | |
96c77dba | 950 | xen_pv_printf(xendev, 2, "re-trigger connected (frontend bug)\n"); |
e7151f83 AL |
951 | xen_be_set_state(xendev, XenbusStateConnected); |
952 | fb->bug_trigger = 1; /* only once */ | |
953 | } | |
954 | } | |
955 | ||
2d0ed5e6 | 956 | static void fb_event(struct XenLegacyDevice *xendev) |
e7151f83 AL |
957 | { |
958 | struct XenFB *xenfb = container_of(xendev, struct XenFB, c.xendev); | |
959 | ||
960 | xenfb_handle_events(xenfb); | |
ba18fa2a | 961 | xen_pv_send_notify(&xenfb->c.xendev); |
e7151f83 AL |
962 | } |
963 | ||
964 | /* -------------------------------------------------------------------- */ | |
965 | ||
966 | struct XenDevOps xen_kbdmouse_ops = { | |
967 | .size = sizeof(struct XenInput), | |
968 | .init = input_init, | |
384087b2 | 969 | .initialise = input_initialise, |
6d646730 | 970 | .connected = input_connected, |
e7151f83 AL |
971 | .disconnect = input_disconnect, |
972 | .event = input_event, | |
973 | }; | |
974 | ||
975 | struct XenDevOps xen_framebuffer_ops = { | |
976 | .size = sizeof(struct XenFB), | |
977 | .init = fb_init, | |
384087b2 | 978 | .initialise = fb_initialise, |
e7151f83 AL |
979 | .disconnect = fb_disconnect, |
980 | .event = fb_event, | |
981 | .frontend_changed = fb_frontend_changed, | |
982 | }; | |
983 | ||
380cd056 GH |
984 | static const GraphicHwOps xenfb_ops = { |
985 | .invalidate = xenfb_invalidate, | |
986 | .gfx_update = xenfb_update, | |
dea1b0bd | 987 | .update_interval = xenfb_update_interval, |
380cd056 | 988 | }; |