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 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 */ 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 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 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 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 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 */ 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