blob: fb27336e000a84cfc331ce68958d9268437d0ebc [file] [log] [blame]
/*
* Texas Instruments K3 Secure Proxy Driver
* Based on Linux and U-Boot implementation
*
* Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <errno.h>
#include <stdlib.h>
#include <platform_def.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <lib/mmio.h>
#include <lib/utils.h>
#include <lib/utils_def.h>
#include "sec_proxy.h"
/* SEC PROXY RT THREAD STATUS */
#define RT_THREAD_STATUS (0x0)
#define RT_THREAD_STATUS_ERROR_SHIFT (31)
#define RT_THREAD_STATUS_ERROR_MASK BIT(31)
#define RT_THREAD_STATUS_CUR_CNT_SHIFT (0)
#define RT_THREAD_STATUS_CUR_CNT_MASK GENMASK(7, 0)
/* SEC PROXY SCFG THREAD CTRL */
#define SCFG_THREAD_CTRL (0x1000)
#define SCFG_THREAD_CTRL_DIR_SHIFT (31)
#define SCFG_THREAD_CTRL_DIR_MASK BIT(31)
#define SEC_PROXY_THREAD(base, x) ((base) + (0x1000 * (x)))
#define THREAD_IS_RX (1)
#define THREAD_IS_TX (0)
/**
* struct k3_sec_proxy_desc - Description of secure proxy integration
* @timeout_us: Timeout for communication (in Microseconds)
* @max_msg_size: Message size in bytes
* @data_start_offset: Offset of the First data register of the thread
* @data_end_offset: Offset of the Last data register of the thread
*/
struct k3_sec_proxy_desc {
uint32_t timeout_us;
uint16_t max_msg_size;
uint16_t data_start_offset;
uint16_t data_end_offset;
};
/**
* struct k3_sec_proxy_thread - Description of a Secure Proxy Thread
* @name: Thread Name
* @data: Thread Data path region for target
* @scfg: Secure Config Region for Thread
* @rt: RealTime Region for Thread
*/
struct k3_sec_proxy_thread {
const char *name;
uintptr_t data;
uintptr_t scfg;
uintptr_t rt;
};
/**
* struct k3_sec_proxy_mbox - Description of a Secure Proxy Instance
* @desc: Description of the SoC integration
* @chans: Array for valid thread instances
*/
struct k3_sec_proxy_mbox {
const struct k3_sec_proxy_desc desc;
struct k3_sec_proxy_thread threads[];
};
/*
* Thread ID #0: DMSC notify
* Thread ID #1: DMSC request response
* Thread ID #2: DMSC request high priority
* Thread ID #3: DMSC request low priority
* Thread ID #4: DMSC notify response
*/
#define SP_THREAD(_x) \
[_x] = { \
.name = #_x, \
.data = SEC_PROXY_THREAD(SEC_PROXY_DATA_BASE, _x), \
.scfg = SEC_PROXY_THREAD(SEC_PROXY_SCFG_BASE, _x), \
.rt = SEC_PROXY_THREAD(SEC_PROXY_RT_BASE, _x), \
}
static struct k3_sec_proxy_mbox spm = {
.desc = {
.timeout_us = SEC_PROXY_TIMEOUT_US,
.max_msg_size = SEC_PROXY_MAX_MESSAGE_SIZE,
.data_start_offset = 0x4,
.data_end_offset = 0x3C,
},
.threads = {
#if !K3_SEC_PROXY_LITE
SP_THREAD(SP_NOTIFY),
SP_THREAD(SP_RESPONSE),
SP_THREAD(SP_HIGH_PRIORITY),
SP_THREAD(SP_LOW_PRIORITY),
SP_THREAD(SP_NOTIFY_RESP),
#else
SP_THREAD(SP_RESPONSE),
SP_THREAD(SP_HIGH_PRIORITY),
#endif /* K3_SEC_PROXY_LITE */
},
};
/**
* struct sec_msg_hdr - Message header for secure messages and responses
* @checksum: CRC of message for integrity checking
*/
union sec_msg_hdr {
struct {
uint16_t checksum;
uint16_t reserved;
} __packed;
uint32_t data;
};
/**
* k3_sec_proxy_verify_thread() - Verify thread status before
* sending/receiving data
* @spt: Pointer to Secure Proxy thread description
* @dir: Direction of the thread
*
* Return: 0 if all goes well, else appropriate error message
*/
static int k3_sec_proxy_verify_thread(struct k3_sec_proxy_thread *spt,
uint32_t dir)
{
/* Check for any errors already available */
if (mmio_read_32(spt->rt + RT_THREAD_STATUS) &
RT_THREAD_STATUS_ERROR_MASK) {
ERROR("Thread %s is corrupted, cannot send data\n", spt->name);
return -EINVAL;
}
/* Make sure thread is configured for right direction */
if ((mmio_read_32(spt->scfg + SCFG_THREAD_CTRL) & SCFG_THREAD_CTRL_DIR_MASK)
!= (dir << SCFG_THREAD_CTRL_DIR_SHIFT)) {
if (dir == THREAD_IS_TX)
ERROR("Trying to send data on RX Thread %s\n",
spt->name);
else
ERROR("Trying to receive data on TX Thread %s\n",
spt->name);
return -EINVAL;
}
/* Check the message queue before sending/receiving data */
uint32_t tick_start = (uint32_t)read_cntpct_el0();
uint32_t ticks_per_us = SYS_COUNTER_FREQ_IN_TICKS / 1000000;
while (!(mmio_read_32(spt->rt + RT_THREAD_STATUS) & RT_THREAD_STATUS_CUR_CNT_MASK)) {
VERBOSE("Waiting for thread %s to %s\n",
spt->name, (dir == THREAD_IS_TX) ? "empty" : "fill");
if (((uint32_t)read_cntpct_el0() - tick_start) >
(spm.desc.timeout_us * ticks_per_us)) {
ERROR("Timeout waiting for thread %s to %s\n",
spt->name, (dir == THREAD_IS_TX) ? "empty" : "fill");
return -ETIMEDOUT;
}
}
return 0;
}
/**
* k3_sec_proxy_clear_rx_thread() - Clear Secure Proxy thread
*
* @id: Channel Identifier
*
* Return: 0 if all goes well, else appropriate error message
*/
int k3_sec_proxy_clear_rx_thread(enum k3_sec_proxy_chan_id id)
{
struct k3_sec_proxy_thread *spt = &spm.threads[id];
/* Check for any errors already available */
if (mmio_read_32(spt->rt + RT_THREAD_STATUS) &
RT_THREAD_STATUS_ERROR_MASK) {
ERROR("Thread %s is corrupted, cannot send data\n", spt->name);
return -EINVAL;
}
/* Make sure thread is configured for right direction */
if (!(mmio_read_32(spt->scfg + SCFG_THREAD_CTRL) & SCFG_THREAD_CTRL_DIR_MASK)) {
ERROR("Cannot clear a transmit thread %s\n", spt->name);
return -EINVAL;
}
/* Read off messages from thread until empty */
uint32_t try_count = 10;
while (mmio_read_32(spt->rt + RT_THREAD_STATUS) & RT_THREAD_STATUS_CUR_CNT_MASK) {
if (!(try_count--)) {
ERROR("Could not clear all messages from thread %s\n", spt->name);
return -ETIMEDOUT;
}
WARN("Clearing message from thread %s\n", spt->name);
mmio_read_32(spt->data + spm.desc.data_end_offset);
}
return 0;
}
/**
* k3_sec_proxy_send() - Send data over a Secure Proxy thread
* @id: Channel Identifier
* @msg: Pointer to k3_sec_proxy_msg
*
* Return: 0 if all goes well, else appropriate error message
*/
int k3_sec_proxy_send(enum k3_sec_proxy_chan_id id, const struct k3_sec_proxy_msg *msg)
{
struct k3_sec_proxy_thread *spt = &spm.threads[id];
union sec_msg_hdr secure_header;
int num_words, trail_bytes, i, ret;
uintptr_t data_reg;
ret = k3_sec_proxy_verify_thread(spt, THREAD_IS_TX);
if (ret) {
ERROR("Thread %s verification failed (%d)\n", spt->name, ret);
return ret;
}
/* Check the message size */
if (msg->len + sizeof(secure_header) > spm.desc.max_msg_size) {
ERROR("Thread %s message length %lu > max msg size\n",
spt->name, msg->len);
return -EINVAL;
}
/* TODO: Calculate checksum */
secure_header.checksum = 0;
/* Send the secure header */
data_reg = spm.desc.data_start_offset;
mmio_write_32(spt->data + data_reg, secure_header.data);
data_reg += sizeof(uint32_t);
/* Send whole words */
num_words = msg->len / sizeof(uint32_t);
for (i = 0; i < num_words; i++) {
mmio_write_32(spt->data + data_reg, ((uint32_t *)msg->buf)[i]);
data_reg += sizeof(uint32_t);
}
/* Send remaining bytes */
trail_bytes = msg->len % sizeof(uint32_t);
if (trail_bytes) {
uint32_t data_trail = 0;
i = msg->len - trail_bytes;
while (trail_bytes--) {
data_trail <<= 8;
data_trail |= msg->buf[i++];
}
mmio_write_32(spt->data + data_reg, data_trail);
data_reg += sizeof(uint32_t);
}
/*
* 'data_reg' indicates next register to write. If we did not already
* write on tx complete reg(last reg), we must do so for transmit
* In addition, we also need to make sure all intermediate data
* registers(if any required), are reset to 0 for TISCI backward
* compatibility to be maintained.
*/
while (data_reg <= spm.desc.data_end_offset) {
mmio_write_32(spt->data + data_reg, 0);
data_reg += sizeof(uint32_t);
}
VERBOSE("Message successfully sent on thread %s\n", spt->name);
return 0;
}
/**
* k3_sec_proxy_recv() - Receive data from a Secure Proxy thread
* @id: Channel Identifier
* @msg: Pointer to k3_sec_proxy_msg
*
* Return: 0 if all goes well, else appropriate error message
*/
int k3_sec_proxy_recv(enum k3_sec_proxy_chan_id id, struct k3_sec_proxy_msg *msg)
{
struct k3_sec_proxy_thread *spt = &spm.threads[id];
union sec_msg_hdr secure_header;
uintptr_t data_reg;
int num_words, trail_bytes, i, ret;
ret = k3_sec_proxy_verify_thread(spt, THREAD_IS_RX);
if (ret) {
ERROR("Thread %s verification failed (%d)\n", spt->name, ret);
return ret;
}
/* Read secure header */
data_reg = spm.desc.data_start_offset;
secure_header.data = mmio_read_32(spt->data + data_reg);
data_reg += sizeof(uint32_t);
/* Read whole words */
num_words = msg->len / sizeof(uint32_t);
for (i = 0; i < num_words; i++) {
((uint32_t *)msg->buf)[i] = mmio_read_32(spt->data + data_reg);
data_reg += sizeof(uint32_t);
}
/* Read remaining bytes */
trail_bytes = msg->len % sizeof(uint32_t);
if (trail_bytes) {
uint32_t data_trail = mmio_read_32(spt->data + data_reg);
data_reg += sizeof(uint32_t);
i = msg->len - trail_bytes;
while (trail_bytes--) {
msg->buf[i++] = data_trail & 0xff;
data_trail >>= 8;
}
}
/*
* 'data_reg' indicates next register to read. If we did not already
* read on rx complete reg(last reg), we must do so for receive
*/
if (data_reg <= spm.desc.data_end_offset)
mmio_read_32(spt->data + spm.desc.data_end_offset);
/* TODO: Verify checksum */
(void)secure_header.checksum;
VERBOSE("Message successfully received from thread %s\n", spt->name);
return 0;
}