]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/video/fbdev/hyperv_fb.c
Merge tag 'pm-5.6-rc1-3' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
[linux.git] / drivers / video / fbdev / hyperv_fb.c
index 08bc0dfb5ce7d1037e5c5e69540a072229132736..f47d50e560c063d2aa02ab1e29772355aed24474 100644 (file)
  * "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.
  */
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -228,7 +238,6 @@ struct synthvid_msg {
 } __packed;
 
 
-
 /* FB driver definitions and structures */
 #define HVFB_WIDTH 1152 /* default screen width */
 #define HVFB_HEIGHT 864 /* default screen height */
@@ -258,12 +267,15 @@ struct hvfb_par {
        /* If true, the VSC notifies the VSP on every framebuffer change */
        bool synchronous_fb;
 
+       /* If true, need to copy from deferred IO mem to framebuffer mem */
+       bool need_docopy;
+
        struct notifier_block hvfb_panic_nb;
 
        /* Memory for deferred IO and frame buffer itself */
        unsigned char *dio_vp;
        unsigned char *mmio_vp;
-       unsigned long mmio_pp;
+       phys_addr_t mmio_pp;
 
        /* Dirty rectangle, protected by delayed_refresh_lock */
        int x1, y1, x2, y2;
@@ -434,7 +446,7 @@ static void synthvid_deferred_io(struct fb_info *p,
                maxy = max_t(int, maxy, y2);
 
                /* Copy from dio space to mmio address */
-               if (par->fb_ready)
+               if (par->fb_ready && par->need_docopy)
                        hvfb_docopy(par, start, PAGE_SIZE);
        }
 
@@ -751,12 +763,12 @@ static void hvfb_update_work(struct work_struct *w)
                return;
 
        /* Copy the dirty rectangle to frame buffer memory */
-       for (j = y1; j < y2; j++) {
-               hvfb_docopy(par,
-                           j * info->fix.line_length +
-                           (x1 * screen_depth / 8),
-                           (x2 - x1) * screen_depth / 8);
-       }
+       if (par->need_docopy)
+               for (j = y1; j < y2; j++)
+                       hvfb_docopy(par,
+                                   j * info->fix.line_length +
+                                   (x1 * screen_depth / 8),
+                                   (x2 - x1) * screen_depth / 8);
 
        /* Refresh */
        if (par->fb_ready && par->update)
@@ -801,7 +813,8 @@ static int hvfb_on_panic(struct notifier_block *nb,
        par = container_of(nb, struct hvfb_par, hvfb_panic_nb);
        par->synchronous_fb = true;
        info = par->info;
-       hvfb_docopy(par, 0, dio_fb_size);
+       if (par->need_docopy)
+               hvfb_docopy(par, 0, dio_fb_size);
        synthvid_update(info, 0, 0, INT_MAX, INT_MAX);
 
        return NOTIFY_DONE;
@@ -895,7 +908,7 @@ static void hvfb_cfb_imageblit(struct fb_info *p,
                                               image->width, image->height);
 }
 
-static struct fb_ops hvfb_ops = {
+static const struct fb_ops hvfb_ops = {
        .owner = THIS_MODULE,
        .fb_check_var = hvfb_check_var,
        .fb_set_par = hvfb_set_par,
@@ -940,6 +953,62 @@ static void hvfb_get_option(struct fb_info *info)
        return;
 }
 
+/*
+ * Allocate enough contiguous physical memory.
+ * Return physical address if succeeded or -1 if failed.
+ */
+static phys_addr_t hvfb_get_phymem(struct hv_device *hdev,
+                                  unsigned int request_size)
+{
+       struct page *page = NULL;
+       dma_addr_t dma_handle;
+       void *vmem;
+       phys_addr_t paddr = 0;
+       unsigned int order = get_order(request_size);
+
+       if (request_size == 0)
+               return -1;
+
+       if (order < MAX_ORDER) {
+               /* Call alloc_pages if the size is less than 2^MAX_ORDER */
+               page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
+               if (!page)
+                       return -1;
+
+               paddr = (page_to_pfn(page) << PAGE_SHIFT);
+       } else {
+               /* Allocate from CMA */
+               hdev->device.coherent_dma_mask = DMA_BIT_MASK(64);
+
+               vmem = dma_alloc_coherent(&hdev->device,
+                                         round_up(request_size, PAGE_SIZE),
+                                         &dma_handle,
+                                         GFP_KERNEL | __GFP_NOWARN);
+
+               if (!vmem)
+                       return -1;
+
+               paddr = virt_to_phys(vmem);
+       }
+
+       return paddr;
+}
+
+/* Release contiguous physical memory */
+static void hvfb_release_phymem(struct hv_device *hdev,
+                               phys_addr_t paddr, unsigned int size)
+{
+       unsigned int order = get_order(size);
+
+       if (order < MAX_ORDER)
+               __free_pages(pfn_to_page(paddr >> PAGE_SHIFT), order);
+       else
+               dma_free_coherent(&hdev->device,
+                                 round_up(size, PAGE_SIZE),
+                                 phys_to_virt(paddr),
+                                 paddr);
+}
+
 
 /* Get framebuffer memory from Hyper-V video pci space */
 static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
@@ -949,22 +1018,61 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
        void __iomem *fb_virt;
        int gen2vm = efi_enabled(EFI_BOOT);
        resource_size_t pot_start, pot_end;
+       phys_addr_t paddr;
        int ret;
 
-       dio_fb_size =
-               screen_width * screen_height * screen_depth / 8;
+       info->apertures = alloc_apertures(1);
+       if (!info->apertures)
+               return -ENOMEM;
 
-       if (gen2vm) {
-               pot_start = 0;
-               pot_end = -1;
-       } else {
+       if (!gen2vm) {
                pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT,
-                             PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
+                       PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
                if (!pdev) {
                        pr_err("Unable to find PCI Hyper-V video\n");
+                       kfree(info->apertures);
                        return -ENODEV;
                }
 
+               info->apertures->ranges[0].base = pci_resource_start(pdev, 0);
+               info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
+
+               /*
+                * 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);
+
+                       info->fix.smem_start = paddr;
+                       info->fix.smem_len = screen_fb_size;
+                       info->screen_base = par->mmio_vp;
+                       info->screen_size = screen_fb_size;
+
+                       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 {
+               info->apertures->ranges[0].base = screen_info.lfb_base;
+               info->apertures->ranges[0].size = screen_info.lfb_size;
+       }
+
+       /*
+        * Cannot use the contiguous physical memory.
+        * Allocate mmio space for framebuffer.
+        */
+       dio_fb_size =
+               screen_width * screen_height * screen_depth / 8;
+
+       if (gen2vm) {
+               pot_start = 0;
+               pot_end = -1;
+       } else {
                if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) ||
                    pci_resource_len(pdev, 0) < screen_fb_size) {
                        pr_err("Resource not available or (0x%lx < 0x%lx)\n",
@@ -993,20 +1101,6 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
        if (par->dio_vp == NULL)
                goto err3;
 
-       info->apertures = alloc_apertures(1);
-       if (!info->apertures)
-               goto err4;
-
-       if (gen2vm) {
-               info->apertures->ranges[0].base = screen_info.lfb_base;
-               info->apertures->ranges[0].size = screen_info.lfb_size;
-               remove_conflicting_framebuffers(info->apertures,
-                                               KBUILD_MODNAME, false);
-       } else {
-               info->apertures->ranges[0].base = pci_resource_start(pdev, 0);
-               info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
-       }
-
        /* Physical address of FB device */
        par->mmio_pp = par->mem->start;
        /* Virtual address of FB device */
@@ -1017,13 +1111,15 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
        info->screen_base = par->dio_vp;
        info->screen_size = dio_fb_size;
 
+getmem_done:
+       remove_conflicting_framebuffers(info->apertures,
+                                       KBUILD_MODNAME, false);
        if (!gen2vm)
                pci_dev_put(pdev);
+       kfree(info->apertures);
 
        return 0;
 
-err4:
-       vfree(par->dio_vp);
 err3:
        iounmap(fb_virt);
 err2:
@@ -1032,18 +1128,25 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
 err1:
        if (!gen2vm)
                pci_dev_put(pdev);
+       kfree(info->apertures);
 
        return -ENOMEM;
 }
 
 /* Release the framebuffer */
-static void hvfb_putmem(struct fb_info *info)
+static void hvfb_putmem(struct hv_device *hdev, struct fb_info *info)
 {
        struct hvfb_par *par = info->par;
 
-       vfree(par->dio_vp);
-       iounmap(info->screen_base);
-       vmbus_free_mmio(par->mem->start, screen_fb_size);
+       if (par->need_docopy) {
+               vfree(par->dio_vp);
+               iounmap(info->screen_base);
+               vmbus_free_mmio(par->mem->start, screen_fb_size);
+       } else {
+               hvfb_release_phymem(hdev, info->fix.smem_start,
+                                   screen_fb_size);
+       }
+
        par->mem = NULL;
 }
 
@@ -1062,6 +1165,7 @@ static int hvfb_probe(struct hv_device *hdev,
        par = info->par;
        par->info = info;
        par->fb_ready = false;
+       par->need_docopy = true;
        init_completion(&par->wait);
        INIT_DELAYED_WORK(&par->dwork, hvfb_update_work);
 
@@ -1147,7 +1251,7 @@ static int hvfb_probe(struct hv_device *hdev,
 
 error:
        fb_deferred_io_cleanup(info);
-       hvfb_putmem(info);
+       hvfb_putmem(hdev, info);
 error2:
        vmbus_close(hdev->channel);
 error1:
@@ -1177,7 +1281,7 @@ static int hvfb_remove(struct hv_device *hdev)
        vmbus_close(hdev->channel);
        hv_set_drvdata(hdev, NULL);
 
-       hvfb_putmem(info);
+       hvfb_putmem(hdev, info);
        framebuffer_release(info);
 
        return 0;