Unverified Commit 14672a9b authored by Arnd Bergmann's avatar Arnd Bergmann
Browse files

Merge tag 'qcom-drivers-fixes-for-6.9' of...

Merge tag 'qcom-drivers-fixes-for-6.9' of https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux into for-next

Qualcomm driver fix for v6.9

This reworks the memory layout of the argument buffers passed to trusted
applications in QSEECOM, to avoid failures and system crashes.

* tag 'qcom-drivers-fixes-for-6.9' of https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux:
  firmware: qcom: uefisecapp: Fix memory related IO errors and crashes

Link: https://lore.kernel.org/r/20240420163816.1133528-1-andersson@kernel.org


Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
parents 7e685383 ed09f81e
Loading
Loading
Loading
Loading
+91 −46
Original line number Diff line number Diff line
@@ -221,6 +221,19 @@ struct qsee_rsp_uefi_query_variable_info {
 * alignment of 8 bytes (64 bits) for GUIDs. Our definition of efi_guid_t,
 * however, has an alignment of 4 byte (32 bits). So far, this seems to work
 * fine here. See also the comment on the typedef of efi_guid_t.
 *
 * Note: It looks like uefisecapp is quite picky about how the memory passed to
 * it is structured and aligned. In particular the request/response setup used
 * for QSEE_CMD_UEFI_GET_VARIABLE. While qcom_qseecom_app_send(), in theory,
 * accepts separate buffers/addresses for the request and response parts, in
 * practice, however, it seems to expect them to be both part of a larger
 * contiguous block. We initially allocated separate buffers for the request
 * and response but this caused the QSEE_CMD_UEFI_GET_VARIABLE command to
 * either not write any response to the response buffer or outright crash the
 * device. Therefore, we now allocate a single contiguous block of DMA memory
 * for both and properly align the data using the macros below. In particular,
 * request and response structs are aligned at 8 byte (via __reqdata_offs()),
 * following the driver that this has been reverse-engineered from.
 */
#define qcuefi_buf_align_fields(fields...)					\
	({									\
@@ -244,6 +257,12 @@ struct qsee_rsp_uefi_query_variable_info {
#define __array_offs(type, count, offset)					\
	__field_impl(sizeof(type) * (count), __alignof__(type), offset)

#define __array_offs_aligned(type, count, align, offset)			\
	__field_impl(sizeof(type) * (count), align, offset)

#define __reqdata_offs(size, offset)						\
	__array_offs_aligned(u8, size, 8, offset)

#define __array(type, count)		__array_offs(type, count, NULL)
#define __field_offs(type, offset)	__array_offs(type, 1, offset)
#define __field(type)			__array_offs(type, 1, NULL)
@@ -277,10 +296,15 @@ static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const e
	unsigned long buffer_size = *data_size;
	efi_status_t efi_status = EFI_SUCCESS;
	unsigned long name_length;
	dma_addr_t cmd_buf_dma;
	size_t cmd_buf_size;
	void *cmd_buf;
	size_t guid_offs;
	size_t name_offs;
	size_t req_size;
	size_t rsp_size;
	size_t req_offs;
	size_t rsp_offs;
	ssize_t status;

	if (!name || !guid)
@@ -304,17 +328,19 @@ static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const e
		__array(u8, buffer_size)
	);

	req_data = kzalloc(req_size, GFP_KERNEL);
	if (!req_data) {
	cmd_buf_size = qcuefi_buf_align_fields(
		__reqdata_offs(req_size, &req_offs)
		__reqdata_offs(rsp_size, &rsp_offs)
	);

	cmd_buf = qseecom_dma_alloc(qcuefi->client, cmd_buf_size, &cmd_buf_dma, GFP_KERNEL);
	if (!cmd_buf) {
		efi_status = EFI_OUT_OF_RESOURCES;
		goto out;
	}

	rsp_data = kzalloc(rsp_size, GFP_KERNEL);
	if (!rsp_data) {
		efi_status = EFI_OUT_OF_RESOURCES;
		goto out_free_req;
	}
	req_data = cmd_buf + req_offs;
	rsp_data = cmd_buf + rsp_offs;

	req_data->command_id = QSEE_CMD_UEFI_GET_VARIABLE;
	req_data->data_size = buffer_size;
@@ -332,7 +358,9 @@ static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const e

	memcpy(((void *)req_data) + req_data->guid_offset, guid, req_data->guid_size);

	status = qcom_qseecom_app_send(qcuefi->client, req_data, req_size, rsp_data, rsp_size);
	status = qcom_qseecom_app_send(qcuefi->client,
				       cmd_buf_dma + req_offs, req_size,
				       cmd_buf_dma + rsp_offs, rsp_size);
	if (status) {
		efi_status = EFI_DEVICE_ERROR;
		goto out_free;
@@ -407,9 +435,7 @@ static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const e
	memcpy(data, ((void *)rsp_data) + rsp_data->data_offset, rsp_data->data_size);

out_free:
	kfree(rsp_data);
out_free_req:
	kfree(req_data);
	qseecom_dma_free(qcuefi->client, cmd_buf_size, cmd_buf, cmd_buf_dma);
out:
	return efi_status;
}
@@ -422,10 +448,15 @@ static efi_status_t qsee_uefi_set_variable(struct qcuefi_client *qcuefi, const e
	struct qsee_rsp_uefi_set_variable *rsp_data;
	efi_status_t efi_status = EFI_SUCCESS;
	unsigned long name_length;
	dma_addr_t cmd_buf_dma;
	size_t cmd_buf_size;
	void *cmd_buf;
	size_t name_offs;
	size_t guid_offs;
	size_t data_offs;
	size_t req_size;
	size_t req_offs;
	size_t rsp_offs;
	ssize_t status;

	if (!name || !guid)
@@ -450,17 +481,19 @@ static efi_status_t qsee_uefi_set_variable(struct qcuefi_client *qcuefi, const e
		__array_offs(u8, data_size, &data_offs)
	);

	req_data = kzalloc(req_size, GFP_KERNEL);
	if (!req_data) {
	cmd_buf_size = qcuefi_buf_align_fields(
		__reqdata_offs(req_size, &req_offs)
		__reqdata_offs(sizeof(*rsp_data), &rsp_offs)
	);

	cmd_buf = qseecom_dma_alloc(qcuefi->client, cmd_buf_size, &cmd_buf_dma, GFP_KERNEL);
	if (!cmd_buf) {
		efi_status = EFI_OUT_OF_RESOURCES;
		goto out;
	}

	rsp_data = kzalloc(sizeof(*rsp_data), GFP_KERNEL);
	if (!rsp_data) {
		efi_status = EFI_OUT_OF_RESOURCES;
		goto out_free_req;
	}
	req_data = cmd_buf + req_offs;
	rsp_data = cmd_buf + rsp_offs;

	req_data->command_id = QSEE_CMD_UEFI_SET_VARIABLE;
	req_data->attributes = attributes;
@@ -483,8 +516,9 @@ static efi_status_t qsee_uefi_set_variable(struct qcuefi_client *qcuefi, const e
	if (data_size)
		memcpy(((void *)req_data) + req_data->data_offset, data, req_data->data_size);

	status = qcom_qseecom_app_send(qcuefi->client, req_data, req_size, rsp_data,
				       sizeof(*rsp_data));
	status = qcom_qseecom_app_send(qcuefi->client,
				       cmd_buf_dma + req_offs, req_size,
				       cmd_buf_dma + rsp_offs, sizeof(*rsp_data));
	if (status) {
		efi_status = EFI_DEVICE_ERROR;
		goto out_free;
@@ -507,9 +541,7 @@ static efi_status_t qsee_uefi_set_variable(struct qcuefi_client *qcuefi, const e
	}

out_free:
	kfree(rsp_data);
out_free_req:
	kfree(req_data);
	qseecom_dma_free(qcuefi->client, cmd_buf_size, cmd_buf, cmd_buf_dma);
out:
	return efi_status;
}
@@ -521,10 +553,15 @@ static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
	struct qsee_req_uefi_get_next_variable *req_data;
	struct qsee_rsp_uefi_get_next_variable *rsp_data;
	efi_status_t efi_status = EFI_SUCCESS;
	dma_addr_t cmd_buf_dma;
	size_t cmd_buf_size;
	void *cmd_buf;
	size_t guid_offs;
	size_t name_offs;
	size_t req_size;
	size_t rsp_size;
	size_t req_offs;
	size_t rsp_offs;
	ssize_t status;

	if (!name_size || !name || !guid)
@@ -545,17 +582,19 @@ static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
		__array(*name, *name_size / sizeof(*name))
	);

	req_data = kzalloc(req_size, GFP_KERNEL);
	if (!req_data) {
	cmd_buf_size = qcuefi_buf_align_fields(
		__reqdata_offs(req_size, &req_offs)
		__reqdata_offs(rsp_size, &rsp_offs)
	);

	cmd_buf = qseecom_dma_alloc(qcuefi->client, cmd_buf_size, &cmd_buf_dma, GFP_KERNEL);
	if (!cmd_buf) {
		efi_status = EFI_OUT_OF_RESOURCES;
		goto out;
	}

	rsp_data = kzalloc(rsp_size, GFP_KERNEL);
	if (!rsp_data) {
		efi_status = EFI_OUT_OF_RESOURCES;
		goto out_free_req;
	}
	req_data = cmd_buf + req_offs;
	rsp_data = cmd_buf + rsp_offs;

	req_data->command_id = QSEE_CMD_UEFI_GET_NEXT_VARIABLE;
	req_data->guid_offset = guid_offs;
@@ -572,7 +611,9 @@ static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
		goto out_free;
	}

	status = qcom_qseecom_app_send(qcuefi->client, req_data, req_size, rsp_data, rsp_size);
	status = qcom_qseecom_app_send(qcuefi->client,
				       cmd_buf_dma + req_offs, req_size,
				       cmd_buf_dma + rsp_offs, rsp_size);
	if (status) {
		efi_status = EFI_DEVICE_ERROR;
		goto out_free;
@@ -645,9 +686,7 @@ static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
	}

out_free:
	kfree(rsp_data);
out_free_req:
	kfree(req_data);
	qseecom_dma_free(qcuefi->client, cmd_buf_size, cmd_buf, cmd_buf_dma);
out:
	return efi_status;
}
@@ -659,26 +698,34 @@ static efi_status_t qsee_uefi_query_variable_info(struct qcuefi_client *qcuefi,
	struct qsee_req_uefi_query_variable_info *req_data;
	struct qsee_rsp_uefi_query_variable_info *rsp_data;
	efi_status_t efi_status = EFI_SUCCESS;
	dma_addr_t cmd_buf_dma;
	size_t cmd_buf_size;
	void *cmd_buf;
	size_t req_offs;
	size_t rsp_offs;
	int status;

	req_data = kzalloc(sizeof(*req_data), GFP_KERNEL);
	if (!req_data) {
	cmd_buf_size = qcuefi_buf_align_fields(
		__reqdata_offs(sizeof(*req_data), &req_offs)
		__reqdata_offs(sizeof(*rsp_data), &rsp_offs)
	);

	cmd_buf = qseecom_dma_alloc(qcuefi->client, cmd_buf_size, &cmd_buf_dma, GFP_KERNEL);
	if (!cmd_buf) {
		efi_status = EFI_OUT_OF_RESOURCES;
		goto out;
	}

	rsp_data = kzalloc(sizeof(*rsp_data), GFP_KERNEL);
	if (!rsp_data) {
		efi_status = EFI_OUT_OF_RESOURCES;
		goto out_free_req;
	}
	req_data = cmd_buf + req_offs;
	rsp_data = cmd_buf + rsp_offs;

	req_data->command_id = QSEE_CMD_UEFI_QUERY_VARIABLE_INFO;
	req_data->attributes = attr;
	req_data->length = sizeof(*req_data);

	status = qcom_qseecom_app_send(qcuefi->client, req_data, sizeof(*req_data), rsp_data,
				       sizeof(*rsp_data));
	status = qcom_qseecom_app_send(qcuefi->client,
				       cmd_buf_dma + req_offs, sizeof(*req_data),
				       cmd_buf_dma + rsp_offs, sizeof(*rsp_data));
	if (status) {
		efi_status = EFI_DEVICE_ERROR;
		goto out_free;
@@ -711,9 +758,7 @@ static efi_status_t qsee_uefi_query_variable_info(struct qcuefi_client *qcuefi,
		*max_variable_size = rsp_data->max_variable_size;

out_free:
	kfree(rsp_data);
out_free_req:
	kfree(req_data);
	qseecom_dma_free(qcuefi->client, cmd_buf_size, cmd_buf, cmd_buf_dma);
out:
	return efi_status;
}
+6 −31
Original line number Diff line number Diff line
@@ -1576,9 +1576,9 @@ EXPORT_SYMBOL_GPL(qcom_scm_qseecom_app_get_id);
/**
 * qcom_scm_qseecom_app_send() - Send to and receive data from a given QSEE app.
 * @app_id:   The ID of the target app.
 * @req:      Request buffer sent to the app (must be DMA-mappable).
 * @req:      DMA address of the request buffer sent to the app.
 * @req_size: Size of the request buffer.
 * @rsp:      Response buffer, written to by the app (must be DMA-mappable).
 * @rsp:      DMA address of the response buffer, written to by the app.
 * @rsp_size: Size of the response buffer.
 *
 * Sends a request to the QSEE app associated with the given ID and read back
@@ -1589,33 +1589,13 @@ EXPORT_SYMBOL_GPL(qcom_scm_qseecom_app_get_id);
 *
 * Return: Zero on success, nonzero on failure.
 */
int qcom_scm_qseecom_app_send(u32 app_id, void *req, size_t req_size, void *rsp,
			      size_t rsp_size)
int qcom_scm_qseecom_app_send(u32 app_id, dma_addr_t req, size_t req_size,
			      dma_addr_t rsp, size_t rsp_size)
{
	struct qcom_scm_qseecom_resp res = {};
	struct qcom_scm_desc desc = {};
	dma_addr_t req_phys;
	dma_addr_t rsp_phys;
	int status;

	/* Map request buffer */
	req_phys = dma_map_single(__scm->dev, req, req_size, DMA_TO_DEVICE);
	status = dma_mapping_error(__scm->dev, req_phys);
	if (status) {
		dev_err(__scm->dev, "qseecom: failed to map request buffer\n");
		return status;
	}

	/* Map response buffer */
	rsp_phys = dma_map_single(__scm->dev, rsp, rsp_size, DMA_FROM_DEVICE);
	status = dma_mapping_error(__scm->dev, rsp_phys);
	if (status) {
		dma_unmap_single(__scm->dev, req_phys, req_size, DMA_TO_DEVICE);
		dev_err(__scm->dev, "qseecom: failed to map response buffer\n");
		return status;
	}

	/* Set up SCM call data */
	desc.owner = QSEECOM_TZ_OWNER_TZ_APPS;
	desc.svc = QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER;
	desc.cmd = QSEECOM_TZ_CMD_APP_SEND;
@@ -1623,18 +1603,13 @@ int qcom_scm_qseecom_app_send(u32 app_id, void *req, size_t req_size, void *rsp,
				     QCOM_SCM_RW, QCOM_SCM_VAL,
				     QCOM_SCM_RW, QCOM_SCM_VAL);
	desc.args[0] = app_id;
	desc.args[1] = req_phys;
	desc.args[1] = req;
	desc.args[2] = req_size;
	desc.args[3] = rsp_phys;
	desc.args[3] = rsp;
	desc.args[4] = rsp_size;

	/* Perform call */
	status = qcom_scm_qseecom_call(&desc, &res);

	/* Unmap buffers */
	dma_unmap_single(__scm->dev, rsp_phys, rsp_size, DMA_FROM_DEVICE);
	dma_unmap_single(__scm->dev, req_phys, req_size, DMA_TO_DEVICE);

	if (status)
		return status;

+51 −4
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#define __QCOM_QSEECOM_H

#include <linux/auxiliary_bus.h>
#include <linux/dma-mapping.h>
#include <linux/types.h>

#include <linux/firmware/qcom/qcom_scm.h>
@@ -24,12 +25,57 @@ struct qseecom_client {
	u32 app_id;
};

/**
 * qseecom_scm_dev() - Get the SCM device associated with the QSEECOM client.
 * @client: The QSEECOM client device.
 *
 * Returns the SCM device under which the provided QSEECOM client device
 * operates. This function is intended to be used for DMA allocations.
 */
static inline struct device *qseecom_scm_dev(struct qseecom_client *client)
{
	return client->aux_dev.dev.parent->parent;
}

/**
 * qseecom_dma_alloc() - Allocate DMA memory for a QSEECOM client.
 * @client:     The QSEECOM client to allocate the memory for.
 * @size:       The number of bytes to allocate.
 * @dma_handle: Pointer to where the DMA address should be stored.
 * @gfp:        Allocation flags.
 *
 * Wrapper function for dma_alloc_coherent(), allocating DMA memory usable for
 * TZ/QSEECOM communication. Refer to dma_alloc_coherent() for details.
 */
static inline void *qseecom_dma_alloc(struct qseecom_client *client, size_t size,
				      dma_addr_t *dma_handle, gfp_t gfp)
{
	return dma_alloc_coherent(qseecom_scm_dev(client), size, dma_handle, gfp);
}

/**
 * dma_free_coherent() - Free QSEECOM DMA memory.
 * @client:     The QSEECOM client for which the memory has been allocated.
 * @size:       The number of bytes allocated.
 * @cpu_addr:   Virtual memory address to free.
 * @dma_handle: DMA memory address to free.
 *
 * Wrapper function for dma_free_coherent(), freeing memory previously
 * allocated with qseecom_dma_alloc(). Refer to dma_free_coherent() for
 * details.
 */
static inline void qseecom_dma_free(struct qseecom_client *client, size_t size,
				    void *cpu_addr, dma_addr_t dma_handle)
{
	return dma_free_coherent(qseecom_scm_dev(client), size, cpu_addr, dma_handle);
}

/**
 * qcom_qseecom_app_send() - Send to and receive data from a given QSEE app.
 * @client:   The QSEECOM client associated with the target app.
 * @req:      Request buffer sent to the app (must be DMA-mappable).
 * @req:      DMA address of the request buffer sent to the app.
 * @req_size: Size of the request buffer.
 * @rsp:      Response buffer, written to by the app (must be DMA-mappable).
 * @rsp:      DMA address of the response buffer, written to by the app.
 * @rsp_size: Size of the response buffer.
 *
 * Sends a request to the QSEE app associated with the given client and read
@@ -43,8 +89,9 @@ struct qseecom_client {
 *
 * Return: Zero on success, nonzero on failure.
 */
static inline int qcom_qseecom_app_send(struct qseecom_client *client, void *req, size_t req_size,
					void *rsp, size_t rsp_size)
static inline int qcom_qseecom_app_send(struct qseecom_client *client,
					dma_addr_t req, size_t req_size,
					dma_addr_t rsp, size_t rsp_size)
{
	return qcom_scm_qseecom_app_send(client->app_id, req, req_size, rsp, rsp_size);
}
+5 −5
Original line number Diff line number Diff line
@@ -118,8 +118,8 @@ bool qcom_scm_lmh_dcvsh_available(void);
#ifdef CONFIG_QCOM_QSEECOM

int qcom_scm_qseecom_app_get_id(const char *app_name, u32 *app_id);
int qcom_scm_qseecom_app_send(u32 app_id, void *req, size_t req_size, void *rsp,
			      size_t rsp_size);
int qcom_scm_qseecom_app_send(u32 app_id, dma_addr_t req, size_t req_size,
			      dma_addr_t rsp, size_t rsp_size);

#else /* CONFIG_QCOM_QSEECOM */

@@ -128,9 +128,9 @@ static inline int qcom_scm_qseecom_app_get_id(const char *app_name, u32 *app_id)
	return -EINVAL;
}

static inline int qcom_scm_qseecom_app_send(u32 app_id, void *req,
					    size_t req_size, void *rsp,
					    size_t rsp_size)
static inline int qcom_scm_qseecom_app_send(u32 app_id,
					    dma_addr_t req, size_t req_size,
					    dma_addr_t rsp, size_t rsp_size)
{
	return -EINVAL;
}