xref: /linux/drivers/firmware/cirrus/test/cs_dsp_mock_wmfw.c (revision 4fc012daf9c074772421c904357abf586336b1ca)
1 // SPDX-License-Identifier: GPL-2.0-only
2 //
3 // wmfw file builder for cs_dsp KUnit tests.
4 //
5 // Copyright (C) 2024 Cirrus Logic, Inc. and
6 //                    Cirrus Logic International Semiconductor Ltd.
7 
8 #include <kunit/resource.h>
9 #include <kunit/test.h>
10 #include <linux/firmware/cirrus/cs_dsp.h>
11 #include <linux/firmware/cirrus/cs_dsp_test_utils.h>
12 #include <linux/firmware/cirrus/wmfw.h>
13 #include <linux/firmware.h>
14 #include <linux/math.h>
15 #include <linux/overflow.h>
16 #include <linux/string.h>
17 #include <linux/vmalloc.h>
18 
19 /* Buffer large enough for bin file content */
20 #define CS_DSP_MOCK_WMFW_BUF_SIZE	131072
21 
22 struct cs_dsp_mock_wmfw_builder {
23 	struct cs_dsp_test *test_priv;
24 	int format_version;
25 	void *buf;
26 	size_t buf_size_bytes;
27 	void *write_p;
28 	size_t bytes_used;
29 
30 	void *alg_data_header;
31 	unsigned int num_coeffs;
32 };
33 
34 struct wmfw_adsp2_halo_header {
35 	struct wmfw_header header;
36 	struct wmfw_adsp2_sizes sizes;
37 	struct wmfw_footer footer;
38 } __packed;
39 
40 struct wmfw_long_string {
41 	__le16 len;
42 	u8 data[] __nonstring __counted_by(len);
43 } __packed;
44 
45 struct wmfw_short_string {
46 	u8 len;
47 	u8 data[] __nonstring __counted_by(len);
48 } __packed;
49 
50 KUNIT_DEFINE_ACTION_WRAPPER(vfree_action_wrapper, vfree, void *)
51 
52 /**
53  * cs_dsp_mock_wmfw_format_version() - Return format version.
54  *
55  * @builder:	Pointer to struct cs_dsp_mock_wmfw_builder.
56  *
57  * Return: Format version.
58  */
59 int cs_dsp_mock_wmfw_format_version(struct cs_dsp_mock_wmfw_builder *builder)
60 {
61 	return builder->format_version;
62 }
63 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_format_version, "FW_CS_DSP_KUNIT_TEST_UTILS");
64 
65 /**
66  * cs_dsp_mock_wmfw_get_firmware() - Get struct firmware wrapper for data.
67  *
68  * @builder:	Pointer to struct cs_dsp_mock_wmfw_builder.
69  *
70  * Return: Pointer to a struct firmware wrapper for the data.
71  */
72 struct firmware *cs_dsp_mock_wmfw_get_firmware(struct cs_dsp_mock_wmfw_builder *builder)
73 {
74 	struct firmware *fw;
75 
76 	if (!builder)
77 		return NULL;
78 
79 	fw = kunit_kzalloc(builder->test_priv->test, sizeof(*fw), GFP_KERNEL);
80 	KUNIT_ASSERT_NOT_ERR_OR_NULL(builder->test_priv->test, fw);
81 
82 	fw->data = builder->buf;
83 	fw->size = builder->bytes_used;
84 
85 	return fw;
86 }
87 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_get_firmware, "FW_CS_DSP_KUNIT_TEST_UTILS");
88 
89 /**
90  * cs_dsp_mock_wmfw_add_raw_block() - Add a block to the wmfw file.
91  *
92  * @builder:		Pointer to struct cs_dsp_mock_bin_builder.
93  * @block_type:		Block type.
94  * @offset:		Offset.
95  * @payload_data:	Pointer to buffer containing the payload data,
96  *			or NULL if no data.
97  * @payload_len_bytes:	Length of payload data in bytes, or zero.
98  */
99 void cs_dsp_mock_wmfw_add_raw_block(struct cs_dsp_mock_wmfw_builder *builder,
100 				    int block_type, unsigned int offset,
101 				    const void *payload_data, size_t payload_len_bytes)
102 {
103 	struct wmfw_region *header = builder->write_p;
104 	unsigned int bytes_needed = struct_size_t(struct wmfw_region, data, payload_len_bytes);
105 
106 	KUNIT_ASSERT_TRUE(builder->test_priv->test,
107 			  (builder->write_p + bytes_needed) <
108 			  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
109 
110 	header->offset = cpu_to_le32(offset | (block_type << 24));
111 	header->len = cpu_to_le32(payload_len_bytes);
112 	if (payload_len_bytes > 0)
113 		memcpy(header->data, payload_data, payload_len_bytes);
114 
115 	builder->write_p += bytes_needed;
116 	builder->bytes_used += bytes_needed;
117 }
118 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_raw_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
119 
120 /**
121  * cs_dsp_mock_wmfw_add_info() - Add an info block to the wmfw file.
122  *
123  * @builder:	Pointer to struct cs_dsp_mock_bin_builder.
124  * @info:	Pointer to info string to be copied into the file.
125  *
126  * The string will be padded to a length that is a multiple of 4 bytes.
127  */
128 void cs_dsp_mock_wmfw_add_info(struct cs_dsp_mock_wmfw_builder *builder,
129 			       const char *info)
130 {
131 	size_t info_len = strlen(info);
132 	char *tmp = NULL;
133 
134 	if (info_len % 4) {
135 		/* Create a padded string with length a multiple of 4 */
136 		info_len = round_up(info_len, 4);
137 		tmp = kunit_kzalloc(builder->test_priv->test, info_len, GFP_KERNEL);
138 		KUNIT_ASSERT_NOT_ERR_OR_NULL(builder->test_priv->test, tmp);
139 		memcpy(tmp, info, info_len);
140 		info = tmp;
141 	}
142 
143 	cs_dsp_mock_wmfw_add_raw_block(builder, WMFW_INFO_TEXT, 0, info, info_len);
144 	kunit_kfree(builder->test_priv->test, tmp);
145 }
146 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_info, "FW_CS_DSP_KUNIT_TEST_UTILS");
147 
148 /**
149  * cs_dsp_mock_wmfw_add_data_block() - Add a data block to the wmfw file.
150  *
151  * @builder:		  Pointer to struct cs_dsp_mock_bin_builder.
152  * @mem_region:		  Memory region for the block.
153  * @mem_offset_dsp_words: Offset to start of destination in DSP words.
154  * @payload_data:	  Pointer to buffer containing the payload data.
155  * @payload_len_bytes:	  Length of payload data in bytes.
156  */
157 void cs_dsp_mock_wmfw_add_data_block(struct cs_dsp_mock_wmfw_builder *builder,
158 				     int mem_region, unsigned int mem_offset_dsp_words,
159 				     const void *payload_data, size_t payload_len_bytes)
160 {
161 	/* Blob payload length must be a multiple of 4 */
162 	KUNIT_ASSERT_EQ(builder->test_priv->test, payload_len_bytes % 4, 0);
163 
164 	cs_dsp_mock_wmfw_add_raw_block(builder, mem_region, mem_offset_dsp_words,
165 				       payload_data, payload_len_bytes);
166 }
167 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_data_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
168 
169 void cs_dsp_mock_wmfw_start_alg_info_block(struct cs_dsp_mock_wmfw_builder *builder,
170 					   unsigned int alg_id,
171 					   const char *name,
172 					   const char *description)
173 {
174 	struct wmfw_region *rgn = builder->write_p;
175 	struct wmfw_adsp_alg_data *v1;
176 	struct wmfw_short_string *shortstring;
177 	struct wmfw_long_string *longstring;
178 	size_t bytes_needed, name_len, description_len;
179 	int offset;
180 
181 	KUNIT_ASSERT_LE(builder->test_priv->test, alg_id, 0xffffff);
182 
183 	/* Bytes needed for region header */
184 	bytes_needed = offsetof(struct wmfw_region, data);
185 
186 	builder->alg_data_header = builder->write_p;
187 	builder->num_coeffs = 0;
188 
189 	switch (builder->format_version) {
190 	case 0:
191 		KUNIT_FAIL(builder->test_priv->test, "wmfwV0 does not have alg blocks\n");
192 		return;
193 	case 1:
194 		bytes_needed += offsetof(struct wmfw_adsp_alg_data, data);
195 		KUNIT_ASSERT_TRUE(builder->test_priv->test,
196 				  (builder->write_p + bytes_needed) <
197 				  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
198 
199 		memset(builder->write_p, 0, bytes_needed);
200 
201 		/* Create region header */
202 		rgn->offset = cpu_to_le32(WMFW_ALGORITHM_DATA << 24);
203 
204 		/* Create algorithm entry */
205 		v1 = (struct wmfw_adsp_alg_data *)&rgn->data[0];
206 		v1->id = cpu_to_le32(alg_id);
207 		if (name)
208 			strscpy(v1->name, name, sizeof(v1->name));
209 
210 		if (description)
211 			strscpy(v1->descr, description, sizeof(v1->descr));
212 		break;
213 	default:
214 		name_len = 0;
215 		description_len = 0;
216 
217 		if (name)
218 			name_len = strlen(name);
219 
220 		if (description)
221 			description_len = strlen(description);
222 
223 		bytes_needed += sizeof(__le32); /* alg id */
224 		bytes_needed += round_up(name_len + sizeof(u8), sizeof(__le32));
225 		bytes_needed += round_up(description_len + sizeof(__le16), sizeof(__le32));
226 		bytes_needed += sizeof(__le32); /* coeff count */
227 
228 		KUNIT_ASSERT_TRUE(builder->test_priv->test,
229 				  (builder->write_p + bytes_needed) <
230 				  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
231 
232 		memset(builder->write_p, 0, bytes_needed);
233 
234 		/* Create region header */
235 		rgn->offset = cpu_to_le32(WMFW_ALGORITHM_DATA << 24);
236 
237 		/* Create algorithm entry */
238 		*(__force __le32 *)&rgn->data[0] = cpu_to_le32(alg_id);
239 
240 		shortstring = (struct wmfw_short_string *)&rgn->data[4];
241 		shortstring->len = name_len;
242 
243 		if (name_len)
244 			memcpy(shortstring->data, name, name_len);
245 
246 		/* Round up to next __le32 */
247 		offset = round_up(4 + struct_size_t(struct wmfw_short_string, data, name_len),
248 				  sizeof(__le32));
249 
250 		longstring = (struct wmfw_long_string *)&rgn->data[offset];
251 		longstring->len = cpu_to_le16(description_len);
252 
253 		if (description_len)
254 			memcpy(longstring->data, description, description_len);
255 		break;
256 	}
257 
258 	builder->write_p += bytes_needed;
259 	builder->bytes_used += bytes_needed;
260 }
261 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_start_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
262 
263 void cs_dsp_mock_wmfw_add_coeff_desc(struct cs_dsp_mock_wmfw_builder *builder,
264 				     const struct cs_dsp_mock_coeff_def *def)
265 {
266 	struct wmfw_adsp_coeff_data *v1;
267 	struct wmfw_short_string *shortstring;
268 	struct wmfw_long_string *longstring;
269 	size_t bytes_needed, shortname_len, fullname_len, description_len;
270 	__le32 *ple32;
271 
272 	KUNIT_ASSERT_NOT_NULL(builder->test_priv->test, builder->alg_data_header);
273 
274 	switch (builder->format_version) {
275 	case 0:
276 		return;
277 	case 1:
278 		bytes_needed = offsetof(struct wmfw_adsp_coeff_data, data);
279 		KUNIT_ASSERT_TRUE(builder->test_priv->test,
280 				  (builder->write_p + bytes_needed) <
281 				  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
282 
283 		v1 = (struct wmfw_adsp_coeff_data *)builder->write_p;
284 		memset(v1, 0, sizeof(*v1));
285 		v1->hdr.offset = cpu_to_le16(def->offset_dsp_words);
286 		v1->hdr.type = cpu_to_le16(def->mem_type);
287 		v1->hdr.size = cpu_to_le32(bytes_needed - sizeof(v1->hdr));
288 		v1->ctl_type = cpu_to_le16(def->type);
289 		v1->flags = cpu_to_le16(def->flags);
290 		v1->len = cpu_to_le32(def->length_bytes);
291 
292 		if (def->fullname)
293 			strscpy(v1->name, def->fullname, sizeof(v1->name));
294 
295 		if (def->description)
296 			strscpy(v1->descr, def->description, sizeof(v1->descr));
297 		break;
298 	default:
299 		fullname_len = 0;
300 		description_len = 0;
301 		shortname_len = strlen(def->shortname);
302 
303 		if (def->fullname)
304 			fullname_len = strlen(def->fullname);
305 
306 		if (def->description)
307 			description_len = strlen(def->description);
308 
309 		bytes_needed = sizeof(__le32) * 2; /* type, offset and size */
310 		bytes_needed += round_up(shortname_len + sizeof(u8), sizeof(__le32));
311 		bytes_needed += round_up(fullname_len + sizeof(u8), sizeof(__le32));
312 		bytes_needed += round_up(description_len + sizeof(__le16), sizeof(__le32));
313 		bytes_needed += sizeof(__le32) * 2; /* flags, type and length */
314 		KUNIT_ASSERT_TRUE(builder->test_priv->test,
315 				  (builder->write_p + bytes_needed) <
316 				  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
317 
318 		ple32 = (__force __le32 *)builder->write_p;
319 		*ple32++ = cpu_to_le32(def->offset_dsp_words | (def->mem_type << 16));
320 		*ple32++ = cpu_to_le32(bytes_needed - sizeof(__le32) - sizeof(__le32));
321 
322 		shortstring = (__force struct wmfw_short_string *)ple32;
323 		shortstring->len = shortname_len;
324 		memcpy(shortstring->data, def->shortname, shortname_len);
325 
326 		/* Round up to next __le32 multiple */
327 		ple32 += round_up(struct_size_t(struct wmfw_short_string, data, shortname_len),
328 				  sizeof(*ple32)) / sizeof(*ple32);
329 
330 		shortstring = (__force struct wmfw_short_string *)ple32;
331 		shortstring->len = fullname_len;
332 		memcpy(shortstring->data, def->fullname, fullname_len);
333 
334 		/* Round up to next __le32 multiple */
335 		ple32 += round_up(struct_size_t(struct wmfw_short_string, data, fullname_len),
336 				  sizeof(*ple32)) / sizeof(*ple32);
337 
338 		longstring = (__force struct wmfw_long_string *)ple32;
339 		longstring->len = cpu_to_le16(description_len);
340 		memcpy(longstring->data, def->description, description_len);
341 
342 		/* Round up to next __le32 multiple */
343 		ple32 += round_up(struct_size_t(struct wmfw_long_string, data, description_len),
344 				  sizeof(*ple32)) / sizeof(*ple32);
345 
346 		*ple32++ = cpu_to_le32(def->type | (def->flags << 16));
347 		*ple32 = cpu_to_le32(def->length_bytes);
348 		break;
349 	}
350 
351 	builder->write_p += bytes_needed;
352 	builder->bytes_used += bytes_needed;
353 	builder->num_coeffs++;
354 }
355 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_coeff_desc, "FW_CS_DSP_KUNIT_TEST_UTILS");
356 
357 void cs_dsp_mock_wmfw_end_alg_info_block(struct cs_dsp_mock_wmfw_builder *builder)
358 {
359 	struct wmfw_region *rgn = builder->alg_data_header;
360 	struct wmfw_adsp_alg_data *v1;
361 	const struct wmfw_short_string *shortstring;
362 	const struct wmfw_long_string *longstring;
363 	size_t offset;
364 
365 	KUNIT_ASSERT_NOT_NULL(builder->test_priv->test, rgn);
366 
367 	/* Fill in data size */
368 	rgn->len = cpu_to_le32((u8 *)builder->write_p - (u8 *)rgn->data);
369 
370 	/* Fill in coefficient count */
371 	switch (builder->format_version) {
372 	case 0:
373 		return;
374 	case 1:
375 		v1 = (struct wmfw_adsp_alg_data *)&rgn->data[0];
376 		v1->ncoeff = cpu_to_le32(builder->num_coeffs);
377 		break;
378 	default:
379 		offset = 4; /* skip alg id */
380 
381 		/* Get name length and round up to __le32 multiple */
382 		shortstring = (const struct wmfw_short_string *)&rgn->data[offset];
383 		offset += round_up(struct_size_t(struct wmfw_short_string, data, shortstring->len),
384 				   sizeof(__le32));
385 
386 		/* Get description length and round up to __le32 multiple */
387 		longstring = (const struct wmfw_long_string *)&rgn->data[offset];
388 		offset += round_up(struct_size_t(struct wmfw_long_string, data,
389 				   le16_to_cpu(longstring->len)),
390 				   sizeof(__le32));
391 
392 		*(__force __le32 *)&rgn->data[offset] = cpu_to_le32(builder->num_coeffs);
393 		break;
394 	}
395 
396 	builder->alg_data_header = NULL;
397 }
398 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_end_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
399 
400 static void cs_dsp_init_adsp2_halo_wmfw(struct cs_dsp_mock_wmfw_builder *builder)
401 {
402 	struct wmfw_adsp2_halo_header *hdr = builder->buf;
403 	const struct cs_dsp *dsp = builder->test_priv->dsp;
404 
405 	memcpy(hdr->header.magic, "WMFW", sizeof(hdr->header.magic));
406 	hdr->header.len = cpu_to_le32(sizeof(*hdr));
407 	hdr->header.ver = builder->format_version;
408 	hdr->header.core = dsp->type;
409 	hdr->header.rev = cpu_to_le16(dsp->rev);
410 
411 	hdr->sizes.pm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_PM));
412 	hdr->sizes.xm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_XM));
413 	hdr->sizes.ym = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_YM));
414 
415 	switch (dsp->type) {
416 	case WMFW_ADSP2:
417 		hdr->sizes.zm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_ZM));
418 		break;
419 	default:
420 		break;
421 	}
422 
423 	builder->write_p = &hdr[1];
424 	builder->bytes_used += sizeof(*hdr);
425 }
426 
427 /**
428  * cs_dsp_mock_wmfw_init() - Initialize a struct cs_dsp_mock_wmfw_builder.
429  *
430  * @priv:		Pointer to struct cs_dsp_test.
431  * @format_version:	Required wmfw format version.
432  *
433  * Return: Pointer to created struct cs_dsp_mock_wmfw_builder.
434  */
435 struct cs_dsp_mock_wmfw_builder *cs_dsp_mock_wmfw_init(struct cs_dsp_test *priv,
436 						       int format_version)
437 {
438 	struct cs_dsp_mock_wmfw_builder *builder;
439 
440 	KUNIT_ASSERT_LE(priv->test, format_version, 0xff);
441 
442 	/* If format version isn't given use the default for the target core */
443 	if (format_version < 0) {
444 		switch (priv->dsp->type) {
445 		case WMFW_ADSP2:
446 			format_version = 2;
447 			break;
448 		default:
449 			format_version = 3;
450 			break;
451 		}
452 	}
453 
454 	builder = kunit_kzalloc(priv->test, sizeof(*builder), GFP_KERNEL);
455 	KUNIT_ASSERT_NOT_ERR_OR_NULL(priv->test, builder);
456 
457 	builder->test_priv = priv;
458 	builder->format_version = format_version;
459 
460 	builder->buf = vmalloc(CS_DSP_MOCK_WMFW_BUF_SIZE);
461 	KUNIT_ASSERT_NOT_NULL(priv->test, builder->buf);
462 	kunit_add_action_or_reset(priv->test, vfree_action_wrapper, builder->buf);
463 
464 	builder->buf_size_bytes = CS_DSP_MOCK_WMFW_BUF_SIZE;
465 
466 	switch (priv->dsp->type) {
467 	case WMFW_ADSP2:
468 	case WMFW_HALO:
469 		cs_dsp_init_adsp2_halo_wmfw(builder);
470 		break;
471 	default:
472 		break;
473 	}
474 
475 	return builder;
476 }
477 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_init, "FW_CS_DSP_KUNIT_TEST_UTILS");
478