github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/exp/cbmem/main_linux.go (about)

     1  // Copyright 2016-2021 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // cbmem prints out coreboot mem table information in JSON by default,
     6  // and also implements the basic cbmem -list and -console commands.
     7  // TODO: checksum tables.
     8  package main
     9  
    10  import (
    11  	"bufio"
    12  	"encoding/hex"
    13  	"encoding/json"
    14  	"flag"
    15  	"fmt"
    16  	"io"
    17  	"log"
    18  	"os"
    19  	"reflect"
    20  	"text/tabwriter"
    21  
    22  	"golang.org/x/text/language"
    23  	"golang.org/x/text/message"
    24  )
    25  
    26  var (
    27  	mem                 = flag.String("mem", "/dev/mem", "file for coreboot image")
    28  	debug               = func(string, ...interface{}) {}
    29  	addr                int64
    30  	size                int
    31  	console             bool
    32  	coverage            bool
    33  	list                bool
    34  	hexdump             bool
    35  	timestamps          bool
    36  	parseabletimestamps bool
    37  	verbose             bool
    38  	version             bool
    39  	dumpJSON            bool
    40  )
    41  
    42  //
    43  // usage: /home/rminnich/bin/cbmem [-cCltTLxVvh?]
    44  //   -c | --console:                   print cbmem console
    45  
    46  func init() {
    47  	const longfmt = "-%s | --%s:%s%s (default %v)\n"
    48  	var (
    49  		ushort = "Usage: cbmem [h?"
    50  		ulong  string
    51  	)
    52  
    53  	for _, f := range []struct {
    54  		b     *bool
    55  		def   bool
    56  		short string
    57  		long  string
    58  		help  string
    59  		tab   string
    60  	}{
    61  		{&console, false, "c", "console", "print cbmem console", "\t\t\t"},
    62  		{&coverage, false, "C", "coverage", "dump coverage information", "\t\t"},
    63  		{&list, false, "l", "list", "print cbmem table of contents", "\t\t\t"},
    64  		{&hexdump, false, "x", "hexdump", "print hexdump of cbmem area", "\t\t\t"},
    65  		{&timestamps, true, "t", "timestamps", "print timestamp information (default)", "\t\t"},
    66  		{&parseabletimestamps, false, "p", "parseable-timestamps", "print parseable timestamps", "\t"},
    67  		{&verbose, false, "v", "verbose", "verbose (debugging) output", "\t\t\t"},
    68  		{&version, false, "V", "version", "print version information", "\t\t\t"},
    69  		{&dumpJSON, false, "j", "json", "Output tables in JSON format", "\t\t\t"},
    70  	} {
    71  		flag.BoolVar(f.b, f.short, f.def, f.help)
    72  		flag.BoolVar(f.b, f.long, f.def, f.help)
    73  		ushort += f.short
    74  		ulong += fmt.Sprintf(longfmt, f.short, f.long, f.tab, f.help, f.def)
    75  	}
    76  	flag.Usage = func() {
    77  		fmt.Fprintf(os.Stderr, "%s]\n\n%s", ushort, ulong)
    78  		os.Exit(1)
    79  	}
    80  }
    81  
    82  // parseCBtable looks for a coreboot table in the range address, address + size - 1
    83  // If it finds one it tries to parse it.
    84  // If it found a table it returns true.
    85  // If the parsing had an error, it returns the error.
    86  func parseCBtable(f *os.File, address int64, sz int) (*CBmem, bool, error) {
    87  	var found bool
    88  	r, err := newOffsetReader(f, address, sz)
    89  	if err != nil {
    90  		return nil, found, err
    91  	}
    92  	debug("Looking for coreboot table at %#08x %d bytes", address, sz)
    93  	var (
    94  		i     int64
    95  		lbh   Header
    96  		cbmem = &CBmem{StringVars: make(map[string]string)}
    97  	)
    98  
    99  	for i = address; i < address+0x1000 && !found; i += 0x10 {
   100  		if err := readOne(r, &lbh, i); err != nil {
   101  			return nil, found, err
   102  		}
   103  		debug("header is %s", lbh.String())
   104  		if string(lbh.Signature[:]) != "LBIO" {
   105  			debug("no LBIO at %#08x", i)
   106  			continue
   107  		}
   108  		if lbh.HeaderSz == 0 {
   109  			debug("HeaderSz is 0 at %#08x", i)
   110  		}
   111  		debug("Found at %#08x!", i)
   112  
   113  		// TODO: checksum the header.
   114  		// Although I know of no case in 10 years where that
   115  		// was useful.
   116  		addr = i + int64(lbh.HeaderSz)
   117  		found = true
   118  
   119  		/* Keep reference to lbtable. */
   120  		size = int(lbh.TableSz)
   121  		j := addr
   122  		debug("Process %d entires", lbh.TableEntries)
   123  		for j < addr+int64(lbh.TableSz) {
   124  			var rec Record
   125  			debug("\tcoreboot table entry 0x%02x\n", rec.Tag)
   126  			if err := readOne(r, &rec, j); err != nil {
   127  				return nil, found, err
   128  			}
   129  			debug("\tFound Tag %s (%v)@%#08x Size %v", tagNames[rec.Tag], rec.Tag, j, rec.Size)
   130  			start := j
   131  			j += int64(reflect.TypeOf(r).Size())
   132  			n := tagNames[rec.Tag]
   133  			switch rec.Tag {
   134  			case LB_TAG_BOARD_ID:
   135  				if err := readOne(r, &cbmem.BoardID, start); err != nil {
   136  					return nil, found, err
   137  				}
   138  			case
   139  				LB_TAG_VERSION,
   140  				LB_TAG_EXTRA_VERSION,
   141  				LB_TAG_BUILD,
   142  				LB_TAG_COMPILE_TIME,
   143  				LB_TAG_COMPILE_BY,
   144  				LB_TAG_COMPILE_HOST,
   145  				LB_TAG_COMPILE_DOMAIN,
   146  				LB_TAG_COMPILER,
   147  				LB_TAG_LINKER,
   148  				LB_TAG_ASSEMBLER,
   149  				LB_TAG_PLATFORM_BLOB_VERSION:
   150  				s, err := bufio.NewReader(io.NewSectionReader(r, j, 65536)).ReadString(0)
   151  				if err != nil {
   152  					return nil, false, fmt.Errorf("trying to read string for %s: %v", n, err)
   153  				}
   154  				cbmem.StringVars[n] = s[:len(s)-1]
   155  			case LB_TAG_SERIAL:
   156  				var s serialEntry
   157  				if err := readOne(r, &s, start); err != nil {
   158  					return nil, found, err
   159  				}
   160  				cbmem.UART = append(cbmem.UART, s)
   161  
   162  			case LB_TAG_CONSOLE:
   163  				var c uint32
   164  				if err := readOne(r, &c, j); err != nil {
   165  					return nil, found, err
   166  				}
   167  				cbmem.Consoles = append(cbmem.Consoles, consoleNames[c])
   168  			case LB_TAG_VERSION_TIMESTAMP:
   169  				if err := readOne(r, &cbmem.VersionTimeStamp, start); err != nil {
   170  					return nil, found, err
   171  				}
   172  			case LB_TAG_BOOT_MEDIA_PARAMS:
   173  				if err := readOne(r, &cbmem.BootMediaParams, start); err != nil {
   174  					return nil, found, err
   175  				}
   176  			case LB_TAG_CBMEM_ENTRY:
   177  				var c cbmemEntry
   178  				if err := readOne(r, &c, start); err != nil {
   179  					return nil, found, err
   180  				}
   181  				cbmem.CBMemory = append(cbmem.CBMemory, c)
   182  			case LB_TAG_MEMORY:
   183  				debug("    Found memory map.\n")
   184  				cbmem.Memory = &memoryEntry{Record: rec}
   185  				nel := (int64(cbmem.Memory.Size) - (j - start)) / int64(reflect.TypeOf(memoryRange{}).Size())
   186  				cbmem.Memory.Maps = make([]memoryRange, nel)
   187  				if err := readOne(r, cbmem.Memory.Maps, j); err != nil {
   188  					return nil, found, err
   189  				}
   190  			case LB_TAG_TIMESTAMPS:
   191  				if err := readOne(r, &cbmem.TimeStampsTable, start); err != nil {
   192  					return nil, found, err
   193  				}
   194  				if cbmem.TimeStampsTable.Addr == 0 {
   195  					continue
   196  				}
   197  				if cbmem.TimeStamps, err = cbmem.readTimeStamps(f); err != nil {
   198  					log.Printf("TimeStampAddress is %#x but ReadTimeStamps failed: %v", cbmem.TimeStampsTable, err)
   199  					return nil, found, err
   200  				}
   201  			case LB_TAG_MAINBOARD:
   202  				// The mainboard entry is a bit weird.
   203  				// There is a byte after the Record
   204  				// for the Vendor Index and a byte after
   205  				// that for the Part Number Index.
   206  				// In general, the vx is 0, and it's also
   207  				// null terminated. The struct is a bit
   208  				// over-general, actually, and the indexes
   209  				// can be safely ignored.
   210  				cbmem.MainBoard.Record = rec
   211  				v, err := bufio.NewReader(io.NewSectionReader(r, j+2, 65536)).ReadString(0)
   212  				if err != nil {
   213  					return nil, false, fmt.Errorf("trying to read string for %s: %v", n, err)
   214  				}
   215  				p, err := bufio.NewReader(io.NewSectionReader(r, j+2+int64(len(v)), 65536)).ReadString(0)
   216  				if err != nil {
   217  					return nil, false, fmt.Errorf("trying to read string for %s: %v", n, err)
   218  				}
   219  				cbmem.MainBoard.Vendor = v[:len(v)-1]
   220  				cbmem.MainBoard.PartNumber = p[:len(p)-1]
   221  			case LB_TAG_HWRPB:
   222  				if err := readOne(r, &cbmem.Hwrpb, start); err != nil {
   223  					return nil, found, err
   224  				}
   225  
   226  				// "Nobody knew consoles could be so hard."
   227  			case LB_TAG_CBMEM_CONSOLE:
   228  				c := &memconsoleEntry{Record: rec}
   229  				debug("    Found cbmem console(%#x), %d byte record.\n", rec, c.Size)
   230  				if err := readOne(r, &c.Address, j); err != nil {
   231  					return nil, found, err
   232  				}
   233  				debug("    console data is at %#x", c.Address)
   234  				cbcons := int64(c.Address)
   235  				// u32 size;
   236  				// u32 cursor;
   237  				// u8  body[0];
   238  				// The cbmem size is a guess.
   239  				cr, err := newOffsetReader(f, cbcons, 8)
   240  				if err != nil {
   241  					return nil, found, err
   242  				}
   243  				if err := readOne(cr, &c.Size, cbcons); err != nil {
   244  					return nil, found, err
   245  				}
   246  
   247  				cbcons += int64(reflect.TypeOf(c.Size).Size())
   248  				if err := readOne(cr, &c.Cursor, cbcons); err != nil {
   249  					return nil, found, err
   250  				}
   251  				cbcons += int64(reflect.TypeOf(c.Cursor).Size())
   252  				debug("CSize is %#x, and Cursor is at %#x", c.CSize, c.Cursor)
   253  				// p.cur f8b4 p.si 1fff8 curs f8b4 size f8b4
   254  				sz := int(c.Size)
   255  
   256  				cr, err = newOffsetReader(f, cbcons, sz)
   257  				if err != nil {
   258  					return nil, found, err
   259  				}
   260  
   261  				curse := int(c.Cursor & CBMC_CURSOR_MASK)
   262  				data := make([]byte, sz)
   263  				// This one is easy. Read from 0 to the cursor.
   264  				if c.Cursor&CBMC_OVERFLOW == 0 {
   265  					if curse < int(c.Size) {
   266  						sz = curse
   267  						data = data[:sz]
   268  					}
   269  
   270  					debug("CSize is %d, and Cursor is at %d", c.CSize, c.Cursor)
   271  
   272  					if n, err := cr.ReadAt(data, cbcons); err != nil || n != len(data) {
   273  						return nil, found, err
   274  					}
   275  				} else {
   276  					debug("CSize is %#x, and Cursor is at %#x", curse, sz)
   277  					// This should not happen, but that means that it WILL happen
   278  					// some day ...
   279  					if curse > sz {
   280  						curse = 0
   281  					}
   282  					off := cbcons + int64(curse)
   283  					if n, err := cr.ReadAt(data[:curse], off); err != nil || n != len(data[:curse]) {
   284  						return nil, found, err
   285  					}
   286  					if n, err := cr.ReadAt(data[curse:], cbcons); err != nil || n != len(data[curse:]) {
   287  						debug("2nd read: %v", err)
   288  						return nil, found, err
   289  					}
   290  				}
   291  
   292  				c.Data = string(data)
   293  				cbmem.MemConsole = c
   294  
   295  			case LB_TAG_FORWARD:
   296  				var newTable int64
   297  				if err := readOne(r, &newTable, j); err != nil {
   298  					return nil, found, err
   299  				}
   300  				debug("Forward to %08x", newTable)
   301  				return parseCBtable(f, newTable, 1048576)
   302  			default:
   303  				if n, ok := tagNames[rec.Tag]; ok {
   304  					debug("Ignoring record %v", n)
   305  					cbmem.Ignored = append(cbmem.Ignored, n)
   306  					j = start + int64(rec.Size)
   307  					continue
   308  				}
   309  				log.Printf("Unknown tag record %v %#x", rec, rec.Tag)
   310  				cbmem.Unknown = append(cbmem.Unknown, rec.Tag)
   311  
   312  			}
   313  			j = start + int64(rec.Size)
   314  		}
   315  	}
   316  	return cbmem, found, nil
   317  }
   318  
   319  // DumpMem prints the memory areas. If hexdump is set, it will hexdump
   320  // LB tables.
   321  func DumpMem(f *os.File, cbmem *CBmem, hexdump bool, w io.Writer) error {
   322  	if cbmem.Memory == nil {
   323  		fmt.Fprintf(w, "No cbmem table name")
   324  	}
   325  	m := cbmem.Memory.Maps
   326  	if len(m) == 0 {
   327  		fmt.Fprintf(w, "No cbmem map entries")
   328  	}
   329  	fmt.Fprintf(w, "%19s %8s %8s\n", "Name", "Start", "Size")
   330  	for _, e := range m {
   331  		fmt.Fprintf(w, "%19s %08x %08x\n", memTags[e.Mtype], e.Start, e.Size)
   332  		if hexdump && e.Mtype == LB_MEM_TABLE {
   333  			r, err := newOffsetReader(f, int64(e.Start), int(e.Size))
   334  			if err != nil {
   335  				log.Print(err)
   336  				continue
   337  			}
   338  			// The hexdump does a lot of what we want, but not all of
   339  			// what we want. In particular, we'd like better control of
   340  			// what is printed with the offset. So ... hackery.
   341  			out := ""
   342  			same := 0
   343  			var line [16]byte
   344  			for i := e.Start; i < e.Start+e.Size; i += 16 {
   345  				n, err := r.ReadAt(line[:], int64(i))
   346  				if err == io.EOF {
   347  					break
   348  				}
   349  				if err != nil {
   350  					return err
   351  				}
   352  
   353  				s := hex.Dump(line[:n])[10:]
   354  				// If it's the same as the previous, increment same
   355  				if s == out {
   356  					if same == 0 {
   357  						fmt.Fprintf(w, "...\n")
   358  					}
   359  					same++
   360  					continue
   361  				}
   362  				same = 0
   363  				out = s
   364  				fmt.Fprintf(w, "%08x: %s", i, s)
   365  			}
   366  		}
   367  	}
   368  	return nil
   369  }
   370  
   371  func cbMem(w io.Writer) error {
   372  	var err error
   373  	if version {
   374  		fmt.Fprintln(w, "cbmem in Go, including JSON output")
   375  		return err
   376  	}
   377  	if verbose {
   378  		debug = log.Printf
   379  	}
   380  
   381  	f, err := os.Open(*mem)
   382  	if err != nil {
   383  		return err
   384  	}
   385  
   386  	var cbmem *CBmem
   387  	var found bool
   388  	for _, addr := range []int64{0, 0xf0000} {
   389  		cbmem, found, err = parseCBtable(f, addr, 0x10000)
   390  		if err != nil {
   391  			return err
   392  		}
   393  		if found {
   394  			break
   395  		}
   396  	}
   397  	if err != nil {
   398  		return fmt.Errorf("reading coreboot table: %v", err)
   399  	}
   400  	if !found {
   401  		return fmt.Errorf("no coreboot table found")
   402  	}
   403  
   404  	if timestamps {
   405  		ts := cbmem.TimeStamps
   406  
   407  		// Format in tab-separated columns with a tab stop of 8.
   408  		tw := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
   409  		freq := uint64(ts.TickFreqMHZ)
   410  		debug("ts %#x freq %#x stamps %#x\n", ts, freq, ts.TS)
   411  		prev := ts.TS[0].EntryStamp
   412  		p := message.NewPrinter(language.English)
   413  		p.Fprintf(tw, "%d entries total:\n\n", len(ts.TS))
   414  		for _, t := range ts.TS {
   415  			n, ok := TimeStampNames[t.EntryID]
   416  			if !ok {
   417  				n = fmt.Sprintf("[%#x]", t.EntryID)
   418  			}
   419  			cur := t.EntryStamp
   420  			debug("cur %#x cur / freq %#x", cur, cur/freq)
   421  			p.Fprintf(tw, "\t%d:%s\t%d (%d)\n", t.EntryID, n, cur/freq, (cur-prev)/freq)
   422  			prev = cur
   423  
   424  		}
   425  		tw.Flush()
   426  	}
   427  	if dumpJSON {
   428  		b, err := json.MarshalIndent(cbmem, "", "\t")
   429  		if err != nil {
   430  			return fmt.Errorf("json marshal: %v", err)
   431  		}
   432  		fmt.Fprintf(w, "%s\n", b)
   433  	}
   434  	// list is kind of misnamed I think. It really just prints
   435  	// memory table entries.
   436  	if list || hexdump {
   437  		DumpMem(f, cbmem, hexdump, os.Stdout)
   438  	}
   439  	if console && cbmem.MemConsole != nil {
   440  		fmt.Fprintf(w, "%s%s", cbmem.MemConsole.Data[cbmem.MemConsole.Cursor:], cbmem.MemConsole.Data[0:cbmem.MemConsole.Cursor])
   441  	}
   442  	return err
   443  }
   444  
   445  //go:generate go run gen/gen.go -apu2
   446  
   447  func main() {
   448  	flag.Parse()
   449  	if err := cbMem(os.Stdout); err != nil {
   450  		log.Fatal(err)
   451  	}
   452  }