Commit | Line | Data |
---|---|---|
d1eb86e5 ZR |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | ||
3 | /* | |
4 | * FPDT support for exporting boot and suspend/resume performance data | |
5 | * | |
6 | * Copyright (C) 2021 Intel Corporation. All rights reserved. | |
7 | */ | |
8 | ||
9 | #define pr_fmt(fmt) "ACPI FPDT: " fmt | |
10 | ||
11 | #include <linux/acpi.h> | |
12 | ||
13 | /* | |
14 | * FPDT contains ACPI table header and a number of fpdt_subtable_entries. | |
15 | * Each fpdt_subtable_entry points to a subtable: FBPT or S3PT. | |
16 | * Each FPDT subtable (FBPT/S3PT) is composed of a fpdt_subtable_header | |
17 | * and a number of fpdt performance records. | |
18 | * Each FPDT performance record is composed of a fpdt_record_header and | |
19 | * performance data fields, for boot or suspend or resume phase. | |
20 | */ | |
21 | enum fpdt_subtable_type { | |
22 | SUBTABLE_FBPT, | |
23 | SUBTABLE_S3PT, | |
24 | }; | |
25 | ||
26 | struct fpdt_subtable_entry { | |
27 | u16 type; /* refer to enum fpdt_subtable_type */ | |
28 | u8 length; | |
29 | u8 revision; | |
30 | u32 reserved; | |
31 | u64 address; /* physical address of the S3PT/FBPT table */ | |
32 | }; | |
33 | ||
34 | struct fpdt_subtable_header { | |
35 | u32 signature; | |
36 | u32 length; | |
37 | }; | |
38 | ||
39 | enum fpdt_record_type { | |
40 | RECORD_S3_RESUME, | |
41 | RECORD_S3_SUSPEND, | |
42 | RECORD_BOOT, | |
43 | }; | |
44 | ||
45 | struct fpdt_record_header { | |
46 | u16 type; /* refer to enum fpdt_record_type */ | |
47 | u8 length; | |
48 | u8 revision; | |
49 | }; | |
50 | ||
51 | struct resume_performance_record { | |
52 | struct fpdt_record_header header; | |
53 | u32 resume_count; | |
54 | u64 resume_prev; | |
55 | u64 resume_avg; | |
56 | } __attribute__((packed)); | |
57 | ||
58 | struct boot_performance_record { | |
59 | struct fpdt_record_header header; | |
60 | u32 reserved; | |
61 | u64 firmware_start; | |
62 | u64 bootloader_load; | |
63 | u64 bootloader_launch; | |
64 | u64 exitbootservice_start; | |
65 | u64 exitbootservice_end; | |
66 | } __attribute__((packed)); | |
67 | ||
68 | struct suspend_performance_record { | |
69 | struct fpdt_record_header header; | |
70 | u64 suspend_start; | |
71 | u64 suspend_end; | |
72 | } __attribute__((packed)); | |
73 | ||
74 | ||
75 | static struct resume_performance_record *record_resume; | |
76 | static struct suspend_performance_record *record_suspend; | |
77 | static struct boot_performance_record *record_boot; | |
78 | ||
79 | #define FPDT_ATTR(phase, name) \ | |
80 | static ssize_t name##_show(struct kobject *kobj, \ | |
81 | struct kobj_attribute *attr, char *buf) \ | |
82 | { \ | |
83 | return sprintf(buf, "%llu\n", record_##phase->name); \ | |
84 | } \ | |
85 | static struct kobj_attribute name##_attr = \ | |
86 | __ATTR(name##_ns, 0444, name##_show, NULL) | |
87 | ||
88 | FPDT_ATTR(resume, resume_prev); | |
89 | FPDT_ATTR(resume, resume_avg); | |
90 | FPDT_ATTR(suspend, suspend_start); | |
91 | FPDT_ATTR(suspend, suspend_end); | |
92 | FPDT_ATTR(boot, firmware_start); | |
93 | FPDT_ATTR(boot, bootloader_load); | |
94 | FPDT_ATTR(boot, bootloader_launch); | |
95 | FPDT_ATTR(boot, exitbootservice_start); | |
96 | FPDT_ATTR(boot, exitbootservice_end); | |
97 | ||
98 | static ssize_t resume_count_show(struct kobject *kobj, | |
99 | struct kobj_attribute *attr, char *buf) | |
100 | { | |
101 | return sprintf(buf, "%u\n", record_resume->resume_count); | |
102 | } | |
103 | ||
104 | static struct kobj_attribute resume_count_attr = | |
105 | __ATTR_RO(resume_count); | |
106 | ||
107 | static struct attribute *resume_attrs[] = { | |
108 | &resume_count_attr.attr, | |
109 | &resume_prev_attr.attr, | |
110 | &resume_avg_attr.attr, | |
111 | NULL | |
112 | }; | |
113 | ||
114 | static const struct attribute_group resume_attr_group = { | |
115 | .attrs = resume_attrs, | |
116 | .name = "resume", | |
117 | }; | |
118 | ||
119 | static struct attribute *suspend_attrs[] = { | |
120 | &suspend_start_attr.attr, | |
121 | &suspend_end_attr.attr, | |
122 | NULL | |
123 | }; | |
124 | ||
125 | static const struct attribute_group suspend_attr_group = { | |
126 | .attrs = suspend_attrs, | |
127 | .name = "suspend", | |
128 | }; | |
129 | ||
130 | static struct attribute *boot_attrs[] = { | |
131 | &firmware_start_attr.attr, | |
132 | &bootloader_load_attr.attr, | |
133 | &bootloader_launch_attr.attr, | |
134 | &exitbootservice_start_attr.attr, | |
135 | &exitbootservice_end_attr.attr, | |
136 | NULL | |
137 | }; | |
138 | ||
139 | static const struct attribute_group boot_attr_group = { | |
140 | .attrs = boot_attrs, | |
141 | .name = "boot", | |
142 | }; | |
143 | ||
144 | static struct kobject *fpdt_kobj; | |
145 | ||
211391bf HG |
146 | #if defined CONFIG_X86 && defined CONFIG_PHYS_ADDR_T_64BIT |
147 | #include <linux/processor.h> | |
148 | static bool fpdt_address_valid(u64 address) | |
149 | { | |
150 | /* | |
151 | * On some systems the table contains invalid addresses | |
152 | * with unsuppored high address bits set, check for this. | |
153 | */ | |
154 | return !(address >> boot_cpu_data.x86_phys_bits); | |
155 | } | |
156 | #else | |
157 | static bool fpdt_address_valid(u64 address) | |
158 | { | |
159 | return true; | |
160 | } | |
161 | #endif | |
162 | ||
d1eb86e5 ZR |
163 | static int fpdt_process_subtable(u64 address, u32 subtable_type) |
164 | { | |
165 | struct fpdt_subtable_header *subtable_header; | |
166 | struct fpdt_record_header *record_header; | |
167 | char *signature = (subtable_type == SUBTABLE_FBPT ? "FBPT" : "S3PT"); | |
168 | u32 length, offset; | |
169 | int result; | |
170 | ||
211391bf HG |
171 | if (!fpdt_address_valid(address)) { |
172 | pr_info(FW_BUG "invalid physical address: 0x%llx!\n", address); | |
173 | return -EINVAL; | |
174 | } | |
175 | ||
d1eb86e5 ZR |
176 | subtable_header = acpi_os_map_memory(address, sizeof(*subtable_header)); |
177 | if (!subtable_header) | |
178 | return -ENOMEM; | |
179 | ||
180 | if (strncmp((char *)&subtable_header->signature, signature, 4)) { | |
181 | pr_info(FW_BUG "subtable signature and type mismatch!\n"); | |
182 | return -EINVAL; | |
183 | } | |
184 | ||
185 | length = subtable_header->length; | |
186 | acpi_os_unmap_memory(subtable_header, sizeof(*subtable_header)); | |
187 | ||
188 | subtable_header = acpi_os_map_memory(address, length); | |
189 | if (!subtable_header) | |
190 | return -ENOMEM; | |
191 | ||
192 | offset = sizeof(*subtable_header); | |
193 | while (offset < length) { | |
194 | record_header = (void *)subtable_header + offset; | |
195 | offset += record_header->length; | |
196 | ||
197 | switch (record_header->type) { | |
198 | case RECORD_S3_RESUME: | |
199 | if (subtable_type != SUBTABLE_S3PT) { | |
200 | pr_err(FW_BUG "Invalid record %d for subtable %s\n", | |
201 | record_header->type, signature); | |
202 | return -EINVAL; | |
203 | } | |
204 | if (record_resume) { | |
205 | pr_err("Duplicate resume performance record found.\n"); | |
206 | continue; | |
207 | } | |
208 | record_resume = (struct resume_performance_record *)record_header; | |
209 | result = sysfs_create_group(fpdt_kobj, &resume_attr_group); | |
210 | if (result) | |
211 | return result; | |
212 | break; | |
213 | case RECORD_S3_SUSPEND: | |
214 | if (subtable_type != SUBTABLE_S3PT) { | |
215 | pr_err(FW_BUG "Invalid %d for subtable %s\n", | |
216 | record_header->type, signature); | |
217 | continue; | |
218 | } | |
219 | if (record_suspend) { | |
220 | pr_err("Duplicate suspend performance record found.\n"); | |
221 | continue; | |
222 | } | |
223 | record_suspend = (struct suspend_performance_record *)record_header; | |
224 | result = sysfs_create_group(fpdt_kobj, &suspend_attr_group); | |
225 | if (result) | |
226 | return result; | |
227 | break; | |
228 | case RECORD_BOOT: | |
229 | if (subtable_type != SUBTABLE_FBPT) { | |
230 | pr_err(FW_BUG "Invalid %d for subtable %s\n", | |
231 | record_header->type, signature); | |
232 | return -EINVAL; | |
233 | } | |
234 | if (record_boot) { | |
235 | pr_err("Duplicate boot performance record found.\n"); | |
236 | continue; | |
237 | } | |
238 | record_boot = (struct boot_performance_record *)record_header; | |
239 | result = sysfs_create_group(fpdt_kobj, &boot_attr_group); | |
240 | if (result) | |
241 | return result; | |
242 | break; | |
243 | ||
244 | default: | |
97e03410 AH |
245 | /* Other types are reserved in ACPI 6.4 spec. */ |
246 | break; | |
d1eb86e5 ZR |
247 | } |
248 | } | |
249 | return 0; | |
250 | } | |
251 | ||
252 | static int __init acpi_init_fpdt(void) | |
253 | { | |
254 | acpi_status status; | |
255 | struct acpi_table_header *header; | |
256 | struct fpdt_subtable_entry *subtable; | |
257 | u32 offset = sizeof(*header); | |
258 | ||
259 | status = acpi_get_table(ACPI_SIG_FPDT, 0, &header); | |
260 | ||
261 | if (ACPI_FAILURE(status)) | |
262 | return 0; | |
263 | ||
264 | fpdt_kobj = kobject_create_and_add("fpdt", acpi_kobj); | |
dd9eaa23 JX |
265 | if (!fpdt_kobj) { |
266 | acpi_put_table(header); | |
d1eb86e5 | 267 | return -ENOMEM; |
dd9eaa23 | 268 | } |
d1eb86e5 ZR |
269 | |
270 | while (offset < header->length) { | |
271 | subtable = (void *)header + offset; | |
272 | switch (subtable->type) { | |
273 | case SUBTABLE_FBPT: | |
274 | case SUBTABLE_S3PT: | |
275 | fpdt_process_subtable(subtable->address, | |
276 | subtable->type); | |
277 | break; | |
278 | default: | |
97e03410 | 279 | /* Other types are reserved in ACPI 6.4 spec. */ |
d1eb86e5 ZR |
280 | break; |
281 | } | |
282 | offset += sizeof(*subtable); | |
283 | } | |
284 | return 0; | |
285 | } | |
286 | ||
287 | fs_initcall(acpi_init_fpdt); |