493 lines · 10.6 KB · C
RawDownload
// SPDX-License-Identifier: MIT/* * Copyright 2019 Advanced Micro Devices, Inc. */  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/errno.h>#include <linux/device.h>#include <linux/firmware.h>#include <linux/io.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/psp-tee.h>#include <linux/slab.h>#include <linux/string.h>#include <linux/tee_core.h>#include <linux/types.h>#include <linux/uaccess.h> #include "amdtee_private.h" static struct amdtee_driver_data *drv_data;static DEFINE_MUTEX(session_list_mutex); static void amdtee_get_version(struct tee_device *teedev,			       struct tee_ioctl_version_data *vers){	struct tee_ioctl_version_data v = {		.impl_id = TEE_IMPL_ID_AMDTEE,		.impl_caps = 0,		.gen_caps = TEE_GEN_CAP_GP,	};	*vers = v;} static int amdtee_open(struct tee_context *ctx){	struct amdtee_context_data *ctxdata; 	ctxdata = kzalloc_obj(*ctxdata);	if (!ctxdata)		return -ENOMEM; 	INIT_LIST_HEAD(&ctxdata->sess_list); 	ctx->data = ctxdata;	return 0;} static void release_session(struct amdtee_session *sess){	int i; 	/* Close any open session */	for (i = 0; i < TEE_NUM_SESSIONS; ++i) {		/* Check if session entry 'i' is valid */		if (!test_bit(i, sess->sess_mask))			continue; 		handle_close_session(sess->ta_handle, sess->session_info[i]);		handle_unload_ta(sess->ta_handle);	} 	kfree(sess);} static void amdtee_release(struct tee_context *ctx){	struct amdtee_context_data *ctxdata = ctx->data; 	if (!ctxdata)		return; 	while (true) {		struct amdtee_session *sess; 		sess = list_first_entry_or_null(&ctxdata->sess_list,						struct amdtee_session,						list_node); 		if (!sess)			break; 		list_del(&sess->list_node);		release_session(sess);	}	kfree(ctxdata); 	ctx->data = NULL;} /** * alloc_session() - Allocate a session structure * @ctxdata:    TEE Context data structure * @session:    Session ID for which 'struct amdtee_session' structure is to be *              allocated. * * Scans the TEE context's session list to check if TA is already loaded in to * TEE. If yes, returns the 'session' structure for that TA. Else allocates, * initializes a new 'session' structure and adds it to context's session list. * * The caller must hold a mutex. * * Returns: * 'struct amdtee_session *' on success and NULL on failure. */static struct amdtee_session *alloc_session(struct amdtee_context_data *ctxdata,					    u32 session){	struct amdtee_session *sess;	u32 ta_handle = get_ta_handle(session); 	/* Scan session list to check if TA is already loaded in to TEE */	list_for_each_entry(sess, &ctxdata->sess_list, list_node)		if (sess->ta_handle == ta_handle) {			kref_get(&sess->refcount);			return sess;		} 	/* Allocate a new session and add to list */	sess = kzalloc_obj(*sess);	if (sess) {		sess->ta_handle = ta_handle;		kref_init(&sess->refcount);		spin_lock_init(&sess->lock);		list_add(&sess->list_node, &ctxdata->sess_list);	} 	return sess;} /* Requires mutex to be held */static struct amdtee_session *find_session(struct amdtee_context_data *ctxdata,					   u32 session){	u32 ta_handle = get_ta_handle(session);	u32 index = get_session_index(session);	struct amdtee_session *sess; 	if (index >= TEE_NUM_SESSIONS)		return NULL; 	list_for_each_entry(sess, &ctxdata->sess_list, list_node)		if (ta_handle == sess->ta_handle &&		    test_bit(index, sess->sess_mask))			return sess; 	return NULL;} static DEFINE_MUTEX(drv_mutex);static int copy_ta_binary(struct tee_context *ctx, void *ptr, void **ta,			  size_t *ta_size){	const struct firmware *fw;	char fw_name[TA_PATH_MAX];	struct {		u32 lo;		u16 mid;		u16 hi_ver;		u8 seq_n[8];	} *uuid = ptr;	int n, rc = 0; 	n = snprintf(fw_name, TA_PATH_MAX,		     "%s/%08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x.bin",		     TA_LOAD_PATH, uuid->lo, uuid->mid, uuid->hi_ver,		     uuid->seq_n[0], uuid->seq_n[1],		     uuid->seq_n[2], uuid->seq_n[3],		     uuid->seq_n[4], uuid->seq_n[5],		     uuid->seq_n[6], uuid->seq_n[7]);	if (n < 0 || n >= TA_PATH_MAX) {		pr_err("failed to get firmware name\n");		return -EINVAL;	} 	mutex_lock(&drv_mutex);	n = request_firmware(&fw, fw_name, &ctx->teedev->dev);	if (n) {		pr_err("failed to load firmware %s\n", fw_name);		rc = -ENOMEM;		goto unlock;	} 	*ta_size = roundup(fw->size, PAGE_SIZE);	*ta = (void *)__get_free_pages(GFP_KERNEL, get_order(*ta_size));	if (!*ta) {		pr_err("%s: get_free_pages failed\n", __func__);		rc = -ENOMEM;		goto rel_fw;	} 	memcpy(*ta, fw->data, fw->size);rel_fw:	release_firmware(fw);unlock:	mutex_unlock(&drv_mutex);	return rc;} /* mutex must be held by caller */static void destroy_session(struct kref *ref){	struct amdtee_session *sess = container_of(ref, struct amdtee_session,						   refcount); 	list_del(&sess->list_node);	mutex_unlock(&session_list_mutex);	kfree(sess);} int amdtee_open_session(struct tee_context *ctx,			struct tee_ioctl_open_session_arg *arg,			struct tee_param *param){	struct amdtee_context_data *ctxdata = ctx->data;	struct amdtee_session *sess = NULL;	u32 session_info, ta_handle;	size_t ta_size;	int rc, i;	void *ta; 	if (arg->clnt_login != TEE_IOCTL_LOGIN_PUBLIC) {		pr_err("unsupported client login method\n");		return -EINVAL;	} 	rc = copy_ta_binary(ctx, &arg->uuid[0], &ta, &ta_size);	if (rc) {		pr_err("failed to copy TA binary\n");		return rc;	} 	/* Load the TA binary into TEE environment */	handle_load_ta(ta, ta_size, arg);	if (arg->ret != TEEC_SUCCESS)		goto out; 	ta_handle = get_ta_handle(arg->session); 	mutex_lock(&session_list_mutex);	sess = alloc_session(ctxdata, arg->session);	mutex_unlock(&session_list_mutex); 	if (!sess) {		handle_unload_ta(ta_handle);		rc = -ENOMEM;		goto out;	} 	/* Open session with loaded TA */	handle_open_session(arg, &session_info, param);	if (arg->ret != TEEC_SUCCESS) {		pr_err("open_session failed %d\n", arg->ret);		handle_unload_ta(ta_handle);		kref_put_mutex(&sess->refcount, destroy_session,			       &session_list_mutex);		goto out;	} 	/* Find an empty session index for the given TA */	spin_lock(&sess->lock);	i = find_first_zero_bit(sess->sess_mask, TEE_NUM_SESSIONS);	if (i < TEE_NUM_SESSIONS) {		sess->session_info[i] = session_info;		set_session_id(ta_handle, i, &arg->session);		set_bit(i, sess->sess_mask);	}	spin_unlock(&sess->lock); 	if (i >= TEE_NUM_SESSIONS) {		pr_err("reached maximum session count %d\n", TEE_NUM_SESSIONS);		handle_close_session(ta_handle, session_info);		handle_unload_ta(ta_handle);		kref_put_mutex(&sess->refcount, destroy_session,			       &session_list_mutex);		rc = -ENOMEM;		goto out;	} out:	free_pages((u64)ta, get_order(ta_size));	return rc;} int amdtee_close_session(struct tee_context *ctx, u32 session){	struct amdtee_context_data *ctxdata = ctx->data;	u32 i, ta_handle, session_info;	struct amdtee_session *sess; 	pr_debug("%s: sid = 0x%x\n", __func__, session); 	/*	 * Check that the session is valid and clear the session	 * usage bit	 */	mutex_lock(&session_list_mutex);	sess = find_session(ctxdata, session);	if (sess) {		ta_handle = get_ta_handle(session);		i = get_session_index(session);		session_info = sess->session_info[i];		spin_lock(&sess->lock);		clear_bit(i, sess->sess_mask);		spin_unlock(&sess->lock);	}	mutex_unlock(&session_list_mutex); 	if (!sess)		return -EINVAL; 	/* Close the session */	handle_close_session(ta_handle, session_info);	handle_unload_ta(ta_handle); 	kref_put_mutex(&sess->refcount, destroy_session, &session_list_mutex); 	return 0;} int amdtee_map_shmem(struct tee_shm *shm){	struct shmem_desc shmem;	int rc, count;	u32 buf_id; 	if (!shm)		return -EINVAL; 	count = 1;	shmem.kaddr = shm->kaddr;	shmem.size = shm->size; 	/*	 * Send a MAP command to TEE and get the corresponding	 * buffer Id	 */	rc = handle_map_shmem(count, &shmem, &buf_id);	if (rc) {		pr_err("map_shmem failed: ret = %d\n", rc);		return rc;	} 	shm->sec_world_id = buf_id; 	pr_debug("buf_id :[%x] kaddr[%p]\n", buf_id, shm->kaddr); 	return 0;} void amdtee_unmap_shmem(struct tee_shm *shm){	u32 buf_id; 	if (!shm)		return; 	buf_id = (u32)shm->sec_world_id;	handle_unmap_shmem(buf_id);	shm->sec_world_id = 0;} int amdtee_invoke_func(struct tee_context *ctx,		       struct tee_ioctl_invoke_arg *arg,		       struct tee_param *param){	struct amdtee_context_data *ctxdata = ctx->data;	struct amdtee_session *sess;	u32 i, session_info; 	/* Check that the session is valid */	mutex_lock(&session_list_mutex);	sess = find_session(ctxdata, arg->session);	if (sess) {		i = get_session_index(arg->session);		session_info = sess->session_info[i];	}	mutex_unlock(&session_list_mutex); 	if (!sess)		return -EINVAL; 	handle_invoke_cmd(arg, session_info, param); 	return 0;} int amdtee_cancel_req(struct tee_context *ctx, u32 cancel_id, u32 session){	return -EINVAL;} static const struct tee_driver_ops amdtee_ops = {	.get_version = amdtee_get_version,	.open = amdtee_open,	.release = amdtee_release,	.open_session = amdtee_open_session,	.close_session = amdtee_close_session,	.invoke_func = amdtee_invoke_func,	.cancel_req = amdtee_cancel_req,}; static const struct tee_desc amdtee_desc = {	.name = DRIVER_NAME "-clnt",	.ops = &amdtee_ops,	.owner = THIS_MODULE,}; static int __init amdtee_driver_init(void){	struct tee_device *teedev;	struct tee_shm_pool *pool;	struct amdtee *amdtee;	int rc; 	rc = psp_check_tee_status();	if (rc) {		pr_err("tee not present\n");		return rc;	} 	drv_data = kzalloc_obj(*drv_data);	if (!drv_data)		return -ENOMEM; 	amdtee = kzalloc_obj(*amdtee);	if (!amdtee) {		rc = -ENOMEM;		goto err_kfree_drv_data;	} 	pool = amdtee_config_shm();	if (IS_ERR(pool)) {		pr_err("shared pool configuration error\n");		rc = PTR_ERR(pool);		goto err_kfree_amdtee;	} 	teedev = tee_device_alloc(&amdtee_desc, NULL, pool, amdtee);	if (IS_ERR(teedev)) {		rc = PTR_ERR(teedev);		goto err_free_pool;	}	amdtee->teedev = teedev; 	rc = tee_device_register(amdtee->teedev);	if (rc)		goto err_device_unregister; 	amdtee->pool = pool; 	drv_data->amdtee = amdtee; 	return 0; err_device_unregister:	tee_device_unregister(amdtee->teedev); err_free_pool:	tee_shm_pool_free(pool); err_kfree_amdtee:	kfree(amdtee); err_kfree_drv_data:	kfree(drv_data);	drv_data = NULL; 	pr_err("initialization failed\n");	return rc;}module_init(amdtee_driver_init); static void __exit amdtee_driver_exit(void){	struct amdtee *amdtee; 	if (!drv_data || !drv_data->amdtee)		return; 	amdtee = drv_data->amdtee; 	tee_device_unregister(amdtee->teedev);	tee_shm_pool_free(amdtee->pool);}module_exit(amdtee_driver_exit); MODULE_AUTHOR(DRIVER_AUTHOR);MODULE_DESCRIPTION("AMD-TEE driver");MODULE_VERSION("1.0");MODULE_LICENSE("Dual MIT/GPL");