blob: 5fbe66850e45756bde7698465a44113b7cb54360 [file] [log] [blame]
/*
* Copyright (c) 2015-2017 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.
*/
#include <ubi_uboot.h>
#include <linux/stddef.h>
#include <linux/compat.h>
#include <asm-generic/errno.h>
#include <asm/io.h>
#include <asm/arch-qca-common/scm.h>
#include <common.h>
/**
* alloc_scm_command() - Allocate an SCM command
* @cmd_size: size of the command buffer
* @resp_size: size of the response buffer
*
* Allocate an SCM command, including enough room for the command
* and response headers as well as the command and response buffers.
*
* Returns a valid &scm_command on success or %NULL if the allocation fails.
*/
static struct scm_command *alloc_scm_command(size_t cmd_size, size_t resp_size)
{
struct scm_command *cmd;
size_t len;
len = sizeof(*cmd) + sizeof(struct scm_response) + cmd_size;
if (cmd_size > len)
return NULL;
len += resp_size;
if (resp_size > len)
return NULL;
cmd = kzalloc(PAGE_ALIGN(len), GFP_KERNEL);
if (cmd) {
cmd->len = len;
cmd->buf_offset = offsetof(struct scm_command, buf);
cmd->resp_hdr_offset = cmd->buf_offset + cmd_size;
}
return cmd;
}
/**
* free_scm_command() - Free an SCM command
* @cmd: command to free
*
* Free an SCM command.
*/
static inline void free_scm_command(struct scm_command *cmd)
{
kfree(cmd);
}
/**
* scm_command_to_response() - Get a pointer to a scm_response
* @cmd: command
*
* Returns a pointer to a response for a command.
*/
static inline struct scm_response *scm_command_to_response(
const struct scm_command *cmd)
{
return (void *)cmd + cmd->resp_hdr_offset;
}
/**
* scm_get_command_buffer() - Get a pointer to a command buffer
* @cmd: command
*
* Returns a pointer to the command buffer of a command.
*/
static inline void *scm_get_command_buffer(const struct scm_command *cmd)
{
return (void *)cmd->buf;
}
/**
* scm_get_response_buffer() - Get a pointer to a response buffer
* @rsp: response
*
* Returns a pointer to a response buffer of a response.
*/
static inline void *scm_get_response_buffer(const struct scm_response *rsp)
{
return (void *)rsp + rsp->buf_offset;
}
static int scm_remap_error(int err)
{
switch (err) {
case SCM_ERROR:
return -EIO;
case SCM_EINVAL_ADDR:
case SCM_EINVAL_ARG:
return -EINVAL;
case SCM_EOPNOTSUPP:
return -EOPNOTSUPP;
case SCM_ENOMEM:
return -ENOMEM;
case SCM_EBUSY:
return -EBUSY;
}
return -EINVAL;
}
static u32 smc(u32 cmd_addr)
{
int context_id;
register u32 r0 asm("r0") = 1;
register u32 r1 asm("r1") = (u32)&context_id;
register u32 r2 asm("r2") = cmd_addr;
do {
asm volatile(
__asmeq("%0", "r0")
__asmeq("%1", "r0")
__asmeq("%2", "r1")
__asmeq("%3", "r2")
".arch_extension sec\n"
"smc #0 @ switch to secure world\n"
: "=r" (r0)
: "r" (r0), "r" (r1), "r" (r2)
: "r3");
} while (r0 == SCM_INTERRUPTED);
return r0;
}
static int __scm_call(const struct scm_command *cmd)
{
int ret;
u32 cmd_addr = virt_to_phys((void *)cmd);
/*
* Flush the entire cache here so callers don't have to remember
* to flush the cache when passing physical addresses to the secure
* side in the buffer.
*/
flush_dcache_all();
ret = smc(cmd_addr);
if (ret < 0)
ret = scm_remap_error(ret);
return ret;
}
static u32 cacheline_size;
static void scm_inv_range(unsigned long start, unsigned long end)
{
start = round_down(start, cacheline_size);
end = round_up(end, cacheline_size);
while (start < end) {
asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start)
: "memory");
start += cacheline_size;
}
}
/**
* scm_call() - Send an SCM command
* @svc_id: service identifier
* @cmd_id: command identifier
* @cmd_buf: command buffer
* @cmd_len: length of the command buffer
* @resp_buf: response buffer
* @resp_len: length of the response buffer
*
* Sends a command to the SCM and waits for the command to finish processing.
*/
int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len,
void *resp_buf, size_t resp_len)
{
int ret;
struct scm_command *cmd;
struct scm_response *rsp;
unsigned long start, end;
cmd = alloc_scm_command(cmd_len, resp_len);
if (!cmd)
return -ENOMEM;
cmd->id = (svc_id << 10) | cmd_id;
if (cmd_buf)
memcpy(scm_get_command_buffer(cmd), cmd_buf, cmd_len);
ret = __scm_call(cmd);
if (ret)
goto out;
rsp = scm_command_to_response(cmd);
start = (unsigned long)rsp;
do {
scm_inv_range(start, start + sizeof(*rsp));
} while (!rsp->is_complete);
end = (unsigned long)scm_get_response_buffer(rsp) + resp_len;
scm_inv_range(start, end);
if (resp_buf)
memcpy(resp_buf, scm_get_response_buffer(rsp), resp_len);
out:
free_scm_command(cmd);
return ret;
}
int scm_init(void)
{
u32 ctr;
asm volatile("mrc p15, 0, %0, c0, c0, 1" : "=r" (ctr));
cacheline_size = 4 << ((ctr >> 16) & 0xf);
return 0;
}
int __qca_scm_call_armv8_32(u32 x0, u32 x1, u32 x2, u32 x3, u32 x4, u32 x5,
u32 *ret1, u32 *ret2, u32 *ret3)
{
register u32 r0 __asm__("r0") = x0;
register u32 r1 __asm__("r1") = x1;
register u32 r2 __asm__("r2") = x2;
register u32 r3 __asm__("r3") = x3;
register u32 r4 __asm__("r4") = x4;
register u32 r5 __asm__("r5") = x5;
asm volatile(
__asmeq("%0", "r0")
__asmeq("%1", "r1")
__asmeq("%2", "r2")
__asmeq("%3", "r3")
__asmeq("%4", "r0")
__asmeq("%5", "r1")
__asmeq("%6", "r2")
__asmeq("%7", "r3")
__asmeq("%8", "r4")
__asmeq("%9", "r5")
".arch_extension sec\n"
"smc #0\n"
: "=r" (r0), "=r" (r1), "=r" (r2), "=r" (r3)
: "r" (r0), "r" (r1), "r" (r2), "r" (r3), "r" (r4),
"r" (r5));
if (ret1)
*ret1 = r1;
if (ret2)
*ret2 = r2;
if (ret3)
*ret3 = r3;
return r0;
}
/**
* __scm_call_64() - Invoke a syscall in the secure world
* <at> svc_id: service identifier
* <at> cmd_id: command identifier
* <at> ownr_id: owner identifier
* <at> fn_id: The function ID for this syscall
* <at> desc: Descriptor structure containing arguments and return values
*
* Sends a command to the SCM and waits for the command to finish processing.
*
*/
static int __scm_call_64(u32 svc_id, u32 cmd_id, u32 ownr_id, struct qca_scm_desc *desc)
{
int arglen = desc->arginfo & 0xf;
int ret;
u32 fn_id = QCA_SCM_FNID(svc_id, cmd_id, ownr_id);
if (arglen > QCA_MAX_ARG_LEN) {
printf("Error Extra args not supported\n");
hang();
}
desc->ret[0] = desc->ret[1] = desc->ret[2] = 0;
flush_dcache_all();
ret = __qca_scm_call_armv8_32(fn_id, desc->arginfo,
desc->args[0], desc->args[1],
desc->args[2], desc->x5,
&desc->ret[0], &desc->ret[1],
&desc->ret[2]);
return ret;
}
/**
* scm_call_64() - Invoke a syscall in the secure world
* <at> svc_id: service identifier
* <at> cmd_id: command identifier
* <at> fn_id: The function ID for this syscall
* <at> desc: Descriptor structure containing arguments and return values
*
* Sends a command to the SCM and waits for the command to finish processing.
*
*/
static int scm_call_64(u32 svc_id, u32 cmd_id, struct qca_scm_desc *desc)
{
return __scm_call_64(svc_id, cmd_id, SCM_OWNR_SIP, desc);
}
static enum scm_interface_version {
SCM_UNKNOWN,
SCM_LEGACY,
SCM_ARMV8_32,
} scm_version = SCM_UNKNOWN;
/* This function is used to find whether TZ is in AARCH64 mode.
* If this function returns 1, then its in AARCH64 mode and
* calling conventions for AARCH64 TZ is different, we need to
* use them.
*/
bool is_scm_armv8(void)
{
int ret;
u32 ret1, x0;
if (likely(scm_version != SCM_UNKNOWN))
return (scm_version == SCM_ARMV8_32);
/* Try SMC32 call */
ret1 = 0;
x0 = QCA_SCM_SIP_FNID(SCM_SVC_INFO, QCA_IS_CALL_AVAIL_CMD) |
QCA_SMC_ATOMIC_MASK;
flush_dcache_all();
ret = __qca_scm_call_armv8_32(x0, QCA_SCM_ARGS(1), x0, 0, 0, 0,
&ret1, NULL, NULL);
if (ret || !ret1)
scm_version = SCM_LEGACY;
else
scm_version = SCM_ARMV8_32;
pr_debug("scm_call: scm version is %x\n", scm_version);
return (scm_version == SCM_ARMV8_32);
}
void __attribute__ ((noreturn)) jump_kernel64(void *kernel_entry,
void *fdt_addr)
{
struct qca_scm_desc desc = {0};
int ret = 0;
kernel_params param = {0};
param.reg_x0 = (u32)fdt_addr;
param.kernel_start = (u32)kernel_entry;
desc.arginfo = QCA_SCM_ARGS(2, SCM_READ_OP);
desc.args[0] = (u32) &param;
desc.args[1] = sizeof(param);
if (!is_scm_armv8()) {
printf("Can't boot kernel: %d\n", ret);
hang();
}
printf("Jumping to AARCH64 kernel via monitor\n");
ret = scm_call_64(SCM_ARCH64_SWITCH_ID, SCM_EL1SWITCH_CMD_ID,
&desc);
printf("Can't boot kernel: %d\n", ret);
hang();
}
void __attribute__ ((noreturn)) execute_tzt(void *entry_addr)
{
struct qca_scm_desc desc = {0};
int ret = 0;
kernel_params param = {0};
param.kernel_start = (u32)entry_addr;
desc.arginfo = QCA_SCM_ARGS(2, SCM_READ_OP);
desc.args[0] = (u32) &param;
desc.args[1] = sizeof(param);
printf("Jumping to AARCH64 TZT via monitor\n");
ret = scm_call_64(SCM_ARCH64_SWITCH_ID, SCM_EL1SWITCH_CMD_ID,
&desc);
printf("Can't boot TZT: %d\n", ret);
hang();
}
/* We need to invalidate the buffer written by TZ before we use in u-boot
* In some calls TZ writes to desc.args[0].
* This arg is passed with an address of local variable with size 1.
* This is not cache aligned and invalidate_dcache_range won't
* invalidate this cache line at all. To avoid this we are passing a
* buffer which is cache aligned and we will copy contents of this
* buffer back to buffer passed by callers.
*/
/* Just aliging the address of local variable with cache line is also
* not correct as the other stack contents will also be invalidated wrongly
* Hence creating a seperate cache aligned buffer
*/
#ifndef CONFIG_SYS_CACHELINE_SIZE
#define CONFIG_SYS_CACHELINE_SIZE 128
#endif
static uint8_t tz_buf[CONFIG_SYS_CACHELINE_SIZE] __aligned(CONFIG_SYS_CACHELINE_SIZE);
#ifndef CONFIG_QCA_DISABLE_SCM
int qca_scm_call(u32 svc_id, u32 cmd_id, void *buf, size_t len)
{
int ret = 0;
if (is_scm_armv8())
{
struct qca_scm_desc desc = {0};
memcpy(tz_buf, buf, len);
desc.arginfo = QCA_SCM_ARGS(2, SCM_READ_OP);
desc.args[0] = (u32)tz_buf;
desc.args[1] = len;
ret = scm_call_64(svc_id, cmd_id, &desc);
/* qca_scm_call is called with len 1, hence tz_buf is enough */
invalidate_dcache_range((unsigned long)tz_buf,
(unsigned long)tz_buf +
CONFIG_SYS_CACHELINE_SIZE);
memcpy(buf, tz_buf, len);
}
else
{
ret = scm_call(svc_id, cmd_id, NULL, 0, buf, len);
}
return ret;
}
int qca_scm_fuseipq(u32 svc_id, u32 cmd_id, void *buf, size_t len)
{
int ret = 0;
uint32_t *status;
if (is_scm_armv8())
{
struct qca_scm_desc desc = {0};
#ifdef CONFIG_ARCH_IPQ5018
desc.arginfo = QCA_SCM_ARGS(2, SCM_READ_OP);
desc.args[1] = 0x800;
#else
desc.arginfo = QCA_SCM_ARGS(1, SCM_READ_OP);
#endif
desc.args[0] = *((unsigned int *)buf);
ret = scm_call_64(svc_id, cmd_id, &desc);
status = (uint32_t *)(*(((uint32_t *)buf) + 1));
*status = desc.ret[0];
}
else
{
ret = scm_call(svc_id, cmd_id, buf, len, NULL, 0);
}
return ret;
}
int qca_scm_auth_kernel(void *cmd_buf,
size_t cmd_len)
{
int ret = 0;
if (is_scm_armv8())
{
struct qca_scm_desc desc = {0};
desc.arginfo = QCA_SCM_ARGS(1, SCM_VAL);
/* args[0] has the kernel load address */
desc.args[0] = * ((unsigned int *)cmd_buf);
/* args[1] has the kernel image size */
desc.args[1] = * (((unsigned int *)cmd_buf) + 1);
ret = scm_call_64(SCM_SVC_BOOT, KERNEL_AUTH_CMD, &desc);
}
else
{
ret = scm_call(SCM_SVC_BOOT, KERNEL_AUTH_CMD, cmd_buf, cmd_len,
NULL, 0);
}
return ret;
}
int is_scm_sec_auth_available(u32 svc_id, u32 cmd_id)
{
int ret;
__le32 scm_ret;
if (is_scm_armv8())
{
struct qca_scm_desc desc = {0};
desc.args[0] = QCA_SCM_SIP_FNID(svc_id, cmd_id);
desc.arginfo = QCA_SCM_ARGS(1);
ret = scm_call_64(SCM_SVC_INFO, IS_CALL_AVAIL_CMD, &desc);
scm_ret = desc.ret[0];
}
else
{
__le32 svc_cmd = cpu_to_le32((svc_id << SCM_SVC_ID_SHIFT) | cmd_id);
ret = scm_call(SCM_SVC_INFO, IS_CALL_AVAIL_CMD, &svc_cmd,
sizeof(svc_cmd), &scm_ret, sizeof(scm_ret));
}
if (!ret)
return le32_to_cpu(scm_ret);
return ret;
}
int qca_scm_secure_authenticate(void *cmd_buf, size_t cmd_len)
{
int ret = 0;
if (is_scm_armv8())
{
struct qca_scm_desc desc = {0};
desc.arginfo = QCA_SCM_ARGS(3, SCM_VAL, SCM_VAL, SCM_IO_WRITE);
/* args[0] has the image SW ID*/
desc.args[0] = * ((unsigned long *)cmd_buf);
/* args[1] has the image size */
desc.args[1] = * (((unsigned long *)cmd_buf) + 1);
/* args[2] has the load address*/
desc.args[2] = * (((unsigned long *)cmd_buf) + 2);
ret = scm_call_64(SCM_SVC_BOOT, SCM_CMD_SEC_AUTH, &desc);
}
else
{
ret = scm_call(SCM_SVC_BOOT, SCM_CMD_SEC_AUTH, cmd_buf, cmd_len,
NULL, 0);
}
return ret;
}
#ifdef CONFIG_IPQ_BT_SUPPORT
int qti_scm_otp(u32 peripheral)
{
int ret;
if (is_scm_armv8())
{
struct qca_scm_desc desc = {0};
desc.arginfo = QCA_SCM_ARGS(1);
desc.args[0] = peripheral;
ret = scm_call_64(SCM_SVC_PIL, SCM_CMD_OTP, &desc);
}
else
{
u32 cmd = peripheral;
ret = scm_call(SCM_SVC_PIL, SCM_CMD_OTP, &cmd, sizeof(cmd),
NULL, 0);
}
return ret;
}
int qti_scm_pas_init_image(u32 peripheral, u32 addr)
{
int ret;
if (is_scm_armv8())
{
struct qca_scm_desc desc = {0};
desc.arginfo = QCA_SCM_ARGS(2, SCM_VAL, SCM_IO_WRITE);
desc.args[0] = peripheral;
desc.args[1] = addr;
ret = scm_call_64(SCM_SVC_PIL, SCM_PAS_INIT_IMAGE_CMD, &desc);
}
else
{
struct {
u32 proc;
u32 image_addr;
} request;
request.proc = peripheral;
request.image_addr = addr;
ret = scm_call(SCM_SVC_PIL, SCM_PAS_INIT_IMAGE_CMD, &request,
sizeof(request), NULL, 0);
}
return ret;
}
int qti_pas_and_auth_reset(u32 peripheral)
{
int ret;
u32 cmd = peripheral;
if (is_scm_armv8())
{
struct qca_scm_desc desc = {0};
desc.arginfo = QCA_SCM_ARGS(1);
desc.args[0] = peripheral;
ret = scm_call_64(SCM_SVC_PIL, SCM_PAS_AUTH_AND_RESET_CMD, &desc);
}
else
{
ret = scm_call(SCM_SVC_PIL, SCM_PAS_AUTH_AND_RESET_CMD, &cmd, sizeof(cmd),
NULL, 0);
}
return ret;
}
#endif
#else
int qca_scm_call(u32 svc_id, u32 cmd_id, void *buf, size_t len)
{
return 0;
}
int qca_scm_fuseipq(u32 svc_id, u32 cmd_id, void *buf, size_t len)
{
return 0;
}
int qca_scm_auth_kernel(void *cmd_buf,
size_t cmd_len)
{
return 0;
}
int is_scm_sec_auth_available(u32 svc_id, u32 cmd_id)
{
return 0;
}
int qca_scm_secure_authenticate(void *cmd_buf, size_t cmd_len)
{
return 0;
}
#endif
int qca_scm_call_crypto_v8(u32 svc_id, u32 cmd_id, u32 *addr, u32 val)
{
int ret = 0;
struct qca_scm_desc desc = {0};
desc.arginfo = QCA_SCM_ARGS(2, SCM_RW_OP, SCM_VAL);
desc.args[0] = (u32)addr;
desc.args[1] = val;
ret = scm_call_64(svc_id, cmd_id, &desc);
return ret;
}
int qca_scm_call_write(u32 svc_id, u32 cmd_id, u32 *addr, u32 val)
{
int ret = 0;
struct qca_scm_desc desc = {0};
/* In ipq807x, this SCM call is called as a Fast
* SCM call which means it will get executed in
* EL3 monitor mode itself without jumping to QSEE.
* But, In ipq6018, We need to jump into QSEE which
* will execute the SCM call, as we do not have
* support for Fast SCM call in ipq6018.
*/
desc.arginfo = QCA_SCM_ARGS(2, SCM_VAL, SCM_VAL);
desc.args[0] = (u32)addr;
desc.args[1] = val;
ret = scm_call_64(svc_id, cmd_id, &desc);
return ret;
}
static int qca_scm_sdi_v8(void)
{
struct qca_scm_desc desc = {0};
int ret;
desc.args[0] = 1ul; /* Disable wdog debug */
desc.args[1] = 0ul; /* SDI Enable */
desc.arginfo = QCA_SCM_ARGS(2, SCM_VAL, SCM_VAL);
ret = scm_call_64(SCM_SVC_BOOT,
SCM_CMD_TZ_CONFIG_HW_FOR_RAM_DUMP_ID, &desc);
if (ret)
return ret;
return le32_to_cpu(desc.ret[0]);
}
int qca_scm_sdi(void)
{
int ret;
unsigned int clear_info[] = {
1 /* Disable wdog debug */, 0 /* SDI enable*/, };
if (is_scm_armv8())
return qca_scm_sdi_v8();
ret = scm_call(SCM_SVC_BOOT, SCM_CMD_TZ_CONFIG_HW_FOR_RAM_DUMP_ID, &clear_info,
sizeof(clear_info), NULL, 0);
return ret;
}
int qca_scm_crypto(int cmd_id, void *req_ptr, uint32_t req_size)
{
int ret;
if (is_scm_armv8())
ret = qca_scm_call_crypto_v8(SCM_SVC_CRYPTO, cmd_id,
(u32 *)req_ptr, req_size);
else
ret = -ENOTSUPP;
return ret;
}
int qca_scm_dload(unsigned int magic_cookie)
{
int ret;
if (is_scm_armv8())
{
ret = qca_scm_call_write(SCM_SVC_IO, SCM_IO_WRITE,
(u32 *)0x193D100, magic_cookie);
}
else
{
ret = scm_call(SCM_SVC_BOOT, SCM_CMD_TZ_FORCE_DLOAD_ID, &magic_cookie,
sizeof(magic_cookie), NULL, 0);
}
return ret;
}
#define SCM_CLASS_REGISTER (0x2 << 8)
#define SCM_MASK_IRQS BIT(5)
#define SCM_ATOMIC(svc, cmd, n) (((((svc) << 10) | ((cmd) & 0x3ff)) << 12) | \
SCM_CLASS_REGISTER | \
SCM_MASK_IRQS | \
(n & 0xf))
s32 qca_scm_call_atomic_ver2_32(u32 svc, u32 cmd, u32 arg1, u32 arg2)
{
int context_id;
register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 2);
register u32 r1 asm("r1") = virt_to_phys((void *)&context_id);
register u32 r2 asm("r2") = arg1;
register u32 r3 asm("r3") = arg2;
asm volatile(
__asmeq("%0", "r0")
__asmeq("%1", "r0")
__asmeq("%2", "r1")
__asmeq("%3", "r2")
__asmeq("%4", "r3")
"smc #0 @switch to secure world\n"
: "=r" (r0)
: "r" (r0), "r" (r1), "r" (r2), "r" (r3)
);
return r0;
}
int qca_scm_usb_mode_write(u32 arg1, u32 arg2)
{
s32 ret;
ret = qca_scm_call_atomic_ver2_32(SCM_SVC_IO, SCM_IO_WRITE, arg1, arg2);
return ret;
}
#ifdef CONFIG_IPQ_TZT
int qca_scm(u32 svc_id, u32 cmd_id, u32 ownr_id, u32 *addr, u32 val)
{
struct qca_scm_desc desc = {0};
memcpy(&desc, addr, sizeof(u32)*val);
return __scm_call_64(svc_id, cmd_id, ownr_id, &desc);
}
#endif