chiark / gitweb /
Prep v220: Apply "Fixes to user and session saving"
[elogind.git] / src / shared / acpi-fpdt.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Kay Sievers
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdio.h>
23 #include <stdint.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27
28 #include <util.h>
29 #include <fileio.h>
30 #include <time-util.h>
31 #include <acpi-fpdt.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 -EINVAL;
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.startup_start == 0 || brec.exit_services_exit < brec.startup_start)
150                 return -EINVAL;
151         if (brec.exit_services_exit > NSEC_PER_HOUR)
152                 return -EINVAL;
153
154         if (loader_start)
155                 *loader_start = brec.startup_start / 1000;
156         if (loader_exit)
157                 *loader_exit = brec.exit_services_exit / 1000;
158
159         return 0;
160 }