blob: 1774bc31001dd0194fdfd378572bf4c1861b7c3f [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "rtosfmw: " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/arm-smccc.h>
#include <linux/psci.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/firmware.h>
#include <uapi/linux/psci.h>
#include <linux/debugfs.h>
#include <linux/sched/signal.h>
#include <linux/highmem.h>
#include <linux/miscdevice.h>
#include <linux/amlogic/scpi_protocol.h>
#include <linux/amlogic/rtosfmw.h>
#define AML_RTOSFMW_NAME "rtosfmw"
#define SIP_FREERTOS_FID 0x8200004B
#define RTOSFMW_START 3
#define RTOSFMW_LOAD 4
#define RTOSFMW_FINISH 5
#define SMC_UNK 0xffffffff
struct rtosfmw_attr {
enum rtosfmw_type type;
char *fmwname;
int status;
struct class_attribute attr;
};
struct rtosfmw_data_st {
int disabled;
int inited;
struct platform_device *pdev;
struct class cls;
struct rtosfmw_attr attr[RFT_MAX];
};
static struct rtosfmw_data_st rtosfmw_data;
static DEFINE_MUTEX(rtosfmw_lock);
#define FIRMWARE_DIR "rtos"
#define CONFIG_PATH_LENG 128
static const char *rtosfmw_type_name[RFT_MAX][2] = {
[RFT_SECPU] = {"TYPE_SECPU", "secpu"}
};
static struct attribute *rtosfmw_class_attrs[RFT_MAX + 1];
ATTRIBUTE_GROUPS(rtosfmw_class);
static int _rtosfmw_start(enum rtosfmw_type type, int size,
unsigned long *sbuf, unsigned long *ssize)
{
struct arm_smccc_res res;
if (type >= RFT_MAX || size <= 0)
return -1;
arm_smccc_smc(SIP_FREERTOS_FID, RTOSFMW_START,
(unsigned long)type, (unsigned long)size,
0, 0, 0, 0, &res);
if (res.a0 == 0) {
*sbuf = res.a1;
*ssize = res.a2;
}
return (int)res.a0;
}
static int _rtosfmw_load(int size)
{
struct arm_smccc_res res;
if (size <= 0)
return -1;
arm_smccc_smc(SIP_FREERTOS_FID, RTOSFMW_LOAD,
size, 0, 0, 0, 0, 0, &res);
return (int)res.a0;
}
static int _rtosfmw_finish(void)
{
struct arm_smccc_res res;
arm_smccc_smc(SIP_FREERTOS_FID, RTOSFMW_FINISH,
0, 0, 0, 0, 0, 0, &res);
return (int)res.a0;
}
static inline int _rtosfmw_chk_mem(unsigned long phy, unsigned long size)
{
unsigned long pfn_ed, pfn;
int valid = 0, high = 0;
if (phy == 0 && size == 0)
return -1;
if (!PAGE_ALIGNED(phy))
return -1;
pfn = __phys_to_pfn(phy);
pfn_ed = __phys_to_pfn(phy + size - 1);
valid = pfn_valid(pfn);
high = valid ? PageHighMem(pfn_to_page(pfn)) : 0;
for (pfn++; pfn <= pfn_ed; pfn++) {
if (valid != pfn_valid(pfn))
return -1;
if (!valid)
continue;
if (PageHighMem(pfn_to_page(pfn)))
high = 1;
}
return valid + high;
}
static void _rtosfmw_memcpy_phy(unsigned long phy, const char *data, int size)
{
unsigned long pfn;
char *pbuf;
int len = 0, rdlen;
pfn = __phys_to_pfn(phy);
while (size > 0) {
if (size >= PAGE_SIZE)
rdlen = PAGE_SIZE;
else
rdlen = size;
pbuf = kmap(pfn_to_page(pfn));
memcpy(pbuf, data + len, rdlen);
kunmap(pfn_to_page(pfn));
pfn++;
len += rdlen;
size -= rdlen;
}
}
static int _rtosfmw_run_firmware(enum rtosfmw_type type,
const char *data, int size)
{
unsigned long phy = 0, ssize = 0;
int memtype = 0;
char *pbuf = NULL;
int ret = -1, len = 0, rdlen;
struct rtosfmw_attr *fmwattr;
if (type >= RFT_MAX || !data || size <= 0)
return -1;
fmwattr = &rtosfmw_data.attr[type];
mutex_lock(&rtosfmw_lock);
if (fmwattr->status) {
pr_err("can't run firmware again\n");
goto exit;
}
if (_rtosfmw_start(type, size, &phy, &ssize) != 0) {
pr_err("firmware start fail\n");
goto update_status;
}
pr_info("firmware buf: [%lx,%lx]\n", phy, ssize);
memtype = _rtosfmw_chk_mem(phy, ssize);
if (memtype < 0) {
pr_err("check memory fail\n");
goto finish;
}
if (memtype == 1) {
pbuf = phys_to_virt(phy);
} else if (memtype == 0) {
pbuf = ioremap(phy, ssize);
if (!pbuf) {
pr_err("map firmware buf fail\n");
goto finish;
}
}
while (size > 0) {
if (size >= ssize)
rdlen = ssize;
else
rdlen = size;
if (memtype < 2)
memcpy(pbuf, data + len, rdlen);
else
_rtosfmw_memcpy_phy(phy, data + len, rdlen);
_rtosfmw_load(rdlen);
len += rdlen;
size -= rdlen;
}
if (memtype == 0)
iounmap(pbuf);
ret = 0;
finish:
ret |= _rtosfmw_finish();
update_status:
if (!ret)
fmwattr->status = 1;
else
fmwattr->status = -1;
exit:
mutex_unlock(&rtosfmw_lock);
return ret;
}
int aml_rtosfmw_run(enum rtosfmw_type type, const char *name)
{
const struct firmware *firmware;
int ret = -1;
char *path = NULL;
if (rtosfmw_data.inited == 0 || rtosfmw_data.disabled) {
pr_err("not inited or disabled\n");
return -1;
}
if (type >= RFT_MAX || !name) {
pr_err("invalid arguments\n");
return -1;
}
path = kzalloc(CONFIG_PATH_LENG, GFP_KERNEL);
if (!path) {
pr_err("%s: kzalloc failed!\n", __func__);
return -1;
}
snprintf(path, (CONFIG_PATH_LENG - 1), "%s/%s", FIRMWARE_DIR, name);
ret = request_firmware(&firmware, path, &rtosfmw_data.pdev->dev);
if (ret < 0) {
pr_err("request '/lib/firmware/%s' failed %d\n", path, ret);
kfree(path);
return ret;
}
pr_info("'/lib/firmware/%s' size %lu\n", path,
(unsigned long)firmware->size);
ret = _rtosfmw_run_firmware(type, firmware->data, firmware->size);
if (ret < 0)
goto exit;
ret = 0;
exit:
release_firmware(firmware);
kfree(path);
return ret;
}
EXPORT_SYMBOL(aml_rtosfmw_run);
static enum rtosfmw_type _rtosfmw_type(const char *tname)
{
int i;
if (!tname)
return RFT_MAX;
for (i = 0; i < RFT_MAX; i++) {
if (rtosfmw_type_name[i][0] &&
!strcmp(tname, rtosfmw_type_name[i][0]))
return i;
}
return RFT_MAX;
}
static ssize_t rtosfmw_attr_show(struct class *cls,
struct class_attribute *attr,
char *buf)
{
struct rtosfmw_attr *fmwattr;
ssize_t n = 0;
fmwattr = container_of(attr, struct rtosfmw_attr, attr);
n = sprintf(buf, "%d\n", fmwattr->status);
return n + 1;
}
static ssize_t rtosfmw_attr_store(struct class *cls,
struct class_attribute *attr,
const char *buf, size_t count)
{
struct rtosfmw_attr *fmwattr;
int ret;
if (count < 1)
return -EINVAL;
if (buf[0] != '1' && buf[0] != 1) {
pr_err("invalid value\n");
return -EINVAL;
}
fmwattr = container_of(attr, struct rtosfmw_attr, attr);
ret = aml_rtosfmw_run(fmwattr->type, fmwattr->fmwname);
pr_info("run firmware \"%s\" result = %d\n", fmwattr->fmwname, ret);
if (ret < 0)
return -EIO;
return count;
}
static int _rtosfmw_create_file(void)
{
struct platform_device *pdev;
int cnt, i, ret = 0, idx = 0;
const char *fwlst[RFT_MAX * 2];
pdev = rtosfmw_data.pdev;
for (i = 0; i < RFT_MAX ; i++) {
struct rtosfmw_attr *attr = rtosfmw_data.attr + i;
const char *name = rtosfmw_type_name[i][1];
attr->type = i;
attr->fmwname = NULL;
attr->status = 0;
attr->attr.attr.name = name;
attr->attr.attr.mode = 0644;
attr->attr.show = rtosfmw_attr_show;
attr->attr.store = rtosfmw_attr_store;
}
cnt = device_property_read_string_array(&pdev->dev,
"firmware",
fwlst,
RFT_MAX * 2);
if (cnt <= 0)
return 0;
for (i = 0, idx = 0; (i + 2) <= cnt; i += 2) {
enum rtosfmw_type type = _rtosfmw_type(fwlst[i]);
struct rtosfmw_attr *attr = rtosfmw_data.attr + type;
if (type >= RFT_MAX)
continue;
if (!attr->attr.attr.name)
continue;
if (attr->fmwname)
continue;
attr->fmwname = kstrdup(fwlst[i + 1], GFP_KERNEL);
if (!attr->fmwname)
continue;
rtosfmw_class_attrs[idx] = &attr->attr.attr;
idx++;
}
rtosfmw_class_attrs[idx] = NULL;
rtosfmw_data.cls.name = AML_RTOSFMW_NAME;
rtosfmw_data.cls.owner = THIS_MODULE;
rtosfmw_data.cls.class_groups = rtosfmw_class_groups;
ret = class_register(&rtosfmw_data.cls);
if (ret) {
pr_err("register class fail\n");
goto fail_exit;
}
return 0;
fail_exit:
for (i = 0; i < RFT_MAX ; i++) {
struct rtosfmw_attr *attr = rtosfmw_data.attr + i;
kfree(attr->fmwname);
attr->fmwname = NULL;
}
return -1;
}
static int secpu_dev_open_flag;
static DEFINE_MUTEX(secpu_dev_lock);
struct secpu_msg_buf *msg_buf;
struct secpu_spi_device *secure_spi;
struct secpu_spi_device cur_secure_spi;
struct secpu_spi_device last_secure_spi;
static int secpu_dev_fops_open(struct inode *inode, struct file *f)
{
int ret;
ret = mutex_lock_interruptible(&secpu_dev_lock);
if (ret < 0)
return ret;
if (secpu_dev_open_flag) {
pr_err("open fail, already opened\n");
ret = -EBUSY;
} else {
secpu_dev_open_flag = 1;
msg_buf = kzalloc(sizeof(*msg_buf), GFP_KERNEL);
secure_spi = kzalloc(sizeof(*secure_spi), GFP_KERNEL);
if (!msg_buf || !secure_spi) {
ret = -ENOMEM;
secpu_dev_open_flag = 0;
kfree(msg_buf);
kfree(secure_spi);
}
}
mutex_unlock(&secpu_dev_lock);
return ret;
}
static int secpu_dev_fops_release(struct inode *inode, struct file *f)
{
int ret;
ret = mutex_lock_interruptible(&secpu_dev_lock);
if (ret < 0)
return ret;
if (secpu_dev_open_flag) {
secpu_dev_open_flag = 0;
} else {
pr_err("release fail, wrong flag\n");
ret = -EINVAL;
}
if (msg_buf)
kfree(msg_buf);
if (secure_spi)
kfree(secure_spi);
mutex_unlock(&secpu_dev_lock);
return ret;
}
static ssize_t secpu_dev_fops_write(struct file *filp,
const char __user *userbuf,
size_t count, loff_t *ppos)
{
if (!msg_buf) {
pr_err("%s: 'msg_buf' is NULL\n", __func__);
return -EFAULT;
}
memset(msg_buf->send_msg, 0, SECPU_MSG_LEN);
if (count > SECPU_MSG_LEN) {
pr_info("%s: wr_len=%d greater than max_len=%d\n",
__func__, (int)count, SECPU_MSG_LEN);
count = SECPU_MSG_LEN;
}
if (copy_from_user(msg_buf->send_msg, userbuf, count)) {
pr_err("%s: Failed to copy_from_user\n", __func__);
return -EFAULT;
}
return count;
}
static ssize_t secpu_dev_fops_read(struct file *filp,
char __user *userbuf,
size_t count, loff_t *ppos)
{
if (!msg_buf) {
pr_err("%s: 'msg_buf' is NULL\n", __func__);
return -EFAULT;
}
if (count > SECPU_MSG_LEN) {
pr_info("%s: rd_len=%d greater than max_len=%d\n",
__func__, (int)count, SECPU_MSG_LEN);
count = SECPU_MSG_LEN;
}
if (copy_to_user(userbuf, msg_buf->rev_msg, count)) {
pr_err("%s: Failed to copy_to_user\n", __func__);
return -EFAULT;
}
return count;
}
static long secpu_dev_fops_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
long rtn = 0;
void __user *argp = (void __user *)arg;
int ret = 0;
struct uintcase secpu_msg_packet;
size_t msg_packet_len;
struct rtosfmw_attr *secpu_attr = rtosfmw_data.attr;
u32 tmp;
memset(&secpu_msg_packet, 0, sizeof(secpu_msg_packet));
secpu_msg_packet.ultaskdelay = 100;
switch (cmd) {
case REE_SECPU_MSG: //REE
msg_packet_len = sizeof(secpu_msg_packet.data) / sizeof(char);
memcpy(secpu_msg_packet.data, msg_buf->send_msg, msg_packet_len);
ret = scpi_send_data(&secpu_msg_packet, sizeof(secpu_msg_packet),
SCPI_SECPU, MBX_CMD_RPCUINTREE_TEST,
msg_buf->rev_msg, sizeof(msg_buf->rev_msg));
if (copy_to_user(argp, &ret, sizeof(int))) {
rtn = -1;
pr_err("%s :Failed to copy\n", __func__);
}
break;
case TEE_SECPU_MSG: //TEE
msg_packet_len = sizeof(secpu_msg_packet.data) / sizeof(char);
memcpy(secpu_msg_packet.data, msg_buf->send_msg, msg_packet_len);
ret = scpi_send_data(&secpu_msg_packet, sizeof(secpu_msg_packet),
SCPI_SECPU, MBX_CMD_RPCUINTTEE_TEST,
msg_buf->rev_msg, sizeof(msg_buf->rev_msg));
if (copy_to_user(argp, &ret, sizeof(int))) {
rtn = -1;
pr_err("%s :Failed to copy\n", __func__);
}
break;
case TRIGGER_SECPU_RUN:
rtn = aml_rtosfmw_run(secpu_attr->type, secpu_attr->fmwname);
pr_info("run firmware \"%s\" result = %d\n", secpu_attr->fmwname, ret);
if (rtn < 0)
rtn = -1;
if (copy_to_user(argp, &secpu_attr->status, sizeof(int))) {
rtn = -1;
pr_err("%s :Failed to copy\n", __func__);
}
break;
case GET_RUN_STATUS:
if (copy_to_user(argp, &secpu_attr->status, sizeof(int))) {
rtn = -1;
pr_err("%s :Failed to copy\n", __func__);
}
break;
/*get*/
case SPI_MODE_RD_32:
if (put_user(last_secure_spi.mode & 0xfff, (__u32 __user *)arg)) {
rtn = -1;
pr_err("%s :Failed to get mode\n", __func__);
}
break;
case SPI_BITS_PER_WORD_RD:
if (put_user(last_secure_spi.bits_per_word, (__u8 __user *)arg)) {
rtn = -1;
pr_err("%s :Failed to get bpw\n", __func__);
}
break;
case SPI_MAX_SPEED_HZ_RD:
if (put_user(last_secure_spi.max_speed_hz, (__u32 __user *)arg)) {
rtn = -1;
pr_err("%s :Failed to get speed\n", __func__);
}
break;
case SPI_CFG_RD:
memset(&cur_secure_spi, 0, sizeof(cur_secure_spi));
secure_spi->direction = SECPU2ARM;
ret = scpi_send_data(secure_spi, sizeof(*secure_spi),
SCPI_SECPU, MBX_CMD_SPI_CFG_RW,
&cur_secure_spi, sizeof(cur_secure_spi));
// update spi cfg status;
if (ret == 0) {
last_secure_spi.mode = cur_secure_spi.mode;
last_secure_spi.bits_per_word = cur_secure_spi.bits_per_word;
last_secure_spi.max_speed_hz = cur_secure_spi.max_speed_hz;
} else {
rtn = -1;
pr_err("%s :Failed to get spi config\n", __func__);
}
break;
/*set*/
case SPI_MODE_WR_32:
if (!get_user(tmp, (u32 __user *)arg)) {
if (tmp & ~0xfff) {
rtn = -EINVAL;
break;
}
secure_spi->mode = (u16)tmp;
} else {
secure_spi->mode = last_secure_spi.mode;
rtn = -1;
}
break;
case SPI_BITS_PER_WORD_WR:
ret = get_user(tmp, (__u8 __user *)arg);
if (ret == 0) {
secure_spi->bits_per_word = tmp;
} else {
secure_spi->bits_per_word = last_secure_spi.bits_per_word;
rtn = -1;
}
break;
case SPI_MAX_SPEED_HZ_WR:
ret = get_user(tmp, (__u32 __user *)arg);
if (ret == 0) {
secure_spi->max_speed_hz = tmp;
} else {
secure_spi->max_speed_hz = last_secure_spi.max_speed_hz;
rtn = -1;
}
break;
case SPI_CFG_WR:
memset(&cur_secure_spi, 0, sizeof(cur_secure_spi));
secure_spi->direction = ARM2SECPU;
ret = scpi_send_data(secure_spi, sizeof(*secure_spi),
SCPI_SECPU, MBX_CMD_SPI_CFG_RW,
&cur_secure_spi, sizeof(cur_secure_spi));
// update spi cfg status;
if (ret == 0) {
last_secure_spi.mode = cur_secure_spi.mode;
last_secure_spi.bits_per_word = cur_secure_spi.bits_per_word;
last_secure_spi.max_speed_hz = cur_secure_spi.max_speed_hz;
} else {
rtn = -1;
pr_err("%s :Failed to set spi config\n", __func__);
}
break;
default:
break;
}
return rtn;
}
static const struct file_operations secpu_dev_fops = {
.owner = THIS_MODULE,
.open = secpu_dev_fops_open,
.release = secpu_dev_fops_release,
.unlocked_ioctl = secpu_dev_fops_ioctl,
.write = secpu_dev_fops_write,
.read = secpu_dev_fops_read,
};
#define SECPU_DEV "secpu_dev"
static struct miscdevice secpu_misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = SECPU_DEV,
.fops = &secpu_dev_fops,
};
static int scpi_channel;
static int scpi_cmd;
static ssize_t scpi_read_file(struct file *file, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct scpitest {
char data[20];
u32 ulTaskDelay;
} scpitest;
char rx_data[40] = {0};
if (scpi_channel != SCPI_SECPU ||
(scpi_cmd != MBX_CMD_RPCUINTREE_TEST &&
scpi_cmd != MBX_CMD_RPCUINTTEE_TEST)) {
pr_err("%s: wrong command.\n", __func__);
pr_err("example: REE-0x6 TEE-0x7\n");
pr_err("echo 0x3 0x6 > /sys/kernel/debug/scpi\n");
pr_err("cat /sys/kernel/debug/scpi\n");
return 0;
}
strcpy(scpitest.data, "Hello RISC-V.");
scpitest.ulTaskDelay = 100;
pr_info("%s: arm2risc-v - Send a message :%s\n",
__func__, scpitest.data);
scpi_send_data(&scpitest, sizeof(scpitest), scpi_channel, scpi_cmd,
rx_data, sizeof(rx_data));
pr_info("%s: arm2risc-v - Received response:%s\n",
__func__, rx_data);
return 0;
}
static ssize_t scpi_write_file(struct file *file, const char __user *userbuf,
size_t count, loff_t *ppos)
{
unsigned int reg, val;
char buf[80];
int ret;
count = min_t(size_t, count, (sizeof(buf) - 1));
if (copy_from_user(buf, userbuf, count))
return -EFAULT;
buf[count] = 0;
ret = sscanf(buf, "%x %x", &reg, &val);
if (ret == 1)
scpi_channel = reg;
if (ret == 2) {
scpi_channel = reg;
scpi_cmd = val;
}
return count;
}
static const struct file_operations scpi_file_ops = {
.open = simple_open,
.read = scpi_read_file,
.write = scpi_write_file,
};
static int aml_rtos_secpu_dev_init(void)
{
int rc = 0;
rc = misc_register(&secpu_misc_dev);
if (rc)
pr_err("%s: register status data dev fail\n", __func__);
debugfs_create_file("scpi", S_IFREG | 0440,
NULL, NULL, &scpi_file_ops);
return rc;
}
static void aml_rtos_secpu_dev_deinit(void)
{
misc_deregister(&secpu_misc_dev);
}
static int aml_rtosfmw_probe(struct platform_device *pdev)
{
if (rtosfmw_data.inited) {
pr_err("already inited\n");
return -1;
}
rtosfmw_data.pdev = pdev;
if (rtosfmw_data.disabled)
return 0;
if (!fwnode_device_is_available(dev_fwnode(&pdev->dev)))
return 0;
if (_rtosfmw_create_file() < 0)
return -1;
aml_rtos_secpu_dev_init();
memset(&cur_secure_spi, 0, sizeof(cur_secure_spi));
memset(&last_secure_spi, 0, sizeof(last_secure_spi));
rtosfmw_data.inited = 1;
return 0;
}
static int __exit aml_rtosfmw_remove(struct platform_device *pdev)
{
int i;
if (!rtosfmw_data.inited)
return 0;
class_unregister(&rtosfmw_data.cls);
for (i = 0; i < RFT_MAX ; i++) {
struct rtosfmw_attr *attr = rtosfmw_data.attr + i;
kfree(attr->fmwname);
attr->fmwname = NULL;
}
aml_rtos_secpu_dev_deinit();
rtosfmw_data.inited = 0;
rtosfmw_data.pdev = NULL;
return 0;
}
static const struct of_device_id aml_rtosfmw_dt_match[] = {
{
.compatible = "amlogic,rtosfmw",
},
{},
};
static struct platform_driver aml_rtosfmw_driver = {
.driver = {
.name = AML_RTOSFMW_NAME,
.owner = THIS_MODULE,
.of_match_table = aml_rtosfmw_dt_match,
},
.probe = aml_rtosfmw_probe,
.remove = __exit_p(aml_rtosfmw_remove),
};
static int __init disable_rtosfmw(char *str)
{
rtosfmw_data.disabled = 1;
return 0;
}
early_param("disable_rtosfmw", disable_rtosfmw);
static int __init aml_rtosfmw_init(void)
{
return platform_driver_register(&aml_rtosfmw_driver);
}
module_init(aml_rtosfmw_init);
static void __exit aml_rtosfmw_exit(void)
{
platform_driver_unregister(&aml_rtosfmw_driver);
}
module_exit(aml_rtosfmw_exit);
MODULE_DESCRIPTION("RtosFMW Driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Amlogic, Inc.");