Move the shared drm ioctl abi into internal/drm

This commit is contained in:
flamingcow
2026-08-04 13:21:50 -07:00
parent 66bfb88dd7
commit 4fd3abaf25
3 changed files with 280 additions and 308 deletions
+96 -196
View File
@@ -8,116 +8,21 @@ import (
"unsafe"
"golang.org/x/sys/unix"
"g.fc.run/theater/cabletest/internal/drm"
)
// Drawing lands in memory and is blitted to a buffer the display is not
// reading, then swapped in whole at a vertical blank. Writing into the live
// scanout buffer instead, as fbdev invites, races the beam: the blit takes a
// few hundred microseconds and the display reads half of each frame.
const (
drmIoctlBase = 0x64
drmModeConnected = 1
drmModeTypePreferred = 1 << 3
drmModePageFlipEvent = 0x01
drmEventFlipComplete = 0x02
// What ADDFB means by 32 bits per pixel and 24 bits of colour.
xrgbRedShift = 16
xrgbGreenShift = 8
xrgbBlueShift = 0
// Two would be enough to stop the panel tearing, since one buffer being
// displayed while the other is drawn into is all double buffering means.
// More than two is for anything reading a frame back out: they are cycled
// in order, so a buffer is left alone for the three frames between going on
// screen and coming round again, and reading one out of uncached scanout
// memory takes a good fraction of a frame.
scanoutBuffers = 4
)
func drmIO(nr uintptr) uintptr { return drmIoctlBase<<8 | nr }
func drmIOWR(nr, size uintptr) uintptr { return 3<<30 | size<<16 | drmIoctlBase<<8 | nr }
type drmModeInfo struct {
clock uint32
hdisplay, hsyncStart, hsyncEnd, htotal, hskew uint16
vdisplay, vsyncStart, vsyncEnd, vtotal, vscan uint16
vrefresh, flags, typ uint32
name [32]byte
}
type drmModeCardRes struct {
fbIDPtr, crtcIDPtr, connIDPtr, encIDPtr uint64
countFBs, countCRTCs, countConns, countEncs uint32
minWidth, maxWidth, minHeight, maxHeight uint32
}
type drmModeGetConnector struct {
encodersPtr, modesPtr, propsPtr, propValuesPtr uint64
countModes, countProps, countEncoders uint32
encoderID, connectorID, connectorType uint32
connectorTypeID, connection uint32
mmWidth, mmHeight, subpixel, pad uint32
}
type drmModeGetEncoder struct {
encoderID, encoderType uint32
crtcID uint32
possibleCRTCs, possibleClones uint32
}
type drmModeCrtc struct {
setConnectorsPtr uint64
countConnectors uint32
crtcID uint32
fbID uint32
x, y uint32
gammaSize uint32
modeValid uint32
mode drmModeInfo
}
type drmModeFBCmd struct {
fbID, width, height, pitch, bpp, depth uint32
handle uint32
}
type drmModeCreateDumb struct {
height, width, bpp, flags uint32
handle, pitch uint32
size uint64
}
type drmModeMapDumb struct {
handle, pad uint32
offset uint64
}
type drmModeCrtcPageFlip struct {
crtcID, fbID, flags, reserved uint32
userData uint64
}
var (
drmSetMaster = drmIO(0x1e)
drmDropMaster = drmIO(0x1f)
drmGetResources = drmIOWR(0xa0, unsafe.Sizeof(drmModeCardRes{}))
drmSetCrtc = drmIOWR(0xa2, unsafe.Sizeof(drmModeCrtc{}))
drmGetEncoder = drmIOWR(0xa6, unsafe.Sizeof(drmModeGetEncoder{}))
drmGetConnector = drmIOWR(0xa7, unsafe.Sizeof(drmModeGetConnector{}))
drmAddFB = drmIOWR(0xae, unsafe.Sizeof(drmModeFBCmd{}))
drmPageFlip = drmIOWR(0xb0, unsafe.Sizeof(drmModeCrtcPageFlip{}))
drmCreateDumb = drmIOWR(0xb2, unsafe.Sizeof(drmModeCreateDumb{}))
drmMapDumb = drmIOWR(0xb3, unsafe.Sizeof(drmModeMapDumb{}))
)
func drmIoctl(fd int, req uintptr, arg unsafe.Pointer) error {
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), req, uintptr(arg)); errno != 0 {
return errno
}
return nil
}
//
// Two buffers would be enough to stop the panel tearing. More than two is for
// anything reading a frame back out: they are cycled in order, so a buffer is
// left alone for the three frames between going on screen and coming round
// again, and reading one out of uncached scanout memory takes a good fraction
// of a frame.
const scanoutBuffers = 4
type scanout struct {
fbID uint32
@@ -125,8 +30,7 @@ type scanout struct {
}
// w and h are the logical canvas, which is portrait; pw and ph are the panel,
// which is landscape. Every draw is turned a quarter turn on its way to memory,
// so logical top lands on the panel's right edge.
// which is landscape.
type framebuffer struct {
fd int
back []byte
@@ -147,7 +51,7 @@ type framebuffer struct {
}
func (fb *framebuffer) offset(x, y int) int {
return x*fb.stride + (fb.pw-1-y)*4
return drm.Offset(x, y, fb.stride, fb.pw)
}
func (fb *framebuffer) fromPanel(x, y int) (int, int) {
@@ -155,20 +59,20 @@ func (fb *framebuffer) fromPanel(x, y int) (int, int) {
}
func cardResources(fd int) (crtcs, conns []uint32, err error) {
var res drmModeCardRes
if err := drmIoctl(fd, drmGetResources, unsafe.Pointer(&res)); err != nil {
var res drm.ModeCardRes
if err := drm.Ioctl(fd, drm.GetResources, unsafe.Pointer(&res)); err != nil {
return nil, nil, fmt.Errorf("get resources: %w", err)
}
if res.countCRTCs == 0 || res.countConns == 0 {
if res.CountCRTCs == 0 || res.CountConns == 0 {
return nil, nil, fmt.Errorf("card has no crtcs or connectors")
}
crtcs = make([]uint32, res.countCRTCs)
conns = make([]uint32, res.countConns)
res.countFBs, res.countEncs = 0, 0
res.fbIDPtr, res.encIDPtr = 0, 0
res.crtcIDPtr = uint64(uintptr(unsafe.Pointer(&crtcs[0])))
res.connIDPtr = uint64(uintptr(unsafe.Pointer(&conns[0])))
if err := drmIoctl(fd, drmGetResources, unsafe.Pointer(&res)); err != nil {
crtcs = make([]uint32, res.CountCRTCs)
conns = make([]uint32, res.CountConns)
res.CountFBs, res.CountEncs = 0, 0
res.FBIDPtr, res.EncIDPtr = 0, 0
res.CrtcIDPtr = uint64(uintptr(unsafe.Pointer(&crtcs[0])))
res.ConnIDPtr = uint64(uintptr(unsafe.Pointer(&conns[0])))
if err := drm.Ioctl(fd, drm.GetResources, unsafe.Pointer(&res)); err != nil {
return nil, nil, fmt.Errorf("get resources: %w", err)
}
return crtcs, conns, nil
@@ -176,66 +80,66 @@ func cardResources(fd int) (crtcs, conns []uint32, err error) {
// The preferred mode is the panel's native one; anything else would be the
// driver scaling a wrong-sized image onto it.
func preferredMode(fd int, connID uint32) (drmModeInfo, error) {
c := drmModeGetConnector{connectorID: connID}
if err := drmIoctl(fd, drmGetConnector, unsafe.Pointer(&c)); err != nil {
return drmModeInfo{}, err
func preferredMode(fd int, connID uint32) (drm.ModeInfo, error) {
c := drm.ModeGetConnector{ConnectorID: connID}
if err := drm.Ioctl(fd, drm.GetConnector, unsafe.Pointer(&c)); err != nil {
return drm.ModeInfo{}, err
}
if c.countModes == 0 {
return drmModeInfo{}, fmt.Errorf("connector %d reported no modes", connID)
if c.CountModes == 0 {
return drm.ModeInfo{}, fmt.Errorf("connector %d reported no modes", connID)
}
modes := make([]drmModeInfo, c.countModes)
q := drmModeGetConnector{
connectorID: connID,
countModes: c.countModes,
modesPtr: uint64(uintptr(unsafe.Pointer(&modes[0]))),
modes := make([]drm.ModeInfo, c.CountModes)
q := drm.ModeGetConnector{
ConnectorID: connID,
CountModes: c.CountModes,
ModesPtr: uint64(uintptr(unsafe.Pointer(&modes[0]))),
}
if err := drmIoctl(fd, drmGetConnector, unsafe.Pointer(&q)); err != nil {
return drmModeInfo{}, err
if err := drm.Ioctl(fd, drm.GetConnector, unsafe.Pointer(&q)); err != nil {
return drm.ModeInfo{}, err
}
if q.countModes == 0 {
return drmModeInfo{}, fmt.Errorf("connector %d reported no modes", connID)
if q.CountModes == 0 {
return drm.ModeInfo{}, fmt.Errorf("connector %d reported no modes", connID)
}
for _, m := range modes[:q.countModes] {
if m.typ&drmModeTypePreferred != 0 {
for _, m := range modes[:q.CountModes] {
if m.Type&drm.TypePreferred != 0 {
return m, nil
}
}
return modes[0], nil
}
func crtcFor(fd int, c drmModeGetConnector, crtcs []uint32) (uint32, error) {
encoders := []uint32{c.encoderID}
if c.countEncoders > 0 {
list := make([]uint32, c.countEncoders)
q := drmModeGetConnector{
connectorID: c.connectorID,
countEncoders: c.countEncoders,
encodersPtr: uint64(uintptr(unsafe.Pointer(&list[0]))),
func crtcFor(fd int, c drm.ModeGetConnector, crtcs []uint32) (uint32, error) {
encoders := []uint32{c.EncoderID}
if c.CountEncoders > 0 {
list := make([]uint32, c.CountEncoders)
q := drm.ModeGetConnector{
ConnectorID: c.ConnectorID,
CountEncoders: c.CountEncoders,
EncodersPtr: uint64(uintptr(unsafe.Pointer(&list[0]))),
}
if err := drmIoctl(fd, drmGetConnector, unsafe.Pointer(&q)); err == nil {
encoders = append(encoders, list[:q.countEncoders]...)
if err := drm.Ioctl(fd, drm.GetConnector, unsafe.Pointer(&q)); err == nil {
encoders = append(encoders, list[:q.CountEncoders]...)
}
}
for _, id := range encoders {
if id == 0 {
continue
}
e := drmModeGetEncoder{encoderID: id}
if err := drmIoctl(fd, drmGetEncoder, unsafe.Pointer(&e)); err != nil {
e := drm.ModeGetEncoder{EncoderID: id}
if err := drm.Ioctl(fd, drm.GetEncoder, unsafe.Pointer(&e)); err != nil {
continue
}
// Already driving this connector, otherwise anything it can be wired to.
if e.crtcID != 0 {
return e.crtcID, nil
if e.CrtcID != 0 {
return e.CrtcID, nil
}
for i, crtc := range crtcs {
if e.possibleCRTCs&(1<<uint(i)) != 0 {
if e.PossibleCRTCs&(1<<uint(i)) != 0 {
return crtc, nil
}
}
}
return 0, fmt.Errorf("connector %d has no usable crtc", c.connectorID)
return 0, fmt.Errorf("connector %d has no usable crtc", c.ConnectorID)
}
func findDisplay(fd int) (connID, crtcID uint32, err error) {
@@ -244,11 +148,11 @@ func findDisplay(fd int) (connID, crtcID uint32, err error) {
return 0, 0, err
}
for _, id := range conns {
c := drmModeGetConnector{connectorID: id}
if err := drmIoctl(fd, drmGetConnector, unsafe.Pointer(&c)); err != nil {
c := drm.ModeGetConnector{ConnectorID: id}
if err := drm.Ioctl(fd, drm.GetConnector, unsafe.Pointer(&c)); err != nil {
continue
}
if c.connection != drmModeConnected || c.countModes == 0 {
if c.Connection != drm.Connected || c.CountModes == 0 {
continue
}
crtc, err := crtcFor(fd, c, crtcs)
@@ -281,30 +185,30 @@ func openCard() (int, error) {
}
func (fb *framebuffer) addScanout(i int) error {
create := drmModeCreateDumb{width: uint32(fb.pw), height: uint32(fb.ph), bpp: 32}
if err := drmIoctl(fb.fd, drmCreateDumb, unsafe.Pointer(&create)); err != nil {
create := drm.ModeCreateDumb{Width: uint32(fb.pw), Height: uint32(fb.ph), Bpp: 32}
if err := drm.Ioctl(fb.fd, drm.CreateDumb, unsafe.Pointer(&create)); err != nil {
return fmt.Errorf("create dumb buffer: %w", err)
}
fb.stride = int(create.pitch)
fb.stride = int(create.Pitch)
add := drmModeFBCmd{
width: uint32(fb.pw),
height: uint32(fb.ph),
pitch: create.pitch,
bpp: 32,
depth: 24,
handle: create.handle,
add := drm.ModeFBCmd{
Width: uint32(fb.pw),
Height: uint32(fb.ph),
Pitch: create.Pitch,
Bpp: 32,
Depth: 24,
Handle: create.Handle,
}
if err := drmIoctl(fb.fd, drmAddFB, unsafe.Pointer(&add)); err != nil {
if err := drm.Ioctl(fb.fd, drm.AddFB, unsafe.Pointer(&add)); err != nil {
return fmt.Errorf("add fb: %w", err)
}
fb.bufs[i].fbID = add.fbID
fb.bufs[i].fbID = add.FBID
m := drmModeMapDumb{handle: create.handle}
if err := drmIoctl(fb.fd, drmMapDumb, unsafe.Pointer(&m)); err != nil {
m := drm.ModeMapDumb{Handle: create.Handle}
if err := drm.Ioctl(fb.fd, drm.MapDumb, unsafe.Pointer(&m)); err != nil {
return fmt.Errorf("map dumb buffer: %w", err)
}
mem, err := unix.Mmap(fb.fd, int64(m.offset), int(create.size),
mem, err := unix.Mmap(fb.fd, int64(m.Offset), int(create.Size),
unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
if err != nil {
return fmt.Errorf("mmap scanout: %w", err)
@@ -322,7 +226,7 @@ func openFramebuffer() (*framebuffer, error) {
// Without master the modeset below is refused, and taking it is also what
// stops the kernel console drawing into the display behind us.
if err := drmIoctl(fd, drmSetMaster, nil); err != nil {
if err := drm.Ioctl(fd, drm.SetMaster, nil); err != nil {
unix.Close(fd)
return nil, fmt.Errorf("take drm master: %w", err)
}
@@ -338,7 +242,7 @@ func openFramebuffer() (*framebuffer, error) {
return nil, err
}
fb.connID, fb.crtcID = connID, crtcID
fb.pw, fb.ph = int(mode.hdisplay), int(mode.vdisplay)
fb.pw, fb.ph = int(mode.Hdisplay), int(mode.Vdisplay)
fb.w, fb.h = fb.ph, fb.pw
for i := range fb.bufs {
@@ -349,15 +253,15 @@ func openFramebuffer() (*framebuffer, error) {
}
fb.back = make([]byte, fb.stride*fb.ph)
set := drmModeCrtc{
setConnectorsPtr: uint64(uintptr(unsafe.Pointer(&fb.connID))),
countConnectors: 1,
crtcID: crtcID,
fbID: fb.bufs[0].fbID,
modeValid: 1,
mode: mode,
set := drm.ModeCrtc{
SetConnectorsPtr: uint64(uintptr(unsafe.Pointer(&fb.connID))),
CountConnectors: 1,
CrtcID: crtcID,
FBID: fb.bufs[0].fbID,
ModeValid: 1,
Mode: mode,
}
if err := drmIoctl(fd, drmSetCrtc, unsafe.Pointer(&set)); err != nil {
if err := drm.Ioctl(fd, drm.SetCrtc, unsafe.Pointer(&set)); err != nil {
fb.close()
return nil, fmt.Errorf("set crtc: %w", err)
}
@@ -382,7 +286,7 @@ func (fb *framebuffer) readEvents() {
if length < 8 || off+length > n {
return
}
if typ == drmEventFlipComplete {
if typ == drm.EventFlipComplete {
select {
case fb.flips <- struct{}{}:
default:
@@ -402,20 +306,18 @@ func (fb *framebuffer) close() {
// Dropping master hands the display back to the kernel console, which
// restores its own mode. The framebuffers and dumb buffers are reclaimed
// when the last reference to the fd goes.
drmIoctl(fb.fd, drmDropMaster, nil)
drm.Ioctl(fb.fd, drm.DropMaster, nil)
unix.Close(fb.fd)
}
func (fb *framebuffer) pixel(c rgb) uint32 {
return uint32(c.r)<<xrgbRedShift | uint32(c.g)<<xrgbGreenShift | uint32(c.b)<<xrgbBlueShift
}
type rgb struct {
r, g, b uint8
}
func pixel(c rgb) uint32 { return drm.Pack(c.r, c.g, c.b) }
func (fb *framebuffer) fill(c rgb) {
v := fb.pixel(c)
v := pixel(c)
row := make([]byte, fb.stride)
for x := 0; x+4 <= fb.stride; x += 4 {
row[x+0] = byte(v)
@@ -435,7 +337,7 @@ func (fb *framebuffer) rect(x0, y0, w, h int, c rgb) {
if x0 >= x1 || y0 >= y1 {
return
}
v := fb.pixel(c)
v := pixel(c)
span := make([]byte, (y1-y0)*4)
for i := 0; i+4 <= len(span); i += 4 {
span[i+0] = byte(v)
@@ -480,7 +382,7 @@ func (fb *framebuffer) blend(x, y int, c rgb, cov uint8) {
}
o := fb.offset(x, y)
if cov == 255 {
v := fb.pixel(c)
v := pixel(c)
fb.back[o+0] = byte(v)
fb.back[o+1] = byte(v >> 8)
fb.back[o+2] = byte(v >> 16)
@@ -490,15 +392,13 @@ func (fb *framebuffer) blend(x, y int, c rgb, cov uint8) {
a := uint32(cov)
old := uint32(fb.back[o+0]) | uint32(fb.back[o+1])<<8 |
uint32(fb.back[o+2])<<16 | uint32(fb.back[o+3])<<24
orr := uint8(old >> xrgbRedShift)
og := uint8(old >> xrgbGreenShift)
ob := uint8(old >> xrgbBlueShift)
orr, og, ob := drm.Unpack(old)
mix := rgb{
r: uint8((uint32(c.r)*a + uint32(orr)*(255-a)) / 255),
g: uint8((uint32(c.g)*a + uint32(og)*(255-a)) / 255),
b: uint8((uint32(c.b)*a + uint32(ob)*(255-a)) / 255),
}
v := fb.pixel(mix)
v := pixel(mix)
fb.back[o+0] = byte(v)
fb.back[o+1] = byte(v >> 8)
fb.back[o+2] = byte(v >> 16)
@@ -511,12 +411,12 @@ func (fb *framebuffer) flush() error {
next := (fb.front + 1) % scanoutBuffers
copy(fb.bufs[next].mem, fb.back)
flip := drmModeCrtcPageFlip{
crtcID: fb.crtcID,
fbID: fb.bufs[next].fbID,
flags: drmModePageFlipEvent,
flip := drm.ModeCrtcPageFlip{
CrtcID: fb.crtcID,
FBID: fb.bufs[next].fbID,
Flags: drm.PageFlipEvent,
}
if err := drmIoctl(fb.fd, drmPageFlip, unsafe.Pointer(&flip)); err != nil {
if err := drm.Ioctl(fb.fd, drm.PageFlip, unsafe.Pointer(&flip)); err != nil {
return fmt.Errorf("page flip: %w", err)
}
fb.front = next