Path: blob/master/drivers/firmware/cirrus/test/cs_dsp_mock_wmfw.c
26444 views
// SPDX-License-Identifier: GPL-2.0-only1//2// wmfw file builder for cs_dsp KUnit tests.3//4// Copyright (C) 2024 Cirrus Logic, Inc. and5// Cirrus Logic International Semiconductor Ltd.67#include <kunit/resource.h>8#include <kunit/test.h>9#include <linux/firmware/cirrus/cs_dsp.h>10#include <linux/firmware/cirrus/cs_dsp_test_utils.h>11#include <linux/firmware/cirrus/wmfw.h>12#include <linux/firmware.h>13#include <linux/math.h>14#include <linux/overflow.h>15#include <linux/string.h>16#include <linux/vmalloc.h>1718/* Buffer large enough for bin file content */19#define CS_DSP_MOCK_WMFW_BUF_SIZE 1310722021struct cs_dsp_mock_wmfw_builder {22struct cs_dsp_test *test_priv;23int format_version;24void *buf;25size_t buf_size_bytes;26void *write_p;27size_t bytes_used;2829void *alg_data_header;30unsigned int num_coeffs;31};3233struct wmfw_adsp2_halo_header {34struct wmfw_header header;35struct wmfw_adsp2_sizes sizes;36struct wmfw_footer footer;37} __packed;3839struct wmfw_long_string {40__le16 len;41u8 data[] __nonstring __counted_by(len);42} __packed;4344struct wmfw_short_string {45u8 len;46u8 data[] __nonstring __counted_by(len);47} __packed;4849KUNIT_DEFINE_ACTION_WRAPPER(vfree_action_wrapper, vfree, void *)5051/**52* cs_dsp_mock_wmfw_format_version() - Return format version.53*54* @builder: Pointer to struct cs_dsp_mock_wmfw_builder.55*56* Return: Format version.57*/58int cs_dsp_mock_wmfw_format_version(struct cs_dsp_mock_wmfw_builder *builder)59{60return builder->format_version;61}62EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_format_version, "FW_CS_DSP_KUNIT_TEST_UTILS");6364/**65* cs_dsp_mock_wmfw_get_firmware() - Get struct firmware wrapper for data.66*67* @builder: Pointer to struct cs_dsp_mock_wmfw_builder.68*69* Return: Pointer to a struct firmware wrapper for the data.70*/71struct firmware *cs_dsp_mock_wmfw_get_firmware(struct cs_dsp_mock_wmfw_builder *builder)72{73struct firmware *fw;7475if (!builder)76return NULL;7778fw = kunit_kzalloc(builder->test_priv->test, sizeof(*fw), GFP_KERNEL);79KUNIT_ASSERT_NOT_ERR_OR_NULL(builder->test_priv->test, fw);8081fw->data = builder->buf;82fw->size = builder->bytes_used;8384return fw;85}86EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_get_firmware, "FW_CS_DSP_KUNIT_TEST_UTILS");8788/**89* cs_dsp_mock_wmfw_add_raw_block() - Add a block to the wmfw file.90*91* @builder: Pointer to struct cs_dsp_mock_bin_builder.92* @block_type: Block type.93* @offset: Offset.94* @payload_data: Pointer to buffer containing the payload data,95* or NULL if no data.96* @payload_len_bytes: Length of payload data in bytes, or zero.97*/98void cs_dsp_mock_wmfw_add_raw_block(struct cs_dsp_mock_wmfw_builder *builder,99int block_type, unsigned int offset,100const void *payload_data, size_t payload_len_bytes)101{102struct wmfw_region *header = builder->write_p;103unsigned int bytes_needed = struct_size_t(struct wmfw_region, data, payload_len_bytes);104105KUNIT_ASSERT_TRUE(builder->test_priv->test,106(builder->write_p + bytes_needed) <107(builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));108109header->offset = cpu_to_le32(offset | (block_type << 24));110header->len = cpu_to_le32(payload_len_bytes);111if (payload_len_bytes > 0)112memcpy(header->data, payload_data, payload_len_bytes);113114builder->write_p += bytes_needed;115builder->bytes_used += bytes_needed;116}117EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_raw_block, "FW_CS_DSP_KUNIT_TEST_UTILS");118119/**120* cs_dsp_mock_wmfw_add_info() - Add an info block to the wmfw file.121*122* @builder: Pointer to struct cs_dsp_mock_bin_builder.123* @info: Pointer to info string to be copied into the file.124*125* The string will be padded to a length that is a multiple of 4 bytes.126*/127void cs_dsp_mock_wmfw_add_info(struct cs_dsp_mock_wmfw_builder *builder,128const char *info)129{130size_t info_len = strlen(info);131char *tmp = NULL;132133if (info_len % 4) {134/* Create a padded string with length a multiple of 4 */135size_t copy_len = info_len;136info_len = round_up(info_len, 4);137tmp = kunit_kzalloc(builder->test_priv->test, info_len, GFP_KERNEL);138KUNIT_ASSERT_NOT_ERR_OR_NULL(builder->test_priv->test, tmp);139memcpy(tmp, info, copy_len);140info = tmp;141}142143cs_dsp_mock_wmfw_add_raw_block(builder, WMFW_INFO_TEXT, 0, info, info_len);144kunit_kfree(builder->test_priv->test, tmp);145}146EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_info, "FW_CS_DSP_KUNIT_TEST_UTILS");147148/**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*/157void cs_dsp_mock_wmfw_add_data_block(struct cs_dsp_mock_wmfw_builder *builder,158int mem_region, unsigned int mem_offset_dsp_words,159const void *payload_data, size_t payload_len_bytes)160{161/* Blob payload length must be a multiple of 4 */162KUNIT_ASSERT_EQ(builder->test_priv->test, payload_len_bytes % 4, 0);163164cs_dsp_mock_wmfw_add_raw_block(builder, mem_region, mem_offset_dsp_words,165payload_data, payload_len_bytes);166}167EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_data_block, "FW_CS_DSP_KUNIT_TEST_UTILS");168169void cs_dsp_mock_wmfw_start_alg_info_block(struct cs_dsp_mock_wmfw_builder *builder,170unsigned int alg_id,171const char *name,172const char *description)173{174struct wmfw_region *rgn = builder->write_p;175struct wmfw_adsp_alg_data *v1;176struct wmfw_short_string *shortstring;177struct wmfw_long_string *longstring;178size_t bytes_needed, name_len, description_len;179int offset;180181KUNIT_ASSERT_LE(builder->test_priv->test, alg_id, 0xffffff);182183/* Bytes needed for region header */184bytes_needed = offsetof(struct wmfw_region, data);185186builder->alg_data_header = builder->write_p;187builder->num_coeffs = 0;188189switch (builder->format_version) {190case 0:191KUNIT_FAIL(builder->test_priv->test, "wmfwV0 does not have alg blocks\n");192return;193case 1:194bytes_needed += offsetof(struct wmfw_adsp_alg_data, data);195KUNIT_ASSERT_TRUE(builder->test_priv->test,196(builder->write_p + bytes_needed) <197(builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));198199memset(builder->write_p, 0, bytes_needed);200201/* Create region header */202rgn->offset = cpu_to_le32(WMFW_ALGORITHM_DATA << 24);203204/* Create algorithm entry */205v1 = (struct wmfw_adsp_alg_data *)&rgn->data[0];206v1->id = cpu_to_le32(alg_id);207if (name)208strscpy(v1->name, name, sizeof(v1->name));209210if (description)211strscpy(v1->descr, description, sizeof(v1->descr));212break;213default:214name_len = 0;215description_len = 0;216217if (name)218name_len = strlen(name);219220if (description)221description_len = strlen(description);222223bytes_needed += sizeof(__le32); /* alg id */224bytes_needed += round_up(name_len + sizeof(u8), sizeof(__le32));225bytes_needed += round_up(description_len + sizeof(__le16), sizeof(__le32));226bytes_needed += sizeof(__le32); /* coeff count */227228KUNIT_ASSERT_TRUE(builder->test_priv->test,229(builder->write_p + bytes_needed) <230(builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));231232memset(builder->write_p, 0, bytes_needed);233234/* Create region header */235rgn->offset = cpu_to_le32(WMFW_ALGORITHM_DATA << 24);236237/* Create algorithm entry */238*(__force __le32 *)&rgn->data[0] = cpu_to_le32(alg_id);239240shortstring = (struct wmfw_short_string *)&rgn->data[4];241shortstring->len = name_len;242243if (name_len)244memcpy(shortstring->data, name, name_len);245246/* Round up to next __le32 */247offset = round_up(4 + struct_size_t(struct wmfw_short_string, data, name_len),248sizeof(__le32));249250longstring = (struct wmfw_long_string *)&rgn->data[offset];251longstring->len = cpu_to_le16(description_len);252253if (description_len)254memcpy(longstring->data, description, description_len);255break;256}257258builder->write_p += bytes_needed;259builder->bytes_used += bytes_needed;260}261EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_start_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS");262263void cs_dsp_mock_wmfw_add_coeff_desc(struct cs_dsp_mock_wmfw_builder *builder,264const struct cs_dsp_mock_coeff_def *def)265{266struct wmfw_adsp_coeff_data *v1;267struct wmfw_short_string *shortstring;268struct wmfw_long_string *longstring;269size_t bytes_needed, shortname_len, fullname_len, description_len;270__le32 *ple32;271272KUNIT_ASSERT_NOT_NULL(builder->test_priv->test, builder->alg_data_header);273274switch (builder->format_version) {275case 0:276return;277case 1:278bytes_needed = offsetof(struct wmfw_adsp_coeff_data, data);279KUNIT_ASSERT_TRUE(builder->test_priv->test,280(builder->write_p + bytes_needed) <281(builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));282283v1 = (struct wmfw_adsp_coeff_data *)builder->write_p;284memset(v1, 0, sizeof(*v1));285v1->hdr.offset = cpu_to_le16(def->offset_dsp_words);286v1->hdr.type = cpu_to_le16(def->mem_type);287v1->hdr.size = cpu_to_le32(bytes_needed - sizeof(v1->hdr));288v1->ctl_type = cpu_to_le16(def->type);289v1->flags = cpu_to_le16(def->flags);290v1->len = cpu_to_le32(def->length_bytes);291292if (def->fullname)293strscpy(v1->name, def->fullname, sizeof(v1->name));294295if (def->description)296strscpy(v1->descr, def->description, sizeof(v1->descr));297break;298default:299fullname_len = 0;300description_len = 0;301shortname_len = strlen(def->shortname);302303if (def->fullname)304fullname_len = strlen(def->fullname);305306if (def->description)307description_len = strlen(def->description);308309bytes_needed = sizeof(__le32) * 2; /* type, offset and size */310bytes_needed += round_up(shortname_len + sizeof(u8), sizeof(__le32));311bytes_needed += round_up(fullname_len + sizeof(u8), sizeof(__le32));312bytes_needed += round_up(description_len + sizeof(__le16), sizeof(__le32));313bytes_needed += sizeof(__le32) * 2; /* flags, type and length */314KUNIT_ASSERT_TRUE(builder->test_priv->test,315(builder->write_p + bytes_needed) <316(builder->buf + CS_DSP_MOCK_WMFW_BUF_SIZE));317318ple32 = (__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));321322shortstring = (__force struct wmfw_short_string *)ple32;323shortstring->len = shortname_len;324memcpy(shortstring->data, def->shortname, shortname_len);325326/* Round up to next __le32 multiple */327ple32 += round_up(struct_size_t(struct wmfw_short_string, data, shortname_len),328sizeof(*ple32)) / sizeof(*ple32);329330shortstring = (__force struct wmfw_short_string *)ple32;331shortstring->len = fullname_len;332memcpy(shortstring->data, def->fullname, fullname_len);333334/* Round up to next __le32 multiple */335ple32 += round_up(struct_size_t(struct wmfw_short_string, data, fullname_len),336sizeof(*ple32)) / sizeof(*ple32);337338longstring = (__force struct wmfw_long_string *)ple32;339longstring->len = cpu_to_le16(description_len);340memcpy(longstring->data, def->description, description_len);341342/* Round up to next __le32 multiple */343ple32 += round_up(struct_size_t(struct wmfw_long_string, data, description_len),344sizeof(*ple32)) / sizeof(*ple32);345346*ple32++ = cpu_to_le32(def->type | (def->flags << 16));347*ple32 = cpu_to_le32(def->length_bytes);348break;349}350351builder->write_p += bytes_needed;352builder->bytes_used += bytes_needed;353builder->num_coeffs++;354}355EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_add_coeff_desc, "FW_CS_DSP_KUNIT_TEST_UTILS");356357void cs_dsp_mock_wmfw_end_alg_info_block(struct cs_dsp_mock_wmfw_builder *builder)358{359struct wmfw_region *rgn = builder->alg_data_header;360struct wmfw_adsp_alg_data *v1;361const struct wmfw_short_string *shortstring;362const struct wmfw_long_string *longstring;363size_t offset;364365KUNIT_ASSERT_NOT_NULL(builder->test_priv->test, rgn);366367/* Fill in data size */368rgn->len = cpu_to_le32((u8 *)builder->write_p - (u8 *)rgn->data);369370/* Fill in coefficient count */371switch (builder->format_version) {372case 0:373return;374case 1:375v1 = (struct wmfw_adsp_alg_data *)&rgn->data[0];376v1->ncoeff = cpu_to_le32(builder->num_coeffs);377break;378default:379offset = 4; /* skip alg id */380381/* Get name length and round up to __le32 multiple */382shortstring = (const struct wmfw_short_string *)&rgn->data[offset];383offset += round_up(struct_size_t(struct wmfw_short_string, data, shortstring->len),384sizeof(__le32));385386/* Get description length and round up to __le32 multiple */387longstring = (const struct wmfw_long_string *)&rgn->data[offset];388offset += round_up(struct_size_t(struct wmfw_long_string, data,389le16_to_cpu(longstring->len)),390sizeof(__le32));391392*(__force __le32 *)&rgn->data[offset] = cpu_to_le32(builder->num_coeffs);393break;394}395396builder->alg_data_header = NULL;397}398EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_end_alg_info_block, "FW_CS_DSP_KUNIT_TEST_UTILS");399400static void cs_dsp_init_adsp2_halo_wmfw(struct cs_dsp_mock_wmfw_builder *builder)401{402struct wmfw_adsp2_halo_header *hdr = builder->buf;403const struct cs_dsp *dsp = builder->test_priv->dsp;404405memcpy(hdr->header.magic, "WMFW", sizeof(hdr->header.magic));406hdr->header.len = cpu_to_le32(sizeof(*hdr));407hdr->header.ver = builder->format_version;408hdr->header.core = dsp->type;409hdr->header.rev = cpu_to_le16(dsp->rev);410411hdr->sizes.pm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_PM));412hdr->sizes.xm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_XM));413hdr->sizes.ym = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_YM));414415switch (dsp->type) {416case WMFW_ADSP2:417hdr->sizes.zm = cpu_to_le32(cs_dsp_mock_size_of_region(dsp, WMFW_ADSP2_ZM));418break;419default:420break;421}422423builder->write_p = &hdr[1];424builder->bytes_used += sizeof(*hdr);425}426427/**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*/435struct cs_dsp_mock_wmfw_builder *cs_dsp_mock_wmfw_init(struct cs_dsp_test *priv,436int format_version)437{438struct cs_dsp_mock_wmfw_builder *builder;439440KUNIT_ASSERT_LE(priv->test, format_version, 0xff);441442/* If format version isn't given use the default for the target core */443if (format_version < 0) {444switch (priv->dsp->type) {445case WMFW_ADSP2:446format_version = 2;447break;448default:449format_version = 3;450break;451}452}453454builder = kunit_kzalloc(priv->test, sizeof(*builder), GFP_KERNEL);455KUNIT_ASSERT_NOT_ERR_OR_NULL(priv->test, builder);456457builder->test_priv = priv;458builder->format_version = format_version;459460builder->buf = vmalloc(CS_DSP_MOCK_WMFW_BUF_SIZE);461KUNIT_ASSERT_NOT_NULL(priv->test, builder->buf);462kunit_add_action_or_reset(priv->test, vfree_action_wrapper, builder->buf);463464builder->buf_size_bytes = CS_DSP_MOCK_WMFW_BUF_SIZE;465466switch (priv->dsp->type) {467case WMFW_ADSP2:468case WMFW_HALO:469cs_dsp_init_adsp2_halo_wmfw(builder);470break;471default:472break;473}474475return builder;476}477EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_wmfw_init, "FW_CS_DSP_KUNIT_TEST_UTILS");478479480