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 {×tamps, 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 }