/* * GRUB -- GRand Unified Bootloader * Copyright (C) 2013 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #define grub_video_render_target grub_video_fbrender_target #include #include #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); struct virtfb { int handle; char *fullname; char *backend_dir; char *frontend_dir; struct xenfb_page *fbpage; grub_xen_grant_t grant; grub_xen_evtchn_t evtchn; char *framebuffer; grub_xen_mfn_t *page_directory; int width, height; }; struct virtfb vfb; static grub_xen_mfn_t grub_xen_ptr2mfn (void *ptr) { grub_xen_mfn_t *mfn_list = (grub_xen_mfn_t *) grub_xen_start_page_addr->mfn_list; return mfn_list[(grub_addr_t) ptr >> GRUB_XEN_LOG_PAGE_SIZE]; } static int fill (const char *dir, void *data __attribute__ ((unused))) { domid_t dom; /* "dir" is just a number, at most 19 characters. */ char fdir[200]; char num[20]; grub_err_t err; void *buf; struct evtchn_alloc_unbound alloc_unbound; if (vfb.fbpage) return 1; vfb.handle = grub_strtoul (dir, 0, 10); if (grub_errno) { grub_errno = 0; return 0; } vfb.fullname = 0; vfb.backend_dir = 0; grub_snprintf (fdir, sizeof (fdir), "device/vfb/%s/backend", dir); vfb.backend_dir = grub_xenstore_get_file (fdir, NULL); if (!vfb.backend_dir) goto out_fail_1; grub_snprintf (fdir, sizeof (fdir), "%s/dev", vfb.backend_dir); grub_snprintf (fdir, sizeof (fdir), "device/vfb/%s/backend-id", dir); buf = grub_xenstore_get_file (fdir, NULL); if (!buf) goto out_fail_1; dom = grub_strtoul (buf, 0, 10); grub_free (buf); if (grub_errno) goto out_fail_1; vfb.fbpage = grub_xen_alloc_shared_page (dom, &vfb.grant); if (!vfb.fbpage) goto out_fail_1; grub_snprintf (fdir, sizeof (fdir), "device/vfb/%s/page-ref", dir); grub_snprintf (num, sizeof (num), "%llu", (unsigned long long) grub_xen_ptr2mfn (vfb.fbpage)); err = grub_xenstore_write_file (fdir, num, grub_strlen (num)); if (err) goto out_fail_3; grub_snprintf (fdir, sizeof (fdir), "device/vfb/%s/protocol", dir); err = grub_xenstore_write_file (fdir, XEN_IO_PROTO_ABI_NATIVE, grub_strlen (XEN_IO_PROTO_ABI_NATIVE)); if (err) goto out_fail_3; alloc_unbound.dom = DOMID_SELF; alloc_unbound.remote_dom = dom; grub_xen_event_channel_op (EVTCHNOP_alloc_unbound, &alloc_unbound); vfb.evtchn = alloc_unbound.port; grub_snprintf (fdir, sizeof (fdir), "device/vfb/%s/event-channel", dir); grub_snprintf (num, sizeof (num), "%u", vfb.evtchn); err = grub_xenstore_write_file (fdir, num, grub_strlen (num)); if (err) goto out_fail_3; struct gnttab_dump_table dt; dt.dom = DOMID_SELF; grub_xen_grant_table_op (GNTTABOP_dump_table, (void *) &dt, 1); grub_snprintf (fdir, sizeof (fdir), "device/vfb/%s", dir); vfb.frontend_dir = grub_strdup (fdir); grub_size_t i; const unsigned pages_per_pd = (GRUB_XEN_PAGE_SIZE / sizeof (grub_xen_mfn_t)); vfb.fbpage->in_cons = 0; vfb.fbpage->in_prod = 0; vfb.fbpage->out_cons = 0; vfb.fbpage->out_prod = 0; vfb.fbpage->width = vfb.width; vfb.fbpage->height = vfb.height; vfb.fbpage->line_length = vfb.width * 4; vfb.fbpage->depth = 32; grub_size_t fbsize = ALIGN_UP (vfb.width * vfb.height * 4, GRUB_XEN_PAGE_SIZE); vfb.fbpage->mem_length = fbsize; grub_size_t fbpages = fbsize / GRUB_XEN_PAGE_SIZE; grub_size_t pd_size = ALIGN_UP (fbpages, pages_per_pd); vfb.framebuffer = grub_memalign (GRUB_XEN_PAGE_SIZE, fbsize); if (!vfb.framebuffer) goto out_fail_3; vfb.page_directory = grub_memalign (GRUB_XEN_PAGE_SIZE, pd_size * sizeof (grub_xen_mfn_t)); if (!vfb.page_directory) goto out_fail_3; for (i = 0; i < fbpages; i++) vfb.page_directory[i] = grub_xen_ptr2mfn (vfb.framebuffer + GRUB_XEN_PAGE_SIZE * i); for (; i < pd_size; i++) vfb.page_directory[i] = 0; for (i = 0; i < pd_size / pages_per_pd; i++) vfb.fbpage->pd[i] = grub_xen_ptr2mfn (vfb.page_directory + (GRUB_XEN_PAGE_SIZE / sizeof (vfb.page_directory[0])) * i); grub_snprintf (fdir, sizeof (fdir), "device/vfb/%s/state", dir); err = grub_xenstore_write_file (fdir, "3", 1); if (err) goto out_fail_3; while (1) { grub_snprintf (fdir, sizeof (fdir), "%s/state", vfb.backend_dir); buf = grub_xenstore_get_file (fdir, NULL); if (!buf) goto out_fail_3; if (grub_strcmp (buf, "2") != 0) break; grub_free (buf); grub_xen_sched_op (SCHEDOP_yield, 0); } grub_dprintf ("xen", "state=%s\n", (char *) buf); grub_free (buf); return 1; out_fail_3: grub_xen_free_shared_page (vfb.fbpage); out_fail_1: grub_free (vfb.framebuffer); vfb.framebuffer = 0; grub_free (vfb.page_directory); vfb.page_directory = 0; vfb.fbpage = 0; grub_free (vfb.backend_dir); grub_free (vfb.fullname); grub_errno = 0; return 0; } static void vfb_fini (void) { char fdir[200]; char *buf; struct evtchn_close close_op = {.port = vfb.evtchn }; if (!vfb.fbpage) return; grub_snprintf (fdir, sizeof (fdir), "%s/state", vfb.frontend_dir); grub_xenstore_write_file (fdir, "6", 1); while (1) { grub_snprintf (fdir, sizeof (fdir), "%s/state", vfb.backend_dir); buf = grub_xenstore_get_file (fdir, NULL); grub_dprintf ("xen", "state=%s\n", (char *) buf); if (!buf || grub_strcmp (buf, "6") == 0) break; grub_free (buf); grub_xen_sched_op (SCHEDOP_yield, 0); } grub_free (buf); grub_snprintf (fdir, sizeof (fdir), "%s/page-ref", vfb.frontend_dir); grub_xenstore_write_file (fdir, NULL, 0); grub_snprintf (fdir, sizeof (fdir), "%s/event-channel", vfb.frontend_dir); grub_xenstore_write_file (fdir, NULL, 0); grub_xen_free_shared_page (vfb.fbpage); vfb.fbpage = 0; grub_xen_event_channel_op (EVTCHNOP_close, &close_op); /* Prepare for handoff. */ grub_snprintf (fdir, sizeof (fdir), "%s/state", vfb.frontend_dir); grub_xenstore_write_file (fdir, "1", 1); grub_free (vfb.framebuffer); vfb.framebuffer = 0; grub_free (vfb.page_directory); vfb.page_directory = 0; } static grub_err_t vfb_init (int width, int height) { if (vfb.fbpage) vfb_fini (); vfb.width = width; vfb.height = height; grub_xenstore_dir ("device/vfb", fill, NULL); if (vfb.fbpage) grub_errno = 0; if (!vfb.fbpage) return grub_error (GRUB_ERR_IO, "couldn't init vfb"); return GRUB_ERR_NONE; } static void grub_video_xenfb_fill_mode_info (struct grub_video_mode_info *out) { grub_memset (out, 0, sizeof (*out)); out->pitch = vfb.width * 4; out->width = vfb.width; out->height = vfb.height; out->mode_type = GRUB_VIDEO_MODE_TYPE_RGB; out->bpp = 32; out->bytes_per_pixel = 4; out->number_of_colors = 256; out->reserved_mask_size = 8; out->reserved_field_pos = 24; out->red_mask_size = 8; out->red_field_pos = 16; out->green_mask_size = 8; out->green_field_pos = 8; out->blue_mask_size = 8; out->blue_field_pos = 0; out->blit_format = grub_video_get_blit_format (out); } static grub_err_t grub_video_ieee1275_setup (unsigned int width, unsigned int height, unsigned int mode_type __attribute__ ((unused)), unsigned int mode_mask __attribute__ ((unused))) { struct grub_video_mode_info modeinfo; grub_err_t err; err = vfb_init (width ? : 800, height ? : 600); if (err) return err; grub_video_xenfb_fill_mode_info (&modeinfo); err = grub_video_fb_setup (mode_type, mode_mask, &modeinfo, vfb.framebuffer, NULL, NULL); if (err) return err; err = grub_video_fb_set_palette (0, GRUB_VIDEO_FBSTD_NUMCOLORS, grub_video_fbstd_colors); return err; } static grub_err_t grub_video_xenfb_get_info_and_fini (struct grub_video_mode_info *mode_info, void **framebuf) { grub_video_xenfb_fill_mode_info (mode_info); *framebuf = vfb.framebuffer; grub_video_fb_fini (); return GRUB_ERR_NONE; } static grub_err_t grub_video_vfb_fini (void) { vfb_fini (); return grub_video_fb_fini (); } static struct grub_video_adapter grub_video_xen_adapter = { .name = "XEN video driver", .prio = GRUB_VIDEO_ADAPTER_PRIO_NATIVE, .id = GRUB_VIDEO_DRIVER_XEN, .init = grub_video_fb_init, .fini = grub_video_vfb_fini, .setup = grub_video_ieee1275_setup, .get_info = grub_video_fb_get_info, .get_info_and_fini = grub_video_xenfb_get_info_and_fini, .set_palette = grub_video_fb_set_palette, .get_palette = grub_video_fb_get_palette, .set_viewport = grub_video_fb_set_viewport, .get_viewport = grub_video_fb_get_viewport, .set_region = grub_video_fb_set_region, .get_region = grub_video_fb_get_region, .set_area_status = grub_video_fb_set_area_status, .get_area_status = grub_video_fb_get_area_status, .map_color = grub_video_fb_map_color, .map_rgb = grub_video_fb_map_rgb, .map_rgba = grub_video_fb_map_rgba, .unmap_color = grub_video_fb_unmap_color, .fill_rect = grub_video_fb_fill_rect, .blit_bitmap = grub_video_fb_blit_bitmap, .blit_render_target = grub_video_fb_blit_render_target, .scroll = grub_video_fb_scroll, .swap_buffers = grub_video_fb_swap_buffers, .create_render_target = grub_video_fb_create_render_target, .delete_render_target = grub_video_fb_delete_render_target, .set_active_render_target = grub_video_fb_set_active_render_target, .get_active_render_target = grub_video_fb_get_active_render_target, .next = 0 }; void grub_video_xen_init (void) { grub_video_register (&grub_video_xen_adapter); } void grub_video_xen_fini (void) { vfb_fini (); grub_video_unregister (&grub_video_xen_adapter); }