From 8251e9070c6f4168654b75a4d0fd1f756d128ebd Mon Sep 17 00:00:00 2001 From: Vladimir Serbinenko Date: Sun, 23 Nov 2014 16:19:48 +0100 Subject: [PATCH] Rewrite board_status in go. This allows easy creation of redistribuable binary. Change-Id: I12a82d509cd4bd46baeb4f4066e509c69301ab95 Signed-off-by: Vladimir Serbinenko Reviewed-on: http://review.coreboot.org/7565 Tested-by: build bot (Jenkins) Reviewed-by: Edward O'Callaghan Reviewed-by: Ronald G. Minnich --- util/board_status/go/src/cbfs/cbfs.go | 239 +++++++++++ util/board_status/go/src/cbtables/cbtables.go | 391 ++++++++++++++++++ util/board_status/go/src/kconfig/kconfig.go | 30 ++ util/board_status/go/src/main/board_status.go | 134 ++++++ 4 files changed, 794 insertions(+) create mode 100644 util/board_status/go/src/cbfs/cbfs.go create mode 100644 util/board_status/go/src/cbtables/cbtables.go create mode 100644 util/board_status/go/src/kconfig/kconfig.go create mode 100644 util/board_status/go/src/main/board_status.go diff --git a/util/board_status/go/src/cbfs/cbfs.go b/util/board_status/go/src/cbfs/cbfs.go new file mode 100644 index 0000000000..042d35fa72 --- /dev/null +++ b/util/board_status/go/src/cbfs/cbfs.go @@ -0,0 +1,239 @@ +package cbfs + +import ( + "bytes" + "encoding/binary" + "fmt" + "os" + "sort" + "strings" + "text/tabwriter" +) + +type CBFSReader interface { + GetFile(name string) ([]byte, error) + ListFiles() ([]string, error) +} + +type ArchType uint32 +type FileType uint32 + +type CBFSHeader struct { + Magic uint32 + Version uint32 + ROMSize uint32 + BootBlockSize uint32 + Align uint32 + Offset uint32 + Architecture ArchType + Pad [1]uint32 +} + +func (a ArchType) String() string { + switch a { + case 0xFFFFFFFF: + return "unknown" + case 0x00000001: + return "x86" + case 0x00000010: + return "arm" + default: + return fmt.Sprintf("0x%x", a) + } +} + +func (f FileType) String() string { + switch f { + case 0xffffffff: + return "null" + case 0x10: + return "stage" + case 0x20: + return "payload" + case 0x30: + return "optionrom" + case 0x40: + return "bootsplash" + case 0x50: + return "raw" + case 0x51: + return "vsa" + case 0x52: + return "mbi" + case 0x53: + return "microcode" + case 0xaa: + return "cmos_default" + case 0x1aa: + return "cmos_layout" + default: + return fmt.Sprintf("0x%x", uint32(f)) + } +} + +func (c CBFSHeader) String() (ret string) { + ret = fmt.Sprintf("bootblocksize: %d\n", c.BootBlockSize) + ret += fmt.Sprintf("romsize: %d\n", c.ROMSize) + ret += fmt.Sprintf("offset: 0x%x\n", c.Offset) + ret += fmt.Sprintf("alignment: %d bytes\n", c.Align) + ret += fmt.Sprintf("architecture: %v\n", c.Architecture) + ret += fmt.Sprintf("version: 0x%x\n", c.Version) + return ret +} + +const sizeofFileHeader = 24 +const CBFSHeaderMagic = 0x4F524243 + +type CBFSFileHeader struct { + Magic [8]byte + Len uint32 + Type FileType + CheckSum uint32 + Offset uint32 +} + +type cBFSFile struct { + headerOffset uint64 + header CBFSFileHeader + name string +} + +type cBFSDesc struct { + file *os.File + end uint64 + headerPos uint64 + rOMStart uint64 + fileNames map[string]cBFSFile + files []cBFSFile + header CBFSHeader +} + +func (c cBFSDesc) align(offset uint32) uint32 { + a := uint32(c.header.Align) + return (a + offset - 1) & ^(a - 1) +} + +func (c cBFSDesc) ListFiles() (files []string, err error) { + for name, _ := range c.fileNames { + files = append(files, name) + } + sort.Strings(files) + return files, nil +} + +func (c cBFSDesc) GetFile(name string) ([]byte, error) { + file, ok := c.fileNames[name] + if !ok { + return nil, fmt.Errorf("file not found: %s", name) + } + _, err := c.file.Seek(int64(file.headerOffset)+int64(file.header.Offset), 0) + if err != nil { + return nil, err + } + ret := make([]byte, file.header.Len, file.header.Len) + r, err := c.file.Read(ret) + if err != nil { + return nil, err + } + if r != len(ret) { + return nil, fmt.Errorf("incomplete read") + } + return ret, nil +} + +func (c cBFSDesc) String() (ret string) { + ret = c.header.String() + ret += "\n" + buf := bytes.NewBuffer([]byte{}) + w := new(tabwriter.Writer) + w.Init(buf, 15, 0, 1, ' ', 0) + fmt.Fprintln(w, "Name\tOffset\tType\tSize\t") + for _, file := range c.files { + name := file.name + if file.header.Type == 0xffffffff { + name = "(empty)" + } + fmt.Fprintf(w, "%s\t0x%x\t%v\t%d\t\n", + name, file.headerOffset-c.rOMStart, + file.header.Type, file.header.Len) + } + w.Flush() + ret += buf.String() + return ret +} + +func openGeneric(cbfs *cBFSDesc) (CBFSReader, error) { + _, err := cbfs.file.Seek(int64(cbfs.end-4), 0) + if err != nil { + return nil, err + } + headerPos := int32(0) + binary.Read(cbfs.file, binary.LittleEndian, &headerPos) + if headerPos < 0 { + cbfs.headerPos = cbfs.end - uint64(-headerPos) + } else { + cbfs.headerPos = uint64(headerPos) + } + _, err = cbfs.file.Seek(int64(cbfs.headerPos), 0) + if err != nil { + return nil, err + } + err = binary.Read(cbfs.file, binary.BigEndian, &cbfs.header) + if err != nil { + return nil, err + } + if cbfs.header.Magic != CBFSHeaderMagic { + return nil, fmt.Errorf("invalid header magic") + } + + cbfs.fileNames = map[string]cBFSFile{} + + curptr := cbfs.end - uint64(cbfs.header.ROMSize) + uint64(cbfs.header.Offset) + cbfs.rOMStart = cbfs.end - uint64(cbfs.header.ROMSize) + for { + file := cBFSFile{headerOffset: curptr} + _, err = cbfs.file.Seek(int64(curptr), 0) + if err != nil { + return nil, err + } + err = binary.Read(cbfs.file, binary.BigEndian, &file.header) + if err != nil { + return nil, err + } + if string(file.header.Magic[:]) != "LARCHIVE" { + return *cbfs, nil + } + name := make([]byte, file.header.Offset-sizeofFileHeader, file.header.Offset-sizeofFileHeader) + _, err = cbfs.file.Read(name) + if err != nil { + return nil, err + } + nameStr := string(name) + idx := strings.Index(nameStr, "\000") + if idx >= 0 { + nameStr = nameStr[0:idx] + } + file.name = nameStr + cbfs.fileNames[nameStr] = file + cbfs.files = append(cbfs.files, file) + curptr += uint64(cbfs.align(file.header.Offset + file.header.Len)) + } +} + +func OpenFile(file *os.File) (CBFSReader, error) { + stat, err := file.Stat() + if err != nil { + return nil, err + } + cbfs := cBFSDesc{file: file, end: uint64(stat.Size())} + return openGeneric(&cbfs) +} + +func OpenROM() (CBFSReader, error) { + file, err := os.Open("/dev/mem") + if err != nil { + return nil, err + } + cbfs := cBFSDesc{file: file, end: 0x100000000} + return openGeneric(&cbfs) +} diff --git a/util/board_status/go/src/cbtables/cbtables.go b/util/board_status/go/src/cbtables/cbtables.go new file mode 100644 index 0000000000..291400617d --- /dev/null +++ b/util/board_status/go/src/cbtables/cbtables.go @@ -0,0 +1,391 @@ +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) +} diff --git a/util/board_status/go/src/kconfig/kconfig.go b/util/board_status/go/src/kconfig/kconfig.go new file mode 100644 index 0000000000..6ce308e61d --- /dev/null +++ b/util/board_status/go/src/kconfig/kconfig.go @@ -0,0 +1,30 @@ +package kconfig + +import ( + "bufio" + "bytes" + "strings" +) + +func ParseKConfig(raw []byte) map[string]string { + buffer := bytes.NewBuffer(raw) + + scanner := bufio.NewScanner(buffer) + ret := map[string]string{} + for scanner.Scan() { + line := scanner.Text() + if line[0] == '#' { + continue + } + idx := strings.Index(line, "=") + if idx < 0 { + continue + } + ret[line[0:idx]] = line[idx+1:] + } + return ret +} + +func UnQuote(in string) string { + return in[1 : len(in)-1] +} diff --git a/util/board_status/go/src/main/board_status.go b/util/board_status/go/src/main/board_status.go new file mode 100644 index 0000000000..c52b60f8f7 --- /dev/null +++ b/util/board_status/go/src/main/board_status.go @@ -0,0 +1,134 @@ +package main + +import ( + "cbfs" + "cbtables" + "flag" + "fmt" + "io/ioutil" + "kconfig" + "log" + "os" + "os/exec" +) + +var ClobberDir = flag.Bool("clobber", false, "Clobber temporary output when finished. Useful for debugging.") + +func RunAndSave(output string, name string, arg ...string) { + cmd := exec.Command(name, arg...) + + f, err := os.Create(output) + if err != nil { + log.Fatal(err) + } + + cmd.Stdout = f + cmd.Stderr = f + + err = cmd.Start() + if err != nil { + log.Fatal(err) + } + cmd.Wait() +} + +/* Missing features: serial, upload, ssh */ + +func main() { + flag.Parse() + + cb, err := cbfs.OpenROM() + if err != nil { + log.Fatal(err) + } + config, err := cb.GetFile("config") + if err != nil { + log.Fatal(err) + } + revision, err := cb.GetFile("revision") + if err != nil { + log.Fatal(err) + } + + parsedConfig := kconfig.ParseKConfig(config) + mainboardDir := kconfig.UnQuote(parsedConfig["CONFIG_MAINBOARD_DIR"]) + + tempDir, err := ioutil.TempDir("", "coreboot_board_status") + if err != nil { + log.Fatal(err) + } + + tbl, err := cbtables.Open() + if err != nil { + log.Fatal(err) + } + + taggedVersion, err := tbl.GetVersion() + if err != nil { + log.Fatal(err) + } + versionTimestamp, err := tbl.GetVersionTimestamp() + if err != nil { + log.Fatal(err) + } + timestampFormated := versionTimestamp.UTC().Format("2006-01-02T15:04:05Z") + outputDir := tempDir + "/" + mainboardDir + "/" + taggedVersion + "/" + timestampFormated + os.MkdirAll(outputDir, 0755) + fmt.Printf("Temporarily placing output in %s\n", outputDir) + cf, err := os.Create(outputDir + "/cbfs.txt") + if err != nil { + log.Fatal(err) + } + defer cf.Close() + fmt.Fprintf(cf, "%v", cb) + + cf, err = os.Create(outputDir + "/config.txt") + if err != nil { + log.Fatal(err) + } + defer cf.Close() + cf.Write(config) + + cf, err = os.Create(outputDir + "/revision.txt") + if err != nil { + log.Fatal(err) + } + defer cf.Close() + cf.Write(revision) + + RunAndSave(outputDir+"/kernel_log.txt", "dmesg") + + cons, lost, err := tbl.GetConsole() + if err != nil { + log.Fatal(err) + } + + cf, err = os.Create(outputDir + "/coreboot_console.txt") + if err != nil { + log.Fatal(err) + } + defer cf.Close() + cf.Write(cons) + switch lost { + case 0: + case 1: + fmt.Fprintf(cf, "\none byte lost\n") + default: + fmt.Fprintf(cf, "\n%d bytes lost\n", lost) + } + timest, err := tbl.GetTimestamps() + if err != nil { + log.Fatal(err) + } + + ts, err := os.Create(outputDir + "/coreboot_timestamps.txt") + if err != nil { + log.Fatal(err) + } + defer ts.Close() + fmt.Fprintf(ts, "%v", timest) + + if *ClobberDir { + os.RemoveAll(tempDir) + } +}