From e87eb57c69003a99b18d43abe58d5850ad19e188 Mon Sep 17 00:00:00 2001 From: Hoegeun Kwon Date: Thu, 5 Jan 2017 19:20:06 +0900 Subject: drm/exynos: mic: Add mode_set callback function Before applying the patch, used the of_get_videomode function to parse the display-timings in the panel which is the child driver of dsi in the devicetree. this is wrong. So removed the of_get_videomode and fixed to get videomode struct through mode_set callback function. Signed-off-by: Hoegeun Kwon Reviewed-by: Andrzej Hajda Signed-off-by: Inki Dae --- drivers/gpu/drm/exynos/exynos_drm_mic.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'drivers/gpu/drm/exynos') diff --git a/drivers/gpu/drm/exynos/exynos_drm_mic.c b/drivers/gpu/drm/exynos/exynos_drm_mic.c index a0def0be6d65..fed1a940ca5d 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_mic.c +++ b/drivers/gpu/drm/exynos/exynos_drm_mic.c @@ -286,13 +286,6 @@ static int parse_dt(struct exynos_mic *mic) } nodes[j++] = remote_node; - ret = of_get_videomode(remote_node, - &mic->vm, 0); - if (ret) { - DRM_ERROR("mic: failed to get videomode"); - goto exit; - } - break; default: DRM_ERROR("mic: Unknown endpoint from MIC"); @@ -329,6 +322,17 @@ already_disabled: mutex_unlock(&mic_mutex); } +static void mic_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct exynos_mic *mic = bridge->driver_private; + + mutex_lock(&mic_mutex); + drm_display_mode_to_videomode(mode, &mic->vm); + mutex_unlock(&mic_mutex); +} + static void mic_pre_enable(struct drm_bridge *bridge) { struct exynos_mic *mic = bridge->driver_private; @@ -377,6 +381,7 @@ static void mic_enable(struct drm_bridge *bridge) { } static const struct drm_bridge_funcs mic_bridge_funcs = { .disable = mic_disable, .post_disable = mic_post_disable, + .mode_set = mic_mode_set, .pre_enable = mic_pre_enable, .enable = mic_enable, }; -- cgit v1.2.3 From cc2b022518b0d3b040a9016dffccd35d47e8bd8f Mon Sep 17 00:00:00 2001 From: Hoegeun Kwon Date: Thu, 5 Jan 2017 19:20:07 +0900 Subject: drm/exynos: mic: Fix parse_dt function The OF graph is not necessary because the panel is a child of dsi. therefore, the parse_dt function of dsi does not need to check the remote_node connected to the panel. and the whole parse_dt function should be refactored later. Signed-off-by: Hoegeun Kwon Reviewed-by: Andrzej Hajda Signed-off-by: Inki Dae --- drivers/gpu/drm/exynos/exynos_drm_mic.c | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) (limited to 'drivers/gpu/drm/exynos') diff --git a/drivers/gpu/drm/exynos/exynos_drm_mic.c b/drivers/gpu/drm/exynos/exynos_drm_mic.c index fed1a940ca5d..cf9361ab0eb7 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_mic.c +++ b/drivers/gpu/drm/exynos/exynos_drm_mic.c @@ -269,28 +269,9 @@ static int parse_dt(struct exynos_mic *mic) } nodes[j++] = remote_node; - switch (i) { - case ENDPOINT_DECON_NODE: - /* decon node */ - if (of_get_child_by_name(remote_node, - "i80-if-timings")) - mic->i80_mode = 1; - - break; - case ENDPOINT_DSI_NODE: - /* panel node */ - remote_node = get_remote_node(remote_node, 1); - if (!remote_node) { - ret = -EPIPE; - goto exit; - } - nodes[j++] = remote_node; - - break; - default: - DRM_ERROR("mic: Unknown endpoint from MIC"); - break; - } + if (i == ENDPOINT_DECON_NODE && + of_get_child_by_name(remote_node, "i80-if-timings")) + mic->i80_mode = 1; } exit: -- cgit v1.2.3 From 328c057ca4def7040872032cd22df5347fa5e956 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Tue, 27 Dec 2016 11:49:23 +0100 Subject: drm/exynos: Stop using drm_framebuffer_unregister_private This is the deprecated function for when you embedded the framebuffer somewhere else (which breaks refcounting). But exynos is using drm_framebuffer_remove and a free-standing fb, so this is rendundant. Signed-off-by: Daniel Vetter Signed-off-by: Inki Dae --- drivers/gpu/drm/exynos/exynos_drm_fbdev.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/gpu/drm/exynos') diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c index d8808158d418..a7884bea42eb 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c @@ -270,10 +270,8 @@ static void exynos_drm_fbdev_destroy(struct drm_device *dev, /* release drm framebuffer and real buffer */ if (fb_helper->fb && fb_helper->fb->funcs) { fb = fb_helper->fb; - if (fb) { - drm_framebuffer_unregister_private(fb); + if (fb) drm_framebuffer_remove(fb); - } } drm_fb_helper_unregister_fbi(fb_helper); -- cgit v1.2.3 From 4e8ba5cc8863562cb948e26f614217a873f34bac Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 13 Jan 2017 09:30:00 +0100 Subject: drm/exynos: mic: Add runtime PM support This patch adds runtime support calls to notify device core when MIC device is really in use. Runtime PM is implemented by enabling and disabling clocks like in other Exynos DRM subdrivers. Adding runtime PM support is needed to let power domain with this device to be turned off when display is not used. Signed-off-by: Marek Szyprowski Signed-off-by: Inki Dae --- drivers/gpu/drm/exynos/exynos_drm_mic.c | 82 ++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 23 deletions(-) (limited to 'drivers/gpu/drm/exynos') diff --git a/drivers/gpu/drm/exynos/exynos_drm_mic.c b/drivers/gpu/drm/exynos/exynos_drm_mic.c index cf9361ab0eb7..2ef43d403eaa 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_mic.c +++ b/drivers/gpu/drm/exynos/exynos_drm_mic.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -286,7 +287,6 @@ static void mic_disable(struct drm_bridge *bridge) { } static void mic_post_disable(struct drm_bridge *bridge) { struct exynos_mic *mic = bridge->driver_private; - int i; mutex_lock(&mic_mutex); if (!mic->enabled) @@ -294,9 +294,7 @@ static void mic_post_disable(struct drm_bridge *bridge) mic_set_path(mic, 0); - for (i = NUM_CLKS - 1; i > -1; i--) - clk_disable_unprepare(mic->clks[i]); - + pm_runtime_put(mic->dev); mic->enabled = 0; already_disabled: @@ -317,27 +315,22 @@ static void mic_mode_set(struct drm_bridge *bridge, static void mic_pre_enable(struct drm_bridge *bridge) { struct exynos_mic *mic = bridge->driver_private; - int ret, i; + int ret; mutex_lock(&mic_mutex); if (mic->enabled) - goto already_enabled; + goto unlock; - for (i = 0; i < NUM_CLKS; i++) { - ret = clk_prepare_enable(mic->clks[i]); - if (ret < 0) { - DRM_ERROR("Failed to enable clock (%s)\n", - clk_names[i]); - goto turn_off_clks; - } - } + ret = pm_runtime_get_sync(mic->dev); + if (ret < 0) + goto unlock; mic_set_path(mic, 1); ret = mic_sw_reset(mic); if (ret) { DRM_ERROR("Failed to reset\n"); - goto turn_off_clks; + goto turn_off; } if (!mic->i80_mode) @@ -350,10 +343,9 @@ static void mic_pre_enable(struct drm_bridge *bridge) return; -turn_off_clks: - while (--i > -1) - clk_disable_unprepare(mic->clks[i]); -already_enabled: +turn_off: + pm_runtime_put(mic->dev); +unlock: mutex_unlock(&mic_mutex); } @@ -387,14 +379,12 @@ static void exynos_mic_unbind(struct device *dev, struct device *master, void *data) { struct exynos_mic *mic = dev_get_drvdata(dev); - int i; mutex_lock(&mic_mutex); if (!mic->enabled) goto already_disabled; - for (i = NUM_CLKS - 1; i > -1; i--) - clk_disable_unprepare(mic->clks[i]); + pm_runtime_put(mic->dev); already_disabled: mutex_unlock(&mic_mutex); @@ -407,6 +397,41 @@ static const struct component_ops exynos_mic_component_ops = { .unbind = exynos_mic_unbind, }; +#ifdef CONFIG_PM +static int exynos_mic_suspend(struct device *dev) +{ + struct exynos_mic *mic = dev_get_drvdata(dev); + int i; + + for (i = NUM_CLKS - 1; i > -1; i--) + clk_disable_unprepare(mic->clks[i]); + + return 0; +} + +static int exynos_mic_resume(struct device *dev) +{ + struct exynos_mic *mic = dev_get_drvdata(dev); + int ret, i; + + for (i = 0; i < NUM_CLKS; i++) { + ret = clk_prepare_enable(mic->clks[i]); + if (ret < 0) { + DRM_ERROR("Failed to enable clock (%s)\n", + clk_names[i]); + while (--i > -1) + clk_disable_unprepare(mic->clks[i]); + return ret; + } + } + return 0; +} +#endif + +static const struct dev_pm_ops exynos_mic_pm_ops = { + SET_RUNTIME_PM_OPS(exynos_mic_suspend, exynos_mic_resume, NULL) +}; + static int exynos_mic_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -459,9 +484,18 @@ static int exynos_mic_probe(struct platform_device *pdev) platform_set_drvdata(pdev, mic); + pm_runtime_enable(dev); + + ret = component_add(dev, &exynos_mic_component_ops); + if (ret) + goto err_pm; + DRM_DEBUG_KMS("MIC has been probed\n"); - return component_add(dev, &exynos_mic_component_ops); + return 0; + +err_pm: + pm_runtime_disable(dev); err: return ret; } @@ -469,6 +503,7 @@ err: static int exynos_mic_remove(struct platform_device *pdev) { component_del(&pdev->dev, &exynos_mic_component_ops); + pm_runtime_disable(&pdev->dev); return 0; } @@ -483,6 +518,7 @@ struct platform_driver mic_driver = { .remove = exynos_mic_remove, .driver = { .name = "exynos-mic", + .pm = &exynos_mic_pm_ops, .owner = THIS_MODULE, .of_match_table = exynos_mic_of_match, }, -- cgit v1.2.3 From 9db41d43236411d38778eda2b5987f9b65498c48 Mon Sep 17 00:00:00 2001 From: Inki Dae Date: Tue, 17 Jan 2017 19:47:26 +0900 Subject: drm/exynos: remove unnecessary codes This patch removes exynos_drm_crtc_cancel_page_flip call when drm is closed because at that time, events will be released by drm_events_release function. Changelog v1: - remove exynos_drm_crtc_cancel_page_flip function also because this funtion isn't used anymore. Signed-off-by: Inki Dae Reviewed-by: Andrzej Hajda --- drivers/gpu/drm/exynos/exynos_drm_crtc.c | 20 -------------------- drivers/gpu/drm/exynos/exynos_drm_crtc.h | 4 ---- drivers/gpu/drm/exynos/exynos_drm_drv.c | 5 ----- 3 files changed, 29 deletions(-) (limited to 'drivers/gpu/drm/exynos') diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index 309c8ee52524..8dd59b9e5694 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -203,23 +203,3 @@ void exynos_drm_crtc_te_handler(struct drm_crtc *crtc) if (exynos_crtc->ops->te_handler) exynos_crtc->ops->te_handler(exynos_crtc); } - -void exynos_drm_crtc_cancel_page_flip(struct drm_crtc *crtc, - struct drm_file *file) -{ - struct drm_pending_vblank_event *e; - unsigned long flags; - - spin_lock_irqsave(&crtc->dev->event_lock, flags); - - e = crtc->state->event; - if (e && e->base.file_priv == file) - crtc->state->event = NULL; - else - e = NULL; - - spin_unlock_irqrestore(&crtc->dev->event_lock, flags); - - if (e) - drm_event_cancel_free(crtc->dev, &e->base); -} diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.h b/drivers/gpu/drm/exynos/exynos_drm_crtc.h index cfdcf3e4eb1b..6a581a8af465 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.h +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.h @@ -40,8 +40,4 @@ int exynos_drm_crtc_get_pipe_from_type(struct drm_device *drm_dev, */ void exynos_drm_crtc_te_handler(struct drm_crtc *crtc); -/* This function cancels a page flip request. */ -void exynos_drm_crtc_cancel_page_flip(struct drm_crtc *crtc, - struct drm_file *file); - #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 3ec053542e93..34e13422852e 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -307,12 +307,7 @@ err_file_priv_free: static void exynos_drm_preclose(struct drm_device *dev, struct drm_file *file) { - struct drm_crtc *crtc; - exynos_drm_subdrv_close(dev, file); - - list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) - exynos_drm_crtc_cancel_page_flip(crtc, file); } static void exynos_drm_postclose(struct drm_device *dev, struct drm_file *file) -- cgit v1.2.3 From 41cbf0fdaa2886241f92f014ae1fd12bd5689af4 Mon Sep 17 00:00:00 2001 From: Inki Dae Date: Fri, 20 Jan 2017 12:51:41 +0900 Subject: drm/exynos: use atomic helper commit This patch replaces specific atomic commit function with atomic helper commit one. For this, it removes existing atomic commit function and relevant code specific to Exynos DRM and makes atomic helper commit to be used instead. Below are changes for the use of atomic helper commit: - add atomic_commit_tail callback specific to Exynos DRM . default implemention of atomic helper doesn't mesh well with runtime PM so the device driver which supports runtime PM should call drm_atomic_helper_commit_modeset_enables function prior to drm_atomic_helper_commit_planes function call. atomic_commit_tail callback implements this call ordering. - allow plane commit only in case that CRTC device is enabled. . for this, it calls atomic_helper_commit_planes function with DRM_PLANE_COMMIT_ACTIVE_ONLY flag in atomic_commit_tail callback. Signed-off-by: Inki Dae Reviewed-by: Gustavo Padovan --- drivers/gpu/drm/exynos/exynos_drm_crtc.c | 8 +++ drivers/gpu/drm/exynos/exynos_drm_drv.c | 109 ------------------------------- drivers/gpu/drm/exynos/exynos_drm_fb.c | 32 ++++++++- 3 files changed, 39 insertions(+), 110 deletions(-) (limited to 'drivers/gpu/drm/exynos') diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index 8dd59b9e5694..5367b6664fe3 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -39,6 +39,14 @@ static void exynos_drm_crtc_disable(struct drm_crtc *crtc) if (exynos_crtc->ops->disable) exynos_crtc->ops->disable(exynos_crtc); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } } static void diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 34e13422852e..035d02ecffcd 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -38,56 +38,6 @@ #define DRIVER_MAJOR 1 #define DRIVER_MINOR 0 -struct exynos_atomic_commit { - struct work_struct work; - struct drm_device *dev; - struct drm_atomic_state *state; - u32 crtcs; -}; - -static void exynos_atomic_commit_complete(struct exynos_atomic_commit *commit) -{ - struct drm_device *dev = commit->dev; - struct exynos_drm_private *priv = dev->dev_private; - struct drm_atomic_state *state = commit->state; - - drm_atomic_helper_commit_modeset_disables(dev, state); - - drm_atomic_helper_commit_modeset_enables(dev, state); - - /* - * Exynos can't update planes with CRTCs and encoders disabled, - * its updates routines, specially for FIMD, requires the clocks - * to be enabled. So it is necessary to handle the modeset operations - * *before* the commit_planes() step, this way it will always - * have the relevant clocks enabled to perform the update. - */ - - drm_atomic_helper_commit_planes(dev, state, 0); - - drm_atomic_helper_wait_for_vblanks(dev, state); - - drm_atomic_helper_cleanup_planes(dev, state); - - drm_atomic_state_put(state); - - spin_lock(&priv->lock); - priv->pending &= ~commit->crtcs; - spin_unlock(&priv->lock); - - wake_up_all(&priv->wait); - - kfree(commit); -} - -static void exynos_drm_atomic_work(struct work_struct *work) -{ - struct exynos_atomic_commit *commit = container_of(work, - struct exynos_atomic_commit, work); - - exynos_atomic_commit_complete(commit); -} - static struct device *exynos_drm_get_dma_device(void); static int exynos_drm_load(struct drm_device *dev, unsigned long flags) @@ -202,65 +152,6 @@ static void exynos_drm_unload(struct drm_device *dev) dev->dev_private = NULL; } -static int commit_is_pending(struct exynos_drm_private *priv, u32 crtcs) -{ - bool pending; - - spin_lock(&priv->lock); - pending = priv->pending & crtcs; - spin_unlock(&priv->lock); - - return pending; -} - -int exynos_atomic_commit(struct drm_device *dev, struct drm_atomic_state *state, - bool nonblock) -{ - struct exynos_drm_private *priv = dev->dev_private; - struct exynos_atomic_commit *commit; - struct drm_crtc *crtc; - struct drm_crtc_state *crtc_state; - int i, ret; - - commit = kzalloc(sizeof(*commit), GFP_KERNEL); - if (!commit) - return -ENOMEM; - - ret = drm_atomic_helper_prepare_planes(dev, state); - if (ret) { - kfree(commit); - return ret; - } - - /* This is the point of no return */ - - INIT_WORK(&commit->work, exynos_drm_atomic_work); - commit->dev = dev; - commit->state = state; - - /* Wait until all affected CRTCs have completed previous commits and - * mark them as pending. - */ - for_each_crtc_in_state(state, crtc, crtc_state, i) - commit->crtcs |= drm_crtc_mask(crtc); - - wait_event(priv->wait, !commit_is_pending(priv, commit->crtcs)); - - spin_lock(&priv->lock); - priv->pending |= commit->crtcs; - spin_unlock(&priv->lock); - - drm_atomic_helper_swap_state(state, true); - - drm_atomic_state_get(state); - if (nonblock) - schedule_work(&commit->work); - else - exynos_atomic_commit_complete(commit); - - return 0; -} - int exynos_atomic_check(struct drm_device *dev, struct drm_atomic_state *state) { diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.c b/drivers/gpu/drm/exynos/exynos_drm_fb.c index 68d414227533..c77a5aced81a 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fb.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fb.c @@ -187,11 +187,40 @@ dma_addr_t exynos_drm_fb_dma_addr(struct drm_framebuffer *fb, int index) return exynos_fb->dma_addr[index]; } +static void exynos_drm_atomic_commit_tail(struct drm_atomic_state *state) +{ + struct drm_device *dev = state->dev; + + drm_atomic_helper_commit_modeset_disables(dev, state); + + drm_atomic_helper_commit_modeset_enables(dev, state); + + /* + * Exynos can't update planes with CRTCs and encoders disabled, + * its updates routines, specially for FIMD, requires the clocks + * to be enabled. So it is necessary to handle the modeset operations + * *before* the commit_planes() step, this way it will always + * have the relevant clocks enabled to perform the update. + */ + drm_atomic_helper_commit_planes(dev, state, + DRM_PLANE_COMMIT_ACTIVE_ONLY); + + drm_atomic_helper_commit_hw_done(state); + + drm_atomic_helper_wait_for_vblanks(dev, state); + + drm_atomic_helper_cleanup_planes(dev, state); +} + +static struct drm_mode_config_helper_funcs exynos_drm_mode_config_helpers = { + .atomic_commit_tail = exynos_drm_atomic_commit_tail, +}; + static const struct drm_mode_config_funcs exynos_drm_mode_config_funcs = { .fb_create = exynos_user_fb_create, .output_poll_changed = exynos_drm_output_poll_changed, .atomic_check = exynos_atomic_check, - .atomic_commit = exynos_atomic_commit, + .atomic_commit = drm_atomic_helper_commit, }; void exynos_drm_mode_config_init(struct drm_device *dev) @@ -208,4 +237,5 @@ void exynos_drm_mode_config_init(struct drm_device *dev) dev->mode_config.max_height = 4096; dev->mode_config.funcs = &exynos_drm_mode_config_funcs; + dev->mode_config.helper_private = &exynos_drm_mode_config_helpers; } -- cgit v1.2.3 From 8646dcb8a0def1c1cd4c855e08b17abce0cdc5d1 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 20 Jan 2017 17:54:32 +0100 Subject: drm/exynos: fix a timeout loop We were trying to print an error message if we timed out here, but the loop actually ends with "tries" set to UINT_MAX and not zero. Fix this by changing from tries-- to --tries. A for loop would actually be the most natural way to do this. My fix means we only loop 99 times instead of 100 but that's probably ok. Fixes: a696394c5224 ('drm/exynos: mixer: simplify loop in vp_win_reset()') Signed-off-by: Dan Carpenter Reviewed-by: Tobias Jakobi Signed-off-by: Inki Dae --- drivers/gpu/drm/exynos/exynos_mixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpu/drm/exynos') diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index a106046e0c93..72143ac10525 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -701,7 +701,7 @@ static void vp_win_reset(struct mixer_context *ctx) unsigned int tries = 100; vp_reg_write(res, VP_SRESET, VP_SRESET_PROCESSING); - while (tries--) { + while (--tries) { /* waiting until VP_SRESET_PROCESSING is 0 */ if (~vp_reg_read(res, VP_SRESET) & VP_SRESET_PROCESSING) break; -- cgit v1.2.3 From e41456bfc811f12b5dcda6f2d6849bdff68f6c0a Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Mon, 23 Jan 2017 18:13:54 +0900 Subject: drm/exynos: g2d: prevent integer overflow in The size computations done in the ioctl function use an integer. If userspace submits a request with req->cmd_nr or req->cmd_buf_nr set to INT_MAX, the integer computations overflow later, leading to potential (kernel) memory corruption. Prevent this issue by enforcing a limit on the number of submitted commands, so that we have enough headroom later for the size computations. Note that this change has no impact on the currently available users in userspace, like e.g. libdrm/exynos. While at it, also make a comment about the size computation more detailed. Signed-off-by: Joonyoung Shim Signed-off-by: Tobias Jakobi Signed-off-by: Inki Dae --- drivers/gpu/drm/exynos/exynos_drm_g2d.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'drivers/gpu/drm/exynos') diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.c b/drivers/gpu/drm/exynos/exynos_drm_g2d.c index fbd13fabdf2d..603d8425cca6 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_g2d.c +++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.c @@ -1193,6 +1193,17 @@ int exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data, if (!node) return -ENOMEM; + /* + * To avoid an integer overflow for the later size computations, we + * enforce a maximum number of submitted commands here. This limit is + * sufficient for all conceivable usage cases of the G2D. + */ + if (req->cmd_nr > G2D_CMDLIST_DATA_NUM || + req->cmd_buf_nr > G2D_CMDLIST_DATA_NUM) { + dev_err(dev, "number of submitted G2D commands exceeds limit\n"); + return -EINVAL; + } + node->event = NULL; if (req->event_type != G2D_EVENT_NOT) { @@ -1250,7 +1261,11 @@ int exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data, cmdlist->data[cmdlist->last++] = G2D_INTEN_ACF; } - /* Check size of cmdlist: last 2 is about G2D_BITBLT_START */ + /* + * Check the size of cmdlist. The 2 that is added last comes from + * the implicit G2D_BITBLT_START that is appended once we have + * checked all the submitted commands. + */ size = cmdlist->last + req->cmd_nr * 2 + req->cmd_buf_nr * 2 + 2; if (size > G2D_CMDLIST_DATA_NUM) { dev_err(dev, "cmdlist size is too big\n"); -- cgit v1.2.3