github.com/platinasystems/nvram@v1.0.1-0.20190709235807-51a23abd5aec/nvram.go (about)

     1  // Copyright © 2019 Platina Systems, Inc. All rights reserved.
     2  // Use of this source code is governed by the GPL-2 license described in the
     3  // LICENSE file.
     4  
     5  // Package nvram provides access to the coreboot CMOS option table and allows
     6  // reading, writing and listing CMOS parameters.
     7  package nvram
     8  
     9  import (
    10  	"encoding/binary"
    11  	"errors"
    12  	"fmt"
    13  	"github.com/platinasystems/nvram/debug"
    14  	"strings"
    15  	"sync/atomic"
    16  )
    17  
    18  var (
    19  	ErrNVRAMAccessInUse = errors.New("nvram: NVRAM is busy.")
    20  	ErrInvalidCMOSIndex = errors.New("nvram: Invalid CMOS index!")
    21  	ErrCMOSNotOpen = errors.New("nvram: CMOS Not Opened")
    22  )
    23  
    24  var lockstate uint32
    25  
    26  type NVRAM struct {
    27  	CMOS
    28  	*Layout
    29  	modified bool
    30  }
    31  
    32  // Open opens NVRAM access.
    33  //
    34  // Calling Open with no parameters will use the machine's coreboot table
    35  // and NVRAM hardware.
    36  //		nv.Open()
    37  // Calling Open with a single layout file name will use the CMOS layout
    38  // text file.
    39  //		nv.Open("cmos_layout")
    40  // If the layout file name ends in .bin the coreboot CMOS layout will be
    41  // read in binary form.
    42  //		nv.Open("cmos_layout.bin")
    43  // If the first argument is empty the machine's coreboot table will be used.
    44  //		nv.Open("")
    45  // Calling Open with a second CMOS memory file name will use the mem mapped
    46  // CMOS file instead of the NVRAM hardware.
    47  //		nv.Open("", "cmos.bin")
    48  
    49  func (nv *NVRAM) Open(args ...string) (err error) {
    50  	// Only one NVRAM access is allowed at a time.
    51  	if !atomic.CompareAndSwapUint32(&lockstate, 0, 1) {
    52  		return ErrNVRAMAccessInUse
    53  	}
    54  
    55  	// Get file name arguments if they exist.
    56  	var layoutFileName, cmosMemFileName string
    57  	if len(args) > 0 {
    58  		layoutFileName = args[0]
    59  	}
    60  	if len(args) > 1 {
    61  		cmosMemFileName = args[1]
    62  	}
    63  
    64  	// Load layout file from machine's Coreboot table, coreboot table binary,
    65  	// or CMOS layout text file.
    66  	if layoutFileName == "" {
    67  		nv.Layout, err = ReadLayoutFromCoreBootTable()
    68  	} else {
    69  		if strings.HasSuffix(layoutFileName, ".bin") {
    70  			nv.Layout, err = ReadLayoutFromCMOSTableBinary(layoutFileName)
    71  		} else {
    72  			nv.Layout, err = ReadLayoutFromTextFile(layoutFileName)
    73  		}
    74  	}
    75  
    76  	// If we don't have any CMOS layout return error.
    77  	if err != nil {
    78  		return
    79  	}
    80  
    81  	// Open CMOS NVRAM access with hardware access or using a binary file.
    82  	if cmosMemFileName == "" {
    83  		err = nv.CMOS.Open()
    84  	} else {
    85  		err = nv.CMOS.OpenMem(cmosMemFileName)
    86  	}
    87  
    88  	// If we don't have any CMOS access return error
    89  	if err != nil {
    90  		return
    91  	}
    92  
    93  	// Initialize CMOS with layout checksum
    94  	nv.CMOS.checksum = *nv.Layout.cmosChecksum
    95  
    96  	return
    97  }
    98  
    99  // Close closes the currently opened CMOS layout and NVRAM access.
   100  // If the CMOS data has been modified a new checksum is calculed and written
   101  // before closing the CMOS access.
   102  func (nv *NVRAM) Close() (err error) {
   103  
   104  	defer atomic.StoreUint32(&lockstate, 0)
   105  
   106  	if nv.modified {
   107  		debug.Trace(debug.LevelMSG1, "NVRAM Modified computing checksum.\n")
   108  		sum, err := nv.CMOS.ComputeChecksum()
   109  		if err == nil {
   110  			debug.Trace(debug.LevelMSG1, "NVRAM Modified writing checksum %02X.\n", sum)
   111  			err = nv.CMOS.WriteChecksum(sum)
   112  			if err == nil {
   113  				debug.Trace(debug.LevelMSG1, "NVRAM cheksum updated.\n")
   114  				nv.modified = false
   115  			}
   116  		}
   117  	}
   118  
   119  	return nv.CMOS.Close()
   120  }
   121  
   122  // ValidateChechsum will calculate the CMOS checksum on the checksum area
   123  // and compare it to the checksum value.
   124  // If there is an error it will be a warning and contain the computed and
   125  // stored checksum value.
   126  func (nv *NVRAM) ValidateChecksum() (err error) {
   127  	computed_sum, err := nv.CMOS.ComputeChecksum()
   128  	if err != nil {
   129  		return
   130  	}
   131  	stored_sum, err := nv.CMOS.ReadChecksum()
   132  	if err != nil {
   133  		return
   134  	}
   135  
   136  	if computed_sum != stored_sum {
   137  		err = fmt.Errorf("Warning: coreboot CMOS checksum is bad.\nComputed checksum: 0x%X. Stored checksum: 0x%X",
   138  			computed_sum, stored_sum)
   139  	}
   140  	return
   141  }
   142  
   143  // NewParameterType will return an interface value for the CMOS parameter.
   144  // This will wither be a string or a uint64.
   145  func (nv *NVRAM) NewParameterType(name string) (value interface{}, err error) {
   146  	e, ok := nv.FindCMOSEntry(name)
   147  	if !ok {
   148  		err = fmt.Errorf("CMOS parameter %s not found.", name)
   149  		return
   150  	}
   151  
   152  	switch e.config {
   153  	case CMOSEntryString:
   154  		fallthrough
   155  	case CMOSEntryEnum:
   156  		value = string("")
   157  	case CMOSEntryHex:
   158  		value = uint64(0)
   159  	case CMOSEntryReserved:
   160  		err = fmt.Errorf("Parameter %s is reserved.", e.name)
   161  	default:
   162  		err = fmt.Errorf("CMOS entry %s has invalid config type.", e.name)
   163  	}
   164  
   165  	return
   166  }
   167  
   168  // WriteCMOSParameter writes provided value to a named CMOS parameter.
   169  func (nv *NVRAM) WriteCMOSParameter(name string, value interface{}) (err error) {
   170  	e, ok := nv.FindCMOSEntry(name)
   171  	if !ok || name == "check_sum" {
   172  		err = fmt.Errorf("CMOS parameter %s not found.", name)
   173  		return
   174  	}
   175  
   176  	var v []byte
   177  
   178  	switch e.config {
   179  	case CMOSEntryString:
   180  		s, ok := value.(string)
   181  		if !ok {
   182  			err = fmt.Errorf("A string value is required.")
   183  		}
   184  		if e.length < uint(len(s)*8) {
   185  			err = fmt.Errorf("Can not write value %s to CMOS parameter %s that is only %d-bits wide.", s, name, e.length)
   186  			return
   187  		}
   188  		// Copy string to byte array
   189  		v = make([]byte, (e.length+7)/8)
   190  		copy(v[:], []byte(s))
   191  
   192  	case CMOSEntryEnum:
   193  		s, ok := value.(string)
   194  		if !ok {
   195  			err = fmt.Errorf("A string value is required.")
   196  		}
   197  		n, ok := nv.FindCMOSEnumValue(e.config_id, s)
   198  		if !ok {
   199  			err = fmt.Errorf("Bad value for parameter %s", name)
   200  			return
   201  		}
   202  		// Check length
   203  		if e.length < 64 && (uint64(n) >= (uint64(1) << e.length)) {
   204  			err = fmt.Errorf("Enum value is too wide for parameter %s", name)
   205  			return
   206  		}
   207  		// Copy uint64 to byte array
   208  		v = make([]byte, 8)
   209  		binary.LittleEndian.PutUint64(v, uint64(n))
   210  
   211  	case CMOSEntryHex:
   212  		n, ok := value.(uint64)
   213  		if !ok {
   214  			err = fmt.Errorf("A uint64 value is required.")
   215  		}
   216  		// Check length
   217  		if e.length < 64 && (n >= (uint64(1) << e.length)) {
   218  			err = fmt.Errorf("Can not write value 0x%X to CMOS parameter %s that is only %d-bits wide.", n, name, e.length)
   219  			return
   220  		}
   221  
   222  		// Copy uint64 to byte array
   223  		v = make([]byte, 8)
   224  		binary.LittleEndian.PutUint64(v, n)
   225  	}
   226  
   227  	err = nv.CMOS.WriteEntry(e, v)
   228  	if err == nil {
   229  		nv.modified = true
   230  	}
   231  	return
   232  }
   233  
   234  // ReadCMOSParameter read the current value of a named CMOS parameter.
   235  func (nv *NVRAM) ReadCMOSParameter(name string) (value interface{}, err error) {
   236  	e, ok := nv.FindCMOSEntry(name)
   237  	if !ok || name == "check_sum" {
   238  		err = fmt.Errorf("CMOS parameter %s not found.", name)
   239  		return
   240  	}
   241  
   242  	v, err := nv.CMOS.ReadEntry(e)
   243  	if err != nil {
   244  		return
   245  	}
   246  
   247  	switch e.config {
   248  	case CMOSEntryString:
   249  		value = string(v)
   250  	case CMOSEntryEnum:
   251  		n := binary.LittleEndian.Uint64(v)
   252  		s, ok := nv.FindCMOSEnumText(e.config_id, uint(n))
   253  		if !ok {
   254  			s = fmt.Sprintf("0x%X # Bad Value", n)
   255  		}
   256  		value = s
   257  	case CMOSEntryHex:
   258  		value = binary.LittleEndian.Uint64(v)
   259  	default:
   260  		err = fmt.Errorf("CMOS entry %s has invalid config type.", e.name)
   261  	}
   262  
   263  	return
   264  }