]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/acpi-fpdt.c
Merge pull request #7388 from keszybz/doc-tweak
[thirdparty/systemd.git] / src / shared / acpi-fpdt.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2013 Kay Sievers
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <stddef.h>
23 #include <stdint.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "acpi-fpdt.h"
28 #include "alloc-util.h"
29 #include "fd-util.h"
30 #include "fileio.h"
31 #include "time-util.h"
32
33 struct acpi_table_header {
34 char signature[4];
35 uint32_t length;
36 uint8_t revision;
37 uint8_t checksum;
38 char oem_id[6];
39 char oem_table_id[8];
40 uint32_t oem_revision;
41 char asl_compiler_id[4];
42 uint32_t asl_compiler_revision;
43 };
44
45 enum {
46 ACPI_FPDT_TYPE_BOOT = 0,
47 ACPI_FPDT_TYPE_S3PERF = 1,
48 };
49
50 struct acpi_fpdt_header {
51 uint16_t type;
52 uint8_t length;
53 uint8_t revision;
54 uint8_t reserved[4];
55 uint64_t ptr;
56 };
57
58 struct acpi_fpdt_boot_header {
59 char signature[4];
60 uint32_t length;
61 };
62
63 enum {
64 ACPI_FPDT_S3PERF_RESUME_REC = 0,
65 ACPI_FPDT_S3PERF_SUSPEND_REC = 1,
66 ACPI_FPDT_BOOT_REC = 2,
67 };
68
69 struct acpi_fpdt_boot {
70 uint16_t type;
71 uint8_t length;
72 uint8_t revision;
73 uint8_t reserved[4];
74 uint64_t reset_end;
75 uint64_t load_start;
76 uint64_t startup_start;
77 uint64_t exit_services_entry;
78 uint64_t exit_services_exit;
79 };
80
81 int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) {
82 _cleanup_free_ char *buf = NULL;
83 struct acpi_table_header *tbl;
84 size_t l = 0;
85 struct acpi_fpdt_header *rec;
86 int r;
87 uint64_t ptr = 0;
88 _cleanup_close_ int fd = -1;
89 struct acpi_fpdt_boot_header hbrec;
90 struct acpi_fpdt_boot brec;
91
92 r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l);
93 if (r < 0)
94 return r;
95
96 if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header))
97 return -EINVAL;
98
99 tbl = (struct acpi_table_header *)buf;
100 if (l != tbl->length)
101 return -EINVAL;
102
103 if (memcmp(tbl->signature, "FPDT", 4) != 0)
104 return -EINVAL;
105
106 /* find Firmware Basic Boot Performance Pointer Record */
107 for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header));
108 (char *)rec < buf + l;
109 rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) {
110 if (rec->length <= 0)
111 break;
112 if (rec->type != ACPI_FPDT_TYPE_BOOT)
113 continue;
114 if (rec->length != sizeof(struct acpi_fpdt_header))
115 continue;
116
117 ptr = rec->ptr;
118 break;
119 }
120
121 if (ptr == 0)
122 return -ENODATA;
123
124 /* read Firmware Basic Boot Performance Data Record */
125 fd = open("/dev/mem", O_CLOEXEC|O_RDONLY);
126 if (fd < 0)
127 return -errno;
128
129 l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr);
130 if (l != sizeof(struct acpi_fpdt_boot_header))
131 return -EINVAL;
132
133 if (memcmp(hbrec.signature, "FBPT", 4) != 0)
134 return -EINVAL;
135
136 if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot))
137 return -EINVAL;
138
139 l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header));
140 if (l != sizeof(struct acpi_fpdt_boot))
141 return -EINVAL;
142
143 if (brec.length != sizeof(struct acpi_fpdt_boot))
144 return -EINVAL;
145
146 if (brec.type != ACPI_FPDT_BOOT_REC)
147 return -EINVAL;
148
149 if (brec.exit_services_exit == 0)
150 /* Non-UEFI compatible boot. */
151 return -ENODATA;
152
153 if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start)
154 return -EINVAL;
155 if (brec.exit_services_exit > NSEC_PER_HOUR)
156 return -EINVAL;
157
158 if (loader_start)
159 *loader_start = brec.startup_start / 1000;
160 if (loader_exit)
161 *loader_exit = brec.exit_services_exit / 1000;
162
163 return 0;
164 }