This patchset adds support for simple-framebuffer platform devices and a handover mechanism for native drivers to take-over control of the hardware.
The new driver, called simpledrm, binds to a simple-frambuffer platform device. The kernel's boot code creates such devices for firmware-provided framebuffers, such as EFI-GOP or VESA. Typically the BIOS, UEFI or boot loader sets up the framebuffers. Description via device tree is also an option.
Simpledrm is small enough to be linked into the kernel. The driver's main purpose is to provide graphical output during the early phases of the boot process, before the native DRM drivers are available. Native drivers are typically loaded from an initrd ram disk. Occationally simpledrm can also serve as interim solution on graphics hardware without native DRM driver.
So far distributions rely on fbdev drivers, such as efifb, vesafb or simplefb, for early-boot graphical output. However fbdev is deprecated and the drivers do not provide DRM interfaces for modern userspace.
Patches 1 and 2 prepare the DRM format helpers for simpledrm.
Patches 3 and 4 add a hand-over mechanism. Simpledrm acquires it's framebuffer's I/O-memory range and provides a callback function to be removed by a native driver. The native driver will remove simpledrm before taking over the hardware. The removal is integrated into existing helpers, so drivers use it automatically.
Patches 5 to 10 add the simpledrm driver. It's build on simple DRM helpers and SHMEM. It supports 16-bit, 24-bit and 32-bit RGB framebuffers. During pageflips, SHMEM buffers are copied into the framebuffer memory, similar to cirrus or mgag200. The code in patches 8 and 9 handles clocks and regulators. It's based on the simplefb drivers, but has been modified for DRM.
I've also been working on fastboot support (i.e., flicker-free booting). This requires state-readout from simpledrm via generic interfaces, as outlined in [1]. I do have some prototype code, but it will take a while to get this ready. Simpledrm will then support it.
I've tested simpledrm with x86 EFI and VESA framebuffers, which both work reliably. The fbdev console and Weston work automatically. Xorg requires manual configuration of the device. Xorgs current modesetting driver does not work with both, platform and PCI device, for the same physical hardware. Once configured, X11 works. I looked into X11, but couldn't see an easy way of fixing the problem. With the push towards Wayland+Xwayland I expect the problem to become a non-issue soon. Additional testing has been reported at [2].
One cosmetical issue is that simpledrm's device file is card0 and the native driver's device file is card1. After simpledrm has been kicked out, only card1 is left. This does not seem to be a practical problem however.
TODO/IDEAS:
* provide deferred takeover * provide bootsplash DRM client * make simplekms usable with ARM-EFI fbs
v2: * rename to simpledrm, aperture helpers * reorganized patches * use hotplug helpers for removal (Daniel) * added DT match tables (Rob) * use shadow-plane helpers * lots of minor cleanups
[1] https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9Bw... [2] https://lore.kernel.org/dri-devel/1761762.3HQLrFs1K7@nerdopolis/
Thomas Zimmermann (10): drm/format-helper: Pass destination pitch to drm_fb_memcpy_dstclip() drm/format-helper: Add blitter functions drm/aperture: Move fbdev conflict helpers into drm_aperture.h drm/aperture: Add infrastructure for aperture ownership drm: Add simpledrm driver drm/simpledrm: Add fbdev emulation drm/simpledrm: Initialize framebuffer data from device-tree node drm/simpledrm: Acquire clocks from DT device node drm/simpledrm: Acquire regulators from DT device node drm/simpledrm: Acquire memory aperture for framebuffer
Documentation/gpu/drm-internals.rst | 12 + MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++ drivers/gpu/drm/drm_format_helper.c | 96 ++- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/Kconfig | 17 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/cirrus.c | 2 +- drivers/gpu/drm/tiny/simpledrm.c | 932 +++++++++++++++++++++++++ include/drm/drm_aperture.h | 96 +++ include/drm/drm_fb_helper.h | 56 +- include/drm/drm_format_helper.h | 10 +- 14 files changed, 1466 insertions(+), 60 deletions(-) create mode 100644 drivers/gpu/drm/drm_aperture.c create mode 100644 drivers/gpu/drm/tiny/simpledrm.c create mode 100644 include/drm/drm_aperture.h
-- 2.30.1
The memcpy's destination buffer might have a different pitch than the source. Support different pitches as function argument.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Reviewed-by: Daniel Vetter daniel.vetter@ffwll.ch Tested-by: nerdopolis bluescreen_avenger@verizon.net --- drivers/gpu/drm/drm_format_helper.c | 9 +++++---- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/cirrus.c | 2 +- include/drm/drm_format_helper.h | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c index c043ca364c86..8d5a683afea7 100644 --- a/drivers/gpu/drm/drm_format_helper.c +++ b/drivers/gpu/drm/drm_format_helper.c @@ -52,6 +52,7 @@ EXPORT_SYMBOL(drm_fb_memcpy); /** * drm_fb_memcpy_dstclip - Copy clip buffer * @dst: Destination buffer (iomem) + * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @vaddr: Source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy @@ -59,12 +60,12 @@ EXPORT_SYMBOL(drm_fb_memcpy); * This function applies clipping on dst, i.e. the destination is a * full (iomem) framebuffer but only the clip rect content is copied over. */ -void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr, - struct drm_framebuffer *fb, +void drm_fb_memcpy_dstclip(void __iomem *dst, unsigned int dst_pitch, + void *vaddr, struct drm_framebuffer *fb, struct drm_rect *clip) { unsigned int cpp = fb->format->cpp[0]; - unsigned int offset = clip_offset(clip, fb->pitches[0], cpp); + unsigned int offset = clip_offset(clip, dst_pitch, cpp); size_t len = (clip->x2 - clip->x1) * cpp; unsigned int y, lines = clip->y2 - clip->y1;
@@ -73,7 +74,7 @@ void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr, for (y = 0; y < lines; y++) { memcpy_toio(dst, vaddr, len); vaddr += fb->pitches[0]; - dst += fb->pitches[0]; + dst += dst_pitch; } } EXPORT_SYMBOL(drm_fb_memcpy_dstclip); diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c index cece3e57fb27..9d576240faed 100644 --- a/drivers/gpu/drm/mgag200/mgag200_mode.c +++ b/drivers/gpu/drm/mgag200/mgag200_mode.c @@ -1554,7 +1554,7 @@ mgag200_handle_damage(struct mga_device *mdev, struct drm_framebuffer *fb, { void *vmap = map->vaddr; /* TODO: Use mapping abstraction properly */
- drm_fb_memcpy_dstclip(mdev->vram, vmap, fb, clip); + drm_fb_memcpy_dstclip(mdev->vram, fb->pitches[0], vmap, fb, clip);
/* Always scanout image at VRAM offset 0 */ mgag200_set_startadd(mdev, (u32)0); diff --git a/drivers/gpu/drm/tiny/cirrus.c b/drivers/gpu/drm/tiny/cirrus.c index ad922c3ec681..ae5643b0a6f4 100644 --- a/drivers/gpu/drm/tiny/cirrus.c +++ b/drivers/gpu/drm/tiny/cirrus.c @@ -323,7 +323,7 @@ static int cirrus_fb_blit_rect(struct drm_framebuffer *fb, const struct dma_buf_ return -ENODEV;
if (cirrus->cpp == fb->format->cpp[0]) - drm_fb_memcpy_dstclip(cirrus->vram, + drm_fb_memcpy_dstclip(cirrus->vram, fb->pitches[0], vmap, fb, rect);
else if (fb->format->cpp[0] == 4 && cirrus->cpp == 2) diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h index 5f9e37032468..2b5036a5fbe7 100644 --- a/include/drm/drm_format_helper.h +++ b/include/drm/drm_format_helper.h @@ -11,7 +11,7 @@ struct drm_rect;
void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb, struct drm_rect *clip); -void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr, +void drm_fb_memcpy_dstclip(void __iomem *dst, unsigned int dst_pitch, void *vaddr, struct drm_framebuffer *fb, struct drm_rect *clip); void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
The blitter functions copy a framebuffer to I/O memory using one of the existing conversion functions.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Reviewed-by: Daniel Vetter daniel.vetter@ffwll.ch Tested-by: nerdopolis bluescreen_avenger@verizon.net --- drivers/gpu/drm/drm_format_helper.c | 87 +++++++++++++++++++++++++++++ include/drm/drm_format_helper.h | 8 +++ 2 files changed, 95 insertions(+)
diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c index 8d5a683afea7..0e885cd34107 100644 --- a/drivers/gpu/drm/drm_format_helper.c +++ b/drivers/gpu/drm/drm_format_helper.c @@ -344,3 +344,90 @@ void drm_fb_xrgb8888_to_gray8(u8 *dst, void *vaddr, struct drm_framebuffer *fb, } EXPORT_SYMBOL(drm_fb_xrgb8888_to_gray8);
+/** + * drm_fb_blit_rect_dstclip - Copy parts of a framebuffer to display memory + * @dst: The display memory to copy to + * @dst_pitch: Number of bytes between two consecutive scanlines within dst + * @dst_format: FOURCC code of the display's color format + * @vmap: The framebuffer memory to copy from + * @fb: The framebuffer to copy from + * @clip: Clip rectangle area to copy + * + * This function copies parts of a framebuffer to display memory. If the + * formats of the display and the framebuffer mismatch, the blit function + * will attempt to convert between them. + * + * Use drm_fb_blit_dstclip() to copy the full framebuffer. + * + * Returns: + * 0 on success, or + * -EINVAL if the color-format conversion failed, or + * a negative error code otherwise. + */ +int drm_fb_blit_rect_dstclip(void __iomem *dst, unsigned int dst_pitch, + uint32_t dst_format, void *vmap, + struct drm_framebuffer *fb, + struct drm_rect *clip) +{ + uint32_t fb_format = fb->format->format; + + /* treat alpha channel like filler bits */ + if (fb_format == DRM_FORMAT_ARGB8888) + fb_format = DRM_FORMAT_XRGB8888; + if (dst_format == DRM_FORMAT_ARGB8888) + dst_format = DRM_FORMAT_XRGB8888; + + if (dst_format == fb_format) { + drm_fb_memcpy_dstclip(dst, dst_pitch, vmap, fb, clip); + return 0; + + } else if (dst_format == DRM_FORMAT_RGB565) { + if (fb_format == DRM_FORMAT_XRGB8888) { + drm_fb_xrgb8888_to_rgb565_dstclip(dst, dst_pitch, + vmap, fb, clip, + false); + return 0; + } + } else if (dst_format == DRM_FORMAT_RGB888) { + if (fb_format == DRM_FORMAT_XRGB8888) { + drm_fb_xrgb8888_to_rgb888_dstclip(dst, dst_pitch, + vmap, fb, clip); + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL(drm_fb_blit_rect_dstclip); + +/** + * drm_fb_blit_dstclip - Copy framebuffer to display memory + * @dst: The display memory to copy to + * @dst_pitch: Number of bytes between two consecutive scanlines within dst + * @dst_format: FOURCC code of the display's color format + * @vmap: The framebuffer memory to copy from + * @fb: The framebuffer to copy from + * + * This function copies a full framebuffer to display memory. If the formats + * of the display and the framebuffer mismatch, the copy function will + * attempt to convert between them. + * + * See drm_fb_blit_rect_dstclip() for more inforamtion. + * + * Returns: + * 0 on success, or a negative error code otherwise. + */ +int drm_fb_blit_dstclip(void __iomem *dst, unsigned int dst_pitch, + uint32_t dst_format, void *vmap, + struct drm_framebuffer *fb) +{ + struct drm_rect fullscreen = { + .x1 = 0, + .x2 = fb->width, + .y1 = 0, + .y2 = fb->height, + }; + return drm_fb_blit_rect_dstclip(dst, dst_pitch, dst_format, vmap, fb, + &fullscreen); +} +EXPORT_SYMBOL(drm_fb_blit_dstclip); diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h index 2b5036a5fbe7..4e0258a61311 100644 --- a/include/drm/drm_format_helper.h +++ b/include/drm/drm_format_helper.h @@ -28,4 +28,12 @@ void drm_fb_xrgb8888_to_rgb888_dstclip(void __iomem *dst, unsigned int dst_pitch void drm_fb_xrgb8888_to_gray8(u8 *dst, void *vaddr, struct drm_framebuffer *fb, struct drm_rect *clip);
+int drm_fb_blit_rect_dstclip(void __iomem *dst, unsigned int dst_pitch, + uint32_t dst_format, void *vmap, + struct drm_framebuffer *fb, + struct drm_rect *rect); +int drm_fb_blit_dstclip(void __iomem *dst, unsigned int dst_pitch, + uint32_t dst_format, void *vmap, + struct drm_framebuffer *fb); + #endif /* __LINUX_DRM_FORMAT_HELPER_H */
Fbdev's helpers for handling conflicting framebuffers are related to framebuffer apertures, not console emulation. Therefore move them into a drm_aperture.h, which will contain the interfaces for the new aperture helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net --- Documentation/gpu/drm-internals.rst | 6 +++ include/drm/drm_aperture.h | 60 +++++++++++++++++++++++++++++ include/drm/drm_fb_helper.h | 56 ++------------------------- 3 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 include/drm/drm_aperture.h
diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 12272b168580..4c7642d2ca34 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -75,6 +75,12 @@ update it, its value is mostly useless. The DRM core prints it to the kernel log at initialization time and passes it to userspace through the DRM_IOCTL_VERSION ioctl.
+Managing Ownership of the Framebuffer Aperture +---------------------------------------------- + +.. kernel-doc:: include/drm/drm_aperture.h + :internal: + Device Instance and Driver Handling -----------------------------------
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h new file mode 100644 index 000000000000..13766efe9517 --- /dev/null +++ b/include/drm/drm_aperture.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef _DRM_APERTURE_H_ +#define _DRM_APERTURE_H_ + +#include <linux/fb.h> +#include <linux/vgaarb.h> + +/** + * drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers + * @a: memory range, users of which are to be removed + * @name: requesting driver name + * @primary: also kick vga16fb if present + * + * This function removes framebuffer devices (initialized by firmware/bootloader) + * which use memory range described by @a. If @a is NULL all such devices are + * removed. + */ +static inline int +drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, + const char *name, bool primary) +{ +#if IS_REACHABLE(CONFIG_FB) + return remove_conflicting_framebuffers(a, name, primary); +#else + return 0; +#endif +} + +/** + * drm_fb_helper_remove_conflicting_pci_framebuffers - remove firmware-configured + * framebuffers for PCI devices + * @pdev: PCI device + * @name: requesting driver name + * + * This function removes framebuffer devices (eg. initialized by firmware) + * using memory range configured for any of @pdev's memory bars. + * + * The function assumes that PCI device with shadowed ROM drives a primary + * display and so kicks out vga16fb. + */ +static inline int +drm_fb_helper_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, + const char *name) +{ + int ret = 0; + + /* + * WARNING: Apparently we must kick fbdev drivers before vgacon, + * otherwise the vga fbdev driver falls over. + */ +#if IS_REACHABLE(CONFIG_FB) + ret = remove_conflicting_pci_framebuffers(pdev, name); +#endif + if (ret == 0) + ret = vga_remove_vgacon(pdev); + return ret; +} + +#endif diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index 3b273f9ca39a..d06a3942fddb 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -30,13 +30,13 @@ #ifndef DRM_FB_HELPER_H #define DRM_FB_HELPER_H
-struct drm_fb_helper; - +#include <drm/drm_aperture.h> #include <drm/drm_client.h> #include <drm/drm_crtc.h> #include <drm/drm_device.h> #include <linux/kgdb.h> -#include <linux/vgaarb.h> + +struct drm_fb_helper;
enum mode_set_atomic { LEAVE_ATOMIC_MODE_SET, @@ -451,54 +451,4 @@ drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
#endif
-/** - * drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers - * @a: memory range, users of which are to be removed - * @name: requesting driver name - * @primary: also kick vga16fb if present - * - * This function removes framebuffer devices (initialized by firmware/bootloader) - * which use memory range described by @a. If @a is NULL all such devices are - * removed. - */ -static inline int -drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, - const char *name, bool primary) -{ -#if IS_REACHABLE(CONFIG_FB) - return remove_conflicting_framebuffers(a, name, primary); -#else - return 0; -#endif -} - -/** - * drm_fb_helper_remove_conflicting_pci_framebuffers - remove firmware-configured framebuffers for PCI devices - * @pdev: PCI device - * @name: requesting driver name - * - * This function removes framebuffer devices (eg. initialized by firmware) - * using memory range configured for any of @pdev's memory bars. - * - * The function assumes that PCI device with shadowed ROM drives a primary - * display and so kicks out vga16fb. - */ -static inline int -drm_fb_helper_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, - const char *name) -{ - int ret = 0; - - /* - * WARNING: Apparently we must kick fbdev drivers before vgacon, - * otherwise the vga fbdev driver falls over. - */ -#if IS_REACHABLE(CONFIG_FB) - ret = remove_conflicting_pci_framebuffers(pdev, name); -#endif - if (ret == 0) - ret = vga_remove_vgacon(pdev); - return ret; -} - #endif
On Thu, Mar 18, 2021 at 11:29:14AM +0100, Thomas Zimmermann wrote:
Fbdev's helpers for handling conflicting framebuffers are related to framebuffer apertures, not console emulation. Therefore move them into a drm_aperture.h, which will contain the interfaces for the new aperture helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Documentation/gpu/drm-internals.rst | 6 +++ include/drm/drm_aperture.h | 60 +++++++++++++++++++++++++++++ include/drm/drm_fb_helper.h | 56 ++------------------------- 3 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 include/drm/drm_aperture.h
diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 12272b168580..4c7642d2ca34 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -75,6 +75,12 @@ update it, its value is mostly useless. The DRM core prints it to the kernel log at initialization time and passes it to userspace through the DRM_IOCTL_VERSION ioctl.
+Managing Ownership of the Framebuffer Aperture +----------------------------------------------
+.. kernel-doc:: include/drm/drm_aperture.h
- :internal:
Device Instance and Driver Handling
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h new file mode 100644 index 000000000000..13766efe9517 --- /dev/null +++ b/include/drm/drm_aperture.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: MIT */
+#ifndef _DRM_APERTURE_H_ +#define _DRM_APERTURE_H_
+#include <linux/fb.h> +#include <linux/vgaarb.h>
+/**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
Annoying bikeshed, but I'd give them drm_aperture_ prefixes, for ocd consistency. Also make them real functions, they're quite big and will grow more in the next patch.
I'm also not super happy about the naming here but oh well.
Either way: Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
- @a: memory range, users of which are to be removed
- @name: requesting driver name
- @primary: also kick vga16fb if present
- This function removes framebuffer devices (initialized by firmware/bootloader)
- which use memory range described by @a. If @a is NULL all such devices are
- removed.
- */
+static inline int +drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a,
const char *name, bool primary)
+{ +#if IS_REACHABLE(CONFIG_FB)
- return remove_conflicting_framebuffers(a, name, primary);
+#else
- return 0;
+#endif +}
+/**
- drm_fb_helper_remove_conflicting_pci_framebuffers - remove firmware-configured
framebuffers for PCI devices
- @pdev: PCI device
- @name: requesting driver name
- This function removes framebuffer devices (eg. initialized by firmware)
- using memory range configured for any of @pdev's memory bars.
- The function assumes that PCI device with shadowed ROM drives a primary
- display and so kicks out vga16fb.
- */
+static inline int +drm_fb_helper_remove_conflicting_pci_framebuffers(struct pci_dev *pdev,
const char *name)
+{
- int ret = 0;
- /*
* WARNING: Apparently we must kick fbdev drivers before vgacon,
* otherwise the vga fbdev driver falls over.
*/
+#if IS_REACHABLE(CONFIG_FB)
- ret = remove_conflicting_pci_framebuffers(pdev, name);
+#endif
- if (ret == 0)
ret = vga_remove_vgacon(pdev);
- return ret;
+}
+#endif diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index 3b273f9ca39a..d06a3942fddb 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -30,13 +30,13 @@ #ifndef DRM_FB_HELPER_H #define DRM_FB_HELPER_H
-struct drm_fb_helper;
+#include <drm/drm_aperture.h> #include <drm/drm_client.h> #include <drm/drm_crtc.h> #include <drm/drm_device.h> #include <linux/kgdb.h> -#include <linux/vgaarb.h>
+struct drm_fb_helper;
enum mode_set_atomic { LEAVE_ATOMIC_MODE_SET, @@ -451,54 +451,4 @@ drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
#endif
-/**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
- @name: requesting driver name
- @primary: also kick vga16fb if present
- This function removes framebuffer devices (initialized by firmware/bootloader)
- which use memory range described by @a. If @a is NULL all such devices are
- removed.
- */
-static inline int -drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a,
const char *name, bool primary)
-{ -#if IS_REACHABLE(CONFIG_FB)
- return remove_conflicting_framebuffers(a, name, primary);
-#else
- return 0;
-#endif -}
-/**
- drm_fb_helper_remove_conflicting_pci_framebuffers - remove firmware-configured framebuffers for PCI devices
- @pdev: PCI device
- @name: requesting driver name
- This function removes framebuffer devices (eg. initialized by firmware)
- using memory range configured for any of @pdev's memory bars.
- The function assumes that PCI device with shadowed ROM drives a primary
- display and so kicks out vga16fb.
- */
-static inline int -drm_fb_helper_remove_conflicting_pci_framebuffers(struct pci_dev *pdev,
const char *name)
-{
- int ret = 0;
- /*
* WARNING: Apparently we must kick fbdev drivers before vgacon,
* otherwise the vga fbdev driver falls over.
*/
-#if IS_REACHABLE(CONFIG_FB)
- ret = remove_conflicting_pci_framebuffers(pdev, name);
-#endif
- if (ret == 0)
ret = vga_remove_vgacon(pdev);
- return ret;
-}
#endif
2.30.1
Hi
Am 08.04.21 um 11:50 schrieb Daniel Vetter:
On Thu, Mar 18, 2021 at 11:29:14AM +0100, Thomas Zimmermann wrote:
Fbdev's helpers for handling conflicting framebuffers are related to framebuffer apertures, not console emulation. Therefore move them into a drm_aperture.h, which will contain the interfaces for the new aperture helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Documentation/gpu/drm-internals.rst | 6 +++ include/drm/drm_aperture.h | 60 +++++++++++++++++++++++++++++ include/drm/drm_fb_helper.h | 56 ++------------------------- 3 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 include/drm/drm_aperture.h
diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 12272b168580..4c7642d2ca34 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -75,6 +75,12 @@ update it, its value is mostly useless. The DRM core prints it to the kernel log at initialization time and passes it to userspace through the DRM_IOCTL_VERSION ioctl.
+Managing Ownership of the Framebuffer Aperture +----------------------------------------------
+.. kernel-doc:: include/drm/drm_aperture.h
- :internal:
Device Instance and Driver Handling
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h new file mode 100644 index 000000000000..13766efe9517 --- /dev/null +++ b/include/drm/drm_aperture.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: MIT */
+#ifndef _DRM_APERTURE_H_ +#define _DRM_APERTURE_H_
+#include <linux/fb.h> +#include <linux/vgaarb.h>
+/**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
Annoying bikeshed, but I'd give them drm_aperture_ prefixes, for ocd consistency. Also make them real functions, they're quite big and will grow more in the next patch.
I'm also not super happy about the naming here but oh well.
The original name for this was platform helpers, which was worse. So it's not like we're not improving. :)
I'll take this patch + some docs from patch 4 + your feedback and turn it into a separate patchset. It should be useful even without simpledrm.
Best regards Thomas
Either way: Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
- @a: memory range, users of which are to be removed
- @name: requesting driver name
- @primary: also kick vga16fb if present
- This function removes framebuffer devices (initialized by firmware/bootloader)
- which use memory range described by @a. If @a is NULL all such devices are
- removed.
- */
+static inline int +drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a,
const char *name, bool primary)
+{ +#if IS_REACHABLE(CONFIG_FB)
- return remove_conflicting_framebuffers(a, name, primary);
+#else
- return 0;
+#endif +}
+/**
- drm_fb_helper_remove_conflicting_pci_framebuffers - remove firmware-configured
framebuffers for PCI devices
- @pdev: PCI device
- @name: requesting driver name
- This function removes framebuffer devices (eg. initialized by firmware)
- using memory range configured for any of @pdev's memory bars.
- The function assumes that PCI device with shadowed ROM drives a primary
- display and so kicks out vga16fb.
- */
+static inline int +drm_fb_helper_remove_conflicting_pci_framebuffers(struct pci_dev *pdev,
const char *name)
+{
- int ret = 0;
- /*
* WARNING: Apparently we must kick fbdev drivers before vgacon,
* otherwise the vga fbdev driver falls over.
*/
+#if IS_REACHABLE(CONFIG_FB)
- ret = remove_conflicting_pci_framebuffers(pdev, name);
+#endif
- if (ret == 0)
ret = vga_remove_vgacon(pdev);
- return ret;
+}
+#endif diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index 3b273f9ca39a..d06a3942fddb 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -30,13 +30,13 @@ #ifndef DRM_FB_HELPER_H #define DRM_FB_HELPER_H
-struct drm_fb_helper;
+#include <drm/drm_aperture.h> #include <drm/drm_client.h> #include <drm/drm_crtc.h> #include <drm/drm_device.h> #include <linux/kgdb.h> -#include <linux/vgaarb.h>
+struct drm_fb_helper;
enum mode_set_atomic { LEAVE_ATOMIC_MODE_SET, @@ -451,54 +451,4 @@ drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
#endif
-/**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
- @name: requesting driver name
- @primary: also kick vga16fb if present
- This function removes framebuffer devices (initialized by firmware/bootloader)
- which use memory range described by @a. If @a is NULL all such devices are
- removed.
- */
-static inline int -drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a,
const char *name, bool primary)
-{ -#if IS_REACHABLE(CONFIG_FB)
- return remove_conflicting_framebuffers(a, name, primary);
-#else
- return 0;
-#endif -}
-/**
- drm_fb_helper_remove_conflicting_pci_framebuffers - remove firmware-configured framebuffers for PCI devices
- @pdev: PCI device
- @name: requesting driver name
- This function removes framebuffer devices (eg. initialized by firmware)
- using memory range configured for any of @pdev's memory bars.
- The function assumes that PCI device with shadowed ROM drives a primary
- display and so kicks out vga16fb.
- */
-static inline int -drm_fb_helper_remove_conflicting_pci_framebuffers(struct pci_dev *pdev,
const char *name)
-{
- int ret = 0;
- /*
* WARNING: Apparently we must kick fbdev drivers before vgacon,
* otherwise the vga fbdev driver falls over.
*/
-#if IS_REACHABLE(CONFIG_FB)
- ret = remove_conflicting_pci_framebuffers(pdev, name);
-#endif
- if (ret == 0)
ret = vga_remove_vgacon(pdev);
- return ret;
-}
- #endif
-- 2.30.1
Platform devices might operate on firmware framebuffers, such as VESA or EFI. Before a native driver for the graphics hardware can take over the device, it has to remove any platform driver that operates on the firmware framebuffer. Aperture helpers provide the infrastructure for platform drivers to acquire firmware framebuffers, and for native drivers to remove them later on.
It works similar to the related fbdev mechanism. During initialization, the platform driver acquires the firmware framebuffer's I/O memory and provides a callback to be removed. The native driver later uses this information to remove any platform driver for it's framebuffer I/O memory.
The aperture removal code is integrated into the existing code for removing conflicting framebuffers, so native drivers use it automatically.
v2: * rename plaform helpers to aperture helpers * tie to device lifetime with devm_ functions * removed unsued remove() callback * rename kickout to detach * make struct drm_aperture private * rebase onto existing drm_aperture.h header file * use MIT license only for simplicity * documentation
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net --- Documentation/gpu/drm-internals.rst | 6 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++++++++++++++++++++++ include/drm/drm_aperture.h | 38 +++- 5 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/drm_aperture.c
diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 4c7642d2ca34..06af044c882f 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -78,9 +78,15 @@ DRM_IOCTL_VERSION ioctl. Managing Ownership of the Framebuffer Aperture ----------------------------------------------
+.. kernel-doc:: drivers/gpu/drm/drm_aperture.c + :doc: overview + .. kernel-doc:: include/drm/drm_aperture.h :internal:
+.. kernel-doc:: drivers/gpu/drm/drm_aperture.c + :export: + Device Instance and Driver Handling -----------------------------------
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1461652921be..b9d3fb91d22d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -221,6 +221,13 @@ config DRM_SCHED tristate depends on DRM
+config DRM_APERTURE + bool + depends on DRM + help + Controls ownership of graphics apertures. Required to + synchronize with firmware-based drivers. + source "drivers/gpu/drm/i2c/Kconfig"
source "drivers/gpu/drm/arm/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 5eb5bf7c16e3..c9ecb02df0f3 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -32,6 +32,7 @@ drm-$(CONFIG_AGP) += drm_agpsupport.o drm-$(CONFIG_PCI) += drm_pci.o drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o +drm-$(CONFIG_DRM_APERTURE) += drm_aperture.o
drm_vram_helper-y := drm_gem_vram_helper.o obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o diff --git a/drivers/gpu/drm/drm_aperture.c b/drivers/gpu/drm/drm_aperture.c new file mode 100644 index 000000000000..4b02b5fed0a1 --- /dev/null +++ b/drivers/gpu/drm/drm_aperture.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: MIT + +#include <linux/device.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <drm/drm_aperture.h> +#include <drm/drm_drv.h> +#include <drm/drm_print.h> + +/** + * DOC: overview + * + * A graphics device might be supported by different drivers, but only one + * driver can be active at any given time. Many systems load a generic + * graphics drivers, such as EFI-GOP or VESA, early during the boot process. + * During later boot stages, they replace the generic driver with a dedicated, + * hardware-specific driver. To take over the device the dedicated driver + * first has to remove the generic driver. DRM aperture functions manage + * ownership of DRM framebuffer memory and hand-over between drivers. + * + * DRM drivers should call drm_fb_helper_remove_conflicting_framebuffers() + * at the top of their probe function. The function removes any generic + * driver that is currently associated with the given framebuffer memory. + * If the framebuffer is located at PCI BAR 0, the rsp code looks as in the + * example given below. + * + * .. code-block:: c + * + * static int remove_conflicting_framebuffers(struct pci_dev *pdev) + * { + * struct apertures_struct *ap; + * bool primary = false; + * int ret; + * + * ap = alloc_apertures(1); + * if (!ap) + * return -ENOMEM; + * + * ap->ranges[0].base = pci_resource_start(pdev, 0); + * ap->ranges[0].size = pci_resource_len(pdev, 0); + * + * #ifdef CONFIG_X86 + * primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; + * #endif + * ret = drm_fb_helper_remove_conflicting_framebuffers(ap, "example driver", primary); + * kfree(ap); + * + * return ret; + * } + * + * static int probe(struct pci_dev *pdev) + * { + * int ret; + * + * // Remove any generic drivers... + * ret = remove_conflicting_framebuffers(pdev); + * if (ret) + * return ret; + * + * // ... and initialize the hardware. + * ... + * + * drm_dev_register(); + * + * return 0; + * } + * + * For PCI devices it is often sufficient to use drm_fb_helper_remove_conflicting_pci_framebuffers() + * and let it detect the framebuffer apertures automatically. + * + * .. code-block:: c + * + * static int probe(struct pci_dev *pdev) + * { + * int ret; + * + * // Remove any generic drivers... + * ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "example driver"); + * if (ret) + * return ret; + * + * // ... and initialize the hardware. + * ... + * + * drm_dev_register(); + * + * return 0; + * } + * + * Drivers that are susceptible to being removed be 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(). 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 struct drm_aperture_funcs ap_funcs = { + * .detach = ... + * }; + * + * static int acquire_framebuffers(struct drm_device *dev, struct pci_dev *pdev) + * { + * resource_size_t start, len; + * struct drm_aperture *ap; + * + * base = pci_resource_start(pdev, 0); + * size = pci_resource_len(pdev, 0); + * + * ap = devm_acquire_aperture(dev, base, size, &ap_funcs); + * if (IS_ERR(ap)) + * return PTR_ERR(ap); + * + * return 0; + * } + * + * static int probe(struct pci_dev *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(); + * + * return 0; + * } + * + * The generic driver is now subject to forced removal by other drivers. This + * is when the detach function in struct &drm_aperture_funcs comes into play. + * When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al + * for the registered framebuffer range, the DRM core calls struct + * &drm_aperture_funcs.detach and the generic driver has to onload itself. It + * may not access the device's registers, framebuffer memory, ROM, etc after + * detach returned. If the driver supports hotplugging, detach can be treated + * like an unplug event. + * + * .. code-block:: c + * + * static void detach_from_device(struct drm_device *dev, + * resource_size_t base, + * resource_size_t size) + * { + * // Signal unplug + * drm_dev_unplug(dev); + * + * // Maybe do other clean-up operations + * ... + * } + * + * static struct drm_aperture_funcs ap_funcs = { + * .detach = detach_from_device, + * }; + */ + +/** + * struct drm_aperture - Represents a DRM framebuffer aperture + * + * This structure has no public fields. + */ +struct drm_aperture { + struct drm_device *dev; + resource_size_t base; + resource_size_t size; + + const struct drm_aperture_funcs *funcs; + + struct list_head lh; +}; + +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) + mutex_lock(&drm_apertures_lock); + + list_del(&ap->lh); + + if (!detached) + mutex_unlock(&drm_apertures_lock); +} + +/** + * devm_aperture_acquire - Acquires ownership of a 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 + * @funcs: callback functions + * + * Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this + * function. Acquired apertures are released automatically if the underlying + * device goes away. + * + * Returns: + * An instance of struct &drm_aperture on success, or a pointer-encoded + * errno value otherwise. + */ +struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev, + resource_size_t base, resource_size_t size, + const struct drm_aperture_funcs *funcs) +{ + size_t end = base + size; + struct list_head *pos; + struct drm_aperture *ap; + int ret; + + 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 ERR_PTR(-EBUSY); + } + + ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL); + if (!ap) + return ERR_PTR(-ENOMEM); + + ap->dev = dev; + ap->base = base; + ap->size = size; + ap->funcs = funcs; + INIT_LIST_HEAD(&ap->lh); + + list_add(&ap->lh, &drm_apertures); + + mutex_unlock(&drm_apertures_lock); + + ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap); + if (ret) + return ERR_PTR(ret); + + return ap; +} +EXPORT_SYMBOL(devm_aperture_acquire); + +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 (!overlap(base, end, ap->base, ap->base + ap->size)) + continue; + + ap->dev = NULL; /* detach from device */ + if (drm_WARN_ON(dev, !ap->funcs->detach)) + continue; + ap->funcs->detach(dev, ap->base, ap->size); + } + + mutex_unlock(&drm_apertures_lock); +} +EXPORT_SYMBOL(drm_aperture_detach_drivers); diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_
#include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h>
+struct drm_aperture; +struct drm_device; + +struct drm_aperture_funcs { + void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size); +}; + +struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev, + resource_size_t base, resource_size_t size, + const struct drm_aperture_funcs *funcs); + +#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ +} +#endif + /** * drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers * @a: memory range, users of which are to be removed @@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) { + int i; + + for (i = 0; i < a->count; ++i) + drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size); + #if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else @@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
On Thu, Mar 18, 2021 at 11:29:15AM +0100, Thomas Zimmermann wrote:
Platform devices might operate on firmware framebuffers, such as VESA or EFI. Before a native driver for the graphics hardware can take over the device, it has to remove any platform driver that operates on the firmware framebuffer. Aperture helpers provide the infrastructure for platform drivers to acquire firmware framebuffers, and for native drivers to remove them later on.
It works similar to the related fbdev mechanism. During initialization, the platform driver acquires the firmware framebuffer's I/O memory and provides a callback to be removed. The native driver later uses this information to remove any platform driver for it's framebuffer I/O memory.
The aperture removal code is integrated into the existing code for removing conflicting framebuffers, so native drivers use it automatically.
v2:
- rename plaform helpers to aperture helpers
- tie to device lifetime with devm_ functions
- removed unsued remove() callback
- rename kickout to detach
- make struct drm_aperture private
- rebase onto existing drm_aperture.h header file
- use MIT license only for simplicity
- documentation
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Bunch of bikesheds for your considerations below, but overall lgtm.
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
Cheers, Daniel
Documentation/gpu/drm-internals.rst | 6 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++++++++++++++++++++++ include/drm/drm_aperture.h | 38 +++- 5 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/drm_aperture.c
diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 4c7642d2ca34..06af044c882f 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -78,9 +78,15 @@ DRM_IOCTL_VERSION ioctl. Managing Ownership of the Framebuffer Aperture
+.. kernel-doc:: drivers/gpu/drm/drm_aperture.c
- :doc: overview
.. kernel-doc:: include/drm/drm_aperture.h :internal:
+.. kernel-doc:: drivers/gpu/drm/drm_aperture.c
- :export:
Device Instance and Driver Handling
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1461652921be..b9d3fb91d22d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -221,6 +221,13 @@ config DRM_SCHED tristate depends on DRM
+config DRM_APERTURE
- bool
- depends on DRM
- help
Controls ownership of graphics apertures. Required to
synchronize with firmware-based drivers.
Uh I'm not a big fan of Kconfig and .ko modules for every little helper code. Imo just stuff this into the drm kms helpers and done. Or stuff it into drm core code, I think either is a good case for this. Everything is its own module means we need to EXPORT_SYMBOL more stuff, and then drivers get funny ideas about using these internals ...
source "drivers/gpu/drm/i2c/Kconfig"
source "drivers/gpu/drm/arm/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 5eb5bf7c16e3..c9ecb02df0f3 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -32,6 +32,7 @@ drm-$(CONFIG_AGP) += drm_agpsupport.o drm-$(CONFIG_PCI) += drm_pci.o drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o +drm-$(CONFIG_DRM_APERTURE) += drm_aperture.o
drm_vram_helper-y := drm_gem_vram_helper.o obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o diff --git a/drivers/gpu/drm/drm_aperture.c b/drivers/gpu/drm/drm_aperture.c new file mode 100644 index 000000000000..4b02b5fed0a1 --- /dev/null +++ b/drivers/gpu/drm/drm_aperture.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: MIT
+#include <linux/device.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/types.h>
+#include <drm/drm_aperture.h> +#include <drm/drm_drv.h> +#include <drm/drm_print.h>
+/**
- DOC: overview
- A graphics device might be supported by different drivers, but only one
- driver can be active at any given time. Many systems load a generic
- graphics drivers, such as EFI-GOP or VESA, early during the boot process.
- During later boot stages, they replace the generic driver with a dedicated,
- hardware-specific driver. To take over the device the dedicated driver
- first has to remove the generic driver. DRM aperture functions manage
- ownership of DRM framebuffer memory and hand-over between drivers.
- DRM drivers should call drm_fb_helper_remove_conflicting_framebuffers()
- at the top of their probe function. The function removes any generic
- driver that is currently associated with the given framebuffer memory.
- If the framebuffer is located at PCI BAR 0, the rsp code looks as in the
- example given below.
- .. code-block:: c
- static int remove_conflicting_framebuffers(struct pci_dev *pdev)
- {
struct apertures_struct *ap;
bool primary = false;
int ret;
ap = alloc_apertures(1);
if (!ap)
return -ENOMEM;
ap->ranges[0].base = pci_resource_start(pdev, 0);
ap->ranges[0].size = pci_resource_len(pdev, 0);
- #ifdef CONFIG_X86
primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
- #endif
ret = drm_fb_helper_remove_conflicting_framebuffers(ap, "example driver", primary);
kfree(ap);
return ret;
- }
- static int probe(struct pci_dev *pdev)
- {
int ret;
// Remove any generic drivers...
ret = remove_conflicting_framebuffers(pdev);
if (ret)
return ret;
// ... and initialize the hardware.
...
drm_dev_register();
return 0;
- }
- For PCI devices it is often sufficient to use drm_fb_helper_remove_conflicting_pci_framebuffers()
- and let it detect the framebuffer apertures automatically.
Maybe just me, but to avoid overstretching the attention spawn of doc readers I'd avoid this example here. And maybe make the recommendation stronger, e.g. "PCI device drivers can avoid open-coding remove_conflicting_framebuffers() by calling drm_fb_helper_remove_conflicting_pci_framebuffers()."
- .. code-block:: c
- static int probe(struct pci_dev *pdev)
- {
int ret;
// Remove any generic drivers...
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "example driver");
if (ret)
return ret;
// ... and initialize the hardware.
...
drm_dev_register();
return 0;
- }
- Drivers that are susceptible to being removed be 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(). 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 struct drm_aperture_funcs ap_funcs = {
.detach = ...
Is there really value in allowing/forcing drivers to set up their own detach ops? You already make this specific to struct drm_device, an implementation that just calls drm_dev_unplug feels like the right thing to do?
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
- };
- static int acquire_framebuffers(struct drm_device *dev, struct pci_dev *pdev)
- {
resource_size_t start, len;
struct drm_aperture *ap;
base = pci_resource_start(pdev, 0);
size = pci_resource_len(pdev, 0);
ap = devm_acquire_aperture(dev, base, size, &ap_funcs);
if (IS_ERR(ap))
return PTR_ERR(ap);
return 0;
- }
- static int probe(struct pci_dev *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();
return 0;
- }
- The generic driver is now subject to forced removal by other drivers. This
- is when the detach function in struct &drm_aperture_funcs comes into play.
- When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al
- for the registered framebuffer range, the DRM core calls struct
- &drm_aperture_funcs.detach and the generic driver has to onload itself. It
- may not access the device's registers, framebuffer memory, ROM, etc after
- detach returned. If the driver supports hotplugging, detach can be treated
- like an unplug event.
- .. code-block:: c
- static void detach_from_device(struct drm_device *dev,
resource_size_t base,
resource_size_t size)
- {
// Signal unplug
drm_dev_unplug(dev);
// Maybe do other clean-up operations
...
- }
- static struct drm_aperture_funcs ap_funcs = {
.detach = detach_from_device,
- };
- */
+/**
- struct drm_aperture - Represents a DRM framebuffer aperture
- This structure has no public fields.
- */
+struct drm_aperture {
- struct drm_device *dev;
- resource_size_t base;
- resource_size_t size;
- const struct drm_aperture_funcs *funcs;
- struct list_head lh;
+};
+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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
- list_del(&ap->lh);
- if (!detached)
mutex_unlock(&drm_apertures_lock);
+}
+/**
- devm_aperture_acquire - Acquires ownership of a 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
- @funcs: callback functions
- Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this
- function. Acquired apertures are released automatically if the underlying
- device goes away.
- Returns:
- An instance of struct &drm_aperture on success, or a pointer-encoded
- errno value otherwise.
- */
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs)
+{
- size_t end = base + size;
- struct list_head *pos;
- struct drm_aperture *ap;
- int ret;
- 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 ERR_PTR(-EBUSY);
- }
- ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
- if (!ap)
return ERR_PTR(-ENOMEM);
- ap->dev = dev;
- ap->base = base;
- ap->size = size;
- ap->funcs = funcs;
- INIT_LIST_HEAD(&ap->lh);
- list_add(&ap->lh, &drm_apertures);
- mutex_unlock(&drm_apertures_lock);
- ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
- if (ret)
return ERR_PTR(ret);
- return ap;
+} +EXPORT_SYMBOL(devm_aperture_acquire);
+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 (!overlap(base, end, ap->base, ap->base + ap->size))
continue;
ap->dev = NULL; /* detach from device */
if (drm_WARN_ON(dev, !ap->funcs->detach))
continue;
ap->funcs->detach(dev, ap->base, ap->size);
- }
- mutex_unlock(&drm_apertures_lock);
+} +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers? Imo better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_
#include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h>
+struct drm_aperture; +struct drm_device;
+struct drm_aperture_funcs {
- void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size);
+};
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs);
+#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ +} +#endif
/**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
@@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) {
- int i;
- for (i = 0; i < a->count; ++i)
drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size);
#if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else @@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
-- 2.30.1
Hi
Am 08.04.21 um 11:48 schrieb Daniel Vetter:
Maybe just me, but to avoid overstretching the attention spawn of doc readers I'd avoid this example here. And maybe make the recommendation stronger, e.g. "PCI device drivers can avoid open-coding remove_conflicting_framebuffers() by calling drm_fb_helper_remove_conflicting_pci_framebuffers()."
It's a tutorial. In my expectation, everyone just copies the tutorial code and fills the gaps.
- .. code-block:: c
- static int probe(struct pci_dev *pdev)
- {
int ret;
// Remove any generic drivers...
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "example driver");
if (ret)
return ret;
// ... and initialize the hardware.
...
drm_dev_register();
return 0;
- }
- Drivers that are susceptible to being removed be 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(). 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 struct drm_aperture_funcs ap_funcs = {
.detach = ...
Is there really value in allowing/forcing drivers to set up their own detach ops? You already make this specific to struct drm_device, an implementation that just calls drm_dev_unplug feels like the right thing to do?
Is it that easy? simepldrm's detach function has code to synchronize with concurrent hotplug removals. If we can use drm_dev_unplug() for everything, I'm all for it.
Best regards Thomas
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
- };
- static int acquire_framebuffers(struct drm_device *dev, struct pci_dev *pdev)
- {
resource_size_t start, len;
struct drm_aperture *ap;
base = pci_resource_start(pdev, 0);
size = pci_resource_len(pdev, 0);
ap = devm_acquire_aperture(dev, base, size, &ap_funcs);
if (IS_ERR(ap))
return PTR_ERR(ap);
return 0;
- }
- static int probe(struct pci_dev *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();
return 0;
- }
- The generic driver is now subject to forced removal by other drivers. This
- is when the detach function in struct &drm_aperture_funcs comes into play.
- When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al
- for the registered framebuffer range, the DRM core calls struct
- &drm_aperture_funcs.detach and the generic driver has to onload itself. It
- may not access the device's registers, framebuffer memory, ROM, etc after
- detach returned. If the driver supports hotplugging, detach can be treated
- like an unplug event.
- .. code-block:: c
- static void detach_from_device(struct drm_device *dev,
resource_size_t base,
resource_size_t size)
- {
// Signal unplug
drm_dev_unplug(dev);
// Maybe do other clean-up operations
...
- }
- static struct drm_aperture_funcs ap_funcs = {
.detach = detach_from_device,
- };
- */
+/**
- struct drm_aperture - Represents a DRM framebuffer aperture
- This structure has no public fields.
- */
+struct drm_aperture {
- struct drm_device *dev;
- resource_size_t base;
- resource_size_t size;
- const struct drm_aperture_funcs *funcs;
- struct list_head lh;
+};
+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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
- list_del(&ap->lh);
- if (!detached)
mutex_unlock(&drm_apertures_lock);
+}
+/**
- devm_aperture_acquire - Acquires ownership of a 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
- @funcs: callback functions
- Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this
- function. Acquired apertures are released automatically if the underlying
- device goes away.
- Returns:
- An instance of struct &drm_aperture on success, or a pointer-encoded
- errno value otherwise.
- */
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs)
+{
- size_t end = base + size;
- struct list_head *pos;
- struct drm_aperture *ap;
- int ret;
- 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 ERR_PTR(-EBUSY);
- }
- ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
- if (!ap)
return ERR_PTR(-ENOMEM);
- ap->dev = dev;
- ap->base = base;
- ap->size = size;
- ap->funcs = funcs;
- INIT_LIST_HEAD(&ap->lh);
- list_add(&ap->lh, &drm_apertures);
- mutex_unlock(&drm_apertures_lock);
- ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
- if (ret)
return ERR_PTR(ret);
- return ap;
+} +EXPORT_SYMBOL(devm_aperture_acquire);
+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 (!overlap(base, end, ap->base, ap->base + ap->size))
continue;
ap->dev = NULL; /* detach from device */
if (drm_WARN_ON(dev, !ap->funcs->detach))
continue;
ap->funcs->detach(dev, ap->base, ap->size);
- }
- mutex_unlock(&drm_apertures_lock);
+} +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers? Imo better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_
#include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h>
+struct drm_aperture; +struct drm_device;
+struct drm_aperture_funcs {
- void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size);
+};
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs);
+#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ +} +#endif
- /**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
@@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) {
- int i;
- for (i = 0; i < a->count; ++i)
drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size);
- #if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else
@@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
-- 2.30.1
On Fri, Apr 09, 2021 at 09:06:56AM +0200, Thomas Zimmermann wrote:
Hi
Am 08.04.21 um 11:48 schrieb Daniel Vetter:
Maybe just me, but to avoid overstretching the attention spawn of doc readers I'd avoid this example here. And maybe make the recommendation stronger, e.g. "PCI device drivers can avoid open-coding remove_conflicting_framebuffers() by calling drm_fb_helper_remove_conflicting_pci_framebuffers()."
It's a tutorial. In my expectation, everyone just copies the tutorial code and fills the gaps.
Sure, but we also have default functions for most common cases, so most people just end up copypasting the single function call. Feels like overkill to have a tutorial for that.
Imo tutorial/pseudo-code are good if there's more involved code flow that many places need to copypaste and customize. Or to show how different functions work together collectively. This doesn't quite feel like it's clearing that bar.
And please don't get me wrong, solid docs is great. It's just that I think we need to have reader's attention span in mind too (and mine personally might be on the extremely short side here) to make sure our docs are effective at conveying information.
- .. code-block:: c
- static int probe(struct pci_dev *pdev)
- {
int ret;
// Remove any generic drivers...
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "example driver");
if (ret)
return ret;
// ... and initialize the hardware.
...
drm_dev_register();
return 0;
- }
- Drivers that are susceptible to being removed be 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(). 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 struct drm_aperture_funcs ap_funcs = {
.detach = ...
Is there really value in allowing/forcing drivers to set up their own detach ops? You already make this specific to struct drm_device, an implementation that just calls drm_dev_unplug feels like the right thing to do?
Is it that easy? simepldrm's detach function has code to synchronize with concurrent hotplug removals. If we can use drm_dev_unplug() for everything, I'm all for it.
Uh, I should have looked at the code instead of just asking silly questions :-)
Now I'm even more scared, and also more convinced that we're recreating a bad version of some of the core driver model concepts.
I think the ideal option here would be if drm_aperture could unload (unbind really) the platform driver for us, through the driver model. Then there's only one place that keeps track whether the driver is unbound or not. I'm not sure whether this can be done fully generic on a struct device, or whether we need special code for each type. Since atm we only have simpledrm we can just specialize on platform_device and it's good enough.
I think best here would be to Cc: gregkh on this patch and the simpledrm ->detach implementatation, and ask for his feedback as driver model maintainer. Maybe if you could hack together the platform_device unbind path as proof of concept would be even better.
Either way, this is really tricky. -Daniel
Best regards Thomas
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
- };
- static int acquire_framebuffers(struct drm_device *dev, struct pci_dev *pdev)
- {
resource_size_t start, len;
struct drm_aperture *ap;
base = pci_resource_start(pdev, 0);
size = pci_resource_len(pdev, 0);
ap = devm_acquire_aperture(dev, base, size, &ap_funcs);
if (IS_ERR(ap))
return PTR_ERR(ap);
return 0;
- }
- static int probe(struct pci_dev *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();
return 0;
- }
- The generic driver is now subject to forced removal by other drivers. This
- is when the detach function in struct &drm_aperture_funcs comes into play.
- When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al
- for the registered framebuffer range, the DRM core calls struct
- &drm_aperture_funcs.detach and the generic driver has to onload itself. It
- may not access the device's registers, framebuffer memory, ROM, etc after
- detach returned. If the driver supports hotplugging, detach can be treated
- like an unplug event.
- .. code-block:: c
- static void detach_from_device(struct drm_device *dev,
resource_size_t base,
resource_size_t size)
- {
// Signal unplug
drm_dev_unplug(dev);
// Maybe do other clean-up operations
...
- }
- static struct drm_aperture_funcs ap_funcs = {
.detach = detach_from_device,
- };
- */
+/**
- struct drm_aperture - Represents a DRM framebuffer aperture
- This structure has no public fields.
- */
+struct drm_aperture {
- struct drm_device *dev;
- resource_size_t base;
- resource_size_t size;
- const struct drm_aperture_funcs *funcs;
- struct list_head lh;
+};
+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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
- list_del(&ap->lh);
- if (!detached)
mutex_unlock(&drm_apertures_lock);
+}
+/**
- devm_aperture_acquire - Acquires ownership of a 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
- @funcs: callback functions
- Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this
- function. Acquired apertures are released automatically if the underlying
- device goes away.
- Returns:
- An instance of struct &drm_aperture on success, or a pointer-encoded
- errno value otherwise.
- */
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs)
+{
- size_t end = base + size;
- struct list_head *pos;
- struct drm_aperture *ap;
- int ret;
- 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 ERR_PTR(-EBUSY);
- }
- ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
- if (!ap)
return ERR_PTR(-ENOMEM);
- ap->dev = dev;
- ap->base = base;
- ap->size = size;
- ap->funcs = funcs;
- INIT_LIST_HEAD(&ap->lh);
- list_add(&ap->lh, &drm_apertures);
- mutex_unlock(&drm_apertures_lock);
- ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
- if (ret)
return ERR_PTR(ret);
- return ap;
+} +EXPORT_SYMBOL(devm_aperture_acquire);
+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 (!overlap(base, end, ap->base, ap->base + ap->size))
continue;
ap->dev = NULL; /* detach from device */
if (drm_WARN_ON(dev, !ap->funcs->detach))
continue;
ap->funcs->detach(dev, ap->base, ap->size);
- }
- mutex_unlock(&drm_apertures_lock);
+} +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers? Imo better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_ #include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h> +struct drm_aperture; +struct drm_device;
+struct drm_aperture_funcs {
- void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size);
+};
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs);
+#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ +} +#endif
- /**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
@@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) {
- int i;
- for (i = 0; i < a->count; ++i)
drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size);
- #if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else
@@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
-- 2.30.1
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
Hi
Am 09.04.21 um 11:22 schrieb Daniel Vetter:
Is it that easy? simepldrm's detach function has code to synchronize with concurrent hotplug removals. If we can use drm_dev_unplug() for everything, I'm all for it.
Uh, I should have looked at the code instead of just asking silly questions :-)
Now I'm even more scared, and also more convinced that we're recreating
a
bad version of some of the core driver model concepts.
I think the ideal option here would be if drm_aperture could unload (unbind really) the platform driver for us, through the driver model. Then there's only one place that keeps track whether the driver is unbound or not. I'm not sure whether this can be done fully generic on a struct device, or whether we need special code for each type. Since atm we only have simpledrm we can just specialize on platform_device and it's good enough.
I meanwhile found that calling platform_device_unregister() is the right thing to do. It is like a hot-unplug event. It's simple to implement and removes the generic device as well. Any memory ranges for the generic device are gone as well. Only the native driver's native device will remain. That's better than the existing simplefb driver.
Which unregister function to call still driver-specific, so I kept the callback.
Best regards Thomas
I think best here would be to Cc: gregkh on this patch and the simpledrm ->detach implementatation, and ask for his feedback as driver model maintainer. Maybe if you could hack together the platform_device unbind path as proof of concept would be even better.
Either way, this is really tricky. -Daniel
Best regards Thomas
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
- };
- static int acquire_framebuffers(struct drm_device *dev, struct pci_dev *pdev)
- {
resource_size_t start, len;
struct drm_aperture *ap;
base = pci_resource_start(pdev, 0);
size = pci_resource_len(pdev, 0);
ap = devm_acquire_aperture(dev, base, size, &ap_funcs);
if (IS_ERR(ap))
return PTR_ERR(ap);
return 0;
- }
- static int probe(struct pci_dev *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();
return 0;
- }
- The generic driver is now subject to forced removal by other drivers. This
- is when the detach function in struct &drm_aperture_funcs comes into play.
- When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al
- for the registered framebuffer range, the DRM core calls struct
- &drm_aperture_funcs.detach and the generic driver has to onload itself. It
- may not access the device's registers, framebuffer memory, ROM, etc after
- detach returned. If the driver supports hotplugging, detach can be treated
- like an unplug event.
- .. code-block:: c
- static void detach_from_device(struct drm_device *dev,
resource_size_t base,
resource_size_t size)
- {
// Signal unplug
drm_dev_unplug(dev);
// Maybe do other clean-up operations
...
- }
- static struct drm_aperture_funcs ap_funcs = {
.detach = detach_from_device,
- };
- */
+/**
- struct drm_aperture - Represents a DRM framebuffer aperture
- This structure has no public fields.
- */
+struct drm_aperture {
- struct drm_device *dev;
- resource_size_t base;
- resource_size_t size;
- const struct drm_aperture_funcs *funcs;
- struct list_head lh;
+};
+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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
- list_del(&ap->lh);
- if (!detached)
mutex_unlock(&drm_apertures_lock);
+}
+/**
- devm_aperture_acquire - Acquires ownership of a 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
- @funcs: callback functions
- Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this
- function. Acquired apertures are released automatically if the underlying
- device goes away.
- Returns:
- An instance of struct &drm_aperture on success, or a pointer-encoded
- errno value otherwise.
- */
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs)
+{
- size_t end = base + size;
- struct list_head *pos;
- struct drm_aperture *ap;
- int ret;
- 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 ERR_PTR(-EBUSY);
- }
- ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
- if (!ap)
return ERR_PTR(-ENOMEM);
- ap->dev = dev;
- ap->base = base;
- ap->size = size;
- ap->funcs = funcs;
- INIT_LIST_HEAD(&ap->lh);
- list_add(&ap->lh, &drm_apertures);
- mutex_unlock(&drm_apertures_lock);
- ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
- if (ret)
return ERR_PTR(ret);
- return ap;
+} +EXPORT_SYMBOL(devm_aperture_acquire);
+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 (!overlap(base, end, ap->base, ap->base + ap->size))
continue;
ap->dev = NULL; /* detach from device */
if (drm_WARN_ON(dev, !ap->funcs->detach))
continue;
ap->funcs->detach(dev, ap->base, ap->size);
- }
- mutex_unlock(&drm_apertures_lock);
+} +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers?
Imo
better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_ #include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h> +struct drm_aperture; +struct drm_device;
+struct drm_aperture_funcs {
- void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size);
+};
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs);
+#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ +} +#endif
- /**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
@@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) {
- int i;
- for (i = 0; i < a->count; ++i)
drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size);
- #if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else
@@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
-- 2.30.1
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
On Thu, Apr 15, 2021 at 08:56:20AM +0200, Thomas Zimmermann wrote:
Hi
Am 09.04.21 um 11:22 schrieb Daniel Vetter:
Is it that easy? simepldrm's detach function has code to synchronize with concurrent hotplug removals. If we can use drm_dev_unplug() for everything, I'm all for it.
Uh, I should have looked at the code instead of just asking silly questions :-)
Now I'm even more scared, and also more convinced that we're recreating
a
bad version of some of the core driver model concepts.
I think the ideal option here would be if drm_aperture could unload (unbind really) the platform driver for us, through the driver model. Then there's only one place that keeps track whether the driver is unbound or not. I'm not sure whether this can be done fully generic on a struct device, or whether we need special code for each type. Since atm we only have simpledrm we can just specialize on platform_device and it's good enough.
I meanwhile found that calling platform_device_unregister() is the right thing to do. It is like a hot-unplug event. It's simple to implement and removes the generic device as well. Any memory ranges for the generic device are gone as well. Only the native driver's native device will remain. That's better than the existing simplefb driver.
That sounds great.
Which unregister function to call still driver-specific, so I kept the callback.
Could we have the callback in core code, and you do something like drm_aperture_acquire_platform (and later on drm_aperture_acquire_pci or whatever, although tbh I'm not sure we ever get anything else than platform). That function can do a runtime check that drm_device->dev is actually a platform dev.
Another idea: Do the runtime casting in the core without anything? Atm we have platform that needs support, maybe pci device, so we could easily extend this and just let it do the right thing. Then no callback is needed. I.e.
if (is_platform_dev(drm_device->dev)) platform_device_unregister(drm_device->dev); else WARN(1, "not yet implemented\n");
or something like that.
I just find the callback to essentially unregister a device a bit redundant. -Daniel
Best regards Thomas
I think best here would be to Cc: gregkh on this patch and the simpledrm ->detach implementatation, and ask for his feedback as driver model maintainer. Maybe if you could hack together the platform_device unbind path as proof of concept would be even better.
Either way, this is really tricky. -Daniel
Best regards Thomas
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
- };
- static int acquire_framebuffers(struct drm_device *dev, struct pci_dev *pdev)
- {
resource_size_t start, len;
struct drm_aperture *ap;
base = pci_resource_start(pdev, 0);
size = pci_resource_len(pdev, 0);
ap = devm_acquire_aperture(dev, base, size, &ap_funcs);
if (IS_ERR(ap))
return PTR_ERR(ap);
return 0;
- }
- static int probe(struct pci_dev *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();
return 0;
- }
- The generic driver is now subject to forced removal by other drivers. This
- is when the detach function in struct &drm_aperture_funcs comes into play.
- When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al
- for the registered framebuffer range, the DRM core calls struct
- &drm_aperture_funcs.detach and the generic driver has to onload itself. It
- may not access the device's registers, framebuffer memory, ROM, etc after
- detach returned. If the driver supports hotplugging, detach can be treated
- like an unplug event.
- .. code-block:: c
- static void detach_from_device(struct drm_device *dev,
resource_size_t base,
resource_size_t size)
- {
// Signal unplug
drm_dev_unplug(dev);
// Maybe do other clean-up operations
...
- }
- static struct drm_aperture_funcs ap_funcs = {
.detach = detach_from_device,
- };
- */
+/**
- struct drm_aperture - Represents a DRM framebuffer aperture
- This structure has no public fields.
- */
+struct drm_aperture {
- struct drm_device *dev;
- resource_size_t base;
- resource_size_t size;
- const struct drm_aperture_funcs *funcs;
- struct list_head lh;
+};
+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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
- list_del(&ap->lh);
- if (!detached)
mutex_unlock(&drm_apertures_lock);
+}
+/**
- devm_aperture_acquire - Acquires ownership of a 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
- @funcs: callback functions
- Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this
- function. Acquired apertures are released automatically if the underlying
- device goes away.
- Returns:
- An instance of struct &drm_aperture on success, or a pointer-encoded
- errno value otherwise.
- */
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs)
+{
- size_t end = base + size;
- struct list_head *pos;
- struct drm_aperture *ap;
- int ret;
- 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 ERR_PTR(-EBUSY);
- }
- ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
- if (!ap)
return ERR_PTR(-ENOMEM);
- ap->dev = dev;
- ap->base = base;
- ap->size = size;
- ap->funcs = funcs;
- INIT_LIST_HEAD(&ap->lh);
- list_add(&ap->lh, &drm_apertures);
- mutex_unlock(&drm_apertures_lock);
- ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
- if (ret)
return ERR_PTR(ret);
- return ap;
+} +EXPORT_SYMBOL(devm_aperture_acquire);
+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 (!overlap(base, end, ap->base, ap->base + ap->size))
continue;
ap->dev = NULL; /* detach from device */
if (drm_WARN_ON(dev, !ap->funcs->detach))
continue;
ap->funcs->detach(dev, ap->base, ap->size);
- }
- mutex_unlock(&drm_apertures_lock);
+} +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers?
Imo
better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_ #include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h> +struct drm_aperture; +struct drm_device;
+struct drm_aperture_funcs {
- void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size);
+};
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs);
+#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ +} +#endif
- /**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
@@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) {
- int i;
- for (i = 0; i < a->count; ++i)
drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size);
- #if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else
@@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
-- 2.30.1
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
Hi
Am 15.04.21 um 14:57 schrieb Daniel Vetter:
On Thu, Apr 15, 2021 at 08:56:20AM +0200, Thomas Zimmermann wrote:
Hi
Am 09.04.21 um 11:22 schrieb Daniel Vetter:
Is it that easy? simepldrm's detach function has code to synchronize
with
concurrent hotplug removals. If we can use drm_dev_unplug() for everything, I'm all for it.
Uh, I should have looked at the code instead of just asking silly questions :-)
Now I'm even more scared, and also more convinced that we're recreating
a
bad version of some of the core driver model concepts.
I think the ideal option here would be if drm_aperture could unload (unbind really) the platform driver for us, through the driver model.
Then
there's only one place that keeps track whether the driver is unbound
or
not. I'm not sure whether this can be done fully generic on a struct device, or whether we need special code for each type. Since atm we only have simpledrm we can just specialize on platform_device and it's good enough.
I meanwhile found that calling platform_device_unregister() is the right thing to do. It is like a hot-unplug event. It's simple to implement and removes the generic device as well. Any memory ranges for the generic device are gone as well. Only the native driver's native device will remain. That's better than the existing simplefb driver.
That sounds great.
Which unregister function to call still driver-specific, so I kept the callback.
Could we have the callback in core code, and you do something like drm_aperture_acquire_platform (and later on drm_aperture_acquire_pci or whatever, although tbh I'm not sure we ever get anything else than platform). That function can do a runtime check that drm_device->dev is actually a platform dev.
Somehow I knew you wouldn't like the current abstraction. :)
Another idea: Do the runtime casting in the core without anything? Atm we have platform that needs support, maybe pci device, so we could easily extend this and just let it do the right thing. Then no callback is needed. I.e.
if (is_platform_dev(drm_device->dev)) platform_device_unregister(drm_device->dev); else WARN(1, "not yet implemented\n");
or something like that.
I don't like that. I spend time to remove the usb and pci device pointers from code and structs. I don't want to introduce a new hard-coded special case here.
I just find the callback to essentially unregister a device a bit redundant.
I'd like to go with your first idea. The callback would be internal and the public acquire function is specifically for firmware-based platform devices. That covers simple-framebuffer, VESA, EFI, and probably any other generic interface that fbdev supported in the last 20+ yrs. I don't think we'll ever need anything else.
Still, I'd like to have some abstraction between the internals of the aperture helpers and our actual use case. I'll update the patchset accordingly.
Best regards Thomas
-Daniel
Best regards Thomas
I think best here would be to Cc: gregkh on this patch and the simpledrm ->detach implementatation, and ask for his feedback as driver model maintainer. Maybe if you could hack together the platform_device unbind path as proof of concept would be even better.
Either way, this is really tricky. -Daniel
Best regards Thomas
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
- };
- static int acquire_framebuffers(struct drm_device *dev, struct
pci_dev *pdev)
- {
resource_size_t start, len;
struct drm_aperture *ap;
base = pci_resource_start(pdev, 0);
size = pci_resource_len(pdev, 0);
ap = devm_acquire_aperture(dev, base, size, &ap_funcs);
if (IS_ERR(ap))
return PTR_ERR(ap);
return 0;
- }
- static int probe(struct pci_dev *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();
return 0;
- }
- The generic driver is now subject to forced removal by other drivers. This
- is when the detach function in struct &drm_aperture_funcs comes into play.
- When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al
- for the registered framebuffer range, the DRM core calls struct
- &drm_aperture_funcs.detach and the generic driver has to onload itself. It
- may not access the device's registers, framebuffer memory, ROM, etc after
- detach returned. If the driver supports hotplugging, detach can be treated
- like an unplug event.
- .. code-block:: c
- static void detach_from_device(struct drm_device *dev,
resource_size_t base,
resource_size_t size)
- {
// Signal unplug
drm_dev_unplug(dev);
// Maybe do other clean-up operations
...
- }
- static struct drm_aperture_funcs ap_funcs = {
.detach = detach_from_device,
- };
- */
+/**
- struct drm_aperture - Represents a DRM framebuffer aperture
- This structure has no public fields.
- */
+struct drm_aperture {
- struct drm_device *dev;
- resource_size_t base;
- resource_size_t size;
- const struct drm_aperture_funcs *funcs;
- struct list_head lh;
+};
+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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
- list_del(&ap->lh);
- if (!detached)
mutex_unlock(&drm_apertures_lock);
+}
+/**
- devm_aperture_acquire - Acquires ownership of a 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
- @funcs: callback functions
- Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this
- function. Acquired apertures are released automatically if the
underlying
- device goes away.
- Returns:
- An instance of struct &drm_aperture on success, or a pointer-encoded
- errno value otherwise.
- */
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs)
+{
- size_t end = base + size;
- struct list_head *pos;
- struct drm_aperture *ap;
- int ret;
- 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 ERR_PTR(-EBUSY);
- }
- ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
- if (!ap)
return ERR_PTR(-ENOMEM);
- ap->dev = dev;
- ap->base = base;
- ap->size = size;
- ap->funcs = funcs;
- INIT_LIST_HEAD(&ap->lh);
- list_add(&ap->lh, &drm_apertures);
- mutex_unlock(&drm_apertures_lock);
- ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
- if (ret)
return ERR_PTR(ret);
- return ap;
+} +EXPORT_SYMBOL(devm_aperture_acquire);
+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 (!overlap(base, end, ap->base, ap->base + ap->size))
continue;
ap->dev = NULL; /* detach from device */
if (drm_WARN_ON(dev, !ap->funcs->detach))
continue;
ap->funcs->detach(dev, ap->base, ap->size);
- }
- mutex_unlock(&drm_apertures_lock);
+} +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers?
Imo
better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_ #include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h> +struct drm_aperture; +struct drm_device;
+struct drm_aperture_funcs {
- void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size);
+};
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs);
+#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t
size)
+{ +} +#endif
- /**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
@@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) {
- int i;
- for (i = 0; i < a->count; ++i)
drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size);
- #if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else
@@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
-- 2.30.1
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
On Thu, Apr 15, 2021 at 09:12:14PM +0200, Thomas Zimmermann wrote:
Hi
Am 15.04.21 um 14:57 schrieb Daniel Vetter:
On Thu, Apr 15, 2021 at 08:56:20AM +0200, Thomas Zimmermann wrote:
Hi
Am 09.04.21 um 11:22 schrieb Daniel Vetter:
Is it that easy? simepldrm's detach function has code to synchronize
with
concurrent hotplug removals. If we can use drm_dev_unplug() for everything, I'm all for it.
Uh, I should have looked at the code instead of just asking silly questions :-)
Now I'm even more scared, and also more convinced that we're recreating
a
bad version of some of the core driver model concepts.
I think the ideal option here would be if drm_aperture could unload (unbind really) the platform driver for us, through the driver model.
Then
there's only one place that keeps track whether the driver is unbound
or
not. I'm not sure whether this can be done fully generic on a struct device, or whether we need special code for each type. Since atm we only have simpledrm we can just specialize on platform_device and it's good enough.
I meanwhile found that calling platform_device_unregister() is the right thing to do. It is like a hot-unplug event. It's simple to implement and removes the generic device as well. Any memory ranges for the generic device are gone as well. Only the native driver's native device will remain. That's better than the existing simplefb driver.
That sounds great.
Which unregister function to call still driver-specific, so I kept the callback.
Could we have the callback in core code, and you do something like drm_aperture_acquire_platform (and later on drm_aperture_acquire_pci or whatever, although tbh I'm not sure we ever get anything else than platform). That function can do a runtime check that drm_device->dev is actually a platform dev.
Somehow I knew you wouldn't like the current abstraction. :)
Another idea: Do the runtime casting in the core without anything? Atm we have platform that needs support, maybe pci device, so we could easily extend this and just let it do the right thing. Then no callback is needed. I.e.
if (is_platform_dev(drm_device->dev)) platform_device_unregister(drm_device->dev); else WARN(1, "not yet implemented\n");
or something like that.
I don't like that. I spend time to remove the usb and pci device pointers from code and structs. I don't want to introduce a new hard-coded special case here.
I just find the callback to essentially unregister a device a bit redundant.
I'd like to go with your first idea. The callback would be internal and the public acquire function is specifically for firmware-based platform devices. That covers simple-framebuffer, VESA, EFI, and probably any other generic interface that fbdev supported in the last 20+ yrs. I don't think we'll ever need anything else.
Still, I'd like to have some abstraction between the internals of the aperture helpers and our actual use case. I'll update the patchset accordingly.
Makes sense and I'm happy with that pick of color choice. That keeps the noise out of drivers, and also keeps the concepts clean internally. -Daniel
Best regards Thomas
-Daniel
Best regards Thomas
I think best here would be to Cc: gregkh on this patch and the simpledrm ->detach implementatation, and ask for his feedback as driver model maintainer. Maybe if you could hack together the platform_device unbind path as proof of concept would be even better.
Either way, this is really tricky. -Daniel
Best regards Thomas
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
> + * }; > + * > + * static int acquire_framebuffers(struct > drm_device *dev, struct
pci_dev *pdev)
> + * { > + * resource_size_t start, len; > + * struct drm_aperture *ap; > + * > + * base = pci_resource_start(pdev, 0); > + * size = pci_resource_len(pdev, 0); > + * > + * ap = devm_acquire_aperture(dev, base, size, &ap_funcs); > + * if (IS_ERR(ap)) > + * return PTR_ERR(ap); > + * > + * return 0; > + * } > + * > + * static int probe(struct pci_dev *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(); > + * > + * return 0; > + * } > + * > + * The generic driver is now subject to forced removal by other drivers. This > + * is when the detach function in struct &drm_aperture_funcs comes into play. > + * When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al > + * for the registered framebuffer range, the DRM core calls struct > + * &drm_aperture_funcs.detach and the generic driver has to onload itself. It > + * may not access the device's registers, framebuffer memory, ROM, etc after > + * detach returned. If the driver supports hotplugging, detach can be treated > + * like an unplug event. > + * > + * .. code-block:: c > + * > + * static void detach_from_device(struct drm_device *dev, > + * resource_size_t base, > + * resource_size_t size) > + * { > + * // Signal unplug > + * drm_dev_unplug(dev); > + * > + * // Maybe do other clean-up operations > + * ... > + * } > + * > + * static struct drm_aperture_funcs ap_funcs = { > + * .detach = detach_from_device, > + * }; > + */ > + > +/** > + * struct drm_aperture - Represents a DRM framebuffer aperture > + * > + * This structure has no public fields. > + */ > +struct drm_aperture { > + struct drm_device *dev; > + resource_size_t base; > + resource_size_t size; > + > + const struct drm_aperture_funcs *funcs; > + > + struct list_head lh; > +}; > + > +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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
> + mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
> + > + list_del(&ap->lh); > + > + if (!detached) > + mutex_unlock(&drm_apertures_lock); > +} > + > +/** > + * devm_aperture_acquire - Acquires ownership of a > 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 > + * @funcs: callback functions > + * > + * Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this > + * function. Acquired apertures are released > automatically if the
underlying
> + * device goes away. > + * > + * Returns: > + * An instance of struct &drm_aperture on success, or a pointer-encoded > + * errno value otherwise. > + */ > +struct drm_aperture * > +devm_aperture_acquire(struct drm_device *dev, > + resource_size_t base, resource_size_t size, > + const struct drm_aperture_funcs *funcs) > +{ > + size_t end = base + size; > + struct list_head *pos; > + struct drm_aperture *ap; > + int ret; > + > + 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 ERR_PTR(-EBUSY); > + } > + > + ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL); > + if (!ap) > + return ERR_PTR(-ENOMEM); > + > + ap->dev = dev; > + ap->base = base; > + ap->size = size; > + ap->funcs = funcs; > + INIT_LIST_HEAD(&ap->lh); > + > + list_add(&ap->lh, &drm_apertures); > + > + mutex_unlock(&drm_apertures_lock); > + > + ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap); > + if (ret) > + return ERR_PTR(ret); > + > + return ap; > +} > +EXPORT_SYMBOL(devm_aperture_acquire); > + > +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 (!overlap(base, end, ap->base, ap->base + ap->size)) > + continue; > + > + ap->dev = NULL; /* detach from device */ > + if (drm_WARN_ON(dev, !ap->funcs->detach)) > + continue; > + ap->funcs->detach(dev, ap->base, ap->size); > + } > + > + mutex_unlock(&drm_apertures_lock); > +} > +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers?
Imo
better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
> diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h > index 13766efe9517..696cec75ef78 100644 > --- a/include/drm/drm_aperture.h > +++ b/include/drm/drm_aperture.h > @@ -4,8 +4,30 @@ > #define _DRM_APERTURE_H_ > #include <linux/fb.h> > +#include <linux/pci.h> > #include <linux/vgaarb.h> > +struct drm_aperture; > +struct drm_device; > + > +struct drm_aperture_funcs { > + void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size); > +}; > + > +struct drm_aperture * > +devm_aperture_acquire(struct drm_device *dev, > + resource_size_t base, resource_size_t size, > + const struct drm_aperture_funcs *funcs); > + > +#if defined(CONFIG_DRM_APERTURE) > +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); > +#else > +static inline void > +drm_aperture_detach_drivers(resource_size_t base, > resource_size_t
size)
> +{ > +} > +#endif > + > /** > * drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers > * @a: memory range, users of which are to be removed > @@ -20,6 +42,11 @@ static inline int > drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, > const char *name, bool primary) > { > + int i; > + > + for (i = 0; i < a->count; ++i) > + drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size); > + > #if IS_REACHABLE(CONFIG_FB) > return remove_conflicting_framebuffers(a, name, primary); > #else > @@ -43,7 +70,16 @@ static inline int > drm_fb_helper_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, > -- > 2.30.1 >
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
Hi
Am 08.04.21 um 11:48 schrieb Daniel Vetter:
On Thu, Mar 18, 2021 at 11:29:15AM +0100, Thomas Zimmermann wrote:
Platform devices might operate on firmware framebuffers, such as VESA or EFI. Before a native driver for the graphics hardware can take over the device, it has to remove any platform driver that operates on the firmware framebuffer. Aperture helpers provide the infrastructure for platform drivers to acquire firmware framebuffers, and for native drivers to remove them later on.
It works similar to the related fbdev mechanism. During initialization, the platform driver acquires the firmware framebuffer's I/O memory and provides a callback to be removed. The native driver later uses this information to remove any platform driver for it's framebuffer I/O memory.
The aperture removal code is integrated into the existing code for removing conflicting framebuffers, so native drivers use it automatically.
v2:
- rename plaform helpers to aperture helpers
- tie to device lifetime with devm_ functions
- removed unsued remove() callback
- rename kickout to detach
- make struct drm_aperture private
- rebase onto existing drm_aperture.h header file
- use MIT license only for simplicity
- documentation
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Bunch of bikesheds for your considerations below, but overall lgtm.
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
Cheers, Daniel
Documentation/gpu/drm-internals.rst | 6 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++++++++++++++++++++++ include/drm/drm_aperture.h | 38 +++- 5 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/drm_aperture.c
diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 4c7642d2ca34..06af044c882f 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -78,9 +78,15 @@ DRM_IOCTL_VERSION ioctl. Managing Ownership of the Framebuffer Aperture
+.. kernel-doc:: drivers/gpu/drm/drm_aperture.c
- :doc: overview
- .. kernel-doc:: include/drm/drm_aperture.h :internal:
+.. kernel-doc:: drivers/gpu/drm/drm_aperture.c
- :export:
Device Instance and Driver Handling
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1461652921be..b9d3fb91d22d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -221,6 +221,13 @@ config DRM_SCHED tristate depends on DRM
+config DRM_APERTURE
- bool
- depends on DRM
- help
Controls ownership of graphics apertures. Required to
synchronize with firmware-based drivers.
Uh I'm not a big fan of Kconfig and .ko modules for every little helper code. Imo just stuff this into the drm kms helpers and done. Or stuff it into drm core code, I think either is a good case for this. Everything is its own module means we need to EXPORT_SYMBOL more stuff, and then drivers get funny ideas about using these internals ...
The code lives in the DRM core module. There's no extra ko file. But I'd like to keep the Kconfig option. The aperture helpers will only be required if there are generic drivers in the kernel and for many systems this is not the case.
Best regards Thomas
source "drivers/gpu/drm/i2c/Kconfig"
source "drivers/gpu/drm/arm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 5eb5bf7c16e3..c9ecb02df0f3 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -32,6 +32,7 @@ drm-$(CONFIG_AGP) += drm_agpsupport.o drm-$(CONFIG_PCI) += drm_pci.o drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o +drm-$(CONFIG_DRM_APERTURE) += drm_aperture.o
drm_vram_helper-y := drm_gem_vram_helper.o obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o diff --git a/drivers/gpu/drm/drm_aperture.c b/drivers/gpu/drm/drm_aperture.c new file mode 100644 index 000000000000..4b02b5fed0a1 --- /dev/null +++ b/drivers/gpu/drm/drm_aperture.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: MIT
+#include <linux/device.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/types.h>
+#include <drm/drm_aperture.h> +#include <drm/drm_drv.h> +#include <drm/drm_print.h>
+/**
- DOC: overview
- A graphics device might be supported by different drivers, but only one
- driver can be active at any given time. Many systems load a generic
- graphics drivers, such as EFI-GOP or VESA, early during the boot process.
- During later boot stages, they replace the generic driver with a dedicated,
- hardware-specific driver. To take over the device the dedicated driver
- first has to remove the generic driver. DRM aperture functions manage
- ownership of DRM framebuffer memory and hand-over between drivers.
- DRM drivers should call drm_fb_helper_remove_conflicting_framebuffers()
- at the top of their probe function. The function removes any generic
- driver that is currently associated with the given framebuffer memory.
- If the framebuffer is located at PCI BAR 0, the rsp code looks as in the
- example given below.
- .. code-block:: c
- static int remove_conflicting_framebuffers(struct pci_dev *pdev)
- {
struct apertures_struct *ap;
bool primary = false;
int ret;
ap = alloc_apertures(1);
if (!ap)
return -ENOMEM;
ap->ranges[0].base = pci_resource_start(pdev, 0);
ap->ranges[0].size = pci_resource_len(pdev, 0);
- #ifdef CONFIG_X86
primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
- #endif
ret = drm_fb_helper_remove_conflicting_framebuffers(ap, "example driver", primary);
kfree(ap);
return ret;
- }
- static int probe(struct pci_dev *pdev)
- {
int ret;
// Remove any generic drivers...
ret = remove_conflicting_framebuffers(pdev);
if (ret)
return ret;
// ... and initialize the hardware.
...
drm_dev_register();
return 0;
- }
- For PCI devices it is often sufficient to use drm_fb_helper_remove_conflicting_pci_framebuffers()
- and let it detect the framebuffer apertures automatically.
Maybe just me, but to avoid overstretching the attention spawn of doc readers I'd avoid this example here. And maybe make the recommendation stronger, e.g. "PCI device drivers can avoid open-coding remove_conflicting_framebuffers() by calling drm_fb_helper_remove_conflicting_pci_framebuffers()."
- .. code-block:: c
- static int probe(struct pci_dev *pdev)
- {
int ret;
// Remove any generic drivers...
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "example driver");
if (ret)
return ret;
// ... and initialize the hardware.
...
drm_dev_register();
return 0;
- }
- Drivers that are susceptible to being removed be 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(). 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 struct drm_aperture_funcs ap_funcs = {
.detach = ...
Is there really value in allowing/forcing drivers to set up their own detach ops? You already make this specific to struct drm_device, an implementation that just calls drm_dev_unplug feels like the right thing to do?
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
- };
- static int acquire_framebuffers(struct drm_device *dev, struct pci_dev *pdev)
- {
resource_size_t start, len;
struct drm_aperture *ap;
base = pci_resource_start(pdev, 0);
size = pci_resource_len(pdev, 0);
ap = devm_acquire_aperture(dev, base, size, &ap_funcs);
if (IS_ERR(ap))
return PTR_ERR(ap);
return 0;
- }
- static int probe(struct pci_dev *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();
return 0;
- }
- The generic driver is now subject to forced removal by other drivers. This
- is when the detach function in struct &drm_aperture_funcs comes into play.
- When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al
- for the registered framebuffer range, the DRM core calls struct
- &drm_aperture_funcs.detach and the generic driver has to onload itself. It
- may not access the device's registers, framebuffer memory, ROM, etc after
- detach returned. If the driver supports hotplugging, detach can be treated
- like an unplug event.
- .. code-block:: c
- static void detach_from_device(struct drm_device *dev,
resource_size_t base,
resource_size_t size)
- {
// Signal unplug
drm_dev_unplug(dev);
// Maybe do other clean-up operations
...
- }
- static struct drm_aperture_funcs ap_funcs = {
.detach = detach_from_device,
- };
- */
+/**
- struct drm_aperture - Represents a DRM framebuffer aperture
- This structure has no public fields.
- */
+struct drm_aperture {
- struct drm_device *dev;
- resource_size_t base;
- resource_size_t size;
- const struct drm_aperture_funcs *funcs;
- struct list_head lh;
+};
+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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
- list_del(&ap->lh);
- if (!detached)
mutex_unlock(&drm_apertures_lock);
+}
+/**
- devm_aperture_acquire - Acquires ownership of a 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
- @funcs: callback functions
- Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this
- function. Acquired apertures are released automatically if the underlying
- device goes away.
- Returns:
- An instance of struct &drm_aperture on success, or a pointer-encoded
- errno value otherwise.
- */
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs)
+{
- size_t end = base + size;
- struct list_head *pos;
- struct drm_aperture *ap;
- int ret;
- 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 ERR_PTR(-EBUSY);
- }
- ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
- if (!ap)
return ERR_PTR(-ENOMEM);
- ap->dev = dev;
- ap->base = base;
- ap->size = size;
- ap->funcs = funcs;
- INIT_LIST_HEAD(&ap->lh);
- list_add(&ap->lh, &drm_apertures);
- mutex_unlock(&drm_apertures_lock);
- ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
- if (ret)
return ERR_PTR(ret);
- return ap;
+} +EXPORT_SYMBOL(devm_aperture_acquire);
+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 (!overlap(base, end, ap->base, ap->base + ap->size))
continue;
ap->dev = NULL; /* detach from device */
if (drm_WARN_ON(dev, !ap->funcs->detach))
continue;
ap->funcs->detach(dev, ap->base, ap->size);
- }
- mutex_unlock(&drm_apertures_lock);
+} +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers? Imo better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_
#include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h>
+struct drm_aperture; +struct drm_device;
+struct drm_aperture_funcs {
- void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size);
+};
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs);
+#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ +} +#endif
- /**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
@@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) {
- int i;
- for (i = 0; i < a->count; ++i)
drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size);
- #if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else
@@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
-- 2.30.1
On Fri, Apr 09, 2021 at 09:54:03AM +0200, Thomas Zimmermann wrote:
Hi
Am 08.04.21 um 11:48 schrieb Daniel Vetter:
On Thu, Mar 18, 2021 at 11:29:15AM +0100, Thomas Zimmermann wrote:
Platform devices might operate on firmware framebuffers, such as VESA or EFI. Before a native driver for the graphics hardware can take over the device, it has to remove any platform driver that operates on the firmware framebuffer. Aperture helpers provide the infrastructure for platform drivers to acquire firmware framebuffers, and for native drivers to remove them later on.
It works similar to the related fbdev mechanism. During initialization, the platform driver acquires the firmware framebuffer's I/O memory and provides a callback to be removed. The native driver later uses this information to remove any platform driver for it's framebuffer I/O memory.
The aperture removal code is integrated into the existing code for removing conflicting framebuffers, so native drivers use it automatically.
v2:
- rename plaform helpers to aperture helpers
- tie to device lifetime with devm_ functions
- removed unsued remove() callback
- rename kickout to detach
- make struct drm_aperture private
- rebase onto existing drm_aperture.h header file
- use MIT license only for simplicity
- documentation
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Bunch of bikesheds for your considerations below, but overall lgtm.
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
Cheers, Daniel
Documentation/gpu/drm-internals.rst | 6 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++++++++++++++++++++++ include/drm/drm_aperture.h | 38 +++- 5 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/drm_aperture.c
diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 4c7642d2ca34..06af044c882f 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -78,9 +78,15 @@ DRM_IOCTL_VERSION ioctl. Managing Ownership of the Framebuffer Aperture
+.. kernel-doc:: drivers/gpu/drm/drm_aperture.c
- :doc: overview
- .. kernel-doc:: include/drm/drm_aperture.h :internal:
+.. kernel-doc:: drivers/gpu/drm/drm_aperture.c
- :export:
Device Instance and Driver Handling
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1461652921be..b9d3fb91d22d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -221,6 +221,13 @@ config DRM_SCHED tristate depends on DRM +config DRM_APERTURE
- bool
- depends on DRM
- help
Controls ownership of graphics apertures. Required to
synchronize with firmware-based drivers.
Uh I'm not a big fan of Kconfig and .ko modules for every little helper code. Imo just stuff this into the drm kms helpers and done. Or stuff it into drm core code, I think either is a good case for this. Everything is its own module means we need to EXPORT_SYMBOL more stuff, and then drivers get funny ideas about using these internals ...
The code lives in the DRM core module. There's no extra ko file. But I'd like to keep the Kconfig option. The aperture helpers will only be required if there are generic drivers in the kernel and for many systems this is not the case.
Imo this kind of optimization is what LTO is for. Having hundreds of Kconfig symbols just to shave of 2 functions, or something like that, in each case just doesn't feel like it's justified spending of effort and complexity. Configuring out entire subsystems, sure, but not individual pieces like this.
So minimally a new Kconfig like this needs to show in a a) real world config b) actual relevant savings in terms of bytes. Otherwise it's really just cargo culting. I also feel like Kconfig symbols for everything is an appeasement tactic to sneak code int that has seen some resistance about potential overhead and all that. The cost in maintenance and complexity in keeping all the combinations working is much, much bigger though. Just look at the absolute endless amounts of pain that disabling CONFIG_BACKLIGHT is causing drm drivers. We do not want more of that, except if it's really solidly justified.
And for the "this saves memory" justification, we've done that for i915 to kick out support code for old platforms. LTO is what gives you actual real-world benefits here, since anything else means a bazillion of Kconfig options, each for minimal gain, resulting in an overall unmaintainable mess.
</rant>
Cheers, Daniel
Best regards Thomas
- source "drivers/gpu/drm/i2c/Kconfig" source "drivers/gpu/drm/arm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 5eb5bf7c16e3..c9ecb02df0f3 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -32,6 +32,7 @@ drm-$(CONFIG_AGP) += drm_agpsupport.o drm-$(CONFIG_PCI) += drm_pci.o drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o +drm-$(CONFIG_DRM_APERTURE) += drm_aperture.o drm_vram_helper-y := drm_gem_vram_helper.o obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o diff --git a/drivers/gpu/drm/drm_aperture.c b/drivers/gpu/drm/drm_aperture.c new file mode 100644 index 000000000000..4b02b5fed0a1 --- /dev/null +++ b/drivers/gpu/drm/drm_aperture.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: MIT
+#include <linux/device.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/types.h>
+#include <drm/drm_aperture.h> +#include <drm/drm_drv.h> +#include <drm/drm_print.h>
+/**
- DOC: overview
- A graphics device might be supported by different drivers, but only one
- driver can be active at any given time. Many systems load a generic
- graphics drivers, such as EFI-GOP or VESA, early during the boot process.
- During later boot stages, they replace the generic driver with a dedicated,
- hardware-specific driver. To take over the device the dedicated driver
- first has to remove the generic driver. DRM aperture functions manage
- ownership of DRM framebuffer memory and hand-over between drivers.
- DRM drivers should call drm_fb_helper_remove_conflicting_framebuffers()
- at the top of their probe function. The function removes any generic
- driver that is currently associated with the given framebuffer memory.
- If the framebuffer is located at PCI BAR 0, the rsp code looks as in the
- example given below.
- .. code-block:: c
- static int remove_conflicting_framebuffers(struct pci_dev *pdev)
- {
struct apertures_struct *ap;
bool primary = false;
int ret;
ap = alloc_apertures(1);
if (!ap)
return -ENOMEM;
ap->ranges[0].base = pci_resource_start(pdev, 0);
ap->ranges[0].size = pci_resource_len(pdev, 0);
- #ifdef CONFIG_X86
primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
- #endif
ret = drm_fb_helper_remove_conflicting_framebuffers(ap, "example driver", primary);
kfree(ap);
return ret;
- }
- static int probe(struct pci_dev *pdev)
- {
int ret;
// Remove any generic drivers...
ret = remove_conflicting_framebuffers(pdev);
if (ret)
return ret;
// ... and initialize the hardware.
...
drm_dev_register();
return 0;
- }
- For PCI devices it is often sufficient to use drm_fb_helper_remove_conflicting_pci_framebuffers()
- and let it detect the framebuffer apertures automatically.
Maybe just me, but to avoid overstretching the attention spawn of doc readers I'd avoid this example here. And maybe make the recommendation stronger, e.g. "PCI device drivers can avoid open-coding remove_conflicting_framebuffers() by calling drm_fb_helper_remove_conflicting_pci_framebuffers()."
- .. code-block:: c
- static int probe(struct pci_dev *pdev)
- {
int ret;
// Remove any generic drivers...
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "example driver");
if (ret)
return ret;
// ... and initialize the hardware.
...
drm_dev_register();
return 0;
- }
- Drivers that are susceptible to being removed be 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(). 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 struct drm_aperture_funcs ap_funcs = {
.detach = ...
Is there really value in allowing/forcing drivers to set up their own detach ops? You already make this specific to struct drm_device, an implementation that just calls drm_dev_unplug feels like the right thing to do?
Or maybe we should tie this more into the struct device mode and force an unload that way? That way devm cleanup would work as one expects, and avoid the need for anything specific (hopefully) in this detach callback.
Just feels a bit like we're reinventing half of the driver model here, badly.
- };
- static int acquire_framebuffers(struct drm_device *dev, struct pci_dev *pdev)
- {
resource_size_t start, len;
struct drm_aperture *ap;
base = pci_resource_start(pdev, 0);
size = pci_resource_len(pdev, 0);
ap = devm_acquire_aperture(dev, base, size, &ap_funcs);
if (IS_ERR(ap))
return PTR_ERR(ap);
return 0;
- }
- static int probe(struct pci_dev *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();
return 0;
- }
- The generic driver is now subject to forced removal by other drivers. This
- is when the detach function in struct &drm_aperture_funcs comes into play.
- When a driver calls drm_fb_helper_remove_conflicting_framebuffers() et al
- for the registered framebuffer range, the DRM core calls struct
- &drm_aperture_funcs.detach and the generic driver has to onload itself. It
- may not access the device's registers, framebuffer memory, ROM, etc after
- detach returned. If the driver supports hotplugging, detach can be treated
- like an unplug event.
- .. code-block:: c
- static void detach_from_device(struct drm_device *dev,
resource_size_t base,
resource_size_t size)
- {
// Signal unplug
drm_dev_unplug(dev);
// Maybe do other clean-up operations
...
- }
- static struct drm_aperture_funcs ap_funcs = {
.detach = detach_from_device,
- };
- */
+/**
- struct drm_aperture - Represents a DRM framebuffer aperture
- This structure has no public fields.
- */
+struct drm_aperture {
- struct drm_device *dev;
- resource_size_t base;
- resource_size_t size;
- const struct drm_aperture_funcs *funcs;
- struct list_head lh;
+};
+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)
Uh this needs a comment that if ap->dev is NULL then we're called from drm_aperture_detach_drivers() and hence the lock is already held.
mutex_lock(&drm_apertures_lock);
and an
else locdep_assert_held(&drm_apertures_lock);
here to check that. I was scratching my head first quite a bit how you'd solve the deadlock, this is a neat solution (much simpler than anything I came up with in my head). But needs comments.
- list_del(&ap->lh);
- if (!detached)
mutex_unlock(&drm_apertures_lock);
+}
+/**
- devm_aperture_acquire - Acquires ownership of a 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
- @funcs: callback functions
- Installs the given device as the new owner. 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_fb_helper_remove_conflicting_framebuffers() et al. before calling this
- function. Acquired apertures are released automatically if the underlying
- device goes away.
- Returns:
- An instance of struct &drm_aperture on success, or a pointer-encoded
- errno value otherwise.
- */
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs)
+{
- size_t end = base + size;
- struct list_head *pos;
- struct drm_aperture *ap;
- int ret;
- 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 ERR_PTR(-EBUSY);
- }
- ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL);
- if (!ap)
return ERR_PTR(-ENOMEM);
- ap->dev = dev;
- ap->base = base;
- ap->size = size;
- ap->funcs = funcs;
- INIT_LIST_HEAD(&ap->lh);
- list_add(&ap->lh, &drm_apertures);
- mutex_unlock(&drm_apertures_lock);
- ret = devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap);
- if (ret)
return ERR_PTR(ret);
- return ap;
+} +EXPORT_SYMBOL(devm_aperture_acquire);
+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 (!overlap(base, end, ap->base, ap->base + ap->size))
continue;
ap->dev = NULL; /* detach from device */
if (drm_WARN_ON(dev, !ap->funcs->detach))
continue;
ap->funcs->detach(dev, ap->base, ap->size);
- }
- mutex_unlock(&drm_apertures_lock);
+} +EXPORT_SYMBOL(drm_aperture_detach_drivers);
Is this just exported because of the inline functions in the headers? Imo better to make them proper functions (they're big after your patch¬ perf critical, so not good candidates for inlining anyway).
diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h index 13766efe9517..696cec75ef78 100644 --- a/include/drm/drm_aperture.h +++ b/include/drm/drm_aperture.h @@ -4,8 +4,30 @@ #define _DRM_APERTURE_H_ #include <linux/fb.h> +#include <linux/pci.h> #include <linux/vgaarb.h> +struct drm_aperture; +struct drm_device;
+struct drm_aperture_funcs {
- void (*detach)(struct drm_device *dev, resource_size_t base, resource_size_t size);
+};
+struct drm_aperture * +devm_aperture_acquire(struct drm_device *dev,
resource_size_t base, resource_size_t size,
const struct drm_aperture_funcs *funcs);
+#if defined(CONFIG_DRM_APERTURE) +void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size); +#else +static inline void +drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ +} +#endif
- /**
- drm_fb_helper_remove_conflicting_framebuffers - remove firmware-configured framebuffers
- @a: memory range, users of which are to be removed
@@ -20,6 +42,11 @@ static inline int drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) {
- int i;
- for (i = 0; i < a->count; ++i)
drm_aperture_detach_drivers(a->ranges[i].base, a->ranges[i].size);
- #if IS_REACHABLE(CONFIG_FB) return remove_conflicting_framebuffers(a, name, primary); #else
@@ -43,7 +70,16 @@ static inline int drm_fb_helper_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,
-- 2.30.1
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
The simpledrm driver is a DRM driver for simplefb framebuffers as provided by the kernel's boot code. This driver enables basic graphical output on many different graphics devices that are provided by the platform (e.g., EFI, VESA, embedded framebuffers).
With the kernel's simplefb infrastructure, the kernel receives a pre-configured framebuffer from the system (i.e., firmware, boot loader). It creates a platform device to which simpledrm attaches. The system's framebuffer consists of a memory range, size and format. Based on these values, simpledrm creates a DRM devices. No actual modesetting is possible.
v2: * rename driver to simpledrm * add dri-devel to MAINTAINERS entry * put native format first in primary-plane format list (Daniel) * inline simplekms_device_cleanup() (Daniel) * use helpers for shadow-buffered planes * fix whitespace errors
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net --- MAINTAINERS | 7 + drivers/gpu/drm/tiny/Kconfig | 16 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/simpledrm.c | 527 +++++++++++++++++++++++++++++++ 4 files changed, 551 insertions(+) create mode 100644 drivers/gpu/drm/tiny/simpledrm.c
diff --git a/MAINTAINERS b/MAINTAINERS index 3dc7b57be31d..e9d53daf8b58 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5744,6 +5744,13 @@ S: Orphan / Obsolete F: drivers/gpu/drm/savage/ F: include/uapi/drm/savage_drm.h
+DRM DRIVER FOR SIMPLE FRAMEBUFFERS +M: Thomas Zimmermann tzimmermann@suse.de +L: dri-devel@lists.freedesktop.org +S: Maintained +T: git git://anongit.freedesktop.org/drm/drm-misc +F: drivers/gpu/drm/tiny/simplekms.c + DRM DRIVER FOR SIS VIDEO CARDS S: Orphan / Obsolete F: drivers/gpu/drm/sis/ diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig index 9bbaa1a69050..d46f95d9196d 100644 --- a/drivers/gpu/drm/tiny/Kconfig +++ b/drivers/gpu/drm/tiny/Kconfig @@ -38,6 +38,22 @@ config DRM_GM12U320 This is a KMS driver for projectors which use the GM12U320 chipset for video transfer over USB2/3, such as the Acer C120 mini projector.
+config DRM_SIMPLEDRM + tristate "Simple framebuffer driver" + depends on DRM + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + help + DRM driver for simple platform-provided framebuffers. + + This driver assumes that the display hardware has been initialized + by the firmware or bootloader before the kernel boots. Scanout + buffer, size, and display format must be provided via device tree, + UEFI, VESA, etc. + + On x86 and compatible, you should also select CONFIG_X86_SYSFB to + use UEFI and VESA framebuffers. + config TINYDRM_HX8357D tristate "DRM support for HX8357D display panels" depends on DRM && SPI diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile index bef6780bdd6f..9cc847e756da 100644 --- a/drivers/gpu/drm/tiny/Makefile +++ b/drivers/gpu/drm/tiny/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus.o obj-$(CONFIG_DRM_GM12U320) += gm12u320.o +obj-$(CONFIG_DRM_SIMPLEDRM) += simpledrm.o obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c new file mode 100644 index 000000000000..0422c549b97a --- /dev/null +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/platform_data/simplefb.h> +#include <linux/platform_device.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_format_helper.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#define DRIVER_NAME "simpledrm" +#define DRIVER_DESC "DRM driver for simple-framebuffer platform devices" +#define DRIVER_DATE "20200625" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +/* + * Assume a monitor resolution of 96 dpi to + * get a somewhat reasonable screen size. + */ +#define RES_MM(d) \ + (((d) * 254ul) / (96ul * 10ul)) + +#define SIMPLEDRM_MODE(hd, vd) \ + DRM_SIMPLE_MODE(hd, vd, RES_MM(hd), RES_MM(vd)) + +/* + * Helpers for simplefb + */ + +static int +simplefb_get_validated_int(struct drm_device *dev, const char *name, + uint32_t value) +{ + if (value > INT_MAX) { + drm_err(dev, "simplefb: invalid framebuffer %s of %u\n", + name, value); + return -EINVAL; + } + return (int)value; +} + +static int +simplefb_get_validated_int0(struct drm_device *dev, const char *name, + uint32_t value) +{ + if (!value) { + drm_err(dev, "simplefb: invalid framebuffer %s of %u\n", + name, value); + return -EINVAL; + } + return simplefb_get_validated_int(dev, name, value); +} + +static const struct drm_format_info * +simplefb_get_validated_format(struct drm_device *dev, const char *format_name) +{ + static const struct simplefb_format formats[] = SIMPLEFB_FORMATS; + const struct simplefb_format *fmt = formats; + const struct simplefb_format *end = fmt + ARRAY_SIZE(formats); + + if (!format_name) { + drm_err(dev, "simplefb: missing framebuffer format\n"); + return ERR_PTR(-EINVAL); + } + + while (fmt < end) { + if (!strcmp(format_name, fmt->name)) + return drm_format_info(fmt->fourcc); + ++fmt; + } + + drm_err(dev, "simplefb: unknown framebuffer format %s\n", + format_name); + + return ERR_PTR(-EINVAL); +} + +static int +simplefb_get_width_pd(struct drm_device *dev, + const struct simplefb_platform_data *pd) +{ + return simplefb_get_validated_int0(dev, "width", pd->width); +} + +static int +simplefb_get_height_pd(struct drm_device *dev, + const struct simplefb_platform_data *pd) +{ + return simplefb_get_validated_int0(dev, "height", pd->height); +} + +static int +simplefb_get_stride_pd(struct drm_device *dev, + const struct simplefb_platform_data *pd) +{ + return simplefb_get_validated_int(dev, "stride", pd->stride); +} + +static const struct drm_format_info * +simplefb_get_format_pd(struct drm_device *dev, + const struct simplefb_platform_data *pd) +{ + return simplefb_get_validated_format(dev, pd->format); +} + +/* + * Simple Framebuffer device + */ + +struct simpledrm_device { + struct drm_device dev; + struct platform_device *pdev; + + /* simplefb settings */ + struct drm_display_mode mode; + const struct drm_format_info *format; + unsigned int pitch; + + /* memory management */ + struct resource *mem; + void __iomem *screen_base; + + /* modesetting */ + uint32_t formats[8]; + size_t nformats; + struct drm_connector connector; + struct drm_simple_display_pipe pipe; +}; + +static struct simpledrm_device *simpledrm_device_of_dev(struct drm_device *dev) +{ + return container_of(dev, struct simpledrm_device, dev); +} + +/* + * Simplefb settings + */ + +static struct drm_display_mode simpledrm_mode(unsigned int width, + unsigned int height) +{ + struct drm_display_mode mode = { SIMPLEDRM_MODE(width, height) }; + + mode.clock = 60 /* Hz */ * mode.hdisplay * mode.vdisplay; + drm_mode_set_name(&mode); + + return mode; +} + +static int simpledrm_device_init_fb(struct simpledrm_device *sdev) +{ + int width, height, stride; + const struct drm_format_info *format; + struct drm_format_name_buf buf; + struct drm_device *dev = &sdev->dev; + struct platform_device *pdev = sdev->pdev; + const struct simplefb_platform_data *pd = dev_get_platdata(&pdev->dev); + + if (pd) { + width = simplefb_get_width_pd(dev, pd); + if (width < 0) + return width; + height = simplefb_get_height_pd(dev, pd); + if (height < 0) + return height; + stride = simplefb_get_stride_pd(dev, pd); + if (stride < 0) + return stride; + format = simplefb_get_format_pd(dev, pd); + if (IS_ERR(format)) + return PTR_ERR(format); + } else { + drm_err(dev, "no simplefb configuration found\n"); + return -ENODEV; + } + + sdev->mode = simpledrm_mode(width, height); + sdev->format = format; + sdev->pitch = stride; + + drm_dbg_kms(dev, "display mode={" DRM_MODE_FMT "}\n", + DRM_MODE_ARG(&sdev->mode)); + drm_dbg_kms(dev, + "framebuffer format="%s", size=%dx%d, stride=%d byte\n", + drm_get_format_name(format->format, &buf), width, + height, stride); + + return 0; +} + +/* + * Memory management + */ + +static int simpledrm_device_init_mm(struct simpledrm_device *sdev) +{ + struct platform_device *pdev = sdev->pdev; + struct resource *mem; + void __iomem *screen_base; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + return -EINVAL; + + screen_base = devm_ioremap_wc(&pdev->dev, mem->start, + resource_size(mem)); + if (!screen_base) + return -ENOMEM; + + sdev->mem = mem; + sdev->screen_base = screen_base; + + return 0; +} + +/* + * Modesetting + */ + +/* + * Support all formats of simplefb and maybe more; in order + * of preference. The display's update function will do any + * conversion necessary. + * + * TODO: Add blit helpers for remaining formats and uncomment + * constants. + */ +static const uint32_t simpledrm_default_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGB565, + //DRM_FORMAT_XRGB1555, + //DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB888, + //DRM_FORMAT_XRGB2101010, + //DRM_FORMAT_ARGB2101010, +}; + +static const uint64_t simpledrm_format_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static int simpledrm_connector_helper_get_modes(struct drm_connector *connector) +{ + struct simpledrm_device *sdev = simpledrm_device_of_dev(connector->dev); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &sdev->mode); + if (!mode) + return 0; + + if (mode->name[0] == '\0') + drm_mode_set_name(mode); + + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + if (mode->width_mm) + connector->display_info.width_mm = mode->width_mm; + if (mode->height_mm) + connector->display_info.height_mm = mode->height_mm; + + return 1; +} + +static const struct drm_connector_helper_funcs simpledrm_connector_helper_funcs = { + .get_modes = simpledrm_connector_helper_get_modes, +}; + +static const struct drm_connector_funcs simpledrm_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int +simpledrm_simple_display_pipe_mode_valid(struct drm_simple_display_pipe *pipe, + const struct drm_display_mode *mode) +{ + struct simpledrm_device *sdev = simpledrm_device_of_dev(pipe->connector->dev); + + if (mode->hdisplay != sdev->mode.hdisplay && + mode->vdisplay != sdev->mode.vdisplay) + return MODE_ONE_SIZE; + else if (mode->hdisplay != sdev->mode.hdisplay) + return MODE_ONE_WIDTH; + else if (mode->vdisplay != sdev->mode.vdisplay) + return MODE_ONE_HEIGHT; + + return MODE_OK; +} + +static void +simpledrm_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) +{ + struct simpledrm_device *sdev = simpledrm_device_of_dev(pipe->connector->dev); + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + void *vmap = shadow_plane_state->map[0].vaddr; /* TODO: Use mapping abstraction properly */ + + if (!fb) + return; + + drm_fb_blit_dstclip(sdev->screen_base, sdev->pitch, + sdev->format->format, vmap, fb); +} + +static void +simpledrm_simple_display_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_plane_state) +{ + struct simpledrm_device *sdev = simpledrm_device_of_dev(pipe->connector->dev); + struct drm_plane_state *plane_state = pipe->plane.state; + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + void *vmap = shadow_plane_state->map[0].vaddr; /* TODO: Use mapping abstraction properly */ + struct drm_framebuffer *fb = plane_state->fb; + struct drm_rect clip; + + if (!fb) + return; + + if (!drm_atomic_helper_damage_merged(old_plane_state, plane_state, &clip)) + return; + + drm_fb_blit_rect_dstclip(sdev->screen_base, sdev->pitch, + sdev->format->format, vmap, fb, &clip); +} + +static const struct drm_simple_display_pipe_funcs +simpledrm_simple_display_pipe_funcs = { + .mode_valid = simpledrm_simple_display_pipe_mode_valid, + .enable = simpledrm_simple_display_pipe_enable, + .update = simpledrm_simple_display_pipe_update, + DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS, +}; + +static const struct drm_mode_config_funcs simpledrm_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const uint32_t *simpledrm_device_formats(struct simpledrm_device *sdev, + size_t *nformats_out) +{ + struct drm_device *dev = &sdev->dev; + size_t i; + + if (sdev->nformats) + goto out; /* don't rebuild list on recurring calls */ + + /* native format goes first */ + sdev->formats[0] = sdev->format->format; + sdev->nformats = 1; + + /* default formats go second */ + for (i = 0; i < ARRAY_SIZE(simpledrm_default_formats); ++i) { + if (simpledrm_default_formats[i] == sdev->format->format) + continue; /* native format already went first */ + sdev->formats[sdev->nformats] = simpledrm_default_formats[i]; + sdev->nformats++; + } + + /* + * TODO: The simpledrm driver converts framebuffers to the native + * format when copying them to device memory. If there are more + * formats listed than supported by the driver, the native format + * is not supported by the conversion helpers. Therefore *only* + * support the native format and add a conversion helper ASAP. + */ + if (drm_WARN_ONCE(dev, i != sdev->nformats, + "format conversion helpers required for %p4cc", + &sdev->format->format)) { + sdev->nformats = 1; + } + +out: + *nformats_out = sdev->nformats; + return sdev->formats; +} + +static int simpledrm_device_init_modeset(struct simpledrm_device *sdev) +{ + struct drm_device *dev = &sdev->dev; + struct drm_display_mode *mode = &sdev->mode; + struct drm_connector *connector = &sdev->connector; + struct drm_simple_display_pipe *pipe = &sdev->pipe; + const uint32_t *formats; + size_t nformats; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + dev->mode_config.min_width = mode->hdisplay; + dev->mode_config.max_width = mode->hdisplay; + dev->mode_config.min_height = mode->vdisplay; + dev->mode_config.max_height = mode->vdisplay; + dev->mode_config.prefer_shadow = true; + dev->mode_config.preferred_depth = sdev->format->cpp[0] * 8; + dev->mode_config.funcs = &simpledrm_mode_config_funcs; + + ret = drm_connector_init(dev, connector, &simpledrm_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + return ret; + drm_connector_helper_add(connector, &simpledrm_connector_helper_funcs); + + formats = simpledrm_device_formats(sdev, &nformats); + + ret = drm_simple_display_pipe_init(dev, pipe, &simpledrm_simple_display_pipe_funcs, + formats, nformats, simpledrm_format_modifiers, + connector); + if (ret) + return ret; + + drm_mode_config_reset(dev); + + return 0; +} + +/* + * Init / Cleanup + */ + +static struct simpledrm_device * +simpledrm_device_create(struct drm_driver *drv, struct platform_device *pdev) +{ + struct simpledrm_device *sdev; + int ret; + + sdev = devm_drm_dev_alloc(&pdev->dev, drv, struct simpledrm_device, + dev); + if (IS_ERR(sdev)) + return ERR_CAST(sdev); + sdev->pdev = pdev; + + ret = simpledrm_device_init_fb(sdev); + if (ret) + return ERR_PTR(ret); + ret = simpledrm_device_init_mm(sdev); + if (ret) + return ERR_PTR(ret); + ret = simpledrm_device_init_modeset(sdev); + if (ret) + return ERR_PTR(ret); + + return sdev; +} + +/* + * DRM driver + */ + +DEFINE_DRM_GEM_FOPS(simpledrm_fops); + +static struct drm_driver simpledrm_driver = { + DRM_GEM_SHMEM_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + .fops = &simpledrm_fops, +}; + +/* + * Platform driver + */ + +static int simpledrm_probe(struct platform_device *pdev) +{ + struct simpledrm_device *sdev; + struct drm_device *dev; + int ret; + + sdev = simpledrm_device_create(&simpledrm_driver, pdev); + if (IS_ERR(sdev)) + return PTR_ERR(sdev); + dev = &sdev->dev; + + ret = drm_dev_register(dev, 0); + if (ret) + return ret; + + return 0; +} + +static int simpledrm_remove(struct platform_device *pdev) +{ + struct simpledrm_device *sdev = platform_get_drvdata(pdev); + struct drm_device *dev = &sdev->dev; + + drm_dev_unregister(dev); + + return 0; +} + +static struct platform_driver simpledrm_platform_driver = { + .driver = { + .name = "simple-framebuffer", /* connect to sysfb */ + }, + .probe = simpledrm_probe, + .remove = simpledrm_remove, +}; + +module_platform_driver(simpledrm_platform_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2");
This displays a console on simpledrm's framebuffer. The default framebuffer format is being used.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Reviewed-by: Daniel Vetter daniel.vetter@ffwll.ch Tested-by: nerdopolis bluescreen_avenger@verizon.net --- drivers/gpu/drm/tiny/simpledrm.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c index 0422c549b97a..4f0d4ec0b432 100644 --- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -8,6 +8,7 @@ #include <drm/drm_damage_helper.h> #include <drm/drm_device.h> #include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> #include <drm/drm_format_helper.h> #include <drm/drm_gem_atomic_helper.h> #include <drm/drm_gem_framebuffer_helper.h> @@ -500,6 +501,8 @@ static int simpledrm_probe(struct platform_device *pdev) if (ret) return ret;
+ drm_fbdev_generic_setup(dev, 0); + return 0; }
A firmware framebuffer might also be specified via device-tree files. If no device platform data is given, try the DT device node.
v2: * add Device Tree match table * clean-up parser wrappers
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net --- drivers/gpu/drm/tiny/simpledrm.c | 89 ++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+)
diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c index 4f0d4ec0b432..c9cef2b50de6 100644 --- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -114,6 +114,74 @@ simplefb_get_format_pd(struct drm_device *dev, return simplefb_get_validated_format(dev, pd->format); }
+static int +simplefb_read_u32_of(struct drm_device *dev, struct device_node *of_node, + const char *name, u32 *value) +{ + int ret = of_property_read_u32(of_node, name, value); + + if (ret) + drm_err(dev, "simplefb: cannot parse framebuffer %s: error %d\n", + name, ret); + return ret; +} + +static int +simplefb_read_string_of(struct drm_device *dev, struct device_node *of_node, + const char *name, const char **value) +{ + int ret = of_property_read_string(of_node, name, value); + + if (ret) + drm_err(dev, "simplefb: cannot parse framebuffer %s: error %d\n", + name, ret); + return ret; +} + +static int +simplefb_get_width_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 width; + int ret = simplefb_read_u32_of(dev, of_node, "width", &width); + + if (ret) + return ret; + return simplefb_get_validated_int0(dev, "width", width); +} + +static int +simplefb_get_height_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 height; + int ret = simplefb_read_u32_of(dev, of_node, "height", &height); + + if (ret) + return ret; + return simplefb_get_validated_int0(dev, "height", height); +} + +static int +simplefb_get_stride_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 stride; + int ret = simplefb_read_u32_of(dev, of_node, "stride", &stride); + + if (ret) + return ret; + return simplefb_get_validated_int(dev, "stride", stride); +} + +static const struct drm_format_info * +simplefb_get_format_of(struct drm_device *dev, struct device_node *of_node) +{ + const char *format; + int ret = simplefb_read_string_of(dev, of_node, "format", &format); + + if (ret) + return ERR_PTR(ret); + return simplefb_get_validated_format(dev, format); +} + /* * Simple Framebuffer device */ @@ -166,6 +234,7 @@ static int simpledrm_device_init_fb(struct simpledrm_device *sdev) struct drm_device *dev = &sdev->dev; struct platform_device *pdev = sdev->pdev; const struct simplefb_platform_data *pd = dev_get_platdata(&pdev->dev); + struct device_node *of_node = pdev->dev.of_node;
if (pd) { width = simplefb_get_width_pd(dev, pd); @@ -180,6 +249,19 @@ static int simpledrm_device_init_fb(struct simpledrm_device *sdev) format = simplefb_get_format_pd(dev, pd); if (IS_ERR(format)) return PTR_ERR(format); + } else if (of_node) { + width = simplefb_get_width_of(dev, of_node); + if (width < 0) + return width; + height = simplefb_get_height_of(dev, of_node); + if (height < 0) + return height; + stride = simplefb_get_stride_of(dev, of_node); + if (stride < 0) + return stride; + format = simplefb_get_format_of(dev, of_node); + if (IS_ERR(format)) + return PTR_ERR(format); } else { drm_err(dev, "no simplefb configuration found\n"); return -ENODEV; @@ -516,9 +598,16 @@ static int simpledrm_remove(struct platform_device *pdev) return 0; }
+static const struct of_device_id simpledrm_of_match_table[] = { + { .compatible = "simple-framebuffer", }, + { }, +}; +MODULE_DEVICE_TABLE(of, simpledrm_of_match_table); + static struct platform_driver simpledrm_platform_driver = { .driver = { .name = "simple-framebuffer", /* connect to sysfb */ + .of_match_table = simpledrm_of_match_table, }, .probe = simpledrm_probe, .remove = simpledrm_remove,
Make sure required hardware clocks are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Clocks are released automatically via devres helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net --- drivers/gpu/drm/tiny/simpledrm.c | 108 +++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+)
diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c index c9cef2b50de6..10ca3373b61f 100644 --- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only
+#include <linux/clk.h> +#include <linux/of_clk.h> #include <linux/platform_data/simplefb.h> #include <linux/platform_device.h>
@@ -190,6 +192,12 @@ struct simpledrm_device { struct drm_device dev; struct platform_device *pdev;
+ /* clocks */ +#if defined CONFIG_OF && defined CONFIG_COMMON_CLK + unsigned int clk_count; + struct clk **clks; +#endif + /* simplefb settings */ struct drm_display_mode mode; const struct drm_format_info *format; @@ -211,6 +219,103 @@ static struct simpledrm_device *simpledrm_device_of_dev(struct drm_device *dev) return container_of(dev, struct simpledrm_device, dev); }
+/* + * Hardware + */ + +#if defined CONFIG_OF && defined CONFIG_COMMON_CLK +/* + * Clock handling code. + * + * Here we handle the clocks property of our "simple-framebuffer" dt node. + * This is necessary so that we can make sure that any clocks needed by + * the display engine that the bootloader set up for us (and for which it + * provided a simplefb dt node), stay up, for the life of the simplefb + * driver. + * + * When the driver unloads, we cleanly disable, and then release the clocks. + * + * We only complain about errors here, no action is taken as the most likely + * error can only happen due to a mismatch between the bootloader which set + * up simplefb, and the clock definitions in the device tree. Chances are + * that there are no adverse effects, and if there are, a clean teardown of + * the fb probe will not help us much either. So just complain and carry on, + * and hope that the user actually gets a working fb at the end of things. + */ + +static void simpledrm_device_release_clocks(void *res) +{ + struct simpledrm_device *sdev = simpledrm_device_of_dev(res); + unsigned int i; + + for (i = 0; i < sdev->clk_count; ++i) { + if (sdev->clks[i]) { + clk_disable_unprepare(sdev->clks[i]); + clk_put(sdev->clks[i]); + } + } +} + +static int simpledrm_device_init_clocks(struct simpledrm_device *sdev) +{ + struct drm_device *dev = &sdev->dev; + struct platform_device *pdev = sdev->pdev; + struct device_node *of_node = pdev->dev.of_node; + struct clk *clock; + unsigned int i; + int ret; + + if (dev_get_platdata(&pdev->dev) || !of_node) + return 0; + + sdev->clk_count = of_clk_get_parent_count(of_node); + if (!sdev->clk_count) + return 0; + + sdev->clks = drmm_kzalloc(dev, sdev->clk_count * sizeof(sdev->clks[0]), + GFP_KERNEL); + if (!sdev->clks) + return -ENOMEM; + + for (i = 0; i < sdev->clk_count; ++i) { + clock = of_clk_get(of_node, i); + if (IS_ERR(clock)) { + ret = PTR_ERR(clock); + if (ret == -EPROBE_DEFER) + goto err; + drm_err(dev, "clock %u not found: %d\n", i, ret); + continue; + } + ret = clk_prepare_enable(clock); + if (ret) { + drm_err(dev, "failed to enable clock %u: %d\n", + i, ret); + clk_put(clock); + } + sdev->clks[i] = clock; + } + + return devm_add_action_or_reset(&pdev->dev, + simpledrm_device_release_clocks, + sdev); + +err: + while (i) { + --i; + if (sdev->clks[i]) { + clk_disable_unprepare(sdev->clks[i]); + clk_put(sdev->clks[i]); + } + } + return ret; +} +#else +static int simpledrm_device_init_clocks(struct simpledrm_device *sdev) +{ + return 0; +} +#endif + /* * Simplefb settings */ @@ -534,6 +639,9 @@ simpledrm_device_create(struct drm_driver *drv, struct platform_device *pdev) return ERR_CAST(sdev); sdev->pdev = pdev;
+ ret = simpledrm_device_init_clocks(sdev); + if (ret) + return ERR_PTR(ret); ret = simpledrm_device_init_fb(sdev); if (ret) return ERR_PTR(ret);
Hi Thomas,
On Thu, Mar 18, 2021 at 11:29 AM Thomas Zimmermann tzimmermann@suse.de wrote:
Make sure required hardware clocks are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Clocks are released automatically via devres helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Thanks for your patch!
--- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c
+static int simpledrm_device_init_clocks(struct simpledrm_device *sdev) +{
struct drm_device *dev = &sdev->dev;
struct platform_device *pdev = sdev->pdev;
struct device_node *of_node = pdev->dev.of_node;
struct clk *clock;
unsigned int i;
int ret;
if (dev_get_platdata(&pdev->dev) || !of_node)
return 0;
sdev->clk_count = of_clk_get_parent_count(of_node);
if (!sdev->clk_count)
return 0;
sdev->clks = drmm_kzalloc(dev, sdev->clk_count * sizeof(sdev->clks[0]),
GFP_KERNEL);
if (!sdev->clks)
return -ENOMEM;
for (i = 0; i < sdev->clk_count; ++i) {
clock = of_clk_get(of_node, i);
if (IS_ERR(clock)) {
ret = PTR_ERR(clock);
if (ret == -EPROBE_DEFER)
goto err;
drm_err(dev, "clock %u not found: %d\n", i, ret);
continue;
}
ret = clk_prepare_enable(clock);
if (ret) {
drm_err(dev, "failed to enable clock %u: %d\n",
i, ret);
clk_put(clock);
}
sdev->clks[i] = clock;
}
of_clk_bulk_get_all() + clk_bulk_prepare_enable()?
There's also devm_clk_bulk_get_all(), but not for the OF variant.
Gr{oetje,eeting}s,
Geert
Hi
Am 18.03.21 um 11:39 schrieb Geert Uytterhoeven:
Hi Thomas,
On Thu, Mar 18, 2021 at 11:29 AM Thomas Zimmermann tzimmermann@suse.de wrote:
Make sure required hardware clocks are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Clocks are released automatically via devres helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Thanks for your patch!
--- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c
+static int simpledrm_device_init_clocks(struct simpledrm_device *sdev) +{
struct drm_device *dev = &sdev->dev;
struct platform_device *pdev = sdev->pdev;
struct device_node *of_node = pdev->dev.of_node;
struct clk *clock;
unsigned int i;
int ret;
if (dev_get_platdata(&pdev->dev) || !of_node)
return 0;
sdev->clk_count = of_clk_get_parent_count(of_node);
if (!sdev->clk_count)
return 0;
sdev->clks = drmm_kzalloc(dev, sdev->clk_count * sizeof(sdev->clks[0]),
GFP_KERNEL);
if (!sdev->clks)
return -ENOMEM;
for (i = 0; i < sdev->clk_count; ++i) {
clock = of_clk_get(of_node, i);
if (IS_ERR(clock)) {
ret = PTR_ERR(clock);
if (ret == -EPROBE_DEFER)
goto err;
drm_err(dev, "clock %u not found: %d\n", i, ret);
continue;
}
ret = clk_prepare_enable(clock);
if (ret) {
drm_err(dev, "failed to enable clock %u: %d\n",
i, ret);
clk_put(clock);
}
sdev->clks[i] = clock;
}
of_clk_bulk_get_all() + clk_bulk_prepare_enable()?
There's also devm_clk_bulk_get_all(), but not for the OF variant.
Right, you mentioned this on the original patch set. I tried to use the functions, but TBH I found them to obfuscate the overall logic of the function. So I went back to the original code. Hopefully this is not too much of an issue.
Best regards Thomas
Gr{oetje,eeting}s,
Geert
Hi,
On Thu, Mar 18, 2021 at 11:29:19AM +0100, Thomas Zimmermann wrote:
Make sure required hardware clocks are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Clocks are released automatically via devres helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Even though it's definitely simpler to review, merging the driver first and then the clocks and regulators will break bisection on the platforms that rely on them
Another thing worth considering is also that both drivers will probe if they are enabled (which is pretty likely), which is not great :)
I guess we should make them mutually exclusive through Kconfig
Maxime
Hi
Am 08.04.21 um 10:13 schrieb Maxime Ripard:
Hi,
On Thu, Mar 18, 2021 at 11:29:19AM +0100, Thomas Zimmermann wrote:
Make sure required hardware clocks are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Clocks are released automatically via devres helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Even though it's definitely simpler to review, merging the driver first and then the clocks and regulators will break bisection on the platforms that rely on them
I'd like to keep the patches separate for now, but can squash patches 6 to 8 them into one before pushing them. OK?
Another thing worth considering is also that both drivers will probe if they are enabled (which is pretty likely), which is not great :)
I guess we should make them mutually exclusive through Kconfig
We already have several drivers in fbdev and DRM that handle the same hardware. We don't do this for any other pair, why bother now?
Best regards Thomas
Maxime
Hi,
On Thu, Apr 15, 2021 at 09:31:01AM +0200, Thomas Zimmermann wrote:
Am 08.04.21 um 10:13 schrieb Maxime Ripard:
Hi,
On Thu, Mar 18, 2021 at 11:29:19AM +0100, Thomas Zimmermann wrote:
Make sure required hardware clocks are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Clocks are released automatically via devres helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Even though it's definitely simpler to review, merging the driver first and then the clocks and regulators will break bisection on the platforms that rely on them
I'd like to keep the patches separate for now, but can squash patches 6 to 8 them into one before pushing them. OK?
Yep, that works for me :)
Another thing worth considering is also that both drivers will probe if they are enabled (which is pretty likely), which is not great :)
I guess we should make them mutually exclusive through Kconfig
We already have several drivers in fbdev and DRM that handle the same hardware. We don't do this for any other pair, why bother now?
Yeah, but simplefb/simpledrm are going to be enabled pretty much everywhere, as opposed to the other drivers that are more specialized.
Maxime
Hi
Am 15.04.21 um 11:21 schrieb Maxime Ripard:
Hi,
On Thu, Apr 15, 2021 at 09:31:01AM +0200, Thomas Zimmermann wrote:
Am 08.04.21 um 10:13 schrieb Maxime Ripard:
Hi,
On Thu, Mar 18, 2021 at 11:29:19AM +0100, Thomas Zimmermann wrote:
Make sure required hardware clocks are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Clocks are released automatically via devres helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Even though it's definitely simpler to review, merging the driver first and then the clocks and regulators will break bisection on the platforms that rely on them
I'd like to keep the patches separate for now, but can squash patches 6 to 8 them into one before pushing them. OK?
Yep, that works for me :)
Another thing worth considering is also that both drivers will probe if they are enabled (which is pretty likely), which is not great :)
I guess we should make them mutually exclusive through Kconfig
We already have several drivers in fbdev and DRM that handle the same hardware. We don't do this for any other pair, why bother now?
Yeah, but simplefb/simpledrm are going to be enabled pretty much everywhere, as opposed to the other drivers that are more specialized.
Well, OK. But I'd like to give simpledrm preference over simplefb. There should be an incentive to switch to DRM.
Best regards Thomas
Maxime
On Thu, Apr 15, 2021 at 01:02:44PM +0200, Thomas Zimmermann wrote:
Hi
Am 15.04.21 um 11:21 schrieb Maxime Ripard:
Hi,
On Thu, Apr 15, 2021 at 09:31:01AM +0200, Thomas Zimmermann wrote:
Am 08.04.21 um 10:13 schrieb Maxime Ripard:
Hi,
On Thu, Mar 18, 2021 at 11:29:19AM +0100, Thomas Zimmermann wrote:
Make sure required hardware clocks are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Clocks are released automatically via devres helpers.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net
Even though it's definitely simpler to review, merging the driver first and then the clocks and regulators will break bisection on the platforms that rely on them
I'd like to keep the patches separate for now, but can squash patches 6 to 8 them into one before pushing them. OK?
Yep, that works for me :)
Another thing worth considering is also that both drivers will probe if they are enabled (which is pretty likely), which is not great :)
I guess we should make them mutually exclusive through Kconfig
We already have several drivers in fbdev and DRM that handle the same hardware. We don't do this for any other pair, why bother now?
Yeah, but simplefb/simpledrm are going to be enabled pretty much everywhere, as opposed to the other drivers that are more specialized.
Well, OK. But I'd like to give simpledrm preference over simplefb. There should be an incentive to switch to DRM.
Yeah that makes total sense :)
Maxime
Make sure required hardware regulators are enabled while the firmware framebuffer is in use.
The basic code has been taken from the simplefb driver and adapted to DRM. Regulators are released automatically via devres helpers.
v2: * use strscpy()
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net --- drivers/gpu/drm/tiny/simpledrm.c | 128 +++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+)
diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c index 10ca3373b61f..2e27eeb791a1 100644 --- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -4,6 +4,7 @@ #include <linux/of_clk.h> #include <linux/platform_data/simplefb.h> #include <linux/platform_device.h> +#include <linux/regulator/consumer.h>
#include <drm/drm_atomic_state_helper.h> #include <drm/drm_connector.h> @@ -197,6 +198,11 @@ struct simpledrm_device { unsigned int clk_count; struct clk **clks; #endif + /* regulators */ +#if defined CONFIG_OF && defined CONFIG_REGULATOR + unsigned int regulator_count; + struct regulator **regulators; +#endif
/* simplefb settings */ struct drm_display_mode mode; @@ -316,6 +322,125 @@ static int simpledrm_device_init_clocks(struct simpledrm_device *sdev) } #endif
+#if defined CONFIG_OF && defined CONFIG_REGULATOR + +#define SUPPLY_SUFFIX "-supply" + +/* + * Regulator handling code. + * + * Here we handle the num-supplies and vin*-supply properties of our + * "simple-framebuffer" dt node. This is necessary so that we can make sure + * that any regulators needed by the display hardware that the bootloader + * set up for us (and for which it provided a simplefb dt node), stay up, + * for the life of the simplefb driver. + * + * When the driver unloads, we cleanly disable, and then release the + * regulators. + * + * We only complain about errors here, no action is taken as the most likely + * error can only happen due to a mismatch between the bootloader which set + * up simplefb, and the regulator definitions in the device tree. Chances are + * that there are no adverse effects, and if there are, a clean teardown of + * the fb probe will not help us much either. So just complain and carry on, + * and hope that the user actually gets a working fb at the end of things. + */ + +static void simpledrm_device_release_regulators(void *res) +{ + struct simpledrm_device *sdev = simpledrm_device_of_dev(res); + unsigned int i; + + for (i = 0; i < sdev->regulator_count; ++i) { + if (sdev->regulators[i]) { + regulator_disable(sdev->regulators[i]); + regulator_put(sdev->regulators[i]); + } + } +} + +static int simpledrm_device_init_regulators(struct simpledrm_device *sdev) +{ + struct drm_device *dev = &sdev->dev; + struct platform_device *pdev = sdev->pdev; + struct device_node *of_node = pdev->dev.of_node; + struct property *prop; + struct regulator *regulator; + const char *p; + unsigned int count = 0, i = 0; + int ret; + + if (dev_get_platdata(&pdev->dev) || !of_node) + return 0; + + /* Count the number of regulator supplies */ + for_each_property_of_node(of_node, prop) { + p = strstr(prop->name, SUPPLY_SUFFIX); + if (p && p != prop->name) + ++count; + } + + if (!count) + return 0; + + sdev->regulators = drmm_kzalloc(dev, + count * sizeof(sdev->regulators[0]), + GFP_KERNEL); + if (!sdev->regulators) + return -ENOMEM; + + for_each_property_of_node(of_node, prop) { + char name[32]; /* 32 is max size of property name */ + size_t len; + + p = strstr(prop->name, SUPPLY_SUFFIX); + if (!p || p == prop->name) + continue; + len = strlen(prop->name) - strlen(SUPPLY_SUFFIX) + 1; + strscpy(name, prop->name, min(sizeof(name), len)); + + regulator = regulator_get_optional(&pdev->dev, name); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + if (ret == -EPROBE_DEFER) + goto err; + drm_err(dev, "regulator %s not found: %d\n", + name, ret); + continue; + } + + ret = regulator_enable(regulator); + if (ret) { + drm_err(dev, "failed to enable regulator %u: %d\n", + i, ret); + regulator_put(regulator); + } + + sdev->regulators[i++] = regulator; + } + sdev->regulator_count = i; + + return devm_add_action_or_reset(&pdev->dev, + simpledrm_device_release_regulators, + sdev); + +err: + while (i) { + --i; + if (sdev->regulators[i]) { + regulator_disable(sdev->regulators[i]); + regulator_put(sdev->regulators[i]); + } + } + return ret; +} +#else +static int simpledrm_device_init_regulators(struct simpledrm_device *sdev) +{ + return 0; +} +#endif + /* * Simplefb settings */ @@ -640,6 +765,9 @@ simpledrm_device_create(struct drm_driver *drv, struct platform_device *pdev) sdev->pdev = pdev;
ret = simpledrm_device_init_clocks(sdev); + if (ret) + return ERR_PTR(ret); + ret = simpledrm_device_init_regulators(sdev); if (ret) return ERR_PTR(ret); ret = simpledrm_device_init_fb(sdev);
We register the simplekms device with the DRM platform helpers. A native driver for the graphics hardware will kick-out the simpledrm driver before taking over the device.
v2: * adapt to aperture changes * use drm_dev_unplug() and drm_dev_enter/exit() * don't split error string
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Tested-by: nerdopolis bluescreen_avenger@verizon.net --- drivers/gpu/drm/tiny/Kconfig | 1 + drivers/gpu/drm/tiny/simpledrm.c | 83 ++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig index d46f95d9196d..5b72dd8e93f9 100644 --- a/drivers/gpu/drm/tiny/Kconfig +++ b/drivers/gpu/drm/tiny/Kconfig @@ -41,6 +41,7 @@ config DRM_GM12U320 config DRM_SIMPLEDRM tristate "Simple framebuffer driver" depends on DRM + select DRM_APERTURE select DRM_GEM_SHMEM_HELPER select DRM_KMS_HELPER help diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c index 2e27eeb791a1..67d33af19086 100644 --- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -5,7 +5,9 @@ #include <linux/platform_data/simplefb.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> +#include <linux/spinlock.h>
+#include <drm/drm_aperture.h> #include <drm/drm_atomic_state_helper.h> #include <drm/drm_connector.h> #include <drm/drm_damage_helper.h> @@ -37,6 +39,12 @@ #define SIMPLEDRM_MODE(hd, vd) \ DRM_SIMPLE_MODE(hd, vd, RES_MM(hd), RES_MM(vd))
+/* + * Protects the platform device's drvdata against + * concurrent manipulation. + */ +static DEFINE_SPINLOCK(simpledrm_drvdata_lock); + /* * Helpers for simplefb */ @@ -515,16 +523,53 @@ static int simpledrm_device_init_fb(struct simpledrm_device *sdev) * Memory management */
+static void simpledrm_aperture_detach(struct drm_device *dev, resource_size_t base, + resource_size_t size) +{ + struct simpledrm_device *sdev = simpledrm_device_of_dev(dev); + struct platform_device *pdev = sdev->pdev; + + if (WARN_ON(drm_dev_is_unplugged(dev))) + return; /* BUG: driver already got detached */ + + /* + * If simpledrm gets detached from the aperture, it's like unplugging + * the device. So call drm_dev_unplug(). + */ + drm_dev_unplug(dev); + + spin_lock(&simpledrm_drvdata_lock); + sdev = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); /* required; see simpledrm_remove() */ + spin_unlock(&simpledrm_drvdata_lock); +} + +static const struct drm_aperture_funcs simpledrm_aperture_funcs = { + .detach = simpledrm_aperture_detach, +}; + static int simpledrm_device_init_mm(struct simpledrm_device *sdev) { + struct drm_device *dev = &sdev->dev; struct platform_device *pdev = sdev->pdev; struct resource *mem; + struct drm_aperture *ap; void __iomem *screen_base; + int ret;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) return -EINVAL;
+ ap = devm_aperture_acquire(dev, mem->start, resource_size(mem), + &simpledrm_aperture_funcs); + if (IS_ERR(ap)) { + ret = PTR_ERR(ap); + drm_err(dev, "could not acquire memory range [0x%llx:0x%llx]: error %d\n", + mem->start, mem->end, ret); + return ret; + } + screen_base = devm_ioremap_wc(&pdev->dev, mem->start, resource_size(mem)); if (!screen_base) @@ -625,12 +670,18 @@ simpledrm_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe, struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); struct drm_framebuffer *fb = plane_state->fb; void *vmap = shadow_plane_state->map[0].vaddr; /* TODO: Use mapping abstraction properly */ + struct drm_device *dev = &sdev->dev; + int idx;
if (!fb) return;
+ if (!drm_dev_enter(dev, &idx)) + return; + drm_fb_blit_dstclip(sdev->screen_base, sdev->pitch, sdev->format->format, vmap, fb); + drm_dev_exit(idx); }
static void @@ -642,7 +693,9 @@ simpledrm_simple_display_pipe_update(struct drm_simple_display_pipe *pipe, struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); void *vmap = shadow_plane_state->map[0].vaddr; /* TODO: Use mapping abstraction properly */ struct drm_framebuffer *fb = plane_state->fb; + struct drm_device *dev = &sdev->dev; struct drm_rect clip; + int idx;
if (!fb) return; @@ -650,8 +703,13 @@ simpledrm_simple_display_pipe_update(struct drm_simple_display_pipe *pipe, if (!drm_atomic_helper_damage_merged(old_plane_state, plane_state, &clip)) return;
+ if (!drm_dev_enter(dev, &idx)) + return; + drm_fb_blit_rect_dstclip(sdev->screen_base, sdev->pitch, sdev->format->format, vmap, fb, &clip); + + drm_dev_exit(idx); }
static const struct drm_simple_display_pipe_funcs @@ -826,10 +884,29 @@ static int simpledrm_probe(struct platform_device *pdev)
static int simpledrm_remove(struct platform_device *pdev) { - struct simpledrm_device *sdev = platform_get_drvdata(pdev); - struct drm_device *dev = &sdev->dev; + struct simpledrm_device *sdev; + + spin_lock(&simpledrm_drvdata_lock); + sdev = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + spin_unlock(&simpledrm_drvdata_lock); + + /* + * The platform driver shares its reference to dev with the + * platform helpers for apertures. That reference is either + * released here when unloading the driver; or it's released + * when the driver gets kicked out by another driver. In the + * latter case, the aperture release routine clears the data + * field of the platform device. + * + * Therefore, sdev being NULL is a valid state if the driver + * has been kicked out by another DRM driver. In this case, + * it's all been cleaned up and we can return immediately. + */ + if (!sdev) + return 0;
- drm_dev_unregister(dev); + drm_dev_unplug(&sdev->dev);
return 0; }
Hi,
On 3/18/21 11:29 AM, Thomas Zimmermann wrote:
This patchset adds support for simple-framebuffer platform devices and a handover mechanism for native drivers to take-over control of the hardware.
The new driver, called simpledrm, binds to a simple-frambuffer platform device. The kernel's boot code creates such devices for firmware-provided framebuffers, such as EFI-GOP or VESA. Typically the BIOS, UEFI or boot loader sets up the framebuffers. Description via device tree is also an option.
Simpledrm is small enough to be linked into the kernel. The driver's main purpose is to provide graphical output during the early phases of the boot process, before the native DRM drivers are available. Native drivers are typically loaded from an initrd ram disk. Occationally simpledrm can also serve as interim solution on graphics hardware without native DRM driver.
So far distributions rely on fbdev drivers, such as efifb, vesafb or simplefb, for early-boot graphical output. However fbdev is deprecated and the drivers do not provide DRM interfaces for modern userspace.
Patches 1 and 2 prepare the DRM format helpers for simpledrm.
Patches 3 and 4 add a hand-over mechanism. Simpledrm acquires it's framebuffer's I/O-memory range and provides a callback function to be removed by a native driver. The native driver will remove simpledrm before taking over the hardware. The removal is integrated into existing helpers, so drivers use it automatically.
Patches 5 to 10 add the simpledrm driver. It's build on simple DRM helpers and SHMEM. It supports 16-bit, 24-bit and 32-bit RGB framebuffers. During pageflips, SHMEM buffers are copied into the framebuffer memory, similar to cirrus or mgag200. The code in patches 8 and 9 handles clocks and regulators. It's based on the simplefb drivers, but has been modified for DRM.
Thank you for your work on this, this is very interesting.
I've also been working on fastboot support (i.e., flicker-free booting). This requires state-readout from simpledrm via generic interfaces, as outlined in [1]. I do have some prototype code, but it will take a while to get this ready. Simpledrm will then support it.
I've tested simpledrm with x86 EFI and VESA framebuffers, which both work reliably. The fbdev console and Weston work automatically. Xorg requires manual configuration of the device. Xorgs current modesetting driver does not work with both, platform and PCI device, for the same physical hardware. Once configured, X11 works. I looked into X11, but couldn't see an easy way of fixing the problem. With the push towards Wayland+Xwayland I expect the problem to become a non-issue soon. Additional testing has been reported at [2].
One cosmetical issue is that simpledrm's device file is card0 and the native driver's device file is card1. After simpledrm has been kicked out, only card1 is left. This does not seem to be a practical problem however.
TODO/IDEAS:
- provide deferred takeover
I'm not sure what you mean with this ? Currently deferred-takeover is handled in the fbcon code. Current flickerfree boot works like this (assuming a single LCD panel in a laptop):
1. EFI/GOP sets up the framebuffer, draws a vendor logo 2. The bootloader runs in silent mode and does not touch anything gfx related 3. kernel boots, with a loglevel of 3 so only CRIT/EMERG messages are shown 2. efifb loads; and tells fbcon that a framebuffer is now available for it to "bind" to. Since CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y fbcon defers taking over the console and leaves the dummy-console driver in place (unless there have already been kernel messages logged, which there shouldn't because loglevel=3) 3. i915 loads, reads out the hw state compares this to the preferred-mode for the panel which it would set, they match, nothing happens. i915 takes ownership of the scanout-buffer set up by the GOP, but leaves it in place. i915 also removes the efifb /dev/fb0 and installs its own /dev/fb0 fbdev compat device, fbcon is notified of this, but is still deferred and leaves the dummy console driver in place as console driver. 4. Plymouth loads, allocates a new scan-out buffer at the panel's preferred resolution, plymouth reads the vendor-logo through the BGRT ACPI interface and fills the scanout-buffer with the vendor-logo + a spinner. Then plymouth installs the new scanout-buffer on the crtc, this is done atomically during vsync, so the user sees no changes, other then the spinner appearing (note the active VT is now in graphical mode) 5. From here on not flickering is a userspace problem
AFAICT this should work fine with simplekms too, unless it clears the screen to black when it binds.
An addition to the above sequence, if at any time either the kernel or userspace prints a message to the console; and at that time a fbdev is registered then fbcon will takeover as the console driver from the dummy driver and it will start drawing to the registered fbdev (1), destroying the framebuffer contents. Also if any messages where printend while no fbdev was registered, then fbcon will takeover the console as soon as a fbdev gets registered.
So since we already have deferred-takeover in the fbcon code, I wonder what you mean when you are talking about "provide deferred takeover" for simplekms?
Regards,
Hans
1) Except when the VT has been switched to GFX mode when this happens, then fbcon will delay using the fbdev until the VT is switched back to text mode.
p.s.
This has the interesting side effect then when logging into a desktop GUI session: kernel -> plymouth -> gdm -> GNOME user session
There never is any output to the text-console and fbcon never takes-over, so on many Laptops running say Fedora workstation the fbcon code is actually unused until the user manually switches to another virtual-console to log in in text-mode:
[hans@x1 ~]$ dmesg | grep -E 'fbcon|Console:|Truecolor' [ 0.258904] Console: colour dummy device 80x25 [ 1.274726] efifb: Truecolor: size=8:8:8:8, shift=24:16:8:0 [ 1.274768] fbcon: Deferring console take-over [ 2.540894] fbcon: i915drmfb (fb0) is primary device [ 2.540896] fbcon: Deferring console take-over [hans@x1 ~]$ uptime 12:29:39 up 4:19, 1 user, load average: 0.58, 0.75, 0.81
Look mom no fbcon
- provide bootsplash DRM client
- make simplekms usable with ARM-EFI fbs
v2:
- rename to simpledrm, aperture helpers
- reorganized patches
- use hotplug helpers for removal (Daniel)
- added DT match tables (Rob)
- use shadow-plane helpers
- lots of minor cleanups
[1] https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9Bw... [2] https://lore.kernel.org/dri-devel/1761762.3HQLrFs1K7@nerdopolis/
Thomas Zimmermann (10): drm/format-helper: Pass destination pitch to drm_fb_memcpy_dstclip() drm/format-helper: Add blitter functions drm/aperture: Move fbdev conflict helpers into drm_aperture.h drm/aperture: Add infrastructure for aperture ownership drm: Add simpledrm driver drm/simpledrm: Add fbdev emulation drm/simpledrm: Initialize framebuffer data from device-tree node drm/simpledrm: Acquire clocks from DT device node drm/simpledrm: Acquire regulators from DT device node drm/simpledrm: Acquire memory aperture for framebuffer
Documentation/gpu/drm-internals.rst | 12 + MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++ drivers/gpu/drm/drm_format_helper.c | 96 ++- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/Kconfig | 17 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/cirrus.c | 2 +- drivers/gpu/drm/tiny/simpledrm.c | 932 +++++++++++++++++++++++++ include/drm/drm_aperture.h | 96 +++ include/drm/drm_fb_helper.h | 56 +- include/drm/drm_format_helper.h | 10 +- 14 files changed, 1466 insertions(+), 60 deletions(-) create mode 100644 drivers/gpu/drm/drm_aperture.c create mode 100644 drivers/gpu/drm/tiny/simpledrm.c create mode 100644 include/drm/drm_aperture.h
-- 2.30.1
Hi
Am 25.03.21 um 12:29 schrieb Hans de Goede:
Hi,
On 3/18/21 11:29 AM, Thomas Zimmermann wrote:
This patchset adds support for simple-framebuffer platform devices and a handover mechanism for native drivers to take-over control of the hardware.
The new driver, called simpledrm, binds to a simple-frambuffer platform device. The kernel's boot code creates such devices for firmware-provided framebuffers, such as EFI-GOP or VESA. Typically the BIOS, UEFI or boot loader sets up the framebuffers. Description via device tree is also an option.
Simpledrm is small enough to be linked into the kernel. The driver's main purpose is to provide graphical output during the early phases of the boot process, before the native DRM drivers are available. Native drivers are typically loaded from an initrd ram disk. Occationally simpledrm can also serve as interim solution on graphics hardware without native DRM driver.
So far distributions rely on fbdev drivers, such as efifb, vesafb or simplefb, for early-boot graphical output. However fbdev is deprecated and the drivers do not provide DRM interfaces for modern userspace.
Patches 1 and 2 prepare the DRM format helpers for simpledrm.
Patches 3 and 4 add a hand-over mechanism. Simpledrm acquires it's framebuffer's I/O-memory range and provides a callback function to be removed by a native driver. The native driver will remove simpledrm before taking over the hardware. The removal is integrated into existing helpers, so drivers use it automatically.
Patches 5 to 10 add the simpledrm driver. It's build on simple DRM helpers and SHMEM. It supports 16-bit, 24-bit and 32-bit RGB framebuffers. During pageflips, SHMEM buffers are copied into the framebuffer memory, similar to cirrus or mgag200. The code in patches 8 and 9 handles clocks and regulators. It's based on the simplefb drivers, but has been modified for DRM.
Thank you for your work on this, this is very interesting.
I've also been working on fastboot support (i.e., flicker-free booting). This requires state-readout from simpledrm via generic interfaces, as outlined in [1]. I do have some prototype code, but it will take a while to get this ready. Simpledrm will then support it.
I've tested simpledrm with x86 EFI and VESA framebuffers, which both work reliably. The fbdev console and Weston work automatically. Xorg requires manual configuration of the device. Xorgs current modesetting driver does not work with both, platform and PCI device, for the same physical hardware. Once configured, X11 works. I looked into X11, but couldn't see an easy way of fixing the problem. With the push towards Wayland+Xwayland I expect the problem to become a non-issue soon. Additional testing has been reported at [2].
One cosmetical issue is that simpledrm's device file is card0 and the native driver's device file is card1. After simpledrm has been kicked out, only card1 is left. This does not seem to be a practical problem however.
TODO/IDEAS:
- provide deferred takeover
I'm not sure what you mean with this ? Currently deferred-takeover is handled in the fbcon code. Current flickerfree boot works like this (assuming a single LCD panel in a laptop):
- EFI/GOP sets up the framebuffer, draws a vendor logo
- The bootloader runs in silent mode and does not touch anything gfx related
- kernel boots, with a loglevel of 3 so only CRIT/EMERG messages are shown
- efifb loads; and tells fbcon that a framebuffer is now available for it to "bind" to. Since CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y fbcon defers taking over the console and leaves the dummy-console driver in place (unless there have already been kernel messages logged, which there shouldn't because loglevel=3)
- i915 loads, reads out the hw state compares this to the preferred-mode for the panel which it would set, they match, nothing happens. i915 takes ownership of the scanout-buffer set up by the GOP, but leaves it in place. i915 also removes the efifb /dev/fb0 and installs its own /dev/fb0 fbdev compat device, fbcon is notified of this, but is still deferred and leaves the dummy console driver in place as console driver.
- Plymouth loads, allocates a new scan-out buffer at the panel's preferred resolution, plymouth reads the vendor-logo through the BGRT ACPI interface and fills the scanout-buffer with the vendor-logo + a spinner. Then plymouth installs the new scanout-buffer on the crtc, this is done atomically during vsync, so the user sees no changes, other then the spinner appearing (note the active VT is now in graphical mode)
- From here on not flickering is a userspace problem
AFAICT this should work fine with simplekms too, unless it clears the screen to black when it binds.
I forgot to add the code that clears the screen, but that's the case here.
Instead of a plane-disable operation, simpledrm can at best clear the screen. This would happen during the initial mode-config reset IIRC. But we need to keep the display content stored in a framebuffer, so read-out helpers are required. There are more users of these read-out helpers. Adding them at some point probably makes sense.
Other drivers might also want to read the initial config from simpledrm via read-out helpers. I think only i915 currently supports something like that ATM.
Best regards Thomas
An addition to the above sequence, if at any time either the kernel or userspace prints a message to the console; and at that time a fbdev is registered then fbcon will takeover as the console driver from the dummy driver and it will start drawing to the registered fbdev (1), destroying the framebuffer contents. Also if any messages where printend while no fbdev was registered, then fbcon will takeover the console as soon as a fbdev gets registered.
So since we already have deferred-takeover in the fbcon code, I wonder what you mean when you are talking about "provide deferred takeover" for simplekms?
Regards,
Hans
- Except when the VT has been switched to GFX mode when this happens, then fbcon
will delay using the fbdev until the VT is switched back to text mode.
p.s.
This has the interesting side effect then when logging into a desktop GUI session: kernel -> plymouth -> gdm -> GNOME user session
There never is any output to the text-console and fbcon never takes-over, so on many Laptops running say Fedora workstation the fbcon code is actually unused until the user manually switches to another virtual-console to log in in text-mode:
[hans@x1 ~]$ dmesg | grep -E 'fbcon|Console:|Truecolor' [ 0.258904] Console: colour dummy device 80x25 [ 1.274726] efifb: Truecolor: size=8:8:8:8, shift=24:16:8:0 [ 1.274768] fbcon: Deferring console take-over [ 2.540894] fbcon: i915drmfb (fb0) is primary device [ 2.540896] fbcon: Deferring console take-over [hans@x1 ~]$ uptime 12:29:39 up 4:19, 1 user, load average: 0.58, 0.75, 0.81
Look mom no fbcon
- provide bootsplash DRM client
- make simplekms usable with ARM-EFI fbs
v2:
- rename to simpledrm, aperture helpers
- reorganized patches
- use hotplug helpers for removal (Daniel)
- added DT match tables (Rob)
- use shadow-plane helpers
- lots of minor cleanups
[1] https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9Bw... [2] https://lore.kernel.org/dri-devel/1761762.3HQLrFs1K7@nerdopolis/
Thomas Zimmermann (10): drm/format-helper: Pass destination pitch to drm_fb_memcpy_dstclip() drm/format-helper: Add blitter functions drm/aperture: Move fbdev conflict helpers into drm_aperture.h drm/aperture: Add infrastructure for aperture ownership drm: Add simpledrm driver drm/simpledrm: Add fbdev emulation drm/simpledrm: Initialize framebuffer data from device-tree node drm/simpledrm: Acquire clocks from DT device node drm/simpledrm: Acquire regulators from DT device node drm/simpledrm: Acquire memory aperture for framebuffer
Documentation/gpu/drm-internals.rst | 12 + MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++ drivers/gpu/drm/drm_format_helper.c | 96 ++- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/Kconfig | 17 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/cirrus.c | 2 +- drivers/gpu/drm/tiny/simpledrm.c | 932 +++++++++++++++++++++++++ include/drm/drm_aperture.h | 96 +++ include/drm/drm_fb_helper.h | 56 +- include/drm/drm_format_helper.h | 10 +- 14 files changed, 1466 insertions(+), 60 deletions(-) create mode 100644 drivers/gpu/drm/drm_aperture.c create mode 100644 drivers/gpu/drm/tiny/simpledrm.c create mode 100644 include/drm/drm_aperture.h
-- 2.30.1
Hi,
On 3/29/21 2:31 PM, Thomas Zimmermann wrote:
Hi
Am 25.03.21 um 12:29 schrieb Hans de Goede:
Hi,
On 3/18/21 11:29 AM, Thomas Zimmermann wrote:
This patchset adds support for simple-framebuffer platform devices and a handover mechanism for native drivers to take-over control of the hardware.
The new driver, called simpledrm, binds to a simple-frambuffer platform device. The kernel's boot code creates such devices for firmware-provided framebuffers, such as EFI-GOP or VESA. Typically the BIOS, UEFI or boot loader sets up the framebuffers. Description via device tree is also an option.
Simpledrm is small enough to be linked into the kernel. The driver's main purpose is to provide graphical output during the early phases of the boot process, before the native DRM drivers are available. Native drivers are typically loaded from an initrd ram disk. Occationally simpledrm can also serve as interim solution on graphics hardware without native DRM driver.
So far distributions rely on fbdev drivers, such as efifb, vesafb or simplefb, for early-boot graphical output. However fbdev is deprecated and the drivers do not provide DRM interfaces for modern userspace.
Patches 1 and 2 prepare the DRM format helpers for simpledrm.
Patches 3 and 4 add a hand-over mechanism. Simpledrm acquires it's framebuffer's I/O-memory range and provides a callback function to be removed by a native driver. The native driver will remove simpledrm before taking over the hardware. The removal is integrated into existing helpers, so drivers use it automatically.
Patches 5 to 10 add the simpledrm driver. It's build on simple DRM helpers and SHMEM. It supports 16-bit, 24-bit and 32-bit RGB framebuffers. During pageflips, SHMEM buffers are copied into the framebuffer memory, similar to cirrus or mgag200. The code in patches 8 and 9 handles clocks and regulators. It's based on the simplefb drivers, but has been modified for DRM.
Thank you for your work on this, this is very interesting.
I've also been working on fastboot support (i.e., flicker-free booting). This requires state-readout from simpledrm via generic interfaces, as outlined in [1]. I do have some prototype code, but it will take a while to get this ready. Simpledrm will then support it.
I've tested simpledrm with x86 EFI and VESA framebuffers, which both work reliably. The fbdev console and Weston work automatically. Xorg requires manual configuration of the device. Xorgs current modesetting driver does not work with both, platform and PCI device, for the same physical hardware. Once configured, X11 works. I looked into X11, but couldn't see an easy way of fixing the problem. With the push towards Wayland+Xwayland I expect the problem to become a non-issue soon. Additional testing has been reported at [2].
One cosmetical issue is that simpledrm's device file is card0 and the native driver's device file is card1. After simpledrm has been kicked out, only card1 is left. This does not seem to be a practical problem however.
TODO/IDEAS:
* provide deferred takeover
I'm not sure what you mean with this ? Currently deferred-takeover is handled in the fbcon code. Current flickerfree boot works like this (assuming a single LCD panel in a laptop):
- EFI/GOP sets up the framebuffer, draws a vendor logo
- The bootloader runs in silent mode and does not touch anything gfx related
- kernel boots, with a loglevel of 3 so only CRIT/EMERG messages are shown
- efifb loads; and tells fbcon that a framebuffer is now available for it to "bind"
to. Since CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y fbcon defers taking over the console and leaves the dummy-console driver in place (unless there have already been kernel messages logged, which there shouldn't because loglevel=3) 3. i915 loads, reads out the hw state compares this to the preferred-mode for the panel which it would set, they match, nothing happens. i915 takes ownership of the scanout-buffer set up by the GOP, but leaves it in place. i915 also removes the efifb /dev/fb0 and installs its own /dev/fb0 fbdev compat device, fbcon is notified of this, but is still deferred and leaves the dummy console driver in place as console driver. 4. Plymouth loads, allocates a new scan-out buffer at the panel's preferred resolution, plymouth reads the vendor-logo through the BGRT ACPI interface and fills the scanout-buffer with the vendor-logo + a spinner. Then plymouth installs the new scanout-buffer on the crtc, this is done atomically during vsync, so the user sees no changes, other then the spinner appearing (note the active VT is now in graphical mode) 5. From here on not flickering is a userspace problem
AFAICT this should work fine with simplekms too, unless it clears the screen to black when it binds.
I forgot to add the code that clears the screen, but that's the case here.
Instead of a plane-disable operation, simpledrm can at best clear the screen. This would happen during the initial mode-config reset IIRC.
Hmm, clearing the screen without any (atomic) modesets being initiated by either an in kernel drm-client or userspace seems wrong, this is certainly different from what the i915 driver does. The way I see it either a drm client provides a new framebuffer in which case you copy that over the old contents, effectively clearing it. Or a drm-client gets a handle and draws to the current fb directly, in which case it is the clients responsibility to clear it itself first.
IOW I believe that simpledrm should not clear the screen itself at all.
Regards,
Hans
But we need to keep the display content stored in a framebuffer, so read-out helpers are required. There are more users of these read-out helpers. Adding them at some point probably makes sense.
Other drivers might also want to read the initial config from simpledrm via read-out helpers. I think only i915 currently supports something like that ATM.
Best regards Thomas
An addition to the above sequence, if at any time either the kernel or userspace prints a message to the console; and at that time a fbdev is registered then fbcon will takeover as the console driver from the dummy driver and it will start drawing to the registered fbdev (1), destroying the framebuffer contents. Also if any messages where printend while no fbdev was registered, then fbcon will takeover the console as soon as a fbdev gets registered.
So since we already have deferred-takeover in the fbcon code, I wonder what you mean when you are talking about "provide deferred takeover" for simplekms?
Regards,
Hans
- Except when the VT has been switched to GFX mode when this happens, then fbcon
will delay using the fbdev until the VT is switched back to text mode.
p.s.
This has the interesting side effect then when logging into a desktop GUI session: kernel -> plymouth -> gdm -> GNOME user session
There never is any output to the text-console and fbcon never takes-over, so on many Laptops running say Fedora workstation the fbcon code is actually unused until the user manually switches to another virtual-console to log in in text-mode:
[hans@x1 ~]$ dmesg | grep -E 'fbcon|Console:|Truecolor' [ 0.258904] Console: colour dummy device 80x25 [ 1.274726] efifb: Truecolor: size=8:8:8:8, shift=24:16:8:0 [ 1.274768] fbcon: Deferring console take-over [ 2.540894] fbcon: i915drmfb (fb0) is primary device [ 2.540896] fbcon: Deferring console take-over [hans@x1 ~]$ uptime 12:29:39 up 4:19, 1 user, load average: 0.58, 0.75, 0.81
Look mom no fbcon
* provide bootsplash DRM client * make simplekms usable with ARM-EFI fbs
v2: * rename to simpledrm, aperture helpers * reorganized patches * use hotplug helpers for removal (Daniel) * added DT match tables (Rob) * use shadow-plane helpers * lots of minor cleanups
[1] https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9Bw... [2] https://lore.kernel.org/dri-devel/1761762.3HQLrFs1K7@nerdopolis/
Thomas Zimmermann (10): drm/format-helper: Pass destination pitch to drm_fb_memcpy_dstclip() drm/format-helper: Add blitter functions drm/aperture: Move fbdev conflict helpers into drm_aperture.h drm/aperture: Add infrastructure for aperture ownership drm: Add simpledrm driver drm/simpledrm: Add fbdev emulation drm/simpledrm: Initialize framebuffer data from device-tree node drm/simpledrm: Acquire clocks from DT device node drm/simpledrm: Acquire regulators from DT device node drm/simpledrm: Acquire memory aperture for framebuffer
Documentation/gpu/drm-internals.rst | 12 + MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++ drivers/gpu/drm/drm_format_helper.c | 96 ++- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/Kconfig | 17 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/cirrus.c | 2 +- drivers/gpu/drm/tiny/simpledrm.c | 932 +++++++++++++++++++++++++ include/drm/drm_aperture.h | 96 +++ include/drm/drm_fb_helper.h | 56 +- include/drm/drm_format_helper.h | 10 +- 14 files changed, 1466 insertions(+), 60 deletions(-) create mode 100644 drivers/gpu/drm/drm_aperture.c create mode 100644 drivers/gpu/drm/tiny/simpledrm.c create mode 100644 include/drm/drm_aperture.h
-- 2.30.1
Hi
Am 29.03.21 um 16:50 schrieb Hans de Goede:
Hi,
On 3/29/21 2:31 PM, Thomas Zimmermann wrote:
Hi
Am 25.03.21 um 12:29 schrieb Hans de Goede:
Hi,
On 3/18/21 11:29 AM, Thomas Zimmermann wrote:
This patchset adds support for simple-framebuffer platform devices and a handover mechanism for native drivers to take-over control of the hardware.
The new driver, called simpledrm, binds to a simple-frambuffer platform device. The kernel's boot code creates such devices for firmware-provided framebuffers, such as EFI-GOP or VESA. Typically the BIOS, UEFI or boot loader sets up the framebuffers. Description via device tree is also an option.
Simpledrm is small enough to be linked into the kernel. The driver's main purpose is to provide graphical output during the early phases of the boot process, before the native DRM drivers are available. Native drivers are typically loaded from an initrd ram disk. Occationally simpledrm can also serve as interim solution on graphics hardware without native DRM driver.
So far distributions rely on fbdev drivers, such as efifb, vesafb or simplefb, for early-boot graphical output. However fbdev is deprecated and the drivers do not provide DRM interfaces for modern userspace.
Patches 1 and 2 prepare the DRM format helpers for simpledrm.
Patches 3 and 4 add a hand-over mechanism. Simpledrm acquires it's framebuffer's I/O-memory range and provides a callback function to be removed by a native driver. The native driver will remove simpledrm before taking over the hardware. The removal is integrated into existing helpers, so drivers use it automatically.
Patches 5 to 10 add the simpledrm driver. It's build on simple DRM helpers and SHMEM. It supports 16-bit, 24-bit and 32-bit RGB framebuffers. During pageflips, SHMEM buffers are copied into the framebuffer memory, similar to cirrus or mgag200. The code in patches 8 and 9 handles clocks and regulators. It's based on the simplefb drivers, but has been modified for DRM.
Thank you for your work on this, this is very interesting.
I've also been working on fastboot support (i.e., flicker-free booting). This requires state-readout from simpledrm via generic interfaces, as outlined in [1]. I do have some prototype code, but it will take a while to get this ready. Simpledrm will then support it.
I've tested simpledrm with x86 EFI and VESA framebuffers, which both work reliably. The fbdev console and Weston work automatically. Xorg requires manual configuration of the device. Xorgs current modesetting driver does not work with both, platform and PCI device, for the same physical hardware. Once configured, X11 works. I looked into X11, but couldn't see an easy way of fixing the problem. With the push towards Wayland+Xwayland I expect the problem to become a non-issue soon. Additional testing has been reported at [2].
One cosmetical issue is that simpledrm's device file is card0 and the native driver's device file is card1. After simpledrm has been kicked out, only card1 is left. This does not seem to be a practical problem however.
TODO/IDEAS:
* provide deferred takeover
I'm not sure what you mean with this ? Currently deferred-takeover is handled in the fbcon code. Current flickerfree boot works like this (assuming a single LCD panel in a laptop):
- EFI/GOP sets up the framebuffer, draws a vendor logo
- The bootloader runs in silent mode and does not touch anything gfx related
- kernel boots, with a loglevel of 3 so only CRIT/EMERG messages are shown
- efifb loads; and tells fbcon that a framebuffer is now available for it to "bind"
to. Since CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y fbcon defers taking over the console and leaves the dummy-console driver in place (unless there have already been kernel messages logged, which there shouldn't because loglevel=3) 3. i915 loads, reads out the hw state compares this to the preferred-mode for the panel which it would set, they match, nothing happens. i915 takes ownership of the scanout-buffer set up by the GOP, but leaves it in place. i915 also removes the efifb /dev/fb0 and installs its own /dev/fb0 fbdev compat device, fbcon is notified of this, but is still deferred and leaves the dummy console driver in place as console driver. 4. Plymouth loads, allocates a new scan-out buffer at the panel's preferred resolution, plymouth reads the vendor-logo through the BGRT ACPI interface and fills the scanout-buffer with the vendor-logo + a spinner. Then plymouth installs the new scanout-buffer on the crtc, this is done atomically during vsync, so the user sees no changes, other then the spinner appearing (note the active VT is now in graphical mode) 5. From here on not flickering is a userspace problem
AFAICT this should work fine with simplekms too, unless it clears the screen to black when it binds.
I forgot to add the code that clears the screen, but that's the case here.
Instead of a plane-disable operation, simpledrm can at best clear the screen. This would happen during the initial mode-config reset IIRC.
Hmm, clearing the screen without any (atomic) modesets being initiated by either an in kernel drm-client or userspace seems wrong, this is certainly different from what the i915 driver does. The way I see it either a drm client provides a new framebuffer in which case you copy that over the old contents, effectively clearing it. Or a drm-client gets a handle and draws to the current fb directly, in which case it is the clients responsibility to clear it itself first.
IOW I believe that simpledrm should not clear the screen itself at all.
I do as well. And when I boot with simpledrm + native driver it's flicker-free from what I can tell. But drm_mode_config_reset() is supposed to reset HW and software state. There could be some corner case where we'd have to clear the screen. For now, it should be fine.
BTW if you have the time I'd appreciate your review of the patchset.
Best regards Thomas
Regards,
Hans
But we need to keep the display content stored in a framebuffer, so read-out helpers are required. There are more users of these read-out helpers. Adding them at some point probably makes sense.
Other drivers might also want to read the initial config from simpledrm via read-out helpers. I think only i915 currently supports something like that ATM.
Best regards Thomas
An addition to the above sequence, if at any time either the kernel or userspace prints a message to the console; and at that time a fbdev is registered then fbcon will takeover as the console driver from the dummy driver and it will start drawing to the registered fbdev (1), destroying the framebuffer contents. Also if any messages where printend while no fbdev was registered, then fbcon will takeover the console as soon as a fbdev gets registered.
So since we already have deferred-takeover in the fbcon code, I wonder what you mean when you are talking about "provide deferred takeover" for simplekms?
Regards,
Hans
- Except when the VT has been switched to GFX mode when this happens, then fbcon
will delay using the fbdev until the VT is switched back to text mode.
p.s.
This has the interesting side effect then when logging into a desktop GUI session: kernel -> plymouth -> gdm -> GNOME user session
There never is any output to the text-console and fbcon never takes-over, so on many Laptops running say Fedora workstation the fbcon code is actually unused until the user manually switches to another virtual-console to log in in text-mode:
[hans@x1 ~]$ dmesg | grep -E 'fbcon|Console:|Truecolor' [ 0.258904] Console: colour dummy device 80x25 [ 1.274726] efifb: Truecolor: size=8:8:8:8, shift=24:16:8:0 [ 1.274768] fbcon: Deferring console take-over [ 2.540894] fbcon: i915drmfb (fb0) is primary device [ 2.540896] fbcon: Deferring console take-over [hans@x1 ~]$ uptime 12:29:39 up 4:19, 1 user, load average: 0.58, 0.75, 0.81
Look mom no fbcon
* provide bootsplash DRM client * make simplekms usable with ARM-EFI fbs
v2: * rename to simpledrm, aperture helpers * reorganized patches * use hotplug helpers for removal (Daniel) * added DT match tables (Rob) * use shadow-plane helpers * lots of minor cleanups
[1] https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9Bw... [2] https://lore.kernel.org/dri-devel/1761762.3HQLrFs1K7@nerdopolis/
Thomas Zimmermann (10): drm/format-helper: Pass destination pitch to drm_fb_memcpy_dstclip() drm/format-helper: Add blitter functions drm/aperture: Move fbdev conflict helpers into drm_aperture.h drm/aperture: Add infrastructure for aperture ownership drm: Add simpledrm driver drm/simpledrm: Add fbdev emulation drm/simpledrm: Initialize framebuffer data from device-tree node drm/simpledrm: Acquire clocks from DT device node drm/simpledrm: Acquire regulators from DT device node drm/simpledrm: Acquire memory aperture for framebuffer
Documentation/gpu/drm-internals.rst | 12 + MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++ drivers/gpu/drm/drm_format_helper.c | 96 ++- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/Kconfig | 17 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/cirrus.c | 2 +- drivers/gpu/drm/tiny/simpledrm.c | 932 +++++++++++++++++++++++++ include/drm/drm_aperture.h | 96 +++ include/drm/drm_fb_helper.h | 56 +- include/drm/drm_format_helper.h | 10 +- 14 files changed, 1466 insertions(+), 60 deletions(-) create mode 100644 drivers/gpu/drm/drm_aperture.c create mode 100644 drivers/gpu/drm/tiny/simpledrm.c create mode 100644 include/drm/drm_aperture.h
-- 2.30.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi,
On 3/30/21 9:09 AM, Thomas Zimmermann wrote:
Hi
Am 29.03.21 um 16:50 schrieb Hans de Goede:
Hi,
On 3/29/21 2:31 PM, Thomas Zimmermann wrote:
Hi
Am 25.03.21 um 12:29 schrieb Hans de Goede:
Hi,
On 3/18/21 11:29 AM, Thomas Zimmermann wrote:
This patchset adds support for simple-framebuffer platform devices and a handover mechanism for native drivers to take-over control of the hardware.
The new driver, called simpledrm, binds to a simple-frambuffer platform device. The kernel's boot code creates such devices for firmware-provided framebuffers, such as EFI-GOP or VESA. Typically the BIOS, UEFI or boot loader sets up the framebuffers. Description via device tree is also an option.
Simpledrm is small enough to be linked into the kernel. The driver's main purpose is to provide graphical output during the early phases of the boot process, before the native DRM drivers are available. Native drivers are typically loaded from an initrd ram disk. Occationally simpledrm can also serve as interim solution on graphics hardware without native DRM driver.
So far distributions rely on fbdev drivers, such as efifb, vesafb or simplefb, for early-boot graphical output. However fbdev is deprecated and the drivers do not provide DRM interfaces for modern userspace.
Patches 1 and 2 prepare the DRM format helpers for simpledrm.
Patches 3 and 4 add a hand-over mechanism. Simpledrm acquires it's framebuffer's I/O-memory range and provides a callback function to be removed by a native driver. The native driver will remove simpledrm before taking over the hardware. The removal is integrated into existing helpers, so drivers use it automatically.
Patches 5 to 10 add the simpledrm driver. It's build on simple DRM helpers and SHMEM. It supports 16-bit, 24-bit and 32-bit RGB framebuffers. During pageflips, SHMEM buffers are copied into the framebuffer memory, similar to cirrus or mgag200. The code in patches 8 and 9 handles clocks and regulators. It's based on the simplefb drivers, but has been modified for DRM.
Thank you for your work on this, this is very interesting.
I've also been working on fastboot support (i.e., flicker-free booting). This requires state-readout from simpledrm via generic interfaces, as outlined in [1]. I do have some prototype code, but it will take a while to get this ready. Simpledrm will then support it.
I've tested simpledrm with x86 EFI and VESA framebuffers, which both work reliably. The fbdev console and Weston work automatically. Xorg requires manual configuration of the device. Xorgs current modesetting driver does not work with both, platform and PCI device, for the same physical hardware. Once configured, X11 works. I looked into X11, but couldn't see an easy way of fixing the problem. With the push towards Wayland+Xwayland I expect the problem to become a non-issue soon. Additional testing has been reported at [2].
One cosmetical issue is that simpledrm's device file is card0 and the native driver's device file is card1. After simpledrm has been kicked out, only card1 is left. This does not seem to be a practical problem however.
TODO/IDEAS:
* provide deferred takeover
I'm not sure what you mean with this ? Currently deferred-takeover is handled in the fbcon code. Current flickerfree boot works like this (assuming a single LCD panel in a laptop):
- EFI/GOP sets up the framebuffer, draws a vendor logo
- The bootloader runs in silent mode and does not touch anything gfx related
- kernel boots, with a loglevel of 3 so only CRIT/EMERG messages are shown
- efifb loads; and tells fbcon that a framebuffer is now available for it to "bind"
to. Since CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y fbcon defers taking over the console and leaves the dummy-console driver in place (unless there have already been kernel messages logged, which there shouldn't because loglevel=3) 3. i915 loads, reads out the hw state compares this to the preferred-mode for the panel which it would set, they match, nothing happens. i915 takes ownership of the scanout-buffer set up by the GOP, but leaves it in place. i915 also removes the efifb /dev/fb0 and installs its own /dev/fb0 fbdev compat device, fbcon is notified of this, but is still deferred and leaves the dummy console driver in place as console driver. 4. Plymouth loads, allocates a new scan-out buffer at the panel's preferred resolution, plymouth reads the vendor-logo through the BGRT ACPI interface and fills the scanout-buffer with the vendor-logo + a spinner. Then plymouth installs the new scanout-buffer on the crtc, this is done atomically during vsync, so the user sees no changes, other then the spinner appearing (note the active VT is now in graphical mode) 5. From here on not flickering is a userspace problem
AFAICT this should work fine with simplekms too, unless it clears the screen to black when it binds.
I forgot to add the code that clears the screen, but that's the case here.
Instead of a plane-disable operation, simpledrm can at best clear the screen. This would happen during the initial mode-config reset IIRC.
Hmm, clearing the screen without any (atomic) modesets being initiated by either an in kernel drm-client or userspace seems wrong, this is certainly different from what the i915 driver does. The way I see it either a drm client provides a new framebuffer in which case you copy that over the old contents, effectively clearing it. Or a drm-client gets a handle and draws to the current fb directly, in which case it is the clients responsibility to clear it itself first.
IOW I believe that simpledrm should not clear the screen itself at all.
I do as well. And when I boot with simpledrm + native driver it's flicker-free from what I can tell. But drm_mode_config_reset() is supposed to reset HW and software state. There could be some corner case where we'd have to clear the screen. For now, it should be fine.
Sounds good, thanks.
BTW if you have the time I'd appreciate your review of the patchset.
Sorry, but I'm burried under a whole pile of other work. So although I would like to help it is better to say no.
Regards,
Hans
But we need to keep the display content stored in a framebuffer, so read-out helpers are required. There are more users of these read-out helpers. Adding them at some point probably makes sense.
Other drivers might also want to read the initial config from simpledrm via read-out helpers. I think only i915 currently supports something like that ATM.
Best regards Thomas
An addition to the above sequence, if at any time either the kernel or userspace prints a message to the console; and at that time a fbdev is registered then fbcon will takeover as the console driver from the dummy driver and it will start drawing to the registered fbdev (1), destroying the framebuffer contents. Also if any messages where printend while no fbdev was registered, then fbcon will takeover the console as soon as a fbdev gets registered.
So since we already have deferred-takeover in the fbcon code, I wonder what you mean when you are talking about "provide deferred takeover" for simplekms?
Regards,
Hans
- Except when the VT has been switched to GFX mode when this happens, then fbcon
will delay using the fbdev until the VT is switched back to text mode.
p.s.
This has the interesting side effect then when logging into a desktop GUI session: kernel -> plymouth -> gdm -> GNOME user session
There never is any output to the text-console and fbcon never takes-over, so on many Laptops running say Fedora workstation the fbcon code is actually unused until the user manually switches to another virtual-console to log in in text-mode:
[hans@x1 ~]$ dmesg | grep -E 'fbcon|Console:|Truecolor' [ 0.258904] Console: colour dummy device 80x25 [ 1.274726] efifb: Truecolor: size=8:8:8:8, shift=24:16:8:0 [ 1.274768] fbcon: Deferring console take-over [ 2.540894] fbcon: i915drmfb (fb0) is primary device [ 2.540896] fbcon: Deferring console take-over [hans@x1 ~]$ uptime 12:29:39 up 4:19, 1 user, load average: 0.58, 0.75, 0.81
Look mom no fbcon
* provide bootsplash DRM client * make simplekms usable with ARM-EFI fbs
v2: * rename to simpledrm, aperture helpers * reorganized patches * use hotplug helpers for removal (Daniel) * added DT match tables (Rob) * use shadow-plane helpers * lots of minor cleanups
[1] https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9Bw... [2] https://lore.kernel.org/dri-devel/1761762.3HQLrFs1K7@nerdopolis/
Thomas Zimmermann (10): drm/format-helper: Pass destination pitch to drm_fb_memcpy_dstclip() drm/format-helper: Add blitter functions drm/aperture: Move fbdev conflict helpers into drm_aperture.h drm/aperture: Add infrastructure for aperture ownership drm: Add simpledrm driver drm/simpledrm: Add fbdev emulation drm/simpledrm: Initialize framebuffer data from device-tree node drm/simpledrm: Acquire clocks from DT device node drm/simpledrm: Acquire regulators from DT device node drm/simpledrm: Acquire memory aperture for framebuffer
Documentation/gpu/drm-internals.rst | 12 + MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++ drivers/gpu/drm/drm_format_helper.c | 96 ++- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/Kconfig | 17 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/cirrus.c | 2 +- drivers/gpu/drm/tiny/simpledrm.c | 932 +++++++++++++++++++++++++ include/drm/drm_aperture.h | 96 +++ include/drm/drm_fb_helper.h | 56 +- include/drm/drm_format_helper.h | 10 +- 14 files changed, 1466 insertions(+), 60 deletions(-) create mode 100644 drivers/gpu/drm/drm_aperture.c create mode 100644 drivers/gpu/drm/tiny/simpledrm.c create mode 100644 include/drm/drm_aperture.h
-- 2.30.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Tue, Mar 30, 2021 at 10:34:12AM +0200, Hans de Goede wrote:
Hi,
On 3/30/21 9:09 AM, Thomas Zimmermann wrote:
Hi
Am 29.03.21 um 16:50 schrieb Hans de Goede:
Hi,
On 3/29/21 2:31 PM, Thomas Zimmermann wrote:
Hi
Am 25.03.21 um 12:29 schrieb Hans de Goede:
Hi,
On 3/18/21 11:29 AM, Thomas Zimmermann wrote:
This patchset adds support for simple-framebuffer platform devices and a handover mechanism for native drivers to take-over control of the hardware.
The new driver, called simpledrm, binds to a simple-frambuffer platform device. The kernel's boot code creates such devices for firmware-provided framebuffers, such as EFI-GOP or VESA. Typically the BIOS, UEFI or boot loader sets up the framebuffers. Description via device tree is also an option.
Simpledrm is small enough to be linked into the kernel. The driver's main purpose is to provide graphical output during the early phases of the boot process, before the native DRM drivers are available. Native drivers are typically loaded from an initrd ram disk. Occationally simpledrm can also serve as interim solution on graphics hardware without native DRM driver.
So far distributions rely on fbdev drivers, such as efifb, vesafb or simplefb, for early-boot graphical output. However fbdev is deprecated and the drivers do not provide DRM interfaces for modern userspace.
Patches 1 and 2 prepare the DRM format helpers for simpledrm.
Patches 3 and 4 add a hand-over mechanism. Simpledrm acquires it's framebuffer's I/O-memory range and provides a callback function to be removed by a native driver. The native driver will remove simpledrm before taking over the hardware. The removal is integrated into existing helpers, so drivers use it automatically.
Patches 5 to 10 add the simpledrm driver. It's build on simple DRM helpers and SHMEM. It supports 16-bit, 24-bit and 32-bit RGB framebuffers. During pageflips, SHMEM buffers are copied into the framebuffer memory, similar to cirrus or mgag200. The code in patches 8 and 9 handles clocks and regulators. It's based on the simplefb drivers, but has been modified for DRM.
Thank you for your work on this, this is very interesting.
I've also been working on fastboot support (i.e., flicker-free booting). This requires state-readout from simpledrm via generic interfaces, as outlined in [1]. I do have some prototype code, but it will take a while to get this ready. Simpledrm will then support it.
I've tested simpledrm with x86 EFI and VESA framebuffers, which both work reliably. The fbdev console and Weston work automatically. Xorg requires manual configuration of the device. Xorgs current modesetting driver does not work with both, platform and PCI device, for the same physical hardware. Once configured, X11 works. I looked into X11, but couldn't see an easy way of fixing the problem. With the push towards Wayland+Xwayland I expect the problem to become a non-issue soon. Additional testing has been reported at [2].
One cosmetical issue is that simpledrm's device file is card0 and the native driver's device file is card1. After simpledrm has been kicked out, only card1 is left. This does not seem to be a practical problem however.
TODO/IDEAS:
* provide deferred takeover
I'm not sure what you mean with this ? Currently deferred-takeover is handled in the fbcon code. Current flickerfree boot works like this (assuming a single LCD panel in a laptop):
- EFI/GOP sets up the framebuffer, draws a vendor logo
- The bootloader runs in silent mode and does not touch anything gfx related
- kernel boots, with a loglevel of 3 so only CRIT/EMERG messages are shown
- efifb loads; and tells fbcon that a framebuffer is now available for it to "bind"
to. Since CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y fbcon defers taking over the console and leaves the dummy-console driver in place (unless there have already been kernel messages logged, which there shouldn't because loglevel=3) 3. i915 loads, reads out the hw state compares this to the preferred-mode for the panel which it would set, they match, nothing happens. i915 takes ownership of the scanout-buffer set up by the GOP, but leaves it in place. i915 also removes the efifb /dev/fb0 and installs its own /dev/fb0 fbdev compat device, fbcon is notified of this, but is still deferred and leaves the dummy console driver in place as console driver. 4. Plymouth loads, allocates a new scan-out buffer at the panel's preferred resolution, plymouth reads the vendor-logo through the BGRT ACPI interface and fills the scanout-buffer with the vendor-logo + a spinner. Then plymouth installs the new scanout-buffer on the crtc, this is done atomically during vsync, so the user sees no changes, other then the spinner appearing (note the active VT is now in graphical mode) 5. From here on not flickering is a userspace problem
AFAICT this should work fine with simplekms too, unless it clears the screen to black when it binds.
I forgot to add the code that clears the screen, but that's the case here.
Instead of a plane-disable operation, simpledrm can at best clear the screen. This would happen during the initial mode-config reset IIRC.
Hmm, clearing the screen without any (atomic) modesets being initiated by either an in kernel drm-client or userspace seems wrong, this is certainly different from what the i915 driver does. The way I see it either a drm client provides a new framebuffer in which case you copy that over the old contents, effectively clearing it. Or a drm-client gets a handle and draws to the current fb directly, in which case it is the clients responsibility to clear it itself first.
IOW I believe that simpledrm should not clear the screen itself at all.
I do as well. And when I boot with simpledrm + native driver it's flicker-free from what I can tell. But drm_mode_config_reset() is supposed to reset HW and software state. There could be some corner case where we'd have to clear the screen. For now, it should be fine.
Sounds good, thanks.
To clarify: Atomic assumes that the sw state always matches hw state, even at boot-up. Most drivers use drm_mode_config_reset to achieve that, which forces everything to off. But that breaks flicker-free boot.
To avoid that i915 (and it's the only driver doing so) has fairly elaborate state-readout code, to make sure we do faithfully preserve the full boot-up display state. This is anything but trivial to implement.
For simpledrm it's a bit simple, since state recover boils down to reading out the fb and putting it into the new one, since there's not even provisions (afaiui) for simpledrm to enable/disable the output physically.
So i915 is the exception here allowing flicker-free boot, not the rule. -Daniel
BTW if you have the time I'd appreciate your review of the patchset.
Sorry, but I'm burried under a whole pile of other work. So although I would like to help it is better to say no.
Regards,
Hans
But we need to keep the display content stored in a framebuffer, so read-out helpers are required. There are more users of these read-out helpers. Adding them at some point probably makes sense.
Other drivers might also want to read the initial config from simpledrm via read-out helpers. I think only i915 currently supports something like that ATM.
Best regards Thomas
An addition to the above sequence, if at any time either the kernel or userspace prints a message to the console; and at that time a fbdev is registered then fbcon will takeover as the console driver from the dummy driver and it will start drawing to the registered fbdev (1), destroying the framebuffer contents. Also if any messages where printend while no fbdev was registered, then fbcon will takeover the console as soon as a fbdev gets registered.
So since we already have deferred-takeover in the fbcon code, I wonder what you mean when you are talking about "provide deferred takeover" for simplekms?
Regards,
Hans
- Except when the VT has been switched to GFX mode when this happens, then fbcon
will delay using the fbdev until the VT is switched back to text mode.
p.s.
This has the interesting side effect then when logging into a desktop GUI session: kernel -> plymouth -> gdm -> GNOME user session
There never is any output to the text-console and fbcon never takes-over, so on many Laptops running say Fedora workstation the fbcon code is actually unused until the user manually switches to another virtual-console to log in in text-mode:
[hans@x1 ~]$ dmesg | grep -E 'fbcon|Console:|Truecolor' [ 0.258904] Console: colour dummy device 80x25 [ 1.274726] efifb: Truecolor: size=8:8:8:8, shift=24:16:8:0 [ 1.274768] fbcon: Deferring console take-over [ 2.540894] fbcon: i915drmfb (fb0) is primary device [ 2.540896] fbcon: Deferring console take-over [hans@x1 ~]$ uptime 12:29:39 up 4:19, 1 user, load average: 0.58, 0.75, 0.81
Look mom no fbcon
* provide bootsplash DRM client * make simplekms usable with ARM-EFI fbs
v2: * rename to simpledrm, aperture helpers * reorganized patches * use hotplug helpers for removal (Daniel) * added DT match tables (Rob) * use shadow-plane helpers * lots of minor cleanups
[1] https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9Bw... [2] https://lore.kernel.org/dri-devel/1761762.3HQLrFs1K7@nerdopolis/
Thomas Zimmermann (10): drm/format-helper: Pass destination pitch to drm_fb_memcpy_dstclip() drm/format-helper: Add blitter functions drm/aperture: Move fbdev conflict helpers into drm_aperture.h drm/aperture: Add infrastructure for aperture ownership drm: Add simpledrm driver drm/simpledrm: Add fbdev emulation drm/simpledrm: Initialize framebuffer data from device-tree node drm/simpledrm: Acquire clocks from DT device node drm/simpledrm: Acquire regulators from DT device node drm/simpledrm: Acquire memory aperture for framebuffer
Documentation/gpu/drm-internals.rst | 12 + MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++ drivers/gpu/drm/drm_format_helper.c | 96 ++- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/Kconfig | 17 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/cirrus.c | 2 +- drivers/gpu/drm/tiny/simpledrm.c | 932 +++++++++++++++++++++++++ include/drm/drm_aperture.h | 96 +++ include/drm/drm_fb_helper.h | 56 +- include/drm/drm_format_helper.h | 10 +- 14 files changed, 1466 insertions(+), 60 deletions(-) create mode 100644 drivers/gpu/drm/drm_aperture.c create mode 100644 drivers/gpu/drm/tiny/simpledrm.c create mode 100644 include/drm/drm_aperture.h
-- 2.30.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thursday, March 18, 2021 6:29:11 AM EDT Thomas Zimmermann wrote:
This patchset adds support for simple-framebuffer platform devices and a handover mechanism for native drivers to take-over control of the hardware.
The new driver, called simpledrm, binds to a simple-frambuffer platform device. The kernel's boot code creates such devices for firmware-provided framebuffers, such as EFI-GOP or VESA. Typically the BIOS, UEFI or boot loader sets up the framebuffers. Description via device tree is also an option.
Simpledrm is small enough to be linked into the kernel. The driver's main purpose is to provide graphical output during the early phases of the boot process, before the native DRM drivers are available. Native drivers are typically loaded from an initrd ram disk. Occationally simpledrm can also serve as interim solution on graphics hardware without native DRM driver.
So far distributions rely on fbdev drivers, such as efifb, vesafb or simplefb, for early-boot graphical output. However fbdev is deprecated and the drivers do not provide DRM interfaces for modern userspace.
Patches 1 and 2 prepare the DRM format helpers for simpledrm.
Patches 3 and 4 add a hand-over mechanism. Simpledrm acquires it's framebuffer's I/O-memory range and provides a callback function to be removed by a native driver. The native driver will remove simpledrm before taking over the hardware. The removal is integrated into existing helpers, so drivers use it automatically.
Patches 5 to 10 add the simpledrm driver. It's build on simple DRM helpers and SHMEM. It supports 16-bit, 24-bit and 32-bit RGB framebuffers. During pageflips, SHMEM buffers are copied into the framebuffer memory, similar to cirrus or mgag200. The code in patches 8 and 9 handles clocks and regulators. It's based on the simplefb drivers, but has been modified for DRM.
I've also been working on fastboot support (i.e., flicker-free booting). This requires state-readout from simpledrm via generic interfaces, as outlined in [1]. I do have some prototype code, but it will take a while to get this ready. Simpledrm will then support it.
I've tested simpledrm with x86 EFI and VESA framebuffers, which both work reliably. The fbdev console and Weston work automatically. Xorg requires manual configuration of the device. Xorgs current modesetting driver does not work with both, platform and PCI device, for the same physical hardware. Once configured, X11 works. I looked into X11, but couldn't see an easy way of fixing the problem. With the push towards Wayland+Xwayland I expect the problem to become a non-issue soon. Additional testing has been reported at [2].
One cosmetical issue is that simpledrm's device file is card0 and the native driver's device file is card1. After simpledrm has been kicked out, only card1 is left. This does not seem to be a practical problem however.
TODO/IDEAS:
- provide deferred takeover
- provide bootsplash DRM client
- make simplekms usable with ARM-EFI fbs
v2:
- rename to simpledrm, aperture helpers
- reorganized patches
- use hotplug helpers for removal (Daniel)
- added DT match tables (Rob)
- use shadow-plane helpers
- lots of minor cleanups
[1] https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9Bw... [2] https://lore.kernel.org/dri-devel/1761762.3HQLrFs1K7@nerdopolis/
Thomas Zimmermann (10): drm/format-helper: Pass destination pitch to drm_fb_memcpy_dstclip() drm/format-helper: Add blitter functions drm/aperture: Move fbdev conflict helpers into drm_aperture.h drm/aperture: Add infrastructure for aperture ownership drm: Add simpledrm driver drm/simpledrm: Add fbdev emulation drm/simpledrm: Initialize framebuffer data from device-tree node drm/simpledrm: Acquire clocks from DT device node drm/simpledrm: Acquire regulators from DT device node drm/simpledrm: Acquire memory aperture for framebuffer
Documentation/gpu/drm-internals.rst | 12 + MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 7 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_aperture.c | 287 ++++++++ drivers/gpu/drm/drm_format_helper.c | 96 ++- drivers/gpu/drm/mgag200/mgag200_mode.c | 2 +- drivers/gpu/drm/tiny/Kconfig | 17 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/cirrus.c | 2 +- drivers/gpu/drm/tiny/simpledrm.c | 932 +++++++++++++++++++++++++ include/drm/drm_aperture.h | 96 +++ include/drm/drm_fb_helper.h | 56 +- include/drm/drm_format_helper.h | 10 +- 14 files changed, 1466 insertions(+), 60 deletions(-) create mode 100644 drivers/gpu/drm/drm_aperture.c create mode 100644 drivers/gpu/drm/tiny/simpledrm.c create mode 100644 include/drm/drm_aperture.h
-- 2.30.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi
Testing the new version against drm-next and it works! I found I had to turn off building CONFIG_FB_SIMPLE as it was taking over as the VGA device before simpledrm (or something. But it works, and as with the older version, testing with the 'vmware' device that QEMU supports as a graphics device now has mode setting support which is pretty cool
Thanks
Thanks
dri-devel@lists.freedesktop.org