1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
11 #include "time-util.h"
13 struct acpi_table_header
{
20 uint32_t oem_revision
;
21 char asl_compiler_id
[4];
22 uint32_t asl_compiler_revision
;
26 ACPI_FPDT_TYPE_BOOT
= 0,
27 ACPI_FPDT_TYPE_S3PERF
= 1,
30 struct acpi_fpdt_header
{
38 struct acpi_fpdt_boot_header
{
44 ACPI_FPDT_S3PERF_RESUME_REC
= 0,
45 ACPI_FPDT_S3PERF_SUSPEND_REC
= 1,
46 ACPI_FPDT_BOOT_REC
= 2,
49 struct acpi_fpdt_boot
{
56 uint64_t startup_start
;
57 uint64_t exit_services_entry
;
58 uint64_t exit_services_exit
;
61 /* /dev/mem is deprecated on many systems, try using /sys/firmware/acpi/fpdt parsing instead.
62 * This code requires kernel version 5.12 on x86 based machines or 6.2 for arm64 */
63 static int acpi_get_boot_usec_kernel_parsed(usec_t
*ret_loader_start
, usec_t
*ret_loader_exit
) {
67 r
= read_timestamp_file("/sys/firmware/acpi/fpdt/boot/exitbootservice_end_ns", &end
);
72 /* Non-UEFI compatible boot. */
75 r
= read_timestamp_file("/sys/firmware/acpi/fpdt/boot/bootloader_launch_ns", &start
);
79 if (start
== 0 || end
< start
)
81 if (end
> NSEC_PER_HOUR
)
85 *ret_loader_start
= start
/ 1000;
87 *ret_loader_exit
= end
/ 1000;
92 int acpi_get_boot_usec(usec_t
*ret_loader_start
, usec_t
*ret_loader_exit
) {
93 _cleanup_free_
char *buf
= NULL
;
94 struct acpi_table_header
*tbl
;
97 struct acpi_fpdt_header
*rec
;
100 _cleanup_close_
int fd
= -EBADF
;
101 struct acpi_fpdt_boot_header hbrec
;
102 struct acpi_fpdt_boot brec
;
104 r
= acpi_get_boot_usec_kernel_parsed(ret_loader_start
, ret_loader_exit
);
105 if (r
!= -ENOENT
) /* fallback to /dev/mem hack only if kernel doesn't support the new sysfs files */
108 r
= read_full_virtual_file("/sys/firmware/acpi/tables/FPDT", &buf
, &l
);
112 if (l
< sizeof(struct acpi_table_header
) + sizeof(struct acpi_fpdt_header
))
115 tbl
= (struct acpi_table_header
*)buf
;
116 if (l
!= tbl
->length
)
119 if (memcmp(tbl
->signature
, "FPDT", 4) != 0)
122 /* find Firmware Basic Boot Performance Pointer Record */
123 for (rec
= (struct acpi_fpdt_header
*)(buf
+ sizeof(struct acpi_table_header
));
124 (char *)rec
+ offsetof(struct acpi_fpdt_header
, revision
) <= buf
+ l
;
125 rec
= (struct acpi_fpdt_header
*)((char *)rec
+ rec
->length
)) {
126 if (rec
->length
<= 0)
128 if (rec
->type
!= ACPI_FPDT_TYPE_BOOT
)
130 if (rec
->length
!= sizeof(struct acpi_fpdt_header
))
140 /* read Firmware Basic Boot Performance Data Record */
141 fd
= open("/dev/mem", O_CLOEXEC
|O_RDONLY
);
145 ll
= pread(fd
, &hbrec
, sizeof(struct acpi_fpdt_boot_header
), ptr
);
148 if ((size_t) ll
!= sizeof(struct acpi_fpdt_boot_header
))
151 if (memcmp(hbrec
.signature
, "FBPT", 4) != 0)
154 if (hbrec
.length
< sizeof(struct acpi_fpdt_boot_header
) + sizeof(struct acpi_fpdt_boot
))
157 ll
= pread(fd
, &brec
, sizeof(struct acpi_fpdt_boot
), ptr
+ sizeof(struct acpi_fpdt_boot_header
));
160 if ((size_t) ll
!= sizeof(struct acpi_fpdt_boot
))
163 if (brec
.length
!= sizeof(struct acpi_fpdt_boot
))
166 if (brec
.type
!= ACPI_FPDT_BOOT_REC
)
169 if (brec
.exit_services_exit
== 0)
170 /* Non-UEFI compatible boot. */
173 if (brec
.startup_start
== 0 || brec
.exit_services_exit
< brec
.startup_start
)
175 if (brec
.exit_services_exit
> NSEC_PER_HOUR
)
178 if (ret_loader_start
)
179 *ret_loader_start
= brec
.startup_start
/ 1000;
181 *ret_loader_exit
= brec
.exit_services_exit
/ 1000;