[coreboot-gerrit] New patch to review for coreboot: 6b483ef Rewrite board_status in go.

Vladimir Serbinenko (phcoder@gmail.com) gerrit at coreboot.org
Sun Nov 23 17:19:54 CET 2014


Vladimir Serbinenko (phcoder at gmail.com) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/7565

-gerrit

commit 6b483ef58360eef96c761e68f5b6b780501cc488
Author: Vladimir Serbinenko <phcoder at gmail.com>
Date:   Sun Nov 23 16:19:48 2014 +0100

    Rewrite board_status in go.
    
    This allows easy creation of redistribuable binary.
    
    Change-Id: I12a82d509cd4bd46baeb4f4066e509c69301ab95
    Signed-off-by: Vladimir Serbinenko <phcoder at gmail.com>
---
 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(+)

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 0000000..042d35f
--- /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 0000000..2914006
--- /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 = "<unknown>"
+		}
+		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 0000000..6ce308e
--- /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 0000000..c52b60f
--- /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)
+	}
+}



More information about the coreboot-gerrit mailing list