| 1 | |
| 2 | * Xen para-virtual frame buffer device
|
| 3 | *
|
| 4 | * Copyright (C) 2005-2006 Anthony Liguori <[email protected]>
|
| 5 | * Copyright (C) 2006-2008 Red Hat, Inc., Markus Armbruster <[email protected]>
|
| 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; |
| 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;
|
| 53 | int feature_resize;
|
| 54 | struct xenfb_resize resize;
|
| 55 | int resize_dpy;
|
| 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 |
|
| 82 | mb();
|
| 83 | XENFB_OUT_RING_REF(info->page, prod) = *event;
|
| 84 | wmb();
|
| 85 | info->page->out_prod = prod + 1;
|
| 86 |
|
| 87 | notify_remote_via_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 |
|
| 103 | xenfb_send_event(info, &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 |
|
| 114 | xenfb_send_event(info, &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(&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 |
|
| 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 |
|
| 166 | info->x1 = x1;
|
| 167 | info->x2 = x2;
|
| 168 | info->y1 = y1;
|
| 169 | info->y2 = y2;
|
| 170 | spin_unlock_irqrestore(&info->dirty_lock, flags);
|
| 171 | return;
|
| 172 | }
|
| 173 |
|
| 174 |
|
| 175 | info->x1 = info->y1 = INT_MAX;
|
| 176 | info->x2 = info->y2 = 0;
|
| 177 |
|
| 178 | spin_unlock_irqrestore(&info->dirty_lock, flags);
|
| 179 |
|
| 180 | if (x1 <= x2 && y1 <= y2)
|
| 181 | xenfb_do_update(info, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
|
| 182 | }
|
| 183 |
|
| 184 | static void xenfb_deferred_io(struct fb_info *fb_info, struct list_head *pagereflist)
|
| 185 | {
|
| 186 | struct xenfb_info *info = fb_info->par;
|
| 187 | struct fb_deferred_io_pageref *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, 0, miny, fb_info->var.xres, 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 |
|
| 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(&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(xenfb_info, 0, 0, xenfb_info->page->width, 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(xenfb_info, x, y, width, 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(info->irq);
|
| 334 | }
|
| 335 |
|
| 336 |
|
| 337 | xenfb_refresh(info, INT_MAX, INT_MAX, -INT_MAX, -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_obj(*info);
|
| 352 | if (info == NULL) {
|
| 353 | xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure");
|
| 354 | return -ENOMEM;
|
| 355 | }
|
| 356 |
|
| 357 |
|
| 358 | if (xenbus_scanf(XBT_NIL, dev->otherend, "videoram", "%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 |
|
| 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(info->nr_pages, sizeof(unsigned long));
|
| 394 | if (!info->gfns)
|
| 395 | goto error_nomem;
|
| 396 |
|
| 397 |
|
| 398 | info->page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
|
| 399 | if (!info->page)
|
| 400 | goto error_nomem;
|
| 401 |
|
| 402 |
|
| 403 | fb_info = framebuffer_alloc(sizeof(u32) * 256, NULL);
|
| 404 | if (fb_info == NULL)
|
| 405 | goto error_nomem;
|
| 406 |
|
| 407 |
|
| 408 | fb_info->pseudo_palette = fb_info->par;
|
| 409 | fb_info->par = info;
|
| 410 | fb_info->device = &dev->dev;
|
| 411 |
|
| 412 | fb_info->screen_buffer = info->fb;
|
| 413 |
|
| 414 | fb_info->fbops = &xenfb_fb_ops;
|
| 415 | fb_info->var.xres_virtual = fb_info->var.xres = video[KPARAM_WIDTH];
|
| 416 | fb_info->var.yres_virtual = fb_info->var.yres = video[KPARAM_HEIGHT];
|
| 417 | fb_info->var.bits_per_pixel = XENFB_DEPTH;
|
| 418 |
|
| 419 | fb_info->var.red = (struct fb_bitfield){16, 8, 0};
|
| 420 | fb_info->var.green = (struct fb_bitfield){8, 8, 0};
|
| 421 | fb_info->var.blue = (struct fb_bitfield){0, 8, 0};
|
| 422 |
|
| 423 | fb_info->var.activate = FB_ACTIVATE_NOW;
|
| 424 | fb_info->var.height = -1;
|
| 425 | fb_info->var.width = -1;
|
| 426 | fb_info->var.vmode = FB_VMODE_NONINTERLACED;
|
| 427 |
|
| 428 | fb_info->fix.visual = FB_VISUAL_TRUECOLOR;
|
| 429 | fb_info->fix.line_length = fb_info->var.xres * XENFB_DEPTH / 8;
|
| 430 | fb_info->fix.smem_start = 0;
|
| 431 | fb_info->fix.smem_len = fb_size;
|
| 432 | strcpy(fb_info->fix.id, "xen");
|
| 433 | fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
|
| 434 | fb_info->fix.accel = FB_ACCEL_NONE;
|
| 435 |
|
| 436 | fb_info->flags = FBINFO_VIRTFB;
|
| 437 |
|
| 438 | ret = fb_alloc_cmap(&fb_info->cmap, 256, 0);
|
| 439 | if (ret < 0) {
|
| 440 | framebuffer_release(fb_info);
|
| 441 | xenbus_dev_fatal(dev, ret, "fb_alloc_cmap");
|
| 442 | goto error;
|
| 443 | }
|
| 444 |
|
| 445 | fb_info->fbdefio = &xenfb_defio;
|
| 446 | fb_deferred_io_init(fb_info);
|
| 447 |
|
| 448 | xenfb_init_shared_page(info, fb_info);
|
| 449 |
|
| 450 | ret = xenfb_connect_backend(dev, info);
|
| 451 | if (ret < 0) {
|
| 452 | xenbus_dev_fatal(dev, ret, "xenfb_connect_backend");
|
| 453 | goto error_fb;
|
| 454 | }
|
| 455 |
|
| 456 | ret = register_framebuffer(fb_info);
|
| 457 | if (ret) {
|
| 458 | xenbus_dev_fatal(dev, ret, "register_framebuffer");
|
| 459 | goto error_fb;
|
| 460 | }
|
| 461 | info->fb_info = fb_info;
|
| 462 |
|
| 463 | xenfb_make_preferred_console();
|
| 464 | return 0;
|
| 465 |
|
| 466 | error_fb:
|
| 467 | fb_deferred_io_cleanup(fb_info);
|
| 468 | fb_dealloc_cmap(&fb_info->cmap);
|
| 469 | framebuffer_release(fb_info);
|
| 470 | error_nomem:
|
| 471 | if (!ret) {
|
| 472 | ret = -ENOMEM;
|
| 473 | xenbus_dev_fatal(dev, ret, "allocating device memory");
|
| 474 | }
|
| 475 | error:
|
| 476 | xenfb_remove(dev);
|
| 477 | return ret;
|
| 478 | }
|
| 479 |
|
| 480 | static void xenfb_make_preferred_console(void)
|
| 481 | {
|
| 482 | struct console *c;
|
| 483 |
|
| 484 | if (console_set_on_cmdline)
|
| 485 | return;
|
| 486 |
|
| 487 | console_list_lock();
|
| 488 | for_each_console(c) {
|
| 489 | if (!strcmp(c->name, "tty") && c->index == 0)
|
| 490 | break;
|
| 491 | }
|
| 492 | if (c)
|
| 493 | console_force_preferred_locked(c);
|
| 494 | console_list_unlock();
|
| 495 | }
|
| 496 |
|
| 497 | static int xenfb_resume(struct xenbus_device *dev)
|
| 498 | {
|
| 499 | struct xenfb_info *info = dev_get_drvdata(&dev->dev);
|
| 500 |
|
| 501 | xenfb_disconnect_backend(info);
|
| 502 | xenfb_init_shared_page(info, info->fb_info);
|
| 503 | return xenfb_connect_backend(dev, info);
|
| 504 | }
|
| 505 |
|
| 506 | static void xenfb_remove(struct xenbus_device *dev)
|
| 507 | {
|
| 508 | struct xenfb_info *info = dev_get_drvdata(&dev->dev);
|
| 509 |
|
| 510 | xenfb_disconnect_backend(info);
|
| 511 | if (info->fb_info) {
|
| 512 | fb_deferred_io_cleanup(info->fb_info);
|
| 513 | unregister_framebuffer(info->fb_info);
|
| 514 | fb_dealloc_cmap(&info->fb_info->cmap);
|
| 515 | framebuffer_release(info->fb_info);
|
| 516 | }
|
| 517 | free_page((unsigned long)info->page);
|
| 518 | vfree(info->gfns);
|
| 519 | vfree(info->fb);
|
| 520 | kfree(info);
|
| 521 | }
|
| 522 |
|
| 523 | static unsigned long vmalloc_to_gfn(void *address)
|
| 524 | {
|
| 525 | return xen_page_to_gfn(vmalloc_to_page(address));
|
| 526 | }
|
| 527 |
|
| 528 | static void xenfb_init_shared_page(struct xenfb_info *info,
|
| 529 | struct fb_info *fb_info)
|
| 530 | {
|
| 531 | int i;
|
| 532 | int epd = PAGE_SIZE / sizeof(info->gfns[0]);
|
| 533 |
|
| 534 | for (i = 0; i < info->nr_pages; i++)
|
| 535 | info->gfns[i] = vmalloc_to_gfn(info->fb + i * PAGE_SIZE);
|
| 536 |
|
| 537 | for (i = 0; i * epd < info->nr_pages; i++)
|
| 538 | info->page->pd[i] = vmalloc_to_gfn(&info->gfns[i * epd]);
|
| 539 |
|
| 540 | info->page->width = fb_info->var.xres;
|
| 541 | info->page->height = fb_info->var.yres;
|
| 542 | info->page->depth = fb_info->var.bits_per_pixel;
|
| 543 | info->page->line_length = fb_info->fix.line_length;
|
| 544 | info->page->mem_length = fb_info->fix.smem_len;
|
| 545 | info->page->in_cons = info->page->in_prod = 0;
|
| 546 | info->page->out_cons = info->page->out_prod = 0;
|
| 547 | }
|
| 548 |
|
| 549 | static int xenfb_connect_backend(struct xenbus_device *dev,
|
| 550 | struct xenfb_info *info)
|
| 551 | {
|
| 552 | int ret, evtchn, irq;
|
| 553 | struct xenbus_transaction xbt;
|
| 554 |
|
| 555 | ret = xenbus_alloc_evtchn(dev, &evtchn);
|
| 556 | if (ret)
|
| 557 | return ret;
|
| 558 | irq = bind_evtchn_to_irqhandler(evtchn, xenfb_event_handler,
|
| 559 | 0, dev->devicetype, info);
|
| 560 | if (irq < 0) {
|
| 561 | xenbus_free_evtchn(dev, evtchn);
|
| 562 | xenbus_dev_fatal(dev, ret, "bind_evtchn_to_irqhandler");
|
| 563 | return irq;
|
| 564 | }
|
| 565 | again:
|
| 566 | ret = xenbus_transaction_start(&xbt);
|
| 567 | if (ret) {
|
| 568 | xenbus_dev_fatal(dev, ret, "starting transaction");
|
| 569 | goto unbind_irq;
|
| 570 | }
|
| 571 | ret = xenbus_printf(xbt, dev->nodename, "page-ref", "%lu",
|
| 572 | virt_to_gfn(info->page));
|
| 573 | if (ret)
|
| 574 | goto error_xenbus;
|
| 575 | ret = xenbus_printf(xbt, dev->nodename, "event-channel", "%u",
|
| 576 | evtchn);
|
| 577 | if (ret)
|
| 578 | goto error_xenbus;
|
| 579 | ret = xenbus_printf(xbt, dev->nodename, "protocol", "%s",
|
| 580 | XEN_IO_PROTO_ABI_NATIVE);
|
| 581 | if (ret)
|
| 582 | goto error_xenbus;
|
| 583 | ret = xenbus_printf(xbt, dev->nodename, "feature-update", "1");
|
| 584 | if (ret)
|
| 585 | goto error_xenbus;
|
| 586 | ret = xenbus_transaction_end(xbt, 0);
|
| 587 | if (ret) {
|
| 588 | if (ret == -EAGAIN)
|
| 589 | goto again;
|
| 590 | xenbus_dev_fatal(dev, ret, "completing transaction");
|
| 591 | goto unbind_irq;
|
| 592 | }
|
| 593 |
|
| 594 | xenbus_switch_state(dev, XenbusStateInitialised);
|
| 595 | info->irq = irq;
|
| 596 | return 0;
|
| 597 |
|
| 598 | error_xenbus:
|
| 599 | xenbus_transaction_end(xbt, 1);
|
| 600 | xenbus_dev_fatal(dev, ret, "writing xenstore");
|
| 601 | unbind_irq:
|
| 602 | unbind_from_irqhandler(irq, info);
|
| 603 | return ret;
|
| 604 | }
|
| 605 |
|
| 606 | static void xenfb_disconnect_backend(struct xenfb_info *info)
|
| 607 | {
|
| 608 |
|
| 609 | info->update_wanted = 0;
|
| 610 | if (info->irq >= 0)
|
| 611 | unbind_from_irqhandler(info->irq, info);
|
| 612 | info->irq = -1;
|
| 613 | }
|
| 614 |
|
| 615 | static void xenfb_backend_changed(struct xenbus_device *dev,
|
| 616 | enum xenbus_state backend_state)
|
| 617 | {
|
| 618 | struct xenfb_info *info = dev_get_drvdata(&dev->dev);
|
| 619 |
|
| 620 | switch (backend_state) {
|
| 621 | case XenbusStateInitialising:
|
| 622 | case XenbusStateInitialised:
|
| 623 | case XenbusStateReconfiguring:
|
| 624 | case XenbusStateReconfigured:
|
| 625 | case XenbusStateUnknown:
|
| 626 | break;
|
| 627 |
|
| 628 | case XenbusStateInitWait:
|
| 629 | xenbus_switch_state(dev, XenbusStateConnected);
|
| 630 | break;
|
| 631 |
|
| 632 | case XenbusStateConnected:
|
| 633 | |
| 634 | * Work around xenbus race condition: If backend goes
|
| 635 | * through InitWait to Connected fast enough, we can
|
| 636 | * get Connected twice here.
|
| 637 |
|
| 638 | if (dev->state != XenbusStateConnected)
|
| 639 |
|
| 640 | xenbus_switch_state(dev, XenbusStateConnected);
|
| 641 |
|
| 642 | if (xenbus_read_unsigned(info->xbdev->otherend,
|
| 643 | "request-update", 0))
|
| 644 | info->update_wanted = 1;
|
| 645 |
|
| 646 | info->feature_resize = xenbus_read_unsigned(dev->otherend,
|
| 647 | "feature-resize", 0);
|
| 648 | break;
|
| 649 |
|
| 650 | case XenbusStateClosed:
|
| 651 | if (dev->state == XenbusStateClosed)
|
| 652 | break;
|
| 653 | fallthrough;
|
| 654 | case XenbusStateClosing:
|
| 655 | xenbus_frontend_closed(dev);
|
| 656 | break;
|
| 657 | }
|
| 658 | }
|
| 659 |
|
| 660 | static const struct xenbus_device_id xenfb_ids[] = {
|
| 661 | { "vfb" },
|
| 662 | { "" }
|
| 663 | };
|
| 664 |
|
| 665 | static struct xenbus_driver xenfb_driver = {
|
| 666 | .ids = xenfb_ids,
|
| 667 | .probe = xenfb_probe,
|
| 668 | .remove = xenfb_remove,
|
| 669 | .resume = xenfb_resume,
|
| 670 | .otherend_changed = xenfb_backend_changed,
|
| 671 | .not_essential = true,
|
| 672 | };
|
| 673 |
|
| 674 | static int __init xenfb_init(void)
|
| 675 | {
|
| 676 | if (!xen_domain())
|
| 677 | return -ENODEV;
|
| 678 |
|
| 679 |
|
| 680 | if (xen_initial_domain())
|
| 681 | return -ENODEV;
|
| 682 |
|
| 683 | if (!xen_has_pv_devices())
|
| 684 | return -ENODEV;
|
| 685 |
|
| 686 | return xenbus_register_frontend(&xenfb_driver);
|
| 687 | }
|
| 688 |
|
| 689 | static void __exit xenfb_cleanup(void)
|
| 690 | {
|
| 691 | xenbus_unregister_driver(&xenfb_driver);
|
| 692 | }
|
| 693 |
|
| 694 | module_init(xenfb_init);
|
| 695 | module_exit(xenfb_cleanup);
|
| 696 |
|
| 697 | MODULE_DESCRIPTION("Xen virtual framebuffer device frontend");
|
| 698 | MODULE_LICENSE("GPL");
|
| 699 | MODULE_ALIAS("xen:vfb");
|