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