[PATCH 2/5] drm: Add initial dnyamic power off feature
Daniel Vetter
daniel at ffwll.ch
Mon Sep 10 00:18:55 PDT 2012
On Mon, Sep 10, 2012 at 02:31:52PM +1000, Dave Airlie wrote:
> From: Dave Airlie <airlied at redhat.com>
>
> For secondary GPUs in laptops, i.e. optimus or powerxpress, we have
> methods for powering down the GPU completely. This adds support
> to the drm core for powering back up the GPU on any access from
> ioctls or sysfs interfaces, and fires a 5s timer to test if
> we can power the GPU off.
>
> This is just an initial implementation to get discussions started!
>
> Signed-off-by: Dave Airlie <airlied at redhat.com>
> ---
> drivers/gpu/drm/drm_drv.c | 68 +++++++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/drm_fb_helper.c | 2 +-
> drivers/gpu/drm/drm_fops.c | 6 +++-
> drivers/gpu/drm/drm_stub.c | 1 +
> drivers/gpu/drm/drm_sysfs.c | 4 +++
> include/drm/drmP.h | 9 ++++++
> 6 files changed, 88 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
> index 9238de4..9fae62a 100644
> --- a/drivers/gpu/drm/drm_drv.c
> +++ b/drivers/gpu/drm/drm_drv.c
> @@ -383,12 +383,17 @@ long drm_ioctl(struct file *filp,
> char stack_kdata[128];
> char *kdata = NULL;
> unsigned int usize, asize;
> + int ret;
>
> dev = file_priv->minor->dev;
>
> if (drm_device_is_unplugged(dev))
> return -ENODEV;
>
> + ret = drm_dynamic_power_wakeup(dev, __func__);
> + if (ret)
> + return ret;
> +
> atomic_inc(&dev->ioctl_count);
> atomic_inc(&dev->counts[_DRM_STAT_IOCTLS]);
> ++file_priv->ioctl_count;
> @@ -494,3 +499,66 @@ struct drm_local_map *drm_getsarea(struct drm_device *dev)
> return NULL;
> }
> EXPORT_SYMBOL(drm_getsarea);
> +
> +#define POWER_OFF_PERIOD (5*HZ)
> +
> +static void drm_dynamic_enable_poll(struct drm_device *dev)
> +{
> + queue_delayed_work(system_nrt_wq, &dev->dynamic_power_poll, POWER_OFF_PERIOD);
> +}
> +
> +static void drm_power_poll_execute(struct work_struct *work)
> +{
> + struct delayed_work *delayed_work = to_delayed_work(work);
> + struct drm_device *dev = container_of(delayed_work, struct drm_device, dynamic_power_poll);
> + bool ret;
> +
> + /* ask driver if okay to power off */
My midlayer-smell-o-meter just cranked up to 11 when reading this comment
;-)
I'd have expected:
- Drivers to check the power state and enable the gpu if it's off in their
cs ioctl (instead of the brute-force every ioctl there is approach in
the drm core)
- Launch a delayed work item to cut the power again once they notice that
the gpu is idle (dunno how radeon/nouveau work exactly, but i915 has
this nice retire_requests work item which does a few similar things for
power management, like lvds downclocking)
- Same thing for any other kind of usage, like e.g. kms: Drivers can wrap the
crtc helper set_mode to ensure the gpu is on and also check for any
enabled outputs before launching the delayed power off switch. Same
applies to any sysfs/debugfs files (although in the case of i915.ko,
many of these don't need the hw to be on).
I guess you could add a small vga_switcheroo_dynamic_power_state struct or
something with a few helpers to do that to extract some duplicated code
from drivers. But tbh managing a piece of state lazily with a
timer/delayed work item is a common code pattern, so I don't think even
that little bit of code sharing is worth it.
Cheers, Daniel
PS: I've read a bit around in the switcheroo code and I think the
switcheroo ->can_switch callback is actually worse ... since the drm
drivers just check the open_count (which is hilariously racy in itself,
too) and there's no locking to ensure that stays the same between the
->can_switch check and the actual ->set_state calls ...
/me needs morning coffee to think this through
> + ret = dev->driver->dynamic_off_check(dev);
> + if (ret == false)
> + goto out_requeue;
> +
> + ret = dev->driver->dynamic_set_state(dev, DRM_SWITCH_POWER_DYNAMIC_OFF);
> + DRM_INFO("powering down\n");
> + return;
> +out_requeue:
> + queue_delayed_work(system_nrt_wq, delayed_work, POWER_OFF_PERIOD);
> +}
> +
> +int drm_dynamic_power_wakeup(struct drm_device *dev, const char *reason)
> +{
> + int ret;
> +
> + if (!dev->driver->dynamic_off_check)
> + return 0;
> +
> + cancel_delayed_work_sync(&dev->dynamic_power_poll);
> +
> + ret = mutex_lock_interruptible(&dev->dynamic_power_lock);
> + if (ret) {
> + drm_dynamic_enable_poll(dev);
> + return ret;
> + }
> +
> + if (dev->switch_power_state != DRM_SWITCH_POWER_DYNAMIC_OFF) {
> + mutex_unlock(&dev->dynamic_power_lock);
> + drm_dynamic_enable_poll(dev);
> + return 0;
> + }
> +
> + DRM_INFO("waking up GPU for %s\n", reason);
> + ret = dev->driver->dynamic_set_state(dev, DRM_SWITCH_POWER_ON);
> + mutex_unlock(&dev->dynamic_power_lock);
> +
> + drm_dynamic_enable_poll(dev);
> + return 0;
> +}
> +EXPORT_SYMBOL(drm_dynamic_power_wakeup);
> +
> +void drm_dynamic_power_init(struct drm_device *dev)
> +{
> + INIT_DELAYED_WORK(&dev->dynamic_power_poll, drm_power_poll_execute);
> + if (dev->driver->dynamic_off_check)
> + drm_dynamic_enable_poll(dev);
> +}
> +EXPORT_SYMBOL(drm_dynamic_power_init);
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index f546d1e..9a2c56b 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -245,7 +245,7 @@ bool drm_fb_helper_force_kernel_mode(void)
> return false;
>
> list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
> - if (helper->dev->switch_power_state == DRM_SWITCH_POWER_OFF)
> + if (helper->dev->switch_power_state == DRM_SWITCH_POWER_OFF || helper->dev->switch_power_state == DRM_SWITCH_POWER_DYNAMIC_OFF)
> continue;
>
> ret = drm_fb_helper_restore_fbdev_mode(helper);
> diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c
> index 5062eec..285e53f 100644
> --- a/drivers/gpu/drm/drm_fops.c
> +++ b/drivers/gpu/drm/drm_fops.c
> @@ -239,9 +239,13 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
> return -EBUSY; /* No exclusive opens */
> if (!drm_cpu_valid())
> return -EINVAL;
> - if (dev->switch_power_state != DRM_SWITCH_POWER_ON)
> + if (dev->switch_power_state != DRM_SWITCH_POWER_ON && dev->switch_power_state != DRM_SWITCH_POWER_DYNAMIC_OFF)
> return -EINVAL;
>
> + ret = drm_dynamic_power_wakeup(dev, __func__);
> + if (ret)
> + return ret;
> +
> DRM_DEBUG("pid = %d, minor = %d\n", task_pid_nr(current), minor_id);
>
> priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c
> index 21bcd4a..0e56a40 100644
> --- a/drivers/gpu/drm/drm_stub.c
> +++ b/drivers/gpu/drm/drm_stub.c
> @@ -273,6 +273,7 @@ int drm_fill_in_dev(struct drm_device *dev,
> spin_lock_init(&dev->event_lock);
> mutex_init(&dev->struct_mutex);
> mutex_init(&dev->ctxlist_mutex);
> + mutex_init(&dev->dynamic_power_lock);
>
> if (drm_ht_create(&dev->map_hash, 12)) {
> return -ENOMEM;
> diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
> index 45ac8d6..850c210 100644
> --- a/drivers/gpu/drm/drm_sysfs.c
> +++ b/drivers/gpu/drm/drm_sysfs.c
> @@ -162,6 +162,10 @@ static ssize_t status_show(struct device *device,
> enum drm_connector_status status;
> int ret;
>
> + ret = drm_dynamic_power_wakeup(connector->dev, __func__);
> + if (ret)
> + return ret;
> +
> ret = mutex_lock_interruptible(&connector->dev->mode_config.mutex);
> if (ret)
> return ret;
> diff --git a/include/drm/drmP.h b/include/drm/drmP.h
> index d6b67bb..65154b0 100644
> --- a/include/drm/drmP.h
> +++ b/include/drm/drmP.h
> @@ -933,6 +933,9 @@ struct drm_driver {
> struct drm_device *dev,
> uint32_t handle);
>
> + bool (*dynamic_off_check)(struct drm_device *dev);
> + int (*dynamic_set_state)(struct drm_device *dev, int state);
> +
> /* Driver private ops for this object */
> const struct vm_operations_struct *gem_vm_ops;
>
> @@ -1197,11 +1200,15 @@ struct drm_device {
> int switch_power_state;
>
> atomic_t unplugged; /* device has been unplugged or gone away */
> +
> + struct delayed_work dynamic_power_poll;
> + struct mutex dynamic_power_lock;
> };
>
> #define DRM_SWITCH_POWER_ON 0
> #define DRM_SWITCH_POWER_OFF 1
> #define DRM_SWITCH_POWER_CHANGING 2
> +#define DRM_SWITCH_POWER_DYNAMIC_OFF 3
>
> static __inline__ int drm_core_check_feature(struct drm_device *dev,
> int feature)
> @@ -1770,5 +1777,7 @@ static __inline__ bool drm_can_sleep(void)
> return true;
> }
>
> +void drm_dynamic_power_init(struct drm_device *dev);
> +int drm_dynamic_power_wakeup(struct drm_device *dev, const char *reason);
> #endif /* __KERNEL__ */
> #endif
> --
> 1.7.12
>
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
More information about the dri-devel
mailing list