1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
10 #include "acpi-fpdt.h"
11 #include "alloc-util.h"
14 #include "time-util.h"
16 struct acpi_table_header
{
23 uint32_t oem_revision
;
24 char asl_compiler_id
[4];
25 uint32_t asl_compiler_revision
;
29 ACPI_FPDT_TYPE_BOOT
= 0,
30 ACPI_FPDT_TYPE_S3PERF
= 1,
33 struct acpi_fpdt_header
{
41 struct acpi_fpdt_boot_header
{
47 ACPI_FPDT_S3PERF_RESUME_REC
= 0,
48 ACPI_FPDT_S3PERF_SUSPEND_REC
= 1,
49 ACPI_FPDT_BOOT_REC
= 2,
52 struct acpi_fpdt_boot
{
59 uint64_t startup_start
;
60 uint64_t exit_services_entry
;
61 uint64_t exit_services_exit
;
64 /* /dev/mem is deprecated on many systems, try using /sys/firmware/acpi/fpdt parsing instead.
65 * This code requires kernel version 5.12 on x86 based machines or 6.2 for arm64 */
66 static int acpi_get_boot_usec_kernel_parsed(usec_t
*ret_loader_start
, usec_t
*ret_loader_exit
) {
70 r
= read_timestamp_file("/sys/firmware/acpi/fpdt/boot/exitbootservice_end_ns", &end
);
75 /* Non-UEFI compatible boot. */
78 r
= read_timestamp_file("/sys/firmware/acpi/fpdt/boot/bootloader_launch_ns", &start
);
82 if (start
== 0 || end
< start
)
84 if (end
> NSEC_PER_HOUR
)
88 *ret_loader_start
= start
/ 1000;
90 *ret_loader_exit
= end
/ 1000;
95 int acpi_get_boot_usec(usec_t
*ret_loader_start
, usec_t
*ret_loader_exit
) {
96 _cleanup_free_
char *buf
= NULL
;
97 struct acpi_table_header
*tbl
;
100 struct acpi_fpdt_header
*rec
;
103 _cleanup_close_
int fd
= -EBADF
;
104 struct acpi_fpdt_boot_header hbrec
;
105 struct acpi_fpdt_boot brec
;
107 r
= acpi_get_boot_usec_kernel_parsed(ret_loader_start
, ret_loader_exit
);
108 if (r
!= -ENOENT
) /* fallback to /dev/mem hack only if kernel doesn't support the new sysfs files */
111 r
= read_full_virtual_file("/sys/firmware/acpi/tables/FPDT", &buf
, &l
);
115 if (l
< sizeof(struct acpi_table_header
) + sizeof(struct acpi_fpdt_header
))
118 tbl
= (struct acpi_table_header
*)buf
;
119 if (l
!= tbl
->length
)
122 if (memcmp(tbl
->signature
, "FPDT", 4) != 0)
125 /* find Firmware Basic Boot Performance Pointer Record */
126 for (rec
= (struct acpi_fpdt_header
*)(buf
+ sizeof(struct acpi_table_header
));
127 (char *)rec
+ offsetof(struct acpi_fpdt_header
, revision
) <= buf
+ l
;
128 rec
= (struct acpi_fpdt_header
*)((char *)rec
+ rec
->length
)) {
129 if (rec
->length
<= 0)
131 if (rec
->type
!= ACPI_FPDT_TYPE_BOOT
)
133 if (rec
->length
!= sizeof(struct acpi_fpdt_header
))
143 /* read Firmware Basic Boot Performance Data Record */
144 fd
= open("/dev/mem", O_CLOEXEC
|O_RDONLY
);
148 ll
= pread(fd
, &hbrec
, sizeof(struct acpi_fpdt_boot_header
), ptr
);
151 if ((size_t) ll
!= sizeof(struct acpi_fpdt_boot_header
))
154 if (memcmp(hbrec
.signature
, "FBPT", 4) != 0)
157 if (hbrec
.length
< sizeof(struct acpi_fpdt_boot_header
) + sizeof(struct acpi_fpdt_boot
))
160 ll
= pread(fd
, &brec
, sizeof(struct acpi_fpdt_boot
), ptr
+ sizeof(struct acpi_fpdt_boot_header
));
163 if ((size_t) ll
!= sizeof(struct acpi_fpdt_boot
))
166 if (brec
.length
!= sizeof(struct acpi_fpdt_boot
))
169 if (brec
.type
!= ACPI_FPDT_BOOT_REC
)
172 if (brec
.exit_services_exit
== 0)
173 /* Non-UEFI compatible boot. */
176 if (brec
.startup_start
== 0 || brec
.exit_services_exit
< brec
.startup_start
)
178 if (brec
.exit_services_exit
> NSEC_PER_HOUR
)
181 if (ret_loader_start
)
182 *ret_loader_start
= brec
.startup_start
/ 1000;
184 *ret_loader_exit
= brec
.exit_services_exit
/ 1000;