Olaf Hering 02cd93
From: Wei Hu <weh@microsoft.com>
Olaf Hering 02cd93
Date: Mon, 9 Dec 2019 15:57:49 +0800
Olaf Hering 02cd93
Patch-mainline: v5.6-rc1
Olaf Hering 02cd93
References: bsc#1175306
Olaf Hering 02cd93
Subject: video: hyperv: hyperv_fb: Use physical memory for fb on HyperV Gen 1 VMs.
Olaf Hering 02cd93
Git-commit: 3a6fb6c4255c3893ab61e2bd4e9ae01ca6bbcd94
Olaf Hering 02cd93
Olaf Hering 02cd93
On Hyper-V, Generation 1 VMs can directly use VM's physical memory for
Olaf Hering 02cd93
their framebuffers. This can improve the efficiency of framebuffer and
Olaf Hering 02cd93
overall performence for VM. The physical memory assigned to framebuffer
Olaf Hering 02cd93
must be contiguous. We use CMA allocator to get contiguouse physicial
Olaf Hering 02cd93
memory when the framebuffer size is greater than 4MB. For size under
Olaf Hering 02cd93
4MB, we use alloc_pages to achieve this.
Olaf Hering 02cd93
Olaf Hering 02cd93
To enable framebuffer memory allocation from CMA, supply a kernel
Olaf Hering 02cd93
parameter to give enough space to CMA allocator at boot time. For
Olaf Hering 02cd93
example:
Olaf Hering 02cd93
    cma=130m
Olaf Hering 02cd93
This gives 130MB memory to CAM allocator that can be allocated to
Olaf Hering 02cd93
framebuffer. If this fails, we fall back to the old way of using
Olaf Hering 02cd93
mmio for framebuffer.
Olaf Hering 02cd93
Olaf Hering 02cd93
Reported-by: kbuild test robot <lkp@intel.com>
Olaf Hering 02cd93
Signed-off-by: Wei Hu <weh@microsoft.com>
Olaf Hering 02cd93
Acked-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
Olaf Hering 02cd93
Signed-off-by: Sasha Levin <sashal@kernel.org>
Olaf Hering 02cd93
Acked-by: Olaf Hering <ohering@suse.de>
Olaf Hering 02cd93
---
Olaf Hering 02cd93
 drivers/video/fbdev/Kconfig     |   1 +
Olaf Hering 02cd93
 drivers/video/fbdev/hyperv_fb.c | 182 +++++++++++++++++++++++++++++++---------
Olaf Hering 02cd93
 2 files changed, 144 insertions(+), 39 deletions(-)
Olaf Hering 02cd93
Olaf Hering 02cd93
diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
Olaf Hering 02cd93
--- a/drivers/video/fbdev/Kconfig
Olaf Hering 02cd93
+++ b/drivers/video/fbdev/Kconfig
Olaf Hering 02cd93
@@ -2215,6 +2215,7 @@ config FB_HYPERV
Olaf Hering 02cd93
 	select FB_CFB_COPYAREA
Olaf Hering 02cd93
 	select FB_CFB_IMAGEBLIT
Olaf Hering 02cd93
 	select FB_DEFERRED_IO
Olaf Hering 02cd93
+	select DMA_CMA if HAVE_DMA_CONTIGUOUS && CMA
Olaf Hering 02cd93
 	help
Olaf Hering 02cd93
 	  This framebuffer driver supports Microsoft Hyper-V Synthetic Video.
Olaf Hering 02cd93
 
Olaf Hering 02cd93
diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c
Olaf Hering 02cd93
--- a/drivers/video/fbdev/hyperv_fb.c
Olaf Hering 02cd93
+++ b/drivers/video/fbdev/hyperv_fb.c
Olaf Hering 02cd93
@@ -31,6 +31,16 @@
Olaf Hering 02cd93
  * "set-vmvideo" command. For example
Olaf Hering 02cd93
  *     set-vmvideo -vmname name -horizontalresolution:1920 \
Olaf Hering 02cd93
  * -verticalresolution:1200 -resolutiontype single
Olaf Hering 02cd93
+ *
Olaf Hering 02cd93
+ * Gen 1 VMs also support direct using VM's physical memory for framebuffer.
Olaf Hering 02cd93
+ * It could improve the efficiency and performance for framebuffer and VM.
Olaf Hering 02cd93
+ * This requires to allocate contiguous physical memory from Linux kernel's
Olaf Hering 02cd93
+ * CMA memory allocator. To enable this, supply a kernel parameter to give
Olaf Hering 02cd93
+ * enough memory space to CMA allocator for framebuffer. For example:
Olaf Hering 02cd93
+ *    cma=130m
Olaf Hering 02cd93
+ * This gives 130MB memory to CMA allocator that can be allocated to
Olaf Hering 02cd93
+ * framebuffer. For reference, 8K resolution (7680x4320) takes about
Olaf Hering 02cd93
+ * 127MB memory.
Olaf Hering 02cd93
  */
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
Olaf Hering 02cd93
@@ -228,7 +238,6 @@ struct synthvid_msg {
Olaf Hering 02cd93
 } __packed;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 
Olaf Hering 02cd93
-
Olaf Hering 02cd93
 /* FB driver definitions and structures */
Olaf Hering 02cd93
 #define HVFB_WIDTH 1152 /* default screen width */
Olaf Hering 02cd93
 #define HVFB_HEIGHT 864 /* default screen height */
Olaf Hering 02cd93
@@ -258,12 +267,15 @@ struct hvfb_par {
Olaf Hering 02cd93
 	/* If true, the VSC notifies the VSP on every framebuffer change */
Olaf Hering 02cd93
 	bool synchronous_fb;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
+	/* If true, need to copy from deferred IO mem to framebuffer mem */
Olaf Hering 02cd93
+	bool need_docopy;
Olaf Hering 02cd93
+
Olaf Hering 02cd93
 	struct notifier_block hvfb_panic_nb;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 	/* Memory for deferred IO and frame buffer itself */
Olaf Hering 02cd93
 	unsigned char *dio_vp;
Olaf Hering 02cd93
 	unsigned char *mmio_vp;
Olaf Hering 02cd93
-	unsigned long mmio_pp;
Olaf Hering 02cd93
+	phys_addr_t mmio_pp;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 	/* Dirty rectangle, protected by delayed_refresh_lock */
Olaf Hering 02cd93
 	int x1, y1, x2, y2;
Olaf Hering 02cd93
@@ -434,7 +446,7 @@ static void synthvid_deferred_io(struct fb_info *p,
Olaf Hering 02cd93
 		maxy = max_t(int, maxy, y2);
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 		/* Copy from dio space to mmio address */
Olaf Hering 02cd93
-		if (par->fb_ready)
Olaf Hering 02cd93
+		if (par->fb_ready && par->need_docopy)
Olaf Hering 02cd93
 			hvfb_docopy(par, start, PAGE_SIZE);
Olaf Hering 02cd93
 	}
Olaf Hering 02cd93
 
Olaf Hering 02cd93
@@ -751,12 +763,12 @@ static void hvfb_update_work(struct work_struct *w)
Olaf Hering 02cd93
 		return;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 	/* Copy the dirty rectangle to frame buffer memory */
Olaf Hering 02cd93
-	for (j = y1; j < y2; j++) {
Olaf Hering 02cd93
-		hvfb_docopy(par,
Olaf Hering 02cd93
-			    j * info->fix.line_length +
Olaf Hering 02cd93
-			    (x1 * screen_depth / 8),
Olaf Hering 02cd93
-			    (x2 - x1) * screen_depth / 8);
Olaf Hering 02cd93
-	}
Olaf Hering 02cd93
+	if (par->need_docopy)
Olaf Hering 02cd93
+		for (j = y1; j < y2; j++)
Olaf Hering 02cd93
+			hvfb_docopy(par,
Olaf Hering 02cd93
+				    j * info->fix.line_length +
Olaf Hering 02cd93
+				    (x1 * screen_depth / 8),
Olaf Hering 02cd93
+				    (x2 - x1) * screen_depth / 8);
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 	/* Refresh */
Olaf Hering 02cd93
 	if (par->fb_ready && par->update)
Olaf Hering 02cd93
@@ -801,7 +813,8 @@ static int hvfb_on_panic(struct notifier_block *nb,
Olaf Hering 02cd93
 	par = container_of(nb, struct hvfb_par, hvfb_panic_nb);
Olaf Hering 02cd93
 	par->synchronous_fb = true;
Olaf Hering 02cd93
 	info = par->info;
Olaf Hering 02cd93
-	hvfb_docopy(par, 0, dio_fb_size);
Olaf Hering 02cd93
+	if (par->need_docopy)
Olaf Hering 02cd93
+		hvfb_docopy(par, 0, dio_fb_size);
Olaf Hering 02cd93
 	synthvid_update(info, 0, 0, INT_MAX, INT_MAX);
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 	return NOTIFY_DONE;
Olaf Hering 02cd93
@@ -940,6 +953,62 @@ static void hvfb_get_option(struct fb_info *info)
Olaf Hering 02cd93
 	return;
Olaf Hering 02cd93
 }
Olaf Hering 02cd93
 
Olaf Hering 02cd93
+/*
Olaf Hering 02cd93
+ * Allocate enough contiguous physical memory.
Olaf Hering 02cd93
+ * Return physical address if succeeded or -1 if failed.
Olaf Hering 02cd93
+ */
Olaf Hering 02cd93
+static phys_addr_t hvfb_get_phymem(struct hv_device *hdev,
Olaf Hering 02cd93
+				   unsigned int request_size)
Olaf Hering 02cd93
+{
Olaf Hering 02cd93
+	struct page *page = NULL;
Olaf Hering 02cd93
+	dma_addr_t dma_handle;
Olaf Hering 02cd93
+	void *vmem;
Olaf Hering 02cd93
+	phys_addr_t paddr = 0;
Olaf Hering 02cd93
+	unsigned int order = get_order(request_size);
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+	if (request_size == 0)
Olaf Hering 02cd93
+		return -1;
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+	if (order < MAX_ORDER) {
Olaf Hering 02cd93
+		/* Call alloc_pages if the size is less than 2^MAX_ORDER */
Olaf Hering 02cd93
+		page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
Olaf Hering 02cd93
+		if (!page)
Olaf Hering 02cd93
+			return -1;
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+		paddr = (page_to_pfn(page) << PAGE_SHIFT);
Olaf Hering 02cd93
+	} else {
Olaf Hering 02cd93
+		/* Allocate from CMA */
Olaf Hering 02cd93
+		hdev->device.coherent_dma_mask = DMA_BIT_MASK(64);
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+		vmem = dma_alloc_coherent(&hdev->device,
Olaf Hering 02cd93
+					  round_up(request_size, PAGE_SIZE),
Olaf Hering 02cd93
+					  &dma_handle,
Olaf Hering 02cd93
+					  GFP_KERNEL | __GFP_NOWARN);
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+		if (!vmem)
Olaf Hering 02cd93
+			return -1;
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+		paddr = virt_to_phys(vmem);
Olaf Hering 02cd93
+	}
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+	return paddr;
Olaf Hering 02cd93
+}
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+/* Release contiguous physical memory */
Olaf Hering 02cd93
+static void hvfb_release_phymem(struct hv_device *hdev,
Olaf Hering 02cd93
+				phys_addr_t paddr, unsigned int size)
Olaf Hering 02cd93
+{
Olaf Hering 02cd93
+	unsigned int order = get_order(size);
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+	if (order < MAX_ORDER)
Olaf Hering 02cd93
+		__free_pages(pfn_to_page(paddr >> PAGE_SHIFT), order);
Olaf Hering 02cd93
+	else
Olaf Hering 02cd93
+		dma_free_coherent(&hdev->device,
Olaf Hering 02cd93
+				  round_up(size, PAGE_SIZE),
Olaf Hering 02cd93
+				  phys_to_virt(paddr),
Olaf Hering 02cd93
+				  paddr);
Olaf Hering 02cd93
+}
Olaf Hering 02cd93
+
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 /* Get framebuffer memory from Hyper-V video pci space */
Olaf Hering 02cd93
 static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
Olaf Hering 02cd93
@@ -949,22 +1018,61 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
Olaf Hering 02cd93
 	void __iomem *fb_virt;
Olaf Hering 02cd93
 	int gen2vm = efi_enabled(EFI_BOOT);
Olaf Hering 02cd93
 	resource_size_t pot_start, pot_end;
Olaf Hering 02cd93
+	phys_addr_t paddr;
Olaf Hering 02cd93
 	int ret;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
-	dio_fb_size =
Olaf Hering 02cd93
-		screen_width * screen_height * screen_depth / 8;
Olaf Hering 02cd93
+	info->apertures = alloc_apertures(1);
Olaf Hering 02cd93
+	if (!info->apertures)
Olaf Hering 02cd93
+		return -ENOMEM;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
-	if (gen2vm) {
Olaf Hering 02cd93
-		pot_start = 0;
Olaf Hering 02cd93
-		pot_end = -1;
Olaf Hering 02cd93
-	} else {
Olaf Hering 02cd93
+	if (!gen2vm) {
Olaf Hering 02cd93
 		pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT,
Olaf Hering 02cd93
-			      PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
Olaf Hering 02cd93
+			PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
Olaf Hering 02cd93
 		if (!pdev) {
Olaf Hering 02cd93
 			pr_err("Unable to find PCI Hyper-V video\n");
Olaf Hering 02cd93
+			kfree(info->apertures);
Olaf Hering 02cd93
 			return -ENODEV;
Olaf Hering 02cd93
 		}
Olaf Hering 02cd93
 
Olaf Hering 02cd93
+		info->apertures->ranges[0].base = pci_resource_start(pdev, 0);
Olaf Hering 02cd93
+		info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+		/*
Olaf Hering 02cd93
+		 * For Gen 1 VM, we can directly use the contiguous memory
Olaf Hering 02cd93
+		 * from VM. If we succeed, deferred IO happens directly
Olaf Hering 02cd93
+		 * on this allocated framebuffer memory, avoiding extra
Olaf Hering 02cd93
+		 * memory copy.
Olaf Hering 02cd93
+		 */
Olaf Hering 02cd93
+		paddr = hvfb_get_phymem(hdev, screen_fb_size);
Olaf Hering 02cd93
+		if (paddr != (phys_addr_t) -1) {
Olaf Hering 02cd93
+			par->mmio_pp = paddr;
Olaf Hering 02cd93
+			par->mmio_vp = par->dio_vp = __va(paddr);
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+			info->fix.smem_start = paddr;
Olaf Hering 02cd93
+			info->fix.smem_len = screen_fb_size;
Olaf Hering 02cd93
+			info->screen_base = par->mmio_vp;
Olaf Hering 02cd93
+			info->screen_size = screen_fb_size;
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+			par->need_docopy = false;
Olaf Hering 02cd93
+			goto getmem_done;
Olaf Hering 02cd93
+		}
Olaf Hering 02cd93
+		pr_info("Unable to allocate enough contiguous physical memory on Gen 1 VM. Using MMIO instead.\n");
Olaf Hering 02cd93
+	} else {
Olaf Hering 02cd93
+		info->apertures->ranges[0].base = screen_info.lfb_base;
Olaf Hering 02cd93
+		info->apertures->ranges[0].size = screen_info.lfb_size;
Olaf Hering 02cd93
+	}
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+	/*
Olaf Hering 02cd93
+	 * Cannot use the contiguous physical memory.
Olaf Hering 02cd93
+	 * Allocate mmio space for framebuffer.
Olaf Hering 02cd93
+	 */
Olaf Hering 02cd93
+	dio_fb_size =
Olaf Hering 02cd93
+		screen_width * screen_height * screen_depth / 8;
Olaf Hering 02cd93
+
Olaf Hering 02cd93
+	if (gen2vm) {
Olaf Hering 02cd93
+		pot_start = 0;
Olaf Hering 02cd93
+		pot_end = -1;
Olaf Hering 02cd93
+	} else {
Olaf Hering 02cd93
 		if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) ||
Olaf Hering 02cd93
 		    pci_resource_len(pdev, 0) < screen_fb_size) {
Olaf Hering 02cd93
 			pr_err("Resource not available or (0x%lx < 0x%lx)\n",
Olaf Hering 02cd93
@@ -993,20 +1101,6 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
Olaf Hering 02cd93
 	if (par->dio_vp == NULL)
Olaf Hering 02cd93
 		goto err3;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
-	info->apertures = alloc_apertures(1);
Olaf Hering 02cd93
-	if (!info->apertures)
Olaf Hering 02cd93
-		goto err4;
Olaf Hering 02cd93
-
Olaf Hering 02cd93
-	if (gen2vm) {
Olaf Hering 02cd93
-		info->apertures->ranges[0].base = screen_info.lfb_base;
Olaf Hering 02cd93
-		info->apertures->ranges[0].size = screen_info.lfb_size;
Olaf Hering 02cd93
-		remove_conflicting_framebuffers(info->apertures,
Olaf Hering 02cd93
-						KBUILD_MODNAME, false);
Olaf Hering 02cd93
-	} else {
Olaf Hering 02cd93
-		info->apertures->ranges[0].base = pci_resource_start(pdev, 0);
Olaf Hering 02cd93
-		info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
Olaf Hering 02cd93
-	}
Olaf Hering 02cd93
-
Olaf Hering 02cd93
 	/* Physical address of FB device */
Olaf Hering 02cd93
 	par->mmio_pp = par->mem->start;
Olaf Hering 02cd93
 	/* Virtual address of FB device */
Olaf Hering 02cd93
@@ -1017,13 +1111,15 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
Olaf Hering 02cd93
 	info->screen_base = par->dio_vp;
Olaf Hering 02cd93
 	info->screen_size = dio_fb_size;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
+getmem_done:
Olaf Hering 02cd93
+	remove_conflicting_framebuffers(info->apertures,
Olaf Hering 02cd93
+					KBUILD_MODNAME, false);
Olaf Hering 02cd93
 	if (!gen2vm)
Olaf Hering 02cd93
 		pci_dev_put(pdev);
Olaf Hering 02cd93
+	kfree(info->apertures);
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 	return 0;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
-err4:
Olaf Hering 02cd93
-	vfree(par->dio_vp);
Olaf Hering 02cd93
 err3:
Olaf Hering 02cd93
 	iounmap(fb_virt);
Olaf Hering 02cd93
 err2:
Olaf Hering 02cd93
@@ -1032,18 +1128,25 @@ err2:
Olaf Hering 02cd93
 err1:
Olaf Hering 02cd93
 	if (!gen2vm)
Olaf Hering 02cd93
 		pci_dev_put(pdev);
Olaf Hering 02cd93
+	kfree(info->apertures);
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 	return -ENOMEM;
Olaf Hering 02cd93
 }
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 /* Release the framebuffer */
Olaf Hering 02cd93
-static void hvfb_putmem(struct fb_info *info)
Olaf Hering 02cd93
+static void hvfb_putmem(struct hv_device *hdev, struct fb_info *info)
Olaf Hering 02cd93
 {
Olaf Hering 02cd93
 	struct hvfb_par *par = info->par;
Olaf Hering 02cd93
 
Olaf Hering 02cd93
-	vfree(par->dio_vp);
Olaf Hering 02cd93
-	iounmap(info->screen_base);
Olaf Hering 02cd93
-	vmbus_free_mmio(par->mem->start, screen_fb_size);
Olaf Hering 02cd93
+	if (par->need_docopy) {
Olaf Hering 02cd93
+		vfree(par->dio_vp);
Olaf Hering 02cd93
+		iounmap(info->screen_base);
Olaf Hering 02cd93
+		vmbus_free_mmio(par->mem->start, screen_fb_size);
Olaf Hering 02cd93
+	} else {
Olaf Hering 02cd93
+		hvfb_release_phymem(hdev, info->fix.smem_start,
Olaf Hering 02cd93
+				    screen_fb_size);
Olaf Hering 02cd93
+	}
Olaf Hering 02cd93
+
Olaf Hering 02cd93
 	par->mem = NULL;
Olaf Hering 02cd93
 }
Olaf Hering 02cd93
 
Olaf Hering 02cd93
@@ -1062,6 +1165,7 @@ static int hvfb_probe(struct hv_device *hdev,
Olaf Hering 02cd93
 	par = info->par;
Olaf Hering 02cd93
 	par->info = info;
Olaf Hering 02cd93
 	par->fb_ready = false;
Olaf Hering 02cd93
+	par->need_docopy = true;
Olaf Hering 02cd93
 	init_completion(&par->wait);
Olaf Hering 02cd93
 	INIT_DELAYED_WORK(&par->dwork, hvfb_update_work);
Olaf Hering 02cd93
 
Olaf Hering 02cd93
@@ -1147,7 +1251,7 @@ static int hvfb_probe(struct hv_device *hdev,
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 error:
Olaf Hering 02cd93
 	fb_deferred_io_cleanup(info);
Olaf Hering 02cd93
-	hvfb_putmem(info);
Olaf Hering 02cd93
+	hvfb_putmem(hdev, info);
Olaf Hering 02cd93
 error2:
Olaf Hering 02cd93
 	vmbus_close(hdev->channel);
Olaf Hering 02cd93
 error1:
Olaf Hering 02cd93
@@ -1177,7 +1281,7 @@ static int hvfb_remove(struct hv_device *hdev)
Olaf Hering 02cd93
 	vmbus_close(hdev->channel);
Olaf Hering 02cd93
 	hv_set_drvdata(hdev, NULL);
Olaf Hering 02cd93
 
Olaf Hering 02cd93
-	hvfb_putmem(info);
Olaf Hering 02cd93
+	hvfb_putmem(hdev, info);
Olaf Hering 02cd93
 	framebuffer_release(info);
Olaf Hering 02cd93
 
Olaf Hering 02cd93
 	return 0;