/* * 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)