osbase/efivarfs: implement OsIndications
This allows easy interfacing with EFI's OsIndications mechanism.
https: //uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html#exchanging-information-between-the-os-and-firmware
Change-Id: I6187a9a002ac06a82138ea10676641e3eb00c7a5
Reviewed-on: https://review.monogon.dev/c/monogon/+/3388
Tested-by: Jenkins CI
Reviewed-by: Jan Schär <jan@monogon.tech>
diff --git a/osbase/efivarfs/osindications.go b/osbase/efivarfs/osindications.go
new file mode 100644
index 0000000..a112163
--- /dev/null
+++ b/osbase/efivarfs/osindications.go
@@ -0,0 +1,94 @@
+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
+}