blob: a1121631e8cfbfb4a53bbbdb1d23742dcd288bbd [file] [log] [blame]
package efivarfs
import (
"encoding/binary"
"errors"
"fmt"
"os"
"sync"
)
// OSIndications is a bitset used to indicate firmware support for various
// features as well as to trigger some of these features.
// If a constant ends in Supported, it cannot be triggered, the others
// can be.
type OSIndications uint64
const (
// Indicates that on next boot firmware should boot to a firmware-provided
// UI instead of the normal boot order.
BootToFirmwareUI = OSIndications(1 << iota)
// Indicates that firmware supports timestamp-based revocation and the
// "dbt" authorized timestamp database variable.
TimestampRevocationSupported
// Indicates that on next boot firmware should look for an EFI update
// capsule on an EFI system partition and try to install it.
FileCapsuleDelivery
// Indicates that firmware supports UEFI FMP update capsules.
FirmwareManagementProtocolCapsuleSupported
// Indicates that firmware supports reporting results of deferred (i.e.
// processed on next boot) capsule installs via variables.
CapsuleResultVarSupported
// Indicates that firmware should skip Boot# processing on next boot
// and instead use OsRecovery# for selecting a load option.
StartOSRecovery
// Indicates that firmware should skip Boot# processing on next boot
// and instead use PlatformRecovery# for selecting a load option.
StartPlatformRecovery
// Indicates that firmware should collect the current config and report
// the data to the EFI system configuration table on next boot.
JSONConfigDataRefresh
)
// osIndicationMutex protects against race conditions in read-modify-write
// sequences on the OsIndications EFI variable.
var osIndicationsMutex sync.Mutex
// OSIndicationsSupported indicates which of the OS indication features and
// actions that the firmware supports.
func OSIndicationsSupported() (OSIndications, error) {
osIndicationsRaw, _, err := Read(ScopeGlobal, "OsIndicationsSupported")
if err != nil {
return 0, fmt.Errorf("unable to read OsIndicationsSupported: %w", err)
}
if len(osIndicationsRaw) != 8 {
return 0, fmt.Errorf("value of OsIndicationsSupported is not 8 bytes / 64 bits, is %d bytes", len(osIndicationsRaw))
}
return OSIndications(binary.LittleEndian.Uint64(osIndicationsRaw)), nil
}
// SetOSIndications sets all OS indication bits set in i in firmware. It does
// not clear any already-set bits, use ClearOSIndications for that.
func SetOSIndications(i OSIndications) error {
return modifyOSIndications(func(prev OSIndications) OSIndications {
return prev | i
})
}
// ClearOSIndications clears all OS indication bits set in i in firmware.
// Note that this effectively inverts i, bits set in i will be cleared.
func ClearOSIndications(i OSIndications) error {
return modifyOSIndications(func(prev OSIndications) OSIndications {
return prev & ^i
})
}
func modifyOSIndications(f func(prev OSIndications) OSIndications) error {
osIndicationsMutex.Lock()
defer osIndicationsMutex.Unlock()
var osIndications OSIndications
rawIn, _, err := Read(ScopeGlobal, "OsIndications")
if err == nil && len(rawIn) == 8 {
osIndications = OSIndications(binary.LittleEndian.Uint64(rawIn))
} else if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("unable to read OsIndications variable: %w", err)
}
osIndications = f(osIndications)
var raw [8]byte
binary.LittleEndian.PutUint64(raw[:], uint64(osIndications))
if err := Write(ScopeGlobal, "OsIndications", AttrNonVolatile|AttrRuntimeAccess, raw[:]); err != nil {
return fmt.Errorf("failed to write OSIndications variable: %w", err)
}
return nil
}