/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include "ticks.h" #include "util.h" EFI_STATUS parse_boolean(const CHAR8 *v, BOOLEAN *b) { assert(b); if (!v) return EFI_INVALID_PARAMETER; if (strcmpa(v, (CHAR8 *)"1") == 0 || strcmpa(v, (CHAR8 *)"yes") == 0 || strcmpa(v, (CHAR8 *)"y") == 0 || strcmpa(v, (CHAR8 *)"true") == 0 || strcmpa(v, (CHAR8 *)"t") == 0 || strcmpa(v, (CHAR8 *)"on") == 0) { *b = TRUE; return EFI_SUCCESS; } if (strcmpa(v, (CHAR8 *)"0") == 0 || strcmpa(v, (CHAR8 *)"no") == 0 || strcmpa(v, (CHAR8 *)"n") == 0 || strcmpa(v, (CHAR8 *)"false") == 0 || strcmpa(v, (CHAR8 *)"f") == 0 || strcmpa(v, (CHAR8 *)"off") == 0) { *b = FALSE; return EFI_SUCCESS; } return EFI_INVALID_PARAMETER; } EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const CHAR16 *name, const void *buf, UINTN size, UINT32 flags) { assert(vendor); assert(name); assert(buf || size == 0); flags |= EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS; return RT->SetVariable((CHAR16 *) name, (EFI_GUID *) vendor, flags, size, (void *) buf); } EFI_STATUS efivar_set(const EFI_GUID *vendor, const CHAR16 *name, const CHAR16 *value, UINT32 flags) { assert(vendor); assert(name); return efivar_set_raw(vendor, name, value, value ? StrSize(value) : 0, flags); } EFI_STATUS efivar_set_uint_string(const EFI_GUID *vendor, const CHAR16 *name, UINTN i, UINT32 flags) { CHAR16 str[32]; assert(vendor); assert(name); /* Note that SPrint has no native sized length specifier and will always use ValueToString() * regardless of what sign we tell it to use. Therefore, UINTN_MAX will come out as -1 on * 64bit machines. */ ValueToString(str, FALSE, i); return efivar_set(vendor, name, str, flags); } EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const CHAR16 *name, UINT32 value, UINT32 flags) { UINT8 buf[4]; assert(vendor); assert(name); buf[0] = (UINT8)(value >> 0U & 0xFF); buf[1] = (UINT8)(value >> 8U & 0xFF); buf[2] = (UINT8)(value >> 16U & 0xFF); buf[3] = (UINT8)(value >> 24U & 0xFF); return efivar_set_raw(vendor, name, buf, sizeof(buf), flags); } EFI_STATUS efivar_set_uint64_le(const EFI_GUID *vendor, const CHAR16 *name, UINT64 value, UINT32 flags) { UINT8 buf[8]; assert(vendor); assert(name); buf[0] = (UINT8)(value >> 0U & 0xFF); buf[1] = (UINT8)(value >> 8U & 0xFF); buf[2] = (UINT8)(value >> 16U & 0xFF); buf[3] = (UINT8)(value >> 24U & 0xFF); buf[4] = (UINT8)(value >> 32U & 0xFF); buf[5] = (UINT8)(value >> 40U & 0xFF); buf[6] = (UINT8)(value >> 48U & 0xFF); buf[7] = (UINT8)(value >> 56U & 0xFF); return efivar_set_raw(vendor, name, buf, sizeof(buf), flags); } EFI_STATUS efivar_get(const EFI_GUID *vendor, const CHAR16 *name, CHAR16 **value) { _cleanup_freepool_ CHAR16 *buf = NULL; EFI_STATUS err; CHAR16 *val; UINTN size; assert(vendor); assert(name); err = efivar_get_raw(vendor, name, (CHAR8**)&buf, &size); if (EFI_ERROR(err)) return err; /* Make sure there are no incomplete characters in the buffer */ if ((size % sizeof(CHAR16)) != 0) return EFI_INVALID_PARAMETER; if (!value) return EFI_SUCCESS; /* Return buffer directly if it happens to be NUL terminated already */ if (size >= sizeof(CHAR16) && buf[size / sizeof(CHAR16) - 1] == 0) { *value = TAKE_PTR(buf); return EFI_SUCCESS; } /* Make sure a terminating NUL is available at the end */ val = xallocate_pool(size + sizeof(CHAR16)); CopyMem(val, buf, size); val[size / sizeof(CHAR16) - 1] = 0; /* NUL terminate */ *value = val; return EFI_SUCCESS; } EFI_STATUS efivar_get_uint_string(const EFI_GUID *vendor, const CHAR16 *name, UINTN *i) { _cleanup_freepool_ CHAR16 *val = NULL; EFI_STATUS err; assert(vendor); assert(name); assert(i); err = efivar_get(vendor, name, &val); if (!EFI_ERROR(err)) *i = Atoi(val); return err; } EFI_STATUS efivar_get_uint32_le(const EFI_GUID *vendor, const CHAR16 *name, UINT32 *ret) { _cleanup_freepool_ CHAR8 *buf = NULL; UINTN size; EFI_STATUS err; assert(vendor); assert(name); err = efivar_get_raw(vendor, name, &buf, &size); if (!EFI_ERROR(err) && ret) { if (size != sizeof(UINT32)) return EFI_BUFFER_TOO_SMALL; *ret = (UINT32) buf[0] << 0U | (UINT32) buf[1] << 8U | (UINT32) buf[2] << 16U | (UINT32) buf[3] << 24U; } return err; } EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const CHAR16 *name, UINT64 *ret) { _cleanup_freepool_ CHAR8 *buf = NULL; UINTN size; EFI_STATUS err; assert(vendor); assert(name); err = efivar_get_raw(vendor, name, &buf, &size); if (!EFI_ERROR(err) && ret) { if (size != sizeof(UINT64)) return EFI_BUFFER_TOO_SMALL; *ret = (UINT64) buf[0] << 0U | (UINT64) buf[1] << 8U | (UINT64) buf[2] << 16U | (UINT64) buf[3] << 24U | (UINT64) buf[4] << 32U | (UINT64) buf[5] << 40U | (UINT64) buf[6] << 48U | (UINT64) buf[7] << 56U; } return err; } EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const CHAR16 *name, CHAR8 **buffer, UINTN *size) { _cleanup_freepool_ CHAR8 *buf = NULL; UINTN l; EFI_STATUS err; assert(vendor); assert(name); l = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE; buf = xallocate_pool(l); err = RT->GetVariable((CHAR16 *) name, (EFI_GUID *) vendor, NULL, &l, buf); if (!EFI_ERROR(err)) { if (buffer) *buffer = TAKE_PTR(buf); if (size) *size = l; } return err; } EFI_STATUS efivar_get_boolean_u8(const EFI_GUID *vendor, const CHAR16 *name, BOOLEAN *ret) { _cleanup_freepool_ CHAR8 *b = NULL; UINTN size; EFI_STATUS err; assert(vendor); assert(name); assert(ret); err = efivar_get_raw(vendor, name, &b, &size); if (!EFI_ERROR(err)) *ret = *b > 0; return err; } void efivar_set_time_usec(const EFI_GUID *vendor, const CHAR16 *name, UINT64 usec) { CHAR16 str[32]; assert(vendor); assert(name); if (usec == 0) usec = time_usec(); if (usec == 0) return; /* See comment on ValueToString in efivar_set_uint_string(). */ ValueToString(str, FALSE, usec); efivar_set(vendor, name, str, 0); } static INTN utf8_to_16(const CHAR8 *stra, CHAR16 *c) { CHAR16 unichar; UINTN len; assert(stra); assert(c); if (!(stra[0] & 0x80)) len = 1; else if ((stra[0] & 0xe0) == 0xc0) len = 2; else if ((stra[0] & 0xf0) == 0xe0) len = 3; else if ((stra[0] & 0xf8) == 0xf0) len = 4; else if ((stra[0] & 0xfc) == 0xf8) len = 5; else if ((stra[0] & 0xfe) == 0xfc) len = 6; else return -1; switch (len) { case 1: unichar = stra[0]; break; case 2: unichar = stra[0] & 0x1f; break; case 3: unichar = stra[0] & 0x0f; break; case 4: unichar = stra[0] & 0x07; break; case 5: unichar = stra[0] & 0x03; break; case 6: unichar = stra[0] & 0x01; break; } for (UINTN i = 1; i < len; i++) { if ((stra[i] & 0xc0) != 0x80) return -1; unichar <<= 6; unichar |= stra[i] & 0x3f; } *c = unichar; return len; } CHAR16 *xstra_to_str(const CHAR8 *stra) { UINTN strlen; UINTN len; UINTN i; CHAR16 *str; assert(stra); len = strlena(stra); str = xnew(CHAR16, len + 1); strlen = 0; i = 0; while (i < len) { INTN utf8len; utf8len = utf8_to_16(stra + i, str + strlen); if (utf8len <= 0) { /* invalid utf8 sequence, skip the garbage */ i++; continue; } strlen++; i += utf8len; } str[strlen] = '\0'; return str; } CHAR16 *xstra_to_path(const CHAR8 *stra) { CHAR16 *str; UINTN strlen; UINTN len; UINTN i; assert(stra); len = strlena(stra); str = xnew(CHAR16, len + 2); str[0] = '\\'; strlen = 1; i = 0; while (i < len) { INTN utf8len; utf8len = utf8_to_16(stra + i, str + strlen); if (utf8len <= 0) { /* invalid utf8 sequence, skip the garbage */ i++; continue; } if (str[strlen] == '/') str[strlen] = '\\'; if (str[strlen] == '\\' && str[strlen-1] == '\\') { /* skip double slashes */ i += utf8len; continue; } strlen++; i += utf8len; } str[strlen] = '\0'; return str; } CHAR8 *strchra(const CHAR8 *s, CHAR8 c) { if (!s) return NULL; do { if (*s == c) return (CHAR8*) s; } while (*s++); return NULL; } EFI_STATUS file_read(EFI_FILE *dir, const CHAR16 *name, UINTN off, UINTN size, CHAR8 **ret, UINTN *ret_size) { _cleanup_(file_closep) EFI_FILE *handle = NULL; _cleanup_freepool_ CHAR8 *buf = NULL; EFI_STATUS err; assert(dir); assert(name); assert(ret); err = dir->Open(dir, &handle, (CHAR16*) name, EFI_FILE_MODE_READ, 0ULL); if (EFI_ERROR(err)) return err; if (size == 0) { _cleanup_freepool_ EFI_FILE_INFO *info = NULL; err = get_file_info_harder(handle, &info, NULL); if (EFI_ERROR(err)) return err; size = info->FileSize; } if (off > 0) { err = handle->SetPosition(handle, off); if (EFI_ERROR(err)) return err; } /* Allocate some extra bytes to guarantee the result is NUL-terminated for CHAR8 and CHAR16 strings. */ UINTN extra = size % sizeof(CHAR16) + sizeof(CHAR16); buf = xallocate_pool(size + extra); err = handle->Read(handle, &size, buf); if (EFI_ERROR(err)) return err; /* Note that handle->Read() changes size to reflect the actually bytes read. */ ZeroMem(buf + size, extra); *ret = TAKE_PTR(buf); if (ret_size) *ret_size = size; return err; } void log_error_stall(const CHAR16 *fmt, ...) { va_list args; assert(fmt); INT32 attr = ST->ConOut->Mode->Attribute; ST->ConOut->SetAttribute(ST->ConOut, EFI_LIGHTRED|EFI_BACKGROUND_BLACK); if (ST->ConOut->Mode->CursorColumn > 0) Print(L"\n"); va_start(args, fmt); VPrint(fmt, args); va_end(args); Print(L"\n"); ST->ConOut->SetAttribute(ST->ConOut, attr); /* Give the user a chance to see the message. */ BS->Stall(3 * 1000 * 1000); } EFI_STATUS log_oom(void) { log_error_stall(L"Out of memory."); return EFI_OUT_OF_RESOURCES; } void print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str) { assert(str); ST->ConOut->SetCursorPosition(ST->ConOut, x, y); ST->ConOut->SetAttribute(ST->ConOut, attr); ST->ConOut->OutputString(ST->ConOut, (CHAR16*)str); } void clear_screen(UINTN attr) { ST->ConOut->SetAttribute(ST->ConOut, attr); ST->ConOut->ClearScreen(ST->ConOut); } void sort_pointer_array( void **array, UINTN n_members, compare_pointer_func_t compare) { assert(array || n_members == 0); assert(compare); if (n_members <= 1) return; for (UINTN i = 1; i < n_members; i++) { UINTN k; void *entry = array[i]; for (k = i; k > 0; k--) { if (compare(array[k - 1], entry) <= 0) break; array[k] = array[k - 1]; } array[k] = entry; } } EFI_STATUS get_file_info_harder( EFI_FILE *handle, EFI_FILE_INFO **ret, UINTN *ret_size) { UINTN size = offsetof(EFI_FILE_INFO, FileName) + 256; _cleanup_freepool_ EFI_FILE_INFO *fi = NULL; EFI_STATUS err; assert(handle); assert(ret); /* A lot like LibFileInfo() but with useful error propagation */ fi = xallocate_pool(size); err = handle->GetInfo(handle, &GenericFileInfo, &size, fi); if (err == EFI_BUFFER_TOO_SMALL) { FreePool(fi); fi = xallocate_pool(size); /* GetInfo tells us the required size, let's use that now */ err = handle->GetInfo(handle, &GenericFileInfo, &size, fi); } if (EFI_ERROR(err)) return err; *ret = TAKE_PTR(fi); if (ret_size) *ret_size = size; return EFI_SUCCESS; } EFI_STATUS readdir_harder( EFI_FILE *handle, EFI_FILE_INFO **buffer, UINTN *buffer_size) { EFI_STATUS err; UINTN sz; assert(handle); assert(buffer); assert(buffer_size); /* buffer/buffer_size are both in and output parameters. Should be zero-initialized initially, and * the specified buffer needs to be freed by caller, after final use. */ if (!*buffer) { /* Some broken firmware violates the EFI spec by still advancing the readdir * position when returning EFI_BUFFER_TOO_SMALL, effectively skipping over any files when * the buffer was too small. Therefore, start with a buffer that should handle FAT32 max * file name length. * As a side effect, most readdir_harder() calls will now be slightly faster. */ sz = sizeof(EFI_FILE_INFO) + 256 * sizeof(CHAR16); *buffer = xallocate_pool(sz); *buffer_size = sz; } else sz = *buffer_size; err = handle->Read(handle, &sz, *buffer); if (err == EFI_BUFFER_TOO_SMALL) { FreePool(*buffer); *buffer = xallocate_pool(sz); *buffer_size = sz; err = handle->Read(handle, &sz, *buffer); } if (EFI_ERROR(err)) return err; if (sz == 0) { /* End of directory */ FreePool(*buffer); *buffer = NULL; *buffer_size = 0; } return EFI_SUCCESS; } UINTN strnlena(const CHAR8 *p, UINTN maxlen) { UINTN c; if (!p) return 0; for (c = 0; c < maxlen; c++) if (p[c] == 0) break; return c; } INTN strncasecmpa(const CHAR8 *a, const CHAR8 *b, UINTN maxlen) { if (!a || !b) return CMP(a, b); while (maxlen > 0) { CHAR8 ca = *a, cb = *b; if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A'; if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A'; if (!ca || ca != cb) return ca - cb; a++; b++; maxlen--; } return 0; } CHAR8 *xstrndup8(const CHAR8 *p, UINTN sz) { CHAR8 *n; /* Following efilib's naming scheme this function would be called strndupa(), but we already have a * function named like this in userspace, and it does something different there, hence to minimize * confusion, let's pick a different name here */ assert(p || sz == 0); sz = strnlena(p, sz); n = xallocate_pool(sz + 1); if (sz > 0) CopyMem(n, p, sz); n[sz] = 0; return n; } BOOLEAN is_ascii(const CHAR16 *f) { if (!f) return FALSE; for (; *f != 0; f++) if (*f > 127) return FALSE; return TRUE; } CHAR16 **strv_free(CHAR16 **v) { if (!v) return NULL; for (CHAR16 **i = v; *i; i++) FreePool(*i); FreePool(v); return NULL; } EFI_STATUS open_directory( EFI_FILE *root, const CHAR16 *path, EFI_FILE **ret) { _cleanup_(file_closep) EFI_FILE *dir = NULL; _cleanup_freepool_ EFI_FILE_INFO *file_info = NULL; EFI_STATUS err; assert(root); /* Opens a file, and then verifies it is actually a directory */ err = root->Open(root, &dir, (CHAR16*) path, EFI_FILE_MODE_READ, 0ULL); if (EFI_ERROR(err)) return err; err = get_file_info_harder(dir, &file_info, NULL); if (EFI_ERROR(err)) return err; if (!FLAGS_SET(file_info->Attribute, EFI_FILE_DIRECTORY)) return EFI_LOAD_ERROR; *ret = TAKE_PTR(dir); return EFI_SUCCESS; } UINT64 get_os_indications_supported(void) { UINT64 osind; EFI_STATUS err; /* Returns the supported OS indications. If we can't acquire it, returns a zeroed out mask, i.e. no * supported features. */ err = efivar_get_uint64_le(EFI_GLOBAL_GUID, L"OsIndicationsSupported", &osind); if (EFI_ERROR(err)) return 0; return osind; } #ifdef EFI_DEBUG __attribute__((noinline)) void debug_break(void) { /* This is a poor programmer's breakpoint to wait until a debugger * has attached to us. Just "set variable wait = 0" or "return" to continue. */ volatile BOOLEAN wait = TRUE; while (wait) /* Prefer asm based stalling so that gdb has a source location to present. */ #if defined(__i386__) || defined(__x86_64__) asm volatile("pause"); #elif defined(__aarch64__) asm volatile("wfi"); #else BS->Stall(5000); #endif } #endif #if defined(__i386__) || defined(__x86_64__) static inline UINT8 inb(UINT16 port) { UINT8 value; asm volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); return value; } static inline void outb(UINT16 port, UINT8 value) { asm volatile("outb %0, %1" : : "a"(value), "Nd"(port)); } void beep(UINTN beep_count) { enum { PITCH = 500, BEEP_DURATION_USEC = 100 * 1000, WAIT_DURATION_USEC = 400 * 1000, PIT_FREQUENCY = 0x1234dd, SPEAKER_CONTROL_PORT = 0x61, SPEAKER_ON_MASK = 0x03, TIMER_PORT_MAGIC = 0xB6, TIMER_CONTROL_PORT = 0x43, TIMER_CONTROL2_PORT = 0x42, }; /* Set frequency. */ UINT32 counter = PIT_FREQUENCY / PITCH; outb(TIMER_CONTROL_PORT, TIMER_PORT_MAGIC); outb(TIMER_CONTROL2_PORT, counter & 0xFF); outb(TIMER_CONTROL2_PORT, (counter >> 8) & 0xFF); UINT8 value = inb(SPEAKER_CONTROL_PORT); while (beep_count > 0) { /* Turn speaker on. */ value |= SPEAKER_ON_MASK; outb(SPEAKER_CONTROL_PORT, value); BS->Stall(BEEP_DURATION_USEC); /* Turn speaker off. */ value &= ~SPEAKER_ON_MASK; outb(SPEAKER_CONTROL_PORT, value); beep_count--; if (beep_count > 0) BS->Stall(WAIT_DURATION_USEC); } } #endif