[RFC 5/6] drm: add helpers to kick out firmware drivers
From: David Herrmann
Date: Mon Jun 24 2013 - 18:28:50 EST
If we load a real hardware DRM driver, we want all firmware drivers to be
unloaded. Historically, this was done via
remove_conflicting_framebuffers(), but for DRM drivers (like SimpleDRM) we
need a new way.
This patch introduces DRIVER_FIRMWARE and DRM apertures to provide a quite
similar way to kick out firmware DRM drivers. Additionally, unlike the
fbdev equivalent, DRM firmware drivers can now query the system whether a
real hardware driver is already loaded and prevent loading themselves.
Signed-off-by: David Herrmann <dh.herrmann@xxxxxxxxx>
---
drivers/gpu/drm/drm_pci.c | 1 +
drivers/gpu/drm/drm_platform.c | 1 +
drivers/gpu/drm/drm_stub.c | 107 +++++++++++++++++++++++++++++
drivers/gpu/drm/drm_usb.c | 1 +
drivers/gpu/drm/simpledrm/simpledrm.h | 1 +
drivers/gpu/drm/simpledrm/simpledrm_drv.c | 3 +-
drivers/gpu/drm/simpledrm/simpledrm_main.c | 34 +++++++++
include/drm/drmP.h | 26 +++++++
8 files changed, 173 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_pci.c b/drivers/gpu/drm/drm_pci.c
index 14194b6..4dcb2a4 100644
--- a/drivers/gpu/drm/drm_pci.c
+++ b/drivers/gpu/drm/drm_pci.c
@@ -366,6 +366,7 @@ int drm_get_pci_dev(struct pci_dev *pdev, const struct pci_device_id *ent,
}
list_add_tail(&dev->driver_item, &driver->device_list);
+ list_add_tail(&dev->global_item, &drm_devlist);
DRM_INFO("Initialized %s %d.%d.%d %s for %s on minor %d\n",
driver->name, driver->major, driver->minor, driver->patchlevel,
diff --git a/drivers/gpu/drm/drm_platform.c b/drivers/gpu/drm/drm_platform.c
index b8a282e..94923c8 100644
--- a/drivers/gpu/drm/drm_platform.c
+++ b/drivers/gpu/drm/drm_platform.c
@@ -88,6 +88,7 @@ int drm_get_platform_dev(struct platform_device *platdev,
}
list_add_tail(&dev->driver_item, &driver->device_list);
+ list_add_tail(&dev->global_item, &drm_devlist);
mutex_unlock(&drm_global_mutex);
diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c
index 16f3ec5..a433ab0 100644
--- a/drivers/gpu/drm/drm_stub.c
+++ b/drivers/gpu/drm/drm_stub.c
@@ -46,6 +46,9 @@ EXPORT_SYMBOL(drm_vblank_offdelay);
unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */
EXPORT_SYMBOL(drm_timestamp_precision);
+LIST_HEAD(drm_devlist); /* device list; protected by global lock */
+EXPORT_SYMBOL(drm_devlist);
+
/*
* Default to use monotonic timestamps for wait-for-vblank and page-flip
* complete events.
@@ -484,7 +487,9 @@ void drm_put_dev(struct drm_device *dev)
drm_put_minor(&dev->primary);
+ list_del(&dev->global_item);
list_del(&dev->driver_item);
+ kfree(dev->apertures);
kfree(dev->devname);
kfree(dev);
}
@@ -507,3 +512,105 @@ void drm_unplug_dev(struct drm_device *dev)
mutex_unlock(&drm_global_mutex);
}
EXPORT_SYMBOL(drm_unplug_dev);
+
+void drm_unplug_dev_locked(struct drm_device *dev)
+{
+ /* for a USB device */
+ if (drm_core_check_feature(dev, DRIVER_MODESET))
+ drm_unplug_minor(dev->control);
+ drm_unplug_minor(dev->primary);
+
+ drm_device_set_unplugged(dev);
+
+ /* TODO: schedule drm_put_dev if open_count == 0 */
+}
+EXPORT_SYMBOL(drm_unplug_dev_locked);
+
+#define VGA_FB_PHYS 0xa0000
+
+static bool apertures_overlap(struct drm_device *dev,
+ struct apertures_struct *ap,
+ bool boot)
+{
+ unsigned int i, j;
+ struct aperture *a, *b;
+
+ if (!dev->apertures)
+ return false;
+
+ for (i = 0; i < dev->apertures->count; ++i) {
+ a = &dev->apertures->ranges[i];
+
+ if (boot && a->base == VGA_FB_PHYS)
+ return true;
+
+ for (j = 0; ap && j < ap->count; ++j) {
+ b = &ap->ranges[j];
+ if (a->base <= b->base && a->base + a->size > b->base)
+ return true;
+ if (b->base <= a->base && b->base + b->size > a->base)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Kick out firmware
+ *
+ * Virtually unplug any firmware graphics devices which overlap the given
+ * region. This must be called with the global-drm-mutex locked.
+ * This calls the kick_out_firmware() callback on any firmware DRM driver and
+ * after that remove_conflicting_framebuffers() to remove legacy fbdev
+ * framebuffers.
+ */
+void drm_kick_out_firmware(struct apertures_struct *ap, bool boot)
+{
+ struct drm_device *dev;
+
+ list_for_each_entry(dev, &drm_devlist, global_item) {
+ if (!drm_core_check_feature(dev, DRIVER_FIRMWARE))
+ continue;
+ if (apertures_overlap(dev, ap, boot)) {
+ DRM_INFO("kicking out firmware %s\n",
+ dev->devname);
+ dev->driver->kick_out_firmware(dev);
+ }
+ }
+
+ remove_conflicting_framebuffers(ap, "DRM", boot);
+}
+EXPORT_SYMBOL(drm_kick_out_firmware);
+
+/**
+ * Verify that no driver uses firmware FBs
+ *
+ * Whenever you register a firmware framebuffer driver, you should store the
+ * apertures in @ap and test whether any other registered driver already
+ * claimed this area. Hence, if this function returns true, you should _not_
+ * register your driver!
+ */
+bool drm_is_firmware_used(struct apertures_struct *ap)
+{
+ struct drm_device *dev;
+ unsigned int i;
+ bool boot = false;
+
+ for (i = 0; ap && i < ap->count; ++i) {
+ if (ap->ranges[i].base == VGA_FB_PHYS) {
+ boot = true;
+ break;
+ }
+ }
+
+ list_for_each_entry(dev, &drm_devlist, global_item) {
+ if (dev->apert_boot && boot)
+ return true;
+ if (apertures_overlap(dev, ap, false))
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(drm_is_firmware_used);
diff --git a/drivers/gpu/drm/drm_usb.c b/drivers/gpu/drm/drm_usb.c
index 34a156f..5ad8dba 100644
--- a/drivers/gpu/drm/drm_usb.c
+++ b/drivers/gpu/drm/drm_usb.c
@@ -50,6 +50,7 @@ int drm_get_usb_dev(struct usb_interface *interface,
goto err_g3;
list_add_tail(&dev->driver_item, &driver->device_list);
+ list_add_tail(&dev->global_item, &drm_devlist);
mutex_unlock(&drm_global_mutex);
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
index cfd99f9..03373fe 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm.h
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -55,6 +55,7 @@ struct sdrm_device {
int sdrm_drm_load(struct drm_device *ddev, unsigned long flags);
int sdrm_drm_unload(struct drm_device *ddev);
+void sdrm_drm_kick_out_firmware(struct drm_device *ddev);
int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma);
int sdrm_pdev_init(struct sdrm_device *sdrm);
void sdrm_pdev_destroy(struct sdrm_device *sdrm);
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
index 282752c..e5d0ce0 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm_drv.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
@@ -36,9 +36,10 @@ static const struct file_operations sdrm_drm_fops = {
};
static struct drm_driver sdrm_drm_driver = {
- .driver_features = DRIVER_MODESET | DRIVER_GEM,
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_FIRMWARE,
.load = sdrm_drm_load,
.unload = sdrm_drm_unload,
+ .kick_out_firmware = sdrm_drm_kick_out_firmware,
.fops = &sdrm_drm_fops,
.gem_init_object = sdrm_gem_init_object,
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_main.c b/drivers/gpu/drm/simpledrm/simpledrm_main.c
index ffa1abb..6b7696e 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm_main.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm_main.c
@@ -269,6 +269,21 @@ int sdrm_drm_load(struct drm_device *ddev, unsigned long flags)
if (ret)
goto err_name;
+ ddev->apertures = alloc_apertures(1);
+ if (!ddev->apertures) {
+ ret = -ENOMEM;
+ goto err_pdev;
+ }
+
+ ddev->apertures->ranges[0].base = sdrm->fb_base;
+ ddev->apertures->ranges[0].size = sdrm->fb_size;
+
+ if (drm_is_firmware_used(ddev->apertures)) {
+ dev_info(ddev->dev, "firmware framebuffer is already in use\n");
+ ret = -EBUSY;
+ goto err_apert;
+ }
+
drm_mode_config_init(ddev);
ddev->mode_config.min_width = 0;
ddev->mode_config.min_height = 0;
@@ -310,6 +325,9 @@ int sdrm_drm_load(struct drm_device *ddev, unsigned long flags)
err_cleanup:
drm_mode_config_cleanup(ddev);
+err_apert:
+ kfree(ddev->apertures);
+err_pdev:
sdrm_pdev_destroy(sdrm);
err_name:
kfree(ddev->devname);
@@ -326,7 +344,23 @@ int sdrm_drm_unload(struct drm_device *ddev)
sdrm_fbdev_cleanup(sdrm);
drm_mode_config_cleanup(ddev);
sdrm_pdev_destroy(sdrm);
+ kfree(ddev->apertures);
kfree(sdrm);
return 0;
}
+
+void sdrm_drm_kick_out_firmware(struct drm_device *ddev)
+{
+ struct sdrm_device *sdrm = ddev->dev_private;
+
+ mutex_lock(&ddev->struct_mutex);
+
+ sdrm_fbdev_cleanup(sdrm);
+ drm_unplug_dev_locked(ddev);
+ if (sdrm->fb_obj)
+ sdrm_gem_unmap_object(sdrm->fb_obj);
+ sdrm_pdev_destroy(sdrm);
+
+ mutex_unlock(&ddev->struct_mutex);
+}
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
index 63d17ee..a19e710 100644
--- a/include/drm/drmP.h
+++ b/include/drm/drmP.h
@@ -47,6 +47,7 @@
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
+#include <linux/fb.h>
#include <linux/file.h>
#include <linux/platform_device.h>
#include <linux/pci.h>
@@ -156,6 +157,7 @@ int drm_err(const char *func, const char *format, ...);
#define DRIVER_GEM 0x1000
#define DRIVER_MODESET 0x2000
#define DRIVER_PRIME 0x4000
+#define DRIVER_FIRMWARE 0x8000
#define DRIVER_BUS_PCI 0x1
#define DRIVER_BUS_PLATFORM 0x2
@@ -954,6 +956,23 @@ struct drm_driver {
struct drm_device *dev,
uint32_t handle);
+ /**
+ * kick_out_firmware - kick out firmware driver
+ * @dev: DRM device
+ *
+ * Iff this driver has DRIVER_FIRMWARE set, this function is called
+ * when a real hw-driver is loaded and claims the framebuffer memory
+ * that is mapped by the firmware driver. This is called with the
+ * global drm mutex held.
+ * Drivers should unmap any memory-mappings, disable the device and
+ * schedule a driver removal.
+ *
+ * Note that a driver setting DRIVER_FIRMWARE is supposed to also set
+ * the "apertures" information on @dev. It is used to match the memory
+ * regions that are used by firmware drivers.
+ */
+ void (*kick_out_firmware) (struct drm_device *dev);
+
/* Driver private ops for this object */
const struct vm_operations_struct *gem_vm_ops;
@@ -1077,6 +1096,7 @@ struct drm_pending_vblank_event {
*/
struct drm_device {
struct list_head driver_item; /**< list of devices per driver */
+ struct list_head global_item; /**< global list of devices */
char *devname; /**< For /proc/interrupts */
int if_version; /**< Highest interface version set */
@@ -1218,6 +1238,8 @@ struct drm_device {
int switch_power_state;
atomic_t unplugged; /* device has been unplugged or gone away */
+ struct apertures_struct *apertures; /**< fbmem apertures */
+ bool apert_boot; /**< true if mapped as boot fb */
};
#define DRM_SWITCH_POWER_ON 0
@@ -1532,6 +1554,10 @@ extern void drm_master_put(struct drm_master **master);
extern void drm_put_dev(struct drm_device *dev);
extern int drm_put_minor(struct drm_minor **minor);
extern void drm_unplug_dev(struct drm_device *dev);
+extern void drm_unplug_dev_locked(struct drm_device *dev);
+extern void drm_kick_out_firmware(struct apertures_struct *ap, bool boot);
+extern bool drm_is_firmware_used(struct apertures_struct *ap);
+extern struct list_head drm_devlist;
extern unsigned int drm_debug;
extern unsigned int drm_vblank_offdelay;
--
1.8.3.1
--
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/