drm/msm: block incoming update on pending updates
We can't have multiple updates pending on a given CRTC, and we don't want a sync update to race w/ an async update that preceeded it. So keep track of which CRTCs have updates in flight, and block later updates that would conflict. Signed-off-by: Rob Clark <robdclark@gmail.com>
This commit is contained in:
@ -331,17 +331,8 @@ static int mdp4_crtc_atomic_check(struct drm_crtc *crtc,
|
|||||||
struct drm_crtc_state *state)
|
struct drm_crtc_state *state)
|
||||||
{
|
{
|
||||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||||
struct drm_device *dev = crtc->dev;
|
|
||||||
|
|
||||||
DBG("%s: check", mdp4_crtc->name);
|
DBG("%s: check", mdp4_crtc->name);
|
||||||
|
|
||||||
if (mdp4_crtc->event) {
|
|
||||||
dev_err(dev->dev, "already pending flip!\n");
|
|
||||||
return -EBUSY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO anything else to check?
|
// TODO anything else to check?
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,7 +348,7 @@ static void mdp4_crtc_atomic_flush(struct drm_crtc *crtc)
|
|||||||
struct drm_device *dev = crtc->dev;
|
struct drm_device *dev = crtc->dev;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
DBG("%s: flush", mdp4_crtc->name);
|
DBG("%s: event: %p", mdp4_crtc->name, crtc->state->event);
|
||||||
|
|
||||||
WARN_ON(mdp4_crtc->event);
|
WARN_ON(mdp4_crtc->event);
|
||||||
|
|
||||||
|
@ -303,11 +303,6 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
|
|||||||
|
|
||||||
DBG("%s: check", mdp5_crtc->name);
|
DBG("%s: check", mdp5_crtc->name);
|
||||||
|
|
||||||
if (mdp5_crtc->event) {
|
|
||||||
dev_err(dev->dev, "already pending flip!\n");
|
|
||||||
return -EBUSY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* request a free CTL, if none is already allocated for this CRTC */
|
/* request a free CTL, if none is already allocated for this CRTC */
|
||||||
if (state->enable && !mdp5_crtc->ctl) {
|
if (state->enable && !mdp5_crtc->ctl) {
|
||||||
mdp5_crtc->ctl = mdp5_ctlm_request(mdp5_kms->ctlm, crtc);
|
mdp5_crtc->ctl = mdp5_ctlm_request(mdp5_kms->ctlm, crtc);
|
||||||
@ -364,7 +359,7 @@ static void mdp5_crtc_atomic_flush(struct drm_crtc *crtc)
|
|||||||
struct drm_device *dev = crtc->dev;
|
struct drm_device *dev = crtc->dev;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
DBG("%s: flush", mdp5_crtc->name);
|
DBG("%s: event: %p", mdp5_crtc->name, crtc->state->event);
|
||||||
|
|
||||||
WARN_ON(mdp5_crtc->event);
|
WARN_ON(mdp5_crtc->event);
|
||||||
|
|
||||||
|
@ -23,10 +23,41 @@ struct msm_commit {
|
|||||||
struct drm_atomic_state *state;
|
struct drm_atomic_state *state;
|
||||||
uint32_t fence;
|
uint32_t fence;
|
||||||
struct msm_fence_cb fence_cb;
|
struct msm_fence_cb fence_cb;
|
||||||
|
uint32_t crtc_mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void fence_cb(struct msm_fence_cb *cb);
|
static void fence_cb(struct msm_fence_cb *cb);
|
||||||
|
|
||||||
|
/* block until specified crtcs are no longer pending update, and
|
||||||
|
* atomically mark them as pending update
|
||||||
|
*/
|
||||||
|
static int start_atomic(struct msm_drm_private *priv, uint32_t crtc_mask)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
spin_lock(&priv->pending_crtcs_event.lock);
|
||||||
|
ret = wait_event_interruptible_locked(priv->pending_crtcs_event,
|
||||||
|
!(priv->pending_crtcs & crtc_mask));
|
||||||
|
if (ret == 0) {
|
||||||
|
DBG("start: %08x", crtc_mask);
|
||||||
|
priv->pending_crtcs |= crtc_mask;
|
||||||
|
}
|
||||||
|
spin_unlock(&priv->pending_crtcs_event.lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clear specified crtcs (no longer pending update)
|
||||||
|
*/
|
||||||
|
static void end_atomic(struct msm_drm_private *priv, uint32_t crtc_mask)
|
||||||
|
{
|
||||||
|
spin_lock(&priv->pending_crtcs_event.lock);
|
||||||
|
DBG("end: %08x", crtc_mask);
|
||||||
|
priv->pending_crtcs &= ~crtc_mask;
|
||||||
|
wake_up_all_locked(&priv->pending_crtcs_event);
|
||||||
|
spin_unlock(&priv->pending_crtcs_event.lock);
|
||||||
|
}
|
||||||
|
|
||||||
static struct msm_commit *new_commit(struct drm_atomic_state *state)
|
static struct msm_commit *new_commit(struct drm_atomic_state *state)
|
||||||
{
|
{
|
||||||
struct msm_commit *c = kzalloc(sizeof(*c), GFP_KERNEL);
|
struct msm_commit *c = kzalloc(sizeof(*c), GFP_KERNEL);
|
||||||
@ -58,12 +89,27 @@ static void complete_commit(struct msm_commit *c)
|
|||||||
|
|
||||||
drm_atomic_helper_commit_post_planes(dev, state);
|
drm_atomic_helper_commit_post_planes(dev, state);
|
||||||
|
|
||||||
|
/* NOTE: _wait_for_vblanks() only waits for vblank on
|
||||||
|
* enabled CRTCs. So we end up faulting when disabling
|
||||||
|
* due to (potentially) unref'ing the outgoing fb's
|
||||||
|
* before the vblank when the disable has latched.
|
||||||
|
*
|
||||||
|
* But if it did wait on disabled (or newly disabled)
|
||||||
|
* CRTCs, that would be racy (ie. we could have missed
|
||||||
|
* the irq. We need some way to poll for pipe shut
|
||||||
|
* down. Or just live with occasionally hitting the
|
||||||
|
* timeout in the CRTC disable path (which really should
|
||||||
|
* not be critical path)
|
||||||
|
*/
|
||||||
|
|
||||||
drm_atomic_helper_wait_for_vblanks(dev, state);
|
drm_atomic_helper_wait_for_vblanks(dev, state);
|
||||||
|
|
||||||
drm_atomic_helper_cleanup_planes(dev, state);
|
drm_atomic_helper_cleanup_planes(dev, state);
|
||||||
|
|
||||||
drm_atomic_state_free(state);
|
drm_atomic_state_free(state);
|
||||||
|
|
||||||
|
end_atomic(dev->dev_private, c->crtc_mask);
|
||||||
|
|
||||||
kfree(c);
|
kfree(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +143,9 @@ static void add_fb(struct msm_commit *c, struct drm_framebuffer *fb)
|
|||||||
int msm_atomic_commit(struct drm_device *dev,
|
int msm_atomic_commit(struct drm_device *dev,
|
||||||
struct drm_atomic_state *state, bool async)
|
struct drm_atomic_state *state, bool async)
|
||||||
{
|
{
|
||||||
struct msm_commit *c;
|
|
||||||
int nplanes = dev->mode_config.num_total_plane;
|
int nplanes = dev->mode_config.num_total_plane;
|
||||||
|
int ncrtcs = dev->mode_config.num_crtc;
|
||||||
|
struct msm_commit *c;
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
|
||||||
ret = drm_atomic_helper_prepare_planes(dev, state);
|
ret = drm_atomic_helper_prepare_planes(dev, state);
|
||||||
@ -106,6 +153,18 @@ int msm_atomic_commit(struct drm_device *dev,
|
|||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
c = new_commit(state);
|
c = new_commit(state);
|
||||||
|
if (!c)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Figure out what crtcs we have:
|
||||||
|
*/
|
||||||
|
for (i = 0; i < ncrtcs; i++) {
|
||||||
|
struct drm_crtc *crtc = state->crtcs[i];
|
||||||
|
if (!crtc)
|
||||||
|
continue;
|
||||||
|
c->crtc_mask |= (1 << drm_crtc_index(crtc));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Figure out what fence to wait for:
|
* Figure out what fence to wait for:
|
||||||
@ -121,6 +180,14 @@ int msm_atomic_commit(struct drm_device *dev,
|
|||||||
add_fb(c, new_state->fb);
|
add_fb(c, new_state->fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for pending updates on any of the same crtc's and then
|
||||||
|
* mark our set of crtc's as busy:
|
||||||
|
*/
|
||||||
|
ret = start_atomic(dev->dev_private, c->crtc_mask);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is the point of no return - everything below never fails except
|
* This is the point of no return - everything below never fails except
|
||||||
* when the hw goes bonghits. Which means we can commit the new state on
|
* when the hw goes bonghits. Which means we can commit the new state on
|
||||||
|
@ -193,6 +193,7 @@ static int msm_load(struct drm_device *dev, unsigned long flags)
|
|||||||
|
|
||||||
priv->wq = alloc_ordered_workqueue("msm", 0);
|
priv->wq = alloc_ordered_workqueue("msm", 0);
|
||||||
init_waitqueue_head(&priv->fence_event);
|
init_waitqueue_head(&priv->fence_event);
|
||||||
|
init_waitqueue_head(&priv->pending_crtcs_event);
|
||||||
|
|
||||||
INIT_LIST_HEAD(&priv->inactive_list);
|
INIT_LIST_HEAD(&priv->inactive_list);
|
||||||
INIT_LIST_HEAD(&priv->fence_cbs);
|
INIT_LIST_HEAD(&priv->fence_cbs);
|
||||||
|
@ -96,6 +96,10 @@ struct msm_drm_private {
|
|||||||
/* callbacks deferred until bo is inactive: */
|
/* callbacks deferred until bo is inactive: */
|
||||||
struct list_head fence_cbs;
|
struct list_head fence_cbs;
|
||||||
|
|
||||||
|
/* crtcs pending async atomic updates: */
|
||||||
|
uint32_t pending_crtcs;
|
||||||
|
wait_queue_head_t pending_crtcs_event;
|
||||||
|
|
||||||
/* registered MMUs: */
|
/* registered MMUs: */
|
||||||
unsigned int num_mmus;
|
unsigned int num_mmus;
|
||||||
struct msm_mmu *mmus[NUM_DOMAINS];
|
struct msm_mmu *mmus[NUM_DOMAINS];
|
||||||
|
Reference in New Issue
Block a user