github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/boot/systemboot/main.go (about) 1 // Copyright 2017-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 main 6 7 import ( 8 "flag" 9 "fmt" 10 "log" 11 "os" 12 "os/exec" 13 "os/signal" 14 "strings" 15 "time" 16 17 "github.com/mvdan/u-root-coreutils/pkg/boot/systembooter" 18 "github.com/mvdan/u-root-coreutils/pkg/ipmi" 19 "github.com/mvdan/u-root-coreutils/pkg/ipmi/ocp" 20 "github.com/mvdan/u-root-coreutils/pkg/smbios" 21 "github.com/mvdan/u-root-coreutils/pkg/vpd" 22 ) 23 24 var ( 25 allowInteractive = flag.Bool("i", true, "Allow user to interrupt boot process and run commands") 26 doQuiet = flag.Bool("q", false, fmt.Sprintf("Disable verbose output. If not specified, read it from VPD var '%s'. Default false", vpdSystembootLogLevel)) 27 interval = flag.Int("I", 1, "Interval in seconds before looping to the next boot command") 28 noDefaultBoot = flag.Bool("nodefault", false, "Do not attempt default boot entries if regular ones fail") 29 ) 30 31 const ( 32 // vpdSystembootLogLevel is the name of the VPD variable used to set the log level. 33 vpdSystembootLogLevel = "systemboot_log_level" 34 ) 35 36 // isFlagPassed checks whether a flag was explicitly passed on the command line 37 func isFlagPassed(name string) bool { 38 found := false 39 flag.Visit(func(f *flag.Flag) { 40 if f.Name == name { 41 found = true 42 } 43 }) 44 return found 45 } 46 47 var defaultBootsequence = [][]string{ 48 {"fbnetboot", "-userclass", "linuxboot"}, 49 {"localboot", "-grub"}, 50 } 51 52 // VPD variable for enabling IPMI BMC overriding boot order, default is not set 53 const VpdBmcBootOrderOverride = "bmc_bootorder_override" 54 55 var bmcBootOverride bool 56 57 // Product list for running IPMI OEM commands 58 var productList = [5]string{"Tioga Pass", "Mono Lake", "Delta Lake", "Crater Lake", "S9S"} 59 60 var selRecorded bool 61 62 func isMatched(productName string) bool { 63 for _, v := range productList { 64 if strings.HasPrefix(productName, v) { 65 return true 66 } 67 } 68 return false 69 } 70 71 func getBaseboardProductName(si *smbios.Info) (string, error) { 72 t2, err := si.GetBaseboardInfo() 73 if err != nil { 74 log.Printf("Error getting Baseboard Information: %v", err) 75 return "", err 76 } 77 return t2[0].Product, nil 78 } 79 80 func getSystemFWVersion(si *smbios.Info) (string, error) { 81 t0, err := si.GetBIOSInfo() 82 if err != nil { 83 log.Printf("Error getting BIOS Information: %v", err) 84 return "", err 85 } 86 return t0.Version, nil 87 } 88 89 func checkCMOSClear(ipmi *ipmi.IPMI) error { 90 if cmosclear, bootorder, err := ocp.IsCMOSClearSet(ipmi); cmosclear { 91 log.Printf("CMOS clear starts") 92 if err = cmosClear(); err != nil { 93 return err 94 } 95 if err = vpd.ClearRwVpd(); err != nil { 96 return err 97 } 98 99 if err = ocp.ClearCMOSClearValidBits(ipmi, bootorder); err != nil { 100 return err 101 } 102 addSEL("cmosclear") 103 if err = reboot(); err != nil { 104 return err 105 } 106 } else if err != nil { 107 return err 108 } 109 110 return nil 111 } 112 113 func runIPMICommands() { 114 i, err := ipmi.Open(0) 115 if err != nil { 116 log.Printf("Failed to open ipmi device %v, watchdog may still be running", err) 117 return 118 } 119 defer i.Close() 120 121 if err = i.ShutoffWatchdog(); err != nil { 122 log.Printf("Failed to stop watchdog %v.", err) 123 } else { 124 log.Printf("Watchdog is stopped.") 125 } 126 // Try RW_VPD first 127 value, err := systembooter.Get(VpdBmcBootOrderOverride, false) 128 if err != nil { 129 // Try RO_VPD 130 value, err = systembooter.Get(VpdBmcBootOrderOverride, true) 131 } 132 if err == nil && string(value) == "1" { 133 bmcBootOverride = true 134 } 135 log.Printf("VPD %s is %v", VpdBmcBootOrderOverride, string(value)) 136 // Below IPMI commands would require SMBIOS data 137 si, err := smbios.FromSysfs() 138 if err != nil { 139 log.Printf("Error reading SMBIOS info: %v", err) 140 return 141 } 142 143 if fwVersion, err := getSystemFWVersion(si); err == nil { 144 log.Printf("System firmware version: %s", fwVersion) 145 if err = i.SetSystemFWVersion(fwVersion); err != nil { 146 log.Printf("Failed to set system firmware version to BMC %v.", err) 147 } 148 } 149 150 if productName, err := getBaseboardProductName(si); err == nil { 151 if isMatched(productName) { 152 log.Printf("Running OEM IPMI commands.") 153 if err = checkCMOSClear(i); err != nil { 154 log.Printf("IPMI CMOS clear err: %v", err) 155 } 156 if err = ocp.CheckBMCBootOrder(i, bmcBootOverride); err != nil { 157 log.Printf("Failed to sync BMC Boot Order %v.", err) 158 } 159 dimmInfo, err := ocp.GetOemIpmiDimmInfo(si) 160 if err == nil { 161 if err = ocp.SendOemIpmiDimmInfo(i, dimmInfo); err == nil { 162 log.Printf("Send the information of DIMMs to BMC.") 163 } else { 164 log.Printf("Failed to send the information of DIMMs to BMC: %v.", err) 165 } 166 } else { 167 log.Printf("Failed to get the information of DIMMs: %v.", err) 168 } 169 170 processorInfo, err := ocp.GetOemIpmiProcessorInfo(si) 171 if err == nil { 172 if err = ocp.SendOemIpmiProcessorInfo(i, processorInfo); err == nil { 173 log.Printf("Send the information of processors to BMC.") 174 } else { 175 log.Printf("Failed to send the information of processors to BMC: %v.", err) 176 } 177 } else { 178 log.Printf("Failed to get the information of Processors: %v.", err) 179 } 180 181 BootDriveInfo, err := ocp.GetOemIpmiBootDriveInfo(si) 182 if err == nil { 183 if BootDriveInfo != nil { 184 if err = ocp.SendOemIpmiBootDriveInfo(i, BootDriveInfo); err == nil { 185 log.Printf("Send the information of boot drive to BMC.") 186 } else { 187 log.Printf("Failed to send the information of boot drive to BMC: %v.", err) 188 } 189 } else { 190 log.Printf("The information of boot drive is not found.") 191 } 192 } else { 193 log.Printf("Failed to get the information of boot drive: %v.", err) 194 } 195 196 if err = ocp.SetOemIpmiPostEnd(i); err == nil { 197 log.Printf("Send IPMI POST end to BMC") 198 } else { 199 log.Printf("Failed to send IPMI POST end to BMC: %v.", err) 200 } 201 202 } else { 203 log.Printf("No product name is matched for OEM commands.") 204 } 205 } 206 } 207 208 func addSEL(sequence string) { 209 var bootErr ipmi.Event 210 211 i, err := ipmi.Open(0) 212 if err != nil { 213 log.Printf("Failed to open ipmi device to send SEL %v", err) 214 return 215 } 216 defer i.Close() 217 218 switch sequence { 219 case "netboot": 220 fallthrough 221 case "fbnetboot": 222 bootErr.RecordID = 0 223 bootErr.RecordType = ipmi.OEM_NTS_TYPE 224 bootErr.OEMNontsDefinedData[0] = 0x28 225 bootErr.OEMNontsDefinedData[5] = 0xf0 226 for idx := 6; idx < 13; idx++ { 227 bootErr.OEMNontsDefinedData[idx] = 0xff 228 } 229 if err := i.LogSystemEvent(&bootErr); err != nil { 230 log.Printf("SEL recorded: %s fail\n", sequence) 231 } 232 case "cmosclear": 233 bootErr.RecordID = 0 234 bootErr.RecordType = ipmi.OEM_NTS_TYPE 235 bootErr.OEMNontsDefinedData[0] = 0x28 236 bootErr.OEMNontsDefinedData[5] = 0xf1 237 for idx := 6; idx < 13; idx++ { 238 bootErr.OEMNontsDefinedData[idx] = 0xff 239 } 240 if err := i.LogSystemEvent(&bootErr); err != nil { 241 log.Printf("SEL recorded: %s fail\n", sequence) 242 } 243 default: 244 } 245 } 246 247 // getDebugEnabled checks whether debug output is requested, either via command line or via VPD 248 // variables. 249 // If -q was explicitly passed on the command line, will use that value, otherwise will look for 250 // the VPD variable "systemboot_log_level". 251 // Valid values are coreboot loglevels https://review.coreboot.org/cgit/coreboot.git/tree/src/commonlib/include/commonlib/loglevel.h, 252 // either as integer (1, 2) or string ("debug"). 253 // If the VPD variable is missing or it is set to an invalid value, it will use the default. 254 func getDebugEnabled() bool { 255 if isFlagPassed("q") { 256 return !*doQuiet 257 } 258 259 // -q was not passed, so `doQuiet` contains the default value 260 defaultDebugEnabled := !*doQuiet 261 // check for the VPD variable "systemboot_log_level". First the read-write, then the read-only 262 v, err := vpd.Get(vpdSystembootLogLevel, false) 263 if err != nil { 264 // TODO do not print warning if file is not found 265 log.Printf("Warning: failed to read read-write VPD variable '%s', will try the read-only one. Error was: %v", vpdSystembootLogLevel, err) 266 v, err = vpd.Get(vpdSystembootLogLevel, true) 267 if err != nil { 268 // TODO do not print warning if file is not found 269 log.Printf("Warning: failed to read read-only VPD variable '%s', will use the default value. Error was: %v", vpdSystembootLogLevel, err) 270 return defaultDebugEnabled 271 } 272 } 273 level := strings.ToLower(strings.TrimSpace(string(v))) 274 switch level { 275 case "0", "emerg", "1", "alert", "2", "crit", "3", "err", "4", "warning", "9", "never": 276 // treat as quiet 277 return false 278 case "5", "notice", "6", "info", "7", "debug", "8", "spew": 279 // treat as debug 280 return true 281 default: 282 log.Printf("Invalid value '%s' for VPD variable '%s', using default", level, vpdSystembootLogLevel) 283 return defaultDebugEnabled 284 } 285 } 286 287 func main() { 288 flag.Parse() 289 290 debugEnabled := getDebugEnabled() 291 292 log.Print(` 293 ____ _ _ _ 294 / ___| _ _ ___| |_ ___ _ __ ___ | |__ ___ ___ | |_ 295 \___ \| | | / __| __/ _ \ '_ ` + "`" + ` _ \| '_ \ / _ \ / _ \| __| 296 ___) | |_| \__ \ || __/ | | | | | |_) | (_) | (_) | |_ 297 |____/ \__, |___/\__\___|_| |_| |_|_.__/ \___/ \___/ \__| 298 |___/ 299 `) 300 runIPMICommands() 301 sleepInterval := time.Duration(*interval) * time.Second 302 if *allowInteractive { 303 log.Printf("**************************************************************************") 304 log.Print("Starting boot sequence, press CTRL-C within 5 seconds to drop into a shell") 305 log.Printf("**************************************************************************") 306 time.Sleep(5 * time.Second) 307 } else { 308 signal.Ignore() 309 } 310 311 // Get and show boot entries 312 var bootEntries []systembooter.BootEntry 313 if bmcBootOverride && ocp.BmcUpdatedBootorder { 314 bootEntries = ocp.BootEntries 315 } else { 316 bootEntries = systembooter.GetBootEntries() 317 } 318 log.Printf("BOOT ENTRIES:") 319 for _, entry := range bootEntries { 320 log.Printf(" %v) %+v", entry.Name, string(entry.Config)) 321 } 322 for _, entry := range bootEntries { 323 log.Printf("Trying boot entry %s: %s", entry.Name, string(entry.Config)) 324 if err := entry.Booter.Boot(debugEnabled); err != nil { 325 log.Printf("Warning: failed to boot with configuration: %+v", entry) 326 addSEL(entry.Booter.TypeName()) 327 } 328 if debugEnabled { 329 log.Printf("Sleeping %v before attempting next boot command", sleepInterval) 330 } 331 time.Sleep(sleepInterval) 332 } 333 334 // if boot entries failed, use the default boot sequence 335 log.Printf("Boot entries failed") 336 337 if !*noDefaultBoot { 338 log.Print("Falling back to the default boot sequence") 339 for { 340 for _, bootcmd := range defaultBootsequence { 341 if debugEnabled { 342 bootcmd = append(bootcmd, "-d") 343 } 344 log.Printf("Running boot command: %v", bootcmd) 345 cmd := exec.Command(bootcmd[0], bootcmd[1:]...) 346 cmd.Stdout = os.Stdout 347 cmd.Stderr = os.Stderr 348 if err := cmd.Run(); err != nil { 349 log.Printf("Error executing %v: %v", cmd, err) 350 if !selRecorded { 351 addSEL(bootcmd[0]) 352 } 353 } 354 } 355 selRecorded = true 356 357 if debugEnabled { 358 log.Printf("Sleeping %v before attempting next boot command", sleepInterval) 359 } 360 time.Sleep(sleepInterval) 361 } 362 } 363 }