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 smi_buffer smi_buf; 24 static struct calling_interface_buffer *buffer; 25 static struct platform_device *platform_device; 26 static DEFINE_MUTEX(smm_mutex); 27 28 static void parse_da_table(const struct dmi_header *dm) 29 { 30 struct calling_interface_structure *table = 31 container_of(dm, struct calling_interface_structure, header); 32 33 /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least 34 * 6 bytes of entry 35 */ 36 if (dm->length < 17) 37 return; 38 39 da_command_address = table->cmdIOAddress; 40 da_command_code = table->cmdIOCode; 41 } 42 43 static void find_cmd_address(const struct dmi_header *dm, void *dummy) 44 { 45 switch (dm->type) { 46 case 0xda: /* Calling interface */ 47 parse_da_table(dm); 48 break; 49 } 50 } 51 52 static int dell_smbios_smm_call(struct calling_interface_buffer *input) 53 { 54 struct smi_cmd command; 55 size_t size; 56 57 size = sizeof(struct calling_interface_buffer); 58 command.magic = SMI_CMD_MAGIC; 59 command.command_address = da_command_address; 60 command.command_code = da_command_code; 61 command.ebx = smi_buf.dma; 62 command.ecx = 0x42534931; 63 64 mutex_lock(&smm_mutex); 65 memcpy(buffer, input, size); 66 dcdbas_smi_request(&command); 67 memcpy(input, buffer, size); 68 mutex_unlock(&smm_mutex); 69 return 0; 70 } 71 72 /* When enabled this indicates that SMM won't work */ 73 static bool test_wsmt_enabled(void) 74 { 75 struct calling_interface_token *wsmt; 76 77 /* if token doesn't exist, SMM will work */ 78 wsmt = dell_smbios_find_token(WSMT_EN_TOKEN); 79 if (!wsmt) 80 return false; 81 82 /* If token exists, try to access over SMM but set a dummy return. 83 * - If WSMT disabled it will be overwritten by SMM 84 * - If WSMT enabled then dummy value will remain 85 */ 86 buffer->cmd_class = CLASS_TOKEN_READ; 87 buffer->cmd_select = SELECT_TOKEN_STD; 88 memset(buffer, 0, sizeof(struct calling_interface_buffer)); 89 buffer->input[0] = wsmt->location; 90 buffer->output[0] = 99; 91 dell_smbios_smm_call(buffer); 92 if (buffer->output[0] == 99) 93 return true; 94 95 return false; 96 } 97 98 int init_dell_smbios_smm(void) 99 { 100 int ret; 101 /* 102 * Allocate buffer below 4GB for SMI data--only 32-bit physical addr 103 * is passed to SMI handler. 104 */ 105 ret = dcdbas_smi_alloc(&smi_buf, PAGE_SIZE); 106 if (ret) 107 return ret; 108 buffer = (void *)smi_buf.virt; 109 110 dmi_walk(find_cmd_address, NULL); 111 112 if (test_wsmt_enabled()) { 113 pr_debug("Disabling due to WSMT enabled\n"); 114 ret = -ENODEV; 115 goto fail_wsmt; 116 } 117 118 platform_device = platform_device_alloc("dell-smbios", 1); 119 if (!platform_device) { 120 ret = -ENOMEM; 121 goto fail_platform_device_alloc; 122 } 123 124 ret = platform_device_add(platform_device); 125 if (ret) 126 goto fail_platform_device_add; 127 128 ret = dell_smbios_register_device(&platform_device->dev, 129 &dell_smbios_smm_call); 130 if (ret) 131 goto fail_register; 132 133 return 0; 134 135 fail_register: 136 platform_device_del(platform_device); 137 138 fail_platform_device_add: 139 platform_device_put(platform_device); 140 141 fail_wsmt: 142 fail_platform_device_alloc: 143 dcdbas_smi_free(&smi_buf); 144 return ret; 145 } 146 147 void exit_dell_smbios_smm(void) 148 { 149 if (platform_device) { 150 dell_smbios_unregister_device(&platform_device->dev); 151 platform_device_unregister(platform_device); 152 dcdbas_smi_free(&smi_buf); 153 } 154 } 155