Re: [PATCH v4 3/4] firmware: qcom_scm: Add support for Qualcomm Secure Execution Environment SCM interface

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, May 29, 2023 at 01:03:50AM +0200, 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 added interface attempts to automatically detect known and supported
> applications, creating a client (auxiliary) device for each one. The
> respective client/auxiliary driver is then responsible for managing and
> communicating with the application.
> 
> 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 <luzmaximilian@xxxxxxxxx>
> ---
> 
> Changes in v4:
>  - Remove instantiation of dedicated QSEECOM device and load the driver
>    via qcom_scm instead. In particular:
>    - Add a list of tested devices to ensure that we don't run into any
>      issues with the currently unimplemented re-entrant calls.
>    - Use the QSEECOM version to check for general availability of the
>      interface.
>    - Attempt to automatically detect available QSEECOM applications
>      (and instantiate respective clients) based on a fixed list.
>  - Use auxiliary bus and devices for clients instead of MFD.
>  - Restructure DMA allocations: Use dma_map_single() directly inside 
>    qcom_scm_qseecom_app_send() instead of requiring clients to allocate
>    DMA memory themselves.
 
> +#ifdef CONFIG_QCOM_SCM_QSEECOM
> +
> +/* Lock for QSEECOM SCM call executions */
> +DEFINE_MUTEX(qcom_scm_qseecom_call_lock);

Missing static keyword.

> +/**
> + * qcom_scm_qseecom_call() - Perform a QSEECOM SCM call.
> + * @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.

Nit: You should drop "Returns" here and capitalise Zero. Similar below.

> + */

> +/**
> + * qcom_scm_qseecom_get_version() - Query the QSEECOM version.
> + * @version: Pointer where the QSEECOM version will be stored.
> + *
> + * Performs the QSEECOM SCM querying the QSEECOM version currently running in
> + * the TrustZone.
> + *
> + * Return: Returns zero on success, nonzero on failure.
> + */
> +static int qcom_scm_qseecom_get_version(u32 *version)
> +{
> +	struct qcom_scm_desc desc = {};
> +	struct qcom_scm_qseecom_resp res = {};
> +	u32 feature = 10;
> +	int ret;
> +
> +	desc.owner = QSEECOM_TZ_OWNER_SIP;
> +	desc.svc = QSEECOM_TZ_SVC_INFO;
> +	desc.cmd = 0x03;

I know this has been reverse engineered, but is it possible to name also
these cmd codes?

> +	desc.arginfo = QCOM_SCM_ARGS(1, QCOM_SCM_VAL);
> +	desc.args[0] = feature;
> +
> +	ret = qcom_scm_qseecom_call(&desc, &res);
> +	if (ret)
> +		return ret;
> +
> +	*version = res.result;
> +	return 0;
> +}
> +
> +/**
> + * qcom_scm_qseecom_app_get_id() - Query the app ID for a given QSEE app name.
> + * @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.
> + */
> +static int qcom_scm_qseecom_app_get_id(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 qcom_scm_qseecom_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(__scm->dev, name_buf, name_buf_size, DMA_TO_DEVICE);
> +	if (dma_mapping_error(__scm->dev, name_buf_phys)) {
> +		kfree(name_buf);
> +		dev_err(__scm->dev, "qseecom: failed to map dma address\n");
> +		return -EFAULT;

This should be -ENOMEM (you can also just use the return value from
dma_mapping_error()). Similar below.

> +	}
> +
> +	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 = qcom_scm_qseecom_call(&desc, &res);
> +	dma_unmap_single(__scm->dev, name_buf_phys, name_buf_size, DMA_TO_DEVICE);
> +	kfree(name_buf);
> +
> +	if (status)
> +		return status;
> +
> +	if (res.result == QSEECOM_RESULT_FAILURE)
> +		return -ENOENT;
> +
> +	if (res.result != QSEECOM_RESULT_SUCCESS)
> +		return -EINVAL;
> +
> +	if (res.resp_type != QSEECOM_SCM_RES_APP_ID)
> +		return -EINVAL;
> +
> +	*app_id = res.data;
> +	return 0;
> +}
> +
> +/**
> + * qcom_scm_qseecom_app_send() - Send to and receive data from a given QSEE app.
> + * @client:   The QSEECOM client device corresponding to the target app.
> + * @req:      Request buffer sent to the app (must be DMA-mappable).
> + * @req_size: Size of the request buffer.
> + * @rsp:      Response buffer, written to by the app (must be DMA-mappable).
> + * @rsp_size: Size of the response buffer.
> + *
> + * Sends a request to the QSEE app associated with the given client 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 QSEE app reads this and returns
> + * its response in the @rsp region.
> + *
> + * Return: Returns zero on success, nonzero error code on failure.
> + */
> +int qcom_scm_qseecom_app_send(struct qseecom_client *client, void *req,
> +			      size_t req_size, void *rsp, size_t rsp_size)
> +{

> +}
> +EXPORT_SYMBOL(qcom_scm_qseecom_app_send);

Should this not be EXPORT_SYMBOL_GPL()?

> +
> +static void qseecom_client_release(struct device *dev)
> +{
> +	struct qseecom_client *client = container_of(dev, struct qseecom_client, aux_dev.dev);
> +
> +	kfree(client);
> +}
> +
> +static void qseecom_client_remove(void *data)
> +{
> +	struct qseecom_client *client = data;
> +
> +	auxiliary_device_delete(&client->aux_dev);
> +	auxiliary_device_uninit(&client->aux_dev);
> +}
> +
> +static int qseecom_client_register(const struct qseecom_app_desc *desc)
> +{
> +	struct qseecom_client *client;
> +	u32 app_id;
> +	int ret;
> +
> +	/* Try to find the app ID, skip device if not found */
> +	ret = qcom_scm_qseecom_app_get_id(desc->app_name, &app_id);
> +	if (ret)
> +		return ret == -ENOENT ? 0 : ret;
> +
> +	dev_info(__scm->dev, "qseecom: setting up client for %s\n", desc->app_name);
> +
> +	/* Allocate and set-up the client device */
> +	client = kzalloc(sizeof(*client), GFP_KERNEL);
> +	if (!client)
> +		return -ENOMEM;
> +
> +	client->aux_dev.name = desc->dev_name;
> +	client->aux_dev.dev.parent = __scm->dev;
> +	client->aux_dev.dev.release = qseecom_client_release;
> +	client->app_id = app_id;
> +
> +	ret = auxiliary_device_init(&client->aux_dev);
> +	if (ret) {
> +		kfree(client);
> +		return ret;
> +	}
> +
> +	ret = auxiliary_device_add(&client->aux_dev);
> +	if (ret) {
> +		auxiliary_device_uninit(&client->aux_dev);
> +		return ret;
> +	}
> +
> +	/*
> +	 * Ensure that the device is properly cleaned up in case of removal or
> +	 * errors.
> +	 */
> +	ret = devm_add_action_or_reset(__scm->dev, qseecom_client_remove, client);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +/*
> + * We do not yet support re-entrant calls via the qseecom interface. To prevent
> + + any potential issues with this, only allow validated devices for now.
> + */
> +static const struct of_device_id qcom_scm_qseecom_allowlist[] = {
> +	{ .compatible = "lenovo,thinkpad-x13s", },
> +	{ }
> +};
> +
> +static bool qcom_scm_qseecom_device_allowed(void)
> +{
> +	struct device_node *np;
> +	bool match;
> +
> +	np = of_find_node_by_path("/");
> +	match = of_match_node(qcom_scm_qseecom_allowlist, np);
> +	of_node_put(np);
> +
> +	return match;
> +}
> +
> +static const struct qseecom_app_desc qcom_scm_qseecom_apps[] = {};
> +
> +static int qcom_scm_qseecom_init(void)
> +{
> +	u32 version;
> +	int ret, i;
> +
> +	/* Try to query the qseecom version, skip qseecom setup if this fails */
> +	ret = qcom_scm_qseecom_get_version(&version);
> +	if (ret)
> +		return 0;
> +
> +	dev_info(__scm->dev, "qseecom: found qseecom with version 0x%x\n", version);
> +
> +	/* Check against tested/verified devices */
> +	if (!qcom_scm_qseecom_device_allowed()) {
> +		dev_info(__scm->dev, "qseecom: untested device, skipping\n");

Perhaps call the helper machine_allowed() and update the commit message
to match (cf. of_machine_is_compatible())?

> +		return 0;
> +	}
> +
> +	/* Set up client devices for each base application */
> +	for (i = 0; i < ARRAY_SIZE(qcom_scm_qseecom_apps); i++) {
> +		ret = qseecom_client_register(&qcom_scm_qseecom_apps[i]);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +#else /* CONFIG_QCOM_SCM_QSEECOM */
> +
> +static int qcom_scm_qseecom_init(void)
> +{
> +	return 0;
> +}
> +
> +#endif /* CONFIG_QCOM_SCM_QSEECOM */
> +
>  /**
>   * qcom_scm_is_available() - Checks if SCM is available
>   */
> @@ -1496,6 +1903,12 @@ static int qcom_scm_probe(struct platform_device *pdev)
>  
>  	__get_convention();
>  
> +	ret = qcom_scm_qseecom_init();
> +	if (ret < 0) {
> +		__scm = NULL;

So as I mentioned in my reply to 2/4, you can still have clients
registered here when you clear the __scm pointer which they rely on
after an error.

Not sure how best to handle this, but perhaps registering a qseecom
platform device here and have it's driver probe defer until scm is
available would work?

That way you could also separate out the qseecom implementation in a
separate file (driver) rather than having the ifdef above.

> +		return dev_err_probe(scm->dev, ret, "Failed to initialize qseecom\n");
> +	}
> +
>  	/*
>  	 * If requested enable "download mode", from this point on warmboot
>  	 * will cause the boot stages to enter download mode, unless

Overall this looks really good. Nice job!

Johan



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux