blob: 291400617dcaf3cdf0d618365ee5021eb38c3303 [file] [log] [blame]
Vladimir Serbinenko8251e902014-11-23 16:19:48 +01001package 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}