Hi
This series tries to fix the mess with global system framebuffer access in device drivers. Currently, architecture initialization sets the "screen_info" object according to the system framebuffer that was detected during boot. The device driver that can map VMEM first gets access to it. There is no way to give a specific driver access to the device and no _proper_ way to revoke access again. In fact, some drivers don't even check whether they mapped the VMEM successfully, letting multiple drivers to access the system framebuffer at the same time.
This series introduces a new bus in patch #1. Global system framebuffers are added as devices to this bus and drivers can register as bus drivers. Via sysfs we can now bind/unbind the drivers. This guarantees that only one driver has access to a single system-framebuffer at a time. But we can also easily control which driver gets loaded (and extend this logic at a central place if we want).
If a real hardware drivers gets loaded via another bus that actually provides the system-framebuffer (like pci-bus), these drivers can use a special BUS function to claim the system framebuffer and unload all other drivers from this device. This can be used by _real_ DRM drivers that want to kill all other generic framebuffer drivers.
This series adds a SYSFB_VBE (VESA/VBE) framebuffer type as example, but the bus can be used with any other system-framebuffer type. Any other architecture can add their own type like SYSFB_VBE. Patch #3 is currently a HACK to provide the VBE framebuffer on all architectures. However, the platform-device for system-framebuffers should instead be provided by architecture code.
Why a new BUS type? We need a way to allow transition from a generic driver to a real hardware driver (like most DRM drivers or special fbdev drivers). We could implement this logic separately, but the BUS driver-core code is available, anyway. So lets use it and save a lot of .text space. Additionally, we get some extra features like driver binding/unbinding via sysfs for free (which is really handy for debugging). The new bus is actually implemented in <200 lines of code. I don't think we can get a smaller implementation when not using the bus-core code.
Patch #4 fixes vesafb.c to be hotplug-capable. It doesn't depend on this new bus so it would be nice if it could get applied right away. It allows vesafb to be compiled as a module.
Patch #5 makes vesafb.c register as new bus driver on the system-framebuffer bus.
Patch #6-#9 introduce DVBE. It's a DRM driver based on VBE/VESA. It also uses the new system-framebuffer bus and provides merely the same functionality as vesafb.c but with a sane user-space API and without any fbdev dependency.
What needs to be done? All drivers that use screen_info currently don't _have_ to be converted to the new bus as the request_memory() calls protect the drivers from interfering. So this new bus works even if no other driver gets converted. However, we really _should_ convert the drivers. I will do that if a maintainer agrees to take the bus code through their tree. But I hope to avoid converting all drivers if no maintainer thinks this bus is a good idea. The DVBE and vesafb drivers show how it is done.
I also like to see the system-framebuffer platform-devices being registered during architecture initialization. I haven't worked much there so any comments are welcome. Otherwise, I will keep the HACK to add the devices during sysfb module-init.
Regards David
David Herrmann (9): video: introduce system framebuffer bus video: sysfb: new vbefb device type video: sysfb: always provide vbefb device video: vesafb: allow building as module video: vesafb: use sysfb bus drm: new sysfb DRM bus module drm: new VESA BIOS Extension DRM driver stub drm: dvbe: implement VBE/VESA blitting backend drm: dvbe: add optional fbdev frontend
drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 2 + drivers/gpu/drm/drm_sysfb.c | 145 +++++++++++++ drivers/gpu/drm/dvbe/Kconfig | 47 +++++ drivers/gpu/drm/dvbe/Makefile | 5 + drivers/gpu/drm/dvbe/dvbe.h | 121 +++++++++++ drivers/gpu/drm/dvbe/dvbe_drv.c | 104 +++++++++ drivers/gpu/drm/dvbe/dvbe_fbdev.c | 235 +++++++++++++++++++++ drivers/gpu/drm/dvbe/dvbe_main.c | 432 ++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/dvbe/dvbe_mem.c | 269 ++++++++++++++++++++++++ drivers/gpu/drm/dvbe/dvbe_vesa.c | 263 +++++++++++++++++++++++ drivers/video/Kconfig | 20 +- drivers/video/Makefile | 1 + drivers/video/sysfb.c | 315 +++++++++++++++++++++++++++ drivers/video/vesafb.c | 105 ++++----- include/drm/drmP.h | 4 + include/drm/drm_sysfb.h | 35 +++ include/linux/sysfb.h | 137 ++++++++++++ 18 files changed, 2197 insertions(+), 50 deletions(-) create mode 100644 drivers/gpu/drm/drm_sysfb.c create mode 100644 drivers/gpu/drm/dvbe/Kconfig create mode 100644 drivers/gpu/drm/dvbe/Makefile create mode 100644 drivers/gpu/drm/dvbe/dvbe.h create mode 100644 drivers/gpu/drm/dvbe/dvbe_drv.c create mode 100644 drivers/gpu/drm/dvbe/dvbe_fbdev.c create mode 100644 drivers/gpu/drm/dvbe/dvbe_main.c create mode 100644 drivers/gpu/drm/dvbe/dvbe_mem.c create mode 100644 drivers/gpu/drm/dvbe/dvbe_vesa.c create mode 100644 drivers/video/sysfb.c create mode 100644 include/drm/drm_sysfb.h create mode 100644 include/linux/sysfb.h
From: David Herrmann dh.herrmann@googlemail.com
For a long time now we have the problem that there are multiple drivers available that try to use system framebuffers (like EFI, VESA/VBE, ...). There is no way to control which driver gets access to the devices, but instead works on a first-come-first-serve basis.
Furthermore, hardware drivers (eg., gpu/drm/*) that get loaded on the real hardware bus (eg., pci-bus) of the framebuffer devices have a hard time unloading other drivers that currently use system framebuffers.
This introduces a new bus-type: sysfb (system framebuffer bus)
Any available system framebuffer gets registered as a device on this bus. A bus-driver can then pick up the device and use it. Standard sysfs bind/unbind interfaces can be used to change drivers on-the-fly.
There are actually two types of drivers: generic and real drivers
Generic drivers use the generic framebuffer interface exclusively. They are often used as a fallback interface where no real driver for the hardware is available. Generic drivers register as sysfb drivers to the sysfb bus and will get loaded dynamically. User-space can bind/unbind them via sysfs to control which driver should get access. Only one driver can be active per device. During probe the driver can retrieve additional information via a screen_info object of the device. Generic drivers include: efifb, (u)vesafb, vgacon, ...
Real drivers work differently. Instead of being loaded via sysfb, they register as drivers on the real bus (eg., pci-bus). During probe they should verify whether their real device provides a system-framebuffer. If it does, they call sysfb_claim() to claim exclusive access to the device. This guarantees that any generic driver gets unloaded and the real hardware driver can gain access. This also guarantees that a real hardware driver always takes precedence over generic fallback drivers. Real drivers include: i915, radeon, nouveau, ...
Signed-off-by: David Herrmann dh.herrmann@googlemail.com --- drivers/video/Kconfig | 17 ++++ drivers/video/Makefile | 1 + drivers/video/sysfb.c | 230 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/sysfb.h | 134 ++++++++++++++++++++++++++++ 4 files changed, 382 insertions(+) create mode 100644 drivers/video/sysfb.c create mode 100644 include/linux/sysfb.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index d08d799..eac56ef 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -27,6 +27,23 @@ config VGASTATE tristate default n
+config SYSFB + tristate "System Framebuffer Bus" + help + Framebuffers like VGA, VESA/VBE, EFI and others can be handled by many + different drivers. This bus provides an infrastructure for drivers to + register themselves and then get bound/unbound to these system-wide + framebuffers. + This bus prevents framebuffers from being used by multiple drivers + simultaneously and also provides a sysfs API to bind/rebind different + drivers to each device from userspace. + + Chipset-specific drivers (like real GPU drivers) will always take + precedence over generic framebuffer drivers. + + A driver should normally select this bus-option automatically. Enable + it only if you need out-of-tree builds. + config VIDEO_OUTPUT_CONTROL tristate "Lowlevel video output switch controls" help diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 23e948e..f0eb006 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -5,6 +5,7 @@ # Each configuration option enables a list of files.
obj-$(CONFIG_VGASTATE) += vgastate.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/sysfb.c b/drivers/video/sysfb.c new file mode 100644 index 0000000..8249006 --- /dev/null +++ b/drivers/video/sysfb.c @@ -0,0 +1,230 @@ +/* + * System framebuffer bus + * Copyright (c) 2013 David Herrmann + */ + +/* + * 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. + */ + +/* + * The system framebuffer bus (sysfb) provides a way to register global system + * framebuffers and load different drivers for it. This includes VESA/VBE and + * EFI framebuffers. + * Platform code is responsible of adding the framebuffer devices to the system + * platform bus. The sysfb bus will pick up known devices and provide them via + * the sysfb bus to system drivers. This guarantees that only one driver uses + * a single system framebuffer at a time. + * + * Drivers that can make use of the generic interfaces of system framebuffers + * should register as a sysfb driver. They will get notified via probe/remove + * callbacks just like any other hotpluggable driver. Users can load/unload + * drivers via the sysfs bus interface so drivers must be hotplug capable. + * + * Drivers that cannot make use of the generic interfaces but instead control + * the real hardware should instead claim the device. These drivers normally + * register through PCI or platform devices and control the device via another + * interface. + * By claiming a device, all other generic drivers are unregistered and no more + * drivers will be probed unless the device is released again. + * + * Only _real_ hardware drivers should claim devices as there is always another + * mechanism to control which real hardware driver gets loaded (eg. pci-bus). + * Generic drivers which aren't controlled via another bus should use this + * generic sysfb driver interface instead of claiming a device. + * + * All drivers must make sure that after they get unloaded or release a device, + * the device is reset to a usable state. If the driver cannot guarantee that, + * it should taint the device so other drivers will notice it and can + * optionally recover the device. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/screen_info.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/sysfb.h> + +static DEFINE_SPINLOCK(sysfb_lock); +static unsigned int claimed_types; + +static int sysfb_bus_match(struct device *dev, struct device_driver *drv) +{ + struct sysfb_device *sdev = to_sysfb_device(dev); + struct sysfb_driver *sdrv = to_sysfb_driver(drv); + + return (sdrv->type_mask & sdev->type) && + (sdrv->allow_tainted || !sdev->tainted); +} + +static int sysfb_bus_probe(struct device *dev) +{ + struct sysfb_device *sdev = to_sysfb_device(dev); + struct sysfb_driver *sdrv = to_sysfb_driver(dev->driver); + unsigned long flags; + int ret; + + if (!(sdrv->type_mask & sdev->type)) + return -ENODEV; + if (!sdrv->allow_tainted && sdev->tainted) + return -ENODEV; + + spin_lock_irqsave(&sysfb_lock, flags); + if ((claimed_types & sdev->type)) { + spin_unlock_irqrestore(&sysfb_lock, flags); + return -ENODEV; + } + spin_unlock_irqrestore(&sysfb_lock, flags); + + if (sdrv->probe) { + ret = sdrv->probe(sdev); + if (ret) + return ret; + } + + return 0; +} + +static int sysfb_bus_remove(struct device *dev) +{ + struct sysfb_device *sdev = to_sysfb_device(dev); + struct sysfb_driver *sdrv = to_sysfb_driver(dev->driver); + + if (sdrv->remove) + sdrv->remove(sdev); + + return 0; +} + +static struct bus_type sysfb_bus_type = { + .name = "sysfb", + .match = sysfb_bus_match, + .probe = sysfb_bus_probe, + .remove = sysfb_bus_remove, +}; + +int sysfb_register_driver(struct sysfb_driver *drv) +{ + int ret; + + drv->driver.bus = &sysfb_bus_type; + + ret = driver_register(&drv->driver); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(sysfb_register_driver); + +void sysfb_unregister_driver(struct sysfb_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL(sysfb_unregister_driver); + +static int __sysfb_rescan(struct device *dev, void *data) +{ + return device_attach(dev); +} + +static void sysfb_rescan(void) +{ + bus_for_each_dev(&sysfb_bus_type, NULL, NULL, __sysfb_rescan); +} + +static int __sysfb_claim(struct device *dev, void *data) +{ + struct sysfb_device *sdev = to_sysfb_device(dev); + unsigned int claim = (long)data; + + if (!(sdev->type & claim)) + return 0; + + device_release_driver(dev); + return 0; +} + +int sysfb_claim(unsigned int types) +{ + unsigned long flags; + int ret; + + if (!(types & SYSFB_TYPES)) + return -EINVAL; + + spin_lock_irqsave(&sysfb_lock, flags); + if ((claimed_types & types)) { + spin_unlock_irqrestore(&sysfb_lock, flags); + return -EALREADY; + } + claimed_types |= types; + spin_unlock_irqrestore(&sysfb_lock, flags); + + ret = bus_for_each_dev(&sysfb_bus_type, NULL, (void*)(long)types, + __sysfb_claim); + if (ret) + goto err_restore; + + return 0; + +err_restore: + spin_lock_irqsave(&sysfb_lock, flags); + claimed_types &= ~types; + spin_unlock_irqrestore(&sysfb_lock, flags); + + sysfb_rescan(); + return ret; +} +EXPORT_SYMBOL(sysfb_claim); + +void sysfb_release(unsigned int types) +{ + unsigned long flags; + + spin_lock_irqsave(&sysfb_lock, flags); + claimed_types &= ~types; + spin_unlock_irqrestore(&sysfb_lock, flags); + + sysfb_rescan(); +} +EXPORT_SYMBOL(sysfb_release); + +void sysfb_taint(struct sysfb_device *sdev, bool set) +{ + sdev->tainted = set; +} +EXPORT_SYMBOL(sysfb_taint); + +static int __init sysfb_init(void) +{ + int ret; + + ret = bus_register(&sysfb_bus_type); + if (ret) { + pr_err("cannot register sysfb bus\n"); + return ret; + } + + return 0; +} + +static void __exit sysfb_exit(void) +{ + bus_unregister(&sysfb_bus_type); +} + +module_init(sysfb_init); +module_exit(sysfb_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Herrmann dh.herrmann@gmail.com"); +MODULE_DESCRIPTION("System framebuffer bus"); diff --git a/include/linux/sysfb.h b/include/linux/sysfb.h new file mode 100644 index 0000000..6cd3c24 --- /dev/null +++ b/include/linux/sysfb.h @@ -0,0 +1,134 @@ +#ifndef __LINUX_SYSFB_H_ +#define __LINUX_SYSFB_H_ + +/* + * System framebuffer bus + * Copyright (c) 2013 David Herrmann + */ + +/* + * 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/device.h> +#include <linux/kernel.h> +#include <linux/screen_info.h> +#include <linux/types.h> + +/** + * sysfb_type + * + * Different types of available framebuffer devices. Only one device of each + * type can be available at a time. In most systems there even is only one + * device at all. + * + * Use the sysfb_device->screen pointer to get information about the framebuffer + * devices. + */ +enum sysfb_type { + SYSFB_TYPES = 0, +}; + +/** + * sysfb_device + * @tainted: whether the device was tainted or not + * @type: type of the fb device (@sysfb_type) + * @screen: pointer to supplemental screen-info object + * @dev: device object + * + * Each framebuffer device is represented by a sysfb_device object. The sysfb + * core manages them and they cannot be registered from the outside. + */ +struct sysfb_device { + bool tainted; + unsigned int type; + struct screen_info *screen; + struct device dev; +}; + +#define to_sysfb_device(_dev) container_of((_dev), struct sysfb_device, dev) + +/** + * sysfb_driver + * @type_mask: mask of device-types that are supported (@sysfb_type) + * @allow_tainted: whether the driver can be bound to tainted devices + * @driver: driver object + * @probe: probe callback + * @remove: remove callback + * + * Each generic framebuffer driver must provide this structure when registering + * to the sysfb core. The @driver field must also be provided by the caller + * except for the 'driver.bus' field which is initialized by the core. + */ +struct sysfb_driver { + unsigned int type_mask; + bool allow_tainted; + struct device_driver driver; + + int (*probe) (struct sysfb_device *dev); + void (*remove) (struct sysfb_device *dev); +}; + +#define to_sysfb_driver(_drv) container_of((_drv), struct sysfb_driver, driver) + +/** + * sysfb_register_driver + * @drv: Driver object + * + * Register a new driver on the sysfb bus. + */ +int sysfb_register_driver(struct sysfb_driver *drv); + +/** + * sysfb_unregister_driver + * @drv: Driver object + * + * Remove a driver from the sysfb bus. + */ +void sysfb_unregister_driver(struct sysfb_driver *drv); + +/** + * sysfb_claim + * @types: Bitmask of sysfb_type flags + * + * Unbind all drivers from all devices matching the given types and prevent + * further drivers to get loaded on these types of devices. This allows real + * hardware drivers that are loaded by other bus-types (eg. pci-bus) to prevent + * any generic driver from using the given framebuffer types. + * + * Return 0 if the types could be claimed, otherwise a negative error code + * is returned. + */ +int sysfb_claim(unsigned int types); + +/** + * sysfb_release + * @types: Bitmask of sysfb_type flags + * + * Releases the given previously claimed types. See sysfb_claim(). This does + * not check whether the types are actually claimed or who claimed them. So make + * sure to call this only when you really claimed these types previously. + */ +void sysfb_release(unsigned int types); + +/** + * sysfb_taint + * @sdev: sysfb device + * @set: whether to taint or untaint + * + * This taints a given sysfb device. This should be done by all drivers if they + * change the framebuffer device in a way that other generic drivers might not + * be able to detect afterwards. + * This includes changing the resolution or properties of a framebuffer without + * adjusting the screen_info object. + * This can be reset to 'false' after all the changes have been undone. + * + * This is an unlocked function. You must call it from within your probe/remove + * callbacks in the driver. + */ +void sysfb_taint(struct sysfb_device *sdev, bool set); + +#endif /* __LINUX_SYSFB_H_ */
From: David Herrmann dh.herrmann@googlemail.com
This adds the VESA BIOS Extension (VBE) device type. Platform code needs to provide the "vbefb" platform-device with a screen_info structure as platform code.
All drivers that depend on VBE can now register as bus drivers and bind to SYSFB_VBE devices. There is no distinction between graphics framebuffers or plain text VGA. Drivers ought to inspect the screen_info and return -ENODEV during probe() is they cannot make use of the device.
Only one framebuffer of type SYSFB_VBE is available on a system at a time.
Signed-off-by: David Herrmann dh.herrmann@googlemail.com --- drivers/video/sysfb.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/sysfb.h | 5 +++- 2 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/drivers/video/sysfb.c b/drivers/video/sysfb.c index 8249006..5b47a9a 100644 --- a/drivers/video/sysfb.c +++ b/drivers/video/sysfb.c @@ -205,6 +205,72 @@ void sysfb_taint(struct sysfb_device *sdev, bool set) } EXPORT_SYMBOL(sysfb_taint);
+static void sysfb_dev_release(struct device *dev) +{ + struct sysfb_device *sdev = to_sysfb_device(dev); + + kfree(sdev); +} + +static struct sysfb_device *sysfb_dev_new(struct device *parent) +{ + struct sysfb_device *sdev; + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return NULL; + + device_initialize(&sdev->dev); + sdev->dev.release = sysfb_dev_release; + sdev->dev.bus = &sysfb_bus_type; + sdev->dev.parent = parent; + + return sdev; +} + +static int sysfb_vbe_probe(struct platform_device *pdev) +{ + int ret; + struct sysfb_device *sdev; + + sdev = sysfb_dev_new(&pdev->dev); + if (!sdev) + return -ENOMEM; + + dev_set_name(&sdev->dev, "vbefb"); + sdev->type = SYSFB_VBE; + sdev->screen = pdev->dev.platform_data; + + ret = device_add(&sdev->dev); + if (ret) + goto err_free; + + platform_set_drvdata(pdev, sdev); + return 0; + +err_free: + put_device(&sdev->dev); + return ret; +} + +static int sysfb_vbe_remove(struct platform_device *pdev) +{ + struct sysfb_device *sdev = platform_get_drvdata(pdev); + + device_del(&sdev->dev); + put_device(&sdev->dev); + return 0; +} + +static struct platform_driver sysfb_vbe_driver = { + .driver = { + .name = "vbefb", + .owner = THIS_MODULE, + }, + .probe = sysfb_vbe_probe, + .remove = sysfb_vbe_remove, +}; + static int __init sysfb_init(void) { int ret; @@ -215,11 +281,22 @@ static int __init sysfb_init(void) return ret; }
+ ret = platform_driver_register(&sysfb_vbe_driver); + if (ret) { + pr_err("cannot register VBE framebuffer driver\n"); + goto err_bus; + } + return 0; + +err_bus: + bus_unregister(&sysfb_bus_type); + return ret; }
static void __exit sysfb_exit(void) { + platform_driver_unregister(&sysfb_vbe_driver); bus_unregister(&sysfb_bus_type); }
diff --git a/include/linux/sysfb.h b/include/linux/sysfb.h index 6cd3c24..1796c1e 100644 --- a/include/linux/sysfb.h +++ b/include/linux/sysfb.h @@ -20,6 +20,7 @@
/** * sysfb_type + * @SYSFB_VBE: VESA BIOS Extension compatible device (includes VGA devices) * * Different types of available framebuffer devices. Only one device of each * type can be available at a time. In most systems there even is only one @@ -29,7 +30,9 @@ * devices. */ enum sysfb_type { - SYSFB_TYPES = 0, + SYSFB_VBE = 0x01, + + SYSFB_TYPES = SYSFB_VBE, };
/**
From: David Herrmann dh.herrmann@googlemail.com
HACK: This should be provided by architecture setup code. But to show how it is supposed to work, we now simply add a "vbefb" device during initialization.
The better way to do this is by moving this into arch-code. So for instance the x86 boot initialization should create this platform-device after VBE/VESA screen detection. Other architectures can do the same or introduce other framebuffer types than SYSFB_VBE.
Signed-off-by: David Herrmann dh.herrmann@googlemail.com --- drivers/video/sysfb.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/drivers/video/sysfb.c b/drivers/video/sysfb.c index 5b47a9a..0c0a4e7 100644 --- a/drivers/video/sysfb.c +++ b/drivers/video/sysfb.c @@ -271,6 +271,8 @@ static struct platform_driver sysfb_vbe_driver = { .remove = sysfb_vbe_remove, };
+static struct platform_device *sysfb_vbe_device; + static int __init sysfb_init(void) { int ret; @@ -287,6 +289,12 @@ static int __init sysfb_init(void) goto err_bus; }
+ sysfb_vbe_device = platform_device_register_data(NULL, "vbefb", -1, + &screen_info, + sizeof(screen_info)); + if (!sysfb_vbe_device) + pr_warn("cannot create vbefb device\n"); + return 0;
err_bus:
From: David Herrmann dh.herrmann@googlemail.com
Fix the vesafb module to no longer use any static __init data. Also add a module_exit() function that destroys the platform device.
Note that fbdev hotplugging is broken and the self-reference actually prevents sane module-unloading. Anyway, this at least allows delayed module loading of vesafb and helps debugging vesafb a lot.
Signed-off-by: David Herrmann dh.herrmann@googlemail.com --- drivers/video/Kconfig | 2 +- drivers/video/vesafb.c | 100 +++++++++++++++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 38 deletions(-)
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index eac56ef..d5723c2 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -770,7 +770,7 @@ config FB_UVESA If unsure, say N.
config FB_VESA - bool "VESA VGA graphics support" + tristate "VESA VGA graphics support" depends on (FB = y) && X86 select FB_CFB_FILLRECT select FB_CFB_COPYAREA diff --git a/drivers/video/vesafb.c b/drivers/video/vesafb.c index 501b340..4ad7b40 100644 --- a/drivers/video/vesafb.c +++ b/drivers/video/vesafb.c @@ -29,27 +29,10 @@
/* --------------------------------------------------------------------- */
-static struct fb_var_screeninfo vesafb_defined __initdata = { - .activate = FB_ACTIVATE_NOW, - .height = -1, - .width = -1, - .right_margin = 32, - .upper_margin = 16, - .lower_margin = 4, - .vsync_len = 4, - .vmode = FB_VMODE_NONINTERLACED, -}; - -static struct fb_fix_screeninfo vesafb_fix __initdata = { - .id = "VESA VGA", - .type = FB_TYPE_PACKED_PIXELS, - .accel = FB_ACCEL_NONE, -}; - static int inverse __read_mostly; static int mtrr __read_mostly; /* disable mtrr */ -static int vram_remap __initdata; /* Set amount of memory to be used */ -static int vram_total __initdata; /* Set total amount of memory */ +static int vram_remap; /* Set amount of memory to be used */ +static int vram_total; /* Set total amount of memory */ static int pmi_setpal __read_mostly = 1; /* pmi for palette changes ??? */ static int ypan __read_mostly; /* 0..nothing, 1..ypan, 2..ywrap */ static void (*pmi_start)(void) __read_mostly; @@ -58,6 +41,11 @@ static int depth __read_mostly; static int vga_compat __read_mostly; /* --------------------------------------------------------------------- */
+struct vesafb_par { + struct fb_ops ops; + u32 palette[256]; +}; + static int vesafb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { @@ -182,7 +170,7 @@ static void vesafb_destroy(struct fb_info *info) framebuffer_release(info); }
-static struct fb_ops vesafb_ops = { +static const struct fb_ops vesafb_ops = { .owner = THIS_MODULE, .fb_destroy = vesafb_destroy, .fb_setcolreg = vesafb_setcolreg, @@ -192,7 +180,7 @@ static struct fb_ops vesafb_ops = { .fb_imageblit = cfb_imageblit, };
-static int __init vesafb_setup(char *options) +static int vesafb_setup(char *options) { char *this_opt; @@ -226,13 +214,29 @@ static int __init vesafb_setup(char *options) return 0; }
-static int __init vesafb_probe(struct platform_device *dev) +static int vesafb_probe(struct platform_device *dev) { struct fb_info *info; + struct vesafb_par *par; int i, err; unsigned int size_vmode; unsigned int size_remap; unsigned int size_total; + struct fb_var_screeninfo vesafb_defined = { + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .right_margin = 32, + .upper_margin = 16, + .lower_margin = 4, + .vsync_len = 4, + .vmode = FB_VMODE_NONINTERLACED, + }; + struct fb_fix_screeninfo vesafb_fix = { + .id = "VESA VGA", + .type = FB_TYPE_PACKED_PIXELS, + .accel = FB_ACCEL_NONE, + };
if (screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB) return -ENODEV; @@ -287,13 +291,14 @@ static int __init vesafb_probe(struct platform_device *dev) spaces our resource handlers simply don't know about */ }
- info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev); + info = framebuffer_alloc(sizeof(struct vesafb_par), &dev->dev); if (!info) { release_mem_region(vesafb_fix.smem_start, size_total); return -ENOMEM; } - info->pseudo_palette = info->par; - info->par = NULL; + par = info->par; + par->ops = vesafb_ops; + info->pseudo_palette = par->palette;
/* set vesafb aperture size for generic probing */ info->apertures = alloc_apertures(1); @@ -466,7 +471,7 @@ static int __init vesafb_probe(struct platform_device *dev) vesafb_fix.smem_start, info->screen_base, size_remap/1024, size_total/1024);
- info->fbops = &vesafb_ops; + info->fbops = &par->ops; info->var = vesafb_defined; info->fix = vesafb_fix; info->flags = FBINFO_FLAG_DEFAULT | FBINFO_MISC_FIRMWARE | @@ -486,6 +491,7 @@ static int __init vesafb_probe(struct platform_device *dev) } printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node, info->fix.id); + platform_set_drvdata(dev, info); return 0; err: if (info->screen_base) @@ -495,7 +501,17 @@ err: return err; }
+static int vesafb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + unregister_framebuffer(info); + return 0; +} + static struct platform_driver vesafb_driver = { + .probe = vesafb_probe, + .remove = vesafb_remove, .driver = { .name = "vesafb", }, @@ -512,24 +528,34 @@ static int __init vesafb_init(void) fb_get_options("vesafb", &option); vesafb_setup(option);
- vesafb_device = platform_device_alloc("vesafb", 0); + vesafb_device = platform_device_alloc("vesafb", -1); if (!vesafb_device) return -ENOMEM;
+ ret = platform_driver_register(&vesafb_driver); + if (ret) + goto err_dev; + ret = platform_device_add(vesafb_device); - if (!ret) { - ret = platform_driver_probe(&vesafb_driver, vesafb_probe); - if (ret) - platform_device_del(vesafb_device); - } + if (ret) + goto err_drv;
- if (ret) { - platform_device_put(vesafb_device); - vesafb_device = NULL; - } + return 0;
+err_drv: + platform_driver_unregister(&vesafb_driver); +err_dev: + platform_device_put(vesafb_device); return ret; } -module_init(vesafb_init);
+static void __exit vesafb_exit(void) +{ + platform_device_del(vesafb_device); + platform_device_put(vesafb_device); + platform_driver_unregister(&vesafb_driver); +} + +module_init(vesafb_init); +module_exit(vesafb_exit); MODULE_LICENSE("GPL");
Instead of using our own platform device, we now register as sysfb driver and get notified whenever we are loaded on a system VBE device. This allows other VBE drivers to be loaded at the same time and users can bind/unbind drivers via sysfs. We also no longer need to fake a platform-device because the sysfb bus provides all devices now.
Signed-off-by: David Herrmann dh.herrmann@gmail.com --- drivers/video/Kconfig | 1 + drivers/video/vesafb.c | 49 +++++++++++++++---------------------------------- 2 files changed, 16 insertions(+), 34 deletions(-)
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index d5723c2..5c23d32 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -776,6 +776,7 @@ config FB_VESA select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT select FB_BOOT_VESA_SUPPORT + select SYSFB help This is the frame buffer device driver for generic VESA 2.0 compliant graphic cards. The older VESA 1.2 cards are not supported. diff --git a/drivers/video/vesafb.c b/drivers/video/vesafb.c index 4ad7b40..652858f 100644 --- a/drivers/video/vesafb.c +++ b/drivers/video/vesafb.c @@ -19,6 +19,7 @@ #include <linux/init.h> #include <linux/platform_device.h> #include <linux/screen_info.h> +#include <linux/sysfb.h>
#include <video/vga.h> #include <asm/io.h> @@ -214,7 +215,7 @@ static int vesafb_setup(char *options) return 0; }
-static int vesafb_probe(struct platform_device *dev) +static int vesafb_probe(struct sysfb_device *dev) { struct fb_info *info; struct vesafb_par *par; @@ -491,7 +492,7 @@ static int vesafb_probe(struct platform_device *dev) } printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node, info->fix.id); - platform_set_drvdata(dev, info); + dev_set_drvdata(&dev->dev, info); return 0; err: if (info->screen_base) @@ -501,59 +502,39 @@ err: return err; }
-static int vesafb_remove(struct platform_device *dev) +static void vesafb_remove(struct sysfb_device *dev) { - struct fb_info *info = platform_get_drvdata(dev); + struct fb_info *info = dev_get_drvdata(&dev->dev);
unregister_framebuffer(info); - return 0; }
-static struct platform_driver vesafb_driver = { +static struct sysfb_driver vesafb_driver = { + .type_mask = SYSFB_VBE, + .allow_tainted = false, + .driver = { + .name = "vesafb", + .owner = THIS_MODULE, + .mod_name = KBUILD_MODNAME, + }, .probe = vesafb_probe, .remove = vesafb_remove, - .driver = { - .name = "vesafb", - }, };
-static struct platform_device *vesafb_device; - static int __init vesafb_init(void) { - int ret; char *option = NULL;
/* ignore error return of fb_get_options */ fb_get_options("vesafb", &option); vesafb_setup(option);
- vesafb_device = platform_device_alloc("vesafb", -1); - if (!vesafb_device) - return -ENOMEM; - - ret = platform_driver_register(&vesafb_driver); - if (ret) - goto err_dev; - - ret = platform_device_add(vesafb_device); - if (ret) - goto err_drv; - - return 0; - -err_drv: - platform_driver_unregister(&vesafb_driver); -err_dev: - platform_device_put(vesafb_device); - return ret; + return sysfb_register_driver(&vesafb_driver); }
static void __exit vesafb_exit(void) { - platform_device_del(vesafb_device); - platform_device_put(vesafb_device); - platform_driver_unregister(&vesafb_driver); + sysfb_unregister_driver(&vesafb_driver); }
module_init(vesafb_init);
This provides a new DRM bus helper for the system framebuffer bus. It is very similar in its functionality to the DRM_USB helper. It allows to write DRM drivers that register as SYSFB drivers to the system.
Signed-off-by: David Herrmann dh.herrmann@gmail.com --- drivers/gpu/drm/Kconfig | 5 ++ drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_sysfb.c | 145 ++++++++++++++++++++++++++++++++++++++++++++ include/drm/drmP.h | 4 ++ include/drm/drm_sysfb.h | 35 +++++++++++ 5 files changed, 190 insertions(+) create mode 100644 drivers/gpu/drm/drm_sysfb.c create mode 100644 include/drm/drm_sysfb.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 18321b68b..2df448e 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -25,6 +25,11 @@ config DRM_USB depends on USB_SUPPORT && USB_ARCH_HAS_HCD select USB
+config DRM_SYSFB + tristate + depends on DRM + select SYSFB + config DRM_KMS_HELPER tristate depends on DRM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 2ff5cef..9249b66 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -29,6 +29,7 @@ CFLAGS_drm_trace_points.o := -I$(src)
obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_USB) += drm_usb.o +obj-$(CONFIG_DRM_SYSFB) += drm_sysfb.o obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_TDFX) += tdfx/ obj-$(CONFIG_DRM_R128) += r128/ diff --git a/drivers/gpu/drm/drm_sysfb.c b/drivers/gpu/drm/drm_sysfb.c new file mode 100644 index 0000000..4e8a823 --- /dev/null +++ b/drivers/gpu/drm/drm_sysfb.c @@ -0,0 +1,145 @@ +/* + * DRM Bus for sysfb drivers + * + * Copyright 2013 David Herrmann dh.herrmann@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <linux/export.h> +#include <linux/module.h> +#include <linux/sysfb.h> +#include <drm/drmP.h> + +int drm_get_sysfb_dev(struct sysfb_device *sdev, + struct drm_driver *driver) +{ + struct drm_device *dev; + int ret; + + DRM_DEBUG("\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev_set_drvdata(&sdev->dev, dev); + dev->sysfbdev = sdev; + dev->dev = &sdev->dev; + + mutex_lock(&drm_global_mutex); + + ret = drm_fill_in_dev(dev, NULL, driver); + if (ret) { + DRM_ERROR("drm_fill_in_dev failed\n"); + goto err_unlock; + } + + if (drm_core_check_feature(dev, DRIVER_MODESET)) { + ret = drm_get_minor(dev, &dev->control, DRM_MINOR_CONTROL); + if (ret) + goto err_unlock; + } + + ret = drm_get_minor(dev, &dev->primary, DRM_MINOR_LEGACY); + if (ret) + goto err_ctrl; + + if (dev->driver->load) { + ret = dev->driver->load(dev, 0); + if (ret) + goto err_primary; + } + + if (drm_core_check_feature(dev, DRIVER_MODESET)) { + ret = drm_mode_group_init_legacy_group(dev, + &dev->primary->mode_group); + if (ret) + goto err_primary; + } + + list_add_tail(&dev->driver_item, &driver->device_list); + + mutex_unlock(&drm_global_mutex); + + DRM_INFO("initialized %s %d.%d.%d %s on minor %d\n", + driver->name, driver->major, driver->minor, driver->patchlevel, + driver->date, dev->primary->index); + + return 0; + +err_primary: + drm_put_minor(&dev->primary); +err_ctrl: + if (drm_core_check_feature(dev, DRIVER_MODESET)) + drm_put_minor(&dev->control); +err_unlock: + mutex_unlock(&drm_global_mutex); + kfree(dev); + return ret; +} +EXPORT_SYMBOL(drm_get_sysfb_dev); + +static int drm_sysfb_get_irq(struct drm_device *dev) +{ + return 0; +} + +static const char *drm_sysfb_get_name(struct drm_device *dev) +{ + return dev_name(&dev->sysfbdev->dev); +} + +static int drm_sysfb_set_busid(struct drm_device *dev, + struct drm_master *master) +{ + return 0; +} + +static struct drm_bus drm_sysfb_bus = { + .bus_type = DRIVER_BUS_SYSFB, + .get_irq = drm_sysfb_get_irq, + .get_name = drm_sysfb_get_name, + .set_busid = drm_sysfb_set_busid, +}; + +int drm_sysfb_init(struct drm_driver *driver, struct sysfb_driver *sdrv) +{ + DRM_DEBUG("\n"); + + INIT_LIST_HEAD(&driver->device_list); + driver->kdriver.sysfb = sdrv; + driver->bus = &drm_sysfb_bus; + + return sysfb_register_driver(sdrv); +} +EXPORT_SYMBOL(drm_sysfb_init); + +void drm_sysfb_exit(struct drm_driver *driver, struct sysfb_driver *sdrv) +{ + DRM_DEBUG("\n"); + + sysfb_unregister_driver(sdrv); + DRM_INFO("module unloaded\n"); +} +EXPORT_SYMBOL(drm_sysfb_exit); + +MODULE_AUTHOR("David Herrmann dh.herrmann@gmail.com"); +MODULE_DESCRIPTION("DRM sysfb support"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 3fd8280..e5d73fe 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -71,6 +71,7 @@ #endif #include <linux/workqueue.h> #include <linux/poll.h> +#include <linux/sysfb.h> #include <asm/pgalloc.h> #include <drm/drm.h> #include <drm/drm_sarea.h> @@ -157,6 +158,7 @@ int drm_err(const char *func, const char *format, ...); #define DRIVER_BUS_PCI 0x1 #define DRIVER_BUS_PLATFORM 0x2 #define DRIVER_BUS_USB 0x3 +#define DRIVER_BUS_SYSFB 0x4
/***********************************************************************/ /** \name Begin the DRM... */ @@ -953,6 +955,7 @@ struct drm_driver { struct pci_driver *pci; struct platform_device *platform_device; struct usb_driver *usb; + struct sysfb_driver *sysfb; } kdriver; struct drm_bus *bus;
@@ -1173,6 +1176,7 @@ struct drm_device {
struct platform_device *platformdev; /**< Platform device struture */ struct usb_device *usbdev; + struct sysfb_device *sysfbdev;
struct drm_sg_mem *sg; /**< Scatter gather memory */ unsigned int num_crtcs; /**< Number of CRTCs on this device */ diff --git a/include/drm/drm_sysfb.h b/include/drm/drm_sysfb.h new file mode 100644 index 0000000..ced54d7 --- /dev/null +++ b/include/drm/drm_sysfb.h @@ -0,0 +1,35 @@ +#ifndef __DRM_SYSFB_H +#define __DRM_SYSFB_H +/* + * DRM Bus for sysfb drivers + * + * Copyright 2013 David Herrmann dh.herrmann@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <drmP.h> +#include <linux/sysfb.h> + +int drm_sysfb_init(struct drm_driver *driver, struct sysfb_driver *sdrv); +void drm_sysfb_exit(struct drm_driver *driver, struct sysfb_driver *sdrv); +int drm_get_sysfb_dev(struct sysfb_device *sdev, + struct drm_driver *driver); + +#endif /* __DRM_SYSFB_H */
This driver uses the VESA BIOS Extensions to control the display device. Modesetting has to be done during the boot-process by the architecture code (same way as vesafb requires it). No runtime modesetting is allowed due to RealMode/ProtectedMode restrictions.
This patch only introduces the stub DRM driver without any VESA/VBE backend that actually touches the hardware.
The driver simply provides a single crtc+encoder+connector combination that user-space can use to access the VBE framebuffer. No page-flips are supported and users must explicitly mark buffers as dirty to get them copied into the framebuffer.
All buffer objects are backed by shmem so we can later add PRIME support.
Signed-off-by: David Herrmann dh.herrmann@gmail.com --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/dvbe/Kconfig | 28 +++ drivers/gpu/drm/dvbe/Makefile | 4 + drivers/gpu/drm/dvbe/dvbe.h | 73 +++++++ drivers/gpu/drm/dvbe/dvbe_drv.c | 104 ++++++++++ drivers/gpu/drm/dvbe/dvbe_main.c | 399 +++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/dvbe/dvbe_mem.c | 269 ++++++++++++++++++++++++++ 8 files changed, 880 insertions(+) create mode 100644 drivers/gpu/drm/dvbe/Kconfig create mode 100644 drivers/gpu/drm/dvbe/Makefile create mode 100644 drivers/gpu/drm/dvbe/dvbe.h create mode 100644 drivers/gpu/drm/dvbe/dvbe_drv.c create mode 100644 drivers/gpu/drm/dvbe/dvbe_main.c create mode 100644 drivers/gpu/drm/dvbe/dvbe_mem.c
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 2df448e..fbbdabc 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -215,3 +215,5 @@ source "drivers/gpu/drm/mgag200/Kconfig" source "drivers/gpu/drm/cirrus/Kconfig"
source "drivers/gpu/drm/shmobile/Kconfig" + +source "drivers/gpu/drm/dvbe/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 9249b66..ec91ae8 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -49,4 +49,5 @@ obj-$(CONFIG_DRM_GMA500) += gma500/ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ +obj-$(CONFIG_DRM_DVBE) += dvbe/ obj-y += i2c/ diff --git a/drivers/gpu/drm/dvbe/Kconfig b/drivers/gpu/drm/dvbe/Kconfig new file mode 100644 index 0000000..bb3aa7b --- /dev/null +++ b/drivers/gpu/drm/dvbe/Kconfig @@ -0,0 +1,28 @@ +config DRM_DVBE + tristate "VESA BIOS Extension DRM Driver" + depends on DRM + select DRM_KMS_HELPER + select DRM_SYSFB + help + This is a DRM/KMS driver for VESA BIOS Extension (VBE) compatible + cards. At least VBE 2.0 is needed. Older VBE 1.2 cards are not + supported. + Nearly all modern x86 graphics cards support VBE 2.0 so this driver + should work with all those graphics cards. However, it does not allow + mode-switching during runtime but requires the kernel to setup the + mode with the vga= kernel command line option. If you want full + support for your graphics card, please select a driver for your + specific model. + + This driver can be enabled together with any other DRM graphics + driver. If another driver probes a device that conflicts with DVBE, + then DVBE will automatically drop the device and let the + model-specific driver take precedence. + + This driver replaces the old vesafb framebuffer driver and provides + full backwards compatibility. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called dvbe. diff --git a/drivers/gpu/drm/dvbe/Makefile b/drivers/gpu/drm/dvbe/Makefile new file mode 100644 index 0000000..b053da3 --- /dev/null +++ b/drivers/gpu/drm/dvbe/Makefile @@ -0,0 +1,4 @@ +ccflags-y := -Iinclude/drm + +dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o +obj-$(CONFIG_DRM_DVBE) := dvbe.o diff --git a/drivers/gpu/drm/dvbe/dvbe.h b/drivers/gpu/drm/dvbe/dvbe.h new file mode 100644 index 0000000..0235a95 --- /dev/null +++ b/drivers/gpu/drm/dvbe/dvbe.h @@ -0,0 +1,73 @@ +/* + * DRM VESA BIOS Extension Driver + * 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. + */ + +#ifndef DVBE_DRV_H +#define DVBE_DRV_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> + +/* dvbe devices */ + +struct dvbe_device { + struct drm_device *ddev; + + /* mode-setting objects */ + struct drm_crtc crtc; + struct drm_encoder enc; + struct drm_connector conn; + struct drm_display_mode *mode; +}; + +int dvbe_drm_load(struct drm_device *ddev, unsigned long flags); +int dvbe_drm_unload(struct drm_device *ddev); +int dvbe_drm_mmap(struct file *filp, struct vm_area_struct *vma); + +/* dvbe gem objects */ + +struct dvbe_gem_object { + struct drm_gem_object base; + struct page **pages; + uint8_t *vmapping; +}; + +#define to_dvbe_bo(x) container_of(x, struct dvbe_gem_object, base) + +int dvbe_gem_init_object(struct drm_gem_object *obj); +void dvbe_gem_free_object(struct drm_gem_object *obj); +int dvbe_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); +int dvbe_gem_vmap(struct dvbe_gem_object *obj); +void dvbe_gem_vunmap(struct dvbe_gem_object *obj); + +/* dumb buffers */ + +int dvbe_dumb_create(struct drm_file *file_priv, struct drm_device *ddev, + struct drm_mode_create_dumb *arg); +int dvbe_dumb_destroy(struct drm_file *file_priv, struct drm_device *ddev, + uint32_t handle); +int dvbe_dumb_map_offset(struct drm_file *file_priv, struct drm_device *ddev, + uint32_t handle, uint64_t *offset); + +/* dvbe framebuffers */ + +struct dvbe_framebuffer { + struct drm_framebuffer base; + struct dvbe_gem_object *obj; +}; + +#define to_dvbe_fb(x) container_of(x, struct dvbe_framebuffer, base) + +#endif /* DVBE_DRV_H */ diff --git a/drivers/gpu/drm/dvbe/dvbe_drv.c b/drivers/gpu/drm/dvbe/dvbe_drv.c new file mode 100644 index 0000000..98d4d37 --- /dev/null +++ b/drivers/gpu/drm/dvbe/dvbe_drv.c @@ -0,0 +1,104 @@ +/* + * DRM VESA BIOS Extension Driver + * 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/errno.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/sysfb.h> +#include <drm/drmP.h> +#include <drm/drm_sysfb.h> +#include "dvbe.h" + +static const struct vm_operations_struct dvbe_gem_vm_ops = { + .fault = dvbe_gem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct file_operations dvbe_drm_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .mmap = dvbe_drm_mmap, + .poll = drm_poll, + .read = drm_read, + .unlocked_ioctl = drm_ioctl, + .release = drm_release, + .fasync = drm_fasync, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .llseek = noop_llseek, +}; + +static struct drm_driver dvbe_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM, + .load = dvbe_drm_load, + .unload = dvbe_drm_unload, + .fops = &dvbe_drm_fops, + + .gem_init_object = dvbe_gem_init_object, + .gem_free_object = dvbe_gem_free_object, + .gem_vm_ops = &dvbe_gem_vm_ops, + + .dumb_create = dvbe_dumb_create, + .dumb_map_offset = dvbe_dumb_map_offset, + .dumb_destroy = dvbe_dumb_destroy, + + .name = "dvbe", + .desc = "DRM VESA BIOS Extension Driver", + .date = "20130127", + .major = 0, + .minor = 0, + .patchlevel = 1, +}; + +static int dvbe_sysfb_probe(struct sysfb_device *sdev) +{ + return drm_get_sysfb_dev(sdev, &dvbe_drm_driver); +} + +static void dvbe_sysfb_remove(struct sysfb_device *sdev) +{ + struct drm_device *ddev = dev_get_drvdata(&sdev->dev); + + drm_unplug_dev(ddev); +} + +static struct sysfb_driver dvbe_sysfb_driver = { + .type_mask = SYSFB_VBE, + .allow_tainted = false, + .driver = { + .name = "dvbe", + .owner = THIS_MODULE, + .mod_name = KBUILD_MODNAME, + }, + .probe = dvbe_sysfb_probe, + .remove = dvbe_sysfb_remove, +}; + +static int __init dvbe_init(void) +{ + return drm_sysfb_init(&dvbe_drm_driver, &dvbe_sysfb_driver); +} + +static void __exit dvbe_exit(void) +{ + drm_sysfb_exit(&dvbe_drm_driver, &dvbe_sysfb_driver); +} + +module_init(dvbe_init); +module_exit(dvbe_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Herrmann dh.herrmann@gmail.com"); +MODULE_DESCRIPTION("DRM VESA BIOS Extension Driver"); diff --git a/drivers/gpu/drm/dvbe/dvbe_main.c b/drivers/gpu/drm/dvbe/dvbe_main.c new file mode 100644 index 0000000..e73c77e --- /dev/null +++ b/drivers/gpu/drm/dvbe/dvbe_main.c @@ -0,0 +1,399 @@ +/* + * DRM VESA BIOS Extension Driver + * 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. + */ + +/* + * Main Modesetting and Control + * We register exactly one CRTC, encoder and connector on initialization. This + * simplifies the logic a lot. The actual device control is not implemented + * here. This file only provides the interaction with DRM core. + * + * Userspace has only one valid CRTC+encoder+connector combination which + * corresponds to the VESA framebuffer that we detected. The initial mode is + * deduced from the initial VESA configuration and should be used by user-space. + * Real mode-setting (i.e., changing display resolution) requires the processor + * to be in RealMode, which we cannot do in the kernel so it is not provided. + * Driver-specific ioctls may be used to allow userspace to perform mode-setting + * themself. + */ + +#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_helper.h> +#include "dvbe.h" + +/* crtcs */ + +static void dvbe_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + /* There is no way to tell DRM core that we do not support DPMS. + * Therefore, simply make this a no-op and always be online. */ +} + +static bool dvbe_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + drm_mode_copy(adjusted_mode, mode); + return true; +} + +static void dvbe_crtc_prepare(struct drm_crtc *crtc) +{ + /* nothing to prepare */ +} + +static void dvbe_crtc_commit(struct drm_crtc *crtc) +{ + /* all work already done by immediate mode-set */ +} + +static int dvbe_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb); + + /* We can scan out any framebuffer that is given. The framebuffer + * allocation guarantees that it is a valid framebuffer. If the x/y + * corrdinates are out of bounds, we fail. We don't care whether the + * framebuffer is bigger/smaller than the real screen. The + * blit-functions clip it correctly. + * The mode was already checked by mode_fixup above so we can savely let + * it pass. */ + + if (x >= dfb->base.width || y >= dfb->base.height) + return -EINVAL; + + return 0; +} + +/* + * Shortcut if no full mode-set is needed but only the offsets or framebuffer + * parameters changed. See @mode_set for details. + */ +static int dvbe_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *fb) +{ + struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb); + + if (x >= dfb->base.width || y >= dfb->base.height) + return -EINVAL; + + return 0; +} + +static const struct drm_crtc_helper_funcs dvbe_crtc_helper_ops = { + .dpms = dvbe_crtc_dpms, + .mode_fixup = dvbe_crtc_mode_fixup, + .prepare = dvbe_crtc_prepare, + .commit = dvbe_crtc_commit, + .mode_set = dvbe_crtc_mode_set, + .mode_set_base = dvbe_crtc_mode_set_base, +}; + +static const struct drm_crtc_funcs dvbe_crtc_ops = { + .destroy = drm_crtc_cleanup, + .set_config = drm_crtc_helper_set_config, +}; + +/* encoders */ + +static void dvbe_enc_dpms(struct drm_encoder *enc, int mode) +{ + /* DPMS is done by the CRTC */ +} + +static bool dvbe_enc_mode_fixup(struct drm_encoder *enc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* mode-fixup is done by the CRTC */ + return true; +} + +static void dvbe_enc_prepare(struct drm_encoder *enc) +{ + /* no preparation to do */ +} + +static void dvbe_enc_mode_set(struct drm_encoder *enc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* mode-setting is done by the CRTC */ +} + +static void dvbe_enc_commit(struct drm_encoder *enc) +{ + /* committing mode changes is done by the CRTC */ +} + +static const struct drm_encoder_helper_funcs dvbe_enc_helper_ops = { + .dpms = dvbe_enc_dpms, + .mode_fixup = dvbe_enc_mode_fixup, + .prepare = dvbe_enc_prepare, + .mode_set = dvbe_enc_mode_set, + .commit = dvbe_enc_commit, +}; + +static const struct drm_encoder_funcs dvbe_enc_ops = { + .destroy = drm_encoder_cleanup, +}; + +/* connectors */ + +static int dvbe_conn_get_modes(struct drm_connector *conn) +{ + return 0; +} + +static int dvbe_conn_mode_valid(struct drm_connector *conn, + struct drm_display_mode *mode) +{ + /* every mode of our own is valid */ + return MODE_OK; +} + +static struct drm_encoder *dvbe_conn_best_encoder(struct drm_connector *conn) +{ + struct dvbe_device *dvbe = conn->dev->dev_private; + + /* We only have a single encoder for the VBE device. Hence, return it + * as best-encoder so it is always used. There is no real encoder that + * we control as VBE doesn't provide such information. */ + return &dvbe->enc; +} + +static const struct drm_connector_helper_funcs dvbe_conn_helper_ops = { + .get_modes = dvbe_conn_get_modes, + .mode_valid = dvbe_conn_mode_valid, + .best_encoder = dvbe_conn_best_encoder, +}; + +static enum drm_connector_status dvbe_conn_detect(struct drm_connector *conn, + bool force) +{ + /* We simulate an always connected monitor. The VESA ABI 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 void dvbe_conn_destroy(struct drm_connector *conn) +{ + /* Remove the fake-connector from sysfs and then let the DRM core + * clean up all associated resources. */ + if (device_is_registered(&conn->kdev)) + drm_sysfs_connector_remove(conn); + drm_connector_cleanup(conn); +} + +static const struct drm_connector_funcs dvbe_conn_ops = { + .dpms = drm_helper_connector_dpms, + .detect = dvbe_conn_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = dvbe_conn_destroy, +}; + +/* framebuffers */ + +static int dvbe_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *dfile, + unsigned int *handle) +{ + struct dvbe_framebuffer *dfb = to_dvbe_fb(fb); + + return drm_gem_handle_create(dfile, &dfb->obj->base, handle); +} + +static int dvbe_fb_dirty(struct drm_framebuffer *fb, struct drm_file *file, + unsigned int flags, unsigned int color, + struct drm_clip_rect *clips, unsigned int num) +{ + struct dvbe_device *dvbe = fb->dev->dev_private; + + if (dvbe->crtc.fb != fb) + return 0; + + return 0; +} + +static void dvbe_fb_destroy(struct drm_framebuffer *fb) +{ + struct dvbe_framebuffer *dfb = to_dvbe_fb(fb); + + drm_framebuffer_cleanup(fb); + drm_gem_object_unreference_unlocked(&dfb->obj->base); + kfree(dfb); +} + +static const struct drm_framebuffer_funcs dvbe_fb_ops = { + .create_handle = dvbe_fb_create_handle, + .dirty = dvbe_fb_dirty, + .destroy = dvbe_fb_destroy, +}; + +static struct drm_framebuffer *dvbe_fb_create(struct drm_device *ddev, + struct drm_file *dfile, + struct drm_mode_fb_cmd2 *cmd) +{ + struct dvbe_framebuffer *dfb; + int ret; + struct drm_gem_object *dobj; + unsigned int Bpp; + size_t siz; + void *err; + + if (cmd->flags) + return ERR_PTR(-EINVAL); + + switch (cmd->pixel_format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + Bpp = 2; + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + Bpp = 4; + break; + default: + return ERR_PTR(-EINVAL); + } + + siz = cmd->pitches[0] * (cmd->height - 1) + + Bpp * cmd->width + cmd->offsets[0]; + + dobj = drm_gem_object_lookup(ddev, dfile, cmd->handles[0]); + if (!dobj) + return ERR_PTR(-EINVAL); + if (dobj->size < siz) { + err = ERR_PTR(-ENOMEM); + goto err_unref; + } + + dfb = kzalloc(sizeof(*dfb), GFP_KERNEL); + if (!dfb) { + err = ERR_PTR(-ENOMEM); + goto err_unref; + } + dfb->obj = to_dvbe_bo(dobj); + + ret = drm_helper_mode_fill_fb_struct(&dfb->base, cmd); + if (ret < 0) { + err = ERR_PTR(ret); + goto err_free; + } + + ret = drm_framebuffer_init(ddev, &dfb->base, &dvbe_fb_ops); + if (ret < 0) { + err = ERR_PTR(ret); + goto err_free; + } + + return &dfb->base; + +err_free: + kfree(dfb); +err_unref: + drm_gem_object_unreference_unlocked(dobj); + return err; +} + +static const struct drm_mode_config_funcs dvbe_mode_config_ops = { + .fb_create = dvbe_fb_create, +}; + +/* initialization */ + +int dvbe_drm_load(struct drm_device *ddev, unsigned long flags) +{ + struct dvbe_device *dvbe; + int ret; + + dvbe = kzalloc(sizeof(*dvbe), GFP_KERNEL); + if (!dvbe) + return -ENOMEM; + + dvbe->ddev = ddev; + ddev->dev_private = dvbe; + + drm_mode_config_init(ddev); + ddev->mode_config.min_width = 0; + ddev->mode_config.min_height = 0; + ddev->mode_config.max_width = 4095; + ddev->mode_config.max_height = 4095; + ddev->mode_config.funcs = &dvbe_mode_config_ops; + + ret = drm_mode_create_dirty_info_property(ddev); + if (ret) + goto err_cleanup; + + ret = drm_crtc_init(ddev, &dvbe->crtc, &dvbe_crtc_ops); + if (ret) + goto err_cleanup; + drm_crtc_helper_add(&dvbe->crtc, &dvbe_crtc_helper_ops); + + dvbe->enc.possible_crtcs = 1; + dvbe->enc.possible_clones = 0; + ret = drm_encoder_init(ddev, &dvbe->enc, &dvbe_enc_ops, + DRM_MODE_ENCODER_VIRTUAL); + if (ret) + goto err_cleanup; + drm_encoder_helper_add(&dvbe->enc, &dvbe_enc_helper_ops); + + dvbe->conn.display_info.width_mm = 0; + dvbe->conn.display_info.height_mm = 0; + dvbe->conn.interlace_allowed = false; + dvbe->conn.doublescan_allowed = false; + dvbe->conn.polled = 0; + ret = drm_connector_init(ddev, &dvbe->conn, &dvbe_conn_ops, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto err_cleanup; + drm_connector_helper_add(&dvbe->conn, &dvbe_conn_helper_ops); + + drm_object_attach_property(&dvbe->conn.base, + ddev->mode_config.dirty_info_property, 1); + + ret = drm_mode_connector_attach_encoder(&dvbe->conn, &dvbe->enc); + if (ret) + goto err_cleanup; + + ret = drm_sysfs_connector_add(&dvbe->conn); + if (ret) + goto err_cleanup; + + return 0; + +err_cleanup: + drm_mode_config_cleanup(ddev); + kfree(dvbe); + return ret; +} + +int dvbe_drm_unload(struct drm_device *ddev) +{ + struct dvbe_device *dvbe = ddev->dev_private; + + drm_mode_config_cleanup(ddev); + kfree(dvbe); + + return 0; +} diff --git a/drivers/gpu/drm/dvbe/dvbe_mem.c b/drivers/gpu/drm/dvbe/dvbe_mem.c new file mode 100644 index 0000000..211aebf --- /dev/null +++ b/drivers/gpu/drm/dvbe/dvbe_mem.c @@ -0,0 +1,269 @@ +/* + * DRM VESA BIOS Extension Driver + * 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. + */ + +/* + * DVBA Memory Objects + * Due to the limited functionality of the VESA ABI, we support only one memory + * object type which is the framebuffer object. To allow file-descriptors for + * frambuffers we use shmem storage for all gem objects. + * + * Furthermore, we use lazy storage allocation. As long as the storage isn't + * mapped or accessed, we do not allocate it. If it is mapped, we allocate an + * array of pointers to the pages so we can easily vmap/vunmap it for CPU + * access. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/shmem_fs.h> +#include <linux/string.h> +#include <drm/drmP.h> +#include <drm/drm_mem_util.h> +#include "dvbe.h" + +static struct dvbe_gem_object *dvbe_gem_alloc_object(struct drm_device *ddev, + size_t size) +{ + struct dvbe_gem_object *obj; + + BUG_ON((size & (PAGE_SIZE - 1)) != 0); + + obj = kzalloc(sizeof(*obj), GFP_KERNEL); + if (!obj) + return NULL; + + if (drm_gem_object_init(ddev, &obj->base, size)) { + kfree(obj); + return NULL; + } + + return obj; +} + +/* + * First time user-space requests access to a gem object, we allocate an array + * of all backing pages so our page-fault handler can map them efficiently. + * shmem_read_mapping_page_gfp allocates missing pages with the given GFP mask + * which we deduce from the tmpfs-dentry object. + */ +static int dvbe_gem_get_pages(struct dvbe_gem_object *obj) +{ + size_t i, page_count = obj->base.size / PAGE_SIZE; + struct page *page; + struct address_space *mapping; + gfp_t gfp; + + if (obj->pages) + return 0; + + obj->pages = drm_malloc_ab(page_count, sizeof(struct page*)); + if (!obj->pages) + return -ENOMEM; + + mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping; + gfp = mapping_gfp_mask(mapping) | GFP_KERNEL; + + for (i = 0; i < page_count; ++i) { + page = shmem_read_mapping_page_gfp(mapping, i, gfp); + if (IS_ERR(page)) + goto err_pages; + + obj->pages[i] = page; + } + + return 0; + +err_pages: + while (i--) + page_cache_release(obj->pages[i]); + drm_free_large(obj->pages); + obj->pages = NULL; + return PTR_ERR(page); +} + +static void dvbe_gem_put_pages(struct dvbe_gem_object *obj) +{ + size_t i, page_count = obj->base.size / PAGE_SIZE; + + if (!obj->pages) + return; + + for (i = 0; i < page_count; ++i) + page_cache_release(obj->pages[i]); + + drm_free_large(obj->pages); + obj->pages = NULL; +} + +int dvbe_gem_vmap(struct dvbe_gem_object *obj) +{ + int ret, page_count = obj->base.size / PAGE_SIZE; + + if (obj->vmapping) + return 0; + + ret = dvbe_gem_get_pages(obj); + if (ret) + return ret; + + obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL); + if (!obj->vmapping) + return -ENOMEM; + + return 0; +} + +void dvbe_gem_vunmap(struct dvbe_gem_object *obj) +{ + if (!obj->vmapping) + return; + + vunmap(obj->vmapping); + obj->vmapping = NULL; +} + +/* drm_gem_object_alloc() is not supported */ +int dvbe_gem_init_object(struct drm_gem_object *gobj) +{ + BUG(); + + return -EINVAL; +} + +void dvbe_gem_free_object(struct drm_gem_object *gobj) +{ + struct dvbe_gem_object *obj = to_dvbe_bo(gobj); + + if (gobj->map_list.map) + drm_gem_free_mmap_offset(gobj); + dvbe_gem_vunmap(obj); + dvbe_gem_put_pages(obj); + drm_gem_object_release(gobj); + kfree(obj); +} + +/* + * On page-faults we need to calculate the page-offset ourself as the vma-offset + * is based on the fake-offset of drm_mmap() instead of any real offset. + * We simply insert the page via vm_insert_page() as it has been marked as + * VM_MIXEDMAP in the vma. + */ +int dvbe_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct dvbe_gem_object *obj = to_dvbe_bo(vma->vm_private_data); + unsigned long off, vaddr; + int ret; + + if (!obj->pages) + return VM_FAULT_SIGBUS; + + vaddr = (unsigned long)vmf->virtual_address; + off = (vaddr - vma->vm_start) >> PAGE_SHIFT; + + ret = vm_insert_page(vma, vaddr, obj->pages[off]); + switch (ret) { + case -ENOMEM: + return VM_FAULT_OOM; + case -EAGAIN: + set_need_resched(); + /* fallthrough */ + case -ERESTARTSYS: + case 0: + return VM_FAULT_NOPAGE; + } + + return VM_FAULT_SIGBUS; +} + +int dvbe_dumb_create(struct drm_file *dfile, struct drm_device *ddev, + struct drm_mode_create_dumb *args) +{ + struct dvbe_gem_object *obj; + int ret; + + args->pitch = args->width * ((args->bpp + 7) / 8); + args->size = args->pitch * args->height; + + if (!args->size) + return -EINVAL; + + args->size = roundup(args->size, PAGE_SIZE); + obj = dvbe_gem_alloc_object(ddev, args->size); + if (!obj) + return -ENOMEM; + + ret = drm_gem_handle_create(dfile, &obj->base, &args->handle); + if (ret) { + drm_gem_object_unreference(&obj->base); + return ret; + } + + drm_gem_object_unreference(&obj->base); + return 0; +} + +int dvbe_dumb_destroy(struct drm_file *dfile, struct drm_device *ddev, + uint32_t handle) +{ + return drm_gem_handle_delete(dfile, handle); +} + +int dvbe_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev, + uint32_t handle, uint64_t *offset) +{ + struct dvbe_gem_object *obj; + struct drm_gem_object *gobj; + int ret; + + mutex_lock(&ddev->struct_mutex); + gobj = drm_gem_object_lookup(ddev, dfile, handle); + if (!gobj) { + ret = -ENOENT; + goto out_unlock; + } + obj = to_dvbe_bo(gobj); + + ret = dvbe_gem_get_pages(obj); + if (ret) + goto out_unref; + + if (!gobj->map_list.map) { + ret = drm_gem_create_mmap_offset(gobj); + if (ret) + goto out_unref; + } + + *offset = gobj->map_list.hash.key << PAGE_SHIFT; + +out_unref: + drm_gem_object_unreference(gobj); +out_unlock: + mutex_unlock(&ddev->struct_mutex); + return ret; +} + +int dvbe_drm_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) + return ret; + + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_flags |= VM_MIXEDMAP; + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + + return ret; +}
Extend the dvbe core driver by a VESA/VBE backend that simply blits the data from a framebuffer into the hardware framebuffer on damage. Modesetting has to be done during the boot-process by the architecture code (same way as vesafb requires it). No runtime modesetting is allowed due to RealMode/ProtectedMode restrictions.
On dirty-ioctls we simply vmap the framebuffer memory and copy each pixel into the target framebuffer. Unfortunately, the VBE bpp/depth combinations cannot easily be forwarded to the user via the DRM API as it allows a lot more combinations. Hence, we need to convert each pixel from the user's buffer format into the target format while blitting. Fast-paths for xrgb32/etc. could be implemented if we want to improve blitting performance.
Signed-off-by: David Herrmann dh.herrmann@gmail.com --- drivers/gpu/drm/dvbe/Kconfig | 1 + drivers/gpu/drm/dvbe/Makefile | 2 +- drivers/gpu/drm/dvbe/dvbe.h | 25 ++++ drivers/gpu/drm/dvbe/dvbe_main.c | 39 +++++- drivers/gpu/drm/dvbe/dvbe_vesa.c | 263 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 325 insertions(+), 5 deletions(-) create mode 100644 drivers/gpu/drm/dvbe/dvbe_vesa.c
diff --git a/drivers/gpu/drm/dvbe/Kconfig b/drivers/gpu/drm/dvbe/Kconfig index bb3aa7b..e49df10 100644 --- a/drivers/gpu/drm/dvbe/Kconfig +++ b/drivers/gpu/drm/dvbe/Kconfig @@ -3,6 +3,7 @@ config DRM_DVBE depends on DRM select DRM_KMS_HELPER select DRM_SYSFB + select FB_BOOT_VESA_SUPPORT help This is a DRM/KMS driver for VESA BIOS Extension (VBE) compatible cards. At least VBE 2.0 is needed. Older VBE 1.2 cards are not diff --git a/drivers/gpu/drm/dvbe/Makefile b/drivers/gpu/drm/dvbe/Makefile index b053da3..f6fb888 100644 --- a/drivers/gpu/drm/dvbe/Makefile +++ b/drivers/gpu/drm/dvbe/Makefile @@ -1,4 +1,4 @@ ccflags-y := -Iinclude/drm
-dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o +dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o dvbe_vesa.o obj-$(CONFIG_DRM_DVBE) := dvbe.o diff --git a/drivers/gpu/drm/dvbe/dvbe.h b/drivers/gpu/drm/dvbe/dvbe.h index 0235a95..68fd452 100644 --- a/drivers/gpu/drm/dvbe/dvbe.h +++ b/drivers/gpu/drm/dvbe/dvbe.h @@ -25,6 +25,23 @@ struct dvbe_device { struct drm_device *ddev;
+ /* vbe information */ + unsigned long vbe_addr; + unsigned long vbe_vsize; + unsigned long vbe_size; + unsigned int vbe_depth; + unsigned int vbe_bpp; + unsigned int vbe_width; + unsigned int vbe_height; + unsigned int vbe_stride; + uint8_t vbe_red_size; + uint8_t vbe_red_pos; + uint8_t vbe_green_size; + uint8_t vbe_green_pos; + uint8_t vbe_blue_size; + uint8_t vbe_blue_pos; + uint8_t *vbe_map; + /* mode-setting objects */ struct drm_crtc crtc; struct drm_encoder enc; @@ -70,4 +87,12 @@ struct dvbe_framebuffer {
#define to_dvbe_fb(x) container_of(x, struct dvbe_framebuffer, base)
+/* vesa helpers */ + +int dvbe_vesa_init(struct dvbe_device *dvbe); +void dvbe_vesa_cleanup(struct dvbe_device *dvbe); +int dvbe_vesa_damage(struct dvbe_device *dvbe, struct dvbe_framebuffer *fb, + unsigned int flags, unsigned int color, + struct drm_clip_rect *clips, unsigned int num); + #endif /* DVBE_DRV_H */ diff --git a/drivers/gpu/drm/dvbe/dvbe_main.c b/drivers/gpu/drm/dvbe/dvbe_main.c index e73c77e..c418310 100644 --- a/drivers/gpu/drm/dvbe/dvbe_main.c +++ b/drivers/gpu/drm/dvbe/dvbe_main.c @@ -46,6 +46,15 @@ static bool dvbe_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { + struct dvbe_device *dvbe = crtc->dev->dev_private; + + if (mode->hdisplay != dvbe->vbe_width || + mode->vdisplay != dvbe->vbe_height) { + dev_dbg(dvbe->ddev->dev, "invalid mode %ux%u\n", + mode->hdisplay, mode->vdisplay); + return false; + } + drm_mode_copy(adjusted_mode, mode); return true; } @@ -66,6 +75,7 @@ static int dvbe_crtc_mode_set(struct drm_crtc *crtc, int x, int y, struct drm_framebuffer *old_fb) { + struct dvbe_device *dvbe = crtc->dev->dev_private; struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb);
/* We can scan out any framebuffer that is given. The framebuffer @@ -79,7 +89,7 @@ static int dvbe_crtc_mode_set(struct drm_crtc *crtc, if (x >= dfb->base.width || y >= dfb->base.height) return -EINVAL;
- return 0; + return dvbe_vesa_damage(dvbe, dfb, 0, 0, NULL, 0); }
/* @@ -89,12 +99,13 @@ static int dvbe_crtc_mode_set(struct drm_crtc *crtc, static int dvbe_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, struct drm_framebuffer *fb) { + struct dvbe_device *dvbe = crtc->dev->dev_private; struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb);
if (x >= dfb->base.width || y >= dfb->base.height) return -EINVAL;
- return 0; + return dvbe_vesa_damage(dvbe, dfb, 0, 0, NULL, 0); }
static const struct drm_crtc_helper_funcs dvbe_crtc_helper_ops = { @@ -159,7 +170,19 @@ static const struct drm_encoder_funcs dvbe_enc_ops = {
static int dvbe_conn_get_modes(struct drm_connector *conn) { - return 0; + struct dvbe_device *dvbe = conn->dev->dev_private; + struct drm_display_mode *mode; + + mode = drm_gtf_mode(dvbe->ddev, dvbe->vbe_width, dvbe->vbe_height, + 60, 0, 0); + if (!mode) + return 0; + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(conn, mode); + dvbe->mode = mode; + + return 1; }
static int dvbe_conn_mode_valid(struct drm_connector *conn, @@ -226,11 +249,12 @@ static int dvbe_fb_dirty(struct drm_framebuffer *fb, struct drm_file *file, struct drm_clip_rect *clips, unsigned int num) { struct dvbe_device *dvbe = fb->dev->dev_private; + struct dvbe_framebuffer *dfb = to_dvbe_fb(fb);
if (dvbe->crtc.fb != fb) return 0;
- return 0; + return dvbe_vesa_damage(dvbe, dfb, flags, color, clips, num); }
static void dvbe_fb_destroy(struct drm_framebuffer *fb) @@ -334,6 +358,10 @@ int dvbe_drm_load(struct drm_device *ddev, unsigned long flags) dvbe->ddev = ddev; ddev->dev_private = dvbe;
+ ret = dvbe_vesa_init(dvbe); + if (ret) + goto err_free; + drm_mode_config_init(ddev); ddev->mode_config.min_width = 0; ddev->mode_config.min_height = 0; @@ -384,6 +412,8 @@ int dvbe_drm_load(struct drm_device *ddev, unsigned long flags)
err_cleanup: drm_mode_config_cleanup(ddev); + dvbe_vesa_cleanup(dvbe); +err_free: kfree(dvbe); return ret; } @@ -393,6 +423,7 @@ int dvbe_drm_unload(struct drm_device *ddev) struct dvbe_device *dvbe = ddev->dev_private;
drm_mode_config_cleanup(ddev); + dvbe_vesa_cleanup(dvbe); kfree(dvbe);
return 0; diff --git a/drivers/gpu/drm/dvbe/dvbe_vesa.c b/drivers/gpu/drm/dvbe/dvbe_vesa.c new file mode 100644 index 0000000..c3f96a0 --- /dev/null +++ b/drivers/gpu/drm/dvbe/dvbe_vesa.c @@ -0,0 +1,263 @@ +/* + * DRM VESA BIOS Extension Driver + * 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. + */ + +/* + * VESA BIOS Extension Layer + * This layer provides access to the VBE data for the dvbe driver. It reads the + * mode information from the initial boot screen_info and initializes the + * framebuffer for user-mode access. + * + * This driver requires the VESA mode to be a TRUECOLOR format with a bpp value + * of 8, 15, 16 or 32. All other layouts are unsupported. + */ + +#include <asm/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/screen_info.h> +#include <drm/drmP.h> +#include "dvbe.h" + +static void dvbe_vesa_read(const uint8_t *src, unsigned int format, + uint8_t *r, uint8_t *g, uint8_t *b) +{ + uint32_t val; + + switch (format) { + case DRM_FORMAT_RGB565: + val = *(uint16_t*)src; + *r = (val & 0xf800) >> 11; + *g = (val & 0x07e0) >> 5; + *b = (val & 0x001f) >> 0; + break; + case DRM_FORMAT_BGR565: + val = *(uint16_t*)src; + *b = (val & 0xf800) >> 11; + *g = (val & 0x07e0) >> 5; + *r = (val & 0x001f) >> 0; + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + val = *(uint32_t*)src; + *r = (val & 0x00ff0000) >> 16; + *g = (val & 0x0000ff00) >> 8; + *b = (val & 0x000000ff) >> 0; + break; + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + val = *(uint32_t*)src; + *b = (val & 0x00ff0000) >> 16; + *g = (val & 0x0000ff00) >> 8; + *r = (val & 0x000000ff) >> 0; + break; + default: + *r = 0; + *g = 0; + *b = 0; + } +} + +static void dvbe_vesa_write(struct dvbe_device *dvbe, uint8_t *dst, + uint8_t r, uint8_t g, uint8_t b) +{ + uint32_t val; + + val = (r >> (8 - dvbe->vbe_red_size)) << dvbe->vbe_red_pos; + val |= (g >> (8 - dvbe->vbe_green_size)) << dvbe->vbe_green_pos; + val |= (b >> (8 - dvbe->vbe_blue_size)) << dvbe->vbe_blue_pos; + + switch (dvbe->vbe_bpp) { + case 8: + *dst = val & 0xff; + break; + case 16: + *((uint16_t*)dst) = val & 0xffff; + break; + case 32: + *((uint32_t*)dst) = val & 0xffffffff; + break; + } +} + +static void dvbe_vesa_blit(struct dvbe_device *dvbe, const uint8_t *src, + unsigned int src_stride, unsigned int src_f, + unsigned int src_bpp, unsigned int x, unsigned int y, + unsigned int width, unsigned int height) +{ + uint8_t *dst, *d, r, g, b; + const uint8_t *s; + unsigned int i, j, sBpp, dBpp; + + sBpp = src_bpp / 8; + dBpp = dvbe->vbe_bpp / 8; + src = src + y * src_stride + x * sBpp; + dst = dvbe->vbe_map + y * dvbe->vbe_stride + x * dBpp; + + for (i = 0; i < height; ++i) { + s = src; + d = dst; + for (j = 0; j < width; ++j) { + dvbe_vesa_read(src, src_f, &r, &g, &b); + dvbe_vesa_write(dvbe, d, r, g, b); + s += sBpp; + d += dBpp; + } + + src += src_stride; + dst += dvbe->vbe_stride; + } +} + +int dvbe_vesa_damage(struct dvbe_device *dvbe, struct dvbe_framebuffer *fb, + unsigned int flags, unsigned int color, + struct drm_clip_rect *clips, unsigned int num) +{ + unsigned int i, maxw, maxh; + unsigned int width, height, ret; + uint8_t *src; + bool annotated; + + ret = dvbe_gem_vmap(fb->obj); + if (ret) + return ret; + + annotated = flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY; + src = fb->obj->vmapping + fb->base.offsets[0]; + maxw = min(fb->base.width, dvbe->vbe_width); + maxh = min(fb->base.height, dvbe->vbe_height); + + if (!num) { + dvbe_vesa_blit(dvbe, src, fb->base.pitches[0], + fb->base.pixel_format, fb->base.bits_per_pixel, + 0, 0, maxw, maxh); + return 0; + } + + for (i = 0; i < num; ++i) { + if (annotated && !(i & 0x1)) + continue; + if (clips[i].x2 <= clips[i].x1 || clips[i].y2 <= clips[i].y1) + continue; + + /* clip to framebuffer size */ + if (clips[i].x1 >= maxw || + clips[i].y1 >= maxh) + continue; + if (clips[i].x2 > maxw) + width = maxw - clips[i].x1; + else + width = clips[i].x2 - clips[i].x1; + if (clips[i].y2 > maxh) + height = maxh - clips[i].y1; + else + height = clips[i].y2 - clips[i].y1; + + dvbe_vesa_blit(dvbe, src, fb->base.pitches[0], + fb->base.pixel_format, fb->base.bits_per_pixel, + clips[i].x1, clips[i].y1, width, height); + } + + return 0; +} + +int dvbe_vesa_init(struct dvbe_device *dvbe) +{ + int ret; + + if (screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB) { + dev_info(dvbe->ddev->dev, "no VBE capable device found\n"); + return -ENODEV; + } + + dvbe->vbe_addr = (unsigned long)screen_info.lfb_base; + dvbe->vbe_width = screen_info.lfb_width; + dvbe->vbe_height = screen_info.lfb_height; + dvbe->vbe_stride = screen_info.lfb_linelength; + dvbe->vbe_depth = screen_info.lfb_depth; + dvbe->vbe_bpp = (dvbe->vbe_depth == 15) ? 16 : dvbe->vbe_depth; + dvbe->vbe_size = dvbe->vbe_height * dvbe->vbe_stride; + dvbe->vbe_vsize = screen_info.lfb_size * 0x10000; + if (dvbe->vbe_vsize < dvbe->vbe_size) + dvbe->vbe_vsize = dvbe->vbe_size; + + dev_info(dvbe->ddev->dev, "VMEM at: %ld vsize: %ld rsize: %ld\n", + dvbe->vbe_addr, dvbe->vbe_vsize, dvbe->vbe_size); + dev_info(dvbe->ddev->dev, "width: %d height: %d stride: %d bpp: %d\n", + dvbe->vbe_width, dvbe->vbe_height, dvbe->vbe_stride, + dvbe->vbe_bpp); + + if (dvbe->vbe_bpp != 8 && dvbe->vbe_bpp != 16 && dvbe->vbe_bpp != 32) { + dev_err(dvbe->ddev->dev, "unsupported bpp value %d\n", + dvbe->vbe_bpp); + return -ENODEV; + } + if (!screen_info.red_pos && !screen_info.green_pos && + !screen_info.blue_pos) { + dev_err(dvbe->ddev->dev, "hardware not truecolor capable\n"); + return -ENODEV; + } + if (!screen_info.red_size && !screen_info.green_size && + !screen_info.blue_size) { + dev_err(dvbe->ddev->dev, "hardware not truecolor capable\n"); + return -ENODEV; + } + + dvbe->vbe_red_size = screen_info.red_size; + dvbe->vbe_red_pos = screen_info.red_pos; + dvbe->vbe_green_size = screen_info.green_size; + dvbe->vbe_green_pos = screen_info.green_pos; + dvbe->vbe_blue_size = screen_info.blue_size; + dvbe->vbe_blue_pos = screen_info.blue_pos; + + dev_info(dvbe->ddev->dev, "color %d:%d r: %d:%d g: %d:%d b: %d:%d\n", + dvbe->vbe_depth, dvbe->vbe_bpp, + dvbe->vbe_red_pos, dvbe->vbe_red_size, + dvbe->vbe_green_pos, dvbe->vbe_green_size, + dvbe->vbe_blue_pos, dvbe->vbe_blue_size); + + if (!request_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize, "dvbe")) { + dev_err(dvbe->ddev->dev, "cannot reserve VMEM\n"); + return -EIO; + } + + if (!request_region(0x3c0, 32, "dvbe")) { + dev_err(dvbe->ddev->dev, "cannot reserve VBIOS\n"); + ret = -EIO; + goto err_mem_region; + } + + dvbe->vbe_map = ioremap(dvbe->vbe_addr, dvbe->vbe_size); + if (!dvbe->vbe_map) { + dev_err(dvbe->ddev->dev, "cannot remap VMEM\n"); + ret = -EIO; + goto err_region; + } + + dev_info(dvbe->ddev->dev, "initialized VBE mode to %ux%u at %p\n", + dvbe->vbe_width, dvbe->vbe_height, dvbe->vbe_map); + + return 0; + +err_region: + release_region(0x3c0, 32); +err_mem_region: + release_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize); + return -ENODEV; +} + +void dvbe_vesa_cleanup(struct dvbe_device *dvbe) +{ + dev_info(dvbe->ddev->dev, "VBE cleanup\n"); + iounmap(dvbe->vbe_map); + release_region(0x3c0, 32); + release_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize); +}
This adds a new fbdev frontend to the dvbe driver. It allows userspace to access the dvbe driver via an fbdev frontend.
It is disabled by default so you can use dvbe without CONFIG_FB. It should only be used for backwards-compatibility. Use the DRM dumb API to access dvbe buffers properly.
Signed-off-by: David Herrmann dh.herrmann@gmail.com --- drivers/gpu/drm/dvbe/Kconfig | 18 +++ drivers/gpu/drm/dvbe/Makefile | 1 + drivers/gpu/drm/dvbe/dvbe.h | 23 ++++ drivers/gpu/drm/dvbe/dvbe_fbdev.c | 235 ++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/dvbe/dvbe_main.c | 2 + 5 files changed, 279 insertions(+) create mode 100644 drivers/gpu/drm/dvbe/dvbe_fbdev.c
diff --git a/drivers/gpu/drm/dvbe/Kconfig b/drivers/gpu/drm/dvbe/Kconfig index e49df10..ca27455 100644 --- a/drivers/gpu/drm/dvbe/Kconfig +++ b/drivers/gpu/drm/dvbe/Kconfig @@ -27,3 +27,21 @@ config DRM_DVBE
To compile this driver as a module, choose M here: the module will be called dvbe. + +config DRM_DVBE_FBDEV + bool "VESA BIOS Extension DRM fbdev Compatibility Layer" + depends on DRM_DVBE && FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This provides an fbdev frontend (via /dev/fbX) for the DVBE VESA + driver. Old userspace that depends on the fbdev API can access the + DVBE driver this way. It provides full backwards compatibility to the + old vesafb driver. + + Newer userspace accesses graphics devices via the DRM API and the old + fbdev compatibility layer is not needed. Activate it only if you + really need to run old userspace programs. + + If unsure, say N. diff --git a/drivers/gpu/drm/dvbe/Makefile b/drivers/gpu/drm/dvbe/Makefile index f6fb888..e01aaa1 100644 --- a/drivers/gpu/drm/dvbe/Makefile +++ b/drivers/gpu/drm/dvbe/Makefile @@ -1,4 +1,5 @@ ccflags-y := -Iinclude/drm
dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o dvbe_vesa.o +dvbe-$(CONFIG_DRM_DVBE_FBDEV) += dvbe_fbdev.o obj-$(CONFIG_DRM_DVBE) := dvbe.o diff --git a/drivers/gpu/drm/dvbe/dvbe.h b/drivers/gpu/drm/dvbe/dvbe.h index 68fd452..dfe7c20 100644 --- a/drivers/gpu/drm/dvbe/dvbe.h +++ b/drivers/gpu/drm/dvbe/dvbe.h @@ -14,6 +14,7 @@ #define DVBE_DRV_H
#include <linux/errno.h> +#include <linux/fb.h> #include <linux/kernel.h> #include <linux/mm.h> #include <linux/module.h> @@ -47,6 +48,9 @@ struct dvbe_device { struct drm_encoder enc; struct drm_connector conn; struct drm_display_mode *mode; + + /* fbdev */ + void *fbdev; };
int dvbe_drm_load(struct drm_device *ddev, unsigned long flags); @@ -95,4 +99,23 @@ int dvbe_vesa_damage(struct dvbe_device *dvbe, struct dvbe_framebuffer *fb, unsigned int flags, unsigned int color, struct drm_clip_rect *clips, unsigned int num);
+/* fbdev helpers */ + +#ifdef CONFIG_DRM_DVBE_FBDEV + +void dvbe_fbdev_init(struct dvbe_device *dvbe); +void dvbe_fbdev_cleanup(struct dvbe_device *dvbe); + +#else /* CONFIG_DRM_DVBE_FBDEV */ + +static inline void dvbe_fbdev_init(struct dvbe_device *dvbe) +{ +} + +static inline void dvbe_fbdev_cleanup(struct dvbe_device *dvbe) +{ +} + +#endif /* CONFIG_DRM_DVBE_FBDEV */ + #endif /* DVBE_DRV_H */ diff --git a/drivers/gpu/drm/dvbe/dvbe_fbdev.c b/drivers/gpu/drm/dvbe/dvbe_fbdev.c new file mode 100644 index 0000000..0e22e12 --- /dev/null +++ b/drivers/gpu/drm/dvbe/dvbe_fbdev.c @@ -0,0 +1,235 @@ +/* + * DRM VESA BIOS Extension Driver + * 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. + */ + +/* + * fbdev compatibility layer + * This provides an fbdev framebuffer device for the DVBE driver. It is + * based on the old vesafb.c driver: + * (c) 1998 Gerd Knorr kraxel@goldbach.in-berlin.de + */ + +#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 "dvbe.h" + +struct dvbe_fbdev { + struct dvbe_device *dvbe; + u32 palette[256]; +}; + +static int dvbe_fbdev_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return != 0 for + * invalid regno. + */ + + if (regno >= info->cmap.len) + return 1; + if (info->var.bits_per_pixel == 8) + return -EINVAL; + if (regno >= 16) + return 0; + + switch (info->var.bits_per_pixel) { + case 16: + if (info->var.red.offset == 10) { + /* 1:5:5:5 */ + ((u32*) (info->pseudo_palette))[regno] = + ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11); + } else { + /* 0:5:6:5 */ + ((u32*) (info->pseudo_palette))[regno] = + ((red & 0xf800) ) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + } + break; + case 24: + case 32: + red >>= 8; + green >>= 8; + blue >>= 8; + ((u32*) (info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + break; + } + + return 0; +} + +static void dvbe_fbdev_free(struct dvbe_device *dvbe, struct fb_info *info) +{ + dev_info(dvbe->ddev->dev, "fbdev cleanup\n"); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + +static void dvbe_fbdev_destroy(struct fb_info *info) +{ + struct dvbe_fbdev *fb = info->par; + struct dvbe_device *dvbe = fb->dvbe; + + mutex_lock(&dvbe->ddev->struct_mutex); + info = dvbe->fbdev; + dvbe->fbdev = NULL; + mutex_unlock(&dvbe->ddev->struct_mutex); + + if (info) + dvbe_fbdev_free(dvbe, info); +} + +static struct fb_ops dvbe_fbdev_ops = { + .owner = THIS_MODULE, + .fb_destroy = dvbe_fbdev_destroy, + .fb_setcolreg = dvbe_fbdev_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +void dvbe_fbdev_init(struct dvbe_device *dvbe) +{ + struct dvbe_fbdev *fb; + struct fb_info *info; + int ret; + + info = framebuffer_alloc(sizeof(struct dvbe_fbdev), dvbe->ddev->dev); + if (!info) { + ret = -ENOMEM; + goto err_out; + } + + fb = info->par; + fb->dvbe = dvbe; + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_MISC_FIRMWARE; + info->pseudo_palette = fb->palette; + info->screen_base = dvbe->vbe_map; + info->fbops = &dvbe_fbdev_ops; + + strncpy(info->fix.id, "dvbedrmfb", 15); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.accel = FB_ACCEL_NONE; + info->fix.smem_start = (unsigned long)dvbe->vbe_addr; + info->fix.smem_len = dvbe->vbe_size; + info->fix.line_length = dvbe->vbe_stride; + + if (dvbe->vbe_bpp == 8) + info->fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + info->var.activate = FB_ACTIVATE_NOW; + info->var.vmode = FB_VMODE_NONINTERLACED; + info->var.bits_per_pixel = dvbe->vbe_bpp; + info->var.xres = dvbe->vbe_width; + info->var.yres = dvbe->vbe_height; + info->var.xres_virtual = info->var.xres; + info->var.yres_virtual = info->var.yres; + + /* 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; + + info->var.red.offset = dvbe->vbe_red_pos; + info->var.red.length = dvbe->vbe_red_size; + info->var.green.offset = dvbe->vbe_green_pos; + info->var.green.length = dvbe->vbe_green_size; + info->var.blue.offset = dvbe->vbe_blue_pos; + info->var.blue.length = dvbe->vbe_blue_size; + + if (info->var.bits_per_pixel <= 8) { + info->var.red.length = info->var.bits_per_pixel; + info->var.green.length = info->var.bits_per_pixel; + info->var.blue.length = info->var.bits_per_pixel; + } + + info->apertures = alloc_apertures(1); + if (!info->apertures) { + ret = -ENOMEM; + goto err_free; + } + info->apertures->ranges[0].base = (unsigned long)dvbe->vbe_addr; + info->apertures->ranges[0].size = dvbe->vbe_vsize; + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret < 0) + goto err_free; + + /* + * TODO: Fix register_framebuffer() to not reset refcounts! + * So register_framebuffer() resets the refcnt of \info to 1. However, + * any other context might call remove_conflicting_framebuffers() + * before our call to register_framebuffer() returns. + * remove_conflicting_framebuffers() calls unregister_framebuffer() + * which then calls put_fb_info() and destroys \info by calling our + * fb_destroy() callback. + * To summarize, \info might be dead after register_framebuffer() + * returns so don't access it afterwards. There is _no_ reliable way to + * detect that so don't use \info at all now. + * Instead we lock all accesses around dvbe->fbdev and the first one who + * resets it is responsible of freeing it. + */ + + dvbe->fbdev = info; + ret = register_framebuffer(info); + if (ret < 0) + goto err_cmap; + + mutex_lock(&dvbe->ddev->struct_mutex); + if (dvbe->fbdev) + dev_info(dvbe->ddev->dev, "fbdev frontend %s as fb%d\n", + info->fix.id, info->node); + mutex_unlock(&dvbe->ddev->struct_mutex); + + return; + +err_cmap: + fb_dealloc_cmap(&info->cmap); +err_free: + framebuffer_release(info); +err_out: + dev_warn(dvbe->ddev->dev, "cannot create fbdev frontend (%d)\n", ret); +} + +void dvbe_fbdev_cleanup(struct dvbe_device *dvbe) +{ + struct fb_info *info; + + mutex_lock(&dvbe->ddev->struct_mutex); + info = dvbe->fbdev; + dvbe->fbdev = NULL; + mutex_unlock(&dvbe->ddev->struct_mutex); + + if (!info) + return; + + unregister_framebuffer(info); + dvbe_fbdev_free(dvbe, info); +} diff --git a/drivers/gpu/drm/dvbe/dvbe_main.c b/drivers/gpu/drm/dvbe/dvbe_main.c index c418310..95241a6 100644 --- a/drivers/gpu/drm/dvbe/dvbe_main.c +++ b/drivers/gpu/drm/dvbe/dvbe_main.c @@ -408,6 +408,7 @@ int dvbe_drm_load(struct drm_device *ddev, unsigned long flags) if (ret) goto err_cleanup;
+ dvbe_fbdev_init(dvbe); return 0;
err_cleanup: @@ -422,6 +423,7 @@ int dvbe_drm_unload(struct drm_device *ddev) { struct dvbe_device *dvbe = ddev->dev_private;
+ dvbe_fbdev_cleanup(dvbe); drm_mode_config_cleanup(ddev); dvbe_vesa_cleanup(dvbe); kfree(dvbe);
I'm unsure if I like this or not, and I don't see why its greatly more useful than the interface we have now.
It doesn't solve the main problem with the current interface, which is that if somebody opens has vesafb /dev/fb0, mmaps it, and modprobes a real driver, things will fail either miserably or crappy from a users pov.
The internal reference counts stop vesafb from unloading due to the mmaps, then i915 loads anyways and one bashes the other, or we fix so i915 doesn't load and the user gets fail.
Dave.
Hi Dave
On Sun, Feb 17, 2013 at 11:02 PM, Dave Airlie airlied@gmail.com wrote:
This interface at least solves the problem with having vesafb, uvesafb, vgacon, vga16fb, efifb, dvbe, defi and all other similar drivers from accessing the system framebuffer simultaneously. And provides a sane way of registering devices and drivers for it.
It also provides a way for real drivers to unload these drivers (sysfb_claim()) instead of using remove_conflicting_framebuffers(), which is horribly broken and has lots of race-conditions. (I tried fixing the fbdev refcounting/locking, but every time I tried, some driver broke because they worked around the bug. And fixing all drivers is just a lot of work...). And remove_conflicting_framebuffers() also doesn't work with vgacon/etc. which do _not_ use fbdev.
We could simplify this approach by removing the bus and just providing the platform-device for vbefb/etc. from the arch-code. But I thought the bus-infrastructure allows nice rebinding of drivers if we need it at almost no cost.
You could even register an oops/panic-screen as driver there and switch to it if necessary.
It's not the mmap that prevents vesafb from unloading, it's the open-file instead. If a user does open(), mmap(), close(), they can still access the mapped memory but vesafb might get unloaded (this is, in fact, used by several user-space apps). So it's not about whether vesafb is still loaded, but rather what to do about users which have vesafb mmaped but don't expect it to go away.
So what do you propose to detect this case? Keep track of every user who mmap's vesafb? How can we detect when they unmap the memory? I think the only way to detect this is to wait for the pages' "mmap-count" to drop to zero and then release the memory.
So lets compare this to other subsystems. If you unlink a file that is still mmaped, I think the file isn't removed from memory until the last user unmaps it. However, the memory-mapping is 'dead' in that it doesn't have any effect on real files. So why not copy that behavior to framebuffers? When a real DRM driver is loaded, simply reserve the VBE framebuffer-memory in VMEM until the last user unmaps it. But mark it 'dead' so it doesn't really belong to a _real_ framebuffer. So any access to the mmap'ed framebuffers will be a no-op as it just modifies 'dead' framebuffers.
Another idea is copying the VBE framebuffer into the DRM driver so all old memory-maps are still valid. However, this prevents us from doing any kind of mode-setting in the DRM driver until the last fbdev user is gone (because there is no way to notify fbdev users of mode-setting). So in this case we are also stuck in a situation where we need to wait for all users to unmap their framebuffers.
Any comments? If you have a plan on how it is supposed to work (or what the user-space semantics should be), tell me and I will try to make it work. I still think a central system-framebuffer registry like sysfb-bus (which does _not_ explicitly depend on fbdev) is the way to go. Whether it's a bus or not is just a matter of taste. I am willing to rework this.
Thanks David
But do we have the problem now? or is it more when we get dvbe/defi?
Also vgacon is kinda different since fbcon kicks it off, not a driver,
but vgacon is always kicked off by fbcon, so I'm not sure what etc there is apart from it. I'd like to make fbcon a bool as well, so we don't have to deal with it appearing after the drivers.
I suspect a platform device is the right answer, since vesafb is that,
I think we should resolve vesafb vs dvbe by just making them not config compatible, and dvbe vs defi seems like the same solution as vesafb vs efi.
I'm just trying to work out what exactly you are fixing here, the problems we have now don't seem to be addressed by this, if it addresses future problems then it needs to be more upfront.
In theory we'd have to do like GEM/TTM, and unmap_mapping_range for all the open fd's mmaps and just point them to map something useless or even just return -EFAULT, because really userspace needs to be told something :-)
It all kinda sucks, from the problems we've had previously with things like plymouth racing, (which all this will make much worse if we have kms vbe devices) really what we want the system to do is give the user the proper driver asap, stalling waiting for endless other things to let go is just going to screw users, so I think we need to be as upfront and brutal in the userspace interface if people mmaping vesafb or efifb or any generic interface then expect to load a real driver, then their old apps get killed.
As I said maybe I'm concentrating on the problem you aren't trying to fix, but then I'm not sure I've enough information on the problem you are trying to fix,
remove_confilicting_framebuffers might be ugly but it does 90% of what we want, I just want to understand why this will make it better,
Dave.
Hi Dave
Sorry, I was busy reworking the HIDP layer. I finally got time to continue my work on this again. See below:
On Mon, Feb 18, 2013 at 12:47 AM, Dave Airlie airlied@gmail.com wrote: [..snap..]
Ok, let me describe the problem in more detail:
We currently have different drivers that can make use of "system framebuffers" (as I call them for now): - vgacon - fbdev - DRM (- vgalog) (similar to fblog/drmlog but using vga/VBE)
Scenarios: - Imagine you have CONFIG_FB=n but VGACON=y for debugging. Who is then responsible of kicking out VGACON when DRM drivers are loaded? (and vice versa) - i915 is loaded and the user does a "modprobe vesafb". Who prevents vesafb from loading? - If I use vgalog, who unloads it when fbdev/DRM drivers are loaded? (and vice versa)
Our current solution consists of "vgacon_cleanup()" and "remove_conflicting_frambuffers()". We could add similar helpers for all other scenarios, but I promise, this will get pretty complex.
Another problem: All VBE/EFI framebuffer drivers currently do something like: platform_driver_register("my-device"...); platform_device_add("my-device"...); Why not provide a unique platform-device-name and add the device in architecture setup code? This would prevent multiple drivers from being probed on a single platform device.
My bus-solution was the most straightforward to implement and makes testing really easy (as you can probe/remove drivers from userspace). However, I understand if we don't want to introduce any ABI.
So I was rethinking this idea and maybe we simplify the bus-solution and instead use some priority-based driver loading. That is, the architecture code creates a platform-device for all available system-framebuffers (which is probably always at most one device). Then drivers simply provide platform-drivers that can be loaded on the device. But instead of using platform_driver_register(), they use vbe_driver_register() or something similar and pass in the platform-driver _plus_ a priority. The wrapper then compares the priorities, unloads the old driver and probes the new one. This guarantees that a new driver will only replace the current driver if it has a higher priority. vgacon/vgalog->fbdev->DRM
How does that fix the problems you described? Well, it doesn't. But it makes them more obvious as there is now a central place that manages the hand-over. On the other hand, I don't understand why the problems you described have anything to do with the hand-over itself? They also occur on driver-unloading without any handover, don't they? So I think we simply need to fix vesafb, efifb, ... to unmap any pending user-space mappings during unloading. This also guarantees that these mappings are dead when any other driver takes over. And with the platform-devices that I want to introduce, we guarantee that the drivers get unloaded correctly even during hand-over.
The remove_conflicting_framebuffers() call can stay for all other conflicts, although I think these _really_ should be handled by Kconfig. But ok, I don't mind this call. It doesn't break anything.
Why am I doing this? To get dvbe/defi and vbelog working. I use these on my machine as nouveau doesn't work and fbdev is just ugly to work with. I was also told that they proved to be useful in virtualization environments. I don't mind setting dvbe as "depends on !<any-drm-driver> && !CONFIG_FB && !CONFIG_VT" but I thought fixing this directly where it is broken ought to be the better way. So, any comments welcome.
If there are no major objections, I will try to implement it and also try to fix vesafb to unmap the mmap()s during unloading. If that turns out to work well, I can also fix the other drivers.
Thanks for the comments David
On Thu, Feb 28, 2013 at 1:20 PM, David Herrmann dh.herrmann@gmail.com wrote:
offb
Gr{oetje,eeting}s,
Geert
-- Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that. -- Linus Torvalds
dri-devel@lists.freedesktop.org