Files
linux-legacy/drivers/mxc/security/sahara2/sah_driver_interface.c
Anish Trivedi fea4e4245d ENGR00142679 SCC2 and SAHARA: changes to support loadable modules
To allow SCC2 and SAHARA drivers to work as loadable modules, needed
to add GPL license to SAHARA driver, export a couple of functions
from SCC2 driver, and the following data buffer mapping change in
SAHARA driver:

When compiled as a loadable module, a data buffer to be DMA'ed in the
SAHARA driver may not be in the kernel direct-mapped region but in
the "Kernel module space" between TASK_SIZE and high_memory-1
(see http://www.arm.linux.org.uk/developer/memory.txt). In this
scenario, the driver canno simply use the __pa macro to obtain
the physical address. It must walk the page tables to find the
page and use the page_to_phys function to find the physical
address that corresponds to the data buffer.

Signed-off-by: Anish Trivedi <anish@freescale.com>
2011-07-03 15:58:18 -05:00

2184 lines
57 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 sah_driver_interface.c
*
* @brief Provides a Linux Kernel Module interface to the SAHARA h/w device.
*
*/
/* SAHARA Includes */
#include <sah_driver_common.h>
#include <sah_kernel.h>
#include <sah_memory_mapper.h>
#include <sah_queue_manager.h>
#include <sah_status_manager.h>
#include <sah_interrupt_handler.h>
#include <sah_hardware_interface.h>
#include <fsl_shw_keystore.h>
#include <adaptor.h>
#ifdef FSL_HAVE_SCC
#include <linux/mxc_scc_driver.h>
#else
#include <linux/mxc_scc2_driver.h>
#endif
#ifdef DIAG_DRV_IF
#include <diagnostic.h>
#endif
#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0))
#include <linux/devfs_fs_kernel.h>
#else
#include <linux/proc_fs.h>
#endif
#ifdef PERF_TEST
#define interruptible_sleep_on(x) sah_Handle_Interrupt()
#endif
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Freescale Semiconductor");
MODULE_DESCRIPTION("Device Driver for SAHARA2 crypto hw block");
#define TEST_MODE_OFF 1
#define TEST_MODE_ON 2
/*! Version register on first deployments */
#define SAHARA_VERSION2 2
/*! Version register on MX27 */
#define SAHARA_VERSION3 3
/*! Version register on MXC92323 */
#define SAHARA_VERSION4 4
/******************************************************************************
* Module function declarations
******************************************************************************/
OS_DEV_INIT_DCL(sah_init);
OS_DEV_SHUTDOWN_DCL(sah_cleanup);
OS_DEV_OPEN_DCL(sah_open);
OS_DEV_CLOSE_DCL(sah_release);
OS_DEV_IOCTL_DCL(sah_ioctl);
OS_DEV_MMAP_DCL(sah_mmap);
static os_error_code sah_handle_get_capabilities(fsl_shw_uco_t* user_ctx,
uint32_t info);
static void sah_user_callback(fsl_shw_uco_t * user_ctx);
static os_error_code sah_handle_scc_sfree(fsl_shw_uco_t* user_ctx,
uint32_t info);
static os_error_code sah_handle_scc_sstatus(fsl_shw_uco_t* user_ctx,
uint32_t info);
static os_error_code sah_handle_scc_drop_perms(fsl_shw_uco_t* user_ctx,
uint32_t info);
static os_error_code sah_handle_scc_encrypt(fsl_shw_uco_t* user_ctx,
uint32_t info);
static os_error_code sah_handle_scc_decrypt(fsl_shw_uco_t* user_ctx,
uint32_t info);
#ifdef FSL_HAVE_SCC2
static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx,
uint32_t user_base,
void *kernel_base);
static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx,
uint32_t user_base);
#endif
static os_error_code sah_handle_sk_slot_alloc(uint32_t info);
static os_error_code sah_handle_sk_slot_dealloc(uint32_t info);
static os_error_code sah_handle_sk_slot_load(uint32_t info);
static os_error_code sah_handle_sk_slot_read(uint32_t info);
static os_error_code sah_handle_sk_slot_decrypt(uint32_t info);
static os_error_code sah_handle_sk_slot_encrypt(uint32_t info);
/*! Boolean flag for whether interrupt handler needs to be released on exit */
static unsigned interrupt_registered;
static int handle_sah_ioctl_dar(fsl_shw_uco_t * filp, uint32_t user_space_desc);
#if !defined(CONFIG_DEVFS_FS) || (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0))
static int sah_read_procfs(char *buf,
char **start,
off_t offset, int count, int *eof, void *data);
static int sah_write_procfs(struct file *file, const char __user * buffer,
unsigned long count, void *data);
#endif
#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0))
/* This is a handle to the sahara DEVFS entry. */
static devfs_handle_t Sahara_devfs_handle;
#else
/* Major number assigned to our device driver */
static int Major;
/* This is a handle to the sahara PROCFS entry */
static struct proc_dir_entry *Sahara_procfs_handle;
#endif
uint32_t sah_hw_version;
extern void *sah_virt_base;
/* This is the wait queue to this driver. Linux declaration. */
DECLARE_WAIT_QUEUE_HEAD(Wait_queue);
/* This is a global variable that is used to track how many times the device
* has been opened simultaneously. */
#ifdef DIAG_DRV_IF
static int Device_in_use = 0;
#endif
/* This is the system keystore object */
fsl_shw_kso_t system_keystore;
/*!
* OS-dependent handle used for registering user interface of a driver.
*/
static os_driver_reg_t reg_handle;
#ifdef DIAG_DRV_IF
/* This is for sprintf() to use when constructing output. */
#define DIAG_MSG_SIZE 1024
static char Diag_msg[DIAG_MSG_SIZE];
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18))
/** Pointer to Sahara clock information. Initialized during os_dev_init(). */
static struct clk *sah_clk;
#endif
/*!
*******************************************************************************
* This function gets called when the module is inserted (insmod) into the
* running kernel.
*
* @brief SAHARA device initialisation function.
*
* @return 0 on success
* @return -EBUSY if the device or proc file entry cannot be created.
* @return OS_ERROR_NO_MEMORY_S if kernel memory could not be allocated.
* @return OS_ERROR_FAIL_S if initialisation of proc entry failed
*/
OS_DEV_INIT(sah_init)
{
/* Status variable */
int os_error_code = 0;
uint32_t sah_phys_base = SAHARA_BASE_ADDR;
interrupt_registered = 0;
/* Enable the SAHARA Clocks */
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA : Enabling the IPG and AHB clocks\n")
#endif /*DIAG_DRV_IF */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18))
mxc_clks_enable(SAHARA2_CLK);
#else
{
sah_clk = clk_get(NULL, "sahara_clk");
if (sah_clk != ERR_PTR(ENOENT))
clk_enable(sah_clk);
}
#endif
if (cpu_is_mx53())
sah_phys_base -= 0x20000000;
sah_virt_base = (void *)ioremap(sah_phys_base, SZ_16K);
if (sah_virt_base == NULL) {
os_printk(KERN_ERR
"SAHARA: Register mapping failed\n");
os_error_code = OS_ERROR_FAIL_S;
}
if (os_error_code == OS_ERROR_OK_S) {
sah_hw_version = sah_HW_Read_Version();
os_printk("Sahara HW Version is 0x%08x\n", sah_hw_version);
/* verify code and hardware are version compatible */
if ((sah_hw_version != SAHARA_VERSION2)
&& (sah_hw_version != SAHARA_VERSION3)) {
if (((sah_hw_version >> 8) & 0xff) != SAHARA_VERSION4) {
os_printk
("Sahara HW Version was not expected value.\n");
os_error_code = OS_ERROR_FAIL_S;
}
}
}
if (os_error_code == OS_ERROR_OK_S) {
#ifdef DIAG_DRV_IF
LOG_KDIAG("Calling sah_Init_Mem_Map to initialise "
"memory subsystem.");
#endif
/* Do any memory-routine initialization */
os_error_code = sah_Init_Mem_Map();
}
if (os_error_code == OS_ERROR_OK_S) {
#ifdef DIAG_DRV_IF
LOG_KDIAG("Calling sah_HW_Reset() to Initialise the Hardware.");
#endif
/* Initialise the hardware */
os_error_code = sah_HW_Reset();
if (os_error_code != OS_ERROR_OK_S) {
os_printk
("sah_HW_Reset() failed to Initialise the Hardware.\n");
}
}
if (os_error_code == OS_ERROR_OK_S) {
#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0))
/* Register the DEVFS entry */
Sahara_devfs_handle = devfs_register(NULL,
SAHARA_DEVICE_SHORT,
DEVFS_FL_AUTO_DEVNUM,
0, 0,
SAHARA_DEVICE_MODE,
&Fops, NULL);
if (Sahara_devfs_handle == NULL) {
#ifdef DIAG_DRV_IF
LOG_KDIAG
("Registering the DEVFS character device failed.");
#endif /* DIAG_DRV_IF */
os_error_code = -EBUSY;
}
#else /* CONFIG_DEVFS_FS */
/* Create the PROCFS entry. This is used to report the assigned device
* major number back to user-space. */
#if 1
Sahara_procfs_handle = create_proc_entry(SAHARA_DEVICE_SHORT, 0700, /* default mode */
NULL); /* parent dir */
if (Sahara_procfs_handle == NULL) {
#ifdef DIAG_DRV_IF
LOG_KDIAG("Registering the PROCFS interface failed.");
#endif /* DIAG_DRV_IF */
os_error_code = OS_ERROR_FAIL_S;
} else {
Sahara_procfs_handle->nlink = 1;
Sahara_procfs_handle->data = 0;
Sahara_procfs_handle->read_proc = sah_read_procfs;
Sahara_procfs_handle->write_proc = sah_write_procfs;
}
#endif /* #if 1 */
}
if (os_error_code == OS_ERROR_OK_S) {
#ifdef DIAG_DRV_IF
LOG_KDIAG
("Calling sah_Queue_Manager_Init() to Initialise the Queue "
"Manager.");
#endif
/* Initialise the Queue Manager */
if (sah_Queue_Manager_Init() != FSL_RETURN_OK_S) {
os_error_code = -ENOMEM;
}
}
#ifndef SAHARA_POLL_MODE
if (os_error_code == OS_ERROR_OK_S) {
#ifdef DIAG_DRV_IF
LOG_KDIAG("Calling sah_Intr_Init() to Initialise the Interrupt "
"Handler.");
#endif
/* Initialise the Interrupt Handler */
os_error_code = sah_Intr_Init(&Wait_queue);
if (os_error_code == OS_ERROR_OK_S) {
interrupt_registered = 1;
}
}
#endif /* ifndef SAHARA_POLL_MODE */
#ifdef SAHARA_POWER_MANAGEMENT
if (os_error_code == OS_ERROR_OK_S) {
/* set up dynamic power management (dmp) */
os_error_code = sah_dpm_init();
}
#endif
if (os_error_code == OS_ERROR_OK_S) {
os_driver_init_registration(reg_handle);
os_driver_add_registration(reg_handle, OS_FN_OPEN,
OS_DEV_OPEN_REF(sah_open));
os_driver_add_registration(reg_handle, OS_FN_IOCTL,
OS_DEV_IOCTL_REF(sah_ioctl));
os_driver_add_registration(reg_handle, OS_FN_CLOSE,
OS_DEV_CLOSE_REF(sah_release));
os_driver_add_registration(reg_handle, OS_FN_MMAP,
OS_DEV_MMAP_REF(sah_mmap));
os_error_code =
os_driver_complete_registration(reg_handle, Major,
"sahara");
if (os_error_code < OS_ERROR_OK_S) {
#ifdef DIAG_DRV_IF
snprintf(Diag_msg, DIAG_MSG_SIZE,
"Registering the regular "
"character device failed with error code: %d\n",
os_error_code);
LOG_KDIAG(Diag_msg);
#endif
}
}
#endif /* CONFIG_DEVFS_FS */
if (os_error_code == OS_ERROR_OK_S) {
/* set up the system keystore, using the default keystore handler */
fsl_shw_init_keystore_default(&system_keystore);
if (fsl_shw_establish_keystore(NULL, &system_keystore)
== FSL_RETURN_OK_S) {
os_error_code = OS_ERROR_OK_S;
} else {
os_error_code = OS_ERROR_FAIL_S;
}
if (os_error_code != OS_ERROR_OK_S) {
#ifdef DIAG_DRV_IF
snprintf(Diag_msg, DIAG_MSG_SIZE,
"Registering the system keystore "
"failed with error code: %d\n", os_error_code);
LOG_KDIAG(Diag_msg);
#endif
}
}
if (os_error_code != OS_ERROR_OK_S) {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0))
cleanup_module();
#else
sah_cleanup();
#endif
}
#ifdef DIAG_DRV_IF
else {
LOG_KDIAG_ARGS("Sahara major node is %d\n", Major);
}
#endif
/* Disabling the Clock after the driver has been registered fine.
This is done to save power when Sahara is not in use.*/
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA : Disabling the clocks\n")
#endif /* DIAG_DRV_IF */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18))
mxc_clks_disable(SAHARA2_CLK);
#else
{
if (sah_clk != ERR_PTR(ENOENT))
clk_disable(sah_clk);
}
#endif
os_dev_init_return(os_error_code);
}
/*!
*******************************************************************************
* This function gets called when the module is removed (rmmod) from the running
* kernel.
*
* @brief SAHARA device clean-up function.
*
* @return void
*/
OS_DEV_SHUTDOWN(sah_cleanup)
{
int ret_val = 0;
printk(KERN_ALERT "Sahara going into cleanup\n");
/* clear out the system keystore */
fsl_shw_release_keystore(NULL, &system_keystore);
/* Unregister the device */
#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0))
devfs_unregister(Sahara_devfs_handle);
#else
if (Sahara_procfs_handle != NULL) {
remove_proc_entry(SAHARA_DEVICE_SHORT, NULL);
}
if (Major >= 0) {
ret_val = os_driver_remove_registration(reg_handle);
}
#ifdef DIAG_DRV_IF
if (ret_val < 0) {
snprintf(Diag_msg, DIAG_MSG_SIZE, "Error while attempting to "
"unregister the device: %d\n", ret_val);
LOG_KDIAG(Diag_msg);
}
#endif
#endif /* CONFIG_DEVFS_FS */
sah_Queue_Manager_Close();
#ifndef SAHARA_POLL_MODE
if (interrupt_registered) {
sah_Intr_Release();
interrupt_registered = 0;
}
#endif
sah_Stop_Mem_Map();
#ifdef SAHARA_POWER_MANAGEMENT
sah_dpm_close();
#endif
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA : Disabling the clocks\n")
#endif /* DIAG_DRV_IF */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18))
mxc_clks_disable(SAHARA2_CLK);
#else
{
if (sah_clk != ERR_PTR(ENOENT))
clk_disable(sah_clk);
clk_put(sah_clk);
}
#endif
os_dev_shutdown_return(OS_ERROR_OK_S);
}
/*!
*******************************************************************************
* This function simply increments the module usage count.
*
* @brief SAHARA device open function.
*
* @param inode Part of the kernel prototype.
* @param file Part of the kernel prototype.
*
* @return 0 - Always returns 0 since any number of calls to this function are
* allowed.
*
*/
OS_DEV_OPEN(sah_open)
{
#if defined(LINUX_VERSION) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10))
MOD_INC_USE_COUNT;
#endif
#ifdef DIAG_DRV_IF
Device_in_use++;
snprintf(Diag_msg, DIAG_MSG_SIZE,
"Incrementing module use count to: %d ", Device_in_use);
LOG_KDIAG(Diag_msg);
#endif
os_dev_set_user_private(NULL);
/* Return 0 to indicate success */
os_dev_open_return(0);
}
/*!
*******************************************************************************
* This function simply decrements the module usage count.
*
* @brief SAHARA device release function.
*
* @param inode Part of the kernel prototype.
* @param file Part of the kernel prototype.
*
* @return 0 - Always returns 0 since this function does not fail.
*/
OS_DEV_CLOSE(sah_release)
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
#if defined(LINUX_VERSION) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10))
MOD_DEC_USE_COUNT;
#endif
#ifdef DIAG_DRV_IF
Device_in_use--;
snprintf(Diag_msg, DIAG_MSG_SIZE,
"Decrementing module use count to: %d ", Device_in_use);
LOG_KDIAG(Diag_msg);
#endif
if (user_ctx != NULL) {
sah_handle_deregistration(user_ctx);
os_free_memory(user_ctx);
os_dev_set_user_private(NULL);
}
/* Return 0 to indicate success */
os_dev_close_return(OS_ERROR_OK_S);
}
/*!
*******************************************************************************
* This function provides the IO Controls for the SAHARA driver. Three IO
* Controls are supported:
*
* SAHARA_HWRESET and
* SAHARA_SET_HA
* SAHARA_CHK_TEST_MODE
*
* @brief SAHARA device IO Control function.
*
* @param inode Part of the kernel prototype.
* @param filp Part of the kernel prototype.
* @param cmd Part of the kernel prototype.
* @param arg Part of the kernel prototype.
*
* @return 0 on success
* @return -EBUSY if the HA bit could not be set due to busy hardware.
* @return -ENOTTY if an unsupported IOCTL was attempted on the device.
* @return -EFAULT if put_user() fails
*/
OS_DEV_IOCTL(sah_ioctl)
{
int status = 0;
int test_mode;
switch (os_dev_get_ioctl_op()) {
case SAHARA_HWRESET:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_HWRESET IOCTL.");
#endif
/* We need to reset the hardware. */
sah_HW_Reset();
/* Mark all the entries in the Queue Manager's queue with state
* SAH_STATE_RESET.
*/
sah_Queue_Manager_Reset_Entries();
/* Wake up all sleeping write() calls. */
wake_up_interruptible(&Wait_queue);
break;
#ifdef SAHARA_HA_ENABLED
case SAHARA_SET_HA:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SET_HA IOCTL.");
#endif /* DIAG_DRV_IF */
if (sah_HW_Set_HA() == ERR_INTERNAL) {
status = -EBUSY;
}
break;
#endif /* SAHARA_HA_ENABLED */
case SAHARA_CHK_TEST_MODE:
/* load test_mode */
test_mode = TEST_MODE_OFF;
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_CHECK_TEST_MODE IOCTL.");
test_mode = TEST_MODE_ON;
#endif /* DIAG_DRV_IF */
#if defined(KERNEL_TEST) || defined(PERF_TEST)
test_mode = TEST_MODE_ON;
#endif /* KERNEL_TEST || PERF_TEST */
/* copy test_mode back to user space. put_user() is Linux fn */
/* compiler warning `register': no problem found so ignored */
status = put_user(test_mode, (int *)os_dev_get_ioctl_arg());
break;
case SAHARA_DAR:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_DAR IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
if (user_ctx != NULL) {
status =
handle_sah_ioctl_dar(user_ctx,
os_dev_get_ioctl_arg
());
} else {
status = OS_ERROR_FAIL_S;
}
}
break;
case SAHARA_GET_RESULTS:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_GET_RESULTS IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
if (user_ctx != NULL) {
status =
sah_get_results_pointers(user_ctx,
os_dev_get_ioctl_arg
());
} else {
status = OS_ERROR_FAIL_S;
}
}
break;
case SAHARA_REGISTER:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_REGISTER IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
if (user_ctx != NULL) {
status = OS_ERROR_FAIL_S; /* already registered */
} else {
user_ctx =
os_alloc_memory(sizeof(fsl_shw_uco_t),
GFP_KERNEL);
if (user_ctx == NULL) {
status = OS_ERROR_NO_MEMORY_S;
} else {
/* Copy UCO from user, but only as big as the common UCO */
if (os_copy_from_user(user_ctx,
(void *)
os_dev_get_ioctl_arg
(),
offsetof
(fsl_shw_uco_t,
result_pool))) {
status = OS_ERROR_FAIL_S;
} else {
os_dev_set_user_private
(user_ctx);
status =
sah_handle_registration
(user_ctx);
}
}
}
}
break;
/* This ioctl cmd should disappear in favor of a close() routine. */
case SAHARA_DEREGISTER:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_DEREGISTER IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
if (user_ctx == NULL) {
status = OS_ERROR_FAIL_S;
} else {
status = sah_handle_deregistration(user_ctx);
os_free_memory(user_ctx);
os_dev_set_user_private(NULL);
}
}
break;
case SAHARA_SCC_DROP_PERMS:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SCC_DROP_PERMS IOCTL.");
#endif /* DIAG_DRV_IF */
{
/* drop permissions on the specified partition */
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
status =
sah_handle_scc_drop_perms(user_ctx,
os_dev_get_ioctl_arg());
}
break;
case SAHARA_SCC_SFREE:
/* Unmap the specified partition from the users space, and then
* free it for use by someone else.
*/
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SCC_SFREE IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
status =
sah_handle_scc_sfree(user_ctx,
os_dev_get_ioctl_arg());
}
break;
case SAHARA_SCC_SSTATUS:
/* Unmap the specified partition from the users space, and then
* free it for use by someone else.
*/
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SCC_SSTATUS IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
status =
sah_handle_scc_sstatus(user_ctx,
os_dev_get_ioctl_arg());
}
break;
case SAHARA_SCC_ENCRYPT:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SCC_ENCRYPT IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
status =
sah_handle_scc_encrypt(user_ctx,
os_dev_get_ioctl_arg());
}
break;
case SAHARA_SCC_DECRYPT:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SCC_DECRYPT IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
status =
sah_handle_scc_decrypt(user_ctx,
os_dev_get_ioctl_arg());
}
break;
case SAHARA_SK_ALLOC:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SK_ALLOC IOCTL.");
#endif /* DIAG_DRV_IF */
status = sah_handle_sk_slot_alloc(os_dev_get_ioctl_arg());
break;
case SAHARA_SK_DEALLOC:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SK_DEALLOC IOCTL.");
#endif /* DIAG_DRV_IF */
status = sah_handle_sk_slot_dealloc(os_dev_get_ioctl_arg());
break;
case SAHARA_SK_LOAD:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SK_LOAD IOCTL.");
#endif /* DIAG_DRV_IF */
status = sah_handle_sk_slot_load(os_dev_get_ioctl_arg());
break;
case SAHARA_SK_READ:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SK_READ IOCTL.");
#endif /* DIAG_DRV_IF */
status = sah_handle_sk_slot_read(os_dev_get_ioctl_arg());
break;
case SAHARA_SK_SLOT_DEC:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SK_SLOT_DECRYPT IOCTL.");
#endif /* DIAG_DRV_IF */
status = sah_handle_sk_slot_decrypt(os_dev_get_ioctl_arg());
break;
case SAHARA_SK_SLOT_ENC:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_SK_SLOT_ENCRYPT IOCTL.");
#endif /* DIAG_DRV_IF */
status = sah_handle_sk_slot_encrypt(os_dev_get_ioctl_arg());
break;
case SAHARA_GET_CAPS:
#ifdef DIAG_DRV_IF
LOG_KDIAG("SAHARA_GET_CAPS IOCTL.");
#endif /* DIAG_DRV_IF */
{
fsl_shw_uco_t *user_ctx = os_dev_get_user_private();
status =
sah_handle_get_capabilities(user_ctx,
os_dev_get_ioctl_arg());
}
break;
default:
#ifdef DIAG_DRV_IF
LOG_KDIAG("Unknown SAHARA IOCTL.");
#endif /* DIAG_DRV_IF */
status = OS_ERROR_FAIL_S;
}
os_dev_ioctl_return(status);
}
/* Fill in the user's capabilities structure */
static os_error_code sah_handle_get_capabilities(fsl_shw_uco_t * user_ctx,
uint32_t info)
{
os_error_code status = OS_ERROR_FAIL_S;
fsl_shw_pco_t capabilities;
status = os_copy_from_user(&capabilities, (void *)info,
sizeof(fsl_shw_pco_t));
if (status != OS_ERROR_OK_S) {
goto out;
}
if (get_capabilities(user_ctx, &capabilities) == FSL_RETURN_OK_S) {
status = os_copy_to_user((void *)info, &capabilities,
sizeof(fsl_shw_pco_t));
}
out:
return status;
}
#ifdef FSL_HAVE_SCC2
/* Find the kernel-mode address of the partition.
* This can then be passed to the SCC functions.
*/
void *lookup_user_partition(fsl_shw_uco_t * user_ctx, uint32_t user_base)
{
/* search through the partition chain to find one that matches the user base
* address.
*/
fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition;
while (curr != NULL) {
if (curr->user_base == user_base) {
return curr->kernel_base;
}
curr = (fsl_shw_spo_t *) curr->next;
}
return NULL;
}
/* user_base: userspace base address of the partition
* kernel_base: kernel mode base address of the partition
*/
static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx,
uint32_t user_base,
void *kernel_base)
{
fsl_shw_spo_t *partition_info;
fsl_shw_return_t ret = FSL_RETURN_ERROR_S;
if (user_ctx == NULL) {
goto out;
}
partition_info = os_alloc_memory(sizeof(fsl_shw_spo_t), GFP_KERNEL);
if (partition_info == NULL) {
goto out;
}
/* stuff the partition info, then put it at the front of the chain */
partition_info->user_base = user_base;
partition_info->kernel_base = kernel_base;
partition_info->next = user_ctx->partition;
user_ctx->partition = (struct fsl_shw_spo_t *)partition_info;
#ifdef DIAG_DRV_IF
LOG_KDIAG_ARGS
("partition with user_base=%p, kernel_base=%p registered.",
(void *)user_base, kernel_base);
#endif
ret = FSL_RETURN_OK_S;
out:
return ret;
}
/* if the partition is in the users list, remove it */
static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx,
uint32_t user_base)
{
fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition;
fsl_shw_spo_t *last = (fsl_shw_spo_t *) user_ctx->partition;
while (curr != NULL) {
if (curr->user_base == user_base) {
#ifdef DIAG_DRV_IF
LOG_KDIAG_ARGS
("deregister_user_partition: partition with "
"user_base=%p, kernel_base=%p deregistered.\n",
(void *)curr->user_base, curr->kernel_base);
#endif
if (last == curr) {
user_ctx->partition = curr->next;
os_free_memory(curr);
return FSL_RETURN_OK_S;
} else {
last->next = curr->next;
os_free_memory(curr);
return FSL_RETURN_OK_S;
}
}
last = curr;
curr = (fsl_shw_spo_t *) curr->next;
}
return FSL_RETURN_ERROR_S;
}
#endif /* FSL_HAVE_SCC2 */
static os_error_code sah_handle_scc_drop_perms(fsl_shw_uco_t * user_ctx,
uint32_t info)
{
os_error_code status = OS_ERROR_NO_MEMORY_S;
#ifdef FSL_HAVE_SCC2
scc_return_t scc_ret;
scc_partition_info_t partition_info;
void *kernel_base;
status =
os_copy_from_user(&partition_info, (void *)info,
sizeof(partition_info));
if (status != OS_ERROR_OK_S) {
goto out;
}
/* validate that the user owns this partition, and look up its handle */
kernel_base = lookup_user_partition(user_ctx, partition_info.user_base);
if (kernel_base == NULL) {
status = OS_ERROR_FAIL_S;
#ifdef DIAG_DRV_IF
LOG_KDIAG("_scc_drop_perms(): failed to find partition\n");
#endif
goto out;
}
/* call scc driver to perform the drop */
scc_ret = scc_diminish_permissions(kernel_base,
partition_info.permissions);
if (scc_ret == SCC_RET_OK) {
status = OS_ERROR_OK_S;
} else {
status = OS_ERROR_FAIL_S;
}
out:
#endif /* FSL_HAVE_SCC2 */
return status;
}
static os_error_code sah_handle_scc_sfree(fsl_shw_uco_t * user_ctx,
uint32_t info)
{
os_error_code status = OS_ERROR_NO_MEMORY_S;
#ifdef FSL_HAVE_SCC2
{
scc_partition_info_t partition_info;
void *kernel_base;
int ret;
status =
os_copy_from_user(&partition_info, (void *)info,
sizeof(partition_info));
/* check that the copy was successful */
if (status != OS_ERROR_OK_S) {
goto out;
}
/* validate that the user owns this partition, and look up its handle */
kernel_base =
lookup_user_partition(user_ctx, partition_info.user_base);
if (kernel_base == NULL) {
status = OS_ERROR_FAIL_S;
#ifdef DIAG_DRV_IF
LOG_KDIAG("failed to find partition\n");
#endif /*DIAG_DRV_IF */
goto out;
}
/* Unmap the memory region (see sys_munmap in mmap.c) */
ret = unmap_user_memory(partition_info.user_base, 8192);
/* If the memory was successfully released */
if (ret == OS_ERROR_OK_S) {
/* release the partition */
scc_release_partition(kernel_base);
/* and remove it from the users context */
deregister_user_partition(user_ctx,
partition_info.user_base);
status = OS_ERROR_OK_S;
}
}
out:
#endif /* FSL_HAVE_SCC2 */
return status;
}
static os_error_code sah_handle_scc_sstatus(fsl_shw_uco_t * user_ctx,
uint32_t info)
{
os_error_code status = OS_ERROR_NO_MEMORY_S;
#ifdef FSL_HAVE_SCC2
{
scc_partition_info_t partition_info;
void *kernel_base;
status =
os_copy_from_user(&partition_info, (void *)info,
sizeof(partition_info));
/* check that the copy was successful */
if (status != OS_ERROR_OK_S) {
goto out;
}
/* validate that the user owns this partition, and look up its handle */
kernel_base =
lookup_user_partition(user_ctx, partition_info.user_base);
if (kernel_base == NULL) {
status = OS_ERROR_FAIL_S;
#ifdef DIAG_DRV_IF
LOG_KDIAG("failed to find partition\n");
#endif /*DIAG_DRV_IF */
goto out;
}
partition_info.status = scc_partition_status(kernel_base);
status =
os_copy_to_user((void *)info, &partition_info,
sizeof(partition_info));
}
out:
#endif /* FSL_HAVE_SCC2 */
return status;
}
static os_error_code sah_handle_scc_encrypt(fsl_shw_uco_t * user_ctx,
uint32_t info)
{
os_error_code os_err = OS_ERROR_FAIL_S;
#ifdef FSL_HAVE_SCC2
{
fsl_shw_return_t retval;
scc_region_t region_info;
void *page_ctx = NULL;
void *black_addr = NULL;
void *partition_base = NULL;
scc_config_t *scc_configuration;
os_err =
os_copy_from_user(&region_info, (void *)info,
sizeof(region_info));
if (os_err != OS_ERROR_OK_S) {
goto out;
}
#ifdef DIAG_DRV_IF
LOG_KDIAG_ARGS
("partition_base: %p, offset: %i, length: %i, black data: %p",
(void *)region_info.partition_base, region_info.offset,
region_info.length, (void *)region_info.black_data);
#endif
/* validate that the user owns this partition, and look up its handle */
partition_base = lookup_user_partition(user_ctx,
region_info.
partition_base);
if (partition_base == NULL) {
retval = FSL_RETURN_ERROR_S;
#ifdef DIAG_DRV_IF
LOG_KDIAG("failed to find secure partition\n");
#endif
goto out;
}
/* Check that the memory size requested is correct */
scc_configuration = scc_get_configuration();
if (region_info.offset + region_info.length >
scc_configuration->partition_size_bytes) {
retval = FSL_RETURN_ERROR_S;
goto out;
}
/* wire down black data */
black_addr = wire_user_memory(region_info.black_data,
region_info.length, &page_ctx);
if (black_addr == NULL) {
retval = FSL_RETURN_ERROR_S;
goto out;
}
retval =
do_scc_encrypt_region(NULL, partition_base,
region_info.offset,
region_info.length, black_addr,
region_info.IV,
region_info.cypher_mode);
/* release black data */
unwire_user_memory(&page_ctx);
out:
if (os_err == OS_ERROR_OK_S) {
/* Return error code */
region_info.code = retval;
os_err =
os_copy_to_user((void *)info, &region_info,
sizeof(region_info));
}
}
#endif
return os_err;
}
static os_error_code sah_handle_scc_decrypt(fsl_shw_uco_t * user_ctx,
uint32_t info)
{
os_error_code os_err = OS_ERROR_FAIL_S;
#ifdef FSL_HAVE_SCC2
{
fsl_shw_return_t retval;
scc_region_t region_info;
void *page_ctx = NULL;
void *black_addr;
void *partition_base;
scc_config_t *scc_configuration;
os_err =
os_copy_from_user(&region_info, (void *)info,
sizeof(region_info));
if (os_err != OS_ERROR_OK_S) {
goto out;
}
#ifdef DIAG_DRV_IF
LOG_KDIAG_ARGS
("partition_base: %p, offset: %i, length: %i, black data: %p",
(void *)region_info.partition_base, region_info.offset,
region_info.length, (void *)region_info.black_data);
#endif
/* validate that the user owns this partition, and look up its handle */
partition_base = lookup_user_partition(user_ctx,
region_info.
partition_base);
if (partition_base == NULL) {
retval = FSL_RETURN_ERROR_S;
#ifdef DIAG_DRV_IF
LOG_KDIAG("failed to find partition\n");
#endif
goto out;
}
/* Check that the memory size requested is correct */
scc_configuration = scc_get_configuration();
if (region_info.offset + region_info.length >
scc_configuration->partition_size_bytes) {
retval = FSL_RETURN_ERROR_S;
goto out;
}
/* wire down black data */
black_addr = wire_user_memory(region_info.black_data,
region_info.length, &page_ctx);
if (black_addr == NULL) {
retval = FSL_RETURN_ERROR_S;
goto out;
}
retval =
do_scc_decrypt_region(NULL, partition_base,
region_info.offset,
region_info.length, black_addr,
region_info.IV,
region_info.cypher_mode);
/* release black data */
unwire_user_memory(&page_ctx);
out:
if (os_err == OS_ERROR_OK_S) {
/* Return error code */
region_info.code = retval;
os_err =
os_copy_to_user((void *)info, &region_info,
sizeof(region_info));
}
}
#endif /* FSL_HAVE_SCC2 */
return os_err;
}
/*****************************************************************************/
/* fn get_user_smid() */
/*****************************************************************************/
uint32_t get_user_smid(void *proc)
{
/*
* A real implementation would have some way to handle signed applications
* which wouild be assigned distinct SMIDs. For the reference
* implementation, we show where this would be determined (here), but
* always provide a fixed answer, thus not separating users at all.
*/
return 0x42eaae42;
}
/*!
*******************************************************************************
* This function implements the smalloc() function for userspace programs, by
* making a call to the SCC2 mmap() function that acquires a region of secure
* memory on behalf of the user, and then maps it into the users memory space.
* Currently, the only memory size supported is that of a single SCC2 partition.
* Requests for other sized memory regions will fail.
*/
OS_DEV_MMAP(sah_mmap)
{
os_error_code status = OS_ERROR_NO_MEMORY_S;
#ifdef FSL_HAVE_SCC2
{
scc_return_t scc_ret;
fsl_shw_return_t fsl_ret;
uint32_t partition_registered = FALSE;
uint32_t user_base;
void *partition_base;
uint32_t smid;
scc_config_t *scc_configuration;
int part_no = -1;
uint32_t part_phys;
fsl_shw_uco_t *user_ctx =
(fsl_shw_uco_t *) os_dev_get_user_private();
/* Make sure that the user context is valid */
if (user_ctx == NULL) {
user_ctx =
os_alloc_memory(sizeof(*user_ctx), GFP_KERNEL);
if (user_ctx == NULL) {
status = OS_ERROR_NO_MEMORY_S;
goto out;
}
sah_handle_registration(user_ctx);
os_dev_set_user_private(user_ctx);
}
/* Determine the size of a secure partition */
scc_configuration = scc_get_configuration();
/* Check that the memory size requested is equal to the partition
* size, and that the requested destination is on a page boundary.
*/
if (((os_mmap_user_base() % PAGE_SIZE) != 0) ||
(os_mmap_memory_size() !=
scc_configuration->partition_size_bytes)) {
status = OS_ERROR_BAD_ARG_S;
goto out;
}
/* Retrieve the SMID associated with the user */
smid = get_user_smid(user_ctx->process);
/* Attempt to allocate a secure partition */
scc_ret =
scc_allocate_partition(smid, &part_no, &partition_base,
&part_phys);
if (scc_ret != SCC_RET_OK) {
pr_debug
("SCC mmap() request failed to allocate partition;"
" error %d\n", status);
status = OS_ERROR_FAIL_S;
goto out;
}
pr_debug("scc_mmap() acquired partition %d at %08x\n",
part_no, part_phys);
/* Record partition info in the user context */
user_base = os_mmap_user_base();
fsl_ret =
register_user_partition(user_ctx, user_base,
partition_base);
if (fsl_ret != FSL_RETURN_OK_S) {
pr_debug
("SCC mmap() request failed to register partition with user"
" context, error: %d\n", fsl_ret);
status = OS_ERROR_FAIL_S;
}
partition_registered = TRUE;
status = map_user_memory(os_mmap_memory_ctx(), part_phys,
os_mmap_memory_size());
#ifdef SHW_DEBUG
if (status == OS_ERROR_OK_S) {
LOG_KDIAG_ARGS
("Partition allocated: user_base=%p, partition_base=%p.",
(void *)user_base, partition_base);
}
#endif
out:
/* If there is an error it has to be handled here */
if (status != OS_ERROR_OK_S) {
/* if the partition was registered with the user, unregister it. */
if (partition_registered == TRUE) {
deregister_user_partition(user_ctx, user_base);
}
/* if the partition was allocated, deallocate it */
if (partition_base != NULL) {
scc_release_partition(partition_base);
}
}
}
#endif /* FSL_HAVE_SCC2 */
return status;
}
/* Find the physical address of a key stored in the system keystore */
fsl_shw_return_t
system_keystore_get_slot_info(uint64_t owner_id, uint32_t slot,
uint32_t * address, uint32_t * slot_size_bytes)
{
fsl_shw_return_t retval;
void *kernel_address;
/* First verify that the key access is valid */
retval = system_keystore.slot_verify_access(system_keystore.user_data,
owner_id, slot);
if (retval != FSL_RETURN_OK_S) {
#ifdef DIAG_DRV_IF
LOG_KDIAG("verification failed");
#endif
return retval;
}
if (address != NULL) {
#ifdef FSL_HAVE_SCC2
kernel_address =
system_keystore.slot_get_address(system_keystore.user_data,
slot);
(*address) = scc_virt_to_phys(kernel_address);
#else
kernel_address =
system_keystore.slot_get_address((void *)&owner_id, slot);
(*address) = (uint32_t) kernel_address;
#endif
}
if (slot_size_bytes != NULL) {
#ifdef FSL_HAVE_SCC2
*slot_size_bytes =
system_keystore.slot_get_slot_size(system_keystore.
user_data, slot);
#else
*slot_size_bytes =
system_keystore.slot_get_slot_size((void *)&owner_id, slot);
#endif
}
return retval;
}
static os_error_code sah_handle_sk_slot_alloc(uint32_t info)
{
scc_slot_t slot_info;
os_error_code os_err;
scc_return_t scc_ret;
os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info));
if (os_err == OS_ERROR_OK_S) {
scc_ret = keystore_slot_alloc(&system_keystore,
slot_info.key_length,
slot_info.ownerid,
&slot_info.slot);
if (scc_ret == SCC_RET_OK) {
slot_info.code = FSL_RETURN_OK_S;
} else if (scc_ret == SCC_RET_INSUFFICIENT_SPACE) {
slot_info.code = FSL_RETURN_NO_RESOURCE_S;
} else {
slot_info.code = FSL_RETURN_ERROR_S;
}
#ifdef DIAG_DRV_IF
LOG_KDIAG_ARGS("key length: %i, handle: %i\n",
slot_info.key_length, slot_info.slot);
#endif
/* Return error code and slot info */
os_err =
os_copy_to_user((void *)info, &slot_info,
sizeof(slot_info));
if (os_err != OS_ERROR_OK_S) {
(void)keystore_slot_dealloc(&system_keystore,
slot_info.ownerid,
slot_info.slot);
}
}
return os_err;
}
static os_error_code sah_handle_sk_slot_dealloc(uint32_t info)
{
fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S;
scc_slot_t slot_info;
os_error_code os_err;
scc_return_t scc_ret;
os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info));
if (os_err == OS_ERROR_OK_S) {
scc_ret = keystore_slot_dealloc(&system_keystore,
slot_info.ownerid,
slot_info.slot);
if (scc_ret == SCC_RET_OK) {
ret = FSL_RETURN_OK_S;
} else {
ret = FSL_RETURN_ERROR_S;
}
slot_info.code = ret;
os_err =
os_copy_to_user((void *)info, &slot_info,
sizeof(slot_info));
}
return os_err;
}
static os_error_code sah_handle_sk_slot_load(uint32_t info)
{
fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S;
scc_slot_t slot_info;
os_error_code os_err;
uint8_t *key = NULL;
os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info));
if (os_err == OS_ERROR_OK_S) {
/* Allow slop in alloc in case we are rounding up to word multiple */
key = os_alloc_memory(slot_info.key_length + 3, GFP_KERNEL);
if (key == NULL) {
ret = FSL_RETURN_NO_RESOURCE_S;
os_err = OS_ERROR_NO_MEMORY_S;
} else {
os_err = os_copy_from_user(key, slot_info.key,
slot_info.key_length);
}
}
if (os_err == OS_ERROR_OK_S) {
unsigned key_length = slot_info.key_length;
/* Round up if necessary, as SCC call wants a multiple of 32-bit
* values for the full object being loaded. */
if ((key_length & 3) != 0) {
key_length += 4 - (key_length & 3);
}
ret = keystore_slot_load(&system_keystore,
slot_info.ownerid, slot_info.slot, key,
key_length);
slot_info.code = ret;
os_err =
os_copy_to_user((void *)info, &slot_info,
sizeof(slot_info));
}
if (key != NULL) {
memset(key, 0, slot_info.key_length);
os_free_memory(key);
}
return os_err;
}
static os_error_code sah_handle_sk_slot_read(uint32_t info)
{
fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S;
scc_slot_t slot_info;
os_error_code os_err;
uint8_t *key = NULL;
os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info));
if (os_err == OS_ERROR_OK_S) {
/* This operation is not allowed for user keys */
slot_info.code = FSL_RETURN_NO_RESOURCE_S;
os_err =
os_copy_to_user((void *)info, &slot_info,
sizeof(slot_info));
return os_err;
}
if (os_err == OS_ERROR_OK_S) {
/* Allow slop in alloc in case we are rounding up to word multiple */
key = os_alloc_memory(slot_info.key_length + 3, GFP_KERNEL);
if (key == NULL) {
ret = FSL_RETURN_NO_RESOURCE_S;
os_err = OS_ERROR_NO_MEMORY_S;
}
}
if (os_err == OS_ERROR_OK_S) {
unsigned key_length = slot_info.key_length;
/* @bug Do some PERMISSIONS checking - make sure this is SW key */
/* Round up if necessary, as SCC call wants a multiple of 32-bit
* values for the full object being loaded. */
if ((key_length & 3) != 0) {
key_length += 4 - (key_length & 3);
}
ret = keystore_slot_read(&system_keystore,
slot_info.ownerid, slot_info.slot,
key_length, key);
/* @bug do some error checking */
/* Send key back to user */
os_err = os_copy_to_user(slot_info.key, key,
slot_info.key_length);
slot_info.code = ret;
os_err =
os_copy_to_user((void *)info, &slot_info,
sizeof(slot_info));
}
if (key != NULL) {
memset(key, 0, slot_info.key_length);
os_free_memory(key);
}
return os_err;
}
static os_error_code sah_handle_sk_slot_encrypt(uint32_t info)
{
fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S;
scc_slot_t slot_info;
os_error_code os_err;
scc_return_t scc_ret;
uint8_t *key = NULL;
os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info));
if (os_err == OS_ERROR_OK_S) {
key = os_alloc_memory(slot_info.key_length, GFP_KERNEL);
if (key == NULL) {
ret = FSL_RETURN_NO_RESOURCE_S;
}
}
if (key != NULL) {
scc_ret = keystore_slot_encrypt(NULL, &system_keystore,
slot_info.ownerid,
slot_info.slot,
slot_info.key_length, key);
if (scc_ret != SCC_RET_OK) {
ret = FSL_RETURN_ERROR_S;
} else {
os_err =
os_copy_to_user(slot_info.key, key,
slot_info.key_length);
if (os_err != OS_ERROR_OK_S) {
ret = FSL_RETURN_INTERNAL_ERROR_S;
} else {
ret = FSL_RETURN_OK_S;
}
}
slot_info.code = ret;
os_err =
os_copy_to_user((void *)info, &slot_info,
sizeof(slot_info));
memset(key, 0, slot_info.key_length);
os_free_memory(key);
}
return os_err;
}
static os_error_code sah_handle_sk_slot_decrypt(uint32_t info)
{
fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S;
scc_slot_t slot_info; /*!< decrypt request fields */
os_error_code os_err;
scc_return_t scc_ret;
uint8_t *key = NULL;
os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info));
if (os_err == OS_ERROR_OK_S) {
key = os_alloc_memory(slot_info.key_length, GFP_KERNEL);
if (key == NULL) {
ret = FSL_RETURN_NO_RESOURCE_S;
os_err = OS_ERROR_OK_S;
} else {
os_err = os_copy_from_user(key, slot_info.key,
slot_info.key_length);
}
}
if (os_err == OS_ERROR_OK_S) {
scc_ret = keystore_slot_decrypt(NULL, &system_keystore,
slot_info.ownerid,
slot_info.slot,
slot_info.key_length, key);
if (scc_ret == SCC_RET_OK) {
ret = FSL_RETURN_OK_S;
} else {
ret = FSL_RETURN_ERROR_S;
}
slot_info.code = ret;
os_err =
os_copy_to_user((void *)info, &slot_info,
sizeof(slot_info));
}
if (key != NULL) {
memset(key, 0, slot_info.key_length);
os_free_memory(key);
}
return os_err;
}
/*!
* Register a user
*
* @brief Register a user
*
* @param user_ctx information about this user
*
* @return status code
*/
fsl_shw_return_t sah_handle_registration(fsl_shw_uco_t * user_ctx)
{
/* Initialize the user's result pool (like sah_Queue_Construct() */
user_ctx->result_pool.head = NULL;
user_ctx->result_pool.tail = NULL;
user_ctx->result_pool.count = 0;
/* initialize the user's partition chain */
user_ctx->partition = NULL;
return FSL_RETURN_OK_S;
}
/*!
* Deregister a user
*
* @brief Deregister a user
*
* @param user_ctx information about this user
*
* @return status code
*/
fsl_shw_return_t sah_handle_deregistration(fsl_shw_uco_t * user_ctx)
{
/* NOTE:
* This will release any secure partitions that are held by the user.
* Encryption keys that were placed in the system keystore by the user
* should not be removed here, because they might have been shared with
* another process. The user must be careful to release any that are no
* longer in use.
*/
fsl_shw_return_t ret = FSL_RETURN_OK_S;
#ifdef FSL_HAVE_SCC2
fsl_shw_spo_t *partition;
struct mm_struct *mm = current->mm;
while ((user_ctx->partition != NULL) && (ret == FSL_RETURN_OK_S)) {
partition = user_ctx->partition;
#ifdef DIAG_DRV_IF
LOG_KDIAG_ARGS
("Found an abandoned secure partition at %p, releasing",
partition);
#endif
/* It appears that current->mm is not valid if this is called from a
* close routine (perhaps only if the program raised an exception that
* caused it to close?) If that is the case, then still free the
* partition, but do not remove it from the memory space (dangerous?)
*/
if (mm == NULL) {
#ifdef DIAG_DRV_IF
LOG_KDIAG
("Warning: no mm structure found, not unmapping "
"partition from user memory\n");
#endif
} else {
/* Unmap the memory region (see sys_munmap in mmap.c) */
/* Note that this assumes a single memory partition */
unmap_user_memory(partition->user_base, 8192);
}
/* If the memory was successfully released */
if (ret == OS_ERROR_OK_S) {
/* release the partition */
scc_release_partition(partition->kernel_base);
/* and remove it from the users context */
deregister_user_partition(user_ctx,
partition->user_base);
ret = FSL_RETURN_OK_S;
} else {
ret = FSL_RETURN_ERROR_S;
goto out;
}
}
out:
#endif /* FSL_HAVE_SCC2 */
return ret;
}
/*!
* Sets up memory to extract results from results pool
*
* @brief Sets up memory to extract results from results pool
*
* @param user_ctx information about this user
* @param[in,out] arg contains input parameters and fields that the driver
* fills in
*
* @return os error code or 0 on success
*/
int sah_get_results_pointers(fsl_shw_uco_t * user_ctx, uint32_t arg)
{
sah_results results_arg; /* kernel mode usable version of 'arg' */
fsl_shw_result_t *user_results; /* user mode address of results */
unsigned *user_actual; /* user mode address of actual number of results */
unsigned actual; /* local memory of actual number of results */
int ret_val = OS_ERROR_FAIL_S;
sah_Head_Desc *finished_request;
unsigned int loop;
/* copy structure from user to kernel space */
if (!os_copy_from_user(&results_arg, (void *)arg, sizeof(sah_results))) {
/* save user space pointers */
user_actual = results_arg.actual; /* where count goes */
user_results = results_arg.results; /* where results goe */
/* Set pointer for actual value to temporary kernel memory location */
results_arg.actual = &actual;
/* Allocate kernel memory to hold temporary copy of the results */
results_arg.results =
os_alloc_memory(sizeof(fsl_shw_result_t) *
results_arg.requested, GFP_KERNEL);
/* if memory allocated, continue */
if (results_arg.results == NULL) {
ret_val = OS_ERROR_NO_MEMORY_S;
} else {
fsl_shw_return_t get_status;
/* get the results */
get_status =
sah_get_results_from_pool(user_ctx, &results_arg);
/* free the copy of the user space descriptor chain */
for (loop = 0; loop < actual; ++loop) {
/* get sah_Head_Desc from results and put user address into
* the return structure */
finished_request =
results_arg.results[loop].user_desc;
results_arg.results[loop].user_desc =
finished_request->user_desc;
/* return the descriptor chain memory to the block free pool */
sah_Free_Chained_Descriptors(finished_request);
}
/* if no errors, copy results and then the actual number of results
* back to user space
*/
if (get_status == FSL_RETURN_OK_S) {
if (os_copy_to_user
(user_results, results_arg.results,
actual * sizeof(fsl_shw_result_t))
|| os_copy_to_user(user_actual, &actual,
sizeof(user_actual))) {
ret_val = OS_ERROR_FAIL_S;
} else {
ret_val = 0; /* no error */
}
}
/* free the allocated memory */
os_free_memory(results_arg.results);
}
}
return ret_val;
}
/*!
* Extracts results from results pool
*
* @brief Extract results from results pool
*
* @param user_ctx information about this user
* @param[in,out] arg contains input parameters and fields that the
* driver fills in
*
* @return status code
*/
fsl_shw_return_t sah_get_results_from_pool(volatile fsl_shw_uco_t * user_ctx,
sah_results * arg)
{
sah_Head_Desc *finished_request;
unsigned int loop = 0;
os_lock_context_t int_flags;
/* Get the number of results requested, up to total number of results
* available
*/
do {
/* Protect state of user's result pool until we have retrieved and
* remove the first entry, or determined that the pool is empty. */
os_lock_save_context(desc_queue_lock, int_flags);
finished_request = user_ctx->result_pool.head;
if (finished_request != NULL) {
sah_Queue_Remove_Entry((sah_Queue *) & user_ctx->
result_pool);
os_unlock_restore_context(desc_queue_lock, int_flags);
/* Prepare to free. */
(void)sah_DePhysicalise_Descriptors(finished_request);
arg->results[loop].user_ref =
finished_request->user_ref;
arg->results[loop].code = finished_request->result;
arg->results[loop].detail1 =
finished_request->fault_address;
arg->results[loop].detail2 = 0;
arg->results[loop].user_desc = finished_request;
loop++;
} else { /* finished_request is NULL */
/* pool is empty */
os_unlock_restore_context(desc_queue_lock, int_flags);
}
} while ((loop < arg->requested) && (finished_request != NULL));
/* record number of results actually obtained */
*arg->actual = loop;
return FSL_RETURN_OK_S;
}
/*!
* Converts descriptor chain to kernel space (from user space) and submits
* chain to Sahara for processing
*
* @brief Submits converted descriptor chain to sahara
*
* @param user_ctx Pointer to Kernel version of user's ctx
* @param user_space_desc user space address of descriptor chain that is
* in user space
*
* @return OS status code
*/
static int handle_sah_ioctl_dar(fsl_shw_uco_t * user_ctx,
uint32_t user_space_desc)
{
int os_error_code = OS_ERROR_FAIL_S;
sah_Head_Desc *desc_chain_head; /* chain in kernel - virtual address */
/* This will re-create the linked list so that the SAHARA hardware can
* DMA on it.
*/
desc_chain_head = sah_Copy_Descriptors(user_ctx,
(sah_Head_Desc *)
user_space_desc);
if (desc_chain_head == NULL) {
/* We may have failed due to a -EFAULT as well, but we will return
* OS_ERROR_NO_MEMORY_S since either way it is a memory related
* failure.
*/
os_error_code = OS_ERROR_NO_MEMORY_S;
} else {
fsl_shw_return_t stat;
desc_chain_head->user_info = user_ctx;
desc_chain_head->user_desc = (sah_Head_Desc *) user_space_desc;
if (desc_chain_head->uco_flags & FSL_UCO_BLOCKING_MODE) {
#ifdef SAHARA_POLL_MODE
sah_Handle_Poll(desc_chain_head);
#else
sah_blocking_mode(desc_chain_head);
#endif
stat = desc_chain_head->result;
/* return the descriptor chain memory to the block free pool */
sah_Free_Chained_Descriptors(desc_chain_head);
/* Tell user how the call turned out */
/* Copy 'result' back up to the result member.
*
* The dereference of the different member will cause correct the
* arithmetic to occur on the user-space address because of the
* missing dma/bus locations in the user mode version of the
* sah_Desc structure. */
os_error_code =
os_copy_to_user((void *)(user_space_desc
+ offsetof(sah_Head_Desc,
uco_flags)),
&stat, sizeof(fsl_shw_return_t));
} else { /* not blocking mode - queue and forget */
if (desc_chain_head->uco_flags & FSL_UCO_CALLBACK_MODE) {
user_ctx->process = os_get_process_handle();
user_ctx->callback = sah_user_callback;
}
#ifdef SAHARA_POLL_MODE
/* will put results in result pool */
sah_Handle_Poll(desc_chain_head);
#else
/* just put someting in the DAR */
sah_Queue_Manager_Append_Entry(desc_chain_head);
#endif
/* assume all went well */
os_error_code = OS_ERROR_OK_S;
}
}
return os_error_code;
}
static void sah_user_callback(fsl_shw_uco_t * user_ctx)
{
os_send_signal(user_ctx->process, SIGUSR2);
}
/*!
* This function is called when a thread attempts to read from the /proc/sahara
* file. Upon read, statistics and information about the state of the driver
* are returned in nthe supplied buffer.
*
* @brief SAHARA PROCFS read function.
*
* @param buf Anything written to this buffer will be returned to the
* user-space process that is reading from this proc entry.
* @param start Part of the kernel prototype.
* @param offset Part of the kernel prototype.
* @param count The size of the buf argument.
* @param eof An integer which is set to one to tell the user-space
* process that there is no more data to read.
* @param data Part of the kernel prototype.
*
* @return The number of bytes written to the proc entry.
*/
#if !defined(CONFIG_DEVFS_FS) || (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0))
static int sah_read_procfs(char *buf,
char **start,
off_t offset, int count, int *eof, void *data)
{
int output_bytes = 0;
int in_queue_count = 0;
os_lock_context_t lock_context;
os_lock_save_context(desc_queue_lock, lock_context);
in_queue_count = sah_Queue_Manager_Count_Entries(TRUE, 0);
os_unlock_restore_context(desc_queue_lock, lock_context);
output_bytes += snprintf(buf, count - output_bytes, "queued: %d\n",
in_queue_count);
output_bytes += snprintf(buf + output_bytes, count - output_bytes,
"Descriptors: %d, "
"Interrupts %d (%d Done1Done2, %d Done1Busy2, "
" %d Done1)\n",
dar_count, interrupt_count, done1done2_count,
done1busy2_count, done1_count);
output_bytes += snprintf(buf + output_bytes, count - output_bytes,
"Control: %08x\n", sah_HW_Read_Control());
#if !defined(FSL_HAVE_SAHARA4) || defined(SAHARA4_NO_USE_SQUIB)
output_bytes += snprintf(buf + output_bytes, count - output_bytes,
"IDAR: %08x; CDAR: %08x\n",
sah_HW_Read_IDAR(), sah_HW_Read_CDAR());
#endif
#ifdef DIAG_DRV_STATUS
output_bytes += snprintf(buf + output_bytes, count - output_bytes,
"Status: %08x; Error Status: %08x; Op Status: %08x\n",
sah_HW_Read_Status(),
sah_HW_Read_Error_Status(),
sah_HW_Read_Op_Status());
#endif
#ifdef FSL_HAVE_SAHARA4
output_bytes += snprintf(buf + output_bytes, count - output_bytes,
"MMStat: %08x; Config: %08x\n",
sah_HW_Read_MM_Status(), sah_HW_Read_Config());
#endif
/* Signal the end of the file */
*eof = 1;
/* To get rid of the unused parameter warnings */
(void)start;
(void)data;
(void)offset;
return output_bytes;
}
static int sah_write_procfs(struct file *file, const char __user * buffer,
unsigned long count, void *data)
{
/* Any write to this file will reset all counts. */
dar_count = interrupt_count = done1done2_count =
done1busy2_count = done1_count = 0;
(void)file;
(void)buffer;
(void)data;
return count;
}
#endif
#ifndef SAHARA_POLL_MODE
/*!
* Block user call until processing is complete.
*
* @param entry The user's request.
*
* @return An OS error code, or 0 if no error
*/
int sah_blocking_mode(sah_Head_Desc * entry)
{
int os_error_code = 0;
sah_Queue_Status status;
/* queue entry, put something in the DAR, if nothing is there currently */
sah_Queue_Manager_Append_Entry(entry);
/* get this descriptor chain's current status */
status = ((volatile sah_Head_Desc *)entry)->status;
while (!SAH_DESC_PROCESSED(status)) {
extern sah_Queue *main_queue;
DEFINE_WAIT(sahara_wait); /* create a wait queue entry. Linux */
/* enter the wait queue entry into the queue */
prepare_to_wait(&Wait_queue, &sahara_wait, TASK_INTERRUPTIBLE);
/* check if this entry has been processed */
status = ((volatile sah_Head_Desc *)entry)->status;
if (!SAH_DESC_PROCESSED(status)) {
/* go to sleep - Linux */
schedule();
}
/* un-queue the 'prepare to wait' queue? - Linux */
finish_wait(&Wait_queue, &sahara_wait);
/* signal belongs to this thread? */
if (signal_pending(current)) { /* Linux */
os_lock_context_t lock_flags;
/* don't allow access during this check and operation */
os_lock_save_context(desc_queue_lock, lock_flags);
status = ((volatile sah_Head_Desc *)entry)->status;
if (status == SAH_STATE_PENDING) {
sah_Queue_Remove_Any_Entry(main_queue, entry);
entry->result = FSL_RETURN_INTERNAL_ERROR_S;
((volatile sah_Head_Desc *)entry)->status =
SAH_STATE_FAILED;
}
os_unlock_restore_context(desc_queue_lock, lock_flags);
}
status = ((volatile sah_Head_Desc *)entry)->status;
} /* while ... */
/* Do this so that caller can free */
(void)sah_DePhysicalise_Descriptors(entry);
return os_error_code;
}
/*!
* If interrupt does not return in a reasonable time, time out, trigger
* interrupt, and continue with process
*
* @param data ignored
*/
void sahara_timeout_handler(unsigned long data)
{
/* Sahara has not issuing an interrupt, so timed out */
#ifdef DIAG_DRV_IF
LOG_KDIAG("Sahara HW did not respond. Resetting.\n");
#endif
/* assume hardware needs resetting */
sah_Handle_Interrupt(SAH_EXEC_FAULT);
/* wake up sleeping thread to try again */
wake_up_interruptible(&Wait_queue);
}
#endif /* ifndef SAHARA_POLL_MODE */
/* End of sah_driver_interface.c */