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
KUNIT_DEFINE_ACTION_WRAPPER(vfree_action_wrapper,vfree,void *)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 */
cs_dsp_mock_wmfw_get_firmware(struct cs_dsp_mock_wmfw_builder * builder)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 */
cs_dsp_mock_wmfw_add_raw_block(struct cs_dsp_mock_wmfw_builder * builder,int block_type,unsigned int offset,const void * payload_data,size_t payload_len_bytes)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 */
cs_dsp_mock_wmfw_add_info(struct cs_dsp_mock_wmfw_builder * builder,const char * info)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 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 }
147 EXPORT_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 */
cs_dsp_mock_wmfw_add_data_block(struct cs_dsp_mock_wmfw_builder * builder,int mem_region,unsigned int mem_offset_dsp_words,const void * payload_data,size_t payload_len_bytes)158 void 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 }
168 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_data_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
169
cs_dsp_mock_wmfw_start_alg_info_block(struct cs_dsp_mock_wmfw_builder * builder,unsigned int alg_id,const char * name,const char * description)170 void 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 }
262 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_start_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
263
cs_dsp_mock_wmfw_add_coeff_desc(struct cs_dsp_mock_wmfw_builder * builder,const struct cs_dsp_mock_coeff_def * def)264 void 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 }
356 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_coeff_desc, "FW_CS_DSP_KUNIT_TEST_UTILS");
357
cs_dsp_mock_wmfw_end_alg_info_block(struct cs_dsp_mock_wmfw_builder * builder)358 void 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 }
399 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_end_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS");
400
cs_dsp_init_adsp2_halo_wmfw(struct cs_dsp_mock_wmfw_builder * builder)401 static 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 */
cs_dsp_mock_wmfw_init(struct cs_dsp_test * priv,int format_version)436 struct 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 }
478 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_init, "FW_CS_DSP_KUNIT_TEST_UTILS");
479