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