/* * Hyper-V Synthetic Video Frame Buffer Driver * * This is the driver for the Hyper-V Synthetic Video, which supports * screen resolution up to Full HD 1920x1080 with 32 bit color on Windows * Server 2012, and 1600x1200 with 16 bit color on Windows Server 2008 R2 * or earlier. * * It also solves the double mouse cursor issue of the emulated video mode. * * The default screen resolution is 1152x864, which may be changed by a * kernel parameter: * video=hyperv_fb:<width>x<height> * For example: video=hyperv_fb:1280x1024 * * Portrait orientation is also supported: * For example: video=hyperv_fb:864x1152 * * When a Windows 10 RS5+ host is used, the virtual machine screen * resolution is obtained from the host. The "video=hyperv_fb" option is * not needed, but still can be used to overwrite what the host specifies. * The VM resolution on the host could be set by executing the powershell * "set-vmvideo" command. For example * set-vmvideo -vmname name -horizontalresolution:1920 \ * -verticalresolution:1200 -resolutiontype single * * Gen 1 VMs also support direct using VM's physical memory for framebuffer. * It could improve the efficiency and performance for framebuffer and VM. * This requires to allocate contiguous physical memory from Linux kernel's * CMA memory allocator. To enable this, supply a kernel parameter to give * enough memory space to CMA allocator for framebuffer. For example: * cma=130m * This gives 130MB memory to CMA allocator that can be allocated to * framebuffer. For reference, 8K resolution (7680x4320) takes about * 127MB memory.
*/
/* * Merge dirty pages. It is possible that last page cross * over the end of frame buffer row yres. This is taken care of * in synthvid_update function by clamping the y2 * value to yres.
*/
list_for_each_entry(pageref, pagereflist, list) {
start = pageref->offset;
end = start + PAGE_SIZE - 1;
y1 = start / p->fix.line_length;
y2 = end / p->fix.line_length;
miny = min_t(int, miny, y1);
maxy = max_t(int, maxy, y2);
/* Copy from dio space to mmio address */ if (par->fb_ready && par->need_docopy)
hvfb_docopy(par, start, PAGE_SIZE);
}
/* Reply with screen and cursor info */ if (msg->vid_hdr.type == SYNTHVID_FEATURE_CHANGE) { if (par->fb_ready) {
synthvid_send_ptr(hdev);
synthvid_send_situ(hdev);
}
par->update = msg->feature_chg.is_dirt_needed; if (par->update)
schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY);
}
}
/* Receive callback for messages from the host */ staticvoid synthvid_receive(void *ctx)
{ struct hv_device *hdev = ctx; struct fb_info *info = hv_get_drvdata(hdev); struct hvfb_par *par; struct synthvid_msg *recv_buf;
u32 bytes_recvd;
u64 req_id; int ret;
if (!info) return;
par = info->par;
recv_buf = (struct synthvid_msg *)par->recv_buf;
do {
ret = vmbus_recvpacket(hdev->channel, recv_buf,
MAX_VMBUS_PKT_SIZE,
&bytes_recvd, &req_id); if (bytes_recvd > 0 &&
recv_buf->pipe_hdr.type == PIPE_MSG_DATA)
synthvid_recv_sub(hdev);
} while (bytes_recvd > 0 && ret == 0);
}
/* Check if the ver1 version is equal or greater than ver2 */ staticinlinebool synthvid_ver_ge(u32 ver1, u32 ver2)
{ if (SYNTHVID_VER_GET_MAJOR(ver1) > SYNTHVID_VER_GET_MAJOR(ver2) ||
(SYNTHVID_VER_GET_MAJOR(ver1) == SYNTHVID_VER_GET_MAJOR(ver2) &&
SYNTHVID_VER_GET_MINOR(ver1) >= SYNTHVID_VER_GET_MINOR(ver2))) returntrue;
returnfalse;
}
/* Check synthetic video protocol version with the host */ staticint synthvid_negotiate_ver(struct hv_device *hdev, u32 ver)
{ struct fb_info *info = hv_get_drvdata(hdev); struct hvfb_par *par = info->par; struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf; int ret = 0; unsignedlong t;
t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT); if (!t) {
pr_err("Time out on waiting version response\n");
ret = -ETIMEDOUT; goto out;
} if (!msg->ver_resp.is_accepted) {
ret = -ENODEV; goto out;
}
par->synthvid_version = ver;
pr_info("Synthvid Version major %d, minor %d\n",
SYNTHVID_VER_GET_MAJOR(ver), SYNTHVID_VER_GET_MINOR(ver));
out: return ret;
}
/* Get current resolution from the host */ staticint synthvid_get_supported_resolution(struct hv_device *hdev)
{ struct fb_info *info = hv_get_drvdata(hdev); struct hvfb_par *par = info->par; struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf; int ret = 0; unsignedlong t;
u8 index;
/* Connect to VSP (Virtual Service Provider) on host */ staticint synthvid_connect_vsp(struct hv_device *hdev)
{ struct fb_info *info = hv_get_drvdata(hdev); struct hvfb_par *par = info->par; int ret;
ret = vmbus_open(hdev->channel, RING_BUFSIZE, RING_BUFSIZE,
NULL, 0, synthvid_receive, hdev); if (ret) {
pr_err("Unable to open vmbus channel\n"); return ret;
}
/* Negotiate the protocol version with host */ switch (vmbus_proto_version) { case VERSION_WIN10: case VERSION_WIN10_V5:
ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN10); if (!ret) break;
fallthrough; case VERSION_WIN8: case VERSION_WIN8_1:
ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN8); break; default:
ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN10); break;
}
if (ret) {
pr_err("Synthetic video device version not accepted\n"); goto error;
}
screen_depth = SYNTHVID_DEPTH_WIN8; if (synthvid_ver_ge(par->synthvid_version, SYNTHVID_VERSION_WIN10)) {
ret = synthvid_get_supported_resolution(hdev); if (ret)
pr_info("Failed to get supported resolution from host, use default\n");
}
t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT); if (!t) {
pr_err("Time out on waiting vram location ack\n");
ret = -ETIMEDOUT; goto out;
} if (msg->vram_ack.user_ctx != par->mmio_pp) {
pr_err("Unable to set VRAM location\n");
ret = -ENODEV; goto out;
}
/* Send pointer and situation update */
synthvid_send_ptr(hdev);
synthvid_send_situ(hdev);
out: return ret;
}
/* * Delayed work callback: * It is scheduled to call whenever update request is received and it has * not been called in last HVFB_ONDEMAND_THROTTLE time interval.
*/ staticvoid hvfb_update_work(struct work_struct *w)
{ struct hvfb_par *par = container_of(w, struct hvfb_par, dwork.work); struct fb_info *info = par->info; unsignedlong flags; int x1, x2, y1, y2; int j;
spin_lock_irqsave(&par->delayed_refresh_lock, flags); /* Reset the request flag */
par->delayed_refresh = false;
/* Store the dirty rectangle to local variables */
x1 = par->x1;
x2 = par->x2;
y1 = par->y1;
y2 = par->y2;
/* * Control the on-demand refresh frequency. It schedules a delayed * screen update if it has not yet.
*/ staticvoid hvfb_ondemand_refresh_throttle(struct hvfb_par *par, int x1, int y1, int w, int h)
{ unsignedlong flags; int x2 = x1 + w; int y2 = y1 + h;
if (par->synchronous_fb)
synthvid_update(info, 0, 0, INT_MAX, INT_MAX); else
hvfb_ondemand_refresh_throttle(par, x, y, width, height);
}
/* * fb_ops.fb_destroy is called by the last put_fb_info() call at the end * of unregister_framebuffer() or fb_release(). Do any cleanup related to * framebuffer here.
*/ staticvoid hvfb_destroy(struct fb_info *info)
{
hvfb_putmem(info);
framebuffer_release(info);
}
/* * TODO: GEN1 codepaths allocate from system or DMA-able memory. Fix the * driver to use the _SYSMEM_ or _DMAMEM_ helpers in these cases.
*/
FB_GEN_DEFAULT_DEFERRED_IOMEM_OPS(hvfb_ops,
hvfb_ops_damage_range,
hvfb_ops_damage_area)
if (order <= MAX_PAGE_ORDER) { /* Call alloc_pages if the size is less than 2^MAX_PAGE_ORDER */
page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); if (!page) return -1;
/* * For Gen 1 VM, we can directly use the contiguous memory * from VM. If we succeed, deferred IO happens directly * on this allocated framebuffer memory, avoiding extra * memory copy.
*/
paddr = hvfb_get_phymem(hdev, screen_fb_size); if (paddr != (phys_addr_t) -1) {
par->mmio_pp = paddr;
par->mmio_vp = par->dio_vp = __va(paddr);
par->need_docopy = false; goto getmem_done;
}
pr_info("Unable to allocate enough contiguous physical memory on Gen 1 VM. Using MMIO instead.\n");
} else {
aperture_remove_all_conflicting_devices(KBUILD_MODNAME);
}
/* * Cannot use contiguous physical memory, so allocate MMIO space for * the framebuffer. At this point in the function, conflicting devices * that might have claimed the framebuffer MMIO space based on * screen_info.lfb_base must have already been removed so that * vmbus_allocate_mmio() does not allocate different MMIO space. If the * kdump image were to be loaded using kexec_file_load(), the * framebuffer location in the kdump image would be set from * screen_info.lfb_base at the time that kdump is enabled. If the * framebuffer has moved elsewhere, this could be the wrong location, * causing kdump to hang when efifb (for example) loads.
*/
dio_fb_size =
screen_width * screen_height * screen_depth / 8;
ret = vmbus_allocate_mmio(&par->mem, hdev, 0, -1,
screen_fb_size, 0x100000, true); if (ret != 0) {
pr_err("Unable to allocate framebuffer memory\n"); goto err1;
}
/* * Map the VRAM cacheable for performance. This is also required for * VM Connect to display properly for ARM64 Linux VM, as the host also * maps the VRAM cacheable.
*/
fb_virt = ioremap_cache(par->mem->start, screen_fb_size); if (!fb_virt) goto err2;
/* Allocate memory for deferred IO */
par->dio_vp = vzalloc(round_up(dio_fb_size, PAGE_SIZE)); if (par->dio_vp == NULL) goto err3;
/* Send config to host */
ret = synthvid_send_config(hdev); if (ret) goto error;
ret = devm_register_framebuffer(&hdev->device, info); if (ret) {
pr_err("Unable to register framebuffer\n"); goto error;
}
par->fb_ready = true;
par->synchronous_fb = false;
/* * We need to be sure this panic notifier runs _before_ the * vmbus disconnect, so order it by priority. It must execute * before the function hv_panic_vmbus_unload() [drivers/hv/vmbus_drv.c], * which is almost at the end of list, with priority = INT_MIN + 1.
*/
par->hvfb_panic_nb.notifier_call = hvfb_on_panic;
par->hvfb_panic_nb.priority = INT_MIN + 10;
atomic_notifier_chain_register(&panic_notifier_list,
&par->hvfb_panic_nb);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.