1 // SPDX-License-Identifier: GPL-2.0-only
4 * FPDT support for exporting boot and suspend/resume performance data
6 * Copyright (C) 2021 Intel Corporation. All rights reserved.
9 #define pr_fmt(fmt) "ACPI FPDT: " fmt
11 #include <linux/acpi.h>
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.
21 enum fpdt_subtable_type
{
26 struct fpdt_subtable_entry
{
27 u16 type
; /* refer to enum fpdt_subtable_type */
31 u64 address
; /* physical address of the S3PT/FBPT table */
34 struct fpdt_subtable_header
{
39 enum fpdt_record_type
{
45 struct fpdt_record_header
{
46 u16 type
; /* refer to enum fpdt_record_type */
51 struct resume_performance_record
{
52 struct fpdt_record_header header
;
56 } __attribute__((packed
));
58 struct boot_performance_record
{
59 struct fpdt_record_header header
;
63 u64 bootloader_launch
;
64 u64 exitbootservice_start
;
65 u64 exitbootservice_end
;
66 } __attribute__((packed
));
68 struct suspend_performance_record
{
69 struct fpdt_record_header header
;
72 } __attribute__((packed
));
75 static struct resume_performance_record
*record_resume
;
76 static struct suspend_performance_record
*record_suspend
;
77 static struct boot_performance_record
*record_boot
;
79 #define FPDT_ATTR(phase, name) \
80 static ssize_t name##_show(struct kobject *kobj, \
81 struct kobj_attribute *attr, char *buf) \
83 return sprintf(buf, "%llu\n", record_##phase->name); \
85 static struct kobj_attribute name##_attr = \
86 __ATTR(name##_ns, 0444, name##_show, NULL)
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
);
98 static ssize_t
resume_count_show(struct kobject
*kobj
,
99 struct kobj_attribute
*attr
, char *buf
)
101 return sprintf(buf
, "%u\n", record_resume
->resume_count
);
104 static struct kobj_attribute resume_count_attr
=
105 __ATTR_RO(resume_count
);
107 static struct attribute
*resume_attrs
[] = {
108 &resume_count_attr
.attr
,
109 &resume_prev_attr
.attr
,
110 &resume_avg_attr
.attr
,
114 static const struct attribute_group resume_attr_group
= {
115 .attrs
= resume_attrs
,
119 static struct attribute
*suspend_attrs
[] = {
120 &suspend_start_attr
.attr
,
121 &suspend_end_attr
.attr
,
125 static const struct attribute_group suspend_attr_group
= {
126 .attrs
= suspend_attrs
,
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
,
139 static const struct attribute_group boot_attr_group
= {
144 static struct kobject
*fpdt_kobj
;
146 #if defined CONFIG_X86 && defined CONFIG_PHYS_ADDR_T_64BIT
147 #include <linux/processor.h>
148 static bool fpdt_address_valid(u64 address
)
151 * On some systems the table contains invalid addresses
152 * with unsuppored high address bits set, check for this.
154 return !(address
>> boot_cpu_data
.x86_phys_bits
);
157 static bool fpdt_address_valid(u64 address
)
163 static int fpdt_process_subtable(u64 address
, u32 subtable_type
)
165 struct fpdt_subtable_header
*subtable_header
;
166 struct fpdt_record_header
*record_header
;
167 char *signature
= (subtable_type
== SUBTABLE_FBPT
? "FBPT" : "S3PT");
171 if (!fpdt_address_valid(address
)) {
172 pr_info(FW_BUG
"invalid physical address: 0x%llx!\n", address
);
176 subtable_header
= acpi_os_map_memory(address
, sizeof(*subtable_header
));
177 if (!subtable_header
)
180 if (strncmp((char *)&subtable_header
->signature
, signature
, 4)) {
181 pr_info(FW_BUG
"subtable signature and type mismatch!\n");
185 length
= subtable_header
->length
;
186 acpi_os_unmap_memory(subtable_header
, sizeof(*subtable_header
));
188 subtable_header
= acpi_os_map_memory(address
, length
);
189 if (!subtable_header
)
192 offset
= sizeof(*subtable_header
);
193 while (offset
< length
) {
194 record_header
= (void *)subtable_header
+ offset
;
195 offset
+= record_header
->length
;
197 if (!record_header
->length
) {
198 pr_err(FW_BUG
"Zero-length record found in FPTD.\n");
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
);
212 pr_err("Duplicate resume performance record found.\n");
215 record_resume
= (struct resume_performance_record
*)record_header
;
216 result
= sysfs_create_group(fpdt_kobj
, &resume_attr_group
);
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
);
226 if (record_suspend
) {
227 pr_err("Duplicate suspend performance record found.\n");
230 record_suspend
= (struct suspend_performance_record
*)record_header
;
231 result
= sysfs_create_group(fpdt_kobj
, &suspend_attr_group
);
236 if (subtable_type
!= SUBTABLE_FBPT
) {
237 pr_err(FW_BUG
"Invalid %d for subtable %s\n",
238 record_header
->type
, signature
);
243 pr_err("Duplicate boot performance record found.\n");
246 record_boot
= (struct boot_performance_record
*)record_header
;
247 result
= sysfs_create_group(fpdt_kobj
, &boot_attr_group
);
253 /* Other types are reserved in ACPI 6.4 spec. */
261 sysfs_remove_group(fpdt_kobj
, &boot_attr_group
);
264 sysfs_remove_group(fpdt_kobj
, &suspend_attr_group
);
267 sysfs_remove_group(fpdt_kobj
, &resume_attr_group
);
272 static int __init
acpi_init_fpdt(void)
275 struct acpi_table_header
*header
;
276 struct fpdt_subtable_entry
*subtable
;
277 u32 offset
= sizeof(*header
);
280 status
= acpi_get_table(ACPI_SIG_FPDT
, 0, &header
);
282 if (ACPI_FAILURE(status
))
285 fpdt_kobj
= kobject_create_and_add("fpdt", acpi_kobj
);
291 while (offset
< header
->length
) {
292 subtable
= (void *)header
+ offset
;
293 switch (subtable
->type
) {
296 result
= fpdt_process_subtable(subtable
->address
,
302 /* Other types are reserved in ACPI 6.4 spec. */
305 offset
+= sizeof(*subtable
);
309 kobject_put(fpdt_kobj
);
312 acpi_put_table(header
);
316 fs_initcall(acpi_init_fpdt
);