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(smi_cmd: &command); |
67 | memcpy(input, buffer, size); |
68 | mutex_unlock(lock: &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(input: 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_buffer: &smi_buf, PAGE_SIZE); |
106 | if (ret) |
107 | return ret; |
108 | buffer = (void *)smi_buf.virt; |
109 | |
110 | dmi_walk(decode: 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(name: "dell-smbios" , id: 1); |
119 | if (!platform_device) { |
120 | ret = -ENOMEM; |
121 | goto fail_platform_device_alloc; |
122 | } |
123 | |
124 | ret = platform_device_add(pdev: platform_device); |
125 | if (ret) |
126 | goto fail_platform_device_add; |
127 | |
128 | ret = dell_smbios_register_device(d: &platform_device->dev, |
129 | call_fn: &dell_smbios_smm_call); |
130 | if (ret) |
131 | goto fail_register; |
132 | |
133 | return 0; |
134 | |
135 | fail_register: |
136 | platform_device_del(pdev: platform_device); |
137 | |
138 | fail_platform_device_add: |
139 | platform_device_put(pdev: platform_device); |
140 | |
141 | fail_wsmt: |
142 | fail_platform_device_alloc: |
143 | dcdbas_smi_free(smi_buffer: &smi_buf); |
144 | return ret; |
145 | } |
146 | |
147 | void exit_dell_smbios_smm(void) |
148 | { |
149 | if (platform_device) { |
150 | dell_smbios_unregister_device(d: &platform_device->dev); |
151 | platform_device_unregister(platform_device); |
152 | dcdbas_smi_free(smi_buffer: &smi_buf); |
153 | } |
154 | } |
155 | |