diff options
Diffstat (limited to 'drivers/video/fbdev/xen-fbfront.c')
-rw-r--r-- | drivers/video/fbdev/xen-fbfront.c | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/drivers/video/fbdev/xen-fbfront.c b/drivers/video/fbdev/xen-fbfront.c new file mode 100644 index 000000000000..901014bbc821 --- /dev/null +++ b/drivers/video/fbdev/xen-fbfront.c | |||
@@ -0,0 +1,719 @@ | |||
1 | /* | ||
2 | * Xen para-virtual frame buffer device | ||
3 | * | ||
4 | * Copyright (C) 2005-2006 Anthony Liguori <aliguori@us.ibm.com> | ||
5 | * Copyright (C) 2006-2008 Red Hat, Inc., Markus Armbruster <armbru@redhat.com> | ||
6 | * | ||
7 | * Based on linux/drivers/video/q40fb.c | ||
8 | * | ||
9 | * This file is subject to the terms and conditions of the GNU General Public | ||
10 | * License. See the file COPYING in the main directory of this archive for | ||
11 | * more details. | ||
12 | */ | ||
13 | |||
14 | /* | ||
15 | * TODO: | ||
16 | * | ||
17 | * Switch to grant tables when they become capable of dealing with the | ||
18 | * frame buffer. | ||
19 | */ | ||
20 | |||
21 | #include <linux/console.h> | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/errno.h> | ||
24 | #include <linux/fb.h> | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/vmalloc.h> | ||
28 | #include <linux/mm.h> | ||
29 | |||
30 | #include <asm/xen/hypervisor.h> | ||
31 | |||
32 | #include <xen/xen.h> | ||
33 | #include <xen/events.h> | ||
34 | #include <xen/page.h> | ||
35 | #include <xen/interface/io/fbif.h> | ||
36 | #include <xen/interface/io/protocols.h> | ||
37 | #include <xen/xenbus.h> | ||
38 | #include <xen/platform_pci.h> | ||
39 | |||
40 | struct xenfb_info { | ||
41 | unsigned char *fb; | ||
42 | struct fb_info *fb_info; | ||
43 | int x1, y1, x2, y2; /* dirty rectangle, | ||
44 | protected by dirty_lock */ | ||
45 | spinlock_t dirty_lock; | ||
46 | int nr_pages; | ||
47 | int irq; | ||
48 | struct xenfb_page *page; | ||
49 | unsigned long *mfns; | ||
50 | int update_wanted; /* XENFB_TYPE_UPDATE wanted */ | ||
51 | int feature_resize; /* XENFB_TYPE_RESIZE ok */ | ||
52 | struct xenfb_resize resize; /* protected by resize_lock */ | ||
53 | int resize_dpy; /* ditto */ | ||
54 | spinlock_t resize_lock; | ||
55 | |||
56 | struct xenbus_device *xbdev; | ||
57 | }; | ||
58 | |||
59 | #define XENFB_DEFAULT_FB_LEN (XENFB_WIDTH * XENFB_HEIGHT * XENFB_DEPTH / 8) | ||
60 | |||
61 | enum { KPARAM_MEM, KPARAM_WIDTH, KPARAM_HEIGHT, KPARAM_CNT }; | ||
62 | static int video[KPARAM_CNT] = { 2, XENFB_WIDTH, XENFB_HEIGHT }; | ||
63 | module_param_array(video, int, NULL, 0); | ||
64 | MODULE_PARM_DESC(video, | ||
65 | "Video memory size in MB, width, height in pixels (default 2,800,600)"); | ||
66 | |||
67 | static void xenfb_make_preferred_console(void); | ||
68 | static int xenfb_remove(struct xenbus_device *); | ||
69 | static void xenfb_init_shared_page(struct xenfb_info *, struct fb_info *); | ||
70 | static int xenfb_connect_backend(struct xenbus_device *, struct xenfb_info *); | ||
71 | static void xenfb_disconnect_backend(struct xenfb_info *); | ||
72 | |||
73 | static void xenfb_send_event(struct xenfb_info *info, | ||
74 | union xenfb_out_event *event) | ||
75 | { | ||
76 | u32 prod; | ||
77 | |||
78 | prod = info->page->out_prod; | ||
79 | /* caller ensures !xenfb_queue_full() */ | ||
80 | mb(); /* ensure ring space available */ | ||
81 | XENFB_OUT_RING_REF(info->page, prod) = *event; | ||
82 | wmb(); /* ensure ring contents visible */ | ||
83 | info->page->out_prod = prod + 1; | ||
84 | |||
85 | notify_remote_via_irq(info->irq); | ||
86 | } | ||
87 | |||
88 | static void xenfb_do_update(struct xenfb_info *info, | ||
89 | int x, int y, int w, int h) | ||
90 | { | ||
91 | union xenfb_out_event event; | ||
92 | |||
93 | memset(&event, 0, sizeof(event)); | ||
94 | event.type = XENFB_TYPE_UPDATE; | ||
95 | event.update.x = x; | ||
96 | event.update.y = y; | ||
97 | event.update.width = w; | ||
98 | event.update.height = h; | ||
99 | |||
100 | /* caller ensures !xenfb_queue_full() */ | ||
101 | xenfb_send_event(info, &event); | ||
102 | } | ||
103 | |||
104 | static void xenfb_do_resize(struct xenfb_info *info) | ||
105 | { | ||
106 | union xenfb_out_event event; | ||
107 | |||
108 | memset(&event, 0, sizeof(event)); | ||
109 | event.resize = info->resize; | ||
110 | |||
111 | /* caller ensures !xenfb_queue_full() */ | ||
112 | xenfb_send_event(info, &event); | ||
113 | } | ||
114 | |||
115 | static int xenfb_queue_full(struct xenfb_info *info) | ||
116 | { | ||
117 | u32 cons, prod; | ||
118 | |||
119 | prod = info->page->out_prod; | ||
120 | cons = info->page->out_cons; | ||
121 | return prod - cons == XENFB_OUT_RING_LEN; | ||
122 | } | ||
123 | |||
124 | static void xenfb_handle_resize_dpy(struct xenfb_info *info) | ||
125 | { | ||
126 | unsigned long flags; | ||
127 | |||
128 | spin_lock_irqsave(&info->resize_lock, flags); | ||
129 | if (info->resize_dpy) { | ||
130 | if (!xenfb_queue_full(info)) { | ||
131 | info->resize_dpy = 0; | ||
132 | xenfb_do_resize(info); | ||
133 | } | ||
134 | } | ||
135 | spin_unlock_irqrestore(&info->resize_lock, flags); | ||
136 | } | ||
137 | |||
138 | static void xenfb_refresh(struct xenfb_info *info, | ||
139 | int x1, int y1, int w, int h) | ||
140 | { | ||
141 | unsigned long flags; | ||
142 | int x2 = x1 + w - 1; | ||
143 | int y2 = y1 + h - 1; | ||
144 | |||
145 | xenfb_handle_resize_dpy(info); | ||
146 | |||
147 | if (!info->update_wanted) | ||
148 | return; | ||
149 | |||
150 | spin_lock_irqsave(&info->dirty_lock, flags); | ||
151 | |||
152 | /* Combine with dirty rectangle: */ | ||
153 | if (info->y1 < y1) | ||
154 | y1 = info->y1; | ||
155 | if (info->y2 > y2) | ||
156 | y2 = info->y2; | ||
157 | if (info->x1 < x1) | ||
158 | x1 = info->x1; | ||
159 | if (info->x2 > x2) | ||
160 | x2 = info->x2; | ||
161 | |||
162 | if (xenfb_queue_full(info)) { | ||
163 | /* Can't send right now, stash it in the dirty rectangle */ | ||
164 | info->x1 = x1; | ||
165 | info->x2 = x2; | ||
166 | info->y1 = y1; | ||
167 | info->y2 = y2; | ||
168 | spin_unlock_irqrestore(&info->dirty_lock, flags); | ||
169 | return; | ||
170 | } | ||
171 | |||
172 | /* Clear dirty rectangle: */ | ||
173 | info->x1 = info->y1 = INT_MAX; | ||
174 | info->x2 = info->y2 = 0; | ||
175 | |||
176 | spin_unlock_irqrestore(&info->dirty_lock, flags); | ||
177 | |||
178 | if (x1 <= x2 && y1 <= y2) | ||
179 | xenfb_do_update(info, x1, y1, x2 - x1 + 1, y2 - y1 + 1); | ||
180 | } | ||
181 | |||
182 | static void xenfb_deferred_io(struct fb_info *fb_info, | ||
183 | struct list_head *pagelist) | ||
184 | { | ||
185 | struct xenfb_info *info = fb_info->par; | ||
186 | struct page *page; | ||
187 | unsigned long beg, end; | ||
188 | int y1, y2, miny, maxy; | ||
189 | |||
190 | miny = INT_MAX; | ||
191 | maxy = 0; | ||
192 | list_for_each_entry(page, pagelist, lru) { | ||
193 | beg = page->index << PAGE_SHIFT; | ||
194 | end = beg + PAGE_SIZE - 1; | ||
195 | y1 = beg / fb_info->fix.line_length; | ||
196 | y2 = end / fb_info->fix.line_length; | ||
197 | if (y2 >= fb_info->var.yres) | ||
198 | y2 = fb_info->var.yres - 1; | ||
199 | if (miny > y1) | ||
200 | miny = y1; | ||
201 | if (maxy < y2) | ||
202 | maxy = y2; | ||
203 | } | ||
204 | xenfb_refresh(info, 0, miny, fb_info->var.xres, maxy - miny + 1); | ||
205 | } | ||
206 | |||
207 | static struct fb_deferred_io xenfb_defio = { | ||
208 | .delay = HZ / 20, | ||
209 | .deferred_io = xenfb_deferred_io, | ||
210 | }; | ||
211 | |||
212 | static int xenfb_setcolreg(unsigned regno, unsigned red, unsigned green, | ||
213 | unsigned blue, unsigned transp, | ||
214 | struct fb_info *info) | ||
215 | { | ||
216 | u32 v; | ||
217 | |||
218 | if (regno > info->cmap.len) | ||
219 | return 1; | ||
220 | |||
221 | #define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) | ||
222 | red = CNVT_TOHW(red, info->var.red.length); | ||
223 | green = CNVT_TOHW(green, info->var.green.length); | ||
224 | blue = CNVT_TOHW(blue, info->var.blue.length); | ||
225 | transp = CNVT_TOHW(transp, info->var.transp.length); | ||
226 | #undef CNVT_TOHW | ||
227 | |||
228 | v = (red << info->var.red.offset) | | ||
229 | (green << info->var.green.offset) | | ||
230 | (blue << info->var.blue.offset); | ||
231 | |||
232 | switch (info->var.bits_per_pixel) { | ||
233 | case 16: | ||
234 | case 24: | ||
235 | case 32: | ||
236 | ((u32 *)info->pseudo_palette)[regno] = v; | ||
237 | break; | ||
238 | } | ||
239 | |||
240 | return 0; | ||
241 | } | ||
242 | |||
243 | static void xenfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) | ||
244 | { | ||
245 | struct xenfb_info *info = p->par; | ||
246 | |||
247 | sys_fillrect(p, rect); | ||
248 | xenfb_refresh(info, rect->dx, rect->dy, rect->width, rect->height); | ||
249 | } | ||
250 | |||
251 | static void xenfb_imageblit(struct fb_info *p, const struct fb_image *image) | ||
252 | { | ||
253 | struct xenfb_info *info = p->par; | ||
254 | |||
255 | sys_imageblit(p, image); | ||
256 | xenfb_refresh(info, image->dx, image->dy, image->width, image->height); | ||
257 | } | ||
258 | |||
259 | static void xenfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) | ||
260 | { | ||
261 | struct xenfb_info *info = p->par; | ||
262 | |||
263 | sys_copyarea(p, area); | ||
264 | xenfb_refresh(info, area->dx, area->dy, area->width, area->height); | ||
265 | } | ||
266 | |||
267 | static ssize_t xenfb_write(struct fb_info *p, const char __user *buf, | ||
268 | size_t count, loff_t *ppos) | ||
269 | { | ||
270 | struct xenfb_info *info = p->par; | ||
271 | ssize_t res; | ||
272 | |||
273 | res = fb_sys_write(p, buf, count, ppos); | ||
274 | xenfb_refresh(info, 0, 0, info->page->width, info->page->height); | ||
275 | return res; | ||
276 | } | ||
277 | |||
278 | static int | ||
279 | xenfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | ||
280 | { | ||
281 | struct xenfb_info *xenfb_info; | ||
282 | int required_mem_len; | ||
283 | |||
284 | xenfb_info = info->par; | ||
285 | |||
286 | if (!xenfb_info->feature_resize) { | ||
287 | if (var->xres == video[KPARAM_WIDTH] && | ||
288 | var->yres == video[KPARAM_HEIGHT] && | ||
289 | var->bits_per_pixel == xenfb_info->page->depth) { | ||
290 | return 0; | ||
291 | } | ||
292 | return -EINVAL; | ||
293 | } | ||
294 | |||
295 | /* Can't resize past initial width and height */ | ||
296 | if (var->xres > video[KPARAM_WIDTH] || var->yres > video[KPARAM_HEIGHT]) | ||
297 | return -EINVAL; | ||
298 | |||
299 | required_mem_len = var->xres * var->yres * xenfb_info->page->depth / 8; | ||
300 | if (var->bits_per_pixel == xenfb_info->page->depth && | ||
301 | var->xres <= info->fix.line_length / (XENFB_DEPTH / 8) && | ||
302 | required_mem_len <= info->fix.smem_len) { | ||
303 | var->xres_virtual = var->xres; | ||
304 | var->yres_virtual = var->yres; | ||
305 | return 0; | ||
306 | } | ||
307 | return -EINVAL; | ||
308 | } | ||
309 | |||
310 | static int xenfb_set_par(struct fb_info *info) | ||
311 | { | ||
312 | struct xenfb_info *xenfb_info; | ||
313 | unsigned long flags; | ||
314 | |||
315 | xenfb_info = info->par; | ||
316 | |||
317 | spin_lock_irqsave(&xenfb_info->resize_lock, flags); | ||
318 | xenfb_info->resize.type = XENFB_TYPE_RESIZE; | ||
319 | xenfb_info->resize.width = info->var.xres; | ||
320 | xenfb_info->resize.height = info->var.yres; | ||
321 | xenfb_info->resize.stride = info->fix.line_length; | ||
322 | xenfb_info->resize.depth = info->var.bits_per_pixel; | ||
323 | xenfb_info->resize.offset = 0; | ||
324 | xenfb_info->resize_dpy = 1; | ||
325 | spin_unlock_irqrestore(&xenfb_info->resize_lock, flags); | ||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | static struct fb_ops xenfb_fb_ops = { | ||
330 | .owner = THIS_MODULE, | ||
331 | .fb_read = fb_sys_read, | ||
332 | .fb_write = xenfb_write, | ||
333 | .fb_setcolreg = xenfb_setcolreg, | ||
334 | .fb_fillrect = xenfb_fillrect, | ||
335 | .fb_copyarea = xenfb_copyarea, | ||
336 | .fb_imageblit = xenfb_imageblit, | ||
337 | .fb_check_var = xenfb_check_var, | ||
338 | .fb_set_par = xenfb_set_par, | ||
339 | }; | ||
340 | |||
341 | static irqreturn_t xenfb_event_handler(int rq, void *dev_id) | ||
342 | { | ||
343 | /* | ||
344 | * No in events recognized, simply ignore them all. | ||
345 | * If you need to recognize some, see xen-kbdfront's | ||
346 | * input_handler() for how to do that. | ||
347 | */ | ||
348 | struct xenfb_info *info = dev_id; | ||
349 | struct xenfb_page *page = info->page; | ||
350 | |||
351 | if (page->in_cons != page->in_prod) { | ||
352 | info->page->in_cons = info->page->in_prod; | ||
353 | notify_remote_via_irq(info->irq); | ||
354 | } | ||
355 | |||
356 | /* Flush dirty rectangle: */ | ||
357 | xenfb_refresh(info, INT_MAX, INT_MAX, -INT_MAX, -INT_MAX); | ||
358 | |||
359 | return IRQ_HANDLED; | ||
360 | } | ||
361 | |||
362 | static int xenfb_probe(struct xenbus_device *dev, | ||
363 | const struct xenbus_device_id *id) | ||
364 | { | ||
365 | struct xenfb_info *info; | ||
366 | struct fb_info *fb_info; | ||
367 | int fb_size; | ||
368 | int val; | ||
369 | int ret = 0; | ||
370 | |||
371 | info = kzalloc(sizeof(*info), GFP_KERNEL); | ||
372 | if (info == NULL) { | ||
373 | xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure"); | ||
374 | return -ENOMEM; | ||
375 | } | ||
376 | |||
377 | /* Limit kernel param videoram amount to what is in xenstore */ | ||
378 | if (xenbus_scanf(XBT_NIL, dev->otherend, "videoram", "%d", &val) == 1) { | ||
379 | if (val < video[KPARAM_MEM]) | ||
380 | video[KPARAM_MEM] = val; | ||
381 | } | ||
382 | |||
383 | /* If requested res does not fit in available memory, use default */ | ||
384 | fb_size = video[KPARAM_MEM] * 1024 * 1024; | ||
385 | if (video[KPARAM_WIDTH] * video[KPARAM_HEIGHT] * XENFB_DEPTH / 8 | ||
386 | > fb_size) { | ||
387 | video[KPARAM_WIDTH] = XENFB_WIDTH; | ||
388 | video[KPARAM_HEIGHT] = XENFB_HEIGHT; | ||
389 | fb_size = XENFB_DEFAULT_FB_LEN; | ||
390 | } | ||
391 | |||
392 | dev_set_drvdata(&dev->dev, info); | ||
393 | info->xbdev = dev; | ||
394 | info->irq = -1; | ||
395 | info->x1 = info->y1 = INT_MAX; | ||
396 | spin_lock_init(&info->dirty_lock); | ||
397 | spin_lock_init(&info->resize_lock); | ||
398 | |||
399 | info->fb = vzalloc(fb_size); | ||
400 | if (info->fb == NULL) | ||
401 | goto error_nomem; | ||
402 | |||
403 | info->nr_pages = (fb_size + PAGE_SIZE - 1) >> PAGE_SHIFT; | ||
404 | |||
405 | info->mfns = vmalloc(sizeof(unsigned long) * info->nr_pages); | ||
406 | if (!info->mfns) | ||
407 | goto error_nomem; | ||
408 | |||
409 | /* set up shared page */ | ||
410 | info->page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO); | ||
411 | if (!info->page) | ||
412 | goto error_nomem; | ||
413 | |||
414 | /* abusing framebuffer_alloc() to allocate pseudo_palette */ | ||
415 | fb_info = framebuffer_alloc(sizeof(u32) * 256, NULL); | ||
416 | if (fb_info == NULL) | ||
417 | goto error_nomem; | ||
418 | |||
419 | /* complete the abuse: */ | ||
420 | fb_info->pseudo_palette = fb_info->par; | ||
421 | fb_info->par = info; | ||
422 | |||
423 | fb_info->screen_base = info->fb; | ||
424 | |||
425 | fb_info->fbops = &xenfb_fb_ops; | ||
426 | fb_info->var.xres_virtual = fb_info->var.xres = video[KPARAM_WIDTH]; | ||
427 | fb_info->var.yres_virtual = fb_info->var.yres = video[KPARAM_HEIGHT]; | ||
428 | fb_info->var.bits_per_pixel = XENFB_DEPTH; | ||
429 | |||
430 | fb_info->var.red = (struct fb_bitfield){16, 8, 0}; | ||
431 | fb_info->var.green = (struct fb_bitfield){8, 8, 0}; | ||
432 | fb_info->var.blue = (struct fb_bitfield){0, 8, 0}; | ||
433 | |||
434 | fb_info->var.activate = FB_ACTIVATE_NOW; | ||
435 | fb_info->var.height = -1; | ||
436 | fb_info->var.width = -1; | ||
437 | fb_info->var.vmode = FB_VMODE_NONINTERLACED; | ||
438 | |||
439 | fb_info->fix.visual = FB_VISUAL_TRUECOLOR; | ||
440 | fb_info->fix.line_length = fb_info->var.xres * XENFB_DEPTH / 8; | ||
441 | fb_info->fix.smem_start = 0; | ||
442 | fb_info->fix.smem_len = fb_size; | ||
443 | strcpy(fb_info->fix.id, "xen"); | ||
444 | fb_info->fix.type = FB_TYPE_PACKED_PIXELS; | ||
445 | fb_info->fix.accel = FB_ACCEL_NONE; | ||
446 | |||
447 | fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; | ||
448 | |||
449 | ret = fb_alloc_cmap(&fb_info->cmap, 256, 0); | ||
450 | if (ret < 0) { | ||
451 | framebuffer_release(fb_info); | ||
452 | xenbus_dev_fatal(dev, ret, "fb_alloc_cmap"); | ||
453 | goto error; | ||
454 | } | ||
455 | |||
456 | fb_info->fbdefio = &xenfb_defio; | ||
457 | fb_deferred_io_init(fb_info); | ||
458 | |||
459 | xenfb_init_shared_page(info, fb_info); | ||
460 | |||
461 | ret = xenfb_connect_backend(dev, info); | ||
462 | if (ret < 0) { | ||
463 | xenbus_dev_fatal(dev, ret, "xenfb_connect_backend"); | ||
464 | goto error_fb; | ||
465 | } | ||
466 | |||
467 | ret = register_framebuffer(fb_info); | ||
468 | if (ret) { | ||
469 | xenbus_dev_fatal(dev, ret, "register_framebuffer"); | ||
470 | goto error_fb; | ||
471 | } | ||
472 | info->fb_info = fb_info; | ||
473 | |||
474 | xenfb_make_preferred_console(); | ||
475 | return 0; | ||
476 | |||
477 | error_fb: | ||
478 | fb_deferred_io_cleanup(fb_info); | ||
479 | fb_dealloc_cmap(&fb_info->cmap); | ||
480 | framebuffer_release(fb_info); | ||
481 | error_nomem: | ||
482 | if (!ret) { | ||
483 | ret = -ENOMEM; | ||
484 | xenbus_dev_fatal(dev, ret, "allocating device memory"); | ||
485 | } | ||
486 | error: | ||
487 | xenfb_remove(dev); | ||
488 | return ret; | ||
489 | } | ||
490 | |||
491 | static void xenfb_make_preferred_console(void) | ||
492 | { | ||
493 | struct console *c; | ||
494 | |||
495 | if (console_set_on_cmdline) | ||
496 | return; | ||
497 | |||
498 | console_lock(); | ||
499 | for_each_console(c) { | ||
500 | if (!strcmp(c->name, "tty") && c->index == 0) | ||
501 | break; | ||
502 | } | ||
503 | console_unlock(); | ||
504 | if (c) { | ||
505 | unregister_console(c); | ||
506 | c->flags |= CON_CONSDEV; | ||
507 | c->flags &= ~CON_PRINTBUFFER; /* don't print again */ | ||
508 | register_console(c); | ||
509 | } | ||
510 | } | ||
511 | |||
512 | static int xenfb_resume(struct xenbus_device *dev) | ||
513 | { | ||
514 | struct xenfb_info *info = dev_get_drvdata(&dev->dev); | ||
515 | |||
516 | xenfb_disconnect_backend(info); | ||
517 | xenfb_init_shared_page(info, info->fb_info); | ||
518 | return xenfb_connect_backend(dev, info); | ||
519 | } | ||
520 | |||
521 | static int xenfb_remove(struct xenbus_device *dev) | ||
522 | { | ||
523 | struct xenfb_info *info = dev_get_drvdata(&dev->dev); | ||
524 | |||
525 | xenfb_disconnect_backend(info); | ||
526 | if (info->fb_info) { | ||
527 | fb_deferred_io_cleanup(info->fb_info); | ||
528 | unregister_framebuffer(info->fb_info); | ||
529 | fb_dealloc_cmap(&info->fb_info->cmap); | ||
530 | framebuffer_release(info->fb_info); | ||
531 | } | ||
532 | free_page((unsigned long)info->page); | ||
533 | vfree(info->mfns); | ||
534 | vfree(info->fb); | ||
535 | kfree(info); | ||
536 | |||
537 | return 0; | ||
538 | } | ||
539 | |||
540 | static unsigned long vmalloc_to_mfn(void *address) | ||
541 | { | ||
542 | return pfn_to_mfn(vmalloc_to_pfn(address)); | ||
543 | } | ||
544 | |||
545 | static void xenfb_init_shared_page(struct xenfb_info *info, | ||
546 | struct fb_info *fb_info) | ||
547 | { | ||
548 | int i; | ||
549 | int epd = PAGE_SIZE / sizeof(info->mfns[0]); | ||
550 | |||
551 | for (i = 0; i < info->nr_pages; i++) | ||
552 | info->mfns[i] = vmalloc_to_mfn(info->fb + i * PAGE_SIZE); | ||
553 | |||
554 | for (i = 0; i * epd < info->nr_pages; i++) | ||
555 | info->page->pd[i] = vmalloc_to_mfn(&info->mfns[i * epd]); | ||
556 | |||
557 | info->page->width = fb_info->var.xres; | ||
558 | info->page->height = fb_info->var.yres; | ||
559 | info->page->depth = fb_info->var.bits_per_pixel; | ||
560 | info->page->line_length = fb_info->fix.line_length; | ||
561 | info->page->mem_length = fb_info->fix.smem_len; | ||
562 | info->page->in_cons = info->page->in_prod = 0; | ||
563 | info->page->out_cons = info->page->out_prod = 0; | ||
564 | } | ||
565 | |||
566 | static int xenfb_connect_backend(struct xenbus_device *dev, | ||
567 | struct xenfb_info *info) | ||
568 | { | ||
569 | int ret, evtchn, irq; | ||
570 | struct xenbus_transaction xbt; | ||
571 | |||
572 | ret = xenbus_alloc_evtchn(dev, &evtchn); | ||
573 | if (ret) | ||
574 | return ret; | ||
575 | irq = bind_evtchn_to_irqhandler(evtchn, xenfb_event_handler, | ||
576 | 0, dev->devicetype, info); | ||
577 | if (irq < 0) { | ||
578 | xenbus_free_evtchn(dev, evtchn); | ||
579 | xenbus_dev_fatal(dev, ret, "bind_evtchn_to_irqhandler"); | ||
580 | return irq; | ||
581 | } | ||
582 | again: | ||
583 | ret = xenbus_transaction_start(&xbt); | ||
584 | if (ret) { | ||
585 | xenbus_dev_fatal(dev, ret, "starting transaction"); | ||
586 | goto unbind_irq; | ||
587 | } | ||
588 | ret = xenbus_printf(xbt, dev->nodename, "page-ref", "%lu", | ||
589 | virt_to_mfn(info->page)); | ||
590 | if (ret) | ||
591 | goto error_xenbus; | ||
592 | ret = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", | ||
593 | evtchn); | ||
594 | if (ret) | ||
595 | goto error_xenbus; | ||
596 | ret = xenbus_printf(xbt, dev->nodename, "protocol", "%s", | ||
597 | XEN_IO_PROTO_ABI_NATIVE); | ||
598 | if (ret) | ||
599 | goto error_xenbus; | ||
600 | ret = xenbus_printf(xbt, dev->nodename, "feature-update", "1"); | ||
601 | if (ret) | ||
602 | goto error_xenbus; | ||
603 | ret = xenbus_transaction_end(xbt, 0); | ||
604 | if (ret) { | ||
605 | if (ret == -EAGAIN) | ||
606 | goto again; | ||
607 | xenbus_dev_fatal(dev, ret, "completing transaction"); | ||
608 | goto unbind_irq; | ||
609 | } | ||
610 | |||
611 | xenbus_switch_state(dev, XenbusStateInitialised); | ||
612 | info->irq = irq; | ||
613 | return 0; | ||
614 | |||
615 | error_xenbus: | ||
616 | xenbus_transaction_end(xbt, 1); | ||
617 | xenbus_dev_fatal(dev, ret, "writing xenstore"); | ||
618 | unbind_irq: | ||
619 | unbind_from_irqhandler(irq, info); | ||
620 | return ret; | ||
621 | } | ||
622 | |||
623 | static void xenfb_disconnect_backend(struct xenfb_info *info) | ||
624 | { | ||
625 | /* Prevent xenfb refresh */ | ||
626 | info->update_wanted = 0; | ||
627 | if (info->irq >= 0) | ||
628 | unbind_from_irqhandler(info->irq, info); | ||
629 | info->irq = -1; | ||
630 | } | ||
631 | |||
632 | static void xenfb_backend_changed(struct xenbus_device *dev, | ||
633 | enum xenbus_state backend_state) | ||
634 | { | ||
635 | struct xenfb_info *info = dev_get_drvdata(&dev->dev); | ||
636 | int val; | ||
637 | |||
638 | switch (backend_state) { | ||
639 | case XenbusStateInitialising: | ||
640 | case XenbusStateInitialised: | ||
641 | case XenbusStateReconfiguring: | ||
642 | case XenbusStateReconfigured: | ||
643 | case XenbusStateUnknown: | ||
644 | break; | ||
645 | |||
646 | case XenbusStateInitWait: | ||
647 | InitWait: | ||
648 | xenbus_switch_state(dev, XenbusStateConnected); | ||
649 | break; | ||
650 | |||
651 | case XenbusStateConnected: | ||
652 | /* | ||
653 | * Work around xenbus race condition: If backend goes | ||
654 | * through InitWait to Connected fast enough, we can | ||
655 | * get Connected twice here. | ||
656 | */ | ||
657 | if (dev->state != XenbusStateConnected) | ||
658 | goto InitWait; /* no InitWait seen yet, fudge it */ | ||
659 | |||
660 | if (xenbus_scanf(XBT_NIL, info->xbdev->otherend, | ||
661 | "request-update", "%d", &val) < 0) | ||
662 | val = 0; | ||
663 | if (val) | ||
664 | info->update_wanted = 1; | ||
665 | |||
666 | if (xenbus_scanf(XBT_NIL, dev->otherend, | ||
667 | "feature-resize", "%d", &val) < 0) | ||
668 | val = 0; | ||
669 | info->feature_resize = val; | ||
670 | break; | ||
671 | |||
672 | case XenbusStateClosed: | ||
673 | if (dev->state == XenbusStateClosed) | ||
674 | break; | ||
675 | /* Missed the backend's CLOSING state -- fallthrough */ | ||
676 | case XenbusStateClosing: | ||
677 | xenbus_frontend_closed(dev); | ||
678 | break; | ||
679 | } | ||
680 | } | ||
681 | |||
682 | static const struct xenbus_device_id xenfb_ids[] = { | ||
683 | { "vfb" }, | ||
684 | { "" } | ||
685 | }; | ||
686 | |||
687 | static DEFINE_XENBUS_DRIVER(xenfb, , | ||
688 | .probe = xenfb_probe, | ||
689 | .remove = xenfb_remove, | ||
690 | .resume = xenfb_resume, | ||
691 | .otherend_changed = xenfb_backend_changed, | ||
692 | ); | ||
693 | |||
694 | static int __init xenfb_init(void) | ||
695 | { | ||
696 | if (!xen_domain()) | ||
697 | return -ENODEV; | ||
698 | |||
699 | /* Nothing to do if running in dom0. */ | ||
700 | if (xen_initial_domain()) | ||
701 | return -ENODEV; | ||
702 | |||
703 | if (!xen_has_pv_devices()) | ||
704 | return -ENODEV; | ||
705 | |||
706 | return xenbus_register_frontend(&xenfb_driver); | ||
707 | } | ||
708 | |||
709 | static void __exit xenfb_cleanup(void) | ||
710 | { | ||
711 | xenbus_unregister_driver(&xenfb_driver); | ||
712 | } | ||
713 | |||
714 | module_init(xenfb_init); | ||
715 | module_exit(xenfb_cleanup); | ||
716 | |||
717 | MODULE_DESCRIPTION("Xen virtual framebuffer device frontend"); | ||
718 | MODULE_LICENSE("GPL"); | ||
719 | MODULE_ALIAS("xen:vfb"); | ||