feat/data-dnd e05cb15

lease.go RawHistoryFolder

1package drm
2
3import (
4	"errors"
5	"fmt"
6	"os"
7	"runtime"
8	"slices"
9	"unsafe"
10
11	"github.com/bnema/neferwl/internal/ports"
12	"golang.org/x/sys/unix"
13)
14
15const (
16	ioctlCreateLease = 0xC01864C6
17	ioctlRevokeLease = 0xC00464C9
18	ioctlDropMaster  = 0x641f
19)
20
21type modeCreateLease struct {
22	objects                  uint64
23	count, flags, lessee, fd uint32
24}
25type modeRevokeLease struct{ lessee uint32 }
26
27func (k kmsDevice) createLease(objects []uint32) (*os.File, uint32, error) {
28	if len(objects) == 0 {
29		return nil, 0, errors.New("empty lease")
30	}
31	q := modeCreateLease{objects: uint64(uintptr(unsafe.Pointer(&objects[0]))), count: uint32(len(objects)), flags: unix.O_CLOEXEC}
32	err := ioctl(k.fd, ioctlCreateLease, unsafe.Pointer(&q))
33	runtime.KeepAlive(objects)
34	if err != nil {
35		return nil, 0, err
36	}
37	return os.NewFile(uintptr(q.fd), "drm lease"), q.lessee, nil
38}
39func (k kmsDevice) revokeLease(id uint32) error {
40	return ioctl(k.fd, ioctlRevokeLease, unsafe.Pointer(&modeRevokeLease{lessee: id}))
41}
42func (k kmsDevice) resources() ([]uint32, []uint32, error) { return resources(k.fd) }
43func (k kmsDevice) connector(id uint32) (connector, error) { return readConnector(k.fd, id) }
44func (k kmsDevice) pickCrtc(c connector, crtcs []uint32, used map[uint32]bool) (uint32, error) {
45	return pickCrtc(k.fd, c, crtcs, used)
46}
47
48type leaseRecord struct {
49	names         []string
50	crtcs, planes []uint32
51}
52
53// Leasable returns connected non-desktop connectors not held by a lease.
54// Scan and lease operations share one owner goroutine.
55func (c *Card) Leasable() []ports.LeaseConnector {
56	var result []ports.LeaseConnector
57	for _, conn := range c.leasable {
58		if !conn.connected || c.leasedName(conn.name) {
59			continue
60		}
61		m := readMonitor(c.path, conn.name)
62		result = append(result, ports.LeaseConnector{Card: c.path, Name: conn.name, Description: fmt.Sprintf("%s %s", m.Make, m.Model), ConnectorID: conn.id})
63	}
64	slices.SortFunc(result, func(a, b ports.LeaseConnector) int {
65		if a.Name < b.Name {
66			return -1
67		}
68		if a.Name > b.Name {
69			return 1
70		}
71		return 0
72	})
73	return result
74}
75func (c *Card) leasedName(name string) bool {
76	for _, l := range c.leases {
77		if slices.Contains(l.names, name) {
78			return true
79		}
80	}
81	return false
82}
83func (c *Card) Lease(names []string) (*os.File, uint32, error) {
84	if len(names) == 0 {
85		return nil, 0, errors.New("empty lease")
86	}
87	used := map[uint32]bool{}
88	planesUsed := map[uint32]bool{}
89	for _, o := range c.outputs {
90		used[o.crtc] = true
91		for _, p := range o.owned() {
92			planesUsed[p.id] = true
93		}
94	}
95	for _, l := range c.leases {
96		for _, id := range l.crtcs {
97			used[id] = true
98		}
99		for _, id := range l.planes {
100			planesUsed[id] = true
101		}
102	}
103	objects := make([]uint32, 0, len(names)*3)
104	record := leaseRecord{}
105	seen := map[string]bool{}
106	for _, name := range names {
107		if seen[name] || c.leasedName(name) {
108			return nil, 0, fmt.Errorf("connector %s unavailable", name)
109		}
110		seen[name] = true
111		var conn connector
112		found := false
113		for _, v := range c.leasable {
114			if v.name == name && v.connected {
115				conn = v
116				found = true
117				break
118			}
119		}
120		if !found {
121			return nil, 0, fmt.Errorf("connector %s unavailable", name)
122		}
123		crtc, err := c.k.pickCrtc(conn, c.crtcs, used)
124		if err != nil {
125			return nil, 0, err
126		}
127		pipe := slices.Index(c.crtcs, crtc)
128		if pipe < 0 {
129			return nil, 0, fmt.Errorf("CRTC %d not on card", crtc)
130		}
131		ps, err := readPlanes(c.k, pipe)
132		if err != nil {
133			return nil, 0, err
134		}
135		var planeID uint32
136		for _, p := range ps {
137			if p.typ == planePrimary && !planesUsed[p.id] && !c.taken[p.id] {
138				planeID = p.id
139				break
140			}
141		}
142		if planeID == 0 {
143			return nil, 0, fmt.Errorf("no primary plane for %s", name)
144		}
145		used[crtc] = true
146		planesUsed[planeID] = true
147		record.names = append(record.names, name)
148		record.crtcs = append(record.crtcs, crtc)
149		record.planes = append(record.planes, planeID)
150		objects = append(objects, conn.id, crtc, planeID)
151	}
152	fd, id, err := c.k.createLease(objects)
153	if err != nil {
154		return nil, 0, err
155	}
156	if c.leases == nil {
157		c.leases = map[uint32]leaseRecord{}
158	}
159	c.leases[id] = record
160	for _, plane := range record.planes {
161		c.taken[plane] = true
162	}
163	return fd, id, nil
164}
165func (c *Card) Revoke(id uint32) error {
166	record, ok := c.leases[id]
167	if !ok {
168		return nil
169	}
170	if err := c.k.revokeLease(id); err != nil {
171		return err
172	}
173	for _, plane := range record.planes {
174		delete(c.taken, plane)
175	}
176	delete(c.leases, id)
177	return nil
178}
179func (c *Card) LeaseIDs() []uint32 {
180	ids := make([]uint32, 0, len(c.leases))
181	for id := range c.leases {
182		ids = append(ids, id)
183	}
184	slices.Sort(ids)
185	return ids
186}
187
188// ClientFD opens a separate card fd and makes sure it is not a DRM master.
189func (c *Card) ClientFD() (*os.File, error) {
190	fd, err := unix.Open(c.path, unix.O_RDWR|unix.O_CLOEXEC, 0)
191	if err != nil {
192		return nil, err
193	}
194	if err = ioctl(fd, ioctlDropMaster, nil); err != nil && !errors.Is(err, unix.EINVAL) && !errors.Is(err, unix.EACCES) {
195		unix.Close(fd)
196		return nil, err
197	}
198	return os.NewFile(uintptr(fd), c.path), nil
199}
200
201// FinishedLeases drains IDs revoked after a connector disappeared during Scan.
202func (c *Card) FinishedLeases() []uint32 {
203	ids := c.finished
204	c.finished = nil
205	return ids
206}

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 25

Tags 10

↑↓ moveenter openesc close