github.com/linuxboot/fiano@v1.2.0/pkg/intel/metadata/fit/entry_headers.go (about)

     1  // Copyright 2017-2021 the LinuxBoot 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 fit
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"strings"
    14  
    15  	"github.com/xaionaro-go/bytesextra"
    16  )
    17  
    18  var (
    19  	entryHeadersSize = uint(binary.Size(EntryHeaders{}))
    20  )
    21  
    22  // EntryHeaders implements a "FIT Entry Format".
    23  //
    24  // See "Table 1-1" in "1.2 Firmware Interface Table" in "Firmware Interface Table" specification:
    25  //  * https://www.intel.com/content/dam/www/public/us/en/documents/guides/fit-bios-specification.pdf
    26  //
    27  // Descriptions of the fields are adapted descriptions from the document by the link above.
    28  type EntryHeaders struct {
    29  	// Address is the base address of the firmware component.
    30  	// Must be aligned on 16 byte boundary.
    31  	Address Address64
    32  
    33  	Size Uint24
    34  
    35  	// Reserved should always be equal to zero.
    36  	Reserved uint8
    37  
    38  	Version EntryVersion
    39  
    40  	TypeAndIsChecksumValid TypeAndIsChecksumValid
    41  
    42  	Checksum uint8
    43  }
    44  
    45  func (hdr EntryHeaders) copy() *EntryHeaders {
    46  	return &hdr
    47  }
    48  
    49  // GoString implements fmt.GoStringer.
    50  func (hdr *EntryHeaders) GoString() string {
    51  	var result strings.Builder
    52  	result.WriteString(fmt.Sprintf("   Address: 0x%x\n", hdr.Address.Pointer()))
    53  	result.WriteString(fmt.Sprintf("   size: 0x%x\n", hdr.Size.Uint32()))
    54  	result.WriteString(fmt.Sprintf("   Version: 0x%x\n", uint16(hdr.Version)))
    55  	result.WriteString(fmt.Sprintf("   Type: 0x%x\n", uint8(hdr.TypeAndIsChecksumValid)))
    56  	result.WriteString(fmt.Sprintf("   Checksum: 0x%x\n", hdr.Checksum))
    57  	return result.String()
    58  }
    59  
    60  type entryHeadersForJSON struct {
    61  	Address         uint64
    62  	Size            uint32
    63  	Reserved        uint8 `json:",omitempty"`
    64  	Version         EntryVersion
    65  	Type            EntryType
    66  	IsChecksumValid bool
    67  	Checksum        uint8
    68  }
    69  
    70  // MarshalJSON just implements encoding/json.Marshaler
    71  func (hdr EntryHeaders) MarshalJSON() ([]byte, error) {
    72  	return json.Marshal(&entryHeadersForJSON{
    73  		Address:         hdr.Address.Pointer(),
    74  		Size:            hdr.Size.Uint32(),
    75  		Reserved:        hdr.Reserved,
    76  		Version:         hdr.Version,
    77  		Type:            hdr.Type(),
    78  		IsChecksumValid: hdr.IsChecksumValid(),
    79  		Checksum:        hdr.Checksum,
    80  	})
    81  }
    82  
    83  // UnmarshalJSON just implements encoding/json.Unmarshaler
    84  func (hdr *EntryHeaders) UnmarshalJSON(b []byte) error {
    85  	var parsed entryHeadersForJSON
    86  	err := json.Unmarshal(b, &parsed)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	*hdr = EntryHeaders{
    91  		Address:  Address64(parsed.Address),
    92  		Reserved: parsed.Reserved,
    93  		Version:  parsed.Version,
    94  		Checksum: parsed.Checksum,
    95  	}
    96  	hdr.Size.SetUint32(parsed.Size)
    97  	hdr.TypeAndIsChecksumValid.SetType(parsed.Type)
    98  	hdr.TypeAndIsChecksumValid.SetIsChecksumValid(parsed.IsChecksumValid)
    99  	return nil
   100  }
   101  
   102  // Uint24 is a 24 bit unsigned little-endian integer value.
   103  type Uint24 struct {
   104  	Value [3]byte
   105  }
   106  
   107  // Uint32 returns the value as parsed uint32.
   108  //
   109  // If the value is used in "Size" then in the most cases the value should be
   110  // shifted with "<< 4" to get the real size value.
   111  //
   112  // See also the code of EntryHeaders.getDataCoordinates()
   113  func (size Uint24) Uint32() uint32 {
   114  	b := make([]byte, 4)
   115  	copy(b[:], size.Value[:])
   116  	return binary.LittleEndian.Uint32(b)
   117  }
   118  
   119  // SetUint32 sets the value. See also Uint32.
   120  func (size *Uint24) SetUint32(newValue uint32) {
   121  	if newValue >= 1<<24 {
   122  		panic(fmt.Errorf("too big integer: %d >= %d", newValue, 1<<24))
   123  	}
   124  	b := make([]byte, 4)
   125  	binary.LittleEndian.PutUint32(b, newValue)
   126  	copy(size.Value[:], b[:])
   127  }
   128  
   129  // MarshalJSON just implements encoding/json.Marshaler
   130  func (size Uint24) MarshalJSON() ([]byte, error) {
   131  	return json.Marshal(size.Uint32())
   132  }
   133  
   134  // UnmarshalJSON just implements encoding/json.Unmarshaler
   135  func (size *Uint24) UnmarshalJSON(b []byte) error {
   136  	var parsed uint32
   137  	err := json.Unmarshal(b, &parsed)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	if parsed >= 1<<24 {
   142  		return fmt.Errorf("too big integer: %d >= %d", parsed, 1<<24)
   143  	}
   144  	size.SetUint32(parsed)
   145  	return nil
   146  }
   147  
   148  // Address64 is a 64bit address type
   149  type Address64 uint64
   150  
   151  // Pointer returns the pointer which could be used for pointer arithmetics.
   152  func (addr Address64) Pointer() uint64 { return uint64(addr) }
   153  
   154  // Offset returns an offset from the beginning of a firmware of a defined size.
   155  func (addr Address64) Offset(firmwareSize uint64) uint64 {
   156  	return CalculateOffsetFromPhysAddr(addr.Pointer(), firmwareSize)
   157  }
   158  
   159  // SetOffset sets the value to a physical address corresponding to
   160  // an offset from the beginning of the firmware.
   161  //
   162  // See also the description of calculatePhysAddrFromOffset.
   163  func (addr *Address64) SetOffset(offset, firmwareSize uint64) {
   164  	physAddr := CalculatePhysAddrFromOffset(offset, firmwareSize)
   165  	*addr = Address64(physAddr)
   166  }
   167  
   168  // String implements fmt.Stringer
   169  func (addr Address64) String() string { return fmt.Sprintf("0x%x", addr.Pointer()) }
   170  
   171  // EntryVersion contains the component's version number in binary
   172  // coded decimal (BCD) format. For the FIT header entry, the value in this
   173  // field will indicate the revision number of the FIT data structure.
   174  // The upper byte of the revision field indicates the major revision and
   175  // the lower byte indicates the minor revision. The format 0x1234 conveys
   176  // the major number encoded in the first two digits and the minor number
   177  // in the last two with a fixed point assumed in between
   178  type EntryVersion uint16
   179  
   180  // Major returns the major part of the entry version
   181  func (ver EntryVersion) Major() uint8 { return uint8(ver & 0xff00 >> 8) }
   182  
   183  // Minor returns the minor part of the entry version
   184  func (ver EntryVersion) Minor() uint8 { return uint8(ver & 0xff) }
   185  
   186  func (ver EntryVersion) String() string {
   187  	b, _ := ver.MarshalJSON()
   188  	return string(b)
   189  }
   190  
   191  type entryVersionStruct struct {
   192  	Major uint8 `json:"maj"`
   193  	Minor uint8 `json:"min,omitempty"`
   194  }
   195  
   196  // MarshalJSON just implements encoding/json.Marshaler
   197  func (ver EntryVersion) MarshalJSON() ([]byte, error) {
   198  	return json.Marshal(&entryVersionStruct{
   199  		Major: ver.Major(),
   200  		Minor: ver.Minor(),
   201  	})
   202  }
   203  
   204  // UnmarshalJSON just implements encoding/json.Unmarshaler
   205  func (ver *EntryVersion) UnmarshalJSON(b []byte) error {
   206  	parsed := entryVersionStruct{}
   207  	err := json.Unmarshal(b, &parsed)
   208  	if err != nil {
   209  		return err
   210  	}
   211  	*ver = EntryVersion(parsed.Major)<<8 | EntryVersion(parsed.Minor)
   212  	return nil
   213  }
   214  
   215  // SizeM16 is a size in multiple of 16 bytes (M16).
   216  type SizeM16 uint16
   217  
   218  // Size returns the size in bytes
   219  func (size SizeM16) Size() uint     { return uint(size) << 4 }
   220  func (size SizeM16) String() string { return fmt.Sprintf("0x%x*0x10", uint16(size)) }
   221  
   222  // TypeAndIsChecksumValid combines two fields:
   223  // * "C_V" -- Checksum Valid bit. This is a one bit field that indicates,
   224  //            whether component has a valid checksum. CPU must ignore
   225  //            "Checksum" field, if C_V bit is not set.
   226  // * EntryType (see "entry_type.go").
   227  type TypeAndIsChecksumValid uint8
   228  
   229  // IsChecksumValid returns bit "C_V" of the FIT entry.
   230  //
   231  // A quote from the specification:
   232  // Checksum Valid bit. This is a one bit field that indicates, whether
   233  // component has a valid checksum. CPU must ignore CHKSUM field, if C_V bit is not set.
   234  func (f TypeAndIsChecksumValid) IsChecksumValid() bool {
   235  	return f&0x80 != 0
   236  }
   237  
   238  // Type returns field EntryType ("TYPE" of the FIT entry in terms of
   239  // the specification).
   240  func (f TypeAndIsChecksumValid) Type() EntryType {
   241  	return EntryType(f & 0x7f)
   242  }
   243  
   244  func (f TypeAndIsChecksumValid) String() string {
   245  	b, _ := f.MarshalJSON()
   246  	return string(b)
   247  }
   248  
   249  // SetType sets the value of field EntryType ("TYPE" of the FIT entry in terms of
   250  // the specification).
   251  func (f *TypeAndIsChecksumValid) SetType(newType EntryType) {
   252  	if uint(newType) & ^uint(0x7f) != 0 {
   253  		panic(fmt.Errorf("invalid type: 0x%X", newType))
   254  	}
   255  	otherBits := TypeAndIsChecksumValid(uint(*f) & ^uint(0x7f))
   256  	*f = TypeAndIsChecksumValid(newType) | otherBits
   257  }
   258  
   259  // SetIsChecksumValid sets the value of field IsChecksumValid ("C_V" of the FIT entry in terms of
   260  // the specification).
   261  func (f *TypeAndIsChecksumValid) SetIsChecksumValid(newValue bool) {
   262  	valueBits := TypeAndIsChecksumValid(0)
   263  	if newValue {
   264  		valueBits = TypeAndIsChecksumValid(0x80)
   265  	}
   266  
   267  	otherBits := TypeAndIsChecksumValid(uint(*f) & uint(0x7f))
   268  	*f = valueBits | otherBits
   269  }
   270  
   271  type typeAndIsChecksumValidStruct struct {
   272  	Type            EntryType `json:"type"`
   273  	IsChecksumValid bool      `json:"isChecksumValid,omitempty"`
   274  }
   275  
   276  // MarshalJSON just implements encoding/json.Marshaler
   277  func (f TypeAndIsChecksumValid) MarshalJSON() ([]byte, error) {
   278  	return json.Marshal(&typeAndIsChecksumValidStruct{
   279  		IsChecksumValid: f.IsChecksumValid(),
   280  		Type:            f.Type(),
   281  	})
   282  }
   283  
   284  // UnmarshalJSON just implements encoding/json.Unmarshaler
   285  func (f *TypeAndIsChecksumValid) UnmarshalJSON(b []byte) error {
   286  	parsed := typeAndIsChecksumValidStruct{}
   287  	err := json.Unmarshal(b, &parsed)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	if parsed.Type >= 0x80 {
   292  		return fmt.Errorf(`"type" value is too high`)
   293  	}
   294  	*f = TypeAndIsChecksumValid(parsed.Type & 0x7f)
   295  	if parsed.IsChecksumValid {
   296  		*f |= 0x80
   297  	}
   298  	return nil
   299  }
   300  
   301  // GetEntry returns a full entry (headers + data)
   302  func (hdr EntryHeaders) GetEntry(firmware []byte) Entry {
   303  	return hdr.GetEntryFrom(bytesextra.NewReadWriteSeeker(firmware))
   304  }
   305  
   306  // GetEntryFrom returns a full entry (headers + data)
   307  func (hdr EntryHeaders) GetEntryFrom(firmware io.ReadSeeker) Entry {
   308  	return NewEntry(hdr.copy(), firmware)
   309  }
   310  
   311  // Type returns the type of the FIT entry
   312  func (hdr *EntryHeaders) Type() EntryType {
   313  	return hdr.TypeAndIsChecksumValid.Type()
   314  }
   315  
   316  // IsChecksumValid returns if bit "C_V" has value "1".
   317  func (hdr *EntryHeaders) IsChecksumValid() bool {
   318  	return hdr.TypeAndIsChecksumValid.IsChecksumValid()
   319  }
   320  
   321  func (hdr *EntryHeaders) String() string {
   322  	return fmt.Sprintf("&%+v", *hdr)
   323  }
   324  
   325  var _ io.Writer = (*EntryHeaders)(nil)
   326  
   327  // Write implements io.Writer. It writes the headers in a binary format to `b`.
   328  func (hdr *EntryHeaders) Write(b []byte) (int, error) {
   329  	n, err := hdr.WriteTo(bytes.NewBuffer(b))
   330  	return int(n), err
   331  }
   332  
   333  var _ io.WriterTo = (*EntryHeaders)(nil)
   334  
   335  // WriteTo implements io.WriterTo. It writes the headers in a binary format to `w`.
   336  func (hdr *EntryHeaders) WriteTo(w io.Writer) (int64, error) {
   337  	if hdr == nil {
   338  		return 0, nil
   339  	}
   340  
   341  	err := binary.Write(w, binary.LittleEndian, hdr)
   342  	if err != nil {
   343  		return -1, fmt.Errorf("unable to write headers %#+v: %w", *hdr, err)
   344  	}
   345  
   346  	return int64(binary.Size(*hdr)), nil
   347  }
   348  
   349  // CalculateChecksum calculates the checksum ("CHKSUM")
   350  // according to point 4.0 of the FIT specification.
   351  func (hdr *EntryHeaders) CalculateChecksum() uint8 {
   352  	_copy := *hdr
   353  	_copy.Checksum = 0
   354  
   355  	var buf bytes.Buffer
   356  	if err := binary.Write(&buf, binary.LittleEndian, _copy); err != nil {
   357  		panic(err)
   358  	}
   359  
   360  	result := uint8(0)
   361  	for _, _byte := range buf.Bytes() {
   362  		result += _byte
   363  	}
   364  
   365  	return result
   366  }
   367  
   368  // getDataSegmentCoordinates returns the offset of the data segment
   369  // associated with the entry.
   370  func (hdr *EntryHeaders) getDataSegmentOffset(firmware io.Seeker) (uint64, error) {
   371  	firmwareSize, err := firmware.Seek(0, io.SeekEnd)
   372  	if err != nil {
   373  		return 0, fmt.Errorf("unable to get the size of the firmware: %w", err)
   374  	}
   375  
   376  	return hdr.Address.Offset(uint64(firmwareSize)), nil
   377  }
   378  
   379  // mostCommonGetDataSegmentCoordinates returns the length of the data segment
   380  // associated with the entry using the most common rule:
   381  // * The size equals to "Size" multiplied by 16.
   382  //
   383  // This is considered the most common rule for the most FIT entry types. But different types may break it.
   384  func (hdr *EntryHeaders) mostCommonGetDataSegmentSize() uint64 {
   385  	return uint64(hdr.Size.Uint32()) << 4
   386  }