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