xref: /openbmc/linux/drivers/video/aperture.c (revision 979ac5ef)
1 // SPDX-License-Identifier: MIT
2 
3 #include <linux/aperture.h>
4 #include <linux/device.h>
5 #include <linux/list.h>
6 #include <linux/mutex.h>
7 #include <linux/pci.h>
8 #include <linux/platform_device.h>
9 #include <linux/slab.h>
10 #include <linux/sysfb.h>
11 #include <linux/types.h>
12 #include <linux/vgaarb.h>
13 
14 #include <video/vga.h>
15 
16 /**
17  * DOC: overview
18  *
19  * A graphics device might be supported by different drivers, but only one
20  * driver can be active at any given time. Many systems load a generic
21  * graphics drivers, such as EFI-GOP or VESA, early during the boot process.
22  * During later boot stages, they replace the generic driver with a dedicated,
23  * hardware-specific driver. To take over the device the dedicated driver
24  * first has to remove the generic driver. Aperture functions manage
25  * ownership of framebuffer memory and hand-over between drivers.
26  *
27  * Graphics drivers should call aperture_remove_conflicting_devices()
28  * at the top of their probe function. The function removes any generic
29  * driver that is currently associated with the given framebuffer memory.
30  * An example for a graphics device on the platform bus is shown below.
31  *
32  * .. code-block:: c
33  *
34  *	static int example_probe(struct platform_device *pdev)
35  *	{
36  *		struct resource *mem;
37  *		resource_size_t base, size;
38  *		int ret;
39  *
40  *		mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
41  *		if (!mem)
42  *			return -ENODEV;
43  *		base = mem->start;
44  *		size = resource_size(mem);
45  *
46  *		ret = aperture_remove_conflicting_devices(base, size, false, "example");
47  *		if (ret)
48  *			return ret;
49  *
50  *		// Initialize the hardware
51  *		...
52  *
53  *		return 0;
54  *	}
55  *
56  *	static const struct platform_driver example_driver = {
57  *		.probe = example_probe,
58  *		...
59  *	};
60  *
61  * The given example reads the platform device's I/O-memory range from the
62  * device instance. An active framebuffer will be located within this range.
63  * The call to aperture_remove_conflicting_devices() releases drivers that
64  * have previously claimed ownership of the range and are currently driving
65  * output on the framebuffer. If successful, the new driver can take over
66  * the device.
67  *
68  * While the given example uses a platform device, the aperture helpers work
69  * with every bus that has an addressable framebuffer. In the case of PCI,
70  * device drivers can also call aperture_remove_conflicting_pci_devices() and
71  * let the function detect the apertures automatically. Device drivers without
72  * knowledge of the framebuffer's location can call
73  * aperture_remove_all_conflicting_devices(), which removes all known devices.
74  *
75  * Drivers that are susceptible to being removed by other drivers, such as
76  * generic EFI or VESA drivers, have to register themselves as owners of their
77  * framebuffer apertures. Ownership of the framebuffer memory is achieved
78  * by calling devm_aperture_acquire_for_platform_device(). If successful, the
79  * driveris the owner of the framebuffer range. The function fails if the
80  * framebuffer is already owned by another driver. See below for an example.
81  *
82  * .. code-block:: c
83  *
84  *	static int generic_probe(struct platform_device *pdev)
85  *	{
86  *		struct resource *mem;
87  *		resource_size_t base, size;
88  *
89  *		mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
90  *		if (!mem)
91  *			return -ENODEV;
92  *		base = mem->start;
93  *		size = resource_size(mem);
94  *
95  *		ret = devm_aperture_acquire_for_platform_device(pdev, base, size);
96  *		if (ret)
97  *			return ret;
98  *
99  *		// Initialize the hardware
100  *		...
101  *
102  *		return 0;
103  *	}
104  *
105  *	static int generic_remove(struct platform_device *)
106  *	{
107  *		// Hot-unplug the device
108  *		...
109  *
110  *		return 0;
111  *	}
112  *
113  *	static const struct platform_driver generic_driver = {
114  *		.probe = generic_probe,
115  *		.remove = generic_remove,
116  *		...
117  *	};
118  *
119  * The similar to the previous example, the generic driver claims ownership
120  * of the framebuffer memory from its probe function. This will fail if the
121  * memory range, or parts of it, is already owned by another driver.
122  *
123  * If successful, the generic driver is now subject to forced removal by
124  * another driver. This only works for platform drivers that support hot
125  * unplugging. When a driver calls aperture_remove_conflicting_devices()
126  * et al for the registered framebuffer range, the aperture helpers call
127  * platform_device_unregister() and the generic driver unloads itself. The
128  * generic driver also has to provide a remove function to make this work.
129  * Once hot unplugged fro mhardware, it may not access the device's
130  * registers, framebuffer memory, ROM, etc afterwards.
131  */
132 
133 struct aperture_range {
134 	struct device *dev;
135 	resource_size_t base;
136 	resource_size_t size;
137 	struct list_head lh;
138 	void (*detach)(struct device *dev);
139 };
140 
141 static LIST_HEAD(apertures);
142 static DEFINE_MUTEX(apertures_lock);
143 
144 static bool overlap(resource_size_t base1, resource_size_t end1,
145 		    resource_size_t base2, resource_size_t end2)
146 {
147 	return (base1 < end2) && (end1 > base2);
148 }
149 
150 static void devm_aperture_acquire_release(void *data)
151 {
152 	struct aperture_range *ap = data;
153 	bool detached = !ap->dev;
154 
155 	if (detached)
156 		return;
157 
158 	mutex_lock(&apertures_lock);
159 	list_del(&ap->lh);
160 	mutex_unlock(&apertures_lock);
161 }
162 
163 static int devm_aperture_acquire(struct device *dev,
164 				 resource_size_t base, resource_size_t size,
165 				 void (*detach)(struct device *))
166 {
167 	size_t end = base + size;
168 	struct list_head *pos;
169 	struct aperture_range *ap;
170 
171 	mutex_lock(&apertures_lock);
172 
173 	list_for_each(pos, &apertures) {
174 		ap = container_of(pos, struct aperture_range, lh);
175 		if (overlap(base, end, ap->base, ap->base + ap->size)) {
176 			mutex_unlock(&apertures_lock);
177 			return -EBUSY;
178 		}
179 	}
180 
181 	ap = devm_kzalloc(dev, sizeof(*ap), GFP_KERNEL);
182 	if (!ap) {
183 		mutex_unlock(&apertures_lock);
184 		return -ENOMEM;
185 	}
186 
187 	ap->dev = dev;
188 	ap->base = base;
189 	ap->size = size;
190 	ap->detach = detach;
191 	INIT_LIST_HEAD(&ap->lh);
192 
193 	list_add(&ap->lh, &apertures);
194 
195 	mutex_unlock(&apertures_lock);
196 
197 	return devm_add_action_or_reset(dev, devm_aperture_acquire_release, ap);
198 }
199 
200 static void aperture_detach_platform_device(struct device *dev)
201 {
202 	struct platform_device *pdev = to_platform_device(dev);
203 
204 	/*
205 	 * Remove the device from the device hierarchy. This is the right thing
206 	 * to do for firmware-based DRM drivers, such as EFI, VESA or VGA. After
207 	 * the new driver takes over the hardware, the firmware device's state
208 	 * will be lost.
209 	 *
210 	 * For non-platform devices, a new callback would be required.
211 	 *
212 	 * If the aperture helpers ever need to handle native drivers, this call
213 	 * would only have to unplug the DRM device, so that the hardware device
214 	 * stays around after detachment.
215 	 */
216 	platform_device_unregister(pdev);
217 }
218 
219 /**
220  * devm_aperture_acquire_for_platform_device - Acquires ownership of an aperture
221  *                                             on behalf of a platform device.
222  * @pdev:	the platform device to own the aperture
223  * @base:	the aperture's byte offset in physical memory
224  * @size:	the aperture size in bytes
225  *
226  * Installs the given device as the new owner of the aperture. The function
227  * expects the aperture to be provided by a platform device. If another
228  * driver takes over ownership of the aperture, aperture helpers will then
229  * unregister the platform device automatically. All acquired apertures are
230  * released automatically when the underlying device goes away.
231  *
232  * The function fails if the aperture, or parts of it, is currently
233  * owned by another device. To evict current owners, callers should use
234  * remove_conflicting_devices() et al. before calling this function.
235  *
236  * Returns:
237  * 0 on success, or a negative errno value otherwise.
238  */
239 int devm_aperture_acquire_for_platform_device(struct platform_device *pdev,
240 					      resource_size_t base,
241 					      resource_size_t size)
242 {
243 	return devm_aperture_acquire(&pdev->dev, base, size, aperture_detach_platform_device);
244 }
245 EXPORT_SYMBOL(devm_aperture_acquire_for_platform_device);
246 
247 static void aperture_detach_devices(resource_size_t base, resource_size_t size)
248 {
249 	resource_size_t end = base + size;
250 	struct list_head *pos, *n;
251 
252 	mutex_lock(&apertures_lock);
253 
254 	list_for_each_safe(pos, n, &apertures) {
255 		struct aperture_range *ap = container_of(pos, struct aperture_range, lh);
256 		struct device *dev = ap->dev;
257 
258 		if (WARN_ON_ONCE(!dev))
259 			continue;
260 
261 		if (!overlap(base, end, ap->base, ap->base + ap->size))
262 			continue;
263 
264 		ap->dev = NULL; /* detach from device */
265 		list_del(&ap->lh);
266 
267 		ap->detach(dev);
268 	}
269 
270 	mutex_unlock(&apertures_lock);
271 }
272 
273 /**
274  * aperture_remove_conflicting_devices - remove devices in the given range
275  * @base: the aperture's base address in physical memory
276  * @size: aperture size in bytes
277  * @primary: also kick vga16fb if present; only relevant for VGA devices
278  * @name: a descriptive name of the requesting driver
279  *
280  * This function removes devices that own apertures within @base and @size.
281  *
282  * Returns:
283  * 0 on success, or a negative errno code otherwise
284  */
285 int aperture_remove_conflicting_devices(resource_size_t base, resource_size_t size,
286 					bool primary, const char *name)
287 {
288 	/*
289 	 * If a driver asked to unregister a platform device registered by
290 	 * sysfb, then can be assumed that this is a driver for a display
291 	 * that is set up by the system firmware and has a generic driver.
292 	 *
293 	 * Drivers for devices that don't have a generic driver will never
294 	 * ask for this, so let's assume that a real driver for the display
295 	 * was already probed and prevent sysfb to register devices later.
296 	 */
297 	sysfb_disable();
298 
299 	aperture_detach_devices(base, size);
300 
301 	/*
302 	 * If this is the primary adapter, there could be a VGA device
303 	 * that consumes the VGA framebuffer I/O range. Remove this device
304 	 * as well.
305 	 */
306 	if (primary)
307 		aperture_detach_devices(VGA_FB_PHYS_BASE, VGA_FB_PHYS_SIZE);
308 
309 	return 0;
310 }
311 EXPORT_SYMBOL(aperture_remove_conflicting_devices);
312 
313 /**
314  * aperture_remove_conflicting_pci_devices - remove existing framebuffers for PCI devices
315  * @pdev: PCI device
316  * @name: a descriptive name of the requesting driver
317  *
318  * This function removes devices that own apertures within any of @pdev's
319  * memory bars. The function assumes that PCI device with shadowed ROM
320  * drives a primary display and therefore kicks out vga16fb as well.
321  *
322  * Returns:
323  * 0 on success, or a negative errno code otherwise
324  */
325 int aperture_remove_conflicting_pci_devices(struct pci_dev *pdev, const char *name)
326 {
327 	bool primary = false;
328 	resource_size_t base, size;
329 	int bar, ret;
330 
331 #ifdef CONFIG_X86
332 	primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
333 #endif
334 
335 	for (bar = 0; bar < PCI_STD_NUM_BARS; ++bar) {
336 		if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM))
337 			continue;
338 
339 		base = pci_resource_start(pdev, bar);
340 		size = pci_resource_len(pdev, bar);
341 		ret = aperture_remove_conflicting_devices(base, size, primary, name);
342 		if (ret)
343 			return ret;
344 	}
345 
346 	/*
347 	 * WARNING: Apparently we must kick fbdev drivers before vgacon,
348 	 * otherwise the vga fbdev driver falls over.
349 	 */
350 	ret = vga_remove_vgacon(pdev);
351 	if (ret)
352 		return ret;
353 
354 	return 0;
355 
356 }
357 EXPORT_SYMBOL(aperture_remove_conflicting_pci_devices);
358