blob: 291400617dcaf3cdf0d618365ee5021eb38c3303 [file] [log] [blame]
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)
}