From patchwork Mon Jun 24 22:27:28 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Herrmann X-Patchwork-Id: 2773771 Return-Path: X-Original-To: patchwork-linux-fbdev@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id D72F99F758 for ; Mon, 24 Jun 2013 22:29:12 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 9AC8C20223 for ; Mon, 24 Jun 2013 22:29:11 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 39F8520218 for ; Mon, 24 Jun 2013 22:29:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751029Ab3FXW2q (ORCPT ); Mon, 24 Jun 2013 18:28:46 -0400 Received: from mail-ee0-f51.google.com ([74.125.83.51]:38949 "EHLO mail-ee0-f51.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750824Ab3FXW2U (ORCPT ); Mon, 24 Jun 2013 18:28:20 -0400 Received: by mail-ee0-f51.google.com with SMTP id e52so6315862eek.24 for ; Mon, 24 Jun 2013 15:28:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; bh=+VMWg5xZSxPwjMBQY+e9OTzVIa6TUoLX9hcbBmavGxU=; b=rFVZ77t1uLRwF8SfBCysWAEGTwGqRlBXJ223d45505/4h2hIwVQ+FajrG6oI79qtt5 ilzg0bedg67Ag9hGzwdEKzdhrJstEPF3r1kggR67u1EUHZzk4mX852ARPJuy+6i5w1FT yOWMfnRNfSPOuImDGMdEcKPm9G4R0Vu8l6Whpa+G4Kh606978Gr5nlUvvXms1amRNofe 5hIgAcY5lw46Z8embaymlZCQ0La8Krc2RPzA+a4j3REBpY6lrqZgwUYghsmuSnpM4Nrr LyKvUBHkUEn2H7zQ8wqV3bFxHU6tM2tFW3QiBXP/efKKEp0u644KNtjio5nWMu+jd8WR NALw== X-Received: by 10.15.98.203 with SMTP id bj51mr26975698eeb.22.1372112899270; Mon, 24 Jun 2013 15:28:19 -0700 (PDT) Received: from localhost.localdomain (stgt-5f718d2d.pool.mediaWays.net. [95.113.141.45]) by mx.google.com with ESMTPSA id b7sm31487934eef.16.2013.06.24.15.28.17 for (version=TLSv1.2 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Mon, 24 Jun 2013 15:28:18 -0700 (PDT) From: David Herrmann To: dri-devel@lists.freedesktop.org Cc: linux-kernel@vger.kernel.org, Dave Airlie , linux-fbdev@vger.kernel.org, Stephen Warren , Olof Johansson , David Herrmann Subject: [RFC 5/6] drm: add helpers to kick out firmware drivers Date: Tue, 25 Jun 2013 00:27:28 +0200 Message-Id: <1372112849-670-6-git-send-email-dh.herrmann@gmail.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1372112849-670-1-git-send-email-dh.herrmann@gmail.com> References: <1372112849-670-1-git-send-email-dh.herrmann@gmail.com> Sender: linux-fbdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fbdev@vger.kernel.org X-Spam-Status: No, score=-7.9 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP If we load a real hardware DRM driver, we want all firmware drivers to be unloaded. Historically, this was done via remove_conflicting_framebuffers(), but for DRM drivers (like SimpleDRM) we need a new way. This patch introduces DRIVER_FIRMWARE and DRM apertures to provide a quite similar way to kick out firmware DRM drivers. Additionally, unlike the fbdev equivalent, DRM firmware drivers can now query the system whether a real hardware driver is already loaded and prevent loading themselves. Signed-off-by: David Herrmann --- drivers/gpu/drm/drm_pci.c | 1 + drivers/gpu/drm/drm_platform.c | 1 + drivers/gpu/drm/drm_stub.c | 107 +++++++++++++++++++++++++++++ drivers/gpu/drm/drm_usb.c | 1 + drivers/gpu/drm/simpledrm/simpledrm.h | 1 + drivers/gpu/drm/simpledrm/simpledrm_drv.c | 3 +- drivers/gpu/drm/simpledrm/simpledrm_main.c | 34 +++++++++ include/drm/drmP.h | 26 +++++++ 8 files changed, 173 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_pci.c b/drivers/gpu/drm/drm_pci.c index 14194b6..4dcb2a4 100644 --- a/drivers/gpu/drm/drm_pci.c +++ b/drivers/gpu/drm/drm_pci.c @@ -366,6 +366,7 @@ int drm_get_pci_dev(struct pci_dev *pdev, const struct pci_device_id *ent, } list_add_tail(&dev->driver_item, &driver->device_list); + list_add_tail(&dev->global_item, &drm_devlist); DRM_INFO("Initialized %s %d.%d.%d %s for %s on minor %d\n", driver->name, driver->major, driver->minor, driver->patchlevel, diff --git a/drivers/gpu/drm/drm_platform.c b/drivers/gpu/drm/drm_platform.c index b8a282e..94923c8 100644 --- a/drivers/gpu/drm/drm_platform.c +++ b/drivers/gpu/drm/drm_platform.c @@ -88,6 +88,7 @@ int drm_get_platform_dev(struct platform_device *platdev, } list_add_tail(&dev->driver_item, &driver->device_list); + list_add_tail(&dev->global_item, &drm_devlist); mutex_unlock(&drm_global_mutex); diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c index 16f3ec5..a433ab0 100644 --- a/drivers/gpu/drm/drm_stub.c +++ b/drivers/gpu/drm/drm_stub.c @@ -46,6 +46,9 @@ EXPORT_SYMBOL(drm_vblank_offdelay); unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */ EXPORT_SYMBOL(drm_timestamp_precision); +LIST_HEAD(drm_devlist); /* device list; protected by global lock */ +EXPORT_SYMBOL(drm_devlist); + /* * Default to use monotonic timestamps for wait-for-vblank and page-flip * complete events. @@ -484,7 +487,9 @@ void drm_put_dev(struct drm_device *dev) drm_put_minor(&dev->primary); + list_del(&dev->global_item); list_del(&dev->driver_item); + kfree(dev->apertures); kfree(dev->devname); kfree(dev); } @@ -507,3 +512,105 @@ void drm_unplug_dev(struct drm_device *dev) mutex_unlock(&drm_global_mutex); } EXPORT_SYMBOL(drm_unplug_dev); + +void drm_unplug_dev_locked(struct drm_device *dev) +{ + /* for a USB device */ + if (drm_core_check_feature(dev, DRIVER_MODESET)) + drm_unplug_minor(dev->control); + drm_unplug_minor(dev->primary); + + drm_device_set_unplugged(dev); + + /* TODO: schedule drm_put_dev if open_count == 0 */ +} +EXPORT_SYMBOL(drm_unplug_dev_locked); + +#define VGA_FB_PHYS 0xa0000 + +static bool apertures_overlap(struct drm_device *dev, + struct apertures_struct *ap, + bool boot) +{ + unsigned int i, j; + struct aperture *a, *b; + + if (!dev->apertures) + return false; + + for (i = 0; i < dev->apertures->count; ++i) { + a = &dev->apertures->ranges[i]; + + if (boot && a->base == VGA_FB_PHYS) + return true; + + for (j = 0; ap && j < ap->count; ++j) { + b = &ap->ranges[j]; + if (a->base <= b->base && a->base + a->size > b->base) + return true; + if (b->base <= a->base && b->base + b->size > a->base) + return true; + } + } + + return false; +} + +/** + * Kick out firmware + * + * Virtually unplug any firmware graphics devices which overlap the given + * region. This must be called with the global-drm-mutex locked. + * This calls the kick_out_firmware() callback on any firmware DRM driver and + * after that remove_conflicting_framebuffers() to remove legacy fbdev + * framebuffers. + */ +void drm_kick_out_firmware(struct apertures_struct *ap, bool boot) +{ + struct drm_device *dev; + + list_for_each_entry(dev, &drm_devlist, global_item) { + if (!drm_core_check_feature(dev, DRIVER_FIRMWARE)) + continue; + if (apertures_overlap(dev, ap, boot)) { + DRM_INFO("kicking out firmware %s\n", + dev->devname); + dev->driver->kick_out_firmware(dev); + } + } + + remove_conflicting_framebuffers(ap, "DRM", boot); +} +EXPORT_SYMBOL(drm_kick_out_firmware); + +/** + * Verify that no driver uses firmware FBs + * + * Whenever you register a firmware framebuffer driver, you should store the + * apertures in @ap and test whether any other registered driver already + * claimed this area. Hence, if this function returns true, you should _not_ + * register your driver! + */ +bool drm_is_firmware_used(struct apertures_struct *ap) +{ + struct drm_device *dev; + unsigned int i; + bool boot = false; + + for (i = 0; ap && i < ap->count; ++i) { + if (ap->ranges[i].base == VGA_FB_PHYS) { + boot = true; + break; + } + } + + list_for_each_entry(dev, &drm_devlist, global_item) { + if (dev->apert_boot && boot) + return true; + if (apertures_overlap(dev, ap, false)) + return true; + } + + return false; +} +EXPORT_SYMBOL(drm_is_firmware_used); diff --git a/drivers/gpu/drm/drm_usb.c b/drivers/gpu/drm/drm_usb.c index 34a156f..5ad8dba 100644 --- a/drivers/gpu/drm/drm_usb.c +++ b/drivers/gpu/drm/drm_usb.c @@ -50,6 +50,7 @@ int drm_get_usb_dev(struct usb_interface *interface, goto err_g3; list_add_tail(&dev->driver_item, &driver->device_list); + list_add_tail(&dev->global_item, &drm_devlist); mutex_unlock(&drm_global_mutex); diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h index cfd99f9..03373fe 100644 --- a/drivers/gpu/drm/simpledrm/simpledrm.h +++ b/drivers/gpu/drm/simpledrm/simpledrm.h @@ -55,6 +55,7 @@ struct sdrm_device { int sdrm_drm_load(struct drm_device *ddev, unsigned long flags); int sdrm_drm_unload(struct drm_device *ddev); +void sdrm_drm_kick_out_firmware(struct drm_device *ddev); int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma); int sdrm_pdev_init(struct sdrm_device *sdrm); void sdrm_pdev_destroy(struct sdrm_device *sdrm); diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c index 282752c..e5d0ce0 100644 --- a/drivers/gpu/drm/simpledrm/simpledrm_drv.c +++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c @@ -36,9 +36,10 @@ static const struct file_operations sdrm_drm_fops = { }; static struct drm_driver sdrm_drm_driver = { - .driver_features = DRIVER_MODESET | DRIVER_GEM, + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_FIRMWARE, .load = sdrm_drm_load, .unload = sdrm_drm_unload, + .kick_out_firmware = sdrm_drm_kick_out_firmware, .fops = &sdrm_drm_fops, .gem_init_object = sdrm_gem_init_object, diff --git a/drivers/gpu/drm/simpledrm/simpledrm_main.c b/drivers/gpu/drm/simpledrm/simpledrm_main.c index ffa1abb..6b7696e 100644 --- a/drivers/gpu/drm/simpledrm/simpledrm_main.c +++ b/drivers/gpu/drm/simpledrm/simpledrm_main.c @@ -269,6 +269,21 @@ int sdrm_drm_load(struct drm_device *ddev, unsigned long flags) if (ret) goto err_name; + ddev->apertures = alloc_apertures(1); + if (!ddev->apertures) { + ret = -ENOMEM; + goto err_pdev; + } + + ddev->apertures->ranges[0].base = sdrm->fb_base; + ddev->apertures->ranges[0].size = sdrm->fb_size; + + if (drm_is_firmware_used(ddev->apertures)) { + dev_info(ddev->dev, "firmware framebuffer is already in use\n"); + ret = -EBUSY; + goto err_apert; + } + drm_mode_config_init(ddev); ddev->mode_config.min_width = 0; ddev->mode_config.min_height = 0; @@ -310,6 +325,9 @@ int sdrm_drm_load(struct drm_device *ddev, unsigned long flags) err_cleanup: drm_mode_config_cleanup(ddev); +err_apert: + kfree(ddev->apertures); +err_pdev: sdrm_pdev_destroy(sdrm); err_name: kfree(ddev->devname); @@ -326,7 +344,23 @@ int sdrm_drm_unload(struct drm_device *ddev) sdrm_fbdev_cleanup(sdrm); drm_mode_config_cleanup(ddev); sdrm_pdev_destroy(sdrm); + kfree(ddev->apertures); kfree(sdrm); return 0; } + +void sdrm_drm_kick_out_firmware(struct drm_device *ddev) +{ + struct sdrm_device *sdrm = ddev->dev_private; + + mutex_lock(&ddev->struct_mutex); + + sdrm_fbdev_cleanup(sdrm); + drm_unplug_dev_locked(ddev); + if (sdrm->fb_obj) + sdrm_gem_unmap_object(sdrm->fb_obj); + sdrm_pdev_destroy(sdrm); + + mutex_unlock(&ddev->struct_mutex); +} diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 63d17ee..a19e710 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,7 @@ int drm_err(const char *func, const char *format, ...); #define DRIVER_GEM 0x1000 #define DRIVER_MODESET 0x2000 #define DRIVER_PRIME 0x4000 +#define DRIVER_FIRMWARE 0x8000 #define DRIVER_BUS_PCI 0x1 #define DRIVER_BUS_PLATFORM 0x2 @@ -954,6 +956,23 @@ struct drm_driver { struct drm_device *dev, uint32_t handle); + /** + * kick_out_firmware - kick out firmware driver + * @dev: DRM device + * + * Iff this driver has DRIVER_FIRMWARE set, this function is called + * when a real hw-driver is loaded and claims the framebuffer memory + * that is mapped by the firmware driver. This is called with the + * global drm mutex held. + * Drivers should unmap any memory-mappings, disable the device and + * schedule a driver removal. + * + * Note that a driver setting DRIVER_FIRMWARE is supposed to also set + * the "apertures" information on @dev. It is used to match the memory + * regions that are used by firmware drivers. + */ + void (*kick_out_firmware) (struct drm_device *dev); + /* Driver private ops for this object */ const struct vm_operations_struct *gem_vm_ops; @@ -1077,6 +1096,7 @@ struct drm_pending_vblank_event { */ struct drm_device { struct list_head driver_item; /**< list of devices per driver */ + struct list_head global_item; /**< global list of devices */ char *devname; /**< For /proc/interrupts */ int if_version; /**< Highest interface version set */ @@ -1218,6 +1238,8 @@ struct drm_device { int switch_power_state; atomic_t unplugged; /* device has been unplugged or gone away */ + struct apertures_struct *apertures; /**< fbmem apertures */ + bool apert_boot; /**< true if mapped as boot fb */ }; #define DRM_SWITCH_POWER_ON 0 @@ -1532,6 +1554,10 @@ extern void drm_master_put(struct drm_master **master); extern void drm_put_dev(struct drm_device *dev); extern int drm_put_minor(struct drm_minor **minor); extern void drm_unplug_dev(struct drm_device *dev); +extern void drm_unplug_dev_locked(struct drm_device *dev); +extern void drm_kick_out_firmware(struct apertures_struct *ap, bool boot); +extern bool drm_is_firmware_used(struct apertures_struct *ap); +extern struct list_head drm_devlist; extern unsigned int drm_debug; extern unsigned int drm_vblank_offdelay;