summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2020-05-25 15:31:12 +0100
committerMark Brown <broonie@kernel.org>2020-05-25 15:31:12 +0100
commit3ca570da20357c4b317fb08897f4ac311cbc7dbe (patch)
treec0619c36aadfffde25c0be16a4c9f5ae5be3e63e
parent393dc21d0f25e8fcde8baca78b8a38afe61db2a7 (diff)
parent4c4a975178ef06324c80baef0e95209f431645a5 (diff)
downloadlinux-3ca570da20357c4b317fb08897f4ac311cbc7dbe.tar.bz2
Merge series "ASoC: SOF: extended manifest support for 5.8" from Kai Vehmanen <kai.vehmanen@linux.intel.com>:
Hello, extended firmware manifest is a method to retrieve capabilities directly from the firmware file instead of routing the information via the DSP and reading it back via IPC (latter mechanism still supported but will be deprecated). This feature was briefly merged to 5.8 with the series sent on 2020-Apr-15, but due to a regression hit with exporting uapi headers, the patches got dropped. Here's an update with the uapi header issue fixed, rebased to latest 'for-5.8' and a few minor fixes. This has been sitting in sof-dev for some weeks and no further issues have been found. We also added a check for the uapi-export case to SOF CI, so such errors would not slip through again in the future. Tooling support to create firmware files with an extended header is available in SOF firmware repository (see the rimage tool) and this part is already merged. Karol Trzcinski (5): ASoC: SOF: loader: Adjust validation condition for fw_offset ASoC: SOF: Introduce extended manifest ASoC: SOF: ext_manifest: parse firmware version ASoC: SOF: ext_manifest: parse windows ASoC: SOF: ext_manifest: parse compiler version include/sound/sof/ext_manifest.h | 95 +++++++++++++++++ sound/soc/sof/intel/hda-loader.c | 2 +- sound/soc/sof/loader.c | 176 ++++++++++++++++++++++++++++++- 3 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 include/sound/sof/ext_manifest.h -- 2.26.2
-rw-r--r--include/sound/sof/ext_manifest.h95
-rw-r--r--sound/soc/sof/intel/hda-loader.c2
-rw-r--r--sound/soc/sof/loader.c176
3 files changed, 269 insertions, 4 deletions
diff --git a/include/sound/sof/ext_manifest.h b/include/sound/sof/ext_manifest.h
new file mode 100644
index 000000000000..04359cda92dc
--- /dev/null
+++ b/include/sound/sof/ext_manifest.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ */
+
+/*
+ * Extended manifest is a place to store metadata about firmware, known during
+ * compilation time - for example firmware version or used compiler.
+ * Given information are read on host side before firmware startup.
+ * This part of output binary is not signed.
+ */
+
+#ifndef __SOF_FIRMWARE_EXT_MANIFEST_H__
+#define __SOF_FIRMWARE_EXT_MANIFEST_H__
+
+#include <linux/bits.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <sound/sof/info.h>
+
+/* In ASCII `XMan` */
+#define SOF_EXT_MAN_MAGIC_NUMBER 0x6e614d58
+
+/* Build u32 number in format MMmmmppp */
+#define SOF_EXT_MAN_BUILD_VERSION(MAJOR, MINOR, PATH) ((uint32_t)( \
+ ((MAJOR) << 24) | \
+ ((MINOR) << 12) | \
+ (PATH)))
+
+/* check extended manifest version consistency */
+#define SOF_EXT_MAN_VERSION_INCOMPATIBLE(host_ver, cli_ver) ( \
+ ((host_ver) & GENMASK(31, 24)) != \
+ ((cli_ver) & GENMASK(31, 24)))
+
+/* used extended manifest header version */
+#define SOF_EXT_MAN_VERSION SOF_EXT_MAN_BUILD_VERSION(1, 0, 0)
+
+/* extended manifest header, deleting any field breaks backward compatibility */
+struct sof_ext_man_header {
+ uint32_t magic; /*< identification number, */
+ /*< EXT_MAN_MAGIC_NUMBER */
+ uint32_t full_size; /*< [bytes] full size of ext_man, */
+ /*< (header + content + padding) */
+ uint32_t header_size; /*< [bytes] makes header extensionable, */
+ /*< after append new field to ext_man header */
+ /*< then backward compatible won't be lost */
+ uint32_t header_version; /*< value of EXT_MAN_VERSION */
+ /*< not related with following content */
+
+ /* just after this header should be list of ext_man_elem_* elements */
+} __packed;
+
+/* Now define extended manifest elements */
+
+/* Extended manifest elements types */
+enum sof_ext_man_elem_type {
+ SOF_EXT_MAN_ELEM_FW_VERSION = 0,
+ SOF_EXT_MAN_ELEM_WINDOW = SOF_IPC_EXT_WINDOW,
+ SOF_EXT_MAN_ELEM_CC_VERSION = SOF_IPC_EXT_CC_INFO,
+};
+
+/* extended manifest element header */
+struct sof_ext_man_elem_header {
+ uint32_t type; /*< SOF_EXT_MAN_ELEM_ */
+ uint32_t size; /*< in bytes, including header size */
+
+ /* just after this header should be type dependent content */
+} __packed;
+
+/* FW version */
+struct sof_ext_man_fw_version {
+ struct sof_ext_man_elem_header hdr;
+ /* use sof_ipc struct because of code re-use */
+ struct sof_ipc_fw_version version;
+ uint32_t flags;
+} __packed;
+
+/* extended data memory windows for IPC, trace and debug */
+struct sof_ext_man_window {
+ struct sof_ext_man_elem_header hdr;
+ /* use sof_ipc struct because of code re-use */
+ struct sof_ipc_window ipc_window;
+} __packed;
+
+/* Used C compiler description */
+struct sof_ext_man_cc_version {
+ struct sof_ext_man_elem_header hdr;
+ /* use sof_ipc struct because of code re-use */
+ struct sof_ipc_cc_version cc_version;
+} __packed;
+
+#endif /* __SOF_FIRMWARE_EXT_MANIFEST_H__ */
diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c
index d762b3e1ce4a..441d05cda604 100644
--- a/sound/soc/sof/intel/hda-loader.c
+++ b/sound/soc/sof/intel/hda-loader.c
@@ -293,7 +293,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev)
chip_info = desc->chip_info;
- if (plat_data->fw->size < plat_data->fw_offset) {
+ if (plat_data->fw->size <= plat_data->fw_offset) {
dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n");
return -EINVAL;
}
diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c
index 4a5b57ecf359..b94fa5f5d480 100644
--- a/sound/soc/sof/loader.c
+++ b/sound/soc/sof/loader.c
@@ -12,6 +12,7 @@
#include <linux/firmware.h>
#include <sound/sof.h>
+#include <sound/sof/ext_manifest.h>
#include "ops.h"
static int get_ext_windows(struct snd_sof_dev *sdev,
@@ -19,13 +20,21 @@ static int get_ext_windows(struct snd_sof_dev *sdev,
{
const struct sof_ipc_window *w =
container_of(ext_hdr, struct sof_ipc_window, ext_hdr);
+ size_t w_size = struct_size(w, window, w->num_windows);
if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)
return -EINVAL;
+ if (sdev->info_window) {
+ if (memcmp(sdev->info_window, w, w_size)) {
+ dev_err(sdev->dev, "error: mismatch between window descriptor from extended manifest and mailbox");
+ return -EINVAL;
+ }
+ return 0;
+ }
+
/* keep a local copy of the data */
- sdev->info_window = kmemdup(w, struct_size(w, window, w->num_windows),
- GFP_KERNEL);
+ sdev->info_window = kmemdup(w, w_size, GFP_KERNEL);
if (!sdev->info_window)
return -ENOMEM;
@@ -40,6 +49,14 @@ static int get_cc_info(struct snd_sof_dev *sdev,
const struct sof_ipc_cc_version *cc =
container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr);
+ if (sdev->cc_version) {
+ if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) {
+ dev_err(sdev->dev, "error: receive diverged cc_version descriptions");
+ return -EINVAL;
+ }
+ return 0;
+ }
+
dev_dbg(sdev->dev, "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n",
cc->name, cc->major, cc->minor, cc->micro, cc->desc,
cc->optim);
@@ -126,6 +143,142 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset)
}
EXPORT_SYMBOL(snd_sof_fw_parse_ext_data);
+static int ext_man_get_fw_version(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct sof_ext_man_fw_version *v =
+ container_of(hdr, struct sof_ext_man_fw_version, hdr);
+
+ memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version));
+ sdev->fw_ready.flags = v->flags;
+
+ /* log ABI versions and check FW compatibility */
+ return snd_sof_ipc_valid(sdev);
+}
+
+static int ext_man_get_windows(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct sof_ext_man_window *w;
+
+ w = container_of(hdr, struct sof_ext_man_window, hdr);
+
+ return get_ext_windows(sdev, &w->ipc_window.ext_hdr);
+}
+
+static int ext_man_get_cc_info(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct sof_ext_man_cc_version *cc;
+
+ cc = container_of(hdr, struct sof_ext_man_cc_version, hdr);
+
+ return get_cc_info(sdev, &cc->cc_version.ext_hdr);
+}
+
+static ssize_t snd_sof_ext_man_size(const struct firmware *fw)
+{
+ const struct sof_ext_man_header *head;
+
+ head = (struct sof_ext_man_header *)fw->data;
+
+ /*
+ * assert fw size is big enough to contain extended manifest header,
+ * it prevents from reading unallocated memory from `head` in following
+ * step.
+ */
+ if (fw->size < sizeof(*head))
+ return -EINVAL;
+
+ /*
+ * When fw points to extended manifest,
+ * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER.
+ */
+ if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER)
+ return head->full_size;
+
+ /* otherwise given fw don't have an extended manifest */
+ return 0;
+}
+
+/* parse extended FW manifest data structures */
+static int snd_sof_fw_ext_man_parse(struct snd_sof_dev *sdev,
+ const struct firmware *fw)
+{
+ const struct sof_ext_man_elem_header *elem_hdr;
+ const struct sof_ext_man_header *head;
+ ssize_t ext_man_size;
+ ssize_t remaining;
+ uintptr_t iptr;
+ int ret = 0;
+
+ head = (struct sof_ext_man_header *)fw->data;
+ remaining = head->full_size - head->header_size;
+ ext_man_size = snd_sof_ext_man_size(fw);
+
+ /* Assert firmware starts with extended manifest */
+ if (ext_man_size <= 0)
+ return ext_man_size;
+
+ /* incompatible version */
+ if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION,
+ head->header_version)) {
+ dev_err(sdev->dev, "error: extended manifest version 0x%X differ from used 0x%X\n",
+ head->header_version, SOF_EXT_MAN_VERSION);
+ return -EINVAL;
+ }
+
+ /* get first extended manifest element header */
+ iptr = (uintptr_t)fw->data + head->header_size;
+
+ while (remaining > sizeof(*elem_hdr)) {
+ elem_hdr = (struct sof_ext_man_elem_header *)iptr;
+
+ dev_dbg(sdev->dev, "found sof_ext_man header type %d size 0x%X\n",
+ elem_hdr->type, elem_hdr->size);
+
+ if (elem_hdr->size < sizeof(*elem_hdr) ||
+ elem_hdr->size > remaining) {
+ dev_err(sdev->dev, "error: invalid sof_ext_man header size, type %d size 0x%X\n",
+ elem_hdr->type, elem_hdr->size);
+ return -EINVAL;
+ }
+
+ /* process structure data */
+ switch (elem_hdr->type) {
+ case SOF_EXT_MAN_ELEM_FW_VERSION:
+ ret = ext_man_get_fw_version(sdev, elem_hdr);
+ break;
+ case SOF_EXT_MAN_ELEM_WINDOW:
+ ret = ext_man_get_windows(sdev, elem_hdr);
+ break;
+ case SOF_EXT_MAN_ELEM_CC_VERSION:
+ ret = ext_man_get_cc_info(sdev, elem_hdr);
+ break;
+ default:
+ dev_warn(sdev->dev, "warning: unknown sof_ext_man header type %d size 0x%X\n",
+ elem_hdr->type, elem_hdr->size);
+ break;
+ }
+
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: failed to parse sof_ext_man header type %d size 0x%X\n",
+ elem_hdr->type, elem_hdr->size);
+ return ret;
+ }
+
+ remaining -= elem_hdr->size;
+ iptr += elem_hdr->size;
+ }
+
+ if (remaining) {
+ dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n");
+ return -EINVAL;
+ }
+
+ return ext_man_size;
+}
+
/*
* IPC Firmware ready.
*/
@@ -385,7 +538,7 @@ static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw,
struct snd_sof_fw_header *header;
size_t fw_size = fw->size - fw_offset;
- if (fw->size < fw_offset) {
+ if (fw->size <= fw_offset) {
dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n");
return -EINVAL;
}
@@ -473,6 +626,7 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *plat_data = sdev->pdata;
const char *fw_filename;
+ ssize_t ext_man_size;
int ret;
/* Don't request firmware again if firmware is already requested */
@@ -490,11 +644,27 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
if (ret < 0) {
dev_err(sdev->dev, "error: request firmware %s failed err: %d\n",
fw_filename, ret);
+ goto err;
} else {
dev_dbg(sdev->dev, "request_firmware %s successful\n",
fw_filename);
}
+ /* check for extended manifest */
+ ext_man_size = snd_sof_fw_ext_man_parse(sdev, plat_data->fw);
+ if (ext_man_size > 0) {
+ /* when no error occurred, drop extended manifest */
+ plat_data->fw_offset = ext_man_size;
+ } else if (!ext_man_size) {
+ /* No extended manifest, so nothing to skip during FW load */
+ dev_dbg(sdev->dev, "firmware doesn't contain extended manifest\n");
+ } else {
+ ret = ext_man_size;
+ dev_err(sdev->dev, "error: firmware %s contains unsupported or invalid extended manifest: %d\n",
+ fw_filename, ret);
+ }
+
+err:
kfree(fw_filename);
return ret;