Skip to content

Commit 4dfd648

Browse files
vsyrjaladanvet
authored andcommitted
drm: Use vblank timestamps to guesstimate how many vblanks were missed
When lacking am accurate hardware frame counter, we can fall back to using the vblank timestamps to guesstimagte how many vblanks have elapsed since the last time the vblank counter was updated. Take the oppostunity to unify the vblank_disable_and_save() and drm_handle_vblank_events() to call the same function (drm_update_vblank_count()) to perform the vblank updates. If the hardware/driver has an accurate frame counter use it instead of the timestamp based guesstimate. If the hardware/driver has neither a frame counter nor acurate vblank timestamps, we fall back to assuming that each drm_handle_vblank_events() should increment the vblank count by one. Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
1 parent 1b2eb71 commit 4dfd648

File tree

1 file changed

+91
-115
lines changed

1 file changed

+91
-115
lines changed

drivers/gpu/drm/drm_irq.c

Lines changed: 91 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,15 @@ module_param_named(timestamp_monotonic, drm_timestamp_monotonic, int, 0600);
7676

7777
static void store_vblank(struct drm_device *dev, unsigned int pipe,
7878
u32 vblank_count_inc,
79-
struct timeval *t_vblank)
79+
struct timeval *t_vblank, u32 last)
8080
{
8181
struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
8282
u32 tslot;
8383

8484
assert_spin_locked(&dev->vblank_time_lock);
8585

86+
vblank->last = last;
87+
8688
/* All writers hold the spinlock, but readers are serialized by
8789
* the latching of vblank->count below.
8890
*/
@@ -102,6 +104,54 @@ static void store_vblank(struct drm_device *dev, unsigned int pipe,
102104
smp_wmb();
103105
}
104106

107+
/**
108+
* drm_reset_vblank_timestamp - reset the last timestamp to the last vblank
109+
* @dev: DRM device
110+
* @pipe: index of CRTC for which to reset the timestamp
111+
*
112+
* Reset the stored timestamp for the current vblank count to correspond
113+
* to the last vblank occurred.
114+
*
115+
* Only to be called from drm_vblank_on().
116+
*
117+
* Note: caller must hold dev->vbl_lock since this reads & writes
118+
* device vblank fields.
119+
*/
120+
static void drm_reset_vblank_timestamp(struct drm_device *dev, unsigned int pipe)
121+
{
122+
u32 cur_vblank;
123+
bool rc;
124+
struct timeval t_vblank;
125+
int count = DRM_TIMESTAMP_MAXRETRIES;
126+
127+
spin_lock(&dev->vblank_time_lock);
128+
129+
/*
130+
* sample the current counter to avoid random jumps
131+
* when drm_vblank_enable() applies the diff
132+
*/
133+
do {
134+
cur_vblank = dev->driver->get_vblank_counter(dev, pipe);
135+
rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, 0);
136+
} while (cur_vblank != dev->driver->get_vblank_counter(dev, pipe) && --count > 0);
137+
138+
/*
139+
* Only reinitialize corresponding vblank timestamp if high-precision query
140+
* available and didn't fail. Otherwise reinitialize delayed at next vblank
141+
* interrupt and assign 0 for now, to mark the vblanktimestamp as invalid.
142+
*/
143+
if (!rc)
144+
t_vblank = (struct timeval) {0, 0};
145+
146+
/*
147+
* +1 to make sure user will never see the same
148+
* vblank counter value before and after a modeset
149+
*/
150+
store_vblank(dev, pipe, 1, &t_vblank, cur_vblank);
151+
152+
spin_unlock(&dev->vblank_time_lock);
153+
}
154+
105155
/**
106156
* drm_update_vblank_count - update the master vblank counter
107157
* @dev: DRM device
@@ -126,6 +176,7 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe,
126176
bool rc;
127177
struct timeval t_vblank;
128178
int count = DRM_TIMESTAMP_MAXRETRIES;
179+
int framedur_ns = vblank->framedur_ns;
129180

130181
/*
131182
* Interrupts were disabled prior to this call, so deal with counter
@@ -144,20 +195,40 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe,
144195
rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, flags);
145196
} while (cur_vblank != dev->driver->get_vblank_counter(dev, pipe) && --count > 0);
146197

147-
/* Deal with counter wrap */
148-
diff = cur_vblank - vblank->last;
149-
if (cur_vblank < vblank->last) {
150-
diff += dev->max_vblank_count + 1;
198+
if (dev->max_vblank_count != 0) {
199+
/* trust the hw counter when it's around */
200+
diff = (cur_vblank - vblank->last) & dev->max_vblank_count;
201+
} else if (rc && framedur_ns) {
202+
const struct timeval *t_old;
203+
u64 diff_ns;
204+
205+
t_old = &vblanktimestamp(dev, pipe, vblank->count);
206+
diff_ns = timeval_to_ns(&t_vblank) - timeval_to_ns(t_old);
151207

152-
DRM_DEBUG("last_vblank[%u]=0x%x, cur_vblank=0x%x => diff=0x%x\n",
153-
pipe, vblank->last, cur_vblank, diff);
208+
/*
209+
* Figure out how many vblanks we've missed based
210+
* on the difference in the timestamps and the
211+
* frame/field duration.
212+
*/
213+
diff = DIV_ROUND_CLOSEST_ULL(diff_ns, framedur_ns);
214+
215+
if (diff == 0 && flags & DRM_CALLED_FROM_VBLIRQ)
216+
DRM_DEBUG("crtc %u: Redundant vblirq ignored."
217+
" diff_ns = %lld, framedur_ns = %d)\n",
218+
pipe, (long long) diff_ns, framedur_ns);
219+
} else {
220+
/* some kind of default for drivers w/o accurate vbl timestamping */
221+
diff = (flags & DRM_CALLED_FROM_VBLIRQ) != 0;
154222
}
155223

156-
DRM_DEBUG("updating vblank count on crtc %u, missed %d\n",
157-
pipe, diff);
224+
DRM_DEBUG("updating vblank count on crtc %u:"
225+
" current=%u, diff=%u, hw=%u hw_last=%u\n",
226+
pipe, vblank->count, diff, cur_vblank, vblank->last);
158227

159-
if (diff == 0)
228+
if (diff == 0) {
229+
WARN_ON_ONCE(cur_vblank != vblank->last);
160230
return;
231+
}
161232

162233
/*
163234
* Only reinitialize corresponding vblank timestamp if high-precision query
@@ -167,7 +238,7 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe,
167238
if (!rc)
168239
t_vblank = (struct timeval) {0, 0};
169240

170-
store_vblank(dev, pipe, diff, &t_vblank);
241+
store_vblank(dev, pipe, diff, &t_vblank, cur_vblank);
171242
}
172243

173244
/*
@@ -180,38 +251,13 @@ static void vblank_disable_and_save(struct drm_device *dev, unsigned int pipe)
180251
{
181252
struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
182253
unsigned long irqflags;
183-
u32 vblcount;
184-
s64 diff_ns;
185-
bool vblrc;
186-
struct timeval tvblank;
187-
int count = DRM_TIMESTAMP_MAXRETRIES;
188254

189255
/* Prevent vblank irq processing while disabling vblank irqs,
190256
* so no updates of timestamps or count can happen after we've
191257
* disabled. Needed to prevent races in case of delayed irq's.
192258
*/
193259
spin_lock_irqsave(&dev->vblank_time_lock, irqflags);
194260

195-
/*
196-
* If the vblank interrupt was already disabled update the count
197-
* and timestamp to maintain the appearance that the counter
198-
* has been ticking all along until this time. This makes the
199-
* count account for the entire time between drm_vblank_on() and
200-
* drm_vblank_off().
201-
*
202-
* But only do this if precise vblank timestamps are available.
203-
* Otherwise we might read a totally bogus timestamp since drivers
204-
* lacking precise timestamp support rely upon sampling the system clock
205-
* at vblank interrupt time. Which obviously won't work out well if the
206-
* vblank interrupt is disabled.
207-
*/
208-
if (!vblank->enabled &&
209-
drm_get_last_vbltimestamp(dev, pipe, &tvblank, 0)) {
210-
drm_update_vblank_count(dev, pipe, 0);
211-
spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags);
212-
return;
213-
}
214-
215261
/*
216262
* Only disable vblank interrupts if they're enabled. This avoids
217263
* calling the ->disable_vblank() operation in atomic context with the
@@ -222,47 +268,13 @@ static void vblank_disable_and_save(struct drm_device *dev, unsigned int pipe)
222268
vblank->enabled = false;
223269
}
224270

225-
/* No further vblank irq's will be processed after
226-
* this point. Get current hardware vblank count and
227-
* vblank timestamp, repeat until they are consistent.
228-
*
229-
* FIXME: There is still a race condition here and in
230-
* drm_update_vblank_count() which can cause off-by-one
231-
* reinitialization of software vblank counter. If gpu
232-
* vblank counter doesn't increment exactly at the leading
233-
* edge of a vblank interval, then we can lose 1 count if
234-
* we happen to execute between start of vblank and the
235-
* delayed gpu counter increment.
236-
*/
237-
do {
238-
vblank->last = dev->driver->get_vblank_counter(dev, pipe);
239-
vblrc = drm_get_last_vbltimestamp(dev, pipe, &tvblank, 0);
240-
} while (vblank->last != dev->driver->get_vblank_counter(dev, pipe) && (--count) && vblrc);
241-
242-
if (!count)
243-
vblrc = 0;
244-
245-
/* Compute time difference to stored timestamp of last vblank
246-
* as updated by last invocation of drm_handle_vblank() in vblank irq.
247-
*/
248-
vblcount = vblank->count;
249-
diff_ns = timeval_to_ns(&tvblank) -
250-
timeval_to_ns(&vblanktimestamp(dev, pipe, vblcount));
251-
252-
/* If there is at least 1 msec difference between the last stored
253-
* timestamp and tvblank, then we are currently executing our
254-
* disable inside a new vblank interval, the tvblank timestamp
255-
* corresponds to this new vblank interval and the irq handler
256-
* for this vblank didn't run yet and won't run due to our disable.
257-
* Therefore we need to do the job of drm_handle_vblank() and
258-
* increment the vblank counter by one to account for this vblank.
259-
*
260-
* Skip this step if there isn't any high precision timestamp
261-
* available. In that case we can't account for this and just
262-
* hope for the best.
271+
/*
272+
* Always update the count and timestamp to maintain the
273+
* appearance that the counter has been ticking all along until
274+
* this time. This makes the count account for the entire time
275+
* between drm_vblank_on() and drm_vblank_off().
263276
*/
264-
if (vblrc && (abs64(diff_ns) > 1000000))
265-
store_vblank(dev, pipe, 1, &tvblank);
277+
drm_update_vblank_count(dev, pipe, 0);
266278

267279
spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags);
268280
}
@@ -1325,16 +1337,8 @@ void drm_vblank_on(struct drm_device *dev, unsigned int pipe)
13251337
vblank->inmodeset = 0;
13261338
}
13271339

1328-
/*
1329-
* sample the current counter to avoid random jumps
1330-
* when drm_vblank_enable() applies the diff
1331-
*
1332-
* -1 to make sure user will never see the same
1333-
* vblank counter value before and after a modeset
1334-
*/
1335-
vblank->last =
1336-
(dev->driver->get_vblank_counter(dev, pipe) - 1) &
1337-
dev->max_vblank_count;
1340+
drm_reset_vblank_timestamp(dev, pipe);
1341+
13381342
/*
13391343
* re-enable interrupts if there are users left, or the
13401344
* user wishes vblank interrupts to be enabled all the time.
@@ -1717,9 +1721,6 @@ static void drm_handle_vblank_events(struct drm_device *dev, unsigned int pipe)
17171721
bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe)
17181722
{
17191723
struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
1720-
u32 vblcount;
1721-
s64 diff_ns;
1722-
struct timeval tvblank;
17231724
unsigned long irqflags;
17241725

17251726
if (WARN_ON_ONCE(!dev->num_crtcs))
@@ -1743,32 +1744,7 @@ bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe)
17431744
return false;
17441745
}
17451746

1746-
/* Fetch corresponding timestamp for this vblank interval from
1747-
* driver and store it in proper slot of timestamp ringbuffer.
1748-
*/
1749-
1750-
/* Get current timestamp and count. */
1751-
vblcount = vblank->count;
1752-
drm_get_last_vbltimestamp(dev, pipe, &tvblank, DRM_CALLED_FROM_VBLIRQ);
1753-
1754-
/* Compute time difference to timestamp of last vblank */
1755-
diff_ns = timeval_to_ns(&tvblank) -
1756-
timeval_to_ns(&vblanktimestamp(dev, pipe, vblcount));
1757-
1758-
/* Update vblank timestamp and count if at least
1759-
* DRM_REDUNDANT_VBLIRQ_THRESH_NS nanoseconds
1760-
* difference between last stored timestamp and current
1761-
* timestamp. A smaller difference means basically
1762-
* identical timestamps. Happens if this vblank has
1763-
* been already processed and this is a redundant call,
1764-
* e.g., due to spurious vblank interrupts. We need to
1765-
* ignore those for accounting.
1766-
*/
1767-
if (abs64(diff_ns) > DRM_REDUNDANT_VBLIRQ_THRESH_NS)
1768-
store_vblank(dev, pipe, 1, &tvblank);
1769-
else
1770-
DRM_DEBUG("crtc %u: Redundant vblirq ignored. diff_ns = %d\n",
1771-
pipe, (int) diff_ns);
1747+
drm_update_vblank_count(dev, pipe, DRM_CALLED_FROM_VBLIRQ);
17721748

17731749
spin_unlock(&dev->vblank_time_lock);
17741750

0 commit comments

Comments
 (0)