github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/dt/fdt.go (about) 1 // Copyright 2019 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 // Package dt contains utilities for device tree. 6 package dt 7 8 import ( 9 "bufio" 10 "bytes" 11 "encoding/binary" 12 "errors" 13 "fmt" 14 "io" 15 "math" 16 "os" 17 "unsafe" 18 19 "github.com/mvdan/u-root-coreutils/pkg/align" 20 "github.com/mvdan/u-root-coreutils/pkg/uio" 21 ) 22 23 const ( 24 // Magic value seen in the FDT Header. 25 Magic uint32 = 0xd00dfeed 26 27 // MaxTotalSize is a limitation imposed by this implementation. This 28 // prevents the integers from wrapping around. Typically, the total size is 29 // a few megabytes, so this is not restrictive. 30 MaxTotalSize = 1024 * 1024 * 1024 31 ) 32 33 type token uint32 34 35 const ( 36 tokenBeginNode token = 0x1 37 tokenEndNode token = 0x2 38 tokenProp token = 0x3 39 tokenNop token = 0x4 40 tokenEnd token = 0x9 41 ) 42 43 // FDT contains the parsed contents of a Flattend Device Tree (.dtb). 44 // 45 // The format is relatively simple and defined in chapter 5 of the Devicetree 46 // Specification Release 0.2. 47 // 48 // See: https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.2 49 // 50 // This package is compatible with version 16 and 17 of DTSpec. 51 type FDT struct { 52 Header Header 53 ReserveEntries []ReserveEntry 54 RootNode *Node 55 } 56 57 // Header appears at offset 0. 58 type Header struct { 59 Magic uint32 60 TotalSize uint32 61 OffDtStruct uint32 62 OffDtStrings uint32 63 OffMemRsvmap uint32 64 Version uint32 65 LastCompVersion uint32 66 BootCpuidPhys uint32 67 SizeDtStrings uint32 68 SizeDtStruct uint32 69 } 70 71 // ReserveEntry defines a memory region which is reserved. 72 type ReserveEntry struct { 73 Address uint64 74 Size uint64 75 } 76 77 // ReadFDT reads an FDT from an io.ReadSeeker. 78 func ReadFDT(f io.ReadSeeker) (*FDT, error) { 79 fdt := &FDT{} 80 if err := fdt.readHeader(f); err != nil { 81 return nil, err 82 } 83 if err := fdt.readMemoryReservationBlock(f); err != nil { 84 return nil, err 85 } 86 if err := fdt.checkLayout(); err != nil { 87 return nil, err 88 } 89 strs, err := fdt.readStringsBlock(f) 90 if err != nil { 91 return nil, err 92 } 93 if err := fdt.readStructBlock(f, strs); err != nil { 94 return nil, err 95 } 96 return fdt, nil 97 } 98 99 func (fdt *FDT) readHeader(f io.ReadSeeker) error { 100 h := &fdt.Header 101 if _, err := f.Seek(0, io.SeekStart); err != nil { 102 return err 103 } 104 if err := binary.Read(f, binary.BigEndian, h); err != nil { 105 return err 106 } 107 if h.Magic != Magic { 108 return fmt.Errorf("invalid FDT magic, got %#08x, expected %#08x", 109 h.Magic, Magic) 110 } 111 if !(h.Version == 16 || h.Version == 17 || 112 (h.LastCompVersion <= 17 && h.Version > 17)) { 113 return fmt.Errorf( 114 "incompatible FDT version, must be compatible with 16/17,"+ 115 "but version is %d last compatible with %d", 116 h.Version, h.LastCompVersion) 117 } 118 if h.TotalSize > MaxTotalSize { 119 return fmt.Errorf("FDT too large, %d > %d", h.TotalSize, MaxTotalSize) 120 } 121 return nil 122 } 123 124 func (fdt *FDT) readMemoryReservationBlock(f io.ReadSeeker) error { 125 if fdt.Header.OffMemRsvmap < uint32(unsafe.Sizeof(fdt.Header)) { 126 return fmt.Errorf("memory reservation block may not overlap header, %#x < %#x", 127 fdt.Header.OffMemRsvmap, unsafe.Sizeof(fdt.Header)) 128 } 129 if fdt.Header.OffMemRsvmap%8 != 0 { 130 return fmt.Errorf( 131 "memory reservation offset must be aligned to 8 bytes, but is %#x", 132 fdt.Header.OffMemRsvmap) 133 } 134 if _, err := f.Seek(int64(fdt.Header.OffMemRsvmap), io.SeekStart); err != nil { 135 return err 136 } 137 fdt.ReserveEntries = []ReserveEntry{} 138 for { 139 entry := ReserveEntry{} 140 if err := binary.Read(f, binary.BigEndian, &entry); err != nil { 141 return err 142 } 143 if entry.Address == 0 && entry.Size == 0 { 144 break 145 } 146 fdt.ReserveEntries = append(fdt.ReserveEntries, entry) 147 } 148 return nil 149 } 150 151 // checkLayout returns any errors if any of the blocks overlap, are in the 152 // wrong order or stray past the end of the file. This function must be called 153 // after readHeader and readMemoryReservationBlock. 154 func (fdt *FDT) checkLayout() error { 155 memRscEnd := fdt.Header.OffMemRsvmap + 156 uint32(len(fdt.ReserveEntries)+1)*uint32(unsafe.Sizeof(ReserveEntry{})) 157 if fdt.Header.OffDtStruct < memRscEnd { 158 return fmt.Errorf( 159 "struct block must not overlap memory reservation block, %#x < %#x", 160 fdt.Header.OffDtStruct, memRscEnd) 161 } 162 // TODO: there are more checks which should be done 163 return nil 164 } 165 166 func (fdt *FDT) readStringsBlock(f io.ReadSeeker) (strs []byte, err error) { 167 if _, err = f.Seek(int64(fdt.Header.OffDtStrings), io.SeekStart); err != nil { 168 return 169 } 170 strs = make([]byte, fdt.Header.SizeDtStrings) 171 _, err = f.Read(strs) 172 return 173 } 174 175 // readStructBlock reads the nodes and properties of the device and creates the 176 // tree structure. strs contains the strings block. 177 func (fdt *FDT) readStructBlock(f io.ReadSeeker, strs []byte) error { 178 if fdt.Header.OffDtStruct%4 != 0 { 179 return fmt.Errorf( 180 "struct offset must be aligned to 4 bytes, but is %#v", 181 fdt.Header.OffDtStruct) 182 } 183 if _, err := f.Seek(int64(fdt.Header.OffDtStruct), io.SeekStart); err != nil { 184 return err 185 } 186 187 // Buffer file so we don't perform a bajillion syscalls when looking for 188 // null-terminating characters. 189 r := &uio.AlignReader{ 190 R: bufio.NewReader( 191 &io.LimitedReader{ 192 R: f, 193 N: int64(fdt.Header.SizeDtStruct), 194 }, 195 ), 196 } 197 198 stack := []*Node{} 199 for { 200 var t token 201 if err := binary.Read(r, binary.BigEndian, &t); err != nil { 202 return err 203 } 204 switch t { 205 case tokenBeginNode: 206 // Push new node onto the stack. 207 child := &Node{} 208 stack = append(stack, child) 209 if len(stack) == 1 { 210 // Root node 211 if fdt.RootNode != nil { 212 return errors.New("invalid multiple root nodes") 213 } 214 fdt.RootNode = child 215 } else if len(stack) > 1 { 216 // Non-root node 217 parent := stack[len(stack)-2] 218 parent.Children = append(parent.Children, child) 219 } 220 221 // The name is a null-terminating string. 222 for { 223 if b, err := r.ReadByte(); err != nil { 224 return err 225 } else if b == 0 { 226 break 227 } else { 228 child.Name += string(b) 229 } 230 } 231 232 case tokenEndNode: 233 if len(stack) == 0 { 234 return errors.New( 235 "unbalanced FDT_BEGIN_NODE and FDT_END_NODE tokens") 236 } 237 stack = stack[:len(stack)-1] 238 239 case tokenProp: 240 pHeader := struct { 241 Len, Nameoff uint32 242 }{} 243 if err := binary.Read(r, binary.BigEndian, &pHeader); err != nil { 244 return err 245 } 246 if pHeader.Nameoff >= uint32(len(strs)) { 247 return fmt.Errorf( 248 "name offset is larger than strings block: %#x >= %#x", 249 pHeader.Nameoff, len(strs)) 250 } 251 null := bytes.IndexByte(strs[pHeader.Nameoff:], 0) 252 if null == -1 { 253 return fmt.Errorf( 254 "property name does not having terminating null at %#x", 255 pHeader.Nameoff) 256 } 257 p := Property{ 258 Name: string(strs[pHeader.Nameoff : pHeader.Nameoff+uint32(null)]), 259 Value: make([]byte, pHeader.Len), 260 } 261 _, err := io.ReadFull(r, p.Value) 262 if err != nil { 263 return err 264 } 265 if len(stack) == 0 { 266 return fmt.Errorf("property %q appears outside a node", p.Name) 267 } 268 curNode := stack[len(stack)-1] 269 curNode.Properties = append(curNode.Properties, p) 270 271 case tokenNop: 272 273 case tokenEnd: 274 if uint32(r.N) < fdt.Header.SizeDtStruct { 275 return fmt.Errorf( 276 "extra data at end of structure block, %#x < %#x", 277 uint32(r.N), fdt.Header.SizeDtStruct) 278 } 279 if fdt.RootNode == nil { 280 return errors.New("no root node") 281 } 282 return nil 283 284 default: 285 return fmt.Errorf("undefined token %d", t) 286 } 287 288 // Align to four bytes. 289 // was: pad, err := 290 _, err := r.Align(4) 291 if err != nil { 292 return err 293 } 294 /*for _, v := range pad { 295 if v != 0 { 296 // TODO: Some of the padding is not zero. Is this a mistake? 297 return fmt.Errorf("padding is non-zero: %d", v) 298 } 299 }*/ 300 } 301 } 302 303 // Write marshals the FDT to an io.Writer and returns the size. 304 func (fdt *FDT) Write(f io.Writer) (int, error) { 305 // Create string block and offset map. 306 strs := []byte{} 307 strOff := map[string]uint32{} 308 fdt.RootNode.Walk(func(n *Node) error { 309 for _, p := range n.Properties { 310 if _, ok := strOff[p.Name]; !ok { // deduplicate 311 strOff[p.Name] = uint32(len(strs)) 312 strs = append(strs, []byte(p.Name)...) 313 strs = append(strs, 0) 314 } 315 } 316 return nil 317 }) 318 319 // Calculate block sizes and offsets. 320 fdt.Header.SizeDtStrings = uint32(len(strs)) 321 fdt.Header.SizeDtStruct = 4 322 fdt.RootNode.Walk(func(n *Node) error { 323 fdt.Header.SizeDtStruct += 8 + uint32(align.Up(uint(len(n.Name)+1), 4)) 324 for _, p := range n.Properties { 325 fdt.Header.SizeDtStruct += 12 + uint32(align.Up(uint(len(p.Value)), 4)) 326 } 327 return nil 328 }) 329 fdt.Header.OffMemRsvmap = uint32(align.Up(uint(unsafe.Sizeof(fdt.Header)), 16)) 330 fdt.Header.OffDtStruct = fdt.Header.OffMemRsvmap + 331 uint32(align.Up((uint(len(fdt.ReserveEntries)+1))*uint(unsafe.Sizeof(ReserveEntry{})), 4)) 332 fdt.Header.OffDtStrings = fdt.Header.OffDtStruct + fdt.Header.SizeDtStruct 333 fdt.Header.TotalSize = fdt.Header.OffDtStrings + fdt.Header.SizeDtStrings 334 335 // Setup AlignWriter. 336 w := &uio.AlignWriter{W: f} 337 338 // Write header. 339 if err := binary.Write(w, binary.BigEndian, fdt.Header); err != nil { 340 return w.N, err 341 } 342 343 // Write memreserve block. 344 if err := w.Align(16, 0x00); err != nil { 345 return w.N, err 346 } 347 if err := binary.Write(w, binary.BigEndian, &fdt.ReserveEntries); err != nil { 348 return w.N, err 349 } 350 if err := binary.Write(w, binary.BigEndian, &ReserveEntry{}); err != nil { 351 return w.N, err 352 } 353 354 // Write struct block. 355 if err := w.Align(4, 0x00); err != nil { 356 return w.N, err 357 } 358 var writeNode func(n *Node) error 359 writeNode = func(n *Node) error { 360 if err := binary.Write(w, binary.BigEndian, tokenBeginNode); err != nil { 361 return err 362 } 363 if _, err := w.Write([]byte(n.Name + "\000")); err != nil { 364 return err 365 } 366 if err := w.Align(4, 0x00); err != nil { 367 return err 368 } 369 for _, p := range n.Properties { 370 property := struct { 371 Token token 372 Len, Nameoff uint32 373 }{ 374 tokenProp, 375 uint32(len(p.Value)), 376 strOff[p.Name], 377 } 378 if err := binary.Write(w, binary.BigEndian, &property); err != nil { 379 return err 380 } 381 if _, err := w.Write(p.Value); err != nil { 382 return err 383 } 384 if err := w.Align(4, 0x00); err != nil { 385 return err 386 } 387 } 388 for _, child := range n.Children { 389 if err := writeNode(child); err != nil { 390 return err 391 } 392 } 393 if err := binary.Write(w, binary.BigEndian, tokenEndNode); err != nil { 394 return err 395 } 396 return nil 397 } 398 if err := writeNode(fdt.RootNode); err != nil { 399 return w.N, err 400 } 401 if err := binary.Write(w, binary.BigEndian, tokenEnd); err != nil { 402 return w.N, err 403 } 404 405 // Write strings block 406 _, err := w.Write(strs) 407 return w.N, err 408 } 409 410 // NodeByName finds a node by name. 411 func (fdt *FDT) NodeByName(name string) (*Node, bool) { 412 return fdt.RootNode.Find(func(n *Node) bool { 413 return n.Name == name 414 }) 415 } 416 417 // ReadFile accepts a file name and returns an *FDT or error. 418 func ReadFile(n string) (*FDT, error) { 419 f, err := os.Open(n) 420 if err != nil { 421 return nil, err 422 } 423 424 defer f.Close() 425 return ReadFDT(f) 426 } 427 428 // FDTReader is a function type with no args that returns 429 // a *FDT or an error. 430 type FDTReader func() (*FDT, error) 431 432 // WithReaderAt constructs an FDTReader with the provided io.ReaderAt. 433 func WithReaderAt(r io.ReaderAt) FDTReader { 434 return func() (*FDT, error) { 435 return ReadFDT(io.NewSectionReader(r, 0, math.MaxInt64)) 436 } 437 } 438 439 // WithFileName constructs an FDTReader with the provided file name. 440 func WithFileName(n string) FDTReader { 441 return func() (*FDT, error) { 442 return ReadFile(n) 443 } 444 } 445 446 // ErrNoValidReaders indicates that no readers succeeded. 447 var ErrNoValidReaders = errors.New("No FDT readers succeeded") 448 449 // New returns a new FDT, trying each FDTReader in turn 450 // until it succeeds or all have failed. It will return 451 // the last error. 452 // TODO: once we move to go 1.20, use the new error tree 453 // support. 454 func New(readers ...FDTReader) (*FDT, error) { 455 for _, r := range readers { 456 f, err := r() 457 if err != nil { 458 continue 459 } 460 return f, nil 461 } 462 return nil, ErrNoValidReaders 463 }