]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/shared/acpi-fpdt.c
ci: enable arm64 runner for build/unit jobs
[thirdparty/systemd.git] / src / shared / acpi-fpdt.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <fcntl.h>
4#include <string.h>
5#include <unistd.h>
6
7#include "acpi-fpdt.h"
8#include "alloc-util.h"
9#include "fd-util.h"
10#include "fileio.h"
11#include "time-util.h"
12
13struct acpi_table_header {
14 char signature[4];
15 uint32_t length;
16 uint8_t revision;
17 uint8_t checksum;
18 char oem_id[6];
19 char oem_table_id[8];
20 uint32_t oem_revision;
21 char asl_compiler_id[4];
22 uint32_t asl_compiler_revision;
23} _packed_;
24
25enum {
26 ACPI_FPDT_TYPE_BOOT = 0,
27 ACPI_FPDT_TYPE_S3PERF = 1,
28};
29
30struct acpi_fpdt_header {
31 uint16_t type;
32 uint8_t length;
33 uint8_t revision;
34 uint8_t reserved[4];
35 uint64_t ptr;
36} _packed_;
37
38struct acpi_fpdt_boot_header {
39 char signature[4];
40 uint32_t length;
41} _packed_;
42
43enum {
44 ACPI_FPDT_S3PERF_RESUME_REC = 0,
45 ACPI_FPDT_S3PERF_SUSPEND_REC = 1,
46 ACPI_FPDT_BOOT_REC = 2,
47};
48
49struct acpi_fpdt_boot {
50 uint16_t type;
51 uint8_t length;
52 uint8_t revision;
53 uint8_t reserved[4];
54 uint64_t reset_end;
55 uint64_t load_start;
56 uint64_t startup_start;
57 uint64_t exit_services_entry;
58 uint64_t exit_services_exit;
59} _packed;
60
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 */
63static int acpi_get_boot_usec_kernel_parsed(usec_t *ret_loader_start, usec_t *ret_loader_exit) {
64 usec_t start, end;
65 int r;
66
67 r = read_timestamp_file("/sys/firmware/acpi/fpdt/boot/exitbootservice_end_ns", &end);
68 if (r < 0)
69 return r;
70
71 if (end == 0)
72 /* Non-UEFI compatible boot. */
73 return -ENODATA;
74
75 r = read_timestamp_file("/sys/firmware/acpi/fpdt/boot/bootloader_launch_ns", &start);
76 if (r < 0)
77 return r;
78
79 if (start == 0 || end < start)
80 return -EINVAL;
81 if (end > NSEC_PER_HOUR)
82 return -EINVAL;
83
84 if (ret_loader_start)
85 *ret_loader_start = start / 1000;
86 if (ret_loader_exit)
87 *ret_loader_exit = end / 1000;
88
89 return 0;
90}
91
92int 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;
95 size_t l;
96 ssize_t ll;
97 struct acpi_fpdt_header *rec;
98 int r;
99 uint64_t ptr = 0;
100 _cleanup_close_ int fd = -EBADF;
101 struct acpi_fpdt_boot_header hbrec;
102 struct acpi_fpdt_boot brec;
103
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 */
106 return r;
107
108 r = read_full_virtual_file("/sys/firmware/acpi/tables/FPDT", &buf, &l);
109 if (r < 0)
110 return r;
111
112 if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header))
113 return -EINVAL;
114
115 tbl = (struct acpi_table_header *)buf;
116 if (l != tbl->length)
117 return -EINVAL;
118
119 if (memcmp(tbl->signature, "FPDT", 4) != 0)
120 return -EINVAL;
121
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)
127 break;
128 if (rec->type != ACPI_FPDT_TYPE_BOOT)
129 continue;
130 if (rec->length != sizeof(struct acpi_fpdt_header))
131 continue;
132
133 ptr = rec->ptr;
134 break;
135 }
136
137 if (ptr == 0)
138 return -ENODATA;
139
140 /* read Firmware Basic Boot Performance Data Record */
141 fd = open("/dev/mem", O_CLOEXEC|O_RDONLY);
142 if (fd < 0)
143 return -errno;
144
145 ll = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr);
146 if (ll < 0)
147 return -errno;
148 if ((size_t) ll != sizeof(struct acpi_fpdt_boot_header))
149 return -EINVAL;
150
151 if (memcmp(hbrec.signature, "FBPT", 4) != 0)
152 return -EINVAL;
153
154 if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot))
155 return -EINVAL;
156
157 ll = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header));
158 if (ll < 0)
159 return -errno;
160 if ((size_t) ll != sizeof(struct acpi_fpdt_boot))
161 return -EINVAL;
162
163 if (brec.length != sizeof(struct acpi_fpdt_boot))
164 return -EINVAL;
165
166 if (brec.type != ACPI_FPDT_BOOT_REC)
167 return -EINVAL;
168
169 if (brec.exit_services_exit == 0)
170 /* Non-UEFI compatible boot. */
171 return -ENODATA;
172
173 if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start)
174 return -EINVAL;
175 if (brec.exit_services_exit > NSEC_PER_HOUR)
176 return -EINVAL;
177
178 if (ret_loader_start)
179 *ret_loader_start = brec.startup_start / 1000;
180 if (ret_loader_exit)
181 *ret_loader_exit = brec.exit_services_exit / 1000;
182
183 return 0;
184}