Path: blob/main/sys/compat/linuxkpi/common/src/linux_aperture.c
39586 views
// SPDX-License-Identifier: MIT12#include <linux/aperture.h>3#include <linux/device.h>4#include <linux/list.h>5#include <linux/mutex.h>6#include <linux/pci.h>7#include <linux/platform_device.h>8#include <linux/slab.h>9#include <linux/sysfb.h>10#include <linux/types.h>11#include <linux/vgaarb.h>1213#include <video/vga.h>1415/**16* DOC: overview17*18* A graphics device might be supported by different drivers, but only one19* driver can be active at any given time. Many systems load a generic20* graphics drivers, such as EFI-GOP or VESA, early during the boot process.21* During later boot stages, they replace the generic driver with a dedicated,22* hardware-specific driver. To take over the device, the dedicated driver23* first has to remove the generic driver. Aperture functions manage24* ownership of framebuffer memory and hand-over between drivers.25*26* Graphics drivers should call aperture_remove_conflicting_devices()27* at the top of their probe function. The function removes any generic28* driver that is currently associated with the given framebuffer memory.29* An example for a graphics device on the platform bus is shown below.30*31* .. code-block:: c32*33* static int example_probe(struct platform_device *pdev)34* {35* struct resource *mem;36* resource_size_t base, size;37* int ret;38*39* mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);40* if (!mem)41* return -ENODEV;42* base = mem->start;43* size = resource_size(mem);44*45* ret = aperture_remove_conflicting_devices(base, size, "example");46* if (ret)47* return ret;48*49* // Initialize the hardware50* ...51*52* return 0;53* }54*55* static const struct platform_driver example_driver = {56* .probe = example_probe,57* ...58* };59*60* The given example reads the platform device's I/O-memory range from the61* device instance. An active framebuffer will be located within this range.62* The call to aperture_remove_conflicting_devices() releases drivers that63* have previously claimed ownership of the range and are currently driving64* output on the framebuffer. If successful, the new driver can take over65* the device.66*67* While the given example uses a platform device, the aperture helpers work68* with every bus that has an addressable framebuffer. In the case of PCI,69* device drivers can also call aperture_remove_conflicting_pci_devices() and70* let the function detect the apertures automatically. Device drivers without71* knowledge of the framebuffer's location can call72* aperture_remove_all_conflicting_devices(), which removes all known devices.73*74* Drivers that are susceptible to being removed by other drivers, such as75* generic EFI or VESA drivers, have to register themselves as owners of their76* framebuffer apertures. Ownership of the framebuffer memory is achieved77* by calling devm_aperture_acquire_for_platform_device(). If successful, the78* driver is the owner of the framebuffer range. The function fails if the79* framebuffer is already owned by another driver. See below for an example.80*81* .. code-block:: c82*83* static int generic_probe(struct platform_device *pdev)84* {85* struct resource *mem;86* resource_size_t base, size;87*88* mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);89* if (!mem)90* return -ENODEV;91* base = mem->start;92* size = resource_size(mem);93*94* ret = devm_aperture_acquire_for_platform_device(pdev, base, size);95* if (ret)96* return ret;97*98* // Initialize the hardware99* ...100*101* return 0;102* }103*104* static int generic_remove(struct platform_device *)105* {106* // Hot-unplug the device107* ...108*109* return 0;110* }111*112* static const struct platform_driver generic_driver = {113* .probe = generic_probe,114* .remove = generic_remove,115* ...116* };117*118* The similar to the previous example, the generic driver claims ownership119* of the framebuffer memory from its probe function. This will fail if the120* memory range, or parts of it, is already owned by another driver.121*122* If successful, the generic driver is now subject to forced removal by123* another driver. This only works for platform drivers that support hot124* unplugging. When a driver calls aperture_remove_conflicting_devices()125* et al for the registered framebuffer range, the aperture helpers call126* platform_device_unregister() and the generic driver unloads itself. The127* generic driver also has to provide a remove function to make this work.128* Once hot unplugged from hardware, it may not access the device's129* registers, framebuffer memory, ROM, etc afterwards.130*/131132struct aperture_range {133struct device *dev;134resource_size_t base;135resource_size_t size;136struct list_head lh;137void (*detach)(struct device *dev);138};139140static LIST_HEAD(apertures);141static DEFINE_MUTEX(apertures_lock);142143static bool overlap(resource_size_t base1, resource_size_t end1,144resource_size_t base2, resource_size_t end2)145{146return (base1 < end2) && (end1 > base2);147}148149static void devm_aperture_acquire_release(void *data)150{151struct aperture_range *ap = data;152bool detached = !ap->dev;153154if (detached)155return;156157mutex_lock(&apertures_lock);158list_del(&ap->lh);159mutex_unlock(&apertures_lock);160}161162static int devm_aperture_acquire(struct device *dev,163resource_size_t base, resource_size_t size,164void (*detach)(struct device *))165{166size_t end = base + size;167struct list_head *pos;168struct aperture_range *ap;169170mutex_lock(&apertures_lock);171172list_for_each(pos, &apertures) {173ap = container_of(pos, struct aperture_range, lh);174if (overlap(base, end, ap->base, ap->base + ap->size)) {175mutex_unlock(&apertures_lock);176return -EBUSY;177}178}179180ap = devm_kzalloc(dev, sizeof(*ap), GFP_KERNEL);181if (!ap) {182mutex_unlock(&apertures_lock);183return -ENOMEM;184}185186ap->dev = dev;187ap->base = base;188ap->size = size;189ap->detach = detach;190INIT_LIST_HEAD(&ap->lh);191192list_add(&ap->lh, &apertures);193194mutex_unlock(&apertures_lock);195196return devm_add_action_or_reset(dev, devm_aperture_acquire_release, ap);197}198199static void aperture_detach_platform_device(struct device *dev)200{201struct platform_device *pdev = to_platform_device(dev);202203/*204* Remove the device from the device hierarchy. This is the right thing205* to do for firmware-based fb drivers, such as EFI, VESA or VGA. After206* the new driver takes over the hardware, the firmware device's state207* will be lost.208*209* For non-platform devices, a new callback would be required.210*211* If the aperture helpers ever need to handle native drivers, this call212* would only have to unplug the DRM device, so that the hardware device213* stays around after detachment.214*/215platform_device_unregister(pdev);216}217218/**219* devm_aperture_acquire_for_platform_device - Acquires ownership of an aperture220* on behalf of a platform device.221* @pdev: the platform device to own the aperture222* @base: the aperture's byte offset in physical memory223* @size: the aperture size in bytes224*225* Installs the given device as the new owner of the aperture. The function226* expects the aperture to be provided by a platform device. If another227* driver takes over ownership of the aperture, aperture helpers will then228* unregister the platform device automatically. All acquired apertures are229* released automatically when the underlying device goes away.230*231* The function fails if the aperture, or parts of it, is currently232* owned by another device. To evict current owners, callers should use233* remove_conflicting_devices() et al. before calling this function.234*235* Returns:236* 0 on success, or a negative errno value otherwise.237*/238int devm_aperture_acquire_for_platform_device(struct platform_device *pdev,239resource_size_t base,240resource_size_t size)241{242return devm_aperture_acquire(&pdev->dev, base, size, aperture_detach_platform_device);243}244EXPORT_SYMBOL(devm_aperture_acquire_for_platform_device);245246static void aperture_detach_devices(resource_size_t base, resource_size_t size)247{248resource_size_t end = base + size;249struct list_head *pos, *n;250251mutex_lock(&apertures_lock);252253list_for_each_safe(pos, n, &apertures) {254struct aperture_range *ap = container_of(pos, struct aperture_range, lh);255struct device *dev = ap->dev;256257if (WARN_ON_ONCE(!dev))258continue;259260if (!overlap(base, end, ap->base, ap->base + ap->size))261continue;262263ap->dev = NULL; /* detach from device */264list_del(&ap->lh);265266ap->detach(dev);267}268269mutex_unlock(&apertures_lock);270}271272/**273* aperture_remove_conflicting_devices - remove devices in the given range274* @base: the aperture's base address in physical memory275* @size: aperture size in bytes276* @name: a descriptive name of the requesting driver277*278* This function removes devices that own apertures within @base and @size.279*280* Returns:281* 0 on success, or a negative errno code otherwise282*/283int aperture_remove_conflicting_devices(resource_size_t base, resource_size_t size,284const char *name)285{286/*287* If a driver asked to unregister a platform device registered by288* sysfb, then can be assumed that this is a driver for a display289* that is set up by the system firmware and has a generic driver.290*291* Drivers for devices that don't have a generic driver will never292* ask for this, so let's assume that a real driver for the display293* was already probed and prevent sysfb to register devices later.294*/295#ifdef __linux__296sysfb_disable();297#endif298299aperture_detach_devices(base, size);300301return 0;302}303EXPORT_SYMBOL(aperture_remove_conflicting_devices);304305/**306* __aperture_remove_legacy_vga_devices - remove legacy VGA devices of a PCI devices307* @pdev: PCI device308*309* This function removes VGA devices provided by @pdev, such as a VGA310* framebuffer or a console. This is useful if you have a VGA-compatible311* PCI graphics device with framebuffers in non-BAR locations. Drivers312* should acquire ownership of those memory areas and afterwards call313* this helper to release remaining VGA devices.314*315* If your hardware has its framebuffers accessible via PCI BARS, use316* aperture_remove_conflicting_pci_devices() instead. The function will317* release any VGA devices automatically.318*319* WARNING: Apparently we must remove graphics drivers before calling320* this helper. Otherwise the vga fbdev driver falls over if321* we have vgacon configured.322*323* Returns:324* 0 on success, or a negative errno code otherwise325*/326int __aperture_remove_legacy_vga_devices(struct pci_dev *pdev)327{328/* VGA framebuffer */329aperture_detach_devices(VGA_FB_PHYS_BASE, VGA_FB_PHYS_SIZE);330331/* VGA textmode console */332#ifdef __linux__333return vga_remove_vgacon(pdev);334#elif defined(__FreeBSD__)335return 0;336#endif337}338EXPORT_SYMBOL(__aperture_remove_legacy_vga_devices);339340/**341* aperture_remove_conflicting_pci_devices - remove existing framebuffers for PCI devices342* @pdev: PCI device343* @name: a descriptive name of the requesting driver344*345* This function removes devices that own apertures within any of @pdev's346* memory bars. The function assumes that PCI device with shadowed ROM347* drives a primary display and therefore kicks out vga16fb as well.348*349* Returns:350* 0 on success, or a negative errno code otherwise351*/352int aperture_remove_conflicting_pci_devices(struct pci_dev *pdev, const char *name)353{354bool primary = false;355resource_size_t base, size;356int bar, ret = 0;357358#ifdef __linux__359if (pdev == vga_default_device())360primary = true;361362if (primary)363sysfb_disable();364#endif365366for (bar = 0; bar < PCI_STD_NUM_BARS; ++bar) {367if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM))368continue;369370base = pci_resource_start(pdev, bar);371size = pci_resource_len(pdev, bar);372aperture_detach_devices(base, size);373}374375/*376* If this is the primary adapter, there could be a VGA device377* that consumes the VGA framebuffer I/O range. Remove this378* device as well.379*/380if (primary)381ret = __aperture_remove_legacy_vga_devices(pdev);382383return ret;384385}386EXPORT_SYMBOL(aperture_remove_conflicting_pci_devices);387388389