Zhang Rui | d1eb86e | 2021-01-29 14:15:48 +0800 | [diff] [blame] | 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 | static 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: |
Adrian Huang | 97e0341 | 2021-08-19 15:14:16 +0800 | [diff] [blame] | 223 | /* Other types are reserved in ACPI 6.4 spec. */ |
| 224 | break; |
Zhang Rui | d1eb86e | 2021-01-29 14:15:48 +0800 | [diff] [blame] | 225 | } |
| 226 | } |
| 227 | return 0; |
| 228 | } |
| 229 | |
| 230 | static 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); |
Jing Xiangfeng | dd9eaa2 | 2021-06-02 19:58:12 +0800 | [diff] [blame] | 243 | if (!fpdt_kobj) { |
| 244 | acpi_put_table(header); |
Zhang Rui | d1eb86e | 2021-01-29 14:15:48 +0800 | [diff] [blame] | 245 | return -ENOMEM; |
Jing Xiangfeng | dd9eaa2 | 2021-06-02 19:58:12 +0800 | [diff] [blame] | 246 | } |
Zhang Rui | d1eb86e | 2021-01-29 14:15:48 +0800 | [diff] [blame] | 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: |
Adrian Huang | 97e0341 | 2021-08-19 15:14:16 +0800 | [diff] [blame] | 257 | /* Other types are reserved in ACPI 6.4 spec. */ |
Zhang Rui | d1eb86e | 2021-01-29 14:15:48 +0800 | [diff] [blame] | 258 | break; |
| 259 | } |
| 260 | offset += sizeof(*subtable); |
| 261 | } |
| 262 | return 0; |
| 263 | } |
| 264 | |
| 265 | fs_initcall(acpi_init_fpdt); |