Rewrite board_status in go.

This allows easy creation of redistribuable binary.

Change-Id: I12a82d509cd4bd46baeb4f4066e509c69301ab95
Signed-off-by: Vladimir Serbinenko <phcoder@gmail.com>
Reviewed-on: http://review.coreboot.org/7565
Tested-by: build bot (Jenkins)
Reviewed-by: Edward O'Callaghan <eocallaghan@alterapraxis.com>
Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
This commit is contained in:
Vladimir Serbinenko 2014-11-23 16:19:48 +01:00
parent 6ead253fbd
commit 8251e9070c
4 changed files with 794 additions and 0 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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]
}

View File

@ -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)
}
}