Re: [PATCH v3] efi: arm: defer probe of PCIe backed efifb on DT systems

From: Ard Biesheuvel
Date: Fri Jan 10 2020 - 01:41:12 EST


On Fri, 10 Jan 2020 at 04:01, Saravana Kannan <saravanak@xxxxxxxxxx> wrote:
>
> From: Ard Biesheuvel <ardb@xxxxxxxxxx>
>
> The new of_devlink support breaks PCIe probing on ARM platforms booting
> via UEFI if the firmware exposes a EFI framebuffer that is backed by a
> PCI device. The reason is that the probing order gets reversed,
> resulting in a resource conflict on the framebuffer memory window when
> the PCIe probes last, causing it to give up entirely.
>
> Given that we rely on PCI quirks to deal with EFI framebuffers that get
> moved around in memory, we cannot simply drop the memory reservation, so
> instead, let's use the device link infrastructure to register this
> dependency, and force the probing to occur in the expected order.
>
> Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
> Signed-off-by: Ard Biesheuvel <ardb@xxxxxxxxxx>
> Co-developed-by: Saravana Kannan <saravanak@xxxxxxxxxx>
> Signed-off-by: Saravana Kannan <saravanak@xxxxxxxxxx>
> Reviewed-by: Ard Biesheuvel <ardb@xxxxxxxxxx>
> ---
>
> v1 -> v2:
> - Rewrote the device linking part to not depend on initcall ordering
> v2 -> v3:
> - Added const and check for CONFIG_PCI
>

Thanks. I've queued this version in efi/next for v5.6


> drivers/firmware/efi/arm-init.c | 107 ++++++++++++++++++++++++++++++--
> 1 file changed, 103 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/firmware/efi/arm-init.c b/drivers/firmware/efi/arm-init.c
> index 904fa09e6a6b..d99f5b0c8a09 100644
> --- a/drivers/firmware/efi/arm-init.c
> +++ b/drivers/firmware/efi/arm-init.c
> @@ -10,10 +10,12 @@
> #define pr_fmt(fmt) "efi: " fmt
>
> #include <linux/efi.h>
> +#include <linux/fwnode.h>
> #include <linux/init.h>
> #include <linux/memblock.h>
> #include <linux/mm_types.h>
> #include <linux/of.h>
> +#include <linux/of_address.h>
> #include <linux/of_fdt.h>
> #include <linux/platform_device.h>
> #include <linux/screen_info.h>
> @@ -276,15 +278,112 @@ void __init efi_init(void)
> efi_memmap_unmap();
> }
>
> +static bool efifb_overlaps_pci_range(const struct of_pci_range *range)
> +{
> + u64 fb_base = screen_info.lfb_base;
> +
> + if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE)
> + fb_base |= (u64)(unsigned long)screen_info.ext_lfb_base << 32;
> +
> + return fb_base >= range->cpu_addr &&
> + fb_base < (range->cpu_addr + range->size);
> +}
> +
> +static struct device_node *find_pci_overlap_node(void)
> +{
> + struct device_node *np;
> +
> + for_each_node_by_type(np, "pci") {
> + struct of_pci_range_parser parser;
> + struct of_pci_range range;
> + int err;
> +
> + err = of_pci_range_parser_init(&parser, np);
> + if (err) {
> + pr_warn("of_pci_range_parser_init() failed: %d\n", err);
> + continue;
> + }
> +
> + for_each_of_pci_range(&parser, &range)
> + if (efifb_overlaps_pci_range(&range))
> + return np;
> + }
> + return NULL;
> +}
> +
> +/*
> + * If the efifb framebuffer is backed by a PCI graphics controller, we have
> + * to ensure that this relation is expressed using a device link when
> + * running in DT mode, or the probe order may be reversed, resulting in a
> + * resource reservation conflict on the memory window that the efifb
> + * framebuffer steals from the PCIe host bridge.
> + */
> +static int efifb_add_links(const struct fwnode_handle *fwnode,
> + struct device *dev)
> +{
> + struct device_node *sup_np;
> + struct device *sup_dev;
> +
> + sup_np = find_pci_overlap_node();
> +
> + /*
> + * If there's no PCI graphics controller backing the efifb, we are
> + * done here.
> + */
> + if (!sup_np)
> + return 0;
> +
> + sup_dev = get_dev_from_fwnode(&sup_np->fwnode);
> + of_node_put(sup_np);
> +
> + /*
> + * Return -ENODEV if the PCI graphics controller device hasn't been
> + * registered yet. This ensures that efifb isn't allowed to probe
> + * and this function is retried again when new devices are
> + * registered.
> + */
> + if (!sup_dev)
> + return -ENODEV;
> +
> + /*
> + * If this fails, retrying this function at a later point won't
> + * change anything. So, don't return an error after this.
> + */
> + if (!device_link_add(dev, sup_dev, 0))
> + dev_warn(dev, "device_link_add() failed\n");
> +
> + put_device(sup_dev);
> +
> + return 0;
> +}
> +
> +static const struct fwnode_operations efifb_fwnode_ops = {
> + .add_links = efifb_add_links,
> +};
> +
> +static struct fwnode_handle efifb_fwnode = {
> + .ops = &efifb_fwnode_ops,
> +};
> +
> static int __init register_gop_device(void)
> {
> - void *pd;
> + struct platform_device *pd;
> + int err;
>
> if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI)
> return 0;
>
> - pd = platform_device_register_data(NULL, "efi-framebuffer", 0,
> - &screen_info, sizeof(screen_info));
> - return PTR_ERR_OR_ZERO(pd);
> + pd = platform_device_alloc("efi-framebuffer", 0);
> + if (!pd)
> + return -ENOMEM;
> +
> + if (IS_ENABLED(CONFIG_PCI))
> + pd->dev.fwnode = &efifb_fwnode;
> +
> + err = platform_device_add_data(pd, &screen_info, sizeof(screen_info));
> + if (err)
> + return err;
> +
> + return platform_device_add(pd);
> }
> subsys_initcall(register_gop_device);
> --
> 2.25.0.rc1.283.g88dfdc4193-goog
>