#define pr_fmt(fmt) "papr-phy-attest: " fmt
#include <linux/build_bug.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/lockdep.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/signal.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/uaccess.h>
#include <asm/machdep.h>
#include <asm/rtas-work-area.h>
#include <asm/rtas.h>
#include <uapi/asm/papr-physical-attestation.h>
#include "papr-rtas-common.h"
struct rtas_phy_attest_params {
struct papr_phy_attest_io_block cmd;
struct rtas_work_area *work_area;
u32 cmd_len;
u32 sequence;
u32 written;
s32 status;
};
static int rtas_physical_attestation(struct rtas_phy_attest_params *params)
{
struct rtas_work_area *work_area;
s32 fwrc, token;
u32 rets[2];
int ret;
work_area = params->work_area;
token = rtas_function_token(RTAS_FN_IBM_PHYSICAL_ATTESTATION);
if (token == RTAS_UNKNOWN_SERVICE)
return -ENOENT;
lockdep_assert_held(&rtas_ibm_physical_attestation_lock);
do {
fwrc = rtas_call(token, 3, 3, rets,
rtas_work_area_phys(work_area),
params->cmd_len,
params->sequence);
} while (rtas_busy_delay(fwrc));
switch (fwrc) {
case RTAS_HARDWARE_ERROR:
ret = -EIO;
break;
case RTAS_INVALID_PARAMETER:
ret = -EINVAL;
break;
case RTAS_SEQ_MORE_DATA:
params->sequence = rets[0];
fallthrough;
case RTAS_SEQ_COMPLETE:
params->written = rets[1];
if (WARN(params->written > rtas_work_area_size(work_area),
"possible write beyond end of work area"))
ret = -EFAULT;
else
ret = 0;
break;
default:
ret = -EIO;
pr_err_ratelimited("unexpected ibm,get-phy_attest status %d\n", fwrc);
break;
}
params->status = fwrc;
return ret;
}
static void phy_attest_sequence_begin(struct papr_rtas_sequence *seq)
{
struct rtas_phy_attest_params *param;
mutex_lock(&rtas_ibm_physical_attestation_lock);
param = (struct rtas_phy_attest_params *)seq->params;
param->work_area = rtas_work_area_alloc(SZ_4K);
memcpy(rtas_work_area_raw_buf(param->work_area), ¶m->cmd,
param->cmd_len);
param->sequence = 1;
param->status = 0;
}
static void phy_attest_sequence_end(struct papr_rtas_sequence *seq)
{
struct rtas_phy_attest_params *param;
param = (struct rtas_phy_attest_params *)seq->params;
rtas_work_area_free(param->work_area);
mutex_unlock(&rtas_ibm_physical_attestation_lock);
kfree(param);
}
static const char *phy_attest_sequence_fill_work_area(struct papr_rtas_sequence *seq,
size_t *len)
{
struct rtas_phy_attest_params *p;
bool init_state;
p = (struct rtas_phy_attest_params *)seq->params;
init_state = (p->written == 0) ? true : false;
if (papr_rtas_sequence_should_stop(seq, p->status, init_state))
return NULL;
if (papr_rtas_sequence_set_err(seq, rtas_physical_attestation(p)))
return NULL;
*len = p->written;
return rtas_work_area_raw_buf(p->work_area);
}
static const struct file_operations papr_phy_attest_handle_ops = {
.read = papr_rtas_common_handle_read,
.llseek = papr_rtas_common_handle_seek,
.release = papr_rtas_common_handle_release,
};
static long papr_phy_attest_create_handle(struct papr_phy_attest_io_block __user *ulc)
{
struct rtas_phy_attest_params *params;
struct papr_rtas_sequence seq = {};
int fd;
params = kzalloc_obj(*params, GFP_KERNEL_ACCOUNT);
if (!params)
return -ENOMEM;
if (copy_from_user(¶ms->cmd, ulc,
sizeof(struct papr_phy_attest_io_block)))
return -EFAULT;
params->cmd_len = be32_to_cpu(params->cmd.length);
seq = (struct papr_rtas_sequence) {
.begin = phy_attest_sequence_begin,
.end = phy_attest_sequence_end,
.work = phy_attest_sequence_fill_work_area,
};
seq.params = (void *)params;
fd = papr_rtas_setup_file_interface(&seq,
&papr_phy_attest_handle_ops,
"[papr-physical-attestation]");
return fd;
}
static long papr_phy_attest_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
void __user *argp = (__force void __user *)arg;
long ret;
switch (ioctl) {
case PAPR_PHY_ATTEST_IOC_HANDLE:
ret = papr_phy_attest_create_handle(argp);
break;
default:
ret = -ENOIOCTLCMD;
break;
}
return ret;
}
static const struct file_operations papr_phy_attest_ops = {
.unlocked_ioctl = papr_phy_attest_dev_ioctl,
};
static struct miscdevice papr_phy_attest_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "papr-physical-attestation",
.fops = &papr_phy_attest_ops,
};
static __init int papr_phy_attest_init(void)
{
if (!rtas_function_implemented(RTAS_FN_IBM_PHYSICAL_ATTESTATION))
return -ENODEV;
return misc_register(&papr_phy_attest_dev);
}
machine_device_initcall(pseries, papr_phy_attest_init);