This series adds basic support for the QSEECOM interface used to
communicate with secure applications running in the TrustZone on certain
Qualcomm devices. In addition to that, it also provides a driver for
"uefisecapp", the secure application managing access to UEFI variables
on such platforms.
For a more detailed description, see the blurb of v1.
Previous versions:
- V2: https://lore.kernel.org/lkml/[email protected]/
- V1: https://lore.kernel.org/lkml/[email protected]/
This series depends on the following series:
- "efi: efivars: drop kobject from efivars_register()"
(https://lore.kernel.org/lkml/[email protected]/)
- "efi: verify that variable services are supported"
(https://lore.kernel.org/lkml/[email protected]/)
with subsequent fix
"efivarfs: fix NULL-deref on mount when no efivars"
(https://lore.kernel.org/lkml/[email protected]/)
which have all been included in the "next" branch of
https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git
Changes in v3:
- Fix doc comment in qcom_scm.c
- Rebase on top of latest changes to qcom_scm.
Changes in v2:
- Bind the qseecom interface to a device.
- Establish a device link between the new qseecom device and the SCM
device to ensure proper PM and remove ordering.
- Remove the compatible for uefisecapp. Instead, introduce a compatible
for the qseecom device. This directly reflects ACPI tables and the
QCOM0476 device described therein, which is responsible for the
secure app / qseecom interface (i.e., the same purpose).
Client devices representing apps handled by the kernel (such as
uefisecapp) are now directly instantiated by the qseecom driver,
based on the respective platform-specific compatible.
- Rename the base name (qctree -> qseecom) to allow differentiation
between old (qseecom) and new (smcinvoke) interfaces to the trusted
execution environment. This directly reflects downstream naming by
Qualcomm.
Maximilian Luz (4):
firmware: qcom_scm: Export SCM call functions
firmware: Add support for Qualcomm Secure Execution Environment SCM
interface
dt-bindings: firmware: Add Qualcomm QSEECOM interface
firmware: Add support for Qualcomm UEFI Secure Application
.../bindings/firmware/qcom,qseecom.yaml | 49 ++
MAINTAINERS | 14 +
drivers/firmware/Kconfig | 31 +
drivers/firmware/Makefile | 2 +
drivers/firmware/qcom_qseecom.c | 323 ++++++++
drivers/firmware/qcom_qseecom_uefisecapp.c | 746 ++++++++++++++++++
drivers/firmware/qcom_scm.c | 120 ++-
drivers/firmware/qcom_scm.h | 47 --
include/linux/firmware/qcom/qcom_qseecom.h | 190 +++++
include/linux/firmware/qcom/qcom_scm.h | 49 ++
10 files changed, 1484 insertions(+), 87 deletions(-)
create mode 100644 Documentation/devicetree/bindings/firmware/qcom,qseecom.yaml
create mode 100644 drivers/firmware/qcom_qseecom.c
create mode 100644 drivers/firmware/qcom_qseecom_uefisecapp.c
create mode 100644 include/linux/firmware/qcom/qcom_qseecom.h
--
2.39.2
Make qcom_scm_call, qcom_scm_call_atomic and associated types accessible
to other modules.
Signed-off-by: Maximilian Luz <[email protected]>
---
Changes in v3:
- Rebase ontop of latest qcom_scm changes.
- Fix doc-comment.
Changes in v2:
- No functional changes.
---
drivers/firmware/qcom_scm.c | 120 ++++++++++++++++---------
drivers/firmware/qcom_scm.h | 47 ----------
include/linux/firmware/qcom/qcom_scm.h | 49 ++++++++++
3 files changed, 129 insertions(+), 87 deletions(-)
diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c
index 468d4d5ab550..9b3e4449a563 100644
--- a/drivers/firmware/qcom_scm.c
+++ b/drivers/firmware/qcom_scm.c
@@ -212,16 +212,17 @@ static enum qcom_scm_convention __get_convention(void)
}
/**
- * qcom_scm_call() - Invoke a syscall in the secure world
- * @dev: device
+ * __qcom_scm_call() - Invoke a syscall in the secure world
+ * @dev: Device. Depending on the command and number of arguments, this
+ * is optional.
* @desc: Descriptor structure containing arguments and return values
* @res: Structure containing results from SMC/HVC call
*
* Sends a command to the SCM and waits for the command to finish processing.
* This should *only* be called in pre-emptible context.
*/
-static int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
- struct qcom_scm_res *res)
+static int __qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
+ struct qcom_scm_res *res)
{
might_sleep();
switch (__get_convention()) {
@@ -237,17 +238,38 @@ static int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
}
/**
- * qcom_scm_call_atomic() - atomic variation of qcom_scm_call()
- * @dev: device
+ * qcom_scm_call() - Invoke a syscall in the secure world
+ * @desc: Descriptor structure containing arguments and return values
+ * @res: Structure containing results from SMC/HVC call
+ *
+ * Sends a command to the SCM and waits for the command to finish processing.
+ * This should *only* be called in pre-emptible context.
+ *
+ * Returns zero on success, -ENODEV if the SCM device has not been set up yet,
+ * or other non-zero status codes on failure.
+ */
+int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res)
+{
+ if (!__scm)
+ return -ENODEV;
+
+ return __qcom_scm_call(__scm->dev, desc, res);
+}
+EXPORT_SYMBOL_GPL(qcom_scm_call);
+
+/**
+ * __qcom_scm_call_atomic() - atomic variation of __qcom_scm_call()
+ * @dev: Device. Depending on the command and number of arguments, this
+ * is optional.
* @desc: Descriptor structure containing arguments and return values
* @res: Structure containing results from SMC/HVC call
*
* Sends a command to the SCM and waits for the command to finish processing.
* This can be called in atomic context.
*/
-static int qcom_scm_call_atomic(struct device *dev,
- const struct qcom_scm_desc *desc,
- struct qcom_scm_res *res)
+static int __qcom_scm_call_atomic(struct device *dev,
+ const struct qcom_scm_desc *desc,
+ struct qcom_scm_res *res)
{
switch (__get_convention()) {
case SMC_CONVENTION_ARM_32:
@@ -261,6 +283,26 @@ static int qcom_scm_call_atomic(struct device *dev,
}
}
+/**
+ * qcom_scm_call_atomic() - atomic variation of qcom_scm_call()
+ * @desc: Descriptor structure containing arguments and return values
+ * @res: Structure containing results from SMC/HVC call
+ *
+ * Sends a command to the SCM and waits for the command to finish processing.
+ * This can be called in atomic context.
+ *
+ * Returns zero on success, -ENODEV if the SCM device has not been set up yet,
+ * or other non-zero status codes on failure.
+ */
+int qcom_scm_call_atomic(const struct qcom_scm_desc *desc, struct qcom_scm_res *res)
+{
+ if (!__scm)
+ return -ENODEV;
+
+ return __qcom_scm_call_atomic(__scm->dev, desc, res);
+}
+EXPORT_SYMBOL_GPL(qcom_scm_call_atomic);
+
static bool __qcom_scm_is_call_available(struct device *dev, u32 svc_id,
u32 cmd_id)
{
@@ -287,7 +329,7 @@ static bool __qcom_scm_is_call_available(struct device *dev, u32 svc_id,
return false;
}
- ret = qcom_scm_call(dev, &desc, &res);
+ ret = __qcom_scm_call(dev, &desc, &res);
return ret ? false : !!res.result[0];
}
@@ -312,7 +354,7 @@ static int qcom_scm_set_boot_addr(void *entry, const u8 *cpu_bits)
desc.args[0] = flags;
desc.args[1] = virt_to_phys(entry);
- return qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
+ return __qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
}
static int qcom_scm_set_boot_addr_mc(void *entry, unsigned int flags)
@@ -334,7 +376,7 @@ static int qcom_scm_set_boot_addr_mc(void *entry, unsigned int flags)
if (!__scm || __get_convention() == SMC_CONVENTION_LEGACY)
return -EOPNOTSUPP;
- return qcom_scm_call(__scm->dev, &desc, NULL);
+ return __qcom_scm_call(__scm->dev, &desc, NULL);
}
/**
@@ -384,7 +426,7 @@ void qcom_scm_cpu_power_down(u32 flags)
.owner = ARM_SMCCC_OWNER_SIP,
};
- qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
+ __qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_cpu_power_down);
@@ -401,7 +443,7 @@ int qcom_scm_set_remote_state(u32 state, u32 id)
struct qcom_scm_res res;
int ret;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
return ret ? : res.result[0];
}
@@ -419,7 +461,7 @@ static int __qcom_scm_set_dload_mode(struct device *dev, bool enable)
desc.args[1] = enable ? QCOM_SCM_BOOT_SET_DLOAD_MODE : 0;
- return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
+ return qcom_scm_call_atomic(&desc, NULL);
}
static void qcom_scm_set_download_mode(bool enable)
@@ -499,7 +541,7 @@ int qcom_scm_pas_init_image(u32 peripheral, const void *metadata, size_t size,
desc.args[1] = mdata_phys;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = __qcom_scm_call(__scm->dev, &desc, &res);
qcom_scm_bw_disable();
qcom_scm_clk_disable();
@@ -565,7 +607,7 @@ int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr, phys_addr_t size)
if (ret)
return ret;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
qcom_scm_bw_disable();
qcom_scm_clk_disable();
@@ -600,7 +642,7 @@ int qcom_scm_pas_auth_and_reset(u32 peripheral)
if (ret)
return ret;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
qcom_scm_bw_disable();
qcom_scm_clk_disable();
@@ -634,7 +676,7 @@ int qcom_scm_pas_shutdown(u32 peripheral)
if (ret)
return ret;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
qcom_scm_bw_disable();
qcom_scm_clk_disable();
@@ -666,7 +708,7 @@ bool qcom_scm_pas_supported(u32 peripheral)
QCOM_SCM_PIL_PAS_IS_SUPPORTED))
return false;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = __qcom_scm_call(__scm->dev, &desc, &res);
return ret ? false : !!res.result[0];
}
@@ -685,7 +727,7 @@ static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
struct qcom_scm_res res;
int ret;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
return ret ? : res.result[0];
}
@@ -725,8 +767,7 @@ int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val)
struct qcom_scm_res res;
int ret;
-
- ret = qcom_scm_call_atomic(__scm->dev, &desc, &res);
+ ret = qcom_scm_call_atomic(&desc, &res);
if (ret >= 0)
*val = res.result[0];
@@ -745,7 +786,7 @@ int qcom_scm_io_writel(phys_addr_t addr, unsigned int val)
.owner = ARM_SMCCC_OWNER_SIP,
};
- return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
+ return qcom_scm_call_atomic(&desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_io_writel);
@@ -775,7 +816,7 @@ int qcom_scm_restore_sec_cfg(u32 device_id, u32 spare)
struct qcom_scm_res res;
int ret;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
return ret ? : res.result[0];
}
@@ -793,7 +834,7 @@ int qcom_scm_iommu_secure_ptbl_size(u32 spare, size_t *size)
struct qcom_scm_res res;
int ret;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
if (size)
*size = res.result[0];
@@ -816,7 +857,7 @@ int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare)
};
int ret;
- ret = qcom_scm_call(__scm->dev, &desc, NULL);
+ ret = qcom_scm_call(&desc, NULL);
/* the pg table has been initialized already, ignore the error */
if (ret == -EPERM)
@@ -837,7 +878,7 @@ int qcom_scm_iommu_set_cp_pool_size(u32 spare, u32 size)
.owner = ARM_SMCCC_OWNER_SIP,
};
- return qcom_scm_call(__scm->dev, &desc, NULL);
+ return qcom_scm_call(&desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_iommu_set_cp_pool_size);
@@ -859,7 +900,7 @@ int qcom_scm_mem_protect_video_var(u32 cp_start, u32 cp_size,
};
struct qcom_scm_res res;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
return ret ? : res.result[0];
}
@@ -887,7 +928,7 @@ static int __qcom_scm_assign_mem(struct device *dev, phys_addr_t mem_region,
};
struct qcom_scm_res res;
- ret = qcom_scm_call(dev, &desc, &res);
+ ret = __qcom_scm_call(dev, &desc, &res);
return ret ? : res.result[0];
}
@@ -1004,7 +1045,7 @@ int qcom_scm_ocmem_lock(enum qcom_scm_ocmem_client id, u32 offset, u32 size,
.arginfo = QCOM_SCM_ARGS(4),
};
- return qcom_scm_call(__scm->dev, &desc, NULL);
+ return qcom_scm_call(&desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_ocmem_lock);
@@ -1027,7 +1068,7 @@ int qcom_scm_ocmem_unlock(enum qcom_scm_ocmem_client id, u32 offset, u32 size)
.arginfo = QCOM_SCM_ARGS(3),
};
- return qcom_scm_call(__scm->dev, &desc, NULL);
+ return qcom_scm_call(&desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_ocmem_unlock);
@@ -1068,7 +1109,7 @@ int qcom_scm_ice_invalidate_key(u32 index)
.owner = ARM_SMCCC_OWNER_SIP,
};
- return qcom_scm_call(__scm->dev, &desc, NULL);
+ return qcom_scm_call(&desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_ice_invalidate_key);
@@ -1129,7 +1170,7 @@ int qcom_scm_ice_set_key(u32 index, const u8 *key, u32 key_size,
memcpy(keybuf, key, key_size);
desc.args[1] = key_phys;
- ret = qcom_scm_call(__scm->dev, &desc, NULL);
+ ret = qcom_scm_call(&desc, NULL);
memzero_explicit(keybuf, key_size);
@@ -1198,7 +1239,7 @@ int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp)
if (ret)
return ret;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(&desc, &res);
*resp = res.result[0];
qcom_scm_clk_disable();
@@ -1219,7 +1260,7 @@ int qcom_scm_iommu_set_pt_format(u32 sec_id, u32 ctx_num, u32 pt_fmt)
.owner = ARM_SMCCC_OWNER_SIP,
};
- return qcom_scm_call(__scm->dev, &desc, NULL);
+ return qcom_scm_call(&desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_iommu_set_pt_format);
@@ -1234,8 +1275,7 @@ int qcom_scm_qsmmu500_wait_safe_toggle(bool en)
.owner = ARM_SMCCC_OWNER_SIP,
};
-
- return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
+ return qcom_scm_call_atomic(&desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_qsmmu500_wait_safe_toggle);
@@ -1255,7 +1295,7 @@ int qcom_scm_lmh_profile_change(u32 profile_id)
.owner = ARM_SMCCC_OWNER_SIP,
};
- return qcom_scm_call(__scm->dev, &desc, NULL);
+ return qcom_scm_call(&desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_lmh_profile_change);
@@ -1290,7 +1330,7 @@ int qcom_scm_lmh_dcvsh(u32 payload_fn, u32 payload_reg, u32 payload_val,
desc.args[0] = payload_phys;
- ret = qcom_scm_call(__scm->dev, &desc, NULL);
+ ret = __qcom_scm_call(__scm->dev, &desc, NULL);
dma_free_coherent(__scm->dev, payload_size, payload_buf, payload_phys);
return ret;
diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h
index e6e512bd57d1..87eb726be7d0 100644
--- a/drivers/firmware/qcom_scm.h
+++ b/drivers/firmware/qcom_scm.h
@@ -13,53 +13,6 @@ enum qcom_scm_convention {
extern enum qcom_scm_convention qcom_scm_convention;
-#define MAX_QCOM_SCM_ARGS 10
-#define MAX_QCOM_SCM_RETS 3
-
-enum qcom_scm_arg_types {
- QCOM_SCM_VAL,
- QCOM_SCM_RO,
- QCOM_SCM_RW,
- QCOM_SCM_BUFVAL,
-};
-
-#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\
- (((a) & 0x3) << 4) | \
- (((b) & 0x3) << 6) | \
- (((c) & 0x3) << 8) | \
- (((d) & 0x3) << 10) | \
- (((e) & 0x3) << 12) | \
- (((f) & 0x3) << 14) | \
- (((g) & 0x3) << 16) | \
- (((h) & 0x3) << 18) | \
- (((i) & 0x3) << 20) | \
- (((j) & 0x3) << 22) | \
- ((num) & 0xf))
-
-#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
-
-
-/**
- * struct qcom_scm_desc
- * @arginfo: Metadata describing the arguments in args[]
- * @args: The array of arguments for the secure syscall
- */
-struct qcom_scm_desc {
- u32 svc;
- u32 cmd;
- u32 arginfo;
- u64 args[MAX_QCOM_SCM_ARGS];
- u32 owner;
-};
-
-/**
- * struct qcom_scm_res
- * @result: The values returned by the secure syscall
- */
-struct qcom_scm_res {
- u64 result[MAX_QCOM_SCM_RETS];
-};
-
int qcom_scm_wait_for_wq_completion(u32 wq_ctx);
int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending);
diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h
index 1e449a5d7f5c..162746467c22 100644
--- a/include/linux/firmware/qcom/qcom_scm.h
+++ b/include/linux/firmware/qcom/qcom_scm.h
@@ -11,6 +11,55 @@
#include <dt-bindings/firmware/qcom,scm.h>
+#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\
+ (((a) & 0x3) << 4) | \
+ (((b) & 0x3) << 6) | \
+ (((c) & 0x3) << 8) | \
+ (((d) & 0x3) << 10) | \
+ (((e) & 0x3) << 12) | \
+ (((f) & 0x3) << 14) | \
+ (((g) & 0x3) << 16) | \
+ (((h) & 0x3) << 18) | \
+ (((i) & 0x3) << 20) | \
+ (((j) & 0x3) << 22) | \
+ ((num) & 0xf))
+
+#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+
+#define MAX_QCOM_SCM_ARGS 10
+#define MAX_QCOM_SCM_RETS 3
+
+enum qcom_scm_arg_types {
+ QCOM_SCM_VAL,
+ QCOM_SCM_RO,
+ QCOM_SCM_RW,
+ QCOM_SCM_BUFVAL,
+};
+
+/**
+ * struct qcom_scm_desc - SCM call descriptor.
+ * @arginfo: Metadata describing the arguments in args[]
+ * @args: The array of arguments for the secure syscall
+ */
+struct qcom_scm_desc {
+ u32 svc;
+ u32 cmd;
+ u32 arginfo;
+ u64 args[MAX_QCOM_SCM_ARGS];
+ u32 owner;
+};
+
+/**
+ * struct qcom_scm_res - SCM call response.
+ * @result: The values returned by the secure syscall
+ */
+struct qcom_scm_res {
+ u64 result[MAX_QCOM_SCM_RETS];
+};
+
+int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res);
+int qcom_scm_call_atomic(const struct qcom_scm_desc *desc, struct qcom_scm_res *res);
+
#define QCOM_SCM_VERSION(major, minor) (((major) << 16) | ((minor) & 0xFF))
#define QCOM_SCM_CPU_PWR_DOWN_L2_ON 0x0
#define QCOM_SCM_CPU_PWR_DOWN_L2_OFF 0x1
--
2.39.2
Add bindings for the Qualcomm Secure Execution Environment interface
(QSEECOM).
Signed-off-by: Maximilian Luz <[email protected]>
---
Changes in v3:
- None.
Changes in v2:
- Replaces uefisecapp bindings.
- Fix various dt-checker complaints.
---
.../bindings/firmware/qcom,qseecom.yaml | 49 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 50 insertions(+)
create mode 100644 Documentation/devicetree/bindings/firmware/qcom,qseecom.yaml
diff --git a/Documentation/devicetree/bindings/firmware/qcom,qseecom.yaml b/Documentation/devicetree/bindings/firmware/qcom,qseecom.yaml
new file mode 100644
index 000000000000..540a604f81bc
--- /dev/null
+++ b/Documentation/devicetree/bindings/firmware/qcom,qseecom.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/firmware/qcom,qseecom.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm Secure Execution Environment Communication Interface
+
+maintainers:
+ - Maximilian Luz <[email protected]>
+
+description: |
+ QSEECOM provides an interface to Qualcomm's Secure Execution Environment
+ (SEE) running in the Trust Zone via SCM calls. In particular, it allows
+ communication with secure applications running therein.
+
+ Applications running in this environment can, for example, include
+ 'uefisecapp', which is required for accessing UEFI variables on certain
+ systems as these cannot be accessed directly.
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - qcom,qseecom-sc8280xp
+ - const: qcom,qseecom
+
+ qcom,scm:
+ $ref: '/schemas/types.yaml#/definitions/phandle'
+ description:
+ A phandle pointing to the QCOM SCM device (see ./qcom,scm.yaml).
+
+required:
+ - compatible
+ - qcom,scm
+
+additionalProperties: false
+
+examples:
+ - |
+ firmware {
+ scm {
+ compatible = "qcom,scm-sc8280xp", "qcom,scm";
+ };
+ qseecom {
+ compatible = "qcom,qseecom-sc8280xp", "qcom,qseecom";
+ qcom,scm = <&scm>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 1545914a592c..ef1f806986e9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17384,6 +17384,7 @@ QUALCOMM SECURE EXECUTION ENVIRONMENT COMMUNICATION DRIVER
M: Maximilian Luz <[email protected]>
L: [email protected]
S: Maintained
+F: Documentation/devicetree/bindings/firmware/qcom,qseecom.yaml
F: drivers/firmware/qcom_qseecom.c
F: include/linux/firmware/qcom/qcom_qseecom.h
--
2.39.2
Add support for SCM calls to Secure OS and the Secure Execution
Environment (SEE) residing in the TrustZone (TZ) via the QSEECOM
interface. This allows communication with Secure/TZ applications, for
example 'uefisecapp' managing access to UEFI variables.
The interface is managed by a platform device to ensure correct lifetime
and establish a device link to the Qualcomm SCM device.
While this patch introduces only a very basic interface without the more
advanced features (such as re-entrant and blocking SCM calls and
listeners/callbacks), this is enough to talk to the aforementioned
'uefisecapp'.
Signed-off-by: Maximilian Luz <[email protected]>
---
Changes in v3:
- Rebase ontop of latest qcom_scm changes (qcom_scm.h moved).
- Move qcom_qseecom.h in accordance with qcom_scm.
Changes in v2:
- Bind the interface to a device.
- Establish a device link to the SCM device to ensure proper ordering.
- Register client apps as child devices instead of requiring them to be
specified in the device tree.
- Rename (qctree -> qseecom) to allow differentiation between old
(qseecom) and new (smcinvoke) interfaces to the trusted execution
environment.
---
MAINTAINERS | 7 +
drivers/firmware/Kconfig | 15 +
drivers/firmware/Makefile | 1 +
drivers/firmware/qcom_qseecom.c | 314 +++++++++++++++++++++
include/linux/firmware/qcom/qcom_qseecom.h | 190 +++++++++++++
5 files changed, 527 insertions(+)
create mode 100644 drivers/firmware/qcom_qseecom.c
create mode 100644 include/linux/firmware/qcom/qcom_qseecom.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 9201967d198d..1545914a592c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17380,6 +17380,13 @@ F: Documentation/networking/device_drivers/cellular/qualcomm/rmnet.rst
F: drivers/net/ethernet/qualcomm/rmnet/
F: include/linux/if_rmnet.h
+QUALCOMM SECURE EXECUTION ENVIRONMENT COMMUNICATION DRIVER
+M: Maximilian Luz <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/firmware/qcom_qseecom.c
+F: include/linux/firmware/qcom/qcom_qseecom.h
+
QUALCOMM TSENS THERMAL DRIVER
M: Amit Kucheria <[email protected]>
M: Thara Gopinath <[email protected]>
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index b59e3041fd62..22eec0835abf 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -226,6 +226,21 @@ config QCOM_SCM_DOWNLOAD_MODE_DEFAULT
Say Y here to enable "download mode" by default.
+config QCOM_QSEECOM
+ tristate "Qualcomm QSEECOM interface driver"
+ select MFD_CORE
+ select QCOM_SCM
+ help
+ Various Qualcomm SoCs have a Secure Execution Environment (SEE) running
+ in the Trust Zone. This module provides an interface to that via the
+ QSEECOM mechanism, using SCM calls.
+
+ The QSEECOM interface allows, among other things, access to applications
+ running in the SEE. An example of such an application is 'uefisecapp',
+ which is required to access UEFI variables on certain systems.
+
+ Select M or Y here to enable the QSEECOM interface driver.
+
config SYSFB
bool
select BOOT_VESA_SUPPORT
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 28fcddcd688f..aa48e0821b7d 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
+obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
obj-$(CONFIG_SYSFB) += sysfb.o
obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o
obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
diff --git a/drivers/firmware/qcom_qseecom.c b/drivers/firmware/qcom_qseecom.c
new file mode 100644
index 000000000000..efa5b115b2f1
--- /dev/null
+++ b/drivers/firmware/qcom_qseecom.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Interface driver for the Qualcomm Secure Execution Environment (SEE) /
+ * TrustZone OS (TzOS). Manages communication via the QSEECOM interface, using
+ * Secure Channel Manager (SCM) calls.
+ *
+ * Copyright (C) 2023 Maximilian Luz <[email protected]>
+ */
+
+#include <asm/barrier.h>
+#include <linux/device.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+
+#include <linux/firmware/qcom/qcom_qseecom.h>
+
+
+/* -- Secure-OS SCM call interface. ----------------------------------------- */
+
+static int __qseecom_scm_call(const struct qcom_scm_desc *desc,
+ struct qseecom_scm_resp *res)
+{
+ struct qcom_scm_res scm_res = {};
+ int status;
+
+ status = qcom_scm_call(desc, &scm_res);
+
+ res->status = scm_res.result[0];
+ res->resp_type = scm_res.result[1];
+ res->data = scm_res.result[2];
+
+ if (status)
+ return status;
+
+ return 0;
+}
+
+/**
+ * qseecom_scm_call() - Perform a QSEECOM SCM call.
+ * @qsee: The QSEECOM device.
+ * @desc: SCM call descriptor.
+ * @res: SCM call response (output).
+ *
+ * Performs the QSEECOM SCM call described by @desc, returning the response in
+ * @rsp.
+ *
+ * Return: Returns zero on success, nonzero on failure.
+ */
+int qseecom_scm_call(struct qseecom_device *qsee, const struct qcom_scm_desc *desc,
+ struct qseecom_scm_resp *res)
+{
+ int status;
+
+ /*
+ * Note: Multiple QSEECOM SCM calls should not be executed same time,
+ * so lock things here. This needs to be extended to callback/listener
+ * handling when support for that is implemented.
+ */
+
+ mutex_lock(&qsee->scm_call_lock);
+ status = __qseecom_scm_call(desc, res);
+ mutex_unlock(&qsee->scm_call_lock);
+
+ dev_dbg(qsee->dev, "%s: owner=%x, svc=%x, cmd=%x, status=%lld, type=%llx, data=%llx",
+ __func__, desc->owner, desc->svc, desc->cmd, res->status,
+ res->resp_type, res->data);
+
+ if (status) {
+ dev_err(qsee->dev, "qcom_scm_call failed with error %d\n", status);
+ return status;
+ }
+
+ /*
+ * TODO: Handle incomplete and blocked calls:
+ *
+ * Incomplete and blocked calls are not supported yet. Some devices
+ * and/or commands require those, some don't. Let's warn about them
+ * prominently in case someone attempts to try these commands with a
+ * device/command combination that isn't supported yet.
+ */
+ WARN_ON(res->status == QSEECOM_RESULT_INCOMPLETE);
+ WARN_ON(res->status == QSEECOM_RESULT_BLOCKED_ON_LISTENER);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qseecom_scm_call);
+
+
+/* -- Secure App interface. ------------------------------------------------- */
+
+/**
+ * qseecom_app_get_id() - Query the app ID for a given SEE app name.
+ * @qsee: The QSEECOM device.
+ * @app_name: The name of the app.
+ * @app_id: The returned app ID.
+ *
+ * Query and return the application ID of the SEE app identified by the given
+ * name. This returned ID is the unique identifier of the app required for
+ * subsequent communication.
+ *
+ * Return: Returns zero on success, nonzero on failure. Returns -ENOENT if the
+ * app has not been loaded or could not be found.
+ */
+int qseecom_app_get_id(struct qseecom_device *qsee, const char *app_name, u32 *app_id)
+{
+ unsigned long name_buf_size = QSEECOM_MAX_APP_NAME_SIZE;
+ unsigned long app_name_len = strlen(app_name);
+ struct qcom_scm_desc desc = {};
+ struct qseecom_scm_resp res = {};
+ dma_addr_t name_buf_phys;
+ char *name_buf;
+ int status;
+
+ if (app_name_len >= name_buf_size)
+ return -EINVAL;
+
+ name_buf = kzalloc(name_buf_size, GFP_KERNEL);
+ if (!name_buf)
+ return -ENOMEM;
+
+ memcpy(name_buf, app_name, app_name_len);
+
+ name_buf_phys = dma_map_single(qsee->dev, name_buf, name_buf_size, DMA_TO_DEVICE);
+ if (dma_mapping_error(qsee->dev, name_buf_phys)) {
+ kfree(name_buf);
+ dev_err(qsee->dev, "failed to map dma address\n");
+ return -EFAULT;
+ }
+
+ desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
+ desc.svc = QSEECOM_TZ_SVC_APP_MGR;
+ desc.cmd = 0x03;
+ desc.arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_RW, QCOM_SCM_VAL);
+ desc.args[0] = name_buf_phys;
+ desc.args[1] = app_name_len;
+
+ status = qseecom_scm_call(qsee, &desc, &res);
+ dma_unmap_single(qsee->dev, name_buf_phys, name_buf_size, DMA_TO_DEVICE);
+ kfree(name_buf);
+
+ if (status)
+ return status;
+
+ if (res.status != QSEECOM_RESULT_SUCCESS)
+ return -ENOENT;
+
+ *app_id = res.data;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qseecom_app_get_id);
+
+/**
+ * qseecom_app_send() - Send to and receive data from a given SEE app.
+ * @qsee: The QSEECOM device.
+ * @app_id: The ID of the app to communicate with.
+ * @req: DMA region of the request sent to the app.
+ * @rsp: DMA region of the response returned by the app.
+ *
+ * Sends a request to the SEE app identified by the given ID and read back its
+ * response. The caller must provide two DMA memory regions, one for the
+ * request and one for the response, and fill out the @req region with the
+ * respective (app-specific) request data. The SEE app reads this and returns
+ * its response in the @rsp region.
+ *
+ * Return: Returns zero on success, nonzero on failure.
+ */
+int qseecom_app_send(struct qseecom_device *qsee, u32 app_id, struct qseecom_dma *req,
+ struct qseecom_dma *rsp)
+{
+ struct qseecom_scm_resp res = {};
+ int status;
+
+ struct qcom_scm_desc desc = {
+ .owner = QSEECOM_TZ_OWNER_TZ_APPS,
+ .svc = QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER,
+ .cmd = 0x01,
+ .arginfo = QCOM_SCM_ARGS(5, QCOM_SCM_VAL,
+ QCOM_SCM_RW, QCOM_SCM_VAL,
+ QCOM_SCM_RW, QCOM_SCM_VAL),
+ .args[0] = app_id,
+ .args[1] = req->phys,
+ .args[2] = req->size,
+ .args[3] = rsp->phys,
+ .args[4] = rsp->size,
+ };
+
+ /* Make sure the request is fully written before sending it off. */
+ dma_wmb();
+
+ status = qseecom_scm_call(qsee, &desc, &res);
+
+ /* Make sure we don't attempt any reads before the SCM call is done. */
+ dma_rmb();
+
+ if (status)
+ return status;
+
+ if (res.status != QSEECOM_RESULT_SUCCESS)
+ return -EIO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qseecom_app_send);
+
+
+/* -- Platform specific data. ----------------------------------------------- */
+
+struct qseecom_data {
+ const struct mfd_cell *cells;
+ int num_cells;
+};
+
+static const struct of_device_id qseecom_dt_match[] = {
+ { .compatible = "qcom,qseecom-sc8280xp", },
+ { .compatible = "qcom,qseecom", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qseecom_dt_match);
+
+
+/* -- Driver setup. --------------------------------------------------------- */
+
+static int qseecom_setup_scm_link(struct platform_device *pdev)
+{
+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;
+ struct platform_device *scm_dev;
+ struct device_node *scm_node;
+ struct device_link *link;
+ int status = 0;
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ /* Find the SCM device. */
+ scm_node = of_parse_phandle(pdev->dev.of_node, "qcom,scm", 0);
+ if (!scm_node)
+ return -ENOENT;
+
+ scm_dev = of_find_device_by_node(scm_node);
+ if (!scm_dev) {
+ status = -ENODEV;
+ goto put;
+ }
+
+ /* Establish the device link. */
+ link = device_link_add(&pdev->dev, &scm_dev->dev, flags);
+ if (!link) {
+ status = -EINVAL;
+ goto put;
+ }
+
+ /* Make sure SCM has a driver bound, otherwise defer probe. */
+ if (link->supplier->links.status != DL_DEV_DRIVER_BOUND) {
+ status = -EPROBE_DEFER;
+ goto put;
+ }
+
+put:
+ of_node_put(scm_node);
+ return status;
+}
+
+static int qseecom_probe(struct platform_device *pdev)
+{
+ const struct qseecom_data *data;
+ struct qseecom_device *qsee;
+ int status;
+
+ /* Get platform data. */
+ data = of_device_get_match_data(&pdev->dev);
+
+ /* Set up device link. */
+ status = qseecom_setup_scm_link(pdev);
+ if (status)
+ return status;
+
+ /* Set up QSEECOM device. */
+ qsee = devm_kzalloc(&pdev->dev, sizeof(*qsee), GFP_KERNEL);
+ if (!qsee)
+ return -ENOMEM;
+
+ qsee->dev = &pdev->dev;
+ mutex_init(&qsee->scm_call_lock);
+
+ platform_set_drvdata(pdev, qsee);
+
+ /* Add child devices. */
+ if (data) {
+ status = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, data->cells,
+ data->num_cells, NULL, 0, NULL);
+ }
+
+ return status;
+}
+
+static struct platform_driver qseecom_driver = {
+ .probe = qseecom_probe,
+ .driver = {
+ .name = "qcom_qseecom",
+ .of_match_table = qseecom_dt_match,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_platform_driver(qseecom_driver);
+
+MODULE_AUTHOR("Maximilian Luz <[email protected]>");
+MODULE_DESCRIPTION("Driver for Qualcomm QSEECOM secure OS and secure application interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/firmware/qcom/qcom_qseecom.h b/include/linux/firmware/qcom/qcom_qseecom.h
new file mode 100644
index 000000000000..5bd9371a92f7
--- /dev/null
+++ b/include/linux/firmware/qcom/qcom_qseecom.h
@@ -0,0 +1,190 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Interface driver for the Qualcomm Secure Execution Environment (SEE) /
+ * TrustZone OS (TzOS). Manages communication via the QSEECOM interface, using
+ * Secure Channel Manager (SCM) calls.
+ *
+ * Copyright (C) 2023 Maximilian Luz <[email protected]>
+ */
+
+#ifndef _LINUX_QCOM_QSEECOM_H
+#define _LINUX_QCOM_QSEECOM_H
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+
+/* -- DMA helpers. ---------------------------------------------------------- */
+
+/* DMA requirements for QSEECOM SCM calls. */
+#define QSEECOM_DMA_ALIGNMENT 8
+#define QSEECOM_DMA_ALIGN(ptr) ALIGN(ptr, QSEECOM_DMA_ALIGNMENT)
+
+/**
+ * struct qseecom_dma - DMA memory region.
+ * @size: Size of the memory region, in bytes.
+ * @virt: Pointer / virtual address to the memory, accessible by the kernel.
+ * @phys: Physical address of the memory region.
+ */
+struct qseecom_dma {
+ unsigned long size;
+ void *virt;
+ dma_addr_t phys;
+};
+
+/**
+ * qseecom_dma_alloc() - Allocate a DMA-able memory region suitable for QSEECOM
+ * SCM calls.
+ * @dev: The device used for DMA memory allocation.
+ * @dma: Where to write the allocated memory addresses and size to.
+ * @size: Minimum size of the memory to be allocated.
+ * @gfp: Flags used for allocation.
+ *
+ * Allocate a DMA-able memory region suitable for interaction with SEE
+ * services/applications and the TzOS. The provided size is treated as the
+ * minimum required size and rounded up, if necessary. The actually allocated
+ * memory region will be stored in @dma. Allocated memory must be freed via
+ * qseecom_dma_free().
+ *
+ * Return: Returns zero on success, -ENOMEM on allocation failure.
+ */
+static inline int qseecom_dma_alloc(struct device *dev, struct qseecom_dma *dma,
+ unsigned long size, gfp_t gfp)
+{
+ size = PAGE_ALIGN(size);
+
+ dma->virt = dma_alloc_coherent(dev, size, &dma->phys, GFP_KERNEL);
+ if (!dma->virt)
+ return -ENOMEM;
+
+ dma->size = size;
+ return 0;
+}
+
+/**
+ * qseecom_dma_free() - Free a DMA memory region.
+ * @dev: The device used for allocation.
+ * @dma: The DMA region to be freed.
+ *
+ * Free a DMA region previously allocated via qseecom_dma_alloc(). Note that
+ * freeing sub-regions is not supported.
+ */
+static inline void qseecom_dma_free(struct device *dev, struct qseecom_dma *dma)
+{
+ dma_free_coherent(dev, dma->size, dma->virt, dma->phys);
+}
+
+/**
+ * qseecom_dma_realloc() - Re-allocate DMA memory region with the requested size.
+ * @dev: The device used for allocation.
+ * @dma: The region descriptor to be updated.
+ * @size: The new requested size.
+ * @gfp: Flags used for allocation.
+ *
+ * Re-allocates a DMA memory region suitable for QSEECOM SCM calls to fit the
+ * requested amount of bytes, if necessary. Does nothing if the provided region
+ * already has enough space to store the requested data.
+ *
+ * See qseecom_dma_alloc() for details.
+ *
+ * Return: Returns zero on success, -ENOMEM on allocation failure.
+ */
+static inline int qseecom_dma_realloc(struct device *dev, struct qseecom_dma *dma,
+ unsigned long size, gfp_t gfp)
+{
+ if (PAGE_ALIGN(size) <= dma->size)
+ return 0;
+
+ qseecom_dma_free(dev, dma);
+ return qseecom_dma_alloc(dev, dma, size, gfp);
+}
+
+/**
+ * qseecom_dma_aligned() - Create a aligned DMA memory sub-region suitable for
+ * QSEECOM SCM calls.
+ * @base: Base DMA memory region, in which the new region will reside.
+ * @out: Descriptor to store the aligned sub-region in.
+ * @offset: The offset inside base region at which to place the new sub-region.
+ *
+ * Creates an aligned DMA memory region suitable for QSEECOM SCM calls at or
+ * after the given offset. The size of the sub-region will be set to the
+ * remaining size in the base region after alignment, i.e., the end of the
+ * sub-region will be equal the end of the base region.
+ *
+ * Return: Returns zero on success or -EINVAL if the new aligned memory address
+ * would point outside the base region.
+ */
+static inline int qseecom_dma_aligned(const struct qseecom_dma *base, struct qseecom_dma *out,
+ unsigned long offset)
+{
+ void *aligned = (void *)QSEECOM_DMA_ALIGN((uintptr_t)base->virt + offset);
+
+ if (aligned - base->virt > base->size)
+ return -EINVAL;
+
+ out->virt = aligned;
+ out->phys = base->phys + (out->virt - base->virt);
+ out->size = base->size - (out->virt - base->virt);
+
+ return 0;
+}
+
+
+/* -- Common interface. ----------------------------------------------------- */
+
+struct qseecom_device {
+ struct device *dev;
+ struct mutex scm_call_lock; /* Guards QSEECOM SCM calls. */
+};
+
+
+/* -- Secure-OS SCM call interface. ----------------------------------------- */
+
+#define QSEECOM_TZ_OWNER_TZ_APPS 48
+#define QSEECOM_TZ_OWNER_QSEE_OS 50
+
+#define QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER 0
+#define QSEECOM_TZ_SVC_APP_MGR 1
+
+enum qseecom_scm_result {
+ QSEECOM_RESULT_SUCCESS = 0,
+ QSEECOM_RESULT_INCOMPLETE = 1,
+ QSEECOM_RESULT_BLOCKED_ON_LISTENER = 2,
+ QSEECOM_RESULT_FAILURE = 0xFFFFFFFF,
+};
+
+enum qseecom_scm_resp_type {
+ QSEECOM_SCM_RES_APP_ID = 0xEE01,
+ QSEECOM_SCM_RES_QSEOS_LISTENER_ID = 0xEE02,
+};
+
+/**
+ * struct qseecom_scm_resp - QSEECOM SCM call response.
+ * @status: Status of the SCM call. See &enum qseecom_scm_result.
+ * @resp_type: Type of the response. See &enum qseecom_scm_resp_type.
+ * @data: Response data. The type of this data is given in @resp_type.
+ */
+struct qseecom_scm_resp {
+ u64 status;
+ u64 resp_type;
+ u64 data;
+};
+
+int qseecom_scm_call(struct qseecom_device *qsee, const struct qcom_scm_desc *desc,
+ struct qseecom_scm_resp *res);
+
+
+/* -- Secure App interface. ------------------------------------------------- */
+
+#define QSEECOM_MAX_APP_NAME_SIZE 64
+
+int qseecom_app_get_id(struct qseecom_device *qsee, const char *app_name, u32 *app_id);
+int qseecom_app_send(struct qseecom_device *qsee, u32 app_id, struct qseecom_dma *req,
+ struct qseecom_dma *rsp);
+
+#endif /* _LINUX_QCOM_QSEECOM_H */
--
2.39.2
On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
EFI variables cannot be accessed via the standard interface in EFI
runtime mode. The respective functions return EFI_UNSUPPORTED. On these
platforms, we instead need to talk to uefisecapp. This commit provides
support for this and registers the respective efivars operations to
access EFI variables from the kernel.
Communication with uefisecapp follows the Qualcomm QSEECOM / Secure OS
conventions via the respective SCM call interface. This is also the
reason why variable access works normally while boot services are
active. During this time, said SCM interface is managed by the boot
services. When calling ExitBootServices(), the ownership is transferred
to the kernel. Therefore, UEFI must not use that interface itself (as
multiple parties accessing this interface at the same time may lead to
complications) and cannot access variables for us.
Signed-off-by: Maximilian Luz <[email protected]>
---
Changes in v3:
- No functional changes.
Changes in v2:
- Rename (qctree -> qseecom) to allow differentiation between old
(qseecom) and new (smcinvoke) interfaces to the trusted execution
environment.
---
MAINTAINERS | 6 +
drivers/firmware/Kconfig | 16 +
drivers/firmware/Makefile | 1 +
drivers/firmware/qcom_qseecom.c | 11 +-
drivers/firmware/qcom_qseecom_uefisecapp.c | 746 +++++++++++++++++++++
5 files changed, 779 insertions(+), 1 deletion(-)
create mode 100644 drivers/firmware/qcom_qseecom_uefisecapp.c
diff --git a/MAINTAINERS b/MAINTAINERS
index ef1f806986e9..35d614de6bbc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17371,6 +17371,12 @@ S: Maintained
F: Documentation/devicetree/bindings/mtd/qcom,nandc.yaml
F: drivers/mtd/nand/raw/qcom_nandc.c
+QUALCOMM QSEECOM UEFISECAPP DRIVER
+M: Maximilian Luz <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/firmware/qcom_qseecom_uefisecapp.c
+
QUALCOMM RMNET DRIVER
M: Subash Abhinov Kasiviswanathan <[email protected]>
M: Sean Tranchetti <[email protected]>
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 22eec0835abf..b9ad753a195b 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -241,6 +241,22 @@ config QCOM_QSEECOM
Select M or Y here to enable the QSEECOM interface driver.
+config QCOM_QSEECOM_UEFISECAPP
+ tristate "Qualcomm SEE UEFI Secure App client driver"
+ depends on QCOM_QSEECOM
+ depends on EFI
+ help
+ Various Qualcomm SoCs do not allow direct access to EFI variables.
+ Instead, these need to be accessed via the UEFI Secure Application
+ (uefisecapp), residing in the Secure Execution Environment (SEE).
+
+ This module provides a client driver for uefisecapp, installing efivar
+ operations to allow the kernel accessing EFI variables, and via that also
+ provide user-space with access to EFI variables via efivarfs.
+
+ Select M or Y here to provide access to EFI variables on the
+ aforementioned platforms.
+
config SYSFB
bool
select BOOT_VESA_SUPPORT
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index aa48e0821b7d..d41b094a5e58 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
+obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
obj-$(CONFIG_SYSFB) += sysfb.o
obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o
obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
diff --git a/drivers/firmware/qcom_qseecom.c b/drivers/firmware/qcom_qseecom.c
index efa5b115b2f1..ae42341dce1c 100644
--- a/drivers/firmware/qcom_qseecom.c
+++ b/drivers/firmware/qcom_qseecom.c
@@ -216,8 +216,17 @@ struct qseecom_data {
int num_cells;
};
+static const struct mfd_cell qseecom_cells_sc8280xp[] = {
+ { .name = "qcom_qseecom_uefisecapp", },
+};
+
+static const struct qseecom_data qseecom_data_sc8280xp = {
+ .cells = qseecom_cells_sc8280xp,
+ .num_cells = ARRAY_SIZE(qseecom_cells_sc8280xp),
+};
+
static const struct of_device_id qseecom_dt_match[] = {
- { .compatible = "qcom,qseecom-sc8280xp", },
+ { .compatible = "qcom,qseecom-sc8280xp", .data = &qseecom_data_sc8280xp },
{ .compatible = "qcom,qseecom", },
{ }
};
diff --git a/drivers/firmware/qcom_qseecom_uefisecapp.c b/drivers/firmware/qcom_qseecom_uefisecapp.c
new file mode 100644
index 000000000000..8d772fe1f589
--- /dev/null
+++ b/drivers/firmware/qcom_qseecom_uefisecapp.c
@@ -0,0 +1,746 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Client driver for Qualcomm UEFI Secure Application (qcom.tz.uefisecapp).
+ * Provides access to UEFI variables on platforms where they are secured by the
+ * aforementioned Secure Execution Environment (SEE) application.
+ *
+ * Copyright (C) 2023 Maximilian Luz <[email protected]>
+ */
+
+#include <linux/efi.h>
+#include <linux/firmware/qcom/qcom_qseecom.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+
+/* -- UTF-16 helpers. ------------------------------------------------------- */
+
+static unsigned long utf16_strnlen(const efi_char16_t *str, unsigned long max)
+{
+ size_t i;
+
+ for (i = 0; *str != 0 && i < max; i++, str++) {
+ /* Do nothing, all is handled in the for statement. */
+ }
+
+ return i;
+}
+
+/**
+ * utf16_strsize() - Compute the number of bytes required to store a
+ * null-terminated UTF-16 string.
+ * @str: The string to compute the size for.
+ *
+ * Return: Returns the minimum number of bytes required to store the given
+ * null-terminated string, including its null-terminator.
+ */
+static unsigned long utf16_strsize(const efi_char16_t *str)
+{
+ return (utf16_strnlen(str, U32_MAX) + 1) * sizeof(str[0]);
+}
+
+static unsigned long utf16_strlcpy(efi_char16_t *dst, const efi_char16_t *src, unsigned long size)
+{
+ unsigned long actual = utf16_strnlen(src, size - 1);
+
+ memcpy(dst, src, actual * sizeof(src[0]));
+ dst[actual] = 0;
+
+ return actual;
+}
+
+/**
+ * utf16_copy_to_buf() - Copy the given UTF-16 string to a buffer.
+ * @dst: Pointer to the output buffer
+ * @src: Pointer to the null-terminated UTF-16 string to be copied.
+ * @bytes: Maximum number of bytes to copy.
+ *
+ * Copies the given string to the given buffer, ensuring that the output buffer
+ * is not overrun and that the string in the output buffer will always be
+ * null-terminated.
+ *
+ * Return: Returns the length of the copied string, without null-terminator.
+ */
+static unsigned long utf16_copy_to_buf(efi_char16_t *dst, const efi_char16_t *src,
+ unsigned long bytes)
+{
+ return utf16_strlcpy(dst, src, bytes / sizeof(src[0]));
+}
+
+
+/* -- Qualcomm "uefisecapp" interface definitions. -------------------------- */
+
+#define QSEE_UEFISEC_APP_NAME "qcom.tz.uefisecapp"
+
+#define QSEE_CMD_UEFI(x) (0x8000 | (x))
+#define QSEE_CMD_UEFI_GET_VARIABLE QSEE_CMD_UEFI(0)
+#define QSEE_CMD_UEFI_SET_VARIABLE QSEE_CMD_UEFI(1)
+#define QSEE_CMD_UEFI_GET_NEXT_VARIABLE QSEE_CMD_UEFI(2)
+#define QSEE_CMD_UEFI_QUERY_VARIABLE_INFO QSEE_CMD_UEFI(3)
+
+/**
+ * struct qsee_req_uefi_get_variable - Request for GetVariable command.
+ * @command_id: The ID of the command. Must be %QSEE_CMD_UEFI_GET_VARIABLE.
+ * @length: Length of the request in bytes, including this struct and any
+ * parameters (name, GUID) stored after it as well as any padding
+ * thereof for alignment.
+ * @name_offset: Offset from the start of this struct to where the variable
+ * name is stored (as utf-16 string), in bytes.
+ * @name_size: Size of the name parameter in bytes, including null-terminator.
+ * @guid_offset: Offset from the start of this struct to where the GUID
+ * parameter is stored, in bytes.
+ * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
+ * @data_size: Size of the output buffer, in bytes.
+ */
+struct qsee_req_uefi_get_variable {
+ u32 command_id;
+ u32 length;
+ u32 name_offset;
+ u32 name_size;
+ u32 guid_offset;
+ u32 guid_size;
+ u32 data_size;
+} __packed;
+
+/**
+ * struct qsee_rsp_uefi_get_variable - Response for GetVariable command.
+ * @command_id: The ID of the command. Should be %QSEE_CMD_UEFI_GET_VARIABLE.
+ * @length: Length of the response in bytes, including this struct and the
+ * returned data.
+ * @status: Status of this command.
+ * @attributes: EFI variable attributes.
+ * @data_offset: Offset from the start of this struct to where the data is
+ * stored, in bytes.
+ * @data_size: Size of the returned data, in bytes. In case status indicates
+ * that the buffer is too small, this will be the size required
+ * to store the EFI variable data.
+ */
+struct qsee_rsp_uefi_get_variable {
+ u32 command_id;
+ u32 length;
+ u32 status;
+ u32 attributes;
+ u32 data_offset;
+ u32 data_size;
+} __packed;
+
+/**
+ * struct qsee_req_uefi_set_variable - Request for the SetVariable command.
+ * @command_id: The ID of the command. Must be %QSEE_CMD_UEFI_SET_VARIABLE.
+ * @length: Length of the request in bytes, including this struct and any
+ * parameters (name, GUID, data) stored after it as well as any
+ * padding thereof required for alignment.
+ * @name_offset: Offset from the start of this struct to where the variable
+ * name is stored (as utf-16 string), in bytes.
+ * @name_size: Size of the name parameter in bytes, including null-terminator.
+ * @guid_offset: Offset from the start of this struct to where the GUID
+ * parameter is stored, in bytes.
+ * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
+ * @attributes: The EFI variable attributes to set for this variable.
+ * @data_offset: Offset from the start of this struct to where the EFI variable
+ * data is stored, in bytes.
+ * @data_size: Size of EFI variable data, in bytes.
+ *
+ */
+struct qsee_req_uefi_set_variable {
+ u32 command_id;
+ u32 length;
+ u32 name_offset;
+ u32 name_size;
+ u32 guid_offset;
+ u32 guid_size;
+ u32 attributes;
+ u32 data_offset;
+ u32 data_size;
+} __packed;
+
+/**
+ * struct qsee_rsp_uefi_set_variable - Response for the SetVariable command.
+ * @command_id: The ID of the command. Should be %QSEE_CMD_UEFI_SET_VARIABLE.
+ * @length: The length of this response, i.e. the size of this struct in
+ * bytes.
+ * @status: Status of this command.
+ * @_unknown1: Unknown response field.
+ * @_unknown2: Unknown response field.
+ */
+struct qsee_rsp_uefi_set_variable {
+ u32 command_id;
+ u32 length;
+ u32 status;
+ u32 _unknown1;
+ u32 _unknown2;
+} __packed;
+
+/**
+ * struct qsee_req_uefi_get_next_variable - Request for the
+ * GetNextVariableName command.
+ * @command_id: The ID of the command. Must be
+ * %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
+ * @length: Length of the request in bytes, including this struct and any
+ * parameters (name, GUID) stored after it as well as any padding
+ * thereof for alignment.
+ * @guid_offset: Offset from the start of this struct to where the GUID
+ * parameter is stored, in bytes.
+ * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
+ * @name_offset: Offset from the start of this struct to where the variable
+ * name is stored (as utf-16 string), in bytes.
+ * @name_size: Size of the name parameter in bytes, including null-terminator.
+ */
+struct qsee_req_uefi_get_next_variable {
+ u32 command_id;
+ u32 length;
+ u32 guid_offset;
+ u32 guid_size;
+ u32 name_offset;
+ u32 name_size;
+} __packed;
+
+/**
+ * struct qsee_rsp_uefi_get_next_variable - Response for the
+ * GetNextVariableName command.
+ * @command_id: The ID of the command. Should be
+ * %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
+ * @length: Length of the response in bytes, including this struct and any
+ * parameters (name, GUID) stored after it as well as any padding
+ * thereof for alignment.
+ * @status: Status of this command.
+ * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
+ * @name_offset: Offset from the start of this struct to where the variable
+ * name is stored (as utf-16 string), in bytes.
+ * @name_size: Size of the name parameter in bytes, including null-terminator.
+ */
+struct qsee_rsp_uefi_get_next_variable {
+ u32 command_id;
+ u32 length;
+ u32 status;
+ u32 guid_offset;
+ u32 guid_size;
+ u32 name_offset;
+ u32 name_size;
+} __packed;
+
+/**
+ * struct qsee_req_uefi_query_variable_info - Response for the
+ * GetNextVariableName command.
+ * @command_id: The ID of the command. Must be
+ * %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
+ * @length: The length of this request, i.e. the size of this struct in
+ * bytes.
+ * @attributes: The storage attributes to query the info for.
+ */
+struct qsee_req_uefi_query_variable_info {
+ u32 command_id;
+ u32 length;
+ u32 attributes;
+} __packed;
+
+/**
+ * struct qsee_rsp_uefi_query_variable_info - Response for the
+ * GetNextVariableName command.
+ * @command_id: The ID of the command. Must be
+ * %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
+ * @length: The length of this response, i.e. the size of this
+ * struct in bytes.
+ * @status: Status of this command.
+ * @_pad: Padding.
+ * @storage_space: Full storage space size, in bytes.
+ * @remaining_space: Free storage space available, in bytes.
+ * @max_variable_size: Maximum variable data size, in bytes.
+ */
+struct qsee_rsp_uefi_query_variable_info {
+ u32 command_id;
+ u32 length;
+ u32 status;
+ u32 _pad;
+ u64 storage_space;
+ u64 remaining_space;
+ u64 max_variable_size;
+} __packed;
+
+
+/* -- UEFI app interface. --------------------------------------------------- */
+
+struct qcuefi_client {
+ struct device *dev;
+ struct qseecom_device *qsee;
+ struct qseecom_dma dma;
+ struct efivars efivars;
+ u32 app_id;
+};
+
+static efi_status_t qsee_uefi_status_to_efi(u32 status)
+{
+ u64 category = status & 0xf0000000;
+ u64 code = status & 0x0fffffff;
+
+ return category << (BITS_PER_LONG - 32) | code;
+}
+
+static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
+ const efi_guid_t *guid, u32 *attributes,
+ unsigned long *data_size, void *data)
+{
+ struct qsee_req_uefi_get_variable *req_data;
+ struct qsee_rsp_uefi_get_variable *rsp_data;
+ struct qseecom_dma dma_req;
+ struct qseecom_dma dma_rsp;
+ unsigned long name_size = utf16_strsize(name);
+ unsigned long buffer_size = *data_size;
+ unsigned long size;
+ efi_status_t efi_status;
+ int status;
+
+ /* Validation: We need a name and GUID. */
+ if (!name || !guid)
+ return EFI_INVALID_PARAMETER;
+
+ /* Validation: We need a buffer if the buffer_size is nonzero. */
+ if (buffer_size && !data)
+ return EFI_INVALID_PARAMETER;
+
+ /* Compute required size (upper limit with alignments). */
+ size = sizeof(*req_data) + sizeof(*guid) + name_size /* Inputs. */
+ + sizeof(*rsp_data) + buffer_size /* Outputs. */
+ + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
+ + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
+
+ /* Make sure we have enough DMA memory. */
+ status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
+ if (status)
+ return EFI_OUT_OF_RESOURCES;
+
+ /* Align request struct. */
+ qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
+ req_data = dma_req.virt;
+
+ /* Set up request data. */
+ req_data->command_id = QSEE_CMD_UEFI_GET_VARIABLE;
+ req_data->data_size = buffer_size;
+ req_data->name_offset = sizeof(*req_data);
+ req_data->name_size = name_size;
+ req_data->guid_offset = QSEECOM_DMA_ALIGN(req_data->name_offset + name_size);
+ req_data->guid_size = sizeof(*guid);
+ req_data->length = req_data->guid_offset + req_data->guid_size;
+
+ dma_req.size = req_data->length;
+
+ /* Copy request parameters. */
+ utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, name_size);
+ memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
+
+ /* Align response struct. */
+ qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
+ rsp_data = dma_rsp.virt;
+
+ /* Perform SCM call. */
+ status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
+
+ /* Check for errors and validate. */
+ if (status)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->command_id != QSEE_CMD_UEFI_GET_VARIABLE)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->status) {
+ dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
+ efi_status = qsee_uefi_status_to_efi(rsp_data->status);
+
+ /* Update size and attributes in case buffer is too small. */
+ if (efi_status == EFI_BUFFER_TOO_SMALL) {
+ *data_size = rsp_data->data_size;
+ if (attributes)
+ *attributes = rsp_data->attributes;
+ }
+
+ return efi_status;
+ }
+
+ if (rsp_data->data_offset + rsp_data->data_size > rsp_data->length)
+ return EFI_DEVICE_ERROR;
+
+ /* Set attributes and data size even if buffer is too small. */
+ *data_size = rsp_data->data_size;
+ if (attributes)
+ *attributes = rsp_data->attributes;
+
+ /*
+ * If we have a buffer size of zero and no buffer, just return
+ * attributes and required size.
+ */
+ if (buffer_size == 0 && !data)
+ return EFI_SUCCESS;
+
+ /* Validate output buffer size. */
+ if (buffer_size < rsp_data->data_size)
+ return EFI_BUFFER_TOO_SMALL;
+
+ /* Copy to output buffer. Note: We're guaranteed to have one at this point. */
+ memcpy(data, dma_rsp.virt + rsp_data->data_offset, rsp_data->data_size);
+ return EFI_SUCCESS;
+}
+
+static efi_status_t qsee_uefi_set_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
+ const efi_guid_t *guid, u32 attributes,
+ unsigned long data_size, const void *data)
+{
+ struct qsee_req_uefi_set_variable *req_data;
+ struct qsee_rsp_uefi_set_variable *rsp_data;
+ struct qseecom_dma dma_req;
+ struct qseecom_dma dma_rsp;
+ unsigned long name_size = utf16_strsize(name);
+ unsigned long size;
+ int status;
+
+ /* Validate inputs. */
+ if (!name || !guid)
+ return EFI_INVALID_PARAMETER;
+
+ /*
+ * Make sure we have some data if data_size is nonzero. Note: Using a
+ * size of zero is valid and deletes the variable.
+ */
+ if (data_size && !data)
+ return EFI_INVALID_PARAMETER;
+
+ /* Compute required size (upper limit with alignments). */
+ size = sizeof(*req_data) + name_size + sizeof(*guid) + data_size /* Inputs. */
+ + sizeof(*rsp_data) /* Outputs. */
+ + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
+ + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
+
+ /* Make sure we have enough DMA memory. */
+ status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
+ if (status)
+ return EFI_OUT_OF_RESOURCES;
+
+ /* Align request struct. */
+ qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
+ req_data = dma_req.virt;
+
+ /* Set up request data. */
+ req_data->command_id = QSEE_CMD_UEFI_SET_VARIABLE;
+ req_data->attributes = attributes;
+ req_data->name_offset = sizeof(*req_data);
+ req_data->name_size = name_size;
+ req_data->guid_offset = QSEECOM_DMA_ALIGN(req_data->name_offset + name_size);
+ req_data->guid_size = sizeof(*guid);
+ req_data->data_offset = req_data->guid_offset + req_data->guid_size;
+ req_data->data_size = data_size;
+ req_data->length = req_data->data_offset + data_size;
+
+ /* Copy request parameters. */
+ utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, req_data->name_size);
+ memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
+
+ if (data_size)
+ memcpy(dma_req.virt + req_data->data_offset, data, req_data->data_size);
+
+ /* Align response struct. */
+ qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
+ rsp_data = dma_rsp.virt;
+
+ /* Perform SCM call. */
+ dma_req.size = req_data->length;
+ dma_rsp.size = sizeof(*rsp_data);
+
+ status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
+
+ /* Check for errors and validate. */
+ if (status)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->command_id != QSEE_CMD_UEFI_SET_VARIABLE)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->status) {
+ dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
+ return qsee_uefi_status_to_efi(rsp_data->status);
+ }
+
+ return EFI_SUCCESS;
+}
+
+static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
+ unsigned long *name_size, efi_char16_t *name,
+ efi_guid_t *guid)
+{
+ struct qsee_req_uefi_get_next_variable *req_data;
+ struct qsee_rsp_uefi_get_next_variable *rsp_data;
+ struct qseecom_dma dma_req;
+ struct qseecom_dma dma_rsp;
+ unsigned long size;
+ efi_status_t efi_status;
+ int status;
+
+ /* We need some buffers. */
+ if (!name_size || !name || !guid)
+ return EFI_INVALID_PARAMETER;
+
+ /* There needs to be at least a single null-character. */
+ if (*name_size == 0)
+ return EFI_INVALID_PARAMETER;
+
+ /* Compute required size (upper limit with alignments). */
+ size = sizeof(*req_data) + sizeof(*guid) + *name_size /* Inputs. */
+ + sizeof(*rsp_data) + sizeof(*guid) + *name_size /* Outputs. */
+ + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
+ + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
+
+ /* Make sure we have enough DMA memory. */
+ status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
+ if (status)
+ return EFI_OUT_OF_RESOURCES;
+
+ /* Align request struct. */
+ qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
+ req_data = dma_req.virt;
+
+ /* Set up request data. */
+ req_data->command_id = QSEE_CMD_UEFI_GET_NEXT_VARIABLE;
+ req_data->guid_offset = QSEECOM_DMA_ALIGN(sizeof(*req_data));
+ req_data->guid_size = sizeof(*guid);
+ req_data->name_offset = req_data->guid_offset + req_data->guid_size;
+ req_data->name_size = *name_size;
+ req_data->length = req_data->name_offset + req_data->name_size;
+
+ dma_req.size = req_data->length;
+
+ /* Copy request parameters. */
+ memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
+ utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, *name_size);
+
+ /* Align response struct. */
+ qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
+ rsp_data = dma_rsp.virt;
+
+ /* Perform SCM call. */
+ status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
+
+ /* Check for errors and validate. */
+ if (status)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->command_id != QSEE_CMD_UEFI_GET_NEXT_VARIABLE)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->status) {
+ dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
+ efi_status = qsee_uefi_status_to_efi(rsp_data->status);
+
+ /* Update size with required size in case buffer is too small. */
+ if (efi_status == EFI_BUFFER_TOO_SMALL)
+ *name_size = rsp_data->name_size;
+
+ return efi_status;
+ }
+
+ if (rsp_data->name_offset + rsp_data->name_size > rsp_data->length)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->guid_offset + rsp_data->guid_size > rsp_data->length)
+ return EFI_DEVICE_ERROR;
+
+ if (rsp_data->name_size > *name_size) {
+ *name_size = rsp_data->name_size;
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ if (rsp_data->guid_size != sizeof(*guid))
+ return EFI_DEVICE_ERROR;
+
+ /* Copy response fields. */
+ memcpy(guid, dma_rsp.virt + rsp_data->guid_offset, rsp_data->guid_size);
+ utf16_copy_to_buf(name, dma_rsp.virt + rsp_data->name_offset, rsp_data->name_size);
+ *name_size = rsp_data->name_size;
+
+ return 0;
+}
+
+
+/* -- Global efivar interface. ---------------------------------------------- */
+
+static struct qcuefi_client *__qcuefi;
+static DEFINE_MUTEX(__qcuefi_lock);
+
+static int qcuefi_set_reference(struct qcuefi_client *qcuefi)
+{
+ mutex_lock(&__qcuefi_lock);
+
+ if (qcuefi && __qcuefi) {
+ mutex_unlock(&__qcuefi_lock);
+ return -EEXIST;
+ }
+
+ __qcuefi = qcuefi;
+
+ mutex_unlock(&__qcuefi_lock);
+ return 0;
+}
+
+static struct qcuefi_client *qcuefi_acquire(void)
+{
+ mutex_lock(&__qcuefi_lock);
+ return __qcuefi;
+}
+
+static void qcuefi_release(void)
+{
+ mutex_unlock(&__qcuefi_lock);
+}
+
+static efi_status_t qcuefi_get_variable(efi_char16_t *name, efi_guid_t *vendor, u32 *attr,
+ unsigned long *data_size, void *data)
+{
+ struct qcuefi_client *qcuefi;
+ efi_status_t status;
+
+ qcuefi = qcuefi_acquire();
+ if (!qcuefi)
+ return EFI_NOT_READY;
+
+ status = qsee_uefi_get_variable(qcuefi, name, vendor, attr, data_size, data);
+
+ qcuefi_release();
+ return status;
+}
+
+static efi_status_t qcuefi_set_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size, void *data)
+{
+ struct qcuefi_client *qcuefi;
+ efi_status_t status;
+
+ qcuefi = qcuefi_acquire();
+ if (!qcuefi)
+ return EFI_NOT_READY;
+
+ status = qsee_uefi_set_variable(qcuefi, name, vendor, attr, data_size, data);
+
+ qcuefi_release();
+ return status;
+}
+
+static efi_status_t qcuefi_get_next_variable(unsigned long *name_size, efi_char16_t *name,
+ efi_guid_t *vendor)
+{
+ struct qcuefi_client *qcuefi;
+ efi_status_t status;
+
+ qcuefi = qcuefi_acquire();
+ if (!qcuefi)
+ return EFI_NOT_READY;
+
+ status = qsee_uefi_get_next_variable(qcuefi, name_size, name, vendor);
+
+ qcuefi_release();
+ return status;
+}
+
+static const struct efivar_operations qcom_efivar_ops = {
+ .get_variable = qcuefi_get_variable,
+ .set_variable = qcuefi_set_variable,
+ .get_next_variable = qcuefi_get_next_variable,
+};
+
+
+/* -- Driver setup. --------------------------------------------------------- */
+
+static int qcom_uefisecapp_probe(struct platform_device *pdev)
+{
+ struct qcuefi_client *qcuefi;
+ int status;
+
+ /* Allocate driver data. */
+ qcuefi = devm_kzalloc(&pdev->dev, sizeof(*qcuefi), GFP_KERNEL);
+ if (!qcuefi)
+ return -ENOMEM;
+
+ qcuefi->dev = &pdev->dev;
+
+ /* We expect the parent to be the QSEECOM device. */
+ qcuefi->qsee = dev_get_drvdata(pdev->dev.parent);
+ if (!qcuefi->qsee)
+ return -EINVAL;
+
+ /* Get application id for uefisecapp. */
+ status = qseecom_app_get_id(qcuefi->qsee, QSEE_UEFISEC_APP_NAME, &qcuefi->app_id);
+ if (status) {
+ dev_err(&pdev->dev, "failed to query app ID: %d\n", status);
+ return status;
+ }
+
+ /* Set up DMA. One page should be plenty to start with. */
+ if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) {
+ dev_warn(&pdev->dev, "no suitable DMA available\n");
+ return -EFAULT;
+ }
+
+ status = qseecom_dma_alloc(&pdev->dev, &qcuefi->dma, PAGE_SIZE, GFP_KERNEL);
+ if (status)
+ return status;
+
+ /* Register global reference. */
+ platform_set_drvdata(pdev, qcuefi);
+ status = qcuefi_set_reference(qcuefi);
+ if (status)
+ goto free;
+
+ /* Register efivar ops. */
+ status = efivars_register(&qcuefi->efivars, &qcom_efivar_ops);
+ if (status)
+ goto clear_reference;
+
+ return 0;
+
+clear_reference:
+ qcuefi_set_reference(NULL);
+free:
+ qseecom_dma_free(qcuefi->dev, &qcuefi->dma);
+ return status;
+}
+
+static int qcom_uefisecapp_remove(struct platform_device *pdev)
+{
+ struct qcuefi_client *qcuefi = platform_get_drvdata(pdev);
+
+ /* Unregister efivar ops. */
+ efivars_unregister(&qcuefi->efivars);
+
+ /* Block on pending calls and unregister global reference. */
+ qcuefi_set_reference(NULL);
+
+ /* Free remaining resources. */
+ qseecom_dma_free(qcuefi->dev, &qcuefi->dma);
+
+ return 0;
+}
+
+static struct platform_driver qcom_uefisecapp_driver = {
+ .probe = qcom_uefisecapp_probe,
+ .remove = qcom_uefisecapp_remove,
+ .driver = {
+ .name = "qcom_qseecom_uefisecapp",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_platform_driver(qcom_uefisecapp_driver);
+
+MODULE_AUTHOR("Maximilian Luz <[email protected]>");
+MODULE_DESCRIPTION("Client driver for Qualcomm SEE UEFI Secure App");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:qcom_qseecom_uefisecapp");
--
2.39.2
On 05/03/2023 04:21, Maximilian Luz wrote:
> Make qcom_scm_call, qcom_scm_call_atomic and associated types accessible
> to other modules.
Generally all the qcom_scm calls are a part of qcom_scm.c. I think it is
better to make qseecom_scm_call a part qcom_scm.c (as we were previously
doing) rather than exporting the core function.
If you wish to limit the kernel bloat, you can split the qcom_scm into
per-driver backend and add Kconfig symbols to limit the impact. However
I think that these functions are pretty small to justify the effort.
>
> Signed-off-by: Maximilian Luz <[email protected]>
> ---
>
> Changes in v3:
> - Rebase ontop of latest qcom_scm changes.
> - Fix doc-comment.
>
> Changes in v2:
> - No functional changes.
>
> ---
> drivers/firmware/qcom_scm.c | 120 ++++++++++++++++---------
> drivers/firmware/qcom_scm.h | 47 ----------
> include/linux/firmware/qcom/qcom_scm.h | 49 ++++++++++
> 3 files changed, 129 insertions(+), 87 deletions(-)
>
> diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c
> index 468d4d5ab550..9b3e4449a563 100644
> --- a/drivers/firmware/qcom_scm.c
> +++ b/drivers/firmware/qcom_scm.c
> @@ -212,16 +212,17 @@ static enum qcom_scm_convention __get_convention(void)
> }
>
> /**
> - * qcom_scm_call() - Invoke a syscall in the secure world
> - * @dev: device
> + * __qcom_scm_call() - Invoke a syscall in the secure world
> + * @dev: Device. Depending on the command and number of arguments, this
> + * is optional.
> * @desc: Descriptor structure containing arguments and return values
> * @res: Structure containing results from SMC/HVC call
> *
> * Sends a command to the SCM and waits for the command to finish processing.
> * This should *only* be called in pre-emptible context.
> */
> -static int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
> - struct qcom_scm_res *res)
> +static int __qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
> + struct qcom_scm_res *res)
> {
> might_sleep();
> switch (__get_convention()) {
> @@ -237,17 +238,38 @@ static int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
> }
>
> /**
> - * qcom_scm_call_atomic() - atomic variation of qcom_scm_call()
> - * @dev: device
> + * qcom_scm_call() - Invoke a syscall in the secure world
> + * @desc: Descriptor structure containing arguments and return values
> + * @res: Structure containing results from SMC/HVC call
> + *
> + * Sends a command to the SCM and waits for the command to finish processing.
> + * This should *only* be called in pre-emptible context.
> + *
> + * Returns zero on success, -ENODEV if the SCM device has not been set up yet,
> + * or other non-zero status codes on failure.
> + */
> +int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res)
> +{
> + if (!__scm)
> + return -ENODEV;
> +
> + return __qcom_scm_call(__scm->dev, desc, res);
> +}
> +EXPORT_SYMBOL_GPL(qcom_scm_call);
> +
> +/**
> + * __qcom_scm_call_atomic() - atomic variation of __qcom_scm_call()
> + * @dev: Device. Depending on the command and number of arguments, this
> + * is optional.
> * @desc: Descriptor structure containing arguments and return values
> * @res: Structure containing results from SMC/HVC call
> *
> * Sends a command to the SCM and waits for the command to finish processing.
> * This can be called in atomic context.
> */
> -static int qcom_scm_call_atomic(struct device *dev,
> - const struct qcom_scm_desc *desc,
> - struct qcom_scm_res *res)
> +static int __qcom_scm_call_atomic(struct device *dev,
> + const struct qcom_scm_desc *desc,
> + struct qcom_scm_res *res)
> {
> switch (__get_convention()) {
> case SMC_CONVENTION_ARM_32:
> @@ -261,6 +283,26 @@ static int qcom_scm_call_atomic(struct device *dev,
> }
> }
>
> +/**
> + * qcom_scm_call_atomic() - atomic variation of qcom_scm_call()
> + * @desc: Descriptor structure containing arguments and return values
> + * @res: Structure containing results from SMC/HVC call
> + *
> + * Sends a command to the SCM and waits for the command to finish processing.
> + * This can be called in atomic context.
> + *
> + * Returns zero on success, -ENODEV if the SCM device has not been set up yet,
> + * or other non-zero status codes on failure.
> + */
> +int qcom_scm_call_atomic(const struct qcom_scm_desc *desc, struct qcom_scm_res *res)
> +{
> + if (!__scm)
> + return -ENODEV;
> +
> + return __qcom_scm_call_atomic(__scm->dev, desc, res);
> +}
> +EXPORT_SYMBOL_GPL(qcom_scm_call_atomic);
> +
> static bool __qcom_scm_is_call_available(struct device *dev, u32 svc_id,
> u32 cmd_id)
> {
> @@ -287,7 +329,7 @@ static bool __qcom_scm_is_call_available(struct device *dev, u32 svc_id,
> return false;
> }
>
> - ret = qcom_scm_call(dev, &desc, &res);
> + ret = __qcom_scm_call(dev, &desc, &res);
>
> return ret ? false : !!res.result[0];
> }
> @@ -312,7 +354,7 @@ static int qcom_scm_set_boot_addr(void *entry, const u8 *cpu_bits)
> desc.args[0] = flags;
> desc.args[1] = virt_to_phys(entry);
>
> - return qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
> + return __qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
> }
>
> static int qcom_scm_set_boot_addr_mc(void *entry, unsigned int flags)
> @@ -334,7 +376,7 @@ static int qcom_scm_set_boot_addr_mc(void *entry, unsigned int flags)
> if (!__scm || __get_convention() == SMC_CONVENTION_LEGACY)
> return -EOPNOTSUPP;
>
> - return qcom_scm_call(__scm->dev, &desc, NULL);
> + return __qcom_scm_call(__scm->dev, &desc, NULL);
> }
>
> /**
> @@ -384,7 +426,7 @@ void qcom_scm_cpu_power_down(u32 flags)
> .owner = ARM_SMCCC_OWNER_SIP,
> };
>
> - qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
> + __qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_cpu_power_down);
>
> @@ -401,7 +443,7 @@ int qcom_scm_set_remote_state(u32 state, u32 id)
> struct qcom_scm_res res;
> int ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
>
> return ret ? : res.result[0];
> }
> @@ -419,7 +461,7 @@ static int __qcom_scm_set_dload_mode(struct device *dev, bool enable)
>
> desc.args[1] = enable ? QCOM_SCM_BOOT_SET_DLOAD_MODE : 0;
>
> - return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
> + return qcom_scm_call_atomic(&desc, NULL);
> }
>
> static void qcom_scm_set_download_mode(bool enable)
> @@ -499,7 +541,7 @@ int qcom_scm_pas_init_image(u32 peripheral, const void *metadata, size_t size,
>
> desc.args[1] = mdata_phys;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = __qcom_scm_call(__scm->dev, &desc, &res);
>
> qcom_scm_bw_disable();
> qcom_scm_clk_disable();
> @@ -565,7 +607,7 @@ int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr, phys_addr_t size)
> if (ret)
> return ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
> qcom_scm_bw_disable();
> qcom_scm_clk_disable();
>
> @@ -600,7 +642,7 @@ int qcom_scm_pas_auth_and_reset(u32 peripheral)
> if (ret)
> return ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
> qcom_scm_bw_disable();
> qcom_scm_clk_disable();
>
> @@ -634,7 +676,7 @@ int qcom_scm_pas_shutdown(u32 peripheral)
> if (ret)
> return ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
>
> qcom_scm_bw_disable();
> qcom_scm_clk_disable();
> @@ -666,7 +708,7 @@ bool qcom_scm_pas_supported(u32 peripheral)
> QCOM_SCM_PIL_PAS_IS_SUPPORTED))
> return false;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = __qcom_scm_call(__scm->dev, &desc, &res);
>
> return ret ? false : !!res.result[0];
> }
> @@ -685,7 +727,7 @@ static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
> struct qcom_scm_res res;
> int ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
>
> return ret ? : res.result[0];
> }
> @@ -725,8 +767,7 @@ int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val)
> struct qcom_scm_res res;
> int ret;
>
> -
> - ret = qcom_scm_call_atomic(__scm->dev, &desc, &res);
> + ret = qcom_scm_call_atomic(&desc, &res);
> if (ret >= 0)
> *val = res.result[0];
>
> @@ -745,7 +786,7 @@ int qcom_scm_io_writel(phys_addr_t addr, unsigned int val)
> .owner = ARM_SMCCC_OWNER_SIP,
> };
>
> - return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
> + return qcom_scm_call_atomic(&desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_io_writel);
>
> @@ -775,7 +816,7 @@ int qcom_scm_restore_sec_cfg(u32 device_id, u32 spare)
> struct qcom_scm_res res;
> int ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
>
> return ret ? : res.result[0];
> }
> @@ -793,7 +834,7 @@ int qcom_scm_iommu_secure_ptbl_size(u32 spare, size_t *size)
> struct qcom_scm_res res;
> int ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
>
> if (size)
> *size = res.result[0];
> @@ -816,7 +857,7 @@ int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare)
> };
> int ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, NULL);
> + ret = qcom_scm_call(&desc, NULL);
>
> /* the pg table has been initialized already, ignore the error */
> if (ret == -EPERM)
> @@ -837,7 +878,7 @@ int qcom_scm_iommu_set_cp_pool_size(u32 spare, u32 size)
> .owner = ARM_SMCCC_OWNER_SIP,
> };
>
> - return qcom_scm_call(__scm->dev, &desc, NULL);
> + return qcom_scm_call(&desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_iommu_set_cp_pool_size);
>
> @@ -859,7 +900,7 @@ int qcom_scm_mem_protect_video_var(u32 cp_start, u32 cp_size,
> };
> struct qcom_scm_res res;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
>
> return ret ? : res.result[0];
> }
> @@ -887,7 +928,7 @@ static int __qcom_scm_assign_mem(struct device *dev, phys_addr_t mem_region,
> };
> struct qcom_scm_res res;
>
> - ret = qcom_scm_call(dev, &desc, &res);
> + ret = __qcom_scm_call(dev, &desc, &res);
>
> return ret ? : res.result[0];
> }
> @@ -1004,7 +1045,7 @@ int qcom_scm_ocmem_lock(enum qcom_scm_ocmem_client id, u32 offset, u32 size,
> .arginfo = QCOM_SCM_ARGS(4),
> };
>
> - return qcom_scm_call(__scm->dev, &desc, NULL);
> + return qcom_scm_call(&desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_ocmem_lock);
>
> @@ -1027,7 +1068,7 @@ int qcom_scm_ocmem_unlock(enum qcom_scm_ocmem_client id, u32 offset, u32 size)
> .arginfo = QCOM_SCM_ARGS(3),
> };
>
> - return qcom_scm_call(__scm->dev, &desc, NULL);
> + return qcom_scm_call(&desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_ocmem_unlock);
>
> @@ -1068,7 +1109,7 @@ int qcom_scm_ice_invalidate_key(u32 index)
> .owner = ARM_SMCCC_OWNER_SIP,
> };
>
> - return qcom_scm_call(__scm->dev, &desc, NULL);
> + return qcom_scm_call(&desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_ice_invalidate_key);
>
> @@ -1129,7 +1170,7 @@ int qcom_scm_ice_set_key(u32 index, const u8 *key, u32 key_size,
> memcpy(keybuf, key, key_size);
> desc.args[1] = key_phys;
>
> - ret = qcom_scm_call(__scm->dev, &desc, NULL);
> + ret = qcom_scm_call(&desc, NULL);
>
> memzero_explicit(keybuf, key_size);
>
> @@ -1198,7 +1239,7 @@ int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp)
> if (ret)
> return ret;
>
> - ret = qcom_scm_call(__scm->dev, &desc, &res);
> + ret = qcom_scm_call(&desc, &res);
> *resp = res.result[0];
>
> qcom_scm_clk_disable();
> @@ -1219,7 +1260,7 @@ int qcom_scm_iommu_set_pt_format(u32 sec_id, u32 ctx_num, u32 pt_fmt)
> .owner = ARM_SMCCC_OWNER_SIP,
> };
>
> - return qcom_scm_call(__scm->dev, &desc, NULL);
> + return qcom_scm_call(&desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_iommu_set_pt_format);
>
> @@ -1234,8 +1275,7 @@ int qcom_scm_qsmmu500_wait_safe_toggle(bool en)
> .owner = ARM_SMCCC_OWNER_SIP,
> };
>
> -
> - return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
> + return qcom_scm_call_atomic(&desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_qsmmu500_wait_safe_toggle);
>
> @@ -1255,7 +1295,7 @@ int qcom_scm_lmh_profile_change(u32 profile_id)
> .owner = ARM_SMCCC_OWNER_SIP,
> };
>
> - return qcom_scm_call(__scm->dev, &desc, NULL);
> + return qcom_scm_call(&desc, NULL);
> }
> EXPORT_SYMBOL(qcom_scm_lmh_profile_change);
>
> @@ -1290,7 +1330,7 @@ int qcom_scm_lmh_dcvsh(u32 payload_fn, u32 payload_reg, u32 payload_val,
>
> desc.args[0] = payload_phys;
>
> - ret = qcom_scm_call(__scm->dev, &desc, NULL);
> + ret = __qcom_scm_call(__scm->dev, &desc, NULL);
>
> dma_free_coherent(__scm->dev, payload_size, payload_buf, payload_phys);
> return ret;
> diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h
> index e6e512bd57d1..87eb726be7d0 100644
> --- a/drivers/firmware/qcom_scm.h
> +++ b/drivers/firmware/qcom_scm.h
> @@ -13,53 +13,6 @@ enum qcom_scm_convention {
>
> extern enum qcom_scm_convention qcom_scm_convention;
>
> -#define MAX_QCOM_SCM_ARGS 10
> -#define MAX_QCOM_SCM_RETS 3
> -
> -enum qcom_scm_arg_types {
> - QCOM_SCM_VAL,
> - QCOM_SCM_RO,
> - QCOM_SCM_RW,
> - QCOM_SCM_BUFVAL,
> -};
> -
> -#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\
> - (((a) & 0x3) << 4) | \
> - (((b) & 0x3) << 6) | \
> - (((c) & 0x3) << 8) | \
> - (((d) & 0x3) << 10) | \
> - (((e) & 0x3) << 12) | \
> - (((f) & 0x3) << 14) | \
> - (((g) & 0x3) << 16) | \
> - (((h) & 0x3) << 18) | \
> - (((i) & 0x3) << 20) | \
> - (((j) & 0x3) << 22) | \
> - ((num) & 0xf))
> -
> -#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
> -
> -
> -/**
> - * struct qcom_scm_desc
> - * @arginfo: Metadata describing the arguments in args[]
> - * @args: The array of arguments for the secure syscall
> - */
> -struct qcom_scm_desc {
> - u32 svc;
> - u32 cmd;
> - u32 arginfo;
> - u64 args[MAX_QCOM_SCM_ARGS];
> - u32 owner;
> -};
> -
> -/**
> - * struct qcom_scm_res
> - * @result: The values returned by the secure syscall
> - */
> -struct qcom_scm_res {
> - u64 result[MAX_QCOM_SCM_RETS];
> -};
> -
> int qcom_scm_wait_for_wq_completion(u32 wq_ctx);
> int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending);
>
> diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h
> index 1e449a5d7f5c..162746467c22 100644
> --- a/include/linux/firmware/qcom/qcom_scm.h
> +++ b/include/linux/firmware/qcom/qcom_scm.h
> @@ -11,6 +11,55 @@
>
> #include <dt-bindings/firmware/qcom,scm.h>
>
> +#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\
> + (((a) & 0x3) << 4) | \
> + (((b) & 0x3) << 6) | \
> + (((c) & 0x3) << 8) | \
> + (((d) & 0x3) << 10) | \
> + (((e) & 0x3) << 12) | \
> + (((f) & 0x3) << 14) | \
> + (((g) & 0x3) << 16) | \
> + (((h) & 0x3) << 18) | \
> + (((i) & 0x3) << 20) | \
> + (((j) & 0x3) << 22) | \
> + ((num) & 0xf))
> +
> +#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
> +
> +#define MAX_QCOM_SCM_ARGS 10
> +#define MAX_QCOM_SCM_RETS 3
> +
> +enum qcom_scm_arg_types {
> + QCOM_SCM_VAL,
> + QCOM_SCM_RO,
> + QCOM_SCM_RW,
> + QCOM_SCM_BUFVAL,
> +};
> +
> +/**
> + * struct qcom_scm_desc - SCM call descriptor.
> + * @arginfo: Metadata describing the arguments in args[]
> + * @args: The array of arguments for the secure syscall
> + */
> +struct qcom_scm_desc {
> + u32 svc;
> + u32 cmd;
> + u32 arginfo;
> + u64 args[MAX_QCOM_SCM_ARGS];
> + u32 owner;
> +};
> +
> +/**
> + * struct qcom_scm_res - SCM call response.
> + * @result: The values returned by the secure syscall
> + */
> +struct qcom_scm_res {
> + u64 result[MAX_QCOM_SCM_RETS];
> +};
> +
> +int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res);
> +int qcom_scm_call_atomic(const struct qcom_scm_desc *desc, struct qcom_scm_res *res);
> +
> #define QCOM_SCM_VERSION(major, minor) (((major) << 16) | ((minor) & 0xFF))
> #define QCOM_SCM_CPU_PWR_DOWN_L2_ON 0x0
> #define QCOM_SCM_CPU_PWR_DOWN_L2_OFF 0x1
--
With best wishes
Dmitry
On 05/03/2023 04:21, Maximilian Luz wrote:
> Add support for SCM calls to Secure OS and the Secure Execution
> Environment (SEE) residing in the TrustZone (TZ) via the QSEECOM
> interface. This allows communication with Secure/TZ applications, for
> example 'uefisecapp' managing access to UEFI variables.
>
> The interface is managed by a platform device to ensure correct lifetime
> and establish a device link to the Qualcomm SCM device.
>
> While this patch introduces only a very basic interface without the more
> advanced features (such as re-entrant and blocking SCM calls and
> listeners/callbacks), this is enough to talk to the aforementioned
> 'uefisecapp'.
>
> Signed-off-by: Maximilian Luz <[email protected]>
> ---
>
> Changes in v3:
> - Rebase ontop of latest qcom_scm changes (qcom_scm.h moved).
> - Move qcom_qseecom.h in accordance with qcom_scm.
>
> Changes in v2:
> - Bind the interface to a device.
> - Establish a device link to the SCM device to ensure proper ordering.
> - Register client apps as child devices instead of requiring them to be
> specified in the device tree.
> - Rename (qctree -> qseecom) to allow differentiation between old
> (qseecom) and new (smcinvoke) interfaces to the trusted execution
> environment.
>
> ---
> MAINTAINERS | 7 +
> drivers/firmware/Kconfig | 15 +
> drivers/firmware/Makefile | 1 +
> drivers/firmware/qcom_qseecom.c | 314 +++++++++++++++++++++
> include/linux/firmware/qcom/qcom_qseecom.h | 190 +++++++++++++
> 5 files changed, 527 insertions(+)
> create mode 100644 drivers/firmware/qcom_qseecom.c
> create mode 100644 include/linux/firmware/qcom/qcom_qseecom.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9201967d198d..1545914a592c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17380,6 +17380,13 @@ F: Documentation/networking/device_drivers/cellular/qualcomm/rmnet.rst
> F: drivers/net/ethernet/qualcomm/rmnet/
> F: include/linux/if_rmnet.h
>
> +QUALCOMM SECURE EXECUTION ENVIRONMENT COMMUNICATION DRIVER
> +M: Maximilian Luz <[email protected]>
> +L: [email protected]
> +S: Maintained
> +F: drivers/firmware/qcom_qseecom.c
> +F: include/linux/firmware/qcom/qcom_qseecom.h
> +
> QUALCOMM TSENS THERMAL DRIVER
> M: Amit Kucheria <[email protected]>
> M: Thara Gopinath <[email protected]>
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
> index b59e3041fd62..22eec0835abf 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -226,6 +226,21 @@ config QCOM_SCM_DOWNLOAD_MODE_DEFAULT
>
> Say Y here to enable "download mode" by default.
>
> +config QCOM_QSEECOM
> + tristate "Qualcomm QSEECOM interface driver"
> + select MFD_CORE
> + select QCOM_SCM
> + help
> + Various Qualcomm SoCs have a Secure Execution Environment (SEE) running
> + in the Trust Zone. This module provides an interface to that via the
> + QSEECOM mechanism, using SCM calls.
> +
> + The QSEECOM interface allows, among other things, access to applications
> + running in the SEE. An example of such an application is 'uefisecapp',
> + which is required to access UEFI variables on certain systems.
> +
> + Select M or Y here to enable the QSEECOM interface driver.
> +
> config SYSFB
> bool
> select BOOT_VESA_SUPPORT
> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
> index 28fcddcd688f..aa48e0821b7d 100644
> --- a/drivers/firmware/Makefile
> +++ b/drivers/firmware/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
> obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
> obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
> qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
> +obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
> obj-$(CONFIG_SYSFB) += sysfb.o
> obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o
> obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
> diff --git a/drivers/firmware/qcom_qseecom.c b/drivers/firmware/qcom_qseecom.c
> new file mode 100644
> index 000000000000..efa5b115b2f1
> --- /dev/null
> +++ b/drivers/firmware/qcom_qseecom.c
> @@ -0,0 +1,314 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Interface driver for the Qualcomm Secure Execution Environment (SEE) /
> + * TrustZone OS (TzOS). Manages communication via the QSEECOM interface, using
> + * Secure Channel Manager (SCM) calls.
> + *
> + * Copyright (C) 2023 Maximilian Luz <[email protected]>
> + */
> +
> +#include <asm/barrier.h>
> +#include <linux/device.h>
> +#include <linux/firmware/qcom/qcom_scm.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/string.h>
> +
> +#include <linux/firmware/qcom/qcom_qseecom.h>
> +
> +
> +/* -- Secure-OS SCM call interface. ----------------------------------------- */
> +
> +static int __qseecom_scm_call(const struct qcom_scm_desc *desc,
> + struct qseecom_scm_resp *res)
> +{
> + struct qcom_scm_res scm_res = {};
> + int status;
> +
> + status = qcom_scm_call(desc, &scm_res);
> +
> + res->status = scm_res.result[0];
> + res->resp_type = scm_res.result[1];
> + res->data = scm_res.result[2];
> +
> + if (status)
> + return status;
> +
> + return 0;
> +}
> +
> +/**
> + * qseecom_scm_call() - Perform a QSEECOM SCM call.
> + * @qsee: The QSEECOM device.
> + * @desc: SCM call descriptor.
> + * @res: SCM call response (output).
> + *
> + * Performs the QSEECOM SCM call described by @desc, returning the response in
> + * @rsp.
> + *
> + * Return: Returns zero on success, nonzero on failure.
> + */
> +int qseecom_scm_call(struct qseecom_device *qsee, const struct qcom_scm_desc *desc,
> + struct qseecom_scm_resp *res)
> +{
> + int status;
> +
> + /*
> + * Note: Multiple QSEECOM SCM calls should not be executed same time,
> + * so lock things here. This needs to be extended to callback/listener
> + * handling when support for that is implemented.
> + */
> +
> + mutex_lock(&qsee->scm_call_lock);
> + status = __qseecom_scm_call(desc, res);
> + mutex_unlock(&qsee->scm_call_lock);
> +
> + dev_dbg(qsee->dev, "%s: owner=%x, svc=%x, cmd=%x, status=%lld, type=%llx, data=%llx",
> + __func__, desc->owner, desc->svc, desc->cmd, res->status,
> + res->resp_type, res->data);
> +
> + if (status) {
> + dev_err(qsee->dev, "qcom_scm_call failed with error %d\n", status);
> + return status;
> + }
> +
> + /*
> + * TODO: Handle incomplete and blocked calls:
> + *
> + * Incomplete and blocked calls are not supported yet. Some devices
> + * and/or commands require those, some don't. Let's warn about them
> + * prominently in case someone attempts to try these commands with a
> + * device/command combination that isn't supported yet.
> + */
> + WARN_ON(res->status == QSEECOM_RESULT_INCOMPLETE);
> + WARN_ON(res->status == QSEECOM_RESULT_BLOCKED_ON_LISTENER);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(qseecom_scm_call);
> +
> +
> +/* -- Secure App interface. ------------------------------------------------- */
> +
> +/**
> + * qseecom_app_get_id() - Query the app ID for a given SEE app name.
> + * @qsee: The QSEECOM device.
> + * @app_name: The name of the app.
> + * @app_id: The returned app ID.
> + *
> + * Query and return the application ID of the SEE app identified by the given
> + * name. This returned ID is the unique identifier of the app required for
> + * subsequent communication.
> + *
> + * Return: Returns zero on success, nonzero on failure. Returns -ENOENT if the
> + * app has not been loaded or could not be found.
> + */
> +int qseecom_app_get_id(struct qseecom_device *qsee, const char *app_name, u32 *app_id)
> +{
> + unsigned long name_buf_size = QSEECOM_MAX_APP_NAME_SIZE;
> + unsigned long app_name_len = strlen(app_name);
> + struct qcom_scm_desc desc = {};
> + struct qseecom_scm_resp res = {};
> + dma_addr_t name_buf_phys;
> + char *name_buf;
> + int status;
> +
> + if (app_name_len >= name_buf_size)
> + return -EINVAL;
> +
> + name_buf = kzalloc(name_buf_size, GFP_KERNEL);
> + if (!name_buf)
> + return -ENOMEM;
> +
> + memcpy(name_buf, app_name, app_name_len);
> +
> + name_buf_phys = dma_map_single(qsee->dev, name_buf, name_buf_size, DMA_TO_DEVICE);
> + if (dma_mapping_error(qsee->dev, name_buf_phys)) {
> + kfree(name_buf);
> + dev_err(qsee->dev, "failed to map dma address\n");
> + return -EFAULT;
> + }
> +
> + desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
> + desc.svc = QSEECOM_TZ_SVC_APP_MGR;
> + desc.cmd = 0x03;
> + desc.arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_RW, QCOM_SCM_VAL);
> + desc.args[0] = name_buf_phys;
> + desc.args[1] = app_name_len;
> +
> + status = qseecom_scm_call(qsee, &desc, &res);
> + dma_unmap_single(qsee->dev, name_buf_phys, name_buf_size, DMA_TO_DEVICE);
> + kfree(name_buf);
> +
> + if (status)
> + return status;
> +
> + if (res.status != QSEECOM_RESULT_SUCCESS)
> + return -ENOENT;
> +
> + *app_id = res.data;
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(qseecom_app_get_id);
> +
> +/**
> + * qseecom_app_send() - Send to and receive data from a given SEE app.
> + * @qsee: The QSEECOM device.
> + * @app_id: The ID of the app to communicate with.
> + * @req: DMA region of the request sent to the app.
> + * @rsp: DMA region of the response returned by the app.
> + *
> + * Sends a request to the SEE app identified by the given ID and read back its
> + * response. The caller must provide two DMA memory regions, one for the
> + * request and one for the response, and fill out the @req region with the
> + * respective (app-specific) request data. The SEE app reads this and returns
> + * its response in the @rsp region.
> + *
> + * Return: Returns zero on success, nonzero on failure.
> + */
> +int qseecom_app_send(struct qseecom_device *qsee, u32 app_id, struct qseecom_dma *req,
> + struct qseecom_dma *rsp)
> +{
> + struct qseecom_scm_resp res = {};
> + int status;
> +
> + struct qcom_scm_desc desc = {
> + .owner = QSEECOM_TZ_OWNER_TZ_APPS,
> + .svc = QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER,
> + .cmd = 0x01,
> + .arginfo = QCOM_SCM_ARGS(5, QCOM_SCM_VAL,
> + QCOM_SCM_RW, QCOM_SCM_VAL,
> + QCOM_SCM_RW, QCOM_SCM_VAL),
> + .args[0] = app_id,
> + .args[1] = req->phys,
> + .args[2] = req->size,
> + .args[3] = rsp->phys,
> + .args[4] = rsp->size,
> + };
> +
> + /* Make sure the request is fully written before sending it off. */
> + dma_wmb();
> +
> + status = qseecom_scm_call(qsee, &desc, &res);
> +
> + /* Make sure we don't attempt any reads before the SCM call is done. */
> + dma_rmb();
> +
> + if (status)
> + return status;
> +
> + if (res.status != QSEECOM_RESULT_SUCCESS)
> + return -EIO;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(qseecom_app_send);
> +
> +
> +/* -- Platform specific data. ----------------------------------------------- */
> +
> +struct qseecom_data {
> + const struct mfd_cell *cells;
> + int num_cells;
> +};
> +
> +static const struct of_device_id qseecom_dt_match[] = {
> + { .compatible = "qcom,qseecom-sc8280xp", },
> + { .compatible = "qcom,qseecom", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, qseecom_dt_match);
> +
> +
> +/* -- Driver setup. --------------------------------------------------------- */
> +
> +static int qseecom_setup_scm_link(struct platform_device *pdev)
> +{
> + const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;
> + struct platform_device *scm_dev;
> + struct device_node *scm_node;
> + struct device_link *link;
> + int status = 0;
> +
> + if (!pdev->dev.of_node)
> + return -ENODEV;
> +
> + /* Find the SCM device. */
> + scm_node = of_parse_phandle(pdev->dev.of_node, "qcom,scm", 0);
> + if (!scm_node)
> + return -ENOENT;
> +
> + scm_dev = of_find_device_by_node(scm_node);
> + if (!scm_dev) {
> + status = -ENODEV;
> + goto put;
> + }
> +
> + /* Establish the device link. */
> + link = device_link_add(&pdev->dev, &scm_dev->dev, flags);
> + if (!link) {
> + status = -EINVAL;
> + goto put;
> + }
> +
> + /* Make sure SCM has a driver bound, otherwise defer probe. */
> + if (link->supplier->links.status != DL_DEV_DRIVER_BOUND) {
> + status = -EPROBE_DEFER;
> + goto put;
> + }
> +
> +put:
> + of_node_put(scm_node);
> + return status;
> +}
> +
> +static int qseecom_probe(struct platform_device *pdev)
> +{
> + const struct qseecom_data *data;
> + struct qseecom_device *qsee;
> + int status;
> +
> + /* Get platform data. */
> + data = of_device_get_match_data(&pdev->dev);
> +
> + /* Set up device link. */
> + status = qseecom_setup_scm_link(pdev);
> + if (status)
> + return status;
> +
> + /* Set up QSEECOM device. */
> + qsee = devm_kzalloc(&pdev->dev, sizeof(*qsee), GFP_KERNEL);
> + if (!qsee)
> + return -ENOMEM;
> +
> + qsee->dev = &pdev->dev;
> + mutex_init(&qsee->scm_call_lock);
> +
> + platform_set_drvdata(pdev, qsee);
> +
> + /* Add child devices. */
> + if (data) {
> + status = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, data->cells,
> + data->num_cells, NULL, 0, NULL);
> + }
> +
> + return status;
> +}
> +
> +static struct platform_driver qseecom_driver = {
> + .probe = qseecom_probe,
> + .driver = {
> + .name = "qcom_qseecom",
> + .of_match_table = qseecom_dt_match,
> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> + },
> +};
> +module_platform_driver(qseecom_driver);
> +
> +MODULE_AUTHOR("Maximilian Luz <[email protected]>");
> +MODULE_DESCRIPTION("Driver for Qualcomm QSEECOM secure OS and secure application interface");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/firmware/qcom/qcom_qseecom.h b/include/linux/firmware/qcom/qcom_qseecom.h
> new file mode 100644
> index 000000000000..5bd9371a92f7
> --- /dev/null
> +++ b/include/linux/firmware/qcom/qcom_qseecom.h
> @@ -0,0 +1,190 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Interface driver for the Qualcomm Secure Execution Environment (SEE) /
> + * TrustZone OS (TzOS). Manages communication via the QSEECOM interface, using
> + * Secure Channel Manager (SCM) calls.
> + *
> + * Copyright (C) 2023 Maximilian Luz <[email protected]>
> + */
> +
> +#ifndef _LINUX_QCOM_QSEECOM_H
> +#define _LINUX_QCOM_QSEECOM_H
> +
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/firmware/qcom/qcom_scm.h>
> +#include <linux/kernel.h>
> +#include <linux/mm.h>
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +
> +
> +/* -- DMA helpers. ---------------------------------------------------------- */
> +
> +/* DMA requirements for QSEECOM SCM calls. */
> +#define QSEECOM_DMA_ALIGNMENT 8
> +#define QSEECOM_DMA_ALIGN(ptr) ALIGN(ptr, QSEECOM_DMA_ALIGNMENT)
> +
> +/**
> + * struct qseecom_dma - DMA memory region.
> + * @size: Size of the memory region, in bytes.
> + * @virt: Pointer / virtual address to the memory, accessible by the kernel.
> + * @phys: Physical address of the memory region.
> + */
> +struct qseecom_dma {
> + unsigned long size;
size_t ?
> + void *virt;
> + dma_addr_t phys;
> +};
Do we really need this wrapper and the wrappers bellow? They look like a
pure syntax sugar for me, hiding the dma functions from the user.
> +
> +/**
> + * qseecom_dma_alloc() - Allocate a DMA-able memory region suitable for QSEECOM
> + * SCM calls.
> + * @dev: The device used for DMA memory allocation.
> + * @dma: Where to write the allocated memory addresses and size to.
> + * @size: Minimum size of the memory to be allocated.
> + * @gfp: Flags used for allocation.
> + *
> + * Allocate a DMA-able memory region suitable for interaction with SEE
> + * services/applications and the TzOS. The provided size is treated as the
> + * minimum required size and rounded up, if necessary. The actually allocated
> + * memory region will be stored in @dma. Allocated memory must be freed via
> + * qseecom_dma_free().
> + *
> + * Return: Returns zero on success, -ENOMEM on allocation failure.
> + */
> +static inline int qseecom_dma_alloc(struct device *dev, struct qseecom_dma *dma,
> + unsigned long size, gfp_t gfp)
size_t size
gfp is not used
> +{
> + size = PAGE_ALIGN(size);
> +
> + dma->virt = dma_alloc_coherent(dev, size, &dma->phys, GFP_KERNEL);
> + if (!dma->virt)
> + return -ENOMEM;
> +
> + dma->size = size;
> + return 0;
> +}
> +
> +/**
> + * qseecom_dma_free() - Free a DMA memory region.
> + * @dev: The device used for allocation.
> + * @dma: The DMA region to be freed.
> + *
> + * Free a DMA region previously allocated via qseecom_dma_alloc(). Note that
> + * freeing sub-regions is not supported.
> + */
> +static inline void qseecom_dma_free(struct device *dev, struct qseecom_dma *dma)
> +{
> + dma_free_coherent(dev, dma->size, dma->virt, dma->phys);
> +}
> +
> +/**
> + * qseecom_dma_realloc() - Re-allocate DMA memory region with the requested size.
> + * @dev: The device used for allocation.
> + * @dma: The region descriptor to be updated.
> + * @size: The new requested size.
> + * @gfp: Flags used for allocation.
> + *
> + * Re-allocates a DMA memory region suitable for QSEECOM SCM calls to fit the
> + * requested amount of bytes, if necessary. Does nothing if the provided region
> + * already has enough space to store the requested data.
> + *
> + * See qseecom_dma_alloc() for details.
> + *
> + * Return: Returns zero on success, -ENOMEM on allocation failure.
> + */
> +static inline int qseecom_dma_realloc(struct device *dev, struct qseecom_dma *dma,
> + unsigned long size, gfp_t gfp)
> +{
> + if (PAGE_ALIGN(size) <= dma->size)
> + return 0;
> +
> + qseecom_dma_free(dev, dma);
> + return qseecom_dma_alloc(dev, dma, size, gfp);
> +}
I'll comment on this function when commenting patch 4.
> +
> +/**
> + * qseecom_dma_aligned() - Create a aligned DMA memory sub-region suitable for
> + * QSEECOM SCM calls.
> + * @base: Base DMA memory region, in which the new region will reside.
> + * @out: Descriptor to store the aligned sub-region in.
> + * @offset: The offset inside base region at which to place the new sub-region.
> + *
> + * Creates an aligned DMA memory region suitable for QSEECOM SCM calls at or
> + * after the given offset. The size of the sub-region will be set to the
> + * remaining size in the base region after alignment, i.e., the end of the
> + * sub-region will be equal the end of the base region.
> + *
> + * Return: Returns zero on success or -EINVAL if the new aligned memory address
> + * would point outside the base region.
> + */
> +static inline int qseecom_dma_aligned(const struct qseecom_dma *base, struct qseecom_dma *out,
> + unsigned long offset)
> +{
> + void *aligned = (void *)QSEECOM_DMA_ALIGN((uintptr_t)base->virt + offset);
> +
> + if (aligned - base->virt > base->size)
> + return -EINVAL;
> +
> + out->virt = aligned;
> + out->phys = base->phys + (out->virt - base->virt);
> + out->size = base->size - (out->virt - base->virt);
> +
> + return 0;
> +}
> +
> +
> +/* -- Common interface. ----------------------------------------------------- */
> +
> +struct qseecom_device {
> + struct device *dev;
> + struct mutex scm_call_lock; /* Guards QSEECOM SCM calls. */
There can be only one instance of the qseecom call infrastructure. Make
this mutex static in the qcom_scm.c
> +};
> +
> +
> +/* -- Secure-OS SCM call interface. ----------------------------------------- */
> +
> +#define QSEECOM_TZ_OWNER_TZ_APPS 48
> +#define QSEECOM_TZ_OWNER_QSEE_OS 50
> +
> +#define QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER 0
> +#define QSEECOM_TZ_SVC_APP_MGR 1
> +
> +enum qseecom_scm_result {
> + QSEECOM_RESULT_SUCCESS = 0,
> + QSEECOM_RESULT_INCOMPLETE = 1,
> + QSEECOM_RESULT_BLOCKED_ON_LISTENER = 2,
> + QSEECOM_RESULT_FAILURE = 0xFFFFFFFF,
> +};
> +
> +enum qseecom_scm_resp_type {
> + QSEECOM_SCM_RES_APP_ID = 0xEE01,
> + QSEECOM_SCM_RES_QSEOS_LISTENER_ID = 0xEE02,
> +};
> +
> +/**
> + * struct qseecom_scm_resp - QSEECOM SCM call response.
> + * @status: Status of the SCM call. See &enum qseecom_scm_result.
> + * @resp_type: Type of the response. See &enum qseecom_scm_resp_type.
> + * @data: Response data. The type of this data is given in @resp_type.
> + */
> +struct qseecom_scm_resp {
> + u64 status;
> + u64 resp_type;
> + u64 data;
> +};
> +
> +int qseecom_scm_call(struct qseecom_device *qsee, const struct qcom_scm_desc *desc,
> + struct qseecom_scm_resp *res);
> +
> +
> +/* -- Secure App interface. ------------------------------------------------- */
> +
> +#define QSEECOM_MAX_APP_NAME_SIZE 64
> +
> +int qseecom_app_get_id(struct qseecom_device *qsee, const char *app_name, u32 *app_id);
> +int qseecom_app_send(struct qseecom_device *qsee, u32 app_id, struct qseecom_dma *req,
> + struct qseecom_dma *rsp);
I think that only these calls should be made public / available to other
modules. qseecom_scm_call also is an internal helper.
> +
> +#endif /* _LINUX_QCOM_QSEECOM_H */
--
With best wishes
Dmitry
On 05/03/2023 04:21, Maximilian Luz wrote:
> Add support for SCM calls to Secure OS and the Secure Execution
> Environment (SEE) residing in the TrustZone (TZ) via the QSEECOM
> interface. This allows communication with Secure/TZ applications, for
> example 'uefisecapp' managing access to UEFI variables.
>
> The interface is managed by a platform device to ensure correct lifetime
> and establish a device link to the Qualcomm SCM device.
>
> While this patch introduces only a very basic interface without the more
> advanced features (such as re-entrant and blocking SCM calls and
> listeners/callbacks), this is enough to talk to the aforementioned
> 'uefisecapp'.
>
> Signed-off-by: Maximilian Luz <[email protected]>
> ---
>
> Changes in v3:
> - Rebase ontop of latest qcom_scm changes (qcom_scm.h moved).
> - Move qcom_qseecom.h in accordance with qcom_scm.
>
> Changes in v2:
> - Bind the interface to a device.
> - Establish a device link to the SCM device to ensure proper ordering.
> - Register client apps as child devices instead of requiring them to be
> specified in the device tree.
> - Rename (qctree -> qseecom) to allow differentiation between old
> (qseecom) and new (smcinvoke) interfaces to the trusted execution
> environment.
>
> ---
> MAINTAINERS | 7 +
> drivers/firmware/Kconfig | 15 +
> drivers/firmware/Makefile | 1 +
> drivers/firmware/qcom_qseecom.c | 314 +++++++++++++++++++++
> include/linux/firmware/qcom/qcom_qseecom.h | 190 +++++++++++++
> 5 files changed, 527 insertions(+)
> create mode 100644 drivers/firmware/qcom_qseecom.c
> create mode 100644 include/linux/firmware/qcom/qcom_qseecom.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9201967d198d..1545914a592c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17380,6 +17380,13 @@ F: Documentation/networking/device_drivers/cellular/qualcomm/rmnet.rst
> F: drivers/net/ethernet/qualcomm/rmnet/
> F: include/linux/if_rmnet.h
>
> +QUALCOMM SECURE EXECUTION ENVIRONMENT COMMUNICATION DRIVER
> +M: Maximilian Luz <[email protected]>
> +L: [email protected]
> +S: Maintained
> +F: drivers/firmware/qcom_qseecom.c
> +F: include/linux/firmware/qcom/qcom_qseecom.h
> +
> QUALCOMM TSENS THERMAL DRIVER
> M: Amit Kucheria <[email protected]>
> M: Thara Gopinath <[email protected]>
> +
> +
> +/* -- Platform specific data. ----------------------------------------------- */
> +
> +struct qseecom_data {
> + const struct mfd_cell *cells;
The child qseecom devices are not platform devices, so MFD should not be
used here. Please use aux devices instead.
> + int num_cells;
> +};
> +
> +static const struct of_device_id qseecom_dt_match[] = {
> + { .compatible = "qcom,qseecom-sc8280xp", },
Forgot to mention, while doign review. There is no need for this compat
until you provide the actual data. Please move it to the patch 4.
> + { .compatible = "qcom,qseecom", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, qseecom_dt_match);
--
With best wishes
Dmitry
On 05/03/2023 04:21, Maximilian Luz wrote:
> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
> EFI variables cannot be accessed via the standard interface in EFI
> runtime mode. The respective functions return EFI_UNSUPPORTED. On these
> platforms, we instead need to talk to uefisecapp. This commit provides
> support for this and registers the respective efivars operations to
> access EFI variables from the kernel.
>
> Communication with uefisecapp follows the Qualcomm QSEECOM / Secure OS
> conventions via the respective SCM call interface. This is also the
> reason why variable access works normally while boot services are
> active. During this time, said SCM interface is managed by the boot
> services. When calling ExitBootServices(), the ownership is transferred
> to the kernel. Therefore, UEFI must not use that interface itself (as
> multiple parties accessing this interface at the same time may lead to
> complications) and cannot access variables for us.
>
> Signed-off-by: Maximilian Luz <[email protected]>
> ---
>
> Changes in v3:
> - No functional changes.
>
> Changes in v2:
> - Rename (qctree -> qseecom) to allow differentiation between old
> (qseecom) and new (smcinvoke) interfaces to the trusted execution
> environment.
>
> ---
> MAINTAINERS | 6 +
> drivers/firmware/Kconfig | 16 +
> drivers/firmware/Makefile | 1 +
> drivers/firmware/qcom_qseecom.c | 11 +-
> drivers/firmware/qcom_qseecom_uefisecapp.c | 746 +++++++++++++++++++++
> 5 files changed, 779 insertions(+), 1 deletion(-)
> create mode 100644 drivers/firmware/qcom_qseecom_uefisecapp.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ef1f806986e9..35d614de6bbc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17371,6 +17371,12 @@ S: Maintained
> F: Documentation/devicetree/bindings/mtd/qcom,nandc.yaml
> F: drivers/mtd/nand/raw/qcom_nandc.c
>
> +QUALCOMM QSEECOM UEFISECAPP DRIVER
> +M: Maximilian Luz <[email protected]>
> +L: [email protected]
> +S: Maintained
> +F: drivers/firmware/qcom_qseecom_uefisecapp.c
> +
> QUALCOMM RMNET DRIVER
> M: Subash Abhinov Kasiviswanathan <[email protected]>
> M: Sean Tranchetti <[email protected]>
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
> index 22eec0835abf..b9ad753a195b 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -241,6 +241,22 @@ config QCOM_QSEECOM
>
> Select M or Y here to enable the QSEECOM interface driver.
>
> +config QCOM_QSEECOM_UEFISECAPP
> + tristate "Qualcomm SEE UEFI Secure App client driver"
> + depends on QCOM_QSEECOM
> + depends on EFI
> + help
> + Various Qualcomm SoCs do not allow direct access to EFI variables.
> + Instead, these need to be accessed via the UEFI Secure Application
> + (uefisecapp), residing in the Secure Execution Environment (SEE).
> +
> + This module provides a client driver for uefisecapp, installing efivar
> + operations to allow the kernel accessing EFI variables, and via that also
> + provide user-space with access to EFI variables via efivarfs.
> +
> + Select M or Y here to provide access to EFI variables on the
> + aforementioned platforms.
> +
> config SYSFB
> bool
> select BOOT_VESA_SUPPORT
> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
> index aa48e0821b7d..d41b094a5e58 100644
> --- a/drivers/firmware/Makefile
> +++ b/drivers/firmware/Makefile
> @@ -21,6 +21,7 @@ obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
> obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
> qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
> obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
> +obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
> obj-$(CONFIG_SYSFB) += sysfb.o
> obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o
> obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
> diff --git a/drivers/firmware/qcom_qseecom.c b/drivers/firmware/qcom_qseecom.c
> index efa5b115b2f1..ae42341dce1c 100644
> --- a/drivers/firmware/qcom_qseecom.c
> +++ b/drivers/firmware/qcom_qseecom.c
> @@ -216,8 +216,17 @@ struct qseecom_data {
> int num_cells;
> };
>
> +static const struct mfd_cell qseecom_cells_sc8280xp[] = {
> + { .name = "qcom_qseecom_uefisecapp", },
> +};
> +
> +static const struct qseecom_data qseecom_data_sc8280xp = {
> + .cells = qseecom_cells_sc8280xp,
> + .num_cells = ARRAY_SIZE(qseecom_cells_sc8280xp),
> +};
> +
> static const struct of_device_id qseecom_dt_match[] = {
> - { .compatible = "qcom,qseecom-sc8280xp", },
> + { .compatible = "qcom,qseecom-sc8280xp", .data = &qseecom_data_sc8280xp },
Note: the SoC doesn't fully describe the list of avialble qseecom
applications. It depends on the particular vendor enabling or disabling
them. It might be better to turn qseecom into the bus, create a list of
possible app names and use qseecom_app_get_id() to check whether the
application is available or not. Then you can instantiate only available
devices and provide the app_id via struct qseecom_app_device wrapping
struct device:
struct qseecom_app_device {
struct device dev;
u32 app_id;
};
> { .compatible = "qcom,qseecom", },
> { }
> };
> diff --git a/drivers/firmware/qcom_qseecom_uefisecapp.c b/drivers/firmware/qcom_qseecom_uefisecapp.c
> new file mode 100644
> index 000000000000..8d772fe1f589
> --- /dev/null
> +++ b/drivers/firmware/qcom_qseecom_uefisecapp.c
> @@ -0,0 +1,746 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Client driver for Qualcomm UEFI Secure Application (qcom.tz.uefisecapp).
> + * Provides access to UEFI variables on platforms where they are secured by the
> + * aforementioned Secure Execution Environment (SEE) application.
> + *
> + * Copyright (C) 2023 Maximilian Luz <[email protected]>
> + */
> +
> +#include <linux/efi.h>
> +#include <linux/firmware/qcom/qcom_qseecom.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +
> +
> +/* -- UTF-16 helpers. ------------------------------------------------------- */
> +
> +static unsigned long utf16_strnlen(const efi_char16_t *str, unsigned long max)
> +{
> + size_t i;
> +
> + for (i = 0; *str != 0 && i < max; i++, str++) {
> + /* Do nothing, all is handled in the for statement. */
> + }
> +
> + return i;
> +}
> +
> +/**
> + * utf16_strsize() - Compute the number of bytes required to store a
> + * null-terminated UTF-16 string.
> + * @str: The string to compute the size for.
> + *
> + * Return: Returns the minimum number of bytes required to store the given
> + * null-terminated string, including its null-terminator.
> + */
> +static unsigned long utf16_strsize(const efi_char16_t *str)
> +{
> + return (utf16_strnlen(str, U32_MAX) + 1) * sizeof(str[0]);
> +}
> +
> +static unsigned long utf16_strlcpy(efi_char16_t *dst, const efi_char16_t *src, unsigned long size)
> +{
> + unsigned long actual = utf16_strnlen(src, size - 1);
> +
> + memcpy(dst, src, actual * sizeof(src[0]));
> + dst[actual] = 0;
> +
> + return actual;
> +}
> +
> +/**
> + * utf16_copy_to_buf() - Copy the given UTF-16 string to a buffer.
> + * @dst: Pointer to the output buffer
> + * @src: Pointer to the null-terminated UTF-16 string to be copied.
> + * @bytes: Maximum number of bytes to copy.
> + *
> + * Copies the given string to the given buffer, ensuring that the output buffer
> + * is not overrun and that the string in the output buffer will always be
> + * null-terminated.
> + *
> + * Return: Returns the length of the copied string, without null-terminator.
> + */
> +static unsigned long utf16_copy_to_buf(efi_char16_t *dst, const efi_char16_t *src,
> + unsigned long bytes)
> +{
> + return utf16_strlcpy(dst, src, bytes / sizeof(src[0]));
> +}
These functions seem generic enough, please move them to
lib/ucs2_string.c. Use the existing functions if they suit.
> +
> +
Double empty lines
> +/* -- Qualcomm "uefisecapp" interface definitions. -------------------------- */
> +
> +#define QSEE_UEFISEC_APP_NAME "qcom.tz.uefisecapp"
> +
> +#define QSEE_CMD_UEFI(x) (0x8000 | (x))
> +#define QSEE_CMD_UEFI_GET_VARIABLE QSEE_CMD_UEFI(0)
> +#define QSEE_CMD_UEFI_SET_VARIABLE QSEE_CMD_UEFI(1)
> +#define QSEE_CMD_UEFI_GET_NEXT_VARIABLE QSEE_CMD_UEFI(2)
> +#define QSEE_CMD_UEFI_QUERY_VARIABLE_INFO QSEE_CMD_UEFI(3)
> +
> +/**
> + * struct qsee_req_uefi_get_variable - Request for GetVariable command.
> + * @command_id: The ID of the command. Must be %QSEE_CMD_UEFI_GET_VARIABLE.
> + * @length: Length of the request in bytes, including this struct and any
> + * parameters (name, GUID) stored after it as well as any padding
> + * thereof for alignment.
> + * @name_offset: Offset from the start of this struct to where the variable
> + * name is stored (as utf-16 string), in bytes.
> + * @name_size: Size of the name parameter in bytes, including null-terminator.
> + * @guid_offset: Offset from the start of this struct to where the GUID
> + * parameter is stored, in bytes.
> + * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
> + * @data_size: Size of the output buffer, in bytes.
> + */
> +struct qsee_req_uefi_get_variable {
> + u32 command_id;
> + u32 length;
> + u32 name_offset;
> + u32 name_size;
> + u32 guid_offset;
> + u32 guid_size;
> + u32 data_size;
> +} __packed;
> +
> +/**
> + * struct qsee_rsp_uefi_get_variable - Response for GetVariable command.
> + * @command_id: The ID of the command. Should be %QSEE_CMD_UEFI_GET_VARIABLE.
> + * @length: Length of the response in bytes, including this struct and the
> + * returned data.
> + * @status: Status of this command.
> + * @attributes: EFI variable attributes.
> + * @data_offset: Offset from the start of this struct to where the data is
> + * stored, in bytes.
> + * @data_size: Size of the returned data, in bytes. In case status indicates
> + * that the buffer is too small, this will be the size required
> + * to store the EFI variable data.
> + */
> +struct qsee_rsp_uefi_get_variable {
> + u32 command_id;
> + u32 length;
> + u32 status;
> + u32 attributes;
> + u32 data_offset;
> + u32 data_size;
> +} __packed;
> +
> +/**
> + * struct qsee_req_uefi_set_variable - Request for the SetVariable command.
> + * @command_id: The ID of the command. Must be %QSEE_CMD_UEFI_SET_VARIABLE.
> + * @length: Length of the request in bytes, including this struct and any
> + * parameters (name, GUID, data) stored after it as well as any
> + * padding thereof required for alignment.
> + * @name_offset: Offset from the start of this struct to where the variable
> + * name is stored (as utf-16 string), in bytes.
> + * @name_size: Size of the name parameter in bytes, including null-terminator.
> + * @guid_offset: Offset from the start of this struct to where the GUID
> + * parameter is stored, in bytes.
> + * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
> + * @attributes: The EFI variable attributes to set for this variable.
> + * @data_offset: Offset from the start of this struct to where the EFI variable
> + * data is stored, in bytes.
> + * @data_size: Size of EFI variable data, in bytes.
> + *
> + */
> +struct qsee_req_uefi_set_variable {
> + u32 command_id;
> + u32 length;
> + u32 name_offset;
> + u32 name_size;
> + u32 guid_offset;
> + u32 guid_size;
> + u32 attributes;
> + u32 data_offset;
> + u32 data_size;
> +} __packed;
> +
> +/**
> + * struct qsee_rsp_uefi_set_variable - Response for the SetVariable command.
> + * @command_id: The ID of the command. Should be %QSEE_CMD_UEFI_SET_VARIABLE.
> + * @length: The length of this response, i.e. the size of this struct in
> + * bytes.
> + * @status: Status of this command.
> + * @_unknown1: Unknown response field.
> + * @_unknown2: Unknown response field.
> + */
> +struct qsee_rsp_uefi_set_variable {
> + u32 command_id;
> + u32 length;
> + u32 status;
> + u32 _unknown1;
> + u32 _unknown2;
> +} __packed;
> +
> +/**
> + * struct qsee_req_uefi_get_next_variable - Request for the
> + * GetNextVariableName command.
> + * @command_id: The ID of the command. Must be
> + * %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
> + * @length: Length of the request in bytes, including this struct and any
> + * parameters (name, GUID) stored after it as well as any padding
> + * thereof for alignment.
> + * @guid_offset: Offset from the start of this struct to where the GUID
> + * parameter is stored, in bytes.
> + * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
> + * @name_offset: Offset from the start of this struct to where the variable
> + * name is stored (as utf-16 string), in bytes.
> + * @name_size: Size of the name parameter in bytes, including null-terminator.
> + */
> +struct qsee_req_uefi_get_next_variable {
> + u32 command_id;
> + u32 length;
> + u32 guid_offset;
> + u32 guid_size;
> + u32 name_offset;
> + u32 name_size;
> +} __packed;
> +
> +/**
> + * struct qsee_rsp_uefi_get_next_variable - Response for the
> + * GetNextVariableName command.
> + * @command_id: The ID of the command. Should be
> + * %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
> + * @length: Length of the response in bytes, including this struct and any
> + * parameters (name, GUID) stored after it as well as any padding
> + * thereof for alignment.
> + * @status: Status of this command.
> + * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
> + * @name_offset: Offset from the start of this struct to where the variable
> + * name is stored (as utf-16 string), in bytes.
> + * @name_size: Size of the name parameter in bytes, including null-terminator.
> + */
> +struct qsee_rsp_uefi_get_next_variable {
> + u32 command_id;
> + u32 length;
> + u32 status;
> + u32 guid_offset;
> + u32 guid_size;
> + u32 name_offset;
> + u32 name_size;
> +} __packed;
> +
> +/**
> + * struct qsee_req_uefi_query_variable_info - Response for the
> + * GetNextVariableName command.
> + * @command_id: The ID of the command. Must be
> + * %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
> + * @length: The length of this request, i.e. the size of this struct in
> + * bytes.
> + * @attributes: The storage attributes to query the info for.
> + */
> +struct qsee_req_uefi_query_variable_info {
> + u32 command_id;
> + u32 length;
> + u32 attributes;
> +} __packed;
> +
> +/**
> + * struct qsee_rsp_uefi_query_variable_info - Response for the
> + * GetNextVariableName command.
> + * @command_id: The ID of the command. Must be
> + * %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
> + * @length: The length of this response, i.e. the size of this
> + * struct in bytes.
> + * @status: Status of this command.
> + * @_pad: Padding.
> + * @storage_space: Full storage space size, in bytes.
> + * @remaining_space: Free storage space available, in bytes.
> + * @max_variable_size: Maximum variable data size, in bytes.
> + */
> +struct qsee_rsp_uefi_query_variable_info {
> + u32 command_id;
> + u32 length;
> + u32 status;
> + u32 _pad;
> + u64 storage_space;
> + u64 remaining_space;
> + u64 max_variable_size;
> +} __packed;
> +
> +
> +/* -- UEFI app interface. --------------------------------------------------- */
> +
> +struct qcuefi_client {
> + struct device *dev;
> + struct qseecom_device *qsee;
> + struct qseecom_dma dma;
> + struct efivars efivars;
> + u32 app_id;
> +};
> +
> +static efi_status_t qsee_uefi_status_to_efi(u32 status)
> +{
> + u64 category = status & 0xf0000000;
> + u64 code = status & 0x0fffffff;
> +
> + return category << (BITS_PER_LONG - 32) | code;
> +}
> +
> +static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
> + const efi_guid_t *guid, u32 *attributes,
> + unsigned long *data_size, void *data)
> +{
> + struct qsee_req_uefi_get_variable *req_data;
> + struct qsee_rsp_uefi_get_variable *rsp_data;
> + struct qseecom_dma dma_req;
> + struct qseecom_dma dma_rsp;
> + unsigned long name_size = utf16_strsize(name);
> + unsigned long buffer_size = *data_size;
> + unsigned long size;
> + efi_status_t efi_status;
> + int status;
> +
> + /* Validation: We need a name and GUID. */
> + if (!name || !guid)
> + return EFI_INVALID_PARAMETER;
> +
> + /* Validation: We need a buffer if the buffer_size is nonzero. */
> + if (buffer_size && !data)
> + return EFI_INVALID_PARAMETER;
> +
> + /* Compute required size (upper limit with alignments). */
> + size = sizeof(*req_data) + sizeof(*guid) + name_size /* Inputs. */
> + + sizeof(*rsp_data) + buffer_size /* Outputs. */
> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
Do we need to pack everything into a single DMA buffer? Otherwise it
would be better to add qseecom_dma_alloc_aligned function, which will
take care of the alignment for a single data piece.
> +
> + /* Make sure we have enough DMA memory. */
> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
This looks like an antipattern. Please allocate the buffer before
querrying the data and free it afterwards.
> + if (status)
> + return EFI_OUT_OF_RESOURCES;
> +
> + /* Align request struct. */
> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
> + req_data = dma_req.virt;
> +
> + /* Set up request data. */
> + req_data->command_id = QSEE_CMD_UEFI_GET_VARIABLE;
> + req_data->data_size = buffer_size;
> + req_data->name_offset = sizeof(*req_data);
> + req_data->name_size = name_size;
> + req_data->guid_offset = QSEECOM_DMA_ALIGN(req_data->name_offset + name_size);
> + req_data->guid_size = sizeof(*guid);
> + req_data->length = req_data->guid_offset + req_data->guid_size;
> +
> + dma_req.size = req_data->length;
> +
> + /* Copy request parameters. */
> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, name_size);
> + memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
> +
> + /* Align response struct. */
> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
> + rsp_data = dma_rsp.virt;
> +
> + /* Perform SCM call. */
> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
> +
> + /* Check for errors and validate. */
> + if (status)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->command_id != QSEE_CMD_UEFI_GET_VARIABLE)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->status) {
> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
> + efi_status = qsee_uefi_status_to_efi(rsp_data->status);
> +
> + /* Update size and attributes in case buffer is too small. */
> + if (efi_status == EFI_BUFFER_TOO_SMALL) {
> + *data_size = rsp_data->data_size;
> + if (attributes)
> + *attributes = rsp_data->attributes;
> + }
> +
> + return efi_status;
> + }
> +
> + if (rsp_data->data_offset + rsp_data->data_size > rsp_data->length)
> + return EFI_DEVICE_ERROR;
> +
> + /* Set attributes and data size even if buffer is too small. */
> + *data_size = rsp_data->data_size;
> + if (attributes)
> + *attributes = rsp_data->attributes;
> +
> + /*
> + * If we have a buffer size of zero and no buffer, just return
> + * attributes and required size.
> + */
> + if (buffer_size == 0 && !data)
> + return EFI_SUCCESS;
> +
> + /* Validate output buffer size. */
> + if (buffer_size < rsp_data->data_size)
> + return EFI_BUFFER_TOO_SMALL;
> +
> + /* Copy to output buffer. Note: We're guaranteed to have one at this point. */
> + memcpy(data, dma_rsp.virt + rsp_data->data_offset, rsp_data->data_size);
> + return EFI_SUCCESS;
> +}
> +
> +static efi_status_t qsee_uefi_set_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
> + const efi_guid_t *guid, u32 attributes,
> + unsigned long data_size, const void *data)
> +{
> + struct qsee_req_uefi_set_variable *req_data;
> + struct qsee_rsp_uefi_set_variable *rsp_data;
> + struct qseecom_dma dma_req;
> + struct qseecom_dma dma_rsp;
> + unsigned long name_size = utf16_strsize(name);
> + unsigned long size;
> + int status;
> +
> + /* Validate inputs. */
> + if (!name || !guid)
> + return EFI_INVALID_PARAMETER;
> +
> + /*
> + * Make sure we have some data if data_size is nonzero. Note: Using a
> + * size of zero is valid and deletes the variable.
> + */
> + if (data_size && !data)
> + return EFI_INVALID_PARAMETER;
> +
> + /* Compute required size (upper limit with alignments). */
> + size = sizeof(*req_data) + name_size + sizeof(*guid) + data_size /* Inputs. */
> + + sizeof(*rsp_data) /* Outputs. */
> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
> +
> + /* Make sure we have enough DMA memory. */
> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
> + if (status)
> + return EFI_OUT_OF_RESOURCES;
> +
> + /* Align request struct. */
> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
> + req_data = dma_req.virt;
> +
> + /* Set up request data. */
> + req_data->command_id = QSEE_CMD_UEFI_SET_VARIABLE;
> + req_data->attributes = attributes;
> + req_data->name_offset = sizeof(*req_data);
> + req_data->name_size = name_size;
> + req_data->guid_offset = QSEECOM_DMA_ALIGN(req_data->name_offset + name_size);
> + req_data->guid_size = sizeof(*guid);
> + req_data->data_offset = req_data->guid_offset + req_data->guid_size;
> + req_data->data_size = data_size;
> + req_data->length = req_data->data_offset + data_size;
> +
> + /* Copy request parameters. */
> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, req_data->name_size);
> + memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
> +
> + if (data_size)
> + memcpy(dma_req.virt + req_data->data_offset, data, req_data->data_size);
> +
> + /* Align response struct. */
> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
> + rsp_data = dma_rsp.virt;
> +
> + /* Perform SCM call. */
> + dma_req.size = req_data->length;
> + dma_rsp.size = sizeof(*rsp_data);
> +
> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
> +
> + /* Check for errors and validate. */
> + if (status)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->command_id != QSEE_CMD_UEFI_SET_VARIABLE)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->status) {
> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
> + return qsee_uefi_status_to_efi(rsp_data->status);
> + }
> +
> + return EFI_SUCCESS;
> +}
> +
> +static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
> + unsigned long *name_size, efi_char16_t *name,
> + efi_guid_t *guid)
> +{
> + struct qsee_req_uefi_get_next_variable *req_data;
> + struct qsee_rsp_uefi_get_next_variable *rsp_data;
> + struct qseecom_dma dma_req;
> + struct qseecom_dma dma_rsp;
> + unsigned long size;
> + efi_status_t efi_status;
> + int status;
> +
> + /* We need some buffers. */
> + if (!name_size || !name || !guid)
> + return EFI_INVALID_PARAMETER;
> +
> + /* There needs to be at least a single null-character. */
> + if (*name_size == 0)
> + return EFI_INVALID_PARAMETER;
> +
> + /* Compute required size (upper limit with alignments). */
> + size = sizeof(*req_data) + sizeof(*guid) + *name_size /* Inputs. */
> + + sizeof(*rsp_data) + sizeof(*guid) + *name_size /* Outputs. */
> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
> +
> + /* Make sure we have enough DMA memory. */
> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
> + if (status)
> + return EFI_OUT_OF_RESOURCES;
> +
> + /* Align request struct. */
> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
> + req_data = dma_req.virt;
> +
> + /* Set up request data. */
> + req_data->command_id = QSEE_CMD_UEFI_GET_NEXT_VARIABLE;
> + req_data->guid_offset = QSEECOM_DMA_ALIGN(sizeof(*req_data));
> + req_data->guid_size = sizeof(*guid);
> + req_data->name_offset = req_data->guid_offset + req_data->guid_size;
> + req_data->name_size = *name_size;
> + req_data->length = req_data->name_offset + req_data->name_size;
> +
> + dma_req.size = req_data->length;
> +
> + /* Copy request parameters. */
> + memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, *name_size);
> +
> + /* Align response struct. */
> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
> + rsp_data = dma_rsp.virt;
> +
> + /* Perform SCM call. */
> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
> +
> + /* Check for errors and validate. */
> + if (status)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->command_id != QSEE_CMD_UEFI_GET_NEXT_VARIABLE)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->status) {
> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
> + efi_status = qsee_uefi_status_to_efi(rsp_data->status);
> +
> + /* Update size with required size in case buffer is too small. */
> + if (efi_status == EFI_BUFFER_TOO_SMALL)
> + *name_size = rsp_data->name_size;
> +
> + return efi_status;
> + }
> +
> + if (rsp_data->name_offset + rsp_data->name_size > rsp_data->length)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->guid_offset + rsp_data->guid_size > rsp_data->length)
> + return EFI_DEVICE_ERROR;
> +
> + if (rsp_data->name_size > *name_size) {
> + *name_size = rsp_data->name_size;
> + return EFI_BUFFER_TOO_SMALL;
> + }
> +
> + if (rsp_data->guid_size != sizeof(*guid))
> + return EFI_DEVICE_ERROR;
> +
> + /* Copy response fields. */
> + memcpy(guid, dma_rsp.virt + rsp_data->guid_offset, rsp_data->guid_size);
> + utf16_copy_to_buf(name, dma_rsp.virt + rsp_data->name_offset, rsp_data->name_size);
> + *name_size = rsp_data->name_size;
> +
> + return 0;
> +}
> +
> +
> +/* -- Global efivar interface. ---------------------------------------------- */
> +
> +static struct qcuefi_client *__qcuefi;
> +static DEFINE_MUTEX(__qcuefi_lock);
> +
> +static int qcuefi_set_reference(struct qcuefi_client *qcuefi)
> +{
> + mutex_lock(&__qcuefi_lock);
> +
> + if (qcuefi && __qcuefi) {
> + mutex_unlock(&__qcuefi_lock);
> + return -EEXIST;
> + }
> +
> + __qcuefi = qcuefi;
> +
> + mutex_unlock(&__qcuefi_lock);
> + return 0;
> +}
> +
> +static struct qcuefi_client *qcuefi_acquire(void)
Doesn't this need the __locks annotation? Generally I think it is
easier to inline these two functions, there is no particular need to
have such wrappers.
> +{
> + mutex_lock(&__qcuefi_lock);
> + return __qcuefi;
> +}
> +
> +static void qcuefi_release(void)
> +{
> + mutex_unlock(&__qcuefi_lock);
> +}
> +
> +static efi_status_t qcuefi_get_variable(efi_char16_t *name, efi_guid_t *vendor, u32 *attr,
> + unsigned long *data_size, void *data)
> +{
> + struct qcuefi_client *qcuefi;
> + efi_status_t status;
> +
> + qcuefi = qcuefi_acquire();
> + if (!qcuefi)
> + return EFI_NOT_READY;
> +
> + status = qsee_uefi_get_variable(qcuefi, name, vendor, attr, data_size, data);
> +
> + qcuefi_release();
> + return status;
> +}
> +
> +static efi_status_t qcuefi_set_variable(efi_char16_t *name, efi_guid_t *vendor,
> + u32 attr, unsigned long data_size, void *data)
> +{
> + struct qcuefi_client *qcuefi;
> + efi_status_t status;
> +
> + qcuefi = qcuefi_acquire();
> + if (!qcuefi)
> + return EFI_NOT_READY;
> +
> + status = qsee_uefi_set_variable(qcuefi, name, vendor, attr, data_size, data);
> +
> + qcuefi_release();
> + return status;
> +}
> +
> +static efi_status_t qcuefi_get_next_variable(unsigned long *name_size, efi_char16_t *name,
> + efi_guid_t *vendor)
> +{
> + struct qcuefi_client *qcuefi;
> + efi_status_t status;
> +
> + qcuefi = qcuefi_acquire();
> + if (!qcuefi)
> + return EFI_NOT_READY;
> +
> + status = qsee_uefi_get_next_variable(qcuefi, name_size, name, vendor);
> +
> + qcuefi_release();
> + return status;
> +}
> +
> +static const struct efivar_operations qcom_efivar_ops = {
> + .get_variable = qcuefi_get_variable,
> + .set_variable = qcuefi_set_variable,
> + .get_next_variable = qcuefi_get_next_variable,
> +};
> +
> +
> +/* -- Driver setup. --------------------------------------------------------- */
> +
> +static int qcom_uefisecapp_probe(struct platform_device *pdev)
> +{
> + struct qcuefi_client *qcuefi;
> + int status;
> +
> + /* Allocate driver data. */
> + qcuefi = devm_kzalloc(&pdev->dev, sizeof(*qcuefi), GFP_KERNEL);
> + if (!qcuefi)
> + return -ENOMEM;
> +
> + qcuefi->dev = &pdev->dev;
> +
> + /* We expect the parent to be the QSEECOM device. */
> + qcuefi->qsee = dev_get_drvdata(pdev->dev.parent);
> + if (!qcuefi->qsee)
> + return -EINVAL;
> +
> + /* Get application id for uefisecapp. */
> + status = qseecom_app_get_id(qcuefi->qsee, QSEE_UEFISEC_APP_NAME, &qcuefi->app_id);
> + if (status) {
> + dev_err(&pdev->dev, "failed to query app ID: %d\n", status);
> + return status;
> + }
> +
> + /* Set up DMA. One page should be plenty to start with. */
one page?
> + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) {
> + dev_warn(&pdev->dev, "no suitable DMA available\n");
> + return -EFAULT;
> + }
> +
> + status = qseecom_dma_alloc(&pdev->dev, &qcuefi->dma, PAGE_SIZE, GFP_KERNEL);
> + if (status)
> + return status;
> +
> + /* Register global reference. */
> + platform_set_drvdata(pdev, qcuefi);
> + status = qcuefi_set_reference(qcuefi);
> + if (status)
> + goto free;
> +
> + /* Register efivar ops. */
> + status = efivars_register(&qcuefi->efivars, &qcom_efivar_ops);
> + if (status)
> + goto clear_reference;
> +
> + return 0;
> +
> +clear_reference:
> + qcuefi_set_reference(NULL);
> +free:
> + qseecom_dma_free(qcuefi->dev, &qcuefi->dma);
> + return status;
> +}
> +
> +static int qcom_uefisecapp_remove(struct platform_device *pdev)
> +{
> + struct qcuefi_client *qcuefi = platform_get_drvdata(pdev);
> +
> + /* Unregister efivar ops. */
> + efivars_unregister(&qcuefi->efivars);
> +
> + /* Block on pending calls and unregister global reference. */
> + qcuefi_set_reference(NULL);
> +
> + /* Free remaining resources. */
> + qseecom_dma_free(qcuefi->dev, &qcuefi->dma);
> +
> + return 0;
> +}
> +
> +static struct platform_driver qcom_uefisecapp_driver = {
> + .probe = qcom_uefisecapp_probe,
> + .remove = qcom_uefisecapp_remove,
> + .driver = {
> + .name = "qcom_qseecom_uefisecapp",
> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> + },
> +};
> +module_platform_driver(qcom_uefisecapp_driver);
> +
> +MODULE_AUTHOR("Maximilian Luz <[email protected]>");
> +MODULE_DESCRIPTION("Client driver for Qualcomm SEE UEFI Secure App");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:qcom_qseecom_uefisecapp");
--
With best wishes
Dmitry
On 07/03/2023 15:23, Dmitry Baryshkov wrote:
>
>> Make qcom_scm_call, qcom_scm_call_atomic and associated types accessible
>> to other modules.
>
> Generally all the qcom_scm calls are a part of qcom_scm.c. I think it is
> better to make qseecom_scm_call a part qcom_scm.c (as we were previously
> doing) rather than exporting the core function.
>
Other big issue I see in exporting qcom_scm_call() is that there is
danger of misuse of this api as this could lead to a path where new apis
and its payloads can come directly from userspace via a rogue/hacking
modules. This will bypass scm layer completely within kernel.
--srini
> If you wish to limit the kernel bloat, you can split the qcom_scm into
> per-driver backend and add Kconfig symbols to limit the impact. However
> I think that these functions are pretty small to justify the effort.
>
>>
>> Signed-off-by: Maximilian Luz <[email protected]>
Hi,
thanks for the review.
On 3/7/23 16:23, Dmitry Baryshkov wrote:
> On 05/03/2023 04:21, Maximilian Luz wrote:
>> Make qcom_scm_call, qcom_scm_call_atomic and associated types accessible
>> to other modules.
>
> Generally all the qcom_scm calls are a part of qcom_scm.c. I think it is better to make qseecom_scm_call a part qcom_scm.c (as we were previously doing) rather than exporting the core function.
Only qseecom_scm_call() or the app-related functions as well? I'm asking
since qseecom_scm_call() is already quite generic and would still result
in moving the core structs to the header.
The main reason I did move that to an external module was the
possibility of reentrant/incomplete and blocked calls, which seem to be
a possibility with that function. My idea was to move this to a separate
driver, so we can deal with that more complex and qseecom specific stuff
there (in the future). Note that I can't implement that myself because
the Surface Pro X (sc8180x) that I'm working on doesn't seem to use that
"feature".
I guess we could move qseecom_scm_call() function to qcom_scm, then
still deal with that in qseecom_scm via some wrapper, if that's okay. A
downside of that is that we'd have to make sure that all callers of that
function deal with this stuff properly, or maybe even that qseecom_scm
is the only caller of that function (I don't know the specifics needed
to make incomplete/blocking calls work properly).
We can also move it to qcom_scm for now and then always deal with the
rest once we get to that point. All I want to say is: We should be aware
that these kinds of calls are a possibility on some systems and code
should deal with it (even if it's just warnings about it not being
implemented for now).
Let me know how you'd prefer me to handle this.
Regards,
Max
> If you wish to limit the kernel bloat, you can split the qcom_scm into per-driver backend and add Kconfig symbols to limit the impact. However I think that these functions are pretty small to justify the effort.
>
>>
>> Signed-off-by: Maximilian Luz <[email protected]>
>> ---
>>
>> Changes in v3:
>> - Rebase ontop of latest qcom_scm changes.
>> - Fix doc-comment.
>>
>> Changes in v2:
>> - No functional changes.
>>
>> ---
>> drivers/firmware/qcom_scm.c | 120 ++++++++++++++++---------
>> drivers/firmware/qcom_scm.h | 47 ----------
>> include/linux/firmware/qcom/qcom_scm.h | 49 ++++++++++
>> 3 files changed, 129 insertions(+), 87 deletions(-)
>>
>> diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c
>> index 468d4d5ab550..9b3e4449a563 100644
>> --- a/drivers/firmware/qcom_scm.c
>> +++ b/drivers/firmware/qcom_scm.c
>> @@ -212,16 +212,17 @@ static enum qcom_scm_convention __get_convention(void)
>> }
>> /**
>> - * qcom_scm_call() - Invoke a syscall in the secure world
>> - * @dev: device
>> + * __qcom_scm_call() - Invoke a syscall in the secure world
>> + * @dev: Device. Depending on the command and number of arguments, this
>> + * is optional.
>> * @desc: Descriptor structure containing arguments and return values
>> * @res: Structure containing results from SMC/HVC call
>> *
>> * Sends a command to the SCM and waits for the command to finish processing.
>> * This should *only* be called in pre-emptible context.
>> */
>> -static int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
>> - struct qcom_scm_res *res)
>> +static int __qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
>> + struct qcom_scm_res *res)
>> {
>> might_sleep();
>> switch (__get_convention()) {
>> @@ -237,17 +238,38 @@ static int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
>> }
>> /**
>> - * qcom_scm_call_atomic() - atomic variation of qcom_scm_call()
>> - * @dev: device
>> + * qcom_scm_call() - Invoke a syscall in the secure world
>> + * @desc: Descriptor structure containing arguments and return values
>> + * @res: Structure containing results from SMC/HVC call
>> + *
>> + * Sends a command to the SCM and waits for the command to finish processing.
>> + * This should *only* be called in pre-emptible context.
>> + *
>> + * Returns zero on success, -ENODEV if the SCM device has not been set up yet,
>> + * or other non-zero status codes on failure.
>> + */
>> +int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res)
>> +{
>> + if (!__scm)
>> + return -ENODEV;
>> +
>> + return __qcom_scm_call(__scm->dev, desc, res);
>> +}
>> +EXPORT_SYMBOL_GPL(qcom_scm_call);
>> +
>> +/**
>> + * __qcom_scm_call_atomic() - atomic variation of __qcom_scm_call()
>> + * @dev: Device. Depending on the command and number of arguments, this
>> + * is optional.
>> * @desc: Descriptor structure containing arguments and return values
>> * @res: Structure containing results from SMC/HVC call
>> *
>> * Sends a command to the SCM and waits for the command to finish processing.
>> * This can be called in atomic context.
>> */
>> -static int qcom_scm_call_atomic(struct device *dev,
>> - const struct qcom_scm_desc *desc,
>> - struct qcom_scm_res *res)
>> +static int __qcom_scm_call_atomic(struct device *dev,
>> + const struct qcom_scm_desc *desc,
>> + struct qcom_scm_res *res)
>> {
>> switch (__get_convention()) {
>> case SMC_CONVENTION_ARM_32:
>> @@ -261,6 +283,26 @@ static int qcom_scm_call_atomic(struct device *dev,
>> }
>> }
>> +/**
>> + * qcom_scm_call_atomic() - atomic variation of qcom_scm_call()
>> + * @desc: Descriptor structure containing arguments and return values
>> + * @res: Structure containing results from SMC/HVC call
>> + *
>> + * Sends a command to the SCM and waits for the command to finish processing.
>> + * This can be called in atomic context.
>> + *
>> + * Returns zero on success, -ENODEV if the SCM device has not been set up yet,
>> + * or other non-zero status codes on failure.
>> + */
>> +int qcom_scm_call_atomic(const struct qcom_scm_desc *desc, struct qcom_scm_res *res)
>> +{
>> + if (!__scm)
>> + return -ENODEV;
>> +
>> + return __qcom_scm_call_atomic(__scm->dev, desc, res);
>> +}
>> +EXPORT_SYMBOL_GPL(qcom_scm_call_atomic);
>> +
>> static bool __qcom_scm_is_call_available(struct device *dev, u32 svc_id,
>> u32 cmd_id)
>> {
>> @@ -287,7 +329,7 @@ static bool __qcom_scm_is_call_available(struct device *dev, u32 svc_id,
>> return false;
>> }
>> - ret = qcom_scm_call(dev, &desc, &res);
>> + ret = __qcom_scm_call(dev, &desc, &res);
>> return ret ? false : !!res.result[0];
>> }
>> @@ -312,7 +354,7 @@ static int qcom_scm_set_boot_addr(void *entry, const u8 *cpu_bits)
>> desc.args[0] = flags;
>> desc.args[1] = virt_to_phys(entry);
>> - return qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
>> + return __qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
>> }
>> static int qcom_scm_set_boot_addr_mc(void *entry, unsigned int flags)
>> @@ -334,7 +376,7 @@ static int qcom_scm_set_boot_addr_mc(void *entry, unsigned int flags)
>> if (!__scm || __get_convention() == SMC_CONVENTION_LEGACY)
>> return -EOPNOTSUPP;
>> - return qcom_scm_call(__scm->dev, &desc, NULL);
>> + return __qcom_scm_call(__scm->dev, &desc, NULL);
>> }
>> /**
>> @@ -384,7 +426,7 @@ void qcom_scm_cpu_power_down(u32 flags)
>> .owner = ARM_SMCCC_OWNER_SIP,
>> };
>> - qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
>> + __qcom_scm_call_atomic(__scm ? __scm->dev : NULL, &desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_cpu_power_down);
>> @@ -401,7 +443,7 @@ int qcom_scm_set_remote_state(u32 state, u32 id)
>> struct qcom_scm_res res;
>> int ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> return ret ? : res.result[0];
>> }
>> @@ -419,7 +461,7 @@ static int __qcom_scm_set_dload_mode(struct device *dev, bool enable)
>> desc.args[1] = enable ? QCOM_SCM_BOOT_SET_DLOAD_MODE : 0;
>> - return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
>> + return qcom_scm_call_atomic(&desc, NULL);
>> }
>> static void qcom_scm_set_download_mode(bool enable)
>> @@ -499,7 +541,7 @@ int qcom_scm_pas_init_image(u32 peripheral, const void *metadata, size_t size,
>> desc.args[1] = mdata_phys;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = __qcom_scm_call(__scm->dev, &desc, &res);
>> qcom_scm_bw_disable();
>> qcom_scm_clk_disable();
>> @@ -565,7 +607,7 @@ int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr, phys_addr_t size)
>> if (ret)
>> return ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> qcom_scm_bw_disable();
>> qcom_scm_clk_disable();
>> @@ -600,7 +642,7 @@ int qcom_scm_pas_auth_and_reset(u32 peripheral)
>> if (ret)
>> return ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> qcom_scm_bw_disable();
>> qcom_scm_clk_disable();
>> @@ -634,7 +676,7 @@ int qcom_scm_pas_shutdown(u32 peripheral)
>> if (ret)
>> return ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> qcom_scm_bw_disable();
>> qcom_scm_clk_disable();
>> @@ -666,7 +708,7 @@ bool qcom_scm_pas_supported(u32 peripheral)
>> QCOM_SCM_PIL_PAS_IS_SUPPORTED))
>> return false;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = __qcom_scm_call(__scm->dev, &desc, &res);
>> return ret ? false : !!res.result[0];
>> }
>> @@ -685,7 +727,7 @@ static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
>> struct qcom_scm_res res;
>> int ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> return ret ? : res.result[0];
>> }
>> @@ -725,8 +767,7 @@ int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val)
>> struct qcom_scm_res res;
>> int ret;
>> -
>> - ret = qcom_scm_call_atomic(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call_atomic(&desc, &res);
>> if (ret >= 0)
>> *val = res.result[0];
>> @@ -745,7 +786,7 @@ int qcom_scm_io_writel(phys_addr_t addr, unsigned int val)
>> .owner = ARM_SMCCC_OWNER_SIP,
>> };
>> - return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
>> + return qcom_scm_call_atomic(&desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_io_writel);
>> @@ -775,7 +816,7 @@ int qcom_scm_restore_sec_cfg(u32 device_id, u32 spare)
>> struct qcom_scm_res res;
>> int ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> return ret ? : res.result[0];
>> }
>> @@ -793,7 +834,7 @@ int qcom_scm_iommu_secure_ptbl_size(u32 spare, size_t *size)
>> struct qcom_scm_res res;
>> int ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> if (size)
>> *size = res.result[0];
>> @@ -816,7 +857,7 @@ int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare)
>> };
>> int ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, NULL);
>> + ret = qcom_scm_call(&desc, NULL);
>> /* the pg table has been initialized already, ignore the error */
>> if (ret == -EPERM)
>> @@ -837,7 +878,7 @@ int qcom_scm_iommu_set_cp_pool_size(u32 spare, u32 size)
>> .owner = ARM_SMCCC_OWNER_SIP,
>> };
>> - return qcom_scm_call(__scm->dev, &desc, NULL);
>> + return qcom_scm_call(&desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_iommu_set_cp_pool_size);
>> @@ -859,7 +900,7 @@ int qcom_scm_mem_protect_video_var(u32 cp_start, u32 cp_size,
>> };
>> struct qcom_scm_res res;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> return ret ? : res.result[0];
>> }
>> @@ -887,7 +928,7 @@ static int __qcom_scm_assign_mem(struct device *dev, phys_addr_t mem_region,
>> };
>> struct qcom_scm_res res;
>> - ret = qcom_scm_call(dev, &desc, &res);
>> + ret = __qcom_scm_call(dev, &desc, &res);
>> return ret ? : res.result[0];
>> }
>> @@ -1004,7 +1045,7 @@ int qcom_scm_ocmem_lock(enum qcom_scm_ocmem_client id, u32 offset, u32 size,
>> .arginfo = QCOM_SCM_ARGS(4),
>> };
>> - return qcom_scm_call(__scm->dev, &desc, NULL);
>> + return qcom_scm_call(&desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_ocmem_lock);
>> @@ -1027,7 +1068,7 @@ int qcom_scm_ocmem_unlock(enum qcom_scm_ocmem_client id, u32 offset, u32 size)
>> .arginfo = QCOM_SCM_ARGS(3),
>> };
>> - return qcom_scm_call(__scm->dev, &desc, NULL);
>> + return qcom_scm_call(&desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_ocmem_unlock);
>> @@ -1068,7 +1109,7 @@ int qcom_scm_ice_invalidate_key(u32 index)
>> .owner = ARM_SMCCC_OWNER_SIP,
>> };
>> - return qcom_scm_call(__scm->dev, &desc, NULL);
>> + return qcom_scm_call(&desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_ice_invalidate_key);
>> @@ -1129,7 +1170,7 @@ int qcom_scm_ice_set_key(u32 index, const u8 *key, u32 key_size,
>> memcpy(keybuf, key, key_size);
>> desc.args[1] = key_phys;
>> - ret = qcom_scm_call(__scm->dev, &desc, NULL);
>> + ret = qcom_scm_call(&desc, NULL);
>> memzero_explicit(keybuf, key_size);
>> @@ -1198,7 +1239,7 @@ int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp)
>> if (ret)
>> return ret;
>> - ret = qcom_scm_call(__scm->dev, &desc, &res);
>> + ret = qcom_scm_call(&desc, &res);
>> *resp = res.result[0];
>> qcom_scm_clk_disable();
>> @@ -1219,7 +1260,7 @@ int qcom_scm_iommu_set_pt_format(u32 sec_id, u32 ctx_num, u32 pt_fmt)
>> .owner = ARM_SMCCC_OWNER_SIP,
>> };
>> - return qcom_scm_call(__scm->dev, &desc, NULL);
>> + return qcom_scm_call(&desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_iommu_set_pt_format);
>> @@ -1234,8 +1275,7 @@ int qcom_scm_qsmmu500_wait_safe_toggle(bool en)
>> .owner = ARM_SMCCC_OWNER_SIP,
>> };
>> -
>> - return qcom_scm_call_atomic(__scm->dev, &desc, NULL);
>> + return qcom_scm_call_atomic(&desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_qsmmu500_wait_safe_toggle);
>> @@ -1255,7 +1295,7 @@ int qcom_scm_lmh_profile_change(u32 profile_id)
>> .owner = ARM_SMCCC_OWNER_SIP,
>> };
>> - return qcom_scm_call(__scm->dev, &desc, NULL);
>> + return qcom_scm_call(&desc, NULL);
>> }
>> EXPORT_SYMBOL(qcom_scm_lmh_profile_change);
>> @@ -1290,7 +1330,7 @@ int qcom_scm_lmh_dcvsh(u32 payload_fn, u32 payload_reg, u32 payload_val,
>> desc.args[0] = payload_phys;
>> - ret = qcom_scm_call(__scm->dev, &desc, NULL);
>> + ret = __qcom_scm_call(__scm->dev, &desc, NULL);
>> dma_free_coherent(__scm->dev, payload_size, payload_buf, payload_phys);
>> return ret;
>> diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h
>> index e6e512bd57d1..87eb726be7d0 100644
>> --- a/drivers/firmware/qcom_scm.h
>> +++ b/drivers/firmware/qcom_scm.h
>> @@ -13,53 +13,6 @@ enum qcom_scm_convention {
>> extern enum qcom_scm_convention qcom_scm_convention;
>> -#define MAX_QCOM_SCM_ARGS 10
>> -#define MAX_QCOM_SCM_RETS 3
>> -
>> -enum qcom_scm_arg_types {
>> - QCOM_SCM_VAL,
>> - QCOM_SCM_RO,
>> - QCOM_SCM_RW,
>> - QCOM_SCM_BUFVAL,
>> -};
>> -
>> -#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\
>> - (((a) & 0x3) << 4) | \
>> - (((b) & 0x3) << 6) | \
>> - (((c) & 0x3) << 8) | \
>> - (((d) & 0x3) << 10) | \
>> - (((e) & 0x3) << 12) | \
>> - (((f) & 0x3) << 14) | \
>> - (((g) & 0x3) << 16) | \
>> - (((h) & 0x3) << 18) | \
>> - (((i) & 0x3) << 20) | \
>> - (((j) & 0x3) << 22) | \
>> - ((num) & 0xf))
>> -
>> -#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
>> -
>> -
>> -/**
>> - * struct qcom_scm_desc
>> - * @arginfo: Metadata describing the arguments in args[]
>> - * @args: The array of arguments for the secure syscall
>> - */
>> -struct qcom_scm_desc {
>> - u32 svc;
>> - u32 cmd;
>> - u32 arginfo;
>> - u64 args[MAX_QCOM_SCM_ARGS];
>> - u32 owner;
>> -};
>> -
>> -/**
>> - * struct qcom_scm_res
>> - * @result: The values returned by the secure syscall
>> - */
>> -struct qcom_scm_res {
>> - u64 result[MAX_QCOM_SCM_RETS];
>> -};
>> -
>> int qcom_scm_wait_for_wq_completion(u32 wq_ctx);
>> int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending);
>> diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h
>> index 1e449a5d7f5c..162746467c22 100644
>> --- a/include/linux/firmware/qcom/qcom_scm.h
>> +++ b/include/linux/firmware/qcom/qcom_scm.h
>> @@ -11,6 +11,55 @@
>> #include <dt-bindings/firmware/qcom,scm.h>
>> +#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\
>> + (((a) & 0x3) << 4) | \
>> + (((b) & 0x3) << 6) | \
>> + (((c) & 0x3) << 8) | \
>> + (((d) & 0x3) << 10) | \
>> + (((e) & 0x3) << 12) | \
>> + (((f) & 0x3) << 14) | \
>> + (((g) & 0x3) << 16) | \
>> + (((h) & 0x3) << 18) | \
>> + (((i) & 0x3) << 20) | \
>> + (((j) & 0x3) << 22) | \
>> + ((num) & 0xf))
>> +
>> +#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
>> +
>> +#define MAX_QCOM_SCM_ARGS 10
>> +#define MAX_QCOM_SCM_RETS 3
>> +
>> +enum qcom_scm_arg_types {
>> + QCOM_SCM_VAL,
>> + QCOM_SCM_RO,
>> + QCOM_SCM_RW,
>> + QCOM_SCM_BUFVAL,
>> +};
>> +
>> +/**
>> + * struct qcom_scm_desc - SCM call descriptor.
>> + * @arginfo: Metadata describing the arguments in args[]
>> + * @args: The array of arguments for the secure syscall
>> + */
>> +struct qcom_scm_desc {
>> + u32 svc;
>> + u32 cmd;
>> + u32 arginfo;
>> + u64 args[MAX_QCOM_SCM_ARGS];
>> + u32 owner;
>> +};
>> +
>> +/**
>> + * struct qcom_scm_res - SCM call response.
>> + * @result: The values returned by the secure syscall
>> + */
>> +struct qcom_scm_res {
>> + u64 result[MAX_QCOM_SCM_RETS];
>> +};
>> +
>> +int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res);
>> +int qcom_scm_call_atomic(const struct qcom_scm_desc *desc, struct qcom_scm_res *res);
>> +
>> #define QCOM_SCM_VERSION(major, minor) (((major) << 16) | ((minor) & 0xFF))
>> #define QCOM_SCM_CPU_PWR_DOWN_L2_ON 0x0
>> #define QCOM_SCM_CPU_PWR_DOWN_L2_OFF 0x1
>
On 3/8/23 13:53, Srinivas Kandagatla wrote:
>
>
> On 07/03/2023 15:23, Dmitry Baryshkov wrote:
>>
>>> Make qcom_scm_call, qcom_scm_call_atomic and associated types accessible
>>> to other modules.
>>
>> Generally all the qcom_scm calls are a part of qcom_scm.c. I think it is better to make qseecom_scm_call a part qcom_scm.c (as we were previously doing) rather than exporting the core function.
>>
>
> Other big issue I see in exporting qcom_scm_call() is that there is danger of misuse of this api as this could lead to a path where new apis and its payloads can come directly from userspace via a rogue/hacking modules. This will bypass scm layer completely within kernel.
I'm not sure I follow your argument here. If you have the possibility to
load your own kernel modules, can you not always bypass the kernel and
just directly invoke the respective SCM calls manually? So this is
superficial security at best.
I guess keeping it in qcom_scm could make it easier to spot new
in-kernel users of that function and with that better prevent potential
misuse in the kernel itself. But then again I'd hope that our review
system is good enough to catch such issues regardless and thoroughly
question calls to that function (especially ones involving user-space
APIs).
Regards,
Max
>
> --srini
>
>> If you wish to limit the kernel bloat, you can split the qcom_scm into per-driver backend and add Kconfig symbols to limit the impact. However I think that these functions are pretty small to justify the effort.
>>
>
>
>>>
>>> Signed-off-by: Maximilian Luz <[email protected]>
On 3/7/23 16:32, Dmitry Baryshkov wrote:
> On 05/03/2023 04:21, Maximilian Luz wrote:
[...]
>> +/* -- DMA helpers. ---------------------------------------------------------- */
>> +
>> +/* DMA requirements for QSEECOM SCM calls. */
>> +#define QSEECOM_DMA_ALIGNMENT 8
>> +#define QSEECOM_DMA_ALIGN(ptr) ALIGN(ptr, QSEECOM_DMA_ALIGNMENT)
>> +
>> +/**
>> + * struct qseecom_dma - DMA memory region.
>> + * @size: Size of the memory region, in bytes.
>> + * @virt: Pointer / virtual address to the memory, accessible by the kernel.
>> + * @phys: Physical address of the memory region.
>> + */
>> +struct qseecom_dma {
>> + unsigned long size;
>
> size_t ?
Will fix.
>> + void *virt;
>> + dma_addr_t phys;
>> +};
>
> Do we really need this wrapper and the wrappers bellow? They look like a pure syntax sugar for me, hiding the dma functions from the user.
The idea was that they take care of proper allocation. The Windows driver that
I've reverse-engineered this from allocates memory in multiples of PAGE_SIZE,
so I believe that this might be a requirement (at least on some systems). These
functions are there to ensure that and with that prevent potential bugs by
taking that responsibility from the caller.
>> +
>> +/**
>> + * qseecom_dma_alloc() - Allocate a DMA-able memory region suitable for QSEECOM
>> + * SCM calls.
>> + * @dev: The device used for DMA memory allocation.
>> + * @dma: Where to write the allocated memory addresses and size to.
>> + * @size: Minimum size of the memory to be allocated.
>> + * @gfp: Flags used for allocation.
>> + *
>> + * Allocate a DMA-able memory region suitable for interaction with SEE
>> + * services/applications and the TzOS. The provided size is treated as the
>> + * minimum required size and rounded up, if necessary. The actually allocated
>> + * memory region will be stored in @dma. Allocated memory must be freed via
>> + * qseecom_dma_free().
>> + *
>> + * Return: Returns zero on success, -ENOMEM on allocation failure.
>> + */
>> +static inline int qseecom_dma_alloc(struct device *dev, struct qseecom_dma *dma,
>> + unsigned long size, gfp_t gfp)
>
> size_t size
>
> gfp is not used
Right, that should have been passed to dma_alloc_coherent(). Will fix that.
>> +{
>> + size = PAGE_ALIGN(size);
>> +
>> + dma->virt = dma_alloc_coherent(dev, size, &dma->phys, GFP_KERNEL);
>> + if (!dma->virt)
>> + return -ENOMEM;
>> +
>> + dma->size = size;
>> + return 0;
>> +}
>> +
>> +/**
>> + * qseecom_dma_free() - Free a DMA memory region.
>> + * @dev: The device used for allocation.
>> + * @dma: The DMA region to be freed.
>> + *
>> + * Free a DMA region previously allocated via qseecom_dma_alloc(). Note that
>> + * freeing sub-regions is not supported.
>> + */
>> +static inline void qseecom_dma_free(struct device *dev, struct qseecom_dma *dma)
>> +{
>> + dma_free_coherent(dev, dma->size, dma->virt, dma->phys);
>> +}
>> +
>> +/**
>> + * qseecom_dma_realloc() - Re-allocate DMA memory region with the requested size.
>> + * @dev: The device used for allocation.
>> + * @dma: The region descriptor to be updated.
>> + * @size: The new requested size.
>> + * @gfp: Flags used for allocation.
>> + *
>> + * Re-allocates a DMA memory region suitable for QSEECOM SCM calls to fit the
>> + * requested amount of bytes, if necessary. Does nothing if the provided region
>> + * already has enough space to store the requested data.
>> + *
>> + * See qseecom_dma_alloc() for details.
>> + *
>> + * Return: Returns zero on success, -ENOMEM on allocation failure.
>> + */
>> +static inline int qseecom_dma_realloc(struct device *dev, struct qseecom_dma *dma,
>> + unsigned long size, gfp_t gfp)
>> +{
>> + if (PAGE_ALIGN(size) <= dma->size)
>> + return 0;
>> +
>> + qseecom_dma_free(dev, dma);
>> + return qseecom_dma_alloc(dev, dma, size, gfp);
>> +}
>
> I'll comment on this function when commenting patch 4.
>
>> +
>> +/**
>> + * qseecom_dma_aligned() - Create a aligned DMA memory sub-region suitable for
>> + * QSEECOM SCM calls.
>> + * @base: Base DMA memory region, in which the new region will reside.
>> + * @out: Descriptor to store the aligned sub-region in.
>> + * @offset: The offset inside base region at which to place the new sub-region.
>> + *
>> + * Creates an aligned DMA memory region suitable for QSEECOM SCM calls at or
>> + * after the given offset. The size of the sub-region will be set to the
>> + * remaining size in the base region after alignment, i.e., the end of the
>> + * sub-region will be equal the end of the base region.
>> + *
>> + * Return: Returns zero on success or -EINVAL if the new aligned memory address
>> + * would point outside the base region.
>> + */
>> +static inline int qseecom_dma_aligned(const struct qseecom_dma *base, struct qseecom_dma *out,
>> + unsigned long offset)
>> +{
>> + void *aligned = (void *)QSEECOM_DMA_ALIGN((uintptr_t)base->virt + offset);
>> +
>> + if (aligned - base->virt > base->size)
>> + return -EINVAL;
>> +
>> + out->virt = aligned;
>> + out->phys = base->phys + (out->virt - base->virt);
>> + out->size = base->size - (out->virt - base->virt);
>> +
>> + return 0;
>> +}
>> +
>> +
>> +/* -- Common interface. ----------------------------------------------------- */
>> +
>> +struct qseecom_device {
>> + struct device *dev;
>> + struct mutex scm_call_lock; /* Guards QSEECOM SCM calls. */
>
> There can be only one instance of the qseecom call infrastructure. Make this mutex static in the qcom_scm.c
Right, will do that.
>> +};
>> +
>> +
>> +/* -- Secure-OS SCM call interface. ----------------------------------------- */
>> +
>> +#define QSEECOM_TZ_OWNER_TZ_APPS 48
>> +#define QSEECOM_TZ_OWNER_QSEE_OS 50
>> +
>> +#define QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER 0
>> +#define QSEECOM_TZ_SVC_APP_MGR 1
>> +
>> +enum qseecom_scm_result {
>> + QSEECOM_RESULT_SUCCESS = 0,
>> + QSEECOM_RESULT_INCOMPLETE = 1,
>> + QSEECOM_RESULT_BLOCKED_ON_LISTENER = 2,
>> + QSEECOM_RESULT_FAILURE = 0xFFFFFFFF,
>> +};
>> +
>> +enum qseecom_scm_resp_type {
>> + QSEECOM_SCM_RES_APP_ID = 0xEE01,
>> + QSEECOM_SCM_RES_QSEOS_LISTENER_ID = 0xEE02,
>> +};
>> +
>> +/**
>> + * struct qseecom_scm_resp - QSEECOM SCM call response.
>> + * @status: Status of the SCM call. See &enum qseecom_scm_result.
>> + * @resp_type: Type of the response. See &enum qseecom_scm_resp_type.
>> + * @data: Response data. The type of this data is given in @resp_type.
>> + */
>> +struct qseecom_scm_resp {
>> + u64 status;
>> + u64 resp_type;
>> + u64 data;
>> +};
>> +
>> +int qseecom_scm_call(struct qseecom_device *qsee, const struct qcom_scm_desc *desc,
>> + struct qseecom_scm_resp *res);
>> +
>> +
>> +/* -- Secure App interface. ------------------------------------------------- */
>> +
>> +#define QSEECOM_MAX_APP_NAME_SIZE 64
>> +
>> +int qseecom_app_get_id(struct qseecom_device *qsee, const char *app_name, u32 *app_id);
>> +int qseecom_app_send(struct qseecom_device *qsee, u32 app_id, struct qseecom_dma *req,
>> + struct qseecom_dma *rsp);
>
> I think that only these calls should be made public / available to other modules. qseecom_scm_call also is an internal helper.
So move all calls to qcom_scm and only make these two public? Or move only
qseecom_scm_call() to qcom_scm (which would require to make it public there,
however). Or how would you want this to look?
>> +
>> +#endif /* _LINUX_QCOM_QSEECOM_H */
>
Regards,
Max
On 3/7/23 16:36, Dmitry Baryshkov wrote:
> On 05/03/2023 04:21, Maximilian Luz wrote:
>> Add support for SCM calls to Secure OS and the Secure Execution
>> Environment (SEE) residing in the TrustZone (TZ) via the QSEECOM
>> interface. This allows communication with Secure/TZ applications, for
>> example 'uefisecapp' managing access to UEFI variables.
>>
>> The interface is managed by a platform device to ensure correct lifetime
>> and establish a device link to the Qualcomm SCM device.
>>
>> While this patch introduces only a very basic interface without the more
>> advanced features (such as re-entrant and blocking SCM calls and
>> listeners/callbacks), this is enough to talk to the aforementioned
>> 'uefisecapp'.
>>
>> Signed-off-by: Maximilian Luz <[email protected]>
>> ---
>>
>> Changes in v3:
>> - Rebase ontop of latest qcom_scm changes (qcom_scm.h moved).
>> - Move qcom_qseecom.h in accordance with qcom_scm.
>>
>> Changes in v2:
>> - Bind the interface to a device.
>> - Establish a device link to the SCM device to ensure proper ordering.
>> - Register client apps as child devices instead of requiring them to be
>> specified in the device tree.
>> - Rename (qctree -> qseecom) to allow differentiation between old
>> (qseecom) and new (smcinvoke) interfaces to the trusted execution
>> environment.
>>
>> ---
>> MAINTAINERS | 7 +
>> drivers/firmware/Kconfig | 15 +
>> drivers/firmware/Makefile | 1 +
>> drivers/firmware/qcom_qseecom.c | 314 +++++++++++++++++++++
>> include/linux/firmware/qcom/qcom_qseecom.h | 190 +++++++++++++
>> 5 files changed, 527 insertions(+)
>> create mode 100644 drivers/firmware/qcom_qseecom.c
>> create mode 100644 include/linux/firmware/qcom/qcom_qseecom.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 9201967d198d..1545914a592c 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -17380,6 +17380,13 @@ F: Documentation/networking/device_drivers/cellular/qualcomm/rmnet.rst
>> F: drivers/net/ethernet/qualcomm/rmnet/
>> F: include/linux/if_rmnet.h
>> +QUALCOMM SECURE EXECUTION ENVIRONMENT COMMUNICATION DRIVER
>> +M: Maximilian Luz <[email protected]>
>> +L: [email protected]
>> +S: Maintained
>> +F: drivers/firmware/qcom_qseecom.c
>> +F: include/linux/firmware/qcom/qcom_qseecom.h
>> +
>> QUALCOMM TSENS THERMAL DRIVER
>> M: Amit Kucheria <[email protected]>
>> M: Thara Gopinath <[email protected]>
>
>
>
>> +
>> +
>> +/* -- Platform specific data. ----------------------------------------------- */
>> +
>> +struct qseecom_data {
>> + const struct mfd_cell *cells;
>
> The child qseecom devices are not platform devices, so MFD should not be used here. Please use aux devices instead.
Okay, makes sense. Would this still work with your suggestion in patch 4
regarding a custom (?) bus or can the aux bus be used to implement that? From a
quick look, I believe we could use aux bus for this but I haven't worked with
that before, so I don't know if I'm missing something.
>> + int num_cells;
>> +};
>> +
>> +static const struct of_device_id qseecom_dt_match[] = {
>> + { .compatible = "qcom,qseecom-sc8280xp", },
>
> Forgot to mention, while doign review. There is no need for this compat until you provide the actual data. Please move it to the patch 4.
Sure, will do that.
>> + { .compatible = "qcom,qseecom", },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(of, qseecom_dt_match);
>
>
Regards,
Max
On 08/03/2023 13:48, Maximilian Luz wrote:
> On 3/8/23 13:53, Srinivas Kandagatla wrote:
>>
>>
>> On 07/03/2023 15:23, Dmitry Baryshkov wrote:
>>>
>>>> Make qcom_scm_call, qcom_scm_call_atomic and associated types
>>>> accessible
>>>> to other modules.
>>>
>>> Generally all the qcom_scm calls are a part of qcom_scm.c. I think it
>>> is better to make qseecom_scm_call a part qcom_scm.c (as we were
>>> previously doing) rather than exporting the core function.
>>>
>>
>> Other big issue I see in exporting qcom_scm_call() is that there is
>> danger of misuse of this api as this could lead to a path where new
>> apis and its payloads can come directly from userspace via a
>> rogue/hacking modules. This will bypass scm layer completely within
>> kernel.
>
> I'm not sure I follow your argument here. If you have the possibility to
> load your own kernel modules, can you not always bypass the kernel and
> just directly invoke the respective SCM calls manually? So this is
> superficial security at best.
qcom_scm_call() will expose a much bigger window where the user can add
new SCM APIs but with the current model of exporting symbols at SCM API
level will narrow that down to that API.
>
> I guess keeping it in qcom_scm could make it easier to spot new
> in-kernel users of that function and with that better prevent potential
> misuse in the kernel itself. But then again I'd hope that our review
> system is good enough to catch such issues regardless and thoroughly
> question calls to that function (especially ones involving user-space
> APIs).
One problem I can immediately see here is the facility that will be
exploited and promote more development outside upstream.
ex: vendor modules with GKI compliance.
--srini
>
> Regards,
> Max
>
>>
>> --srini
>>
>>> If you wish to limit the kernel bloat, you can split the qcom_scm
>>> into per-driver backend and add Kconfig symbols to limit the impact.
>>> However I think that these functions are pretty small to justify the
>>> effort.
>>>
>>
>>
>>>>
>>>> Signed-off-by: Maximilian Luz <[email protected]>
On 3/7/23 16:51, Dmitry Baryshkov wrote:
> On 05/03/2023 04:21, Maximilian Luz wrote:
>> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
>> EFI variables cannot be accessed via the standard interface in EFI
>> runtime mode. The respective functions return EFI_UNSUPPORTED. On these
>> platforms, we instead need to talk to uefisecapp. This commit provides
>> support for this and registers the respective efivars operations to
>> access EFI variables from the kernel.
>>
>> Communication with uefisecapp follows the Qualcomm QSEECOM / Secure OS
>> conventions via the respective SCM call interface. This is also the
>> reason why variable access works normally while boot services are
>> active. During this time, said SCM interface is managed by the boot
>> services. When calling ExitBootServices(), the ownership is transferred
>> to the kernel. Therefore, UEFI must not use that interface itself (as
>> multiple parties accessing this interface at the same time may lead to
>> complications) and cannot access variables for us.
>>
>> Signed-off-by: Maximilian Luz <[email protected]>
>> ---
>>
>> Changes in v3:
>> - No functional changes.
>>
>> Changes in v2:
>> - Rename (qctree -> qseecom) to allow differentiation between old
>> (qseecom) and new (smcinvoke) interfaces to the trusted execution
>> environment.
>>
>> ---
>> MAINTAINERS | 6 +
>> drivers/firmware/Kconfig | 16 +
>> drivers/firmware/Makefile | 1 +
>> drivers/firmware/qcom_qseecom.c | 11 +-
>> drivers/firmware/qcom_qseecom_uefisecapp.c | 746 +++++++++++++++++++++
>> 5 files changed, 779 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/firmware/qcom_qseecom_uefisecapp.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index ef1f806986e9..35d614de6bbc 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -17371,6 +17371,12 @@ S: Maintained
>> F: Documentation/devicetree/bindings/mtd/qcom,nandc.yaml
>> F: drivers/mtd/nand/raw/qcom_nandc.c
>> +QUALCOMM QSEECOM UEFISECAPP DRIVER
>> +M: Maximilian Luz <[email protected]>
>> +L: [email protected]
>> +S: Maintained
>> +F: drivers/firmware/qcom_qseecom_uefisecapp.c
>> +
>> QUALCOMM RMNET DRIVER
>> M: Subash Abhinov Kasiviswanathan <[email protected]>
>> M: Sean Tranchetti <[email protected]>
>> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
>> index 22eec0835abf..b9ad753a195b 100644
>> --- a/drivers/firmware/Kconfig
>> +++ b/drivers/firmware/Kconfig
>> @@ -241,6 +241,22 @@ config QCOM_QSEECOM
>> Select M or Y here to enable the QSEECOM interface driver.
>> +config QCOM_QSEECOM_UEFISECAPP
>> + tristate "Qualcomm SEE UEFI Secure App client driver"
>> + depends on QCOM_QSEECOM
>> + depends on EFI
>> + help
>> + Various Qualcomm SoCs do not allow direct access to EFI variables.
>> + Instead, these need to be accessed via the UEFI Secure Application
>> + (uefisecapp), residing in the Secure Execution Environment (SEE).
>> +
>> + This module provides a client driver for uefisecapp, installing efivar
>> + operations to allow the kernel accessing EFI variables, and via that also
>> + provide user-space with access to EFI variables via efivarfs.
>> +
>> + Select M or Y here to provide access to EFI variables on the
>> + aforementioned platforms.
>> +
>> config SYSFB
>> bool
>> select BOOT_VESA_SUPPORT
>> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
>> index aa48e0821b7d..d41b094a5e58 100644
>> --- a/drivers/firmware/Makefile
>> +++ b/drivers/firmware/Makefile
>> @@ -21,6 +21,7 @@ obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
>> obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
>> qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
>> obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
>> +obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
>> obj-$(CONFIG_SYSFB) += sysfb.o
>> obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o
>> obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
>> diff --git a/drivers/firmware/qcom_qseecom.c b/drivers/firmware/qcom_qseecom.c
>> index efa5b115b2f1..ae42341dce1c 100644
>> --- a/drivers/firmware/qcom_qseecom.c
>> +++ b/drivers/firmware/qcom_qseecom.c
>> @@ -216,8 +216,17 @@ struct qseecom_data {
>> int num_cells;
>> };
>> +static const struct mfd_cell qseecom_cells_sc8280xp[] = {
>> + { .name = "qcom_qseecom_uefisecapp", },
>> +};
>> +
>> +static const struct qseecom_data qseecom_data_sc8280xp = {
>> + .cells = qseecom_cells_sc8280xp,
>> + .num_cells = ARRAY_SIZE(qseecom_cells_sc8280xp),
>> +};
>> +
>> static const struct of_device_id qseecom_dt_match[] = {
>> - { .compatible = "qcom,qseecom-sc8280xp", },
>> + { .compatible = "qcom,qseecom-sc8280xp", .data = &qseecom_data_sc8280xp },
>
> Note: the SoC doesn't fully describe the list of avialble qseecom applications. It depends on the particular vendor enabling or disabling them. It might be better to turn qseecom into the bus, create a list of possible app names and use qseecom_app_get_id() to check whether the application is available or not. Then you can instantiate only available devices and provide the app_id via struct qseecom_app_device wrapping struct device:
Alright, so would we have a SoC specific list of apps that we then check or a
global one?
As I've elaborated on a previous version: I'm a bit wary of using
qseecom_app_get_id() in this way, since the Windows driver I've got this from
expects the app to be present when calling that function. So I don't know much
about the failure cases, especially when it isn't present.
At this point, I'm just assuming that "res.status != QSEECOM_RESULT_SUCCESS"
means the app isn't present, but I don't know whether this can fail in other
ways. For a proper detection system I'd prefer if we can differentiate between
"some internal failure" and "not-present" cases.
I can still give that a try thouhg.
Maybe you or someone else knows a bit more about that than I do and can set my
mind at ease? (Again, all my knowledge about this comes from reverse
engineering, so it's not that much.)
> struct qseecom_app_device {
> struct device dev;
> u32 app_id;
> };
>
>> { .compatible = "qcom,qseecom", },
>> { }
>> };
>> diff --git a/drivers/firmware/qcom_qseecom_uefisecapp.c b/drivers/firmware/qcom_qseecom_uefisecapp.c
>> new file mode 100644
>> index 000000000000..8d772fe1f589
>> --- /dev/null
>> +++ b/drivers/firmware/qcom_qseecom_uefisecapp.c
>> @@ -0,0 +1,746 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Client driver for Qualcomm UEFI Secure Application (qcom.tz.uefisecapp).
>> + * Provides access to UEFI variables on platforms where they are secured by the
>> + * aforementioned Secure Execution Environment (SEE) application.
>> + *
>> + * Copyright (C) 2023 Maximilian Luz <[email protected]>
>> + */
>> +
>> +#include <linux/efi.h>
>> +#include <linux/firmware/qcom/qcom_qseecom.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <linux/types.h>
>> +
>> +
>> +/* -- UTF-16 helpers. ------------------------------------------------------- */
>> +
>> +static unsigned long utf16_strnlen(const efi_char16_t *str, unsigned long max)
>> +{
>> + size_t i;
>> +
>> + for (i = 0; *str != 0 && i < max; i++, str++) {
>> + /* Do nothing, all is handled in the for statement. */
>> + }
>> +
>> + return i;
>> +}
>> +
>> +/**
>> + * utf16_strsize() - Compute the number of bytes required to store a
>> + * null-terminated UTF-16 string.
>> + * @str: The string to compute the size for.
>> + *
>> + * Return: Returns the minimum number of bytes required to store the given
>> + * null-terminated string, including its null-terminator.
>> + */
>> +static unsigned long utf16_strsize(const efi_char16_t *str)
>> +{
>> + return (utf16_strnlen(str, U32_MAX) + 1) * sizeof(str[0]);
>> +}
>> +
>> +static unsigned long utf16_strlcpy(efi_char16_t *dst, const efi_char16_t *src, unsigned long size)
>> +{
>> + unsigned long actual = utf16_strnlen(src, size - 1);
>> +
>> + memcpy(dst, src, actual * sizeof(src[0]));
>> + dst[actual] = 0;
>> +
>> + return actual;
>> +}
>> +
>> +/**
>> + * utf16_copy_to_buf() - Copy the given UTF-16 string to a buffer.
>> + * @dst: Pointer to the output buffer
>> + * @src: Pointer to the null-terminated UTF-16 string to be copied.
>> + * @bytes: Maximum number of bytes to copy.
>> + *
>> + * Copies the given string to the given buffer, ensuring that the output buffer
>> + * is not overrun and that the string in the output buffer will always be
>> + * null-terminated.
>> + *
>> + * Return: Returns the length of the copied string, without null-terminator.
>> + */
>> +static unsigned long utf16_copy_to_buf(efi_char16_t *dst, const efi_char16_t *src,
>> + unsigned long bytes)
>> +{
>> + return utf16_strlcpy(dst, src, bytes / sizeof(src[0]));
>> +}
>
> These functions seem generic enough, please move them to lib/ucs2_string.c. Use the existing functions if they suit.
Right, will do. Not sure how I missed those when looking for utf16 string functions...
>> +
>> +
>
> Double empty lines
>
>> +/* -- Qualcomm "uefisecapp" interface definitions. -------------------------- */
>> +
>> +#define QSEE_UEFISEC_APP_NAME "qcom.tz.uefisecapp"
>> +
>> +#define QSEE_CMD_UEFI(x) (0x8000 | (x))
>> +#define QSEE_CMD_UEFI_GET_VARIABLE QSEE_CMD_UEFI(0)
>> +#define QSEE_CMD_UEFI_SET_VARIABLE QSEE_CMD_UEFI(1)
>> +#define QSEE_CMD_UEFI_GET_NEXT_VARIABLE QSEE_CMD_UEFI(2)
>> +#define QSEE_CMD_UEFI_QUERY_VARIABLE_INFO QSEE_CMD_UEFI(3)
>> +
>> +/**
>> + * struct qsee_req_uefi_get_variable - Request for GetVariable command.
>> + * @command_id: The ID of the command. Must be %QSEE_CMD_UEFI_GET_VARIABLE.
>> + * @length: Length of the request in bytes, including this struct and any
>> + * parameters (name, GUID) stored after it as well as any padding
>> + * thereof for alignment.
>> + * @name_offset: Offset from the start of this struct to where the variable
>> + * name is stored (as utf-16 string), in bytes.
>> + * @name_size: Size of the name parameter in bytes, including null-terminator.
>> + * @guid_offset: Offset from the start of this struct to where the GUID
>> + * parameter is stored, in bytes.
>> + * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
>> + * @data_size: Size of the output buffer, in bytes.
>> + */
>> +struct qsee_req_uefi_get_variable {
>> + u32 command_id;
>> + u32 length;
>> + u32 name_offset;
>> + u32 name_size;
>> + u32 guid_offset;
>> + u32 guid_size;
>> + u32 data_size;
>> +} __packed;
>> +
>> +/**
>> + * struct qsee_rsp_uefi_get_variable - Response for GetVariable command.
>> + * @command_id: The ID of the command. Should be %QSEE_CMD_UEFI_GET_VARIABLE.
>> + * @length: Length of the response in bytes, including this struct and the
>> + * returned data.
>> + * @status: Status of this command.
>> + * @attributes: EFI variable attributes.
>> + * @data_offset: Offset from the start of this struct to where the data is
>> + * stored, in bytes.
>> + * @data_size: Size of the returned data, in bytes. In case status indicates
>> + * that the buffer is too small, this will be the size required
>> + * to store the EFI variable data.
>> + */
>> +struct qsee_rsp_uefi_get_variable {
>> + u32 command_id;
>> + u32 length;
>> + u32 status;
>> + u32 attributes;
>> + u32 data_offset;
>> + u32 data_size;
>> +} __packed;
>> +
>> +/**
>> + * struct qsee_req_uefi_set_variable - Request for the SetVariable command.
>> + * @command_id: The ID of the command. Must be %QSEE_CMD_UEFI_SET_VARIABLE.
>> + * @length: Length of the request in bytes, including this struct and any
>> + * parameters (name, GUID, data) stored after it as well as any
>> + * padding thereof required for alignment.
>> + * @name_offset: Offset from the start of this struct to where the variable
>> + * name is stored (as utf-16 string), in bytes.
>> + * @name_size: Size of the name parameter in bytes, including null-terminator.
>> + * @guid_offset: Offset from the start of this struct to where the GUID
>> + * parameter is stored, in bytes.
>> + * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
>> + * @attributes: The EFI variable attributes to set for this variable.
>> + * @data_offset: Offset from the start of this struct to where the EFI variable
>> + * data is stored, in bytes.
>> + * @data_size: Size of EFI variable data, in bytes.
>> + *
>> + */
>> +struct qsee_req_uefi_set_variable {
>> + u32 command_id;
>> + u32 length;
>> + u32 name_offset;
>> + u32 name_size;
>> + u32 guid_offset;
>> + u32 guid_size;
>> + u32 attributes;
>> + u32 data_offset;
>> + u32 data_size;
>> +} __packed;
>> +
>> +/**
>> + * struct qsee_rsp_uefi_set_variable - Response for the SetVariable command.
>> + * @command_id: The ID of the command. Should be %QSEE_CMD_UEFI_SET_VARIABLE.
>> + * @length: The length of this response, i.e. the size of this struct in
>> + * bytes.
>> + * @status: Status of this command.
>> + * @_unknown1: Unknown response field.
>> + * @_unknown2: Unknown response field.
>> + */
>> +struct qsee_rsp_uefi_set_variable {
>> + u32 command_id;
>> + u32 length;
>> + u32 status;
>> + u32 _unknown1;
>> + u32 _unknown2;
>> +} __packed;
>> +
>> +/**
>> + * struct qsee_req_uefi_get_next_variable - Request for the
>> + * GetNextVariableName command.
>> + * @command_id: The ID of the command. Must be
>> + * %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
>> + * @length: Length of the request in bytes, including this struct and any
>> + * parameters (name, GUID) stored after it as well as any padding
>> + * thereof for alignment.
>> + * @guid_offset: Offset from the start of this struct to where the GUID
>> + * parameter is stored, in bytes.
>> + * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
>> + * @name_offset: Offset from the start of this struct to where the variable
>> + * name is stored (as utf-16 string), in bytes.
>> + * @name_size: Size of the name parameter in bytes, including null-terminator.
>> + */
>> +struct qsee_req_uefi_get_next_variable {
>> + u32 command_id;
>> + u32 length;
>> + u32 guid_offset;
>> + u32 guid_size;
>> + u32 name_offset;
>> + u32 name_size;
>> +} __packed;
>> +
>> +/**
>> + * struct qsee_rsp_uefi_get_next_variable - Response for the
>> + * GetNextVariableName command.
>> + * @command_id: The ID of the command. Should be
>> + * %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
>> + * @length: Length of the response in bytes, including this struct and any
>> + * parameters (name, GUID) stored after it as well as any padding
>> + * thereof for alignment.
>> + * @status: Status of this command.
>> + * @guid_size: Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
>> + * @name_offset: Offset from the start of this struct to where the variable
>> + * name is stored (as utf-16 string), in bytes.
>> + * @name_size: Size of the name parameter in bytes, including null-terminator.
>> + */
>> +struct qsee_rsp_uefi_get_next_variable {
>> + u32 command_id;
>> + u32 length;
>> + u32 status;
>> + u32 guid_offset;
>> + u32 guid_size;
>> + u32 name_offset;
>> + u32 name_size;
>> +} __packed;
>> +
>> +/**
>> + * struct qsee_req_uefi_query_variable_info - Response for the
>> + * GetNextVariableName command.
>> + * @command_id: The ID of the command. Must be
>> + * %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
>> + * @length: The length of this request, i.e. the size of this struct in
>> + * bytes.
>> + * @attributes: The storage attributes to query the info for.
>> + */
>> +struct qsee_req_uefi_query_variable_info {
>> + u32 command_id;
>> + u32 length;
>> + u32 attributes;
>> +} __packed;
>> +
>> +/**
>> + * struct qsee_rsp_uefi_query_variable_info - Response for the
>> + * GetNextVariableName command.
>> + * @command_id: The ID of the command. Must be
>> + * %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
>> + * @length: The length of this response, i.e. the size of this
>> + * struct in bytes.
>> + * @status: Status of this command.
>> + * @_pad: Padding.
>> + * @storage_space: Full storage space size, in bytes.
>> + * @remaining_space: Free storage space available, in bytes.
>> + * @max_variable_size: Maximum variable data size, in bytes.
>> + */
>> +struct qsee_rsp_uefi_query_variable_info {
>> + u32 command_id;
>> + u32 length;
>> + u32 status;
>> + u32 _pad;
>> + u64 storage_space;
>> + u64 remaining_space;
>> + u64 max_variable_size;
>> +} __packed;
>> +
>> +
>> +/* -- UEFI app interface. --------------------------------------------------- */
>> +
>> +struct qcuefi_client {
>> + struct device *dev;
>> + struct qseecom_device *qsee;
>> + struct qseecom_dma dma;
>> + struct efivars efivars;
>> + u32 app_id;
>> +};
>> +
>> +static efi_status_t qsee_uefi_status_to_efi(u32 status)
>> +{
>> + u64 category = status & 0xf0000000;
>> + u64 code = status & 0x0fffffff;
>> +
>> + return category << (BITS_PER_LONG - 32) | code;
>> +}
>> +
>> +static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
>> + const efi_guid_t *guid, u32 *attributes,
>> + unsigned long *data_size, void *data)
>> +{
>> + struct qsee_req_uefi_get_variable *req_data;
>> + struct qsee_rsp_uefi_get_variable *rsp_data;
>> + struct qseecom_dma dma_req;
>> + struct qseecom_dma dma_rsp;
>> + unsigned long name_size = utf16_strsize(name);
>> + unsigned long buffer_size = *data_size;
>> + unsigned long size;
>> + efi_status_t efi_status;
>> + int status;
>> +
>> + /* Validation: We need a name and GUID. */
>> + if (!name || !guid)
>> + return EFI_INVALID_PARAMETER;
>> +
>> + /* Validation: We need a buffer if the buffer_size is nonzero. */
>> + if (buffer_size && !data)
>> + return EFI_INVALID_PARAMETER;
>> +
>> + /* Compute required size (upper limit with alignments). */
>> + size = sizeof(*req_data) + sizeof(*guid) + name_size /* Inputs. */
>> + + sizeof(*rsp_data) + buffer_size /* Outputs. */
>> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
>> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
>
> Do we need to pack everything into a single DMA buffer? Otherwise it would be better to add qseecom_dma_alloc_aligned function, which will take care of the alignment for a single data piece.
It may be possible to split this into two buffers, one for input and one for
output, but packing of input parameters would still be required (see the
assignments to req_data below).
For the input, you essentially provide one buffer (address) to qseecom,
starting with req_data describing the layout in it. This description is
offset-based, so there's no way to specify multiple addresses/buffers as input.
The output behaves similarly, it's just the secure OS that does the packing.
And since we already have to take care of aligning the input parameters, I'm
not sure that it makes sense to split this into two.
>
>> +
>> + /* Make sure we have enough DMA memory. */
>> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
>
> This looks like an antipattern. Please allocate the buffer before querrying the data and free it afterwards.
I thought it might make sense to pre-allocate it, but I can change that.
>> + if (status)
>> + return EFI_OUT_OF_RESOURCES;
>> +
>> + /* Align request struct. */
>> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
>> + req_data = dma_req.virt;
>> +
>> + /* Set up request data. */
>> + req_data->command_id = QSEE_CMD_UEFI_GET_VARIABLE;
>> + req_data->data_size = buffer_size;
>> + req_data->name_offset = sizeof(*req_data);
>> + req_data->name_size = name_size;
>> + req_data->guid_offset = QSEECOM_DMA_ALIGN(req_data->name_offset + name_size);
>> + req_data->guid_size = sizeof(*guid);
>> + req_data->length = req_data->guid_offset + req_data->guid_size;
>> +
>> + dma_req.size = req_data->length;
>> +
>> + /* Copy request parameters. */
>> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, name_size);
>> + memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
>> +
>> + /* Align response struct. */
>> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
>> + rsp_data = dma_rsp.virt;
>> +
>> + /* Perform SCM call. */
>> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
>> +
>> + /* Check for errors and validate. */
>> + if (status)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->command_id != QSEE_CMD_UEFI_GET_VARIABLE)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->status) {
>> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
>> + efi_status = qsee_uefi_status_to_efi(rsp_data->status);
>> +
>> + /* Update size and attributes in case buffer is too small. */
>> + if (efi_status == EFI_BUFFER_TOO_SMALL) {
>> + *data_size = rsp_data->data_size;
>> + if (attributes)
>> + *attributes = rsp_data->attributes;
>> + }
>> +
>> + return efi_status;
>> + }
>> +
>> + if (rsp_data->data_offset + rsp_data->data_size > rsp_data->length)
>> + return EFI_DEVICE_ERROR;
>> +
>> + /* Set attributes and data size even if buffer is too small. */
>> + *data_size = rsp_data->data_size;
>> + if (attributes)
>> + *attributes = rsp_data->attributes;
>> +
>> + /*
>> + * If we have a buffer size of zero and no buffer, just return
>> + * attributes and required size.
>> + */
>> + if (buffer_size == 0 && !data)
>> + return EFI_SUCCESS;
>> +
>> + /* Validate output buffer size. */
>> + if (buffer_size < rsp_data->data_size)
>> + return EFI_BUFFER_TOO_SMALL;
>> +
>> + /* Copy to output buffer. Note: We're guaranteed to have one at this point. */
>> + memcpy(data, dma_rsp.virt + rsp_data->data_offset, rsp_data->data_size);
>> + return EFI_SUCCESS;
>> +}
>> +
>> +static efi_status_t qsee_uefi_set_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
>> + const efi_guid_t *guid, u32 attributes,
>> + unsigned long data_size, const void *data)
>> +{
>> + struct qsee_req_uefi_set_variable *req_data;
>> + struct qsee_rsp_uefi_set_variable *rsp_data;
>> + struct qseecom_dma dma_req;
>> + struct qseecom_dma dma_rsp;
>> + unsigned long name_size = utf16_strsize(name);
>> + unsigned long size;
>> + int status;
>> +
>> + /* Validate inputs. */
>> + if (!name || !guid)
>> + return EFI_INVALID_PARAMETER;
>> +
>> + /*
>> + * Make sure we have some data if data_size is nonzero. Note: Using a
>> + * size of zero is valid and deletes the variable.
>> + */
>> + if (data_size && !data)
>> + return EFI_INVALID_PARAMETER;
>> +
>> + /* Compute required size (upper limit with alignments). */
>> + size = sizeof(*req_data) + name_size + sizeof(*guid) + data_size /* Inputs. */
>> + + sizeof(*rsp_data) /* Outputs. */
>> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
>> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
>> +
>> + /* Make sure we have enough DMA memory. */
>> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
>> + if (status)
>> + return EFI_OUT_OF_RESOURCES;
>> +
>> + /* Align request struct. */
>> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
>> + req_data = dma_req.virt;
>> +
>> + /* Set up request data. */
>> + req_data->command_id = QSEE_CMD_UEFI_SET_VARIABLE;
>> + req_data->attributes = attributes;
>> + req_data->name_offset = sizeof(*req_data);
>> + req_data->name_size = name_size;
>> + req_data->guid_offset = QSEECOM_DMA_ALIGN(req_data->name_offset + name_size);
>> + req_data->guid_size = sizeof(*guid);
>> + req_data->data_offset = req_data->guid_offset + req_data->guid_size;
>> + req_data->data_size = data_size;
>> + req_data->length = req_data->data_offset + data_size;
>> +
>> + /* Copy request parameters. */
>> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, req_data->name_size);
>> + memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
>> +
>> + if (data_size)
>> + memcpy(dma_req.virt + req_data->data_offset, data, req_data->data_size);
>> +
>> + /* Align response struct. */
>> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
>> + rsp_data = dma_rsp.virt;
>> +
>> + /* Perform SCM call. */
>> + dma_req.size = req_data->length;
>> + dma_rsp.size = sizeof(*rsp_data);
>> +
>> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
>> +
>> + /* Check for errors and validate. */
>> + if (status)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->command_id != QSEE_CMD_UEFI_SET_VARIABLE)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->status) {
>> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
>> + return qsee_uefi_status_to_efi(rsp_data->status);
>> + }
>> +
>> + return EFI_SUCCESS;
>> +}
>> +
>> +static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
>> + unsigned long *name_size, efi_char16_t *name,
>> + efi_guid_t *guid)
>> +{
>> + struct qsee_req_uefi_get_next_variable *req_data;
>> + struct qsee_rsp_uefi_get_next_variable *rsp_data;
>> + struct qseecom_dma dma_req;
>> + struct qseecom_dma dma_rsp;
>> + unsigned long size;
>> + efi_status_t efi_status;
>> + int status;
>> +
>> + /* We need some buffers. */
>> + if (!name_size || !name || !guid)
>> + return EFI_INVALID_PARAMETER;
>> +
>> + /* There needs to be at least a single null-character. */
>> + if (*name_size == 0)
>> + return EFI_INVALID_PARAMETER;
>> +
>> + /* Compute required size (upper limit with alignments). */
>> + size = sizeof(*req_data) + sizeof(*guid) + *name_size /* Inputs. */
>> + + sizeof(*rsp_data) + sizeof(*guid) + *name_size /* Outputs. */
>> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
>> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
>> +
>> + /* Make sure we have enough DMA memory. */
>> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
>> + if (status)
>> + return EFI_OUT_OF_RESOURCES;
>> +
>> + /* Align request struct. */
>> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
>> + req_data = dma_req.virt;
>> +
>> + /* Set up request data. */
>> + req_data->command_id = QSEE_CMD_UEFI_GET_NEXT_VARIABLE;
>> + req_data->guid_offset = QSEECOM_DMA_ALIGN(sizeof(*req_data));
>> + req_data->guid_size = sizeof(*guid);
>> + req_data->name_offset = req_data->guid_offset + req_data->guid_size;
>> + req_data->name_size = *name_size;
>> + req_data->length = req_data->name_offset + req_data->name_size;
>> +
>> + dma_req.size = req_data->length;
>> +
>> + /* Copy request parameters. */
>> + memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
>> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, *name_size);
>> +
>> + /* Align response struct. */
>> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
>> + rsp_data = dma_rsp.virt;
>> +
>> + /* Perform SCM call. */
>> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id, &dma_req, &dma_rsp);
>> +
>> + /* Check for errors and validate. */
>> + if (status)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->command_id != QSEE_CMD_UEFI_GET_NEXT_VARIABLE)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->status) {
>> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
>> + efi_status = qsee_uefi_status_to_efi(rsp_data->status);
>> +
>> + /* Update size with required size in case buffer is too small. */
>> + if (efi_status == EFI_BUFFER_TOO_SMALL)
>> + *name_size = rsp_data->name_size;
>> +
>> + return efi_status;
>> + }
>> +
>> + if (rsp_data->name_offset + rsp_data->name_size > rsp_data->length)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->guid_offset + rsp_data->guid_size > rsp_data->length)
>> + return EFI_DEVICE_ERROR;
>> +
>> + if (rsp_data->name_size > *name_size) {
>> + *name_size = rsp_data->name_size;
>> + return EFI_BUFFER_TOO_SMALL;
>> + }
>> +
>> + if (rsp_data->guid_size != sizeof(*guid))
>> + return EFI_DEVICE_ERROR;
>> +
>> + /* Copy response fields. */
>> + memcpy(guid, dma_rsp.virt + rsp_data->guid_offset, rsp_data->guid_size);
>> + utf16_copy_to_buf(name, dma_rsp.virt + rsp_data->name_offset, rsp_data->name_size);
>> + *name_size = rsp_data->name_size;
>> +
>> + return 0;
>> +}
>> +
>> +
>> +/* -- Global efivar interface. ---------------------------------------------- */
>> +
>> +static struct qcuefi_client *__qcuefi;
>> +static DEFINE_MUTEX(__qcuefi_lock);
>> +
>> +static int qcuefi_set_reference(struct qcuefi_client *qcuefi)
>> +{
>> + mutex_lock(&__qcuefi_lock);
>> +
>> + if (qcuefi && __qcuefi) {
>> + mutex_unlock(&__qcuefi_lock);
>> + return -EEXIST;
>> + }
>> +
>> + __qcuefi = qcuefi;
>> +
>> + mutex_unlock(&__qcuefi_lock);
>> + return 0;
>> +}
>> +
>> +static struct qcuefi_client *qcuefi_acquire(void)
>
> Doesn't this need the __locks annotation? Generally I think it is easier to inline these two functions, there is no particular need to have such wrappers.
Right, I forgot about the annotations.
The idea of those was that it's easier to reason about locking. Anyone wanting
to access __qcuefi outside of these three functions (set, acquire, release),
must go through qcuefi_acquire(), which ensures exclusive access. So mistakes
are a lot more obvious.
>> +{
>> + mutex_lock(&__qcuefi_lock);
>> + return __qcuefi;
>> +}
>> +
>> +static void qcuefi_release(void)
>> +{
>> + mutex_unlock(&__qcuefi_lock);
>> +}
>> +
>> +static efi_status_t qcuefi_get_variable(efi_char16_t *name, efi_guid_t *vendor, u32 *attr,
>> + unsigned long *data_size, void *data)
>> +{
>> + struct qcuefi_client *qcuefi;
>> + efi_status_t status;
>> +
>> + qcuefi = qcuefi_acquire();
>> + if (!qcuefi)
>> + return EFI_NOT_READY;
>> +
>> + status = qsee_uefi_get_variable(qcuefi, name, vendor, attr, data_size, data);
>> +
>> + qcuefi_release();
>> + return status;
>> +}
>> +
>> +static efi_status_t qcuefi_set_variable(efi_char16_t *name, efi_guid_t *vendor,
>> + u32 attr, unsigned long data_size, void *data)
>> +{
>> + struct qcuefi_client *qcuefi;
>> + efi_status_t status;
>> +
>> + qcuefi = qcuefi_acquire();
>> + if (!qcuefi)
>> + return EFI_NOT_READY;
>> +
>> + status = qsee_uefi_set_variable(qcuefi, name, vendor, attr, data_size, data);
>> +
>> + qcuefi_release();
>> + return status;
>> +}
>> +
>> +static efi_status_t qcuefi_get_next_variable(unsigned long *name_size, efi_char16_t *name,
>> + efi_guid_t *vendor)
>> +{
>> + struct qcuefi_client *qcuefi;
>> + efi_status_t status;
>> +
>> + qcuefi = qcuefi_acquire();
>> + if (!qcuefi)
>> + return EFI_NOT_READY;
>> +
>> + status = qsee_uefi_get_next_variable(qcuefi, name_size, name, vendor);
>> +
>> + qcuefi_release();
>> + return status;
>> +}
>> +
>> +static const struct efivar_operations qcom_efivar_ops = {
>> + .get_variable = qcuefi_get_variable,
>> + .set_variable = qcuefi_set_variable,
>> + .get_next_variable = qcuefi_get_next_variable,
>> +};
>> +
>> +
>> +/* -- Driver setup. --------------------------------------------------------- */
>> +
>> +static int qcom_uefisecapp_probe(struct platform_device *pdev)
>> +{
>> + struct qcuefi_client *qcuefi;
>> + int status;
>> +
>> + /* Allocate driver data. */
>> + qcuefi = devm_kzalloc(&pdev->dev, sizeof(*qcuefi), GFP_KERNEL);
>> + if (!qcuefi)
>> + return -ENOMEM;
>> +
>> + qcuefi->dev = &pdev->dev;
>> +
>> + /* We expect the parent to be the QSEECOM device. */
>> + qcuefi->qsee = dev_get_drvdata(pdev->dev.parent);
>> + if (!qcuefi->qsee)
>> + return -EINVAL;
>> +
>> + /* Get application id for uefisecapp. */
>> + status = qseecom_app_get_id(qcuefi->qsee, QSEE_UEFISEC_APP_NAME, &qcuefi->app_id);
>> + if (status) {
>> + dev_err(&pdev->dev, "failed to query app ID: %d\n", status);
>> + return status;
>> + }
>> +
>> + /* Set up DMA. One page should be plenty to start with. */
>
> one page?
The driver I've reverse-engineered this from allocates the DMA memory for
interaction with qseecom in multiples of PAGE_SIZE. I'm following that in this
driver, as I don't know whether that's a hard requirement (at least on some
platforms) or not. So I pre-allocate one page (1x PAGE_SIZE bytes) here. But as
you've mentioned above, it might be better to allocate this on-demand in each
call.
>> + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) {
>> + dev_warn(&pdev->dev, "no suitable DMA available\n");
>> + return -EFAULT;
>> + }
>> +
>> + status = qseecom_dma_alloc(&pdev->dev, &qcuefi->dma, PAGE_SIZE, GFP_KERNEL);
>> + if (status)
>> + return status;
>> +
>> + /* Register global reference. */
>> + platform_set_drvdata(pdev, qcuefi);
>> + status = qcuefi_set_reference(qcuefi);
>> + if (status)
>> + goto free;
>> +
>> + /* Register efivar ops. */
>> + status = efivars_register(&qcuefi->efivars, &qcom_efivar_ops);
>> + if (status)
>> + goto clear_reference;
>> +
>> + return 0;
>> +
>> +clear_reference:
>> + qcuefi_set_reference(NULL);
>> +free:
>> + qseecom_dma_free(qcuefi->dev, &qcuefi->dma);
>> + return status;
>> +}
>> +
>> +static int qcom_uefisecapp_remove(struct platform_device *pdev)
>> +{
>> + struct qcuefi_client *qcuefi = platform_get_drvdata(pdev);
>> +
>> + /* Unregister efivar ops. */
>> + efivars_unregister(&qcuefi->efivars);
>> +
>> + /* Block on pending calls and unregister global reference. */
>> + qcuefi_set_reference(NULL);
>> +
>> + /* Free remaining resources. */
>> + qseecom_dma_free(qcuefi->dev, &qcuefi->dma);
>> +
>> + return 0;
>> +}
>> +
>> +static struct platform_driver qcom_uefisecapp_driver = {
>> + .probe = qcom_uefisecapp_probe,
>> + .remove = qcom_uefisecapp_remove,
>> + .driver = {
>> + .name = "qcom_qseecom_uefisecapp",
>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>> + },
>> +};
>> +module_platform_driver(qcom_uefisecapp_driver);
>> +
>> +MODULE_AUTHOR("Maximilian Luz <[email protected]>");
>> +MODULE_DESCRIPTION("Client driver for Qualcomm SEE UEFI Secure App");
>> +MODULE_LICENSE("GPL");
>> +MODULE_ALIAS("platform:qcom_qseecom_uefisecapp");
>
On 3/8/23 15:20, Srinivas Kandagatla wrote:
>
>
> On 08/03/2023 13:48, Maximilian Luz wrote:
>> On 3/8/23 13:53, Srinivas Kandagatla wrote:
>>>
>>>
>>> On 07/03/2023 15:23, Dmitry Baryshkov wrote:
>>>>
>>>>> Make qcom_scm_call, qcom_scm_call_atomic and associated types accessible
>>>>> to other modules.
>>>>
>>>> Generally all the qcom_scm calls are a part of qcom_scm.c. I think it is better to make qseecom_scm_call a part qcom_scm.c (as we were previously doing) rather than exporting the core function.
>>>>
>>>
>>> Other big issue I see in exporting qcom_scm_call() is that there is danger of misuse of this api as this could lead to a path where new apis and its payloads can come directly from userspace via a rogue/hacking modules. This will bypass scm layer completely within kernel.
>>
>> I'm not sure I follow your argument here. If you have the possibility to
>> load your own kernel modules, can you not always bypass the kernel and
>> just directly invoke the respective SCM calls manually? So this is
>> superficial security at best.
> qcom_scm_call() will expose a much bigger window where the user can add new SCM APIs but with the current model of exporting symbols at SCM API level will narrow that down to that API.
>
>>
>> I guess keeping it in qcom_scm could make it easier to spot new
>> in-kernel users of that function and with that better prevent potential
>> misuse in the kernel itself. But then again I'd hope that our review
>> system is good enough to catch such issues regardless and thoroughly
>> question calls to that function (especially ones involving user-space
>> APIs).
>
> One problem I can immediately see here is the facility that will be exploited and promote more development outside upstream.
>
> ex: vendor modules with GKI compliance.
Fair point.
I still believe that squashing everything into a single driver is not
particularly great code/architecture style, but then again I'm not a fan of
vendors not integrating their stuff upstream either.
I guess we should be able to find something that has sufficiently low
complexity so that it can be implemented in qcom_scm while also preventing
external use like that.
Regards,
Max
>
> --srini
>>
>> Regards,
>> Max
>>
>>>
>>> --srini
>>>
>>>> If you wish to limit the kernel bloat, you can split the qcom_scm into per-driver backend and add Kconfig symbols to limit the impact. However I think that these functions are pretty small to justify the effort.
>>>>
>>>
>>>
>>>>>
>>>>> Signed-off-by: Maximilian Luz <[email protected]>
On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
> Add bindings for the Qualcomm Secure Execution Environment interface
> (QSEECOM).
Pretty sure I already asked, but no answer in the commit message. Why do
we need this? You've already declared the platform supports SCM calls
with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
is for non-discoverable h/w we are stuck with. Why is software made
non-discoverable too?
Nodes with only a compatible string are usually just an abuse of DT to
instantiate some driver.
Rob
On 3/8/23 23:16, Rob Herring wrote:
> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
>> Add bindings for the Qualcomm Secure Execution Environment interface
>> (QSEECOM).
>
> Pretty sure I already asked, but no answer in the commit message. Why do
> we need this? You've already declared the platform supports SCM calls
> with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
> is for non-discoverable h/w we are stuck with.
Yes, you've asked this before but I can only repeat what I've written in
my last response to your question: I am not aware of any way to properly
discover the interface at runtime from software.
If it makes you happy, I can put this in the commit message as well...
> Why is software made non-discoverable too?
Please direct that question at the Qualcomm guys who actually designed
that interface. I can't give you an answer to that, and I'm not all that
happy about this either.
To reiterate: I've reverse engineered this based on the Windows driver.
The Windows driver loads on an ACPI HID and it doesn't use any function
to check/verify whether the interface is actually present. Adding a DT
entry is the straight-forward adaption to having a HID in ACPI.
> Nodes with only a compatible string are usually just an abuse of DT to
> instantiate some driver.
If you or anyone here has any idea on how to discover the presence of
this, please feel free to let me know and I'd be happy to implement
that. Until then, I unfortunately don't see any other way of dealing
with this.
Regards,
Max
On 09/03/2023 00:44, Maximilian Luz wrote:
> On 3/8/23 23:16, Rob Herring wrote:
>> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
>>> Add bindings for the Qualcomm Secure Execution Environment interface
>>> (QSEECOM).
>>
>> Pretty sure I already asked, but no answer in the commit message. Why do
>> we need this? You've already declared the platform supports SCM calls
>> with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
>> is for non-discoverable h/w we are stuck with.
>
> Yes, you've asked this before but I can only repeat what I've written in
> my last response to your question: I am not aware of any way to properly
> discover the interface at runtime from software.
>
> If it makes you happy, I can put this in the commit message as well...
>
>> Why is software made non-discoverable too?
>
> Please direct that question at the Qualcomm guys who actually designed
> that interface. I can't give you an answer to that, and I'm not all that
> happy about this either.
>
> To reiterate: I've reverse engineered this based on the Windows driver.
> The Windows driver loads on an ACPI HID and it doesn't use any function
> to check/verify whether the interface is actually present. Adding a DT
> entry is the straight-forward adaption to having a HID in ACPI.
>
>> Nodes with only a compatible string are usually just an abuse of DT to
>> instantiate some driver.
>
> If you or anyone here has any idea on how to discover the presence of
> this, please feel free to let me know and I'd be happy to implement
> that. Until then, I unfortunately don't see any other way of dealing
> with this.
You can probably try requesting QSEECOM version. According to msm-3.18:
uint32_t feature = 10;
rc = qseecom_scm_call(6, 3, &feature, sizeof(feature),
&resp, sizeof(resp));
pr_info("qseecom.qsee_version = 0x%x\n", resp.result);
if (rc) {
pr_err("Failed to get QSEE version info %d\n", rc);
goto exit_del_cdev;
}
--
With best wishes
Dmitry
On 3/9/23 02:33, Dmitry Baryshkov wrote:
> On 09/03/2023 00:44, Maximilian Luz wrote:
>> On 3/8/23 23:16, Rob Herring wrote:
>>> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
>>>> Add bindings for the Qualcomm Secure Execution Environment interface
>>>> (QSEECOM).
>>>
>>> Pretty sure I already asked, but no answer in the commit message. Why do
>>> we need this? You've already declared the platform supports SCM calls
>>> with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
>>> is for non-discoverable h/w we are stuck with.
>>
>> Yes, you've asked this before but I can only repeat what I've written in
>> my last response to your question: I am not aware of any way to properly
>> discover the interface at runtime from software.
>>
>> If it makes you happy, I can put this in the commit message as well...
>>
>>> Why is software made non-discoverable too?
>>
>> Please direct that question at the Qualcomm guys who actually designed
>> that interface. I can't give you an answer to that, and I'm not all that
>> happy about this either.
>>
>> To reiterate: I've reverse engineered this based on the Windows driver.
>> The Windows driver loads on an ACPI HID and it doesn't use any function
>> to check/verify whether the interface is actually present. Adding a DT
>> entry is the straight-forward adaption to having a HID in ACPI.
>>
>>> Nodes with only a compatible string are usually just an abuse of DT to
>>> instantiate some driver.
>>
>> If you or anyone here has any idea on how to discover the presence of
>> this, please feel free to let me know and I'd be happy to implement
>> that. Until then, I unfortunately don't see any other way of dealing
>> with this.
>
> You can probably try requesting QSEECOM version. According to msm-3.18:
>
> uint32_t feature = 10;
>
> rc = qseecom_scm_call(6, 3, &feature, sizeof(feature),
> &resp, sizeof(resp));
> pr_info("qseecom.qsee_version = 0x%x\n", resp.result);
> if (rc) {
> pr_err("Failed to get QSEE version info %d\n", rc);
> goto exit_del_cdev;
> }
>
Thanks! I'll give that a try.
As I can't test this on a device that doesn't have qseecom, it would
probably be a good idea if someone could test this on a device that has
qcom_scm but no qseecom (if those even exist) to make sure this doesn't
misbehave.
Regards,
Max
On 08/03/2023 16:06, Maximilian Luz wrote:
> On 3/7/23 16:36, Dmitry Baryshkov wrote:
>> On 05/03/2023 04:21, Maximilian Luz wrote:
>>> Add support for SCM calls to Secure OS and the Secure Execution
>>> Environment (SEE) residing in the TrustZone (TZ) via the QSEECOM
>>> interface. This allows communication with Secure/TZ applications, for
>>> example 'uefisecapp' managing access to UEFI variables.
>>>
>>> The interface is managed by a platform device to ensure correct lifetime
>>> and establish a device link to the Qualcomm SCM device.
>>>
>>> While this patch introduces only a very basic interface without the more
>>> advanced features (such as re-entrant and blocking SCM calls and
>>> listeners/callbacks), this is enough to talk to the aforementioned
>>> 'uefisecapp'.
>>>
>>> Signed-off-by: Maximilian Luz <[email protected]>
>>> ---
>>>
>>> Changes in v3:
>>> - Rebase ontop of latest qcom_scm changes (qcom_scm.h moved).
>>> - Move qcom_qseecom.h in accordance with qcom_scm.
>>>
>>> Changes in v2:
>>> - Bind the interface to a device.
>>> - Establish a device link to the SCM device to ensure proper ordering.
>>> - Register client apps as child devices instead of requiring them
>>> to be
>>> specified in the device tree.
>>> - Rename (qctree -> qseecom) to allow differentiation between old
>>> (qseecom) and new (smcinvoke) interfaces to the trusted execution
>>> environment.
>>>
>>> ---
>>> MAINTAINERS | 7 +
>>> drivers/firmware/Kconfig | 15 +
>>> drivers/firmware/Makefile | 1 +
>>> drivers/firmware/qcom_qseecom.c | 314 +++++++++++++++++++++
>>> include/linux/firmware/qcom/qcom_qseecom.h | 190 +++++++++++++
>>> 5 files changed, 527 insertions(+)
>>> create mode 100644 drivers/firmware/qcom_qseecom.c
>>> create mode 100644 include/linux/firmware/qcom/qcom_qseecom.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 9201967d198d..1545914a592c 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -17380,6 +17380,13 @@ F:
>>> Documentation/networking/device_drivers/cellular/qualcomm/rmnet.rst
>>> F: drivers/net/ethernet/qualcomm/rmnet/
>>> F: include/linux/if_rmnet.h
>>> +QUALCOMM SECURE EXECUTION ENVIRONMENT COMMUNICATION DRIVER
>>> +M: Maximilian Luz <[email protected]>
>>> +L: [email protected]
>>> +S: Maintained
>>> +F: drivers/firmware/qcom_qseecom.c
>>> +F: include/linux/firmware/qcom/qcom_qseecom.h
>>> +
>>> QUALCOMM TSENS THERMAL DRIVER
>>> M: Amit Kucheria <[email protected]>
>>> M: Thara Gopinath <[email protected]>
>>
>>
>>
>>> +
>>> +
>>> +/* -- Platform specific data.
>>> ----------------------------------------------- */
>>> +
>>> +struct qseecom_data {
>>> + const struct mfd_cell *cells;
>>
>> The child qseecom devices are not platform devices, so MFD should not
>> be used here. Please use aux devices instead.
>
> Okay, makes sense. Would this still work with your suggestion in patch 4
> regarding a custom (?) bus or can the aux bus be used to implement that?
> From a
> quick look, I believe we could use aux bus for this but I haven't worked
> with
> that before, so I don't know if I'm missing something.
Initially I thought that a custom bus might be required, to provide
custom probe function. After giving it a thought, I think you can get
away with using aux bus. So, embed the struct auxiliary_device into
qseecom_app_device.
>
>>> + int num_cells;
>>> +};
>>> +
>>> +static const struct of_device_id qseecom_dt_match[] = {
>>> + { .compatible = "qcom,qseecom-sc8280xp", },
>>
>> Forgot to mention, while doign review. There is no need for this
>> compat until you provide the actual data. Please move it to the patch 4.
>
> Sure, will do that.
>
>>> + { .compatible = "qcom,qseecom", },
>>> + { }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, qseecom_dt_match);
>>
>>
>
> Regards,
> Max
--
With best wishes
Dmitry
On 09/03/2023 04:27, Maximilian Luz wrote:
> On 3/9/23 02:33, Dmitry Baryshkov wrote:
>> On 09/03/2023 00:44, Maximilian Luz wrote:
>>> On 3/8/23 23:16, Rob Herring wrote:
>>>> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
>>>>> Add bindings for the Qualcomm Secure Execution Environment interface
>>>>> (QSEECOM).
>>>>
>>>> Pretty sure I already asked, but no answer in the commit message.
>>>> Why do
>>>> we need this? You've already declared the platform supports SCM calls
>>>> with "qcom,scm". Why can't you probe whether you have QSEECOM or
>>>> not? DT
>>>> is for non-discoverable h/w we are stuck with.
>>>
>>> Yes, you've asked this before but I can only repeat what I've written in
>>> my last response to your question: I am not aware of any way to properly
>>> discover the interface at runtime from software.
>>>
>>> If it makes you happy, I can put this in the commit message as well...
>>>
>>>> Why is software made non-discoverable too?
>>>
>>> Please direct that question at the Qualcomm guys who actually designed
>>> that interface. I can't give you an answer to that, and I'm not all that
>>> happy about this either.
>>>
>>> To reiterate: I've reverse engineered this based on the Windows driver.
>>> The Windows driver loads on an ACPI HID and it doesn't use any function
>>> to check/verify whether the interface is actually present. Adding a DT
>>> entry is the straight-forward adaption to having a HID in ACPI.
>>>
>>>> Nodes with only a compatible string are usually just an abuse of DT to
>>>> instantiate some driver.
>>>
>>> If you or anyone here has any idea on how to discover the presence of
>>> this, please feel free to let me know and I'd be happy to implement
>>> that. Until then, I unfortunately don't see any other way of dealing
>>> with this.
>>
>> You can probably try requesting QSEECOM version. According to msm-3.18:
>>
>> uint32_t feature = 10;
>>
>> rc = qseecom_scm_call(6, 3, &feature, sizeof(feature),
>> &resp, sizeof(resp));
>> pr_info("qseecom.qsee_version = 0x%x\n", resp.result);
>> if (rc) {
>> pr_err("Failed to get QSEE version info %d\n", rc);
>> goto exit_del_cdev;
>> }
>>
>
> Thanks! I'll give that a try.
>
> As I can't test this on a device that doesn't have qseecom, it would
> probably be a good idea if someone could test this on a device that has
> qcom_scm but no qseecom (if those even exist) to make sure this doesn't
> misbehave.
I could not find a vendor dts which doesn't have the qseecom device
(checked the source trees from 3.4 to the latest revisions).
--
With best wishes
Dmitry
On 08/03/2023 17:02, Maximilian Luz wrote:
> On 3/7/23 16:51, Dmitry Baryshkov wrote:
>> On 05/03/2023 04:21, Maximilian Luz wrote:
>>> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
>>> EFI variables cannot be accessed via the standard interface in EFI
>>> runtime mode. The respective functions return EFI_UNSUPPORTED. On these
>>> platforms, we instead need to talk to uefisecapp. This commit provides
>>> support for this and registers the respective efivars operations to
>>> access EFI variables from the kernel.
>>>
>>> Communication with uefisecapp follows the Qualcomm QSEECOM / Secure OS
>>> conventions via the respective SCM call interface. This is also the
>>> reason why variable access works normally while boot services are
>>> active. During this time, said SCM interface is managed by the boot
>>> services. When calling ExitBootServices(), the ownership is transferred
>>> to the kernel. Therefore, UEFI must not use that interface itself (as
>>> multiple parties accessing this interface at the same time may lead to
>>> complications) and cannot access variables for us.
>>>
>>> Signed-off-by: Maximilian Luz <[email protected]>
>>> ---
>>>
>>> Changes in v3:
>>> - No functional changes.
>>>
>>> Changes in v2:
>>> - Rename (qctree -> qseecom) to allow differentiation between old
>>> (qseecom) and new (smcinvoke) interfaces to the trusted execution
>>> environment.
>>>
>>> ---
>>> MAINTAINERS | 6 +
>>> drivers/firmware/Kconfig | 16 +
>>> drivers/firmware/Makefile | 1 +
>>> drivers/firmware/qcom_qseecom.c | 11 +-
>>> drivers/firmware/qcom_qseecom_uefisecapp.c | 746 +++++++++++++++++++++
>>> 5 files changed, 779 insertions(+), 1 deletion(-)
>>> create mode 100644 drivers/firmware/qcom_qseecom_uefisecapp.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index ef1f806986e9..35d614de6bbc 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -17371,6 +17371,12 @@ S: Maintained
>>> F: Documentation/devicetree/bindings/mtd/qcom,nandc.yaml
>>> F: drivers/mtd/nand/raw/qcom_nandc.c
>>> +QUALCOMM QSEECOM UEFISECAPP DRIVER
>>> +M: Maximilian Luz <[email protected]>
>>> +L: [email protected]
>>> +S: Maintained
>>> +F: drivers/firmware/qcom_qseecom_uefisecapp.c
>>> +
>>> QUALCOMM RMNET DRIVER
>>> M: Subash Abhinov Kasiviswanathan <[email protected]>
>>> M: Sean Tranchetti <[email protected]>
>>> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
>>> index 22eec0835abf..b9ad753a195b 100644
>>> --- a/drivers/firmware/Kconfig
>>> +++ b/drivers/firmware/Kconfig
>>> @@ -241,6 +241,22 @@ config QCOM_QSEECOM
>>> Select M or Y here to enable the QSEECOM interface driver.
>>> +config QCOM_QSEECOM_UEFISECAPP
>>> + tristate "Qualcomm SEE UEFI Secure App client driver"
>>> + depends on QCOM_QSEECOM
>>> + depends on EFI
>>> + help
>>> + Various Qualcomm SoCs do not allow direct access to EFI
>>> variables.
>>> + Instead, these need to be accessed via the UEFI Secure
>>> Application
>>> + (uefisecapp), residing in the Secure Execution Environment (SEE).
>>> +
>>> + This module provides a client driver for uefisecapp,
>>> installing efivar
>>> + operations to allow the kernel accessing EFI variables, and
>>> via that also
>>> + provide user-space with access to EFI variables via efivarfs.
>>> +
>>> + Select M or Y here to provide access to EFI variables on the
>>> + aforementioned platforms.
>>> +
>>> config SYSFB
>>> bool
>>> select BOOT_VESA_SUPPORT
>>> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
>>> index aa48e0821b7d..d41b094a5e58 100644
>>> --- a/drivers/firmware/Makefile
>>> +++ b/drivers/firmware/Makefile
>>> @@ -21,6 +21,7 @@ obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
>>> obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
>>> qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
>>> obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
>>> +obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
>>> obj-$(CONFIG_SYSFB) += sysfb.o
>>> obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o
>>> obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
>>> diff --git a/drivers/firmware/qcom_qseecom.c
>>> b/drivers/firmware/qcom_qseecom.c
>>> index efa5b115b2f1..ae42341dce1c 100644
>>> --- a/drivers/firmware/qcom_qseecom.c
>>> +++ b/drivers/firmware/qcom_qseecom.c
>>> @@ -216,8 +216,17 @@ struct qseecom_data {
>>> int num_cells;
>>> };
>>> +static const struct mfd_cell qseecom_cells_sc8280xp[] = {
>>> + { .name = "qcom_qseecom_uefisecapp", },
>>> +};
>>> +
>>> +static const struct qseecom_data qseecom_data_sc8280xp = {
>>> + .cells = qseecom_cells_sc8280xp,
>>> + .num_cells = ARRAY_SIZE(qseecom_cells_sc8280xp),
>>> +};
>>> +
>>> static const struct of_device_id qseecom_dt_match[] = {
>>> - { .compatible = "qcom,qseecom-sc8280xp", },
>>> + { .compatible = "qcom,qseecom-sc8280xp", .data =
>>> &qseecom_data_sc8280xp },
>>
>> Note: the SoC doesn't fully describe the list of avialble qseecom
>> applications. It depends on the particular vendor enabling or
>> disabling them. It might be better to turn qseecom into the bus,
>> create a list of possible app names and use qseecom_app_get_id() to
>> check whether the application is available or not. Then you can
>> instantiate only available devices and provide the app_id via struct
>> qseecom_app_device wrapping struct device:
>
> Alright, so would we have a SoC specific list of apps that we then check
> or a
> global one?
Good question. Especially given that additional applications can be
loaded on demand. I'd say, let's go for the global list for now. If
necessary we can customize that further basing on the SoC compatibles.
>
> As I've elaborated on a previous version: I'm a bit wary of using
> qseecom_app_get_id() in this way, since the Windows driver I've got this
> from
> expects the app to be present when calling that function. So I don't
> know much
> about the failure cases, especially when it isn't present.
>
> At this point, I'm just assuming that "res.status !=
> QSEECOM_RESULT_SUCCESS"
> means the app isn't present, but I don't know whether this can fail in
> other
> ways. For a proper detection system I'd prefer if we can differentiate
> between
> "some internal failure" and "not-present" cases.
Please take a look at
https://git.codelinaro.org/clo/la/kernel/msm-5.10/-/blob/KERNEL.PLATFORM.1.0.r1-13000-kernel.0/drivers/misc/qseecom.c#L2683
Note, the driver supports loading and unloading applications, we can
ignore that for now.
>
> I can still give that a try thouhg.
>
> Maybe you or someone else knows a bit more about that than I do and can
> set my
> mind at ease? (Again, all my knowledge about this comes from reverse
> engineering, so it's not that much.)
>
>> struct qseecom_app_device {
>> struct device dev;
>> u32 app_id;
>> };
>>
>>> { .compatible = "qcom,qseecom", },
>>> { }
>>> };
>>> diff --git a/drivers/firmware/qcom_qseecom_uefisecapp.c
>>> b/drivers/firmware/qcom_qseecom_uefisecapp.c
>>> new file mode 100644
>>> index 000000000000..8d772fe1f589
>>> --- /dev/null
>>> +++ b/drivers/firmware/qcom_qseecom_uefisecapp.c
>>> @@ -0,0 +1,746 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * Client driver for Qualcomm UEFI Secure Application
>>> (qcom.tz.uefisecapp).
>>> + * Provides access to UEFI variables on platforms where they are
>>> secured by the
>>> + * aforementioned Secure Execution Environment (SEE) application.
>>> + *
>>> + * Copyright (C) 2023 Maximilian Luz <[email protected]>
>>> + */
>>> +
>>> +#include <linux/efi.h>
>>> +#include <linux/firmware/qcom/qcom_qseecom.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mutex.h>
>>> +#include <linux/of.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/types.h>
>>> +
>>> +
>>> +/* -- UTF-16 helpers.
>>> ------------------------------------------------------- */
>>> +
>>> +static unsigned long utf16_strnlen(const efi_char16_t *str, unsigned
>>> long max)
>>> +{
>>> + size_t i;
>>> +
>>> + for (i = 0; *str != 0 && i < max; i++, str++) {
>>> + /* Do nothing, all is handled in the for statement. */
>>> + }
>>> +
>>> + return i;
>>> +}
>>> +
>>> +/**
>>> + * utf16_strsize() - Compute the number of bytes required to store a
>>> + * null-terminated UTF-16 string.
>>> + * @str: The string to compute the size for.
>>> + *
>>> + * Return: Returns the minimum number of bytes required to store the
>>> given
>>> + * null-terminated string, including its null-terminator.
>>> + */
>>> +static unsigned long utf16_strsize(const efi_char16_t *str)
>>> +{
>>> + return (utf16_strnlen(str, U32_MAX) + 1) * sizeof(str[0]);
>>> +}
>>> +
>>> +static unsigned long utf16_strlcpy(efi_char16_t *dst, const
>>> efi_char16_t *src, unsigned long size)
>>> +{
>>> + unsigned long actual = utf16_strnlen(src, size - 1);
>>> +
>>> + memcpy(dst, src, actual * sizeof(src[0]));
>>> + dst[actual] = 0;
>>> +
>>> + return actual;
>>> +}
>>> +
>>> +/**
>>> + * utf16_copy_to_buf() - Copy the given UTF-16 string to a buffer.
>>> + * @dst: Pointer to the output buffer
>>> + * @src: Pointer to the null-terminated UTF-16 string to be copied.
>>> + * @bytes: Maximum number of bytes to copy.
>>> + *
>>> + * Copies the given string to the given buffer, ensuring that the
>>> output buffer
>>> + * is not overrun and that the string in the output buffer will
>>> always be
>>> + * null-terminated.
>>> + *
>>> + * Return: Returns the length of the copied string, without
>>> null-terminator.
>>> + */
>>> +static unsigned long utf16_copy_to_buf(efi_char16_t *dst, const
>>> efi_char16_t *src,
>>> + unsigned long bytes)
>>> +{
>>> + return utf16_strlcpy(dst, src, bytes / sizeof(src[0]));
>>> +}
>>
>> These functions seem generic enough, please move them to
>> lib/ucs2_string.c. Use the existing functions if they suit.
>
> Right, will do. Not sure how I missed those when looking for utf16
> string functions...
>
>>> +
>>> +
>>
>> Double empty lines
>>
>>> +/* -- Qualcomm "uefisecapp" interface definitions.
>>> -------------------------- */
>>> +
>>> +#define QSEE_UEFISEC_APP_NAME "qcom.tz.uefisecapp"
>>> +
>>> +#define QSEE_CMD_UEFI(x) (0x8000 | (x))
>>> +#define QSEE_CMD_UEFI_GET_VARIABLE QSEE_CMD_UEFI(0)
>>> +#define QSEE_CMD_UEFI_SET_VARIABLE QSEE_CMD_UEFI(1)
>>> +#define QSEE_CMD_UEFI_GET_NEXT_VARIABLE QSEE_CMD_UEFI(2)
>>> +#define QSEE_CMD_UEFI_QUERY_VARIABLE_INFO QSEE_CMD_UEFI(3)
>>> +
>>> +/**
>>> + * struct qsee_req_uefi_get_variable - Request for GetVariable command.
>>> + * @command_id: The ID of the command. Must be
>>> %QSEE_CMD_UEFI_GET_VARIABLE.
>>> + * @length: Length of the request in bytes, including this
>>> struct and any
>>> + * parameters (name, GUID) stored after it as well as
>>> any padding
>>> + * thereof for alignment.
>>> + * @name_offset: Offset from the start of this struct to where the
>>> variable
>>> + * name is stored (as utf-16 string), in bytes.
>>> + * @name_size: Size of the name parameter in bytes, including
>>> null-terminator.
>>> + * @guid_offset: Offset from the start of this struct to where the GUID
>>> + * parameter is stored, in bytes.
>>> + * @guid_size: Size of the GUID parameter in bytes, i.e.
>>> sizeof(efi_guid_t).
>>> + * @data_size: Size of the output buffer, in bytes.
>>> + */
>>> +struct qsee_req_uefi_get_variable {
>>> + u32 command_id;
>>> + u32 length;
>>> + u32 name_offset;
>>> + u32 name_size;
>>> + u32 guid_offset;
>>> + u32 guid_size;
>>> + u32 data_size;
>>> +} __packed;
>>> +
>>> +/**
>>> + * struct qsee_rsp_uefi_get_variable - Response for GetVariable
>>> command.
>>> + * @command_id: The ID of the command. Should be
>>> %QSEE_CMD_UEFI_GET_VARIABLE.
>>> + * @length: Length of the response in bytes, including this
>>> struct and the
>>> + * returned data.
>>> + * @status: Status of this command.
>>> + * @attributes: EFI variable attributes.
>>> + * @data_offset: Offset from the start of this struct to where the
>>> data is
>>> + * stored, in bytes.
>>> + * @data_size: Size of the returned data, in bytes. In case status
>>> indicates
>>> + * that the buffer is too small, this will be the size
>>> required
>>> + * to store the EFI variable data.
>>> + */
>>> +struct qsee_rsp_uefi_get_variable {
>>> + u32 command_id;
>>> + u32 length;
>>> + u32 status;
>>> + u32 attributes;
>>> + u32 data_offset;
>>> + u32 data_size;
>>> +} __packed;
>>> +
>>> +/**
>>> + * struct qsee_req_uefi_set_variable - Request for the SetVariable
>>> command.
>>> + * @command_id: The ID of the command. Must be
>>> %QSEE_CMD_UEFI_SET_VARIABLE.
>>> + * @length: Length of the request in bytes, including this
>>> struct and any
>>> + * parameters (name, GUID, data) stored after it as
>>> well as any
>>> + * padding thereof required for alignment.
>>> + * @name_offset: Offset from the start of this struct to where the
>>> variable
>>> + * name is stored (as utf-16 string), in bytes.
>>> + * @name_size: Size of the name parameter in bytes, including
>>> null-terminator.
>>> + * @guid_offset: Offset from the start of this struct to where the GUID
>>> + * parameter is stored, in bytes.
>>> + * @guid_size: Size of the GUID parameter in bytes, i.e.
>>> sizeof(efi_guid_t).
>>> + * @attributes: The EFI variable attributes to set for this variable.
>>> + * @data_offset: Offset from the start of this struct to where the
>>> EFI variable
>>> + * data is stored, in bytes.
>>> + * @data_size: Size of EFI variable data, in bytes.
>>> + *
>>> + */
>>> +struct qsee_req_uefi_set_variable {
>>> + u32 command_id;
>>> + u32 length;
>>> + u32 name_offset;
>>> + u32 name_size;
>>> + u32 guid_offset;
>>> + u32 guid_size;
>>> + u32 attributes;
>>> + u32 data_offset;
>>> + u32 data_size;
>>> +} __packed;
>>> +
>>> +/**
>>> + * struct qsee_rsp_uefi_set_variable - Response for the SetVariable
>>> command.
>>> + * @command_id: The ID of the command. Should be
>>> %QSEE_CMD_UEFI_SET_VARIABLE.
>>> + * @length: The length of this response, i.e. the size of this
>>> struct in
>>> + * bytes.
>>> + * @status: Status of this command.
>>> + * @_unknown1: Unknown response field.
>>> + * @_unknown2: Unknown response field.
>>> + */
>>> +struct qsee_rsp_uefi_set_variable {
>>> + u32 command_id;
>>> + u32 length;
>>> + u32 status;
>>> + u32 _unknown1;
>>> + u32 _unknown2;
>>> +} __packed;
>>> +
>>> +/**
>>> + * struct qsee_req_uefi_get_next_variable - Request for the
>>> + * GetNextVariableName command.
>>> + * @command_id: The ID of the command. Must be
>>> + * %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
>>> + * @length: Length of the request in bytes, including this
>>> struct and any
>>> + * parameters (name, GUID) stored after it as well as
>>> any padding
>>> + * thereof for alignment.
>>> + * @guid_offset: Offset from the start of this struct to where the GUID
>>> + * parameter is stored, in bytes.
>>> + * @guid_size: Size of the GUID parameter in bytes, i.e.
>>> sizeof(efi_guid_t).
>>> + * @name_offset: Offset from the start of this struct to where the
>>> variable
>>> + * name is stored (as utf-16 string), in bytes.
>>> + * @name_size: Size of the name parameter in bytes, including
>>> null-terminator.
>>> + */
>>> +struct qsee_req_uefi_get_next_variable {
>>> + u32 command_id;
>>> + u32 length;
>>> + u32 guid_offset;
>>> + u32 guid_size;
>>> + u32 name_offset;
>>> + u32 name_size;
>>> +} __packed;
>>> +
>>> +/**
>>> + * struct qsee_rsp_uefi_get_next_variable - Response for the
>>> + * GetNextVariableName command.
>>> + * @command_id: The ID of the command. Should be
>>> + * %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
>>> + * @length: Length of the response in bytes, including this
>>> struct and any
>>> + * parameters (name, GUID) stored after it as well as
>>> any padding
>>> + * thereof for alignment.
>>> + * @status: Status of this command.
>>> + * @guid_size: Size of the GUID parameter in bytes, i.e.
>>> sizeof(efi_guid_t).
>>> + * @name_offset: Offset from the start of this struct to where the
>>> variable
>>> + * name is stored (as utf-16 string), in bytes.
>>> + * @name_size: Size of the name parameter in bytes, including
>>> null-terminator.
>>> + */
>>> +struct qsee_rsp_uefi_get_next_variable {
>>> + u32 command_id;
>>> + u32 length;
>>> + u32 status;
>>> + u32 guid_offset;
>>> + u32 guid_size;
>>> + u32 name_offset;
>>> + u32 name_size;
>>> +} __packed;
>>> +
>>> +/**
>>> + * struct qsee_req_uefi_query_variable_info - Response for the
>>> + * GetNextVariableName command.
>>> + * @command_id: The ID of the command. Must be
>>> + * %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
>>> + * @length: The length of this request, i.e. the size of this
>>> struct in
>>> + * bytes.
>>> + * @attributes: The storage attributes to query the info for.
>>> + */
>>> +struct qsee_req_uefi_query_variable_info {
>>> + u32 command_id;
>>> + u32 length;
>>> + u32 attributes;
>>> +} __packed;
>>> +
>>> +/**
>>> + * struct qsee_rsp_uefi_query_variable_info - Response for the
>>> + * GetNextVariableName command.
>>> + * @command_id: The ID of the command. Must be
>>> + * %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
>>> + * @length: The length of this response, i.e. the size of
>>> this
>>> + * struct in bytes.
>>> + * @status: Status of this command.
>>> + * @_pad: Padding.
>>> + * @storage_space: Full storage space size, in bytes.
>>> + * @remaining_space: Free storage space available, in bytes.
>>> + * @max_variable_size: Maximum variable data size, in bytes.
>>> + */
>>> +struct qsee_rsp_uefi_query_variable_info {
>>> + u32 command_id;
>>> + u32 length;
>>> + u32 status;
>>> + u32 _pad;
>>> + u64 storage_space;
>>> + u64 remaining_space;
>>> + u64 max_variable_size;
>>> +} __packed;
>>> +
>>> +
>>> +/* -- UEFI app interface.
>>> --------------------------------------------------- */
>>> +
>>> +struct qcuefi_client {
>>> + struct device *dev;
>>> + struct qseecom_device *qsee;
>>> + struct qseecom_dma dma;
>>> + struct efivars efivars;
>>> + u32 app_id;
>>> +};
>>> +
>>> +static efi_status_t qsee_uefi_status_to_efi(u32 status)
>>> +{
>>> + u64 category = status & 0xf0000000;
>>> + u64 code = status & 0x0fffffff;
>>> +
>>> + return category << (BITS_PER_LONG - 32) | code;
>>> +}
>>> +
>>> +static efi_status_t qsee_uefi_get_variable(struct qcuefi_client
>>> *qcuefi, const efi_char16_t *name,
>>> + const efi_guid_t *guid, u32 *attributes,
>>> + unsigned long *data_size, void *data)
>>> +{
>>> + struct qsee_req_uefi_get_variable *req_data;
>>> + struct qsee_rsp_uefi_get_variable *rsp_data;
>>> + struct qseecom_dma dma_req;
>>> + struct qseecom_dma dma_rsp;
>>> + unsigned long name_size = utf16_strsize(name);
>>> + unsigned long buffer_size = *data_size;
>>> + unsigned long size;
>>> + efi_status_t efi_status;
>>> + int status;
>>> +
>>> + /* Validation: We need a name and GUID. */
>>> + if (!name || !guid)
>>> + return EFI_INVALID_PARAMETER;
>>> +
>>> + /* Validation: We need a buffer if the buffer_size is nonzero. */
>>> + if (buffer_size && !data)
>>> + return EFI_INVALID_PARAMETER;
>>> +
>>> + /* Compute required size (upper limit with alignments). */
>>> + size = sizeof(*req_data) + sizeof(*guid) + name_size /* Inputs. */
>>> + + sizeof(*rsp_data) + buffer_size /*
>>> Outputs. */
>>> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input
>>> parameter alignments. */
>>> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output
>>> parameter alignments. */
>>
>> Do we need to pack everything into a single DMA buffer? Otherwise it
>> would be better to add qseecom_dma_alloc_aligned function, which will
>> take care of the alignment for a single data piece.
>
> It may be possible to split this into two buffers, one for input and one
> for
> output, but packing of input parameters would still be required (see the
> assignments to req_data below).
>
> For the input, you essentially provide one buffer (address) to qseecom,
> starting with req_data describing the layout in it. This description is
> offset-based, so there's no way to specify multiple addresses/buffers as
> input.
> The output behaves similarly, it's just the secure OS that does the
> packing.
>
> And since we already have to take care of aligning the input parameters,
> I'm
> not sure that it makes sense to split this into two.
I see, thanks for the explanation. Maybe you can add a wrapping call
that will take the sizes of required arguments (as a variadic array?)
and will return prepared req and all the pointers and/or offsets? I
think that having to specify these alignment 'extras' is errror prone.
>
>>
>>> +
>>> + /* Make sure we have enough DMA memory. */
>>> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size,
>>> GFP_KERNEL);
>>
>> This looks like an antipattern. Please allocate the buffer before
>> querrying the data and free it afterwards.
>
> I thought it might make sense to pre-allocate it, but I can change that.
>
>>> + if (status)
>>> + return EFI_OUT_OF_RESOURCES;
>>> +
>>> + /* Align request struct. */
>>> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
>>> + req_data = dma_req.virt;
>>> +
>>> + /* Set up request data. */
>>> + req_data->command_id = QSEE_CMD_UEFI_GET_VARIABLE;
>>> + req_data->data_size = buffer_size;
>>> + req_data->name_offset = sizeof(*req_data);
>>> + req_data->name_size = name_size;
>>> + req_data->guid_offset = QSEECOM_DMA_ALIGN(req_data->name_offset
>>> + name_size);
>>> + req_data->guid_size = sizeof(*guid);
>>> + req_data->length = req_data->guid_offset + req_data->guid_size;
>>> +
>>> + dma_req.size = req_data->length;
>>> +
>>> + /* Copy request parameters. */
>>> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name,
>>> name_size);
>>> + memcpy(dma_req.virt + req_data->guid_offset, guid,
>>> req_data->guid_size);
>>> +
>>> + /* Align response struct. */
>>> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
>>> + rsp_data = dma_rsp.virt;
>>> +
>>> + /* Perform SCM call. */
>>> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id,
>>> &dma_req, &dma_rsp);
>>> +
>>> + /* Check for errors and validate. */
>>> + if (status)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->command_id != QSEE_CMD_UEFI_GET_VARIABLE)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length >
>>> dma_rsp.size)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->status) {
>>> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n",
>>> __func__, rsp_data->status);
>>> + efi_status = qsee_uefi_status_to_efi(rsp_data->status);
>>> +
>>> + /* Update size and attributes in case buffer is too small. */
>>> + if (efi_status == EFI_BUFFER_TOO_SMALL) {
>>> + *data_size = rsp_data->data_size;
>>> + if (attributes)
>>> + *attributes = rsp_data->attributes;
>>> + }
>>> +
>>> + return efi_status;
>>> + }
>>> +
>>> + if (rsp_data->data_offset + rsp_data->data_size > rsp_data->length)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + /* Set attributes and data size even if buffer is too small. */
>>> + *data_size = rsp_data->data_size;
>>> + if (attributes)
>>> + *attributes = rsp_data->attributes;
>>> +
>>> + /*
>>> + * If we have a buffer size of zero and no buffer, just return
>>> + * attributes and required size.
>>> + */
>>> + if (buffer_size == 0 && !data)
>>> + return EFI_SUCCESS;
>>> +
>>> + /* Validate output buffer size. */
>>> + if (buffer_size < rsp_data->data_size)
>>> + return EFI_BUFFER_TOO_SMALL;
>>> +
>>> + /* Copy to output buffer. Note: We're guaranteed to have one at
>>> this point. */
>>> + memcpy(data, dma_rsp.virt + rsp_data->data_offset,
>>> rsp_data->data_size);
>>> + return EFI_SUCCESS;
>>> +}
>>> +
>>> +static efi_status_t qsee_uefi_set_variable(struct qcuefi_client
>>> *qcuefi, const efi_char16_t *name,
>>> + const efi_guid_t *guid, u32 attributes,
>>> + unsigned long data_size, const void *data)
>>> +{
>>> + struct qsee_req_uefi_set_variable *req_data;
>>> + struct qsee_rsp_uefi_set_variable *rsp_data;
>>> + struct qseecom_dma dma_req;
>>> + struct qseecom_dma dma_rsp;
>>> + unsigned long name_size = utf16_strsize(name);
>>> + unsigned long size;
>>> + int status;
>>> +
>>> + /* Validate inputs. */
>>> + if (!name || !guid)
>>> + return EFI_INVALID_PARAMETER;
>>> +
>>> + /*
>>> + * Make sure we have some data if data_size is nonzero. Note:
>>> Using a
>>> + * size of zero is valid and deletes the variable.
>>> + */
>>> + if (data_size && !data)
>>> + return EFI_INVALID_PARAMETER;
>>> +
>>> + /* Compute required size (upper limit with alignments). */
>>> + size = sizeof(*req_data) + name_size + sizeof(*guid) +
>>> data_size /* Inputs. */
>>> + + sizeof(*rsp_data) /*
>>> Outputs. */
>>> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input
>>> parameter alignments. */
>>> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output
>>> parameter alignments. */
>>> +
>>> + /* Make sure we have enough DMA memory. */
>>> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size,
>>> GFP_KERNEL);
>>> + if (status)
>>> + return EFI_OUT_OF_RESOURCES;
>>> +
>>> + /* Align request struct. */
>>> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
>>> + req_data = dma_req.virt;
>>> +
>>> + /* Set up request data. */
>>> + req_data->command_id = QSEE_CMD_UEFI_SET_VARIABLE;
>>> + req_data->attributes = attributes;
>>> + req_data->name_offset = sizeof(*req_data);
>>> + req_data->name_size = name_size;
>>> + req_data->guid_offset = QSEECOM_DMA_ALIGN(req_data->name_offset
>>> + name_size);
>>> + req_data->guid_size = sizeof(*guid);
>>> + req_data->data_offset = req_data->guid_offset +
>>> req_data->guid_size;
>>> + req_data->data_size = data_size;
>>> + req_data->length = req_data->data_offset + data_size;
>>> +
>>> + /* Copy request parameters. */
>>> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name,
>>> req_data->name_size);
>>> + memcpy(dma_req.virt + req_data->guid_offset, guid,
>>> req_data->guid_size);
>>> +
>>> + if (data_size)
>>> + memcpy(dma_req.virt + req_data->data_offset, data,
>>> req_data->data_size);
>>> +
>>> + /* Align response struct. */
>>> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
>>> + rsp_data = dma_rsp.virt;
>>> +
>>> + /* Perform SCM call. */
>>> + dma_req.size = req_data->length;
>>> + dma_rsp.size = sizeof(*rsp_data);
>>> +
>>> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id,
>>> &dma_req, &dma_rsp);
>>> +
>>> + /* Check for errors and validate. */
>>> + if (status)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->command_id != QSEE_CMD_UEFI_SET_VARIABLE)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length >
>>> dma_rsp.size)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->status) {
>>> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n",
>>> __func__, rsp_data->status);
>>> + return qsee_uefi_status_to_efi(rsp_data->status);
>>> + }
>>> +
>>> + return EFI_SUCCESS;
>>> +}
>>> +
>>> +static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client
>>> *qcuefi,
>>> + unsigned long *name_size, efi_char16_t *name,
>>> + efi_guid_t *guid)
>>> +{
>>> + struct qsee_req_uefi_get_next_variable *req_data;
>>> + struct qsee_rsp_uefi_get_next_variable *rsp_data;
>>> + struct qseecom_dma dma_req;
>>> + struct qseecom_dma dma_rsp;
>>> + unsigned long size;
>>> + efi_status_t efi_status;
>>> + int status;
>>> +
>>> + /* We need some buffers. */
>>> + if (!name_size || !name || !guid)
>>> + return EFI_INVALID_PARAMETER;
>>> +
>>> + /* There needs to be at least a single null-character. */
>>> + if (*name_size == 0)
>>> + return EFI_INVALID_PARAMETER;
>>> +
>>> + /* Compute required size (upper limit with alignments). */
>>> + size = sizeof(*req_data) + sizeof(*guid) + *name_size /*
>>> Inputs. */
>>> + + sizeof(*rsp_data) + sizeof(*guid) + *name_size /*
>>> Outputs. */
>>> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /*
>>> Input parameter alignments. */
>>> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /*
>>> Output parameter alignments. */
>>> +
>>> + /* Make sure we have enough DMA memory. */
>>> + status = qseecom_dma_realloc(qcuefi->dev, &qcuefi->dma, size,
>>> GFP_KERNEL);
>>> + if (status)
>>> + return EFI_OUT_OF_RESOURCES;
>>> +
>>> + /* Align request struct. */
>>> + qseecom_dma_aligned(&qcuefi->dma, &dma_req, 0);
>>> + req_data = dma_req.virt;
>>> +
>>> + /* Set up request data. */
>>> + req_data->command_id = QSEE_CMD_UEFI_GET_NEXT_VARIABLE;
>>> + req_data->guid_offset = QSEECOM_DMA_ALIGN(sizeof(*req_data));
>>> + req_data->guid_size = sizeof(*guid);
>>> + req_data->name_offset = req_data->guid_offset +
>>> req_data->guid_size;
>>> + req_data->name_size = *name_size;
>>> + req_data->length = req_data->name_offset + req_data->name_size;
>>> +
>>> + dma_req.size = req_data->length;
>>> +
>>> + /* Copy request parameters. */
>>> + memcpy(dma_req.virt + req_data->guid_offset, guid,
>>> req_data->guid_size);
>>> + utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name,
>>> *name_size);
>>> +
>>> + /* Align response struct. */
>>> + qseecom_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
>>> + rsp_data = dma_rsp.virt;
>>> +
>>> + /* Perform SCM call. */
>>> + status = qseecom_app_send(qcuefi->qsee, qcuefi->app_id,
>>> &dma_req, &dma_rsp);
>>> +
>>> + /* Check for errors and validate. */
>>> + if (status)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->command_id != QSEE_CMD_UEFI_GET_NEXT_VARIABLE)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length >
>>> dma_rsp.size)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->status) {
>>> + dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n",
>>> __func__, rsp_data->status);
>>> + efi_status = qsee_uefi_status_to_efi(rsp_data->status);
>>> +
>>> + /* Update size with required size in case buffer is too
>>> small. */
>>> + if (efi_status == EFI_BUFFER_TOO_SMALL)
>>> + *name_size = rsp_data->name_size;
>>> +
>>> + return efi_status;
>>> + }
>>> +
>>> + if (rsp_data->name_offset + rsp_data->name_size > rsp_data->length)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->guid_offset + rsp_data->guid_size > rsp_data->length)
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + if (rsp_data->name_size > *name_size) {
>>> + *name_size = rsp_data->name_size;
>>> + return EFI_BUFFER_TOO_SMALL;
>>> + }
>>> +
>>> + if (rsp_data->guid_size != sizeof(*guid))
>>> + return EFI_DEVICE_ERROR;
>>> +
>>> + /* Copy response fields. */
>>> + memcpy(guid, dma_rsp.virt + rsp_data->guid_offset,
>>> rsp_data->guid_size);
>>> + utf16_copy_to_buf(name, dma_rsp.virt + rsp_data->name_offset,
>>> rsp_data->name_size);
>>> + *name_size = rsp_data->name_size;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +
>>> +/* -- Global efivar interface.
>>> ---------------------------------------------- */
>>> +
>>> +static struct qcuefi_client *__qcuefi;
>>> +static DEFINE_MUTEX(__qcuefi_lock);
>>> +
>>> +static int qcuefi_set_reference(struct qcuefi_client *qcuefi)
>>> +{
>>> + mutex_lock(&__qcuefi_lock);
>>> +
>>> + if (qcuefi && __qcuefi) {
>>> + mutex_unlock(&__qcuefi_lock);
>>> + return -EEXIST;
>>> + }
>>> +
>>> + __qcuefi = qcuefi;
>>> +
>>> + mutex_unlock(&__qcuefi_lock);
>>> + return 0;
>>> +}
>>> +
>>> +static struct qcuefi_client *qcuefi_acquire(void)
>>
>> Doesn't this need the __locks annotation? Generally I think it is
>> easier to inline these two functions, there is no particular need to
>> have such wrappers.
>
> Right, I forgot about the annotations.
>
> The idea of those was that it's easier to reason about locking. Anyone
> wanting
> to access __qcuefi outside of these three functions (set, acquire,
> release),
> must go through qcuefi_acquire(), which ensures exclusive access. So
> mistakes
> are a lot more obvious.
Ok.
>
>>> +{
>>> + mutex_lock(&__qcuefi_lock);
>>> + return __qcuefi;
>>> +}
>>> +
>>> +static void qcuefi_release(void)
>>> +{
>>> + mutex_unlock(&__qcuefi_lock);
>>> +}
>>> +
>>> +static efi_status_t qcuefi_get_variable(efi_char16_t *name,
>>> efi_guid_t *vendor, u32 *attr,
>>> + unsigned long *data_size, void *data)
>>> +{
>>> + struct qcuefi_client *qcuefi;
>>> + efi_status_t status;
>>> +
>>> + qcuefi = qcuefi_acquire();
>>> + if (!qcuefi)
>>> + return EFI_NOT_READY;
>>> +
>>> + status = qsee_uefi_get_variable(qcuefi, name, vendor, attr,
>>> data_size, data);
>>> +
>>> + qcuefi_release();
>>> + return status;
>>> +}
>>> +
>>> +static efi_status_t qcuefi_set_variable(efi_char16_t *name,
>>> efi_guid_t *vendor,
>>> + u32 attr, unsigned long data_size, void *data)
>>> +{
>>> + struct qcuefi_client *qcuefi;
>>> + efi_status_t status;
>>> +
>>> + qcuefi = qcuefi_acquire();
>>> + if (!qcuefi)
>>> + return EFI_NOT_READY;
>>> +
>>> + status = qsee_uefi_set_variable(qcuefi, name, vendor, attr,
>>> data_size, data);
>>> +
>>> + qcuefi_release();
>>> + return status;
>>> +}
>>> +
>>> +static efi_status_t qcuefi_get_next_variable(unsigned long
>>> *name_size, efi_char16_t *name,
>>> + efi_guid_t *vendor)
>>> +{
>>> + struct qcuefi_client *qcuefi;
>>> + efi_status_t status;
>>> +
>>> + qcuefi = qcuefi_acquire();
>>> + if (!qcuefi)
>>> + return EFI_NOT_READY;
>>> +
>>> + status = qsee_uefi_get_next_variable(qcuefi, name_size, name,
>>> vendor);
>>> +
>>> + qcuefi_release();
>>> + return status;
>>> +}
>>> +
>>> +static const struct efivar_operations qcom_efivar_ops = {
>>> + .get_variable = qcuefi_get_variable,
>>> + .set_variable = qcuefi_set_variable,
>>> + .get_next_variable = qcuefi_get_next_variable,
>>> +};
>>> +
>>> +
>>> +/* -- Driver setup.
>>> --------------------------------------------------------- */
>>> +
>>> +static int qcom_uefisecapp_probe(struct platform_device *pdev)
>>> +{
>>> + struct qcuefi_client *qcuefi;
>>> + int status;
>>> +
>>> + /* Allocate driver data. */
>>> + qcuefi = devm_kzalloc(&pdev->dev, sizeof(*qcuefi), GFP_KERNEL);
>>> + if (!qcuefi)
>>> + return -ENOMEM;
>>> +
>>> + qcuefi->dev = &pdev->dev;
>>> +
>>> + /* We expect the parent to be the QSEECOM device. */
>>> + qcuefi->qsee = dev_get_drvdata(pdev->dev.parent);
>>> + if (!qcuefi->qsee)
>>> + return -EINVAL;
>>> +
>>> + /* Get application id for uefisecapp. */
>>> + status = qseecom_app_get_id(qcuefi->qsee, QSEE_UEFISEC_APP_NAME,
>>> &qcuefi->app_id);
>>> + if (status) {
>>> + dev_err(&pdev->dev, "failed to query app ID: %d\n", status);
>>> + return status;
>>> + }
>>> +
>>> + /* Set up DMA. One page should be plenty to start with. */
>>
>> one page?
>
> The driver I've reverse-engineered this from allocates the DMA memory for
> interaction with qseecom in multiples of PAGE_SIZE. I'm following that
> in this
> driver, as I don't know whether that's a hard requirement (at least on some
> platforms) or not. So I pre-allocate one page (1x PAGE_SIZE bytes) here.
> But as
> you've mentioned above, it might be better to allocate this on-demand in
> each
> call.
Probably the comment was misplaced. It talks about 1 page, but it is
placed right before a call to dma_set_mask rather than dma_alloc.
>
>>> + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) {
>>> + dev_warn(&pdev->dev, "no suitable DMA available\n");
>>> + return -EFAULT;
>>> + }
>>> +
>>> + status = qseecom_dma_alloc(&pdev->dev, &qcuefi->dma, PAGE_SIZE,
>>> GFP_KERNEL);
>>> + if (status)
>>> + return status;
>>> +
>>> + /* Register global reference. */
>>> + platform_set_drvdata(pdev, qcuefi);
>>> + status = qcuefi_set_reference(qcuefi);
>>> + if (status)
>>> + goto free;
>>> +
>>> + /* Register efivar ops. */
>>> + status = efivars_register(&qcuefi->efivars, &qcom_efivar_ops);
>>> + if (status)
>>> + goto clear_reference;
>>> +
>>> + return 0;
>>> +
>>> +clear_reference:
>>> + qcuefi_set_reference(NULL);
>>> +free:
>>> + qseecom_dma_free(qcuefi->dev, &qcuefi->dma);
>>> + return status;
>>> +}
>>> +
>>> +static int qcom_uefisecapp_remove(struct platform_device *pdev)
>>> +{
>>> + struct qcuefi_client *qcuefi = platform_get_drvdata(pdev);
>>> +
>>> + /* Unregister efivar ops. */
>>> + efivars_unregister(&qcuefi->efivars);
>>> +
>>> + /* Block on pending calls and unregister global reference. */
>>> + qcuefi_set_reference(NULL);
>>> +
>>> + /* Free remaining resources. */
>>> + qseecom_dma_free(qcuefi->dev, &qcuefi->dma);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static struct platform_driver qcom_uefisecapp_driver = {
>>> + .probe = qcom_uefisecapp_probe,
>>> + .remove = qcom_uefisecapp_remove,
>>> + .driver = {
>>> + .name = "qcom_qseecom_uefisecapp",
>>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>> + },
>>> +};
>>> +module_platform_driver(qcom_uefisecapp_driver);
>>> +
>>> +MODULE_AUTHOR("Maximilian Luz <[email protected]>");
>>> +MODULE_DESCRIPTION("Client driver for Qualcomm SEE UEFI Secure App");
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_ALIAS("platform:qcom_qseecom_uefisecapp");
>>
--
With best wishes
Dmitry
On 08/03/2023 15:59, Maximilian Luz wrote:
> On 3/7/23 16:32, Dmitry Baryshkov wrote:
>> On 05/03/2023 04:21, Maximilian Luz wrote:
>
> [...]
>
>>> +/* -- DMA helpers.
>>> ---------------------------------------------------------- */
>>> +
>>> +/* DMA requirements for QSEECOM SCM calls. */
>>> +#define QSEECOM_DMA_ALIGNMENT 8
>>> +#define QSEECOM_DMA_ALIGN(ptr) ALIGN(ptr, QSEECOM_DMA_ALIGNMENT)
>>> +
>>> +/**
>>> + * struct qseecom_dma - DMA memory region.
>>> + * @size: Size of the memory region, in bytes.
>>> + * @virt: Pointer / virtual address to the memory, accessible by the
>>> kernel.
>>> + * @phys: Physical address of the memory region.
>>> + */
>>> +struct qseecom_dma {
>>> + unsigned long size;
>>
>> size_t ?
>
> Will fix.
>
>>> + void *virt;
>>> + dma_addr_t phys;
>>> +};
>>
>> Do we really need this wrapper and the wrappers bellow? They look like
>> a pure syntax sugar for me, hiding the dma functions from the user.
>
> The idea was that they take care of proper allocation. The Windows
> driver that
> I've reverse-engineered this from allocates memory in multiples of
> PAGE_SIZE,
> so I believe that this might be a requirement (at least on some
> systems). These
> functions are there to ensure that and with that prevent potential bugs by
> taking that responsibility from the caller.
I see. As I wrote in another comment, it might be better to review the
whole memory allocation system: pass required sizes, get the data filled.
>
>>> +
>>> +/**
>>> + * qseecom_dma_alloc() - Allocate a DMA-able memory region suitable
>>> for QSEECOM
>>> + * SCM calls.
>>> + * @dev: The device used for DMA memory allocation.
>>> + * @dma: Where to write the allocated memory addresses and size to.
>>> + * @size: Minimum size of the memory to be allocated.
>>> + * @gfp: Flags used for allocation.
>>> + *
>>> + * Allocate a DMA-able memory region suitable for interaction with SEE
>>> + * services/applications and the TzOS. The provided size is treated
>>> as the
>>> + * minimum required size and rounded up, if necessary. The actually
>>> allocated
>>> + * memory region will be stored in @dma. Allocated memory must be
>>> freed via
>>> + * qseecom_dma_free().
>>> + *
>>> + * Return: Returns zero on success, -ENOMEM on allocation failure.
>>> + */
>>> +static inline int qseecom_dma_alloc(struct device *dev, struct
>>> qseecom_dma *dma,
>>> + unsigned long size, gfp_t gfp)
>>
>> size_t size
>>
>> gfp is not used
>
> Right, that should have been passed to dma_alloc_coherent(). Will fix that.
>
>>> +{
>>> + size = PAGE_ALIGN(size);
>>> +
>>> + dma->virt = dma_alloc_coherent(dev, size, &dma->phys, GFP_KERNEL);
>>> + if (!dma->virt)
>>> + return -ENOMEM;
>>> +
>>> + dma->size = size;
>>> + return 0;
>>> +}
>>> +
>>> +/**
>>> + * qseecom_dma_free() - Free a DMA memory region.
>>> + * @dev: The device used for allocation.
>>> + * @dma: The DMA region to be freed.
>>> + *
>>> + * Free a DMA region previously allocated via qseecom_dma_alloc().
>>> Note that
>>> + * freeing sub-regions is not supported.
>>> + */
>>> +static inline void qseecom_dma_free(struct device *dev, struct
>>> qseecom_dma *dma)
>>> +{
>>> + dma_free_coherent(dev, dma->size, dma->virt, dma->phys);
>>> +}
>>> +
>>> +/**
>>> + * qseecom_dma_realloc() - Re-allocate DMA memory region with the
>>> requested size.
>>> + * @dev: The device used for allocation.
>>> + * @dma: The region descriptor to be updated.
>>> + * @size: The new requested size.
>>> + * @gfp: Flags used for allocation.
>>> + *
>>> + * Re-allocates a DMA memory region suitable for QSEECOM SCM calls
>>> to fit the
>>> + * requested amount of bytes, if necessary. Does nothing if the
>>> provided region
>>> + * already has enough space to store the requested data.
>>> + *
>>> + * See qseecom_dma_alloc() for details.
>>> + *
>>> + * Return: Returns zero on success, -ENOMEM on allocation failure.
>>> + */
>>> +static inline int qseecom_dma_realloc(struct device *dev, struct
>>> qseecom_dma *dma,
>>> + unsigned long size, gfp_t gfp)
>>> +{
>>> + if (PAGE_ALIGN(size) <= dma->size)
>>> + return 0;
>>> +
>>> + qseecom_dma_free(dev, dma);
>>> + return qseecom_dma_alloc(dev, dma, size, gfp);
>>> +}
>>
>> I'll comment on this function when commenting patch 4.
>>
>>> +
>>> +/**
>>> + * qseecom_dma_aligned() - Create a aligned DMA memory sub-region
>>> suitable for
>>> + * QSEECOM SCM calls.
>>> + * @base: Base DMA memory region, in which the new region will
>>> reside.
>>> + * @out: Descriptor to store the aligned sub-region in.
>>> + * @offset: The offset inside base region at which to place the new
>>> sub-region.
>>> + *
>>> + * Creates an aligned DMA memory region suitable for QSEECOM SCM
>>> calls at or
>>> + * after the given offset. The size of the sub-region will be set to
>>> the
>>> + * remaining size in the base region after alignment, i.e., the end
>>> of the
>>> + * sub-region will be equal the end of the base region.
>>> + *
>>> + * Return: Returns zero on success or -EINVAL if the new aligned
>>> memory address
>>> + * would point outside the base region.
>>> + */
>>> +static inline int qseecom_dma_aligned(const struct qseecom_dma
>>> *base, struct qseecom_dma *out,
>>> + unsigned long offset)
>>> +{
>>> + void *aligned = (void *)QSEECOM_DMA_ALIGN((uintptr_t)base->virt
>>> + offset);
>>> +
>>> + if (aligned - base->virt > base->size)
>>> + return -EINVAL;
>>> +
>>> + out->virt = aligned;
>>> + out->phys = base->phys + (out->virt - base->virt);
>>> + out->size = base->size - (out->virt - base->virt);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +
>>> +/* -- Common interface.
>>> ----------------------------------------------------- */
>>> +
>>> +struct qseecom_device {
>>> + struct device *dev;
>>> + struct mutex scm_call_lock; /* Guards QSEECOM SCM calls. */
>>
>> There can be only one instance of the qseecom call infrastructure.
>> Make this mutex static in the qcom_scm.c
>
> Right, will do that.
>
>>> +};
>>> +
>>> +
>>> +/* -- Secure-OS SCM call interface.
>>> ----------------------------------------- */
>>> +
>>> +#define QSEECOM_TZ_OWNER_TZ_APPS 48
>>> +#define QSEECOM_TZ_OWNER_QSEE_OS 50
>>> +
>>> +#define QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER 0
>>> +#define QSEECOM_TZ_SVC_APP_MGR 1
>>> +
>>> +enum qseecom_scm_result {
>>> + QSEECOM_RESULT_SUCCESS = 0,
>>> + QSEECOM_RESULT_INCOMPLETE = 1,
>>> + QSEECOM_RESULT_BLOCKED_ON_LISTENER = 2,
>>> + QSEECOM_RESULT_FAILURE = 0xFFFFFFFF,
>>> +};
>>> +
>>> +enum qseecom_scm_resp_type {
>>> + QSEECOM_SCM_RES_APP_ID = 0xEE01,
>>> + QSEECOM_SCM_RES_QSEOS_LISTENER_ID = 0xEE02,
>>> +};
>>> +
>>> +/**
>>> + * struct qseecom_scm_resp - QSEECOM SCM call response.
>>> + * @status: Status of the SCM call. See &enum qseecom_scm_result.
>>> + * @resp_type: Type of the response. See &enum qseecom_scm_resp_type.
>>> + * @data: Response data. The type of this data is given in
>>> @resp_type.
>>> + */
>>> +struct qseecom_scm_resp {
>>> + u64 status;
>>> + u64 resp_type;
>>> + u64 data;
>>> +};
>>> +
>>> +int qseecom_scm_call(struct qseecom_device *qsee, const struct
>>> qcom_scm_desc *desc,
>>> + struct qseecom_scm_resp *res);
>>> +
>>> +
>>> +/* -- Secure App interface.
>>> ------------------------------------------------- */
>>> +
>>> +#define QSEECOM_MAX_APP_NAME_SIZE 64
>>> +
>>> +int qseecom_app_get_id(struct qseecom_device *qsee, const char
>>> *app_name, u32 *app_id);
>>> +int qseecom_app_send(struct qseecom_device *qsee, u32 app_id, struct
>>> qseecom_dma *req,
>>> + struct qseecom_dma *rsp);
>>
>> I think that only these calls should be made public / available to
>> other modules. qseecom_scm_call also is an internal helper.
>
> So move all calls to qcom_scm and only make these two public? Or move only
> qseecom_scm_call() to qcom_scm (which would require to make it public
> there,
> however). Or how would you want this to look?
I think we can make it with just these calls being public. Or even with
just the second one being public and available to other drivers. If the
app_id is a part of qseecom_app_device, we can pass that device to the
qseecom_app_send(). And qseecom_app_get_id() becomes a part of app
registration.
>
>>> +
>>> +#endif /* _LINUX_QCOM_QSEECOM_H */
>>
>
> Regards,
> Max
--
With best wishes
Dmitry
On 3/9/23 09:19, Dmitry Baryshkov wrote:
> On 09/03/2023 04:27, Maximilian Luz wrote:
>> On 3/9/23 02:33, Dmitry Baryshkov wrote:
>>> On 09/03/2023 00:44, Maximilian Luz wrote:
>>>> On 3/8/23 23:16, Rob Herring wrote:
>>>>> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
>>>>>> Add bindings for the Qualcomm Secure Execution Environment interface
>>>>>> (QSEECOM).
>>>>>
>>>>> Pretty sure I already asked, but no answer in the commit message. Why do
>>>>> we need this? You've already declared the platform supports SCM calls
>>>>> with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
>>>>> is for non-discoverable h/w we are stuck with.
>>>>
>>>> Yes, you've asked this before but I can only repeat what I've written in
>>>> my last response to your question: I am not aware of any way to properly
>>>> discover the interface at runtime from software.
>>>>
>>>> If it makes you happy, I can put this in the commit message as well...
>>>>
>>>>> Why is software made non-discoverable too?
>>>>
>>>> Please direct that question at the Qualcomm guys who actually designed
>>>> that interface. I can't give you an answer to that, and I'm not all that
>>>> happy about this either.
>>>>
>>>> To reiterate: I've reverse engineered this based on the Windows driver.
>>>> The Windows driver loads on an ACPI HID and it doesn't use any function
>>>> to check/verify whether the interface is actually present. Adding a DT
>>>> entry is the straight-forward adaption to having a HID in ACPI.
>>>>
>>>>> Nodes with only a compatible string are usually just an abuse of DT to
>>>>> instantiate some driver.
>>>>
>>>> If you or anyone here has any idea on how to discover the presence of
>>>> this, please feel free to let me know and I'd be happy to implement
>>>> that. Until then, I unfortunately don't see any other way of dealing
>>>> with this.
>>>
>>> You can probably try requesting QSEECOM version. According to msm-3.18:
>>>
>>> uint32_t feature = 10;
>>>
>>> rc = qseecom_scm_call(6, 3, &feature, sizeof(feature),
>>> &resp, sizeof(resp));
>>> pr_info("qseecom.qsee_version = 0x%x\n", resp.result);
>>> if (rc) {
>>> pr_err("Failed to get QSEE version info %d\n", rc);
>>> goto exit_del_cdev;
>>> }
>>>
>>
>> Thanks! I'll give that a try.
>>
>> As I can't test this on a device that doesn't have qseecom, it would
>> probably be a good idea if someone could test this on a device that has
>> qcom_scm but no qseecom (if those even exist) to make sure this doesn't
>> misbehave.
>
> I could not find a vendor dts which doesn't have the qseecom device (checked the source trees from 3.4 to the latest revisions).
>
Thanks for checking!
So that only leaves one potential issue: The re-entrant/blocking calls
not being handled at the moment. If we detect qseecom based on the
version and then try to query the app ID, we could get some devices that
use those.
I'm not sure what the consequences there are, i.e. if we're potentially
blocking something else if one of those calls blocks on such devices. Is
there any way we can detect this beforehand?
The current proposal isn't very good at handling that either as it
assumes that this depends on the SoC generation (which it probably
doesn't). So I guess one possibility is to make the list of app-names to
be checked SoC specific as well. That at least limits the scope
somewhat. Maybe you have some other ideas?
Regards,
Max
On Thu, 9 Mar 2023 at 22:34, Maximilian Luz <[email protected]> wrote:
>
> On 3/9/23 09:19, Dmitry Baryshkov wrote:
> > On 09/03/2023 04:27, Maximilian Luz wrote:
> >> On 3/9/23 02:33, Dmitry Baryshkov wrote:
> >>> On 09/03/2023 00:44, Maximilian Luz wrote:
> >>>> On 3/8/23 23:16, Rob Herring wrote:
> >>>>> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
> >>>>>> Add bindings for the Qualcomm Secure Execution Environment interface
> >>>>>> (QSEECOM).
> >>>>>
> >>>>> Pretty sure I already asked, but no answer in the commit message. Why do
> >>>>> we need this? You've already declared the platform supports SCM calls
> >>>>> with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
> >>>>> is for non-discoverable h/w we are stuck with.
> >>>>
> >>>> Yes, you've asked this before but I can only repeat what I've written in
> >>>> my last response to your question: I am not aware of any way to properly
> >>>> discover the interface at runtime from software.
> >>>>
> >>>> If it makes you happy, I can put this in the commit message as well...
> >>>>
> >>>>> Why is software made non-discoverable too?
> >>>>
> >>>> Please direct that question at the Qualcomm guys who actually designed
> >>>> that interface. I can't give you an answer to that, and I'm not all that
> >>>> happy about this either.
> >>>>
> >>>> To reiterate: I've reverse engineered this based on the Windows driver.
> >>>> The Windows driver loads on an ACPI HID and it doesn't use any function
> >>>> to check/verify whether the interface is actually present. Adding a DT
> >>>> entry is the straight-forward adaption to having a HID in ACPI.
> >>>>
> >>>>> Nodes with only a compatible string are usually just an abuse of DT to
> >>>>> instantiate some driver.
> >>>>
> >>>> If you or anyone here has any idea on how to discover the presence of
> >>>> this, please feel free to let me know and I'd be happy to implement
> >>>> that. Until then, I unfortunately don't see any other way of dealing
> >>>> with this.
> >>>
> >>> You can probably try requesting QSEECOM version. According to msm-3.18:
> >>>
> >>> uint32_t feature = 10;
> >>>
> >>> rc = qseecom_scm_call(6, 3, &feature, sizeof(feature),
> >>> &resp, sizeof(resp));
> >>> pr_info("qseecom.qsee_version = 0x%x\n", resp.result);
> >>> if (rc) {
> >>> pr_err("Failed to get QSEE version info %d\n", rc);
> >>> goto exit_del_cdev;
> >>> }
> >>>
> >>
> >> Thanks! I'll give that a try.
> >>
> >> As I can't test this on a device that doesn't have qseecom, it would
> >> probably be a good idea if someone could test this on a device that has
> >> qcom_scm but no qseecom (if those even exist) to make sure this doesn't
> >> misbehave.
> >
> > I could not find a vendor dts which doesn't have the qseecom device (checked the source trees from 3.4 to the latest revisions).
> >
>
> Thanks for checking!
>
> So that only leaves one potential issue: The re-entrant/blocking calls
> not being handled at the moment. If we detect qseecom based on the
> version and then try to query the app ID, we could get some devices that
> use those.
>
> I'm not sure what the consequences there are, i.e. if we're potentially
> blocking something else if one of those calls blocks on such devices. Is
> there any way we can detect this beforehand?
Unfortunately I don't know.
>
> The current proposal isn't very good at handling that either as it
> assumes that this depends on the SoC generation (which it probably
> doesn't). So I guess one possibility is to make the list of app-names to
> be checked SoC specific as well. That at least limits the scope
> somewhat. Maybe you have some other ideas?
As long as it doesn't concern the external interfaces, it sounds fine
with me. Let's get the first implementation in, then we can expand and
extend the details/implementation.
--
With best wishes
Dmitry
On 3/9/23 09:36, Dmitry Baryshkov wrote:
> On 08/03/2023 17:02, Maximilian Luz wrote:
>> On 3/7/23 16:51, Dmitry Baryshkov wrote:
>>> On 05/03/2023 04:21, Maximilian Luz wrote:
>>>> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
[...]
>>
>> As I've elaborated on a previous version: I'm a bit wary of using
>> qseecom_app_get_id() in this way, since the Windows driver I've got this from
>> expects the app to be present when calling that function. So I don't know much
>> about the failure cases, especially when it isn't present.
>>
>> At this point, I'm just assuming that "res.status != QSEECOM_RESULT_SUCCESS"
>> means the app isn't present, but I don't know whether this can fail in other
>> ways. For a proper detection system I'd prefer if we can differentiate between
>> "some internal failure" and "not-present" cases.
>
> Please take a look at https://git.codelinaro.org/clo/la/kernel/msm-5.10/-/blob/KERNEL.PLATFORM.1.0.r1-13000-kernel.0/drivers/misc/qseecom.c#L2683
>
> Note, the driver supports loading and unloading applications, we can ignore that for now.
>
Thanks! That looks quite helpful.
[...]
>>>> +static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
>>>> + const efi_guid_t *guid, u32 *attributes,
>>>> + unsigned long *data_size, void *data)
>>>> +{
>>>> + struct qsee_req_uefi_get_variable *req_data;
>>>> + struct qsee_rsp_uefi_get_variable *rsp_data;
>>>> + struct qseecom_dma dma_req;
>>>> + struct qseecom_dma dma_rsp;
>>>> + unsigned long name_size = utf16_strsize(name);
>>>> + unsigned long buffer_size = *data_size;
>>>> + unsigned long size;
>>>> + efi_status_t efi_status;
>>>> + int status;
>>>> +
>>>> + /* Validation: We need a name and GUID. */
>>>> + if (!name || !guid)
>>>> + return EFI_INVALID_PARAMETER;
>>>> +
>>>> + /* Validation: We need a buffer if the buffer_size is nonzero. */
>>>> + if (buffer_size && !data)
>>>> + return EFI_INVALID_PARAMETER;
>>>> +
>>>> + /* Compute required size (upper limit with alignments). */
>>>> + size = sizeof(*req_data) + sizeof(*guid) + name_size /* Inputs. */
>>>> + + sizeof(*rsp_data) + buffer_size /* Outputs. */
>>>> + + 2 * (QSEECOM_DMA_ALIGNMENT - 1) /* Input parameter alignments. */
>>>> + + 1 * (QSEECOM_DMA_ALIGNMENT - 1); /* Output parameter alignments. */
>>>
>>> Do we need to pack everything into a single DMA buffer? Otherwise it would be better to add qseecom_dma_alloc_aligned function, which will take care of the alignment for a single data piece.
>>
>> It may be possible to split this into two buffers, one for input and one for
>> output, but packing of input parameters would still be required (see the
>> assignments to req_data below).
>>
>> For the input, you essentially provide one buffer (address) to qseecom,
>> starting with req_data describing the layout in it. This description is
>> offset-based, so there's no way to specify multiple addresses/buffers as input.
>> The output behaves similarly, it's just the secure OS that does the packing.
>>
>> And since we already have to take care of aligning the input parameters, I'm
>> not sure that it makes sense to split this into two.
>
> I see, thanks for the explanation. Maybe you can add a wrapping call that will take the sizes of required arguments (as a variadic array?) and will return prepared req and all the pointers and/or offsets? I think that having to specify these alignment 'extras' is errror prone.
I'll give that a try.
[...]
>>>> +static int qcom_uefisecapp_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct qcuefi_client *qcuefi;
>>>> + int status;
>>>> +
>>>> + /* Allocate driver data. */
>>>> + qcuefi = devm_kzalloc(&pdev->dev, sizeof(*qcuefi), GFP_KERNEL);
>>>> + if (!qcuefi)
>>>> + return -ENOMEM;
>>>> +
>>>> + qcuefi->dev = &pdev->dev;
>>>> +
>>>> + /* We expect the parent to be the QSEECOM device. */
>>>> + qcuefi->qsee = dev_get_drvdata(pdev->dev.parent);
>>>> + if (!qcuefi->qsee)
>>>> + return -EINVAL;
>>>> +
>>>> + /* Get application id for uefisecapp. */
>>>> + status = qseecom_app_get_id(qcuefi->qsee, QSEE_UEFISEC_APP_NAME, &qcuefi->app_id);
>>>> + if (status) {
>>>> + dev_err(&pdev->dev, "failed to query app ID: %d\n", status);
>>>> + return status;
>>>> + }
>>>> +
>>>> + /* Set up DMA. One page should be plenty to start with. */
>>>
>>> one page?
>>
>> The driver I've reverse-engineered this from allocates the DMA memory for
>> interaction with qseecom in multiples of PAGE_SIZE. I'm following that in this
>> driver, as I don't know whether that's a hard requirement (at least on some
>> platforms) or not. So I pre-allocate one page (1x PAGE_SIZE bytes) here. But as
>> you've mentioned above, it might be better to allocate this on-demand in each
>> call.
>
> Probably the comment was misplaced. It talks about 1 page, but it is placed right before a call to dma_set_mask rather than dma_alloc.
Ah, it was intended for both the dma_set_mask() and the qseecom_dma_alloc()
below. I see how that is a bit confusing, will fix that.
>>>> + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) {
>>>> + dev_warn(&pdev->dev, "no suitable DMA available\n");
>>>> + return -EFAULT;
>>>> + }
>>>> +
>>>> + status = qseecom_dma_alloc(&pdev->dev, &qcuefi->dma, PAGE_SIZE, GFP_KERNEL);
>>>> + if (status)
>>>> + return status;
>>>> +
[...]
Regards
Max
On 3/9/23 09:45, Dmitry Baryshkov wrote:
> On 08/03/2023 15:59, Maximilian Luz wrote:
>> On 3/7/23 16:32, Dmitry Baryshkov wrote:
>>> On 05/03/2023 04:21, Maximilian Luz wrote:
[...]
>>>> +int qseecom_scm_call(struct qseecom_device *qsee, const struct qcom_scm_desc *desc,
>>>> + struct qseecom_scm_resp *res);
>>>> +
>>>> +
>>>> +/* -- Secure App interface. ------------------------------------------------- */
>>>> +
>>>> +#define QSEECOM_MAX_APP_NAME_SIZE 64
>>>> +
>>>> +int qseecom_app_get_id(struct qseecom_device *qsee, const char *app_name, u32 *app_id);
>>>> +int qseecom_app_send(struct qseecom_device *qsee, u32 app_id, struct qseecom_dma *req,
>>>> + struct qseecom_dma *rsp);
>>>
>>> I think that only these calls should be made public / available to other modules. qseecom_scm_call also is an internal helper.
>>
>> So move all calls to qcom_scm and only make these two public? Or move only
>> qseecom_scm_call() to qcom_scm (which would require to make it public there,
>> however). Or how would you want this to look?
>
> I think we can make it with just these calls being public. Or even with just the second one being public and available to other drivers. If the app_id is a part of qseecom_app_device, we can pass that device to the qseecom_app_send(). And qseecom_app_get_id() becomes a part of app registration.
Right, with the bus structure, we only really need qseecom_app_send().
I'm still a bit concerned about things maybe getting too complex for
qcom_scm with the blocking/re-entrant calls (and maybe other future
stuff), but I guess we can always try to break things up when/if we get
around to that.
I'll try to implement that and your other suggestions and see how that
goes. I think that should work quite well overall.
Thanks again for your review and comments.
Regards,
Max
On 08/03/2023 23:44, Maximilian Luz wrote:
> On 3/8/23 23:16, Rob Herring wrote:
>> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
>>> Add bindings for the Qualcomm Secure Execution Environment interface
>>> (QSEECOM).
>>
>> Pretty sure I already asked, but no answer in the commit message. Why do
>> we need this? You've already declared the platform supports SCM calls
>> with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
>> is for non-discoverable h/w we are stuck with.
>
> Yes, you've asked this before but I can only repeat what I've written in
> my last response to your question: I am not aware of any way to properly
> discover the interface at runtime from software.
>
> If it makes you happy, I can put this in the commit message as well...
Yes, please, because commit msg should answer to "why we are doing
this", when this is not obvious. If the reviewer asks the same twice it
means it is not obvious.
>
>> Why is software made non-discoverable too?
>
> Please direct that question at the Qualcomm guys who actually designed
> that interface. I can't give you an answer to that, and I'm not all that
> happy about this either.
>
> To reiterate: I've reverse engineered this based on the Windows driver.
> The Windows driver loads on an ACPI HID and it doesn't use any function
> to check/verify whether the interface is actually present. Adding a DT
> entry is the straight-forward adaption to having a HID in ACPI.
Best regards,
Krzysztof
On Thu, Mar 09, 2023 at 03:27:01AM +0100, Maximilian Luz wrote:
> On 3/9/23 02:33, Dmitry Baryshkov wrote:
> > On 09/03/2023 00:44, Maximilian Luz wrote:
> > > On 3/8/23 23:16, Rob Herring wrote:
> > > > On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
> > > > > Add bindings for the Qualcomm Secure Execution Environment interface
> > > > > (QSEECOM).
> > > >
> > > > Pretty sure I already asked, but no answer in the commit message. Why do
> > > > we need this? You've already declared the platform supports SCM calls
> > > > with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
> > > > is for non-discoverable h/w we are stuck with.
> > >
> > > Yes, you've asked this before but I can only repeat what I've written in
> > > my last response to your question: I am not aware of any way to properly
> > > discover the interface at runtime from software.
> > >
> > > If it makes you happy, I can put this in the commit message as well...
> > >
> > > > Why is software made non-discoverable too?
> > >
> > > Please direct that question at the Qualcomm guys who actually designed
> > > that interface. I can't give you an answer to that, and I'm not all that
> > > happy about this either.
> > >
> > > To reiterate: I've reverse engineered this based on the Windows driver.
> > > The Windows driver loads on an ACPI HID and it doesn't use any function
> > > to check/verify whether the interface is actually present. Adding a DT
> > > entry is the straight-forward adaption to having a HID in ACPI.
> > >
> > > > Nodes with only a compatible string are usually just an abuse of DT to
> > > > instantiate some driver.
> > >
> > > If you or anyone here has any idea on how to discover the presence of
> > > this, please feel free to let me know and I'd be happy to implement
> > > that. Until then, I unfortunately don't see any other way of dealing
> > > with this.
> >
> > You can probably try requesting QSEECOM version. According to msm-3.18:
> >
> > ??????? uint32_t feature = 10;
> >
> > ??????? rc = qseecom_scm_call(6, 3, &feature, sizeof(feature),
> > ??????????????? &resp, sizeof(resp));
> > ??????? pr_info("qseecom.qsee_version = 0x%x\n", resp.result);
> > ??????? if (rc) {
> > ??????????????? pr_err("Failed to get QSEE version info %d\n", rc);
> > ??????????????? goto exit_del_cdev;
> > ??????? }
> >
>
> Thanks! I'll give that a try.
>
> As I can't test this on a device that doesn't have qseecom, it would
> probably be a good idea if someone could test this on a device that has
> qcom_scm but no qseecom (if those even exist) to make sure this doesn't
> misbehave.
>
TBH, this has been going in round for quite sometime. We have been asking
you to depend on existing platform compatible + a query or a check on the
version. Since you do have a platform that is working, we can start making
it min "qseecom.qsee_version" supported and then adjust the version based
on the testing or the requirement. What do you think ?
--
Regards,
Sudeep
On 5/2/23 10:38, Sudeep Holla wrote:
> On Thu, Mar 09, 2023 at 03:27:01AM +0100, Maximilian Luz wrote:
>> On 3/9/23 02:33, Dmitry Baryshkov wrote:
>>> On 09/03/2023 00:44, Maximilian Luz wrote:
>>>> On 3/8/23 23:16, Rob Herring wrote:
>>>>> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
>>>>>> Add bindings for the Qualcomm Secure Execution Environment interface
>>>>>> (QSEECOM).
>>>>>
>>>>> Pretty sure I already asked, but no answer in the commit message. Why do
>>>>> we need this? You've already declared the platform supports SCM calls
>>>>> with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
>>>>> is for non-discoverable h/w we are stuck with.
>>>>
>>>> Yes, you've asked this before but I can only repeat what I've written in
>>>> my last response to your question: I am not aware of any way to properly
>>>> discover the interface at runtime from software.
>>>>
>>>> If it makes you happy, I can put this in the commit message as well...
>>>>
>>>>> Why is software made non-discoverable too?
>>>>
>>>> Please direct that question at the Qualcomm guys who actually designed
>>>> that interface. I can't give you an answer to that, and I'm not all that
>>>> happy about this either.
>>>>
>>>> To reiterate: I've reverse engineered this based on the Windows driver.
>>>> The Windows driver loads on an ACPI HID and it doesn't use any function
>>>> to check/verify whether the interface is actually present. Adding a DT
>>>> entry is the straight-forward adaption to having a HID in ACPI.
>>>>
>>>>> Nodes with only a compatible string are usually just an abuse of DT to
>>>>> instantiate some driver.
>>>>
>>>> If you or anyone here has any idea on how to discover the presence of
>>>> this, please feel free to let me know and I'd be happy to implement
>>>> that. Until then, I unfortunately don't see any other way of dealing
>>>> with this.
>>>
>>> You can probably try requesting QSEECOM version. According to msm-3.18:
>>>
>>> uint32_t feature = 10;
>>>
>>> rc = qseecom_scm_call(6, 3, &feature, sizeof(feature),
>>> &resp, sizeof(resp));
>>> pr_info("qseecom.qsee_version = 0x%x\n", resp.result);
>>> if (rc) {
>>> pr_err("Failed to get QSEE version info %d\n", rc);
>>> goto exit_del_cdev;
>>> }
>>>
>>
>> Thanks! I'll give that a try.
>>
>> As I can't test this on a device that doesn't have qseecom, it would
>> probably be a good idea if someone could test this on a device that has
>> qcom_scm but no qseecom (if those even exist) to make sure this doesn't
>> misbehave.
>>
>
> TBH, this has been going in round for quite sometime. We have been asking
> you to depend on existing platform compatible + a query or a check on the
> version. Since you do have a platform that is working, we can start making
> it min "qseecom.qsee_version" supported and then adjust the version based
> on the testing or the requirement. What do you think ?
Sure, I will add a minimum version check to that.
Regards,
Max
On 5/2/23 10:31, Krzysztof Kozlowski wrote:
> On 08/03/2023 23:44, Maximilian Luz wrote:
>> On 3/8/23 23:16, Rob Herring wrote:
>>> On Sun, Mar 05, 2023 at 03:21:18AM +0100, Maximilian Luz wrote:
>>>> Add bindings for the Qualcomm Secure Execution Environment interface
>>>> (QSEECOM).
>>>
>>> Pretty sure I already asked, but no answer in the commit message. Why do
>>> we need this? You've already declared the platform supports SCM calls
>>> with "qcom,scm". Why can't you probe whether you have QSEECOM or not? DT
>>> is for non-discoverable h/w we are stuck with.
>>
>> Yes, you've asked this before but I can only repeat what I've written in
>> my last response to your question: I am not aware of any way to properly
>> discover the interface at runtime from software.
>>
>> If it makes you happy, I can put this in the commit message as well...
>
> Yes, please, because commit msg should answer to "why we are doing
> this", when this is not obvious. If the reviewer asks the same twice it
> means it is not obvious.
Thanks. Hopefully I can manage to tie that (reliably) to qcom,scm and we
don't need the specific compatible for v4 any more. I will try to be more
descriptive for the next set of patches though.
Regards,
Max
On Tue, Mar 07, 2023 at 05:51:35PM +0200, Dmitry Baryshkov wrote:
> On 05/03/2023 04:21, Maximilian Luz wrote:
> > +static struct qcuefi_client *qcuefi_acquire(void)
>
> Doesn't this need the __locks annotation?
No, the mutex implementation does not use sparse annotation so adding
__acquires() here will instead introduce new warnings.
> > +{
> > + mutex_lock(&__qcuefi_lock);
> > + return __qcuefi;
> > +}
Johan