Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/fb-helper: Fix vt restore

In the past we had a pile of hacks to orchestrate access between fbdev
emulation and native kms clients. We've tried to streamline this, by
always preferring the kms side above fbdev calls when a drm master
exists, because drm master controls access to the display resources.

Unfortunately this breaks existing userspace, specifically Xorg. When
exiting Xorg first restores the console to text mode using the KDSET
ioctl on the vt. This does nothing, because a drm master is still
around. Then it drops the drm master status, which again does nothing,
because logind is keeping additional drm fd open to be able to
orchestrate vt switches. In the past this is the point where fbdev was
restored, as part of the ->lastclose hook on the drm side.

Now to fix this regression we don't want to go back to letting fbdev
restore things whenever it feels like, or to the pile of hacks we've
had before. Instead try and go with a minimal exception to make the
KDSET case work again, and nothing else.

This means that if userspace does a KDSET call when switching between
graphical compositors, there will be some flickering with fbcon
showing up for a bit. But a) that's not a regression and b) userspace
can fix it by improving the vt switching dance - logind should have
all the information it needs.

While pondering all this I'm also wondering wheter we should have a
SWITCH_MASTER ioctl to allow race-free master status handover. But
that's for another day.

v2: Somehow forgot to cc all the fbdev people.

v3: Fix typo Alex spotted.

Reviewed-by: Alex Deucher <alexander.deucher@amd.com>
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=208179
Cc: shlomo@fastmail.com
Reported-and-Tested-by: shlomo@fastmail.com
Cc: Michel Dänzer <michel@daenzer.net>
Fixes: 64914da24ea9 ("drm/fbdev-helper: don't force restores")
Cc: Noralf Trønnes <noralf@tronnes.org>
Cc: Thomas Zimmermann <tzimmermann@suse.de>
Cc: Daniel Vetter <daniel.vetter@intel.com>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Maxime Ripard <mripard@kernel.org>
Cc: David Airlie <airlied@linux.ie>
Cc: Daniel Vetter <daniel@ffwll.ch>
Cc: dri-devel@lists.freedesktop.org
Cc: <stable@vger.kernel.org> # v5.7+
Cc: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Nathan Chancellor <natechancellor@gmail.com>
Cc: Qiujun Huang <hqjagain@gmail.com>
Cc: Peter Rosin <peda@axentia.se>
Cc: linux-fbdev@vger.kernel.org
Signed-off-by: Daniel Vetter <daniel.vetter@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200624092910.3280448-1-daniel.vetter@ffwll.ch

+60 -23
+57 -22
drivers/gpu/drm/drm_fb_helper.c
··· 227 227 } 228 228 EXPORT_SYMBOL(drm_fb_helper_debug_leave); 229 229 230 + static int 231 + __drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper, 232 + bool force) 233 + { 234 + bool do_delayed; 235 + int ret; 236 + 237 + if (!drm_fbdev_emulation || !fb_helper) 238 + return -ENODEV; 239 + 240 + if (READ_ONCE(fb_helper->deferred_setup)) 241 + return 0; 242 + 243 + mutex_lock(&fb_helper->lock); 244 + if (force) { 245 + /* 246 + * Yes this is the _locked version which expects the master lock 247 + * to be held. But for forced restores we're intentionally 248 + * racing here, see drm_fb_helper_set_par(). 249 + */ 250 + ret = drm_client_modeset_commit_locked(&fb_helper->client); 251 + } else { 252 + ret = drm_client_modeset_commit(&fb_helper->client); 253 + } 254 + 255 + do_delayed = fb_helper->delayed_hotplug; 256 + if (do_delayed) 257 + fb_helper->delayed_hotplug = false; 258 + mutex_unlock(&fb_helper->lock); 259 + 260 + if (do_delayed) 261 + drm_fb_helper_hotplug_event(fb_helper); 262 + 263 + return ret; 264 + } 265 + 230 266 /** 231 267 * drm_fb_helper_restore_fbdev_mode_unlocked - restore fbdev configuration 232 268 * @fb_helper: driver-allocated fbdev helper, can be NULL ··· 276 240 */ 277 241 int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper) 278 242 { 279 - bool do_delayed; 280 - int ret; 281 - 282 - if (!drm_fbdev_emulation || !fb_helper) 283 - return -ENODEV; 284 - 285 - if (READ_ONCE(fb_helper->deferred_setup)) 286 - return 0; 287 - 288 - mutex_lock(&fb_helper->lock); 289 - ret = drm_client_modeset_commit(&fb_helper->client); 290 - 291 - do_delayed = fb_helper->delayed_hotplug; 292 - if (do_delayed) 293 - fb_helper->delayed_hotplug = false; 294 - mutex_unlock(&fb_helper->lock); 295 - 296 - if (do_delayed) 297 - drm_fb_helper_hotplug_event(fb_helper); 298 - 299 - return ret; 243 + return __drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, false); 300 244 } 301 245 EXPORT_SYMBOL(drm_fb_helper_restore_fbdev_mode_unlocked); 302 246 ··· 1334 1318 { 1335 1319 struct drm_fb_helper *fb_helper = info->par; 1336 1320 struct fb_var_screeninfo *var = &info->var; 1321 + bool force; 1337 1322 1338 1323 if (oops_in_progress) 1339 1324 return -EBUSY; ··· 1344 1327 return -EINVAL; 1345 1328 } 1346 1329 1347 - drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper); 1330 + /* 1331 + * Normally we want to make sure that a kms master takes precedence over 1332 + * fbdev, to avoid fbdev flickering and occasionally stealing the 1333 + * display status. But Xorg first sets the vt back to text mode using 1334 + * the KDSET IOCTL with KD_TEXT, and only after that drops the master 1335 + * status when exiting. 1336 + * 1337 + * In the past this was caught by drm_fb_helper_lastclose(), but on 1338 + * modern systems where logind always keeps a drm fd open to orchestrate 1339 + * the vt switching, this doesn't work. 1340 + * 1341 + * To not break the userspace ABI we have this special case here, which 1342 + * is only used for the above case. Everything else uses the normal 1343 + * commit function, which ensures that we never steal the display from 1344 + * an active drm master. 1345 + */ 1346 + force = var->activate & FB_ACTIVATE_KD_TEXT; 1347 + 1348 + __drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, force); 1348 1349 1349 1350 return 0; 1350 1351 }
+2 -1
drivers/video/fbdev/core/fbcon.c
··· 2402 2402 ops->graphics = 1; 2403 2403 2404 2404 if (!blank) { 2405 - var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; 2405 + var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE | 2406 + FB_ACTIVATE_KD_TEXT; 2406 2407 fb_set_var(info, &var); 2407 2408 ops->graphics = 0; 2408 2409 ops->var = info->var;
+1
include/uapi/linux/fb.h
··· 205 205 #define FB_ACTIVATE_ALL 64 /* change all VCs on this fb */ 206 206 #define FB_ACTIVATE_FORCE 128 /* force apply even when no change*/ 207 207 #define FB_ACTIVATE_INV_MODE 256 /* invalidate videomode */ 208 + #define FB_ACTIVATE_KD_TEXT 512 /* for KDSET vt ioctl */ 208 209 209 210 #define FB_ACCELF_TEXT 1 /* (OBSOLETE) see fb_info.flags and vc_mode */ 210 211