#include <linux/firmware.h>
#include "sfs.h"
#include "sev-dev.h"
#define SFS_DEFAULT_TIMEOUT (10 * MSEC_PER_SEC)
#define SFS_MAX_PAYLOAD_SIZE (2 * 1024 * 1024)
#define SFS_NUM_2MB_PAGES_CMDBUF (SFS_MAX_PAYLOAD_SIZE / PMD_SIZE)
#define SFS_NUM_PAGES_CMDBUF (SFS_MAX_PAYLOAD_SIZE / PAGE_SIZE)
static DEFINE_MUTEX(sfs_ioctl_mutex);
static struct sfs_misc_dev *misc_dev;
static int send_sfs_cmd(struct sfs_device *sfs_dev, int msg)
{
int ret;
sfs_dev->command_buf->hdr.status = 0;
sfs_dev->command_buf->hdr.sub_cmd_id = msg;
ret = psp_extended_mailbox_cmd(sfs_dev->psp,
SFS_DEFAULT_TIMEOUT,
(struct psp_ext_request *)sfs_dev->command_buf);
if (ret == -EIO) {
dev_dbg(sfs_dev->dev,
"msg 0x%x failed with PSP error: 0x%x, extended status: 0x%x\n",
msg, sfs_dev->command_buf->hdr.status,
*(u32 *)sfs_dev->command_buf->buf);
}
return ret;
}
static int send_sfs_get_fw_versions(struct sfs_device *sfs_dev)
{
memset(sfs_dev->command_buf->sfs_buffer, 0xc7, PAGE_SIZE);
sfs_dev->command_buf->hdr.payload_size = 2 * PAGE_SIZE;
return send_sfs_cmd(sfs_dev, PSP_SFS_GET_FW_VERSIONS);
}
static int send_sfs_update_package(struct sfs_device *sfs_dev, const char *payload_name)
{
char payload_path[PAYLOAD_NAME_SIZE + sizeof("amd/")];
const struct firmware *firmware;
unsigned long package_size;
int ret;
if (!strnchr(payload_name, PAYLOAD_NAME_SIZE, '\0'))
return -EINVAL;
snprintf(payload_path, sizeof(payload_path), "amd/%s", payload_name);
ret = firmware_request_nowarn(&firmware, payload_path, sfs_dev->dev);
if (ret < 0) {
dev_warn_ratelimited(sfs_dev->dev, "firmware request failed for %s (%d)\n",
payload_path, ret);
return -ENOENT;
}
package_size = ALIGN(firmware->size + PAGE_SIZE, 0x10000U);
if (package_size > SFS_MAX_PAYLOAD_SIZE) {
dev_warn_ratelimited(sfs_dev->dev,
"SFS payload size %ld larger than maximum supported payload size of %u\n",
package_size, SFS_MAX_PAYLOAD_SIZE);
release_firmware(firmware);
return -E2BIG;
}
memcpy(sfs_dev->command_buf->sfs_buffer, firmware->data, firmware->size);
sfs_dev->command_buf->hdr.payload_size = package_size;
release_firmware(firmware);
return send_sfs_cmd(sfs_dev, PSP_SFS_UPDATE);
}
static long sfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct sfs_user_get_fw_versions __user *sfs_get_fw_versions;
struct sfs_user_update_package __user *sfs_update_package;
struct psp_device *psp_master = psp_get_master_device();
char payload_name[PAYLOAD_NAME_SIZE];
struct sfs_device *sfs_dev;
int ret = 0;
if (!psp_master || !psp_master->sfs_data)
return -ENODEV;
sfs_dev = psp_master->sfs_data;
guard(mutex)(&sfs_ioctl_mutex);
switch (cmd) {
case SFSIOCFWVERS:
dev_dbg(sfs_dev->dev, "in SFSIOCFWVERS\n");
sfs_get_fw_versions = (struct sfs_user_get_fw_versions __user *)arg;
ret = send_sfs_get_fw_versions(sfs_dev);
if (ret && ret != -EIO)
return ret;
if (copy_to_user(&sfs_get_fw_versions->blob, sfs_dev->command_buf->sfs_buffer,
PAGE_SIZE))
return -EFAULT;
if (copy_to_user(&sfs_get_fw_versions->sfs_status,
&sfs_dev->command_buf->hdr.status,
sizeof(sfs_get_fw_versions->sfs_status)))
return -EFAULT;
if (copy_to_user(&sfs_get_fw_versions->sfs_extended_status,
&sfs_dev->command_buf->buf,
sizeof(sfs_get_fw_versions->sfs_extended_status)))
return -EFAULT;
break;
case SFSIOCUPDATEPKG:
dev_dbg(sfs_dev->dev, "in SFSIOCUPDATEPKG\n");
sfs_update_package = (struct sfs_user_update_package __user *)arg;
if (copy_from_user(payload_name, sfs_update_package->payload_name,
PAYLOAD_NAME_SIZE))
return -EFAULT;
ret = send_sfs_update_package(sfs_dev, payload_name);
if (ret && ret != -EIO)
return ret;
if (copy_to_user(&sfs_update_package->sfs_status,
&sfs_dev->command_buf->hdr.status,
sizeof(sfs_update_package->sfs_status)))
return -EFAULT;
if (copy_to_user(&sfs_update_package->sfs_extended_status,
&sfs_dev->command_buf->buf,
sizeof(sfs_update_package->sfs_extended_status)))
return -EFAULT;
break;
default:
ret = -EINVAL;
}
return ret;
}
static const struct file_operations sfs_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = sfs_ioctl,
};
static void sfs_exit(struct kref *ref)
{
misc_deregister(&misc_dev->misc);
kfree(misc_dev);
misc_dev = NULL;
}
void sfs_dev_destroy(struct psp_device *psp)
{
struct sfs_device *sfs_dev = psp->sfs_data;
if (!sfs_dev)
return;
set_memory_wb((unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF);
snp_free_hv_fixed_pages(sfs_dev->page);
if (sfs_dev->misc)
kref_put(&misc_dev->refcount, sfs_exit);
psp->sfs_data = NULL;
}
static int sfs_misc_init(struct sfs_device *sfs)
{
struct device *dev = sfs->dev;
int ret;
if (!misc_dev) {
struct miscdevice *misc;
misc_dev = kzalloc(sizeof(*misc_dev), GFP_KERNEL);
if (!misc_dev)
return -ENOMEM;
misc = &misc_dev->misc;
misc->minor = MISC_DYNAMIC_MINOR;
misc->name = "sfs";
misc->fops = &sfs_fops;
misc->mode = 0600;
ret = misc_register(misc);
if (ret)
return ret;
kref_init(&misc_dev->refcount);
} else {
kref_get(&misc_dev->refcount);
}
sfs->misc = misc_dev;
dev_dbg(dev, "registered SFS device\n");
return 0;
}
int sfs_dev_init(struct psp_device *psp)
{
struct device *dev = psp->dev;
struct sfs_device *sfs_dev;
struct page *page;
int ret = -ENOMEM;
sfs_dev = devm_kzalloc(dev, sizeof(*sfs_dev), GFP_KERNEL);
if (!sfs_dev)
return -ENOMEM;
page = snp_alloc_hv_fixed_pages(SFS_NUM_2MB_PAGES_CMDBUF);
if (!page) {
dev_dbg(dev, "Command Buffer HV-Fixed page allocation failed\n");
goto cleanup_dev;
}
sfs_dev->page = page;
sfs_dev->command_buf = page_address(page);
dev_dbg(dev, "Command buffer 0x%px to be marked as HV_Fixed\n", sfs_dev->command_buf);
ret = set_memory_uc((unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF);
if (ret) {
dev_dbg(dev, "Set memory uc failed\n");
goto cleanup_cmd_buf;
}
dev_dbg(dev, "Command buffer 0x%px marked uncacheable\n", sfs_dev->command_buf);
psp->sfs_data = sfs_dev;
sfs_dev->dev = dev;
sfs_dev->psp = psp;
ret = sfs_misc_init(sfs_dev);
if (ret)
goto cleanup_mem_attr;
dev_notice(sfs_dev->dev, "SFS support is available\n");
return 0;
cleanup_mem_attr:
set_memory_wb((unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF);
cleanup_cmd_buf:
snp_free_hv_fixed_pages(page);
cleanup_dev:
psp->sfs_data = NULL;
devm_kfree(dev, sfs_dev);
return ret;
}