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