dell-smbios-smm.c 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * SMI methods for use with dell-smbios
  4. *
  5. * Copyright (c) Red Hat <mjg@redhat.com>
  6. * Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com>
  7. * Copyright (c) 2014 Pali Rohár <pali@kernel.org>
  8. * Copyright (c) 2017 Dell Inc.
  9. */
  10. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  11. #include <linux/dmi.h>
  12. #include <linux/gfp.h>
  13. #include <linux/io.h>
  14. #include <linux/module.h>
  15. #include <linux/mutex.h>
  16. #include <linux/platform_device.h>
  17. #include "dcdbas.h"
  18. #include "dell-smbios.h"
  19. static int da_command_address;
  20. static int da_command_code;
  21. static struct calling_interface_buffer *buffer;
  22. static struct platform_device *platform_device;
  23. static DEFINE_MUTEX(smm_mutex);
  24. static const struct dmi_system_id dell_device_table[] __initconst = {
  25. {
  26. .ident = "Dell laptop",
  27. .matches = {
  28. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  29. DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
  30. },
  31. },
  32. {
  33. .matches = {
  34. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  35. DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/
  36. },
  37. },
  38. {
  39. .matches = {
  40. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  41. DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/
  42. },
  43. },
  44. {
  45. .ident = "Dell Computer Corporation",
  46. .matches = {
  47. DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
  48. DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
  49. },
  50. },
  51. { }
  52. };
  53. MODULE_DEVICE_TABLE(dmi, dell_device_table);
  54. static void parse_da_table(const struct dmi_header *dm)
  55. {
  56. struct calling_interface_structure *table =
  57. container_of(dm, struct calling_interface_structure, header);
  58. /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least
  59. * 6 bytes of entry
  60. */
  61. if (dm->length < 17)
  62. return;
  63. da_command_address = table->cmdIOAddress;
  64. da_command_code = table->cmdIOCode;
  65. }
  66. static void find_cmd_address(const struct dmi_header *dm, void *dummy)
  67. {
  68. switch (dm->type) {
  69. case 0xda: /* Calling interface */
  70. parse_da_table(dm);
  71. break;
  72. }
  73. }
  74. static int dell_smbios_smm_call(struct calling_interface_buffer *input)
  75. {
  76. struct smi_cmd command;
  77. size_t size;
  78. size = sizeof(struct calling_interface_buffer);
  79. command.magic = SMI_CMD_MAGIC;
  80. command.command_address = da_command_address;
  81. command.command_code = da_command_code;
  82. command.ebx = virt_to_phys(buffer);
  83. command.ecx = 0x42534931;
  84. mutex_lock(&smm_mutex);
  85. memcpy(buffer, input, size);
  86. dcdbas_smi_request(&command);
  87. memcpy(input, buffer, size);
  88. mutex_unlock(&smm_mutex);
  89. return 0;
  90. }
  91. /* When enabled this indicates that SMM won't work */
  92. static bool test_wsmt_enabled(void)
  93. {
  94. struct calling_interface_token *wsmt;
  95. /* if token doesn't exist, SMM will work */
  96. wsmt = dell_smbios_find_token(WSMT_EN_TOKEN);
  97. if (!wsmt)
  98. return false;
  99. /* If token exists, try to access over SMM but set a dummy return.
  100. * - If WSMT disabled it will be overwritten by SMM
  101. * - If WSMT enabled then dummy value will remain
  102. */
  103. buffer->cmd_class = CLASS_TOKEN_READ;
  104. buffer->cmd_select = SELECT_TOKEN_STD;
  105. memset(buffer, 0, sizeof(struct calling_interface_buffer));
  106. buffer->input[0] = wsmt->location;
  107. buffer->output[0] = 99;
  108. dell_smbios_smm_call(buffer);
  109. if (buffer->output[0] == 99)
  110. return true;
  111. return false;
  112. }
  113. int init_dell_smbios_smm(void)
  114. {
  115. int ret;
  116. /*
  117. * Allocate buffer below 4GB for SMI data--only 32-bit physical addr
  118. * is passed to SMI handler.
  119. */
  120. buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32);
  121. if (!buffer)
  122. return -ENOMEM;
  123. dmi_walk(find_cmd_address, NULL);
  124. if (test_wsmt_enabled()) {
  125. pr_debug("Disabling due to WSMT enabled\n");
  126. ret = -ENODEV;
  127. goto fail_wsmt;
  128. }
  129. platform_device = platform_device_alloc("dell-smbios", 1);
  130. if (!platform_device) {
  131. ret = -ENOMEM;
  132. goto fail_platform_device_alloc;
  133. }
  134. ret = platform_device_add(platform_device);
  135. if (ret)
  136. goto fail_platform_device_add;
  137. ret = dell_smbios_register_device(&platform_device->dev,
  138. &dell_smbios_smm_call);
  139. if (ret)
  140. goto fail_register;
  141. return 0;
  142. fail_register:
  143. platform_device_del(platform_device);
  144. fail_platform_device_add:
  145. platform_device_put(platform_device);
  146. fail_wsmt:
  147. fail_platform_device_alloc:
  148. free_page((unsigned long)buffer);
  149. return ret;
  150. }
  151. void exit_dell_smbios_smm(void)
  152. {
  153. if (platform_device) {
  154. dell_smbios_unregister_device(&platform_device->dev);
  155. platform_device_unregister(platform_device);
  156. free_page((unsigned long)buffer);
  157. }
  158. }