summaryrefslogtreecommitdiff
path: root/gcip-kernel-driver/drivers/gcip/gcip-image-config.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcip-kernel-driver/drivers/gcip/gcip-image-config.c')
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-image-config.c220
1 files changed, 220 insertions, 0 deletions
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-image-config.c b/gcip-kernel-driver/drivers/gcip/gcip-image-config.c
new file mode 100644
index 0000000..312bbdc
--- /dev/null
+++ b/gcip-kernel-driver/drivers/gcip/gcip-image-config.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Framework for parsing the firmware image configuration.
+ *
+ * Copyright (C) 2022 Google LLC
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <gcip/gcip-image-config.h>
+
+#define ADDR_SHIFT 12
+#define SIZE_MODE_BIT BIT(ADDR_SHIFT - 1)
+#define SECURE_SIZE_MASK (SIZE_MODE_BIT - 1u)
+#define NS_SIZE_MASK (BIT(ADDR_SHIFT) - 1u)
+#define ADDR_MASK ~(BIT(ADDR_SHIFT) - 1u)
+
+/* used by ns_iommu_mappings */
+#define CONFIG_TO_MBSIZE(a) (((a) & NS_SIZE_MASK) << 20)
+
+/* used by iommu_mappings */
+static inline __u32 config_to_size(__u32 cfg)
+{
+ __u32 page_size;
+
+ if (cfg & SIZE_MODE_BIT)
+ page_size = cfg & SECURE_SIZE_MASK;
+ else
+ page_size = BIT(cfg & SECURE_SIZE_MASK);
+
+ return page_size << PAGE_SHIFT;
+}
+
+static int setup_iommu_mappings(struct gcip_image_config_parser *parser,
+ struct gcip_image_config *config)
+{
+ int i, ret;
+ dma_addr_t daddr;
+ size_t size;
+ phys_addr_t paddr;
+
+ for (i = 0; i < config->num_iommu_mappings; i++) {
+ daddr = config->iommu_mappings[i].virt_address;
+ if (unlikely(!daddr)) {
+ dev_warn(parser->dev, "Invalid config, device address is zero");
+ ret = -EIO;
+ goto err;
+ }
+ size = config_to_size(config->iommu_mappings[i].image_config_value);
+ paddr = config->iommu_mappings[i].image_config_value & ADDR_MASK;
+
+ dev_dbg(parser->dev, "Image config adding IOMMU mapping: %pad -> %pap", &daddr,
+ &paddr);
+
+ if (unlikely(daddr + size <= daddr || paddr + size <= paddr)) {
+ ret = -EOVERFLOW;
+ goto err;
+ }
+ ret = parser->ops->map(parser->data, daddr, paddr, size,
+ GCIP_IMAGE_CONFIG_FLAGS_SECURE);
+ if (ret) {
+ dev_err(parser->dev,
+ "Unable to Map: %d dma_addr: %pad phys_addr: %pap size: %#lx\n",
+ ret, &daddr, &paddr, size);
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ while (i--) {
+ daddr = config->iommu_mappings[i].virt_address;
+ size = config_to_size(config->iommu_mappings[i].image_config_value);
+ parser->ops->unmap(parser->data, daddr, size, GCIP_IMAGE_CONFIG_FLAGS_SECURE);
+ }
+ return ret;
+}
+
+static void clear_iommu_mappings(struct gcip_image_config_parser *parser,
+ struct gcip_image_config *config)
+{
+ dma_addr_t daddr;
+ size_t size;
+ int i;
+
+ for (i = config->num_iommu_mappings - 1; i >= 0; i--) {
+ daddr = config->iommu_mappings[i].virt_address;
+ size = config_to_size(config->iommu_mappings[i].image_config_value);
+ dev_dbg(parser->dev, "Image config removing IOMMU mapping: %pad size=%#lx", &daddr,
+ size);
+ parser->ops->unmap(parser->data, daddr, size, GCIP_IMAGE_CONFIG_FLAGS_SECURE);
+ }
+}
+
+static int setup_ns_iommu_mappings(struct gcip_image_config_parser *parser,
+ struct gcip_image_config *config)
+{
+ dma_addr_t daddr;
+ size_t size;
+ int ret, i;
+ phys_addr_t paddr = 0;
+
+ for (i = 0; i < config->num_ns_iommu_mappings; i++) {
+ daddr = config->ns_iommu_mappings[i] & ADDR_MASK;
+ if (unlikely(!daddr)) {
+ dev_warn(parser->dev, "Invalid config, device address is zero");
+ ret = -EIO;
+ goto err;
+ }
+ size = CONFIG_TO_MBSIZE(config->ns_iommu_mappings[i]);
+ dev_dbg(parser->dev, "Image config adding NS IOMMU mapping: %pad -> %pap", &daddr,
+ &paddr);
+ if (unlikely(daddr + size <= daddr || paddr + size <= paddr)) {
+ ret = -EOVERFLOW;
+ goto err;
+ }
+ ret = parser->ops->map(parser->data, daddr, paddr, size, 0);
+ if (ret)
+ goto err;
+ paddr += size;
+ }
+
+ return 0;
+
+err:
+ while (i--) {
+ size = CONFIG_TO_MBSIZE(config->ns_iommu_mappings[i]);
+ daddr = config->ns_iommu_mappings[i] & ADDR_MASK;
+ parser->ops->unmap(parser->data, daddr, size, 0);
+ }
+ return ret;
+}
+
+static void clear_ns_iommu_mappings(struct gcip_image_config_parser *parser,
+ struct gcip_image_config *config)
+{
+ dma_addr_t daddr;
+ size_t size;
+ int i;
+
+ for (i = config->num_ns_iommu_mappings - 1; i >= 0; i--) {
+ size = CONFIG_TO_MBSIZE(config->ns_iommu_mappings[i]);
+ daddr = config->ns_iommu_mappings[i] & ADDR_MASK;
+ dev_dbg(parser->dev, "Image config removing NS IOMMU mapping: %pad size=%#lx",
+ &daddr, size);
+ parser->ops->unmap(parser->data, daddr, size, 0);
+ }
+}
+
+static int map_image_config(struct gcip_image_config_parser *parser,
+ struct gcip_image_config *config)
+{
+ int ret = setup_ns_iommu_mappings(parser, config);
+
+ if (ret)
+ return ret;
+ if (gcip_image_config_is_ns(config)) {
+ ret = setup_iommu_mappings(parser, config);
+ if (ret)
+ clear_ns_iommu_mappings(parser, config);
+ }
+ return ret;
+}
+
+static void unmap_image_config(struct gcip_image_config_parser *parser,
+ struct gcip_image_config *config)
+{
+ if (gcip_image_config_is_ns(config))
+ clear_iommu_mappings(parser, config);
+ clear_ns_iommu_mappings(parser, config);
+}
+
+int gcip_image_config_parser_init(struct gcip_image_config_parser *parser,
+ const struct gcip_image_config_ops *ops, struct device *dev,
+ void *data)
+{
+ if (!ops->map || !ops->unmap) {
+ dev_err(dev, "Missing mandatory operations for image config parser");
+ return -EINVAL;
+ }
+ parser->dev = dev;
+ parser->data = data;
+ parser->ops = ops;
+ memset(&parser->last_config, 0, sizeof(parser->last_config));
+ return 0;
+}
+
+int gcip_image_config_parse(struct gcip_image_config_parser *parser,
+ struct gcip_image_config *config)
+{
+ int ret;
+
+ if (!memcmp(config, &parser->last_config, sizeof(*config)))
+ return 0;
+ unmap_image_config(parser, &parser->last_config);
+ ret = map_image_config(parser, config);
+ if (ret) {
+ dev_err(parser->dev, "Map image config failed: %d", ret);
+ /*
+ * Weird case as the mappings in the last config were just removed - might happen
+ * if the IOMMU driver state is corrupted. We can't help to rescue it so let's
+ * simply log a message.
+ */
+ if (unlikely(map_image_config(parser, &parser->last_config)))
+ dev_err(parser->dev, "Failed to roll back the last image config");
+ return ret;
+ }
+ memcpy(&parser->last_config, config, sizeof(parser->last_config));
+ return 0;
+}
+
+void gcip_image_config_clear(struct gcip_image_config_parser *parser)
+{
+ unmap_image_config(parser, &parser->last_config);
+ memset(&parser->last_config, 0, sizeof(parser->last_config));
+}