linux/drivers/acpi/acpi_fpdt.c
<<
>>
Prefs
   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 */
  21enum fpdt_subtable_type {
  22        SUBTABLE_FBPT,
  23        SUBTABLE_S3PT,
  24};
  25
  26struct 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
  34struct fpdt_subtable_header {
  35        u32 signature;
  36        u32 length;
  37};
  38
  39enum fpdt_record_type {
  40        RECORD_S3_RESUME,
  41        RECORD_S3_SUSPEND,
  42        RECORD_BOOT,
  43};
  44
  45struct fpdt_record_header {
  46        u16 type;               /* refer to enum fpdt_record_type */
  47        u8 length;
  48        u8 revision;
  49};
  50
  51struct 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
  58struct 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
  68struct suspend_performance_record {
  69        struct fpdt_record_header header;
  70        u64 suspend_start;
  71        u64 suspend_end;
  72} __attribute__((packed));
  73
  74
  75static struct resume_performance_record *record_resume;
  76static struct suspend_performance_record *record_suspend;
  77static struct boot_performance_record *record_boot;
  78
  79#define FPDT_ATTR(phase, name)  \
  80static 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}       \
  85static struct kobj_attribute name##_attr =      \
  86__ATTR(name##_ns, 0444, name##_show, NULL)
  87
  88FPDT_ATTR(resume, resume_prev);
  89FPDT_ATTR(resume, resume_avg);
  90FPDT_ATTR(suspend, suspend_start);
  91FPDT_ATTR(suspend, suspend_end);
  92FPDT_ATTR(boot, firmware_start);
  93FPDT_ATTR(boot, bootloader_load);
  94FPDT_ATTR(boot, bootloader_launch);
  95FPDT_ATTR(boot, exitbootservice_start);
  96FPDT_ATTR(boot, exitbootservice_end);
  97
  98static 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
 104static struct kobj_attribute resume_count_attr =
 105__ATTR_RO(resume_count);
 106
 107static struct attribute *resume_attrs[] = {
 108        &resume_count_attr.attr,
 109        &resume_prev_attr.attr,
 110        &resume_avg_attr.attr,
 111        NULL
 112};
 113
 114static const struct attribute_group resume_attr_group = {
 115        .attrs = resume_attrs,
 116        .name = "resume",
 117};
 118
 119static struct attribute *suspend_attrs[] = {
 120        &suspend_start_attr.attr,
 121        &suspend_end_attr.attr,
 122        NULL
 123};
 124
 125static const struct attribute_group suspend_attr_group = {
 126        .attrs = suspend_attrs,
 127        .name = "suspend",
 128};
 129
 130static 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
 139static const struct attribute_group boot_attr_group = {
 140        .attrs = boot_attrs,
 141        .name = "boot",
 142};
 143
 144static struct kobject *fpdt_kobj;
 145
 146static int fpdt_process_subtable(u64 address, u32 subtable_type)
 147{
 148        struct fpdt_subtable_header *subtable_header;
 149        struct fpdt_record_header *record_header;
 150        char *signature = (subtable_type == SUBTABLE_FBPT ? "FBPT" : "S3PT");
 151        u32 length, offset;
 152        int result;
 153
 154        subtable_header = acpi_os_map_memory(address, sizeof(*subtable_header));
 155        if (!subtable_header)
 156                return -ENOMEM;
 157
 158        if (strncmp((char *)&subtable_header->signature, signature, 4)) {
 159                pr_info(FW_BUG "subtable signature and type mismatch!\n");
 160                return -EINVAL;
 161        }
 162
 163        length = subtable_header->length;
 164        acpi_os_unmap_memory(subtable_header, sizeof(*subtable_header));
 165
 166        subtable_header = acpi_os_map_memory(address, length);
 167        if (!subtable_header)
 168                return -ENOMEM;
 169
 170        offset = sizeof(*subtable_header);
 171        while (offset < length) {
 172                record_header = (void *)subtable_header + offset;
 173                offset += record_header->length;
 174
 175                switch (record_header->type) {
 176                case RECORD_S3_RESUME:
 177                        if (subtable_type != SUBTABLE_S3PT) {
 178                                pr_err(FW_BUG "Invalid record %d for subtable %s\n",
 179                                     record_header->type, signature);
 180                                return -EINVAL;
 181                        }
 182                        if (record_resume) {
 183                                pr_err("Duplicate resume performance record found.\n");
 184                                continue;
 185                        }
 186                        record_resume = (struct resume_performance_record *)record_header;
 187                        result = sysfs_create_group(fpdt_kobj, &resume_attr_group);
 188                        if (result)
 189                                return result;
 190                        break;
 191                case RECORD_S3_SUSPEND:
 192                        if (subtable_type != SUBTABLE_S3PT) {
 193                                pr_err(FW_BUG "Invalid %d for subtable %s\n",
 194                                     record_header->type, signature);
 195                                continue;
 196                        }
 197                        if (record_suspend) {
 198                                pr_err("Duplicate suspend performance record found.\n");
 199                                continue;
 200                        }
 201                        record_suspend = (struct suspend_performance_record *)record_header;
 202                        result = sysfs_create_group(fpdt_kobj, &suspend_attr_group);
 203                        if (result)
 204                                return result;
 205                        break;
 206                case RECORD_BOOT:
 207                        if (subtable_type != SUBTABLE_FBPT) {
 208                                pr_err(FW_BUG "Invalid %d for subtable %s\n",
 209                                     record_header->type, signature);
 210                                return -EINVAL;
 211                        }
 212                        if (record_boot) {
 213                                pr_err("Duplicate boot performance record found.\n");
 214                                continue;
 215                        }
 216                        record_boot = (struct boot_performance_record *)record_header;
 217                        result = sysfs_create_group(fpdt_kobj, &boot_attr_group);
 218                        if (result)
 219                                return result;
 220                        break;
 221
 222                default:
 223                        pr_err(FW_BUG "Invalid record %d found.\n", record_header->type);
 224                        return -EINVAL;
 225                }
 226        }
 227        return 0;
 228}
 229
 230static int __init acpi_init_fpdt(void)
 231{
 232        acpi_status status;
 233        struct acpi_table_header *header;
 234        struct fpdt_subtable_entry *subtable;
 235        u32 offset = sizeof(*header);
 236
 237        status = acpi_get_table(ACPI_SIG_FPDT, 0, &header);
 238
 239        if (ACPI_FAILURE(status))
 240                return 0;
 241
 242        fpdt_kobj = kobject_create_and_add("fpdt", acpi_kobj);
 243        if (!fpdt_kobj) {
 244                acpi_put_table(header);
 245                return -ENOMEM;
 246        }
 247
 248        while (offset < header->length) {
 249                subtable = (void *)header + offset;
 250                switch (subtable->type) {
 251                case SUBTABLE_FBPT:
 252                case SUBTABLE_S3PT:
 253                        fpdt_process_subtable(subtable->address,
 254                                              subtable->type);
 255                        break;
 256                default:
 257                        pr_info(FW_BUG "Invalid subtable type %d found.\n",
 258                               subtable->type);
 259                        break;
 260                }
 261                offset += sizeof(*subtable);
 262        }
 263        return 0;
 264}
 265
 266fs_initcall(acpi_init_fpdt);
 267