github.com/zaolin/u-root@v0.0.0-20200428085104-64aaafd46c6d/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  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"os/signal"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/u-root/u-root/pkg/booter"
    17  	"github.com/u-root/u-root/pkg/ipmi"
    18  	"github.com/u-root/u-root/pkg/smbios"
    19  )
    20  
    21  var (
    22  	allowInteractive = flag.Bool("i", true, "Allow user to interrupt boot process and run commands")
    23  	doQuiet          = flag.Bool("q", false, "Disable verbose output")
    24  	interval         = flag.Int("I", 1, "Interval in seconds before looping to the next boot command")
    25  	noDefaultBoot    = flag.Bool("nodefault", false, "Do not attempt default boot entries if regular ones fail")
    26  )
    27  
    28  var defaultBootsequence = [][]string{
    29  	{"fbnetboot", "-userclass", "linuxboot"},
    30  	{"localboot", "-grub"},
    31  }
    32  
    33  // Product list for running IPMI OEM commands
    34  var productList = [2]string{"Tioga Pass", "Mono Lake"}
    35  
    36  var selRecorded bool
    37  
    38  func isMatched(productName string) bool {
    39  	for _, v := range productList {
    40  		if strings.HasPrefix(productName, v) {
    41  			return true
    42  		}
    43  	}
    44  	return false
    45  }
    46  
    47  func getSystemProductName(si *smbios.Info) (string, error) {
    48  	t1, err := si.GetSystemInfo()
    49  	if err != nil {
    50  		log.Printf("Error getting System Information: %v", err)
    51  		return "", err
    52  	}
    53  	return t1.ProductName, nil
    54  }
    55  
    56  func getSystemFWVersion(si *smbios.Info) (string, error) {
    57  	t0, err := si.GetBIOSInfo()
    58  	if err != nil {
    59  		log.Printf("Error getting BIOS Information: %v", err)
    60  		return "", err
    61  	}
    62  	return t0.Version, nil
    63  }
    64  
    65  func checkCMOSClear(ipmi *ipmi.IPMI) error {
    66  	if cmosclear, bootorder, err := ipmi.IsCMOSClearSet(); cmosclear {
    67  		log.Printf("CMOS clear starts")
    68  		if err = cmosClear(); err != nil {
    69  			return err
    70  		}
    71  		// ToDo: Reset RW_VPD to default values
    72  		if err = ipmi.ClearCMOSClearValidBits(bootorder); err != nil {
    73  			return err
    74  		}
    75  		addSEL("cmosclear")
    76  		if err = reboot(); err != nil {
    77  			return err
    78  		}
    79  	} else if err != nil {
    80  		return err
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  func runIPMICommands() {
    87  	i, err := ipmi.Open(0)
    88  	if err != nil {
    89  		log.Printf("Failed to open ipmi device %v, watchdog may still be running", err)
    90  		return
    91  	}
    92  	defer i.Close()
    93  
    94  	if err = i.ShutoffWatchdog(); err != nil {
    95  		log.Printf("Failed to stop watchdog %v.", err)
    96  	} else {
    97  		log.Printf("Watchdog is stopped.")
    98  	}
    99  
   100  	// Below IPMI commands would require SMBIOS data
   101  	si, err := smbios.FromSysfs()
   102  	if err != nil {
   103  		log.Printf("Error reading SMBIOS info: %v", err)
   104  		return
   105  	}
   106  
   107  	if fwVersion, err := getSystemFWVersion(si); err == nil {
   108  		log.Printf("System firmware version: %s", fwVersion)
   109  		if err = i.SetSystemFWVersion(fwVersion); err != nil {
   110  			log.Printf("Failed to set system firmware version to BMC %v.", err)
   111  		}
   112  	}
   113  
   114  	if productName, err := getSystemProductName(si); err == nil {
   115  		if isMatched(productName) {
   116  			log.Printf("Running OEM IPMI commands.")
   117  			if err = checkCMOSClear(i); err != nil {
   118  				log.Printf("IPMI CMOS clear err: %v", err)
   119  			}
   120  
   121  			dimmInfo, err := ipmi.GetOemIpmiDimmInfo(si)
   122  			if err == nil {
   123  				if err = i.SendOemIpmiDimmInfo(dimmInfo); err == nil {
   124  					log.Printf("Send the information of DIMMs to BMC.")
   125  				} else {
   126  					log.Printf("Failed to send the information of DIMMs to BMC: %v.", err)
   127  				}
   128  			} else {
   129  				log.Printf("Failed to get the information of DIMMs: %v.", err)
   130  			}
   131  
   132  			processorInfo, err := ipmi.GetOemIpmiProcessorInfo(si)
   133  			if err == nil {
   134  				if err = i.SendOemIpmiProcessorInfo(processorInfo); err == nil {
   135  					log.Printf("Send the information of processors to BMC.")
   136  				} else {
   137  					log.Printf("Failed to send the information of processors to BMC: %v.", err)
   138  				}
   139  			} else {
   140  				log.Printf("Failed to get the information of Processors: %v.", err)
   141  			}
   142  		} else {
   143  			log.Printf("No product name is matched for OEM commands.")
   144  		}
   145  	}
   146  }
   147  
   148  func addSEL(sequence string) {
   149  	var bootErr ipmi.Event
   150  
   151  	i, err := ipmi.Open(0)
   152  	if err != nil {
   153  		log.Printf("Failed to open ipmi device to send SEL %v", err)
   154  		return
   155  	}
   156  	defer i.Close()
   157  
   158  	switch sequence {
   159  	case "fbnetboot":
   160  		bootErr.RecordID = 0
   161  		bootErr.RecordType = ipmi.OEM_NTS_TYPE
   162  		bootErr.OEMNontsDefinedData[0] = 0x28
   163  		bootErr.OEMNontsDefinedData[5] = 0xf0
   164  		for idx := 6; idx < 13; idx++ {
   165  			bootErr.OEMNontsDefinedData[idx] = 0xff
   166  		}
   167  		if err := i.LogSystemEvent(&bootErr); err != nil {
   168  			log.Printf("SEL recorded: %s fail\n", sequence)
   169  		}
   170  	case "cmosclear":
   171  		bootErr.RecordID = 0
   172  		bootErr.RecordType = ipmi.OEM_NTS_TYPE
   173  		bootErr.OEMNontsDefinedData[0] = 0x28
   174  		bootErr.OEMNontsDefinedData[5] = 0xf1
   175  		for idx := 6; idx < 13; idx++ {
   176  			bootErr.OEMNontsDefinedData[idx] = 0xff
   177  		}
   178  		if err := i.LogSystemEvent(&bootErr); err != nil {
   179  			log.Printf("SEL recorded: %s fail\n", sequence)
   180  		}
   181  	default:
   182  	}
   183  }
   184  
   185  func main() {
   186  	flag.Parse()
   187  
   188  	log.Print(`
   189                       ____            _                 _                 _   
   190                      / ___| _   _ ___| |_ ___ _ __ ___ | |__   ___   ___ | |_ 
   191                      \___ \| | | / __| __/ _ \ '_ ` + "`" + ` _ \| '_ \ / _ \ / _ \| __|
   192                       ___) | |_| \__ \ ||  __/ | | | | | |_) | (_) | (_) | |_ 
   193                      |____/ \__, |___/\__\___|_| |_| |_|_.__/ \___/ \___/ \__|
   194                             |___/
   195  `)
   196  	runIPMICommands()
   197  	sleepInterval := time.Duration(*interval) * time.Second
   198  	if *allowInteractive {
   199  		log.Printf("**************************************************************************")
   200  		log.Print("Starting boot sequence, press CTRL-C within 5 seconds to drop into a shell")
   201  		log.Printf("**************************************************************************")
   202  		time.Sleep(5 * time.Second)
   203  	} else {
   204  		signal.Ignore()
   205  	}
   206  
   207  	// Get and show boot entries
   208  	bootEntries := booter.GetBootEntries()
   209  	log.Printf("BOOT ENTRIES:")
   210  	for _, entry := range bootEntries {
   211  		log.Printf("    %v) %+v", entry.Name, string(entry.Config))
   212  	}
   213  	for _, entry := range bootEntries {
   214  		log.Printf("Trying boot entry %s: %s", entry.Name, string(entry.Config))
   215  		if err := entry.Booter.Boot(); err != nil {
   216  			log.Printf("Warning: failed to boot with configuration: %+v", entry)
   217  			addSEL(entry.Booter.TypeName())
   218  		}
   219  		if !*doQuiet {
   220  			log.Printf("Sleeping %v before attempting next boot command", sleepInterval)
   221  		}
   222  		time.Sleep(sleepInterval)
   223  	}
   224  
   225  	// if boot entries failed, use the default boot sequence
   226  	log.Printf("Boot entries failed")
   227  
   228  	if !*noDefaultBoot {
   229  		log.Print("Falling back to the default boot sequence")
   230  		for {
   231  			for _, bootcmd := range defaultBootsequence {
   232  				if !*doQuiet {
   233  					bootcmd = append(bootcmd, "-d")
   234  				}
   235  				log.Printf("Running boot command: %v", bootcmd)
   236  				cmd := exec.Command(bootcmd[0], bootcmd[1:]...)
   237  				cmd.Stdout = os.Stdout
   238  				cmd.Stderr = os.Stderr
   239  				if err := cmd.Run(); err != nil {
   240  					log.Printf("Error executing %v: %v", cmd, err)
   241  					if !selRecorded {
   242  						addSEL(bootcmd[0])
   243  					}
   244  				}
   245  			}
   246  			selRecorded = true
   247  
   248  			if !*doQuiet {
   249  				log.Printf("Sleeping %v before attempting next boot command", sleepInterval)
   250  			}
   251  			time.Sleep(sleepInterval)
   252  		}
   253  	}
   254  }