86d0cd334f
avoid event signaled twice. add ioctl to check if event was signaled. Change-Id: Ie75b9db86f2badddc08d65eee30ecd6a15795256 Signed-off-by: Amir Samuelov <amirs@codeaurora.org>
1258 lines
30 KiB
C
1258 lines
30 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
/*
|
|
* Secure-Processor-SubSystem (SPSS) utilities.
|
|
*
|
|
* This driver provides utilities for the Secure Processor (SP).
|
|
*
|
|
* The SP daemon needs to load different SPSS images based on:
|
|
*
|
|
* 1. Test/Production key used to sign the SPSS image (read fuses).
|
|
* 2. SPSS HW version (selected via Device Tree).
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "spss_utils [%s]: " fmt, __func__
|
|
|
|
#include <linux/kernel.h> /* min() */
|
|
#include <linux/module.h> /* MODULE_LICENSE */
|
|
#include <linux/device.h> /* class_create() */
|
|
#include <linux/slab.h> /* kzalloc() */
|
|
#include <linux/fs.h> /* file_operations */
|
|
#include <linux/cdev.h> /* cdev_add() */
|
|
#include <linux/errno.h> /* EINVAL, ETIMEDOUT */
|
|
#include <linux/printk.h> /* pr_err() */
|
|
#include <linux/bitops.h> /* BIT(x) */
|
|
#include <linux/platform_device.h> /* platform_driver_register() */
|
|
#include <linux/of.h> /* of_property_count_strings() */
|
|
#include <linux/of_address.h> /* of_address_to_resource() */
|
|
#include <linux/io.h> /* ioremap_nocache() */
|
|
#include <linux/notifier.h>
|
|
#include <linux/sizes.h> /* SZ_4K */
|
|
#include <linux/uaccess.h> /* copy_from_user() */
|
|
#include <linux/completion.h> /* wait_for_completion_timeout() */
|
|
|
|
#include <soc/qcom/subsystem_notif.h>
|
|
#include <soc/qcom/subsystem_restart.h>
|
|
#include <soc/qcom/secure_buffer.h> /* VMID_HLOS */
|
|
|
|
#include <uapi/linux/ioctl.h> /* ioctl() */
|
|
#include <uapi/linux/spss_utils.h> /* IOCTL to user space */
|
|
|
|
/* driver name */
|
|
#define DEVICE_NAME "spss_utils"
|
|
|
|
enum spss_firmware_type {
|
|
SPSS_FW_TYPE_DEV = 'd',
|
|
SPSS_FW_TYPE_TEST = 't',
|
|
SPSS_FW_TYPE_PROD = 'p',
|
|
SPSS_FW_TYPE_NONE = 'z',
|
|
};
|
|
|
|
static enum spss_firmware_type firmware_type = SPSS_FW_TYPE_TEST;
|
|
static const char *dev_firmware_name;
|
|
static const char *test_firmware_name;
|
|
static const char *prod_firmware_name;
|
|
static const char *none_firmware_name = "nospss";
|
|
static const char *firmware_name = "NA";
|
|
static struct device *spss_dev;
|
|
static u32 spss_debug_reg_addr; /* SP_SCSR_MBn_SP2CL_GPm(n,m) */
|
|
static u32 spss_emul_type_reg_addr; /* TCSR_SOC_EMULATION_TYPE */
|
|
static void *iar_notif_handle;
|
|
static struct notifier_block *iar_nb;
|
|
static bool is_iar_active;
|
|
|
|
#define CMAC_SIZE_IN_BYTES (128/8) /* 128 bit = 16 bytes */
|
|
#define CMAC_SIZE_IN_DWORDS (CMAC_SIZE_IN_BYTES/sizeof(u32)) /* 4 dwords */
|
|
|
|
/* Asym , Crypt , Keym */
|
|
#define NUM_UEFI_APPS 3
|
|
|
|
static u32 pil_addr;
|
|
static u32 pil_size;
|
|
static u32 cmac_buf[CMAC_SIZE_IN_DWORDS]; /* saved cmac */
|
|
static u32 pbl_cmac_buf[CMAC_SIZE_IN_DWORDS]; /* pbl cmac */
|
|
|
|
static u32 calc_apps_cmac[NUM_UEFI_APPS][CMAC_SIZE_IN_DWORDS];
|
|
static u32 saved_apps_cmac[NUM_UEFI_APPS][CMAC_SIZE_IN_DWORDS];
|
|
|
|
#define FW_AND_APPS_CMAC_SIZE \
|
|
(CMAC_SIZE_IN_DWORDS + NUM_UEFI_APPS*CMAC_SIZE_IN_DWORDS)
|
|
|
|
static u32 iar_state;
|
|
static bool is_iar_enabled;
|
|
|
|
static void __iomem *cmac_mem;
|
|
static size_t cmac_mem_size = SZ_4K; /* XPU align to 4KB */
|
|
static phys_addr_t cmac_mem_addr;
|
|
|
|
#define SPU_EMULATUION (BIT(0) | BIT(1))
|
|
#define SPU_PRESENT_IN_EMULATION BIT(2)
|
|
|
|
/* Events notification */
|
|
static struct completion spss_events[SPSS_NUM_EVENTS];
|
|
static bool spss_events_signaled[SPSS_NUM_EVENTS];
|
|
|
|
/* Protect from ioctl signal func called by multiple-proc at the same time */
|
|
static struct mutex event_lock;
|
|
|
|
/**
|
|
* struct device state
|
|
*/
|
|
struct spss_utils_device {
|
|
/* char device info */
|
|
struct cdev *cdev;
|
|
dev_t device_no;
|
|
struct class *driver_class;
|
|
struct device *class_dev;
|
|
struct platform_device *pdev;
|
|
};
|
|
|
|
/* Device State */
|
|
static struct spss_utils_device *spss_utils_dev;
|
|
|
|
/* static functions declaration */
|
|
static int spss_set_fw_cmac(u32 *cmac, size_t cmac_size);
|
|
static int spss_get_pbl_and_apps_calc_cmac(void);
|
|
|
|
static int spss_get_saved_uefi_apps_cmac(void);
|
|
static int spss_set_saved_uefi_apps_cmac(void);
|
|
|
|
/*==========================================================================*/
|
|
/* Device Sysfs */
|
|
/*==========================================================================*/
|
|
|
|
static ssize_t firmware_name_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret;
|
|
|
|
if (!dev || !attr || !buf) {
|
|
pr_err("invalid param.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (firmware_name == NULL)
|
|
ret = scnprintf(buf, PAGE_SIZE, "%s\n", "unknown");
|
|
else
|
|
ret = scnprintf(buf, PAGE_SIZE, "%s\n", firmware_name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(firmware_name);
|
|
|
|
static ssize_t test_fuse_state_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret;
|
|
|
|
if (!dev || !attr || !buf) {
|
|
pr_err("invalid param.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (firmware_type) {
|
|
case SPSS_FW_TYPE_DEV:
|
|
ret = scnprintf(buf, PAGE_SIZE, "%s", "dev");
|
|
break;
|
|
case SPSS_FW_TYPE_TEST:
|
|
ret = scnprintf(buf, PAGE_SIZE, "%s", "test");
|
|
break;
|
|
case SPSS_FW_TYPE_PROD:
|
|
ret = scnprintf(buf, PAGE_SIZE, "%s", "prod");
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(test_fuse_state);
|
|
|
|
static ssize_t spss_debug_reg_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret;
|
|
void __iomem *spss_debug_reg = NULL;
|
|
u32 val1, val2;
|
|
|
|
if (!dev || !attr || !buf) {
|
|
pr_err("invalid param.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_debug("spss_debug_reg_addr [0x%x].\n", spss_debug_reg_addr);
|
|
|
|
spss_debug_reg = ioremap_nocache(spss_debug_reg_addr, sizeof(u32)*2);
|
|
|
|
if (!spss_debug_reg) {
|
|
pr_err("can't map debug reg addr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
val1 = readl_relaxed(spss_debug_reg);
|
|
val2 = readl_relaxed(((char *) spss_debug_reg) + sizeof(u32));
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE, "val1 [0x%x] val2 [0x%x]\n",
|
|
val1, val2);
|
|
|
|
iounmap(spss_debug_reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(spss_debug_reg);
|
|
|
|
static ssize_t cmac_buf_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!dev || !attr || !buf) {
|
|
pr_err("invalid param.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "0x%08x,0x%08x,0x%08x,0x%08x\n",
|
|
cmac_buf[0], cmac_buf[1], cmac_buf[2], cmac_buf[3]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(cmac_buf);
|
|
|
|
static ssize_t iar_state_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!dev || !attr || !buf) {
|
|
pr_err("invalid param.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* show IAR-STATE from soc fuse */
|
|
ret = snprintf(buf, PAGE_SIZE, "0x%x\n", iar_state);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(iar_state);
|
|
|
|
static ssize_t iar_enabled_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!dev || !attr || !buf) {
|
|
pr_err("invalid param.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "0x%x\n", is_iar_enabled);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(iar_enabled);
|
|
|
|
static ssize_t pbl_cmac_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!dev || !attr || !buf) {
|
|
pr_err("invalid param.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* first make sure the pbl cmac is updated */
|
|
spss_get_pbl_and_apps_calc_cmac();
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "0x%08x,0x%08x,0x%08x,0x%08x\n",
|
|
pbl_cmac_buf[0], pbl_cmac_buf[1], pbl_cmac_buf[2], pbl_cmac_buf[3]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(pbl_cmac);
|
|
|
|
static ssize_t apps_cmac_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
if (!dev || !attr || !buf) {
|
|
pr_err("invalid param.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* first make sure the pbl cmac is updated */
|
|
spss_get_pbl_and_apps_calc_cmac();
|
|
|
|
memcpy(buf, calc_apps_cmac, sizeof(calc_apps_cmac));
|
|
|
|
return sizeof(calc_apps_cmac);
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(apps_cmac);
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
static int spss_create_sysfs(struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = device_create_file(dev, &dev_attr_firmware_name);
|
|
if (ret < 0) {
|
|
pr_err("failed to create sysfs file for firmware_name.\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = device_create_file(dev, &dev_attr_test_fuse_state);
|
|
if (ret < 0) {
|
|
pr_err("failed to create sysfs file for test_fuse_state.\n");
|
|
goto remove_firmware_name;
|
|
}
|
|
|
|
ret = device_create_file(dev, &dev_attr_spss_debug_reg);
|
|
if (ret < 0) {
|
|
pr_err("failed to create sysfs file for spss_debug_reg.\n");
|
|
goto remove_test_fuse_state;
|
|
}
|
|
|
|
ret = device_create_file(dev, &dev_attr_cmac_buf);
|
|
if (ret < 0) {
|
|
pr_err("failed to create sysfs file for cmac_buf.\n");
|
|
goto remove_spss_debug_reg;
|
|
}
|
|
|
|
ret = device_create_file(dev, &dev_attr_iar_state);
|
|
if (ret < 0) {
|
|
pr_err("failed to create sysfs file for iar_state.\n");
|
|
goto remove_cmac_buf;
|
|
}
|
|
|
|
ret = device_create_file(dev, &dev_attr_iar_enabled);
|
|
if (ret < 0) {
|
|
pr_err("failed to create sysfs file for iar_enabled.\n");
|
|
goto remove_iar_state;
|
|
}
|
|
|
|
ret = device_create_file(dev, &dev_attr_pbl_cmac);
|
|
if (ret < 0) {
|
|
pr_err("failed to create sysfs file for pbl_cmac.\n");
|
|
goto remove_iar_enabled;
|
|
}
|
|
|
|
ret = device_create_file(dev, &dev_attr_apps_cmac);
|
|
if (ret < 0) {
|
|
pr_err("failed to create sysfs file for apps_cmac.\n");
|
|
goto remove_pbl_cmac;
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
remove_pbl_cmac:
|
|
device_remove_file(dev, &dev_attr_pbl_cmac);
|
|
remove_iar_enabled:
|
|
device_remove_file(dev, &dev_attr_iar_enabled);
|
|
remove_iar_state:
|
|
device_remove_file(dev, &dev_attr_iar_state);
|
|
remove_cmac_buf:
|
|
device_remove_file(dev, &dev_attr_cmac_buf);
|
|
remove_spss_debug_reg:
|
|
device_remove_file(dev, &dev_attr_spss_debug_reg);
|
|
remove_test_fuse_state:
|
|
device_remove_file(dev, &dev_attr_test_fuse_state);
|
|
remove_firmware_name:
|
|
device_remove_file(dev, &dev_attr_firmware_name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*==========================================================================*/
|
|
/* IOCTL */
|
|
/*==========================================================================*/
|
|
static int spss_wait_for_event(struct spss_ioc_wait_for_event *req)
|
|
{
|
|
int ret;
|
|
uint32_t event_id;
|
|
uint32_t timeout_sec;
|
|
long timeleft = 1;
|
|
|
|
event_id = req->event_id;
|
|
timeout_sec = req->timeout_sec;
|
|
|
|
if (event_id >= SPSS_NUM_EVENTS) {
|
|
pr_err("event_id [%d] invalid\n", event_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_debug("wait for event [%d], timeout_sec [%d]\n",
|
|
event_id, timeout_sec);
|
|
|
|
if (timeout_sec) {
|
|
unsigned long jiffies = 0;
|
|
|
|
jiffies = msecs_to_jiffies(timeout_sec*1000);
|
|
timeleft = wait_for_completion_interruptible_timeout(
|
|
&spss_events[event_id], jiffies);
|
|
ret = timeleft;
|
|
} else {
|
|
ret = wait_for_completion_interruptible(
|
|
&spss_events[event_id]);
|
|
}
|
|
|
|
if (timeleft == 0) {
|
|
pr_err("wait for event [%d] timeout [%d] sec expired\n",
|
|
event_id, timeout_sec);
|
|
req->status = EVENT_STATUS_TIMEOUT;
|
|
} else if (ret < 0) {
|
|
pr_err("wait for event [%d] interrupted. ret [%d]\n",
|
|
event_id, ret);
|
|
req->status = EVENT_STATUS_ABORTED;
|
|
if (ret == -ERESTARTSYS) /* handle LPM event */
|
|
return ret;
|
|
} else {
|
|
pr_debug("wait for event [%d] completed.\n", event_id);
|
|
req->status = EVENT_STATUS_SIGNALED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spss_signal_event(struct spss_ioc_signal_event *req)
|
|
{
|
|
uint32_t event_id;
|
|
|
|
mutex_lock(&event_lock);
|
|
|
|
event_id = req->event_id;
|
|
|
|
if (event_id >= SPSS_NUM_EVENTS) {
|
|
pr_err("event_id [%d] invalid\n", event_id);
|
|
mutex_unlock(&event_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (spss_events_signaled[event_id]) {
|
|
pr_err("event_id [%d] already signaled\n", event_id);
|
|
mutex_unlock(&event_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_debug("signal event [%d]\n", event_id);
|
|
complete_all(&spss_events[event_id]);
|
|
req->status = EVENT_STATUS_SIGNALED;
|
|
spss_events_signaled[event_id] = true;
|
|
|
|
mutex_unlock(&event_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spss_is_event_signaled(struct spss_ioc_is_signaled *req)
|
|
{
|
|
uint32_t event_id;
|
|
|
|
mutex_lock(&event_lock);
|
|
|
|
event_id = req->event_id;
|
|
|
|
if (event_id >= SPSS_NUM_EVENTS) {
|
|
pr_err("event_id [%d] invalid\n", event_id);
|
|
mutex_unlock(&event_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (spss_events_signaled[event_id])
|
|
req->status = EVENT_STATUS_SIGNALED;
|
|
else
|
|
req->status = EVENT_STATUS_NOT_SIGNALED;
|
|
|
|
mutex_unlock(&event_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long spss_utils_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret;
|
|
void *buf = (void *) arg;
|
|
unsigned char data[64] = {0};
|
|
size_t size = 0;
|
|
u32 i = 0;
|
|
/* Saved cmacs of spu firmware and UEFI loaded spu apps */
|
|
u32 fw_and_apps_cmacs[FW_AND_APPS_CMAC_SIZE];
|
|
void *req = (void *) data;
|
|
|
|
if (buf == NULL) {
|
|
pr_err("invalid ioctl arg\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
size = _IOC_SIZE(cmd);
|
|
if (size && (cmd & IOC_IN)) {
|
|
if (size > sizeof(data)) {
|
|
pr_err("cmd [0x%x] size [0x%x] too large\n",
|
|
cmd, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (copy_from_user(data, (void __user *)arg, size)) {
|
|
pr_err("copy_from_user() failed, cmd [0x%x]\n",
|
|
cmd, size);
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
switch (cmd) {
|
|
case SPSS_IOC_SET_FW_CMAC:
|
|
if (size != sizeof(fw_and_apps_cmacs)) {
|
|
pr_err("cmd [0x%x] invalid size [0x%x]\n", cmd, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* spdaemon uses this ioctl only when IAR is active */
|
|
is_iar_active = true;
|
|
|
|
memcpy(fw_and_apps_cmacs, data, sizeof(fw_and_apps_cmacs));
|
|
memcpy(cmac_buf, fw_and_apps_cmacs, sizeof(cmac_buf));
|
|
|
|
for (i = 0; i < NUM_UEFI_APPS; ++i) {
|
|
int x = (i+1)*CMAC_SIZE_IN_DWORDS;
|
|
|
|
memcpy(saved_apps_cmac[i],
|
|
fw_and_apps_cmacs + x,
|
|
CMAC_SIZE_IN_BYTES);
|
|
}
|
|
|
|
/*
|
|
* SPSS is loaded now by UEFI,
|
|
* so IAR callback is not being called on power-up by PIL.
|
|
* therefore read the spu pbl fw cmac and apps cmac from ioctl.
|
|
* The callback shall be called on spss SSR.
|
|
*/
|
|
pr_debug("read pbl cmac from shared memory\n");
|
|
spss_set_fw_cmac(cmac_buf, sizeof(cmac_buf));
|
|
spss_set_saved_uefi_apps_cmac();
|
|
spss_get_saved_uefi_apps_cmac();
|
|
break;
|
|
|
|
case SPSS_IOC_WAIT_FOR_EVENT:
|
|
/* check input params */
|
|
if (size != sizeof(struct spss_ioc_wait_for_event)) {
|
|
pr_err("cmd [0x%x] invalid size [0x%x]\n", cmd, size);
|
|
return -EINVAL;
|
|
}
|
|
ret = spss_wait_for_event(req);
|
|
copy_to_user((void __user *)arg, data, size);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
|
|
case SPSS_IOC_SIGNAL_EVENT:
|
|
/* check input params */
|
|
if (size != sizeof(struct spss_ioc_signal_event)) {
|
|
pr_err("cmd [0x%x] invalid size [0x%x]\n", cmd, size);
|
|
return -EINVAL;
|
|
}
|
|
ret = spss_signal_event(req);
|
|
copy_to_user((void __user *)arg, data, size);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
|
|
case SPSS_IOC_IS_EVENT_SIGNALED:
|
|
/* check input params */
|
|
if (size != sizeof(struct spss_ioc_is_signaled)) {
|
|
pr_err("cmd [0x%x] invalid size [0x%x]\n", cmd, size);
|
|
return -EINVAL;
|
|
}
|
|
ret = spss_is_event_signaled(req);
|
|
copy_to_user((void __user *)arg, data, size);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
|
|
default:
|
|
pr_err("invalid ioctl cmd [0x%x]\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations spss_utils_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = spss_utils_ioctl,
|
|
.compat_ioctl = spss_utils_ioctl,
|
|
};
|
|
|
|
static int spss_utils_create_chardev(struct device *dev)
|
|
{
|
|
int ret;
|
|
unsigned int baseminor = 0;
|
|
unsigned int count = 1;
|
|
void *priv = (void *) spss_utils_dev;
|
|
|
|
spss_utils_dev->cdev =
|
|
kzalloc(sizeof(*spss_utils_dev->cdev), GFP_KERNEL);
|
|
if (!spss_utils_dev->cdev)
|
|
return -ENOMEM;
|
|
|
|
/* get device_no */
|
|
ret = alloc_chrdev_region(&spss_utils_dev->device_no, baseminor, count,
|
|
DEVICE_NAME);
|
|
if (ret < 0) {
|
|
pr_err("alloc_chrdev_region failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
spss_utils_dev->driver_class = class_create(THIS_MODULE, DEVICE_NAME);
|
|
if (IS_ERR(spss_utils_dev->driver_class)) {
|
|
ret = -ENOMEM;
|
|
pr_err("class_create failed %d\n", ret);
|
|
goto exit_unreg_chrdev_region;
|
|
}
|
|
|
|
spss_utils_dev->class_dev =
|
|
device_create(spss_utils_dev->driver_class, NULL,
|
|
spss_utils_dev->device_no, priv,
|
|
DEVICE_NAME);
|
|
|
|
if (IS_ERR(spss_utils_dev->class_dev)) {
|
|
pr_err("class_device_create failed %d\n", ret);
|
|
ret = -ENOMEM;
|
|
goto exit_destroy_class;
|
|
}
|
|
|
|
cdev_init(spss_utils_dev->cdev, &spss_utils_fops);
|
|
spss_utils_dev->cdev->owner = THIS_MODULE;
|
|
|
|
ret = cdev_add(spss_utils_dev->cdev,
|
|
MKDEV(MAJOR(spss_utils_dev->device_no), 0),
|
|
1);
|
|
if (ret < 0) {
|
|
pr_err("cdev_add failed %d\n", ret);
|
|
goto exit_destroy_device;
|
|
}
|
|
|
|
pr_debug("char device created.\n");
|
|
return 0;
|
|
|
|
exit_destroy_device:
|
|
device_destroy(spss_utils_dev->driver_class, spss_utils_dev->device_no);
|
|
exit_destroy_class:
|
|
class_destroy(spss_utils_dev->driver_class);
|
|
exit_unreg_chrdev_region:
|
|
unregister_chrdev_region(spss_utils_dev->device_no, 1);
|
|
return ret;
|
|
}
|
|
|
|
/*==========================================================================*/
|
|
/* Device Tree */
|
|
/*==========================================================================*/
|
|
|
|
/**
|
|
* spss_parse_dt() - Parse Device Tree info.
|
|
*/
|
|
static int spss_parse_dt(struct device_node *node)
|
|
{
|
|
int ret;
|
|
u32 spss_fuse1_addr = 0;
|
|
u32 spss_fuse1_bit = 0;
|
|
u32 spss_fuse1_mask = 0;
|
|
void __iomem *spss_fuse1_reg = NULL;
|
|
u32 spss_fuse2_addr = 0;
|
|
u32 spss_fuse2_bit = 0;
|
|
u32 spss_fuse2_mask = 0;
|
|
void __iomem *spss_fuse2_reg = NULL;
|
|
/* IAR_FEATURE_ENABLED soc fuse */
|
|
u32 spss_fuse3_addr = 0;
|
|
u32 spss_fuse3_bit = 0;
|
|
u32 spss_fuse3_mask = 0;
|
|
void __iomem *spss_fuse3_reg = NULL;
|
|
/* IAR_STATE soc fuses */
|
|
u32 spss_fuse4_addr = 0;
|
|
u32 spss_fuse4_bit = 0;
|
|
u32 spss_fuse4_mask = 0;
|
|
void __iomem *spss_fuse4_reg = NULL;
|
|
struct device_node *np;
|
|
struct resource r;
|
|
u32 val1 = 0;
|
|
u32 val2 = 0;
|
|
void __iomem *spss_emul_type_reg = NULL;
|
|
u32 spss_emul_type_val = 0;
|
|
|
|
ret = of_property_read_string(node, "qcom,spss-dev-firmware-name",
|
|
&dev_firmware_name);
|
|
if (ret < 0) {
|
|
pr_err("can't get dev fw name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_string(node, "qcom,spss-test-firmware-name",
|
|
&test_firmware_name);
|
|
if (ret < 0) {
|
|
pr_err("can't get test fw name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_string(node, "qcom,spss-prod-firmware-name",
|
|
&prod_firmware_name);
|
|
if (ret < 0) {
|
|
pr_err("can't get prod fw name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-fuse1-addr",
|
|
&spss_fuse1_addr);
|
|
if (ret < 0) {
|
|
pr_err("can't get fuse1 addr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-fuse2-addr",
|
|
&spss_fuse2_addr);
|
|
if (ret < 0) {
|
|
pr_err("can't get fuse2 addr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-fuse1-bit",
|
|
&spss_fuse1_bit);
|
|
if (ret < 0) {
|
|
pr_err("can't get fuse1 bit\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-fuse2-bit",
|
|
&spss_fuse2_bit);
|
|
if (ret < 0) {
|
|
pr_err("can't get fuse2 bit\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
spss_fuse1_mask = BIT(spss_fuse1_bit);
|
|
spss_fuse2_mask = BIT(spss_fuse2_bit);
|
|
|
|
pr_debug("spss fuse1 addr [0x%x] bit [%d]\n",
|
|
(int) spss_fuse1_addr, (int) spss_fuse1_bit);
|
|
pr_debug("spss fuse2 addr [0x%x] bit [%d]\n",
|
|
(int) spss_fuse2_addr, (int) spss_fuse2_bit);
|
|
|
|
spss_fuse1_reg = ioremap_nocache(spss_fuse1_addr, sizeof(u32));
|
|
|
|
if (!spss_fuse1_reg) {
|
|
pr_err("can't map fuse1 addr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
spss_fuse2_reg = ioremap_nocache(spss_fuse2_addr, sizeof(u32));
|
|
|
|
if (!spss_fuse2_reg) {
|
|
iounmap(spss_fuse1_reg);
|
|
pr_err("can't map fuse2 addr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
val1 = readl_relaxed(spss_fuse1_reg);
|
|
val2 = readl_relaxed(spss_fuse2_reg);
|
|
|
|
pr_debug("spss fuse1 value [0x%08x]\n", (int) val1);
|
|
pr_debug("spss fuse2 value [0x%08x]\n", (int) val2);
|
|
|
|
pr_debug("spss fuse1 mask [0x%08x]\n", (int) spss_fuse1_mask);
|
|
pr_debug("spss fuse2 mask [0x%08x]\n", (int) spss_fuse2_mask);
|
|
|
|
/**
|
|
* Set firmware_type based on fuses:
|
|
* SPSS_CONFIG_MODE 11: dev
|
|
* SPSS_CONFIG_MODE 01 or 10: test
|
|
* SPSS_CONFIG_MODE 00: prod
|
|
*/
|
|
if ((val1 & spss_fuse1_mask) && (val2 & spss_fuse2_mask))
|
|
firmware_type = SPSS_FW_TYPE_DEV;
|
|
else if ((val1 & spss_fuse1_mask) || (val2 & spss_fuse2_mask))
|
|
firmware_type = SPSS_FW_TYPE_TEST;
|
|
else
|
|
firmware_type = SPSS_FW_TYPE_PROD;
|
|
|
|
iounmap(spss_fuse1_reg);
|
|
iounmap(spss_fuse2_reg);
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-debug-reg-addr",
|
|
&spss_debug_reg_addr);
|
|
if (ret < 0) {
|
|
pr_err("can't get debug regs addr\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-emul-type-reg-addr",
|
|
&spss_emul_type_reg_addr);
|
|
if (ret < 0) {
|
|
pr_err("can't get spss-emulation-type-reg addr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
spss_emul_type_reg = ioremap_nocache(spss_emul_type_reg_addr,
|
|
sizeof(u32));
|
|
if (!spss_emul_type_reg) {
|
|
pr_err("can't map soc-emulation-type reg addr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
spss_emul_type_val = readl_relaxed(spss_emul_type_reg);
|
|
|
|
pr_debug("spss_emul_type value [0x%08x]\n", (int)spss_emul_type_val);
|
|
if ((spss_emul_type_val & SPU_EMULATUION) &&
|
|
!(spss_emul_type_val & SPU_PRESENT_IN_EMULATION)) {
|
|
/* for some emulation platforms SPSS is not present */
|
|
firmware_type = SPSS_FW_TYPE_NONE;
|
|
}
|
|
iounmap(spss_emul_type_reg);
|
|
|
|
/* PIL-SPSS area */
|
|
np = of_parse_phandle(node, "pil-mem", 0);
|
|
if (!np) {
|
|
pr_err("no pil-mem entry, check pil-addr\n");
|
|
ret = of_property_read_u32(node, "qcom,pil-addr",
|
|
&pil_addr);
|
|
if (ret < 0) {
|
|
pr_err("can't get pil_addr\n");
|
|
return -EFAULT;
|
|
}
|
|
} else {
|
|
ret = of_address_to_resource(np, 0, &r);
|
|
of_node_put(np);
|
|
if (ret)
|
|
return ret;
|
|
pil_addr = (u32)r.start;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "qcom,pil-size",
|
|
&pil_size);
|
|
if (ret < 0) {
|
|
pr_err("can't get pil_size\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
pr_debug("pil_addr [0x%08x].\n", pil_addr);
|
|
pr_debug("pil_size [0x%08x].\n", pil_size);
|
|
|
|
/* cmac buffer after spss firmware end */
|
|
cmac_mem_addr = pil_addr + pil_size;
|
|
pr_info("iar_buf_addr [0x%08x].\n", cmac_mem_addr);
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-fuse3-addr",
|
|
&spss_fuse3_addr);
|
|
if (ret < 0) {
|
|
pr_err("can't get fuse3 addr.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-fuse3-bit",
|
|
&spss_fuse3_bit);
|
|
if (ret < 0) {
|
|
pr_err("can't get fuse3 bit.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
spss_fuse3_reg = ioremap_nocache(spss_fuse3_addr, sizeof(u32));
|
|
|
|
if (!spss_fuse3_reg) {
|
|
pr_err("can't map fuse3 addr.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* read IAR_FEATURE_ENABLED from soc fuse */
|
|
val1 = readl_relaxed(spss_fuse3_reg);
|
|
spss_fuse3_mask = (1<<spss_fuse3_bit);
|
|
pr_debug("iar_enabled fuse, addr [0x%x] val [0x%x] mask [0x%x].\n",
|
|
spss_fuse3_addr, val1, spss_fuse3_mask);
|
|
if (val1 & spss_fuse3_mask)
|
|
is_iar_enabled = true;
|
|
else
|
|
is_iar_enabled = false;
|
|
|
|
memset(cmac_buf, 0xA5, sizeof(cmac_buf));
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-fuse4-addr",
|
|
&spss_fuse4_addr);
|
|
if (ret < 0) {
|
|
pr_err("can't get fuse4 addr.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "qcom,spss-fuse4-bit",
|
|
&spss_fuse4_bit);
|
|
if (ret < 0) {
|
|
pr_err("can't get fuse4 bit.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
spss_fuse4_reg = ioremap_nocache(spss_fuse4_addr, sizeof(u32));
|
|
|
|
if (!spss_fuse4_reg) {
|
|
pr_err("can't map fuse4 addr.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
val1 = readl_relaxed(spss_fuse4_reg);
|
|
spss_fuse4_mask = (0x07 << spss_fuse4_bit); /* 3 bits */
|
|
pr_debug("IAR_STATE fuse, addr [0x%x] val [0x%x] mask [0x%x].\n",
|
|
spss_fuse4_addr, val1, spss_fuse4_mask);
|
|
val1 = ((val1 & spss_fuse4_mask) >> spss_fuse4_bit) & 0x07;
|
|
|
|
iar_state = val1;
|
|
|
|
pr_debug("iar_state [%d]\n", iar_state);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spss_set_fw_cmac(u32 *cmac, size_t cmac_size)
|
|
{
|
|
u8 __iomem *reg = NULL;
|
|
int i;
|
|
|
|
if (cmac_mem == NULL) {
|
|
cmac_mem = ioremap_nocache(cmac_mem_addr, cmac_mem_size);
|
|
if (!cmac_mem) {
|
|
pr_err("can't map cmac_mem.\n");
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
pr_debug("pil_addr [0x%x]\n", pil_addr);
|
|
pr_debug("pil_size [0x%x]\n", pil_size);
|
|
pr_debug("cmac_mem [%pK]\n", cmac_mem);
|
|
reg = cmac_mem;
|
|
pr_debug("reg [%pK]\n", reg);
|
|
|
|
for (i = 0; i < cmac_size/4; i++) {
|
|
writel_relaxed(cmac[i], reg + i*sizeof(u32));
|
|
pr_debug("cmac[%d] [0x%x]\n", i, cmac[i]);
|
|
}
|
|
reg += cmac_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spss_get_pbl_and_apps_calc_cmac(void)
|
|
{
|
|
u8 __iomem *reg = NULL;
|
|
int i, j;
|
|
u32 val;
|
|
|
|
if (cmac_mem == NULL)
|
|
return -EFAULT;
|
|
|
|
reg = cmac_mem; /* IAR buffer base */
|
|
reg += CMAC_SIZE_IN_BYTES; /* skip the saved cmac */
|
|
pr_debug("reg [%pK]\n", reg);
|
|
|
|
/* get pbl fw cmac from ddr */
|
|
for (i = 0; i < CMAC_SIZE_IN_DWORDS; i++) {
|
|
val = readl_relaxed(reg);
|
|
pbl_cmac_buf[i] = val;
|
|
reg += sizeof(u32);
|
|
}
|
|
reg += CMAC_SIZE_IN_BYTES; /* skip the saved cmac */
|
|
|
|
pr_debug("pbl_cmac_buf : 0x%08x,0x%08x,0x%08x,0x%08x\n",
|
|
pbl_cmac_buf[0], pbl_cmac_buf[1],
|
|
pbl_cmac_buf[2], pbl_cmac_buf[3]);
|
|
|
|
/* get apps cmac from ddr */
|
|
for (j = 0; j < NUM_UEFI_APPS; j++) {
|
|
for (i = 0; i < CMAC_SIZE_IN_DWORDS; i++) {
|
|
val = readl_relaxed(reg);
|
|
calc_apps_cmac[j][i] = val;
|
|
reg += sizeof(u32);
|
|
}
|
|
reg += CMAC_SIZE_IN_BYTES; /* skip the saved cmac */
|
|
|
|
pr_debug("app [%d] cmac : 0x%08x,0x%08x,0x%08x,0x%08x\n", j,
|
|
calc_apps_cmac[j][0], calc_apps_cmac[j][1],
|
|
calc_apps_cmac[j][2], calc_apps_cmac[j][3]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spss_get_saved_uefi_apps_cmac(void)
|
|
{
|
|
u8 __iomem *reg = NULL;
|
|
int i, j;
|
|
u32 val;
|
|
|
|
if (cmac_mem == NULL)
|
|
return -EFAULT;
|
|
|
|
reg = cmac_mem; /* IAR buffer base */
|
|
reg += (2*CMAC_SIZE_IN_BYTES); /* skip the saved and calc fw cmac */
|
|
pr_debug("reg [%pK]\n", reg);
|
|
|
|
/* get saved apps cmac from ddr - were written by UEFI spss driver */
|
|
for (j = 0; j < NUM_UEFI_APPS; j++) {
|
|
for (i = 0; i < CMAC_SIZE_IN_DWORDS; i++) {
|
|
val = readl_relaxed(reg);
|
|
saved_apps_cmac[j][i] = val;
|
|
reg += sizeof(u32);
|
|
}
|
|
reg += CMAC_SIZE_IN_BYTES; /* skip the calc cmac */
|
|
|
|
pr_debug("app[%d] saved cmac: 0x%08x,0x%08x,0x%08x,0x%08x\n",
|
|
j,
|
|
saved_apps_cmac[j][0], saved_apps_cmac[j][1],
|
|
saved_apps_cmac[j][2], saved_apps_cmac[j][3]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spss_set_saved_uefi_apps_cmac(void)
|
|
{
|
|
u8 __iomem *reg = NULL;
|
|
int i, j;
|
|
u32 val;
|
|
|
|
if (cmac_mem == NULL)
|
|
return -EFAULT;
|
|
|
|
reg = cmac_mem; /* IAR buffer base */
|
|
reg += (2*CMAC_SIZE_IN_BYTES); /* skip the saved and calc fw cmac */
|
|
pr_debug("reg [%pK]\n", reg);
|
|
|
|
/* get saved apps cmac from ddr - were written by UEFI spss driver */
|
|
for (j = 0; j < NUM_UEFI_APPS; j++) {
|
|
for (i = 0; i < CMAC_SIZE_IN_DWORDS; i++) {
|
|
val = saved_apps_cmac[j][i];
|
|
writel_relaxed(val, reg);
|
|
reg += sizeof(u32);
|
|
}
|
|
reg += CMAC_SIZE_IN_BYTES; /* skip the calc app cmac */
|
|
|
|
pr_debug("app[%d] saved cmac: 0x%08x,0x%08x,0x%08x,0x%08x\n",
|
|
j,
|
|
saved_apps_cmac[j][0], saved_apps_cmac[j][1],
|
|
saved_apps_cmac[j][2], saved_apps_cmac[j][3]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spss_utils_pil_callback(struct notifier_block *nb,
|
|
unsigned long code,
|
|
void *data)
|
|
{
|
|
int i, event_id;
|
|
|
|
switch (code) {
|
|
case SUBSYS_BEFORE_SHUTDOWN:
|
|
pr_debug("[SUBSYS_BEFORE_SHUTDOWN] event.\n");
|
|
mutex_lock(&event_lock);
|
|
/* Reset NVM-ready and SPU-ready events */
|
|
for (i = SPSS_EVENT_ID_NVM_READY;
|
|
i <= SPSS_EVENT_ID_SPU_READY; i++) {
|
|
reinit_completion(&spss_events[i]);
|
|
spss_events_signaled[i] = false;
|
|
}
|
|
mutex_unlock(&event_lock);
|
|
pr_debug("reset spss events.\n");
|
|
break;
|
|
case SUBSYS_AFTER_SHUTDOWN:
|
|
pr_debug("[SUBSYS_AFTER_SHUTDOWN] event.\n");
|
|
mutex_lock(&event_lock);
|
|
event_id = SPSS_EVENT_ID_SPU_POWER_DOWN;
|
|
complete_all(&spss_events[event_id]);
|
|
spss_events_signaled[event_id] = true;
|
|
|
|
event_id = SPSS_EVENT_ID_SPU_POWER_UP;
|
|
reinit_completion(&spss_events[event_id]);
|
|
spss_events_signaled[event_id] = false;
|
|
mutex_unlock(&event_lock);
|
|
break;
|
|
case SUBSYS_BEFORE_POWERUP:
|
|
pr_debug("[SUBSYS_BEFORE_POWERUP] event.\n");
|
|
break;
|
|
case SUBSYS_AFTER_POWERUP:
|
|
pr_debug("[SUBSYS_AFTER_POWERUP] event.\n");
|
|
mutex_lock(&event_lock);
|
|
event_id = SPSS_EVENT_ID_SPU_POWER_UP;
|
|
complete_all(&spss_events[event_id]);
|
|
spss_events_signaled[event_id] = true;
|
|
|
|
event_id = SPSS_EVENT_ID_SPU_POWER_DOWN;
|
|
reinit_completion(&spss_events[event_id]);
|
|
spss_events_signaled[event_id] = false;
|
|
mutex_unlock(&event_lock);
|
|
break;
|
|
case SUBSYS_RAMDUMP_NOTIFICATION:
|
|
pr_debug("[SUBSYS_RAMDUMP_NOTIFICATION] event.\n");
|
|
break;
|
|
case SUBSYS_PROXY_VOTE:
|
|
pr_debug("[SUBSYS_PROXY_VOTE] event.\n");
|
|
break;
|
|
case SUBSYS_PROXY_UNVOTE:
|
|
pr_debug("[SUBSYS_PROXY_UNVOTE] event.\n");
|
|
break;
|
|
case SUBSYS_BEFORE_AUTH_AND_RESET:
|
|
/* do nothing if IAR is not active */
|
|
if (!is_iar_active)
|
|
return NOTIFY_OK;
|
|
pr_debug("[SUBSYS_BEFORE_AUTH_AND_RESET] event.\n");
|
|
/* Called on SSR as spss firmware is loaded by UEFI */
|
|
spss_set_fw_cmac(cmac_buf, sizeof(cmac_buf));
|
|
spss_set_saved_uefi_apps_cmac();
|
|
break;
|
|
default:
|
|
pr_err("unknown code [0x%x] .\n", (int) code);
|
|
break;
|
|
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
/**
|
|
* spss_probe() - initialization sequence
|
|
*/
|
|
static int spss_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
int i;
|
|
struct device_node *np = NULL;
|
|
struct device *dev = NULL;
|
|
|
|
if (!pdev) {
|
|
pr_err("invalid pdev.\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
np = pdev->dev.of_node;
|
|
if (!np) {
|
|
pr_err("invalid DT node.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
spss_utils_dev = kzalloc(sizeof(*spss_utils_dev), GFP_KERNEL);
|
|
if (spss_utils_dev == NULL)
|
|
return -ENOMEM;
|
|
|
|
dev = &pdev->dev;
|
|
spss_dev = dev;
|
|
|
|
if (dev == NULL) {
|
|
pr_err("invalid dev.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, dev);
|
|
|
|
ret = spss_parse_dt(np);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
switch (firmware_type) {
|
|
case SPSS_FW_TYPE_DEV:
|
|
firmware_name = dev_firmware_name;
|
|
break;
|
|
case SPSS_FW_TYPE_TEST:
|
|
firmware_name = test_firmware_name;
|
|
break;
|
|
case SPSS_FW_TYPE_PROD:
|
|
firmware_name = prod_firmware_name;
|
|
break;
|
|
case SPSS_FW_TYPE_NONE:
|
|
firmware_name = none_firmware_name;
|
|
break;
|
|
default:
|
|
pr_err("invalid firmware type %d, sysfs entry not created\n",
|
|
firmware_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = subsystem_set_fwname("spss", firmware_name);
|
|
if (ret < 0) {
|
|
pr_err("fail to set fw name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = spss_utils_create_chardev(dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = spss_create_sysfs(dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
pr_info("Initialization completed ok, firmware_name [%s].\n",
|
|
firmware_name);
|
|
|
|
iar_nb = kzalloc(sizeof(*iar_nb), GFP_KERNEL);
|
|
if (!iar_nb)
|
|
return -ENOMEM;
|
|
|
|
iar_nb->notifier_call = spss_utils_pil_callback;
|
|
|
|
iar_notif_handle = subsys_notif_register_notifier("spss", iar_nb);
|
|
if (IS_ERR_OR_NULL(iar_notif_handle)) {
|
|
pr_err("register fail for IAR notifier\n");
|
|
kfree(iar_nb);
|
|
}
|
|
|
|
for (i = 0 ; i < SPSS_NUM_EVENTS; i++) {
|
|
init_completion(&spss_events[i]);
|
|
spss_events_signaled[i] = false;
|
|
}
|
|
mutex_init(&event_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id spss_match_table[] = {
|
|
{ .compatible = "qcom,spss-utils", },
|
|
{ },
|
|
};
|
|
|
|
static struct platform_driver spss_driver = {
|
|
.probe = spss_probe,
|
|
.driver = {
|
|
.name = DEVICE_NAME,
|
|
.of_match_table = of_match_ptr(spss_match_table),
|
|
},
|
|
};
|
|
|
|
/*==========================================================================*/
|
|
/* Driver Init/Exit */
|
|
/*==========================================================================*/
|
|
static int __init spss_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = platform_driver_register(&spss_driver);
|
|
if (ret)
|
|
pr_err("register platform driver failed, ret [%d]\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
late_initcall(spss_init); /* start after PIL driver */
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Secure Processor Utilities");
|