package cbtables import ( "bytes" "encoding/binary" "fmt" "io" "os" "runtime" "strings" "time" ) type Header struct { Signature [4]uint8 /* LBIO */ HeaderBytes uint32 HeaderChecksum uint32 TableBytes uint32 TableChecksum uint32 TableEntries uint32 } type Record struct { Tag uint32 Size uint32 } type rawTable struct { record Record payload []byte } type parsedTables struct { mem *os.File raw []rawTable typeMap map[uint32][]byte } var headerSignature [4]byte = [4]byte{'L', 'B', 'I', 'O'} const HeaderSize = 24 const ( TagVersion = 0x0004 TagForward = 0x0011 TagTimestamps = 0x0016 TagConsole = 0x0017 TagVersionTimestamp = 0x0026 ) type CBTablesReader interface { GetConsole() (cons []byte, lost uint32, err error) GetTimestamps() (*TimeStamps, error) GetVersion() (string, error) GetVersionTimestamp() (time.Time, error) } type CBMemConsole struct { Size uint32 Cursor uint32 } type TimeStampEntry struct { EntryID uint32 EntryStamp uint64 } type TimeStampHeader struct { BaseTime uint64 MaxEntries uint32 NumEntries uint32 } type TimeStamps struct { Head TimeStampHeader Entries []TimeStampEntry FrequencyMHZ uint32 } var timeStampNames map[uint32]string = map[uint32]string{ 1: "start of rom stage", 2: "before ram initialization", 3: "after ram initialization", 4: "end of romstage", 5: "start of verified boot", 6: "end of verified boot", 8: "start of copying ram stage", 9: "end of copying ram stage", 10: "start of ramstage", 30: "device enumeration", 40: "device configuration", 50: "device enable", 60: "device initialization", 70: "device setup done", 75: "cbmem post", 80: "write tables", 90: "load payload", 98: "ACPI wake jump", 99: "selfboot jump", 1000: "depthcharge start", 1001: "RO parameter init", 1002: "RO vboot init", 1003: "RO vboot select firmware", 1004: "RO vboot select&load kernel", 1010: "RW vboot select&load kernel", 1020: "vboot select&load kernel", 1100: "crossystem data", 1101: "start kernel", } func formatSep(val uint64) string { ret := "" for val > 1000 { ret = fmt.Sprintf(",%03d", val%1000) + ret val /= 1000 } ret = fmt.Sprintf("%d", val) + ret return ret } func formatElapsedTime(ticks uint64, frequency uint32) string { if frequency == 0 { return formatSep(ticks) + " cycles" } us := ticks / uint64(frequency) return formatSep(us) + " us" } func (t TimeStamps) String() string { ret := fmt.Sprintf("%d entries total\n\n", len(t.Entries)) for i, e := range t.Entries { name, ok := timeStampNames[e.EntryID] if !ok { name = "" } ret += fmt.Sprintf("%4d:%-30s %s", e.EntryID, name, formatElapsedTime(e.EntryStamp, t.FrequencyMHZ)) if i != 0 { ret += fmt.Sprintf(" (%s)", formatElapsedTime(e.EntryStamp-t.Entries[i-1].EntryStamp, t.FrequencyMHZ)) } ret += "\n" } return ret } func getFrequency() uint32 { /* On non-x86 platforms the timestamp entries are in usecs */ if runtime.GOARCH != "386" && runtime.GOARCH != "amd64" { return 1 } cpuf, err := os.Open("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq") if err != nil { return 0 } freq := uint64(0) fmt.Fscanf(cpuf, "%d", &freq) return uint32(freq / 1000) } func (p parsedTables) GetVersion() (string, error) { str, ok := p.typeMap[TagVersion] if !ok { return "", fmt.Errorf("no coreboot version") } s := string(str) idx := strings.Index(s, "\000") if idx >= 0 { s = s[0:idx] } return s, nil } func (p parsedTables) GetVersionTimestamp() (time.Time, error) { raw, ok := p.typeMap[TagVersionTimestamp] if !ok { return time.Time{}, fmt.Errorf("no coreboot version timestamp") } ts := uint32(0) err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, &ts) if err != nil { return time.Time{}, err } return time.Unix(int64(ts), 0), nil } func (p parsedTables) GetTimestamps() (*TimeStamps, error) { addr := uint64(0) addrRaw, ok := p.typeMap[TagTimestamps] if !ok { return nil, fmt.Errorf("no coreboot console") } err := binary.Read(bytes.NewReader(addrRaw), binary.LittleEndian, &addr) if err != nil { return nil, err } mem := p.mem _, err = mem.Seek(int64(addr), 0) if err != nil { return nil, err } var head TimeStampHeader err = binary.Read(mem, binary.LittleEndian, &head) if err != nil { return nil, err } entries := make([]TimeStampEntry, head.NumEntries, head.NumEntries) err = binary.Read(mem, binary.LittleEndian, &entries) if err != nil { return nil, err } return &TimeStamps{Head: head, Entries: entries, FrequencyMHZ: getFrequency()}, nil } func (p parsedTables) GetConsole() (console []byte, lost uint32, err error) { addr := uint64(0) addrRaw, ok := p.typeMap[TagConsole] if !ok { return nil, 0, fmt.Errorf("no coreboot console") } err = binary.Read(bytes.NewReader(addrRaw), binary.LittleEndian, &addr) if err != nil { return nil, 0, err } mem := p.mem _, err = mem.Seek(int64(addr), 0) if err != nil { return nil, 0, err } var consDesc CBMemConsole err = binary.Read(mem, binary.LittleEndian, &consDesc) if err != nil { return nil, 0, err } readSize := consDesc.Cursor lost = 0 if readSize > consDesc.Size { lost = readSize - consDesc.Size readSize = consDesc.Size } cons := make([]byte, readSize, readSize) mem.Read(cons) if err != nil { return nil, 0, err } return cons, lost, nil } func IPChecksum(b []byte) uint16 { sum := uint32(0) /* Oh boy: coreboot really does is little-endian way. */ for i := 0; i < len(b); i += 2 { sum += uint32(b[i]) } for i := 1; i < len(b); i += 2 { sum += uint32(b[i]) << 8 } sum = (sum >> 16) + (sum & 0xffff) sum += (sum >> 16) return uint16(^sum & 0xffff) } func readFromBase(mem *os.File, base uint64) ([]byte, error) { _, err := mem.Seek(int64(base), 0) if err != nil { return nil, err } var headRaw [HeaderSize]byte var head Header _, err = mem.Read(headRaw[:]) if err != nil { return nil, err } err = binary.Read(bytes.NewReader(headRaw[:]), binary.LittleEndian, &head) if err != nil { return nil, err } if bytes.Compare(head.Signature[:], headerSignature[:]) != 0 || head.HeaderBytes == 0 { return nil, nil } if IPChecksum(headRaw[:]) != 0 { return nil, nil } table := make([]byte, head.TableBytes, head.TableBytes) _, err = mem.Seek(int64(base)+int64(head.HeaderBytes), 0) if err != nil { return nil, err } _, err = mem.Read(table) if err != nil { return nil, err } if uint32(IPChecksum(table)) != head.TableChecksum { return nil, nil } return table, nil } func scanFromBase(mem *os.File, base uint64) ([]byte, error) { for i := uint64(0); i < 0x1000; i += 0x10 { b, err := readFromBase(mem, base+i) if err != nil { return nil, err } if b != nil { return b, nil } } return nil, fmt.Errorf("no coreboot table found") } func readTables(mem *os.File) ([]byte, error) { switch runtime.GOARCH { case "arm": dt, err := os.Open("/proc/device-tree/firmware/coreboot/coreboot-table") defer dt.Close() if err != nil { return nil, err } var base uint32 err = binary.Read(dt, binary.BigEndian, &base) if err != nil { return nil, err } return scanFromBase(mem, uint64(base)) case "386", "amd64": tbl, err := scanFromBase(mem, 0) if err == nil { return tbl, nil } return scanFromBase(mem, 0xf0000) default: return nil, fmt.Errorf("unsuppurted arch: %s", runtime.GOARCH) } } func parseTables(mem *os.File, raw []byte) (p parsedTables, err error) { reader := bytes.NewBuffer(raw) p.typeMap = map[uint32][]byte{} for { record := Record{} err = binary.Read(reader, binary.LittleEndian, &record) if err == io.EOF { p.mem = mem return p, nil } if err != nil { return p, err } payload := make([]byte, record.Size-8, record.Size-8) reader.Read(payload) p.raw = append(p.raw, rawTable{record: record, payload: payload}) p.typeMap[record.Tag] = payload if record.Tag == TagForward { base := uint64(0) err = binary.Read(bytes.NewBuffer(payload), binary.LittleEndian, &base) if err != nil { return p, err } raw, err := readFromBase(mem, base) if err != nil { return p, err } if raw == nil { return p, fmt.Errorf("no coreboot table found") } reader = bytes.NewBuffer(raw) } } } func Open() (reader CBTablesReader, err error) { mem, err := os.Open("/dev/mem") if err != nil { return nil, err } tables, err := readTables(mem) if err != nil { return nil, err } return parseTables(mem, tables) }