mirror of
https://github.com/genesi/linux-legacy.git
synced 2026-09-06 06:47:36 +00:00
SCC2 driver changes to set base address of registers and scc ram based on resources Signed-off-by: Anish Trivedi <anish@freescale.com>
2391 lines
71 KiB
C
2391 lines
71 KiB
C
/*
|
|
* Copyright (C) 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
*/
|
|
|
|
/*
|
|
* The code contained herein is licensed under the GNU General Public
|
|
* License. You may obtain a copy of the GNU General Public License
|
|
* Version 2 or later at the following locations:
|
|
*
|
|
* http://www.opensource.org/licenses/gpl-license.html
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
|
|
/*! @file scc2_driver.c
|
|
*
|
|
* This is the driver code for the Security Controller version 2 (SCC2). It's
|
|
* interaction with the Linux kernel is from calls to #scc_init() when the
|
|
* driver is loaded, and #scc_cleanup() should the driver be unloaded. The
|
|
* driver uses locking and (task-sleep/task-wakeup) functions from the kernel.
|
|
* It also registers itself to handle the interrupt line(s) from the SCC. New
|
|
* to this version of the driver is an interface providing access to the secure
|
|
* partitions. This is in turn exposed to the API user through the
|
|
* fsl_shw_smalloc() series of functions. Other drivers in the kernel may use
|
|
* the remaining API functions to get at the services of the SCC. The main
|
|
* service provided is the Secure Memory, which allows encoding and decoding of
|
|
* secrets with a per-chip secret key.
|
|
*
|
|
* The SCC is single-threaded, and so is this module. When the scc_crypt()
|
|
* routine is called, it will lock out other accesses to the function. If
|
|
* another task is already in the module, the subsequent caller will spin on a
|
|
* lock waiting for the other access to finish.
|
|
*
|
|
* Note that long crypto operations could cause a task to spin for a while,
|
|
* preventing other kernel work (other than interrupt processing) to get done.
|
|
*
|
|
* The external (kernel module) interface is through the following functions:
|
|
* @li scc_get_configuration() @li scc_crypt() @li scc_zeroize_memories() @li
|
|
* scc_monitor_security_failure() @li scc_stop_monitoring_security_failure()
|
|
* @li scc_set_sw_alarm() @li scc_read_register() @li scc_write_register() @li
|
|
* scc_allocate_partition() @li scc_initialize_partition @li
|
|
* scc_release_partition() @li scc_diminish_permissions @li
|
|
* scc_encrypt_region() @li scc_decrypt_region() @li scc_virt_to_phys
|
|
*
|
|
* All other functions are internal to the driver.
|
|
*/
|
|
|
|
#include "sahara2/include/portable_os.h"
|
|
#include "scc2_internals.h"
|
|
#include <linux/delay.h>
|
|
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18))
|
|
|
|
#include <linux/device.h>
|
|
#include <mach/clock.h>
|
|
#include <linux/device.h>
|
|
|
|
#else
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
|
|
#endif
|
|
|
|
#include <linux/dmapool.h>
|
|
|
|
/**
|
|
* This is the set of errors which signal that access to the SCM RAM has
|
|
* failed or will fail.
|
|
*/
|
|
#define SCM_ACCESS_ERRORS \
|
|
(SCM_ERRSTAT_ILM | SCM_ERRSTAT_SUP | SCM_ERRSTAT_ERC_MASK)
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Global / Static Variables
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#ifdef SCC_REGISTER_DEBUG
|
|
|
|
#define REG_PRINT_BUFFER_SIZE 200
|
|
|
|
static char reg_print_buffer[REG_PRINT_BUFFER_SIZE];
|
|
|
|
typedef char *(*reg_print_routine_t) (uint32_t value, char *print_buffer,
|
|
int buf_size);
|
|
|
|
#endif
|
|
|
|
/**
|
|
* This is type void* so that a) it cannot directly be dereferenced,
|
|
* and b) pointer arithmetic on it will function in a 'normal way' for
|
|
* the offsets in scc_defines.h
|
|
*
|
|
* scc_base is the location in the iomap where the SCC's registers
|
|
* (and memory) start.
|
|
*
|
|
* The referenced data is declared volatile so that the compiler will
|
|
* not make any assumptions about the value of registers in the SCC,
|
|
* and thus will always reload the register into CPU memory before
|
|
* using it (i.e. wherever it is referenced in the driver).
|
|
*
|
|
* This value should only be referenced by the #SCC_READ_REGISTER and
|
|
* #SCC_WRITE_REGISTER macros and their ilk. All dereferences must be
|
|
* 32 bits wide.
|
|
*/
|
|
static volatile void *scc_base;
|
|
uint32_t scc_phys_base;
|
|
|
|
/** Array to hold function pointers registered by
|
|
#scc_monitor_security_failure() and processed by
|
|
#scc_perform_callbacks() */
|
|
static void (*scc_callbacks[SCC_CALLBACK_SIZE]) (void);
|
|
/*SCC need IRAM's base address but use only the partitions allocated for it.*/
|
|
uint32_t scm_ram_phys_base;
|
|
|
|
void *scm_ram_base = NULL;
|
|
|
|
/** Calculated once for quick reference to size of the unreserved space in
|
|
* RAM in SCM.
|
|
*/
|
|
uint32_t scm_memory_size_bytes;
|
|
|
|
/** Structure returned by #scc_get_configuration() */
|
|
static scc_config_t scc_configuration = {
|
|
.driver_major_version = SCC_DRIVER_MAJOR_VERSION,
|
|
.driver_minor_version = SCC_DRIVER_MINOR_VERSION_2,
|
|
.scm_version = -1,
|
|
.smn_version = -1,
|
|
.block_size_bytes = -1,
|
|
.partition_size_bytes = -1,
|
|
.partition_count = -1,
|
|
};
|
|
|
|
/** Internal flag to know whether SCC is in Failed state (and thus many
|
|
* registers are unavailable). Once it goes failed, it never leaves it. */
|
|
static volatile enum scc_status scc_availability = SCC_STATUS_INITIAL;
|
|
|
|
/** Flag to say whether interrupt handler has been registered for
|
|
* SMN interrupt */
|
|
static int smn_irq_set = 0;
|
|
|
|
/** Flag to say whether interrupt handler has been registered for
|
|
* SCM interrupt */
|
|
static int scm_irq_set = 0;
|
|
|
|
/** This lock protects the #scc_callbacks list as well as the @c
|
|
* callbacks_performed flag in #scc_perform_callbacks. Since the data this
|
|
* protects may be read or written from either interrupt or base level, all
|
|
* operations should use the irqsave/irqrestore or similar to make sure that
|
|
* interrupts are inhibited when locking from base level.
|
|
*/
|
|
static os_lock_t scc_callbacks_lock = NULL;
|
|
|
|
/**
|
|
* Ownership of this lock prevents conflicts on the crypto operation in the
|
|
* SCC.
|
|
*/
|
|
static os_lock_t scc_crypto_lock = NULL;
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18))
|
|
/** Pointer to SCC's clock information. Initialized during scc_init(). */
|
|
static struct clk *scc_clk = NULL;
|
|
#endif
|
|
|
|
/** The lookup table for an 8-bit value. Calculated once
|
|
* by #scc_init_ccitt_crc().
|
|
*/
|
|
static uint16_t scc_crc_lookup_table[256];
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Function Implementations - Externally Accessible
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
* Allocate a partition of secure memory
|
|
*
|
|
* @param smid_value Value to use for the SMID register. Must be 0 for
|
|
* kernel mode access.
|
|
* @param[out] part_no (If successful) Assigned partition number.
|
|
* @param[out] part_base Kernel virtual address of the partition.
|
|
* @param[out] part_phys Physical address of the partition.
|
|
*
|
|
* @return
|
|
*/
|
|
scc_return_t scc_allocate_partition(uint32_t smid_value,
|
|
int *part_no,
|
|
void **part_base, uint32_t *part_phys)
|
|
{
|
|
uint32_t i;
|
|
os_lock_context_t irq_flags = 0; /* for IRQ save/restore */
|
|
int local_part;
|
|
scc_return_t retval = SCC_RET_FAIL;
|
|
void *base_addr = NULL;
|
|
uint32_t reg_value;
|
|
|
|
local_part = -1;
|
|
|
|
if (scc_availability == SCC_STATUS_INITIAL) {
|
|
scc_init();
|
|
}
|
|
if (scc_availability == SCC_STATUS_UNIMPLEMENTED) {
|
|
goto out;
|
|
}
|
|
|
|
/* ACQUIRE LOCK to prevent others from using crypto or acquiring a
|
|
* partition. Note that crypto operations could take a long time, so the
|
|
* calling process could potentially spin for some time.
|
|
*/
|
|
os_lock_save_context(scc_crypto_lock, irq_flags);
|
|
|
|
do {
|
|
/* Find current state of partition ownership */
|
|
reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG);
|
|
|
|
/* Search for a free one */
|
|
for (i = 0; i < scc_configuration.partition_count; i++) {
|
|
if (((reg_value >> (SCM_POWN_SHIFT * i))
|
|
& SCM_POWN_MASK) == SCM_POWN_PART_FREE) {
|
|
break; /* found a free one */
|
|
}
|
|
}
|
|
if (i == local_part) {
|
|
/* found this one last time, and failed to allocated it */
|
|
pr_debug(KERN_ERR "Partition %d cannot be allocated\n",
|
|
i);
|
|
goto out;
|
|
}
|
|
if (i >= scc_configuration.partition_count) {
|
|
retval = SCC_RET_INSUFFICIENT_SPACE; /* all used up */
|
|
goto out;
|
|
}
|
|
|
|
pr_debug
|
|
("SCC2: Attempting to allocate partition %i, owners:%08x\n",
|
|
i, SCC_READ_REGISTER(SCM_PART_OWNERS_REG));
|
|
|
|
local_part = i;
|
|
/* Store SMID to grab a partition */
|
|
SCC_WRITE_REGISTER(SCM_SMID0_REG +
|
|
SCM_SMID_WIDTH * (local_part), smid_value);
|
|
mdelay(2);
|
|
|
|
/* Now make sure it is ours... ? */
|
|
reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG);
|
|
|
|
if (((reg_value >> (SCM_POWN_SHIFT * (local_part)))
|
|
& SCM_POWN_MASK) != SCM_POWN_PART_OWNED) {
|
|
continue; /* try for another */
|
|
}
|
|
base_addr = scm_ram_base +
|
|
(local_part * scc_configuration.partition_size_bytes);
|
|
break;
|
|
} while (1);
|
|
|
|
out:
|
|
|
|
/* Free the lock */
|
|
os_unlock_restore_context(scc_callbacks_lock, irq_flags);
|
|
|
|
/* If the base address was assigned, then a partition was successfully
|
|
* acquired.
|
|
*/
|
|
if (base_addr != NULL) {
|
|
pr_debug("SCC2 Part owners: %08x, engaged: %08x\n",
|
|
reg_value, SCC_READ_REGISTER(SCM_PART_ENGAGED_REG));
|
|
pr_debug("SCC2 MAP for part %d: %08x\n",
|
|
local_part,
|
|
SCC_READ_REGISTER(SCM_ACC0_REG + 8 * local_part));
|
|
|
|
/* Copy the partition information to the data structures passed by the
|
|
* user.
|
|
*/
|
|
*part_no = local_part;
|
|
*part_base = base_addr;
|
|
*part_phys = (uint32_t) scm_ram_phys_base
|
|
+ (local_part * scc_configuration.partition_size_bytes);
|
|
retval = SCC_RET_OK;
|
|
|
|
pr_debug
|
|
("SCC2 partition engaged. Kernel address: %p. Physical "
|
|
"address: %p, pfn: %08x\n", *part_base, (void *)*part_phys,
|
|
__phys_to_pfn(*part_phys));
|
|
}
|
|
|
|
return retval;
|
|
} /* allocate_partition() */
|
|
|
|
/**
|
|
* Release a partition of secure memory
|
|
*
|
|
* @param part_base Kernel virtual address of the partition to be released.
|
|
*
|
|
* @return SCC_RET_OK if successful.
|
|
*/
|
|
scc_return_t scc_release_partition(void *part_base)
|
|
{
|
|
uint32_t partition_no;
|
|
|
|
if (part_base == NULL) {
|
|
return SCC_RET_FAIL;
|
|
}
|
|
|
|
/* Ensure that this is a proper partition location */
|
|
partition_no = SCM_PART_NUMBER((uint32_t) part_base);
|
|
|
|
pr_debug("SCC2: Attempting to release partition %i, owners:%08x\n",
|
|
partition_no, SCC_READ_REGISTER(SCM_PART_OWNERS_REG));
|
|
|
|
/* check that the partition is ours to de-establish */
|
|
if (!host_owns_partition(partition_no)) {
|
|
return SCC_RET_FAIL;
|
|
}
|
|
|
|
/* TODO: The state of the zeroize engine (SRS field in the Command Status
|
|
* Register) should be examined before issuing the zeroize command here.
|
|
* To make the driver thread-safe, a lock should be taken out before
|
|
* issuing the check and released after the zeroize command has been
|
|
* issued.
|
|
*/
|
|
|
|
/* Zero the partition to release it */
|
|
scc_write_register(SCM_ZCMD_REG,
|
|
(partition_no << SCM_ZCMD_PART_SHIFT) |
|
|
(ZCMD_DEALLOC_PART << SCM_ZCMD_CCMD_SHIFT));
|
|
mdelay(2);
|
|
|
|
pr_debug("SCC2: done releasing partition %i, owners:%08x\n",
|
|
partition_no, SCC_READ_REGISTER(SCM_PART_OWNERS_REG));
|
|
|
|
/* Check that the de-assignment went correctly */
|
|
if (host_owns_partition(partition_no)) {
|
|
return SCC_RET_FAIL;
|
|
}
|
|
|
|
return SCC_RET_OK;
|
|
}
|
|
|
|
/**
|
|
* Diminish the permissions on a partition of secure memory
|
|
*
|
|
* @param part_base Kernel virtual address of the partition.
|
|
* @param permissions ORed values of the type SCM_PERM_* which will be used as
|
|
* initial partition permissions. SHW API users should use
|
|
* the FSL_PERM_* definitions instead.
|
|
*
|
|
* @return SCC_RET_OK if successful.
|
|
*/
|
|
scc_return_t scc_diminish_permissions(void *part_base, uint32_t permissions)
|
|
{
|
|
uint32_t partition_no;
|
|
uint32_t permissions_requested;
|
|
permissions_requested = permissions;
|
|
|
|
/* ensure that this is a proper partition location */
|
|
partition_no = SCM_PART_NUMBER((uint32_t) part_base);
|
|
|
|
/* invert the permissions, masking out unused bits */
|
|
permissions = (~permissions) & SCM_PERM_MASK;
|
|
|
|
/* attempt to diminish the permissions */
|
|
scc_write_register(SCM_ACC0_REG + 8 * partition_no, permissions);
|
|
mdelay(2);
|
|
|
|
/* Reading it back puts it into the original form */
|
|
permissions = SCC_READ_REGISTER(SCM_ACC0_REG + 8 * partition_no);
|
|
if (permissions == permissions_requested) {
|
|
pr_debug("scc_partition_diminish_perms: successful\n");
|
|
pr_debug("scc_partition_diminish_perms: successful\n");
|
|
return SCC_RET_OK;
|
|
}
|
|
pr_debug("scc_partition_diminish_perms: not successful\n");
|
|
|
|
return SCC_RET_FAIL;
|
|
}
|
|
|
|
extern scc_partition_status_t scc_partition_status(void *part_base)
|
|
{
|
|
uint32_t part_no;
|
|
uint32_t part_owner;
|
|
|
|
/* Determine the partition number from the address */
|
|
part_no = SCM_PART_NUMBER((uint32_t) part_base);
|
|
|
|
/* Check if the partition is implemented */
|
|
if (part_no >= scc_configuration.partition_count) {
|
|
return SCC_PART_S_UNUSABLE;
|
|
}
|
|
|
|
/* Determine the value of the partition owners register */
|
|
part_owner = (SCC_READ_REGISTER(SCM_PART_OWNERS_REG)
|
|
>> (part_no * SCM_POWN_SHIFT)) & SCM_POWN_MASK;
|
|
|
|
switch (part_owner) {
|
|
case SCM_POWN_PART_OTHER:
|
|
return SCC_PART_S_UNAVAILABLE;
|
|
break;
|
|
case SCM_POWN_PART_FREE:
|
|
return SCC_PART_S_AVAILABLE;
|
|
break;
|
|
case SCM_POWN_PART_OWNED:
|
|
/* could be allocated or engaged*/
|
|
if (partition_engaged(part_no)) {
|
|
return SCC_PART_S_ENGAGED;
|
|
} else {
|
|
return SCC_PART_S_ALLOCATED;
|
|
}
|
|
break;
|
|
case SCM_POWN_PART_UNUSABLE:
|
|
default:
|
|
return SCC_PART_S_UNUSABLE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the physical address from the kernel virtual address.
|
|
*
|
|
* @param address Kernel virtual address of data in an Secure Partition.
|
|
* @return Physical address of said data.
|
|
*/
|
|
uint32_t scc_virt_to_phys(void *address)
|
|
{
|
|
return (uint32_t) address - (uint32_t) scm_ram_base
|
|
+ (uint32_t) scm_ram_phys_base;
|
|
}
|
|
|
|
/**
|
|
* Engage partition of secure memory
|
|
*
|
|
* @param part_base (kernel) Virtual
|
|
* @param UMID NULL, or 16-byte UMID for partition security
|
|
* @param permissions ORed values from fsl_shw_permission_t which
|
|
* will be used as initial partiition permissions.
|
|
*
|
|
* @return SCC_RET_OK if successful.
|
|
*/
|
|
|
|
scc_return_t
|
|
scc_engage_partition(void *part_base,
|
|
const uint8_t *UMID, uint32_t permissions)
|
|
{
|
|
uint32_t partition_no;
|
|
uint8_t *UMID_base = part_base + 0x10;
|
|
uint32_t *MAP_base = part_base;
|
|
uint8_t i;
|
|
|
|
partition_no = SCM_PART_NUMBER((uint32_t) part_base);
|
|
|
|
if (!host_owns_partition(partition_no) ||
|
|
partition_engaged(partition_no) ||
|
|
!(SCC_READ_REGISTER(SCM_SMID0_REG + (partition_no * 8)) == 0)) {
|
|
|
|
return SCC_RET_FAIL;
|
|
}
|
|
|
|
if (UMID != NULL) {
|
|
for (i = 0; i < 16; i++) {
|
|
UMID_base[i] = UMID[i];
|
|
}
|
|
}
|
|
|
|
MAP_base[0] = permissions;
|
|
|
|
udelay(20);
|
|
|
|
/* Check that the partition was engaged correctly, and that it has the
|
|
* proper permissions.
|
|
*/
|
|
|
|
if ((!partition_engaged(partition_no)) ||
|
|
(permissions !=
|
|
SCC_READ_REGISTER(SCM_ACC0_REG + 8 * partition_no))) {
|
|
return SCC_RET_FAIL;
|
|
}
|
|
|
|
return SCC_RET_OK;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_init() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Initialize the driver at boot time or module load time.
|
|
*
|
|
* Register with the kernel as the interrupt handler for the SCC interrupt
|
|
* line(s).
|
|
*
|
|
* Map the SCC's register space into the driver's memory space.
|
|
*
|
|
* Query the SCC for its configuration and status. Save the configuration in
|
|
* #scc_configuration and save the status in #scc_availability. Called by the
|
|
* kernel.
|
|
*
|
|
* Do any locking/wait queue initialization which may be necessary.
|
|
*
|
|
* The availability fuse may be checked, depending on platform.
|
|
*/
|
|
static int scc_init(void)
|
|
{
|
|
uint32_t smn_status;
|
|
int i;
|
|
int return_value = -EIO; /* assume error */
|
|
|
|
if (scc_availability == SCC_STATUS_INITIAL) {
|
|
|
|
/* Set this until we get an initial reading */
|
|
scc_availability = SCC_STATUS_CHECKING;
|
|
|
|
/* Initialize the constant for the CRC function */
|
|
scc_init_ccitt_crc();
|
|
|
|
/* initialize the callback table */
|
|
for (i = 0; i < SCC_CALLBACK_SIZE; i++) {
|
|
scc_callbacks[i] = 0;
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18))
|
|
mxc_clks_enable(SCC_CLK);
|
|
#else
|
|
scc_clk = clk_get(NULL, "scc_clk");
|
|
if (scc_clk != ERR_PTR(ENOENT)) {
|
|
clk_enable(scc_clk);
|
|
}
|
|
#endif
|
|
|
|
/* Set up the hardware access locks */
|
|
scc_callbacks_lock = os_lock_alloc_init();
|
|
scc_crypto_lock = os_lock_alloc_init();
|
|
if (scc_callbacks_lock == NULL || scc_crypto_lock == NULL) {
|
|
os_printk(KERN_ERR
|
|
"SCC2: Failed to allocate context locks. Exiting.\n");
|
|
goto out;
|
|
}
|
|
|
|
/* See whether there is an SCC available */
|
|
if (0 && !SCC_ENABLED()) {
|
|
os_printk(KERN_ERR
|
|
"SCC2: Fuse for SCC is set to disabled. Exiting.\n");
|
|
goto out;
|
|
}
|
|
/* Map the SCC (SCM and SMN) memory on the internal bus into
|
|
kernel address space */
|
|
scc_base = (void *)ioremap(scc_phys_base, SZ_4K);
|
|
if (scc_base == NULL) {
|
|
os_printk(KERN_ERR
|
|
"SCC2: Register mapping failed. Exiting.\n");
|
|
goto out;
|
|
}
|
|
|
|
/* If that worked, we can try to use the SCC */
|
|
/* Get SCM into 'clean' condition w/interrupts cleared &
|
|
disabled */
|
|
SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0);
|
|
|
|
/* Clear error status register */
|
|
(void)SCC_READ_REGISTER(SCM_ERR_STATUS_REG);
|
|
|
|
/*
|
|
* There is an SCC. Determine its current state. Side effect
|
|
* is to populate scc_config and scc_availability
|
|
*/
|
|
smn_status = scc_grab_config_values();
|
|
|
|
/* Try to set up interrupt handler(s) */
|
|
if (scc_availability != SCC_STATUS_OK) {
|
|
goto out;
|
|
}
|
|
|
|
if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0)
|
|
scm_ram_phys_base += 0x8000;
|
|
|
|
scm_ram_base = (void *)ioremap_nocache(scm_ram_phys_base,
|
|
scc_configuration.
|
|
partition_count *
|
|
scc_configuration.
|
|
partition_size_bytes);
|
|
if (scm_ram_base == NULL) {
|
|
os_printk(KERN_ERR
|
|
"SCC2: RAM failed to remap: %p for %d bytes\n",
|
|
(void *)scm_ram_phys_base,
|
|
scc_configuration.partition_count *
|
|
scc_configuration.partition_size_bytes);
|
|
goto out;
|
|
}
|
|
pr_debug("SCC2: RAM at Physical %p / Virtual %p\n",
|
|
(void *)scm_ram_phys_base, scm_ram_base);
|
|
|
|
pr_debug("Secure Partition Table: Found %i partitions\n",
|
|
scc_configuration.partition_count);
|
|
|
|
if (setup_interrupt_handling() != 0) {
|
|
unsigned err_cond;
|
|
/**
|
|
* The error could be only that the SCM interrupt was
|
|
* not set up. This interrupt is always masked, so
|
|
* that is not an issue.
|
|
* The SMN's interrupt may be shared on that line, it
|
|
* may be separate, or it may not be wired. Do what
|
|
* is necessary to check its status.
|
|
* Although the driver is coded for possibility of not
|
|
* having SMN interrupt, the fact that there is one
|
|
* means it should be available and used.
|
|
*/
|
|
#ifdef USE_SMN_INTERRUPT
|
|
err_cond = !smn_irq_set; /* Separate. Check SMN binding */
|
|
#elif !defined(NO_SMN_INTERRUPT)
|
|
err_cond = !scm_irq_set; /* Shared. Check SCM binding */
|
|
#else
|
|
err_cond = FALSE; /* SMN not wired at all. Ignore. */
|
|
#endif
|
|
if (err_cond) {
|
|
/* setup was not able to set up SMN interrupt */
|
|
scc_availability = SCC_STATUS_UNIMPLEMENTED;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* interrupt handling returned non-zero */
|
|
/* Get SMN into 'clean' condition w/interrupts cleared &
|
|
enabled */
|
|
SCC_WRITE_REGISTER(SMN_COMMAND_REG,
|
|
SMN_COMMAND_CLEAR_INTERRUPT
|
|
| SMN_COMMAND_ENABLE_INTERRUPT);
|
|
|
|
out:
|
|
/*
|
|
* If status is SCC_STATUS_UNIMPLEMENTED or is still
|
|
* SCC_STATUS_CHECKING, could be leaving here with the driver partially
|
|
* initialized. In either case, cleanup (which will mark the SCC as
|
|
* UNIMPLEMENTED).
|
|
*/
|
|
if (scc_availability == SCC_STATUS_CHECKING ||
|
|
scc_availability == SCC_STATUS_UNIMPLEMENTED) {
|
|
scc_cleanup();
|
|
} else {
|
|
return_value = 0; /* All is well */
|
|
}
|
|
}
|
|
/* ! STATUS_INITIAL */
|
|
os_printk(KERN_ALERT "SCC2: Driver Status is %s\n",
|
|
(scc_availability == SCC_STATUS_INITIAL) ? "INITIAL" :
|
|
(scc_availability == SCC_STATUS_CHECKING) ? "CHECKING" :
|
|
(scc_availability ==
|
|
SCC_STATUS_UNIMPLEMENTED) ? "UNIMPLEMENTED"
|
|
: (scc_availability ==
|
|
SCC_STATUS_OK) ? "OK" : (scc_availability ==
|
|
SCC_STATUS_FAILED) ? "FAILED" :
|
|
"UNKNOWN");
|
|
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18))
|
|
mxc_clks_disable(SCC_CLK);
|
|
#else
|
|
if (scc_clk != ERR_PTR(ENOENT))
|
|
clk_disable(scc_clk);
|
|
#endif
|
|
|
|
return return_value;
|
|
} /* scc_init */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_cleanup() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Perform cleanup before driver/module is unloaded by setting the machine
|
|
* state close to what it was when the driver was loaded. This function is
|
|
* called when the kernel is shutting down or when this driver is being
|
|
* unloaded.
|
|
*
|
|
* A driver like this should probably never be unloaded, especially if there
|
|
* are other module relying upon the callback feature for monitoring the SCC
|
|
* status.
|
|
*
|
|
* In any case, cleanup the callback table (by clearing out all of the
|
|
* pointers). Deregister the interrupt handler(s). Unmap SCC registers.
|
|
*
|
|
* Note that this will not release any partitions that have been allocated.
|
|
*
|
|
*/
|
|
static void scc_cleanup(void)
|
|
{
|
|
int i;
|
|
|
|
/******************************************************/
|
|
|
|
/* Mark the driver / SCC as unusable. */
|
|
scc_availability = SCC_STATUS_UNIMPLEMENTED;
|
|
|
|
/* Clear out callback table */
|
|
for (i = 0; i < SCC_CALLBACK_SIZE; i++) {
|
|
scc_callbacks[i] = 0;
|
|
}
|
|
|
|
/* If SCC has been mapped in, clean it up and unmap it */
|
|
if (scc_base) {
|
|
/* For the SCM, disable interrupts. */
|
|
SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0);
|
|
|
|
/* For the SMN, clear and disable interrupts */
|
|
SCC_WRITE_REGISTER(SMN_COMMAND_REG,
|
|
SMN_COMMAND_CLEAR_INTERRUPT);
|
|
}
|
|
|
|
/* Now that interrupts cannot occur, disassociate driver from the interrupt
|
|
* lines.
|
|
*/
|
|
|
|
/* Deregister SCM interrupt handler */
|
|
if (scm_irq_set) {
|
|
os_deregister_interrupt(INT_SCC_SCM);
|
|
}
|
|
|
|
/* Deregister SMN interrupt handler */
|
|
if (smn_irq_set) {
|
|
#ifdef USE_SMN_INTERRUPT
|
|
os_deregister_interrupt(INT_SCC_SMN);
|
|
#endif
|
|
}
|
|
|
|
/* Finally, release the mapped memory */
|
|
iounmap(scm_ram_base);
|
|
|
|
if (scc_callbacks_lock != NULL)
|
|
os_lock_deallocate(scc_callbacks_lock);
|
|
|
|
if (scc_crypto_lock != NULL)
|
|
os_lock_deallocate(scc_crypto_lock);
|
|
|
|
/*Disabling SCC Clock*/
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18))
|
|
mxc_clks_disable(SCC_CLK);
|
|
#else
|
|
if (scc_clk != ERR_PTR(ENOENT))
|
|
clk_disable(scc_clk);
|
|
clk_put(scc_clk);
|
|
#endif
|
|
pr_debug("SCC2 driver cleaned up.\n");
|
|
|
|
} /* scc_cleanup */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_get_configuration() */
|
|
/*****************************************************************************/
|
|
scc_config_t *scc_get_configuration(void)
|
|
{
|
|
/*
|
|
* If some other driver calls scc before the kernel does, make sure that
|
|
* this driver's initialization is performed.
|
|
*/
|
|
if (scc_availability == SCC_STATUS_INITIAL) {
|
|
scc_init();
|
|
}
|
|
|
|
/**
|
|
* If there is no SCC, yet the driver exists, the value -1 will be in
|
|
* the #scc_config_t fields for other than the driver versions.
|
|
*/
|
|
return &scc_configuration;
|
|
} /* scc_get_configuration */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_zeroize_memories() */
|
|
/*****************************************************************************/
|
|
scc_return_t scc_zeroize_memories(void)
|
|
{
|
|
scc_return_t return_status = SCC_RET_FAIL;
|
|
|
|
return return_status;
|
|
} /* scc_zeroize_memories */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_set_sw_alarm() */
|
|
/*****************************************************************************/
|
|
void scc_set_sw_alarm(void)
|
|
{
|
|
|
|
if (scc_availability == SCC_STATUS_INITIAL) {
|
|
scc_init();
|
|
}
|
|
|
|
/* Update scc_availability based on current SMN status. This might
|
|
* perform callbacks.
|
|
*/
|
|
(void)scc_update_state();
|
|
|
|
/* if everything is OK, make it fail */
|
|
if (scc_availability == SCC_STATUS_OK) {
|
|
|
|
/* sound the alarm (and disable SMN interrupts */
|
|
SCC_WRITE_REGISTER(SMN_COMMAND_REG,
|
|
SMN_COMMAND_SET_SOFTWARE_ALARM);
|
|
|
|
scc_availability = SCC_STATUS_FAILED; /* Remember what we've done */
|
|
|
|
/* In case SMN interrupt is not available, tell the world */
|
|
scc_perform_callbacks();
|
|
}
|
|
|
|
return;
|
|
} /* scc_set_sw_alarm */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_monitor_security_failure() */
|
|
/*****************************************************************************/
|
|
scc_return_t scc_monitor_security_failure(void callback_func(void))
|
|
{
|
|
int i;
|
|
os_lock_context_t irq_flags; /* for IRQ save/restore */
|
|
scc_return_t return_status = SCC_RET_TOO_MANY_FUNCTIONS;
|
|
int function_stored = FALSE;
|
|
|
|
if (scc_availability == SCC_STATUS_INITIAL) {
|
|
scc_init();
|
|
}
|
|
|
|
/* Acquire lock of callbacks table. Could be spin_lock_irq() if this
|
|
* routine were just called from base (not interrupt) level
|
|
*/
|
|
os_lock_save_context(scc_callbacks_lock, irq_flags);
|
|
|
|
/* Search through table looking for empty slot */
|
|
for (i = 0; i < SCC_CALLBACK_SIZE; i++) {
|
|
if (scc_callbacks[i] == callback_func) {
|
|
if (function_stored) {
|
|
/* Saved duplicate earlier. Clear this later one. */
|
|
scc_callbacks[i] = NULL;
|
|
}
|
|
/* Exactly one copy is now stored */
|
|
return_status = SCC_RET_OK;
|
|
break;
|
|
} else if (scc_callbacks[i] == NULL && !function_stored) {
|
|
/* Found open slot. Save it and remember */
|
|
scc_callbacks[i] = callback_func;
|
|
return_status = SCC_RET_OK;
|
|
function_stored = TRUE;
|
|
}
|
|
}
|
|
|
|
/* Free the lock */
|
|
os_unlock_restore_context(scc_callbacks_lock, irq_flags);
|
|
|
|
return return_status;
|
|
} /* scc_monitor_security_failure */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_stop_monitoring_security_failure() */
|
|
/*****************************************************************************/
|
|
void scc_stop_monitoring_security_failure(void callback_func(void))
|
|
{
|
|
os_lock_context_t irq_flags; /* for IRQ save/restore */
|
|
int i;
|
|
|
|
if (scc_availability == SCC_STATUS_INITIAL) {
|
|
scc_init();
|
|
}
|
|
|
|
/* Acquire lock of callbacks table. Could be spin_lock_irq() if this
|
|
* routine were just called from base (not interrupt) level
|
|
*/
|
|
os_lock_save_context(scc_callbacks_lock, irq_flags);
|
|
|
|
/* Search every entry of the table for this function */
|
|
for (i = 0; i < SCC_CALLBACK_SIZE; i++) {
|
|
if (scc_callbacks[i] == callback_func) {
|
|
scc_callbacks[i] = NULL; /* found instance - clear it out */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Free the lock */
|
|
os_unlock_restore_context(scc_callbacks_lock, irq_flags);
|
|
|
|
return;
|
|
} /* scc_stop_monitoring_security_failure */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_read_register() */
|
|
/*****************************************************************************/
|
|
scc_return_t scc_read_register(int register_offset, uint32_t * value)
|
|
{
|
|
scc_return_t return_status = SCC_RET_FAIL;
|
|
uint32_t smn_status;
|
|
uint32_t scm_status;
|
|
|
|
if (scc_availability == SCC_STATUS_INITIAL) {
|
|
scc_init();
|
|
}
|
|
|
|
/* First layer of protection -- completely unaccessible SCC */
|
|
if (scc_availability != SCC_STATUS_UNIMPLEMENTED) {
|
|
|
|
/* Second layer -- that offset is valid */
|
|
if (register_offset != SMN_BB_DEC_REG && /* write only! */
|
|
check_register_offset(register_offset) == SCC_RET_OK) {
|
|
|
|
/* Get current status / update local state */
|
|
smn_status = scc_update_state();
|
|
scm_status = SCC_READ_REGISTER(SCM_STATUS_REG);
|
|
|
|
/*
|
|
* Third layer - verify that the register being requested is
|
|
* available in the current state of the SCC.
|
|
*/
|
|
if ((return_status =
|
|
check_register_accessible(register_offset,
|
|
smn_status,
|
|
scm_status)) ==
|
|
SCC_RET_OK) {
|
|
*value = SCC_READ_REGISTER(register_offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
return return_status;
|
|
} /* scc_read_register */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_write_register() */
|
|
/*****************************************************************************/
|
|
scc_return_t scc_write_register(int register_offset, uint32_t value)
|
|
{
|
|
scc_return_t return_status = SCC_RET_FAIL;
|
|
uint32_t smn_status;
|
|
uint32_t scm_status;
|
|
|
|
if (scc_availability == SCC_STATUS_INITIAL) {
|
|
scc_init();
|
|
}
|
|
|
|
/* First layer of protection -- completely unaccessible SCC */
|
|
if (scc_availability != SCC_STATUS_UNIMPLEMENTED) {
|
|
|
|
/* Second layer -- that offset is valid */
|
|
if (!((register_offset == SCM_STATUS_REG) || /* These registers are */
|
|
(register_offset == SCM_VERSION_REG) || /* Read Only */
|
|
(register_offset == SMN_BB_CNT_REG) ||
|
|
(register_offset == SMN_TIMER_REG)) &&
|
|
check_register_offset(register_offset) == SCC_RET_OK) {
|
|
|
|
/* Get current status / update local state */
|
|
smn_status = scc_update_state();
|
|
scm_status = SCC_READ_REGISTER(SCM_STATUS_REG);
|
|
|
|
/*
|
|
* Third layer - verify that the register being requested is
|
|
* available in the current state of the SCC.
|
|
*/
|
|
if (check_register_accessible
|
|
(register_offset, smn_status, scm_status) == 0) {
|
|
SCC_WRITE_REGISTER(register_offset, value);
|
|
return_status = SCC_RET_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
return return_status;
|
|
} /* scc_write_register() */
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Function Implementations - Internal
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_irq() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* This is the interrupt handler for the SCC.
|
|
*
|
|
* This function checks the SMN Status register to see whether it
|
|
* generated the interrupt, then it checks the SCM Status register to
|
|
* see whether it needs attention.
|
|
*
|
|
* If an SMN Interrupt is active, then the SCC state set to failure, and
|
|
* #scc_perform_callbacks() is invoked to notify any interested parties.
|
|
*
|
|
* The SCM Interrupt should be masked, as this driver uses polling to determine
|
|
* when the SCM has completed a crypto or zeroing operation. Therefore, if the
|
|
* interrupt is active, the driver will just clear the interrupt and (re)mask.
|
|
*/
|
|
OS_DEV_ISR(scc_irq)
|
|
{
|
|
uint32_t smn_status;
|
|
uint32_t scm_status;
|
|
int handled = 0; /* assume interrupt isn't from SMN */
|
|
#if defined(USE_SMN_INTERRUPT)
|
|
int smn_irq = INT_SCC_SMN; /* SMN interrupt is on a line by itself */
|
|
#elif defined (NO_SMN_INTERRUPT)
|
|
int smn_irq = -1; /* not wired to CPU at all */
|
|
#else
|
|
int smn_irq = INT_SCC_SCM; /* SMN interrupt shares a line with SCM */
|
|
#endif
|
|
|
|
/* Update current state... This will perform callbacks... */
|
|
smn_status = scc_update_state();
|
|
|
|
/* SMN is on its own interrupt line. Verify the IRQ was triggered
|
|
* before clearing the interrupt and marking it handled. */
|
|
if ((os_dev_get_irq() == smn_irq) &&
|
|
(smn_status & SMN_STATUS_SMN_STATUS_IRQ)) {
|
|
SCC_WRITE_REGISTER(SMN_COMMAND_REG,
|
|
SMN_COMMAND_CLEAR_INTERRUPT);
|
|
handled++; /* tell kernel that interrupt was handled */
|
|
}
|
|
|
|
/* Check on the health of the SCM */
|
|
scm_status = SCC_READ_REGISTER(SCM_STATUS_REG);
|
|
|
|
/* The driver masks interrupts, so this should never happen. */
|
|
if (os_dev_get_irq() == INT_SCC_SCM) {
|
|
/* but if it does, try to prevent it in the future */
|
|
SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0);
|
|
handled++;
|
|
}
|
|
|
|
/* Any non-zero value of handled lets kernel know we got something */
|
|
os_dev_isr_return(handled);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_perform_callbacks() */
|
|
/*****************************************************************************/
|
|
/** Perform callbacks registered by #scc_monitor_security_failure().
|
|
*
|
|
* Make sure callbacks only happen once... Since there may be some reason why
|
|
* the interrupt isn't generated, this routine could be called from base(task)
|
|
* level.
|
|
*
|
|
* One at a time, go through #scc_callbacks[] and call any non-null pointers.
|
|
*/
|
|
static void scc_perform_callbacks(void)
|
|
{
|
|
static int callbacks_performed = 0;
|
|
unsigned long irq_flags; /* for IRQ save/restore */
|
|
int i;
|
|
|
|
/* Acquire lock of callbacks table and callbacks_performed flag */
|
|
os_lock_save_context(scc_callbacks_lock, irq_flags);
|
|
|
|
if (!callbacks_performed) {
|
|
callbacks_performed = 1;
|
|
|
|
/* Loop over all of the entries in the table */
|
|
for (i = 0; i < SCC_CALLBACK_SIZE; i++) {
|
|
/* If not null, ... */
|
|
if (scc_callbacks[i]) {
|
|
scc_callbacks[i] (); /* invoke the callback routine */
|
|
}
|
|
}
|
|
}
|
|
|
|
os_unlock_restore_context(scc_callbacks_lock, irq_flags);
|
|
|
|
return;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_update_state() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Make certain SCC is still running.
|
|
*
|
|
* Side effect is to update #scc_availability and, if the state goes to failed,
|
|
* run #scc_perform_callbacks().
|
|
*
|
|
* (If #SCC_BRINGUP is defined, bring SCC to secure state if it is found to be
|
|
* in health check state)
|
|
*
|
|
* @return Current value of #SMN_STATUS_REG register.
|
|
*/
|
|
static uint32_t scc_update_state(void)
|
|
{
|
|
uint32_t smn_status_register = SMN_STATE_FAIL;
|
|
int smn_state;
|
|
|
|
/* if FAIL or UNIMPLEMENTED, don't bother */
|
|
if (scc_availability == SCC_STATUS_CHECKING ||
|
|
scc_availability == SCC_STATUS_OK) {
|
|
|
|
smn_status_register = SCC_READ_REGISTER(SMN_STATUS_REG);
|
|
smn_state = smn_status_register & SMN_STATUS_STATE_MASK;
|
|
|
|
#ifdef SCC_BRINGUP
|
|
/* If in Health Check while booting, try to 'bringup' to Secure mode */
|
|
if (scc_availability == SCC_STATUS_CHECKING &&
|
|
smn_state == SMN_STATE_HEALTH_CHECK) {
|
|
/* Code up a simple algorithm for the ASC */
|
|
SCC_WRITE_REGISTER(SMN_SEQ_START_REG, 0xaaaa);
|
|
SCC_WRITE_REGISTER(SMN_SEQ_END_REG, 0x5555);
|
|
SCC_WRITE_REGISTER(SMN_SEQ_CHECK_REG, 0x5555);
|
|
/* State should be SECURE now */
|
|
smn_status_register = SCC_READ_REGISTER(SMN_STATUS);
|
|
smn_state = smn_status_register & SMN_STATUS_STATE_MASK;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* State should be SECURE or NON_SECURE for operation of the part. If
|
|
* FAIL, mark failed (i.e. limited access to registers). Any other
|
|
* state, mark unimplemented, as the SCC is unuseable.
|
|
*/
|
|
if (smn_state == SMN_STATE_SECURE
|
|
|| smn_state == SMN_STATE_NON_SECURE) {
|
|
/* Healthy */
|
|
scc_availability = SCC_STATUS_OK;
|
|
} else if (smn_state == SMN_STATE_FAIL) {
|
|
scc_availability = SCC_STATUS_FAILED; /* uh oh - unhealthy */
|
|
scc_perform_callbacks();
|
|
os_printk(KERN_ERR "SCC2: SCC went into FAILED mode\n");
|
|
} else {
|
|
/* START, ZEROIZE RAM, HEALTH CHECK, or unknown */
|
|
scc_availability = SCC_STATUS_UNIMPLEMENTED; /* unuseable */
|
|
os_printk(KERN_ERR
|
|
"SCC2: SCC declared UNIMPLEMENTED\n");
|
|
}
|
|
}
|
|
/* if availability is initial or ok */
|
|
return smn_status_register;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_init_ccitt_crc() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Populate the partial CRC lookup table.
|
|
*
|
|
* @return none
|
|
*
|
|
*/
|
|
static void scc_init_ccitt_crc(void)
|
|
{
|
|
int dividend; /* index for lookup table */
|
|
uint16_t remainder; /* partial value for a given dividend */
|
|
int bit; /* index into bits of a byte */
|
|
|
|
/*
|
|
* Compute the remainder of each possible dividend.
|
|
*/
|
|
for (dividend = 0; dividend < 256; ++dividend) {
|
|
/*
|
|
* Start with the dividend followed by zeros.
|
|
*/
|
|
remainder = dividend << (8);
|
|
|
|
/*
|
|
* Perform modulo-2 division, a bit at a time.
|
|
*/
|
|
for (bit = 8; bit > 0; --bit) {
|
|
/*
|
|
* Try to divide the current data bit.
|
|
*/
|
|
if (remainder & 0x8000) {
|
|
remainder = (remainder << 1) ^ CRC_POLYNOMIAL;
|
|
} else {
|
|
remainder = (remainder << 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Store the result into the table.
|
|
*/
|
|
scc_crc_lookup_table[dividend] = remainder;
|
|
}
|
|
|
|
} /* scc_init_ccitt_crc() */
|
|
|
|
/*****************************************************************************/
|
|
/* fn grab_config_values() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* grab_config_values() will read the SCM Configuration and SMN Status
|
|
* registers and store away version and size information for later use.
|
|
*
|
|
* @return The current value of the SMN Status register.
|
|
*/
|
|
static uint32_t scc_grab_config_values(void)
|
|
{
|
|
uint32_t scm_version_register;
|
|
uint32_t smn_status_register = SMN_STATE_FAIL;
|
|
|
|
if (scc_availability != SCC_STATUS_CHECKING) {
|
|
goto out;
|
|
}
|
|
scm_version_register = SCC_READ_REGISTER(SCM_VERSION_REG);
|
|
pr_debug("SCC2 Driver: SCM version is 0x%08x\n", scm_version_register);
|
|
|
|
/* Get SMN status and update scc_availability */
|
|
smn_status_register = scc_update_state();
|
|
pr_debug("SCC2 Driver: SMN status is 0x%08x\n", smn_status_register);
|
|
|
|
/* save sizes and versions information for later use */
|
|
scc_configuration.block_size_bytes = 16; /* BPCP ? */
|
|
scc_configuration.partition_count =
|
|
1 + ((scm_version_register & SCM_VER_NP_MASK) >> SCM_VER_NP_SHIFT);
|
|
scc_configuration.partition_size_bytes =
|
|
1 << ((scm_version_register & SCM_VER_BPP_MASK) >>
|
|
SCM_VER_BPP_SHIFT);
|
|
scc_configuration.scm_version =
|
|
(scm_version_register & SCM_VER_MAJ_MASK) >> SCM_VER_MAJ_SHIFT;
|
|
scc_configuration.smn_version =
|
|
(smn_status_register & SMN_STATUS_VERSION_ID_MASK)
|
|
>> SMN_STATUS_VERSION_ID_SHIFT;
|
|
if (scc_configuration.scm_version != SCM_MAJOR_VERSION_2) {
|
|
scc_availability = SCC_STATUS_UNIMPLEMENTED; /* Unknown version */
|
|
}
|
|
|
|
out:
|
|
return smn_status_register;
|
|
} /* grab_config_values */
|
|
|
|
/*****************************************************************************/
|
|
/* fn setup_interrupt_handling() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Register the SCM and SMN interrupt handlers.
|
|
*
|
|
* Called from #scc_init()
|
|
*
|
|
* @return 0 on success
|
|
*/
|
|
static int setup_interrupt_handling(void)
|
|
{
|
|
int smn_error_code = -1;
|
|
int scm_error_code = -1;
|
|
|
|
/* Disnable SCM interrupts */
|
|
SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0);
|
|
|
|
#ifdef USE_SMN_INTERRUPT
|
|
/* Install interrupt service routine for SMN. */
|
|
smn_error_code = os_register_interrupt(SCC_DRIVER_NAME,
|
|
INT_SCC_SMN, scc_irq);
|
|
if (smn_error_code != 0) {
|
|
os_printk(KERN_ERR
|
|
"SCC2 Driver: Error installing SMN Interrupt Handler: %d\n",
|
|
smn_error_code);
|
|
} else {
|
|
smn_irq_set = 1; /* remember this for cleanup */
|
|
/* Enable SMN interrupts */
|
|
SCC_WRITE_REGISTER(SMN_COMMAND_REG,
|
|
SMN_COMMAND_CLEAR_INTERRUPT |
|
|
SMN_COMMAND_ENABLE_INTERRUPT);
|
|
}
|
|
#else
|
|
smn_error_code = 0; /* no problems... will handle later */
|
|
#endif
|
|
|
|
/*
|
|
* Install interrupt service routine for SCM (or both together).
|
|
*/
|
|
scm_error_code = os_register_interrupt(SCC_DRIVER_NAME,
|
|
INT_SCC_SCM, scc_irq);
|
|
if (scm_error_code != 0) {
|
|
#ifndef MXC
|
|
os_printk(KERN_ERR
|
|
"SCC2 Driver: Error installing SCM Interrupt Handler: %d\n",
|
|
scm_error_code);
|
|
#else
|
|
os_printk(KERN_ERR
|
|
"SCC2 Driver: Error installing SCC Interrupt Handler: %d\n",
|
|
scm_error_code);
|
|
#endif
|
|
} else {
|
|
scm_irq_set = 1; /* remember this for cleanup */
|
|
#if defined(USE_SMN_INTERRUPT) && !defined(NO_SMN_INTERRUPT)
|
|
/* Enable SMN interrupts */
|
|
SCC_WRITE_REGISTER(SMN_COMMAND_REG,
|
|
SMN_COMMAND_CLEAR_INTERRUPT |
|
|
SMN_COMMAND_ENABLE_INTERRUPT);
|
|
#endif
|
|
}
|
|
|
|
/* Return an error if one was encountered */
|
|
return scm_error_code ? scm_error_code : smn_error_code;
|
|
} /* setup_interrupt_handling */
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_do_crypto() */
|
|
/*****************************************************************************/
|
|
/** Have the SCM perform the crypto function.
|
|
*
|
|
* Set up length register, and the store @c scm_control into control register
|
|
* to kick off the operation. Wait for completion, gather status, clear
|
|
* interrupt / status.
|
|
*
|
|
* @param byte_count number of bytes to perform in this operation
|
|
* @param scm_command Bit values to be set in @c SCM_CCMD_REG register
|
|
*
|
|
* @return 0 on success, value of #SCM_ERR_STATUS_REG on failure
|
|
*/
|
|
static uint32_t scc_do_crypto(int byte_count, uint32_t scm_command)
|
|
{
|
|
int block_count = byte_count / SCC_BLOCK_SIZE_BYTES();
|
|
uint32_t crypto_status;
|
|
scc_return_t ret;
|
|
|
|
/* This seems to be necessary in order to allow subsequent cipher
|
|
* operations to succeed when a partition is deallocated/reallocated!
|
|
*/
|
|
(void)SCC_READ_REGISTER(SCM_STATUS_REG);
|
|
|
|
/* In length register, 0 means 1, etc. */
|
|
scm_command |= (block_count - 1) << SCM_CCMD_LENGTH_SHIFT;
|
|
|
|
/* set modes and kick off the operation */
|
|
SCC_WRITE_REGISTER(SCM_CCMD_REG, scm_command);
|
|
|
|
ret = scc_wait_completion(&crypto_status);
|
|
|
|
/* Only done bit should be on */
|
|
if (crypto_status & SCM_STATUS_ERR) {
|
|
/* Replace with error status instead */
|
|
crypto_status = SCC_READ_REGISTER(SCM_ERR_STATUS_REG);
|
|
pr_debug("SCM Failure: 0x%x\n", crypto_status);
|
|
if (crypto_status == 0) {
|
|
/* That came up 0. Turn on arbitrary bit to signal error. */
|
|
crypto_status = SCM_ERRSTAT_ILM;
|
|
}
|
|
} else {
|
|
crypto_status = 0;
|
|
}
|
|
pr_debug("SCC2: Done waiting.\n");
|
|
|
|
return crypto_status;
|
|
}
|
|
|
|
/**
|
|
* Encrypt a region of secure memory.
|
|
*
|
|
* @param part_base Kernel virtual address of the partition.
|
|
* @param offset_bytes Offset from the start of the partition to the plaintext
|
|
* data.
|
|
* @param byte_count Length of the region (octets).
|
|
* @param black_data Physical location to store the encrypted data.
|
|
* @param IV Value to use for the IV.
|
|
* @param cypher_mode Cyphering mode to use, specified by type
|
|
* #scc_cypher_mode_t
|
|
*
|
|
* @return SCC_RET_OK if successful.
|
|
*/
|
|
scc_return_t
|
|
scc_encrypt_region(uint32_t part_base, uint32_t offset_bytes,
|
|
uint32_t byte_count, uint8_t *black_data,
|
|
uint32_t *IV, scc_cypher_mode_t cypher_mode)
|
|
{
|
|
os_lock_context_t irq_flags; /* for IRQ save/restore */
|
|
scc_return_t status = SCC_RET_OK;
|
|
uint32_t crypto_status;
|
|
uint32_t scm_command;
|
|
int offset_blocks = offset_bytes / SCC_BLOCK_SIZE_BYTES();
|
|
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18))
|
|
mxc_clks_enable(SCC_CLK);
|
|
#else
|
|
if (scc_clk != ERR_PTR(ENOENT))
|
|
clk_enable(scc_clk);
|
|
#endif
|
|
|
|
scm_command = ((offset_blocks << SCM_CCMD_OFFSET_SHIFT) |
|
|
(SCM_PART_NUMBER(part_base) << SCM_CCMD_PART_SHIFT));
|
|
|
|
switch (cypher_mode) {
|
|
case SCC_CYPHER_MODE_CBC:
|
|
scm_command |= SCM_CCMD_AES_ENC_CBC;
|
|
break;
|
|
case SCC_CYPHER_MODE_ECB:
|
|
scm_command |= SCM_CCMD_AES_ENC_ECB;
|
|
break;
|
|
default:
|
|
status = SCC_RET_FAIL;
|
|
break;
|
|
}
|
|
|
|
pr_debug("Received encrypt request. SCM_C_BLACK_ST_REG: %p, "
|
|
"scm_Command: %08x, length: %i (part_base: %08x, "
|
|
"offset: %i)\n",
|
|
black_data, scm_command, byte_count, part_base, offset_blocks);
|
|
|
|
if (status != SCC_RET_OK)
|
|
goto out;
|
|
|
|
/* ACQUIRE LOCK to prevent others from using crypto or releasing slot */
|
|
os_lock_save_context(scc_crypto_lock, irq_flags);
|
|
|
|
if (status == SCC_RET_OK) {
|
|
SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, (uint32_t) black_data);
|
|
|
|
/* Only write the IV if it will actually be used */
|
|
if (cypher_mode == SCC_CYPHER_MODE_CBC) {
|
|
/* Write the IV register */
|
|
SCC_WRITE_REGISTER(SCM_AES_CBC_IV0_REG, *(IV));
|
|
SCC_WRITE_REGISTER(SCM_AES_CBC_IV1_REG, *(IV + 1));
|
|
SCC_WRITE_REGISTER(SCM_AES_CBC_IV2_REG, *(IV + 2));
|
|
SCC_WRITE_REGISTER(SCM_AES_CBC_IV3_REG, *(IV + 3));
|
|
}
|
|
|
|
/* Set modes and kick off the encryption */
|
|
crypto_status = scc_do_crypto(byte_count, scm_command);
|
|
|
|
if (crypto_status != 0) {
|
|
pr_debug("SCM encrypt red crypto failure: 0x%x\n",
|
|
crypto_status);
|
|
} else {
|
|
status = SCC_RET_OK;
|
|
pr_debug("SCC2: Encrypted %d bytes\n", byte_count);
|
|
}
|
|
}
|
|
|
|
os_unlock_restore_context(scc_crypto_lock, irq_flags);
|
|
|
|
out:
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18))
|
|
mxc_clks_disable(SCC_CLK);
|
|
#else
|
|
if (scc_clk != ERR_PTR(ENOENT))
|
|
clk_disable(scc_clk);
|
|
#endif
|
|
|
|
return status;
|
|
}
|
|
|
|
/* Decrypt a region into secure memory
|
|
*
|
|
* @param part_base Kernel virtual address of the partition.
|
|
* @param offset_bytes Offset from the start of the partition to store the
|
|
* plaintext data.
|
|
* @param byte_counts Length of the region (octets).
|
|
* @param black_data Physical location of the encrypted data.
|
|
* @param IV Value to use for the IV.
|
|
* @param cypher_mode Cyphering mode to use, specified by type
|
|
* #scc_cypher_mode_t
|
|
*
|
|
* @return SCC_RET_OK if successful.
|
|
*/
|
|
scc_return_t
|
|
scc_decrypt_region(uint32_t part_base, uint32_t offset_bytes,
|
|
uint32_t byte_count, uint8_t *black_data,
|
|
uint32_t *IV, scc_cypher_mode_t cypher_mode)
|
|
{
|
|
os_lock_context_t irq_flags; /* for IRQ save/restore */
|
|
scc_return_t status = SCC_RET_OK;
|
|
uint32_t crypto_status;
|
|
uint32_t scm_command;
|
|
int offset_blocks = offset_bytes / SCC_BLOCK_SIZE_BYTES();
|
|
|
|
/*Enabling SCC clock.*/
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18))
|
|
mxc_clks_enable(SCC_CLK);
|
|
#else
|
|
if (scc_clk != ERR_PTR(ENOENT))
|
|
clk_enable(scc_clk);
|
|
#endif
|
|
scm_command = ((offset_blocks << SCM_CCMD_OFFSET_SHIFT) |
|
|
(SCM_PART_NUMBER(part_base) << SCM_CCMD_PART_SHIFT));
|
|
|
|
switch (cypher_mode) {
|
|
case SCC_CYPHER_MODE_CBC:
|
|
scm_command |= SCM_CCMD_AES_DEC_CBC;
|
|
break;
|
|
case SCC_CYPHER_MODE_ECB:
|
|
scm_command |= SCM_CCMD_AES_DEC_ECB;
|
|
break;
|
|
default:
|
|
status = SCC_RET_FAIL;
|
|
break;
|
|
}
|
|
|
|
pr_debug("Received decrypt request. SCM_C_BLACK_ST_REG: %p, "
|
|
"scm_Command: %08x, length: %i (part_base: %08x, "
|
|
"offset: %i)\n",
|
|
black_data, scm_command, byte_count, part_base, offset_blocks);
|
|
|
|
if (status != SCC_RET_OK)
|
|
goto out;
|
|
|
|
/* ACQUIRE LOCK to prevent others from using crypto or releasing slot */
|
|
os_lock_save_context(scc_crypto_lock, irq_flags);
|
|
|
|
if (status == SCC_RET_OK) {
|
|
status = SCC_RET_FAIL; /* reset expectations */
|
|
SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, (uint32_t) black_data);
|
|
|
|
/* Write the IV register */
|
|
SCC_WRITE_REGISTER(SCM_AES_CBC_IV0_REG, *(IV));
|
|
SCC_WRITE_REGISTER(SCM_AES_CBC_IV1_REG, *(IV + 1));
|
|
SCC_WRITE_REGISTER(SCM_AES_CBC_IV2_REG, *(IV + 2));
|
|
SCC_WRITE_REGISTER(SCM_AES_CBC_IV3_REG, *(IV + 3));
|
|
|
|
/* Set modes and kick off the decryption */
|
|
crypto_status = scc_do_crypto(byte_count, scm_command);
|
|
|
|
if (crypto_status != 0) {
|
|
pr_debug("SCM decrypt black crypto failure: 0x%x\n",
|
|
crypto_status);
|
|
} else {
|
|
status = SCC_RET_OK;
|
|
pr_debug("SCC2: Decrypted %d bytes\n", byte_count);
|
|
}
|
|
}
|
|
|
|
os_unlock_restore_context(scc_crypto_lock, irq_flags);
|
|
out:
|
|
/*Disabling the Clock when the driver is not in use.*/
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18))
|
|
mxc_clks_disable(SCC_CLK);
|
|
#else
|
|
if (scc_clk != ERR_PTR(ENOENT))
|
|
clk_disable(scc_clk);
|
|
#endif
|
|
return status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn host_owns_partition() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Determine if the host owns a given partition.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param part_no Partition number to query
|
|
*
|
|
* @return TRUE if the host owns the partition, FALSE otherwise.
|
|
*/
|
|
|
|
static uint32_t host_owns_partition(uint32_t part_no)
|
|
{
|
|
uint32_t value;
|
|
|
|
if (part_no < scc_configuration.partition_count) {
|
|
|
|
/* Check the partition owners register */
|
|
value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG);
|
|
if (((value >> (part_no * SCM_POWN_SHIFT)) & SCM_POWN_MASK)
|
|
== SCM_POWN_PART_OWNED)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn partition_engaged() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Determine if the given partition is engaged.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param part_no Partition number to query
|
|
*
|
|
* @return TRUE if the partition is engaged, FALSE otherwise.
|
|
*/
|
|
|
|
static uint32_t partition_engaged(uint32_t part_no)
|
|
{
|
|
uint32_t value;
|
|
|
|
if (part_no < scc_configuration.partition_count) {
|
|
|
|
/* Check the partition engaged register */
|
|
value = SCC_READ_REGISTER(SCM_PART_ENGAGED_REG);
|
|
if (((value >> (part_no * SCM_PENG_SHIFT)) & 0x1)
|
|
== SCM_PENG_ENGAGED)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn scc_wait_completion() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Poll looking for end-of-cipher indication. Only used
|
|
* if @c SCC_SCM_SLEEP is not defined.
|
|
*
|
|
* @internal
|
|
*
|
|
* On a Tahiti, crypto under 230 or so bytes is done after the first loop, all
|
|
* the way up to five sets of spins for 1024 bytes. (8- and 16-byte functions
|
|
* are done when we first look. Zeroizing takes one pass around.
|
|
*
|
|
* @param scm_status Address of the SCM_STATUS register
|
|
*
|
|
* @return A return code of type #scc_return_t
|
|
*/
|
|
static scc_return_t scc_wait_completion(uint32_t * scm_status)
|
|
{
|
|
scc_return_t ret;
|
|
int done;
|
|
int i = 0;
|
|
|
|
/* check for completion by polling */
|
|
do {
|
|
done = is_cipher_done(scm_status);
|
|
if (done)
|
|
break;
|
|
/* TODO: shorten this delay */
|
|
udelay(1000);
|
|
} while (i++ < SCC_CIPHER_MAX_POLL_COUNT);
|
|
|
|
pr_debug("SCC2: Polled DONE %d times\n", i);
|
|
if (!done) {
|
|
ret = SCC_RET_FAIL;
|
|
}
|
|
|
|
return ret;
|
|
} /* scc_wait_completion() */
|
|
|
|
/*****************************************************************************/
|
|
/* fn is_cipher_done() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* This function returns non-zero if SCM Status register indicates
|
|
* that a cipher has terminated or some other interrupt-generating
|
|
* condition has occurred.
|
|
*
|
|
* @param scm_status Address of the SCM STATUS register
|
|
*
|
|
* @return 0 if cipher operations are finished
|
|
*/
|
|
static int is_cipher_done(uint32_t * scm_status)
|
|
{
|
|
register unsigned status;
|
|
register int cipher_done;
|
|
|
|
*scm_status = SCC_READ_REGISTER(SCM_STATUS_REG);
|
|
status = (*scm_status & SCM_STATUS_SRS_MASK) >> SCM_STATUS_SRS_SHIFT;
|
|
|
|
/*
|
|
* Done when SCM is not in 'currently performing a function' states.
|
|
*/
|
|
cipher_done = ((status != SCM_STATUS_SRS_ZBUSY)
|
|
&& (status != SCM_STATUS_SRS_CBUSY)
|
|
&& (status != SCM_STATUS_SRS_ABUSY));
|
|
|
|
return cipher_done;
|
|
} /* is_cipher_done() */
|
|
|
|
/*****************************************************************************/
|
|
/* fn offset_within_smn() */
|
|
/*****************************************************************************/
|
|
/*!
|
|
* Check that the offset is with the bounds of the SMN register set.
|
|
*
|
|
* @param[in] register_offset register offset of SMN.
|
|
*
|
|
* @return 1 if true, 0 if false (not within SMN)
|
|
*/
|
|
static inline int offset_within_smn(uint32_t register_offset)
|
|
{
|
|
return ((register_offset >= SMN_STATUS_REG)
|
|
&& (register_offset <= SMN_HAC_REG));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn offset_within_scm() */
|
|
/*****************************************************************************/
|
|
/*!
|
|
* Check that the offset is with the bounds of the SCM register set.
|
|
*
|
|
* @param[in] register_offset Register offset of SCM
|
|
*
|
|
* @return 1 if true, 0 if false (not within SCM)
|
|
*/
|
|
static inline int offset_within_scm(uint32_t register_offset)
|
|
{
|
|
return 1; /* (register_offset >= SCM_RED_START)
|
|
&& (register_offset < scm_highest_memory_address); */
|
|
/* Although this would cause trouble for zeroize testing, this change would
|
|
* close a security hole which currently allows any kernel program to access
|
|
* any location in RED RAM. Perhaps enforce in non-SCC_DEBUG compiles?
|
|
&& (register_offset <= SCM_INIT_VECTOR_1); */
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn check_register_accessible() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Given the current SCM and SMN status, verify that access to the requested
|
|
* register should be OK.
|
|
*
|
|
* @param[in] register_offset register offset within SCC
|
|
* @param[in] smn_status recent value from #SMN_STATUS_REG
|
|
* @param[in] scm_status recent value from #SCM_STATUS_REG
|
|
*
|
|
* @return #SCC_RET_OK if ok, #SCC_RET_FAIL if not
|
|
*/
|
|
static scc_return_t
|
|
check_register_accessible(uint32_t register_offset, uint32_t smn_status,
|
|
uint32_t scm_status)
|
|
{
|
|
int error_code = SCC_RET_FAIL;
|
|
|
|
/* Verify that the register offset passed in is not among the verboten set
|
|
* if the SMN is in Fail mode.
|
|
*/
|
|
if (offset_within_smn(register_offset)) {
|
|
if ((smn_status & SMN_STATUS_STATE_MASK) == SMN_STATE_FAIL) {
|
|
if (!((register_offset == SMN_STATUS_REG) ||
|
|
(register_offset == SMN_COMMAND_REG) ||
|
|
(register_offset == SMN_SEC_VIO_REG))) {
|
|
pr_debug
|
|
("SCC2 Driver: Note: Security State is in FAIL state.\n");
|
|
} /* register not a safe one */
|
|
else {
|
|
/* SMN is in FAIL, but register is a safe one */
|
|
error_code = SCC_RET_OK;
|
|
}
|
|
} /* State is FAIL */
|
|
else {
|
|
/* State is not fail. All registers accessible. */
|
|
error_code = SCC_RET_OK;
|
|
}
|
|
}
|
|
/* offset within SMN */
|
|
/* Not SCM register. Check for SCM busy. */
|
|
else if (offset_within_scm(register_offset)) {
|
|
/* This is the 'cannot access' condition in the SCM */
|
|
if (0 /* (scm_status & SCM_STATUS_BUSY) */
|
|
/* these are always available - rest fail on busy */
|
|
&& !((register_offset == SCM_STATUS_REG) ||
|
|
(register_offset == SCM_ERR_STATUS_REG) ||
|
|
(register_offset == SCM_INT_CTL_REG) ||
|
|
(register_offset == SCM_VERSION_REG))) {
|
|
pr_debug
|
|
("SCC2 Driver: Note: Secure Memory is in BUSY state.\n");
|
|
} /* status is busy & register inaccessible */
|
|
else {
|
|
error_code = SCC_RET_OK;
|
|
}
|
|
}
|
|
/* offset within SCM */
|
|
return error_code;
|
|
|
|
} /* check_register_accessible() */
|
|
|
|
/*****************************************************************************/
|
|
/* fn check_register_offset() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Check that the offset is with the bounds of the SCC register set.
|
|
*
|
|
* @param[in] register_offset register offset of SMN.
|
|
*
|
|
* #SCC_RET_OK if ok, #SCC_RET_FAIL if not
|
|
*/
|
|
static scc_return_t check_register_offset(uint32_t register_offset)
|
|
{
|
|
int return_value = SCC_RET_FAIL;
|
|
|
|
/* Is it valid word offset ? */
|
|
if (SCC_BYTE_OFFSET(register_offset) == 0) {
|
|
/* Yes. Is register within SCM? */
|
|
if (offset_within_scm(register_offset)) {
|
|
return_value = SCC_RET_OK; /* yes, all ok */
|
|
}
|
|
/* Not in SCM. Now look within the SMN */
|
|
else if (offset_within_smn(register_offset)) {
|
|
return_value = SCC_RET_OK; /* yes, all ok */
|
|
}
|
|
}
|
|
|
|
return return_value;
|
|
}
|
|
|
|
#ifdef SCC_REGISTER_DEBUG
|
|
|
|
/**
|
|
* Names of the SCC Registers, indexed by register number
|
|
*/
|
|
static char *scc_regnames[] = {
|
|
"SCM_VERSION_REG",
|
|
"0x04",
|
|
"SCM_INT_CTL_REG",
|
|
"SCM_STATUS_REG",
|
|
"SCM_ERR_STATUS_REG",
|
|
"SCM_FAULT_ADR_REG",
|
|
"SCM_PART_OWNERS_REG",
|
|
"SCM_PART_ENGAGED_REG",
|
|
"SCM_UNIQUE_ID0_REG",
|
|
"SCM_UNIQUE_ID1_REG",
|
|
"SCM_UNIQUE_ID2_REG",
|
|
"SCM_UNIQUE_ID3_REG",
|
|
"0x30",
|
|
"0x34",
|
|
"0x38",
|
|
"0x3C",
|
|
"0x40",
|
|
"0x44",
|
|
"0x48",
|
|
"0x4C",
|
|
"SCM_ZCMD_REG",
|
|
"SCM_CCMD_REG",
|
|
"SCM_C_BLACK_ST_REG",
|
|
"SCM_DBG_STATUS_REG",
|
|
"SCM_AES_CBC_IV0_REG",
|
|
"SCM_AES_CBC_IV1_REG",
|
|
"SCM_AES_CBC_IV2_REG",
|
|
"SCM_AES_CBC_IV3_REG",
|
|
"0x70",
|
|
"0x74",
|
|
"0x78",
|
|
"0x7C",
|
|
"SCM_SMID0_REG",
|
|
"SCM_ACC0_REG",
|
|
"SCM_SMID1_REG",
|
|
"SCM_ACC1_REG",
|
|
"SCM_SMID2_REG",
|
|
"SCM_ACC2_REG",
|
|
"SCM_SMID3_REG",
|
|
"SCM_ACC3_REG",
|
|
"SCM_SMID4_REG",
|
|
"SCM_ACC4_REG",
|
|
"SCM_SMID5_REG",
|
|
"SCM_ACC5_REG",
|
|
"SCM_SMID6_REG",
|
|
"SCM_ACC6_REG",
|
|
"SCM_SMID7_REG",
|
|
"SCM_ACC7_REG",
|
|
"SCM_SMID8_REG",
|
|
"SCM_ACC8_REG",
|
|
"SCM_SMID9_REG",
|
|
"SCM_ACC9_REG",
|
|
"SCM_SMID10_REG",
|
|
"SCM_ACC10_REG",
|
|
"SCM_SMID11_REG",
|
|
"SCM_ACC11_REG",
|
|
"SCM_SMID12_REG",
|
|
"SCM_ACC12_REG",
|
|
"SCM_SMID13_REG",
|
|
"SCM_ACC13_REG",
|
|
"SCM_SMID14_REG",
|
|
"SCM_ACC14_REG",
|
|
"SCM_SMID15_REG",
|
|
"SCM_ACC15_REG",
|
|
"SMN_STATUS_REG",
|
|
"SMN_COMMAND_REG",
|
|
"SMN_SEQ_START_REG",
|
|
"SMN_SEQ_END_REG",
|
|
"SMN_SEQ_CHECK_REG",
|
|
"SMN_BB_CNT_REG",
|
|
"SMN_BB_INC_REG",
|
|
"SMN_BB_DEC_REG",
|
|
"SMN_COMPARE_REG",
|
|
"SMN_PT_CHK_REG",
|
|
"SMN_CT_CHK_REG",
|
|
"SMN_TIMER_IV_REG",
|
|
"SMN_TIMER_CTL_REG",
|
|
"SMN_SEC_VIO_REG",
|
|
"SMN_TIMER_REG",
|
|
"SMN_HAC_REG"
|
|
};
|
|
|
|
/**
|
|
* Names of the Secure RAM States
|
|
*/
|
|
static char *srs_names[] = {
|
|
"SRS_Reset",
|
|
"SRS_All_Ready",
|
|
"SRS_ZeroizeBusy",
|
|
"SRS_CipherBusy",
|
|
"SRS_AllBusy",
|
|
"SRS_ZeroizeDoneCipherReady",
|
|
"SRS_CipherDoneZeroizeReady",
|
|
"SRS_ZeroizeDoneCipherBusy",
|
|
"SRS_CipherDoneZeroizeBusy",
|
|
"SRS_UNKNOWN_STATE_9",
|
|
"SRS_TransitionalA",
|
|
"SRS_TransitionalB",
|
|
"SRS_TransitionalC",
|
|
"SRS_TransitionalD",
|
|
"SRS_AllDone",
|
|
"SRS_UNKNOWN_STATE_E",
|
|
"SRS_FAIL"
|
|
};
|
|
|
|
/**
|
|
* Create a text interpretation of the SCM Version Register
|
|
*
|
|
* @param value The value of the register
|
|
* @param[out] print_buffer Place to store the interpretation
|
|
* @param buf_size Number of bytes available at print_buffer
|
|
*
|
|
* @return The print_buffer
|
|
*/
|
|
static
|
|
char *scm_print_version_reg(uint32_t value, char *print_buffer, int buf_size)
|
|
{
|
|
snprintf(print_buffer, buf_size,
|
|
"Bpp: %u, Bpcb: %u, np: %u, maj: %u, min: %u",
|
|
(value & SCM_VER_BPP_MASK) >> SCM_VER_BPP_SHIFT,
|
|
((value & SCM_VER_BPCB_MASK) >> SCM_VER_BPCB_SHIFT) + 1,
|
|
((value & SCM_VER_NP_MASK) >> SCM_VER_NP_SHIFT) + 1,
|
|
(value & SCM_VER_MAJ_MASK) >> SCM_VER_MAJ_SHIFT,
|
|
(value & SCM_VER_MIN_MASK) >> SCM_VER_MIN_SHIFT);
|
|
|
|
return print_buffer;
|
|
}
|
|
|
|
/**
|
|
* Create a text interpretation of the SCM Status Register
|
|
*
|
|
* @param value The value of the register
|
|
* @param[out] print_buffer Place to store the interpretation
|
|
* @param buf_size Number of bytes available at print_buffer
|
|
*
|
|
* @return The print_buffer
|
|
*/
|
|
static
|
|
char *scm_print_status_reg(uint32_t value, char *print_buffer, int buf_size)
|
|
{
|
|
|
|
snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s%s%s%s",
|
|
(value & SCM_STATUS_KST_DEFAULT_KEY) ? "KST_DefaultKey " : "",
|
|
/* reserved */
|
|
(value & SCM_STATUS_KST_WRONG_KEY) ? "KST_WrongKey " : "",
|
|
(value & SCM_STATUS_KST_BAD_KEY) ? "KST_BadKey " : "",
|
|
(value & SCM_STATUS_ERR) ? "Error " : "",
|
|
(value & SCM_STATUS_MSS_FAIL) ? "MSS_FailState " : "",
|
|
(value & SCM_STATUS_MSS_SEC) ? "MSS_SecureState " : "",
|
|
(value & SCM_STATUS_RSS_FAIL) ? "RSS_FailState " : "",
|
|
(value & SCM_STATUS_RSS_SEC) ? "RSS_SecureState " : "",
|
|
(value & SCM_STATUS_RSS_INIT) ? "RSS_Initializing " : "",
|
|
(value & SCM_STATUS_UNV) ? "UID_Invalid " : "",
|
|
(value & SCM_STATUS_BIG) ? "BigEndian " : "",
|
|
(value & SCM_STATUS_USK) ? "SecretKey " : "",
|
|
srs_names[(value & SCM_STATUS_SRS_MASK) >>
|
|
SCM_STATUS_SRS_SHIFT]);
|
|
|
|
return print_buffer;
|
|
}
|
|
|
|
/**
|
|
* Names of the SCM Error Codes
|
|
*/
|
|
static
|
|
char *scm_err_code[] = {
|
|
"Unknown_0",
|
|
"UnknownAddress",
|
|
"UnknownCommand",
|
|
"ReadPermErr",
|
|
"WritePermErr",
|
|
"DMAErr",
|
|
"EncBlockLenOvfl",
|
|
"KeyNotEngaged",
|
|
"ZeroizeCmdQOvfl",
|
|
"CipherCmdQOvfl",
|
|
"ProcessIntr",
|
|
"WrongKey",
|
|
"DeviceBusy",
|
|
"DMAUnalignedAddr",
|
|
"Unknown_E",
|
|
"Unknown_F",
|
|
};
|
|
|
|
/**
|
|
* Names of the SMN States
|
|
*/
|
|
static char *smn_state_name[] = {
|
|
"Start",
|
|
"Invalid_01",
|
|
"Invalid_02",
|
|
"Invalid_03",
|
|
"Zeroizing_04",
|
|
"Zeroizing",
|
|
"HealthCheck",
|
|
"HealthCheck_07",
|
|
"Invalid_08",
|
|
"Fail",
|
|
"Secure",
|
|
"Invalid_0B",
|
|
"NonSecure",
|
|
"Invalid_0D",
|
|
"Invalid_0E",
|
|
"Invalid_0F",
|
|
"Invalid_10",
|
|
"Invalid_11",
|
|
"Invalid_12",
|
|
"Invalid_13",
|
|
"Invalid_14",
|
|
"Invalid_15",
|
|
"Invalid_16",
|
|
"Invalid_17",
|
|
"Invalid_18",
|
|
"FailHard",
|
|
"Invalid_1A",
|
|
"Invalid_1B",
|
|
"Invalid_1C",
|
|
"Invalid_1D",
|
|
"Invalid_1E",
|
|
"Invalid_1F"
|
|
};
|
|
|
|
/**
|
|
* Create a text interpretation of the SCM Error Status Register
|
|
*
|
|
* @param value The value of the register
|
|
* @param[out] print_buffer Place to store the interpretation
|
|
* @param buf_size Number of bytes available at print_buffer
|
|
*
|
|
* @return The print_buffer
|
|
*/
|
|
static
|
|
char *scm_print_err_status_reg(uint32_t value, char *print_buffer, int buf_size)
|
|
{
|
|
snprintf(print_buffer, buf_size,
|
|
"MID: 0x%x, %s%s ErrorCode: %s, SMSState: %s, SCMState: %s",
|
|
(value & SCM_ERRSTAT_MID_MASK) >> SCM_ERRSTAT_MID_SHIFT,
|
|
(value & SCM_ERRSTAT_ILM) ? "ILM, " : "",
|
|
(value & SCM_ERRSTAT_SUP) ? "SUP, " : "",
|
|
scm_err_code[(value & SCM_ERRSTAT_ERC_MASK) >>
|
|
SCM_ERRSTAT_ERC_SHIFT],
|
|
smn_state_name[(value & SCM_ERRSTAT_SMS_MASK) >>
|
|
SCM_ERRSTAT_SMS_SHIFT],
|
|
srs_names[(value & SCM_ERRSTAT_SRS_MASK) >>
|
|
SCM_ERRSTAT_SRS_SHIFT]);
|
|
return print_buffer;
|
|
}
|
|
|
|
/**
|
|
* Create a text interpretation of the SCM Zeroize Command Register
|
|
*
|
|
* @param value The value of the register
|
|
* @param[out] print_buffer Place to store the interpretation
|
|
* @param buf_size Number of bytes available at print_buffer
|
|
*
|
|
* @return The print_buffer
|
|
*/
|
|
static
|
|
char *scm_print_zcmd_reg(uint32_t value, char *print_buffer, int buf_size)
|
|
{
|
|
unsigned cmd = (value & SCM_ZCMD_CCMD_MASK) >> SCM_CCMD_CCMD_SHIFT;
|
|
|
|
snprintf(print_buffer, buf_size, "%s %u",
|
|
(cmd ==
|
|
ZCMD_DEALLOC_PART) ? "DeallocPartition" :
|
|
"(unknown function)",
|
|
(value & SCM_ZCMD_PART_MASK) >> SCM_ZCMD_PART_SHIFT);
|
|
|
|
return print_buffer;
|
|
}
|
|
|
|
/**
|
|
* Create a text interpretation of the SCM Cipher Command Register
|
|
*
|
|
* @param value The value of the register
|
|
* @param[out] print_buffer Place to store the interpretation
|
|
* @param buf_size Number of bytes available at print_buffer
|
|
*
|
|
* @return The print_buffer
|
|
*/
|
|
static
|
|
char *scm_print_ccmd_reg(uint32_t value, char *print_buffer, int buf_size)
|
|
{
|
|
unsigned cmd = (value & SCM_CCMD_CCMD_MASK) >> SCM_CCMD_CCMD_SHIFT;
|
|
|
|
snprintf(print_buffer, buf_size,
|
|
"%s %u bytes, %s offset 0x%x, in partition %u",
|
|
(cmd == SCM_CCMD_AES_DEC_ECB) ? "ECB Decrypt" : (cmd ==
|
|
SCM_CCMD_AES_ENC_ECB)
|
|
? "ECB Encrypt" : (cmd ==
|
|
SCM_CCMD_AES_DEC_CBC) ? "CBC Decrypt" : (cmd
|
|
==
|
|
SCM_CCMD_AES_ENC_CBC)
|
|
? "CBC Encrypt" : "(unknown function)",
|
|
16 +
|
|
16 * ((value & SCM_CCMD_LENGTH_MASK) >> SCM_CCMD_LENGTH_SHIFT),
|
|
((cmd == SCM_CCMD_AES_ENC_CBC)
|
|
|| (cmd == SCM_CCMD_AES_ENC_ECB)) ? "at" : "to",
|
|
16 * ((value & SCM_CCMD_OFFSET_MASK) >> SCM_CCMD_OFFSET_SHIFT),
|
|
(value & SCM_CCMD_PART_MASK) >> SCM_CCMD_PART_SHIFT);
|
|
|
|
return print_buffer;
|
|
}
|
|
|
|
/**
|
|
* Create a text interpretation of an SCM Access Permissions Register
|
|
*
|
|
* @param value The value of the register
|
|
* @param[out] print_buffer Place to store the interpretation
|
|
* @param buf_size Number of bytes available at print_buffer
|
|
*
|
|
* @return The print_buffer
|
|
*/
|
|
static
|
|
char *scm_print_acc_reg(uint32_t value, char *print_buffer, int buf_size)
|
|
{
|
|
snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s",
|
|
(value & SCM_PERM_NO_ZEROIZE) ? "NO_ZERO " : "",
|
|
(value & SCM_PERM_HD_SUP_DISABLE) ? "SUP_DIS " : "",
|
|
(value & SCM_PERM_HD_READ) ? "HD_RD " : "",
|
|
(value & SCM_PERM_HD_WRITE) ? "HD_WR " : "",
|
|
(value & SCM_PERM_HD_EXECUTE) ? "HD_EX " : "",
|
|
(value & SCM_PERM_TH_READ) ? "TH_RD " : "",
|
|
(value & SCM_PERM_TH_WRITE) ? "TH_WR " : "",
|
|
(value & SCM_PERM_OT_READ) ? "OT_RD " : "",
|
|
(value & SCM_PERM_OT_WRITE) ? "OT_WR " : "",
|
|
(value & SCM_PERM_OT_EXECUTE) ? "OT_EX" : "");
|
|
|
|
return print_buffer;
|
|
}
|
|
|
|
/**
|
|
* Create a text interpretation of the SCM Partitions Engaged Register
|
|
*
|
|
* @param value The value of the register
|
|
* @param[out] print_buffer Place to store the interpretation
|
|
* @param buf_size Number of bytes available at print_buffer
|
|
*
|
|
* @return The print_buffer
|
|
*/
|
|
static
|
|
char *scm_print_part_eng_reg(uint32_t value, char *print_buffer, int buf_size)
|
|
{
|
|
snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
|
|
(value & 0x8000) ? "15 " : "",
|
|
(value & 0x4000) ? "14 " : "",
|
|
(value & 0x2000) ? "13 " : "",
|
|
(value & 0x1000) ? "12 " : "",
|
|
(value & 0x0800) ? "11 " : "",
|
|
(value & 0x0400) ? "10 " : "",
|
|
(value & 0x0200) ? "9 " : "",
|
|
(value & 0x0100) ? "8 " : "",
|
|
(value & 0x0080) ? "7 " : "",
|
|
(value & 0x0040) ? "6 " : "",
|
|
(value & 0x0020) ? "5 " : "",
|
|
(value & 0x0010) ? "4 " : "",
|
|
(value & 0x0008) ? "3 " : "",
|
|
(value & 0x0004) ? "2 " : "",
|
|
(value & 0x0002) ? "1 " : "", (value & 0x0001) ? "0" : "");
|
|
|
|
return print_buffer;
|
|
}
|
|
|
|
/**
|
|
* Create a text interpretation of the SMN Status Register
|
|
*
|
|
* @param value The value of the register
|
|
* @param[out] print_buffer Place to store the interpretation
|
|
* @param buf_size Number of bytes available at print_buffer
|
|
*
|
|
* @return The print_buffer
|
|
*/
|
|
static
|
|
char *smn_print_status_reg(uint32_t value, char *print_buffer, int buf_size)
|
|
{
|
|
snprintf(print_buffer, buf_size,
|
|
"Version %d %s%s%s%s%s%s%s%s%s%s%s%s%s",
|
|
(value & SMN_STATUS_VERSION_ID_MASK) >>
|
|
SMN_STATUS_VERSION_ID_SHIFT,
|
|
(value & SMN_STATUS_ILLEGAL_MASTER) ? "IllMaster " : "",
|
|
(value & SMN_STATUS_SCAN_EXIT) ? "ScanExit " : "",
|
|
(value & SMN_STATUS_PERIP_INIT) ? "PeripInit " : "",
|
|
(value & SMN_STATUS_SMN_ERROR) ? "SMNError " : "",
|
|
(value & SMN_STATUS_SOFTWARE_ALARM) ? "SWAlarm " : "",
|
|
(value & SMN_STATUS_TIMER_ERROR) ? "TimerErr " : "",
|
|
(value & SMN_STATUS_PC_ERROR) ? "PTCTErr " : "",
|
|
(value & SMN_STATUS_BITBANK_ERROR) ? "BitbankErr " : "",
|
|
(value & SMN_STATUS_ASC_ERROR) ? "ASCErr " : "",
|
|
(value & SMN_STATUS_SECURITY_POLICY_ERROR) ? "SecPlcyErr " :
|
|
"",
|
|
(value & SMN_STATUS_SEC_VIO_ACTIVE_ERROR) ? "SecVioAct " : "",
|
|
(value & SMN_STATUS_INTERNAL_BOOT) ? "IntBoot " : "",
|
|
smn_state_name[(value & SMN_STATUS_STATE_MASK) >>
|
|
SMN_STATUS_STATE_SHIFT]);
|
|
|
|
return print_buffer;
|
|
}
|
|
|
|
/**
|
|
* The array, indexed by register number (byte-offset / 4), of print routines
|
|
* for the SCC (SCM and SMN) registers.
|
|
*/
|
|
static reg_print_routine_t reg_printers[] = {
|
|
scm_print_version_reg,
|
|
NULL, /* 0x04 */
|
|
NULL, /* SCM_INT_CTL_REG */
|
|
scm_print_status_reg,
|
|
scm_print_err_status_reg,
|
|
NULL, /* SCM_FAULT_ADR_REG */
|
|
NULL, /* SCM_PART_OWNERS_REG */
|
|
scm_print_part_eng_reg,
|
|
NULL, /* SCM_UNIQUE_ID0_REG */
|
|
NULL, /* SCM_UNIQUE_ID1_REG */
|
|
NULL, /* SCM_UNIQUE_ID2_REG */
|
|
NULL, /* SCM_UNIQUE_ID3_REG */
|
|
NULL, /* 0x30 */
|
|
NULL, /* 0x34 */
|
|
NULL, /* 0x38 */
|
|
NULL, /* 0x3C */
|
|
NULL, /* 0x40 */
|
|
NULL, /* 0x44 */
|
|
NULL, /* 0x48 */
|
|
NULL, /* 0x4C */
|
|
scm_print_zcmd_reg,
|
|
scm_print_ccmd_reg,
|
|
NULL, /* SCM_C_BLACK_ST_REG */
|
|
NULL, /* SCM_DBG_STATUS_REG */
|
|
NULL, /* SCM_AES_CBC_IV0_REG */
|
|
NULL, /* SCM_AES_CBC_IV1_REG */
|
|
NULL, /* SCM_AES_CBC_IV2_REG */
|
|
NULL, /* SCM_AES_CBC_IV3_REG */
|
|
NULL, /* 0x70 */
|
|
NULL, /* 0x74 */
|
|
NULL, /* 0x78 */
|
|
NULL, /* 0x7C */
|
|
NULL, /* SCM_SMID0_REG */
|
|
scm_print_acc_reg, /* ACC0 */
|
|
NULL, /* SCM_SMID1_REG */
|
|
scm_print_acc_reg, /* ACC1 */
|
|
NULL, /* SCM_SMID2_REG */
|
|
scm_print_acc_reg, /* ACC2 */
|
|
NULL, /* SCM_SMID3_REG */
|
|
scm_print_acc_reg, /* ACC3 */
|
|
NULL, /* SCM_SMID4_REG */
|
|
scm_print_acc_reg, /* ACC4 */
|
|
NULL, /* SCM_SMID5_REG */
|
|
scm_print_acc_reg, /* ACC5 */
|
|
NULL, /* SCM_SMID6_REG */
|
|
scm_print_acc_reg, /* ACC6 */
|
|
NULL, /* SCM_SMID7_REG */
|
|
scm_print_acc_reg, /* ACC7 */
|
|
NULL, /* SCM_SMID8_REG */
|
|
scm_print_acc_reg, /* ACC8 */
|
|
NULL, /* SCM_SMID9_REG */
|
|
scm_print_acc_reg, /* ACC9 */
|
|
NULL, /* SCM_SMID10_REG */
|
|
scm_print_acc_reg, /* ACC10 */
|
|
NULL, /* SCM_SMID11_REG */
|
|
scm_print_acc_reg, /* ACC11 */
|
|
NULL, /* SCM_SMID12_REG */
|
|
scm_print_acc_reg, /* ACC12 */
|
|
NULL, /* SCM_SMID13_REG */
|
|
scm_print_acc_reg, /* ACC13 */
|
|
NULL, /* SCM_SMID14_REG */
|
|
scm_print_acc_reg, /* ACC14 */
|
|
NULL, /* SCM_SMID15_REG */
|
|
scm_print_acc_reg, /* ACC15 */
|
|
smn_print_status_reg,
|
|
NULL, /* SMN_COMMAND_REG */
|
|
NULL, /* SMN_SEQ_START_REG */
|
|
NULL, /* SMN_SEQ_END_REG */
|
|
NULL, /* SMN_SEQ_CHECK_REG */
|
|
NULL, /* SMN_BB_CNT_REG */
|
|
NULL, /* SMN_BB_INC_REG */
|
|
NULL, /* SMN_BB_DEC_REG */
|
|
NULL, /* SMN_COMPARE_REG */
|
|
NULL, /* SMN_PT_CHK_REG */
|
|
NULL, /* SMN_CT_CHK_REG */
|
|
NULL, /* SMN_TIMER_IV_REG */
|
|
NULL, /* SMN_TIMER_CTL_REG */
|
|
NULL, /* SMN_SEC_VIO_REG */
|
|
NULL, /* SMN_TIMER_REG */
|
|
NULL, /* SMN_HAC_REG */
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/* fn dbg_scc_read_register() */
|
|
/*****************************************************************************/
|
|
/**
|
|
* Noisily read a 32-bit value to an SCC register.
|
|
* @param offset The address of the register to read.
|
|
*
|
|
* @return The register value
|
|
* */
|
|
uint32_t dbg_scc_read_register(uint32_t offset)
|
|
{
|
|
uint32_t value;
|
|
char *regname = scc_regnames[offset / 4];
|
|
|
|
value = __raw_readl(scc_base + offset);
|
|
pr_debug("SCC2 RD: 0x%03x : 0x%08x (%s) %s\n", offset, value, regname,
|
|
reg_printers[offset / 4]
|
|
? reg_printers[offset / 4] (value, reg_print_buffer,
|
|
REG_PRINT_BUFFER_SIZE)
|
|
: "");
|
|
|
|
return value;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* fn dbg_scc_write_register() */
|
|
/*****************************************************************************/
|
|
/*
|
|
* Noisily read a 32-bit value to an SCC register.
|
|
* @param offset The address of the register to written.
|
|
*
|
|
* @param value The new register value
|
|
*/
|
|
void dbg_scc_write_register(uint32_t offset, uint32_t value)
|
|
{
|
|
char *regname = scc_regnames[offset / 4];
|
|
|
|
pr_debug("SCC2 WR: 0x%03x : 0x%08x (%s) %s\n", offset, value, regname,
|
|
reg_printers[offset / 4]
|
|
? reg_printers[offset / 4] (value, reg_print_buffer,
|
|
REG_PRINT_BUFFER_SIZE)
|
|
: "");
|
|
(void)__raw_writel(value, scc_base + offset);
|
|
|
|
}
|
|
|
|
#endif /* SCC_REGISTER_DEBUG */
|
|
|
|
static int scc_dev_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *r;
|
|
int ret = 0;
|
|
|
|
/* get the scc registers base address */
|
|
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!r) {
|
|
dev_err(&pdev->dev, "can't get IORESOURCE_MEM (0)\n");
|
|
ret = -ENXIO;
|
|
goto exit;
|
|
}
|
|
|
|
scc_phys_base = r->start;
|
|
|
|
|
|
/* get the scc ram base address */
|
|
r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
if (!r) {
|
|
dev_err(&pdev->dev, "can't get IORESOURCE_MEM (1)\n");
|
|
ret = -ENXIO;
|
|
goto exit;
|
|
}
|
|
|
|
scm_ram_phys_base = r->start;
|
|
|
|
/* now initialize the SCC */
|
|
ret = scc_init();
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static int scc_dev_remove(struct platform_device *pdev)
|
|
{
|
|
scc_cleanup();
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
static int scc_suspend(struct platform_device *pdev,
|
|
pm_message_t state)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int scc_resume(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
#define scc_suspend NULL
|
|
#define scc_resume NULL
|
|
#endif
|
|
|
|
/*! Linux Driver definition
|
|
*
|
|
*/
|
|
static struct platform_driver mxcscc_driver = {
|
|
.driver = {
|
|
.name = SCC_DRIVER_NAME,
|
|
},
|
|
.probe = scc_dev_probe,
|
|
.remove = scc_dev_remove,
|
|
.suspend = scc_suspend,
|
|
.resume = scc_resume,
|
|
};
|
|
|
|
static int __init scc_driver_init(void)
|
|
{
|
|
return platform_driver_register(&mxcscc_driver);
|
|
}
|
|
|
|
module_init(scc_driver_init);
|
|
|
|
static void __exit scc_driver_exit(void)
|
|
{
|
|
platform_driver_unregister(&mxcscc_driver);
|
|
}
|
|
|
|
module_exit(scc_driver_exit);
|