main f2d166b

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		{"create_dumb", unsafe.Sizeof(createDumb{}), ioctlCreateDumb},
36		{"map_dumb", unsafe.Sizeof(mapDumb{}), ioctlMapDumb},
37		{"destroy_dumb", unsafe.Sizeof(uint32(0)), ioctlDestroyDumb},
38		{"destroy_blob", unsafe.Sizeof(uint32(0)), ioctlDestroyBlob},
39	} {
40		if want := (tc.req >> 16) & 0x3fff; tc.got != want {
41			t.Errorf("%s: size %d, ioctl encodes %d", tc.name, tc.got, want)
42		}
43	}
44	if unsafe.Sizeof(modeInfo{}) != 68 {
45		t.Errorf("modeInfo size %d, want 68", unsafe.Sizeof(modeInfo{}))
46	}
47}
48
49func TestWantPicksMode(t *testing.T) {
50	mode := func(w, h uint16, hz uint32, pref bool) modeInfo {
51		// htotal*vtotal = w*h keeps refreshMilli = clock*1000/(w*h).
52		m := modeInfo{HDisplay: w, VDisplay: h, HTotal: w, VTotal: h, Clock: uint32(uint64(w) * uint64(h) * uint64(hz) / 1000), VRefresh: hz}
53		if pref {
54			m.Type = modeTypePrefered
55		}
56		return m
57	}
58	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)}}
59	off := connector{name: "DP-1", modes: []modeInfo{mode(1920, 1080, 60, true)}}
60	for _, tc := range []struct {
61		name string
62		want Want
63		mode string
64	}{
65		{"first preferred", Want{}, "5120x2160@165.000"},
66		{"closest refresh", Want{Modes: map[string][3]float64{"DP-2": {5120, 2160, 99}}}, "5120x2160@100.000"},
67		{"highest refresh", Want{Modes: map[string][3]float64{"DP-2": {5120, 2160, 0}}}, "5120x2160@165.000"},
68		{"unknown mode falls back to preferred", Want{Modes: map[string][3]float64{"DP-2": {800, 600, 0}}}, "5120x2160@165.000"},
69	} {
70		if m := tc.want.pickMode(lg); m.String() != tc.mode {
71			t.Errorf("%s: got %s", tc.name, m)
72		}
73	}
74	vr := lg
75	vr.nonDesktop = true
76	if (Want{}).usable(off) || !(Want{}).usable(lg) || (Want{Disabled: map[string]bool{"DP-2": true}}).usable(lg) || (Want{}).usable(vr) {
77		t.Fatal("usable: non-desktop connectors must not be modeset")
78	}
79}
80
81func TestRefreshMilliLG(t *testing.T) {
82	// Real 5120x2160@165.058 CVT-RB timings.
83	m := modeInfo{Clock: 1_989_530, HDisplay: 5120, HTotal: 5200, VDisplay: 2160, VTotal: 2318}
84	if got := m.refreshMilli(); got < 165_000 || got > 165_100 {
85		t.Fatalf("refresh %d mHz", got)
86	}
87}
88
89func TestParseEDID(t *testing.T) {
90	d := make([]byte, 128)
91	copy(d, "\x00\xff\xff\xff\xff\xff\xff\x00")
92	d[8], d[9] = 0x1e, 0x6d // GSM
93	copy(d[54:], []byte{0, 0, 0, 0xfc, 0})
94	copy(d[59:], "LG ULTRAGEAR+\n")
95	copy(d[72:], []byte{0, 0, 0, 0xff, 0})
96	copy(d[77:], "SN123\n       ")
97	m := parseEDID(d)
98	if m.Model != "LG ULTRAGEAR+" || m.Serial != "SN123" || (m.Make != "GSM" && m.Make != "LG Electronics") {
99		t.Fatalf("%+v", m)
100	}
101}
102
103func TestParseFlips(t *testing.T) {
104	ev := func(typ, crtc uint32, user uint64, sec, usec, seq uint32) []byte {
105		b := make([]byte, 32)
106		le := binary.LittleEndian
107		le.PutUint32(b, typ)
108		le.PutUint32(b[4:], 32)
109		le.PutUint64(b[8:], user)
110		le.PutUint32(b[16:], sec)
111		le.PutUint32(b[20:], usec)
112		le.PutUint32(b[24:], seq)
113		le.PutUint32(b[28:], crtc)
114		return b
115	}
116	buf := append(append(ev(eventFlipDone, 41, userFrame, 3, 250, 7), ev(1, 7, 0, 0, 0, 0)...), ev(eventFlipDone, 42, userState, 0, 1, 8)...)
117	got := parseFlips(nil, buf)
118	want := []flipEvent{{crtc: 41, user: userFrame, when: 3*time.Second + 250*time.Microsecond, seq: 7}, {crtc: 42, user: userState, when: time.Microsecond, seq: 8}}
119	if !slices.Equal(got, want) {
120		t.Fatalf("got %+v, want %+v", got, want)
121	}
122	if got := parseFlips(nil, buf[:20]); len(got) != 0 {
123		t.Fatalf("truncated: got %v", got)
124	}
125	// ReadEvents reuses one slice across reads.
126	reused := parseFlips(nil, buf)
127	if n := testing.AllocsPerRun(100, func() { reused = parseFlips(reused[:0], buf) }); n != 0 || !slices.Equal(reused, want) {
128		t.Fatalf("reused parse: %.1f allocs, got %+v", n, reused)
129	}
130}
131
132func TestParseInFormats(t *testing.T) {
133	le := binary.LittleEndian
134	// 3 formats at 24, 2 modifiers at 40.
135	b := make([]byte, 40+2*24)
136	le.PutUint32(b, 1)
137	le.PutUint32(b[8:], 3)
138	le.PutUint32(b[12:], 24)
139	le.PutUint32(b[16:], 2)
140	le.PutUint32(b[20:], 40)
141	for i, f := range []uint32{fourccXRGB, fourccARGB, 0x3432564e} {
142		le.PutUint32(b[24+i*4:], f)
143	}
144	// linear on formats 0 and 2, a tiled modifier on format 1.
145	le.PutUint64(b[40:], 0b101)
146	le.PutUint64(b[56:], 0)
147	le.PutUint64(b[64:], 0b010)
148	le.PutUint64(b[80:], 0x0200000000000001)
149	got := parseInFormats(b)
150	want := []ports.DMABufFormat{{Format: fourccXRGB}, {Format: 0x3432564e}, {Format: fourccARGB, Modifier: 0x0200000000000001}}
151	if !slices.Equal(got, want) {
152		t.Fatalf("got %+v, want %+v", got, want)
153	}
154	if parseInFormats(b[:30]) != nil {
155		t.Fatal("truncated blob parsed")
156	}
157}

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