Logo Search packages:      
Sourcecode: linux-fsl-imx51 version File versions  Download package

scc2_driver.c

Go to the documentation of this file.
/*
 * Copyright 2004-2009 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.
 */
00071 #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.
 */
00108 static volatile void *scc_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 = IRAM_BASE_ADDR;

void *scm_ram_base = NULL;

/** Calculated once for quick reference to size of the unreserved space in
 *  RAM in SCM.
 */
00122 uint32_t scm_memory_size_bytes;

/** Structure returned by #scc_get_configuration() */
00125 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. */
00137 static volatile enum scc_status scc_availability = SCC_STATUS_INITIAL;

/** Flag to say whether interrupt handler has been registered for
 * SMN interrupt */
00141 static int smn_irq_set = 0;

/** Flag to say whether interrupt handler has been registered for
 * SCM interrupt */
00145 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.
 */
00153 static os_lock_t scc_callbacks_lock = NULL;

/**
 * Ownership of this lock prevents conflicts on the crypto operation in the
 * SCC.
 */
00159 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().
 */
00169 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
 */
00188 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.
 */
00298 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.
 */
00351 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;
}

00379 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.
 */
00424 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
00442 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.
 */
00501 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 *)IO_ADDRESS(SCC_BASE);
            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.
 *
 */
00682 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()                                                */
/*****************************************************************************/
00746 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()                                                 */
/*****************************************************************************/
00766 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()                                                     */
/*****************************************************************************/
00776 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()                                         */
/*****************************************************************************/
00807 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()                                 */
/*****************************************************************************/
00850 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()                                                    */
/*****************************************************************************/
00881 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()                                                   */
/*****************************************************************************/
00922 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.
 */
00984 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.
 */
01034 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.
 */
01074 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
 *
 */
01133 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.
 */
01179 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
 */
01224 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
 */
01294 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
01345 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
01439 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.
 */

01529 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.
 */

01557 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
 */
01589 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
 */
01624 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)
 */
01652 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)
 */
01668 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
01692 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
 */
01750 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 */

Generated by  Doxygen 1.6.0   Back to index