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 }