]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/acpi-fpdt.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / shared / acpi-fpdt.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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 } _packed_;
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 } _packed_;
40
41 struct acpi_fpdt_boot_header {
42 char signature[4];
43 uint32_t length;
44 } _packed_;
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 } _packed;
63
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) {
67 usec_t start, end;
68 int r;
69
70 r = read_timestamp_file("/sys/firmware/acpi/fpdt/boot/exitbootservice_end_ns", &end);
71 if (r < 0)
72 return r;
73
74 if (end == 0)
75 /* Non-UEFI compatible boot. */
76 return -ENODATA;
77
78 r = read_timestamp_file("/sys/firmware/acpi/fpdt/boot/bootloader_launch_ns", &start);
79 if (r < 0)
80 return r;
81
82 if (start == 0 || end < start)
83 return -EINVAL;
84 if (end > NSEC_PER_HOUR)
85 return -EINVAL;
86
87 if (ret_loader_start)
88 *ret_loader_start = start / 1000;
89 if (ret_loader_exit)
90 *ret_loader_exit = end / 1000;
91
92 return 0;
93 }
94
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;
98 size_t l;
99 ssize_t ll;
100 struct acpi_fpdt_header *rec;
101 int r;
102 uint64_t ptr = 0;
103 _cleanup_close_ int fd = -EBADF;
104 struct acpi_fpdt_boot_header hbrec;
105 struct acpi_fpdt_boot brec;
106
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 */
109 return r;
110
111 r = read_full_virtual_file("/sys/firmware/acpi/tables/FPDT", &buf, &l);
112 if (r < 0)
113 return r;
114
115 if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header))
116 return -EINVAL;
117
118 tbl = (struct acpi_table_header *)buf;
119 if (l != tbl->length)
120 return -EINVAL;
121
122 if (memcmp(tbl->signature, "FPDT", 4) != 0)
123 return -EINVAL;
124
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)
130 break;
131 if (rec->type != ACPI_FPDT_TYPE_BOOT)
132 continue;
133 if (rec->length != sizeof(struct acpi_fpdt_header))
134 continue;
135
136 ptr = rec->ptr;
137 break;
138 }
139
140 if (ptr == 0)
141 return -ENODATA;
142
143 /* read Firmware Basic Boot Performance Data Record */
144 fd = open("/dev/mem", O_CLOEXEC|O_RDONLY);
145 if (fd < 0)
146 return -errno;
147
148 ll = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr);
149 if (ll < 0)
150 return -errno;
151 if ((size_t) ll != sizeof(struct acpi_fpdt_boot_header))
152 return -EINVAL;
153
154 if (memcmp(hbrec.signature, "FBPT", 4) != 0)
155 return -EINVAL;
156
157 if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot))
158 return -EINVAL;
159
160 ll = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header));
161 if (ll < 0)
162 return -errno;
163 if ((size_t) ll != sizeof(struct acpi_fpdt_boot))
164 return -EINVAL;
165
166 if (brec.length != sizeof(struct acpi_fpdt_boot))
167 return -EINVAL;
168
169 if (brec.type != ACPI_FPDT_BOOT_REC)
170 return -EINVAL;
171
172 if (brec.exit_services_exit == 0)
173 /* Non-UEFI compatible boot. */
174 return -ENODATA;
175
176 if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start)
177 return -EINVAL;
178 if (brec.exit_services_exit > NSEC_PER_HOUR)
179 return -EINVAL;
180
181 if (ret_loader_start)
182 *ret_loader_start = brec.startup_start / 1000;
183 if (ret_loader_exit)
184 *ret_loader_exit = brec.exit_services_exit / 1000;
185
186 return 0;
187 }