From: Wei Hu weh@microsoft.com Sent: Tuesday, October 22, 2019 4:11 AM
On Hyper-V, Generation 1 VMs can directly use VM's physical memory for their framebuffers. This can improve the efficiency of framebuffer and overall performence for VM. The physical memory assigned to framebuffer must be contiguous. We use CMA allocator to get contiguouse physicial memory when the framebuffer size is greater than 4MB. For size under 4MB, we use alloc_pages to achieve this.
To enable framebuffer memory allocation from CMA, supply a kernel parameter to give enough space to CMA allocator at boot time. For example: cma=130m This gives 130MB memory to CAM allocator that can be allocated to framebuffer. If this fails, we fall back to the old way of using mmio for framebuffer.
Signed-off-by: Wei Hu weh@microsoft.com
[snip]
+/*
- Allocate enough contiguous physical memory.
- Return physical address if succeeded or -1 if failed.
- */
+static unsigned long hvfb_get_phymem(unsigned int request_size) +{
- struct page *page = NULL;
- unsigned int request_pages;
- unsigned long paddr = 0;
Using 'unsigned long' for physical addresses is problematic on 32-bit systems with Physical Address Extension (PAE) enabled. Linux has phys_addr_t that handles 32-bit PAE correctly and that probably should be used here. This whole driver needs to be carefully checked for the correct type for physical addresses.
- unsigned int order = get_order(request_size);
- if (request_size == 0)
return -1;
- /* Try call alloc_pages if the size is less than 2^MAX_ORDER */
- if (order < MAX_ORDER) {
page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
if (!page)
return -1;
request_pages = (1 << order);
goto get_phymem1;
- }
- /* Allocate from CMA */
- // request_pages = (request_size >> PAGE_SHIFT) + 1;
- request_pages = (round_up(request_size, PAGE_SIZE) >> PAGE_SHIFT);
- page = dma_alloc_from_contiguous(NULL, request_pages, 0, false);
- if (page == NULL)
return -1;
+get_phymem1:
- paddr = (page_to_pfn(page) << PAGE_SHIFT);
- pr_info("Allocated %d pages starts at physical addr 0x%lx\n",
request_pages, paddr);
- return paddr;
+}
+/* Release contiguous physical memory */ +static void hvfb_release_phymem(unsigned long paddr, unsigned int size)
Same issue here with 'unsigned long paddr'.
+{
- unsigned int order = get_order(size);
- if (order < MAX_ORDER)
__free_pages(pfn_to_page(paddr >> PAGE_SHIFT), order);
- else
dma_release_from_contiguous(NULL,
pfn_to_page(paddr >> PAGE_SHIFT),
(round_up(size, PAGE_SIZE) >>
PAGE_SHIFT));
// (size >> PAGE_SHIFT) + 1);
+}
/* Get framebuffer memory from Hyper-V video pci space */ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info) @@ -947,8 +1017,58 @@ 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;
- unsigned long paddr;
Same issue with 'unsigned long'.
int ret;
- if (!gen2vm) {
pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT,
PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
if (!pdev) {
pr_err("Unable to find PCI Hyper-V video\n");
return -ENODEV;
}
- }
- info->apertures = alloc_apertures(1);
- if (!info->apertures)
goto err1;
- if (gen2vm) {
info->apertures->ranges[0].base = screen_info.lfb_base;
info->apertures->ranges[0].size = screen_info.lfb_size;
- } else {
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 success, deferred IO happens directly
* on this allocated framebuffer memory, avoiding extra
* memory copy.
*/
- if (!gen2vm) {
paddr = hvfb_get_phymem(screen_fb_size);
if (paddr != (unsigned long) -1) {
phys_addr_t
par->mmio_pp = paddr;
I'm not really sure what to do about the above, because mmio_pp is declared as 'unsigned long' when it really should be phys_addr_t. But maybe a MMIO address will always be less than 4 Gb and therefore will fit in 32 bits?
par->mmio_vp = par->dio_vp = __va(paddr);
info->fix.smem_start = paddr;
smem_start is also declared as 'unsigned long', which seems problematic with 32-bit PAE. There are a lot of drivers that cast values as 'unsigned long' before storing into smem_start
info->fix.smem_len = screen_fb_size;
info->screen_base = par->mmio_vp;
info->screen_size = screen_fb_size;
par->need_docopy = false;
goto getmem1;
} else {
pr_info("Unable to allocate enough contiguous physical memory on
Gen 1 VM. Use MMIO instead.\n");
}
- }
- /*
* Cannot use the contiguous physical memory.
* Allocate mmio space for framebuffer.
dio_fb_size = screen_width * screen_height * screen_depth / 8;*/
@@ -956,13 +1076,6 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info) pot_start = 0; pot_end = -1; } else {
pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT,
PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
if (!pdev) {
pr_err("Unable to find PCI Hyper-V video\n");
return -ENODEV;
}
- 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",
@@ -991,20 +1104,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;
32-bit PAE problem could also occur here, even though the above line isn't part of your patch.
Ideally, we would build the kernel in 32-bit mode with PAE enabled, and test in an environment where the frame buffer gets allocated above the 4 Gbyte line. There may be some similar bugs in other parts of this driver that aren't touched by the patch.
Michael