xref: /linux/drivers/platform/wmi/marshalling.c (revision 23b0f90ba871f096474e1c27c3d14f455189d2d9)
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 
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 
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 
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 
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 
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 
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