Linux Framebuffer Layer development
 help / color / mirror / Atom feed
* [PATCH 06/11] video: sysfb: add generic firmware-fb interface
From: David Herrmann @ 2014-01-23 14:14 UTC (permalink / raw)
  To: dri-devel
  Cc: linux-fbdev, Daniel Vetter, linux-kernel, Tomi Valkeinen,
	Ingo Molnar
In-Reply-To: <1390486503-1504-1-git-send-email-dh.herrmann@gmail.com>

We supported many different firmware-fbs in linux for a long time. On x86,
we tried to unify the different types into platform-devices so their
lifetime and drivers can be more easily controlled. This patch moves the
x86-specific sysfb_*() helpers into drivers/video/sysfb.c so other
architectures can make use of it, too.

The sysfb API consists of 4 functions:
  sysfb_register()
  sysfb_register_dyn()
  sysfb_unregister()
  sysfb_claim()

The first 3 can be used by architecture setup code to register
firmware-framebuffers with the system/sysfb. Once the framebuffers are
registered, matching platform-drivers will pick it up. Via
sysfb_unregister() devices can be manually removed again, in case this is
ever needed (x86 doesn't make use of this).

Real hw-drivers like i915/radeon/nouveau can use sysfb_claim() to evict
any firmware-framebuffer from the system before accessing the hw. This
guarantees that any previous driver (like vesafb/efifb) will be unloaded
before the real hw driver takes over.

The sysfb file contains a thorough API documentation which explains the
corner-cases and how we guarantee firmware-fb drivers can no longer
interfere with real-hw drivers. Additionally, a short documentation of the
different existing firmware-fbs and handover-mechanisms is put into
Documentation/firmware-fbs.txt.

Compared to remove_conflicting_framebuffers(), the sysfb interface is
independent of FBDEV. Thus, we can use it for DRM-only handovers.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
 Documentation/firmware-fbs.txt         | 236 ++++++++++++++++++++++
 arch/x86/Kconfig                       |   1 +
 arch/x86/include/asm/sysfb.h           |   5 -
 arch/x86/kernel/sysfb.c                |  64 ------
 arch/x86/kernel/sysfb_simplefb.c       |  26 ++-
 drivers/video/Kconfig                  |   3 +
 drivers/video/Makefile                 |   1 +
 drivers/video/fbmem.c                  |  24 ++-
 drivers/video/sysfb.c                  | 348 +++++++++++++++++++++++++++++++++
 include/linux/fb.h                     |   9 +-
 include/linux/platform_data/simplefb.h |   1 +
 include/linux/sysfb.h                  |  62 ++++++
 12 files changed, 692 insertions(+), 88 deletions(-)
 create mode 100644 Documentation/firmware-fbs.txt
 create mode 100644 drivers/video/sysfb.c
 create mode 100644 include/linux/sysfb.h

diff --git a/Documentation/firmware-fbs.txt b/Documentation/firmware-fbs.txt
new file mode 100644
index 0000000..e0276ec
--- /dev/null
+++ b/Documentation/firmware-fbs.txt
@@ -0,0 +1,236 @@
+                           Firmware Framebuffers
+----------------------------------------------------------------------------
+
+1. Intro
+~~~~~~~~
+Modern firmware often initializes the graphics hardware before booting the
+kernel. A basic framebuffer is created and used for single-buffered rendering.
+Linux can detect such framebuffers and provide them to user-space. Early
+user-space can use it to draw boot-splashs, disk-encryption prompts and more.
+Once all hardware has been probed, real graphics drivers may take over.
+
+This document describes which firmware framebuffers are currently supported and
+how the handover works.
+
+2. Supported Drivers
+~~~~~~~~~~~~~~~~~~~~
+There are fbdev and DRM drivers which can make use of firmware framebuffers.
+This currently includes:
+
+  fbdev:
+    - vesafb: Uses VBE/VESA graphics mode
+    - efifb: Uses EFI UGA/GOP
+    - simplefb: Binds to custom platform-devices (eg., via DT)
+    - offb: Binds to custom platform-devices via DT
+    - vga16: Uses x86 VGA mode
+  DRM:
+    - SimpleDRM: Binds to custom platform-devices (eg., via DT)
+
+Furthermore, other miscellaneous drivers make use of firmware-framebuffers or
+their properties. Their effect is discussed at the end of this document (this
+includes vgacon and friends).
+
+3. Underlying Devices
+~~~~~~~~~~~~~~~~~~~~~
+Firmware-framebuffer drivers use different techniques to detect devices and bind
+to them. Some of these are compatible, some not. This section describes the
+different device types.
+
+3.1 simple-framebuffer
+~~~~~~~~~~~~~~~~~~~~~~
+The newest and most compatible way to represent firmware-fbs is to create a
+"simple-framebuffer" platform-device. Early boot code in arch/ should create
+such devices from device-tree data or other means of input (BIOS queries or boot
+parameters). Such devices are picked up by simplefb or SimpleDRM and provided to
+user-space as single raw framebuffer.
+
+Currently, the following ways exist to create such fbs:
+ - device-tree:
+   See: Documentation/devicetree/bindings/video/simple-framebuffer.txt
+   An example device-tree binding is:
+       framebuffer {
+           compatible = "simple-framebuffer";
+           reg = <0x1d385000 (1600 * 1200 * 2)>;
+           width = <1600>;
+           height = <1200>;
+           stride = <(1600 * 2)>;
+           format = "r5g6b5";
+       };
+ - platform-data:
+   You can create platform-devices in arch/ setup code and set the platform-data
+   to "struct simplefb_platform_data". It is defined in:
+     include/linux/platform_data/simplefb.h
+   It contains the width, height, stride and format of the framebuffer. Base
+   address and size should be passed as primary IORESOURCE_MEM resource.
+
+Supported pixel-formats are listed in:
+  include/linux/platform_data/simplefb.h
+
+All new code should use either method to advertise firmware-fbs. Other means are
+deprecated and may conflict with simple-framebuffers. If the simple-framebuffer
+method is not suitable, it should be extended to fulfil your needs. If that's
+not possible, you still should register your framebuffer with the device-model
+so it can be properly detected and driver-binding is well defined.
+See below (3.2 platform-devices) for other examples.
+
+3.2 platform-devices
+~~~~~~~~~~~~~~~~~~~~
+There are a bunch of legacy device-types that are incompatible to the
+"simple-framebuffer" platform-device or supported for backwards-compatibility.
+All these devices are represented as a "struct platform_device" similar to
+simple-framebuffers but with a different device-name.
+
+ - "vesa-framebuffer":
+   On x86, a "vesa-framebuffer" platform-device is created if a VESA framebuffer
+   is detected during boot. The platform-data contains a pointer to the
+   "struct screen_info" related to the device.
+   Currently, only the vesafb driver binds to such devices.
+ - "efi-framebuffer":
+   On EFI systems, a "efi-framebuffer" platform-device is created if an EFI
+   framebuffer is provided by the firmware. Similar to vesa-framebuffers, the
+   platform-data contains a pointer to the "struct screen_info" related to the
+   device.
+   Currently, only the efifb driver binds to such devices.
+
+3.3 open-firmware:
+~~~~~~~~~~~~~~~~~~
+There are several open-firmware based framebuffers that are supported by the
+"offb.c" driver. These are all very similar to the simple-framebuffer
+device-tree format and supported for compatibility reasons. See the offb.c
+driver source for more information on the exact format.
+Note that these are specific to the offb.c driver. They are *not* registered as
+platform-device (or any other device) and don't integrate at all with the
+device-model. Instead, only the fbdev device itself is registered as char-dev.
+The underlying un-typed firmware-framebuffer is not represented by a
+"struct device".
+
+3.4 "struct screen_info":
+~~~~~~~~~~~~~~~~~~~~~~~~~
+The legacy mode to register firmware-fbs on x86 was to initialize a global
+instance of type "struct screen_info". Drivers can access this object directly
+via its name "screen_info".
+This structure is defined in include/uapi/linux/screen_info.h and contains
+pixel-mode information, base-address and size of the framebuffer. Drivers simply
+use the information in this structure. There is no synchronization between those
+drivers and no-one prevents multiple of these to bind to the same device.
+
+The screen_info.orig_video_isVGA field defines the hw-mode during bootup. It may
+indicate a graphics or text-mode (see below 3.5 VGA for text-mode). This
+structure is *not* modified if the mode changes during runtime. Thus, such
+drivers will break if hotplugged after another driver was already loaded.
+
+vesafb and efifb have been converted to not use the global "screen_info" object
+but instead bind to platform-devices. All other drivers that use "screen_info"
+are deprecated and may not work well with hw-handover to real graphics drivers.
+Note that some platform-devices contain a "struct screen_info" as platform-data,
+which is fine! The platform-device itself provides synchronization and
+driver-binding. It's only the drivers that rely on the global "screen_info"
+object that will likely break during hw-takeover.
+
+3.5 VGA
+~~~~~~~
+On x86, drivers may use VGA I/O registers directly to test for text-mode or
+basic vga-graphics mode. These drivers usually verify that the system runs in a
+compatible VGA-mode by reading the global "screen_info" object.
+
+VGA drivers suffer from the same problem as screen_info drivers (see 3.4).
+No-one prevents multiple drivers from accessing the same device and there is no
+sane hand-over to real hw-drivers.
+
+VGA drivers should be used with care (best: not used at all!) if hw-handover is
+required. If someone cares for VGA/text-mode and hw-handover, they should add
+"vga-framebuffer" platform-devices and bind to them in the drivers (similar to
+vesa-framebuffer and efi-framebuffer devices). This would allow removing these
+devices on hw-handover and prevent further access from VGA drivers.
+
+4. Hand-over
+~~~~~~~~~~~~
+Many graphics devices support much more features than a single framebuffer.
+Therefore, linux allows real hw-drivers to take over control (eg., radeon-drm
+taking over from efifb).
+
+To support hand-over, we need to unload the previous driver before loading the
+new driver. Furthermore, we must prevent any firmware-driver from loading again
+later. Multiple hand-over helpers exist and are described below.
+
+4.1 sysfb
+~~~~~~~~~
+The sysfb-handover is the newest of all helpers and should be used by new code.
+Currently, only x86 uses it (arch/x86/kernel/sysfb.c). sysfb is quite simple and
+provides a single hand-over layer.
+
+Architecture setup must register all firmware-framebuffers via sysfb_register()
+instead of calling platform_device_register() directly. Currently sysfb supports
+only a single firmware-fb at a time, but could be extended to allow multiple fbs
+(once such systems occur in the wild).
+sysfb_register() remembers the device and calls platform_device_register().
+Generic firmware-fb drivers can now bind to the firmware devices. Once these
+devices are removed from the system, the generic firmware-fb drivers are
+automatically unbound.
+
+Any real hw video-driver that binds to a device *must* call sysfb_claim()
+before using the device. sysfb_claim() will check whether the given resource
+is used by any firmware-fb and remove any conflicting platform-device. Removing
+the platform-device will unbind the related platform-driver. Thus, the real hw
+driver can now be sure that there is no other driver accessing any firmware-fb
+based on its hardware.
+sysfb_claim() also makes sure that no following call to sysfb_register()
+will succeed, thus, preventing any new firmware-fb on the given device.
+
+Currently, remove_conflicting_framebuffers() (see below at 4.2) and DRM drivers
+call sysfb_claim() to remove conflicting firmware-fbs.
+
+See drivers/video/sysfb.c for a thorough API description of sysfb.
+
+4.2 fbdev
+~~~~~~~~~
+The fbdev layer provides a single helper called
+remove_conflicting_framebuffers(). This is implicitly called before any fbdev
+driver is registered and explicitly called by all affected DRM drivers. This
+helper is supposed to remove any existing framebuffer device that conflicts with
+the new device. Note that it does *not* prevent any new device from re-occuring.
+So loading a firmware-fb driver *after* the real hw-driver will break.
+
+Furthermore, this is limited to fbdev. If CONFIG_FB is disabled, it is not
+available.
+
+5. vgacon
+~~~~~~~~~
+The vgacon driver is similar to the vga-fbdev drivers (see above at 3.5). It
+does not register any "struct device" and thus there's no simple way to prevent
+multiple drivers from accessing vga registers simultaneously.
+
+The vgacon driver is created by early-arch-setup and marked as default VT
+console. Once the VT layer is started, it binds vgacon to all VTs. If VTs or
+VGACON are disabled, all this obviously does not apply. Same is true if the
+system is not booted in VGA/text-mode. vgacon only takes over if "screen_info"
+tells it that the system is booted in VGA/text-mode.
+
+vgacon then accesses VGA I/O registers directly to print the VT console. If a
+real-hw driver takes over, it cannot unregister vgacon directly. Instead, it
+needs to register another VT console and call do_take_over_console() (or you may
+call it with dummy_con). Obviously, vgacon must not be registered *after* the
+hw-driver is probed, otherwise, vgacon will take over unconditionally. This,
+however, seems to be no problem as vgacon is only probed by early arch-setup
+code.
+
+Currently, fbdev and DRM drivers register fbdev drivers, which are then picked
+up by fbcon which calls do_take_over_console() and removes vgacon. However, once
+the hw-driver is unloaded, fbcon is unbound, too. Therefore, vgacon will take
+over again (and fail horribly if the driver didn't restore VGA registers!).
+Running a system without fbdev but with vgacon+DRM will break, too. There's
+no-one who unloads vgacon when DRM starts up.
+
+6. Early Consoles
+~~~~~~~~~~~~~~~~~
+Architecture setup-code can register early-consoles. These may include consoles
+that access firmware-framebuffers. Such consoles are automatically removed from
+the system when the first real console-driver is loaded. However, you can
+disable this on the kernel-command line. Therefore, you're highly discouraged to
+enable early-boot consoles by default. You should only enable them for
+debugging. Especially on systems without VTs but DRM enabled, chances are high
+that no real console-driver will be available so no-one will ever remove
+early-boot consoles.
+
+----------------------------------------------------------------------------
+  Written 2013-2014 by David Herrmann <dh.herrmann@gmail.com>
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 098228e..93df439 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -2300,6 +2300,7 @@ source "drivers/rapidio/Kconfig"
 config X86_SYSFB
 	bool "Mark VGA/VBE/EFI FB as generic system framebuffer"
 	depends on FB_SIMPLE
+	select SYSFB
 	help
 	  Firmwares often provide initial graphics framebuffers so the BIOS,
 	  bootloader or kernel can show basic video-output during boot for
diff --git a/arch/x86/include/asm/sysfb.h b/arch/x86/include/asm/sysfb.h
index 4f9fda2..f777949 100644
--- a/arch/x86/include/asm/sysfb.h
+++ b/arch/x86/include/asm/sysfb.h
@@ -59,11 +59,6 @@ struct efifb_dmi_info {
 	int flags;
 };
 
-int __init sysfb_register(const char *name, int id,
-			  const struct resource *res, unsigned int res_num,
-			  const void *data, size_t data_size);
-void sysfb_unregister(const struct apertures_struct *apert, bool primary);
-
 #ifdef CONFIG_EFI
 
 extern struct efifb_dmi_info efifb_dmi_list[];
diff --git a/arch/x86/kernel/sysfb.c b/arch/x86/kernel/sysfb.c
index fd07b09..96f9289 100644
--- a/arch/x86/kernel/sysfb.c
+++ b/arch/x86/kernel/sysfb.c
@@ -39,70 +39,6 @@
 #include <linux/screen_info.h>
 #include <asm/sysfb.h>
 
-static DEFINE_MUTEX(sysfb_lock);
-static struct platform_device *sysfb_dev;
-
-int __init sysfb_register(const char *name, int id,
-			  const struct resource *res, unsigned int res_num,
-			  const void *data, size_t data_size)
-{
-	struct platform_device *pd;
-	int ret = 0;
-
-	mutex_lock(&sysfb_lock);
-	if (!sysfb_dev) {
-		pd = platform_device_register_resndata(NULL, name, id,
-						       res, res_num,
-						       data, data_size);
-		if (IS_ERR(pd))
-			ret = PTR_ERR(pd);
-		else
-			sysfb_dev = pd;
-	}
-	mutex_unlock(&sysfb_lock);
-
-	return ret;
-}
-
-static bool sysfb_match(const struct apertures_struct *apert)
-{
-	struct screen_info *si = &screen_info;
-	unsigned int i;
-	const struct aperture *a;
-
-	for (i = 0; i < apert->count; ++i) {
-		a = &apert->ranges[i];
-		if (a->base >= si->lfb_base &&
-		    a->base < si->lfb_base + ((u64)si->lfb_size << 16))
-			return true;
-		if (si->lfb_base >= a->base &&
-		    si->lfb_base < a->base + a->size)
-			return true;
-	}
-
-	return false;
-}
-
-/* Remove sysfb and disallow new sysfbs from now on. Can be called from any
- * context except recursively (see also remove_conflicting_framebuffers()). */
-void sysfb_unregister(const struct apertures_struct *apert, bool primary)
-{
-	if (!apert)
-		return;
-
-	mutex_lock(&sysfb_lock);
-	if (!IS_ERR(sysfb_dev) && sysfb_dev) {
-		if (primary || sysfb_match(apert)) {
-			platform_device_unregister(sysfb_dev);
-			sysfb_dev = ERR_PTR(-EALREADY);
-		}
-	} else {
-		/* set/overwrite error so no new sysfb is probed later */
-		sysfb_dev = ERR_PTR(-EALREADY);
-	}
-	mutex_unlock(&sysfb_lock);
-}
-
 static __init int sysfb_init(void)
 {
 	struct screen_info *si = &screen_info;
diff --git a/arch/x86/kernel/sysfb_simplefb.c b/arch/x86/kernel/sysfb_simplefb.c
index 9338427..97ed702 100644
--- a/arch/x86/kernel/sysfb_simplefb.c
+++ b/arch/x86/kernel/sysfb_simplefb.c
@@ -22,6 +22,7 @@
 #include <linux/platform_data/simplefb.h>
 #include <linux/platform_device.h>
 #include <linux/screen_info.h>
+#include <linux/sysfb.h>
 #include <asm/sysfb.h>
 
 static const char simplefb_resname[] = "BOOTFB";
@@ -86,7 +87,9 @@ __init bool parse_mode(const struct screen_info *si,
 __init int create_simplefb(const struct simplefb_platform_data *mode)
 {
 	const struct apertures_struct *apert = (void*)mode->apert_buf;
+	struct platform_device *dev;
 	struct resource res;
+	int ret;
 
 	/* setup IORESOURCE_MEM as framebuffer memory */
 	memset(&res, 0, sizeof(res));
@@ -97,6 +100,25 @@ __init int create_simplefb(const struct simplefb_platform_data *mode)
 	if (res.end <= res.start)
 		return -EINVAL;
 
-	return sysfb_register("simple-framebuffer", 0, &res, 1, mode,
-			      sizeof(*mode));
+	dev = platform_device_alloc("simple-framebuffer", 0);
+	if (!dev)
+		return -ENOMEM;
+
+	ret = platform_device_add_resources(dev, &res, 1);
+	if (ret)
+		goto err;
+
+	ret = platform_device_add_data(dev, mode, sizeof(*mode));
+	if (ret)
+		goto err;
+
+	/* platform_data is a copy of @mode, so adjust pointers */
+	mode = dev->dev.platform_data;
+	apert = (void*)mode->apert_buf;
+
+	sysfb_register(dev, apert);
+
+err:
+	platform_device_put(dev);
+	return ret;
 }
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 4f2e1b3..06bbd5f 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -39,6 +39,9 @@ config VIDEOMODE_HELPERS
 config HDMI
 	bool
 
+config SYSFB
+	bool
+
 menuconfig FB
 	tristate "Support for frame buffer devices"
 	---help---
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index e8bae8d..384926e 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -6,6 +6,7 @@
 
 obj-$(CONFIG_VGASTATE)            += vgastate.o
 obj-$(CONFIG_HDMI)                += hdmi.o
+obj-$(CONFIG_SYSFB)               += sysfb.o
 obj-y                             += fb_notify.o
 obj-$(CONFIG_FB)                  += fb.o
 fb-y                              := fbmem.o fbmon.o fbcmap.o fbsysfs.o \
diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c
index 79a47ff..e3bceaa 100644
--- a/drivers/video/fbmem.c
+++ b/drivers/video/fbmem.c
@@ -35,10 +35,6 @@
 
 #include <asm/fb.h>
 
-#ifdef CONFIG_X86_SYSFB
-#  include <asm/sysfb.h>
-#endif
-
     /*
      *  Frame buffer device initialization and setup routines
      */
@@ -1749,14 +1745,23 @@ int unlink_framebuffer(struct fb_info *fb_info)
 }
 EXPORT_SYMBOL(unlink_framebuffer);
 
+static void remove_conflicting_sysfb(struct apertures_struct *apert,
+				     bool primary)
+{
+	/* We must not call into sysfb_claim() from within ->probe() or
+	 * ->remove() of a sysfb-device, otherwise we dead-lock. Luckily, these
+	 * devices don't have any apertures set (and must never add any), so we
+	 * can just skip it then. */
+	if (apert)
+		sysfb_claim(apert, primary ? SYSFB_CLAIM_SHADOW : 0);
+}
+
 int remove_conflicting_framebuffers(struct apertures_struct *a,
 				    const char *name, bool primary)
 {
 	int ret;
 
-#ifdef CONFIG_X86_SYSFB
-	sysfb_unregister(a, primary);
-#endif
+	remove_conflicting_sysfb(a, primary);
 
 	mutex_lock(&registration_lock);
 	ret = do_remove_conflicting_framebuffers(a, name, primary);
@@ -1780,9 +1785,8 @@ register_framebuffer(struct fb_info *fb_info)
 {
 	int ret;
 
-#ifdef CONFIG_X86_SYSFB
-	sysfb_unregister(fb_info->apertures, fb_is_primary_device(fb_info));
-#endif
+	remove_conflicting_sysfb(fb_info->apertures,
+				 fb_is_primary_device(fb_info));
 
 	mutex_lock(&registration_lock);
 	ret = do_register_framebuffer(fb_info);
diff --git a/drivers/video/sysfb.c b/drivers/video/sysfb.c
new file mode 100644
index 0000000..91e0888
--- /dev/null
+++ b/drivers/video/sysfb.c
@@ -0,0 +1,348 @@
+/*
+ * Generic System Framebuffers
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/sysfb.h>
+#include <linux/types.h>
+
+/**
+ * DOC: sysfb
+ *
+ * Firmware might initialize graphics hardware before booting a kernel. Usually,
+ * it sets up a single framebuffer that we can render to (no page-flipping,
+ * double-buffering, vsync, ..). Linux can pick these up to draw early boot
+ * oops/panic screens or allow user-space to render boot-splashs.
+ * However, once all hardware has been detected, we usually want to load real
+ * graphics drivers. But before they take off, we must remove the
+ * firmware-framebuffer first. Otherwise, we will end up with resource conflicts
+ * and invalid memory accesses from a generic firmware-framebuffer driver.
+ *
+ * The sysfb infrastructure allows architecture boot-up code to register
+ * firmware-framebuffers. Real hw-drivers can use sysfb to unload firmware-fbs
+ * before loading the real driver.
+ * A firmware-framebuffer is represented by a platform_device. Other device
+ * types may be supported if required, but currently only platform_devices
+ * make sense. The name and payload of these platform-devices depend on the
+ * firmware-framebuffer type and are outside the scope of sysfb. Known types are
+ * "simple-framebuffer", "vesa-framebuffer", "efi-framebuffer" and more.
+ *
+ * Architecture setup code should allocate a platform-device, set the payload
+ * and then call sysfb_register() to register the platform-device and integrate
+ * it with sysfb. It should then drop any reference to the device and let sysfb
+ * manage it. The architecture code *may* keep a reference and unregister the
+ * firmware-fb at any time via sysfb_unregister() if and only if it has other
+ * means of notification about firmware-framebuffer destruction. Usually, this
+ * is not given.
+ *
+ * Real hw-drivers for the underlying hardware of a firmware-framebuffer must be
+ * probed on separate "struct device" objects! These devices *must* represent
+ * the real hardware instead of the firmware-framebuffer. Once a real hw-driver
+ * is probed, it shall call sysfb_claim() to claim its real resources. This will
+ * evict any conflicting firmware-framebuffers from the system and prevent any
+ * new firmware-framebuffer from being registered (it is assumed that their
+ * underlying resources are invalidated). After that, the real hw-driver can
+ * initialize the device and will have exclusive access to the resources.
+ * sysfb_claim() will unregister any conflicting firmware-framebuffers that have
+ * been registered before. It causes the ->remove() callback of the
+ * platform-devices to be called and generic framebuffers driver will get
+ * removed. It then drops its reference to the platform-devices so they get
+ * destroyed (if no-one else keeps a reference).
+ * sysfb_claim() is synchronous so it may be called in parallel by many
+ * hw-drivers and it always guarantees that it returns *after* all conflicting
+ * framebuffers have been removed.
+ *
+ * After a real hw-driver has claimed resources, sysfb automatically prevents
+ * new firmware-framebuffers from being registered. Architecture setup code
+ * should make sure that firmware-framebuffer platform-devices are registered
+ * *before* real hw-drivers are probed. This is usually implicitly given by most
+ * bus systems.
+ * However, if you unload a real hw-driver, it *may* restore the previous
+ * firmware-fb or create a new one. In that case, the driver explicitly has to
+ * create a new platform-device and register it via sysfb_register_dyn().
+ * Compared to sysfb_register() this helper also allows adding devices after a
+ * real hw-driver has been probed.
+ *
+ * A system may support multiple firmware-framebuffers. For example, firmware
+ * may set up framebuffers for all available connectors on the
+ * display-controller. However, no such system has been seen in the wild and
+ * given that sysfb is usually only used during boot, the implementation is
+ * limited to a single firmware-framebuffer. However, the API doesn't reflect
+ * that so callers *must not* assume that. On the contrary, we may, at any
+ * point, decide to support multiple framebuffers without changing the API. But
+ * as that requires keeping track of *all* previous apertures, we didn't
+ * implement this now. You're highly encouraged to write proper *real*
+ * hw-drivers if you want more sophisticated access to your graphics-hardware.
+ */
+
+static DEFINE_MUTEX(sysfb_lock);
+static const struct apertures_struct *sysfb_apert;
+static struct platform_device *sysfb_dev;
+
+static int __sysfb_register(struct platform_device *dev,
+			    const struct apertures_struct *apert)
+{
+	int ret;
+
+	if (!IS_ERR_OR_NULL(sysfb_dev)) {
+		dev_info(&dev->dev,
+			 "multiple firmware-framebuffers are not supported\n");
+		return -EALREADY;
+	}
+
+	ret = platform_device_add(dev);
+	if (ret)
+		return ret;
+
+	get_device(&dev->dev);
+	sysfb_apert = apert;
+	sysfb_dev = dev;
+	return 0;
+}
+
+/**
+ * sysfb_register - Register firmware-framebuffer
+ * @dev: Non-registered platform-device for firmware-framebuffer
+ * @apert: Aperture describing the framebuffer location/size or NULL
+ *
+ * This takes an initialized platform-device and registers it with the system
+ * via platform_device_add(). Furthermore, the device is remembered by sysfb so
+ * real-hw drivers can evict the firmware-fb later via sysfb_claim(). The
+ * aperture parameter must be constant and is *not* copied by this helper. You
+ * can usually store it in the platform_data member of the platform-device.
+ * The aperture-object describes the regions of the framebuffer data so it can
+ * be matched against real hw-drivers. Set it to NULL if any hw-driver should
+ * evict this firmware-fb.
+ *
+ * The given platform device must not have been added to the system before this
+ * call! Furthermore, on success, this call takes a reference to the
+ * platform-device so the caller can (and should) drop its own.
+ *
+ * The firmware-framebuffer is unregistered and destroyed if a real hw-driver
+ * calls sysfb_claim() and the firmware-fb matches. You can manually unregister
+ * and destroy the device via sysfb_unregister(), if required. You must keep a
+ * reference to the device then, though.
+ *
+ * If the firmware-fb couldn't be registered, this function will fail. Callers
+ * should *not* try to register the fb themselves. Instead, they must assume a
+ * real hw-driver already took over and the firmware-fb has been invalidated.
+ * Furthermore, if a real-hw driver has been probed before, this call will
+ * always fail and prevent firmware-fbs from getting registered. It is assumed
+ * that the fbs have been invalidated by the real hw. Architecture code should
+ * make sure that firmware-fbs are added *before* any real hw-drivers are
+ * probed, otherwise, the firmware-fbs might not get used.
+ * If you create the firmware-fb on-the-fly, you can use sysfb_register_dyn().
+ *
+ * Currently, we also fail if you try to register multiple firmware-fbs. No such
+ * setup has been seen, yet, and there's no real reason to support multiple
+ * firmware-fbs so we simply drop them. This is an implementation detail and may
+ * change in the future. You can safely ignore it even if you actually have
+ * multiple firmware-fbs.
+ *
+ * sysfb_register() can be called from any context *except* from inside any
+ * callbacks of the platform-device itself. So the ->probe() and ->remove()
+ * callbacks of the driver probed on @dev must not call into sysfb or it will
+ * dead-lock.
+ *
+ * RETURNS:
+ * Returns 0 on success, a negative errno on failure. Callers should usually
+ * ignore the return code and just drop their reference to the platform-device.
+ */
+int sysfb_register(struct platform_device *dev,
+		   const struct apertures_struct *apert)
+{
+	int ret = -EALREADY;
+
+	mutex_lock(&sysfb_lock);
+	if (!IS_ERR(sysfb_dev))
+		ret = __sysfb_register(dev, apert);
+	mutex_unlock(&sysfb_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(sysfb_register);
+
+/**
+ * sysfb_register_dyn - Register firmware-framebuffer dynamically
+ * @dev: Non-registered platform-device for firmware-framebuffer
+ * @apert: Aperture describing the framebuffer location/size or NULL
+ *
+ * This is the same as sysfb_register() but also works if a real hw-driver has
+ * already been loaded. This can be used by real hw-drivers on unload. If they
+ * restore a firmware-framebuffer or leave a new one behind, they can setup a
+ * new platform-device and register it. Generic drivers will then be able to
+ * pick it up again and if a conflicting real hw-driver is probed again, it will
+ * evict it.
+ *
+ * Note that this *must* be called from within a safe unload/remove callback.
+ * The real hw-driver must make sure that this returns before it releases the
+ * real hw resources. Otherwise, another real hw-driver might be probed before
+ * this call returns.
+ *
+ * Usually, this helper is only used to allow unloading, recompiling and
+ * reloading the same real hw-driver and get graphics support in between.
+ *
+ * Note that @apert is *not* copied so you should store it in the platform-data
+ * field of @dev (same as for sysfb_register()).
+ *
+ * RETURNS:
+ * Returns 0 on success, a negative errno on failure. Callers should usually
+ * ignore the return code and just drop their reference to the platform-device.
+ */
+int sysfb_register_dyn(struct platform_device *dev,
+		       const struct apertures_struct *apert)
+{
+	int ret;
+
+	mutex_lock(&sysfb_lock);
+	ret = __sysfb_register(dev, apert);
+	mutex_unlock(&sysfb_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(sysfb_register_dyn);
+
+/**
+ * sysfb_unregister - Unregister firmware-framebuffer
+ * @dev: Firmware-framebuffer to unregister
+ *
+ * This undoes sysfb_register(). Usually a caller should just drop the
+ * reference to its platform-device and never call this. However, if it has its
+ * own detection when a platform-framebuffer gets invalidated, it can keep a
+ * reference and call this once the fb is invalid.
+ *
+ * If a real hw-driver has already evicted the firmware-fb, this does nothing.
+ * If, and only if the firmware-fb hasn't been evicted, yet, another firmware-fb
+ * can be registered via sysfb_register() afterwards. It is assumed that the
+ * caller of sysfb_unregister() knows what they're doing.
+ *
+ * sysfb_unregister() can be called from any context *except* from inside any
+ * callbacks of the platform-device itself. So the ->probe() and ->remove()
+ * callbacks of the driver probed on @dev must not call into sysfb or it will
+ * dead-lock.
+ */
+void sysfb_unregister(struct platform_device *dev)
+{
+	mutex_lock(&sysfb_lock);
+	if (sysfb_dev = dev) {
+		platform_device_del(dev);
+		put_device(&dev->dev);
+
+		/* allow new firmware-fbs as it has been explicitly removed */
+		sysfb_dev = NULL;
+		sysfb_apert = NULL;
+	}
+	mutex_unlock(&sysfb_lock);
+}
+EXPORT_SYMBOL(sysfb_unregister);
+
+/**
+ * __sysfb_match - Test whether hw conflicts with firmware-fb
+ * @apert: Apertures describing the real hw or NULL
+ * @flags: Matching flags
+ *
+ * This tests whether the apertures given in @apert overlap with any registered
+ * firmware-fb. If @apert is NULL, it is ignored. As we currently support only
+ * a single firmware-fb, the firmware-fb to match against is passed implicitly.
+ *
+ * Several flags are supported:
+ *   SYSFB_CLAIM_ALL: Regardless of @apert, this always matches. Should be used
+ *     if apertures are unknown.
+ *   SYSFB_CLAIM_SHADOW: Additionally to aperture matching, this also matches
+ *     against shadow mapped firmware-framebuffers. HW-drivers should use hints
+ *     like IORESOURCE_ROM_SHADOW to set/unset this flag.
+ *     Shadow mapped firmware-fbs include PCI-BARs mapped into VGA/VESA regions
+ *     for backwards-compatibility and alike.
+ *
+ * RETURNS:
+ * Returns true if the given apertures conflict with the registered firmware-fb,
+ * false if not.
+ */
+static bool __sysfb_match(const struct apertures_struct *apert,
+			  unsigned int flags)
+{
+	bool claim_shadow = flags & SYSFB_CLAIM_SHADOW;
+	const struct aperture *a, *b;
+	size_t i, j;
+
+	if (flags & SYSFB_CLAIM_ALL || !sysfb_apert)
+		return true;
+
+	for (i = 0; i < sysfb_apert->count; ++i) {
+		a = &sysfb_apert->ranges[i];
+
+		/* VBE/VESA base address is 0xA0000 */
+		if (claim_shadow && a->base = 0xA0000)
+			return true;
+		if (!apert)
+			continue;
+
+		for (j = 0; j < apert->count; ++j) {
+			b = &apert->ranges[j];
+
+			if (a->base >= b->base &&
+			    a->base < b->base + b->size)
+				return true;
+			if (b->base >= a->base &&
+			    b->base < a->base + a->size)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * sysfb_claim - Claim hw-resources and evict conflicting firmware-fbs
+ * @apert: Apertures describing real hw-resources or NULL
+ * @flags: Matching flags
+ *
+ * This shall be called by real hw-drivers to evict all firmware-fbs that
+ * conflict with the real hardware-driver. @apert describes the apertures of
+ * the real hw and is matched against the registered firmware-framebuffers.
+ * @flags contains some additional flags to control matching behavior. See
+ * __sysfb_match() for a description of the matching behavior.
+ *
+ * Note that after this has been called *once*, no new firmware-fb will be able
+ * to get registered. So even when unloading the real-hw driver, no firmware-fb
+ * will take over again. This is to protect against hw-drivers which don't
+ * restore the firmware fb correctly. See sysfb_register_dyn() for a safe
+ * exception to this rule.
+ *
+ * This must not be called from atomic-contexts. This also does *not* protect
+ * multiple real hw-drivers from each other. Real hw-drivers should use their
+ * underlying bus (pci, usb, platform, ..) to correctly bind to real resources.
+ * The sysfb_claim() helper only evicts pseudo-devices that were registered as
+ * firmware-framebuffers.
+ *
+ * sysfb_claim() can be called from any context *except* from inside any
+ * callbacks of the platform-device itself. So the ->probe() and ->remove()
+ * callbacks of the driver probed on @dev must not call into sysfb or it will
+ * dead-lock.
+ */
+void sysfb_claim(const struct apertures_struct *apert, unsigned int flags)
+{
+	mutex_lock(&sysfb_lock);
+	if (IS_ERR_OR_NULL(sysfb_dev)) {
+		/* set err to prevent new firmware-fbs to be probed later */
+		sysfb_dev = ERR_PTR(-EALREADY);
+	} else if (__sysfb_match(apert, flags)) {
+		platform_device_unregister(sysfb_dev);
+		put_device(&sysfb_dev->dev);
+		sysfb_dev = ERR_PTR(-EALREADY);
+		sysfb_apert = NULL;
+	}
+	mutex_unlock(&sysfb_lock);
+}
+EXPORT_SYMBOL(sysfb_claim);
diff --git a/include/linux/fb.h b/include/linux/fb.h
index fe6ac95..70695fc 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -13,6 +13,7 @@
 #include <linux/list.h>
 #include <linux/backlight.h>
 #include <linux/slab.h>
+#include <linux/sysfb.h>
 #include <asm/io.h>
 
 struct vm_area_struct;
@@ -494,13 +495,7 @@ struct fb_info {
 	/* we need the PCI or similar aperture base/size not
 	   smem_start/size as smem_start may just be an object
 	   allocated inside the aperture so may not actually overlap */
-	struct apertures_struct {
-		unsigned int count;
-		struct aperture {
-			resource_size_t base;
-			resource_size_t size;
-		} ranges[0];
-	} *apertures;
+	struct apertures_struct *apertures;
 
 	bool skip_vt_switch; /* no VT switch on suspend/resume required */
 };
diff --git a/include/linux/platform_data/simplefb.h b/include/linux/platform_data/simplefb.h
index 21983cc..00e3575 100644
--- a/include/linux/platform_data/simplefb.h
+++ b/include/linux/platform_data/simplefb.h
@@ -15,6 +15,7 @@
 #include <drm/drm_fourcc.h>
 #include <linux/fb.h>
 #include <linux/kernel.h>
+#include <linux/sysfb.h>
 
 /* format array, use it to initialize a "struct simplefb_format" array */
 #define SIMPLEFB_FORMATS \
diff --git a/include/linux/sysfb.h b/include/linux/sysfb.h
new file mode 100644
index 0000000..f1638ac
--- /dev/null
+++ b/include/linux/sysfb.h
@@ -0,0 +1,62 @@
+#ifndef _LINUX_SYSFB_H
+#define _LINUX_SYSFB_H
+
+/*
+ * Generic System Framebuffers
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+struct platform_device;
+
+struct apertures_struct {
+	unsigned int count;
+	struct aperture {
+		resource_size_t base;
+		resource_size_t size;
+	} ranges[0];
+};
+
+enum sysfb_claim_flags {
+	SYSFB_CLAIM_ALL			= 0x01,
+	SYSFB_CLAIM_SHADOW		= 0x02,
+};
+
+#ifdef CONFIG_SYSFB
+
+int sysfb_register(struct platform_device *dev,
+		   const struct apertures_struct *apert);
+int sysfb_register_dyn(struct platform_device *dev,
+		       const struct apertures_struct *apert);
+void sysfb_unregister(struct platform_device *dev);
+void sysfb_claim(const struct apertures_struct *apert, unsigned int flags);
+
+#else /* CONFIG_SYSFB */
+
+static inline int sysfb_register(struct platform_device *dev,
+				 const struct apertures_struct *apert)
+{
+	return -ENOSYS;
+}
+
+static inline int sysfb_register_dyn(struct platform_device *dev,
+				     const struct apertures_struct *apert)
+{
+	return -ENOSYS;
+}
+
+static inline void sysfb_unregister(struct platform_device *dev) { }
+
+static inline void sysfb_claim(const struct apertures_struct *apert,
+			       unsigned int flags) { }
+
+#endif /* CONFIG_SYSFB */
+
+#endif /* _LINUX_SYSFB_H */
-- 
1.8.5.3


^ permalink raw reply related

* [PATCH 07/11] drm: mgag200: remove redundant fbdev removal
From: David Herrmann @ 2014-01-23 14:14 UTC (permalink / raw)
  To: dri-devel
  Cc: Ingo Molnar, linux-fbdev, Dave Airlie, Daniel Vetter,
	Tomi Valkeinen, linux-kernel, Tom Gundersen, David Herrmann
In-Reply-To: <1390486503-1504-1-git-send-email-dh.herrmann@gmail.com>

We already call remove_conflicting_framebuffers() on PCI BAR0 during
pci-probe, no need to do that again during device loading.

This avoids calling into remove_conflicting_framebuffers() from within DRM
->load() callback, which might deadlock, once we make this call remove
DRM-backed sysfb-devices, too.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
 drivers/gpu/drm/mgag200/mgag200_main.c | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/drivers/gpu/drm/mgag200/mgag200_main.c b/drivers/gpu/drm/mgag200/mgag200_main.c
index 26868e5..0ee093c 100644
--- a/drivers/gpu/drm/mgag200/mgag200_main.c
+++ b/drivers/gpu/drm/mgag200/mgag200_main.c
@@ -117,20 +117,11 @@ static int mga_probe_vram(struct mga_device *mdev, void __iomem *mem)
 static int mga_vram_init(struct mga_device *mdev)
 {
 	void __iomem *mem;
-	struct apertures_struct *aper = alloc_apertures(1);
-	if (!aper)
-		return -ENOMEM;
 
 	/* BAR 0 is VRAM */
 	mdev->mc.vram_base = pci_resource_start(mdev->dev->pdev, 0);
 	mdev->mc.vram_window = pci_resource_len(mdev->dev->pdev, 0);
 
-	aper->ranges[0].base = mdev->mc.vram_base;
-	aper->ranges[0].size = mdev->mc.vram_window;
-
-	remove_conflicting_framebuffers(aper, "mgafb", true);
-	kfree(aper);
-
 	if (!devm_request_mem_region(mdev->dev->dev, mdev->mc.vram_base, mdev->mc.vram_window,
 				"mgadrmfb_vram")) {
 		DRM_ERROR("can't reserve VRAM\n");
-- 
1.8.5.3


^ permalink raw reply related

* [PATCH 08/11] drm/i915: remove sysfbs early
From: David Herrmann @ 2014-01-23 14:15 UTC (permalink / raw)
  To: dri-devel
  Cc: Ingo Molnar, linux-fbdev, Dave Airlie, Daniel Vetter,
	Tomi Valkeinen, linux-kernel, Tom Gundersen, David Herrmann
In-Reply-To: <1390486503-1504-1-git-send-email-dh.herrmann@gmail.com>

Once we allow DRM drivers for system-framebuffers, we need to evict such
devices *before* probing the real driver. A simple call to sysfb_claim()
does this and remove_conflicting_framebuffers() implicitly calls this.
However, it causes the sysfb device to be unloaded and thus locks
drm_global_mutex. remove_conflicting_framebuffers() must be called from
outside any ->load() callback to avoid a dead-lock.

All other DRM drivers call this right before probing the pci-device, which
is fine. For i915 we need to figure out the apertures before we can evict
fw-framebuffers, though. This turns out to be not as easy as you might
think, so lets just evict all sysfbs for now before loading i915.

A proper fix would be to make DRM code allow parallel device probing.
That's not going to happen soon, so be safe and make i915 evict all
sysfbs.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
 drivers/gpu/drm/i915/i915_drv.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c
index 43245b3..ceb875a 100644
--- a/drivers/gpu/drm/i915/i915_drv.c
+++ b/drivers/gpu/drm/i915/i915_drv.c
@@ -838,6 +838,12 @@ static int i915_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 
 	driver.driver_features &= ~(DRIVER_USE_AGP);
 
+	/* We cannot call remove_conflicting_framebuffers() here as we cannot
+	 * easily figure out the apertures here. So lets just remove all
+	 * system-framebuffers early so we don't deadlock later when calling it
+	 * with drm_global_mutex held. */
+	sysfb_claim(NULL, SYSFB_CLAIM_ALL);
+
 	return drm_get_pci_dev(pdev, ent, &driver);
 }
 
-- 
1.8.5.3


^ permalink raw reply related

* [PATCH 09/11] drm: add SimpleDRM driver
From: David Herrmann @ 2014-01-23 14:15 UTC (permalink / raw)
  To: dri-devel
  Cc: Ingo Molnar, linux-fbdev, Dave Airlie, Daniel Vetter,
	Tomi Valkeinen, linux-kernel, Tom Gundersen, David Herrmann
In-Reply-To: <1390486503-1504-1-git-send-email-dh.herrmann@gmail.com>

The SimpleDRM driver binds to simple-framebuffer devices and provides a
DRM/KMS API. It provides only a single CRTC+encoder+connector combination
plus one initial mode.

Userspace can create dumb-buffers which can be blit into the real
framebuffer similar to UDL. No access to the real framebuffer is allowed
(compared to earlier version of this driver) to avoid security issues.
Furthermore, this way we can support arbitrary modes as long as we have a
convertion-helper.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
 MAINTAINERS                                  |   8 +
 drivers/gpu/drm/Kconfig                      |   2 +
 drivers/gpu/drm/Makefile                     |   1 +
 drivers/gpu/drm/simpledrm/Kconfig            |  18 ++
 drivers/gpu/drm/simpledrm/Makefile           |   3 +
 drivers/gpu/drm/simpledrm/simpledrm.c        | 252 ++++++++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm.h        | 100 ++++++++
 drivers/gpu/drm/simpledrm/simpledrm_damage.c | 306 ++++++++++++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_gem.c    | 282 +++++++++++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_kms.c    | 365 +++++++++++++++++++++++++++
 10 files changed, 1337 insertions(+)
 create mode 100644 drivers/gpu/drm/simpledrm/Kconfig
 create mode 100644 drivers/gpu/drm/simpledrm/Makefile
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm.h
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_damage.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_gem.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_kms.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 31a0462..e7adc84 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7725,6 +7725,14 @@ S:	Odd Fixes
 F:	drivers/media/platform/sh_vou.c
 F:	include/media/sh_vou.h
 
+SIMPLE DRM DRIVER
+M:	David Herrmann <dh.herrmann@gmail.com>
+L:	dri-devel@lists.freedesktop.org
+T:	git git://people.freedesktop.org/~dvdhrm/linux
+S:	Maintained
+F:	drivers/gpu/drm/simpledrm
+F:	include/linux/platform_data/simpledrm.h
+
 SIMPLE FIRMWARE INTERFACE (SFI)
 M:	Len Brown <lenb@kernel.org>
 L:	sfi-devel@simplefirmware.org
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 8e7fa4d..f795359 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -199,3 +199,5 @@ source "drivers/gpu/drm/msm/Kconfig"
 source "drivers/gpu/drm/tegra/Kconfig"
 
 source "drivers/gpu/drm/panel/Kconfig"
+
+source "drivers/gpu/drm/simpledrm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 292a79d..7b27e07 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -61,5 +61,6 @@ obj-$(CONFIG_DRM_QXL) += qxl/
 obj-$(CONFIG_DRM_BOCHS) += bochs/
 obj-$(CONFIG_DRM_MSM) += msm/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
+obj-$(CONFIG_DRM_SIMPLEDRM) += simpledrm/
 obj-y			+= i2c/
 obj-y			+= panel/
diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig
new file mode 100644
index 0000000..35bcce8
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Kconfig
@@ -0,0 +1,18 @@
+config DRM_SIMPLEDRM
+	tristate "Simple firmware framebuffer DRM driver"
+	depends on DRM && (FB_SIMPLE = n)
+	help
+	  SimpleDRM can run on all systems with pre-initialized graphics
+	  hardware. It uses a framebuffer that was initialized during
+	  firmware boot. No page-flipping, modesetting or other advanced
+	  features are available. However, other DRM drivers can be loaded
+	  later and take over from SimpleDRM if they provide real hardware
+	  support.
+
+	  SimpleDRM supports "simple-framebuffer" DeviceTree objects and
+	  compatible platform framebuffers.
+
+	  If unsure, say Y.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called simpledrm.
diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile
new file mode 100644
index 0000000..35f73ea
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Makefile
@@ -0,0 +1,3 @@
+sdrm-y := simpledrm.o simpledrm_kms.o simpledrm_gem.o simpledrm_damage.o
+
+obj-$(CONFIG_DRM_SIMPLEDRM) := sdrm.o
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.c b/drivers/gpu/drm/simpledrm/simpledrm.c
new file mode 100644
index 0000000..98273f5
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm.c
@@ -0,0 +1,252 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_data/simplefb.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include "simpledrm.h"
+
+static const struct file_operations sdrm_drm_fops = {
+	.owner = THIS_MODULE,
+	.open = drm_open,
+	.mmap = sdrm_drm_mmap,
+	.poll = drm_poll,
+	.read = drm_read,
+	.unlocked_ioctl = drm_ioctl,
+	.release = drm_release,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = drm_compat_ioctl,
+#endif
+	.llseek = noop_llseek,
+};
+
+static struct drm_driver sdrm_drm_driver = {
+	.driver_features = DRIVER_MODESET | DRIVER_GEM,
+	.load = sdrm_drm_load,
+	.unload = sdrm_drm_unload,
+	.fops = &sdrm_drm_fops,
+
+	.gem_free_object = sdrm_gem_free_object,
+	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+	.gem_prime_import = sdrm_gem_prime_import,
+
+	.dumb_create = sdrm_dumb_create,
+	.dumb_map_offset = sdrm_dumb_map_offset,
+	.dumb_destroy = sdrm_dumb_destroy,
+
+	.name = "simpledrm",
+	.desc = "Simple firmware framebuffer DRM driver",
+	.date = "20130601",
+	.major = 0,
+	.minor = 0,
+	.patchlevel = 1,
+};
+
+static int parse_dt(struct platform_device *pdev,
+		    struct simplefb_platform_data *mode)
+{
+	struct device_node *np = pdev->dev.of_node;
+	const char *format;
+	int ret;
+
+	if (!np)
+		return -ENODEV;
+
+	ret = of_property_read_u32(np, "width", &mode->width);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't parse width property\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(np, "height", &mode->height);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't parse height property\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(np, "stride", &mode->stride);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't parse stride property\n");
+		return ret;
+	}
+
+	ret = of_property_read_string(np, "format", &format);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't parse format property\n");
+		return ret;
+	}
+	mode->format = format;
+
+	return 0;
+}
+
+static struct simplefb_format simplefb_formats[] = SIMPLEFB_FORMATS;
+
+int sdrm_pdev_init(struct sdrm_device *sdrm)
+{
+	struct platform_device *pdev = sdrm->ddev->platformdev;
+	struct simplefb_platform_data *mode = pdev->dev.platform_data;
+	struct simplefb_platform_data pmode;
+	struct resource *mem;
+	unsigned int depth;
+	int ret, i, bpp;
+
+	if (!mode) {
+		mode = &pmode;
+		ret = parse_dt(pdev, mode);
+		if (ret)
+			return ret;
+	}
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(sdrm->ddev->dev, "No memory resource\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(simplefb_formats); ++i) {
+		if (strcmp(mode->format, simplefb_formats[i].name))
+			continue;
+
+		sdrm->fb_sformat = &simplefb_formats[i];
+		sdrm->fb_format = simplefb_formats[i].fourcc;
+		sdrm->fb_width = mode->width;
+		sdrm->fb_height = mode->height;
+		sdrm->fb_stride = mode->stride;
+		sdrm->fb_base = mem->start;
+		sdrm->fb_size = resource_size(mem);
+		break;
+	}
+
+	if (i >= ARRAY_SIZE(simplefb_formats)) {
+		dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
+		return -ENODEV;
+	}
+
+	switch (sdrm->fb_format) {
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_XRGB2101010:
+	case DRM_FORMAT_ARGB2101010:
+		/* You must adjust sdrm_put() whenever you add a new format
+		 * here, otherwise, blitting operations will not work.
+		 * Furthermore, include/linux/platform_data/simplefb.h needs
+		 * to be adjusted so the platform-device actually allows this
+		 * format. */
+		break;
+	default:
+		dev_err(sdrm->ddev->dev, "Unsupported format %s\n",
+			mode->format);
+		return -ENODEV;
+	}
+
+	drm_fb_get_bpp_depth(sdrm->fb_format, &depth, &bpp);
+	if (!bpp) {
+		dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
+		return -ENODEV;
+	}
+
+	if (sdrm->fb_size < sdrm->fb_stride * sdrm->fb_height) {
+		dev_err(sdrm->ddev->dev, "FB too small\n");
+		return -ENODEV;
+	} else if ((bpp + 7) / 8 * sdrm->fb_width > sdrm->fb_stride) {
+		dev_err(sdrm->ddev->dev, "Invalid stride\n");
+		return -ENODEV;
+	}
+
+	sdrm->fb_bpp = bpp;
+
+	sdrm->fb_map = ioremap_wc(sdrm->fb_base, sdrm->fb_size);
+	if (!sdrm->fb_map) {
+		dev_err(sdrm->ddev->dev, "cannot remap VMEM\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+void sdrm_pdev_destroy(struct sdrm_device *sdrm)
+{
+	if (sdrm->fb_map) {
+		iounmap(sdrm->fb_map);
+		sdrm->fb_map = NULL;
+	}
+}
+
+static int sdrm_simplefb_probe(struct platform_device *pdev)
+{
+	return drm_platform_init(&sdrm_drm_driver, pdev);
+}
+
+static int sdrm_simplefb_remove(struct platform_device *pdev)
+{
+	struct drm_device *ddev = platform_get_drvdata(pdev);
+	struct sdrm_device *sdrm = ddev->dev_private;
+
+	/* Oh, god! This is racy, but always has been. Unless we finally get
+	 * immediate device removal in DRM, we can never guarantee that no other
+	 * CPU is currently doing some GEM-bo access in parallel to us. But, eh,
+	 * what can we do.. Fixes for that is being worked on. */
+
+	drm_connector_unplug_all(ddev);
+
+	/* protect fb_map removal against sdrm_blit() */
+	drm_modeset_lock_all(ddev);
+	sdrm_pdev_destroy(sdrm);
+	drm_modeset_unlock_all(ddev);
+
+	drm_unplug_dev(ddev);
+
+	return 0;
+}
+
+static const struct of_device_id simplefb_of_match[] = {
+	{ .compatible = "simple-framebuffer", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, simplefb_of_match);
+
+static struct platform_driver sdrm_simplefb_driver = {
+	.probe = sdrm_simplefb_probe,
+	.remove = sdrm_simplefb_remove,
+	.driver = {
+		.name = "simple-framebuffer",
+		.mod_name = KBUILD_MODNAME,
+		.owner = THIS_MODULE,
+		.of_match_table = simplefb_of_match,
+	},
+};
+
+static int __init sdrm_init(void)
+{
+	return platform_driver_register(&sdrm_simplefb_driver);
+}
+
+static void __exit sdrm_exit(void)
+{
+	platform_driver_unregister(&sdrm_simplefb_driver);
+}
+
+module_init(sdrm_init);
+module_exit(sdrm_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver");
+MODULE_ALIAS("platform:simple-framebuffer");
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
new file mode 100644
index 0000000..4ece7f5
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -0,0 +1,100 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#ifndef SDRM_DRV_H
+#define SDRM_DRV_H
+
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_data/simplefb.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+
+struct sdrm_device;
+struct sdrm_gem_object;
+struct sdrm_framebuffer;
+
+/* simpledrm devices */
+
+struct sdrm_device {
+	struct drm_device *ddev;
+
+	/* framebuffer information */
+	const struct simplefb_format *fb_sformat;
+	u32 fb_format;
+	u32 fb_width;
+	u32 fb_height;
+	u32 fb_stride;
+	u32 fb_bpp;
+	unsigned long fb_base;
+	unsigned long fb_size;
+	void *fb_map;
+
+	/* mode-setting objects */
+	struct drm_crtc crtc;
+	struct drm_encoder enc;
+	struct drm_connector conn;
+	struct drm_display_mode *mode;
+};
+
+int sdrm_drm_load(struct drm_device *ddev, unsigned long flags);
+int sdrm_drm_unload(struct drm_device *ddev);
+int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma);
+int sdrm_pdev_init(struct sdrm_device *sdrm);
+void sdrm_pdev_destroy(struct sdrm_device *sdrm);
+
+int sdrm_dirty(struct drm_framebuffer *fb,
+	       struct drm_file *file,
+	       unsigned flags, unsigned color,
+	       struct drm_clip_rect *clips,
+	       unsigned num_clips);
+int sdrm_dirty_all_locked(struct sdrm_device *sdrm);
+int sdrm_dirty_all_unlocked(struct sdrm_device *sdrm);
+
+/* simpledrm gem objects */
+
+struct sdrm_gem_object {
+	struct drm_gem_object base;
+	struct sg_table *sg;
+	struct page **pages;
+	void *vmapping;
+};
+
+#define to_sdrm_bo(x) container_of(x, struct sdrm_gem_object, base)
+
+struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
+					      size_t size);
+struct drm_gem_object *sdrm_gem_prime_import(struct drm_device *ddev,
+					     struct dma_buf *dma_buf);
+void sdrm_gem_free_object(struct drm_gem_object *obj);
+int sdrm_gem_get_pages(struct sdrm_gem_object *obj);
+
+/* dumb buffers */
+
+int sdrm_dumb_create(struct drm_file *file_priv, struct drm_device *ddev,
+		     struct drm_mode_create_dumb *arg);
+int sdrm_dumb_destroy(struct drm_file *file_priv, struct drm_device *ddev,
+		      uint32_t handle);
+int sdrm_dumb_map_offset(struct drm_file *file_priv, struct drm_device *ddev,
+			 uint32_t handle, uint64_t *offset);
+
+/* simpledrm framebuffers */
+
+struct sdrm_framebuffer {
+	struct drm_framebuffer base;
+	struct sdrm_gem_object *obj;
+};
+
+#define to_sdrm_fb(x) container_of(x, struct sdrm_framebuffer, base)
+
+#endif /* SDRM_DRV_H */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_damage.c b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
new file mode 100644
index 0000000..67a2841
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
@@ -0,0 +1,306 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/dma-buf.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include "simpledrm.h"
+
+static inline void sdrm_put(u8 *dst, u32 four_cc, u16 r, u16 g, u16 b)
+{
+	switch (four_cc) {
+	case DRM_FORMAT_RGB565:
+		r &= 0x1f;
+		g &= 0x3f;
+		b &= 0x1f;
+		put_unaligned((u16)((r << 11) | (g << 5) | b), (u16*)dst);
+		break;
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+		r &= 0x1f;
+		g &= 0x1f;
+		b &= 0x1f;
+		put_unaligned((u16)((r << 10) | (g << 5) | b), (u16*)dst);
+		break;
+	case DRM_FORMAT_RGB888:
+		r &= 0xff;
+		g &= 0xff;
+		b &= 0xff;
+#ifdef __LITTLE_ENDIAN
+		dst[2] = r;
+		dst[1] = g;
+		dst[0] = b;
+#elif defined(__BIG_ENDIAN)
+		dst[0] = r;
+		dst[1] = g;
+		dst[2] = b;
+#endif
+		break;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		r &= 0xff;
+		g &= 0xff;
+		b &= 0xff;
+		put_unaligned((u32)((r << 16) | (g << 8) | b), (u32*)dst);
+		break;
+	case DRM_FORMAT_ABGR8888:
+		r &= 0xff;
+		g &= 0xff;
+		b &= 0xff;
+		put_unaligned((u32)((b << 16) | (g << 8) | r), (u32*)dst);
+		break;
+	case DRM_FORMAT_XRGB2101010:
+	case DRM_FORMAT_ARGB2101010:
+		r &= 0x3ff;
+		g &= 0x3ff;
+		b &= 0x3ff;
+		put_unaligned((u32)((r << 20) | (g << 10) | b), (u32*)dst);
+		break;
+	}
+}
+
+static void sdrm_blit_from_xrgb8888(const u8 *src, u32 src_stride, u32 src_Bpp,
+				    u8 *dst, u32 dst_stride, u32 dst_Bpp,
+				    u32 dst_four_cc, u32 width, u32 height)
+{
+	u32 val, i;
+
+	while (height--) {
+		for (i = 0; i < width; ++i) {
+			val = get_unaligned((const u32*)&src[i * src_Bpp]);
+			sdrm_put(&dst[i * dst_Bpp], dst_four_cc,
+				 (val & 0x00ff0000U) >> 16,
+				 (val & 0x0000ff00U) >> 8,
+				 (val & 0x000000ffU));
+		}
+
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit_from_rgb565(const u8 *src, u32 src_stride, u32 src_Bpp,
+				  u8 *dst, u32 dst_stride, u32 dst_Bpp,
+				  u32 dst_four_cc, u32 width, u32 height)
+{
+	u32 val, i;
+
+	while (height--) {
+		for (i = 0; i < width; ++i) {
+			val = get_unaligned((const u16*)&src[i * src_Bpp]);
+			sdrm_put(&dst[i * dst_Bpp], dst_four_cc,
+				 (val & 0xf800) >> 11,
+				 (val & 0x07e0) >> 5,
+				 (val & 0x001f));
+		}
+
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit_lines(const u8 *src, u32 src_stride,
+			    u8 *dst, u32 dst_stride,
+			    u32 Bpp, u32 width, u32 height)
+{
+	u32 len;
+
+	len = width * Bpp;
+
+	while (height--) {
+		memcpy(dst, src, len);
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit(struct sdrm_framebuffer *sfb, u32 x, u32 y,
+		      u32 width, u32 height)
+{
+	struct drm_framebuffer *fb = &sfb->base;
+	struct drm_device *ddev = fb->dev;
+	struct sdrm_device *sdrm = ddev->dev_private;
+	u32 src_Bpp, dst_Bpp, xoff, yoff, x2, y2;
+	u8 *src, *dst;
+
+	/* already unmapped; ongoing handover? */
+	if (!sdrm->fb_map)
+		return;
+
+	/* empty dirty-region, nothing to do */
+	if (!width || !height)
+		return;
+	if (x >= fb->width || y >= fb->height)
+		return;
+
+	/* sanity checks */
+	if (x + width < x)
+		width = fb->width - x;
+	if (y + height < y)
+		height = fb->height - y;
+
+	/* get scanout offsets */
+	xoff = 0;
+	yoff = 0;
+	if (sdrm->crtc.fb = fb) {
+		xoff = sdrm->crtc.x;
+		yoff = sdrm->crtc.y;
+	}
+
+	/* get intersection of dirty region and scan-out region */
+	x2 = min(x + width, xoff + sdrm->fb_width);
+	y2 = min(y + height, yoff + sdrm->fb_height);
+	x = max(x, xoff);
+	y = max(y, yoff);
+	if (x2 <= x || y2 <= y)
+		return;
+	width = x2 - x;
+	height = y2 - y;
+
+	/* get buffer offsets */
+	src = sfb->obj->vmapping;
+	dst = sdrm->fb_map;
+
+	/* bo is guaranteed to be big enough; size checks not needed */
+	src_Bpp = (fb->bits_per_pixel + 7) / 8;
+	src += fb->offsets[0] + y * fb->pitches[0] + x * src_Bpp;
+
+	dst_Bpp = (sdrm->fb_bpp + 7) / 8;
+	dst += (y - yoff) * sdrm->fb_stride + (x - xoff) * dst_Bpp;
+
+	/* if formats are identical, do a line-by-line copy.. */
+	if (fb->pixel_format = sdrm->fb_format) {
+		sdrm_blit_lines(src, fb->pitches[0],
+				dst, sdrm->fb_stride,
+				src_Bpp, width, height);
+		return;
+	}
+
+	/* ..otherwise call slow blit-function */
+	switch (fb->pixel_format) {
+	case DRM_FORMAT_ARGB8888:
+		/* fallthrough */
+	case DRM_FORMAT_XRGB8888:
+		sdrm_blit_from_xrgb8888(src, fb->pitches[0], src_Bpp,
+					dst, sdrm->fb_stride, dst_Bpp,
+					sdrm->fb_format, width, height);
+		break;
+	case DRM_FORMAT_RGB565:
+		sdrm_blit_from_rgb565(src, fb->pitches[0], src_Bpp,
+				      dst, sdrm->fb_stride, dst_Bpp,
+				      sdrm->fb_format, width, height);
+		break;
+	}
+}
+
+static int sdrm_begin_access(struct sdrm_framebuffer *sfb)
+{
+	int r;
+
+	r = sdrm_gem_get_pages(sfb->obj);
+	if (r)
+		return r;
+
+	if (!sfb->obj->base.import_attach)
+		return 0;
+
+	return dma_buf_begin_cpu_access(sfb->obj->base.import_attach->dmabuf,
+					0, sfb->obj->base.size,
+					DMA_FROM_DEVICE);
+}
+
+static void sdrm_end_access(struct sdrm_framebuffer *sfb)
+{
+	if (!sfb->obj->base.import_attach)
+		return;
+
+	dma_buf_end_cpu_access(sfb->obj->base.import_attach->dmabuf,
+			       0, sfb->obj->base.size,
+			       DMA_FROM_DEVICE);
+}
+
+int sdrm_dirty(struct drm_framebuffer *fb,
+	       struct drm_file *file,
+	       unsigned flags, unsigned color,
+	       struct drm_clip_rect *clips,
+	       unsigned num_clips)
+{
+	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+	struct drm_device *ddev = fb->dev;
+	struct sdrm_device *sdrm = ddev->dev_private;
+	unsigned int i;
+	int r;
+
+	drm_modeset_lock_all(ddev);
+
+	if (sdrm->crtc.fb != fb) {
+		r = 0;
+		goto unlock;
+	}
+
+	r = sdrm_begin_access(sfb);
+	if (r)
+		goto unlock;
+
+	for (i = 0; i < num_clips; i++) {
+		if (clips[i].x2 <= clips[i].x1 ||
+		    clips[i].y2 <= clips[i].y1)
+			continue;
+
+		sdrm_blit(sfb, clips[i].x1, clips[i].y1,
+			  clips[i].x2 - clips[i].x1,
+			  clips[i].y2 - clips[i].y1);
+	}
+
+	sdrm_end_access(sfb);
+
+unlock:
+	drm_modeset_unlock_all(ddev);
+	return 0;
+}
+
+int sdrm_dirty_all_locked(struct sdrm_device *sdrm)
+{
+	struct drm_framebuffer *fb;
+	struct sdrm_framebuffer *sfb;
+	int r;
+
+	fb = sdrm->crtc.fb;
+	if (!fb)
+		return 0;
+
+	sfb = to_sdrm_fb(fb);
+	r = sdrm_begin_access(sfb);
+	if (r)
+		return r;
+
+	sdrm_blit(sfb, 0, 0, fb->width, fb->height);
+
+	sdrm_end_access(sfb);
+
+	return 0;
+}
+
+int sdrm_dirty_all_unlocked(struct sdrm_device *sdrm)
+{
+	int r;
+
+	drm_modeset_lock_all(sdrm->ddev);
+	r = sdrm_dirty_all_locked(sdrm);
+	drm_modeset_unlock_all(sdrm->ddev);
+
+	return r;
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_gem.c b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
new file mode 100644
index 0000000..f12c56c
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
@@ -0,0 +1,282 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include "simpledrm.h"
+
+int sdrm_gem_get_pages(struct sdrm_gem_object *obj)
+{
+	size_t num, i;
+
+	if (obj->vmapping)
+		return 0;
+
+	if (obj->base.import_attach) {
+		obj->vmapping = dma_buf_vmap(obj->base.import_attach->dmabuf);
+		return !obj->vmapping ? -ENOMEM : 0;
+	}
+
+	num = obj->base.size >> PAGE_SHIFT;
+	obj->pages = drm_malloc_ab(num, sizeof(*obj->pages));
+	if (!obj->pages)
+		return -ENOMEM;
+
+	for (i = 0; i < num; ++i) {
+		obj->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
+		if (!obj->pages[i])
+			goto error;
+	}
+
+	obj->vmapping = vmap(obj->pages, num, 0, PAGE_KERNEL);
+	if (!obj->vmapping)
+		goto error;
+
+	return 0;
+
+error:
+	while (i > 0)
+		__free_pages(obj->pages[--i], 0);
+
+	drm_free_large(obj->pages);
+	obj->pages = NULL;
+	return -ENOMEM;
+}
+
+static void sdrm_gem_put_pages(struct sdrm_gem_object *obj)
+{
+	size_t num, i;
+
+	if (!obj->vmapping)
+		return;
+
+	if (obj->base.import_attach) {
+		dma_buf_vunmap(obj->base.import_attach->dmabuf, obj->vmapping);
+		obj->vmapping = NULL;
+		return;
+	}
+
+	vunmap(obj->vmapping);
+	obj->vmapping = NULL;
+
+	num = obj->base.size >> PAGE_SHIFT;
+	for (i = 0; i < num; ++i)
+		__free_pages(obj->pages[i], 0);
+
+	drm_free_large(obj->pages);
+	obj->pages = NULL;
+}
+
+struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
+					      size_t size)
+{
+	struct sdrm_gem_object *obj;
+
+	WARN_ON(!size || (size & ~PAGE_MASK) != 0);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	drm_gem_private_object_init(ddev, &obj->base, size);
+	return obj;
+}
+
+void sdrm_gem_free_object(struct drm_gem_object *gobj)
+{
+	struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
+	struct drm_device *ddev = gobj->dev;
+
+	if (obj->pages) {
+		/* kill all user-space mappings */
+		drm_vma_node_unmap(&gobj->vma_node, ddev->anon_inode->i_mapping);
+		sdrm_gem_put_pages(obj);
+	}
+
+	if (gobj->import_attach)
+		drm_prime_gem_destroy(gobj, obj->sg);
+
+	drm_gem_free_mmap_offset(gobj);
+	drm_gem_object_release(gobj);
+	kfree(obj);
+}
+
+int sdrm_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
+		     struct drm_mode_create_dumb *args)
+{
+	struct sdrm_gem_object *obj;
+	int r;
+
+	if (args->flags)
+		return -EINVAL;
+
+	/* overflow checks are done by DRM core */
+	args->pitch = (args->bpp + 7) / 8 * args->width;
+	args->size = PAGE_ALIGN(args->pitch * args->height);
+
+	obj = sdrm_gem_alloc_object(ddev, args->size);
+	if (!obj)
+		return -ENOMEM;
+
+	r = drm_gem_handle_create(dfile, &obj->base, &args->handle);
+	if (r) {
+		drm_gem_object_unreference(&obj->base);
+		return r;
+	}
+
+	/* handle owns a reference */
+	drm_gem_object_unreference(&obj->base);
+	return 0;
+}
+
+int sdrm_dumb_destroy(struct drm_file *dfile, struct drm_device *ddev,
+		      uint32_t handle)
+{
+	return drm_gem_handle_delete(dfile, handle);
+}
+
+int sdrm_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev,
+			 uint32_t handle, uint64_t *offset)
+{
+	struct drm_gem_object *gobj;
+	int r;
+
+	mutex_lock(&ddev->struct_mutex);
+
+	gobj = drm_gem_object_lookup(ddev, dfile, handle);
+	if (!gobj) {
+		r = -ENOENT;
+		goto out_unlock;
+	}
+
+	r = drm_gem_create_mmap_offset(gobj);
+	if (r)
+		goto out_unref;
+
+	*offset = drm_vma_node_offset_addr(&gobj->vma_node);
+
+out_unref:
+	drm_gem_object_unreference(gobj);
+out_unlock:
+	mutex_unlock(&ddev->struct_mutex);
+	return r;
+}
+
+int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	struct drm_file *priv = filp->private_data;
+	struct drm_device *dev = priv->minor->dev;
+	struct drm_vma_offset_node *node;
+	struct drm_gem_object *gobj;
+	struct sdrm_gem_object *obj;
+	size_t size, i, num;
+	int r;
+
+	if (drm_device_is_unplugged(dev))
+		return -ENODEV;
+
+	mutex_lock(&dev->struct_mutex);
+
+	node = drm_vma_offset_exact_lookup(dev->vma_offset_manager,
+					   vma->vm_pgoff,
+					   vma_pages(vma));
+	if (!node) {
+		mutex_unlock(&dev->struct_mutex);
+		return drm_mmap(filp, vma);
+	} else if (!drm_vma_node_is_allowed(node, filp)) {
+		r = -EACCES;
+		goto out_unlock;
+	}
+
+	gobj = container_of(node, struct drm_gem_object, vma_node);
+	obj = to_sdrm_bo(gobj);
+	size = drm_vma_node_size(node) << PAGE_SHIFT;
+	if (size < vma->vm_end - vma->vm_start) {
+		r = -EINVAL;
+		goto out_unlock;
+	}
+
+	r = sdrm_gem_get_pages(obj);
+	if (r < 0)
+		goto out_unlock;
+
+	/* prevent dmabuf-imported mmap to user-space */
+	if (!obj->pages) {
+		r = -EACCES;
+		goto out_unlock;
+	}
+
+	vma->vm_flags |= VM_DONTEXPAND;
+	vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
+
+	num = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
+	for (i = 0; i < num; ++i) {
+		r = vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE,
+				   obj->pages[i]);
+		if (r < 0) {
+			if (i > 0)
+				zap_vma_ptes(vma, vma->vm_start, i * PAGE_SIZE);
+			goto out_unlock;
+		}
+	}
+
+	r = 0;
+
+out_unlock:
+	mutex_unlock(&dev->struct_mutex);
+	return r;
+}
+
+struct drm_gem_object *sdrm_gem_prime_import(struct drm_device *ddev,
+					     struct dma_buf *dma_buf)
+{
+	struct dma_buf_attachment *attach;
+	struct sdrm_gem_object *obj;
+	struct sg_table *sg;
+	int ret;
+
+	/* need to attach */
+	attach = dma_buf_attach(dma_buf, ddev->dev);
+	if (IS_ERR(attach))
+		return ERR_CAST(attach);
+
+	get_dma_buf(dma_buf);
+
+	sg = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
+	if (IS_ERR(sg)) {
+		ret = PTR_ERR(sg);
+		goto fail_detach;
+	}
+
+	/* dma_buf_vmap() gives us a page-aligned mapping, so lets bump the
+	 * size of the dma-buf to the next page-boundary */
+	obj = sdrm_gem_alloc_object(ddev, PAGE_ALIGN(dma_buf->size));
+	if (!obj) {
+		ret = -ENOMEM;
+		goto fail_unmap;
+	}
+
+	obj->sg = sg;
+	obj->base.import_attach = attach;
+
+	return &obj->base;
+
+fail_unmap:
+	dma_buf_unmap_attachment(attach, sg, DMA_BIDIRECTIONAL);
+fail_detach:
+	dma_buf_detach(dma_buf, attach);
+	dma_buf_put(dma_buf);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
new file mode 100644
index 0000000..3e686bb
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
@@ -0,0 +1,365 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include "simpledrm.h"
+
+/* crtcs */
+
+static int sdrm_crtc_set_config(struct drm_mode_set *set)
+{
+	struct drm_device *ddev;
+	struct sdrm_device *sdrm;
+	struct sdrm_framebuffer *fb;
+
+	if (!set || !set->crtc)
+		return -EINVAL;
+
+	ddev = set->crtc->dev;
+	sdrm = ddev->dev_private;
+
+	if (set->crtc != &sdrm->crtc)
+		return -EINVAL;
+
+	if (!set->mode || !set->fb || !set->num_connectors) {
+		sdrm->conn.encoder = NULL;
+		sdrm->conn.dpms = DRM_MODE_DPMS_OFF;
+		sdrm->enc.crtc = NULL;
+		sdrm->crtc.fb = NULL;
+		sdrm->crtc.enabled = false;
+		return 0;
+	}
+
+	fb = to_sdrm_fb(set->fb);
+
+	if (set->num_connectors != 1 || set->connectors[0] != &sdrm->conn)
+		return -EINVAL;
+	if (set->mode->hdisplay != sdrm->fb_width ||
+	    set->mode->vdisplay != sdrm->fb_height)
+		return -EINVAL;
+	if (fb->base.width <= set->x ||
+	    fb->base.height <= set->y ||
+	    fb->base.width - set->x < sdrm->fb_width ||
+	    fb->base.height - set->y < sdrm->fb_height)
+		return -EINVAL;
+
+	sdrm->conn.encoder = &sdrm->enc;
+	sdrm->conn.dpms = DRM_MODE_DPMS_ON;
+	sdrm->enc.crtc = &sdrm->crtc;
+	sdrm->crtc.fb = set->fb;
+	sdrm->crtc.enabled = true;
+	sdrm->crtc.mode = *set->mode;
+	sdrm->crtc.hwmode = *set->mode;
+	sdrm->crtc.x = set->x;
+	sdrm->crtc.y = set->y;
+
+	drm_calc_timestamping_constants(&sdrm->crtc, &sdrm->crtc.hwmode);
+	sdrm_dirty_all_locked(sdrm);
+	return 0;
+}
+
+static const struct drm_crtc_funcs sdrm_crtc_ops = {
+	.set_config = sdrm_crtc_set_config,
+	.destroy = drm_crtc_cleanup,
+};
+
+/* encoders */
+
+static const struct drm_encoder_funcs sdrm_enc_ops = {
+	.destroy = drm_encoder_cleanup,
+};
+
+/* connectors */
+
+static void sdrm_conn_dpms(struct drm_connector *conn, int mode)
+{
+	conn->dpms = mode;
+}
+
+static enum drm_connector_status sdrm_conn_detect(struct drm_connector *conn,
+						  bool force)
+{
+	/* We simulate an always connected monitor. simple-fb doesn't
+	 * provide any way to detect whether the connector is active. Hence,
+	 * signal DRM core that it is always connected. */
+
+	return connector_status_connected;
+}
+
+static int sdrm_conn_fill_modes(struct drm_connector *conn, uint32_t max_x,
+				uint32_t max_y)
+{
+	struct sdrm_device *sdrm = conn->dev->dev_private;
+	struct drm_display_mode *mode;
+	int r;
+
+	if (conn->force = DRM_FORCE_ON)
+		conn->status = connector_status_connected;
+	else if (conn->force)
+		conn->status = connector_status_disconnected;
+	else
+		conn->status = connector_status_connected;
+
+	list_for_each_entry(mode, &conn->modes, head)
+		mode->status = MODE_UNVERIFIED;
+
+	mode = drm_mode_create(sdrm->ddev);
+	if (!mode) {
+		r = 0;
+	} else {
+		/* some values with 10% vsync/hsync to make user-space happy */
+		mode->hdisplay = sdrm->fb_width;
+		mode->hsync_start = mode->hdisplay;
+		mode->hsync_end = mode->hdisplay * 11 / 10;
+		mode->htotal = mode->hsync_end;
+
+		mode->vdisplay = sdrm->fb_height;
+		mode->vsync_start = mode->vdisplay;
+		mode->vsync_end = mode->vdisplay * 11 / 10;
+		mode->vtotal = mode->vsync_end;
+
+		mode->vrefresh = 60;
+		mode->clock = mode->htotal * mode->vtotal * mode->vrefresh;
+		mode->clock /= 1000; /* Hz => kHz */
+		mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+		mode->crtc_htotal = mode->htotal;
+		mode->crtc_vtotal = mode->vtotal;
+		mode->crtc_clock = mode->clock;
+
+		drm_mode_set_name(mode);
+		drm_mode_probed_add(conn, mode);
+		drm_mode_connector_list_update(conn);
+		r = 1;
+	}
+
+	if (max_x && max_y)
+		drm_mode_validate_size(conn->dev, &conn->modes,
+				       max_x, max_y, 0);
+
+	drm_mode_prune_invalid(conn->dev, &conn->modes, false);
+	if (list_empty(&conn->modes))
+		return 0;
+
+	drm_mode_sort(&conn->modes);
+
+	list_for_each_entry(mode, &conn->modes, head) {
+		mode->vrefresh = drm_mode_vrefresh(mode);
+		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+	}
+
+	return r;
+}
+
+static void sdrm_conn_destroy(struct drm_connector *conn)
+{
+	/* Remove the fake-connector from sysfs and then let the DRM core
+	 * clean up all associated resources. */
+	drm_sysfs_connector_remove(conn);
+	drm_connector_cleanup(conn);
+}
+
+static const struct drm_connector_funcs sdrm_conn_ops = {
+	.dpms = sdrm_conn_dpms,
+	.detect = sdrm_conn_detect,
+	.fill_modes = sdrm_conn_fill_modes,
+	.destroy = sdrm_conn_destroy,
+};
+
+/* framebuffers */
+
+static int sdrm_fb_create_handle(struct drm_framebuffer *fb,
+				 struct drm_file *dfile,
+				 unsigned int *handle)
+{
+	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+
+	return drm_gem_handle_create(dfile, &sfb->obj->base, handle);
+}
+
+static void sdrm_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+
+	drm_framebuffer_cleanup(fb);
+	drm_gem_object_unreference_unlocked(&sfb->obj->base);
+	kfree(sfb);
+}
+
+static const struct drm_framebuffer_funcs sdrm_fb_ops = {
+	.create_handle = sdrm_fb_create_handle,
+	.dirty = sdrm_dirty,
+	.destroy = sdrm_fb_destroy,
+};
+
+static struct drm_framebuffer *sdrm_fb_create(struct drm_device *ddev,
+					      struct drm_file *dfile,
+					      struct drm_mode_fb_cmd2 *cmd)
+{
+	struct sdrm_framebuffer *fb;
+	struct drm_gem_object *gobj;
+	u32 Bpp, size;
+	int ret;
+	void *err;
+
+	if (cmd->flags)
+		return ERR_PTR(-EINVAL);
+	if (cmd->pixel_format != DRM_FORMAT_ARGB8888 &&
+	    cmd->pixel_format != DRM_FORMAT_XRGB8888 &&
+	    cmd->pixel_format != DRM_FORMAT_RGB565)
+		return ERR_PTR(-EINVAL);
+
+	gobj = drm_gem_object_lookup(ddev, dfile, cmd->handles[0]);
+	if (!gobj)
+		return ERR_PTR(-EINVAL);
+
+	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+	if (!fb) {
+		err = ERR_PTR(-ENOMEM);
+		goto err_unref;
+	}
+	fb->obj = to_sdrm_bo(gobj);
+
+	fb->base.pitches[0] = cmd->pitches[0];
+	fb->base.offsets[0] = cmd->offsets[0];
+	fb->base.width = cmd->width;
+	fb->base.height = cmd->height;
+	fb->base.pixel_format = cmd->pixel_format;
+	drm_fb_get_bpp_depth(cmd->pixel_format, &fb->base.depth,
+			     &fb->base.bits_per_pixel);
+
+	/* width/height are already clamped into min/max_width/height range,
+	 * so overflows are not possible */
+
+	Bpp = (fb->base.bits_per_pixel + 7) / 8;
+	size = cmd->pitches[0] * cmd->height;
+	if (!Bpp ||
+	    Bpp > 4 ||
+	    cmd->pitches[0] < Bpp * fb->base.width ||
+	    cmd->pitches[0] > 0xffffU ||
+	    size + fb->base.offsets[0] < size ||
+	    size + fb->base.offsets[0] > fb->obj->base.size) {
+		err = ERR_PTR(-EINVAL);
+		goto err_free;
+	}
+
+	ret = drm_framebuffer_init(ddev, &fb->base, &sdrm_fb_ops);
+	if (ret < 0) {
+		err = ERR_PTR(ret);
+		goto err_free;
+	}
+
+	return &fb->base;
+
+err_free:
+	kfree(fb);
+err_unref:
+	drm_gem_object_unreference_unlocked(gobj);
+	return err;
+}
+
+static const struct drm_mode_config_funcs sdrm_mode_config_ops = {
+	.fb_create = sdrm_fb_create,
+};
+
+/* initialization */
+
+int sdrm_drm_load(struct drm_device *ddev, unsigned long flags)
+{
+	struct sdrm_device *sdrm;
+	int ret;
+
+	sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL);
+	if (!sdrm)
+		return -ENOMEM;
+
+	platform_set_drvdata(ddev->platformdev, ddev);
+	sdrm->ddev = ddev;
+	ddev->dev_private = sdrm;
+
+	ddev->devname = kstrdup("simpledrm", GFP_KERNEL);
+	if (!ddev->devname) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	ret = sdrm_pdev_init(sdrm);
+	if (ret)
+		goto err_name;
+
+	drm_mode_config_init(ddev);
+	ddev->mode_config.min_width = 1;
+	ddev->mode_config.min_height = 1;
+	ddev->mode_config.max_width = 8192;
+	ddev->mode_config.max_height = 8192;
+	ddev->mode_config.funcs = &sdrm_mode_config_ops;
+
+	ret = drm_crtc_init(ddev, &sdrm->crtc, &sdrm_crtc_ops);
+	if (ret)
+		goto err_cleanup;
+
+	sdrm->enc.possible_crtcs = 1;
+	sdrm->enc.possible_clones = 0;
+	ret = drm_encoder_init(ddev, &sdrm->enc, &sdrm_enc_ops,
+			       DRM_MODE_ENCODER_VIRTUAL);
+	if (ret)
+		goto err_cleanup;
+
+	sdrm->conn.display_info.width_mm = 0;
+	sdrm->conn.display_info.height_mm = 0;
+	sdrm->conn.interlace_allowed = false;
+	sdrm->conn.doublescan_allowed = false;
+	sdrm->conn.polled = 0;
+	ret = drm_connector_init(ddev, &sdrm->conn, &sdrm_conn_ops,
+				 DRM_MODE_CONNECTOR_VIRTUAL);
+	if (ret)
+		goto err_cleanup;
+
+	ret = drm_mode_connector_attach_encoder(&sdrm->conn, &sdrm->enc);
+	if (ret)
+		goto err_cleanup;
+
+	ret = drm_mode_create_dirty_info_property(ddev);
+	if (ret)
+		goto err_cleanup;
+
+	ret = drm_sysfs_connector_add(&sdrm->conn);
+	if (ret)
+		goto err_cleanup;
+
+	return 0;
+
+err_cleanup:
+	drm_mode_config_cleanup(ddev);
+	sdrm_pdev_destroy(sdrm);
+err_name:
+	kfree(ddev->devname);
+	ddev->devname = NULL;
+err_free:
+	kfree(sdrm);
+	return ret;
+}
+
+int sdrm_drm_unload(struct drm_device *ddev)
+{
+	struct sdrm_device *sdrm = ddev->dev_private;
+
+	drm_mode_config_cleanup(ddev);
+	sdrm_pdev_destroy(sdrm);
+	kfree(sdrm);
+
+	return 0;
+}
-- 
1.8.5.3


^ permalink raw reply related

* [PATCH 10/11] drm: simpledrm: add fbdev fallback support
From: David Herrmann @ 2014-01-23 14:15 UTC (permalink / raw)
  To: dri-devel
  Cc: linux-fbdev, Daniel Vetter, linux-kernel, Tomi Valkeinen,
	Ingo Molnar
In-Reply-To: <1390486503-1504-1-git-send-email-dh.herrmann@gmail.com>

Create a simple fbdev device during SimpleDRM setup so legacy user-space
and fbcon can use it.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
 drivers/gpu/drm/simpledrm/Kconfig           |  11 +++
 drivers/gpu/drm/simpledrm/Makefile          |   1 +
 drivers/gpu/drm/simpledrm/simpledrm.c       |  13 ++-
 drivers/gpu/drm/simpledrm/simpledrm.h       |  22 +++++
 drivers/gpu/drm/simpledrm/simpledrm_fbdev.c | 148 ++++++++++++++++++++++++++++
 5 files changed, 194 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_fbdev.c

diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig
index 35bcce8..eef2a36 100644
--- a/drivers/gpu/drm/simpledrm/Kconfig
+++ b/drivers/gpu/drm/simpledrm/Kconfig
@@ -12,7 +12,18 @@ config DRM_SIMPLEDRM
 	  SimpleDRM supports "simple-framebuffer" DeviceTree objects and
 	  compatible platform framebuffers.
 
+	  If fbdev support is enabled, this driver will also provide an fbdev
+	  compatibility layer.
+
 	  If unsure, say Y.
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called simpledrm.
+
+config DRM_SIMPLEDRM_FBDEV
+	bool
+	depends on DRM_SIMPLEDRM && FB
+	default y
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile
index 35f73ea..363b17d 100644
--- a/drivers/gpu/drm/simpledrm/Makefile
+++ b/drivers/gpu/drm/simpledrm/Makefile
@@ -1,3 +1,4 @@
 sdrm-y := simpledrm.o simpledrm_kms.o simpledrm_gem.o simpledrm_damage.o
+sdrm-$(CONFIG_DRM_SIMPLEDRM_FBDEV) += simpledrm_fbdev.o
 
 obj-$(CONFIG_DRM_SIMPLEDRM) := sdrm.o
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.c b/drivers/gpu/drm/simpledrm/simpledrm.c
index 98273f5..4346bcd 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm.c
@@ -192,7 +192,17 @@ void sdrm_pdev_destroy(struct sdrm_device *sdrm)
 
 static int sdrm_simplefb_probe(struct platform_device *pdev)
 {
-	return drm_platform_init(&sdrm_drm_driver, pdev);
+	struct drm_device *ddev;
+	int r;
+
+	r = drm_platform_init(&sdrm_drm_driver, pdev);
+	if (r < 0)
+		return r;
+
+	ddev = platform_get_drvdata(pdev);
+	sdrm_fbdev_init(ddev->dev_private);
+
+	return 0;
 }
 
 static int sdrm_simplefb_remove(struct platform_device *pdev)
@@ -205,6 +215,7 @@ static int sdrm_simplefb_remove(struct platform_device *pdev)
 	 * CPU is currently doing some GEM-bo access in parallel to us. But, eh,
 	 * what can we do.. Fixes for that is being worked on. */
 
+	sdrm_fbdev_cleanup(sdrm);
 	drm_connector_unplug_all(ddev);
 
 	/* protect fb_map removal against sdrm_blit() */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
index 4ece7f5..7050594 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm.h
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -45,6 +45,9 @@ struct sdrm_device {
 	struct drm_encoder enc;
 	struct drm_connector conn;
 	struct drm_display_mode *mode;
+
+	/* fbdev */
+	struct fb_info *fbdev;
 };
 
 int sdrm_drm_load(struct drm_device *ddev, unsigned long flags);
@@ -97,4 +100,23 @@ struct sdrm_framebuffer {
 
 #define to_sdrm_fb(x) container_of(x, struct sdrm_framebuffer, base)
 
+/* simpledrm fbdev helpers */
+
+#ifdef CONFIG_DRM_SIMPLEDRM_FBDEV
+
+void sdrm_fbdev_init(struct sdrm_device *sdrm);
+void sdrm_fbdev_cleanup(struct sdrm_device *sdrm);
+
+#else /* CONFIG_DRM_SIMPLEDRM_FBDEV */
+
+static inline void sdrm_fbdev_init(struct sdrm_device *sdrm)
+{
+}
+
+static inline void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
+{
+}
+
+#endif /* CONFIG_DRM_SIMPLEDRM_FBDEV */
+
 #endif /* SDRM_DRV_H */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
new file mode 100644
index 0000000..5df1482
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
@@ -0,0 +1,148 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * fbdev compatibility layer
+ * We provide a basic fbdev device for the same framebuffer that is used for
+ * the pseudo CRTC.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include "simpledrm.h"
+
+struct sdrm_fbdev {
+	u32 palette[16];
+};
+
+static int sdrm_fbdev_setcolreg(u_int regno, u_int red, u_int green,
+				u_int blue, u_int transp, struct fb_info *info)
+{
+	u32 *pal = info->pseudo_palette;
+	u32 cr = red >> (16 - info->var.red.length);
+	u32 cg = green >> (16 - info->var.green.length);
+	u32 cb = blue >> (16 - info->var.blue.length);
+	u32 value;
+
+	if (regno >= 16)
+		return -EINVAL;
+
+	value = (cr << info->var.red.offset) |
+		(cg << info->var.green.offset) |
+		(cb << info->var.blue.offset);
+
+	if (info->var.transp.length > 0) {
+		u32 mask = (1 << info->var.transp.length) - 1;
+		mask <<= info->var.transp.offset;
+		value |= mask;
+	}
+
+	pal[regno] = value;
+
+	return 0;
+}
+
+static struct fb_ops sdrm_fbdev_ops = {
+	.owner		= THIS_MODULE,
+	.fb_setcolreg	= sdrm_fbdev_setcolreg,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+};
+
+void sdrm_fbdev_init(struct sdrm_device *sdrm)
+{
+	struct sdrm_fbdev *fb;
+	struct fb_info *info;
+	int ret;
+
+	if (fb_get_options("simpledrmfb", NULL))
+		return;
+
+	info = framebuffer_alloc(sizeof(struct sdrm_fbdev), sdrm->ddev->dev);
+	if (!info)
+		goto err_out;
+
+	fb = info->par;
+	info->flags = FBINFO_DEFAULT | FBINFO_MISC_FIRMWARE;
+	info->pseudo_palette = fb->palette;
+	info->fbops = &sdrm_fbdev_ops;
+	info->screen_base = sdrm->fb_map;
+
+	strncpy(info->fix.id, "simpledrmfb", 15);
+	info->fix.type = FB_TYPE_PACKED_PIXELS;
+	info->fix.visual = FB_VISUAL_TRUECOLOR;
+	info->fix.accel = FB_ACCEL_NONE;
+	info->fix.smem_start = (unsigned long)sdrm->fb_base;
+	info->fix.smem_len = sdrm->fb_size;
+	info->fix.line_length = sdrm->fb_stride;
+
+	info->var.activate = FB_ACTIVATE_NOW;
+	info->var.vmode = FB_VMODE_NONINTERLACED;
+	info->var.bits_per_pixel = sdrm->fb_bpp;
+	info->var.height = -1;
+	info->var.width = -1;
+	info->var.xres = sdrm->fb_width;
+	info->var.yres = sdrm->fb_height;
+	info->var.xres_virtual = info->var.xres;
+	info->var.yres_virtual = info->var.yres;
+	info->var.red = sdrm->fb_sformat->red;
+	info->var.green = sdrm->fb_sformat->green;
+	info->var.blue = sdrm->fb_sformat->blue;
+	info->var.transp = sdrm->fb_sformat->transp;
+
+	/* some dummy values for timing to make fbset happy */
+	info->var.pixclock = 10000000 / info->var.xres * 1000 / info->var.yres;
+	info->var.left_margin = (info->var.xres / 8) & 0xf8;
+	info->var.right_margin = 32;
+	info->var.upper_margin = 16;
+	info->var.lower_margin = 4;
+	info->var.hsync_len = (info->var.xres / 8) & 0xf8;
+	info->var.vsync_len = 4;
+
+	sdrm->fbdev = info;
+	ret = register_framebuffer(info);
+	if (ret < 0)
+		goto err_free;
+
+	dev_info(sdrm->ddev->dev, "fbdev frontend %s as fb%d\n",
+		 info->fix.id, info->node);
+
+	return;
+
+err_free:
+	framebuffer_release(info);
+	sdrm->fbdev = NULL;
+err_out:
+	dev_warn(sdrm->ddev->dev, "cannot create fbdev frontend\n");
+}
+
+void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
+{
+	struct fb_info *info;
+
+	if (!sdrm->fbdev)
+		return;
+
+	info = sdrm->fbdev;
+	sdrm->fbdev = NULL;
+
+	dev_info(sdrm->ddev->dev, "remove fbdev frontend %s (fb%d)\n",
+		 info->fix.id, info->node);
+
+	if (unregister_framebuffer(info))
+		dev_err(sdrm->ddev->dev, "unregister_framebuffer() failed, leaking fw-fb\n");
+	else
+		framebuffer_release(info);
+}
-- 
1.8.5.3


^ permalink raw reply related

* [PATCH 11/11] x86/sysfb: allow sysfb+simpledrm combination
From: David Herrmann @ 2014-01-23 14:15 UTC (permalink / raw)
  To: dri-devel
  Cc: linux-fbdev, Daniel Vetter, linux-kernel, Tomi Valkeinen,
	Ingo Molnar
In-Reply-To: <1390486503-1504-1-git-send-email-dh.herrmann@gmail.com>

We used to protect X86_SYSFB by depending on FB_SIMPLE so users don't
accidentally end up without a kernel console. Now that DRM_SIMPLEDRM also
provides a simple-framebuffer driver, we can whitelist this driver, too.
---
 arch/x86/Kconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 93df439..f3eae6a 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -2299,7 +2299,7 @@ source "drivers/rapidio/Kconfig"
 
 config X86_SYSFB
 	bool "Mark VGA/VBE/EFI FB as generic system framebuffer"
-	depends on FB_SIMPLE
+	depends on FB_SIMPLE || DRM_SIMPLEDRM
 	select SYSFB
 	help
 	  Firmwares often provide initial graphics framebuffers so the BIOS,
-- 
1.8.5.3


^ permalink raw reply related

* Re: [PATCH 02/11] x86: sysfb: remove sysfb when probing real hw
From: Ingo Molnar @ 2014-01-23 16:51 UTC (permalink / raw)
  To: David Herrmann
  Cc: dri-devel, linux-fbdev, Dave Airlie, Daniel Vetter,
	Tomi Valkeinen, linux-kernel, Tom Gundersen
In-Reply-To: <1390486503-1504-3-git-send-email-dh.herrmann@gmail.com>


Just a couple of small nits:

* David Herrmann <dh.herrmann@gmail.com> wrote:

> --- a/arch/x86/kernel/sysfb.c
> +++ b/arch/x86/kernel/sysfb.c
> @@ -33,11 +33,76 @@
>  #include <linux/init.h>
>  #include <linux/kernel.h>
>  #include <linux/mm.h>
> +#include <linux/mutex.h>
>  #include <linux/platform_data/simplefb.h>
>  #include <linux/platform_device.h>
>  #include <linux/screen_info.h>
>  #include <asm/sysfb.h>
>  
> +static DEFINE_MUTEX(sysfb_lock);
> +static struct platform_device *sysfb_dev;
> +
> +int __init sysfb_register(const char *name, int id,
> +			  const struct resource *res, unsigned int res_num,
> +			  const void *data, size_t data_size)
> +{
> +	struct platform_device *pd;
> +	int ret = 0;
> +
> +	mutex_lock(&sysfb_lock);
> +	if (!sysfb_dev) {
> +		pd = platform_device_register_resndata(NULL, name, id,
> +						       res, res_num,
> +						       data, data_size);
> +		if (IS_ERR(pd))
> +			ret = PTR_ERR(pd);
> +		else
> +			sysfb_dev = pd;
> +	}
> +	mutex_unlock(&sysfb_lock);
> +
> +	return ret;
> +}
> +
> +static bool sysfb_match(const struct apertures_struct *apert)
> +{
> +	struct screen_info *si = &screen_info;
> +	unsigned int i;
> +	const struct aperture *a;
> +
> +	for (i = 0; i < apert->count; ++i) {
> +		a = &apert->ranges[i];
> +		if (a->base >= si->lfb_base &&
> +		    a->base < si->lfb_base + ((u64)si->lfb_size << 16))
> +			return true;
> +		if (si->lfb_base >= a->base &&
> +		    si->lfb_base < a->base + a->size)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/* Remove sysfb and disallow new sysfbs from now on. Can be called from any
> + * context except recursively (see also remove_conflicting_framebuffers()). */
> +void sysfb_unregister(const struct apertures_struct *apert, bool primary)

Please use the customary (multi-line) comment style:

  /*
   * Comment .....
   * ...... goes here.
   */

specified in Documentation/CodingStyle.

> +#ifdef CONFIG_X86_SYSFB
> +#  include <asm/sysfb.h>
> +#endif

I guess a single space is sufficient?

Better yet, I'd include sysfb.h unconditionally:

> @@ -1773,6 +1780,10 @@ register_framebuffer(struct fb_info *fb_info)
>  {
>  	int ret;
>  
> +#ifdef CONFIG_X86_SYSFB
> +	sysfb_unregister(fb_info->apertures, fb_is_primary_device(fb_info));
> +#endif

So, if a dummy sysfb_unregister() inline was defined in the 
!CONFIG_X86_SYSFB case then this ugly #ifdef could possibly be 
removed? Especially as it's used twice.

Thanks,

	Ingo

^ permalink raw reply

* Re: [PATCH 02/11] x86: sysfb: remove sysfb when probing real hw
From: David Herrmann @ 2014-01-23 17:07 UTC (permalink / raw)
  To: Ingo Molnar
  Cc: linux-fbdev@vger.kernel.org, Daniel Vetter, linux-kernel,
	dri-devel@lists.freedesktop.org, Tomi Valkeinen
In-Reply-To: <20140123165115.GB23869@gmail.com>

Hi

On Thu, Jan 23, 2014 at 5:51 PM, Ingo Molnar <mingo@kernel.org> wrote:
>
> Just a couple of small nits:
>
> * David Herrmann <dh.herrmann@gmail.com> wrote:
>
>> --- a/arch/x86/kernel/sysfb.c
>> +++ b/arch/x86/kernel/sysfb.c
>> @@ -33,11 +33,76 @@
>>  #include <linux/init.h>
>>  #include <linux/kernel.h>
>>  #include <linux/mm.h>
>> +#include <linux/mutex.h>
>>  #include <linux/platform_data/simplefb.h>
>>  #include <linux/platform_device.h>
>>  #include <linux/screen_info.h>
>>  #include <asm/sysfb.h>
>>
>> +static DEFINE_MUTEX(sysfb_lock);
>> +static struct platform_device *sysfb_dev;
>> +
>> +int __init sysfb_register(const char *name, int id,
>> +                       const struct resource *res, unsigned int res_num,
>> +                       const void *data, size_t data_size)
>> +{
>> +     struct platform_device *pd;
>> +     int ret = 0;
>> +
>> +     mutex_lock(&sysfb_lock);
>> +     if (!sysfb_dev) {
>> +             pd = platform_device_register_resndata(NULL, name, id,
>> +                                                    res, res_num,
>> +                                                    data, data_size);
>> +             if (IS_ERR(pd))
>> +                     ret = PTR_ERR(pd);
>> +             else
>> +                     sysfb_dev = pd;
>> +     }
>> +     mutex_unlock(&sysfb_lock);
>> +
>> +     return ret;
>> +}
>> +
>> +static bool sysfb_match(const struct apertures_struct *apert)
>> +{
>> +     struct screen_info *si = &screen_info;
>> +     unsigned int i;
>> +     const struct aperture *a;
>> +
>> +     for (i = 0; i < apert->count; ++i) {
>> +             a = &apert->ranges[i];
>> +             if (a->base >= si->lfb_base &&
>> +                 a->base < si->lfb_base + ((u64)si->lfb_size << 16))
>> +                     return true;
>> +             if (si->lfb_base >= a->base &&
>> +                 si->lfb_base < a->base + a->size)
>> +                     return true;
>> +     }
>> +
>> +     return false;
>> +}
>> +
>> +/* Remove sysfb and disallow new sysfbs from now on. Can be called from any
>> + * context except recursively (see also remove_conflicting_framebuffers()). */
>> +void sysfb_unregister(const struct apertures_struct *apert, bool primary)
>
> Please use the customary (multi-line) comment style:
>
>   /*
>    * Comment .....
>    * ...... goes here.
>    */
>
> specified in Documentation/CodingStyle.

Whoops, will fix it up. Still used to that from HID code.

>> +#ifdef CONFIG_X86_SYSFB
>> +#  include <asm/sysfb.h>
>> +#endif
>
> I guess a single space is sufficient?
>
> Better yet, I'd include sysfb.h unconditionally:

Unconditionally won't work as only x86 has this header. If there's a
way to place a dummy into asm-generic which is picked if
arch/xy/include/asm/ doesn't have the header, let me know. But if I
include it unconditionally without any fallback, this will fail on
non-x86.
And adding the header to all archs seems overkill.

>> @@ -1773,6 +1780,10 @@ register_framebuffer(struct fb_info *fb_info)
>>  {
>>       int ret;
>>
>> +#ifdef CONFIG_X86_SYSFB
>> +     sysfb_unregister(fb_info->apertures, fb_is_primary_device(fb_info));
>> +#endif
>
> So, if a dummy sysfb_unregister() inline was defined in the
> !CONFIG_X86_SYSFB case then this ugly #ifdef could possibly be
> removed? Especially as it's used twice.

Again, this is fine for x86, but not for other archs. I would still
need the #ifdef x86.
Note that patch #6 introduces linux/sysfb.h and removes all these ugly
#ifdefs again. They're only needed to fix the x86 code *now*. Patch #6
generalizes the x86-sysfb infrastructure and makes it
arch-independent. But Patch #6 introduces new features and thus
shouldn't go to stable or 3.14.

As Patch #1 already fixes nearly all issues with sysfb, let me know if
you want to drop this patch and just wait for the arch-independent
sysfb to get merged. This patch is only needed if people enable
X86_SYSFB *and* FB_SIMPLE *purposely* and want hw-handover. The case
were people enable it accidentally is fixed by Patch #1.
The situation is kind of screwed.. sorry for that.

Thanks
David

^ permalink raw reply

* Re: [PATCH 02/11] x86: sysfb: remove sysfb when probing real hw
From: Ingo Molnar @ 2014-01-23 17:14 UTC (permalink / raw)
  To: David Herrmann
  Cc: dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org,
	Dave Airlie, Daniel Vetter, Tomi Valkeinen, linux-kernel,
	Tom Gundersen
In-Reply-To: <CANq1E4QQE2qeEz1AaN1NO3p2TE006jHfz8uaJ_LLHptKdHecxQ@mail.gmail.com>


* David Herrmann <dh.herrmann@gmail.com> wrote:

> >> +#ifdef CONFIG_X86_SYSFB
> >> +#  include <asm/sysfb.h>
> >> +#endif
> >
> > I guess a single space is sufficient?
> >
> > Better yet, I'd include sysfb.h unconditionally:
> 
> Unconditionally won't work as only x86 has this header. [...]

Well, in non-x86 code an #ifdef x86 looks ugly as well - but I guess 
better than not building.

> [...] If there's a way to place a dummy into asm-generic which is 
> picked if arch/xy/include/asm/ doesn't have the header, let me know. 

Not that I know of.

> But if I include it unconditionally without any fallback, this will 
> fail on non-x86. And adding the header to all archs seems overkill.

So why not drop the x86-ism and rename it to CONFIG_PLATFORM_SYSFB? 
Some platforms configure it, some don't. Then the prototypes could 
move into include/linux/sysfb.h or so and would be platform agnostic.

Thanks,

	Ingo

^ permalink raw reply

* Re: [PATCH 02/11] x86: sysfb: remove sysfb when probing real hw
From: David Herrmann @ 2014-01-23 19:09 UTC (permalink / raw)
  To: Ingo Molnar
  Cc: linux-fbdev@vger.kernel.org, Daniel Vetter, linux-kernel,
	dri-devel@lists.freedesktop.org, Tomi Valkeinen
In-Reply-To: <20140123171436.GA27345@gmail.com>

Hi

On Thu, Jan 23, 2014 at 6:14 PM, Ingo Molnar <mingo@kernel.org> wrote:
>
> * David Herrmann <dh.herrmann@gmail.com> wrote:
>
>> >> +#ifdef CONFIG_X86_SYSFB
>> >> +#  include <asm/sysfb.h>
>> >> +#endif
>> >
>> > I guess a single space is sufficient?
>> >
>> > Better yet, I'd include sysfb.h unconditionally:
>>
>> Unconditionally won't work as only x86 has this header. [...]
>
> Well, in non-x86 code an #ifdef x86 looks ugly as well - but I guess
> better than not building.
>
>> [...] If there's a way to place a dummy into asm-generic which is
>> picked if arch/xy/include/asm/ doesn't have the header, let me know.
>
> Not that I know of.
>
>> But if I include it unconditionally without any fallback, this will
>> fail on non-x86. And adding the header to all archs seems overkill.
>
> So why not drop the x86-ism and rename it to CONFIG_PLATFORM_SYSFB?
> Some platforms configure it, some don't. Then the prototypes could
> move into include/linux/sysfb.h or so and would be platform agnostic.

This is almost exactly what patch #6 does. But it also adds ~400 lines
of kernel-doc and ~400 lines of Documentation/. Given your remarks, I
guess I will just split this patch into code and docs, so we can just
pick it up for stable in case patch #1 does not fix all issues.

Thanks
David

^ permalink raw reply

* Re: [PATCH 4/10] atyfb: set FBINFO_READS_FAST
From: Linus Torvalds @ 2014-01-23 19:23 UTC (permalink / raw)
  To: Mikulas Patocka, linux-fbdev@vger.kernel.org, Tomi Valkeinen
  Cc: Linux Kernel Mailing List
In-Reply-To: <alpine.LRH.2.02.1401231305390.31739@file01.intranet.prod.int.rdu2.redhat.com>

Hmm. You're doing this for Matrix and now the aty driver.

Maybe the problem is at the fbcon level? Depending on
FBINFO_READS_FAST to decide whether you should scroll or rewrite
sounds a bit silly: even if a device doesn't have fast reads, maybe it
has a fast accelerated BLIT operation and scrolls quickly. Should the
fbcon test perhaps be for FBINFO_READS_FAST _or_ the
FBINFO_HWACCEL_COPYAREA bits?

I dunno. I didn't actually check the ->bcopy implementations, maybe
they don't use copyarea. So I'm just going by a general "this feels
wrong" feeling..

                Linus

On Thu, Jan 23, 2014 at 10:18 AM, Mikulas Patocka <mpatocka@redhat.com> wrote:
> Set FBINFO_READS_FAST so that the console code uses scrolling instead of
> rewriting. This improves scrolling speed.

^ permalink raw reply

* Re: [PATCH 4/10] atyfb: set FBINFO_READS_FAST
From: Mikulas Patocka @ 2014-01-23 19:31 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: linux-fbdev@vger.kernel.org, Tomi Valkeinen,
	Linux Kernel Mailing List
In-Reply-To: <CA+55aFxPfHmpcMH-bmRcTPUQeq5=67cKVmra6+SvT26ybvQaug@mail.gmail.com>



On Thu, 23 Jan 2014, Linus Torvalds wrote:

> Hmm. You're doing this for Matrix and now the aty driver.
> 
> Maybe the problem is at the fbcon level? Depending on
> FBINFO_READS_FAST to decide whether you should scroll or rewrite
> sounds a bit silly: even if a device doesn't have fast reads, maybe it
> has a fast accelerated BLIT operation and scrolls quickly. Should the
> fbcon test perhaps be for FBINFO_READS_FAST _or_ the
> FBINFO_HWACCEL_COPYAREA bits?

Both mach64 and matrox have a hardware bitter that is faster than 
rewriting the console - that's why FBINFO_READS_FAST improves performance 
for them.

One could improve generic framebuffer core to benchmark the performance of 
scrolling using blitter and rewrite and select FBINFO_READS_FAST 
automatically...

Mikulas

> I dunno. I didn't actually check the ->bcopy implementations, maybe
> they don't use copyarea. So I'm just going by a general "this feels
> wrong" feeling..
> 
>                 Linus


> On Thu, Jan 23, 2014 at 10:18 AM, Mikulas Patocka <mpatocka@redhat.com> wrote:
> > Set FBINFO_READS_FAST so that the console code uses scrolling instead of
> > rewriting. This improves scrolling speed.
> 

^ permalink raw reply

* [PATCH 0/10] framebuffer patches
From: Mikulas Patocka @ 2014-01-23 19:37 UTC (permalink / raw)
  To: linux-fbdev

Hi

Here I'm sending some framebuffer patches for matrox, mach64, tga and a 
fix for copying on vesafb.

Mikulas

^ permalink raw reply

* [PATCH 1/10] matroxfb: set FBINFO_READS_FAST
From: Mikulas Patocka @ 2014-01-23 19:38 UTC (permalink / raw)
  To: linux-fbdev

Set FBINFO_READS_FAST so that the console code uses scrolling instead of
rewriting. This improves scrolling speed.

A time to do ls -la /usr/bin:
	original patched
32bpp	5.4	3.6
24bpp	5.1	3.0
16bpp	4.9	2.5
8bpp	4.9	2.0

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>

---
 drivers/video/matrox/matroxfb_base.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

Index: linux-3.12.1/drivers/video/matrox/matroxfb_base.c
=================================--- linux-3.12.1.orig/drivers/video/matrox/matroxfb_base.c	2013-11-18 18:14:25.000000000 +0100
+++ linux-3.12.1/drivers/video/matrox/matroxfb_base.c	2013-11-23 17:58:47.000000000 +0100
@@ -1773,7 +1773,8 @@ static int initMatrox2(struct matrox_fb_
 				      FBINFO_HWACCEL_FILLRECT |  /* And fillrect */
 				      FBINFO_HWACCEL_IMAGEBLIT | /* And imageblit */
 				      FBINFO_HWACCEL_XPAN |      /* And we support both horizontal */
-				      FBINFO_HWACCEL_YPAN;       /* And vertical panning */
+				      FBINFO_HWACCEL_YPAN |      /* And vertical panning */
+				      FBINFO_READS_FAST;
 	minfo->video.len_usable &= PAGE_MASK;
 	fb_alloc_cmap(&minfo->fbcon.cmap, 256, 1);
 


^ permalink raw reply

* [PATCH 2/10] matroxfb: restore the registers M_ACCESS and M_PITCH
From: Mikulas Patocka @ 2014-01-23 19:39 UTC (permalink / raw)
  To: linux-fbdev

When X11 is running and the user switches back to console, the card
modifies the content of registers M_MACCESS and M_PITCH in periodic
intervals.

This patch fixes it by restoring the content of these registers before
issuing any accelerator command.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org

---
 drivers/video/matrox/matroxfb_accel.c |   38 +++++++++++++++++++++++++---------
 drivers/video/matrox/matroxfb_base.h  |    2 +
 2 files changed, 30 insertions(+), 10 deletions(-)

Index: linux-3.12.5/drivers/video/matrox/matroxfb_accel.c
=================================--- linux-3.12.5.orig/drivers/video/matrox/matroxfb_accel.c	2013-12-15 16:32:04.056383894 +0100
+++ linux-3.12.5/drivers/video/matrox/matroxfb_accel.c	2013-12-15 16:33:10.271706929 +0100
@@ -192,10 +192,18 @@ void matrox_cfbX_init(struct matrox_fb_i
 	minfo->accel.m_dwg_rect = M_DWG_TRAP | M_DWG_SOLID | M_DWG_ARZERO | M_DWG_SGNZERO | M_DWG_SHIFTZERO;
 	if (isMilleniumII(minfo)) minfo->accel.m_dwg_rect |= M_DWG_TRANSC;
 	minfo->accel.m_opmode = mopmode;
+	minfo->accel.m_access = maccess;
+	minfo->accel.m_pitch = mpitch;
 }
 
 EXPORT_SYMBOL(matrox_cfbX_init);
 
+static void matrox_accel_restore_maccess(struct matrox_fb_info *minfo)
+{
+	mga_outl(M_MACCESS, minfo->accel.m_access);
+	mga_outl(M_PITCH, minfo->accel.m_pitch);
+}
+
 static void matrox_accel_bmove(struct matrox_fb_info *minfo, int vxres, int sy,
 			       int sx, int dy, int dx, int height, int width)
 {
@@ -207,7 +215,8 @@ static void matrox_accel_bmove(struct ma
 	CRITBEGIN
 
 	if ((dy < sy) || ((dy = sy) && (dx <= sx))) {
-		mga_fifo(2);
+		mga_fifo(4);
+		matrox_accel_restore_maccess(minfo);
 		mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_SGNZERO |
 			 M_DWG_BFCOL | M_DWG_REPLACE);
 		mga_outl(M_AR5, vxres);
@@ -215,7 +224,8 @@ static void matrox_accel_bmove(struct ma
 		start = sy*vxres+sx+curr_ydstorg(minfo);
 		end = start+width;
 	} else {
-		mga_fifo(3);
+		mga_fifo(5);
+		matrox_accel_restore_maccess(minfo);
 		mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_BFCOL | M_DWG_REPLACE);
 		mga_outl(M_SGN, 5);
 		mga_outl(M_AR5, -vxres);
@@ -224,7 +234,8 @@ static void matrox_accel_bmove(struct ma
 		start = end+width;
 		dy += height-1;
 	}
-	mga_fifo(4);
+	mga_fifo(6);
+	matrox_accel_restore_maccess(minfo);
 	mga_outl(M_AR0, end);
 	mga_outl(M_AR3, start);
 	mga_outl(M_FXBNDRY, ((dx+width)<<16) | dx);
@@ -246,7 +257,8 @@ static void matrox_accel_bmove_lin(struc
 	CRITBEGIN
 
 	if ((dy < sy) || ((dy = sy) && (dx <= sx))) {
-		mga_fifo(2);
+		mga_fifo(4);
+		matrox_accel_restore_maccess(minfo);
 		mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_SGNZERO |
 			M_DWG_BFCOL | M_DWG_REPLACE);
 		mga_outl(M_AR5, vxres);
@@ -254,7 +266,8 @@ static void matrox_accel_bmove_lin(struc
 		start = sy*vxres+sx+curr_ydstorg(minfo);
 		end = start+width;
 	} else {
-		mga_fifo(3);
+		mga_fifo(5);
+		matrox_accel_restore_maccess(minfo);
 		mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_BFCOL | M_DWG_REPLACE);
 		mga_outl(M_SGN, 5);
 		mga_outl(M_AR5, -vxres);
@@ -263,7 +276,8 @@ static void matrox_accel_bmove_lin(struc
 		start = end+width;
 		dy += height-1;
 	}
-	mga_fifo(5);
+	mga_fifo(7);
+	matrox_accel_restore_maccess(minfo);
 	mga_outl(M_AR0, end);
 	mga_outl(M_AR3, start);
 	mga_outl(M_FXBNDRY, ((dx+width)<<16) | dx);
@@ -298,7 +312,8 @@ static void matroxfb_accel_clear(struct
 
 	CRITBEGIN
 
-	mga_fifo(5);
+	mga_fifo(7);
+	matrox_accel_restore_maccess(minfo);
 	mga_outl(M_DWGCTL, minfo->accel.m_dwg_rect | M_DWG_REPLACE);
 	mga_outl(M_FCOL, color);
 	mga_outl(M_FXBNDRY, ((sx + width) << 16) | sx);
@@ -341,7 +356,8 @@ static void matroxfb_cfb4_clear(struct m
 	width >>= 1;
 	sx >>= 1;
 	if (width) {
-		mga_fifo(5);
+		mga_fifo(7);
+		matrox_accel_restore_maccess(minfo);
 		mga_outl(M_DWGCTL, minfo->accel.m_dwg_rect | M_DWG_REPLACE2);
 		mga_outl(M_FCOL, bgx);
 		mga_outl(M_FXBNDRY, ((sx + width) << 16) | sx);
@@ -415,7 +431,8 @@ static void matroxfb_1bpp_imageblit(stru
 
 	CRITBEGIN
 
-	mga_fifo(3);
+	mga_fifo(5);
+	matrox_accel_restore_maccess(minfo);
 	if (easy)
 		mga_outl(M_DWGCTL, M_DWG_ILOAD | M_DWG_SGNZERO | M_DWG_SHIFTZERO | M_DWG_BMONOWF | M_DWG_LINEAR | M_DWG_REPLACE);
 	else
@@ -425,7 +442,8 @@ static void matroxfb_1bpp_imageblit(stru
 	fxbndry = ((xx + width - 1) << 16) | xx;
 	mmio = minfo->mmio.vbase;
 
-	mga_fifo(6);
+	mga_fifo(8);
+	matrox_accel_restore_maccess(minfo);
 	mga_writel(mmio, M_FXBNDRY, fxbndry);
 	mga_writel(mmio, M_AR0, ar0);
 	mga_writel(mmio, M_AR3, 0);
Index: linux-3.12.5/drivers/video/matrox/matroxfb_base.h
=================================--- linux-3.12.5.orig/drivers/video/matrox/matroxfb_base.h	2013-12-15 16:31:56.823877143 +0100
+++ linux-3.12.5/drivers/video/matrox/matroxfb_base.h	2013-12-15 16:33:10.275039841 +0100
@@ -307,6 +307,8 @@ struct matrox_accel_data {
 #endif
 	u_int32_t	m_dwg_rect;
 	u_int32_t	m_opmode;
+	u_int32_t	m_access;
+	u_int32_t	m_pitch;
 };
 
 struct v4l2_queryctrl;


^ permalink raw reply

* [PATCH 3/10] framebuffer: fix cfb_copyarea
From: Mikulas Patocka @ 2014-01-23 19:39 UTC (permalink / raw)
  To: linux-fbdev

The function cfb_copyarea is buggy when the copy operation is not aligned on
long boundary (4 bytes on 32-bit machines, 8 bytes on 64-bit machines).

How to reproduce:
- use x86-64 machine
- use a framebuffer driver without acceleration (for example uvesafb)
- set the framebuffer to 8-bit depth
	(for example fbset -a 1024x768-60 -depth 8)
- load a font with character width that is not a multiple of 8 pixels
	note: the console-tools package cannot load a font that has
	width different from 8 pixels. You need to install the packages
	"kbd" and "console-terminus" and use the program "setfont" to
	set font width (for example: setfont Uni2-Terminus20x10)
- move some text left and right on the bash command line and you get a
	screen corruption

To expose more bugs, put this line to the end of uvesafb_init_info:
info->flags |= FBINFO_HWACCEL_COPYAREA | FBINFO_READS_FAST;
- Now framebuffer console will use cfb_copyarea for console scrolling.
You get a screen corruption when console is scrolled.

This patch is a rewrite of cfb_copyarea. It fixes the bugs, with this
patch, console scrolling in 8-bit depth with a font width that is not a
multiple of 8 pixels works fine.

The cfb_copyarea code was very buggy and it looks like it was written
and never tried with non-8-pixel font.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org

---
 drivers/video/cfbcopyarea.c |  153 ++++++++++++++++++++++----------------------
 1 file changed, 78 insertions(+), 75 deletions(-)

Index: linux-3.4.3-fast/drivers/video/cfbcopyarea.c
=================================--- linux-3.4.3-fast.orig/drivers/video/cfbcopyarea.c	2012-07-02 02:02:08.000000000 +0200
+++ linux-3.4.3-fast/drivers/video/cfbcopyarea.c	2012-07-02 02:03:33.000000000 +0200
@@ -43,13 +43,22 @@
      */
 
 static void
-bitcpy(struct fb_info *p, unsigned long __iomem *dst, int dst_idx,
-		const unsigned long __iomem *src, int src_idx, int bits,
+bitcpy(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx,
+		const unsigned long __iomem *src, unsigned src_idx, int bits,
 		unsigned n, u32 bswapmask)
 {
 	unsigned long first, last;
 	int const shift = dst_idx-src_idx;
-	int left, right;
+
+#if 0
+	/*
+	 * If you suspect bug in this function, compare it with this simple
+	 * memmove implementation.
+	 */
+	fb_memmove((char *)dst + ((dst_idx & (bits - 1))) / 8,
+		   (char *)src + ((src_idx & (bits - 1))) / 8, n / 8);
+	return;
+#endif
 
 	first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask);
 	last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask);
@@ -98,9 +107,8 @@ bitcpy(struct fb_info *p, unsigned long
 		unsigned long d0, d1;
 		int m;
 
-		right = shift & (bits - 1);
-		left = -shift & (bits - 1);
-		bswapmask &= shift;
+		int const left = shift & (bits - 1);
+		int const right = -shift & (bits - 1);
 
 		if (dst_idx+n <= bits) {
 			// Single destination word
@@ -110,15 +118,15 @@ bitcpy(struct fb_info *p, unsigned long
 			d0 = fb_rev_pixels_in_long(d0, bswapmask);
 			if (shift > 0) {
 				// Single source word
-				d0 >>= right;
+				d0 <<= left;
 			} else if (src_idx+n <= bits) {
 				// Single source word
-				d0 <<= left;
+				d0 >>= right;
 			} else {
 				// 2 source words
 				d1 = FB_READL(src + 1);
 				d1 = fb_rev_pixels_in_long(d1, bswapmask);
-				d0 = d0<<left | d1>>right;
+				d0 = d0 >> right | d1 << left;
 			}
 			d0 = fb_rev_pixels_in_long(d0, bswapmask);
 			FB_WRITEL(comp(d0, FB_READL(dst), first), dst);
@@ -135,60 +143,59 @@ bitcpy(struct fb_info *p, unsigned long
 			if (shift > 0) {
 				// Single source word
 				d1 = d0;
-				d0 >>= right;
-				dst++;
+				d0 <<= left;
 				n -= bits - dst_idx;
 			} else {
 				// 2 source words
 				d1 = FB_READL(src++);
 				d1 = fb_rev_pixels_in_long(d1, bswapmask);
 
-				d0 = d0<<left | d1>>right;
-				dst++;
+				d0 = d0 >> right | d1 << left;
 				n -= bits - dst_idx;
 			}
 			d0 = fb_rev_pixels_in_long(d0, bswapmask);
 			FB_WRITEL(comp(d0, FB_READL(dst), first), dst);
 			d0 = d1;
+			dst++;
 
 			// Main chunk
 			m = n % bits;
 			n /= bits;
 			while ((n >= 4) && !bswapmask) {
 				d1 = FB_READL(src++);
-				FB_WRITEL(d0 << left | d1 >> right, dst++);
+				FB_WRITEL(d0 >> right | d1 << left, dst++);
 				d0 = d1;
 				d1 = FB_READL(src++);
-				FB_WRITEL(d0 << left | d1 >> right, dst++);
+				FB_WRITEL(d0 >> right | d1 << left, dst++);
 				d0 = d1;
 				d1 = FB_READL(src++);
-				FB_WRITEL(d0 << left | d1 >> right, dst++);
+				FB_WRITEL(d0 >> right | d1 << left, dst++);
 				d0 = d1;
 				d1 = FB_READL(src++);
-				FB_WRITEL(d0 << left | d1 >> right, dst++);
+				FB_WRITEL(d0 >> right | d1 << left, dst++);
 				d0 = d1;
 				n -= 4;
 			}
 			while (n--) {
 				d1 = FB_READL(src++);
 				d1 = fb_rev_pixels_in_long(d1, bswapmask);
-				d0 = d0 << left | d1 >> right;
+				d0 = d0 >> right | d1 << left;
 				d0 = fb_rev_pixels_in_long(d0, bswapmask);
 				FB_WRITEL(d0, dst++);
 				d0 = d1;
 			}
 
 			// Trailing bits
-			if (last) {
-				if (m <= right) {
+			if (m) {
+				if (m <= bits - right) {
 					// Single source word
-					d0 <<= left;
+					d0 >>= right;
 				} else {
 					// 2 source words
 					d1 = FB_READL(src);
 					d1 = fb_rev_pixels_in_long(d1,
 								bswapmask);
-					d0 = d0<<left | d1>>right;
+					d0 = d0 >> right | d1 << left;
 				}
 				d0 = fb_rev_pixels_in_long(d0, bswapmask);
 				FB_WRITEL(comp(d0, FB_READL(dst), last), dst);
@@ -202,43 +209,46 @@ bitcpy(struct fb_info *p, unsigned long
      */
 
 static void
-bitcpy_rev(struct fb_info *p, unsigned long __iomem *dst, int dst_idx,
-		const unsigned long __iomem *src, int src_idx, int bits,
+bitcpy_rev(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx,
+		const unsigned long __iomem *src, unsigned src_idx, int bits,
 		unsigned n, u32 bswapmask)
 {
 	unsigned long first, last;
 	int shift;
 
-	dst += (n-1)/bits;
-	src += (n-1)/bits;
-	if ((n-1) % bits) {
-		dst_idx += (n-1) % bits;
-		dst += dst_idx >> (ffs(bits) - 1);
-		dst_idx &= bits - 1;
-		src_idx += (n-1) % bits;
-		src += src_idx >> (ffs(bits) - 1);
-		src_idx &= bits - 1;
-	}
+#if 0
+	/*
+	 * If you suspect bug in this function, compare it with this simple
+	 * memmove implementation.
+	 */
+	fb_memmove((char *)dst + ((dst_idx & (bits - 1))) / 8,
+		   (char *)src + ((src_idx & (bits - 1))) / 8, n / 8);
+	return;
+#endif
+
+	dst += (dst_idx + n - 1) / bits;
+	src += (src_idx + n - 1) / bits;
+	dst_idx = (dst_idx + n - 1) % bits;
+	src_idx = (src_idx + n - 1) % bits;
 
 	shift = dst_idx-src_idx;
 
-	first = fb_shifted_pixels_mask_long(p, bits - 1 - dst_idx, bswapmask);
-	last = ~fb_shifted_pixels_mask_long(p, bits - 1 - ((dst_idx-n) % bits),
-					    bswapmask);
+	first = ~fb_shifted_pixels_mask_long(p, (dst_idx + 1) % bits, bswapmask);
+	last = fb_shifted_pixels_mask_long(p, (bits + dst_idx + 1 - n) % bits, bswapmask);
 
 	if (!shift) {
 		// Same alignment for source and dest
 
 		if ((unsigned long)dst_idx+1 >= n) {
 			// Single word
-			if (last)
-				first &= last;
-			FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst);
+			if (first)
+				last &= first;
+			FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst);
 		} else {
 			// Multiple destination words
 
 			// Leading bits
-			if (first != ~0UL) {
+			if (first) {
 				FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst);
 				dst--;
 				src--;
@@ -262,7 +272,7 @@ bitcpy_rev(struct fb_info *p, unsigned l
 				FB_WRITEL(FB_READL(src--), dst--);
 
 			// Trailing bits
-			if (last)
+			if (last != -1UL)
 				FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst);
 		}
 	} else {
@@ -270,29 +280,28 @@ bitcpy_rev(struct fb_info *p, unsigned l
 		unsigned long d0, d1;
 		int m;
 
-		int const left = -shift & (bits-1);
-		int const right = shift & (bits-1);
-		bswapmask &= shift;
+		int const left = shift & (bits-1);
+		int const right = -shift & (bits-1);
 
 		if ((unsigned long)dst_idx+1 >= n) {
 			// Single destination word
-			if (last)
-				first &= last;
+			if (first)
+				last &= first;
 			d0 = FB_READL(src);
 			if (shift < 0) {
 				// Single source word
-				d0 <<= left;
+				d0 >>= right;
 			} else if (1+(unsigned long)src_idx >= n) {
 				// Single source word
-				d0 >>= right;
+				d0 <<= left;
 			} else {
 				// 2 source words
 				d1 = FB_READL(src - 1);
 				d1 = fb_rev_pixels_in_long(d1, bswapmask);
-				d0 = d0>>right | d1<<left;
+				d0 = d0 << left | d1 >> right;
 			}
 			d0 = fb_rev_pixels_in_long(d0, bswapmask);
-			FB_WRITEL(comp(d0, FB_READL(dst), first), dst);
+			FB_WRITEL(comp(d0, FB_READL(dst), last), dst);
 		} else {
 			// Multiple destination words
 			/** We must always remember the last value read, because in case
@@ -307,12 +316,12 @@ bitcpy_rev(struct fb_info *p, unsigned l
 			if (shift < 0) {
 				// Single source word
 				d1 = d0;
-				d0 <<= left;
+				d0 >>= right;
 			} else {
 				// 2 source words
 				d1 = FB_READL(src--);
 				d1 = fb_rev_pixels_in_long(d1, bswapmask);
-				d0 = d0>>right | d1<<left;
+				d0 = d0 << left | d1 >> right;
 			}
 			d0 = fb_rev_pixels_in_long(d0, bswapmask);
 			FB_WRITEL(comp(d0, FB_READL(dst), first), dst);
@@ -325,39 +334,39 @@ bitcpy_rev(struct fb_info *p, unsigned l
 			n /= bits;
 			while ((n >= 4) && !bswapmask) {
 				d1 = FB_READL(src--);
-				FB_WRITEL(d0 >> right | d1 << left, dst--);
+				FB_WRITEL(d0 << left | d1 >> right, dst--);
 				d0 = d1;
 				d1 = FB_READL(src--);
-				FB_WRITEL(d0 >> right | d1 << left, dst--);
+				FB_WRITEL(d0 << left | d1 >> right, dst--);
 				d0 = d1;
 				d1 = FB_READL(src--);
-				FB_WRITEL(d0 >> right | d1 << left, dst--);
+				FB_WRITEL(d0 << left | d1 >> right, dst--);
 				d0 = d1;
 				d1 = FB_READL(src--);
-				FB_WRITEL(d0 >> right | d1 << left, dst--);
+				FB_WRITEL(d0 << left | d1 >> right, dst--);
 				d0 = d1;
 				n -= 4;
 			}
 			while (n--) {
 				d1 = FB_READL(src--);
 				d1 = fb_rev_pixels_in_long(d1, bswapmask);
-				d0 = d0 >> right | d1 << left;
+				d0 = d0 << left | d1 >> right;
 				d0 = fb_rev_pixels_in_long(d0, bswapmask);
 				FB_WRITEL(d0, dst--);
 				d0 = d1;
 			}
 
 			// Trailing bits
-			if (last) {
-				if (m <= left) {
+			if (m) {
+				if (m <= bits - left) {
 					// Single source word
-					d0 >>= right;
+					d0 <<= left;
 				} else {
 					// 2 source words
 					d1 = FB_READL(src);
 					d1 = fb_rev_pixels_in_long(d1,
 								bswapmask);
-					d0 = d0>>right | d1<<left;
+					d0 = d0 << left | d1 >> right;
 				}
 				d0 = fb_rev_pixels_in_long(d0, bswapmask);
 				FB_WRITEL(comp(d0, FB_READL(dst), last), dst);
@@ -371,9 +380,9 @@ void cfb_copyarea(struct fb_info *p, con
 	u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy;
 	u32 height = area->height, width = area->width;
 	unsigned long const bits_per_line = p->fix.line_length*8u;
-	unsigned long __iomem *dst = NULL, *src = NULL;
+	unsigned long __iomem *base = NULL;
 	int bits = BITS_PER_LONG, bytes = bits >> 3;
-	int dst_idx = 0, src_idx = 0, rev_copy = 0;
+	unsigned dst_idx = 0, src_idx = 0, rev_copy = 0;
 	u32 bswapmask = fb_compute_bswapmask(p);
 
 	if (p->state != FBINFO_STATE_RUNNING)
@@ -389,7 +398,7 @@ void cfb_copyarea(struct fb_info *p, con
 
 	// split the base of the framebuffer into a long-aligned address and the
 	// index of the first bit
-	dst = src = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1));
+	base = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1));
 	dst_idx = src_idx = 8*((unsigned long)p->screen_base & (bytes-1));
 	// add offset of source and target area
 	dst_idx += dy*bits_per_line + dx*p->var.bits_per_pixel;
@@ -402,20 +411,14 @@ void cfb_copyarea(struct fb_info *p, con
 		while (height--) {
 			dst_idx -= bits_per_line;
 			src_idx -= bits_per_line;
-			dst += dst_idx >> (ffs(bits) - 1);
-			dst_idx &= (bytes - 1);
-			src += src_idx >> (ffs(bits) - 1);
-			src_idx &= (bytes - 1);
-			bitcpy_rev(p, dst, dst_idx, src, src_idx, bits,
+			bitcpy_rev(p, base + (dst_idx / bits), dst_idx % bits,
+				base + (src_idx / bits), src_idx % bits, bits,
 				width*p->var.bits_per_pixel, bswapmask);
 		}
 	} else {
 		while (height--) {
-			dst += dst_idx >> (ffs(bits) - 1);
-			dst_idx &= (bytes - 1);
-			src += src_idx >> (ffs(bits) - 1);
-			src_idx &= (bytes - 1);
-			bitcpy(p, dst, dst_idx, src, src_idx, bits,
+			bitcpy(p, base + (dst_idx / bits), dst_idx % bits,
+				base + (src_idx / bits), src_idx % bits, bits,
 				width*p->var.bits_per_pixel, bswapmask);
 			dst_idx += bits_per_line;
 			src_idx += bits_per_line;


^ permalink raw reply

* [PATCH 4/10] atyfb: set FBINFO_READS_FAST
From: Mikulas Patocka @ 2014-01-23 19:40 UTC (permalink / raw)
  To: linux-fbdev
In-Reply-To: <CA+55aFxPfHmpcMH-bmRcTPUQeq5=67cKVmra6+SvT26ybvQaug@mail.gmail.com>

Set FBINFO_READS_FAST so that the console code uses scrolling instead of
rewriting. This improves scrolling speed.

A time to do ls -la /usr/bin:
	original patched
32bpp	4.9	3.6
24bpp	4.9	2.9
16bpp	4.9	2.1
8bpp	4.9	1.7

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>

---
 drivers/video/aty/atyfb_base.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

Index: linux-3.13-rc1/drivers/video/aty/atyfb_base.c
=================================--- linux-3.13-rc1.orig/drivers/video/aty/atyfb_base.c	2013-11-27 00:25:42.000000000 +0100
+++ linux-3.13-rc1/drivers/video/aty/atyfb_base.c	2013-11-27 00:25:55.000000000 +0100
@@ -2653,7 +2653,8 @@ static int aty_init(struct fb_info *info
 		      FBINFO_HWACCEL_IMAGEBLIT |
 		      FBINFO_HWACCEL_FILLRECT  |
 		      FBINFO_HWACCEL_COPYAREA  |
-		      FBINFO_HWACCEL_YPAN;
+		      FBINFO_HWACCEL_YPAN      |
+		      FBINFO_READS_FAST;
 
 #ifdef CONFIG_PMAC_BACKLIGHT
 	if (M64_HAS(G3_PB_1_1) && of_machine_is_compatible("PowerBook1,1")) {


^ permalink raw reply

* [PATCH 5/10] atyfb: remove resolution limit 1600
From: Mikulas Patocka @ 2014-01-23 19:40 UTC (permalink / raw)
  To: linux-fbdev

The card works fine in 1980x1080 resolution. Therefore, there is no need
to limit the resolution to 1600 pixels.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>

---
 drivers/video/aty/atyfb_base.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Index: linux-3.11-rc7-fast/drivers/video/aty/atyfb_base.c
=================================--- linux-3.11-rc7-fast.orig/drivers/video/aty/atyfb_base.c	2013-09-02 18:50:52.000000000 +0200
+++ linux-3.11-rc7-fast/drivers/video/aty/atyfb_base.c	2013-09-02 18:50:58.000000000 +0200
@@ -862,8 +862,8 @@ static int aty_var_to_crtc(const struct 
 	h_sync_pol = sync & FB_SYNC_HOR_HIGH_ACT ? 0 : 1;
 	v_sync_pol = sync & FB_SYNC_VERT_HIGH_ACT ? 0 : 1;
 
-	if ((xres > 1600) || (yres > 1200)) {
-		FAIL("MACH64 chips are designed for max 1600x1200\n"
+	if ((xres > 1920) || (yres > 1200)) {
+		FAIL("MACH64 chips are designed for max 1920x1200\n"
 		     "select another resolution.");
 	}
 	h_sync_strt = h_disp + var->right_margin;


^ permalink raw reply

* [PATCH 6/10] mach64: use unaligned access
From: Mikulas Patocka @ 2014-01-23 19:41 UTC (permalink / raw)
  To: linux-fbdev

This patch fixes mach64 to use unaligned access to the font bitmap.

This fixes unaligned access warning on sparc64 when 14x8 font is loaded.

On x86(64), unaligned access is handled in hardware, so both functions
le32_to_cpup and get_unaligned_le32 perform the same operation.

On RISC machines, unaligned access is not handled in hardware, so we
better use get_unaligned_le32 to avoid the unaligned trap and warning.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org

---
 drivers/video/aty/mach64_accel.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

Index: linux-3.4.3-fast/drivers/video/aty/mach64_accel.c
=================================--- linux-3.4.3-fast.orig/drivers/video/aty/mach64_accel.c	2012-07-02 00:10:56.000000000 +0200
+++ linux-3.4.3-fast/drivers/video/aty/mach64_accel.c	2012-07-02 00:13:29.000000000 +0200
@@ -4,6 +4,7 @@
  */
 
 #include <linux/delay.h>
+#include <asm/unaligned.h>
 #include <linux/fb.h>
 #include <video/mach64.h>
 #include "atyfb.h"
@@ -419,7 +420,7 @@ void atyfb_imageblit(struct fb_info *inf
 		u32 *pbitmap, dwords = (src_bytes + 3) / 4;
 		for (pbitmap = (u32*)(image->data); dwords; dwords--, pbitmap++) {
 			wait_for_fifo(1, par);
-			aty_st_le32(HOST_DATA0, le32_to_cpup(pbitmap), par);
+			aty_st_le32(HOST_DATA0, get_unaligned_le32(pbitmap), par);
 		}
 	}
 


^ permalink raw reply

* [PATCH 7/10] mach64: fix cursor when character width is not a multiple of 8 pixels
From: Mikulas Patocka @ 2014-01-23 19:41 UTC (permalink / raw)
  To: linux-fbdev

This patch fixes the hardware cursor on mach64 when font width is not a
multiple of 8 pixels.

If you load such a font, the cursor is expanded to the next 8-byte
boundary and a part of the next character after the cursor is not
visible.
For example, when you load a font with 12-pixel width, the cursor width
is 16 pixels and when the cursor is displayed, 4 pixels of the next
character are not visible.

The reason is this: atyfb_cursor is called with proper parameters to
load an image that is 12-pixel wide. However, the number is aligned on
the next 8-pixel boundary on the line
"unsigned int width = (cursor->image.width + 7) >> 3;" and the whole
function acts as it is was loading a 16-pixel image.

This patch fixes it so that the value written to the framebuffer is
padded with 0xaaaa (the transparent pattern) when the image size it not
a multiple of 8 pixels. The transparent pattern causes that the cursor
will not interfere with the next character.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org

---
 drivers/video/aty/mach64_cursor.c |   22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

Index: linux-3.4.3-fast/drivers/video/aty/mach64_cursor.c
=================================--- linux-3.4.3-fast.orig/drivers/video/aty/mach64_cursor.c	2012-07-02 01:15:38.000000000 +0200
+++ linux-3.4.3-fast/drivers/video/aty/mach64_cursor.c	2012-07-02 01:54:10.000000000 +0200
@@ -5,6 +5,7 @@
 #include <linux/fb.h>
 #include <linux/init.h>
 #include <linux/string.h>
+#include "../fb_draw.h"
 
 #include <asm/io.h>
 
@@ -157,24 +158,33 @@ static int atyfb_cursor(struct fb_info *
 
 	    for (i = 0; i < height; i++) {
 		for (j = 0; j < width; j++) {
+			u16 l = 0xaaaa;
 			b = *src++;
 			m = *msk++;
 			switch (cursor->rop) {
 			case ROP_XOR:
 			    // Upper 4 bits of mask data
-			    fb_writeb(cursor_bits_lookup[(b ^ m) >> 4], dst++);
+			    l = cursor_bits_lookup[(b ^ m) >> 4] |
 			    // Lower 4 bits of mask
-			    fb_writeb(cursor_bits_lookup[(b ^ m) & 0x0f],
-				      dst++);
+			    	(cursor_bits_lookup[(b ^ m) & 0x0f] << 8);
 			    break;
 			case ROP_COPY:
 			    // Upper 4 bits of mask data
-			    fb_writeb(cursor_bits_lookup[(b & m) >> 4], dst++);
+			    l = cursor_bits_lookup[(b & m) >> 4] |
 			    // Lower 4 bits of mask
-			    fb_writeb(cursor_bits_lookup[(b & m) & 0x0f],
-				      dst++);
+			    	(cursor_bits_lookup[(b & m) & 0x0f] << 8);
 			    break;
 			}
+			/*
+			 * If cursor size is not a multiple of 8 characters
+			 * we must pad it with transparent pattern (0xaaaa).
+			 */
+			if ((j + 1) * 8 > cursor->image.width) {
+				l = comp(l, 0xaaaa,
+				    (1 << ((cursor->image.width & 7) * 2)) - 1);
+			}
+			fb_writeb(l & 0xff, dst++);
+			fb_writeb(l >> 8, dst++);
 		}
 		dst += offset;
 	    }


^ permalink raw reply

* [PATCH 8/10] tgafb: fix mode setting with fbset
From: Mikulas Patocka @ 2014-01-23 19:42 UTC (permalink / raw)
  To: linux-fbdev

Mode setting in the TGA driver is broken for these reasons:

- info->fix.line_length is set just once in tgafb_init_fix function. If
  we change videomode, info->fix.line_length is not recalculated - so
  the video mode is changed but the screen is corrupted because of wrong
  info->fix.line_length.

- info->fix.smem_len is set in tgafb_init_fix to the size of the default
  video mode (640x480). If we set a higher resolution,
  info->fix.smem_len is smaller than the current screen size, preventing
  the userspace program from mapping the framebuffer.

This patch fixes it:

- info->fix.line_length initialization is moved to tgafb_set_par so that
  it is recalculated with each mode change.

- info->fix.smem_len is set to a fixed value representing the real
  amount of video ram (the values are taken from xfree86 driver).

- add a check to tgafb_check_var to prevent us from setting a videomode
  that doesn't fit into videoram.

- in tgafb_register, tgafb_init_fix is moved upwards, to be called
  before fb_find_mode (because fb_find_mode already needs the videoram
  size set in tgafb_init_fix).

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vga.kernel.org

---
 drivers/video/tgafb.c |   15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

Index: linux-3.8-rc3-fast/drivers/video/tgafb.c
=================================--- linux-3.8-rc3-fast.orig/drivers/video/tgafb.c	2013-01-10 23:42:12.000000000 +0100
+++ linux-3.8-rc3-fast/drivers/video/tgafb.c	2013-01-10 23:43:16.000000000 +0100
@@ -188,6 +188,8 @@ tgafb_check_var(struct fb_var_screeninfo
 
 	if (var->xres_virtual != var->xres || var->yres_virtual != var->yres)
 		return -EINVAL;
+	if (var->xres * var->yres * (var->bits_per_pixel >> 3) > info->fix.smem_len)
+		return -EINVAL;
 	if (var->nonstd)
 		return -EINVAL;
 	if (1000000000 / var->pixclock > TGA_PLL_MAX_FREQ)
@@ -268,6 +270,7 @@ tgafb_set_par(struct fb_info *info)
 	par->yres = info->var.yres;
 	par->pll_freq = pll_freq = 1000000000 / info->var.pixclock;
 	par->bits_per_pixel = info->var.bits_per_pixel;
+	info->fix.line_length = par->xres * (par->bits_per_pixel >> 3);
 
 	tga_type = par->tga_type;
 
@@ -1476,6 +1479,7 @@ tgafb_init_fix(struct fb_info *info)
 	int tga_bus_tc = TGA_BUS_TC(par->dev);
 	u8 tga_type = par->tga_type;
 	const char *tga_type_name = NULL;
+	unsigned memory_size;
 
 	switch (tga_type) {
 	case TGA_TYPE_8PLANE:
@@ -1483,21 +1487,25 @@ tgafb_init_fix(struct fb_info *info)
 			tga_type_name = "Digital ZLXp-E1";
 		if (tga_bus_tc)
 			tga_type_name = "Digital ZLX-E1";
+		memory_size = 2097152;
 		break;
 	case TGA_TYPE_24PLANE:
 		if (tga_bus_pci)
 			tga_type_name = "Digital ZLXp-E2";
 		if (tga_bus_tc)
 			tga_type_name = "Digital ZLX-E2";
+		memory_size = 8388608;
 		break;
 	case TGA_TYPE_24PLUSZ:
 		if (tga_bus_pci)
 			tga_type_name = "Digital ZLXp-E3";
 		if (tga_bus_tc)
 			tga_type_name = "Digital ZLX-E3";
+		memory_size = 16777216;
 		break;
 	default:
 		tga_type_name = "Unknown";
+		memory_size = 16777216;
 		break;
 	}
 
@@ -1509,9 +1517,8 @@ tgafb_init_fix(struct fb_info *info)
 			    ? FB_VISUAL_PSEUDOCOLOR
 			    : FB_VISUAL_DIRECTCOLOR);
 
-	info->fix.line_length = par->xres * (par->bits_per_pixel >> 3);
 	info->fix.smem_start = (size_t) par->tga_fb_base;
-	info->fix.smem_len = info->fix.line_length * par->yres;
+	info->fix.smem_len = memory_size;
 	info->fix.mmio_start = (size_t) par->tga_regs_base;
 	info->fix.mmio_len = 512;
 
@@ -1635,6 +1642,9 @@ static int tgafb_register(struct device 
 		modedb_tga = &modedb_tc;
 		modedbsize_tga = 1;
 	}
+
+	tgafb_init_fix(info);
+
 	ret = fb_find_mode(&info->var, info,
 			   mode_option ? mode_option : mode_option_tga,
 			   modedb_tga, modedbsize_tga, NULL,
@@ -1652,7 +1662,6 @@ static int tgafb_register(struct device 
 	}
 
 	tgafb_set_par(info);
-	tgafb_init_fix(info);
 
 	if (register_framebuffer(info) < 0) {
 		printk(KERN_ERR "tgafb: Could not register framebuffer\n");


^ permalink raw reply

* [PATCH 9/10] tgafb: fix data copying
From: Mikulas Patocka @ 2014-01-23 19:43 UTC (permalink / raw)
  To: linux-fbdev

The functions for data copying copyarea_foreward_8bpp and
copyarea_backward_8bpp are buggy, they produce screen corruption.

This patch fixes the functions and moves the logic to one function
"copyarea_8bpp". For simplicity, the function only handles copying that
is aligned on 8 pixes. If we copy an unaligned area, generic function
cfb_copyarea is used.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org

---
 drivers/video/tgafb.c |  264 +++++++++-----------------------------------------
 1 file changed, 51 insertions(+), 213 deletions(-)

Index: linux-3.8-rc3-fast/drivers/video/tgafb.c
=================================--- linux-3.8-rc3-fast.orig/drivers/video/tgafb.c	2013-01-10 23:43:16.000000000 +0100
+++ linux-3.8-rc3-fast/drivers/video/tgafb.c	2013-01-10 23:43:44.000000000 +0100
@@ -1145,222 +1145,57 @@ copyarea_line_32bpp(struct fb_info *info
 	__raw_writel(TGA_MODE_SBM_24BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG);
 }
 
-/* The general case of forward copy in 8bpp mode.  */
+/* The (almost) general case of backward copy in 8bpp mode.  */
 static inline void
-copyarea_foreward_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy,
-		       u32 height, u32 width, u32 line_length)
+copyarea_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy,
+	      u32 height, u32 width, u32 line_length,
+	      const struct fb_copyarea *area)
 {
 	struct tga_par *par = (struct tga_par *) info->par;
-	unsigned long i, copied, left;
-	unsigned long dpos, spos, dalign, salign, yincr;
-	u32 smask_first, dmask_first, dmask_last;
-	int pixel_shift, need_prime, need_second;
-	unsigned long n64, n32, xincr_first;
+	unsigned i, yincr;
+	int depos, sepos, backward, last_step, step;
+	u32 mask_last;
+	unsigned n32;
 	void __iomem *tga_regs;
 	void __iomem *tga_fb;
 
-	yincr = line_length;
-	if (dy > sy) {
-		dy += height - 1;
-		sy += height - 1;
-		yincr = -yincr;
-	}
-
-	/* Compute the offsets and alignments in the frame buffer.
-	   More than anything else, these control how we do copies.  */
-	dpos = dy * line_length + dx;
-	spos = sy * line_length + sx;
-	dalign = dpos & 7;
-	salign = spos & 7;
-	dpos &= -8;
-	spos &= -8;
-
-	/* Compute the value for the PIXELSHIFT register.  This controls
-	   both non-co-aligned source and destination and copy direction.  */
-	if (dalign >= salign)
-		pixel_shift = dalign - salign;
-	else
-		pixel_shift = 8 - (salign - dalign);
-
-	/* Figure out if we need an additional priming step for the
-	   residue register.  */
-	need_prime = (salign > dalign);
-	if (need_prime)
-		dpos -= 8;
-
-	/* Begin by copying the leading unaligned destination.  Copy enough
-	   to make the next destination address 32-byte aligned.  */
-	copied = 32 - (dalign + (dpos & 31));
-	if (copied = 32)
-		copied = 0;
-	xincr_first = (copied + 7) & -8;
-	smask_first = dmask_first = (1ul << copied) - 1;
-	smask_first <<= salign;
-	dmask_first <<= dalign + need_prime*8;
-	if (need_prime && copied > 24)
-		copied -= 8;
-	left = width - copied;
-
-	/* Care for small copies.  */
-	if (copied > width) {
-		u32 t;
-		t = (1ul << width) - 1;
-		t <<= dalign + need_prime*8;
-		dmask_first &= t;
-		left = 0;
-	}
-
-	/* Attempt to use 64-byte copies.  This is only possible if the
-	   source and destination are co-aligned at 64 bytes.  */
-	n64 = need_second = 0;
-	if ((dpos & 63) = (spos & 63)
-	    && (height = 1 || line_length % 64 = 0)) {
-		/* We may need a 32-byte copy to ensure 64 byte alignment.  */
-		need_second = (dpos + xincr_first) & 63;
-		if ((need_second & 32) != need_second)
-			printk(KERN_ERR "tgafb: need_second wrong\n");
-		if (left >= need_second + 64) {
-			left -= need_second;
-			n64 = left / 64;
-			left %= 64;
-		} else
-			need_second = 0;
-	}
-
-	/* Copy trailing full 32-byte sections.  This will be the main
-	   loop if the 64 byte loop can't be used.  */
-	n32 = left / 32;
-	left %= 32;
-
-	/* Copy the trailing unaligned destination.  */
-	dmask_last = (1ul << left) - 1;
-
-	tga_regs = par->tga_regs_base;
-	tga_fb = par->tga_fb_base;
-
-	/* Set up the MODE and PIXELSHIFT registers.  */
-	__raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_COPY, tga_regs+TGA_MODE_REG);
-	__raw_writel(pixel_shift, tga_regs+TGA_PIXELSHIFT_REG);
-	wmb();
-
-	for (i = 0; i < height; ++i) {
-		unsigned long j;
-		void __iomem *sfb;
-		void __iomem *dfb;
-
-		sfb = tga_fb + spos;
-		dfb = tga_fb + dpos;
-		if (dmask_first) {
-			__raw_writel(smask_first, sfb);
-			wmb();
-			__raw_writel(dmask_first, dfb);
-			wmb();
-			sfb += xincr_first;
-			dfb += xincr_first;
-		}
-
-		if (need_second) {
-			__raw_writel(0xffffffff, sfb);
-			wmb();
-			__raw_writel(0xffffffff, dfb);
-			wmb();
-			sfb += 32;
-			dfb += 32;
-		}
-
-		if (n64 && (((unsigned long)sfb | (unsigned long)dfb) & 63))
-			printk(KERN_ERR
-			       "tgafb: misaligned copy64 (s:%p, d:%p)\n",
-			       sfb, dfb);
-
-		for (j = 0; j < n64; ++j) {
-			__raw_writel(sfb - tga_fb, tga_regs+TGA_COPY64_SRC);
-			wmb();
-			__raw_writel(dfb - tga_fb, tga_regs+TGA_COPY64_DST);
-			wmb();
-			sfb += 64;
-			dfb += 64;
-		}
-
-		for (j = 0; j < n32; ++j) {
-			__raw_writel(0xffffffff, sfb);
-			wmb();
-			__raw_writel(0xffffffff, dfb);
-			wmb();
-			sfb += 32;
-			dfb += 32;
-		}
-
-		if (dmask_last) {
-			__raw_writel(0xffffffff, sfb);
-			wmb();
-			__raw_writel(dmask_last, dfb);
-			wmb();
-		}
-
-		spos += yincr;
-		dpos += yincr;
+	/* Do acceleration only if we are aligned on 8 pixels */
+	if ((dx | sx | width) & 7) {
+		cfb_copyarea(info, area);
+		return;
 	}
 
-	/* Reset the MODE register to normal.  */
-	__raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG);
-}
-
-/* The (almost) general case of backward copy in 8bpp mode.  */
-static inline void
-copyarea_backward_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy,
-		       u32 height, u32 width, u32 line_length,
-		       const struct fb_copyarea *area)
-{
-	struct tga_par *par = (struct tga_par *) info->par;
-	unsigned long i, left, yincr;
-	unsigned long depos, sepos, dealign, sealign;
-	u32 mask_first, mask_last;
-	unsigned long n32;
-	void __iomem *tga_regs;
-	void __iomem *tga_fb;
-
 	yincr = line_length;
 	if (dy > sy) {
 		dy += height - 1;
 		sy += height - 1;
 		yincr = -yincr;
 	}
+	backward = dy = sy && dx > sx && dx < sx + width;
 
 	/* Compute the offsets and alignments in the frame buffer.
 	   More than anything else, these control how we do copies.  */
-	depos = dy * line_length + dx + width;
-	sepos = sy * line_length + sx + width;
-	dealign = depos & 7;
-	sealign = sepos & 7;
-
-	/* ??? The documentation appears to be incorrect (or very
-	   misleading) wrt how pixel shifting works in backward copy
-	   mode, i.e. when PIXELSHIFT is negative.  I give up for now.
-	   Do handle the common case of co-aligned backward copies,
-	   but frob everything else back on generic code.  */
-	if (dealign != sealign) {
-		cfb_copyarea(info, area);
-		return;
-	}
-
-	/* We begin the copy with the trailing pixels of the
-	   unaligned destination.  */
-	mask_first = (1ul << dealign) - 1;
-	left = width - dealign;
-
-	/* Care for small copies.  */
-	if (dealign > width) {
-		mask_first ^= (1ul << (dealign - width)) - 1;
-		left = 0;
-	}
+	depos = dy * line_length + dx;
+	sepos = sy * line_length + sx;
+	if (backward)
+		depos += width, sepos += width;
 
 	/* Next copy full words at a time.  */
-	n32 = left / 32;
-	left %= 32;
+	n32 = width / 32;
+	last_step = width % 32;
 
 	/* Finally copy the unaligned head of the span.  */
-	mask_last = -1 << (32 - left);
+	mask_last = (1ul << last_step) - 1;
+
+	if (!backward) {
+		step = 32;
+		last_step = 32;
+	} else {
+		step = -32;
+		last_step = -last_step;
+		sepos -= 32;
+		depos -= 32;
+	}
 
 	tga_regs = par->tga_regs_base;
 	tga_fb = par->tga_fb_base;
@@ -1377,25 +1212,33 @@ copyarea_backward_8bpp(struct fb_info *i
 
 		sfb = tga_fb + sepos;
 		dfb = tga_fb + depos;
-		if (mask_first) {
-			__raw_writel(mask_first, sfb);
-			wmb();
-			__raw_writel(mask_first, dfb);
-			wmb();
-		}
 
-		for (j = 0; j < n32; ++j) {
-			sfb -= 32;
-			dfb -= 32;
+		for (j = 0; j < n32; j++) {
+			if (j < 2 && j + 1 < n32 && !backward &&
+			    !(((unsigned long)sfb | (unsigned long)dfb) & 63)) {
+				do {
+					__raw_writel(sfb - tga_fb, tga_regs+TGA_COPY64_SRC);
+					wmb();
+					__raw_writel(dfb - tga_fb, tga_regs+TGA_COPY64_DST);
+					wmb();
+					sfb += 64;
+					dfb += 64;
+					j += 2;
+				} while (j + 1 < n32);
+				j--;
+				continue;
+			}
 			__raw_writel(0xffffffff, sfb);
 			wmb();
 			__raw_writel(0xffffffff, dfb);
 			wmb();
+			sfb += step;
+			dfb += step;
 		}
 
 		if (mask_last) {
-			sfb -= 32;
-			dfb -= 32;
+			sfb += last_step - step;
+			dfb += last_step - step;
 			__raw_writel(mask_last, sfb);
 			wmb();
 			__raw_writel(mask_last, dfb);
@@ -1456,14 +1299,9 @@ tgafb_copyarea(struct fb_info *info, con
 	else if (bpp = 32)
 		cfb_copyarea(info, area);
 
-	/* Detect overlapping source and destination that requires
-	   a backward copy.  */
-	else if (dy = sy && dx > sx && dx < sx + width)
-		copyarea_backward_8bpp(info, dx, dy, sx, sy, height,
-				       width, line_length, area);
 	else
-		copyarea_foreward_8bpp(info, dx, dy, sx, sy, height,
-				       width, line_length);
+		copyarea_8bpp(info, dx, dy, sx, sy, height,
+			      width, line_length, area);
 }
 
 


^ permalink raw reply

* [PATCH 10/10] tgafb: avoid restriction on modes with line length not a multiple of 64
From: Mikulas Patocka @ 2014-01-23 19:44 UTC (permalink / raw)
  To: linux-fbdev

In tgafb there is a restriction that prevents the user from setting a
videomode with line length not a multiple of 64 bytes (for example,
800x600 is not allowed).

The reason for this restriction it that functions copyarea_line_8bpp and
copyarea_line_32bpp can not handle a line length that is not a multiple
of 64 bytes.

This patch removes this restriction on mode setting and makes sure that
the functions copyarea_line_8bpp and copyarea_line_32bpp are called only
if line length is a multiple of 64 bytes. If we set a mode 800x600,
the functions copyarea_line_8bpp and copyarea_line_32bpp are not used,
generic functions for copying are used instead and it works just fine.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>

---
 drivers/video/tgafb.c |    6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

Index: linux-3.8-rc3-fast/drivers/video/tgafb.c
=================================--- linux-3.8-rc3-fast.orig/drivers/video/tgafb.c	2013-01-10 23:43:44.000000000 +0100
+++ linux-3.8-rc3-fast/drivers/video/tgafb.c	2013-01-10 23:43:49.000000000 +0100
@@ -198,8 +198,8 @@ tgafb_check_var(struct fb_var_screeninfo
 		return -EINVAL;
 
 	/* Some of the acceleration routines assume the line width is
-	   a multiple of 64 bytes.  */
-	if (var->xres * (par->tga_type = TGA_TYPE_8PLANE ? 1 : 4) % 64)
+	   a multiple of 8 bytes.  */
+	if (var->xres * (par->tga_type = TGA_TYPE_8PLANE ? 1 : 4) % 8)
 		return -EINVAL;
 
 	return 0;
@@ -1286,7 +1286,7 @@ tgafb_copyarea(struct fb_info *info, con
 	bpp = info->var.bits_per_pixel;
 
 	/* Detect copies of the entire line.  */
-	if (width * (bpp >> 3) = line_length) {
+	if (!(line_length & 63) && width * (bpp >> 3) = line_length) {
 		if (bpp = 8)
 			copyarea_line_8bpp(info, dy, sy, height, width);
 		else

^ permalink raw reply

* Re: [PATCH 4/10] atyfb: set FBINFO_READS_FAST
From: Linus Torvalds @ 2014-01-23 19:45 UTC (permalink / raw)
  To: Mikulas Patocka
  Cc: linux-fbdev@vger.kernel.org, Tomi Valkeinen,
	Linux Kernel Mailing List
In-Reply-To: <alpine.LRH.2.02.1401231426470.7971@file01.intranet.prod.int.rdu2.redhat.com>

On Thu, Jan 23, 2014 at 11:31 AM, Mikulas Patocka <mpatocka@redhat.com> wrote:
>
> Both mach64 and matrox have a hardware bitter that is faster than
> rewriting the console - that's why FBINFO_READS_FAST improves performance
> for them.

My point is that I'd expect *anything* that has a hardware blitter to
be faster than rewriting the screen.

FBINFO_READS_FAST is documented to be about "soft-copy" being faster
than re-rendering. Which I take to be about actually doing copying in
*software*.

In particular, updatescrollmode() seems to do this right. It sets
p->scrollmode based on whether there's an accelerated copyarea. But
then SCROLL_PAN/WRAP_MOVE ends up re-testing FBINFO_READS_FAST,
ignoring any hw-accelerated copy-area, and I don't quite see why..

                Linus

^ permalink raw reply

* Re: [PATCH 4/10] atyfb: set FBINFO_READS_FAST
From: Mikulas Patocka @ 2014-01-23 20:03 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: linux-fbdev@vger.kernel.org, Tomi Valkeinen,
	Linux Kernel Mailing List
In-Reply-To: <CA+55aFxrZE6oxu5CxFo3mRwGdVcq3jvnn_ChoSJDFgQz45Lq_A@mail.gmail.com>



On Thu, 23 Jan 2014, Linus Torvalds wrote:

> On Thu, Jan 23, 2014 at 11:31 AM, Mikulas Patocka <mpatocka@redhat.com> wrote:
> >
> > Both mach64 and matrox have a hardware bitter that is faster than
> > rewriting the console - that's why FBINFO_READS_FAST improves performance
> > for them.
> 
> My point is that I'd expect *anything* that has a hardware blitter to
> be faster than rewriting the screen.
>
> FBINFO_READS_FAST is documented to be about "soft-copy" being faster
> than re-rendering. Which I take to be about actually doing copying in
> *software*.
> 
> In particular, updatescrollmode() seems to do this right. It sets
> p->scrollmode based on whether there's an accelerated copyarea. But
> then SCROLL_PAN/WRAP_MOVE ends up re-testing FBINFO_READS_FAST,
> ignoring any hw-accelerated copy-area, and I don't quite see why..
> 
>                 Linus

I think the argument why not to use the blitter was this:

Some hardware have font expansion - you submit monochromatic bitwise image 
of the character via writes to some register and the hardware expands it 
to full color. Mach64 has this capability, matrox doesn't (maybe it does, 
but the driver doesn't use it).

If you use font expansion, you actually generate less load on the videoram 
than using the blitter (the expander only writes to the videoram; the 
blitter reads data from the videoram and stores them back).

But the benchmarks that I performed show that the blitter is faster than 
the font expander. Maybe on a different computer that has better 
bufferring in the PCI chipset the font expander could be faster - who 
knows?

Mikulas

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox