diff options
Diffstat (limited to 'drivers/firmware/qcom')
| -rw-r--r-- | drivers/firmware/qcom/Kconfig | 22 | ||||
| -rw-r--r-- | drivers/firmware/qcom/Makefile | 3 | ||||
| -rw-r--r-- | drivers/firmware/qcom/qcom_pas.c | 298 | ||||
| -rw-r--r-- | drivers/firmware/qcom/qcom_pas.h | 50 | ||||
| -rw-r--r-- | drivers/firmware/qcom/qcom_pas_tee.c | 479 | ||||
| -rw-r--r-- | drivers/firmware/qcom/qcom_scm-smc.c | 10 | ||||
| -rw-r--r-- | drivers/firmware/qcom/qcom_scm.c | 456 | ||||
| -rw-r--r-- | drivers/firmware/qcom/qcom_scm_trace.h | 143 |
8 files changed, 1267 insertions, 194 deletions
diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig index b477d54b495a..c7f8413ab996 100644 --- a/drivers/firmware/qcom/Kconfig +++ b/drivers/firmware/qcom/Kconfig @@ -6,9 +6,29 @@ menu "Qualcomm firmware drivers" +config QCOM_PAS + tristate "Qualcomm generic PAS interface driver" + help + Enable the generic Peripheral Authentication Service (PAS) provided + by the firmware. It acts as the common layer with different TZ + backends plugged in whether it's an SCM implementation or a proper + TEE bus based PAS service implementation. + +config QCOM_PAS_TEE + tristate "Qualcomm PAS TEE interface driver" + select QCOM_PAS + depends on TEE + depends on !CPU_BIG_ENDIAN + default m if ARCH_QCOM + help + Enable the generic Peripheral Authentication Service (PAS) provided + by the firmware TEE implementation as the backend. + config QCOM_SCM + tristate "Qualcomm PAS SCM interface driver" + select QCOM_PAS select QCOM_TZMEM - tristate + default y if ARCH_QCOM config QCOM_TZMEM tristate diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile index 0be40a1abc13..88ce74d74c3e 100644 --- a/drivers/firmware/qcom/Makefile +++ b/drivers/firmware/qcom/Makefile @@ -5,6 +5,9 @@ obj-$(CONFIG_QCOM_SCM) += qcom-scm.o qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o +CFLAGS_qcom_scm-smc.o := -I$(src) obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o +obj-$(CONFIG_QCOM_PAS) += qcom_pas.o +obj-$(CONFIG_QCOM_PAS_TEE) += qcom_pas_tee.o diff --git a/drivers/firmware/qcom/qcom_pas.c b/drivers/firmware/qcom/qcom_pas.c new file mode 100644 index 000000000000..24485dd0fa10 --- /dev/null +++ b/drivers/firmware/qcom/qcom_pas.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2010,2015,2019 The Linux Foundation. All rights reserved. + * Copyright (C) 2015 Linaro Ltd. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/device/devres.h> +#include <linux/firmware/qcom/qcom_pas.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include "qcom_pas.h" + +static struct qcom_pas_ops *ops_ptr; + +/** + * devm_qcom_pas_context_alloc() - Allocate peripheral authentication service + * context for a given peripheral + * + * PAS context is device-resource managed, so the caller does not need + * to worry about freeing the context memory. + * + * @dev: PAS firmware device + * @pas_id: peripheral authentication service id + * @mem_phys: Subsystem reserve memory start address + * @mem_size: Subsystem reserve memory size + * + * Return: The new PAS context, or ERR_PTR() on failure. + */ +struct qcom_pas_context *devm_qcom_pas_context_alloc(struct device *dev, + u32 pas_id, + phys_addr_t mem_phys, + size_t mem_size) +{ + struct qcom_pas_context *ctx; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + ctx->dev = dev; + ctx->pas_id = pas_id; + ctx->mem_phys = mem_phys; + ctx->mem_size = mem_size; + + return ctx; +} +EXPORT_SYMBOL_GPL(devm_qcom_pas_context_alloc); + +/** + * qcom_pas_init_image() - Initialize peripheral authentication service state + * machine for a given peripheral, using the metadata + * @pas_id: peripheral authentication service id + * @metadata: pointer to memory containing ELF header, program header table + * and optional blob of data used for authenticating the metadata + * and the rest of the firmware + * @size: size of the metadata + * @ctx: optional pas context + * + * Return: 0 on success. + * + * Upon successful return, the PAS metadata context (@ctx) will be used to + * track the metadata allocation, this needs to be released by invoking + * qcom_pas_metadata_release() by the caller. + */ +int qcom_pas_init_image(u32 pas_id, const void *metadata, size_t size, + struct qcom_pas_context *ctx) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->init_image(ops_ptr->dev, pas_id, metadata, size, ctx); +} +EXPORT_SYMBOL_GPL(qcom_pas_init_image); + +/** + * qcom_pas_metadata_release() - release metadata context + * @ctx: pas context + */ +void qcom_pas_metadata_release(struct qcom_pas_context *ctx) +{ + if (!ops_ptr || !ctx || !ctx->ptr) + return; + + ops_ptr->metadata_release(ops_ptr->dev, ctx); +} +EXPORT_SYMBOL_GPL(qcom_pas_metadata_release); + +/** + * qcom_pas_mem_setup() - Prepare the memory related to a given peripheral + * for firmware loading + * @pas_id: peripheral authentication service id + * @addr: start address of memory area to prepare + * @size: size of the memory area to prepare + * + * Return: 0 on success. + */ +int qcom_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->mem_setup(ops_ptr->dev, pas_id, addr, size); +} +EXPORT_SYMBOL_GPL(qcom_pas_mem_setup); + +/** + * qcom_pas_get_rsc_table() - Retrieve the resource table in passed output buffer + * for a given peripheral. + * + * Qualcomm remote processor may rely on both static and dynamic resources for + * its functionality. Static resources typically refer to memory-mapped + * addresses required by the subsystem and are often embedded within the + * firmware binary and dynamic resources, such as shared memory in DDR etc., + * are determined at runtime during the boot process. + * + * On Qualcomm Technologies devices, it's possible that static resources are + * not embedded in the firmware binary and instead are provided by TrustZone. + * However, dynamic resources are always expected to come from TrustZone. This + * indicates that for Qualcomm devices, all resources (static and dynamic) will + * be provided by TrustZone PAS service. + * + * If the remote processor firmware binary does contain static resources, they + * should be passed in input_rt. These will be forwarded to TrustZone for + * authentication. TrustZone will then append the dynamic resources and return + * the complete resource table in output_rt_tzm. + * + * If the remote processor firmware binary does not include a resource table, + * the caller of this function should set input_rt as NULL and input_rt_size + * as zero respectively. + * + * More about documentation on resource table data structures can be found in + * include/linux/remoteproc.h + * + * @ctx: PAS context + * @input_rt: resource table buffer which is present in firmware binary + * @input_rt_size: size of the resource table present in firmware binary + * @output_rt_size: TrustZone expects caller should pass worst case size for + * the output_rt_tzm. + * + * Return: + * On success, returns a pointer to the allocated buffer containing the final + * resource table and output_rt_size will have actual resource table size from + * TrustZone. The caller is responsible for freeing the buffer. On failure, + * returns ERR_PTR(-errno). + */ +struct resource_table *qcom_pas_get_rsc_table(struct qcom_pas_context *ctx, + void *input_rt, + size_t input_rt_size, + size_t *output_rt_size) +{ + if (!ops_ptr) + return ERR_PTR(-ENODEV); + if (!ctx) + return ERR_PTR(-EINVAL); + + return ops_ptr->get_rsc_table(ops_ptr->dev, ctx, input_rt, + input_rt_size, output_rt_size); +} +EXPORT_SYMBOL_GPL(qcom_pas_get_rsc_table); + +/** + * qcom_pas_auth_and_reset() - Authenticate the given peripheral firmware + * and reset the remote processor + * @pas_id: peripheral authentication service id + * + * Return: 0 on success. + */ +int qcom_pas_auth_and_reset(u32 pas_id) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->auth_and_reset(ops_ptr->dev, pas_id); +} +EXPORT_SYMBOL_GPL(qcom_pas_auth_and_reset); + +/** + * qcom_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the + * remote processor + * + * @ctx: Context saved during call to devm_qcom_pas_context_alloc() + * + * This function performs the necessary steps to prepare a PAS subsystem, + * authenticate it using the provided metadata, and initiate a reset sequence. + * + * It should be used when Linux is in control setting up the IOMMU hardware + * for remote subsystem during secure firmware loading processes. The + * preparation step sets up a shmbridge over the firmware memory before + * TrustZone accesses the firmware memory region for authentication. The + * authentication step verifies the integrity and authenticity of the firmware + * or configuration using secure metadata. Finally, the reset step ensures the + * subsystem starts in a clean and sane state. + * + * Return: 0 on success, negative errno on failure. + */ +int qcom_pas_prepare_and_auth_reset(struct qcom_pas_context *ctx) +{ + if (!ops_ptr) + return -ENODEV; + if (!ctx) + return -EINVAL; + + return ops_ptr->prepare_and_auth_reset(ops_ptr->dev, ctx); +} +EXPORT_SYMBOL_GPL(qcom_pas_prepare_and_auth_reset); + +/** + * qcom_pas_set_remote_state() - Set the remote processor state + * @state: peripheral state + * @pas_id: peripheral authentication service id + * + * Return: 0 on success. + */ +int qcom_pas_set_remote_state(u32 state, u32 pas_id) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->set_remote_state(ops_ptr->dev, state, pas_id); +} +EXPORT_SYMBOL_GPL(qcom_pas_set_remote_state); + +/** + * qcom_pas_shutdown() - Shut down the remote processor + * @pas_id: peripheral authentication service id + * + * Return: 0 on success. + */ +int qcom_pas_shutdown(u32 pas_id) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->shutdown(ops_ptr->dev, pas_id); +} +EXPORT_SYMBOL_GPL(qcom_pas_shutdown); + +/** + * qcom_pas_supported() - Check if the peripheral authentication service is + * supported for the given peripheral + * @pas_id: peripheral authentication service id + * + * Return: true if PAS is supported for this peripheral, otherwise false. + */ +bool qcom_pas_supported(u32 pas_id) +{ + if (!ops_ptr) + return false; + + return ops_ptr->supported(ops_ptr->dev, pas_id); +} +EXPORT_SYMBOL_GPL(qcom_pas_supported); + +/** + * qcom_pas_is_available() - Check if the peripheral authentication service is + * available. Note that it is mandatory for any PAS + * client to invoke this API. If it returns true then + * only any other PAS API can be invoked. + * + * Return: true if PAS is available, otherwise false. + */ +bool qcom_pas_is_available(void) +{ + /* + * The barrier for ops_ptr is intended to synchronize the data stores + * for the ops data structure when client drivers are in parallel + * checking for PAS service availability. + * + * Once the PAS backend becomes available, it is allowed for multiple + * threads to enter TZ for parallel bringup of co-processors during + * boot. + */ + return !!smp_load_acquire(&ops_ptr); +} +EXPORT_SYMBOL_GPL(qcom_pas_is_available); + +void qcom_pas_ops_register(struct qcom_pas_ops *ops) +{ + if (!qcom_pas_is_available()) + /* Paired with smp_load_acquire() in qcom_pas_is_available() */ + smp_store_release(&ops_ptr, ops); + else + pr_err("qcom_pas: ops already registered by %s\n", + ops_ptr->drv_name); +} +EXPORT_SYMBOL_GPL(qcom_pas_ops_register); + +void qcom_pas_ops_unregister(void) +{ + /* Paired with smp_load_acquire() in qcom_pas_is_available() */ + smp_store_release(&ops_ptr, NULL); +} +EXPORT_SYMBOL_GPL(qcom_pas_ops_unregister); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm generic TZ PAS driver"); diff --git a/drivers/firmware/qcom/qcom_pas.h b/drivers/firmware/qcom/qcom_pas.h new file mode 100644 index 000000000000..8643e2760602 --- /dev/null +++ b/drivers/firmware/qcom/qcom_pas.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __QCOM_PAS_INT_H +#define __QCOM_PAS_INT_H + +struct device; + +/** + * struct qcom_pas_ops - Qcom Peripheral Authentication Service (PAS) ops + * @drv_name: PAS driver name. + * @dev: PAS device pointer. + * @supported: Peripheral supported callback. + * @init_image: Peripheral image initialization callback. + * @mem_setup: Peripheral memory setup callback. + * @get_rsc_table: Peripheral get resource table callback. + * @prepare_and_auth_reset: Peripheral prepare firmware authentication and + * reset callback. + * @auth_and_reset: Peripheral firmware authentication and reset + * callback. + * @set_remote_state: Peripheral set remote state callback. + * @shutdown: Peripheral shutdown callback. + * @metadata_release: Image metadata release callback. + */ +struct qcom_pas_ops { + const char *drv_name; + struct device *dev; + bool (*supported)(struct device *dev, u32 pas_id); + int (*init_image)(struct device *dev, u32 pas_id, const void *metadata, + size_t size, struct qcom_pas_context *ctx); + int (*mem_setup)(struct device *dev, u32 pas_id, phys_addr_t addr, + phys_addr_t size); + void *(*get_rsc_table)(struct device *dev, struct qcom_pas_context *ctx, + void *input_rt, size_t input_rt_size, + size_t *output_rt_size); + int (*prepare_and_auth_reset)(struct device *dev, + struct qcom_pas_context *ctx); + int (*auth_and_reset)(struct device *dev, u32 pas_id); + int (*set_remote_state)(struct device *dev, u32 state, u32 pas_id); + int (*shutdown)(struct device *dev, u32 pas_id); + void (*metadata_release)(struct device *dev, + struct qcom_pas_context *ctx); +}; + +void qcom_pas_ops_register(struct qcom_pas_ops *ops); +void qcom_pas_ops_unregister(void); + +#endif /* __QCOM_PAS_INT_H */ diff --git a/drivers/firmware/qcom/qcom_pas_tee.c b/drivers/firmware/qcom/qcom_pas_tee.c new file mode 100644 index 000000000000..ac33a00687aa --- /dev/null +++ b/drivers/firmware/qcom/qcom_pas_tee.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/firmware/qcom/qcom_pas.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/tee_drv.h> +#include <linux/uuid.h> + +#include "qcom_pas.h" + +/* + * Peripheral Authentication Service (PAS) supported. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + */ +#define TA_QCOM_PAS_IS_SUPPORTED 1 + +/* + * PAS capabilities. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [out] params[1].value.a: PAS capability flags + */ +#define TA_QCOM_PAS_CAPABILITIES 2 + +/* + * PAS image initialization. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [in] params[1].memref: Loadable firmware metadata + */ +#define TA_QCOM_PAS_INIT_IMAGE 3 + +/* + * PAS memory setup. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [in] params[0].value.b: Relocatable firmware size + * [in] params[1].value.a: 32bit LSB relocatable firmware memory address + * [in] params[1].value.b: 32bit MSB relocatable firmware memory address + */ +#define TA_QCOM_PAS_MEM_SETUP 4 + +/* + * PAS get resource table. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [inout] params[1].memref: Resource table config + */ +#define TA_QCOM_PAS_GET_RESOURCE_TABLE 5 + +/* + * PAS image authentication and co-processor reset. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [in] params[0].value.b: Firmware size + * [in] params[1].value.a: 32bit LSB firmware memory address + * [in] params[1].value.b: 32bit MSB firmware memory address + * [in] params[2].memref: Optional fw memory space shared/lent + */ +#define TA_QCOM_PAS_AUTH_AND_RESET 6 + +/* + * PAS co-processor set suspend/resume state. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [in] params[0].value.b: Co-processor state identifier + */ +#define TA_QCOM_PAS_SET_REMOTE_STATE 7 + +/* + * PAS co-processor shutdown. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + */ +#define TA_QCOM_PAS_SHUTDOWN 8 + +#define TEE_NUM_PARAMS 4 + +/** + * struct qcom_pas_tee_private - PAS service private data + * @dev: PAS service device. + * @ctx: TEE context handler. + * @session_id: PAS TA session identifier. + */ +struct qcom_pas_tee_private { + struct device *dev; + struct tee_context *ctx; + u32 session_id; +}; + +static bool qcom_pas_tee_supported(struct device *dev, u32 pas_id) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_IS_SUPPORTED, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS not supported, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return false; + } + + return true; +} + +static int qcom_pas_tee_init_image(struct device *dev, u32 pas_id, + const void *metadata, size_t size, + struct qcom_pas_context *ctx) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_INIT_IMAGE, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id + }, + [1] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT, + } + }; + struct tee_shm *mdata_shm; + u8 *mdata_buf = NULL; + int ret; + + mdata_shm = tee_shm_alloc_kernel_buf(data->ctx, size); + if (IS_ERR(mdata_shm)) { + dev_err(dev, "mdata_shm allocation failed\n"); + return PTR_ERR(mdata_shm); + } + + mdata_buf = tee_shm_get_va(mdata_shm, 0); + if (IS_ERR(mdata_buf)) { + dev_err(dev, "mdata_buf get VA failed\n"); + tee_shm_free(mdata_shm); + return PTR_ERR(mdata_buf); + } + memcpy(mdata_buf, metadata, size); + + param[1].u.memref.shm = mdata_shm; + param[1].u.memref.size = size; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS init image failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + tee_shm_free(mdata_shm); + return ret ?: -EINVAL; + } + + if (ctx) + ctx->ptr = (void *)mdata_shm; + else + tee_shm_free(mdata_shm); + + return ret; +} + +static int qcom_pas_tee_mem_setup(struct device *dev, u32 pas_id, + phys_addr_t addr, phys_addr_t size) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_MEM_SETUP, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id, + .u.value.b = size, + }, + [1] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = lower_32_bits(addr), + .u.value.b = upper_32_bits(addr), + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS mem setup failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return ret ?: -EINVAL; + } + + return ret; +} + +DEFINE_FREE(shm_free, struct tee_shm *, tee_shm_free(_T)) + +static void *qcom_pas_tee_get_rsc_table(struct device *dev, + struct qcom_pas_context *ctx, + void *input_rt, size_t input_rt_size, + size_t *output_rt_size) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_GET_RESOURCE_TABLE, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = ctx->pas_id, + }, + [1] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT, + .u.memref.size = input_rt_size, + } + }; + void *rt_buf = NULL; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n", + ctx->pas_id, ret, inv_arg.ret); + return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL); + } + + if (param[1].u.memref.size >= input_rt_size) { + struct tee_shm *rt_shm __free(shm_free) = + tee_shm_alloc_kernel_buf(data->ctx, + param[1].u.memref.size); + void *rt_shm_va; + + if (IS_ERR_OR_NULL(rt_shm)) { + dev_err(dev, "rt_shm allocation failed\n"); + rt_shm = NULL; + return ERR_PTR(-ENOMEM); + } + + rt_shm_va = tee_shm_get_va(rt_shm, 0); + if (IS_ERR(rt_shm_va)) { + dev_err(dev, "rt_shm get VA failed\n"); + return ERR_CAST(rt_shm_va); + } + memcpy(rt_shm_va, input_rt, input_rt_size); + + param[1].u.memref.shm = rt_shm; + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n", + ctx->pas_id, ret, inv_arg.ret); + return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL); + } + + if (param[1].u.memref.size) { + *output_rt_size = param[1].u.memref.size; + rt_buf = kmemdup(rt_shm_va, *output_rt_size, GFP_KERNEL); + if (!rt_buf) + return ERR_PTR(-ENOMEM); + } + } else { + *output_rt_size = 0; + } + + return rt_buf; +} + +static int __qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id, + phys_addr_t mem_phys, size_t mem_size) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_AUTH_AND_RESET, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id, + .u.value.b = mem_size, + }, + [1] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = lower_32_bits(mem_phys), + .u.value.b = upper_32_bits(mem_phys), + }, + /* Reserved for fw memory space to be shared or lent */ + [2] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT, + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS auth reset failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return ret ?: -EINVAL; + } + + return ret; +} + +static int qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id) +{ + return __qcom_pas_tee_auth_and_reset(dev, pas_id, 0, 0); +} + +static int qcom_pas_tee_prepare_and_auth_reset(struct device *dev, + struct qcom_pas_context *ctx) +{ + return __qcom_pas_tee_auth_and_reset(dev, ctx->pas_id, ctx->mem_phys, + ctx->mem_size); +} + +static int qcom_pas_tee_set_remote_state(struct device *dev, u32 state, + u32 pas_id) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_SET_REMOTE_STATE, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id, + .u.value.b = state, + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS set remote state failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return ret ?: -EINVAL; + } + + return ret; +} + +static int qcom_pas_tee_shutdown(struct device *dev, u32 pas_id) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_SHUTDOWN, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS shutdown failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return ret ?: -EINVAL; + } + + return ret; +} + +static void qcom_pas_tee_metadata_release(struct device *dev, + struct qcom_pas_context *ctx) +{ + struct tee_shm *mdata_shm = ctx->ptr; + + tee_shm_free(mdata_shm); + ctx->ptr = NULL; +} + +static struct qcom_pas_ops qcom_pas_ops_tee = { + .drv_name = "qcom-pas-tee", + .supported = qcom_pas_tee_supported, + .init_image = qcom_pas_tee_init_image, + .mem_setup = qcom_pas_tee_mem_setup, + .get_rsc_table = qcom_pas_tee_get_rsc_table, + .auth_and_reset = qcom_pas_tee_auth_and_reset, + .prepare_and_auth_reset = qcom_pas_tee_prepare_and_auth_reset, + .set_remote_state = qcom_pas_tee_set_remote_state, + .shutdown = qcom_pas_tee_shutdown, + .metadata_release = qcom_pas_tee_metadata_release, +}; + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) +{ + return ver->impl_id == TEE_IMPL_ID_OPTEE; +} + +static int qcom_pas_tee_probe(struct tee_client_device *pas_dev) +{ + struct device *dev = &pas_dev->dev; + struct qcom_pas_tee_private *data; + struct tee_ioctl_open_session_arg sess_arg = { + .clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL + }; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); + if (IS_ERR(data->ctx)) + return -ENODEV; + + export_uuid(sess_arg.uuid, &pas_dev->id.uuid); + ret = tee_client_open_session(data->ctx, &sess_arg, NULL); + if (ret < 0 || sess_arg.ret != 0) { + dev_err(dev, "tee_client_open_session failed, ret: %d, err: 0x%x\n", + ret, sess_arg.ret); + tee_client_close_context(data->ctx); + return ret ?: -EINVAL; + } + + data->session_id = sess_arg.session; + dev_set_drvdata(dev, data); + qcom_pas_ops_tee.dev = dev; + qcom_pas_ops_register(&qcom_pas_ops_tee); + + return ret; +} + +static void qcom_pas_tee_remove(struct tee_client_device *pas_dev) +{ + struct device *dev = &pas_dev->dev; + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + + qcom_pas_ops_unregister(); + tee_client_close_session(data->ctx, data->session_id); + tee_client_close_context(data->ctx); +} + +static const struct tee_client_device_id qcom_pas_tee_id_table[] = { + {UUID_INIT(0xcff7d191, 0x7ca0, 0x4784, + 0xaf, 0x13, 0x48, 0x22, 0x3b, 0x9a, 0x4f, 0xbe)}, + {} +}; +MODULE_DEVICE_TABLE(tee, qcom_pas_tee_id_table); + +static struct tee_client_driver optee_pas_tee_driver = { + .probe = qcom_pas_tee_probe, + .remove = qcom_pas_tee_remove, + .id_table = qcom_pas_tee_id_table, + .driver = { + .name = "qcom-pas-tee", + }, +}; + +module_tee_client_driver(optee_pas_tee_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm PAS TEE driver"); diff --git a/drivers/firmware/qcom/qcom_scm-smc.c b/drivers/firmware/qcom/qcom_scm-smc.c index 574930729ddd..01999c22659c 100644 --- a/drivers/firmware/qcom/qcom_scm-smc.c +++ b/drivers/firmware/qcom/qcom_scm-smc.c @@ -24,6 +24,9 @@ struct arm_smccc_args { unsigned long args[8]; }; +#define CREATE_TRACE_POINTS +#include "qcom_scm_trace.h" + static DEFINE_MUTEX(qcom_scm_lock); #define QCOM_SCM_EBUSY_WAIT_MS 30 @@ -44,6 +47,7 @@ static void __scm_smc_do_quirk(const struct arm_smccc_args *smc, quirk.state.a6 = 0; do { + trace_scm_smc_request(a0, smc); arm_smccc_smc_quirk(a0, smc->args[1], smc->args[2], smc->args[3], smc->args[4], smc->args[5], quirk.state.a6, smc->args[7], res, &quirk); @@ -83,6 +87,7 @@ int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending) if (ret) return ret; + trace_scm_waitq_get_wq_ctx(get_wq_res.a1, get_wq_res.a2, get_wq_res.a3); *wq_ctx = get_wq_res.a1; *flags = get_wq_res.a2; *more_pending = get_wq_res.a3; @@ -105,10 +110,12 @@ static int __scm_smc_do_quirk_handle_waitq(struct device *dev, struct arm_smccc_ wq_ctx = res->a1; smc_call_ctx = res->a2; + trace_scm_waitq_sleep(wq_ctx, smc_call_ctx); ret = qcom_scm_wait_for_wq_completion(wq_ctx); if (ret) return ret; + trace_scm_waitq_resume(smc_call_ctx); fill_wq_resume_args(&resume, smc_call_ctx); smc = &resume; } @@ -201,6 +208,9 @@ int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc, } ret = __scm_smc_do(dev, &smc, &smc_res, atomic); + + trace_scm_smc_done(ret, smc.args[0], &smc_res); + if (ret) return ret; diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 6b601a4b89db..f35f2ee39130 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -13,6 +13,7 @@ #include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/export.h> +#include <linux/firmware/qcom/qcom_pas.h> #include <linux/firmware/qcom/qcom_scm.h> #include <linux/firmware/qcom/qcom_tzmem.h> #include <linux/init.h> @@ -33,6 +34,7 @@ #include <dt-bindings/interrupt-controller/arm-gic.h> +#include "qcom_pas.h" #include "qcom_scm.h" #include "qcom_tzmem.h" @@ -57,6 +59,7 @@ struct qcom_scm { int scm_vote_count; u64 dload_mode_addr; + void __iomem *minidump_sram; struct qcom_tzmem_pool *mempool; unsigned int wq_cnt; @@ -141,6 +144,20 @@ static const u8 qcom_scm_cpu_warm_bits[QCOM_SCM_BOOT_MAX_CPUS] = { #define QCOM_DLOAD_MINIDUMP 2 #define QCOM_DLOAD_BOTHDUMP 3 +/* Minidump destination values written to always-on SRAM for boot firmware */ +#define QCOM_MINIDUMP_DEST_USB 0x0 +#define QCOM_MINIDUMP_DEST_STORAGE 0x2 + +static u32 minidump_dest = QCOM_MINIDUMP_DEST_USB; + +static const struct { + const char *name; + u32 val; +} minidump_dest_map[] = { + { "usb", QCOM_MINIDUMP_DEST_USB }, + { "storage", QCOM_MINIDUMP_DEST_STORAGE }, +}; + #define QCOM_SCM_DEFAULT_WAITQ_COUNT 1 static const char * const qcom_scm_convention_names[] = { @@ -479,25 +496,6 @@ void qcom_scm_cpu_power_down(u32 flags) } EXPORT_SYMBOL_GPL(qcom_scm_cpu_power_down); -int qcom_scm_set_remote_state(u32 state, u32 id) -{ - struct qcom_scm_desc desc = { - .svc = QCOM_SCM_SVC_BOOT, - .cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE, - .arginfo = QCOM_SCM_ARGS(2), - .args[0] = state, - .args[1] = id, - .owner = ARM_SMCCC_OWNER_SIP, - }; - struct qcom_scm_res res; - int ret; - - ret = qcom_scm_call(__scm->dev, &desc, &res); - - return ret ? : res.result[0]; -} -EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state); - static int qcom_scm_disable_sdi(void) { int ret; @@ -551,45 +549,39 @@ static int qcom_scm_io_rmw(phys_addr_t addr, unsigned int mask, unsigned int val return qcom_scm_io_writel(addr, new); } -static void qcom_scm_set_download_mode(u32 dload_mode) +static void qcom_scm_set_download_mode(struct qcom_scm *scm, u32 dload_mode) { int ret = 0; - if (__scm->dload_mode_addr) { - ret = qcom_scm_io_rmw(__scm->dload_mode_addr, QCOM_DLOAD_MASK, + if (scm->dload_mode_addr) { + ret = qcom_scm_io_rmw(scm->dload_mode_addr, QCOM_DLOAD_MASK, FIELD_PREP(QCOM_DLOAD_MASK, dload_mode)); - } else if (__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_BOOT, + } else if (__qcom_scm_is_call_available(scm->dev, QCOM_SCM_SVC_BOOT, QCOM_SCM_BOOT_SET_DLOAD_MODE)) { - ret = __qcom_scm_set_dload_mode(__scm->dev, !!dload_mode); + ret = __qcom_scm_set_dload_mode(scm->dev, !!dload_mode); } else if (dload_mode) { - dev_err(__scm->dev, + dev_err(scm->dev, "No available mechanism for setting download mode\n"); } if (ret) - dev_err(__scm->dev, "failed to set download mode: %d\n", ret); + dev_err(scm->dev, "failed to set download mode: %d\n", ret); + + /* + * Write the destination into the always-on SRAM so boot firmware + * can read it before DDR is initialised on the next warm reset. + * Only written when minidump is active; + */ + if (scm->minidump_sram && (dload_mode & QCOM_DLOAD_MINIDUMP)) + writel_relaxed(minidump_dest, scm->minidump_sram); } -/** - * devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service - * context for a given peripheral - * - * PAS context is device-resource managed, so the caller does not need - * to worry about freeing the context memory. - * - * @dev: PAS firmware device - * @pas_id: peripheral authentication service id - * @mem_phys: Subsystem reserve memory start address - * @mem_size: Subsystem reserve memory size - * - * Returns: The new PAS context, or ERR_PTR() on failure. - */ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, u32 pas_id, phys_addr_t mem_phys, size_t mem_size) { - struct qcom_scm_pas_context *ctx; + struct qcom_pas_context *ctx; ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) @@ -600,11 +592,12 @@ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, ctx->mem_phys = mem_phys; ctx->mem_size = mem_size; - return ctx; + return (struct qcom_scm_pas_context *)ctx; } EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc); -static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys, +static int __qcom_scm_pas_init_image(struct device *dev, u32 pas_id, + dma_addr_t mdata_phys, struct qcom_scm_res *res) { struct qcom_scm_desc desc = { @@ -626,7 +619,7 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys, desc.args[1] = mdata_phys; - ret = qcom_scm_call(__scm->dev, &desc, res); + ret = qcom_scm_call(dev, &desc, res); qcom_scm_bw_disable(); disable_clk: @@ -635,7 +628,8 @@ disable_clk: return ret; } -static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx, +static int qcom_scm_pas_prep_and_init_image(struct device *dev, + struct qcom_pas_context *ctx, const void *metadata, size_t size) { struct qcom_scm_res res; @@ -650,7 +644,7 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx, memcpy(mdata_buf, metadata, size); mdata_phys = qcom_tzmem_to_phys(mdata_buf); - ret = __qcom_scm_pas_init_image(ctx->pas_id, mdata_phys, &res); + ret = __qcom_scm_pas_init_image(dev, ctx->pas_id, mdata_phys, &res); if (ret < 0) qcom_tzmem_free(mdata_buf); else @@ -659,25 +653,9 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx, return ret ? : res.result[0]; } -/** - * qcom_scm_pas_init_image() - Initialize peripheral authentication service - * state machine for a given peripheral, using the - * metadata - * @pas_id: peripheral authentication service id - * @metadata: pointer to memory containing ELF header, program header table - * and optional blob of data used for authenticating the metadata - * and the rest of the firmware - * @size: size of the metadata - * @ctx: optional pas context - * - * Return: 0 on success. - * - * Upon successful return, the PAS metadata context (@ctx) will be used to - * track the metadata allocation, this needs to be released by invoking - * qcom_scm_pas_metadata_release() by the caller. - */ -int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, - struct qcom_scm_pas_context *ctx) +static int __qcom_scm_pas_init_image2(struct device *dev, u32 pas_id, + const void *metadata, size_t size, + struct qcom_pas_context *ctx) { struct qcom_scm_res res; dma_addr_t mdata_phys; @@ -685,7 +663,7 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, int ret; if (ctx && ctx->use_tzmem) - return qcom_scm_pas_prep_and_init_image(ctx, metadata, size); + return qcom_scm_pas_prep_and_init_image(dev, ctx, metadata, size); /* * During the scm call memory protection will be enabled for the meta @@ -699,16 +677,15 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, * If we pass a buffer that is already part of an SHM Bridge to this * call, it will fail. */ - mdata_buf = dma_alloc_coherent(__scm->dev, size, &mdata_phys, - GFP_KERNEL); + mdata_buf = dma_alloc_coherent(dev, size, &mdata_phys, GFP_KERNEL); if (!mdata_buf) return -ENOMEM; memcpy(mdata_buf, metadata, size); - ret = __qcom_scm_pas_init_image(pas_id, mdata_phys, &res); + ret = __qcom_scm_pas_init_image(dev, pas_id, mdata_phys, &res); if (ret < 0 || !ctx) { - dma_free_coherent(__scm->dev, size, mdata_buf, mdata_phys); + dma_free_coherent(dev, size, mdata_buf, mdata_phys); } else if (ctx) { ctx->ptr = mdata_buf; ctx->phys = mdata_phys; @@ -717,36 +694,35 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, return ret ? : res.result[0]; } -EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image); -/** - * qcom_scm_pas_metadata_release() - release metadata context - * @ctx: pas context - */ -void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx) +int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, + struct qcom_scm_pas_context *ctx) { - if (!ctx->ptr) - return; + return __qcom_scm_pas_init_image2(__scm->dev, pas_id, metadata, size, + (struct qcom_pas_context *)ctx); +} +EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image); +static void __qcom_scm_pas_metadata_release(struct device *dev, + struct qcom_pas_context *ctx) +{ if (ctx->use_tzmem) qcom_tzmem_free(ctx->ptr); else - dma_free_coherent(__scm->dev, ctx->size, ctx->ptr, ctx->phys); + dma_free_coherent(dev, ctx->size, ctx->ptr, ctx->phys); ctx->ptr = NULL; } + +void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx) +{ + __qcom_scm_pas_metadata_release(__scm->dev, + (struct qcom_pas_context *)ctx); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_metadata_release); -/** - * qcom_scm_pas_mem_setup() - Prepare the memory related to a given peripheral - * for firmware loading - * @pas_id: peripheral authentication service id - * @addr: start address of memory area to prepare - * @size: size of the memory area to prepare - * - * Returns 0 on success. - */ -int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) +static int __qcom_scm_pas_mem_setup(struct device *dev, u32 pas_id, + phys_addr_t addr, phys_addr_t size) { int ret; struct qcom_scm_desc desc = { @@ -768,7 +744,7 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) if (ret) goto disable_clk; - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); qcom_scm_bw_disable(); disable_clk: @@ -776,9 +752,15 @@ disable_clk: return ret ? : res.result[0]; } + +int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) +{ + return __qcom_scm_pas_mem_setup(__scm->dev, pas_id, addr, size); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_mem_setup); -static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm, +static void *__qcom_scm_pas_get_rsc_table(struct device *dev, u32 pas_id, + void *input_rt_tzm, size_t input_rt_size, size_t *output_rt_size) { @@ -813,7 +795,7 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm, * with output_rt_tzm buffer with res.result[2] size however, It should not * be of unresonable size. */ - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); if (!ret && res.result[2] > SZ_1G) { ret = -E2BIG; goto free_output_rt; @@ -830,51 +812,11 @@ free_output_rt: return ret ? ERR_PTR(ret) : output_rt_tzm; } -/** - * qcom_scm_pas_get_rsc_table() - Retrieve the resource table in passed output buffer - * for a given peripheral. - * - * Qualcomm remote processor may rely on both static and dynamic resources for - * its functionality. Static resources typically refer to memory-mapped addresses - * required by the subsystem and are often embedded within the firmware binary - * and dynamic resources, such as shared memory in DDR etc., are determined at - * runtime during the boot process. - * - * On Qualcomm Technologies devices, it's possible that static resources are not - * embedded in the firmware binary and instead are provided by TrustZone However, - * dynamic resources are always expected to come from TrustZone. This indicates - * that for Qualcomm devices, all resources (static and dynamic) will be provided - * by TrustZone via the SMC call. - * - * If the remote processor firmware binary does contain static resources, they - * should be passed in input_rt. These will be forwarded to TrustZone for - * authentication. TrustZone will then append the dynamic resources and return - * the complete resource table in output_rt_tzm. - * - * If the remote processor firmware binary does not include a resource table, - * the caller of this function should set input_rt as NULL and input_rt_size - * as zero respectively. - * - * More about documentation on resource table data structures can be found in - * include/linux/remoteproc.h - * - * @ctx: PAS context - * @pas_id: peripheral authentication service id - * @input_rt: resource table buffer which is present in firmware binary - * @input_rt_size: size of the resource table present in firmware binary - * @output_rt_size: TrustZone expects caller should pass worst case size for - * the output_rt_tzm. - * - * Return: - * On success, returns a pointer to the allocated buffer containing the final - * resource table and output_rt_size will have actual resource table size from - * TrustZone. The caller is responsible for freeing the buffer. On failure, - * returns ERR_PTR(-errno). - */ -struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx, - void *input_rt, - size_t input_rt_size, - size_t *output_rt_size) +static void *__qcom_scm_pas_get_rsc_table2(struct device *dev, + struct qcom_pas_context *ctx, + void *input_rt, + size_t input_rt_size, + size_t *output_rt_size) { struct resource_table empty_rsc = {}; size_t size = SZ_16K; @@ -909,11 +851,12 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c memcpy(input_rt_tzm, input_rt, input_rt_size); - output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, input_rt_tzm, + output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id, + input_rt_tzm, input_rt_size, &size); if (PTR_ERR(output_rt_tzm) == -EOVERFLOW) /* Try again with the size requested by the TZ */ - output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, + output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id, input_rt_tzm, input_rt_size, &size); @@ -943,16 +886,20 @@ disable_clk: return ret ? ERR_PTR(ret) : tbl_ptr; } + +struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx, + void *input_rt, + size_t input_rt_size, + size_t *output_rt_size) +{ + return __qcom_scm_pas_get_rsc_table2(__scm->dev, + (struct qcom_pas_context *)ctx, + input_rt, input_rt_size, + output_rt_size); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_get_rsc_table); -/** - * qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware - * and reset the remote processor - * @pas_id: peripheral authentication service id - * - * Return 0 on success. - */ -int qcom_scm_pas_auth_and_reset(u32 pas_id) +static int __qcom_scm_pas_auth_and_reset(struct device *dev, u32 pas_id) { int ret; struct qcom_scm_desc desc = { @@ -972,7 +919,7 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id) if (ret) goto disable_clk; - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); qcom_scm_bw_disable(); disable_clk: @@ -980,28 +927,15 @@ disable_clk: return ret ? : res.result[0]; } + +int qcom_scm_pas_auth_and_reset(u32 pas_id) +{ + return __qcom_scm_pas_auth_and_reset(__scm->dev, pas_id); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_auth_and_reset); -/** - * qcom_scm_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the - * remote processor - * - * @ctx: Context saved during call to qcom_scm_pas_context_init() - * - * This function performs the necessary steps to prepare a PAS subsystem, - * authenticate it using the provided metadata, and initiate a reset sequence. - * - * It should be used when Linux is in control setting up the IOMMU hardware - * for remote subsystem during secure firmware loading processes. The preparation - * step sets up a shmbridge over the firmware memory before TrustZone accesses the - * firmware memory region for authentication. The authentication step verifies - * the integrity and authenticity of the firmware or configuration using secure - * metadata. Finally, the reset step ensures the subsystem starts in a clean and - * sane state. - * - * Return: 0 on success, negative errno on failure. - */ -int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) +static int __qcom_scm_pas_prepare_and_auth_reset(struct device *dev, + struct qcom_pas_context *ctx) { u64 handle; int ret; @@ -1012,7 +946,7 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) * memory region and then invokes a call to TrustZone to authenticate. */ if (!ctx->use_tzmem) - return qcom_scm_pas_auth_and_reset(ctx->pas_id); + return __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id); /* * When Linux runs @ EL2 Linux must create the shmbridge itself and then @@ -1022,20 +956,45 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) if (ret) return ret; - ret = qcom_scm_pas_auth_and_reset(ctx->pas_id); + ret = __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id); qcom_tzmem_shm_bridge_delete(handle); return ret; } + +int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) +{ + return __qcom_scm_pas_prepare_and_auth_reset(__scm->dev, + (struct qcom_pas_context *)ctx); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_prepare_and_auth_reset); -/** - * qcom_scm_pas_shutdown() - Shut down the remote processor - * @pas_id: peripheral authentication service id - * - * Returns 0 on success. - */ -int qcom_scm_pas_shutdown(u32 pas_id) +static int __qcom_scm_pas_set_remote_state(struct device *dev, u32 state, + u32 pas_id) +{ + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_BOOT, + .cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE, + .arginfo = QCOM_SCM_ARGS(2), + .args[0] = state, + .args[1] = pas_id, + .owner = ARM_SMCCC_OWNER_SIP, + }; + struct qcom_scm_res res; + int ret; + + ret = qcom_scm_call(dev, &desc, &res); + + return ret ? : res.result[0]; +} + +int qcom_scm_set_remote_state(u32 state, u32 id) +{ + return __qcom_scm_pas_set_remote_state(__scm->dev, state, id); +} +EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state); + +static int __qcom_scm_pas_shutdown(struct device *dev, u32 pas_id) { int ret; struct qcom_scm_desc desc = { @@ -1055,7 +1014,7 @@ int qcom_scm_pas_shutdown(u32 pas_id) if (ret) goto disable_clk; - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); qcom_scm_bw_disable(); disable_clk: @@ -1063,16 +1022,14 @@ disable_clk: return ret ? : res.result[0]; } + +int qcom_scm_pas_shutdown(u32 pas_id) +{ + return __qcom_scm_pas_shutdown(__scm->dev, pas_id); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_shutdown); -/** - * qcom_scm_pas_supported() - Check if the peripheral authentication service is - * available for the given peripherial - * @pas_id: peripheral authentication service id - * - * Returns true if PAS is supported for this peripheral, otherwise false. - */ -bool qcom_scm_pas_supported(u32 pas_id) +static bool __qcom_scm_pas_supported(struct device *dev, u32 pas_id) { int ret; struct qcom_scm_desc desc = { @@ -1084,16 +1041,49 @@ bool qcom_scm_pas_supported(u32 pas_id) }; struct qcom_scm_res res; - if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL, + if (!__qcom_scm_is_call_available(dev, QCOM_SCM_SVC_PIL, QCOM_SCM_PIL_PAS_IS_SUPPORTED)) return false; - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); return ret ? false : !!res.result[0]; } + +bool qcom_scm_pas_supported(u32 pas_id) +{ + return __qcom_scm_pas_supported(__scm->dev, pas_id); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_supported); +static struct qcom_pas_ops qcom_pas_ops_scm = { + .drv_name = "qcom_scm", + .supported = __qcom_scm_pas_supported, + .init_image = __qcom_scm_pas_init_image2, + .mem_setup = __qcom_scm_pas_mem_setup, + .get_rsc_table = __qcom_scm_pas_get_rsc_table2, + .auth_and_reset = __qcom_scm_pas_auth_and_reset, + .prepare_and_auth_reset = __qcom_scm_pas_prepare_and_auth_reset, + .set_remote_state = __qcom_scm_pas_set_remote_state, + .shutdown = __qcom_scm_pas_shutdown, + .metadata_release = __qcom_scm_pas_metadata_release, +}; + +/** + * qcom_scm_is_pas_available() - Check if the peripheral authentication service + * is available via SCM or not + * + * Returns true if PAS is available, otherwise false. + */ +static bool qcom_scm_is_pas_available(void) +{ + if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL, + QCOM_SCM_PIL_PAS_AUTH_AND_RESET)) + return false; + + return true; +} + static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset) { struct qcom_scm_desc desc = { @@ -2040,6 +2030,29 @@ int qcom_scm_gpu_init_regs(u32 gpu_req) } EXPORT_SYMBOL_GPL(qcom_scm_gpu_init_regs); +static int qcom_scm_map_minidump_sram(struct device *dev, void __iomem **out) +{ + struct device_node *np = dev->of_node; + struct device_node *sram_np; + struct resource res; + int ret; + + sram_np = of_parse_phandle(np, "sram", 0); + if (!sram_np) + return 0; + + ret = of_address_to_resource(sram_np, 0, &res); + of_node_put(sram_np); + if (ret) + return ret; + + *out = devm_ioremap(dev, res.start, resource_size(&res)); + if (!*out) + return -ENOMEM; + + return 0; +} + static int qcom_scm_find_dload_address(struct device *dev, u64 *addr) { struct device_node *tcsr; @@ -2295,6 +2308,7 @@ static const struct of_device_id qcom_scm_qseecom_allowlist[] __maybe_unused = { { .compatible = "dell,latitude-7455" }, { .compatible = "dell,xps13-9345" }, { .compatible = "ecs,liva-qc710" }, + { .compatible = "honor,magicbook-art-14-snapdragon" }, { .compatible = "hp,elitebook-ultra-g1q" }, { .compatible = "hp,omnibook-x14" }, { .compatible = "huawei,gaokun3" }, @@ -2705,6 +2719,7 @@ static int get_download_mode(char *buffer, const struct kernel_param *kp) static int set_download_mode(const char *val, const struct kernel_param *kp) { + struct qcom_scm *scm; bool tmp; int ret; @@ -2720,8 +2735,10 @@ static int set_download_mode(const char *val, const struct kernel_param *kp) } download_mode = ret; - if (__scm) - qcom_scm_set_download_mode(download_mode); + /* Pairs with smp_store_release() in qcom_scm_probe(). */ + scm = smp_load_acquire(&__scm); + if (scm) + qcom_scm_set_download_mode(scm, download_mode); return 0; } @@ -2734,6 +2751,47 @@ static const struct kernel_param_ops download_mode_param_ops = { module_param_cb(download_mode, &download_mode_param_ops, NULL, 0644); MODULE_PARM_DESC(download_mode, "download mode: off/0/N for no dump mode, full/on/1/Y for full dump mode, mini for minidump mode and full,mini for both full and minidump mode together are acceptable values"); +static int get_minidump_dest(char *buffer, const struct kernel_param *kp) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(minidump_dest_map); i++) + if (minidump_dest == minidump_dest_map[i].val) + return sysfs_emit(buffer, "%s\n", minidump_dest_map[i].name); + + return sysfs_emit(buffer, "unknown\n"); +} + +static int set_minidump_dest(const char *val, const struct kernel_param *kp) +{ + struct qcom_scm *scm; + int i; + + for (i = 0; i < ARRAY_SIZE(minidump_dest_map); i++) + if (sysfs_streq(val, minidump_dest_map[i].name)) + break; + + if (i >= ARRAY_SIZE(minidump_dest_map)) + return -EINVAL; + + minidump_dest = minidump_dest_map[i].val; + + /* Pairs with smp_store_release() in qcom_scm_probe(). */ + scm = smp_load_acquire(&__scm); + if (scm && scm->minidump_sram && (download_mode & QCOM_DLOAD_MINIDUMP)) + writel_relaxed(minidump_dest, scm->minidump_sram); + + return 0; +} + +static const struct kernel_param_ops minidump_dest_param_ops = { + .get = get_minidump_dest, + .set = set_minidump_dest, +}; + +module_param_cb(minidump_dest, &minidump_dest_param_ops, NULL, 0644); +MODULE_PARM_DESC(minidump_dest, "Minidump SRAM destination: usb (default) or storage"); + static int qcom_scm_probe(struct platform_device *pdev) { struct qcom_tzmem_pool_config pool_config; @@ -2748,7 +2806,13 @@ static int qcom_scm_probe(struct platform_device *pdev) scm->dev = &pdev->dev; ret = qcom_scm_find_dload_address(&pdev->dev, &scm->dload_mode_addr); if (ret < 0) - return ret; + return dev_err_probe(&pdev->dev, ret, + "Failed to get download mode address\n"); + + ret = qcom_scm_map_minidump_sram(&pdev->dev, &scm->minidump_sram); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Failed to map minidump SRAM\n"); mutex_init(&scm->scm_bw_lock); @@ -2837,12 +2901,17 @@ static int qcom_scm_probe(struct platform_device *pdev) __get_convention(); + if (qcom_scm_is_pas_available()) { + qcom_pas_ops_scm.dev = scm->dev; + qcom_pas_ops_register(&qcom_pas_ops_scm); + } + /* * If "download mode" is requested, from this point on warmboot * will cause the boot stages to enter download mode, unless * disabled below by a clean shutdown/reboot. */ - qcom_scm_set_download_mode(download_mode); + qcom_scm_set_download_mode(scm, download_mode); /* * Disable SDI if indicated by DT that it is enabled by default. @@ -2875,7 +2944,8 @@ static int qcom_scm_probe(struct platform_device *pdev) static void qcom_scm_shutdown(struct platform_device *pdev) { /* Clean shutdown, disable download mode to allow normal restart */ - qcom_scm_set_download_mode(QCOM_DLOAD_NODUMP); + qcom_scm_set_download_mode(__scm, QCOM_DLOAD_NODUMP); + qcom_pas_ops_unregister(); } static const struct of_device_id qcom_scm_dt_match[] = { diff --git a/drivers/firmware/qcom/qcom_scm_trace.h b/drivers/firmware/qcom/qcom_scm_trace.h new file mode 100644 index 000000000000..6c911124fc56 --- /dev/null +++ b/drivers/firmware/qcom/qcom_scm_trace.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM qcom_scm + +#if !defined(_TRACE_SCM_SMC_INTERFACE_H) || defined(TRACE_HEADER_MULTI_READ) + +#define _TRACE_SCM_SMC_INTERFACE_H + +#include <linux/tracepoint.h> + +TRACE_EVENT(scm_smc_request, + + TP_PROTO(unsigned long a0, const struct arm_smccc_args *smc), + + TP_ARGS(a0, smc), + + TP_STRUCT__entry( + __field(u64, smc_id) + __field(u8, svc_id) + __field(u8, cmd_id) + __field(u8, args_cnt) + __dynamic_array(unsigned long, args, + min_t(u8, (smc->args[1] & 0xF), (u8)6)) + ), + + TP_fast_assign( + __entry->smc_id = a0; + __entry->svc_id = (smc->args[0] >> 8) & 0xFF; + __entry->cmd_id = smc->args[0] & 0xFF; + u8 n = min_t(u8, (smc->args[1] & 0xF), (u8)6); + + __entry->args_cnt = n; + + unsigned long *dst = __get_dynamic_array(args); + + for (int i = 0; i < n; i++) + dst[i] = smc->args[2 + i]; + ), + + TP_printk("smc_id:0x%08llx svc_id:0x%02x cmd_id:0x%02x args_cnt:%u args:%s", + __entry->smc_id, __entry->svc_id, __entry->cmd_id, __entry->args_cnt, + __print_dynamic_array(args, sizeof(unsigned long))) +); + +TRACE_EVENT(scm_waitq_sleep, + + TP_PROTO(u32 wq_ctx, u32 smc_ctx), + + TP_ARGS(wq_ctx, smc_ctx), + + TP_STRUCT__entry( + __field(u32, wq_ctx) + __field(u32, smc_call_ctx) + ), + + TP_fast_assign( + __entry->wq_ctx = wq_ctx; + __entry->smc_call_ctx = smc_ctx; + ), + + TP_printk("wq_ctx:%u, smc_call_ctx:%u", __entry->wq_ctx, __entry->smc_call_ctx) +); + +TRACE_EVENT(scm_waitq_resume, + + TP_PROTO(u32 smc_ctx), + + TP_ARGS(smc_ctx), + + TP_STRUCT__entry( + __field(u32, smc_call_ctx) + ), + + TP_fast_assign( + __entry->smc_call_ctx = smc_ctx; + ), + + TP_printk("smc_call_ctx:%u", __entry->smc_call_ctx) +); + +TRACE_EVENT(scm_waitq_get_wq_ctx, + + TP_PROTO(u32 wq_ctx, u32 flags, u32 pending), + + TP_ARGS(wq_ctx, flags, pending), + + TP_STRUCT__entry( + __field(u32, wq_ctx) + __field(u32, flags) + __field(u32, more_pending) + ), + + TP_fast_assign( + __entry->wq_ctx = wq_ctx; + __entry->flags = flags; + __entry->more_pending = pending; + ), + + TP_printk("wq_ctx:%u, flags:%u, more_pending:%u", + __entry->wq_ctx, __entry->flags, __entry->more_pending) +); + +TRACE_EVENT(scm_smc_done, + + TP_PROTO(int ret, u64 smc_id, struct arm_smccc_res *smc_res), + + TP_ARGS(ret, smc_id, smc_res), + + TP_STRUCT__entry( + __field(int, ret) + __field(u64, smc_id) + __field(unsigned long, res) + __field(unsigned long, res0) + __field(unsigned long, res1) + __field(unsigned long, res2) + ), + + TP_fast_assign( + __entry->ret = ret; + __entry->smc_id = smc_id; + __entry->res = smc_res->a0; + __entry->res0 = smc_res->a1; + __entry->res1 = smc_res->a2; + __entry->res2 = smc_res->a3; + ), + + TP_printk("smc_id:0x%08llx, ret:%d res_to_callee:0x%lx res0:0x%lx res1:0x%lx res2:0x%lx", + __entry->smc_id, __entry->ret, __entry->res, + __entry->res0, __entry->res1, __entry->res2) +); + +#endif /* _TRACE_SCM_SMC_INTERFACE_H */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE qcom_scm_trace + +#include <trace/define_trace.h> + |
