blob: 55a1ef72cf922c3a1b9b6c2689d8f97ecb518023 [file] [log] [blame]
/*
* MobiCore Driver Kernel Module.
* This module is written as a Linux device driver.
* This driver represents the command proxy on the lowest layer, from the
* secure world to the non secure world, and vice versa.
* This driver is located in the non secure world (Linux).
* This driver offers IOCTL commands, for access to the secure world, and has
* the interface from the secure world to the normal world.
* The access to the driver is possible with a file descriptor,
* which has to be created by the fd = open(/dev/mobicore) command.
*
* <-- Copyright Giesecke & Devrient GmbH 2009-2012 -->
* <-- Copyright Trustonic Limited 2013 -->
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/suspend.h>
#include <linux/device.h>
#include "main.h"
#include "pm.h"
#include "fastcall.h"
#include "ops.h"
#include "logging.h"
#include "debug.h"
#ifdef MC_CRYPTO_CLOCK_MANAGEMENT
#include <linux/clk.h>
#include <linux/err.h>
struct clk *mc_ce_iface_clk = NULL;
struct clk *mc_ce_core_clk = NULL;
struct clk *mc_ce_bus_clk = NULL;
#endif /* MC_CRYPTO_CLOCK_MANAGEMENT */
#ifdef MC_PM_RUNTIME
static struct mc_context *ctx;
static bool sleep_ready(void)
{
if (!ctx->mcp)
return false;
if (!ctx->mcp->flags.sleep_mode.ReadyToSleep & READY_TO_SLEEP)
return false;
return true;
}
static void mc_suspend_handler(struct work_struct *work)
{
if (!ctx->mcp)
return;
ctx->mcp->flags.sleep_mode.SleepReq = REQ_TO_SLEEP;
_nsiq();
}
DECLARE_WORK(suspend_work, mc_suspend_handler);
static inline void dump_sleep_params(struct mc_flags *flags)
{
MCDRV_DBG(mcd, "MobiCore IDLE=%d!", flags->schedule);
MCDRV_DBG(mcd,
"MobiCore Request Sleep=%d!", flags->sleep_mode.SleepReq);
MCDRV_DBG(mcd,
"MobiCore Sleep Ready=%d!", flags->sleep_mode.ReadyToSleep);
}
static int mc_suspend_notifier(struct notifier_block *nb,
unsigned long event, void *dummy)
{
struct mc_mcp_buffer *mcp = ctx->mcp;
/* We have noting to say if MobiCore is not initialized */
if (!mcp)
return 0;
#ifdef MC_MEM_TRACES
mobicore_log_read();
#endif
switch (event) {
case PM_SUSPEND_PREPARE:
/*
* Make sure we have finished all the work otherwise
* we end up in a race condition
*/
cancel_work_sync(&suspend_work);
/*
* We can't go to sleep if MobiCore is not IDLE
* or not Ready to sleep
*/
dump_sleep_params(&mcp->flags);
if (!sleep_ready()) {
ctx->mcp->flags.sleep_mode.SleepReq = REQ_TO_SLEEP;
schedule_work_on(0, &suspend_work);
flush_work(&suspend_work);
if (!sleep_ready()) {
dump_sleep_params(&mcp->flags);
ctx->mcp->flags.sleep_mode.SleepReq = 0;
MCDRV_DBG_ERROR(mcd, "MobiCore can't SLEEP!");
return NOTIFY_BAD;
}
}
break;
case PM_POST_SUSPEND:
MCDRV_DBG(mcd, "Resume MobiCore system!");
ctx->mcp->flags.sleep_mode.SleepReq = 0;
break;
default:
break;
}
return 0;
}
static struct notifier_block mc_notif_block = {
.notifier_call = mc_suspend_notifier,
};
#ifdef MC_BL_NOTIFIER
static int bL_switcher_notifier_handler(struct notifier_block *this,
unsigned long event, void *ptr)
{
unsigned int mpidr, cpu, cluster;
struct mc_mcp_buffer *mcp = ctx->mcp;
if (!mcp)
return 0;
asm volatile ("mrc\tp15, 0, %0, c0, c0, 5" : "=r" (mpidr));
cpu = mpidr & 0x3;
cluster = (mpidr >> 8) & 0xf;
MCDRV_DBG(mcd, "%s switching!!, cpu: %u, Out=%u\n",
(event == SWITCH_ENTER ? "Before" : "After"), cpu, cluster);
if (cpu != 0)
return 0;
switch (event) {
case SWITCH_ENTER:
if (!sleep_ready()) {
ctx->mcp->flags.sleep_mode.SleepReq = REQ_TO_SLEEP;
_nsiq();
/* By this time we should be ready for sleep or we are
* in the middle of something important */
if (!sleep_ready()) {
dump_sleep_params(&mcp->flags);
MCDRV_DBG(mcd,
"MobiCore: Don't allow switch!\n");
ctx->mcp->flags.sleep_mode.SleepReq = 0;
return -EPERM;
}
}
break;
case SWITCH_EXIT:
ctx->mcp->flags.sleep_mode.SleepReq = 0;
break;
default:
MCDRV_DBG(mcd, "MobiCore: Unknown switch event!\n");
}
return 0;
}
static struct notifier_block switcher_nb = {
.notifier_call = bL_switcher_notifier_handler,
};
#endif
int mc_pm_initialize(struct mc_context *context)
{
int ret = 0;
ctx = context;
ret = register_pm_notifier(&mc_notif_block);
if (ret)
MCDRV_DBG_ERROR(mcd, "device pm register failed\n");
#ifdef MC_BL_NOTIFIER
if (register_bL_swicher_notifier(&switcher_nb))
MCDRV_DBG_ERROR(mcd,
"Failed to register to bL_switcher_notifier\n");
#endif
return ret;
}
int mc_pm_free(void)
{
int ret = unregister_pm_notifier(&mc_notif_block);
if (ret)
MCDRV_DBG_ERROR(mcd, "device pm unregister failed\n");
#ifdef MC_BL_NOTIFIER
ret = unregister_bL_swicher_notifier(&switcher_nb);
if (ret)
MCDRV_DBG_ERROR(mcd, "device bl unregister failed\n");
#endif
return ret;
}
#endif /* MC_PM_RUNTIME */
#ifdef MC_CRYPTO_CLOCK_MANAGEMENT
int mc_pm_clock_initialize(void)
{
int ret = 0;
/* Get core clk */
mc_ce_core_clk = clk_get(mcd, "core_clk");
if (IS_ERR(mc_ce_core_clk)) {
ret = PTR_ERR(mc_ce_core_clk);
MCDRV_DBG_ERROR(mcd, "cannot get core clock\n");
goto error;
}
/* Get Interface clk */
mc_ce_iface_clk = clk_get(mcd, "iface_clk");
if (IS_ERR(mc_ce_iface_clk)) {
clk_put(mc_ce_core_clk);
ret = PTR_ERR(mc_ce_iface_clk);
MCDRV_DBG_ERROR(mcd, "cannot get iface clock\n");
goto error;
}
/* Get AXI clk */
mc_ce_bus_clk = clk_get(mcd, "bus_clk");
if (IS_ERR(mc_ce_bus_clk)) {
clk_put(mc_ce_iface_clk);
clk_put(mc_ce_core_clk);
ret = PTR_ERR(mc_ce_bus_clk);
MCDRV_DBG_ERROR(mcd, "cannot get AXI bus clock\n");
goto error;
}
return ret;
error:
mc_ce_core_clk = NULL;
mc_ce_iface_clk = NULL;
mc_ce_bus_clk = NULL;
return ret;
}
void mc_pm_clock_finalize(void)
{
if (mc_ce_iface_clk != NULL)
clk_put(mc_ce_iface_clk);
if (mc_ce_core_clk != NULL)
clk_put(mc_ce_core_clk);
if (mc_ce_bus_clk != NULL)
clk_put(mc_ce_bus_clk);
}
int mc_pm_clock_enable(void)
{
int rc = 0;
rc = clk_prepare_enable(mc_ce_core_clk);
if (rc) {
MCDRV_DBG_ERROR(mcd, "cannot enable clock\n");
} else {
rc = clk_prepare_enable(mc_ce_iface_clk);
if (rc) {
clk_disable_unprepare(mc_ce_core_clk);
MCDRV_DBG_ERROR(mcd, "cannot enable clock\n");
} else {
rc = clk_prepare_enable(mc_ce_bus_clk);
if (rc) {
clk_disable_unprepare(mc_ce_iface_clk);
MCDRV_DBG_ERROR(mcd, "cannot enable clock\n");
}
}
}
return rc;
}
void mc_pm_clock_disable(void)
{
if (mc_ce_iface_clk != NULL)
clk_disable_unprepare(mc_ce_iface_clk);
if (mc_ce_core_clk != NULL)
clk_disable_unprepare(mc_ce_core_clk);
if (mc_ce_bus_clk != NULL)
clk_disable_unprepare(mc_ce_bus_clk);
}
#endif /* MC_CRYPTO_CLOCK_MANAGEMENT */