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