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