| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame^] | 1 | // Copyright The Monogon Project Authors. |
| 2 | // SPDX-License-Identifier: Apache-2.0 |
| 3 | |
| Lorenz Brun | 509c709 | 2024-09-09 17:49:42 +0200 | [diff] [blame] | 4 | package efivarfs |
| 5 | |
| 6 | import ( |
| 7 | "encoding/binary" |
| 8 | "errors" |
| 9 | "fmt" |
| 10 | "os" |
| 11 | "sync" |
| 12 | ) |
| 13 | |
| 14 | // OSIndications is a bitset used to indicate firmware support for various |
| 15 | // features as well as to trigger some of these features. |
| 16 | // If a constant ends in Supported, it cannot be triggered, the others |
| 17 | // can be. |
| 18 | type OSIndications uint64 |
| 19 | |
| 20 | const ( |
| 21 | // Indicates that on next boot firmware should boot to a firmware-provided |
| 22 | // UI instead of the normal boot order. |
| 23 | BootToFirmwareUI = OSIndications(1 << iota) |
| 24 | // Indicates that firmware supports timestamp-based revocation and the |
| 25 | // "dbt" authorized timestamp database variable. |
| 26 | TimestampRevocationSupported |
| 27 | // Indicates that on next boot firmware should look for an EFI update |
| 28 | // capsule on an EFI system partition and try to install it. |
| 29 | FileCapsuleDelivery |
| 30 | // Indicates that firmware supports UEFI FMP update capsules. |
| 31 | FirmwareManagementProtocolCapsuleSupported |
| 32 | // Indicates that firmware supports reporting results of deferred (i.e. |
| 33 | // processed on next boot) capsule installs via variables. |
| 34 | CapsuleResultVarSupported |
| 35 | // Indicates that firmware should skip Boot# processing on next boot |
| 36 | // and instead use OsRecovery# for selecting a load option. |
| 37 | StartOSRecovery |
| 38 | // Indicates that firmware should skip Boot# processing on next boot |
| 39 | // and instead use PlatformRecovery# for selecting a load option. |
| 40 | StartPlatformRecovery |
| 41 | // Indicates that firmware should collect the current config and report |
| 42 | // the data to the EFI system configuration table on next boot. |
| 43 | JSONConfigDataRefresh |
| 44 | ) |
| 45 | |
| 46 | // osIndicationMutex protects against race conditions in read-modify-write |
| 47 | // sequences on the OsIndications EFI variable. |
| 48 | var osIndicationsMutex sync.Mutex |
| 49 | |
| 50 | // OSIndicationsSupported indicates which of the OS indication features and |
| 51 | // actions that the firmware supports. |
| 52 | func OSIndicationsSupported() (OSIndications, error) { |
| 53 | osIndicationsRaw, _, err := Read(ScopeGlobal, "OsIndicationsSupported") |
| 54 | if err != nil { |
| 55 | return 0, fmt.Errorf("unable to read OsIndicationsSupported: %w", err) |
| 56 | } |
| 57 | if len(osIndicationsRaw) != 8 { |
| 58 | return 0, fmt.Errorf("value of OsIndicationsSupported is not 8 bytes / 64 bits, is %d bytes", len(osIndicationsRaw)) |
| 59 | } |
| 60 | return OSIndications(binary.LittleEndian.Uint64(osIndicationsRaw)), nil |
| 61 | } |
| 62 | |
| 63 | // SetOSIndications sets all OS indication bits set in i in firmware. It does |
| 64 | // not clear any already-set bits, use ClearOSIndications for that. |
| 65 | func SetOSIndications(i OSIndications) error { |
| 66 | return modifyOSIndications(func(prev OSIndications) OSIndications { |
| 67 | return prev | i |
| 68 | }) |
| 69 | } |
| 70 | |
| 71 | // ClearOSIndications clears all OS indication bits set in i in firmware. |
| 72 | // Note that this effectively inverts i, bits set in i will be cleared. |
| 73 | func ClearOSIndications(i OSIndications) error { |
| 74 | return modifyOSIndications(func(prev OSIndications) OSIndications { |
| 75 | return prev & ^i |
| 76 | }) |
| 77 | } |
| 78 | |
| 79 | func modifyOSIndications(f func(prev OSIndications) OSIndications) error { |
| 80 | osIndicationsMutex.Lock() |
| 81 | defer osIndicationsMutex.Unlock() |
| 82 | |
| 83 | var osIndications OSIndications |
| 84 | rawIn, _, err := Read(ScopeGlobal, "OsIndications") |
| 85 | if err == nil && len(rawIn) == 8 { |
| 86 | osIndications = OSIndications(binary.LittleEndian.Uint64(rawIn)) |
| 87 | } else if err != nil && !errors.Is(err, os.ErrNotExist) { |
| 88 | return fmt.Errorf("unable to read OsIndications variable: %w", err) |
| 89 | } |
| 90 | osIndications = f(osIndications) |
| 91 | var raw [8]byte |
| 92 | binary.LittleEndian.PutUint64(raw[:], uint64(osIndications)) |
| 93 | if err := Write(ScopeGlobal, "OsIndications", AttrNonVolatile|AttrRuntimeAccess, raw[:]); err != nil { |
| 94 | return fmt.Errorf("failed to write OSIndications variable: %w", err) |
| 95 | } |
| 96 | return nil |
| 97 | } |