You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
475 lines
11 KiB
475 lines
11 KiB
/*
|
|
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that 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.
|
|
*/
|
|
|
|
/*
|
|
* 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/io.h> /* ioremap_nocache() */
|
|
|
|
#include <soc/qcom/subsystem_restart.h>
|
|
|
|
/* 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 */
|
|
|
|
#define SPU_EMULATUION (BIT(0) | BIT(1))
|
|
#define SPU_PRESENT_IN_EMULATION BIT(2)
|
|
|
|
/*==========================================================================*/
|
|
/* 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 = snprintf(buf, PAGE_SIZE, "%s\n", "unknown");
|
|
else
|
|
ret = snprintf(buf, PAGE_SIZE, "%s\n", firmware_name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t firmware_name_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t size)
|
|
{
|
|
pr_err("set firmware name is not allowed.\n");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static DEVICE_ATTR(firmware_name, 0444,
|
|
firmware_name_show, firmware_name_store);
|
|
|
|
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 = snprintf(buf, PAGE_SIZE, "%s", "dev");
|
|
break;
|
|
case SPSS_FW_TYPE_TEST:
|
|
ret = snprintf(buf, PAGE_SIZE, "%s", "test");
|
|
break;
|
|
case SPSS_FW_TYPE_PROD:
|
|
ret = snprintf(buf, PAGE_SIZE, "%s", "prod");
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t test_fuse_state_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t size)
|
|
{
|
|
pr_err("set test fuse state is not allowed.\n");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static DEVICE_ATTR(test_fuse_state, 0444,
|
|
test_fuse_state_show, test_fuse_state_store);
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
val1 = readl_relaxed(spss_debug_reg);
|
|
val2 = readl_relaxed(((char *) spss_debug_reg) + sizeof(u32));
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "val1 [0x%x] val2 [0x%x]", val1, val2);
|
|
|
|
iounmap(spss_debug_reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t spss_debug_reg_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t size)
|
|
{
|
|
pr_err("set debug reg is not allowed.\n");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static DEVICE_ATTR(spss_debug_reg, 0444,
|
|
spss_debug_reg_show, spss_debug_reg_store);
|
|
|
|
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");
|
|
device_remove_file(dev, &dev_attr_firmware_name);
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
device_remove_file(dev, &dev_attr_firmware_name);
|
|
device_remove_file(dev, &dev_attr_test_fuse_state);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*==========================================================================*/
|
|
/* 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;
|
|
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 -EFAULT;
|
|
}
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
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 -EFAULT;
|
|
}
|
|
|
|
spss_emul_type_reg = ioremap_nocache(spss_emul_type_reg_addr,
|
|
sizeof(u32));
|
|
if (!spss_emul_type_reg) {
|
|
iounmap(spss_emul_type_reg);
|
|
pr_err("can't map soc-emulation-type reg addr.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* spss_probe() - initialization sequence
|
|
*/
|
|
static int spss_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
pr_err("fail to parse device tree.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
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:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = subsystem_set_fwname("spss", firmware_name);
|
|
if (ret < 0) {
|
|
pr_err("fail to set fw name.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = spss_create_sysfs(dev);
|
|
if (ret < 0) {
|
|
pr_err("fail to create sysfs.\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
pr_info("Initialization completed ok, firmware_name [%s].\n",
|
|
firmware_name);
|
|
|
|
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,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(spss_match_table),
|
|
},
|
|
};
|
|
|
|
/*==========================================================================*/
|
|
/* Driver Init/Exit */
|
|
/*==========================================================================*/
|
|
static int __init spss_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
pr_info("spss-utils driver Ver 3.0 18-Feb-2018.\n");
|
|
|
|
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");
|
|
|