* reboot.c
*
* functions for TEE reboot
*
* Copyright (c) 2023-2023 Huawei Technologies Co., Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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 "reboot.h"
#include <linux/delay.h>
#include <linux/kthread.h>
#include "agent.h"
#include "cmdmonitor.h"
#include "mailbox_mempool.h"
#include "tlogger.h"
#include "tz_spi_notify.h"
#include "secs_power_ctrl.h"
#include "session_manager.h"
#include "smc_smp.h"
#include "ko_adapt.h"
#include "tz_kthread_affinity.h"
#include "tc_client_driver.h"
#include "tee_compat_check.h"
#include <linux/sched.h>
#include <linux/sched/rt.h>
static atomic_t secondary_cpu_reboot;
static bool g_is_tee_rebooting = false;
static tee_alarm_func g_tee_alarm_func = NULL;
static struct pid *g_teecd_pid = NULL;
#ifdef CONFIG_CONFIDENTIAL_TEE
#if (CONFIG_NEW_TEE_LOADED == 1)
static bool g_new_tee_on = true;
#else
static bool g_new_tee_on = false;
#endif
#endif
static void set_tee_is_dead_flag(void)
{
g_is_tee_rebooting = true;
}
static void clr_tee_is_dead_flag(void)
{
g_is_tee_rebooting = false;
}
bool is_tee_rebooting(void)
{
return g_is_tee_rebooting;
}
static void tee_reboot_smc(uint32_t cmd)
{
int ret;
unsigned long flags;
if (power_on_cc() != 0) {
tloge("power on cc failed\n");
return;
}
local_irq_save(flags);
ret = send_smc_cmd_rebooting(cmd, NULL);
local_irq_restore(flags);
if (power_down_cc() != 0) {
tloge("power down cc failed\n");
return;
}
return;
}
static struct work_struct reboot_work;
static struct work_struct reboot_done_work;
static struct work_struct secondary_cpu_on_work[NR_CPUS];
static void secondary_cpu_on_func(struct work_struct *dummy)
{
tee_reboot_smc(TSP_CPU_ON);
atomic_add(1, &secondary_cpu_reboot);
return;
}
static void tee_reboot_secondary_cpus(void)
{
int i;
tlogd("secondary cpu will reboot\n");
for_each_online_cpu(i) {
if (i != 0) {
INIT_WORK(&secondary_cpu_on_work[i], secondary_cpu_on_func);
schedule_work_on(i, &secondary_cpu_on_work[i]);
tlogi("before flush work cpu %d\n", i);
flush_work(&secondary_cpu_on_work[i]);
tlogi("after flush work cpu %d\n", i);
}
}
}
static void tee_reboot_work_func(struct work_struct *dummy)
{
tlogd("primary cpu will reboot\n");
tee_reboot_smc(TSP_REBOOT);
tee_reboot_secondary_cpus();
return;
}
static int tee_reboot_work(void)
{
uint32_t retry_count = 0;
atomic_set(&secondary_cpu_reboot, 0);
INIT_WORK(&reboot_work, tee_reboot_work_func);
schedule_work_on(0, &reboot_work);
flush_work(&reboot_work);
while (retry_count < REBOOT_MAX_COUNT) {
if (atomic_read(&secondary_cpu_reboot) >= (num_online_cpus() - 1)) {
#ifdef CONFIG_CONFIDENTIAL_TEE
g_new_tee_on = true;
#endif
return 0;
}
msleep(REBOOT_SLEEP);
retry_count++;
}
return -1;
}
static void tee_reboot_done_work_func(struct work_struct *dummy)
{
tlogd("clear reboot flag\n");
tee_reboot_smc(TSP_REBOOT_DONE);
return;
}
static void tee_reboot_done_work(void)
{
INIT_WORK(&reboot_done_work, tee_reboot_done_work_func);
schedule_work_on(0, &reboot_done_work);
flush_work(&reboot_done_work);
}
static bool get_gic_mpidr_mt(void)
{
if (get_mpidr_el1() & MPIDR_MT)
return true;
else
return false;
}
static uint64_t get_gic_cpu_affinity(uint32_t cpu_id)
{
return get_mpidr_el1() & MPIDR_AFF_MASK;
}
static void gic_send_sgi(uint32_t irq, uint32_t target_list)
{
uint32_t i;
for_each_online_cpu(i) {
if ((1 << i) & target_list) {
uint64_t sgi1r;
uint64_t aff = get_gic_cpu_affinity(i);
if (get_gic_mpidr_mt()) {
sgi1r = ((aff >> MPIDR_AFF3_FIELD) << SGI_AFF3_FIELD) |
(((aff >> MPIDR_AFF2_FIELD) & AFF_MASK) << SGI_AFF2_FIELD) |
(((aff >> MPIDR_AFF1_FIELD) & AFF_MASK) << SGI_AFF1_FIELD) |
1 |
((uint64_t)irq << SGI_ID_FIELD);
} else {
sgi1r = ((aff >> MPIDR_AFF3_FIELD) << SGI_AFF3_FIELD) |
(((aff >> MPIDR_AFF2_FIELD) & AFF_MASK) << SGI_AFF2_FIELD) |
(((aff >> MPIDR_AFF1_FIELD) & AFF_MASK) << SGI_AFF1_FIELD) |
(1 << (i % GIC_AFF_LEVEL)) |
((uint64_t)irq << SGI_ID_FIELD);
}
set_icc_sgi1r_el1(sgi1r);
__asm__ volatile ("isb" : : : "memory");
}
}
}
static uint32_t get_online_cpu_mask(void)
{
uint32_t i;
uint32_t cpu_mask = 0;
for_each_online_cpu(i)
cpu_mask |= (1 << i);
return cpu_mask;
}
static void tee_alarm_report(int alarm_id)
{
if (g_tee_alarm_func == NULL)
return;
int ret = g_tee_alarm_func(alarm_id, ALARM_REPORT);
if (ret != 0)
tloge("report crash fail, ret is %d\n", ret);
}
static void tee_alarm_clear(int alarm_id)
{
if (g_tee_alarm_func == NULL)
return;
int ret = g_tee_alarm_func(alarm_id, ALARM_CLEAR);
if (ret != 0)
tloge("clean crash fail, ret is %d\n", ret);
}
int teek_register_alarm_func(tee_alarm_func alarm_func)
{
if (alarm_func == NULL)
return -EINVAL;
g_tee_alarm_func = alarm_func;
return 0;
}
EXPORT_SYMBOL_TZ(teek_register_alarm_func);
void get_teecd_pid(void)
{
get_task_struct(current);
g_teecd_pid = get_task_pid(current, PIDTYPE_PID);
}
static void kill_teecd_and_tlogcat(void)
{
if (g_teecd_pid != NULL)
kill_pid(g_teecd_pid, SIGKILL, 1);
recycle_tlogcat_processes();
}
static bool prepare_reboot(void)
{
#ifdef CONFIG_CONFIDENTIAL_TEE
if (g_new_tee_on == true) {
tloge("new tee is on, cannot upgrade again\n");
return false;
}
if (check_running_ca()) {
tloge("there are one or more running tasks, stop upgrade\n");
return false;
}
#endif
set_tee_is_dead_flag();
tee_alarm_report(TEE_CRASH);
gic_send_sgi(GIC_CPU_RESCHEDULE, get_online_cpu_mask());
return true;
}
int tee_reboot(void)
{
int ret = 0;
if (!prepare_reboot())
return -1;
ret = tee_reboot_work();
if (ret != 0) {
tloge("tee reboot work failed, ret 0x%x\n", ret);
goto err;
}
smc_wakeup_broadcast();
#ifndef CONFIG_DISABLE_SVC
svc_thread_release();
#endif
occupy_clean_cmd_buf();
free_agent_list();
send_smc_cmd_buffer(true);
ret = parse_params_from_tee();
if (ret != 0) {
tloge("reboot parse params from tee failed\n");
goto err;
}
ret = re_register_mailbox();
if (ret != 0) {
tloge("re-register mailbox failed, ret 0x%x\n", ret);
goto err;
}
ret = send_notify_cmd(GLOBAL_CMD_ID_REGISTER_NOTIFY_MEMORY);
if (ret != 0) {
tloge("send notify cmd failed, ret 0x%x\n", ret);
goto err;
}
ret = tc_ns_register_host_nsid_vmid();
if (ret != 0) {
tloge("register host nsid failed, ret 0x%x\n", ret);
goto err;
}
free_all_session();
ret = register_tloger_mem();
if (ret != 0) {
tloge("re-register tloger failed, ret 0x%x\n", ret);
goto err;
}
clr_tee_is_dead_flag();
tee_reboot_done_work();
clr_system_crash_flag();
occupy_clean_cmd_buf();
#ifndef CONFIG_DISABLE_SVC
ret = init_smc_svc_thread();
if (ret != 0) {
tloge("init svc thread failed\n");
goto err;
}
#endif
tee_alarm_clear(TEE_CRASH);
kill_teecd_and_tlogcat();
#if defined(CONFIG_CONFIDENTIAL_CONTAINER) || defined(CONFIG_TEE_TELEPORT_SUPPORT)
if (is_ccos()) {
(void)init_cvm_node();
(void)init_cvm_node_file();
}
#endif
return 0;
err:
tee_alarm_report(TEE_REBOOT_FAIL);
return ret;
}
#ifdef CONFIG_TEE_UPGRADE
EXPORT_SYMBOL_TZ(tee_reboot);
#endif
static struct task_struct *g_reboot_thread;
DEFINE_MUTEX(g_reboot_lock);
static DECLARE_WAIT_QUEUE_HEAD(reboot_th_wait);
static atomic_t g_reboot_th_run;
#define RUN_REBOOT_THREAD 1
#define STOP_REBOOT_THREAD 2
static int tee_reboot_fn(void *arg)
{
int ret;
while (1) {
ret = wait_event_interruptible(reboot_th_wait,
atomic_read(&g_reboot_th_run));
if (ret != 0) {
tloge("wait reboot event interruptible failed!\n");
return -EINTR;
}
if (atomic_read(&g_reboot_th_run) == STOP_REBOOT_THREAD)
break;
mutex_lock(&g_reboot_lock);
if (tee_reboot() != 0)
tloge("tee reboot failed\n");
atomic_set(&g_reboot_th_run, 0);
mutex_unlock(&g_reboot_lock);
}
return ret;
}
static int tee_create_reboot_thread(void)
{
g_reboot_thread = kthread_create(tee_reboot_fn, NULL, "reboot_thread");
if (unlikely(IS_ERR_OR_NULL(g_reboot_thread))) {
tloge("couldn't create reboot thread %ld\n",
PTR_ERR(g_reboot_thread));
return -1;
}
return 0;
}
#ifndef CONFIG_TEE_UPGRADE
int tee_init_reboot_thread(void)
{
if (tee_create_reboot_thread() != 0) {
tloge("init reboot thread failed\n");
return -EFAULT;
}
wake_up_process(g_reboot_thread);
return 0;
}
void free_reboot_thread(void)
{
set_tee_is_dead_flag();
if (!IS_ERR_OR_NULL(g_reboot_thread)) {
atomic_set(&g_reboot_th_run, STOP_REBOOT_THREAD);
wake_up_interruptible(&reboot_th_wait);
kthread_stop(g_reboot_thread);
g_reboot_thread = NULL;
}
}
int tee_wake_up_reboot(void)
{
tloge("tee will reboot\n");
atomic_set(&g_reboot_th_run, RUN_REBOOT_THREAD);
wake_up_interruptible(&reboot_th_wait);
return 0;
}
#else
int tee_init_reboot_thread(void)
{
return 0;
}
int tee_wake_up_reboot(void)
{
return 0;
}
void free_reboot_thread(void)
{
return;
}
#endif