0001
0002
0003
0004
0005
0006
0007
0008
0009 #define pr_fmt(fmt) "ACPI FPDT: " fmt
0010
0011 #include <linux/acpi.h>
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021 enum fpdt_subtable_type {
0022 SUBTABLE_FBPT,
0023 SUBTABLE_S3PT,
0024 };
0025
0026 struct fpdt_subtable_entry {
0027 u16 type;
0028 u8 length;
0029 u8 revision;
0030 u32 reserved;
0031 u64 address;
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;
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
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
0258 break;
0259 }
0260 offset += sizeof(*subtable);
0261 }
0262 return 0;
0263 }
0264
0265 fs_initcall(acpi_init_fpdt);