[PATCH 2/2] drm/tiny: Add ofdrm for Open Firmware framebuffers

Thomas Zimmermann tzimmermann at suse.de
Thu May 19 17:39:13 AEST 2022


Hi

Am 18.05.22 um 23:11 schrieb Mark Cave-Ayland:
> On 18/05/2022 19:30, Thomas Zimmermann wrote:
> 
>> Open Firmware provides basic display output via the 'display' node.
>> DT platform code already provides a device that represents the node's
>> framebuffer. Add a DRM driver for the device. The display mode and
>> color format is pre-initialized by the system's firmware. Runtime
>> modesetting via DRM is not possible. The display is useful during
>> early boot stages or as error fallback.
>>
>> Similar functionality is already provided by fbdev's offb driver,
>> which is insufficient for modern userspace. The old driver includes
>> support for BootX device tree, which can be found on old 32-bit
>> PowerPC Macintosh systems. If these are still in use, the
>> functionality can be added to ofdrm or implemented in a new
>> driver. As with simepldrm, the fbdev driver cannot be selected is
>> ofdrm is already enabled.
>>
>> Two noteable points about the driver:
>>
>>   * Reading the framebuffer aperture from the device tree is not
>> reliable on all systems. Ofdrm takes the heuristics and a comment
>> from offb to pick the correct range.
>>
>>   * No resource management may be tied to the underlying PCI device.
>> Otherwise the handover to the native driver will fail with a resource
>> conflict. PCI management is therefore done as part of the platform
>> device's cleanup.
>>
>> The driver has been tested on qemu's ppc64le emulation. The device
>> hand-over has been tested with bochs.
> 
> Thanks for working on this! Have you tried it on qemu-system-sparc and 
> qemu-system-sparc64 at all? At least under QEMU I'd expect it to work 
> for these platforms too, unless there is a particular dependency on PCI. 
> A couple of comments inline below:

I haven't tried Sparc, as the old offb driver only supports PPC.

I was already wondering why Sparc isn't supported. I wouldn't mind if we 
can add it.

> 
>> Signed-off-by: Thomas Zimmermann <tzimmermann at suse.de>
>> ---
>>   MAINTAINERS                   |   1 +
>>   drivers/gpu/drm/tiny/Kconfig  |  12 +
>>   drivers/gpu/drm/tiny/Makefile |   1 +
>>   drivers/gpu/drm/tiny/ofdrm.c  | 748 ++++++++++++++++++++++++++++++++++
>>   drivers/video/fbdev/Kconfig   |   1 +
>>   5 files changed, 763 insertions(+)
>>   create mode 100644 drivers/gpu/drm/tiny/ofdrm.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 43d833273ae9..090cbe1aa5e3 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -6395,6 +6395,7 @@ L:    dri-devel at lists.freedesktop.org
>>   S:    Maintained
>>   T:    git git://anongit.freedesktop.org/drm/drm-misc
>>   F:    drivers/gpu/drm/drm_aperture.c
>> +F:    drivers/gpu/drm/tiny/ofdrm.c
>>   F:    drivers/gpu/drm/tiny/simpledrm.c
>>   F:    include/drm/drm_aperture.h
>> diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
>> index 627d637a1e7e..0bc54af42e7f 100644
>> --- a/drivers/gpu/drm/tiny/Kconfig
>> +++ b/drivers/gpu/drm/tiny/Kconfig
>> @@ -51,6 +51,18 @@ 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_OFDRM
>> +    tristate "Open Firmware display driver"
>> +    depends on DRM && MMU && PPC
>> +    select DRM_GEM_SHMEM_HELPER
>> +    select DRM_KMS_HELPER
>> +    help
>> +      DRM driver for Open Firmware framebuffers.
>> +
>> +      This driver assumes that the display hardware has been initialized
>> +      by the Open Firmware before the kernel boots. Scanout buffer, 
>> size,
>> +      and display format must be provided via device tree.
>> +
>>   config DRM_PANEL_MIPI_DBI
>>       tristate "DRM support for MIPI DBI compatible panels"
>>       depends on DRM && SPI
>> diff --git a/drivers/gpu/drm/tiny/Makefile 
>> b/drivers/gpu/drm/tiny/Makefile
>> index 1d9d6227e7ab..76dde89a044b 100644
>> --- a/drivers/gpu/drm/tiny/Makefile
>> +++ b/drivers/gpu/drm/tiny/Makefile
>> @@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_ARCPGU)        += arcpgu.o
>>   obj-$(CONFIG_DRM_BOCHS)            += bochs.o
>>   obj-$(CONFIG_DRM_CIRRUS_QEMU)        += cirrus.o
>>   obj-$(CONFIG_DRM_GM12U320)        += gm12u320.o
>> +obj-$(CONFIG_DRM_OFDRM)            += ofdrm.o
>>   obj-$(CONFIG_DRM_PANEL_MIPI_DBI)    += panel-mipi-dbi.o
>>   obj-$(CONFIG_DRM_SIMPLEDRM)        += simpledrm.o
>>   obj-$(CONFIG_TINYDRM_HX8357D)        += hx8357d.o
>> diff --git a/drivers/gpu/drm/tiny/ofdrm.c b/drivers/gpu/drm/tiny/ofdrm.c
>> new file mode 100644
>> index 000000000000..aca715b36179
>> --- /dev/null
>> +++ b/drivers/gpu/drm/tiny/ofdrm.c
>> @@ -0,0 +1,748 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +
>> +#include <linux/pci.h>
>> +#include <linux/platform_device.h>
>> +
>> +#include <drm/drm_aperture.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_fb_helper.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    "ofdrm"
>> +#define DRIVER_DESC    "DRM driver for OF platform devices"
>> +#define DRIVER_DATE    "20220501"
>> +#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 OFDRM_MODE(hd, vd)    \
>> +    DRM_SIMPLE_MODE(hd, vd, RES_MM(hd), RES_MM(vd))
>> +
>> +/*
>> + * Helpers for display nodes
>> + */
>> +
>> +static int display_get_validated_int(struct drm_device *dev, const 
>> char *name, uint32_t value)
>> +{
>> +    if (value > INT_MAX) {
>> +        drm_err(dev, "invalid framebuffer %s of %u\n", name, value);
>> +        return -EINVAL;
>> +    }
>> +    return (int)value;
>> +}
>> +
>> +static int display_get_validated_int0(struct drm_device *dev, const 
>> char *name, uint32_t value)
>> +{
>> +    if (!value) {
>> +        drm_err(dev, "invalid framebuffer %s of %u\n", name, value);
>> +        return -EINVAL;
>> +    }
>> +    return display_get_validated_int(dev, name, value);
>> +}
>> +
>> +static const struct drm_format_info 
>> *display_get_validated_format(struct drm_device *dev,
>> +                                  u32 depth)
>> +{
>> +    const struct drm_format_info *info;
>> +    u32 format;
>> +
>> +    switch (depth) {
>> +    case 8:
>> +        format = drm_mode_legacy_fb_format(8, 8);
>> +        break;
>> +    case 15:
>> +    case 16:
>> +        format = drm_mode_legacy_fb_format(16, depth);
>> +        break;
>> +    case 32:
>> +        format = drm_mode_legacy_fb_format(32, 24);
>> +        break;
>> +    default:
>> +        drm_err(dev, "unsupported framebuffer depth %u\n", depth);
>> +        return ERR_PTR(-EINVAL);
>> +    }
>> +
>> +    info = drm_format_info(format);
>> +    if (!info) {
>> +        drm_err(dev, "cannot find framebuffer format for depth %u\n", 
>> depth);
>> +        return ERR_PTR(-EINVAL);
>> +    }
>> +
>> +    return info;
>> +}
>> +
>> +static int display_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, "cannot parse framebuffer %s: error %d\n", name, 
>> ret);
>> +    return ret;
>> +}
>> +
>> +static int display_get_width_of(struct drm_device *dev, struct 
>> device_node *of_node)
>> +{
>> +    u32 width;
>> +    int ret = display_read_u32_of(dev, of_node, "width", &width);
>> +
>> +    if (ret)
>> +        return ret;
>> +    return display_get_validated_int0(dev, "width", width);
>> +}
>> +
>> +static int display_get_height_of(struct drm_device *dev, struct 
>> device_node *of_node)
>> +{
>> +    u32 height;
>> +    int ret = display_read_u32_of(dev, of_node, "height", &height);
>> +
>> +    if (ret)
>> +        return ret;
>> +    return display_get_validated_int0(dev, "height", height);
>> +}
>> +
>> +static int display_get_linebytes_of(struct drm_device *dev, struct 
>> device_node *of_node)
>> +{
>> +    u32 linebytes;
>> +    int ret = display_read_u32_of(dev, of_node, "linebytes", 
>> &linebytes);
>> +
>> +    if (ret)
>> +        return ret;
>> +    return display_get_validated_int(dev, "linebytes", linebytes);
>> +}
>> +
>> +static const struct drm_format_info *display_get_format_of(struct 
>> drm_device *dev,
>> +                               struct device_node *of_node)
>> +{
>> +    u32 depth;
>> +    int ret = display_read_u32_of(dev, of_node, "depth", &depth);
>> +
>> +    if (ret)
>> +        return ERR_PTR(ret);
>> +    return display_get_validated_format(dev, depth);
>> +}
>> +
>> +static u64 display_get_address_of(struct drm_device *dev, struct 
>> device_node *of_node)
>> +{
>> +    u32 address;
>> +    int ret;
>> +
>> +    /*
>> +     * Not all devices provide an address property, it's not
>> +     * a bug if this fails. The driver will try to find the
>> +     * framebuffer base address from the device's memory regions.
>> +     */
>> +    ret = of_property_read_u32(of_node, "address", &address);
>> +    if (ret)
>> +        return OF_BAD_ADDR;
>> +
>> +    return address;
>> +}
>> +
>> +#if defined(CONFIG_PCI)
>> +static struct pci_dev *display_get_pci_dev_of(struct drm_device *dev, 
>> struct device_node *of_node)
>> +{
>> +    const __be32 *vendor_p, *device_p;
>> +    u32 vendor, device;
>> +    struct pci_dev *pcidev;
>> +
>> +    vendor_p = of_get_property(of_node, "vendor-id", NULL);
>> +    if (!vendor_p)
>> +        return ERR_PTR(-ENODEV);
>> +    vendor = be32_to_cpup(vendor_p);
>> +
>> +    device_p = of_get_property(of_node, "device-id", NULL);
>> +    if (!device_p)
>> +        return ERR_PTR(-ENODEV);
>> +    device = be32_to_cpup(device_p);
>> +
>> +    pcidev = pci_get_device(vendor, device, NULL);
>> +    if (!pcidev)
>> +        return ERR_PTR(-ENODEV);
>> +
>> +    return pcidev;
>> +}
>> +#else
>> +static struct pci_dev *display_get_pci_dev_of(struct drm_device *dev, 
>> struct device_node *of_node)
>> +{
>> +    return ERR_PTR(-ENODEV);
>> +}
>> +#endif
>> +
>> +/*
>> + * Open Firmware display device
>> + */
>> +
>> +struct ofdrm_device {
>> +    struct drm_device dev;
>> +    struct platform_device *pdev;
>> +
>> +    /* OF display settings */
>> +    struct drm_display_mode mode;
>> +    const struct drm_format_info *format;
>> +    unsigned int pitch;
>> +    resource_size_t fb_base;
>> +    resource_size_t fb_size;
>> +
>> +    /* 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 ofdrm_device *ofdrm_device_of_dev(struct drm_device *dev)
>> +{
>> +    return container_of(dev, struct ofdrm_device, dev);
>> +}
>> +
>> +/*
>> + *  OF display settings
>> + */
>> +
>> +static void ofdrm_pci_release(void *data)
>> +{
>> +    struct pci_dev *pcidev = data;
>> +
>> +    pci_disable_device(pcidev);
>> +}
>> +
>> +static struct drm_display_mode ofdrm_mode(unsigned int width, 
>> unsigned int height)
>> +{
>> +    struct drm_display_mode mode = { OFDRM_MODE(width, height) };
>> +
>> +    mode.clock = mode.hdisplay * mode.vdisplay * 60 / 1000 /* kHz */;
>> +    drm_mode_set_name(&mode);
>> +
>> +    return mode;
>> +}
>> +
>> +static struct resource *ofdrm_find_fb_resource(struct ofdrm_device 
>> *odev,
>> +                           struct resource *fb_res)
>> +{
>> +    struct platform_device *pdev = to_platform_device(odev->dev.dev);
>> +    struct resource *res, *max_res = NULL;
>> +    u32 i;
>> +
>> +    for (i = 0; pdev->num_resources; ++i) {
>> +        res = platform_get_resource(pdev, IORESOURCE_MEM, i);
>> +        if (!res)
>> +            break; /* all resources processed */
>> +        if (resource_size(res) < resource_size(fb_res))
>> +            continue; /* resource too small */
>> +        if (fb_res->start && resource_contains(res, fb_res))
>> +            return res; /* resource contains framebuffer */
>> +        if (!max_res || resource_size(res) > resource_size(max_res))
>> +            max_res = res; /* store largest resource as fallback */
>> +    }
>> +
>> +    return max_res;
>> +}
>> +
>> +static int ofdrm_device_init_fb(struct ofdrm_device *odev)
>> +{
>> +    struct drm_device *dev = &odev->dev;
>> +    struct platform_device *pdev = odev->pdev;
>> +    struct device_node *of_node = pdev->dev.of_node;
>> +    int width, height, linebytes;
>> +    u64 address;
>> +    const struct drm_format_info *format;
>> +    struct pci_dev *pcidev;
>> +    struct resource *res;
>> +    int ret;
>> +
>> +    width = display_get_width_of(dev, of_node);
>> +    if (width < 0)
>> +        return width;
>> +    height = display_get_height_of(dev, of_node);
>> +    if (height < 0)
>> +        return height;
>> +    linebytes = display_get_linebytes_of(dev, of_node);
>> +    if (linebytes < 0)
>> +        return linebytes;
>> +    format = display_get_format_of(dev, of_node);
>> +    if (IS_ERR(format))
>> +        return PTR_ERR(format);
>> +    address = display_get_address_of(dev, of_node);
>> +
>> +    /*
>> +     * Never use pcim_ or other managed helpers on the returned PCI
>> +     * device. Otherwise, probing the native driver will fail for
>> +     * resource conflicts. PCI-device management has to be tied to
>> +     * the lifetime of the platform device until the native driver
>> +     * takes over.
>> +     */
>> +    pcidev = display_get_pci_dev_of(dev, of_node);
>> +    if (!IS_ERR(pcidev)) {
>> +        ret = pci_enable_device(pcidev);
>> +        if (drm_WARN(dev, ret, "pci_enable_device(%s) failed: %d\n",
>> +                 dev_name(&pcidev->dev), ret))
>> +            return ret;
>> +        ret = devm_add_action_or_reset(&pdev->dev, ofdrm_pci_release, 
>> pcidev);
>> +        if (ret)
>> +            return ret;
>> +    }
>> +
>> +    odev->mode = ofdrm_mode(width, height);
>> +    odev->format = format;
>> +
>> +    if (linebytes)
>> +        odev->pitch = linebytes;
>> +    else
>> +        odev->pitch = width * format->cpp[0];
>> +
>> +    odev->fb_size = odev->pitch * height;
>> +
>> +    /*
>> +     * Try to figure out the address of the framebuffer. 
>> Unfortunately, Open
>> +     * Firmware doesn't provide a standard way to do so. All we can 
>> do is a
>> +     * dodgy heuristic that happens to work in practice.
> 
> This isn't quite true: there is a Forth value named "frame-buffer-adr" 
> which should be set to the physical screen base address for PCI and 
> non-PCI cards. It is mentioned in both the IEEE-1275 specification (page 
> 32) and according to OpenBIOS is also referenced by BootX: 
> https://mail.coreboot.org/pipermail/openbios/2011-August/006617.html.

I briefly looked at the patch and the spec document, but it looks like 
this is only for OF's Forth interface. (?)  The driver is build on top 
of device-tree values.

> 
>> +     * On most machines, the "address" property contains what we 
>> need, though
>> +     * not on Matrox cards found in IBM machines. What appears to 
>> give good
>> +     * results is to go through the PCI ranges and pick one that 
>> encloses the
>> +     * "address" property. If none match, we pick the largest.
>> +     */
> 
> In my experience so far "frame-buffer-adr" is the definitive way to get 
> the screen physical address, however you'll need to call the CIF 
> interface in order to get the value. The cases where I've seen an 
> "address" property in the display node is when the display has also been 
> mapped to a virtual address, which may or may not be the same as the 
> physical address. I suspect this optional virtual mapping may not be 
> present by the time the ofdrm driver loads?

It's all physical addresses.

At least on qemu's emulation, there's no 'frame-buffer-addr' device-tree 
value. Or is it a Sparc thing?

Best regards
Thomas


-- 
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Maxfeldstr. 5, 90409 Nürnberg, Germany
(HRB 36809, AG Nürnberg)
Geschäftsführer: Ivo Totev
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature
Type: application/pgp-signature
Size: 840 bytes
Desc: OpenPGP digital signature
URL: <http://lists.ozlabs.org/pipermail/linuxppc-dev/attachments/20220519/07b18d1c/attachment-0001.sig>


More information about the Linuxppc-dev mailing list