kms_test.go RawHistoryFolder

1package drm
2
3import (
4	"encoding/binary"
5	"slices"
6	"testing"
7	"time"
8	"unsafe"
9
10	"github.com/bnema/neferwl/internal/ports"
11)
12
13func TestStructSizesMatchIoctls(t *testing.T) {
14	for _, tc := range []struct {
15		name string
16		got  uintptr
17		req  uintptr
18	}{
19		{"card_res", unsafe.Sizeof(cardRes{}), ioctlGetResources},
20		{"get_encoder", unsafe.Sizeof(getEncoder{}), ioctlGetEncoder},
21		{"get_connector", unsafe.Sizeof(getConnector{}), ioctlGetConnector},
22		{"fb_cmd2", unsafe.Sizeof(fbCmd2{}), ioctlAddFB2},
23		{"prime_handle", unsafe.Sizeof(primeHandle{}), ioctlPrimeFDToHandle},
24		{"gem_close", unsafe.Sizeof(gemClose{}), ioctlGemClose},
25		{"obj_get_props", unsafe.Sizeof(objGetProps{}), ioctlObjGetProps},
26		{"get_prop", unsafe.Sizeof(getProp{}), ioctlGetProp},
27		{"mode_crtc", unsafe.Sizeof(modeCrtc{}), ioctlGetCrtc},
28		{"set_client_cap", unsafe.Sizeof(setClientCap{}), ioctlSetClientCap},
29		{"get_plane_res", unsafe.Sizeof(getPlaneRes{}), ioctlGetPlaneRes},
30		{"get_plane", unsafe.Sizeof(getPlane{}), ioctlGetPlane},
31		{"get_blob", unsafe.Sizeof(getBlob{}), ioctlGetPropBlob},
32		{"atomic", unsafe.Sizeof(modeAtomic{}), ioctlAtomic},
33		{"create_blob", unsafe.Sizeof(createBlob{}), ioctlCreateBlob},
34		{"get_cap", unsafe.Sizeof(getCap{}), ioctlGetCap},
35		{"destroy_blob", unsafe.Sizeof(uint32(0)), ioctlDestroyBlob},
36	} {
37		if want := (tc.req >> 16) & 0x3fff; tc.got != want {
38			t.Errorf("%s: size %d, ioctl encodes %d", tc.name, tc.got, want)
39		}
40	}
41	if unsafe.Sizeof(modeInfo{}) != 68 {
42		t.Errorf("modeInfo size %d, want 68", unsafe.Sizeof(modeInfo{}))
43	}
44}
45
46func TestWantPicksMode(t *testing.T) {
47	mode := func(w, h uint16, hz uint32, pref bool) modeInfo {
48		// htotal*vtotal = w*h keeps refreshMilli = clock*1000/(w*h).
49		m := modeInfo{HDisplay: w, VDisplay: h, HTotal: w, VTotal: h, Clock: uint32(uint64(w) * uint64(h) * uint64(hz) / 1000), VRefresh: hz}
50		if pref {
51			m.Type = modeTypePrefered
52		}
53		return m
54	}
55	lg := connector{name: "DP-2", connected: true, modes: []modeInfo{mode(5120, 2160, 165, true), mode(3440, 1440, 100, true), mode(5120, 2160, 100, false), mode(5120, 2160, 60, false)}}
56	off := connector{name: "DP-1", modes: []modeInfo{mode(1920, 1080, 60, true)}}
57	for _, tc := range []struct {
58		name string
59		want Want
60		mode string
61	}{
62		{"first preferred", Want{}, "5120x2160@165.000"},
63		{"closest refresh", Want{Modes: map[string][3]float64{"DP-2": {5120, 2160, 99}}}, "5120x2160@100.000"},
64		{"highest refresh", Want{Modes: map[string][3]float64{"DP-2": {5120, 2160, 0}}}, "5120x2160@165.000"},
65		{"unknown mode falls back to preferred", Want{Modes: map[string][3]float64{"DP-2": {800, 600, 0}}}, "5120x2160@165.000"},
66	} {
67		if m := tc.want.pickMode(lg); m.String() != tc.mode {
68			t.Errorf("%s: got %s", tc.name, m)
69		}
70	}
71	vr := lg
72	vr.nonDesktop = true
73	if (Want{}).usable(off) || !(Want{}).usable(lg) || (Want{Disabled: map[string]bool{"DP-2": true}}).usable(lg) || (Want{}).usable(vr) {
74		t.Fatal("usable: non-desktop connectors must not be modeset")
75	}
76}
77
78func TestRefreshMilliLG(t *testing.T) {
79	// Real 5120x2160@165.058 CVT-RB timings.
80	m := modeInfo{Clock: 1_989_530, HDisplay: 5120, HTotal: 5200, VDisplay: 2160, VTotal: 2318}
81	if got := m.refreshMilli(); got < 165_000 || got > 165_100 {
82		t.Fatalf("refresh %d mHz", got)
83	}
84}
85
86func TestParseEDID(t *testing.T) {
87	d := make([]byte, 128)
88	copy(d, "\x00\xff\xff\xff\xff\xff\xff\x00")
89	d[8], d[9] = 0x1e, 0x6d // GSM
90	copy(d[54:], []byte{0, 0, 0, 0xfc, 0})
91	copy(d[59:], "LG ULTRAGEAR+\n")
92	copy(d[72:], []byte{0, 0, 0, 0xff, 0})
93	copy(d[77:], "SN123\n       ")
94	m := parseEDID(d)
95	if m.Model != "LG ULTRAGEAR+" || m.Serial != "SN123" || (m.Make != "GSM" && m.Make != "LG Electronics") {
96		t.Fatalf("%+v", m)
97	}
98}
99
100func TestParseFlips(t *testing.T) {
101	ev := func(typ, crtc uint32, user uint64, sec, usec, seq uint32) []byte {
102		b := make([]byte, 32)
103		le := binary.LittleEndian
104		le.PutUint32(b, typ)
105		le.PutUint32(b[4:], 32)
106		le.PutUint64(b[8:], user)
107		le.PutUint32(b[16:], sec)
108		le.PutUint32(b[20:], usec)
109		le.PutUint32(b[24:], seq)
110		le.PutUint32(b[28:], crtc)
111		return b
112	}
113	buf := append(append(ev(eventFlipDone, 41, userFrame, 3, 250, 7), ev(1, 7, 0, 0, 0, 0)...), ev(eventFlipDone, 42, userState, 0, 1, 8)...)
114	got := parseFlips(buf)
115	want := []flipEvent{{crtc: 41, user: userFrame, when: 3*time.Second + 250*time.Microsecond, seq: 7}, {crtc: 42, user: userState, when: time.Microsecond, seq: 8}}
116	if !slices.Equal(got, want) {
117		t.Fatalf("got %+v, want %+v", got, want)
118	}
119	if got := parseFlips(buf[:20]); len(got) != 0 {
120		t.Fatalf("truncated: got %v", got)
121	}
122}
123
124func TestParseInFormats(t *testing.T) {
125	le := binary.LittleEndian
126	// 3 formats at 24, 2 modifiers at 40.
127	b := make([]byte, 40+2*24)
128	le.PutUint32(b, 1)
129	le.PutUint32(b[8:], 3)
130	le.PutUint32(b[12:], 24)
131	le.PutUint32(b[16:], 2)
132	le.PutUint32(b[20:], 40)
133	for i, f := range []uint32{fourccXRGB, fourccARGB, 0x3432564e} {
134		le.PutUint32(b[24+i*4:], f)
135	}
136	// linear on formats 0 and 2, a tiled modifier on format 1.
137	le.PutUint64(b[40:], 0b101)
138	le.PutUint64(b[56:], 0)
139	le.PutUint64(b[64:], 0b010)
140	le.PutUint64(b[80:], 0x0200000000000001)
141	got := parseInFormats(b)
142	want := []ports.DMABufFormat{{Format: fourccXRGB}, {Format: 0x3432564e}, {Format: fourccARGB, Modifier: 0x0200000000000001}}
143	if !slices.Equal(got, want) {
144		t.Fatalf("got %+v, want %+v", got, want)
145	}
146	if parseInFormats(b[:30]) != nil {
147		t.Fatal("truncated blob parsed")
148	}
149}

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