acpi_fpdt.c 7.5 KB

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