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