Skip to content

Commit 8e36f9d

Browse files
committed
drm/amdgpu: Fixup hw vblank counter/ts for new drm_update_vblank_count() (v3)
commit 4dfd648 "drm: Use vblank timestamps to guesstimate how many vblanks were missed" introduced in Linux 4.4-rc1 makes the drm core more fragile to drivers which don't update hw vblank counters and vblank timestamps in sync with firing of the vblank irq and essentially at leading edge of vblank. This exposed a problem with radeon-kms/amdgpu-kms which do not satisfy above requirements: The vblank irq fires a few scanlines before start of vblank, but programmed pageflips complete at start of vblank and vblank timestamps update at start of vblank, whereas the hw vblank counter increments only later, at start of vsync. This leads to problems like off by one errors for vblank counter updates, vblank counters apparently going backwards or vblank timestamps apparently having time going backwards. The net result is stuttering of graphics in games, or little hangs, as well as total failure of timing sensitive applications. See bug #93147 for an example of the regression on Linux 4.4-rc: https://bugs.freedesktop.org/show_bug.cgi?id=93147 This patch tries to align all above events better from the viewpoint of the drm core / of external callers to fix the problem: 1. The apparent start of vblank is shifted a few scanlines earlier, so the vblank irq now always happens after start of this extended vblank interval and thereby drm_update_vblank_count() always samples the updated vblank count and timestamp of the new vblank interval. To achieve this, the reporting of scanout positions by radeon_get_crtc_scanoutpos() now operates as if the vblank starts radeon_crtc->lb_vblank_lead_lines before the real start of the hw vblank interval. This means that the vblank timestamps which are based on these scanout positions will now update at this earlier start of vblank. 2. The driver->get_vblank_counter() function will bump the returned vblank count as read from the hw by +1 if the query happens after the shifted earlier start of the vblank, but before the real hw increment at start of vsync, so the counter appears to increment at start of vblank in sync with the timestamp update. 3. Calls from vblank irq-context and regular non-irq calls are now treated identical, always simulating the shifted vblank start, to avoid inconsistent results for queries happening from vblank irq vs. happening from drm_vblank_enable() or vblank_disable_fn(). 4. The radeon_flip_work_func will delay mmio programming a pageflip until the start of the real vblank iff it happens to execute inside the shifted earlier start of the vblank, so pageflips now also appear to execute at start of the shifted vblank, in sync with vblank counter and timestamp updates. This to avoid some races between updates of vblank count and timestamps that are used for swap scheduling and pageflip execution which could cause pageflips to execute before the scheduled target vblank. The lb_vblank_lead_lines "fudge" value is calculated as the size of the display controllers line buffer in scanlines for the given video mode: Vblank irq's are triggered by the line buffer logic when the line buffer refill for a video frame ends, ie. when the line buffer source read position enters the hw vblank. This means that a vblank irq could fire at most as many scanlines before the current reported scanout position of the crtc timing generator as the number of scanlines the line buffer can maximally hold for a given video mode. This patch has been successfully tested on a RV730 card with DCE-3 display engine and on a evergreen card with DCE-4 display engine, in single-display and dual-display configuration, with different video modes. A similar patch is needed for amdgpu-kms to fix the same problem. Limitations: - Maybe replace the udelay() in the flip_work_func() by a suitable usleep_range() for a bit better efficiency? Will try that. - Line buffer sizes in pixels are hard-coded on < DCE-4 to a value i just guessed to be high enough to work ok, lacking info on the true sizes atm. Probably fixes: fdo#93147 Port of Mario's radeon fix to amdgpu. Signed-off-by: Alex Deucher <alexander.deucher@amd.com> (v1) Reviewed-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2) Refine amdgpu_flip_work_func() for better efficiency. In amdgpu_flip_work_func, replace the busy waiting udelay(5) with event lock held by a more performance and energy efficient usleep_range() until at least predicted true start of hw vblank, with some slack for scheduler happiness. Release the event lock during waits to not delay other outputs in doing their stuff, as the waiting can last up to 200 usecs in some cases. Also small fix to code comment and formatting in that function. (v2) Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v3) Fix crash in crtc disabled case
1 parent 5b5561b commit 8e36f9d

File tree

6 files changed

+140
-30
lines changed

6 files changed

+140
-30
lines changed

drivers/gpu/drm/amd/amdgpu/amdgpu_display.c

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ static void amdgpu_flip_work_func(struct work_struct *__work)
7373
struct drm_crtc *crtc = &amdgpuCrtc->base;
7474
unsigned long flags;
7575
unsigned i;
76+
int vpos, hpos, stat, min_udelay;
77+
struct drm_vblank_crtc *vblank = &crtc->dev->vblank[work->crtc_id];
7678

7779
amdgpu_flip_wait_fence(adev, &work->excl);
7880
for (i = 0; i < work->shared_count; ++i)
@@ -81,6 +83,41 @@ static void amdgpu_flip_work_func(struct work_struct *__work)
8183
/* We borrow the event spin lock for protecting flip_status */
8284
spin_lock_irqsave(&crtc->dev->event_lock, flags);
8385

86+
/* If this happens to execute within the "virtually extended" vblank
87+
* interval before the start of the real vblank interval then it needs
88+
* to delay programming the mmio flip until the real vblank is entered.
89+
* This prevents completing a flip too early due to the way we fudge
90+
* our vblank counter and vblank timestamps in order to work around the
91+
* problem that the hw fires vblank interrupts before actual start of
92+
* vblank (when line buffer refilling is done for a frame). It
93+
* complements the fudging logic in amdgpu_get_crtc_scanoutpos() for
94+
* timestamping and amdgpu_get_vblank_counter_kms() for vblank counts.
95+
*
96+
* In practice this won't execute very often unless on very fast
97+
* machines because the time window for this to happen is very small.
98+
*/
99+
for (;;) {
100+
/* GET_DISTANCE_TO_VBLANKSTART returns distance to real vblank
101+
* start in hpos, and to the "fudged earlier" vblank start in
102+
* vpos.
103+
*/
104+
stat = amdgpu_get_crtc_scanoutpos(adev->ddev, work->crtc_id,
105+
GET_DISTANCE_TO_VBLANKSTART,
106+
&vpos, &hpos, NULL, NULL,
107+
&crtc->hwmode);
108+
109+
if ((stat & (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE)) !=
110+
(DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE) ||
111+
!(vpos >= 0 && hpos <= 0))
112+
break;
113+
114+
/* Sleep at least until estimated real start of hw vblank */
115+
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
116+
min_udelay = (-hpos + 1) * max(vblank->linedur_ns / 1000, 5);
117+
usleep_range(min_udelay, 2 * min_udelay);
118+
spin_lock_irqsave(&crtc->dev->event_lock, flags);
119+
};
120+
84121
/* do the flip (mmio) */
85122
adev->mode_info.funcs->page_flip(adev, work->crtc_id, work->base);
86123
/* set the flip status */
@@ -712,6 +749,15 @@ bool amdgpu_crtc_scaling_mode_fixup(struct drm_crtc *crtc,
712749
* \param dev Device to query.
713750
* \param pipe Crtc to query.
714751
* \param flags Flags from caller (DRM_CALLED_FROM_VBLIRQ or 0).
752+
* For driver internal use only also supports these flags:
753+
*
754+
* USE_REAL_VBLANKSTART to use the real start of vblank instead
755+
* of a fudged earlier start of vblank.
756+
*
757+
* GET_DISTANCE_TO_VBLANKSTART to return distance to the
758+
* fudged earlier start of vblank in *vpos and the distance
759+
* to true start of vblank in *hpos.
760+
*
715761
* \param *vpos Location where vertical scanout position should be stored.
716762
* \param *hpos Location where horizontal scanout position should go.
717763
* \param *stime Target location for timestamp taken immediately before
@@ -776,10 +822,40 @@ int amdgpu_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe,
776822
vbl_end = 0;
777823
}
778824

825+
/* Called from driver internal vblank counter query code? */
826+
if (flags & GET_DISTANCE_TO_VBLANKSTART) {
827+
/* Caller wants distance from real vbl_start in *hpos */
828+
*hpos = *vpos - vbl_start;
829+
}
830+
831+
/* Fudge vblank to start a few scanlines earlier to handle the
832+
* problem that vblank irqs fire a few scanlines before start
833+
* of vblank. Some driver internal callers need the true vblank
834+
* start to be used and signal this via the USE_REAL_VBLANKSTART flag.
835+
*
836+
* The cause of the "early" vblank irq is that the irq is triggered
837+
* by the line buffer logic when the line buffer read position enters
838+
* the vblank, whereas our crtc scanout position naturally lags the
839+
* line buffer read position.
840+
*/
841+
if (!(flags & USE_REAL_VBLANKSTART))
842+
vbl_start -= adev->mode_info.crtcs[pipe]->lb_vblank_lead_lines;
843+
779844
/* Test scanout position against vblank region. */
780845
if ((*vpos < vbl_start) && (*vpos >= vbl_end))
781846
in_vbl = false;
782847

848+
/* In vblank? */
849+
if (in_vbl)
850+
ret |= DRM_SCANOUTPOS_IN_VBLANK;
851+
852+
/* Called from driver internal vblank counter query code? */
853+
if (flags & GET_DISTANCE_TO_VBLANKSTART) {
854+
/* Caller wants distance from fudged earlier vbl_start */
855+
*vpos -= vbl_start;
856+
return ret;
857+
}
858+
783859
/* Check if inside vblank area and apply corrective offsets:
784860
* vpos will then be >=0 in video scanout area, but negative
785861
* within vblank area, counting down the number of lines until
@@ -795,32 +871,6 @@ int amdgpu_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe,
795871
/* Correct for shifted end of vbl at vbl_end. */
796872
*vpos = *vpos - vbl_end;
797873

798-
/* In vblank? */
799-
if (in_vbl)
800-
ret |= DRM_SCANOUTPOS_IN_VBLANK;
801-
802-
/* Is vpos outside nominal vblank area, but less than
803-
* 1/100 of a frame height away from start of vblank?
804-
* If so, assume this isn't a massively delayed vblank
805-
* interrupt, but a vblank interrupt that fired a few
806-
* microseconds before true start of vblank. Compensate
807-
* by adding a full frame duration to the final timestamp.
808-
* Happens, e.g., on ATI R500, R600.
809-
*
810-
* We only do this if DRM_CALLED_FROM_VBLIRQ.
811-
*/
812-
if ((flags & DRM_CALLED_FROM_VBLIRQ) && !in_vbl) {
813-
vbl_start = mode->crtc_vdisplay;
814-
vtotal = mode->crtc_vtotal;
815-
816-
if (vbl_start - *vpos < vtotal / 100) {
817-
*vpos -= vtotal;
818-
819-
/* Signal this correction as "applied". */
820-
ret |= 0x8;
821-
}
822-
}
823-
824874
return ret;
825875
}
826876

drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,13 +611,59 @@ void amdgpu_driver_preclose_kms(struct drm_device *dev,
611611
u32 amdgpu_get_vblank_counter_kms(struct drm_device *dev, unsigned int pipe)
612612
{
613613
struct amdgpu_device *adev = dev->dev_private;
614+
int vpos, hpos, stat;
615+
u32 count;
614616

615617
if (pipe >= adev->mode_info.num_crtc) {
616618
DRM_ERROR("Invalid crtc %u\n", pipe);
617619
return -EINVAL;
618620
}
619621

620-
return amdgpu_display_vblank_get_counter(adev, pipe);
622+
/* The hw increments its frame counter at start of vsync, not at start
623+
* of vblank, as is required by DRM core vblank counter handling.
624+
* Cook the hw count here to make it appear to the caller as if it
625+
* incremented at start of vblank. We measure distance to start of
626+
* vblank in vpos. vpos therefore will be >= 0 between start of vblank
627+
* and start of vsync, so vpos >= 0 means to bump the hw frame counter
628+
* result by 1 to give the proper appearance to caller.
629+
*/
630+
if (adev->mode_info.crtcs[pipe]) {
631+
/* Repeat readout if needed to provide stable result if
632+
* we cross start of vsync during the queries.
633+
*/
634+
do {
635+
count = amdgpu_display_vblank_get_counter(adev, pipe);
636+
/* Ask amdgpu_get_crtc_scanoutpos to return vpos as
637+
* distance to start of vblank, instead of regular
638+
* vertical scanout pos.
639+
*/
640+
stat = amdgpu_get_crtc_scanoutpos(
641+
dev, pipe, GET_DISTANCE_TO_VBLANKSTART,
642+
&vpos, &hpos, NULL, NULL,
643+
&adev->mode_info.crtcs[pipe]->base.hwmode);
644+
} while (count != amdgpu_display_vblank_get_counter(adev, pipe));
645+
646+
if (((stat & (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE)) !=
647+
(DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE))) {
648+
DRM_DEBUG_VBL("Query failed! stat %d\n", stat);
649+
} else {
650+
DRM_DEBUG_VBL("crtc %d: dist from vblank start %d\n",
651+
pipe, vpos);
652+
653+
/* Bump counter if we are at >= leading edge of vblank,
654+
* but before vsync where vpos would turn negative and
655+
* the hw counter really increments.
656+
*/
657+
if (vpos >= 0)
658+
count++;
659+
}
660+
} else {
661+
/* Fallback to use value as is. */
662+
count = amdgpu_display_vblank_get_counter(adev, pipe);
663+
DRM_DEBUG_VBL("NULL mode info! Returned count may be wrong.\n");
664+
}
665+
666+
return count;
621667
}
622668

623669
/**

drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ struct amdgpu_crtc {
407407
u32 line_time;
408408
u32 wm_low;
409409
u32 wm_high;
410+
u32 lb_vblank_lead_lines;
410411
struct drm_display_mode hw_mode;
411412
};
412413

@@ -528,6 +529,10 @@ struct amdgpu_framebuffer {
528529
#define ENCODER_MODE_IS_DP(em) (((em) == ATOM_ENCODER_MODE_DP) || \
529530
((em) == ATOM_ENCODER_MODE_DP_MST))
530531

532+
/* Driver internal use only flags of amdgpu_get_crtc_scanoutpos() */
533+
#define USE_REAL_VBLANKSTART (1 << 30)
534+
#define GET_DISTANCE_TO_VBLANKSTART (1 << 31)
535+
531536
void amdgpu_link_encoder_connector(struct drm_device *dev);
532537

533538
struct drm_connector *

drivers/gpu/drm/amd/amdgpu/dce_v10_0.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1250,7 +1250,7 @@ static void dce_v10_0_program_watermarks(struct amdgpu_device *adev,
12501250
u32 pixel_period;
12511251
u32 line_time = 0;
12521252
u32 latency_watermark_a = 0, latency_watermark_b = 0;
1253-
u32 tmp, wm_mask;
1253+
u32 tmp, wm_mask, lb_vblank_lead_lines = 0;
12541254

12551255
if (amdgpu_crtc->base.enabled && num_heads && mode) {
12561256
pixel_period = 1000000 / (u32)mode->clock;
@@ -1333,6 +1333,7 @@ static void dce_v10_0_program_watermarks(struct amdgpu_device *adev,
13331333
(adev->mode_info.disp_priority == 2)) {
13341334
DRM_DEBUG_KMS("force priority to high\n");
13351335
}
1336+
lb_vblank_lead_lines = DIV_ROUND_UP(lb_size, mode->crtc_hdisplay);
13361337
}
13371338

13381339
/* select wm A */
@@ -1357,6 +1358,8 @@ static void dce_v10_0_program_watermarks(struct amdgpu_device *adev,
13571358
amdgpu_crtc->line_time = line_time;
13581359
amdgpu_crtc->wm_high = latency_watermark_a;
13591360
amdgpu_crtc->wm_low = latency_watermark_b;
1361+
/* Save number of lines the linebuffer leads before the scanout */
1362+
amdgpu_crtc->lb_vblank_lead_lines = lb_vblank_lead_lines;
13601363
}
13611364

13621365
/**

drivers/gpu/drm/amd/amdgpu/dce_v11_0.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1238,7 +1238,7 @@ static void dce_v11_0_program_watermarks(struct amdgpu_device *adev,
12381238
u32 pixel_period;
12391239
u32 line_time = 0;
12401240
u32 latency_watermark_a = 0, latency_watermark_b = 0;
1241-
u32 tmp, wm_mask;
1241+
u32 tmp, wm_mask, lb_vblank_lead_lines = 0;
12421242

12431243
if (amdgpu_crtc->base.enabled && num_heads && mode) {
12441244
pixel_period = 1000000 / (u32)mode->clock;
@@ -1321,6 +1321,7 @@ static void dce_v11_0_program_watermarks(struct amdgpu_device *adev,
13211321
(adev->mode_info.disp_priority == 2)) {
13221322
DRM_DEBUG_KMS("force priority to high\n");
13231323
}
1324+
lb_vblank_lead_lines = DIV_ROUND_UP(lb_size, mode->crtc_hdisplay);
13241325
}
13251326

13261327
/* select wm A */
@@ -1345,6 +1346,8 @@ static void dce_v11_0_program_watermarks(struct amdgpu_device *adev,
13451346
amdgpu_crtc->line_time = line_time;
13461347
amdgpu_crtc->wm_high = latency_watermark_a;
13471348
amdgpu_crtc->wm_low = latency_watermark_b;
1349+
/* Save number of lines the linebuffer leads before the scanout */
1350+
amdgpu_crtc->lb_vblank_lead_lines = lb_vblank_lead_lines;
13481351
}
13491352

13501353
/**

drivers/gpu/drm/amd/amdgpu/dce_v8_0.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1193,7 +1193,7 @@ static void dce_v8_0_program_watermarks(struct amdgpu_device *adev,
11931193
u32 pixel_period;
11941194
u32 line_time = 0;
11951195
u32 latency_watermark_a = 0, latency_watermark_b = 0;
1196-
u32 tmp, wm_mask;
1196+
u32 tmp, wm_mask, lb_vblank_lead_lines = 0;
11971197

11981198
if (amdgpu_crtc->base.enabled && num_heads && mode) {
11991199
pixel_period = 1000000 / (u32)mode->clock;
@@ -1276,6 +1276,7 @@ static void dce_v8_0_program_watermarks(struct amdgpu_device *adev,
12761276
(adev->mode_info.disp_priority == 2)) {
12771277
DRM_DEBUG_KMS("force priority to high\n");
12781278
}
1279+
lb_vblank_lead_lines = DIV_ROUND_UP(lb_size, mode->crtc_hdisplay);
12791280
}
12801281

12811282
/* select wm A */
@@ -1302,6 +1303,8 @@ static void dce_v8_0_program_watermarks(struct amdgpu_device *adev,
13021303
amdgpu_crtc->line_time = line_time;
13031304
amdgpu_crtc->wm_high = latency_watermark_a;
13041305
amdgpu_crtc->wm_low = latency_watermark_b;
1306+
/* Save number of lines the linebuffer leads before the scanout */
1307+
amdgpu_crtc->lb_vblank_lead_lines = lb_vblank_lead_lines;
13051308
}
13061309

13071310
/**

0 commit comments

Comments
 (0)