summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/drm_aperture.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/drm_aperture.c')
-rw-r--r--drivers/gpu/drm/drm_aperture.c221
1 files changed, 217 insertions, 4 deletions
diff --git a/drivers/gpu/drm/drm_aperture.c b/drivers/gpu/drm/drm_aperture.c
index e034dd7f9b09..33bf018c3bdf 100644
--- a/drivers/gpu/drm/drm_aperture.c
+++ b/drivers/gpu/drm/drm_aperture.c
@@ -1,9 +1,18 @@
// SPDX-License-Identifier: MIT
+#include <linux/device.h>
#include <linux/fb.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h> /* for firmware helpers */
+#include <linux/slab.h>
+#include <linux/types.h>
#include <linux/vgaarb.h>
#include <drm/drm_aperture.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_print.h>
/**
* DOC: overview
@@ -62,8 +71,200 @@
* framebuffer apertures automatically. Device drivers without knowledge of
* the framebuffer's location shall call drm_aperture_remove_framebuffers(),
* which removes all drivers for known framebuffer.
+ *
+ * Drivers that are susceptible to being removed by other drivers, such as
+ * generic EFI or VESA drivers, have to register themselves as owners of their
+ * given framebuffer memory. Ownership of the framebuffer memory is achived
+ * by calling devm_aperture_acquire_from_firmware(). On success, the driver
+ * is the owner of the framebuffer range. The function fails if the
+ * framebuffer is already by another driver. See below for an example.
+ *
+ * .. code-block:: c
+ *
+ * static int acquire_framebuffers(struct drm_device *dev, struct platform_device *pdev)
+ * {
+ * resource_size_t base, size;
+ *
+ * mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ * if (!mem)
+ * return -EINVAL;
+ * base = mem->start;
+ * size = resource_size(mem);
+ *
+ * return devm_acquire_aperture_from_firmware(dev, base, size);
+ * }
+ *
+ * static int probe(struct platform_device *pdev)
+ * {
+ * struct drm_device *dev;
+ * int ret;
+ *
+ * // ... Initialize the device...
+ * dev = devm_drm_dev_alloc();
+ * ...
+ *
+ * // ... and acquire ownership of the framebuffer.
+ * ret = acquire_framebuffers(dev, pdev);
+ * if (ret)
+ * return ret;
+ *
+ * drm_dev_register(dev, 0);
+ *
+ * return 0;
+ * }
+ *
+ * The generic driver is now subject to forced removal by other drivers. This
+ * only works for platform drivers that support hot unplug.
+ * When a driver calls drm_aperture_remove_conflicting_framebuffers() et al
+ * for the registered framebuffer range, the aperture helpers call
+ * platform_device_unregister() and the generic driver unloads itself. It
+ * may not access the device's registers, framebuffer memory, ROM, etc
+ * afterwards.
*/
+struct drm_aperture {
+ struct drm_device *dev;
+ resource_size_t base;
+ resource_size_t size;
+ struct list_head lh;
+ void (*detach)(struct drm_device *dev);
+};
+
+static LIST_HEAD(drm_apertures);
+static DEFINE_MUTEX(drm_apertures_lock);
+
+static bool overlap(resource_size_t base1, resource_size_t end1,
+ resource_size_t base2, resource_size_t end2)
+{
+ return (base1 < end2) && (end1 > base2);
+}
+
+static void devm_aperture_acquire_release(void *data)
+{
+ struct drm_aperture *ap = data;
+ bool detached = !ap->dev;
+
+ if (detached)
+ return;
+
+ mutex_lock(&drm_apertures_lock);
+ list_del(&ap->lh);
+ mutex_unlock(&drm_apertures_lock);
+}
+
+static int devm_aperture_acquire(struct drm_device *dev,
+ resource_size_t base, resource_size_t size,
+ void (*detach)(struct drm_device *))
+{
+ size_t end = base + size;
+ struct list_head *pos;
+ struct drm_aperture *ap;
+
+ mutex_lock(&drm_apertures_lock);
+
+ list_for_each(pos, &drm_apertures) {
+ ap = container_of(pos, struct drm_aperture, lh);
+ if (overlap(base, end, ap->base, ap->base + ap->size))
+ return -EBUSY;
+ }
+
+ ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
+ if (!ap)
+ return -ENOMEM;
+
+ ap->dev = dev;
+ ap->base = base;
+ ap->size = size;
+ ap->detach = detach;
+ INIT_LIST_HEAD(&ap->lh);
+
+ list_add(&ap->lh, &drm_apertures);
+
+ mutex_unlock(&drm_apertures_lock);
+
+ return devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
+}
+
+static void drm_aperture_detach_firmware(struct drm_device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev->dev);
+
+ /*
+ * Remove the device from the device hierarchy. This is the right thing
+ * to do for firmware-based DRM drivers, such as EFI, VESA or VGA. After
+ * the new driver takes over the hardware, the firmware device's state
+ * will be lost.
+ *
+ * For non-platform devices, a new callback would be required.
+ *
+ * If the aperture helpers ever need to handle native drivers, this call
+ * would only have to unplug the DRM device, so that the hardware device
+ * stays around after detachment.
+ */
+ platform_device_unregister(pdev);
+}
+
+/**
+ * devm_aperture_acquire_from_firmware - Acquires ownership of a firmware framebuffer
+ * on behalf of a DRM driver.
+ * @dev: the DRM device to own the framebuffer memory
+ * @base: the framebuffer's byte offset in physical memory
+ * @size: the framebuffer size in bytes
+ *
+ * Installs the given device as the new owner of the framebuffer. The function
+ * expects the framebuffer to be provided by a platform device that has been
+ * set up by firmware. Firmware can be any generic interface, such as EFI,
+ * VESA, VGA, etc. If the native hardware driver takes over ownership of the
+ * framebuffer range, the firmware state gets lost. Aperture helpers will then
+ * unregister the platform device automatically. Acquired apertures are
+ * released automatically if the underlying device goes away.
+ *
+ * The function fails if the framebuffer range, or parts of it, is currently
+ * owned by another driver. To evict current owners, callers should use
+ * drm_aperture_remove_conflicting_framebuffers() et al. before calling this
+ * function. The function also fails if the given device is not a platform
+ * device.
+ *
+ * Returns:
+ * 0 on success, or a negative errno value otherwise.
+ */
+int devm_aperture_acquire_from_firmware(struct drm_device *dev, resource_size_t base,
+ resource_size_t size)
+{
+ if (drm_WARN_ON(dev, !dev_is_platform(dev->dev)))
+ return -EINVAL;
+
+ return devm_aperture_acquire(dev, base, size, drm_aperture_detach_firmware);
+}
+EXPORT_SYMBOL(devm_aperture_acquire_from_firmware);
+
+static void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size)
+{
+ resource_size_t end = base + size;
+ struct list_head *pos, *n;
+
+ mutex_lock(&drm_apertures_lock);
+
+ list_for_each_safe(pos, n, &drm_apertures) {
+ struct drm_aperture *ap =
+ container_of(pos, struct drm_aperture, lh);
+ struct drm_device *dev = ap->dev;
+
+ if (WARN_ON_ONCE(!dev))
+ continue;
+
+ if (!overlap(base, end, ap->base, ap->base + ap->size))
+ continue;
+
+ ap->dev = NULL; /* detach from device */
+ list_del(&ap->lh);
+
+ ap->detach(dev);
+ }
+
+ mutex_unlock(&drm_apertures_lock);
+}
+
/**
* drm_aperture_remove_conflicting_framebuffers - remove existing framebuffers in the given range
* @base: the aperture's base address in physical memory
@@ -94,10 +295,13 @@ int drm_aperture_remove_conflicting_framebuffers(resource_size_t base, resource_
ret = remove_conflicting_framebuffers(a, name, primary);
kfree(a);
- return ret;
-#else
- return 0;
+ if (ret)
+ return ret;
#endif
+
+ drm_aperture_detach_drivers(base, size);
+
+ return 0;
}
EXPORT_SYMBOL(drm_aperture_remove_conflicting_framebuffers);
@@ -115,7 +319,16 @@ EXPORT_SYMBOL(drm_aperture_remove_conflicting_framebuffers);
*/
int drm_aperture_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, const char *name)
{
- int ret = 0;
+ resource_size_t base, size;
+ int bar, ret = 0;
+
+ for (bar = 0; bar < PCI_STD_NUM_BARS; ++bar) {
+ if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM))
+ continue;
+ base = pci_resource_start(pdev, bar);
+ size = pci_resource_len(pdev, bar);
+ drm_aperture_detach_drivers(base, size);
+ }
/*
* WARNING: Apparently we must kick fbdev drivers before vgacon,