diff options
Diffstat (limited to '')
-rw-r--r-- | src/shared/acpi-fpdt.c | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/src/shared/acpi-fpdt.c b/src/shared/acpi-fpdt.c new file mode 100644 index 0000000..22a36bd --- /dev/null +++ b/src/shared/acpi-fpdt.c @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +#include "acpi-fpdt.h" +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "time-util.h" + +struct acpi_table_header { + char signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_revision; + char asl_compiler_id[4]; + uint32_t asl_compiler_revision; +} _packed_; + +enum { + ACPI_FPDT_TYPE_BOOT = 0, + ACPI_FPDT_TYPE_S3PERF = 1, +}; + +struct acpi_fpdt_header { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t ptr; +} _packed_; + +struct acpi_fpdt_boot_header { + char signature[4]; + uint32_t length; +} _packed_; + +enum { + ACPI_FPDT_S3PERF_RESUME_REC = 0, + ACPI_FPDT_S3PERF_SUSPEND_REC = 1, + ACPI_FPDT_BOOT_REC = 2, +}; + +struct acpi_fpdt_boot { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t reset_end; + uint64_t load_start; + uint64_t startup_start; + uint64_t exit_services_entry; + uint64_t exit_services_exit; +} _packed; + +/* /dev/mem is deprecated on many systems, try using /sys/firmware/acpi/fpdt parsing instead. + * This code requires kernel version 5.12 on x86 based machines or 6.2 for arm64 */ +static int acpi_get_boot_usec_kernel_parsed(usec_t *ret_loader_start, usec_t *ret_loader_exit) { + usec_t start, end; + int r; + + r = read_timestamp_file("/sys/firmware/acpi/fpdt/boot/exitbootservice_end_ns", &end); + if (r < 0) + return r; + + if (end == 0) + /* Non-UEFI compatible boot. */ + return -ENODATA; + + r = read_timestamp_file("/sys/firmware/acpi/fpdt/boot/bootloader_launch_ns", &start); + if (r < 0) + return r; + + if (start == 0 || end < start) + return -EINVAL; + if (end > NSEC_PER_HOUR) + return -EINVAL; + + if (ret_loader_start) + *ret_loader_start = start / 1000; + if (ret_loader_exit) + *ret_loader_exit = end / 1000; + + return 0; +} + +int acpi_get_boot_usec(usec_t *ret_loader_start, usec_t *ret_loader_exit) { + _cleanup_free_ char *buf = NULL; + struct acpi_table_header *tbl; + size_t l; + ssize_t ll; + struct acpi_fpdt_header *rec; + int r; + uint64_t ptr = 0; + _cleanup_close_ int fd = -EBADF; + struct acpi_fpdt_boot_header hbrec; + struct acpi_fpdt_boot brec; + + r = acpi_get_boot_usec_kernel_parsed(ret_loader_start, ret_loader_exit); + if (r != -ENOENT) /* fallback to /dev/mem hack only if kernel doesn't support the new sysfs files */ + return r; + + r = read_full_virtual_file("/sys/firmware/acpi/tables/FPDT", &buf, &l); + if (r < 0) + return r; + + if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header)) + return -EINVAL; + + tbl = (struct acpi_table_header *)buf; + if (l != tbl->length) + return -EINVAL; + + if (memcmp(tbl->signature, "FPDT", 4) != 0) + return -EINVAL; + + /* find Firmware Basic Boot Performance Pointer Record */ + for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header)); + (char *)rec + offsetof(struct acpi_fpdt_header, revision) <= buf + l; + rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) { + if (rec->length <= 0) + break; + if (rec->type != ACPI_FPDT_TYPE_BOOT) + continue; + if (rec->length != sizeof(struct acpi_fpdt_header)) + continue; + + ptr = rec->ptr; + break; + } + + if (ptr == 0) + return -ENODATA; + + /* read Firmware Basic Boot Performance Data Record */ + fd = open("/dev/mem", O_CLOEXEC|O_RDONLY); + if (fd < 0) + return -errno; + + ll = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr); + if (ll < 0) + return -errno; + if ((size_t) ll != sizeof(struct acpi_fpdt_boot_header)) + return -EINVAL; + + if (memcmp(hbrec.signature, "FBPT", 4) != 0) + return -EINVAL; + + if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + ll = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header)); + if (ll < 0) + return -errno; + if ((size_t) ll != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.length != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.type != ACPI_FPDT_BOOT_REC) + return -EINVAL; + + if (brec.exit_services_exit == 0) + /* Non-UEFI compatible boot. */ + return -ENODATA; + + if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start) + return -EINVAL; + if (brec.exit_services_exit > NSEC_PER_HOUR) + return -EINVAL; + + if (ret_loader_start) + *ret_loader_start = brec.startup_start / 1000; + if (ret_loader_exit) + *ret_loader_exit = brec.exit_services_exit / 1000; + + return 0; +} |