From: Peter Dinda Date: Fri, 22 Mar 2013 21:21:04 +0000 (-0500) Subject: Paravirtualized graphics device X-Git-Url: http://v3vee.org/palacios/gitweb/gitweb.cgi?a=commitdiff_plain;h=cafc7daf7fdbd6c7989cac277bdaae284275e32f;hp=e376e4a1e05971bd34c1d45dac70d73429db863b;p=palacios.git Paravirtualized graphics device --- diff --git a/palacios/src/devices/Kconfig b/palacios/src/devices/Kconfig index 9748c1f..6583334 100644 --- a/palacios/src/devices/Kconfig +++ b/palacios/src/devices/Kconfig @@ -445,6 +445,21 @@ config DEBUG_VGA help Enables debugging output for the VGA device +config PARAGRAPH + bool "PARAGRAPH paravirtualized graphics card" + default n + depends GRAPHICS_CONSOLE + help + Includes a paravirtualized graphics card + +config DEBUG_PARAGRAPH + bool "DEBUG_PARAGRAPH" + default n + depends on PARAGRAPH + help + Enables debugging output for the PARAGRAPH device + + config CGA bool "CGA" default n diff --git a/palacios/src/devices/Makefile b/palacios/src/devices/Makefile index 67ca1f9..32a6498 100644 --- a/palacios/src/devices/Makefile +++ b/palacios/src/devices/Makefile @@ -45,6 +45,6 @@ obj-$(V3_CONFIG_SYMMOD) += lnx_virtio_symmod.o obj-$(V3_CONFIG_CHAR_STREAM) += char_stream.o obj-$(V3_CONFIG_VGA) += vga.o - +obj-$(V3_CONFIG_PARAGRAPH) += paragraph.o obj-$(V3_CONFIG_VNET_GUEST_IFACE) += vnet_guest_iface.o diff --git a/palacios/src/devices/paragraph.c b/palacios/src/devices/paragraph.c new file mode 100644 index 0000000..36e44d8 --- /dev/null +++ b/palacios/src/devices/paragraph.c @@ -0,0 +1,539 @@ +/* + * This file is part of the Palacios Virtual Machine Monitor developed + * by the V3VEE Project with funding from the United States National + * Science Foundation and the Department of Energy. + * + * The V3VEE Project is a joint project between Northwestern University + * and the University of New Mexico. You can find out more at + * http://www.v3vee.org + * + * Copyright (c) 2013, Peter Dinda + * Copyright (c) 2013, The V3VEE Project + * All rights reserved. + * + * Author: Peter Dinda + * + * This is free software. You are permitted to use, + * redistribute, and modify it as specified in the file "V3VEE_LICENSE". + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef V3_CONFIG_DEBUG_PARAGRAPH +#undef PrintDebug +#define PrintDebug(fmts, args...) +#endif + +/* + A paravirtualized graphics device is represented in a configuration file as: + + + pci0 + mem|gcons_mem|gcons_direct + + + + The purpose of this device is to project a graphics console + to the guest as a PCI device with a single BAR. The BAR maps + the memory of the frame buffer. + + The mode spec means the following: + mem = create a backing memory within the device (dummy device) + gcons_mem = like mem, but render to graphics console (memcpy) + gcons_direct = use the graphics console as the memory + + the default mode is mem. + +*/ + + +#define VENDOR 0xf00f +#define DEVICE 0xd00f + +#define MAXX 1024 +#define MAXY 1024 +#define MAXBPP 4 + + +#define MAX_REGION_SIZE (MAXX*MAXY*MAXBPP) + +#define DEFAULT_REGION_ADDR 0x80000000 // Right at 2 GB + + + +struct paragraph_state { + enum {MEM, GCONS_MEM, GCONS_DIRECT} mode; + // v3_lock_t lock; // my lock + struct v3_vm_info *vm; // my VM + struct vm_device *pci_bus; // my PCI bus + struct vm_device *dev; // me as a registered device + struct pci_device *pci_dev; // me as a registered PCI device + + void *my_paddr; // me as mapped into the guest (page aligned GPA) + uint64_t my_size_pages; // mapped region in pages + + // If the graphics console is used, these store it and + // the frame buffer spec it is using + struct v3_frame_buffer_spec target_spec; + v3_graphics_console_t host_cons; + void *host_fb_vaddr; + + // If the local memory is used, this stores it and its + // size + void *mem_paddr; // my memory address, paddr + void *mem_vaddr; // my memory address, vaddr + uint64_t mem_size; // my memory size in bytes +}; + +static uint64_t ceil_pages(uint64_t size) +{ + return (size / PAGE_SIZE) + !!(size % PAGE_SIZE); +} + +static uint64_t next_pow2(uint64_t size) +{ + uint64_t test; + + for (test=1; test < size; test<<=1) {} + + return test; +} + + +static int pci_bar_init(int bar_num, uint32_t * dst, void * private_data) +{ + struct paragraph_state * state = (struct paragraph_state *)private_data; + + // I am going to show up as a PCI_BAR_MEM32 at my_paddr + // and going for my_size_pages pages and not prefetchable + + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Bar init %d 0x%x\n",bar_num, *dst); + + if (bar_num!=0) { + PrintError(VM_NONE, VCORE_NONE, "paragraph: Strange - Bar Init for bar %d - ignored\n",bar_num); + return 0; + } + + if (state->mode==MEM || state->mode==GCONS_MEM) { + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Adding shadow memory for 0x%p to 0x%p -> 0x%p\n", (void*)(state->my_paddr), (void*)(state->my_paddr+state->my_size_pages*PAGE_SIZE-1), state->mem_paddr); + + if (v3_add_shadow_mem(state->vm, V3_MEM_CORE_ANY, + (addr_t)(state->my_paddr), + (addr_t)(state->my_paddr+state->my_size_pages*PAGE_SIZE-1), + (addr_t) (state->mem_paddr))) { + PrintError(VM_NONE, VCORE_NONE, "paragraph: Cannot add shadow memory for staging\n"); + return -1; + } + } else if (state->mode==GCONS_DIRECT) { + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Adding shadow memory for 0x%p to 0x%p -> 0x%p\n", (void*)(state->my_paddr), (void*)(state->my_paddr+state->my_size_pages*PAGE_SIZE-1), (void*)V3_PAddr(state->host_fb_vaddr)); + // Note the physical contiguous assumption here.... + if (v3_add_shadow_mem(state->vm, V3_MEM_CORE_ANY, + (addr_t)(state->my_paddr), + (addr_t)(state->my_paddr+state->my_size_pages*PAGE_SIZE-1), + (addr_t)(V3_PAddr(state->host_fb_vaddr)))) { + PrintError(VM_NONE, VCORE_NONE, "paragraph: Cannot add shadow memory for host fb\n"); + return -1; + } + } + + *dst = PCI_MEM32_BAR_VAL((addr_t)(state->my_paddr), 0); + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Bar init done %d is now 0x%x\n",bar_num, *dst); + + return 0; +} + +static int pci_bar_write(int bar_num, uint32_t * src, void * private_data) +{ + struct paragraph_state * state = (struct paragraph_state *)private_data; + struct v3_mem_region *old_reg; + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Bar write %d 0x%x\n",bar_num, *src); + + if (*src==(uint32_t)0xffffffff) { + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Bar write is a size request - complying\n"); + *src = ~(state->my_size_pages*PAGE_SIZE-1); + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Returning size mask as %x\n",*src); + return 0; + } + + // This whacky cast should be ok - my_paddr will be <2^32 since this is a mem32 bar + if (*src==(uint32_t)(uint64_t)(state->my_paddr)) { + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Bar write maps to currently mapped address - done.\n"); + return 0; + } + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Bar write is a remapping\n"); + + if (!(old_reg=v3_get_mem_region(state->vm, V3_MEM_CORE_ANY, (addr_t)(state->my_paddr)))) { + PrintError(VM_NONE,VCORE_NONE, "paragraph: cannot find old region in bar write...\n"); + return -1; + } + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Removing old region at 0x%p\n", (void*)(state->my_paddr)); + + v3_delete_mem_region(state->vm, old_reg); + + state->my_paddr = (void*)(*src & ~(state->my_size_pages*PAGE_SIZE - 1)); + + *src = PCI_MEM32_BAR_VAL((addr_t)(state->my_paddr), 0); + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Moving paragraph to: start=0x%p, size=%llu\n", + state->my_paddr, state->my_size_pages*PAGE_SIZE); + + + if (state->mode==MEM || state->mode==GCONS_MEM) { + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Adding shadow memory for 0x%p to 0x%p -> 0x%p\n", (void*)(state->my_paddr), (void*)(state->my_paddr+state->my_size_pages*PAGE_SIZE-1), state->mem_paddr); + if (v3_add_shadow_mem(state->vm, V3_MEM_CORE_ANY, + (addr_t)(state->my_paddr), + (addr_t)(state->my_paddr+state->my_size_pages*PAGE_SIZE-1), + (addr_t) (state->mem_paddr))) { + PrintError(VM_NONE, VCORE_NONE, "paragraph: Cannot add shadow memory for staging\n"); + return -1; + } + } else if (state->mode==GCONS_DIRECT) { + // Note the physical contiguous assumption here.... + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Adding shadow memory for 0x%p to 0x%p -> 0x%p\n", (void*)(state->my_paddr), (void*)(state->my_paddr+state->my_size_pages*PAGE_SIZE-1), (void*)V3_PAddr(state->host_fb_vaddr)); + if (v3_add_shadow_mem(state->vm, V3_MEM_CORE_ANY, + (addr_t)(state->my_paddr), + (addr_t)(state->my_paddr+state->my_size_pages*PAGE_SIZE-1), + (addr_t)(V3_PAddr(state->host_fb_vaddr)))) { + PrintError(VM_NONE, VCORE_NONE, "paragraph: Cannot add shadow memory for host fb\n"); + return -1; + } + } + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Bar write done %d 0x%x\n",bar_num, *src); + + return 0; +} + + + + + +static int register_dev(struct paragraph_state *state) +{ + int i; + struct v3_pci_bar bars[6]; + uint64_t target_size; + + if (!(state->pci_bus)) { + PrintError(state->vm, VCORE_NONE, "paragraph: no pci bus!\n"); + return -1; + } + + memset(bars,0,sizeof(struct v3_pci_bar)*6); + + for (i = 0; i < 6; i++) { + bars[i].type = PCI_BAR_NONE; + } + + // I will map my memory as a single MEM32 bar + + switch (state->mode) { + case MEM: + case GCONS_MEM: + target_size = state->mem_size; + break; + case GCONS_DIRECT: + target_size = state->target_spec.height*state->target_spec.width*state->target_spec.bytes_per_pixel; + break; + default: + PrintError(state->vm, VCORE_NONE, "paragraph: Unknown mode\n"); + return -1; + } + + if (target_size%PAGE_SIZE) { + PrintError(VM_NONE, VCORE_NONE, "paragraph: strange, target_size is not an integral number of pages\n"); + } + + if (target_size != next_pow2(target_size)) { + PrintError(VM_NONE, VCORE_NONE, "paragraph: strange, target_size is not a power of two\n"); + } + + target_size = next_pow2(target_size); + + target_size /= PAGE_SIZE; // floor(target_size/page_size) + + state->my_paddr = (void*)DEFAULT_REGION_ADDR; + state->my_size_pages = target_size; + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: Requesting passthrough bar at %p (%llu pages)\n", state->my_paddr, state->my_size_pages); + + bars[0].type = PCI_BAR_PASSTHROUGH; + bars[0].private_data = state; + bars[0].bar_init = pci_bar_init; + bars[0].bar_write = pci_bar_write; + + state->pci_dev = + v3_pci_register_device(state->pci_bus, // my bus interace + PCI_STD_DEVICE, // I'm a typical device + 0, // put me in bus zero + -1, // put me in any slot you want + 0, // I am function 0 in that slot + "PARAGRAPH", // My name + bars, // My bars + NULL, // I don't care about config writes + NULL, // I don't care about config reads + NULL, // I don't care about cmd updates + NULL, // I don't care about rom updates + state); // this is my internal state + + + if (!(state->pci_dev)) { + PrintError(state->vm, VCORE_NONE, "paragraph: Could not register PCI Device\n"); + return -1; + } + + // Now lets set up my configuration space + // to identify as the kind of pci device I am + + state->pci_dev->config_header.vendor_id = VENDOR; + state->pci_dev->config_header.device_id = DEVICE; + + return 0; +} + + +static int paragraph_free_internal(struct paragraph_state *state) +{ + if (state->host_cons) { + v3_graphics_console_close(state->host_cons); + } + + if (state->mem_paddr) { + V3_FreePages(state->mem_paddr,ceil_pages(state->mem_size)); + } + + V3_Free(state); + + return 0; +} + +static int paragraph_free(void * private_data) +{ + struct paragraph_state *state = (struct paragraph_state *)private_data; + return paragraph_free_internal(state); +} + + +static int render_callback(v3_graphics_console_t cons, + void *priv) +{ + struct paragraph_state *state = (struct paragraph_state *) priv; + + PrintDebug(VM_NONE, VCORE_NONE, "paragraph: render due to callback\n"); + + switch (state->mode) { + case MEM: + PrintError(state->vm, VCORE_NONE, "paragraph: Huh? render callback when in mem mode?\n"); + return -1; + break; + case GCONS_MEM: { + PrintDebug(state->vm, VCORE_NONE, "paragraph: render callback GCONS_MEM\n"); + + void *fb = v3_graphics_console_get_frame_buffer_data_rw(state->host_cons,&(state->target_spec)); + uint64_t target_size = state->target_spec.height*state->target_spec.width*state->target_spec.bytes_per_pixel; + + // must be smaller than the memory we have allocated + target_size = target_sizemem_size ? target_size : state->mem_size; + + PrintDebug(state->vm, VCORE_NONE, "paragraph: render - copying %llu bytes from our vaddr 0x%p to fb vaddr 0x%p\n", target_size, state->mem_vaddr, fb); + + + memcpy(fb,state->mem_vaddr,target_size); + + v3_graphics_console_release_frame_buffer_data_rw(state->host_cons); + + return 0; + } + break; + case GCONS_DIRECT: + PrintDebug(state->vm, VCORE_NONE, "paragraph: render callback GCONS_DIRECT\n"); + // nothing to do; + return 0; + break; + default: + PrintError(state->vm, VCORE_NONE, "paragraph: Huh? render callback when in unknown mode\n"); + return -1; + break; + } + + return 0; +} + +static int update_callback(v3_graphics_console_t cons, + void *priv) +{ + // Yes, Virginia, there is an update clause + return 1; +} + + +static struct v3_device_ops dev_ops = { + .free = paragraph_free, +}; + + + + +static int paragraph_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) +{ + struct vm_device *bus; + struct paragraph_state *state; + char *id; + char *bus_id; + char *mode; + + + if (!(id = v3_cfg_val(cfg,"id"))) { + PrintError(vm, VCORE_NONE, "paragraph: gnothi seauton!\n"); + return -1; + } + + if (!(bus_id = v3_cfg_val(cfg,"bus"))) { + PrintError(vm, VCORE_NONE, "paragraph: failed because there is no pci bus named\n"); + return -1; + } + + if (!(bus = v3_find_dev(vm,bus_id))) { + PrintError(vm, VCORE_NONE, "paragraph: failed because there is no pci bus given\n"); + return -1; + } + + if (!bus) { + PrintError(vm, VCORE_NONE, "paragraph: failed because there is no bus named %s\n",bus_id); + return -1; + } + + state = (struct paragraph_state *) V3_Malloc(sizeof(struct paragraph_state)); + + if (!state) { + PrintError(vm, VCORE_NONE, "paragraph: cannot allocate state\n"); + return -1; + } + + memset(state, 0, sizeof(struct paragraph_state)); + + if (!(mode=v3_cfg_val(cfg,"mode"))) { + V3_Print(vm, VCORE_NONE, "paragraph: no mode given, assuming you mean mem\n"); + state->mode=MEM; + } else { + if (!strncasecmp(mode,"mem",3)) { + state->mode=MEM; + V3_Print(state->vm, VCORE_NONE, "paragraph: mode set to mem\n"); + } else if (!strncasecmp(mode,"gcons_mem",9)) { + state->mode=GCONS_MEM; + V3_Print(state->vm, VCORE_NONE, "paragraph: mode set to gcons_mem\n"); + } else if (!strncasecmp(mode,"gcons_direct",12)) { + state->mode=GCONS_DIRECT; + V3_Print(state->vm, VCORE_NONE, "paragraph: mode set to gcons_direct\n"); + } else { + PrintError(state->vm, VCORE_NONE, "paragraph: Unknown mode %s\n",mode); + paragraph_free_internal(state); + return -1; + } + } + + state->vm = vm; + state->pci_bus = bus; + + if (state->mode==MEM || state->mode==GCONS_MEM) { + state->mem_size=MAXX*MAXY*MAXBPP; + PrintDebug(vm, VCORE_NONE, "paragraph: allocating %llu bytes for local framebuffer\n", state->mem_size); + state->mem_paddr = V3_AllocPages(ceil_pages(state->mem_size)); + if (!state->mem_paddr) { + PrintError(state->vm, VCORE_NONE, "paragraph: Cannot allocate memory for framebuffer\n"); + paragraph_free_internal(state); + return -1; + } + // the following assumes virtual address continuity + state->mem_vaddr = V3_VAddr(state->mem_paddr); + + PrintDebug(vm, VCORE_NONE, "paragraph: staging memory (state->mem) at paddr 0x%p and vaddr 0x%p size=%llu bytes (%llu pages)\n", + state->mem_paddr, state->mem_vaddr, + state->mem_size, ceil_pages(state->mem_size)); + } + + if (state->mode==GCONS_MEM || state->mode==GCONS_DIRECT) { + struct v3_frame_buffer_spec req; + + PrintDebug(vm, VCORE_NONE, "paragraph: enabling host frame buffer console (GRAPHICS_CONSOLE)\n"); + memset(&req,0,sizeof(struct v3_frame_buffer_spec)); + + req.height=MAXY; + req.width=MAXX; + req.bytes_per_pixel=MAXBPP; + req.bits_per_channel=8; + req.red_offset=0; + req.green_offset=1; + req.blue_offset=2; + + state->host_cons = v3_graphics_console_open(vm,&req,&(state->target_spec)); + + if (!state->host_cons) { + PrintError(vm, VCORE_NONE, "paragraph: unable to open host OS's graphics console\n"); + paragraph_free_internal(state); + return -1; + } + + if (memcmp(&req,&(state->target_spec),sizeof(req))) { + PrintDebug(vm, VCORE_NONE, "paragraph: warning: target spec differs from requested spec\n"); + PrintDebug(vm, VCORE_NONE, "paragraph: request: %u by %u by %u with %u bpc and r,g,b at %u, %u, %u\n", req.width, req.height, req.bytes_per_pixel, req.bits_per_channel, req.red_offset, req.green_offset, req.blue_offset); + PrintDebug(vm, VCORE_NONE, "paragraph: response: %u by %u by %u with %u bpc and r,g,b at %u, %u, %u\n", state->target_spec.width, state->target_spec.height, state->target_spec.bytes_per_pixel, state->target_spec.bits_per_channel, state->target_spec.red_offset, state->target_spec.green_offset, state->target_spec.blue_offset); + } + + if (state->mode==GCONS_DIRECT) { + PrintDebug(state->vm, VCORE_NONE, "paragraph: grabbing host console address\n"); + state->host_fb_vaddr = v3_graphics_console_get_frame_buffer_data_rw(state->host_cons,&(state->target_spec)); + if (!state->host_fb_vaddr) { + PrintError(state->vm, VCORE_NONE, "paragraph: Unable to acquire host's framebuffer address\n"); + paragraph_free_internal(state); + return -1; + } + v3_graphics_console_release_frame_buffer_data_rw(state->host_cons); + // we now assume the host FB will not move... + } + + if (v3_graphics_console_register_render_request(state->host_cons, render_callback, state)!=0) { PrintError(vm, VCORE_NONE, "paragraph: cannot install render callback\n"); + paragraph_free_internal(state); + return -1; + } + if (v3_graphics_console_register_update_inquire(state->host_cons, update_callback, state)!=0) { + PrintError(vm, VCORE_NONE, "paragraph: cannot install update inquire callback\n"); + paragraph_free_internal(state); + return -1; + } + } + + state->dev = v3_add_device(vm, id, &dev_ops, state); + + if (!(state->dev)) { + PrintError(state->vm, VCORE_NONE, "paragraph: could not attach device %s\n", id); + paragraph_free_internal(state); + return -1; + } + + if (register_dev(state) !=0 ) { + PrintError(state->vm, VCORE_NONE, "paragraph: could not set up device for pci\n"); + paragraph_free_internal(state); + return -1; + } + + V3_Print(state->vm, VCORE_NONE, "paragraph: added device id %s\n",id); + + return 0; +} + +device_register("PARAGRAPH", paragraph_init)