summaryrefslogtreecommitdiff
path: root/security/tf_driver/scxlnx_comm_tz.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/tf_driver/scxlnx_comm_tz.c')
-rw-r--r--security/tf_driver/scxlnx_comm_tz.c891
1 files changed, 891 insertions, 0 deletions
diff --git a/security/tf_driver/scxlnx_comm_tz.c b/security/tf_driver/scxlnx_comm_tz.c
new file mode 100644
index 000000000000..b186d98548a4
--- /dev/null
+++ b/security/tf_driver/scxlnx_comm_tz.c
@@ -0,0 +1,891 @@
+/*
+ * Copyright (c) 2010 Trusted Logic S.A.
+ * 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 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <asm/div64.h>
+#include <asm/system.h>
+#include <linux/version.h>
+#include <asm/cputype.h>
+#include <linux/interrupt.h>
+#include <linux/page-flags.h>
+#include <linux/pagemap.h>
+#include <linux/vmalloc.h>
+#include <linux/jiffies.h>
+
+#include "scxlnx_defs.h"
+#include "scxlnx_comm.h"
+#include "scx_protocol.h"
+#include "scxlnx_util.h"
+#include "scxlnx_conn.h"
+
+/*
+ * Structure common to all SMC operations
+ */
+struct SCXLNX_GENERIC_SMC {
+ u32 reg0;
+ u32 reg1;
+ u32 reg2;
+ u32 reg3;
+ u32 reg4;
+};
+
+/*----------------------------------------------------------------------------
+ * SMC operations
+ *----------------------------------------------------------------------------*/
+
+static inline void SCXLNXCommCallGenericSMC(
+ struct SCXLNX_GENERIC_SMC *pGenericSMC)
+{
+#ifdef CONFIG_SMP
+ long ret;
+ cpumask_t saved_cpu_mask;
+ cpumask_t local_cpu_mask = CPU_MASK_NONE;
+
+ cpu_set(0, local_cpu_mask);
+ sched_getaffinity(0, &saved_cpu_mask);
+ ret = sched_setaffinity(0, &local_cpu_mask);
+ if (ret != 0)
+ {
+ dprintk(KERN_ERR "sched_setaffinity #1 -> 0x%lX", ret);
+ }
+#endif
+
+ __asm__ volatile(
+ "mov r0, %2\n"
+ "mov r1, %3\n"
+ "mov r2, %4\n"
+ "mov r3, %5\n"
+ "mov r4, %6\n"
+ ".word 0xe1600070 @ SMC 0\n"
+ "mov %0, r0\n"
+ "mov %1, r1\n"
+ : "=r" (pGenericSMC->reg0), "=r" (pGenericSMC->reg1)
+ : "r" (pGenericSMC->reg0), "r" (pGenericSMC->reg1),
+ "r" (pGenericSMC->reg2), "r" (pGenericSMC->reg3),
+ "r" (pGenericSMC->reg4)
+ : "r0", "r1", "r2", "r3", "r4");
+
+#ifdef CONFIG_SMP
+ ret = sched_setaffinity(0, &saved_cpu_mask);
+ if (ret != 0)
+ {
+ dprintk(KERN_ERR "sched_setaffinity #2 -> 0x%lX", ret);
+ }
+#endif
+}
+
+/*
+ * Calls the get protocol version SMC.
+ * Fills the parameter pProtocolVersion with the version number returned by the
+ * SMC
+ */
+static inline void SCXLNXCommCallGetProtocolVersionSMC(u32 *pProcotolVersion)
+{
+ struct SCXLNX_GENERIC_SMC sGenericSMC;
+
+ sGenericSMC.reg0 = SCX_SMC_GET_PROTOCOL_VERSION;
+ sGenericSMC.reg1 = 0;
+ sGenericSMC.reg2 = 0;
+ sGenericSMC.reg3 = 0;
+ sGenericSMC.reg4 = 0;
+
+ SCXLNXCommCallGenericSMC(&sGenericSMC);
+ *pProcotolVersion = sGenericSMC.reg1;
+}
+
+
+/*
+ * Calls the init SMC with the specified parameters.
+ * Returns zero upon successful completion, or an appropriate error code upon
+ * failure.
+ */
+static inline int SCXLNXCommCallInitSMC(u32 nSharedPageDescriptor)
+{
+ struct SCXLNX_GENERIC_SMC sGenericSMC;
+
+ sGenericSMC.reg0 = SCX_SMC_INIT;
+ /* Descriptor for the layer 1 shared buffer */
+ sGenericSMC.reg1 = nSharedPageDescriptor;
+ sGenericSMC.reg2 = 0;
+ sGenericSMC.reg3 = 0;
+ sGenericSMC.reg4 = 0;
+
+ SCXLNXCommCallGenericSMC(&sGenericSMC);
+ if (sGenericSMC.reg0 != S_SUCCESS)
+ printk(KERN_ERR "SCXLNXCommCallInitSMC:"
+ " r0=0x%08X upon return (expected 0x%08X)!\n",
+ sGenericSMC.reg0,
+ S_SUCCESS);
+
+ return sGenericSMC.reg0;
+}
+
+
+/*
+ * Calls the reset irq SMC.
+ */
+static inline void SCXLNXCommCallResetIrqSMC(void)
+{
+ struct SCXLNX_GENERIC_SMC sGenericSMC;
+
+ sGenericSMC.reg0 = SCX_SMC_RESET_IRQ;
+ sGenericSMC.reg1 = 0;
+ sGenericSMC.reg2 = 0;
+ sGenericSMC.reg3 = 0;
+ sGenericSMC.reg4 = 0;
+
+ SCXLNXCommCallGenericSMC(&sGenericSMC);
+}
+
+
+/*
+ * Calls the WAKE_UP SMC.
+ * Returns zero upon successful completion, or an appropriate error code upon
+ * failure.
+ */
+static inline int SCXLNXCommCallWakeUpSMC(u32 nL1SharedBufferDescriptor,
+ u32 nSharedMemStartOffset,
+ u32 nSharedMemSize)
+{
+ struct SCXLNX_GENERIC_SMC sGenericSMC;
+
+ sGenericSMC.reg0 = SCX_SMC_WAKE_UP;
+ sGenericSMC.reg1 = nSharedMemStartOffset;
+ /* long form command */
+ sGenericSMC.reg2 = nSharedMemSize | 0x80000000;
+ sGenericSMC.reg3 = nL1SharedBufferDescriptor;
+ sGenericSMC.reg4 = 0;
+
+ SCXLNXCommCallGenericSMC(&sGenericSMC);
+
+ if (sGenericSMC.reg0 != S_SUCCESS)
+ printk(KERN_ERR "SCXLNXCommCallWakeUpSMC:"
+ " r0=0x%08X upon return (expected 0x%08X)!\n",
+ sGenericSMC.reg0,
+ S_SUCCESS);
+
+ return sGenericSMC.reg0;
+}
+
+/*
+ * Calls the N-Yield SMC.
+ */
+static inline void SCXLNXCommCallNYieldSMC(void)
+{
+ struct SCXLNX_GENERIC_SMC sGenericSMC;
+
+ sGenericSMC.reg0 = SCX_SMC_N_YIELD;
+ sGenericSMC.reg1 = 0;
+ sGenericSMC.reg2 = 0;
+ sGenericSMC.reg3 = 0;
+ sGenericSMC.reg4 = 0;
+
+ SCXLNXCommCallGenericSMC(&sGenericSMC);
+}
+
+/* Yields the Secure World */
+int tf_schedule_secure_world(struct SCXLNX_COMM *pComm, bool prepare_exit)
+{
+ SCXLNXCommSetCurrentTime(pComm);
+
+ /* yield to the Secure World */
+ SCXLNXCommCallNYieldSMC();
+
+ return 0;
+}
+
+/*
+ * Returns the L2 descriptor for the specified user page.
+ */
+
+#define L2_INIT_DESCRIPTOR_BASE (0x00000003)
+#define L2_INIT_DESCRIPTOR_V13_12_SHIFT (4)
+
+static u32 SCXLNXCommGetL2InitDescriptor(void *pVirtAddr)
+{
+ struct page *pPage;
+ u32 nVirtAddr;
+ u32 nPhysAddr;
+ u32 nDescriptor;
+
+ nDescriptor = L2_INIT_DESCRIPTOR_BASE;
+ nVirtAddr = (u32) pVirtAddr;
+
+ /* get physical address and add to nDescriptor */
+ pPage = virt_to_page(pVirtAddr);
+ nPhysAddr = page_to_phys(pPage);
+ nDescriptor |= (nPhysAddr & L2_DESCRIPTOR_ADDR_MASK);
+
+ /* Add virtual address v[13:12] bits to nDescriptor */
+ nDescriptor |= (DESCRIPTOR_V13_12_GET(nVirtAddr)
+ << L2_INIT_DESCRIPTOR_V13_12_SHIFT);
+
+ nDescriptor |= SCXLNXCommGetL2DescriptorCommon(nVirtAddr, &init_mm);
+
+
+ return nDescriptor;
+}
+
+
+/*----------------------------------------------------------------------------
+ * Power management
+ *----------------------------------------------------------------------------*/
+
+/*
+ * Free the memory used by the W3B buffer for the specified comm.
+ * This function does nothing if no W3B buffer is allocated for the device.
+ */
+static inline void SCXLNXCommFreeW3B(struct SCXLNX_COMM *pComm)
+{
+ SCXLNXCommReleaseSharedMemory(
+ &(pComm->sW3BAllocationContext),
+ &(pComm->sW3BShmemDesc),
+ 0);
+
+ SCXLNXReleaseCoarsePageTableAllocator(&(pComm->sW3BAllocationContext));
+
+ internal_vfree((void *)pComm->nW3BShmemVAddr);
+ pComm->nW3BShmemVAddr = 0;
+ pComm->nW3BShmemSize = 0;
+ clear_bit(SCXLNX_COMM_FLAG_W3B_ALLOCATED, &(pComm->nFlags));
+}
+
+
+/*
+ * Allocates the W3B buffer for the specified comm.
+ * Returns zero upon successful completion, or an appropriate error code upon
+ * failure.
+ */
+static inline int SCXLNXCommAllocateW3B(struct SCXLNX_COMM *pComm)
+{
+ int nError;
+ u32 nFlags;
+ u32 nConfigFlags_S;
+ u32 *pW3BDescriptors;
+ u32 nW3BDescriptorCount;
+ u32 nW3BCurrentSize;
+
+ nConfigFlags_S = SCXLNXCommReadReg32(&pComm->pBuffer->nConfigFlags_S);
+
+retry:
+ if ((test_bit(SCXLNX_COMM_FLAG_W3B_ALLOCATED, &(pComm->nFlags))) == 0) {
+ /*
+ * Initialize the shared memory for the W3B
+ */
+ SCXLNXInitializeCoarsePageTableAllocator(
+ &pComm->sW3BAllocationContext);
+ } else {
+ /*
+ * The W3B is allocated but do we have to reallocate a bigger
+ * one?
+ */
+ /* Check H bit */
+ if ((nConfigFlags_S & (1<<4)) != 0) {
+ /* The size of the W3B may change after SMC_INIT */
+ /* Read the current value */
+ nW3BCurrentSize = SCXLNXCommReadReg32(
+ &pComm->pBuffer->nW3BSizeCurrent_S);
+ if (pComm->nW3BShmemSize > nW3BCurrentSize)
+ return 0;
+
+ SCXLNXCommFreeW3B(pComm);
+ goto retry;
+ } else {
+ return 0;
+ }
+ }
+
+ /* check H bit */
+ if ((nConfigFlags_S & (1<<4)) != 0)
+ /* The size of the W3B may change after SMC_INIT */
+ /* Read the current value */
+ pComm->nW3BShmemSize = SCXLNXCommReadReg32(
+ &pComm->pBuffer->nW3BSizeCurrent_S);
+ else
+ pComm->nW3BShmemSize = SCXLNXCommReadReg32(
+ &pComm->pBuffer->nW3BSizeMax_S);
+
+ pComm->nW3BShmemVAddr = (u32) internal_vmalloc(pComm->nW3BShmemSize);
+ if (pComm->nW3BShmemVAddr == 0) {
+ printk(KERN_ERR "SCXLNXCommAllocateW3B():"
+ " Out of memory for W3B buffer (%u bytes)!\n",
+ (unsigned int)(pComm->nW3BShmemSize));
+ nError = -ENOMEM;
+ goto error;
+ }
+
+ /* initialize the sW3BShmemDesc structure */
+ pComm->sW3BShmemDesc.nType = SCXLNX_SHMEM_TYPE_PM_HIBERNATE;
+ INIT_LIST_HEAD(&(pComm->sW3BShmemDesc.list));
+
+ nFlags = (SCX_SHMEM_TYPE_READ | SCX_SHMEM_TYPE_WRITE);
+
+ /* directly point to the L1 shared buffer W3B descriptors */
+ pW3BDescriptors = pComm->pBuffer->nW3BDescriptors;
+
+ /*
+ * SCXLNXCommFillDescriptorTable uses the following parameter as an
+ * IN/OUT
+ */
+
+ nError = SCXLNXCommFillDescriptorTable(
+ &(pComm->sW3BAllocationContext),
+ &(pComm->sW3BShmemDesc),
+ pComm->nW3BShmemVAddr,
+ NULL,
+ pW3BDescriptors,
+ &(pComm->nW3BShmemSize),
+ &(pComm->nW3BShmemOffset),
+ false,
+ nFlags,
+ &nW3BDescriptorCount);
+ if (nError != 0) {
+ printk(KERN_ERR "SCXLNXCommAllocateW3B():"
+ " SCXLNXCommFillDescriptorTable failed with "
+ "error code 0x%08x!\n",
+ nError);
+ goto error;
+ }
+
+ set_bit(SCXLNX_COMM_FLAG_W3B_ALLOCATED, &(pComm->nFlags));
+
+ /* successful completion */
+ return 0;
+
+error:
+ SCXLNXCommFreeW3B(pComm);
+
+ return nError;
+}
+
+/*
+ * Perform a Secure World shutdown operation.
+ * The routine does not return if the operation succeeds.
+ * the routine returns an appropriate error code if
+ * the operation fails.
+ */
+int SCXLNXCommShutdown(struct SCXLNX_COMM *pComm)
+{
+#ifdef CONFIG_TFN
+ /* this function is useless for the TEGRA product */
+ return 0;
+#else
+ int nError;
+ union SCX_COMMAND_MESSAGE sMessage;
+ union SCX_ANSWER_MESSAGE sAnswer;
+
+ dprintk(KERN_INFO "SCXLNXCommShutdown()\n");
+
+ memset(&sMessage, 0, sizeof(sMessage));
+
+ sMessage.sHeader.nMessageType = SCX_MESSAGE_TYPE_MANAGEMENT;
+ sMessage.sHeader.nMessageSize =
+ (sizeof(struct SCX_COMMAND_MANAGEMENT) -
+ sizeof(struct SCX_COMMAND_HEADER))/sizeof(u32);
+
+ sMessage.sManagementMessage.nCommand = SCX_MANAGEMENT_SHUTDOWN;
+
+ nError = SCXLNXCommSendReceive(
+ pComm,
+ &sMessage,
+ &sAnswer,
+ NULL,
+ false);
+
+ if (nError != 0) {
+ dprintk(KERN_ERR "SCXLNXCommShutdown(): "
+ "SCXLNXCommSendReceive failed (error %d)!\n",
+ nError);
+ return nError;
+ }
+
+#ifdef CONFIG_TF_DRIVER_DEBUG_SUPPORT
+ if (sAnswer.sHeader.nErrorCode != 0)
+ dprintk(KERN_ERR "tf_driver: shutdown failed.\n");
+ else
+ dprintk(KERN_INFO "tf_driver: shutdown succeeded.\n");
+#endif
+
+ return sAnswer.sHeader.nErrorCode;
+#endif
+}
+
+
+/*
+ * Perform a Secure World hibernate operation.
+ * The routine does not return if the operation succeeds.
+ * the routine returns an appropriate error code if
+ * the operation fails.
+ */
+int SCXLNXCommHibernate(struct SCXLNX_COMM *pComm)
+{
+#ifdef CONFIG_TFN
+ /* this function is useless for the TEGRA product */
+ return 0;
+#else
+ int nError;
+ union SCX_COMMAND_MESSAGE sMessage;
+ union SCX_ANSWER_MESSAGE sAnswer;
+ u32 nFirstCommand;
+ u32 nFirstFreeCommand;
+
+ dprintk(KERN_INFO "SCXLNXCommHibernate()\n");
+
+ nError = SCXLNXCommAllocateW3B(pComm);
+ if (nError != 0) {
+ dprintk(KERN_ERR "SCXLNXCommHibernate(): "
+ "SCXLNXCommAllocateW3B failed (error %d)!\n",
+ nError);
+ return nError;
+ }
+
+ /*
+ * As the polling thread is already hibernating, we
+ * should send the message and receive the answer ourself
+ */
+
+ /* build the "prepare to hibernate" message */
+ sMessage.sHeader.nMessageType = SCX_MESSAGE_TYPE_MANAGEMENT;
+ sMessage.sManagementMessage.nCommand = SCX_MANAGEMENT_HIBERNATE;
+ /* Long Form Command */
+ sMessage.sManagementMessage.nSharedMemDescriptors[0] = 0;
+ sMessage.sManagementMessage.nSharedMemDescriptors[1] = 0;
+ sMessage.sManagementMessage.nW3BSize =
+ pComm->nW3BShmemSize | 0x80000000;
+ sMessage.sManagementMessage.nW3BStartOffset =
+ pComm->nW3BShmemOffset;
+ sMessage.sHeader.nOperationID = (u32) &sAnswer;
+
+ SCXLNXDumpMessage(&sMessage);
+
+ /* find a slot to send the message in */
+
+ /* AFY: why not use the function SCXLNXCommSendReceive?? We are
+ * duplicating a lot of subtle code here. And it's not going to be
+ * tested because power management is currently not supported by the
+ * secure world. */
+ for (;;) {
+ int nQueueWordsCount, nCommandSize;
+
+ spin_lock(&(pComm->lock));
+
+ nFirstCommand = SCXLNXCommReadReg32(
+ &pComm->pBuffer->nFirstCommand);
+ nFirstFreeCommand = SCXLNXCommReadReg32(
+ &pComm->pBuffer->nFirstFreeCommand);
+
+ nQueueWordsCount = nFirstFreeCommand - nFirstCommand;
+ nCommandSize = sMessage.sHeader.nMessageSize
+ + sizeof(struct SCX_COMMAND_HEADER);
+ if ((nQueueWordsCount + nCommandSize) <
+ SCX_N_MESSAGE_QUEUE_CAPACITY) {
+ /* Command queue is not full */
+ memcpy(&pComm->pBuffer->sCommandQueue[
+ nFirstFreeCommand %
+ SCX_N_MESSAGE_QUEUE_CAPACITY],
+ &sMessage,
+ nCommandSize * sizeof(u32));
+
+ SCXLNXCommWriteReg32(&pComm->pBuffer->nFirstFreeCommand,
+ nFirstFreeCommand + nCommandSize);
+
+ spin_unlock(&(pComm->lock));
+ break;
+ }
+
+ spin_unlock(&(pComm->lock));
+ (void)tf_schedule_secure_world(pComm, false);
+ }
+
+ /* now wait for the answer, dispatching other answers */
+ while (1) {
+ u32 nFirstAnswer;
+ u32 nFirstFreeAnswer;
+
+ /* check all the answers */
+ nFirstFreeAnswer = SCXLNXCommReadReg32(
+ &pComm->pBuffer->nFirstFreeAnswer);
+ nFirstAnswer = SCXLNXCommReadReg32(
+ &pComm->pBuffer->nFirstAnswer);
+
+ if (nFirstAnswer != nFirstFreeAnswer) {
+ int bFoundAnswer = 0;
+
+ do {
+ /* answer queue not empty */
+ union SCX_ANSWER_MESSAGE sComAnswer;
+ struct SCX_ANSWER_HEADER sHeader;
+ /* size of the command in words of 32bit */
+ int nCommandSize;
+
+ /* get the nMessageSize */
+ memcpy(&sHeader,
+ &pComm->pBuffer->sAnswerQueue[
+ nFirstAnswer %
+ SCX_S_ANSWER_QUEUE_CAPACITY],
+ sizeof(struct SCX_ANSWER_HEADER));
+ nCommandSize = sHeader.nMessageSize +
+ sizeof(struct SCX_ANSWER_HEADER);
+
+ /*
+ * NOTE: nMessageSize is the number of words
+ * following the first word
+ */
+ memcpy(&sComAnswer,
+ &pComm->pBuffer->sAnswerQueue[
+ nFirstAnswer %
+ SCX_S_ANSWER_QUEUE_CAPACITY],
+ nCommandSize * sizeof(u32));
+
+ SCXLNXDumpAnswer(&sComAnswer);
+
+ if (sComAnswer.sHeader.nOperationID ==
+ (u32) &sAnswer) {
+ /*
+ * this is the answer to the "prepare to
+ * hibernate" message
+ */
+ memcpy(&sAnswer,
+ &sComAnswer,
+ nCommandSize * sizeof(u32));
+
+ bFoundAnswer = 1;
+ SCXLNXCommWriteReg32(
+ &pComm->pBuffer->nFirstAnswer,
+ nFirstAnswer + nCommandSize);
+ break;
+ } else {
+ /*
+ * this is a standard message answer,
+ * dispatch it
+ */
+ struct SCXLNX_ANSWER_STRUCT
+ *pAnswerStructure;
+
+ pAnswerStructure =
+ (struct SCXLNX_ANSWER_STRUCT *)
+ sComAnswer.sHeader.nOperationID;
+
+ memcpy(pAnswerStructure->pAnswer,
+ &sComAnswer,
+ nCommandSize * sizeof(u32));
+
+ pAnswerStructure->bAnswerCopied = true;
+ }
+
+ SCXLNXCommWriteReg32(
+ &pComm->pBuffer->nFirstAnswer,
+ nFirstAnswer + nCommandSize);
+ } while (nFirstAnswer != nFirstFreeAnswer);
+
+ if (bFoundAnswer)
+ break;
+ }
+
+ /*
+ * since the Secure World is at least running the "prepare to
+ * hibernate" message, its timeout must be immediate So there is
+ * no need to check its timeout and schedule() the current
+ * thread
+ */
+ (void)tf_schedule_secure_world(pComm, false);
+ } /* while (1) */
+
+ printk(KERN_INFO "tf_driver: hibernate.\n");
+ return 0;
+#endif
+}
+
+
+/*
+ * Perform a Secure World resume operation.
+ * The routine returns once the Secure World is active again
+ * or if an error occurs during the "resume" process
+ */
+int SCXLNXCommResume(struct SCXLNX_COMM *pComm)
+{
+#ifdef CONFIG_TFN
+ /* this function is useless for the TEGRA product */
+ return 0;
+#else
+ int nError;
+ u32 nStatus;
+
+ dprintk(KERN_INFO "SCXLNXCommResume()\n");
+
+ nError = SCXLNXCommCallWakeUpSMC(
+ SCXLNXCommGetL2InitDescriptor(pComm->pBuffer),
+ pComm->nW3BShmemOffset,
+ pComm->nW3BShmemSize);
+
+ if (nError != 0) {
+ dprintk(KERN_ERR "SCXLNXCommResume(): "
+ "SCXLNXCommCallWakeUpSMC failed (error %d)!\n",
+ nError);
+ return nError;
+ }
+
+ nStatus = ((SCXLNXCommReadReg32(&(pComm->pBuffer->nStatus_S))
+ & SCX_STATUS_POWER_STATE_MASK)
+ >> SCX_STATUS_POWER_STATE_SHIFT);
+
+ while ((nStatus != SCX_POWER_MODE_ACTIVE)
+ && (nStatus != SCX_POWER_MODE_PANIC)) {
+ SCXLNXCommCallNYieldSMC();
+
+ nStatus = ((SCXLNXCommReadReg32(&(pComm->pBuffer->nStatus_S))
+ & SCX_STATUS_POWER_STATE_MASK)
+ >> SCX_STATUS_POWER_STATE_SHIFT);
+
+ /*
+ * As this may last quite a while, call the kernel scheduler to
+ * hand over CPU for other operations
+ */
+ schedule();
+ }
+
+ switch (nStatus) {
+ case SCX_POWER_MODE_ACTIVE:
+ break;
+
+ case SCX_POWER_MODE_PANIC:
+ dprintk(KERN_ERR "SCXLNXCommResume(): "
+ "Secure World POWER_MODE_PANIC!\n");
+ return -EINVAL;
+
+ default:
+ dprintk(KERN_ERR "SCXLNXCommResume(): "
+ "unexpected Secure World POWER_MODE (%d)!\n", nStatus);
+ return -EINVAL;
+ }
+
+ dprintk(KERN_INFO "SCXLNXCommResume() succeeded\n");
+ return 0;
+#endif
+}
+
+/*----------------------------------------------------------------------------
+ * Communication initialization and termination
+ *----------------------------------------------------------------------------*/
+
+/*
+ * Handles the software interrupts issued by the Secure World.
+ */
+static irqreturn_t SCXLNXCommSoftIntHandler(int irq, void *dev_id)
+{
+ struct SCXLNX_COMM *pComm = (struct SCXLNX_COMM *) dev_id;
+
+ if (pComm->pBuffer == NULL)
+ return IRQ_NONE;
+
+ if ((SCXLNXCommReadReg32(&pComm->pBuffer->nStatus_S) &
+ SCX_STATUS_P_MASK) == 0)
+ /* interrupt not issued by the Trusted Foundations Software */
+ return IRQ_NONE;
+
+ SCXLNXCommCallResetIrqSMC();
+
+ /* signal N_SM_EVENT */
+ wake_up(&pComm->waitQueue);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Initializes the communication with the Secure World.
+ * The L1 shared buffer is allocated and the Secure World
+ * is yielded for the first time.
+ * returns successfuly once the communication with
+ * the Secure World is up and running
+ *
+ * Returns 0 upon success or appropriate error code
+ * upon failure
+ */
+int SCXLNXCommInit(struct SCXLNX_COMM *pComm)
+{
+ int nError;
+ struct page *pBufferPage;
+ u32 nProtocolVersion;
+
+ dprintk(KERN_INFO "SCXLNXCommInit()\n");
+
+ spin_lock_init(&(pComm->lock));
+ pComm->nFlags = 0;
+ pComm->pBuffer = NULL;
+ init_waitqueue_head(&(pComm->waitQueue));
+
+ /*
+ * Check the Secure World protocol version is the expected one.
+ */
+ SCXLNXCommCallGetProtocolVersionSMC(&nProtocolVersion);
+
+ if ((GET_PROTOCOL_MAJOR_VERSION(nProtocolVersion))
+ != SCX_S_PROTOCOL_MAJOR_VERSION) {
+ printk(KERN_ERR "SCXLNXCommInit():"
+ " Unsupported Secure World Major Version "
+ "(0x%02X, expected 0x%02X)!\n",
+ GET_PROTOCOL_MAJOR_VERSION(nProtocolVersion),
+ SCX_S_PROTOCOL_MAJOR_VERSION);
+ nError = -EIO;
+ goto error;
+ }
+
+ /*
+ * Register the software interrupt handler if required to.
+ */
+ if (pComm->nSoftIntIrq != -1) {
+ dprintk(KERN_INFO "SCXLNXCommInit(): "
+ "Registering software interrupt handler (IRQ %d)\n",
+ pComm->nSoftIntIrq);
+
+ nError = request_irq(pComm->nSoftIntIrq,
+ SCXLNXCommSoftIntHandler,
+ IRQF_SHARED,
+ SCXLNX_DEVICE_BASE_NAME,
+ pComm);
+ if (nError != 0) {
+ dprintk(KERN_ERR "SCXLNXCommInit(): "
+ "request_irq failed for irq %d (error %d)\n",
+ pComm->nSoftIntIrq, nError);
+ goto error;
+ }
+ set_bit(SCXLNX_COMM_FLAG_IRQ_REQUESTED, &(pComm->nFlags));
+ }
+
+ /*
+ * Allocate and initialize the L1 shared buffer.
+ */
+ pComm->pBuffer = (void *) internal_get_zeroed_page(GFP_KERNEL);
+ if (pComm->pBuffer == NULL) {
+ printk(KERN_ERR "SCXLNXCommInit():"
+ " get_zeroed_page failed for L1 shared buffer!\n");
+ nError = -ENOMEM;
+ goto error;
+ }
+
+ /*
+ * Ensure the page storing the L1 shared buffer is mapped.
+ */
+ pBufferPage = virt_to_page(pComm->pBuffer);
+ trylock_page(pBufferPage);
+
+ dprintk(KERN_INFO "SCXLNXCommInit(): "
+ "L1 shared buffer allocated at virtual:%p, "
+ "physical:%p (page:%p)\n",
+ pComm->pBuffer,
+ (void *)virt_to_phys(pComm->pBuffer),
+ pBufferPage);
+
+ set_bit(SCXLNX_COMM_FLAG_L1_SHARED_ALLOCATED, &(pComm->nFlags));
+
+ /*
+ * Init SMC
+ */
+ nError = SCXLNXCommCallInitSMC(
+ SCXLNXCommGetL2InitDescriptor(pComm->pBuffer));
+ if (nError != S_SUCCESS) {
+ dprintk(KERN_ERR "SCXLNXCommInit(): "
+ "SCXLNXCommCallInitSMC failed (error 0x%08X)!\n",
+ nError);
+ goto error;
+ }
+
+ /*
+ * check whether the interrupts are actually enabled
+ * If not, remove irq handler
+ */
+ if ((SCXLNXCommReadReg32(&pComm->pBuffer->nConfigFlags_S) &
+ SCX_CONFIG_FLAG_S) == 0) {
+ if (test_and_clear_bit(SCXLNX_COMM_FLAG_IRQ_REQUESTED,
+ &(pComm->nFlags)) != 0) {
+ dprintk(KERN_INFO "SCXLNXCommInit(): "
+ "Interrupts not used, unregistering "
+ "softint (IRQ %d)\n",
+ pComm->nSoftIntIrq);
+
+ free_irq(pComm->nSoftIntIrq, pComm);
+ }
+ } else {
+ if (test_bit(SCXLNX_COMM_FLAG_IRQ_REQUESTED,
+ &(pComm->nFlags)) == 0) {
+ /*
+ * Interrupts are enabled in the Secure World, but not
+ * handled by driver
+ */
+ dprintk(KERN_ERR "SCXLNXCommInit(): "
+ "soft_interrupt argument not provided\n");
+ nError = -EINVAL;
+ goto error;
+ }
+ }
+
+ /*
+ * Successful completion.
+ */
+
+ /* yield for the first time */
+ (void)tf_schedule_secure_world(pComm, false);
+
+ dprintk(KERN_INFO "SCXLNXCommInit(): Success\n");
+ return S_SUCCESS;
+
+error:
+ /*
+ * Error handling.
+ */
+ dprintk(KERN_INFO "SCXLNXCommInit(): Failure (error %d)\n",
+ nError);
+ SCXLNXCommTerminate(pComm);
+ return nError;
+}
+
+
+/*
+ * Attempt to terminate the communication with the Secure World.
+ * The L1 shared buffer is freed.
+ * Calling this routine terminates definitaly the communication
+ * with the Secure World : there is no way to inform the Secure World of a new
+ * L1 shared buffer to be used once it has been initialized.
+ */
+void SCXLNXCommTerminate(struct SCXLNX_COMM *pComm)
+{
+ dprintk(KERN_INFO "SCXLNXCommTerminate()\n");
+
+ set_bit(SCXLNX_COMM_FLAG_TERMINATING, &(pComm->nFlags));
+
+ if ((test_bit(SCXLNX_COMM_FLAG_W3B_ALLOCATED,
+ &(pComm->nFlags))) != 0) {
+ dprintk(KERN_INFO "SCXLNXCommTerminate(): "
+ "Freeing the W3B buffer...\n");
+ SCXLNXCommFreeW3B(pComm);
+ }
+
+ if ((test_bit(SCXLNX_COMM_FLAG_L1_SHARED_ALLOCATED,
+ &(pComm->nFlags))) != 0) {
+ __clear_page_locked(virt_to_page(pComm->pBuffer));
+ internal_free_page((unsigned long) pComm->pBuffer);
+ }
+
+ if ((test_bit(SCXLNX_COMM_FLAG_IRQ_REQUESTED,
+ &(pComm->nFlags))) != 0) {
+ dprintk(KERN_INFO "SCXLNXCommTerminate(): "
+ "Unregistering softint (IRQ %d)\n",
+ pComm->nSoftIntIrq);
+ free_irq(pComm->nSoftIntIrq, pComm);
+ }
+}