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 }