[PATCH 1/9] video: introduce system framebuffer bus

From: David Herrmann
Date: Sun Feb 17 2013 - 12:59:58 EST


From: David Herrmann <dh.herrmann@xxxxxxxxxxxxxx>

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@xxxxxxxxxxxxxx>
---
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@xxxxxxxxx>");
+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_ */
--
1.8.1.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/