xref: /aosp_15_r20/external/coreboot/util/board_status/go/src/cbtables/cbtables.go (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1package cbtables
2
3import (
4	"bytes"
5	"encoding/binary"
6	"fmt"
7	"io"
8	"os"
9	"runtime"
10	"strings"
11	"time"
12)
13
14type Header struct {
15	Signature      [4]uint8 /* LBIO */
16	HeaderBytes    uint32
17	HeaderChecksum uint32
18	TableBytes     uint32
19	TableChecksum  uint32
20	TableEntries   uint32
21}
22
23type Record struct {
24	Tag  uint32
25	Size uint32
26}
27
28type rawTable struct {
29	record  Record
30	payload []byte
31}
32
33type parsedTables struct {
34	mem     *os.File
35	raw     []rawTable
36	typeMap map[uint32][]byte
37}
38
39var headerSignature [4]byte = [4]byte{'L', 'B', 'I', 'O'}
40
41const HeaderSize = 24
42const (
43	TagVersion    = 0x0004
44	TagForward    = 0x0011
45	TagTimestamps = 0x0016
46	TagConsole    = 0x0017
47	TagVersionTimestamp = 0x0026
48)
49
50type CBTablesReader interface {
51	GetConsole() (cons []byte, lost uint32, err error)
52	GetTimestamps() (*TimeStamps, error)
53	GetVersion() (string, error)
54	GetVersionTimestamp() (time.Time, error)
55}
56
57type CBMemConsole struct {
58	Size   uint32
59	Cursor uint32
60}
61
62type TimeStampEntry struct {
63	EntryID    uint32
64	EntryStamp uint64
65}
66
67type TimeStampHeader struct {
68	BaseTime   uint64
69	MaxEntries uint32
70	NumEntries uint32
71}
72
73type TimeStamps struct {
74	Head         TimeStampHeader
75	Entries      []TimeStampEntry
76	FrequencyMHZ uint32
77}
78
79var timeStampNames map[uint32]string = map[uint32]string{
80	1:    "start of rom stage",
81	2:    "before ram initialization",
82	3:    "after ram initialization",
83	4:    "end of romstage",
84	5:    "start of verified boot",
85	6:    "end of verified boot",
86	8:    "start of copying ram stage",
87	9:    "end of copying ram stage",
88	10:   "start of ramstage",
89	30:   "device enumeration",
90	40:   "device configuration",
91	50:   "device enable",
92	60:   "device initialization",
93	70:   "device setup done",
94	75:   "cbmem post",
95	80:   "write tables",
96	90:   "load payload",
97	98:   "ACPI wake jump",
98	99:   "selfboot jump",
99	1000: "depthcharge start",
100	1001: "RO parameter init",
101	1002: "RO vboot init",
102	1003: "RO vboot select firmware",
103	1004: "RO vboot select&load kernel",
104	1010: "RW vboot select&load kernel",
105	1020: "vboot select&load kernel",
106	1100: "crossystem data",
107	1101: "start kernel",
108}
109
110func formatSep(val uint64) string {
111	ret := ""
112	for val > 1000 {
113		ret = fmt.Sprintf(",%03d", val%1000) + ret
114		val /= 1000
115	}
116	ret = fmt.Sprintf("%d", val) + ret
117	return ret
118}
119
120func formatElapsedTime(ticks uint64, frequency uint32) string {
121	if frequency == 0 {
122		return formatSep(ticks) + " cycles"
123	}
124	us := ticks / uint64(frequency)
125	return formatSep(us) + " us"
126}
127
128func (t TimeStamps) String() string {
129	ret := fmt.Sprintf("%d entries total\n\n", len(t.Entries))
130	for i, e := range t.Entries {
131		name, ok := timeStampNames[e.EntryID]
132		if !ok {
133			name = "<unknown>"
134		}
135		ret += fmt.Sprintf("%4d:%-30s %s", e.EntryID, name, formatElapsedTime(e.EntryStamp, t.FrequencyMHZ))
136		if i != 0 {
137			ret += fmt.Sprintf(" (%s)", formatElapsedTime(e.EntryStamp-t.Entries[i-1].EntryStamp, t.FrequencyMHZ))
138		}
139		ret += "\n"
140	}
141	return ret
142}
143
144func getFrequency() uint32 {
145	/* On non-x86 platforms the timestamp entries are in usecs */
146	if runtime.GOARCH != "386" && runtime.GOARCH != "amd64" {
147		return 1
148	}
149
150	cpuf, err := os.Open("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")
151	if err != nil {
152		return 0
153	}
154
155	freq := uint64(0)
156	fmt.Fscanf(cpuf, "%d", &freq)
157	return uint32(freq / 1000)
158}
159
160func (p parsedTables) GetVersion() (string, error) {
161	str, ok := p.typeMap[TagVersion]
162	if !ok {
163		return "", fmt.Errorf("no coreboot version")
164	}
165	s := string(str)
166	idx := strings.Index(s, "\000")
167	if idx >= 0 {
168		s = s[0:idx]
169	}
170	return s, nil
171}
172
173func (p parsedTables) GetVersionTimestamp() (time.Time, error) {
174	raw, ok := p.typeMap[TagVersionTimestamp]
175	if !ok {
176		return time.Time{}, fmt.Errorf("no coreboot version timestamp")
177	}
178	ts := uint32(0)
179	err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, &ts)
180	if err != nil {
181		return time.Time{}, err
182	}
183	return time.Unix(int64(ts), 0), nil
184}
185
186func (p parsedTables) GetTimestamps() (*TimeStamps, error) {
187	addr := uint64(0)
188	addrRaw, ok := p.typeMap[TagTimestamps]
189	if !ok {
190		return nil, fmt.Errorf("no coreboot console")
191	}
192	err := binary.Read(bytes.NewReader(addrRaw), binary.LittleEndian, &addr)
193	if err != nil {
194		return nil, err
195	}
196	mem := p.mem
197	_, err = mem.Seek(int64(addr), 0)
198	if err != nil {
199		return nil, err
200	}
201	var head TimeStampHeader
202	err = binary.Read(mem, binary.LittleEndian, &head)
203	if err != nil {
204		return nil, err
205	}
206
207	entries := make([]TimeStampEntry, head.NumEntries, head.NumEntries)
208	err = binary.Read(mem, binary.LittleEndian, &entries)
209	if err != nil {
210		return nil, err
211	}
212
213	return &TimeStamps{Head: head, Entries: entries, FrequencyMHZ: getFrequency()}, nil
214}
215
216func (p parsedTables) GetConsole() (console []byte, lost uint32, err error) {
217	addr := uint64(0)
218	addrRaw, ok := p.typeMap[TagConsole]
219	if !ok {
220		return nil, 0, fmt.Errorf("no coreboot console")
221	}
222	err = binary.Read(bytes.NewReader(addrRaw), binary.LittleEndian, &addr)
223	if err != nil {
224		return nil, 0, err
225	}
226	mem := p.mem
227	_, err = mem.Seek(int64(addr), 0)
228	if err != nil {
229		return nil, 0, err
230	}
231	var consDesc CBMemConsole
232	err = binary.Read(mem, binary.LittleEndian, &consDesc)
233	if err != nil {
234		return nil, 0, err
235	}
236
237	readSize := consDesc.Cursor
238	lost = 0
239	if readSize > consDesc.Size {
240		lost = readSize - consDesc.Size
241		readSize = consDesc.Size
242	}
243
244	cons := make([]byte, readSize, readSize)
245	mem.Read(cons)
246	if err != nil {
247		return nil, 0, err
248	}
249
250	return cons, lost, nil
251}
252
253func IPChecksum(b []byte) uint16 {
254	sum := uint32(0)
255	/* Oh boy: coreboot really does is little-endian way.  */
256	for i := 0; i < len(b); i += 2 {
257		sum += uint32(b[i])
258	}
259	for i := 1; i < len(b); i += 2 {
260		sum += uint32(b[i]) << 8
261	}
262
263	sum = (sum >> 16) + (sum & 0xffff)
264	sum += (sum >> 16)
265	return uint16(^sum & 0xffff)
266}
267
268func readFromBase(mem *os.File, base uint64) ([]byte, error) {
269	_, err := mem.Seek(int64(base), 0)
270	if err != nil {
271		return nil, err
272	}
273	var headRaw [HeaderSize]byte
274	var head Header
275	_, err = mem.Read(headRaw[:])
276	if err != nil {
277		return nil, err
278	}
279
280	err = binary.Read(bytes.NewReader(headRaw[:]), binary.LittleEndian, &head)
281	if err != nil {
282		return nil, err
283	}
284	if bytes.Compare(head.Signature[:], headerSignature[:]) != 0 || head.HeaderBytes == 0 {
285		return nil, nil
286	}
287	if IPChecksum(headRaw[:]) != 0 {
288		return nil, nil
289	}
290	table := make([]byte, head.TableBytes, head.TableBytes)
291	_, err = mem.Seek(int64(base)+int64(head.HeaderBytes), 0)
292	if err != nil {
293		return nil, err
294	}
295	_, err = mem.Read(table)
296	if err != nil {
297		return nil, err
298	}
299
300	if uint32(IPChecksum(table)) != head.TableChecksum {
301		return nil, nil
302	}
303	return table, nil
304}
305
306func scanFromBase(mem *os.File, base uint64) ([]byte, error) {
307	for i := uint64(0); i < 0x1000; i += 0x10 {
308		b, err := readFromBase(mem, base+i)
309		if err != nil {
310			return nil, err
311		}
312		if b != nil {
313			return b, nil
314		}
315	}
316	return nil, fmt.Errorf("no coreboot table found")
317}
318
319func readTables(mem *os.File) ([]byte, error) {
320	switch runtime.GOARCH {
321	case "arm":
322		dt, err := os.Open("/proc/device-tree/firmware/coreboot/coreboot-table")
323		defer dt.Close()
324		if err != nil {
325			return nil, err
326		}
327		var base uint32
328		err = binary.Read(dt, binary.BigEndian, &base)
329		if err != nil {
330			return nil, err
331		}
332		return scanFromBase(mem, uint64(base))
333	case "386", "amd64":
334		tbl, err := scanFromBase(mem, 0)
335		if err == nil {
336			return tbl, nil
337		}
338		return scanFromBase(mem, 0xf0000)
339	default:
340		return nil, fmt.Errorf("unsuppurted arch: %s", runtime.GOARCH)
341	}
342}
343
344func parseTables(mem *os.File, raw []byte) (p parsedTables, err error) {
345	reader := bytes.NewBuffer(raw)
346	p.typeMap = map[uint32][]byte{}
347	for {
348		record := Record{}
349		err = binary.Read(reader, binary.LittleEndian, &record)
350		if err == io.EOF {
351			p.mem = mem
352			return p, nil
353		}
354		if err != nil {
355			return p, err
356		}
357		payload := make([]byte, record.Size-8, record.Size-8)
358		reader.Read(payload)
359		p.raw = append(p.raw, rawTable{record: record, payload: payload})
360		p.typeMap[record.Tag] = payload
361		if record.Tag == TagForward {
362			base := uint64(0)
363			err = binary.Read(bytes.NewBuffer(payload), binary.LittleEndian, &base)
364			if err != nil {
365				return p, err
366			}
367			raw, err := readFromBase(mem, base)
368			if err != nil {
369				return p, err
370			}
371			if raw == nil {
372				return p, fmt.Errorf("no coreboot table found")
373			}
374			reader = bytes.NewBuffer(raw)
375		}
376	}
377}
378
379func Open() (reader CBTablesReader, err error) {
380	mem, err := os.Open("/dev/mem")
381	if err != nil {
382		return nil, err
383	}
384
385	tables, err := readTables(mem)
386	if err != nil {
387		return nil, err
388	}
389
390	return parseTables(mem, tables)
391}
392