GNU Linux-libre 5.19-rc6-gnu
[releases.git] / drivers / platform / x86 / dell / dell-smbios-smm.c
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 }