Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */ 2 : 3 : #include <errno.h> 4 : #include <fcntl.h> 5 : #include <stddef.h> 6 : #include <stdint.h> 7 : #include <string.h> 8 : #include <unistd.h> 9 : 10 : #include "acpi-fpdt.h" 11 : #include "alloc-util.h" 12 : #include "fd-util.h" 13 : #include "fileio.h" 14 : #include "time-util.h" 15 : 16 : struct acpi_table_header { 17 : char signature[4]; 18 : uint32_t length; 19 : uint8_t revision; 20 : uint8_t checksum; 21 : char oem_id[6]; 22 : char oem_table_id[8]; 23 : uint32_t oem_revision; 24 : char asl_compiler_id[4]; 25 : uint32_t asl_compiler_revision; 26 : }; 27 : 28 : enum { 29 : ACPI_FPDT_TYPE_BOOT = 0, 30 : ACPI_FPDT_TYPE_S3PERF = 1, 31 : }; 32 : 33 : struct acpi_fpdt_header { 34 : uint16_t type; 35 : uint8_t length; 36 : uint8_t revision; 37 : uint8_t reserved[4]; 38 : uint64_t ptr; 39 : }; 40 : 41 : struct acpi_fpdt_boot_header { 42 : char signature[4]; 43 : uint32_t length; 44 : }; 45 : 46 : enum { 47 : ACPI_FPDT_S3PERF_RESUME_REC = 0, 48 : ACPI_FPDT_S3PERF_SUSPEND_REC = 1, 49 : ACPI_FPDT_BOOT_REC = 2, 50 : }; 51 : 52 : struct acpi_fpdt_boot { 53 : uint16_t type; 54 : uint8_t length; 55 : uint8_t revision; 56 : uint8_t reserved[4]; 57 : uint64_t reset_end; 58 : uint64_t load_start; 59 : uint64_t startup_start; 60 : uint64_t exit_services_entry; 61 : uint64_t exit_services_exit; 62 : }; 63 : 64 2 : int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) { 65 2 : _cleanup_free_ char *buf = NULL; 66 : struct acpi_table_header *tbl; 67 2 : size_t l = 0; 68 : struct acpi_fpdt_header *rec; 69 : int r; 70 2 : uint64_t ptr = 0; 71 2 : _cleanup_close_ int fd = -1; 72 : struct acpi_fpdt_boot_header hbrec; 73 : struct acpi_fpdt_boot brec; 74 : 75 2 : r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l); 76 2 : if (r < 0) 77 2 : return r; 78 : 79 0 : if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header)) 80 0 : return -EINVAL; 81 : 82 0 : tbl = (struct acpi_table_header *)buf; 83 0 : if (l != tbl->length) 84 0 : return -EINVAL; 85 : 86 0 : if (memcmp(tbl->signature, "FPDT", 4) != 0) 87 0 : return -EINVAL; 88 : 89 : /* find Firmware Basic Boot Performance Pointer Record */ 90 0 : for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header)); 91 0 : (char *)rec < buf + l; 92 0 : rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) { 93 0 : if (rec->length <= 0) 94 0 : break; 95 0 : if (rec->type != ACPI_FPDT_TYPE_BOOT) 96 0 : continue; 97 0 : if (rec->length != sizeof(struct acpi_fpdt_header)) 98 0 : continue; 99 : 100 0 : ptr = rec->ptr; 101 0 : break; 102 : } 103 : 104 0 : if (ptr == 0) 105 0 : return -ENODATA; 106 : 107 : /* read Firmware Basic Boot Performance Data Record */ 108 0 : fd = open("/dev/mem", O_CLOEXEC|O_RDONLY); 109 0 : if (fd < 0) 110 0 : return -errno; 111 : 112 0 : l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr); 113 0 : if (l != sizeof(struct acpi_fpdt_boot_header)) 114 0 : return -EINVAL; 115 : 116 0 : if (memcmp(hbrec.signature, "FBPT", 4) != 0) 117 0 : return -EINVAL; 118 : 119 0 : if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot)) 120 0 : return -EINVAL; 121 : 122 0 : l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header)); 123 0 : if (l != sizeof(struct acpi_fpdt_boot)) 124 0 : return -EINVAL; 125 : 126 0 : if (brec.length != sizeof(struct acpi_fpdt_boot)) 127 0 : return -EINVAL; 128 : 129 0 : if (brec.type != ACPI_FPDT_BOOT_REC) 130 0 : return -EINVAL; 131 : 132 0 : if (brec.exit_services_exit == 0) 133 : /* Non-UEFI compatible boot. */ 134 0 : return -ENODATA; 135 : 136 0 : if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start) 137 0 : return -EINVAL; 138 0 : if (brec.exit_services_exit > NSEC_PER_HOUR) 139 0 : return -EINVAL; 140 : 141 0 : if (loader_start) 142 0 : *loader_start = brec.startup_start / 1000; 143 0 : if (loader_exit) 144 0 : *loader_exit = brec.exit_services_exit / 1000; 145 : 146 0 : return 0; 147 : }