Use the PCI device as parent of the system-framebuffer device instead of the coreboot device. Prevents SIGBUS or SIGSEG after hot-unplug of the PCI device while the framebuffer is active.
The simple-framebuffer device depends on the PCI hardware, so this device needs to be its parent. The current coreboot parent is no longer needed after the system-framebuffer evice has been created. On systems without PCI or if no PCI parent device could be found, the platform device hangs on the platform bus directly. The fix here is similar to code in sysfb, which contained that same bug. Signed-off-by: Thomas Zimmermann <[email protected]> --- .../firmware/google/framebuffer-coreboot.c | 85 +++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/google/framebuffer-coreboot.c b/drivers/firmware/google/framebuffer-coreboot.c index 767515a30a52..cdb10f5de6ad 100644 --- a/drivers/firmware/google/framebuffer-coreboot.c +++ b/drivers/firmware/google/framebuffer-coreboot.c @@ -13,6 +13,7 @@ #include <linux/kernel.h> #include <linux/mm.h> #include <linux/module.h> +#include <linux/pci.h> #include <linux/platform_data/simplefb.h> #include <linux/platform_device.h> #include <linux/sysfb.h> @@ -21,14 +22,71 @@ #define CB_TAG_FRAMEBUFFER 0x12 +#if defined(CONFIG_PCI) +static bool framebuffer_pci_dev_is_enabled(struct pci_dev *pdev) +{ + /* + * TODO: Try to integrate this code into the PCI subsystem + */ + int ret; + u16 command; + + ret = pci_read_config_word(pdev, PCI_COMMAND, &command); + if (ret != PCIBIOS_SUCCESSFUL) + return false; + if (!(command & PCI_COMMAND_MEMORY)) + return false; + return true; +} + +static struct pci_dev *framebuffer_parent_pci_dev(struct resource *res) +{ + struct pci_dev *pdev = NULL; + const struct resource *r = NULL; + + while (!r && (pdev = pci_get_base_class(PCI_BASE_CLASS_DISPLAY, pdev))) + r = pci_find_resource(pdev, res); + + if (!r || !pdev) + return NULL; /* not found; not an error */ + + if (!framebuffer_pci_dev_is_enabled(pdev)) { + pci_dev_put(pdev); + return ERR_PTR(-ENODEV); + } + + return pdev; +} +#else +static struct pci_dev *framebuffer_parent_pci_dev(struct resource *res) +{ + return NULL; +} +#endif + +static struct device *framebuffer_parent_dev(struct resource *res) +{ + struct pci_dev *pdev; + + pdev = framebuffer_parent_pci_dev(res); + if (IS_ERR(pdev)) + return ERR_CAST(pdev); + else if (pdev) + return &pdev->dev; + + return NULL; +} + static const struct simplefb_format formats[] = SIMPLEFB_FORMATS; static int framebuffer_probe(struct coreboot_device *dev) { int i; struct lb_framebuffer *fb = &dev->framebuffer; + struct device *parent; struct platform_device *pdev; struct resource res; + int ret; struct simplefb_platform_data pdata = { .width = fb->x_resolution, .height = fb->y_resolution, @@ -57,6 +115,10 @@ static int framebuffer_probe(struct coreboot_device *dev) if (res.end <= res.start) return -EINVAL; + parent = framebuffer_parent_dev(&res); + if (IS_ERR(parent)) + return PTR_ERR(parent); + for (i = 0; i < ARRAY_SIZE(formats); ++i) { if (fb->bits_per_pixel == formats[i].bits_per_pixel && fb->red_mask_pos == formats[i].red.offset && @@ -67,17 +129,30 @@ static int framebuffer_probe(struct coreboot_device *dev) fb->blue_mask_size == formats[i].blue.length) pdata.format = formats[i].name; } - if (!pdata.format) - return -ENODEV; + if (!pdata.format) { + ret = -ENODEV; + goto err_put_device_parent; + } - pdev = platform_device_register_resndata(&dev->dev, + pdev = platform_device_register_resndata(parent, "simple-framebuffer", 0, &res, 1, &pdata, sizeof(pdata)); - if (IS_ERR(pdev)) + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); pr_warn("coreboot: could not register framebuffer\n"); + goto err_put_device_parent; + } + + if (parent) + put_device(parent); + + return 0; - return PTR_ERR_OR_ZERO(pdev); +err_put_device_parent: + if (parent) + put_device(parent); + return ret; } static const struct coreboot_device_id framebuffer_ids[] = { -- 2.52.0
