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 /* Bytes needed for region header */ 182 bytes_needed = offsetof(struct wmfw_region, data); 183 184 builder->alg_data_header = builder->write_p; 185 builder->num_coeffs = 0; 186 187 switch (builder->format_version) { 188 case 0: 189 KUNIT_FAIL(builder->test_priv->test, "wmfwV0 does not have alg blocks\n"); 190 return; 191 case 1: 192 bytes_needed += offsetof(struct wmfw_adsp_alg_data, data); 193 KUNIT_ASSERT_TRUE(builder->test_priv->test, 194 (builder->write_p + bytes_needed) < 195 (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE)); 196 197 memset(builder->write_p, 0, bytes_needed); 198 199 /* Create region header */ 200 rgn->offset = cpu_to_le32(WMFW_ALGORITHM_DATA << 24); 201 202 /* Create algorithm entry */ 203 v1 = (struct wmfw_adsp_alg_data *)&rgn->data[0]; 204 v1->id = cpu_to_le32(alg_id); 205 if (name) 206 strscpy(v1->name, name, sizeof(v1->name)); 207 208 if (description) 209 strscpy(v1->descr, description, sizeof(v1->descr)); 210 break; 211 default: 212 name_len = 0; 213 description_len = 0; 214 215 if (name) 216 name_len = strlen(name); 217 218 if (description) 219 description_len = strlen(description); 220 221 bytes_needed += sizeof(__le32); /* alg id */ 222 bytes_needed += round_up(name_len + sizeof(u8), sizeof(__le32)); 223 bytes_needed += round_up(description_len + sizeof(__le16), sizeof(__le32)); 224 bytes_needed += sizeof(__le32); /* coeff count */ 225 226 KUNIT_ASSERT_TRUE(builder->test_priv->test, 227 (builder->write_p + bytes_needed) < 228 (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE)); 229 230 memset(builder->write_p, 0, bytes_needed); 231 232 /* Create region header */ 233 rgn->offset = cpu_to_le32(WMFW_ALGORITHM_DATA << 24); 234 235 /* Create algorithm entry */ 236 *(__force __le32 *)&rgn->data[0] = cpu_to_le32(alg_id); 237 238 shortstring = (struct wmfw_short_string *)&rgn->data[4]; 239 shortstring->len = name_len; 240 241 if (name_len) 242 memcpy(shortstring->data, name, name_len); 243 244 /* Round up to next __le32 */ 245 offset = round_up(4 + struct_size_t(struct wmfw_short_string, data, name_len), 246 sizeof(__le32)); 247 248 longstring = (struct wmfw_long_string *)&rgn->data[offset]; 249 longstring->len = cpu_to_le16(description_len); 250 251 if (description_len) 252 memcpy(longstring->data, description, description_len); 253 break; 254 } 255 256 builder->write_p += bytes_needed; 257 builder->bytes_used += bytes_needed; 258 } 259 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_start_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS"); 260 261 void cs_dsp_mock_wmfw_add_coeff_desc(struct cs_dsp_mock_wmfw_builder *builder, 262 const struct cs_dsp_mock_coeff_def *def) 263 { 264 struct wmfw_adsp_coeff_data *v1; 265 struct wmfw_short_string *shortstring; 266 struct wmfw_long_string *longstring; 267 size_t bytes_needed, shortname_len, fullname_len, description_len; 268 __le32 *ple32; 269 270 KUNIT_ASSERT_NOT_NULL(builder->test_priv->test, builder->alg_data_header); 271 272 switch (builder->format_version) { 273 case 0: 274 return; 275 case 1: 276 bytes_needed = offsetof(struct wmfw_adsp_coeff_data, data); 277 KUNIT_ASSERT_TRUE(builder->test_priv->test, 278 (builder->write_p + bytes_needed) < 279 (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE)); 280 281 v1 = (struct wmfw_adsp_coeff_data *)builder->write_p; 282 memset(v1, 0, sizeof(*v1)); 283 v1->hdr.offset = cpu_to_le16(def->offset_dsp_words); 284 v1->hdr.type = cpu_to_le16(def->mem_type); 285 v1->hdr.size = cpu_to_le32(bytes_needed - sizeof(v1->hdr)); 286 v1->ctl_type = cpu_to_le16(def->type); 287 v1->flags = cpu_to_le16(def->flags); 288 v1->len = cpu_to_le32(def->length_bytes); 289 290 if (def->fullname) 291 strscpy(v1->name, def->fullname, sizeof(v1->name)); 292 293 if (def->description) 294 strscpy(v1->descr, def->description, sizeof(v1->descr)); 295 break; 296 default: 297 fullname_len = 0; 298 description_len = 0; 299 shortname_len = strlen(def->shortname); 300 301 if (def->fullname) 302 fullname_len = strlen(def->fullname); 303 304 if (def->description) 305 description_len = strlen(def->description); 306 307 bytes_needed = sizeof(__le32) * 2; /* type, offset and size */ 308 bytes_needed += round_up(shortname_len + sizeof(u8), sizeof(__le32)); 309 bytes_needed += round_up(fullname_len + sizeof(u8), sizeof(__le32)); 310 bytes_needed += round_up(description_len + sizeof(__le16), sizeof(__le32)); 311 bytes_needed += sizeof(__le32) * 2; /* flags, type and length */ 312 KUNIT_ASSERT_TRUE(builder->test_priv->test, 313 (builder->write_p + bytes_needed) < 314 (builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE)); 315 316 ple32 = (__force __le32 *)builder->write_p; 317 *ple32++ = cpu_to_le32(def->offset_dsp_words | (def->mem_type << 16)); 318 *ple32++ = cpu_to_le32(bytes_needed - sizeof(__le32) - sizeof(__le32)); 319 320 shortstring = (__force struct wmfw_short_string *)ple32; 321 shortstring->len = shortname_len; 322 memcpy(shortstring->data, def->shortname, shortname_len); 323 324 /* Round up to next __le32 multiple */ 325 ple32 += round_up(struct_size_t(struct wmfw_short_string, data, shortname_len), 326 sizeof(*ple32)) / sizeof(*ple32); 327 328 shortstring = (__force struct wmfw_short_string *)ple32; 329 shortstring->len = fullname_len; 330 memcpy(shortstring->data, def->fullname, fullname_len); 331 332 /* Round up to next __le32 multiple */ 333 ple32 += round_up(struct_size_t(struct wmfw_short_string, data, fullname_len), 334 sizeof(*ple32)) / sizeof(*ple32); 335 336 longstring = (__force struct wmfw_long_string *)ple32; 337 longstring->len = cpu_to_le16(description_len); 338 memcpy(longstring->data, def->description, description_len); 339 340 /* Round up to next __le32 multiple */ 341 ple32 += round_up(struct_size_t(struct wmfw_long_string, data, description_len), 342 sizeof(*ple32)) / sizeof(*ple32); 343 344 *ple32++ = cpu_to_le32(def->type | (def->flags << 16)); 345 *ple32 = cpu_to_le32(def->length_bytes); 346 break; 347 } 348 349 builder->write_p += bytes_needed; 350 builder->bytes_used += bytes_needed; 351 builder->num_coeffs++; 352 } 353 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_coeff_desc, "FW_CS_DSP_KUNIT_TEST_UTILS"); 354 355 void cs_dsp_mock_wmfw_end_alg_info_block(struct cs_dsp_mock_wmfw_builder *builder) 356 { 357 struct wmfw_region *rgn = builder->alg_data_header; 358 struct wmfw_adsp_alg_data *v1; 359 const struct wmfw_short_string *shortstring; 360 const struct wmfw_long_string *longstring; 361 size_t offset; 362 363 KUNIT_ASSERT_NOT_NULL(builder->test_priv->test, rgn); 364 365 /* Fill in data size */ 366 rgn->len = cpu_to_le32((u8 *)builder->write_p - (u8 *)rgn->data); 367 368 /* Fill in coefficient count */ 369 switch (builder->format_version) { 370 case 0: 371 return; 372 case 1: 373 v1 = (struct wmfw_adsp_alg_data *)&rgn->data[0]; 374 v1->ncoeff = cpu_to_le32(builder->num_coeffs); 375 break; 376 default: 377 offset = 4; /* skip alg id */ 378 379 /* Get name length and round up to __le32 multiple */ 380 shortstring = (const struct wmfw_short_string *)&rgn->data[offset]; 381 offset += round_up(struct_size_t(struct wmfw_short_string, data, shortstring->len), 382 sizeof(__le32)); 383 384 /* Get description length and round up to __le32 multiple */ 385 longstring = (const struct wmfw_long_string *)&rgn->data[offset]; 386 offset += round_up(struct_size_t(struct wmfw_long_string, data, 387 le16_to_cpu(longstring->len)), 388 sizeof(__le32)); 389 390 *(__force __le32 *)&rgn->data[offset] = cpu_to_le32(builder->num_coeffs); 391 break; 392 } 393 394 builder->alg_data_header = NULL; 395 } 396 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_end_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS"); 397 398 static void cs_dsp_init_adsp2_halo_wmfw(struct cs_dsp_mock_wmfw_builder *builder) 399 { 400 struct wmfw_adsp2_halo_header *hdr = builder->buf; 401 const struct cs_dsp *dsp = builder->test_priv->dsp; 402 403 memcpy(hdr->header.magic, "WMFW", sizeof(hdr->header.magic)); 404 hdr->header.len = cpu_to_le32(sizeof(*hdr)); 405 hdr->header.ver = builder->format_version; 406 hdr->header.core = dsp->type; 407 hdr->header.rev = cpu_to_le16(dsp->rev); 408 409 hdr->sizes.pm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_PM)); 410 hdr->sizes.xm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_XM)); 411 hdr->sizes.ym = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_YM)); 412 413 switch (dsp->type) { 414 case WMFW_ADSP2: 415 hdr->sizes.zm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_ZM)); 416 break; 417 default: 418 break; 419 } 420 421 builder->write_p = &hdr[1]; 422 builder->bytes_used += sizeof(*hdr); 423 } 424 425 /** 426 * cs_dsp_mock_wmfw_init() - Initialize a struct cs_dsp_mock_wmfw_builder. 427 * 428 * @priv: Pointer to struct cs_dsp_test. 429 * @format_version: Required wmfw format version. 430 * 431 * Return: Pointer to created struct cs_dsp_mock_wmfw_builder. 432 */ 433 struct cs_dsp_mock_wmfw_builder *cs_dsp_mock_wmfw_init(struct cs_dsp_test *priv, 434 int format_version) 435 { 436 struct cs_dsp_mock_wmfw_builder *builder; 437 438 /* If format version isn't given use the default for the target core */ 439 if (format_version < 0) { 440 switch (priv->dsp->type) { 441 case WMFW_ADSP2: 442 format_version = 2; 443 break; 444 default: 445 format_version = 3; 446 break; 447 } 448 } 449 450 builder = kunit_kzalloc(priv->test, sizeof(*builder), GFP_KERNEL); 451 KUNIT_ASSERT_NOT_ERR_OR_NULL(priv->test, builder); 452 453 builder->test_priv = priv; 454 builder->format_version = format_version; 455 456 builder->buf = vmalloc(CS_DSP_MOCK_WMFW_BUF_SIZE); 457 KUNIT_ASSERT_NOT_NULL(priv->test, builder->buf); 458 kunit_add_action_or_reset(priv->test, vfree_action_wrapper, builder->buf); 459 460 builder->buf_size_bytes = CS_DSP_MOCK_WMFW_BUF_SIZE; 461 462 switch (priv->dsp->type) { 463 case WMFW_ADSP2: 464 case WMFW_HALO: 465 cs_dsp_init_adsp2_halo_wmfw(builder); 466 break; 467 default: 468 break; 469 } 470 471 return builder; 472 } 473 EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_init, "FW_CS_DSP_KUNIT_TEST_UTILS"); 474