1// Package syncobj wraps the few DRM syncobj ioctls the bar needs: one acquire
2// timeline signaled from the CPU, and one release timeline per buffer whose
3// points the compositor signals and an eventfd reports.
4package syncobj
5
6import (
7 "fmt"
8 "unsafe"
9
10 "golang.org/x/sys/unix"
11)
12
13// ioctl numbers of DRM_IOCTL_SYNCOBJ_* (Linux drm.h, type 'd').
14const (
15 ioctlCreate = uintptr(0xc00864bf)
16 ioctlDestroy = uintptr(0xc00864c0)
17 ioctlHandleToFD = uintptr(0xc01864c1)
18 ioctlQuery = uintptr(0xc01864cb)
19 ioctlTimelineSig = uintptr(0xc01864cd)
20 ioctlEventFD = uintptr(0xc01864cf)
21)
22
23type createArg struct{ Handle, Flags uint32 }
24type destroyArg struct{ Handle, Pad uint32 }
25type handleArg struct {
26 Handle, Flags uint32
27 FD int32
28 Pad uint32
29 Point uint64
30}
31type arrayArg struct { // drm_syncobj_timeline_array
32 Handles, Points uint64
33 Count, Flags uint32
34}
35type eventFDArg struct {
36 Handle, Flags uint32
37 Point uint64
38 FD int32
39 Pad uint32
40}
41
42// Node owns a DRM render node fd. It is used from one goroutine; its scratch
43// fields live in the heap object so the addresses embedded in ioctl arguments
44// stay valid and no call allocates.
45type Node struct {
46 fd int
47
48 handle uint32
49 point uint64
50 array arrayArg
51}
52
53// Open opens a render node such as /dev/dri/renderD128.
54func Open(path string) (*Node, error) {
55 fd, err := unix.Open(path, unix.O_RDWR|unix.O_CLOEXEC, 0)
56 if err != nil {
57 return nil, fmt.Errorf("syncobj: open %s: %w", path, err)
58 }
59 return &Node{fd: fd}, nil
60}
61
62// Close closes the render node.
63func (n *Node) Close() error {
64 if n.fd < 0 {
65 return nil
66 }
67 err := unix.Close(n.fd)
68 n.fd = -1
69 return err
70}
71
72func (n *Node) ioctl(request uintptr, arg unsafe.Pointer) error {
73 if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(n.fd), request, uintptr(arg)); errno != 0 {
74 return fmt.Errorf("syncobj: ioctl %#x: %w", request, errno)
75 }
76 return nil
77}
78
79// Create returns a new syncobj handle.
80func (n *Node) Create() (uint32, error) {
81 var a createArg
82 if err := n.ioctl(ioctlCreate, unsafe.Pointer(&a)); err != nil {
83 return 0, err
84 }
85 return a.Handle, nil
86}
87
88// Destroy releases a handle.
89func (n *Node) Destroy(handle uint32) error {
90 a := destroyArg{Handle: handle}
91 return n.ioctl(ioctlDestroy, unsafe.Pointer(&a))
92}
93
94// Export returns a new owned syncobj fd for handle; the caller closes it.
95func (n *Node) Export(handle uint32) (int, error) {
96 a := handleArg{Handle: handle, FD: -1}
97 if err := n.ioctl(ioctlHandleToFD, unsafe.Pointer(&a)); err != nil {
98 return -1, err
99 }
100 return int(a.FD), nil
101}
102
103// Signal signals one timeline point from the CPU.
104func (n *Node) Signal(handle uint32, point uint64) error {
105 n.handle, n.point = handle, point
106 n.array = arrayArg{
107 Handles: uint64(uintptr(unsafe.Pointer(&n.handle))),
108 Points: uint64(uintptr(unsafe.Pointer(&n.point))),
109 Count: 1,
110 }
111 return n.ioctl(ioctlTimelineSig, unsafe.Pointer(&n.array))
112}
113
114// Query returns the last signaled point of a timeline.
115func (n *Node) Query(handle uint32) (uint64, error) {
116 n.handle, n.point = handle, 0
117 n.array = arrayArg{
118 Handles: uint64(uintptr(unsafe.Pointer(&n.handle))),
119 Points: uint64(uintptr(unsafe.Pointer(&n.point))),
120 Count: 1,
121 }
122 if err := n.ioctl(ioctlQuery, unsafe.Pointer(&n.array)); err != nil {
123 return 0, err
124 }
125 return n.point, nil
126}
127
128// EventFD arms efd to become readable once point is signaled. The registration
129// is one-shot and returns at once; the point need not be submitted yet.
130func (n *Node) EventFD(handle uint32, point uint64, efd int) error {
131 a := eventFDArg{Handle: handle, Point: point, FD: int32(efd)}
132 return n.ioctl(ioctlEventFD, unsafe.Pointer(&a))
133}