fix(drm): fall back only when KMS rejects the images, wait on lost master at resume
- commit
- 6c2aaa2bd42141e76eb94cd14d2f60f78a89e608
- author
- brice · · 2026-09-25 17:33 UTC
- parent
- 1941387
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +20 -2
M internal/adapters/drm/output.go +19 -1
| @@ -227,7 +227,12 @@ func (o *Output) Run(ctx context.Context, newRenderer func(w, h int) (ports.Rend | |||
| 227 | 227 | // Modeset on every enable: a fast disable+enable can coalesce to one true. | |
| 228 | 228 | if on { | |
| 229 | 229 | err := o.modeset() | |
| 230 | - | if err != nil && !o.validated && o.kind < imagesDumb { | |
| 230 | + | if lostMaster(err) { | |
| 231 | + | // The seat took DRM master back: wait for the next enable. | |
| 232 | + | o.log.Warn().Err(err).Msg("resume") | |
| 233 | + | continue | |
| 234 | + | } | |
| 235 | + | if refused(err) && !o.validated && o.kind < imagesDumb { | |
| 231 | 236 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(o.kind)).Msg("modeset refused the output images") | |
| 232 | 237 | o.freeImages() | |
| 233 | 238 | err = o.showImages(r, o.kind+1, err) | |
| @@ -386,6 +391,9 @@ func (o *Output) showImages(r ports.Renderer, kind imageKind, cause error) error | |||
| 386 | 391 | o.validated = true | |
| 387 | 392 | return nil | |
| 388 | 393 | } | |
| 394 | + | if !refused(err) { | |
| 395 | + | return err | |
| 396 | + | } | |
| 389 | 397 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(got)).Msg("modeset refused the output images") | |
| 390 | 398 | o.freeImages() | |
| 391 | 399 | if got == imagesDumb { | |
| @@ -395,6 +403,16 @@ func (o *Output) showImages(r ports.Renderer, kind imageKind, cause error) error | |||
| 395 | 403 | } | |
| 396 | 404 | } | |
| 397 | 405 | ||
| 406 | + | // refused reports a modeset error meaning KMS rejects the images. | |
| 407 | + | func refused(err error) bool { | |
| 408 | + | return errors.Is(err, unix.EINVAL) || errors.Is(err, unix.ERANGE) | |
| 409 | + | } | |
| 410 | + | ||
| 411 | + | // lostMaster reports a modeset error meaning the seat is switched away. | |
| 412 | + | func lostMaster(err error) bool { | |
| 413 | + | return errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) | |
| 414 | + | } | |
| 415 | + | ||
| 398 | 416 | // setupImages gives the output the two images it flips between: renderer | |
| 399 | 417 | // targets exported as dmabufs, or dumb buffers as a logged fallback. A | |
| 400 | 418 | // kind that fails falls through to the next one. | |
| @@ -227,7 +227,12 @@ func (o *Output) Run(ctx context.Context, newRenderer func(w, h int) (ports.Rend | |||||
| 227 | // Modeset on every enable: a fast disable+enable can coalesce to one true. | 227 | // Modeset on every enable: a fast disable+enable can coalesce to one true. | ||
| 228 | if on { | 228 | if on { | ||
| 229 | err := o.modeset() | 229 | err := o.modeset() | ||
| 230 | - | if err != nil && !o.validated && o.kind < imagesDumb { | 230 | + | if lostMaster(err) { |
| 231 | + | // The seat took DRM master back: wait for the next enable. | |||
| 232 | + | o.log.Warn().Err(err).Msg("resume") | |||
| 233 | + | continue | |||
| 234 | + | } | |||
| 235 | + | if refused(err) && !o.validated && o.kind < imagesDumb { | |||
| 231 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(o.kind)).Msg("modeset refused the output images") | 236 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(o.kind)).Msg("modeset refused the output images") | ||
| 232 | o.freeImages() | 237 | o.freeImages() | ||
| 233 | err = o.showImages(r, o.kind+1, err) | 238 | err = o.showImages(r, o.kind+1, err) | ||
| @@ -386,6 +391,9 @@ func (o *Output) showImages(r ports.Renderer, kind imageKind, cause error) error | |||||
| 386 | o.validated = true | 391 | o.validated = true | ||
| 387 | return nil | 392 | return nil | ||
| 388 | } | 393 | } | ||
| 394 | + | if !refused(err) { | |||
| 395 | + | return err | |||
| 396 | + | } | |||
| 389 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(got)).Msg("modeset refused the output images") | 397 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(got)).Msg("modeset refused the output images") | ||
| 390 | o.freeImages() | 398 | o.freeImages() | ||
| 391 | if got == imagesDumb { | 399 | if got == imagesDumb { | ||
| @@ -395,6 +403,16 @@ func (o *Output) showImages(r ports.Renderer, kind imageKind, cause error) error | |||||
| 395 | } | 403 | } | ||
| 396 | } | 404 | } | ||
| 397 | 405 | ||||
| 406 | + | // refused reports a modeset error meaning KMS rejects the images. | |||
| 407 | + | func refused(err error) bool { | |||
| 408 | + | return errors.Is(err, unix.EINVAL) || errors.Is(err, unix.ERANGE) | |||
| 409 | + | } | |||
| 410 | + | ||||
| 411 | + | // lostMaster reports a modeset error meaning the seat is switched away. | |||
| 412 | + | func lostMaster(err error) bool { | |||
| 413 | + | return errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) | |||
| 414 | + | } | |||
| 415 | + | ||||
| 398 | // setupImages gives the output the two images it flips between: renderer | 416 | // setupImages gives the output the two images it flips between: renderer | ||
| 399 | // targets exported as dmabufs, or dumb buffers as a logged fallback. A | 417 | // targets exported as dmabufs, or dumb buffers as a logged fallback. A | ||
| 400 | // kind that fails falls through to the next one. | 418 | // kind that fails falls through to the next one. | ||
M internal/adapters/vulkan/renderer.go +1 -1
| @@ -745,13 +745,13 @@ func (r *Renderer) CopyBGRX(dst []byte, pitch int) { | |||
| 745 | 745 | } | |
| 746 | 746 | } | |
| 747 | 747 | ||
| 748 | - | // readback copies the last frame into the host buffer, once per frame. | |
| 749 | 748 | // Missing from the bindings: VK_QUEUE_FAMILY_IGNORED, VK_WHOLE_SIZE. | |
| 750 | 749 | const ( | |
| 751 | 750 | queueFamilyIgnored = ^uint32(0) | |
| 752 | 751 | wholeSize = ^vk.DeviceSize(0) | |
| 753 | 752 | ) | |
| 754 | 753 | ||
| 754 | + | // readback copies the last frame into the host buffer, once per frame. | |
| 755 | 755 | func (r *Renderer) readback() error { | |
| 756 | 756 | if r.readBack || r.last == nil || r.last.image == 0 { | |
| 757 | 757 | return nil | |
| @@ -745,13 +745,13 @@ func (r *Renderer) CopyBGRX(dst []byte, pitch int) { | |||||
| 745 | } | 745 | } | ||
| 746 | } | 746 | } | ||
| 747 | 747 | ||||
| 748 | - | // readback copies the last frame into the host buffer, once per frame. | |||
| 749 | // Missing from the bindings: VK_QUEUE_FAMILY_IGNORED, VK_WHOLE_SIZE. | 748 | // Missing from the bindings: VK_QUEUE_FAMILY_IGNORED, VK_WHOLE_SIZE. | ||
| 750 | const ( | 749 | const ( | ||
| 751 | queueFamilyIgnored = ^uint32(0) | 750 | queueFamilyIgnored = ^uint32(0) | ||
| 752 | wholeSize = ^vk.DeviceSize(0) | 751 | wholeSize = ^vk.DeviceSize(0) | ||
| 753 | ) | 752 | ) | ||
| 754 | 753 | ||||
| 754 | + | // readback copies the last frame into the host buffer, once per frame. | |||
| 755 | func (r *Renderer) readback() error { | 755 | func (r *Renderer) readback() error { | ||
| 756 | if r.readBack || r.last == nil || r.last.image == 0 { | 756 | if r.readBack || r.last == nil || r.last.image == 0 { | ||
| 757 | return nil | 757 | return nil | ||