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