File Index
Files and directories
Sign in
FILE

cs_dsp_mock_wmfw.c

File preview with syntax highlighting.

linux-6.19.14/drivers/firmware/cirrus/test/cs_dsp_mock_wmfw.c C
  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
 22struct 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
 34struct wmfw_adsp2_halo_header {
 35	struct wmfw_header header;
 36	struct wmfw_adsp2_sizes sizes;
 37	struct wmfw_footer footer;
 38} __packed;
 39
 40struct wmfw_long_string {
 41	__le16 len;
 42	u8 data[] __nonstring __counted_by(len);
 43} __packed;
 44
 45struct wmfw_short_string {
 46	u8 len;
 47	u8 data[] __nonstring __counted_by(len);
 48} __packed;
 49
 50KUNIT_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 */
 59int cs_dsp_mock_wmfw_format_version(struct cs_dsp_mock_wmfw_builder *builder)
 60{
 61	return builder->format_version;
 62}
 63EXPORT_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 */
 72struct 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}
 87EXPORT_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 */
 99void 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}
118EXPORT_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 */
128void 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		size_t copy_len = info_len;
137		info_len = round_up(info_len, 4);
138		tmp = kunit_kzalloc(builder->test_priv->test, info_len, GFP_KERNEL);
139		KUNIT_ASSERT_NOT_ERR_OR_NULL(builder->test_priv->test, tmp);
140		memcpy(tmp, info, copy_len);
141		info = tmp;
142	}
143
144	cs_dsp_mock_wmfw_add_raw_block(builder, WMFW_INFO_TEXT, 0, info, info_len);
145	kunit_kfree(builder->test_priv->test, tmp);
146}
147EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_info, "FW_CS_DSP_KUNIT_TEST_UTILS");
148
149/**
150 * cs_dsp_mock_wmfw_add_data_block() - Add a data block to the wmfw file.
151 *
152 * @builder:		  Pointer to struct cs_dsp_mock_bin_builder.
153 * @mem_region:		  Memory region for the block.
154 * @mem_offset_dsp_words: Offset to start of destination in DSP words.
155 * @payload_data:	  Pointer to buffer containing the payload data.
156 * @payload_len_bytes:	  Length of payload data in bytes.
157 */
158void cs_dsp_mock_wmfw_add_data_block(struct cs_dsp_mock_wmfw_builder *builder,
159				     int mem_region, unsigned int mem_offset_dsp_words,
160				     const void *payload_data, size_t payload_len_bytes)
161{
162	/* Blob payload length must be a multiple of 4 */
163	KUNIT_ASSERT_EQ(builder->test_priv->test, payload_len_bytes % 4, 0);
164
165	cs_dsp_mock_wmfw_add_raw_block(builder, mem_region, mem_offset_dsp_words,
166				       payload_data, payload_len_bytes);
167}
168EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_data_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
169
170void cs_dsp_mock_wmfw_start_alg_info_block(struct cs_dsp_mock_wmfw_builder *builder,
171					   unsigned int alg_id,
172					   const char *name,
173					   const char *description)
174{
175	struct wmfw_region *rgn = builder->write_p;
176	struct wmfw_adsp_alg_data *v1;
177	struct wmfw_short_string *shortstring;
178	struct wmfw_long_string *longstring;
179	size_t bytes_needed, name_len, description_len;
180	int offset;
181
182	KUNIT_ASSERT_LE(builder->test_priv->test, alg_id, 0xffffff);
183
184	/* Bytes needed for region header */
185	bytes_needed = offsetof(struct wmfw_region, data);
186
187	builder->alg_data_header = builder->write_p;
188	builder->num_coeffs = 0;
189
190	switch (builder->format_version) {
191	case 0:
192		KUNIT_FAIL(builder->test_priv->test, "wmfwV0 does not have alg blocks\n");
193		return;
194	case 1:
195		bytes_needed += offsetof(struct wmfw_adsp_alg_data, data);
196		KUNIT_ASSERT_TRUE(builder->test_priv->test,
197				  (builder->write_p + bytes_needed) <
198				  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
199
200		memset(builder->write_p, 0, bytes_needed);
201
202		/* Create region header */
203		rgn->offset = cpu_to_le32(WMFW_ALGORITHM_DATA << 24);
204
205		/* Create algorithm entry */
206		v1 = (struct wmfw_adsp_alg_data *)&rgn->data[0];
207		v1->id = cpu_to_le32(alg_id);
208		if (name)
209			strscpy(v1->name, name, sizeof(v1->name));
210
211		if (description)
212			strscpy(v1->descr, description, sizeof(v1->descr));
213		break;
214	default:
215		name_len = 0;
216		description_len = 0;
217
218		if (name)
219			name_len = strlen(name);
220
221		if (description)
222			description_len = strlen(description);
223
224		bytes_needed += sizeof(__le32); /* alg id */
225		bytes_needed += round_up(name_len + sizeof(u8), sizeof(__le32));
226		bytes_needed += round_up(description_len + sizeof(__le16), sizeof(__le32));
227		bytes_needed += sizeof(__le32); /* coeff count */
228
229		KUNIT_ASSERT_TRUE(builder->test_priv->test,
230				  (builder->write_p + bytes_needed) <
231				  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
232
233		memset(builder->write_p, 0, bytes_needed);
234
235		/* Create region header */
236		rgn->offset = cpu_to_le32(WMFW_ALGORITHM_DATA << 24);
237
238		/* Create algorithm entry */
239		*(__force __le32 *)&rgn->data[0] = cpu_to_le32(alg_id);
240
241		shortstring = (struct wmfw_short_string *)&rgn->data[4];
242		shortstring->len = name_len;
243
244		if (name_len)
245			memcpy(shortstring->data, name, name_len);
246
247		/* Round up to next __le32 */
248		offset = round_up(4 + struct_size_t(struct wmfw_short_string, data, name_len),
249				  sizeof(__le32));
250
251		longstring = (struct wmfw_long_string *)&rgn->data[offset];
252		longstring->len = cpu_to_le16(description_len);
253
254		if (description_len)
255			memcpy(longstring->data, description, description_len);
256		break;
257	}
258
259	builder->write_p += bytes_needed;
260	builder->bytes_used += bytes_needed;
261}
262EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_start_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
263
264void cs_dsp_mock_wmfw_add_coeff_desc(struct cs_dsp_mock_wmfw_builder *builder,
265				     const struct cs_dsp_mock_coeff_def *def)
266{
267	struct wmfw_adsp_coeff_data *v1;
268	struct wmfw_short_string *shortstring;
269	struct wmfw_long_string *longstring;
270	size_t bytes_needed, shortname_len, fullname_len, description_len;
271	__le32 *ple32;
272
273	KUNIT_ASSERT_NOT_NULL(builder->test_priv->test, builder->alg_data_header);
274
275	switch (builder->format_version) {
276	case 0:
277		return;
278	case 1:
279		bytes_needed = offsetof(struct wmfw_adsp_coeff_data, data);
280		KUNIT_ASSERT_TRUE(builder->test_priv->test,
281				  (builder->write_p + bytes_needed) <
282				  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
283
284		v1 = (struct wmfw_adsp_coeff_data *)builder->write_p;
285		memset(v1, 0, sizeof(*v1));
286		v1->hdr.offset = cpu_to_le16(def->offset_dsp_words);
287		v1->hdr.type = cpu_to_le16(def->mem_type);
288		v1->hdr.size = cpu_to_le32(bytes_needed - sizeof(v1->hdr));
289		v1->ctl_type = cpu_to_le16(def->type);
290		v1->flags = cpu_to_le16(def->flags);
291		v1->len = cpu_to_le32(def->length_bytes);
292
293		if (def->fullname)
294			strscpy(v1->name, def->fullname, sizeof(v1->name));
295
296		if (def->description)
297			strscpy(v1->descr, def->description, sizeof(v1->descr));
298		break;
299	default:
300		fullname_len = 0;
301		description_len = 0;
302		shortname_len = strlen(def->shortname);
303
304		if (def->fullname)
305			fullname_len = strlen(def->fullname);
306
307		if (def->description)
308			description_len = strlen(def->description);
309
310		bytes_needed = sizeof(__le32) * 2; /* type, offset and size */
311		bytes_needed += round_up(shortname_len + sizeof(u8), sizeof(__le32));
312		bytes_needed += round_up(fullname_len + sizeof(u8), sizeof(__le32));
313		bytes_needed += round_up(description_len + sizeof(__le16), sizeof(__le32));
314		bytes_needed += sizeof(__le32) * 2; /* flags, type and length */
315		KUNIT_ASSERT_TRUE(builder->test_priv->test,
316				  (builder->write_p + bytes_needed) <
317				  (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));
318
319		ple32 = (__force __le32 *)builder->write_p;
320		*ple32++ = cpu_to_le32(def->offset_dsp_words | (def->mem_type << 16));
321		*ple32++ = cpu_to_le32(bytes_needed - sizeof(__le32) - sizeof(__le32));
322
323		shortstring = (__force struct wmfw_short_string *)ple32;
324		shortstring->len = shortname_len;
325		memcpy(shortstring->data, def->shortname, shortname_len);
326
327		/* Round up to next __le32 multiple */
328		ple32 += round_up(struct_size_t(struct wmfw_short_string, data, shortname_len),
329				  sizeof(*ple32)) / sizeof(*ple32);
330
331		shortstring = (__force struct wmfw_short_string *)ple32;
332		shortstring->len = fullname_len;
333		memcpy(shortstring->data, def->fullname, fullname_len);
334
335		/* Round up to next __le32 multiple */
336		ple32 += round_up(struct_size_t(struct wmfw_short_string, data, fullname_len),
337				  sizeof(*ple32)) / sizeof(*ple32);
338
339		longstring = (__force struct wmfw_long_string *)ple32;
340		longstring->len = cpu_to_le16(description_len);
341		memcpy(longstring->data, def->description, description_len);
342
343		/* Round up to next __le32 multiple */
344		ple32 += round_up(struct_size_t(struct wmfw_long_string, data, description_len),
345				  sizeof(*ple32)) / sizeof(*ple32);
346
347		*ple32++ = cpu_to_le32(def->type | (def->flags << 16));
348		*ple32 = cpu_to_le32(def->length_bytes);
349		break;
350	}
351
352	builder->write_p += bytes_needed;
353	builder->bytes_used += bytes_needed;
354	builder->num_coeffs++;
355}
356EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_coeff_desc, "FW_CS_DSP_KUNIT_TEST_UTILS");
357
358void cs_dsp_mock_wmfw_end_alg_info_block(struct cs_dsp_mock_wmfw_builder *builder)
359{
360	struct wmfw_region *rgn = builder->alg_data_header;
361	struct wmfw_adsp_alg_data *v1;
362	const struct wmfw_short_string *shortstring;
363	const struct wmfw_long_string *longstring;
364	size_t offset;
365
366	KUNIT_ASSERT_NOT_NULL(builder->test_priv->test, rgn);
367
368	/* Fill in data size */
369	rgn->len = cpu_to_le32((u8 *)builder->write_p - (u8 *)rgn->data);
370
371	/* Fill in coefficient count */
372	switch (builder->format_version) {
373	case 0:
374		return;
375	case 1:
376		v1 = (struct wmfw_adsp_alg_data *)&rgn->data[0];
377		v1->ncoeff = cpu_to_le32(builder->num_coeffs);
378		break;
379	default:
380		offset = 4; /* skip alg id */
381
382		/* Get name length and round up to __le32 multiple */
383		shortstring = (const struct wmfw_short_string *)&rgn->data[offset];
384		offset += round_up(struct_size_t(struct wmfw_short_string, data, shortstring->len),
385				   sizeof(__le32));
386
387		/* Get description length and round up to __le32 multiple */
388		longstring = (const struct wmfw_long_string *)&rgn->data[offset];
389		offset += round_up(struct_size_t(struct wmfw_long_string, data,
390				   le16_to_cpu(longstring->len)),
391				   sizeof(__le32));
392
393		*(__force __le32 *)&rgn->data[offset] = cpu_to_le32(builder->num_coeffs);
394		break;
395	}
396
397	builder->alg_data_header = NULL;
398}
399EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_end_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
400
401static void cs_dsp_init_adsp2_halo_wmfw(struct cs_dsp_mock_wmfw_builder *builder)
402{
403	struct wmfw_adsp2_halo_header *hdr = builder->buf;
404	const struct cs_dsp *dsp = builder->test_priv->dsp;
405
406	memcpy(hdr->header.magic, "WMFW", sizeof(hdr->header.magic));
407	hdr->header.len = cpu_to_le32(sizeof(*hdr));
408	hdr->header.ver = builder->format_version;
409	hdr->header.core = dsp->type;
410	hdr->header.rev = cpu_to_le16(dsp->rev);
411
412	hdr->sizes.pm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_PM));
413	hdr->sizes.xm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_XM));
414	hdr->sizes.ym = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_YM));
415
416	switch (dsp->type) {
417	case WMFW_ADSP2:
418		hdr->sizes.zm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_ZM));
419		break;
420	default:
421		break;
422	}
423
424	builder->write_p = &hdr[1];
425	builder->bytes_used += sizeof(*hdr);
426}
427
428/**
429 * cs_dsp_mock_wmfw_init() - Initialize a struct cs_dsp_mock_wmfw_builder.
430 *
431 * @priv:		Pointer to struct cs_dsp_test.
432 * @format_version:	Required wmfw format version.
433 *
434 * Return: Pointer to created struct cs_dsp_mock_wmfw_builder.
435 */
436struct cs_dsp_mock_wmfw_builder *cs_dsp_mock_wmfw_init(struct cs_dsp_test *priv,
437						       int format_version)
438{
439	struct cs_dsp_mock_wmfw_builder *builder;
440
441	KUNIT_ASSERT_LE(priv->test, format_version, 0xff);
442
443	/* If format version isn't given use the default for the target core */
444	if (format_version < 0) {
445		switch (priv->dsp->type) {
446		case WMFW_ADSP2:
447			format_version = 2;
448			break;
449		default:
450			format_version = 3;
451			break;
452		}
453	}
454
455	builder = kunit_kzalloc(priv->test, sizeof(*builder), GFP_KERNEL);
456	KUNIT_ASSERT_NOT_ERR_OR_NULL(priv->test, builder);
457
458	builder->test_priv = priv;
459	builder->format_version = format_version;
460
461	builder->buf = vmalloc(CS_DSP_MOCK_WMFW_BUF_SIZE);
462	KUNIT_ASSERT_NOT_NULL(priv->test, builder->buf);
463	kunit_add_action_or_reset(priv->test, vfree_action_wrapper, builder->buf);
464
465	builder->buf_size_bytes = CS_DSP_MOCK_WMFW_BUF_SIZE;
466
467	switch (priv->dsp->type) {
468	case WMFW_ADSP2:
469	case WMFW_HALO:
470		cs_dsp_init_adsp2_halo_wmfw(builder);
471		break;
472	default:
473		break;
474	}
475
476	return builder;
477}
478EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_init, "FW_CS_DSP_KUNIT_TEST_UTILS");
~/ Read-only file preview.