Commit 36037a92 authored by xiongmengbiao's avatar xiongmengbiao
Browse files

drivers/crypto/ccp: support tkm key isolation

hygon inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I9C3AM


CVE: NA

---------------------------

Add `vpsp_add_vid` and `vpsp_del_vid` to receive
VID information in host user mode.
Generally, these ioctl calls should be initiated from the QEMU process.

When sending data to the PSP hardware,
place the VID in the bit 56 to bit 63 range of the physical address.
The PSP hardware will then access different key spaces based on the VID.

Signed-off-by: default avatarxiongmengbiao <xiongmengbiao@hygon.cn>
parent 518c81d5
Loading
Loading
Loading
Loading
+23 −2
Original line number Diff line number Diff line
@@ -50,6 +50,9 @@
 *						| -> guest_multiple_level_gpa_restore
 */

#define TKM_CMD_ID_MIN  0x120
#define TKM_CMD_ID_MAX  0x12f

struct psp_cmdresp_head {
	uint32_t buf_size;
	uint32_t cmdresp_size;
@@ -510,6 +513,13 @@ static int kvm_pv_psp_cmd_post_op(struct kvm *kvm, gpa_t data_gpa,
	return ret;
}

static int cmd_type_is_tkm(int cmd)
{
	if (cmd >= TKM_CMD_ID_MIN && cmd <= TKM_CMD_ID_MAX)
		return 1;
	return 0;
}

/*
 * The primary implementation interface of virtual PSP in kernel mode
 */
@@ -522,6 +532,17 @@ int kvm_pv_psp_op(struct kvm *kvm, int cmd, gpa_t data_gpa, gpa_t psp_ret_gpa,
	struct vpsp_cmd *vcmd = (struct vpsp_cmd *)&cmd;
	uint8_t prio = CSV_COMMAND_PRIORITY_LOW;
	uint32_t index = 0;
	uint32_t vid = 0;

	// only tkm cmd need vid
	if (cmd_type_is_tkm(vcmd->cmd_id)) {
		// if vm without set vid, then tkm command is not allowed
		ret = vpsp_get_vid(&vid, kvm->userspace_pid);
		if (ret) {
			pr_err("[%s]: not allowed tkm command without vid\n", __func__);
			return -EFAULT;
		}
	}

	if (unlikely(kvm_read_guest(kvm, psp_ret_gpa, &psp_ret,
					sizeof(psp_ret))))
@@ -540,7 +561,7 @@ int kvm_pv_psp_op(struct kvm *kvm, int cmd, gpa_t data_gpa, gpa_t psp_ret_gpa,
		}

		/* try to send command to the device for execution*/
		ret = vpsp_try_do_cmd(cmd, (void *)hbuf.data,
		ret = vpsp_try_do_cmd(vid, cmd, (void *)hbuf.data,
				(struct vpsp_ret *)&psp_ret);
		if (unlikely(ret)) {
			pr_err("[%s]: vpsp_do_cmd failed\n", __func__);
@@ -578,7 +599,7 @@ int kvm_pv_psp_op(struct kvm *kvm, int cmd, gpa_t data_gpa, gpa_t psp_ret_gpa,
			CSV_COMMAND_PRIORITY_LOW;
		index = psp_ret.index;
		/* try to get the execution result from ringbuffer*/
		ret = vpsp_try_get_result(prio, index, g_hbuf_wrap[prio][index].data,
		ret = vpsp_try_get_result(vid, prio, index, g_hbuf_wrap[prio][index].data,
				(struct vpsp_ret *)&psp_ret);
		if (unlikely(ret)) {
			pr_err("[%s]: vpsp_try_get_result failed\n", __func__);
+14 −12
Original line number Diff line number Diff line
@@ -645,12 +645,12 @@ static int vpsp_dequeue_cmd(int prio, int index,
 * Populate the command from the virtual machine to the queue to
 * support execution in ringbuffer mode
 */
static int vpsp_fill_cmd_queue(int prio, int cmd, void *data, uint16_t flags)
static int vpsp_fill_cmd_queue(uint32_t vid, int prio, int cmd, void *data, uint16_t flags)
{
	struct csv_cmdptr_entry cmdptr = { };
	int index = -1;

	cmdptr.cmd_buf_ptr = __psp_pa(data);
	cmdptr.cmd_buf_ptr = PUT_PSP_VID(__psp_pa(data), vid);
	cmdptr.cmd_id = cmd;
	cmdptr.cmd_flags = flags;

@@ -938,11 +938,12 @@ static int vpsp_rb_check_and_cmd_prio_parse(uint8_t *prio,
	return rb_supported;
}

int __vpsp_do_cmd_locked(uint32_t vid, int cmd, void *data, int *psp_ret);
/*
 * Try to obtain the result again by the command index, this
 * interface is used in ringbuffer mode
 */
int vpsp_try_get_result(uint8_t prio, uint32_t index, void *data,
int vpsp_try_get_result(uint32_t vid, uint8_t prio, uint32_t index, void *data,
		struct vpsp_ret *psp_ret)
{
	int ret = 0;
@@ -962,7 +963,7 @@ int vpsp_try_get_result(uint8_t prio, uint32_t index, void *data,
			/* dequeue command from queue*/
			vpsp_dequeue_cmd(prio, index, &cmd);

			ret = hygon_psp_hooks.__sev_do_cmd_locked(cmd.cmd_id, data,
			ret = __vpsp_do_cmd_locked(vid, cmd.cmd_id, data,
					(int *)psp_ret);
			psp_ret->status = VPSP_FINISH;
			vpsp_psp_mutex_unlock();
@@ -982,7 +983,8 @@ int vpsp_try_get_result(uint8_t prio, uint32_t index, void *data,
			psp_ret->status = VPSP_FINISH;
			vpsp_psp_mutex_unlock();
			if (unlikely(ret)) {
				pr_err("[%s]: vpsp_do_ringbuf_cmds_locked failed\n", __func__);
				pr_err("[%s]: vpsp_do_ringbuf_cmds_locked failed %d\n",
						__func__, ret);
				goto end;
			}
		}
@@ -1005,7 +1007,7 @@ EXPORT_SYMBOL_GPL(vpsp_try_get_result);
 * vpsp_try_get_result interface will be used to obtain the result
 * later again
 */
int vpsp_try_do_cmd(int cmd, void *data, struct vpsp_ret *psp_ret)
int vpsp_try_do_cmd(uint32_t vid, int cmd, void *data, struct vpsp_ret *psp_ret)
{
	int ret = 0;
	int rb_supported;
@@ -1017,10 +1019,10 @@ int vpsp_try_do_cmd(int cmd, void *data, struct vpsp_ret *psp_ret)
			(struct vpsp_cmd *)&cmd);
	if (rb_supported) {
		/* fill command in ringbuffer's queue and get index */
		index = vpsp_fill_cmd_queue(prio, cmd, data, 0);
		index = vpsp_fill_cmd_queue(vid, prio, cmd, data, 0);
		if (unlikely(index < 0)) {
			/* do mailbox command if queuing failed*/
			ret = psp_do_cmd(cmd, data, (int *)psp_ret);
			ret = vpsp_do_cmd(vid, cmd, data, (int *)psp_ret);
			if (unlikely(ret)) {
				if (ret == -EIO) {
					ret = 0;
@@ -1036,14 +1038,14 @@ int vpsp_try_do_cmd(int cmd, void *data, struct vpsp_ret *psp_ret)
		}

		/* try to get result from the ringbuffer command */
		ret = vpsp_try_get_result(prio, index, data, psp_ret);
		ret = vpsp_try_get_result(vid, prio, index, data, psp_ret);
		if (unlikely(ret)) {
			pr_err("[%s]: vpsp_try_get_result failed\n", __func__);
			pr_err("[%s]: vpsp_try_get_result failed %d\n", __func__, ret);
			goto end;
		}
	} else {
		/* mailbox mode */
		ret = psp_do_cmd(cmd, data, (int *)psp_ret);
		ret = vpsp_do_cmd(vid, cmd, data, (int *)psp_ret);
		if (unlikely(ret)) {
			if (ret == -EIO) {
				ret = 0;
+249 −1
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@
#include <linux/psp-hygon.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/sort.h>
#include <linux/bsearch.h>
#include <linux/rwlock.h>

#include "psp-dev.h"

@@ -26,9 +29,23 @@ static struct psp_misc_dev *psp_misc;
enum HYGON_PSP_OPCODE {
	HYGON_PSP_MUTEX_ENABLE = 1,
	HYGON_PSP_MUTEX_DISABLE,
	HYGON_VPSP_CTRL_OPT,
	HYGON_PSP_OPCODE_MAX_NR,
};

enum VPSP_DEV_CTRL_OPCODE {
	VPSP_OP_VID_ADD,
	VPSP_OP_VID_DEL,
};

struct vpsp_dev_ctrl {
	unsigned char op;
	union {
		unsigned int vid;
		unsigned char reserved[128];
	} data;
};

uint64_t atomic64_exchange(uint64_t *dst, uint64_t val)
{
	return xchg(dst, val);
@@ -130,10 +147,141 @@ static ssize_t write_psp(struct file *file, const char __user *buf, size_t count

	return written;
}
DEFINE_RWLOCK(vpsp_rwlock);

/* VPSP_VID_MAX_ENTRIES determines the maximum number of vms that can set vid.
 * but, the performance of finding vid is determined by g_vpsp_vid_num,
 * so VPSP_VID_MAX_ENTRIES can be set larger.
 */
#define VPSP_VID_MAX_ENTRIES    2048
#define VPSP_VID_NUM_MAX        64

struct vpsp_vid_entry {
	uint32_t vid;
	pid_t pid;
};
static struct vpsp_vid_entry g_vpsp_vid_array[VPSP_VID_MAX_ENTRIES];
static uint32_t g_vpsp_vid_num;
static int compare_vid_entries(const void *a, const void *b)
{
	return ((struct vpsp_vid_entry *)a)->pid - ((struct vpsp_vid_entry *)b)->pid;
}
static void swap_vid_entries(void *a, void *b, int size)
{
	struct vpsp_vid_entry entry;

	memcpy(&entry, a, size);
	memcpy(a, b, size);
	memcpy(b, &entry, size);
}

/**
 * When the virtual machine executes the 'tkm' command,
 * it needs to retrieve the corresponding 'vid'
 * by performing a binary search using 'kvm->userspace_pid'.
 */
int vpsp_get_vid(uint32_t *vid, pid_t pid)
{
	struct vpsp_vid_entry new_entry = {.pid = pid};
	struct vpsp_vid_entry *existing_entry = NULL;

	read_lock(&vpsp_rwlock);
	existing_entry = bsearch(&new_entry, g_vpsp_vid_array, g_vpsp_vid_num,
				sizeof(struct vpsp_vid_entry), compare_vid_entries);
	read_unlock(&vpsp_rwlock);

	if (!existing_entry)
		return -ENOENT;
	if (vid) {
		*vid = existing_entry->vid;
		pr_debug("PSP: %s %d, by pid %d\n", __func__, *vid, pid);
	}
	return 0;
}
EXPORT_SYMBOL_GPL(vpsp_get_vid);

/**
 * Upon qemu startup, this section checks whether
 * the '-device psp,vid' parameter is specified.
 * If set, it utilizes the 'vpsp_add_vid' function
 * to insert the 'vid' and 'pid' values into the 'g_vpsp_vid_array'.
 * The insertion is done in ascending order of 'pid'.
 */
static int vpsp_add_vid(uint32_t vid)
{
	pid_t cur_pid = task_pid_nr(current);
	struct vpsp_vid_entry new_entry = {.vid = vid, .pid = cur_pid};

	if (vpsp_get_vid(NULL, cur_pid) == 0)
		return -EEXIST;
	if (g_vpsp_vid_num == VPSP_VID_MAX_ENTRIES)
		return -ENOMEM;
	if (vid >= VPSP_VID_NUM_MAX)
		return -EINVAL;

	write_lock(&vpsp_rwlock);
	memcpy(&g_vpsp_vid_array[g_vpsp_vid_num++], &new_entry, sizeof(struct vpsp_vid_entry));
	sort(g_vpsp_vid_array, g_vpsp_vid_num, sizeof(struct vpsp_vid_entry),
				compare_vid_entries, swap_vid_entries);
	pr_info("PSP: add vid %d, by pid %d, total vid num is %d\n", vid, cur_pid, g_vpsp_vid_num);
	write_unlock(&vpsp_rwlock);
	return 0;
}

/**
 * Upon the virtual machine is shut down,
 * the 'vpsp_del_vid' function is employed to remove
 * the 'vid' associated with the current 'pid'.
 */
static int vpsp_del_vid(void)
{
	pid_t cur_pid = task_pid_nr(current);
	int i, ret = -ENOENT;

	write_lock(&vpsp_rwlock);
	for (i = 0; i < g_vpsp_vid_num; ++i) {
		if (g_vpsp_vid_array[i].pid == cur_pid) {
			--g_vpsp_vid_num;
			pr_info("PSP: delete vid %d, by pid %d, total vid num is %d\n",
				g_vpsp_vid_array[i].vid, cur_pid, g_vpsp_vid_num);
			memcpy(&g_vpsp_vid_array[i], &g_vpsp_vid_array[i + 1],
				sizeof(struct vpsp_vid_entry) * (g_vpsp_vid_num - i));
			ret = 0;
			goto end;
		}
	}

end:
	write_unlock(&vpsp_rwlock);
	return ret;
}

static int do_vpsp_op_ioctl(struct vpsp_dev_ctrl *ctrl)
{
	int ret = 0;
	unsigned char op = ctrl->op;

	switch (op) {
	case VPSP_OP_VID_ADD:
		ret = vpsp_add_vid(ctrl->data.vid);
		break;

	case VPSP_OP_VID_DEL:
		ret = vpsp_del_vid();
		break;

	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

static long ioctl_psp(struct file *file, unsigned int ioctl, unsigned long arg)
{
	unsigned int opcode = 0;
	struct vpsp_dev_ctrl vpsp_ctrl_op;
	int ret = -EFAULT;

	if (_IOC_TYPE(ioctl) != HYGON_PSP_IOC_TYPE) {
		pr_info("%s: invalid ioctl type: 0x%x\n", __func__, _IOC_TYPE(ioctl));
@@ -150,6 +298,7 @@ static long ioctl_psp(struct file *file, unsigned int ioctl, unsigned long arg)
		// Wait 10ms just in case someone is right before getting the psp lock.
		mdelay(10);
		psp_mutex_unlock(&psp_misc->data_pg_aligned->mb_mutex);
		ret = 0;
		break;

	case HYGON_PSP_MUTEX_DISABLE:
@@ -161,13 +310,21 @@ static long ioctl_psp(struct file *file, unsigned int ioctl, unsigned long arg)
		// Wait 10ms just in case someone is right before getting the sev lock.
		mdelay(10);
		mutex_unlock(hygon_psp_hooks.sev_cmd_mutex);
		ret = 0;
		break;

	case HYGON_VPSP_CTRL_OPT:
		if (copy_from_user(&vpsp_ctrl_op, (void __user *)arg,
			sizeof(struct vpsp_dev_ctrl)))
			return -EFAULT;
		ret = do_vpsp_op_ioctl(&vpsp_ctrl_op);
		break;

	default:
		pr_info("%s: invalid ioctl number: %d\n", __func__, opcode);
		return -EINVAL;
	}
	return 0;
	return ret;
}

static const struct file_operations psp_fops = {
@@ -303,6 +460,97 @@ static int __psp_do_cmd_locked(int cmd, void *data, int *psp_ret)
	return ret;
}

int __vpsp_do_cmd_locked(uint32_t vid, int cmd, void *data, int *psp_ret)
{
	struct psp_device *psp = psp_master;
	struct sev_device *sev;
	phys_addr_t phys_addr;
	unsigned int phys_lsb, phys_msb;
	unsigned int reg, ret = 0;

	if (!psp || !psp->sev_data)
		return -ENODEV;

	if (*hygon_psp_hooks.psp_dead)
		return -EBUSY;

	sev = psp->sev_data;

	if (data && WARN_ON_ONCE(!virt_addr_valid(data)))
		return -EINVAL;

	/* Get the physical address of the command buffer */
	phys_addr = PUT_PSP_VID(__psp_pa(data), vid);
	phys_lsb = data ? lower_32_bits(phys_addr) : 0;
	phys_msb = data ? upper_32_bits(phys_addr) : 0;

	dev_dbg(sev->dev, "sev command id %#x buffer 0x%08x%08x timeout %us\n",
		cmd, phys_msb, phys_lsb, *hygon_psp_hooks.psp_timeout);

	print_hex_dump_debug("(in):  ", DUMP_PREFIX_OFFSET, 16, 2, data,
			     hygon_psp_hooks.sev_cmd_buffer_len(cmd), false);

	iowrite32(phys_lsb, sev->io_regs + sev->vdata->cmdbuff_addr_lo_reg);
	iowrite32(phys_msb, sev->io_regs + sev->vdata->cmdbuff_addr_hi_reg);

	sev->int_rcvd = 0;

	reg = FIELD_PREP(SEV_CMDRESP_CMD, cmd) | SEV_CMDRESP_IOC;
	iowrite32(reg, sev->io_regs + sev->vdata->cmdresp_reg);

	/* wait for command completion */
	ret = hygon_psp_hooks.sev_wait_cmd_ioc(sev, &reg, *hygon_psp_hooks.psp_timeout);
	if (ret) {
		if (psp_ret)
			*psp_ret = 0;

		dev_err(sev->dev, "sev command %#x timed out, disabling PSP\n", cmd);
		*hygon_psp_hooks.psp_dead = true;

		return ret;
	}

	*hygon_psp_hooks.psp_timeout = *hygon_psp_hooks.psp_cmd_timeout;

	if (psp_ret)
		*psp_ret = FIELD_GET(PSP_CMDRESP_STS, reg);

	if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
		dev_dbg(sev->dev, "sev command %#x failed (%#010lx)\n",
			cmd, FIELD_GET(PSP_CMDRESP_STS, reg));
		ret = -EIO;
	}

	print_hex_dump_debug("(out): ", DUMP_PREFIX_OFFSET, 16, 2, data,
			     hygon_psp_hooks.sev_cmd_buffer_len(cmd), false);

	return ret;
}

int vpsp_do_cmd(uint32_t vid, int cmd, void *data, int *psp_ret)
{
	int rc;
	int mutex_enabled = READ_ONCE(hygon_psp_hooks.psp_mutex_enabled);

	if (is_vendor_hygon() && mutex_enabled) {
		if (psp_mutex_lock_timeout(&psp_misc->data_pg_aligned->mb_mutex,
					PSP_MUTEX_TIMEOUT) != 1) {
			return -EBUSY;
		}
	} else {
		mutex_lock(hygon_psp_hooks.sev_cmd_mutex);
	}

	rc = __vpsp_do_cmd_locked(vid, cmd, data, psp_ret);

	if (is_vendor_hygon() && mutex_enabled)
		psp_mutex_unlock(&psp_misc->data_pg_aligned->mb_mutex);
	else
		mutex_unlock(hygon_psp_hooks.sev_cmd_mutex);

	return rc;
}

int psp_do_cmd(int cmd, void *data, int *psp_ret)
{
	int rc;
+21 −5
Original line number Diff line number Diff line
@@ -167,8 +167,16 @@ struct vpsp_ret {
	u32 status	:	2;
};

#define PSP_VID_MASK            0xff
#define PSP_VID_SHIFT           56
#define PUT_PSP_VID(hpa, vid)   ((__u64)(hpa) | ((__u64)(PSP_VID_MASK & vid) << PSP_VID_SHIFT))
#define GET_PSP_VID(hpa)        ((__u16)((__u64)(hpa) >> PSP_VID_SHIFT) & PSP_VID_MASK)
#define CLEAR_PSP_VID(hpa)      ((__u64)(hpa) & ~((__u64)PSP_VID_MASK << PSP_VID_SHIFT))

#ifdef CONFIG_CRYPTO_DEV_SP_PSP

int vpsp_do_cmd(uint32_t vid, int cmd, void *data, int *psp_ret);

int psp_do_cmd(int cmd, void *data, int *psp_ret);

int csv_ring_buffer_queue_init(void);
@@ -182,12 +190,17 @@ int csv_check_stat_queue_status(int *psp_ret);
 */
int csv_issue_ringbuf_cmds_external_user(struct file *filep, int *psp_ret);

int vpsp_try_get_result(uint8_t prio, uint32_t index, void *data, struct vpsp_ret *psp_ret);
int vpsp_try_get_result(uint32_t vid, uint8_t prio, uint32_t index,
			void *data, struct vpsp_ret *psp_ret);

int vpsp_try_do_cmd(uint32_t vid, int cmd, void *data, struct vpsp_ret *psp_ret);

int vpsp_try_do_cmd(int cmd, void *data, struct vpsp_ret *psp_ret);
int vpsp_get_vid(uint32_t *vid, pid_t pid);

#else	/* !CONFIG_CRYPTO_DEV_SP_PSP */

static inline int vpsp_do_cmd(uint32_t vid, int cmd, void *data, int *psp_ret) { return -ENODEV; }

static inline int psp_do_cmd(int cmd, void *data, int *psp_ret) { return -ENODEV; }

static inline int csv_ring_buffer_queue_init(void) { return -ENODEV; }
@@ -199,12 +212,15 @@ static inline int
csv_issue_ringbuf_cmds_external_user(struct file *filep, int *psp_ret) { return -ENODEV; }

static inline int
vpsp_try_get_result(uint8_t prio, uint32_t index, void *data,
		    struct vpsp_ret *psp_ret) { return -ENODEV; }
vpsp_try_get_result(uint32_t vid, uint8_t prio,
		uint32_t index, void *data, struct vpsp_ret *psp_ret) { return -ENODEV; }

static inline int
vpsp_try_do_cmd(int cmd, void *data, struct vpsp_ret *psp_ret) { return -ENODEV; }
vpsp_try_do_cmd(uint32_t vid, int cmd,
		void *data, struct vpsp_ret *psp_ret) { return -ENODEV; }

static inline int
vpsp_get_vid(uint32_t *vid, pid_t pid) { return -ENODEV; }
#endif	/* CONFIG_CRYPTO_DEV_SP_PSP */

typedef int (*p2c_notifier_t)(uint32_t id, uint64_t data);