v0.7.1 e0abc49

syncobj.go RawHistoryFolder

1//go:build linux
2
3// Package syncobj bridges Vulkan SYNC_FD semaphores and Wayland DRM syncobj
4// timelines on the compositor-selected render node. It never interprets a
5// Vulkan OPAQUE_FD as a DRM syncobj FD.
6package syncobj
7
8import (
9	"fmt"
10	"runtime"
11	"time"
12	"unsafe"
13
14	"golang.org/x/sys/unix"
15)
16
17// These ABI layouts and request numbers correspond to Linux drm.h syncobj
18// ioctls (DRM_IOWR, type 'd'). They are intentionally fixed-width; keep the
19// layout tests aligned with the pinned host UAPI when updating the bridge.
20type createArg struct{ Handle, Flags uint32 }
21type destroyArg struct{ Handle, Pad uint32 }
22type handleArg struct {
23	Handle, Flags uint32
24	FD            int32
25	Pad           uint32
26	Point         uint64
27}
28type transferArg struct {
29	Src, Dst           uint32
30	SrcPoint, DstPoint uint64
31	Flags, Pad         uint32
32}
33type timelineWaitArg struct {
34	Handles, Points          uint64
35	TimeoutNS                int64
36	Count, Flags, First, Pad uint32
37	DeadlineNS               uint64
38}
39type queryArg struct {
40	Handles, Points uint64
41	Count, Flags    uint32
42}
43
44// eventFDArg is struct drm_syncobj_eventfd: the kernel signals fd once the
45// timeline point is signaled (flags 0) without any thread waiting on it.
46type eventFDArg struct {
47	Handle, Flags uint32
48	Point         uint64
49	FD            int32
50	Pad           uint32
51}
52
53const (
54	ioctlCreate       = uintptr(0xc00864bf)
55	ioctlDestroy      = uintptr(0xc00864c0)
56	ioctlHandleToFD   = uintptr(0xc01864c1)
57	ioctlFDToHandle   = uintptr(0xc01864c2)
58	ioctlTimelineWait = uintptr(0xc03064ca)
59	ioctlQuery        = uintptr(0xc01864cb)
60	ioctlTransfer     = uintptr(0xc02064cc)
61	ioctlTimelineSig  = uintptr(0xc01864cd)
62	ioctlEventFD      = uintptr(0xc01864cf)
63	importSyncFile    = 1
64	exportSyncFile    = 1
65)
66
67// Node owns the DRM render fd. Only open the render node of the Vulkan device
68// matched to linux-dmabuf main_device. Never substitute a different GPU.
69type Node struct{ fd int }
70
71func Open(path string) (*Node, error) {
72	fd, err := unix.Open(path, unix.O_RDWR|unix.O_CLOEXEC, 0)
73	if err != nil {
74		return nil, fmt.Errorf("open matching render node %s: %w", path, err)
75	}
76	return &Node{fd: fd}, nil
77}
78
79func (n *Node) Close() error {
80	if n.fd < 0 {
81		return nil
82	}
83	err := unix.Close(n.fd)
84	n.fd = -1
85	return err
86}
87
88// ioctlErrno performs the ioctl and returns the raw errno without allocating.
89// Embedded addresses must not move: no split-checked call may occur between
90// encoding them and the syscall. KeepAlive alone does not prevent stack moves.
91//
92//go:nosplit
93func (n *Node) ioctlErrno(request uintptr, arg unsafe.Pointer) unix.Errno {
94	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(n.fd), request, uintptr(arg))
95	return errno
96}
97
98// ioctlError adds request context to a real errno. Only failure paths pay for it.
99func ioctlError(request uintptr, errno unix.Errno) error {
100	return fmt.Errorf("DRM syncobj ioctl %#x: %w", request, errno)
101}
102
103func (n *Node) ioctl(request uintptr, arg unsafe.Pointer) error {
104	if errno := n.ioctlErrno(request, arg); errno != 0 {
105		return ioctlError(request, errno)
106	}
107	return nil
108}
109
110func (n *Node) Create() (uint32, error) {
111	var a createArg
112	if err := n.ioctl(ioctlCreate, unsafe.Pointer(&a)); err != nil {
113		return 0, err
114	}
115	return a.Handle, nil
116}
117func (n *Node) Destroy(handle uint32) error {
118	return n.ioctl(ioctlDestroy, unsafe.Pointer(&destroyArg{Handle: handle}))
119}
120
121// ExportTimeline returns a new owned syncobj FD (not a sync_file). The
122// caller owns it and closes it, or passes a duplicate to its Wayland client.
123func (n *Node) ExportTimeline(handle uint32) (int, error) {
124	a := handleArg{Handle: handle, FD: -1}
125	if err := n.ioctl(ioctlHandleToFD, unsafe.Pointer(&a)); err != nil {
126		return -1, err
127	}
128	return int(a.FD), nil
129}
130
131// EventFD arms efd, an eventfd owned by the caller, to become readable when
132// point of the timeline behind handle is signaled. The point need not be
133// submitted yet. The registration is one-shot: re-arm after every use. The
134// call returns at once and never waits.
135func (n *Node) EventFD(handle uint32, point uint64, efd int) error {
136	a := eventFDArg{Handle: handle, Point: point, FD: int32(efd)}
137	return n.ioctl(ioctlEventFD, unsafe.Pointer(&a))
138}
139
140// Signal signals one timeline point from the CPU (DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL).
141// The compositor does this through its release timeline; clients use it only
142// to simulate that in tests.
143func (n *Node) Signal(handle uint32, point uint64) error {
144	h, p := handle, point
145	a := queryArg{Handles: uint64(uintptr(unsafe.Pointer(&h))), Points: uint64(uintptr(unsafe.Pointer(&p))), Count: 1}
146	errno := n.ioctlErrno(ioctlTimelineSig, unsafe.Pointer(&a))
147	runtime.KeepAlive(h)
148	runtime.KeepAlive(p)
149	if errno != 0 {
150		return ioctlError(ioctlTimelineSig, errno)
151	}
152	return nil
153}
154
155// ImportFence consumes syncFile only after FD_TO_HANDLE imports it into a
156// pre-created destination syncobj. With IMPORT_SYNC_FILE, Handle is an input,
157// not an output: asking the kernel to import into handle zero returns ENOENT.
158// The caller retains syncFile if the ioctl rejects it.
159func (n *Node) ImportFence(syncFile int) (uint32, error) {
160	handle, err := n.Create()
161	if err != nil {
162		return 0, err
163	}
164	a := handleArg{Handle: handle, Flags: importSyncFile, FD: int32(syncFile)}
165	if err = n.ioctl(ioctlFDToHandle, unsafe.Pointer(&a)); err != nil {
166		_ = n.Destroy(handle)
167		return 0, err
168	}
169	if err = unix.Close(syncFile); err != nil {
170		_ = n.Destroy(handle)
171		return 0, err
172	}
173	return handle, nil
174}
175
176func (n *Node) Transfer(src uint32, srcPoint uint64, dst uint32, dstPoint uint64) error {
177	a := transferArg{Src: src, Dst: dst, SrcPoint: srcPoint, DstPoint: dstPoint}
178	return n.ioctl(ioctlTransfer, unsafe.Pointer(&a))
179}
180
181// AcquirePoint transfers the Vulkan submission's exported SYNC_FD into the
182// persistent acquire timeline. The caller retains syncFile on import error.
183func (n *Node) AcquirePoint(syncFile int, timeline uint32, point uint64) error {
184	tmp, err := n.ImportFence(syncFile)
185	if err != nil {
186		return err
187	}
188	defer n.Destroy(tmp)
189	return n.Transfer(tmp, 0, timeline, point)
190}
191
192// ReleaseFence exports a compositor release point as a sync_file suitable for
193// TEMPORARY Vulkan semaphore import. The caller owns the returned FD until
194// Vulkan accepts the import; failed imports must close it.
195func (n *Node) ReleaseFence(timeline uint32, point uint64) (int, error) {
196	tmp, err := n.Create()
197	if err != nil {
198		return -1, err
199	}
200	defer n.Destroy(tmp)
201	if err = n.Transfer(timeline, point, tmp, 0); err != nil {
202		return -1, err
203	}
204	a := handleArg{Handle: tmp, Flags: exportSyncFile, FD: -1}
205	if err = n.ioctl(ioctlHandleToFD, unsafe.Pointer(&a)); err != nil {
206		return -1, err
207	}
208	return int(a.FD), nil
209}
210
211// WaitPoint waits for a submitted release point for at most timeout. Call it
212// only off the render path: it can block for up to timeout. WAIT_FOR_SUBMIT
213// handles the interval before the compositor attaches its fence to the point.
214func (n *Node) WaitPoint(handle uint32, point uint64, timeout time.Duration) (bool, error) {
215	if timeout < 0 || timeout > time.Second {
216		return false, fmt.Errorf("invalid syncobj wait timeout %s", timeout)
217	}
218	h := handle
219	p := point
220	var now unix.Timespec
221	if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &now); err != nil {
222		return false, err
223	}
224	deadline := now.Nano() + timeout.Nanoseconds()
225	a := timelineWaitArg{Handles: uint64(uintptr(unsafe.Pointer(&h))), Points: uint64(uintptr(unsafe.Pointer(&p))), TimeoutNS: deadline, Count: 1, Flags: 2}
226	errno := n.ioctlErrno(ioctlTimelineWait, unsafe.Pointer(&a))
227	runtime.KeepAlive(h)
228	runtime.KeepAlive(p)
229	return waitResult(errno)
230}
231
232// waitResult maps a TIMELINE_WAIT errno. ETIME and EBUSY are the expected idle
233// timeout outcomes and build no error value; anything else keeps its context.
234func waitResult(errno unix.Errno) (bool, error) {
235	switch errno {
236	case 0:
237		return true, nil
238	case unix.ETIME, unix.EBUSY:
239		return false, nil
240	default:
241		return false, ioctlError(ioctlTimelineWait, errno)
242	}
243}
244
245// Signaled performs a zero-time wait for tests and shutdown diagnostics.
246func (n *Node) Signaled(handle uint32, point uint64) (bool, error) {
247	return n.WaitPoint(handle, point, 0)
248}
249
250// Query returns the last signaled point. This is diagnostic only: timeline
251// wait, not an optimistic query, authorizes release.
252func (n *Node) Query(handle uint32) (uint64, error) {
253	h := handle
254	var point uint64
255	a := queryArg{Handles: uint64(uintptr(unsafe.Pointer(&h))), Points: uint64(uintptr(unsafe.Pointer(&point))), Count: 1}
256	errno := n.ioctlErrno(ioctlQuery, unsafe.Pointer(&a))
257	runtime.KeepAlive(h)
258	runtime.KeepAlive(point)
259	if errno != 0 {
260		return 0, ioctlError(ioctlQuery, errno)
261	}
262	return point, nil
263}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 12

↑↓ moveenter openesc close