Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/acpi/acpi_fpdt.c
26278 views
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
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
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
171
if (!fpdt_address_valid(address)) {
172
pr_info(FW_BUG "invalid physical address: 0x%llx!\n", address);
173
return -EINVAL;
174
}
175
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
if (!record_header->length) {
198
pr_err(FW_BUG "Zero-length record found in FPTD.\n");
199
result = -EINVAL;
200
goto err;
201
}
202
203
switch (record_header->type) {
204
case RECORD_S3_RESUME:
205
if (subtable_type != SUBTABLE_S3PT) {
206
pr_err(FW_BUG "Invalid record %d for subtable %s\n",
207
record_header->type, signature);
208
result = -EINVAL;
209
goto err;
210
}
211
if (record_resume) {
212
pr_err("Duplicate resume performance record found.\n");
213
continue;
214
}
215
record_resume = (struct resume_performance_record *)record_header;
216
result = sysfs_create_group(fpdt_kobj, &resume_attr_group);
217
if (result)
218
goto err;
219
break;
220
case RECORD_S3_SUSPEND:
221
if (subtable_type != SUBTABLE_S3PT) {
222
pr_err(FW_BUG "Invalid %d for subtable %s\n",
223
record_header->type, signature);
224
continue;
225
}
226
if (record_suspend) {
227
pr_err("Duplicate suspend performance record found.\n");
228
continue;
229
}
230
record_suspend = (struct suspend_performance_record *)record_header;
231
result = sysfs_create_group(fpdt_kobj, &suspend_attr_group);
232
if (result)
233
goto err;
234
break;
235
case RECORD_BOOT:
236
if (subtable_type != SUBTABLE_FBPT) {
237
pr_err(FW_BUG "Invalid %d for subtable %s\n",
238
record_header->type, signature);
239
result = -EINVAL;
240
goto err;
241
}
242
if (record_boot) {
243
pr_err("Duplicate boot performance record found.\n");
244
continue;
245
}
246
record_boot = (struct boot_performance_record *)record_header;
247
result = sysfs_create_group(fpdt_kobj, &boot_attr_group);
248
if (result)
249
goto err;
250
break;
251
252
default:
253
/* Other types are reserved in ACPI 6.4 spec. */
254
break;
255
}
256
}
257
return 0;
258
259
err:
260
if (record_boot)
261
sysfs_remove_group(fpdt_kobj, &boot_attr_group);
262
263
if (record_suspend)
264
sysfs_remove_group(fpdt_kobj, &suspend_attr_group);
265
266
if (record_resume)
267
sysfs_remove_group(fpdt_kobj, &resume_attr_group);
268
269
return result;
270
}
271
272
static int __init acpi_init_fpdt(void)
273
{
274
acpi_status status;
275
struct acpi_table_header *header;
276
struct fpdt_subtable_entry *subtable;
277
u32 offset = sizeof(*header);
278
int result;
279
280
status = acpi_get_table(ACPI_SIG_FPDT, 0, &header);
281
282
if (ACPI_FAILURE(status))
283
return 0;
284
285
fpdt_kobj = kobject_create_and_add("fpdt", acpi_kobj);
286
if (!fpdt_kobj) {
287
result = -ENOMEM;
288
goto err_nomem;
289
}
290
291
while (offset < header->length) {
292
subtable = (void *)header + offset;
293
switch (subtable->type) {
294
case SUBTABLE_FBPT:
295
case SUBTABLE_S3PT:
296
result = fpdt_process_subtable(subtable->address,
297
subtable->type);
298
if (result)
299
goto err_subtable;
300
break;
301
default:
302
/* Other types are reserved in ACPI 6.4 spec. */
303
break;
304
}
305
offset += sizeof(*subtable);
306
}
307
return 0;
308
err_subtable:
309
kobject_put(fpdt_kobj);
310
311
err_nomem:
312
acpi_put_table(header);
313
return result;
314
}
315
316
fs_initcall(acpi_init_fpdt);
317
318