diff options
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 41 | ||||
-rw-r--r-- | drivers/misc/Makefile | 7 | ||||
-rw-r--r-- | drivers/misc/altera_hwmutex.c | 320 | ||||
-rw-r--r-- | drivers/misc/altera_ilc.c | 299 | ||||
-rw-r--r-- | drivers/misc/altera_sysid.c | 141 | ||||
-rw-r--r-- | drivers/misc/intel-rsu.c | 387 | ||||
-rw-r--r-- | drivers/misc/intel-service.c | 1042 | ||||
-rw-r--r-- | drivers/misc/intel-smc.h | 310 |
8 files changed, 2546 insertions, 1 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 3726eacdf65d..4c7d95e7e465 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -152,6 +152,36 @@ config INTEL_MID_PTI an Intel Atom (non-netbook) mobile device containing a MIPI P1149.7 standard implementation. +config INTEL_SERVICE + tristate "Intel Service Layer" + depends on ARCH_STRATIX10 + default n + help + Intel service layer runs at privileged exception level, interfaces with + the service providers (FPGA manager is one of them) and manages secure + monitor call to communicate with secure monitor software at secure monitor + exception level. + + Say Y here if you want Intel service layer support. + +config INTEL_RSU + tristate "Intel Remote System Update" + depends on INTEL_SERVICE + help + The Intel Remote System Update (RSU) driver exposes interfaces + accessed through the Intel Service Layer to user space via SysFS + device attribute nodes. The RSU interfaces report/control some of + the optional RSU features of the Stratix 10 SoC FPGA. + + The RSU feature provides a way for customers to update the boot + configuration of a Stratix 10 SoC device with significantly reduced + risk of corrupting the bitstream storage and bricking the system. + + Enable RSU support if you are using an Intel SoC FPGA with the RSU + feature enabled and you want Linux user space control. + + Say Y here if you want Intel RSU support. + config SGI_IOC4 tristate "SGI IOC4 Base IO support" depends on PCI @@ -513,6 +543,17 @@ config MISC_RTSX tristate default MISC_RTSX_PCI || MISC_RTSX_USB +config ALTERA_SYSID + tristate "Altera System ID" + help + This enables Altera System ID soft core driver. + +config ALTERA_ILC + tristate "Altera Interrupt Latency Counter driver" + help + This enables the Interrupt Latency Counter driver for the Altera + SOCFPGA platform. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index af22bbc3d00c..8df0ba395767 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o obj-$(CONFIG_AD525X_DPOT_I2C) += ad525x_dpot-i2c.o obj-$(CONFIG_AD525X_DPOT_SPI) += ad525x_dpot-spi.o obj-$(CONFIG_INTEL_MID_PTI) += pti.o +obj-$(CONFIG_INTEL_SERVICE) += intel-service.o +obj-$(CONFIG_INTEL_RSU) += intel-rsu.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o obj-$(CONFIG_DUMMY_IRQ) += dummy-irq.o @@ -42,7 +44,10 @@ obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-y += lis3lv02d/ obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o -obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ +obj-$(CONFIG_ALTERA_STAPL) += altera-stapl/ +obj-$(CONFIG_ALTERA_HWMUTEX) += altera_hwmutex.o +obj-$(CONFIG_ALTERA_ILC) += altera_ilc.o +obj-$(CONFIG_ALTERA_SYSID) += altera_sysid.o obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o diff --git a/drivers/misc/altera_hwmutex.c b/drivers/misc/altera_hwmutex.c new file mode 100644 index 000000000000..5004fe92e94b --- /dev/null +++ b/drivers/misc/altera_hwmutex.c @@ -0,0 +1,320 @@ +/* + * Copyright Altera Corporation (C) 2013. All rights reserved + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/altera_hwmutex.h> + +#define DRV_NAME "altera_hwmutex" + + +static DEFINE_SPINLOCK(list_lock); /* protect mutex_list */ +static LIST_HEAD(mutex_list); + +/* Mutex Registers */ +#define MUTEX_REG 0x0 + +#define MUTEX_REG_VALUE_MASK 0xFFFF +#define MUTEX_REG_OWNER_OFFSET 16 +#define MUTEX_REG_OWNER_MASK 0xFFFF +#define MUTEX_GET_OWNER(reg) \ + ((reg >> MUTEX_REG_OWNER_OFFSET) & MUTEX_REG_OWNER_MASK) + +/** + * altera_mutex_request - Retrieves a pointer to an acquired mutex device + * structure + * @mutex_np: The pointer to mutex device node + * + * Returns a pointer to the mutex device structure associated with the + * supplied device node, or NULL if no corresponding mutex device was + * found. + */ +struct altera_mutex *altera_mutex_request(struct device_node *mutex_np) +{ + struct altera_mutex *mutex; + + spin_lock(&list_lock); + list_for_each_entry(mutex, &mutex_list, list) { + if (mutex_np == mutex->pdev->dev.of_node) { + if (!mutex->requested) { + mutex->requested = true; + spin_unlock(&list_lock); + return mutex; + } else { + pr_info("Mutex device is in use.\n"); + spin_unlock(&list_lock); + return NULL; + } + } + } + spin_unlock(&list_lock); + pr_info("Mutex device not found!\n"); + return NULL; +} +EXPORT_SYMBOL(altera_mutex_request); + +/** + * altera_mutex_free - Free the mutex + * @mutex: the mutex + * + * Return 0 if success. Otherwise, returns non-zero. + */ +int altera_mutex_free(struct altera_mutex *mutex) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + spin_lock(&list_lock); + mutex->requested = false; + spin_unlock(&list_lock); + + return 0; +} +EXPORT_SYMBOL(altera_mutex_free); + +static int __mutex_trylock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + u32 read; + int ret = 0; + u32 data = (owner << MUTEX_REG_OWNER_OFFSET) | value; + + mutex_lock(&mutex->lock); + __raw_writel(data, mutex->regs + MUTEX_REG); + read = __raw_readl(mutex->regs + MUTEX_REG); + if (read != data) + ret = -1; + + mutex_unlock(&mutex->lock); + return ret; +} + +/** + * altera_mutex_lock - Acquires a hardware mutex, wait until it can get it. + * @mutex: the mutex to be acquired + * @owner: owner ID + * @value: the new non-zero value to write to mutex + * + * Returns 0 if mutex was successfully locked. Otherwise, returns non-zero. + * + * The mutex must later on be released by the same owner that acquired it. + * This function is not ISR callable. + */ +int altera_mutex_lock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + while (__mutex_trylock(mutex, owner, value) != 0) + ; + + return 0; +} +EXPORT_SYMBOL(altera_mutex_lock); + +/** + * altera_mutex_trylock - Tries once to lock the hardware mutex and returns + * immediately + * @mutex: the mutex to be acquired + * @owner: owner ID + * @value: the new non-zero value to write to mutex + * + * Returns 0 if mutex was successfully locked. Otherwise, returns non-zero. + * + * The mutex must later on be released by the same owner that acquired it. + * This function is not ISR callable. + */ +int altera_mutex_trylock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + return __mutex_trylock(mutex, owner, value); +} +EXPORT_SYMBOL(altera_mutex_trylock); + +/** + * altera_mutex_unlock - Unlock a mutex that has been locked by this owner + * previously that was locked on the + * altera_mutex_lock. Upon release, the value stored + * in the mutex is set to zero. + * @mutex: the mutex to be released + * @owner: Owner ID + * + * Returns 0 if mutex was successfully unlocked. Otherwise, returns + * non-zero. + * + * This function is not ISR callable. + */ +int altera_mutex_unlock(struct altera_mutex *mutex, u16 owner) +{ + u32 reg; + + if (!mutex || !mutex->requested) + return -EINVAL; + + mutex_lock(&mutex->lock); + + __raw_writel(owner << MUTEX_REG_OWNER_OFFSET, + mutex->regs + MUTEX_REG); + + reg = __raw_readl(mutex->regs + MUTEX_REG); + if (reg & MUTEX_REG_VALUE_MASK) { + /* Unlock failed */ + dev_dbg(&mutex->pdev->dev, + "Unlock mutex failed, owner %d and expected owner %d\n", + owner, MUTEX_GET_OWNER(reg)); + mutex_unlock(&mutex->lock); + return -EINVAL; + } + + mutex_unlock(&mutex->lock); + return 0; +} +EXPORT_SYMBOL(altera_mutex_unlock); + +/** + * altera_mutex_owned - Determines if this owner owns the mutex + * @mutex: the mutex to be queried + * @owner: Owner ID + * + * Returns 1 if the owner owns the mutex. Otherwise, returns zero. + */ +int altera_mutex_owned(struct altera_mutex *mutex, u16 owner) +{ + u32 reg; + u16 actual_owner; + int ret = 0; + + if (!mutex || !mutex->requested) + return ret; + + mutex_lock(&mutex->lock); + reg = __raw_readl(mutex->regs + MUTEX_REG); + actual_owner = MUTEX_GET_OWNER(reg); + if (actual_owner == owner) + ret = 1; + + mutex_unlock(&mutex->lock); + return ret; +} +EXPORT_SYMBOL(altera_mutex_owned); + +/** + * altera_mutex_is_locked - Determines if the mutex is locked + * @mutex: the mutex to be queried + * + * Returns 1 if the mutex is locked, 0 if unlocked. + */ +int altera_mutex_is_locked(struct altera_mutex *mutex) +{ + u32 reg; + int ret = 0; + + if (!mutex || !mutex->requested) + return ret; + + mutex_lock(&mutex->lock); + reg = __raw_readl(mutex->regs + MUTEX_REG); + reg &= MUTEX_REG_VALUE_MASK; + if (reg) + ret = 1; + + mutex_unlock(&mutex->lock); + return ret; +} +EXPORT_SYMBOL(altera_mutex_is_locked); + +static int altera_mutex_probe(struct platform_device *pdev) +{ + struct altera_mutex *mutex; + struct resource *regs; + + mutex = devm_kzalloc(&pdev->dev, sizeof(struct altera_mutex), + GFP_KERNEL); + if (!mutex) + return -ENOMEM; + + mutex->pdev = pdev; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + mutex->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(mutex->regs)) + return PTR_ERR(mutex->regs); + + mutex_init(&mutex->lock); + + spin_lock(&list_lock); + list_add_tail(&mutex->list, &mutex_list); + spin_unlock(&list_lock); + + platform_set_drvdata(pdev, mutex); + + return 0; +} + +static int altera_mutex_remove(struct platform_device *pdev) +{ + struct altera_mutex *mutex = platform_get_drvdata(pdev); + + spin_lock(&list_lock); + if (mutex) + list_del(&mutex->list); + spin_unlock(&list_lock); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_mutex_match[] = { + { .compatible = "altr,hwmutex-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_mutex_match); + +static struct platform_driver altera_mutex_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = altera_mutex_match, + }, + .remove = altera_mutex_remove, +}; + +static int __init altera_mutex_init(void) +{ + return platform_driver_probe(&altera_mutex_platform_driver, + altera_mutex_probe); +} + +static void __exit altera_mutex_exit(void) +{ + platform_driver_unregister(&altera_mutex_platform_driver); +} + +module_init(altera_mutex_init); +module_exit(altera_mutex_exit); + +MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera Hardware Mutex driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/altera_ilc.c b/drivers/misc/altera_ilc.c new file mode 100644 index 000000000000..adc78102c7d5 --- /dev/null +++ b/drivers/misc/altera_ilc.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2014 Altera Corporation. All rights reserved + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kfifo.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> + +#define DRV_NAME "altera_ilc" +#define CTRL_REG 0x80 +#define FREQ_REG 0x84 +#define STP_REG 0x88 +#define VLD_REG 0x8C +#define ILC_MAX_PORTS 32 +#define ILC_FIFO_DEFAULT 32 +#define ILC_ENABLE 0x01 +#define CHAR_SIZE 10 +#define POLL_INTERVAL 1 +#define GET_PORT_COUNT(_val) ((_val & 0x7C) >> 2) +#define GET_VLD_BIT(_val, _offset) (((_val) >> _offset) & 0x1) + +struct altera_ilc { + struct platform_device *pdev; + void __iomem *regs; + unsigned int port_count; + unsigned int irq; + unsigned int channel_offset; + unsigned int interrupt_channels[ILC_MAX_PORTS]; + struct kfifo kfifos[ILC_MAX_PORTS]; + struct device_attribute dev_attr[ILC_MAX_PORTS]; + struct delayed_work ilc_work; + char sysfs[ILC_MAX_PORTS][CHAR_SIZE]; + u32 fifo_depth; +}; + +static int ilc_irq_lookup(struct altera_ilc *ilc, int irq) +{ + int i; + for (i = 0; i < ilc->port_count; i++) { + if (irq == platform_get_irq(ilc->pdev, i)) + return i; + } + return -EPERM; +} + +static ssize_t ilc_show_counter(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret, i, id, fifo_len; + unsigned int fifo_buf[ILC_MAX_PORTS]; + char temp[10]; + struct altera_ilc *ilc = dev_get_drvdata(dev); + + fifo_len = 0; + ret = kstrtouint(attr->attr.name, 0, &id); + + for (i = 0; i < ilc->port_count; i++) { + if (id == (ilc->interrupt_channels[i])) { + /*Check for kfifo length*/ + fifo_len = kfifo_len(&ilc->kfifos[i]) + /sizeof(unsigned int); + if (fifo_len <= 0) { + dev_info(&ilc->pdev->dev, "Fifo for interrupt %s is empty\n", + attr->attr.name); + return 0; + } + /*Read from kfifo*/ + ret = kfifo_out(&ilc->kfifos[i], &fifo_buf, + kfifo_len(&ilc->kfifos[i])); + } + } + + for (i = 0; i < fifo_len; i++) { + sprintf(temp, "%u\n", fifo_buf[i]); + strcat(buf, temp); + } + + strcat(buf, "\0"); + + return strlen(buf); +} + +static struct attribute *altera_ilc_attrs[ILC_MAX_PORTS]; + +struct attribute_group altera_ilc_attr_group = { + .name = "ilc_data", + .attrs = altera_ilc_attrs, +}; + +static void ilc_work(struct work_struct *work) +{ + unsigned int ilc_value, ret, offset, stp_reg; + struct altera_ilc *ilc = + container_of(work, struct altera_ilc, ilc_work.work); + + offset = ilc_irq_lookup(ilc, ilc->irq); + if (offset < 0) { + dev_err(&ilc->pdev->dev, "Unable to lookup irq number\n"); + return; + } + + if (GET_VLD_BIT(readl(ilc->regs + VLD_REG), offset)) { + /*Read counter register*/ + ilc_value = readl(ilc->regs + (offset) * 4); + + /*Putting value into kfifo*/ + ret = kfifo_in((&ilc->kfifos[offset]), + (unsigned int *)&ilc_value, sizeof(ilc_value)); + + /*Clearing stop register*/ + stp_reg = readl(ilc->regs + STP_REG); + writel((!(0x1 << offset))&stp_reg, ilc->regs + STP_REG); + + return; + } + + /*Start workqueue to poll data valid*/ + schedule_delayed_work(&ilc->ilc_work, msecs_to_jiffies(POLL_INTERVAL)); +} + +static irqreturn_t ilc_interrupt_handler(int irq, void *p) +{ + unsigned int offset, stp_reg; + + struct altera_ilc *ilc = (struct altera_ilc *)p; + + /*Update ILC struct*/ + ilc->irq = irq; + + dev_dbg(&ilc->pdev->dev, "Interrupt %u triggered\n", + ilc->irq); + + offset = ilc_irq_lookup(ilc, irq); + if (offset < 0) { + dev_err(&ilc->pdev->dev, "Unable to lookup irq number\n"); + return IRQ_RETVAL(IRQ_NONE); + } + + /*Setting stop register*/ + stp_reg = readl(ilc->regs + STP_REG); + writel((0x1 << offset)|stp_reg, ilc->regs + STP_REG); + + /*Start workqueue to poll data valid*/ + schedule_delayed_work(&ilc->ilc_work, 0); + + return IRQ_RETVAL(IRQ_NONE); +} + +static int altera_ilc_probe(struct platform_device *pdev) +{ + struct altera_ilc *ilc; + struct resource *regs; + struct device_node *np = pdev->dev.of_node; + int ret, i; + + ilc = devm_kzalloc(&pdev->dev, sizeof(struct altera_ilc), + GFP_KERNEL); + if (!ilc) + return -ENOMEM; + + ilc->pdev = pdev; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + ilc->regs = devm_ioremap_resource(&pdev->dev, regs); + if (!ilc->regs) + return -EADDRNOTAVAIL; + + ilc->port_count = GET_PORT_COUNT(readl(ilc->regs + CTRL_REG)); + if (ilc->port_count <= 0) { + dev_warn(&pdev->dev, "No interrupt connected to ILC\n"); + return -EPERM; + } + + /*Check for fifo depth*/ + ret = of_property_read_u32(np, "altr,sw-fifo-depth", + &(ilc->fifo_depth)); + if (ret) { + dev_warn(&pdev->dev, "Fifo depth undefined\n"); + dev_warn(&pdev->dev, "Setting fifo depth to default value (32)\n"); + ilc->fifo_depth = ILC_FIFO_DEFAULT; + } + + /*Initialize Kfifo*/ + for (i = 0; i < ilc->port_count; i++) { + ret = kfifo_alloc(&ilc->kfifos[i], (ilc->fifo_depth * + sizeof(unsigned int)), GFP_KERNEL); + if (ret) { + dev_err(&pdev->dev, "Kfifo failed to initialize\n"); + return ret; + } + } + + /*Register each of the IRQs*/ + for (i = 0; i < ilc->port_count; i++) { + ilc->interrupt_channels[i] = platform_get_irq(pdev, i); + + ret = devm_request_irq(&pdev->dev, (ilc->interrupt_channels[i]), + ilc_interrupt_handler, IRQF_SHARED, "ilc_0", + (void *)(ilc)); + + if (ret < 0) + dev_warn(&pdev->dev, "Failed to register interrupt handler"); + } + + /*Setup sysfs interface*/ + for (i = 0; (i < ilc->port_count); i++) { + sprintf(ilc->sysfs[i], "%d", (ilc->interrupt_channels[i])); + ilc->dev_attr[i].attr.name = ilc->sysfs[i]; + ilc->dev_attr[i].attr.mode = S_IRUGO; + ilc->dev_attr[i].show = ilc_show_counter; + altera_ilc_attrs[i] = &ilc->dev_attr[i].attr; + altera_ilc_attrs[i+1] = NULL; + } + ret = sysfs_create_group(&pdev->dev.kobj, &altera_ilc_attr_group); + + /*Initialize workqueue*/ + INIT_DELAYED_WORK(&ilc->ilc_work, ilc_work); + + /*Global enable ILC softIP*/ + writel(ILC_ENABLE, ilc->regs + CTRL_REG); + + platform_set_drvdata(pdev, ilc); + + dev_info(&pdev->dev, "Driver successfully loaded\n"); + + return 0; +} + +static int altera_ilc_remove(struct platform_device *pdev) +{ + int i; + struct altera_ilc *ilc = platform_get_drvdata(pdev); + + /*Remove sysfs interface*/ + sysfs_remove_group(&pdev->dev.kobj, &altera_ilc_attr_group); + + /*Free up kfifo memory*/ + for (i = 0; i < ilc->port_count; i++) + kfifo_free(&ilc->kfifos[i]); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_ilc_match[] = { + { .compatible = "altr,ilc-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_ilc_match); + +static struct platform_driver altera_ilc_platform_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_ilc_match), + }, + .remove = altera_ilc_remove, +}; + +static int __init altera_ilc_init(void) +{ + return platform_driver_probe(&altera_ilc_platform_driver, + altera_ilc_probe); +} + +static void __exit altera_ilc_exit(void) +{ + platform_driver_unregister(&altera_ilc_platform_driver); +} + +module_init(altera_ilc_init); +module_exit(altera_ilc_exit); + +MODULE_AUTHOR("Chee Nouk Phoon <cnphoon@altera.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera Interrupt Latency Counter Driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/altera_sysid.c b/drivers/misc/altera_sysid.c new file mode 100644 index 000000000000..be35530c504b --- /dev/null +++ b/drivers/misc/altera_sysid.c @@ -0,0 +1,141 @@ +/* + * Copyright Altera Corporation (C) 2013. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * Credit: + * Walter Goossens + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> + +#define DRV_NAME "altera_sysid" + +struct altera_sysid { + void __iomem *regs; +}; + +/* System ID Registers*/ +#define SYSID_REG_ID (0x0) +#define SYSID_REG_TIMESTAMP (0x4) + +static ssize_t altera_sysid_show_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct altera_sysid *sysid = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", readl(sysid->regs + SYSID_REG_ID)); +} + +static ssize_t altera_sysid_show_timestamp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int reg; + struct tm timestamp; + struct altera_sysid *sysid = dev_get_drvdata(dev); + + reg = readl(sysid->regs + SYSID_REG_TIMESTAMP); + + time_to_tm(reg, 0, ×tamp); + + return sprintf(buf, "%u (%u-%u-%u %u:%u:%u UTC)\n", reg, + (unsigned int)(timestamp.tm_year + 1900), + timestamp.tm_mon + 1, timestamp.tm_mday, timestamp.tm_hour, + timestamp.tm_min, timestamp.tm_sec); +} + +static DEVICE_ATTR(id, S_IRUGO, altera_sysid_show_id, NULL); +static DEVICE_ATTR(timestamp, S_IRUGO, altera_sysid_show_timestamp, NULL); + +static struct attribute *altera_sysid_attrs[] = { + &dev_attr_id.attr, + &dev_attr_timestamp.attr, + NULL, +}; + +struct attribute_group altera_sysid_attr_group = { + .name = "sysid", + .attrs = altera_sysid_attrs, +}; + +static int altera_sysid_probe(struct platform_device *pdev) +{ + struct altera_sysid *sysid; + struct resource *regs; + + sysid = devm_kzalloc(&pdev->dev, sizeof(struct altera_sysid), + GFP_KERNEL); + if (!sysid) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + sysid->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(sysid->regs)) + return PTR_ERR(sysid->regs); + + platform_set_drvdata(pdev, sysid); + + return sysfs_create_group(&pdev->dev.kobj, &altera_sysid_attr_group); +} + +static int altera_sysid_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &altera_sysid_attr_group); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_sysid_match[] = { + { .compatible = "altr,sysid-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_sysid_match); + +static struct platform_driver altera_sysid_platform_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_sysid_match), + }, + .probe = altera_sysid_probe, + .remove = altera_sysid_remove, +}; + +static int __init altera_sysid_init(void) +{ + return platform_driver_register(&altera_sysid_platform_driver); +} + +static void __exit altera_sysid_exit(void) +{ + platform_driver_unregister(&altera_sysid_platform_driver); +} + +module_init(altera_sysid_init); +module_exit(altera_sysid_exit); + +MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera System ID driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/intel-rsu.c b/drivers/misc/intel-rsu.c new file mode 100644 index 000000000000..94b85416f882 --- /dev/null +++ b/drivers/misc/intel-rsu.c @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Intel Corporation + */ + +/* + * This driver exposes some optional features of the Intel Stratix 10 SoC FPGA. + * The SysFS interfaces exposed here are FPGA Remote System Update (RSU) + * related. They allow user space software to query the configuration system + * status and to request optional reboot behavior specific to Intel FPGAs. + */ + +#include <linux/arm-smccc.h> +#include <linux/completion.h> +#include <linux/intel-service-client.h> +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/sysfs.h> + +#define MAX_U64_STR_LEN 22 + +/* + * Private data structure + */ +struct intel_rsu_priv { + struct intel_svc_chan *chan; + struct intel_svc_client client; + struct completion svc_completion; + struct { + unsigned long current_image; + unsigned long fail_image; + unsigned int version; + unsigned int state; + unsigned int error_details; + unsigned int error_location; + } status; +}; + +/* + * status_svc_callback() - Callback from intel-service layer that returns SMC + * response with RSU status data. Parses up data and + * update driver private data structure. + * client - returned context from intel-service layer + * data - SMC response data + */ +static void status_svc_callback(struct intel_svc_client *client, + struct intel_svc_c_data *data) +{ + struct intel_rsu_priv *priv = client->priv; + struct arm_smccc_res *res = (struct arm_smccc_res *)data->kaddr1; + + if (data->status == BIT(SVC_STATUS_RSU_OK)) { + priv->status.version = + (unsigned int)(res->a2 >> 32) & 0xFFFFFFFF; + priv->status.state = (unsigned int)res->a2 & 0xFFFFFFFF; + priv->status.fail_image = res->a1; + priv->status.current_image = res->a0; + priv->status.error_location = + (unsigned int)res->a3 & 0xFFFFFFFF; + priv->status.error_details = + (unsigned int)(res->a3 >> 32) & 0xFFFFFFFF; + } else { + dev_err(client->dev, "COMMAND_RSU_STATUS returned 0x%lX\n", + res->a0); + priv->status.version = 0; + priv->status.state = 0; + priv->status.fail_image = 0; + priv->status.current_image = 0; + priv->status.error_location = 0; + priv->status.error_details = 0; + } + + complete(&priv->svc_completion); +} + +/* + * get_status() - Start an intel-service layer transaction to perform the SMC + * that is necessary to get RSU status information. Wait for + * completion and timeout if needed. + * priv - driver private data + * + * Returns 0 on success + */ +static int get_status(struct intel_rsu_priv *priv) +{ + struct intel_svc_client_msg msg; + int ret; + unsigned long timeout; + + reinit_completion(&priv->svc_completion); + priv->client.receive_cb = status_svc_callback; + + msg.command = COMMAND_RSU_STATUS; + ret = intel_svc_send(priv->chan, &msg); + if (ret < 0) + goto status_done; + + timeout = msecs_to_jiffies(SVC_RSU_REQUEST_TIMEOUT_MS); + ret = + wait_for_completion_interruptible_timeout(&priv->svc_completion, + timeout); + if (!ret) { + dev_err(priv->client.dev, + "timeout waiting for COMMAND_RSU_STATUS\n"); + ret = -ETIMEDOUT; + goto status_done; + } + if (ret < 0) { + dev_err(priv->client.dev, + "error (%d) waiting for COMMAND_RSU_STATUS\n", ret); + goto status_done; + } + + ret = 0; + +status_done: + intel_svc_done(priv->chan); + return ret; +} + +/* current_image_show() - DEVICE_ATTR callback to show current_image status */ +static ssize_t current_image_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%ld", priv->status.current_image); +} + +/* fail_image_show() - DEVICE_ATTR callback to show fail_image status */ +static ssize_t fail_image_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%ld", priv->status.fail_image); +} + +/* version_show() - DEVICE_ATTR callback to show version status */ +static ssize_t version_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%d", priv->status.version); +} + +/* state_show() - DEVICE_ATTR callback to show state status */ +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%d", priv->status.state); +} + +/* error_location_show() - DEVICE_ATTR callback to show error_location status */ +static ssize_t error_location_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%d", priv->status.error_location); +} + +/* error_details_show() - DEVICE_ATTR callback to show error_details status */ +static ssize_t error_details_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%d", priv->status.error_details); +} + +/* + * update_svc_callback() - Callback from intel-service layer that returns SMC + * response from RSU update. Checks for success/fail. + * client - returned context from intel-service layer + * data - SMC repsonse data + */ +static void update_svc_callback(struct intel_svc_client *client, + struct intel_svc_c_data *data) +{ + struct intel_rsu_priv *priv = client->priv; + + if (data->status != BIT(SVC_STATUS_RSU_OK)) + dev_err(client->dev, "COMMAND_RSU_UPDATE returned %i\n", + data->status); + + complete(&priv->svc_completion); +} + +/* + * send_update() - Start an intel-service layer transaction to perform the SMC + * that is necessary to send an RSU update request. Wait for + * completion and timeout if needed. + * priv - driver private data + * + * Returns 0 on success + */ +static int send_update(struct intel_rsu_priv *priv, + unsigned long address) +{ + struct intel_svc_client_msg msg; + int ret; + unsigned long timeout; + + reinit_completion(&priv->svc_completion); + priv->client.receive_cb = update_svc_callback; + + msg.command = COMMAND_RSU_UPDATE; + msg.arg[0] = address; + + ret = intel_svc_send(priv->chan, &msg); + if (ret < 0) + goto update_done; + + timeout = msecs_to_jiffies(SVC_RSU_REQUEST_TIMEOUT_MS); + ret = wait_for_completion_interruptible_timeout(&priv->svc_completion, + timeout); + if (!ret) { + dev_err(priv->client.dev, + "timeout waiting for COMMAND_RSU_UPDATE\n"); + ret = -ETIMEDOUT; + goto update_done; + } + if (ret < 0) { + dev_err(priv->client.dev, + "error (%d) waiting for COMMAND_RSU_UPDATE\n", ret); + goto update_done; + } + + ret = 0; + +update_done: + intel_svc_done(priv->chan); + return ret; +} + +/* reboot_image_store() - DEVICE_ATTR callback to store reboot_image request */ +static ssize_t reboot_image_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + unsigned long address; + int ret; + + if (priv == 0) + return -ENODEV; + + /* Ensure the input buffer is null terminated and not too long */ + if (strnlen(buf, MAX_U64_STR_LEN) == MAX_U64_STR_LEN) + return -EINVAL; + + ret = kstrtoul(buf, 10, &address); + if (ret) + return ret; + + send_update(priv, address); + + return count; +} + +/* + * Attribute structures + */ + +static DEVICE_ATTR_RO(current_image); +static DEVICE_ATTR_RO(fail_image); +static DEVICE_ATTR_RO(state); +static DEVICE_ATTR_RO(version); +static DEVICE_ATTR_RO(error_location); +static DEVICE_ATTR_RO(error_details); +static DEVICE_ATTR_WO(reboot_image); + +static struct attribute *attrs[] = { + &dev_attr_current_image.attr, + &dev_attr_fail_image.attr, + &dev_attr_state.attr, + &dev_attr_version.attr, + &dev_attr_error_location.attr, + &dev_attr_error_details.attr, + &dev_attr_reboot_image.attr, + NULL +}; + +static struct attribute_group attr_group = { + .attrs = attrs +}; + +static int intel_rsu_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct intel_rsu_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client.dev = dev; + priv->client.receive_cb = update_svc_callback; + priv->client.priv = priv; + + priv->status.current_image = 0; + priv->status.fail_image = 0; + priv->status.error_location = 0; + priv->status.error_details = 0; + priv->status.version = 0; + priv->status.state = 0; + + priv->chan = request_svc_channel_byname(&priv->client, SVC_CLIENT_RSU); + if (IS_ERR(priv->chan)) { + dev_err(dev, "couldn't get service channel (%s)\n", + SVC_CLIENT_RSU); + return PTR_ERR(priv->chan); + } + + init_completion(&priv->svc_completion); + + platform_set_drvdata(pdev, priv); + + ret = get_status(priv); + if (ret) { + dev_err(dev, "Error getting RSU status (%i)\n", ret); + free_svc_channel(priv->chan); + return ret; + } + + ret = sysfs_create_group(&dev->kobj, &attr_group); + if (ret) + free_svc_channel(priv->chan); + + return ret; +} + +static int intel_rsu_remove(struct platform_device *pdev) +{ + struct intel_rsu_priv *priv = platform_get_drvdata(pdev); + + free_svc_channel(priv->chan); + + return 0; +} + +static const struct of_device_id intel_rsu_of_match[] = { + {.compatible = "intel,stratix10-rsu",}, + {}, +}; +MODULE_DEVICE_TABLE(of, intel_rsu_of_match); + +static struct platform_driver intel_rsu_driver = { + .probe = intel_rsu_probe, + .remove = intel_rsu_remove, + .driver = { + .name = "intel-rsu", + .of_match_table = intel_rsu_of_match, + }, +}; + +module_platform_driver(intel_rsu_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Remote System Update SysFS Driver"); +MODULE_AUTHOR("David Koltak <david.koltak@linux.intel.com>"); diff --git a/drivers/misc/intel-service.c b/drivers/misc/intel-service.c new file mode 100644 index 000000000000..ea32db718ca3 --- /dev/null +++ b/drivers/misc/intel-service.c @@ -0,0 +1,1042 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2018, Intel Corporation + */ + +/* + * Intel Stratix10 SoC is composed of a 64 bit quad-core ARM Cortex A53 hard + * processor system (HPS) and Secure Device Manager (SDM). SDM is the + * hardware which does the FPGA configuration, QSPI, Crypto and warm reset. + * + * When the FPGA is configured from HPS, there needs to be a way for HPS to + * notify SDM the location and size of the configuration data. Then SDM will + * get the configuration data from that location and perform the FPGA + * configuration. + * + * To meet the whole system security needs and support virtual machine + * requesting communication with SDM, only the secure world of software (EL3, + * Exception Level 3) can interface with SDM. All software entities running + * on other exception levels must channel through the EL3 software whenever + * it needs service from SDM. + * + * Intel Stratix10 service layer driver is added to provide the service for + * FPGA configuration. Running at privileged exception level (EL1, Exception + * Level 1), Intel Stratix10 service layer driver interfaces with the service + * client at EL1 (Intel Stratix10 FPGA Manager) and manages secure monitor + * call (SMC) to communicate with secure monitor software at secure monitor + * exception level (EL3). + */ + +#include <linux/arm-smccc.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/genalloc.h> +#include <linux/intel-service-client.h> +#include <linux/io.h> +#include <linux/kfifo.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include "intel-smc.h" + +/* SVC_NUM_DATA_IN_FIFO - number of struct intel_svc_data in the FIFO */ +#define SVC_NUM_DATA_IN_FIFO 32 +/* SVC_NUM_CHANNEL - number of channel supported by service layer driver */ +#define SVC_NUM_CHANNEL 2 +/* + * FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS - claim back the submitted buffer(s) + * from the secure world for FPGA manager to reuse, or to free the buffer(s) + * when all bit-stream data had be send. + */ +#define FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS 200 +/* + * FPGA_CONFIG_STATUS_TIMEOUT_SEC - poll the FPGA configuration status, + * service layer will return error to FPGA manager when timeout occurs, + * timeout is set to 30 seconds (30 * 1000) at Intel Stratix10 SoC. + */ +#define FPGA_CONFIG_STATUS_TIMEOUT_SEC 30 + +typedef void (svc_invoke_fn)(unsigned long, unsigned long, unsigned long, + unsigned long, unsigned long, unsigned long, + unsigned long, unsigned long, + struct arm_smccc_res *); +static int svc_normal_to_secure_thread(void *data); +struct intel_svc_chan; + +/** + * struct intel_svc_sh_memory - service shared memory structure + * @sync_complete: state for a completion + * @addr: physical address of shared memory block + * @size: size of shared memory block + * @invoke_fn: function to issue secure monitor or hypervisor call + * + * This struct is used to save physical address and size of shared memory + * block. The shared memory blocked is allocated by secure monitor software + * at secure world. + * + * Service layer driver uses the physical address and size to create a memory + * pool, then allocates data buffer from that memory pool for service client. + */ +struct intel_svc_sh_memory { + struct completion sync_complete; + unsigned long addr; + unsigned long size; + svc_invoke_fn *invoke_fn; +}; + +/** + * struct intel_svc_data_mem - service memory structure + * @vaddr: virtual address + * @paddr: physical address + * @size: size of memory + * @node: link list head node + * + * This struct is used in a list that keeps track of buffers which have + * been allocated or freed from the memory pool. Service layer driver also + * uses this struct to transfer physical address to virtual address. + */ +struct intel_svc_data_mem { + void *vaddr; + phys_addr_t paddr; + size_t size; + struct list_head node; +}; + +/** + * struct intel_svc_data - service data structure + * @chan: service channel + * @paddr: playload physical address + * @size: playload size + * @command: service command requested by client + * @arg[3]: args to be passed via registers and not physically mapped buffers + * This struct is used in service FIFO for inter-process communication. + */ +struct intel_svc_data { + struct intel_svc_chan *chan; + phys_addr_t paddr; + size_t size; + u32 command; + u64 arg[3]; +}; + +/** + * struct intel_svc_controller - service controller + * @dev: device + * @chans: array of service channels + * $num_chans: number of channels in 'chans' array + * @num_active_client: number of active service client + * @node: list management + * @genpool: memory pool pointing to the memory region + * @task: pointer to the thread task which handles SMC or HVC call + * @svc_fifo: a queue for storing service message data + * @complete_status: state for completion + * @svc_fifo_lock: protect access to service message data queue + * @invoke_fn: function to issue secure monitor call or hypervisor call + * + * This struct is used to create communication channels for service clients, to + * handle secure monitor or hypervisor call. + */ +struct intel_svc_controller { + struct device *dev; + struct intel_svc_chan *chans; + int num_chans; + int num_active_client; + struct list_head node; + struct gen_pool *genpool; + struct task_struct *task; + struct kfifo svc_fifo; + struct completion complete_status; + spinlock_t svc_fifo_lock; + svc_invoke_fn *invoke_fn; +}; + +/** + * struct intel_svc_chan - service communication channel + * @ctrl: pointer to service controller which is the provider of this channel + * @scl: pointer to service client which owns the channel + * @name: service client name associated with the channel + * @lock: protect access to the channel + * + * This struct is used by service client to communicate with service layer, each + * service client has its own channel created by service controller. + */ +struct intel_svc_chan { + struct intel_svc_controller *ctrl; + struct intel_svc_client *scl; + char *name; + spinlock_t lock; +}; + +static LIST_HEAD(svc_ctrl); +static LIST_HEAD(svc_data_mem); + +/** + * request_svc_channel_byname() - request a service channel + * @client: pointer to service client + * @name: service client name + * + * This function is used by service client to request a service channel. + * + * Return: a pointer to channel assigned to the client on success, + * or ERR_PTR() on error. + */ +struct intel_svc_chan *request_svc_channel_byname( + struct intel_svc_client *client, const char *name) +{ + struct device *dev = client->dev; + struct intel_svc_controller *controller; + struct intel_svc_chan *chan = NULL; + unsigned long flag; + int i; + + /* if probe was called after client's, or error on probe */ + if (list_empty(&svc_ctrl)) + return ERR_PTR(-EPROBE_DEFER); + + controller = list_first_entry(&svc_ctrl, + struct intel_svc_controller, node); + for (i = 0; i < SVC_NUM_CHANNEL; i++) { + if (!strcmp(controller->chans[i].name, name)) { + chan = &controller->chans[i]; + break; + } + } + + /* if there was no channel match */ + if (i == SVC_NUM_CHANNEL) { + dev_err(dev, "%s: channel not allocated\n", __func__); + return ERR_PTR(-EINVAL); + } + + if (chan->scl || !try_module_get(controller->dev->driver->owner)) { + dev_dbg(dev, "%s: svc not free\n", __func__); + return ERR_PTR(-EBUSY); + } + + spin_lock_irqsave(&chan->lock, flag); + chan->scl = client; + chan->ctrl->num_active_client++; + spin_unlock_irqrestore(&chan->lock, flag); + + return chan; +} +EXPORT_SYMBOL_GPL(request_svc_channel_byname); + +/** + * free_svc_channel() - free service channel + * @chan: service channel to be freed + * + * This function is used by service client to free a service channel. + */ +void free_svc_channel(struct intel_svc_chan *chan) +{ + unsigned long flag; + + spin_lock_irqsave(&chan->lock, flag); + chan->scl = NULL; + chan->ctrl->num_active_client--; + module_put(chan->ctrl->dev->driver->owner); + spin_unlock_irqrestore(&chan->lock, flag); +} +EXPORT_SYMBOL_GPL(free_svc_channel); + +/** + * intel_svc_send() - send a message data to the remote + * @chan: service channel assigned to the client + * @msg: message data to be sent, in the format of "struct intel_svc_client_msg" + * + * This function is used by service client to send command or data to service + * layer driver. + * + * Return: non-negative value for successful submission to the data queue + * created by service layer driver, or negative value on error. + */ +int intel_svc_send(struct intel_svc_chan *chan, void *msg) +{ + struct intel_svc_client_msg *p_msg = (struct intel_svc_client_msg *)msg; + struct intel_svc_data_mem *p_mem; + struct intel_svc_data *p_data; + int ret = 0; + unsigned int cpu = 0; + + p_data = kmalloc(sizeof(*p_data), GFP_KERNEL); + if (!p_data) + return -ENOMEM; + + /* first client will create kernel thread */ + if (!chan->ctrl->task) { + chan->ctrl->task = + kthread_create_on_node(svc_normal_to_secure_thread, + (void *)chan->ctrl, + cpu_to_node(cpu), + "svc_smc_hvc_thread"); + if (IS_ERR(chan->ctrl->task)) { + dev_err(chan->ctrl->dev, + "fails to create svc_smc_hvc_thread\n"); + kfree(p_data); + return -EINVAL; + } + kthread_bind(chan->ctrl->task, cpu); + wake_up_process(chan->ctrl->task); + } + + pr_debug("%s: sent P-va=%p, P-com=%x, P-size=%u\n", __func__, + p_msg->payload, p_msg->command, + (unsigned int)p_msg->payload_length); + + list_for_each_entry(p_mem, &svc_data_mem, node) + if (p_mem->vaddr == p_msg->payload) { + p_data->paddr = p_mem->paddr; + break; + } + + p_data->command = p_msg->command; + p_data->arg[0] = p_msg->arg[0]; + p_data->arg[1] = p_msg->arg[1]; + p_data->arg[2] = p_msg->arg[2]; + p_data->size = p_msg->payload_length; + p_data->chan = chan; + pr_debug("%s: put to FIFO pa=0x%016x, cmd=%x, size=%u\n", __func__, + (unsigned int)p_data->paddr, p_data->command, + (unsigned int)p_data->size); + ret = kfifo_in_spinlocked(&chan->ctrl->svc_fifo, p_data, + sizeof(*p_data), + &chan->ctrl->svc_fifo_lock); + wake_up_process(chan->ctrl->task); + + kfree(p_data); + + if (!ret) + return -ENOBUFS; + + return ret; +} +EXPORT_SYMBOL_GPL(intel_svc_send); + +/** + * intel_svc_done() - complete service request transactions + * @chan: service channel assigned to the client + * + * This function should be called when client has finished its request + * or there is an error in the request process. It allows the service layer + * to stop the running thread to have maximize savings in kernel resources. + */ +void intel_svc_done(struct intel_svc_chan *chan) +{ + /* stop thread when thread is running AND only one active client */ + if (chan->ctrl->task && (chan->ctrl->num_active_client <= 1)) { + pr_debug("svc_smc_hvc_shm_thread is stopped\n"); + kthread_stop(chan->ctrl->task); + chan->ctrl->task = NULL; + } +} +EXPORT_SYMBOL_GPL(intel_svc_done); + +/** + * intel_svc_allocate_memory() - allocate memory + * @chan: service channel assigned to the client + * @size: memory size requested by a specific service client + * + * Service layer allocates the requested number of bytes buffer from the + * memory pool, service client uses this function to get allocated buffers. + * + * Return: address of allocated memory on success, or ERR_PTR() on error. + */ +void *intel_svc_allocate_memory(struct intel_svc_chan *chan, size_t size) +{ + struct intel_svc_data_mem *pmem; + unsigned long va; + phys_addr_t pa; + struct gen_pool *genpool = chan->ctrl->genpool; + size_t s = roundup(size, 1 << genpool->min_alloc_order); + + pmem = devm_kzalloc(chan->ctrl->dev, sizeof(*pmem), GFP_KERNEL); + if (!pmem) + return ERR_PTR(-ENOMEM); + + va = gen_pool_alloc(genpool, s); + if (!va) + return ERR_PTR(-ENOMEM); + + memset((void *)va, 0, s); + pa = gen_pool_virt_to_phys(genpool, va); + + pmem->vaddr = (void *)va; + pmem->paddr = pa; + pmem->size = s; + list_add_tail(&pmem->node, &svc_data_mem); + pr_debug("%s: va=%p, pa=0x%016x\n", __func__, + pmem->vaddr, (unsigned int)pmem->paddr); + + return (void *)va; +} +EXPORT_SYMBOL_GPL(intel_svc_allocate_memory); + +/** + * intel_svc_free_memory() - free allocated memory + * @chan: service channel assigned to the client + * @kaddr: memory to be freed + * + * This function is used by service client to free allocated buffers. + */ +void intel_svc_free_memory(struct intel_svc_chan *chan, void *kaddr) +{ + struct intel_svc_data_mem *pmem; + size_t size = 0; + + list_for_each_entry(pmem, &svc_data_mem, node) + if (pmem->vaddr == kaddr) { + size = pmem->size; + break; + } + + gen_pool_free(chan->ctrl->genpool, (unsigned long)kaddr, size); + pmem->vaddr = NULL; + list_del(&pmem->node); +} +EXPORT_SYMBOL_GPL(intel_svc_free_memory); + +/** + * svc_pa_to_va() - translate physical address to virtual address + * @addr: to be translated physical address + * + * Return: valid virtual address or NULL if the provided physical + * address doesn't exist. + */ +static void *svc_pa_to_va(unsigned long addr) +{ + struct intel_svc_data_mem *pmem; + + pr_debug("claim back P-addr=0x%016x\n", (unsigned int)addr); + list_for_each_entry(pmem, &svc_data_mem, node) + if (pmem->paddr == addr) + return pmem->vaddr; + + /* physical address is not found */ + return NULL; +} + +/** + * svc_thread_cmd_data_claim() - claim back buffer from the secure world + * @addr: pointer to service layer controller + * @p_data: pointer to service data structure + * @c_data: pointer to callback data structure to service client + * + * Claim back the submitted buffers from the secure world and pass buffer + * back to service client (FPGA manager, etc) for reuse. + */ +static void svc_thread_cmd_data_claim(struct intel_svc_controller *ctrl, + struct intel_svc_data *p_data, + struct intel_svc_c_data *c_data) +{ + struct arm_smccc_res res; + unsigned long timeout; + + reinit_completion(&ctrl->complete_status); + timeout = msecs_to_jiffies(FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS); + + pr_debug("%s: claim back the submitted buffer\n", __func__); + do { + ctrl->invoke_fn(INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE, + 0, 0, 0, 0, 0, 0, 0, &res); + + if (res.a0 == INTEL_SIP_SMC_STATUS_OK) { + if (!res.a1) { + complete(&ctrl->complete_status); + break; + } + c_data->status = BIT(SVC_STATUS_RECONFIG_BUFFER_DONE); + c_data->kaddr1 = svc_pa_to_va(res.a1); + c_data->kaddr2 = (res.a2) ? svc_pa_to_va(res.a2) : NULL; + c_data->kaddr3 = (res.a3) ? svc_pa_to_va(res.a3) : NULL; + p_data->chan->scl->receive_cb(p_data->chan->scl, + c_data); + } else { + pr_debug("%s: secure world busy, polling again\n", + __func__); + } + } while (res.a0 == INTEL_SIP_SMC_STATUS_OK || + res.a0 == INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY || + wait_for_completion_timeout(&ctrl->complete_status, timeout)); +} + +/** + * svc_thread_cmd_config_status() - check configuration status + * @ctrl: pointer to service layer controller + * @p_data: pointer to service data structure + * @c_data: pointer to callback data structure to service client + * + * Check whether the secure firmware at secure world has finished the FPGA + * configuration, and then inform FPGA manager the configuration status. + */ +static void svc_thread_cmd_config_status(struct intel_svc_controller *ctrl, + struct intel_svc_data *p_data, + struct intel_svc_c_data *c_data) +{ + struct arm_smccc_res res; + int count_in_sec; + + c_data->kaddr1 = NULL; + c_data->kaddr2 = NULL; + c_data->kaddr3 = NULL; + c_data->status = BIT(SVC_STATUS_RECONFIG_ERROR); + + pr_debug("%s: polling config status\n", __func__); + + count_in_sec = FPGA_CONFIG_STATUS_TIMEOUT_SEC; + while (count_in_sec) { + ctrl->invoke_fn(INTEL_SIP_SMC_FPGA_CONFIG_ISDONE, + 0, 0, 0, 0, 0, 0, 0, &res); + if ((res.a0 == INTEL_SIP_SMC_STATUS_OK) || + (res.a0 == INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR)) + break; + + /* + * configuration is still in progress, wait one second then + * poll again + */ + msleep(1000); + count_in_sec--; + }; + + if (res.a0 == INTEL_SIP_SMC_STATUS_OK && count_in_sec) + c_data->status = BIT(SVC_STATUS_RECONFIG_COMPLETED); + + p_data->chan->scl->receive_cb(p_data->chan->scl, c_data); +} + +/** + * svc_thread_recv_status_ok() - handle the successful status + * @p_data: pointer to service data structure + * @c_data: pointer to callback data structure to service client + * @res: result from SMC or HVC call + * + * Send back the correspond status to the service client (FPGA manager etc). + */ +static void svc_thread_recv_status_ok(struct intel_svc_data *p_data, + struct intel_svc_c_data *c_data, + struct arm_smccc_res res) +{ + c_data->kaddr1 = NULL; + c_data->kaddr2 = NULL; + c_data->kaddr3 = NULL; + + switch (p_data->command) { + case COMMAND_RECONFIG: + c_data->status = BIT(SVC_STATUS_RECONFIG_REQUEST_OK); + break; + case COMMAND_RECONFIG_DATA_SUBMIT: + c_data->status = BIT(SVC_STATUS_RECONFIG_BUFFER_SUBMITTED); + break; + case COMMAND_NOOP: + c_data->status = BIT(SVC_STATUS_RECONFIG_BUFFER_SUBMITTED); + c_data->kaddr1 = svc_pa_to_va(res.a1); + break; + case COMMAND_RECONFIG_STATUS: + c_data->status = BIT(SVC_STATUS_RECONFIG_COMPLETED); + break; + default: + break; + } + + pr_debug("%s: call receive_cb\n", __func__); + p_data->chan->scl->receive_cb(p_data->chan->scl, c_data); +} + +/** + * svc_normal_to_secure_thread() - the function to run in the kthread + * @data: data pointer for kthread function + * + * Service layer driver creates intel_svc_smc_hvc_call kthread on CPU + * node 0, its function intel_svc_secure_call_thread is used to handle + * SMC or HVC calls between kernel driver and secure monitor software. + * + * Return: 0 + */ +static int svc_normal_to_secure_thread(void *data) +{ + struct intel_svc_controller *ctrl = (struct intel_svc_controller *)data; + struct intel_svc_data *pdata; + struct intel_svc_c_data *cdata; + struct arm_smccc_res res; + unsigned long a0, a1, a2; + int ret_fifo = 0; + + pdata = kmalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + cdata = kmalloc(sizeof(*cdata), GFP_KERNEL); + if (!cdata) { + kfree(pdata); + return -ENOMEM; + } + + /* default set, to remove build warning */ + a0 = INTEL_SIP_SMC_FPGA_CONFIG_LOOPBACK; + a1 = 0; + a2 = 0; + + pr_debug("smc_hvc_shm_thread is running\n"); + + while (!kthread_should_stop()) { + ret_fifo = kfifo_out_spinlocked(&ctrl->svc_fifo, + pdata, sizeof(*pdata), + &ctrl->svc_fifo_lock); + + if (!ret_fifo) { + schedule_timeout_interruptible(MAX_SCHEDULE_TIMEOUT); + continue; + } + + pr_debug("get from FIFO pa=0x%016x, command=%u, size=%u\n", + (unsigned int)pdata->paddr, pdata->command, + (unsigned int)pdata->size); + + switch (pdata->command) { + case COMMAND_RECONFIG_DATA_CLAIM: + svc_thread_cmd_data_claim(ctrl, pdata, cdata); + continue; + case COMMAND_RECONFIG: + a0 = INTEL_SIP_SMC_FPGA_CONFIG_START; + a1 = 0; + a2 = 0; + break; + case COMMAND_RECONFIG_DATA_SUBMIT: + a0 = INTEL_SIP_SMC_FPGA_CONFIG_WRITE; + a1 = (unsigned long)pdata->paddr; + a2 = (unsigned long)pdata->size; + break; + case COMMAND_RECONFIG_STATUS: + a0 = INTEL_SIP_SMC_FPGA_CONFIG_ISDONE; + a1 = 0; + a2 = 0; + break; + case COMMAND_RSU_STATUS: + a0 = INTEL_SIP_SMC_RSU_STATUS; + a1 = 0; + a2 = 0; + break; + case COMMAND_RSU_UPDATE: + a0 = INTEL_SIP_SMC_RSU_UPDATE; + a1 = pdata->arg[0]; + a2 = 0; + break; + default: + /* it shouldn't happen */ + break; + } + pr_debug("%s: before SMC call -- a0=0x%016x a1=0x%016x", + __func__, (unsigned int)a0, (unsigned int)a1); + pr_debug(" a2=0x%016x\n", (unsigned int)a2); + + ctrl->invoke_fn(a0, a1, a2, 0, 0, 0, 0, 0, &res); + + pr_debug("%s: after SMC call -- res.a0=0x%016x", + __func__, (unsigned int)res.a0); + pr_debug(" res.a1=0x%016x, res.a2=0x%016x", + (unsigned int)res.a1, (unsigned int)res.a2); + pr_debug(" res.a3=0x%016x\n", (unsigned int)res.a3); + + if (pdata->command == COMMAND_RSU_STATUS) { + if (res.a0 == INTEL_SIP_SMC_RSU_ERROR) + cdata->status = 0; + else + cdata->status = BIT(SVC_STATUS_RSU_OK); + + cdata->kaddr1 = &res; + cdata->kaddr2 = NULL; + cdata->kaddr3 = NULL; + pdata->chan->scl->receive_cb(pdata->chan->scl, cdata); + continue; + } + + if (pdata->command == COMMAND_RSU_UPDATE) { + if (res.a0 == INTEL_SIP_SMC_STATUS_OK) + cdata->status = BIT(SVC_STATUS_RSU_OK); + else + cdata->status = 0; + + cdata->kaddr1 = NULL; + cdata->kaddr2 = NULL; + cdata->kaddr3 = NULL; + pdata->chan->scl->receive_cb(pdata->chan->scl, cdata); + continue; + } + + switch (res.a0) { + case INTEL_SIP_SMC_STATUS_OK: + svc_thread_recv_status_ok(pdata, cdata, res); + break; + case INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY: + switch (pdata->command) { + case COMMAND_RECONFIG_DATA_SUBMIT: + svc_thread_cmd_data_claim(ctrl, + pdata, cdata); + break; + case COMMAND_RECONFIG_STATUS: + svc_thread_cmd_config_status(ctrl, + pdata, cdata); + break; + default: + break; + } + break; + case INTEL_SIP_SMC_FPGA_CONFIG_STATUS_REJECTED: + pr_debug("%s: STATUS_REJECTED\n", __func__); + break; + case INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR: + pr_err("%s: STATUS_ERROR\n", __func__); + cdata->status = BIT(SVC_STATUS_RECONFIG_ERROR); + cdata->kaddr1 = NULL; + cdata->kaddr2 = NULL; + cdata->kaddr3 = NULL; + pdata->chan->scl->receive_cb(pdata->chan->scl, cdata); + break; + default: + break; + } + }; + + kfree(cdata); + kfree(pdata); + + return 0; +} + +/** + * svc_normal_to_secure_shm_thread() - the function to run in the kthread + * @data: data pointer for kthread function + * + * Service layer driver creates intel_svc_smc_hvc_shm kthread on CPU + * node 0, its function intel_svc_secure_shm_thread is used to query the + * physical address of memory block reserved by secure monitor software at + * secure world. + * + * Return: 0 + */ +static int svc_normal_to_secure_shm_thread(void *data) +{ + struct intel_svc_sh_memory *sh_mem = (struct intel_svc_sh_memory *)data; + struct arm_smccc_res res; + + /* SMC or HVC call to get shared memory info from secure world */ + sh_mem->invoke_fn(INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM, + 0, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 == INTEL_SIP_SMC_STATUS_OK) { + sh_mem->addr = res.a1; + sh_mem->size = res.a2; + } else { + pr_err("%s: after SMC call -- res.a0=0x%016x", __func__, + (unsigned int)res.a0); + sh_mem->addr = 0; + sh_mem->size = 0; + } + + complete(&sh_mem->sync_complete); + do_exit(0); +} + +/** + * svc_get_sh_memory_param() - get memory block reserved by secure monitor SW + * @pdev: pointer to service layer device + * @param: pointer to service shared memory structure + * + * Return: zero for successfully getting the physical address of memory block + * reserved by secure monitor software, or negative value on error. + */ +static int svc_get_sh_memory_param(struct platform_device *pdev, + struct intel_svc_sh_memory *param) +{ + struct device *dev = &pdev->dev; + struct task_struct *sh_memory_task; + unsigned int cpu = 0; + + init_completion(¶m->sync_complete); + + /* smc/hvc call happens on cpu 0 bound kthread */ + sh_memory_task = kthread_create_on_node(svc_normal_to_secure_shm_thread, + (void *)param, cpu_to_node(cpu), + "svc_smc_hvc_shm_thread"); + if (IS_ERR(sh_memory_task)) + dev_err(dev, "fail to create intel_svc_smc_shm_thread\n"); + kthread_bind(sh_memory_task, cpu); + wake_up_process(sh_memory_task); + + if (!wait_for_completion_timeout(¶m->sync_complete, 10 * HZ)) { + dev_err(dev, + "timeout to get sh-memory paras from secure world\n"); + return -ETIMEDOUT; + } + + if (!param->addr || !param->size) { + dev_err(dev, + "fails to get shared memory info from secure world\n"); + return -ENOMEM; + } + + dev_dbg(dev, "SM software provides paddr: 0x%016x, size: 0x%08x\n", + (unsigned int)param->addr, + (unsigned int)param->size); + + return 0; +} + +/** + * svc_create_memory_pool() - create a memory pool from reserved memory block + * @pdev: pointer to service layer device + * @param: pointer to service shared memory structure + * + * Return: pool allocated from reserved memory block or ERR_PTR() on error. + */ +static struct gen_pool * +svc_create_memory_pool(struct platform_device *pdev, + struct intel_svc_sh_memory *param) +{ + struct device *dev = &pdev->dev; + struct gen_pool *genpool; + unsigned long vaddr; + phys_addr_t paddr; + size_t size; + phys_addr_t begin; + phys_addr_t end; + void *va; + size_t page_mask = PAGE_SIZE - 1; + int min_alloc_order = 3; + int ret; + + begin = roundup(param->addr, PAGE_SIZE); + end = rounddown(param->addr + param->size, PAGE_SIZE); + paddr = begin; + size = end - begin; + va = memremap(paddr, size, MEMREMAP_WC); + if (!va) { + dev_err(dev, "fail to remap shared memory\n"); + return ERR_PTR(-EINVAL); + } + vaddr = (unsigned long)va; + dev_dbg(dev, + "reserved memory vaddr: %p, paddr: 0x%16x size: 0x%8x\n", + va, (unsigned int)paddr, (unsigned int)size); + if ((vaddr & page_mask) || (paddr & page_mask) || + (size & page_mask)) { + dev_err(dev, "page is not aligned\n"); + return ERR_PTR(-EINVAL); + } + genpool = gen_pool_create(min_alloc_order, -1); + if (!genpool) { + dev_err(dev, "fail to create genpool\n"); + return ERR_PTR(-ENOMEM); + } + gen_pool_set_algo(genpool, gen_pool_best_fit, NULL); + ret = gen_pool_add_virt(genpool, vaddr, paddr, size, -1); + if (ret) { + dev_err(dev, "fail to add memory chunk to the pool\n"); + gen_pool_destroy(genpool); + return ERR_PTR(ret); + } + + return genpool; +} + +/** + * svc_smccc_smc() - secure monitor call between normal and secure world + * @a0-a7: arguments passed in registers 0 to 7 + * @res: result values from register 0 to 3 + */ +static void svc_smccc_smc(unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, + unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res); +} + +/** + * svc_smccc_hvc() - hypervisor call between normal and secure world + * @a0-a7: arguments passed in registers 0 to 7 + * @res: result values from register 0 to 3 + */ +static void svc_smccc_hvc(unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, + unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); +} + +/** + * get_invoke_func() - invoke SMC or HVC call + * @dev: pointer to device + * + * Return: function pointer to svc_smccc_smc or svc_smccc_hvc. + */ +static svc_invoke_fn *get_invoke_func(struct device *dev) +{ + const char *method; + + if (of_property_read_string(dev->of_node, "method", &method)) { + dev_warn(dev, "missing \"method\" property\n"); + return ERR_PTR(-ENXIO); + } + + if (!strcmp(method, "smc")) + return svc_smccc_smc; + if (!strcmp(method, "hvc")) + return svc_smccc_hvc; + + dev_warn(dev, "invalid \"method\" property: %s\n", method); + + return ERR_PTR(-EINVAL); +} + +static const struct of_device_id intel_svc_drv_match[] = { + {.compatible = "intel,stratix10-svc"}, + {}, +}; + +static int intel_svc_drv_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_svc_controller *controller; + struct intel_svc_chan *chans; + struct gen_pool *genpool; + struct intel_svc_sh_memory *sh_memory; + svc_invoke_fn *invoke_fn; + size_t fifo_size; + int ret; + + /* get SMC or HVC function */ + invoke_fn = get_invoke_func(dev); + if (IS_ERR(invoke_fn)) + return -EINVAL; + + sh_memory = devm_kzalloc(dev, sizeof(*sh_memory), GFP_KERNEL); + if (!sh_memory) + return -ENOMEM; + + sh_memory->invoke_fn = invoke_fn; + ret = svc_get_sh_memory_param(pdev, sh_memory); + if (ret) + return ret; + + genpool = svc_create_memory_pool(pdev, sh_memory); + if (!genpool) + return -ENOMEM; + + /* allocate service controller and supporting channel */ + controller = devm_kzalloc(dev, sizeof(*controller), GFP_KERNEL); + if (!controller) + return -ENOMEM; + + chans = devm_kmalloc_array(dev, SVC_NUM_CHANNEL, + sizeof(*chans), GFP_KERNEL | __GFP_ZERO); + if (!chans) + return -ENOMEM; + + controller->dev = dev; + controller->num_chans = SVC_NUM_CHANNEL; + controller->num_active_client = 0; + controller->chans = chans; + controller->genpool = genpool; + controller->task = NULL; + controller->invoke_fn = invoke_fn; + init_completion(&controller->complete_status); + + fifo_size = sizeof(struct intel_svc_data) * SVC_NUM_DATA_IN_FIFO; + ret = kfifo_alloc(&controller->svc_fifo, fifo_size, GFP_KERNEL); + if (ret) { + dev_err(dev, "fails to allocate FIFO\n"); + return ret; + } + spin_lock_init(&controller->svc_fifo_lock); + + chans[0].scl = NULL; + chans[0].ctrl = controller; + chans[0].name = "fpga"; + spin_lock_init(&chans[0].lock); + + chans[1].scl = NULL; + chans[1].ctrl = controller; + chans[1].name = "rsu"; + spin_lock_init(&chans[1].lock); + + list_add_tail(&controller->node, &svc_ctrl); + platform_set_drvdata(pdev, controller); + + pr_info("Intel Service Layer Driver Initialized\n"); + + return ret; +} + +static int intel_svc_drv_remove(struct platform_device *pdev) +{ + struct intel_svc_controller *ctrl = platform_get_drvdata(pdev); + + kfifo_free(&ctrl->svc_fifo); + if (ctrl->task) { + kthread_stop(ctrl->task); + ctrl->task = NULL; + } + if (ctrl->genpool) + gen_pool_destroy(ctrl->genpool); + list_del(&ctrl->node); + + return 0; +} + +static struct platform_driver intel_svc_driver = { + .probe = intel_svc_drv_probe, + .remove = intel_svc_drv_remove, + .driver = { + .name = "intel-svc", + .of_match_table = intel_svc_drv_match, + }, +}; + +static int __init intel_svc_init(void) +{ + struct device_node *fw_np; + struct device_node *np; + int ret; + + fw_np = of_find_node_by_name(NULL, "firmware"); + if (!fw_np) + return -ENODEV; + + np = of_find_matching_node(fw_np, intel_svc_drv_match); + if (!np) { + of_node_put(fw_np); + return -ENODEV; + } + + of_node_put(np); + ret = of_platform_populate(fw_np, intel_svc_drv_match, NULL, NULL); + of_node_put(fw_np); + if (ret) + return ret; + + return platform_driver_register(&intel_svc_driver); +} + +static void __exit intel_svc_exit(void) +{ + return platform_driver_unregister(&intel_svc_driver); +} + +subsys_initcall(intel_svc_init); +module_exit(intel_svc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Stratix10 Service Layer Driver"); +MODULE_AUTHOR("Richard Gong <richard.gong@intel.com>"); +MODULE_ALIAS("platform:intel-svc"); diff --git a/drivers/misc/intel-smc.h b/drivers/misc/intel-smc.h new file mode 100644 index 000000000000..1612e5d8dca1 --- /dev/null +++ b/drivers/misc/intel-smc.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2018, Intel Corporation + */ + +#ifndef __INTEL_SMC_H +#define __INTEL_SMC_H + +#include <linux/arm-smccc.h> +#include <linux/bitops.h> + +/* + * This file defines the Secure Monitor Call (SMC) message protocol used for + * service layer driver in normal world (EL1) to communicate with secure + * monitor software in Secure Monitor Exception Level 3 (EL3). + * + * This file is shared with secure firmware (FW) which is out of kernel tree. + * + * An ARM SMC instruction takes a function identifier and up to 6 64-bit + * register values as arguments, and can return up to 4 64-bit register + * value. The operation of the secure monitor is determined by the parameter + * values passed in through registers. + + * EL1 and EL3 communicates pointer as physical address rather than the + * virtual address. + */ + +/* + * Functions specified by ARM SMC Calling convention: + * + * FAST call executes atomic operations, returns when the requested operation + * has completed. + * STD call starts a operation which can be preempted by a non-secure + * interrupt. The call can return before the requested operation has + * completed. + * + * a0..a7 is used as register names in the descriptions below, on arm32 + * that translates to r0..r7 and on arm64 to w0..w7. + */ + +#define INTEL_SIP_SMC_STD_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL, ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_SIP, (func_num)) + +#define INTEL_SIP_SMC_FAST_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_SIP, (func_num)) + +/* + * Return values in INTEL_SIP_SMC_* call + * + * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION: + * Secure monitor software doesn't recognize the request. + * + * INTEL_SIP_SMC_STATUS_OK: + * FPGA configuration completed successfully, + * In case of FPGA configuration write operation, it means secure monitor + * software can accept the next chunk of FPGA configuration data. + * + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY: + * In case of FPGA configuration write operation, it means secure monitor + * software is still processing previous data & can't accept the next chunk + * of data. Service driver needs to issue + * INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE call to query the + * completed block(s). + * + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR: + * There is error during the FPGA configuration process. + * + * INTEL_SIP_SMC_REG_ERROR: + * There is error during a read or write operation of the protected + * registers. + */ +#define INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION 0xFFFFFFFF +#define INTEL_SIP_SMC_STATUS_OK 0x0 +#define INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY 0x1 +#define INTEL_SIP_SMC_FPGA_CONFIG_STATUS_REJECTED 0x2 +#define INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR 0x4 +#define INTEL_SIP_SMC_REG_ERROR 0x5 +#define INTEL_SIP_SMC_RSU_ERROR 0x7 + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_START + * + * Sync call used by service driver at EL1 to request the FPGA in EL3 to + * be prepare to receive a new configuration. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_START. + * a1: flag for full or partial configuration + * 0 full reconfiguration. + * 1 partial reconfiguration. + * a2-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, or INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_START 1 +#define INTEL_SIP_SMC_FPGA_CONFIG_START \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_START) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_WRITE + * + * Async call used by service driver at EL1 to provide FPGA configuration data + * to secure world. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_WRITE. + * a1: 64bit physical address of the configuration data memory block + * a2: Size of configuration data block. + * a3-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY or + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1: 64bit physical address of 1st completed memory block if any completed + * block, otherwise zero value. + * a2: 64bit physical address of 2nd completed memory block if any completed + * block, otherwise zero value. + * a3: 64bit physical address of 3rd completed memory block if any completed + * block, otherwise zero value. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_WRITE 2 +#define INTEL_SIP_SMC_FPGA_CONFIG_WRITE \ + INTEL_SIP_SMC_STD_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_WRITE) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE + * + * Sync call used by service driver at EL1 to track the completed write + * transactions. This request is called after INTEL_SIP_SMC_FPGA_CONFIG_WRITE + * call returns INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE. + * a1-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY or + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1: 64bit physical address of 1st completed memory block. + * a2: 64bit physical address of 2nd completed memory block if + * any completed block, otherwise zero value. + * a3: 64bit physical address of 3rd completed memory block if + * any completed block, otherwise zero value. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE 3 +#define INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE \ +INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_ISDONE + * + * Sync call used by service driver at EL1 to inform secure world that all + * data are sent, to check whether or not the secure world had completed + * the FPGA configuration process. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_ISDONE. + * a1-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY or + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_ISDONE 4 +#define INTEL_SIP_SMC_FPGA_CONFIG_ISDONE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_ISDONE) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM + * + * Sync call used by service driver at EL1 to query the physical address of + * memory block reserved by secure monitor software. + * + * Call register usage: + * a0:INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM. + * a1-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1: start of physical address of reserved memory block. + * a2: size of reserved memory block. + * a3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_GET_MEM 5 +#define INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_GET_MEM) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_LOOPBACK + * + * For SMC loop-back mode only, used for internal integration, debugging + * or troubleshooting. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_LOOPBACK. + * a1-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_LOOPBACK 6 +#define INTEL_SIP_SMC_FPGA_CONFIG_LOOPBACK \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_LOOPBACK) + +/* + * Request INTEL_SIP_SMC_REG_READ + * + * Read a protected register using SMCCC + * + * Call register usage: + * a0: INTEL_SIP_SMC_REG_READ. + * a1: register address. + * a2-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_REG_ERROR. + * a1: Value in the register + * a2-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_REG_READ 7 +#define INTEL_SIP_SMC_REG_READ \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_REG_READ) + +/* + * Request INTEL_SIP_SMC_REG_WRITE + * + * Write a protected register using SMCCC + * + * Call register usage: + * a0: INTEL_SIP_SMC_REG_WRITE. + * a1: register address + * a2: value to program into register. + * a3-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_REG_ERROR. + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_REG_WRITE 8 +#define INTEL_SIP_SMC_REG_WRITE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_REG_WRITE) + +/* + * Request INTEL_SIP_SMC_FUNCID_REG_UPDATE + * + * Update one or more bits in a protected register using a + * read-modify-write operation. + * + * Call register usage: + * a0: INTEL_SIP_SMC_REG_UPDATE. + * a1: register address + * a2: Write Mask. + * a3: Value to write. + * a4-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_REG_ERROR. + * a1-3: Not used. + */ +#define INTEL_SIP_SMC_FUNCID_REG_UPDATE 9 +#define INTEL_SIP_SMC_REG_UPDATE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_REG_UPDATE) + +/* + * Request INTEL_SIP_SMC_RSU_STATUS + * + * Sync call used by service driver at EL1 to query the RSU status + * + * Call register usage: + * a0 INTEL_SIP_SMC_RSU_STATUS + * a1-7 not used + * + * Return status + * a0: Current Image + * a1: Last Failing Image + * a2: Version | State + * a3: Error details | Error location + * + * Or + * + * a0: INTEL_SIP_SMC_RSU_ERROR + */ +#define INTEL_SIP_SMC_FUNCID_RSU_STATUS 11 +#define INTEL_SIP_SMC_RSU_STATUS \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_RSU_STATUS) + +/* + * Request INTEL_SIP_SMC_RSU_UPDATE + * + * Sync call used by service driver at EL1 to tell you next reboot is RSU_UPDATE + * + * Call register usage: + * a0 INTEL_SIP_SMC_RSU_UPDATE + * a1 64bit physical address of the configuration data memory in flash + * a2-7 not used + * + * Return status + * a0 INTEL_SIP_SMC_STATUS_OK + */ +#define INTEL_SIP_SMC_FUNCID_RSU_UPDATE 12 +#define INTEL_SIP_SMC_RSU_UPDATE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_RSU_UPDATE) + +#endif |