Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 
0003 /*
0004  * FPDT support for exporting boot and suspend/resume performance data
0005  *
0006  * Copyright (C) 2021 Intel Corporation. All rights reserved.
0007  */
0008 
0009 #define pr_fmt(fmt) "ACPI FPDT: " fmt
0010 
0011 #include <linux/acpi.h>
0012 
0013 /*
0014  * FPDT contains ACPI table header and a number of fpdt_subtable_entries.
0015  * Each fpdt_subtable_entry points to a subtable: FBPT or S3PT.
0016  * Each FPDT subtable (FBPT/S3PT) is composed of a fpdt_subtable_header
0017  * and a number of fpdt performance records.
0018  * Each FPDT performance record is composed of a fpdt_record_header and
0019  * performance data fields, for boot or suspend or resume phase.
0020  */
0021 enum fpdt_subtable_type {
0022     SUBTABLE_FBPT,
0023     SUBTABLE_S3PT,
0024 };
0025 
0026 struct fpdt_subtable_entry {
0027     u16 type;       /* refer to enum fpdt_subtable_type */
0028     u8 length;
0029     u8 revision;
0030     u32 reserved;
0031     u64 address;        /* physical address of the S3PT/FBPT table */
0032 };
0033 
0034 struct fpdt_subtable_header {
0035     u32 signature;
0036     u32 length;
0037 };
0038 
0039 enum fpdt_record_type {
0040     RECORD_S3_RESUME,
0041     RECORD_S3_SUSPEND,
0042     RECORD_BOOT,
0043 };
0044 
0045 struct fpdt_record_header {
0046     u16 type;       /* refer to enum fpdt_record_type */
0047     u8 length;
0048     u8 revision;
0049 };
0050 
0051 struct resume_performance_record {
0052     struct fpdt_record_header header;
0053     u32 resume_count;
0054     u64 resume_prev;
0055     u64 resume_avg;
0056 } __attribute__((packed));
0057 
0058 struct boot_performance_record {
0059     struct fpdt_record_header header;
0060     u32 reserved;
0061     u64 firmware_start;
0062     u64 bootloader_load;
0063     u64 bootloader_launch;
0064     u64 exitbootservice_start;
0065     u64 exitbootservice_end;
0066 } __attribute__((packed));
0067 
0068 struct suspend_performance_record {
0069     struct fpdt_record_header header;
0070     u64 suspend_start;
0071     u64 suspend_end;
0072 } __attribute__((packed));
0073 
0074 
0075 static struct resume_performance_record *record_resume;
0076 static struct suspend_performance_record *record_suspend;
0077 static struct boot_performance_record *record_boot;
0078 
0079 #define FPDT_ATTR(phase, name)  \
0080 static ssize_t name##_show(struct kobject *kobj,    \
0081          struct kobj_attribute *attr, char *buf)    \
0082 {   \
0083     return sprintf(buf, "%llu\n", record_##phase->name);    \
0084 }   \
0085 static struct kobj_attribute name##_attr =  \
0086 __ATTR(name##_ns, 0444, name##_show, NULL)
0087 
0088 FPDT_ATTR(resume, resume_prev);
0089 FPDT_ATTR(resume, resume_avg);
0090 FPDT_ATTR(suspend, suspend_start);
0091 FPDT_ATTR(suspend, suspend_end);
0092 FPDT_ATTR(boot, firmware_start);
0093 FPDT_ATTR(boot, bootloader_load);
0094 FPDT_ATTR(boot, bootloader_launch);
0095 FPDT_ATTR(boot, exitbootservice_start);
0096 FPDT_ATTR(boot, exitbootservice_end);
0097 
0098 static ssize_t resume_count_show(struct kobject *kobj,
0099                  struct kobj_attribute *attr, char *buf)
0100 {
0101     return sprintf(buf, "%u\n", record_resume->resume_count);
0102 }
0103 
0104 static struct kobj_attribute resume_count_attr =
0105 __ATTR_RO(resume_count);
0106 
0107 static struct attribute *resume_attrs[] = {
0108     &resume_count_attr.attr,
0109     &resume_prev_attr.attr,
0110     &resume_avg_attr.attr,
0111     NULL
0112 };
0113 
0114 static const struct attribute_group resume_attr_group = {
0115     .attrs = resume_attrs,
0116     .name = "resume",
0117 };
0118 
0119 static struct attribute *suspend_attrs[] = {
0120     &suspend_start_attr.attr,
0121     &suspend_end_attr.attr,
0122     NULL
0123 };
0124 
0125 static const struct attribute_group suspend_attr_group = {
0126     .attrs = suspend_attrs,
0127     .name = "suspend",
0128 };
0129 
0130 static struct attribute *boot_attrs[] = {
0131     &firmware_start_attr.attr,
0132     &bootloader_load_attr.attr,
0133     &bootloader_launch_attr.attr,
0134     &exitbootservice_start_attr.attr,
0135     &exitbootservice_end_attr.attr,
0136     NULL
0137 };
0138 
0139 static const struct attribute_group boot_attr_group = {
0140     .attrs = boot_attrs,
0141     .name = "boot",
0142 };
0143 
0144 static struct kobject *fpdt_kobj;
0145 
0146 static int fpdt_process_subtable(u64 address, u32 subtable_type)
0147 {
0148     struct fpdt_subtable_header *subtable_header;
0149     struct fpdt_record_header *record_header;
0150     char *signature = (subtable_type == SUBTABLE_FBPT ? "FBPT" : "S3PT");
0151     u32 length, offset;
0152     int result;
0153 
0154     subtable_header = acpi_os_map_memory(address, sizeof(*subtable_header));
0155     if (!subtable_header)
0156         return -ENOMEM;
0157 
0158     if (strncmp((char *)&subtable_header->signature, signature, 4)) {
0159         pr_info(FW_BUG "subtable signature and type mismatch!\n");
0160         return -EINVAL;
0161     }
0162 
0163     length = subtable_header->length;
0164     acpi_os_unmap_memory(subtable_header, sizeof(*subtable_header));
0165 
0166     subtable_header = acpi_os_map_memory(address, length);
0167     if (!subtable_header)
0168         return -ENOMEM;
0169 
0170     offset = sizeof(*subtable_header);
0171     while (offset < length) {
0172         record_header = (void *)subtable_header + offset;
0173         offset += record_header->length;
0174 
0175         switch (record_header->type) {
0176         case RECORD_S3_RESUME:
0177             if (subtable_type != SUBTABLE_S3PT) {
0178                 pr_err(FW_BUG "Invalid record %d for subtable %s\n",
0179                      record_header->type, signature);
0180                 return -EINVAL;
0181             }
0182             if (record_resume) {
0183                 pr_err("Duplicate resume performance record found.\n");
0184                 continue;
0185             }
0186             record_resume = (struct resume_performance_record *)record_header;
0187             result = sysfs_create_group(fpdt_kobj, &resume_attr_group);
0188             if (result)
0189                 return result;
0190             break;
0191         case RECORD_S3_SUSPEND:
0192             if (subtable_type != SUBTABLE_S3PT) {
0193                 pr_err(FW_BUG "Invalid %d for subtable %s\n",
0194                      record_header->type, signature);
0195                 continue;
0196             }
0197             if (record_suspend) {
0198                 pr_err("Duplicate suspend performance record found.\n");
0199                 continue;
0200             }
0201             record_suspend = (struct suspend_performance_record *)record_header;
0202             result = sysfs_create_group(fpdt_kobj, &suspend_attr_group);
0203             if (result)
0204                 return result;
0205             break;
0206         case RECORD_BOOT:
0207             if (subtable_type != SUBTABLE_FBPT) {
0208                 pr_err(FW_BUG "Invalid %d for subtable %s\n",
0209                      record_header->type, signature);
0210                 return -EINVAL;
0211             }
0212             if (record_boot) {
0213                 pr_err("Duplicate boot performance record found.\n");
0214                 continue;
0215             }
0216             record_boot = (struct boot_performance_record *)record_header;
0217             result = sysfs_create_group(fpdt_kobj, &boot_attr_group);
0218             if (result)
0219                 return result;
0220             break;
0221 
0222         default:
0223             /* Other types are reserved in ACPI 6.4 spec. */
0224             break;
0225         }
0226     }
0227     return 0;
0228 }
0229 
0230 static int __init acpi_init_fpdt(void)
0231 {
0232     acpi_status status;
0233     struct acpi_table_header *header;
0234     struct fpdt_subtable_entry *subtable;
0235     u32 offset = sizeof(*header);
0236 
0237     status = acpi_get_table(ACPI_SIG_FPDT, 0, &header);
0238 
0239     if (ACPI_FAILURE(status))
0240         return 0;
0241 
0242     fpdt_kobj = kobject_create_and_add("fpdt", acpi_kobj);
0243     if (!fpdt_kobj) {
0244         acpi_put_table(header);
0245         return -ENOMEM;
0246     }
0247 
0248     while (offset < header->length) {
0249         subtable = (void *)header + offset;
0250         switch (subtable->type) {
0251         case SUBTABLE_FBPT:
0252         case SUBTABLE_S3PT:
0253             fpdt_process_subtable(subtable->address,
0254                           subtable->type);
0255             break;
0256         default:
0257             /* Other types are reserved in ACPI 6.4 spec. */
0258             break;
0259         }
0260         offset += sizeof(*subtable);
0261     }
0262     return 0;
0263 }
0264 
0265 fs_initcall(acpi_init_fpdt);