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