1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * ACPI-WMI buffer marshalling.
4 *
5 * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
6 */
7
8 #include <linux/acpi.h>
9 #include <linux/align.h>
10 #include <linux/math.h>
11 #include <linux/overflow.h>
12 #include <linux/slab.h>
13 #include <linux/unaligned.h>
14 #include <linux/wmi.h>
15
16 #include <kunit/visibility.h>
17
18 #include "internal.h"
19
wmi_adjust_buffer_length(size_t * length,const union acpi_object * obj)20 static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj)
21 {
22 size_t alignment, size;
23
24 switch (obj->type) {
25 case ACPI_TYPE_INTEGER:
26 /*
27 * Integers are threated as 32 bit even if the ACPI DSDT
28 * declares 64 bit integer width.
29 */
30 alignment = 4;
31 size = sizeof(u32);
32 break;
33 case ACPI_TYPE_STRING:
34 /*
35 * Strings begin with a single little-endian 16-bit field containing
36 * the string length in bytes and are encoded as UTF-16LE with a terminating
37 * nul character.
38 */
39 if (obj->string.length + 1 > U16_MAX / 2)
40 return -EOVERFLOW;
41
42 alignment = 2;
43 size = struct_size_t(struct wmi_string, chars, obj->string.length + 1);
44 break;
45 case ACPI_TYPE_BUFFER:
46 /*
47 * Buffers are copied as-is.
48 */
49 alignment = 1;
50 size = obj->buffer.length;
51 break;
52 default:
53 return -EPROTO;
54 }
55
56 *length = size_add(ALIGN(*length, alignment), size);
57
58 return 0;
59 }
60
wmi_obj_get_buffer_length(const union acpi_object * obj,size_t * length)61 static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length)
62 {
63 size_t total = 0;
64 int ret;
65
66 if (obj->type == ACPI_TYPE_PACKAGE) {
67 for (int i = 0; i < obj->package.count; i++) {
68 ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]);
69 if (ret < 0)
70 return ret;
71 }
72 } else {
73 ret = wmi_adjust_buffer_length(&total, obj);
74 if (ret < 0)
75 return ret;
76 }
77
78 *length = total;
79
80 return 0;
81 }
82
wmi_obj_transform_simple(const union acpi_object * obj,u8 * buffer,size_t * consumed)83 static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed)
84 {
85 struct wmi_string *string;
86 size_t length;
87 __le32 value;
88 u8 *aligned;
89
90 switch (obj->type) {
91 case ACPI_TYPE_INTEGER:
92 aligned = PTR_ALIGN(buffer, 4);
93 length = sizeof(value);
94
95 value = cpu_to_le32(obj->integer.value);
96 memcpy(aligned, &value, length);
97 break;
98 case ACPI_TYPE_STRING:
99 aligned = PTR_ALIGN(buffer, 2);
100 string = (struct wmi_string *)aligned;
101 length = struct_size(string, chars, obj->string.length + 1);
102
103 /* We do not have to worry about unaligned accesses here as the WMI
104 * string will already be aligned on a two-byte boundary.
105 */
106 string->length = cpu_to_le16((obj->string.length + 1) * 2);
107 for (int i = 0; i < obj->string.length; i++)
108 string->chars[i] = cpu_to_le16(obj->string.pointer[i]);
109
110 /*
111 * The Windows WMI-ACPI driver always emits a terminating nul character,
112 * so we emulate this behavior here as well.
113 */
114 string->chars[obj->string.length] = '\0';
115 break;
116 case ACPI_TYPE_BUFFER:
117 aligned = buffer;
118 length = obj->buffer.length;
119
120 memcpy(aligned, obj->buffer.pointer, length);
121 break;
122 default:
123 return -EPROTO;
124 }
125
126 *consumed = (aligned - buffer) + length;
127
128 return 0;
129 }
130
wmi_obj_transform(const union acpi_object * obj,u8 * buffer)131 static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer)
132 {
133 size_t consumed;
134 int ret;
135
136 if (obj->type == ACPI_TYPE_PACKAGE) {
137 for (int i = 0; i < obj->package.count; i++) {
138 ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer,
139 &consumed);
140 if (ret < 0)
141 return ret;
142
143 buffer += consumed;
144 }
145 } else {
146 ret = wmi_obj_transform_simple(obj, buffer, &consumed);
147 if (ret < 0)
148 return ret;
149 }
150
151 return 0;
152 }
153
wmi_unmarshal_acpi_object(const union acpi_object * obj,struct wmi_buffer * buffer)154 int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer)
155 {
156 size_t length, alloc_length;
157 u8 *data;
158 int ret;
159
160 ret = wmi_obj_get_buffer_length(obj, &length);
161 if (ret < 0)
162 return ret;
163
164 if (ARCH_KMALLOC_MINALIGN < 8) {
165 /*
166 * kmalloc() guarantees that the alignment of the resulting memory allocation is at
167 * least the largest power-of-two divisor of the allocation size. The WMI buffer
168 * data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI
169 * integers, so we have to round the allocation size to the next multiple of 8.
170 */
171 alloc_length = round_up(length, 8);
172 } else {
173 alloc_length = length;
174 }
175
176 data = kzalloc(alloc_length, GFP_KERNEL);
177 if (!data)
178 return -ENOMEM;
179
180 ret = wmi_obj_transform(obj, data);
181 if (ret < 0) {
182 kfree(data);
183 return ret;
184 }
185
186 buffer->length = length;
187 buffer->data = data;
188
189 return 0;
190 }
191 EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object);
192
wmi_marshal_string(const struct wmi_buffer * buffer,struct acpi_buffer * out)193 int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out)
194 {
195 const struct wmi_string *string;
196 u16 length, value;
197 size_t chars;
198 char *str;
199
200 if (buffer->length < sizeof(*string))
201 return -ENODATA;
202
203 string = buffer->data;
204 length = get_unaligned_le16(&string->length);
205 if (buffer->length < sizeof(*string) + length)
206 return -ENODATA;
207
208 /* Each character needs to be 16 bits long */
209 if (length % 2)
210 return -EINVAL;
211
212 chars = length / 2;
213 str = kmalloc(chars + 1, GFP_KERNEL);
214 if (!str)
215 return -ENOMEM;
216
217 for (int i = 0; i < chars; i++) {
218 value = get_unaligned_le16(&string->chars[i]);
219
220 /* ACPI only accepts ASCII strings */
221 if (value > 0x7F) {
222 kfree(str);
223 return -EINVAL;
224 }
225
226 str[i] = value & 0xFF;
227
228 /*
229 * ACPI strings should only contain a single nul character at the end.
230 * Because of this we must not copy any padding from the WMI string.
231 */
232 if (!value) {
233 /* ACPICA wants the length of the string without the nul character */
234 out->length = i;
235 out->pointer = str;
236 return 0;
237 }
238 }
239
240 str[chars] = '\0';
241
242 out->length = chars;
243 out->pointer = str;
244
245 return 0;
246 }
247 EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string);
248