@@ -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
@@ -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 \
new file mode 100644
@@ -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");
new file mode 100644
@@ -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_ */