github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/ipmi/ocp/bootorder.go (about)

     1  // Copyright 2020 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 ocp implements OCP/Facebook-specific IPMI client functions.
     6  package ocp
     7  
     8  import (
     9  	"fmt"
    10  	"log"
    11  	"unsafe"
    12  
    13  	"github.com/mvdan/u-root-coreutils/pkg/boot/systembooter"
    14  	"github.com/mvdan/u-root-coreutils/pkg/ipmi"
    15  	"github.com/mvdan/u-root-coreutils/pkg/vpd"
    16  )
    17  
    18  const (
    19  	// Boot order bit[2:0] defined as
    20  	// 000b: USB device
    21  	// 001b: Network
    22  	// 010b: SATA HDD
    23  	// 011b: SATA-CDROM
    24  	// 100b: Other removable Device
    25  	// If bit[2:0] is 001b (Network), bit3 determines IPv4/IPv6 order,
    26  	// when bit3 is 0: IPv4 first, bit3 is 1: IPv6 first
    27  	NETWORK_BOOT = 0x1
    28  	LOCAL_BOOT   = 0x2
    29  	INVALID_BOOT = 0xff
    30  
    31  	// Default boot order systembooter configurations
    32  	NETBOOTER_CONFIG   = "{\"type\":\"netboot\",\"method\":\"dhcpv6\"}"
    33  	LOCALBOOTER_CONFIG = "{\"type\":\"localboot\",\"method\":\"grub\"}"
    34  )
    35  
    36  var (
    37  	// BmcUpdatedBootorder is true when IPMI set boot order has been issued
    38  	BmcUpdatedBootorder = false
    39  	// BootEntries is created with the new boot order and will be used to boot this time
    40  	BootEntries []systembooter.BootEntry
    41  )
    42  
    43  type BootOrder struct {
    44  	bootMode byte
    45  	bootSeq  [5]byte // Boot sequence, Boot0000, Boot0001...Boot0004
    46  }
    47  
    48  func getBootOrder(i *ipmi.IPMI, BootOrder *BootOrder) error {
    49  	recv, err := i.SendRecv(_IPMI_FB_OEM_NET_FUNCTION1, _FB_OEM_GET_BIOS_BOOT_ORDER, nil)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	BootOrder.bootMode = recv[1]
    54  	copy(BootOrder.bootSeq[:], recv[2:])
    55  	return nil
    56  }
    57  
    58  func setBootOrder(i *ipmi.IPMI, BootOrder *BootOrder) error {
    59  	msg := ipmi.Msg{
    60  		Netfn:   _IPMI_FB_OEM_NET_FUNCTION1,
    61  		Cmd:     _FB_OEM_SET_BIOS_BOOT_ORDER,
    62  		Data:    unsafe.Pointer(BootOrder),
    63  		DataLen: 6,
    64  	}
    65  
    66  	if _, err := i.RawSendRecv(msg); err != nil {
    67  		return err
    68  	}
    69  	return nil
    70  }
    71  
    72  // Currently we only support IPv6 Network boot (netboot) and SATA HDD GRUB (localboot),
    73  // other types will be mapped to INVALID_BOOT and put to the end of the bootSeq.
    74  func remapSortBootOrder(BootOrder *BootOrder) {
    75  	var bootType byte
    76  	sorted := [5]byte{INVALID_BOOT, INVALID_BOOT, INVALID_BOOT, INVALID_BOOT, INVALID_BOOT}
    77  	var idx int
    78  	for _, v := range BootOrder.bootSeq {
    79  		bootType = v & 0x7
    80  		if bootType == NETWORK_BOOT {
    81  			if v&0x8 == 0 { // If IPv6 first bit is not set, set it
    82  				v |= 0x8
    83  			}
    84  			sorted[idx] = v
    85  			idx++
    86  		} else if bootType == LOCAL_BOOT {
    87  			sorted[idx] = v
    88  			idx++
    89  		}
    90  	}
    91  	copy(BootOrder.bootSeq[:], sorted[:])
    92  }
    93  
    94  func updateVPDBootOrder(i *ipmi.IPMI, BootOrder *BootOrder) error {
    95  	var err error
    96  	var key string
    97  	var bootType byte
    98  	var idx int
    99  	for _, v := range BootOrder.bootSeq {
   100  		key = fmt.Sprintf("Boot%04d", idx)
   101  		bootType = v & 0x7
   102  		if bootType == NETWORK_BOOT {
   103  			log.Printf("VPD set %s to %s", key, NETBOOTER_CONFIG)
   104  			BootEntries = append(BootEntries, systembooter.BootEntry{Name: key, Config: []byte(NETBOOTER_CONFIG)})
   105  			if err = vpd.FlashromRWVpdSet(key, []byte(NETBOOTER_CONFIG), false); err != nil {
   106  				return err
   107  			}
   108  			idx++
   109  		} else if bootType == LOCAL_BOOT {
   110  			log.Printf("VPD set %s to %s", key, LOCALBOOTER_CONFIG)
   111  			BootEntries = append(BootEntries, systembooter.BootEntry{Name: key, Config: []byte(LOCALBOOTER_CONFIG)})
   112  			if err = vpd.FlashromRWVpdSet(key, []byte(LOCALBOOTER_CONFIG), false); err != nil {
   113  				return err
   114  			}
   115  			idx++
   116  		} else if bootType == (INVALID_BOOT & 0x7) {
   117  			// No need to write VPD
   118  		} else {
   119  			log.Printf("Ignoring unrecognized boot type: %x", bootType)
   120  		}
   121  	}
   122  
   123  	if BmcUpdatedBootorder {
   124  		// look for a Booter that supports the given configuration
   125  		for idx, entry := range BootEntries {
   126  			entry.Booter = systembooter.GetBooterFor(entry)
   127  			BootEntries[idx] = entry
   128  		}
   129  	}
   130  	// clear valid bit
   131  	BootOrder.bootMode &^= 0x80
   132  	return setBootOrder(i, BootOrder)
   133  }
   134  
   135  // CheckBMCBootOrder synchronize BIOS's boot order with BMC's boot order.
   136  // When BMC IPMI sets boot order (valid bit 1), BIOS will update VPD boot
   137  // order and create new BootEntries accordingly. If BMC didn't set boot order,
   138  // BIOS would set its current boot order to BMC.
   139  func CheckBMCBootOrder(i *ipmi.IPMI, bmcBootOverride bool) error {
   140  	var BMCBootOrder, BIOSBootOrder BootOrder
   141  	// Read boot order from BMC
   142  	if err := getBootOrder(i, &BMCBootOrder); err != nil {
   143  		return err
   144  	}
   145  	remapSortBootOrder(&BMCBootOrder)
   146  	// If valid bit is set, IPMI set boot order has been issued so update
   147  	// VPD boot order accordingly. For now the booter configurations will be
   148  	// set to the default ones.
   149  	if bmcBootOverride && BMCBootOrder.bootMode&0x80 != 0 {
   150  		log.Printf("BMC set boot order valid bit is 1")
   151  		BmcUpdatedBootorder = true
   152  		return updateVPDBootOrder(i, &BMCBootOrder)
   153  	}
   154  
   155  	// Read VPD Boot entries and create BIOS BootOrder
   156  	BIOSBootOrder.bootMode = BMCBootOrder.bootMode
   157  	// Initialize with INVALID_BOOT
   158  	idx := 0
   159  	for idx = range BIOSBootOrder.bootSeq {
   160  		BIOSBootOrder.bootSeq[idx] = INVALID_BOOT
   161  	}
   162  	bootEntries := systembooter.GetBootEntries()
   163  	idx = 0
   164  	var bootType string
   165  	for _, entry := range bootEntries {
   166  		if idx >= 5 {
   167  			break
   168  		}
   169  		if bootType = entry.Booter.TypeName(); len(bootType) > 0 {
   170  			if bootType == "netboot" {
   171  				BIOSBootOrder.bootSeq[idx] = NETWORK_BOOT
   172  				BIOSBootOrder.bootSeq[idx] |= 0x8
   173  				idx++
   174  			} else if bootType == "localboot" {
   175  				BIOSBootOrder.bootSeq[idx] = LOCAL_BOOT
   176  				idx++
   177  			}
   178  		}
   179  	}
   180  	// If there is no valid VPD boot order, write the default systembooter configurations
   181  	if idx == 0 {
   182  		log.Printf("No valid VPD boot order, set default boot orders to RW_VPD")
   183  		BIOSBootOrder.bootSeq[0] = NETWORK_BOOT
   184  		BIOSBootOrder.bootSeq[0] |= 0x8
   185  		BIOSBootOrder.bootSeq[1] = LOCAL_BOOT
   186  		return updateVPDBootOrder(i, &BIOSBootOrder)
   187  	}
   188  	// clear valid bit and set BIOS boot order to BMC
   189  	BIOSBootOrder.bootMode &^= 0x80
   190  	return setBootOrder(i, &BIOSBootOrder)
   191  }