diff options
Diffstat (limited to 'drivers/staging/sep/sep_main.c')
-rw-r--r-- | drivers/staging/sep/sep_main.c | 4518 |
1 files changed, 4518 insertions, 0 deletions
diff --git a/drivers/staging/sep/sep_main.c b/drivers/staging/sep/sep_main.c new file mode 100644 index 0000000..ad54c2e --- /dev/null +++ b/drivers/staging/sep/sep_main.c @@ -0,0 +1,4518 @@ +/* + * + * sep_main.c - Security Processor Driver main group of functions + * + * Copyright(c) 2009-2011 Intel Corporation. All rights reserved. + * Contributions(c) 2009-2011 Discretix. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * CONTACTS: + * + * Mark Allyn mark.a.allyn@intel.com + * Jayant Mangalampalli jayant.mangalampalli@intel.com + * + * CHANGES: + * + * 2009.06.26 Initial publish + * 2010.09.14 Upgrade to Medfield + * 2011.01.21 Move to sep_main.c to allow for sep_crypto.c + * 2011.02.22 Enable kernel crypto operation + * + * Please note that this driver is based on information in the Discretix + * CryptoCell 5.2 Driver Implementation Guide; the Discretix CryptoCell 5.2 + * Integration Intel Medfield appendix; the Discretix CryptoCell 5.2 + * Linux Driver Integration Guide; and the Discretix CryptoCell 5.2 System + * Overview and Integration Guide. + */ +/* #define DEBUG */ +/* #define SEP_PERF_DEBUG */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/kdev_t.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/wait.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <asm/current.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/pagemap.h> +#include <asm/cacheflush.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/async.h> +#include <linux/crypto.h> +#include <crypto/internal/hash.h> +#include <crypto/scatterwalk.h> +#include <crypto/sha.h> +#include <crypto/md5.h> +#include <crypto/aes.h> +#include <crypto/des.h> +#include <crypto/hash.h> + +#include "sep_driver_hw_defs.h" +#include "sep_driver_config.h" +#include "sep_driver_api.h" +#include "sep_dev.h" +#include "sep_crypto.h" + +#define CREATE_TRACE_POINTS +#include "sep_trace_events.h" + +/* + * Let's not spend cycles iterating over message + * area contents if debugging not enabled + */ +#ifdef DEBUG +#define sep_dump_message(sep) _sep_dump_message(sep) +#else +#define sep_dump_message(sep) +#endif + +/** + * Currenlty, there is only one SEP device per platform; + * In event platforms in the future have more than one SEP + * device, this will be a linked list + */ + +struct sep_device *sep_dev; + +/** + * sep_queue_status_remove - Removes transaction from status queue + * @sep: SEP device + * @sep_queue_info: pointer to status queue + * + * This function will removes information about transaction from the queue. + */ +void sep_queue_status_remove(struct sep_device *sep, + struct sep_queue_info **queue_elem) +{ + unsigned long lck_flags; + + dev_dbg(&sep->pdev->dev, "[PID%d] sep_queue_status_remove\n", + current->pid); + + if (!queue_elem || !(*queue_elem)) { + dev_dbg(&sep->pdev->dev, "PID%d %s null\n", + current->pid, __func__); + return; + } + + spin_lock_irqsave(&sep->sep_queue_lock, lck_flags); + list_del(&(*queue_elem)->list); + sep->sep_queue_num--; + spin_unlock_irqrestore(&sep->sep_queue_lock, lck_flags); + + kfree(*queue_elem); + *queue_elem = NULL; + + dev_dbg(&sep->pdev->dev, "[PID%d] sep_queue_status_remove return\n", + current->pid); + return; +} + +/** + * sep_queue_status_add - Adds transaction to status queue + * @sep: SEP device + * @opcode: transaction opcode + * @size: input data size + * @pid: pid of current process + * @name: current process name + * @name_len: length of name (current process) + * + * This function adds information about about transaction started to the status + * queue. + */ +struct sep_queue_info *sep_queue_status_add( + struct sep_device *sep, + u32 opcode, + u32 size, + u32 pid, + u8 *name, size_t name_len) +{ + unsigned long lck_flags; + struct sep_queue_info *my_elem = NULL; + + my_elem = kzalloc(sizeof(struct sep_queue_info), GFP_KERNEL); + + if (!my_elem) + return NULL; + + dev_dbg(&sep->pdev->dev, "[PID%d] kzalloc ok\n", current->pid); + + my_elem->data.opcode = opcode; + my_elem->data.size = size; + my_elem->data.pid = pid; + + if (name_len > TASK_COMM_LEN) + name_len = TASK_COMM_LEN; + + memcpy(&my_elem->data.name, name, name_len); + + spin_lock_irqsave(&sep->sep_queue_lock, lck_flags); + + list_add_tail(&my_elem->list, &sep->sep_queue_status); + sep->sep_queue_num++; + + spin_unlock_irqrestore(&sep->sep_queue_lock, lck_flags); + + return my_elem; +} + +/** + * sep_allocate_dmatables_region - Allocates buf for the MLLI/DMA tables + * @sep: SEP device + * @dmatables_region: Destination pointer for the buffer + * @dma_ctx: DMA context for the transaction + * @table_count: Number of MLLI/DMA tables to create + * The buffer created will not work as-is for DMA operations, + * it needs to be copied over to the appropriate place in the + * shared area. + */ +static int sep_allocate_dmatables_region(struct sep_device *sep, + void **dmatables_region, + struct sep_dma_context *dma_ctx, + const u32 table_count) +{ + const size_t new_len = + SYNCHRONIC_DMA_TABLES_AREA_SIZE_BYTES - 1; + + void *tmp_region = NULL; + + dev_dbg(&sep->pdev->dev, "[PID%d] dma_ctx = 0x%p\n", + current->pid, dma_ctx); + dev_dbg(&sep->pdev->dev, "[PID%d] dmatables_region = 0x%p\n", + current->pid, dmatables_region); + + if (!dma_ctx || !dmatables_region) { + dev_warn(&sep->pdev->dev, + "[PID%d] dma context/region uninitialized\n", + current->pid); + return -EINVAL; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] newlen = 0x%08zX\n", + current->pid, new_len); + dev_dbg(&sep->pdev->dev, "[PID%d] oldlen = 0x%08X\n", current->pid, + dma_ctx->dmatables_len); + tmp_region = kzalloc(new_len + dma_ctx->dmatables_len, GFP_KERNEL); + if (!tmp_region) { + dev_warn(&sep->pdev->dev, + "[PID%d] no mem for dma tables region\n", + current->pid); + return -ENOMEM; + } + + /* Were there any previous tables that need to be preserved ? */ + if (*dmatables_region) { + memcpy(tmp_region, *dmatables_region, dma_ctx->dmatables_len); + kfree(*dmatables_region); + *dmatables_region = NULL; + } + + *dmatables_region = tmp_region; + + dma_ctx->dmatables_len += new_len; + + return 0; +} + +/** + * sep_wait_transaction - Used for synchronizing transactions + * @sep: SEP device + */ +int sep_wait_transaction(struct sep_device *sep) +{ + int error = 0; + DEFINE_WAIT(wait); + + if (0 == test_and_set_bit(SEP_TRANSACTION_STARTED_LOCK_BIT, + &sep->in_use_flags)) { + dev_dbg(&sep->pdev->dev, + "[PID%d] no transactions, returning\n", + current->pid); + goto end_function_setpid; + } + + /* + * Looping needed even for exclusive waitq entries + * due to process wakeup latencies, previous process + * might have already created another transaction. + */ + for (;;) { + /* + * Exclusive waitq entry, so that only one process is + * woken up from the queue at a time. + */ + prepare_to_wait_exclusive(&sep->event_transactions, + &wait, + TASK_INTERRUPTIBLE); + if (0 == test_and_set_bit(SEP_TRANSACTION_STARTED_LOCK_BIT, + &sep->in_use_flags)) { + dev_dbg(&sep->pdev->dev, + "[PID%d] no transactions, breaking\n", + current->pid); + break; + } + dev_dbg(&sep->pdev->dev, + "[PID%d] transactions ongoing, sleeping\n", + current->pid); + schedule(); + dev_dbg(&sep->pdev->dev, "[PID%d] woken up\n", current->pid); + + if (signal_pending(current)) { + dev_dbg(&sep->pdev->dev, "[PID%d] received signal\n", + current->pid); + error = -EINTR; + goto end_function; + } + } +end_function_setpid: + /* + * The pid_doing_transaction indicates that this process + * now owns the facilities to performa a transaction with + * the SEP. While this process is performing a transaction, + * no other process who has the SEP device open can perform + * any transactions. This method allows more than one process + * to have the device open at any given time, which provides + * finer granularity for device utilization by multiple + * processes. + */ + /* Only one process is able to progress here at a time */ + sep->pid_doing_transaction = current->pid; + +end_function: + finish_wait(&sep->event_transactions, &wait); + + return error; +} + +/** + * sep_check_transaction_owner - Checks if current process owns transaction + * @sep: SEP device + */ +static inline int sep_check_transaction_owner(struct sep_device *sep) +{ + dev_dbg(&sep->pdev->dev, "[PID%d] transaction pid = %d\n", + current->pid, + sep->pid_doing_transaction); + + if ((sep->pid_doing_transaction == 0) || + (current->pid != sep->pid_doing_transaction)) { + return -EACCES; + } + + /* We own the transaction */ + return 0; +} + +#ifdef DEBUG + +/** + * sep_dump_message - dump the message that is pending + * @sep: SEP device + * This will only print dump if DEBUG is set; it does + * follow kernel debug print enabling + */ +static void _sep_dump_message(struct sep_device *sep) +{ + int count; + + u32 *p = sep->shared_addr; + + for (count = 0; count < 10 * 4; count += 4) + dev_dbg(&sep->pdev->dev, + "[PID%d] Word %d of the message is %x\n", + current->pid, count/4, *p++); +} + +#endif + +/** + * sep_map_and_alloc_shared_area -allocate shared block + * @sep: security processor + * @size: size of shared area + */ +static int sep_map_and_alloc_shared_area(struct sep_device *sep) +{ + sep->shared_addr = dma_alloc_coherent(&sep->pdev->dev, + sep->shared_size, + &sep->shared_bus, GFP_KERNEL); + + if (!sep->shared_addr) { + dev_dbg(&sep->pdev->dev, + "[PID%d] shared memory dma_alloc_coherent failed\n", + current->pid); + return -ENOMEM; + } + dev_dbg(&sep->pdev->dev, + "[PID%d] shared_addr %zx bytes @%p (bus %llx)\n", + current->pid, + sep->shared_size, sep->shared_addr, + (unsigned long long)sep->shared_bus); + return 0; +} + +/** + * sep_unmap_and_free_shared_area - free shared block + * @sep: security processor + */ +static void sep_unmap_and_free_shared_area(struct sep_device *sep) +{ + dma_free_coherent(&sep->pdev->dev, sep->shared_size, + sep->shared_addr, sep->shared_bus); +} + +#ifdef DEBUG + +/** + * sep_shared_bus_to_virt - convert bus/virt addresses + * @sep: pointer to struct sep_device + * @bus_address: address to convert + * + * Returns virtual address inside the shared area according + * to the bus address. + */ +static void *sep_shared_bus_to_virt(struct sep_device *sep, + dma_addr_t bus_address) +{ + return sep->shared_addr + (bus_address - sep->shared_bus); +} + +#endif + +/** + * sep_open - device open method + * @inode: inode of SEP device + * @filp: file handle to SEP device + * + * Open method for the SEP device. Called when userspace opens + * the SEP device node. + * + * Returns zero on success otherwise an error code. + */ +static int sep_open(struct inode *inode, struct file *filp) +{ + struct sep_device *sep; + struct sep_private_data *priv; + + dev_dbg(&sep_dev->pdev->dev, "[PID%d] open\n", current->pid); + + if (filp->f_flags & O_NONBLOCK) + return -ENOTSUPP; + + /* + * Get the SEP device structure and use it for the + * private_data field in filp for other methods + */ + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sep = sep_dev; + priv->device = sep; + filp->private_data = priv; + + dev_dbg(&sep_dev->pdev->dev, "[PID%d] priv is 0x%p\n", + current->pid, priv); + + /* Anyone can open; locking takes place at transaction level */ + return 0; +} + +/** + * sep_free_dma_table_data_handler - free DMA table + * @sep: pointere to struct sep_device + * @dma_ctx: dma context + * + * Handles the request to free DMA table for synchronic actions + */ +int sep_free_dma_table_data_handler(struct sep_device *sep, + struct sep_dma_context **dma_ctx) +{ + int count; + int dcb_counter; + /* Pointer to the current dma_resource struct */ + struct sep_dma_resource *dma; + + dev_dbg(&sep->pdev->dev, + "[PID%d] sep_free_dma_table_data_handler\n", + current->pid); + + if (!dma_ctx || !(*dma_ctx)) { + /* No context or context already freed */ + dev_dbg(&sep->pdev->dev, + "[PID%d] no DMA context or context already freed\n", + current->pid); + + return 0; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] (*dma_ctx)->nr_dcb_creat 0x%x\n", + current->pid, + (*dma_ctx)->nr_dcb_creat); + + for (dcb_counter = 0; + dcb_counter < (*dma_ctx)->nr_dcb_creat; dcb_counter++) { + dma = &(*dma_ctx)->dma_res_arr[dcb_counter]; + + /* Unmap and free input map array */ + if (dma->in_map_array) { + for (count = 0; count < dma->in_num_pages; count++) { + dma_unmap_page(&sep->pdev->dev, + dma->in_map_array[count].dma_addr, + dma->in_map_array[count].size, + DMA_TO_DEVICE); + } + kfree(dma->in_map_array); + } + + /** + * Output is handled different. If + * this was a secure dma into restricted memory, + * then we skip this step altogether as restricted + * memory is not available to the o/s at all. + */ + if (((*dma_ctx)->secure_dma == false) && + (dma->out_map_array)) { + + for (count = 0; count < dma->out_num_pages; count++) { + dma_unmap_page(&sep->pdev->dev, + dma->out_map_array[count].dma_addr, + dma->out_map_array[count].size, + DMA_FROM_DEVICE); + } + kfree(dma->out_map_array); + } + + /* Free page cache for output */ + if (dma->in_page_array) { + for (count = 0; count < dma->in_num_pages; count++) { + flush_dcache_page(dma->in_page_array[count]); + page_cache_release(dma->in_page_array[count]); + } + kfree(dma->in_page_array); + } + + /* Again, we do this only for non secure dma */ + if (((*dma_ctx)->secure_dma == false) && + (dma->out_page_array)) { + + for (count = 0; count < dma->out_num_pages; count++) { + if (!PageReserved(dma->out_page_array[count])) + + SetPageDirty(dma-> + out_page_array[count]); + + flush_dcache_page(dma->out_page_array[count]); + page_cache_release(dma->out_page_array[count]); + } + kfree(dma->out_page_array); + } + + /** + * Note that here we use in_map_num_entries because we + * don't have a page array; the page array is generated + * only in the lock_user_pages, which is not called + * for kernel crypto, which is what the sg (scatter gather + * is used for exclusively + */ + if (dma->src_sg) { + dma_unmap_sg(&sep->pdev->dev, dma->src_sg, + dma->in_map_num_entries, DMA_TO_DEVICE); + dma->src_sg = NULL; + } + + if (dma->dst_sg) { + dma_unmap_sg(&sep->pdev->dev, dma->dst_sg, + dma->in_map_num_entries, DMA_FROM_DEVICE); + dma->dst_sg = NULL; + } + + /* Reset all the values */ + dma->in_page_array = NULL; + dma->out_page_array = NULL; + dma->in_num_pages = 0; + dma->out_num_pages = 0; + dma->in_map_array = NULL; + dma->out_map_array = NULL; + dma->in_map_num_entries = 0; + dma->out_map_num_entries = 0; + } + + (*dma_ctx)->nr_dcb_creat = 0; + (*dma_ctx)->num_lli_tables_created = 0; + + kfree(*dma_ctx); + *dma_ctx = NULL; + + dev_dbg(&sep->pdev->dev, + "[PID%d] sep_free_dma_table_data_handler end\n", + current->pid); + + return 0; +} + +/** + * sep_end_transaction_handler - end transaction + * @sep: pointer to struct sep_device + * @dma_ctx: DMA context + * @call_status: Call status + * + * This API handles the end transaction request. + */ +static int sep_end_transaction_handler(struct sep_device *sep, + struct sep_dma_context **dma_ctx, + struct sep_call_status *call_status, + struct sep_queue_info **my_queue_elem) +{ + dev_dbg(&sep->pdev->dev, "[PID%d] ending transaction\n", current->pid); + + /* + * Extraneous transaction clearing would mess up PM + * device usage counters and SEP would get suspended + * just before we send a command to SEP in the next + * transaction + * */ + if (sep_check_transaction_owner(sep)) { + dev_dbg(&sep->pdev->dev, "[PID%d] not transaction owner\n", + current->pid); + return 0; + } + + /* Update queue status */ + sep_queue_status_remove(sep, my_queue_elem); + + /* Check that all the DMA resources were freed */ + if (dma_ctx) + sep_free_dma_table_data_handler(sep, dma_ctx); + + /* Reset call status for next transaction */ + if (call_status) + call_status->status = 0; + + /* Clear the message area to avoid next transaction reading + * sensitive results from previous transaction */ + memset(sep->shared_addr, 0, + SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES); + + /* start suspend delay */ +#ifdef SEP_ENABLE_RUNTIME_PM + if (sep->in_use) { + sep->in_use = 0; + pm_runtime_mark_last_busy(&sep->pdev->dev); + pm_runtime_put_autosuspend(&sep->pdev->dev); + } +#endif + + clear_bit(SEP_WORKING_LOCK_BIT, &sep->in_use_flags); + sep->pid_doing_transaction = 0; + + /* Now it's safe for next process to proceed */ + dev_dbg(&sep->pdev->dev, "[PID%d] waking up next transaction\n", + current->pid); + clear_bit(SEP_TRANSACTION_STARTED_LOCK_BIT, &sep->in_use_flags); + wake_up(&sep->event_transactions); + + return 0; +} + + +/** + * sep_release - close a SEP device + * @inode: inode of SEP device + * @filp: file handle being closed + * + * Called on the final close of a SEP device. + */ +static int sep_release(struct inode *inode, struct file *filp) +{ + struct sep_private_data * const private_data = filp->private_data; + struct sep_call_status *call_status = &private_data->call_status; + struct sep_device *sep = private_data->device; + struct sep_dma_context **dma_ctx = &private_data->dma_ctx; + struct sep_queue_info **my_queue_elem = &private_data->my_queue_elem; + + dev_dbg(&sep->pdev->dev, "[PID%d] release\n", current->pid); + + sep_end_transaction_handler(sep, dma_ctx, call_status, + my_queue_elem); + + kfree(filp->private_data); + + return 0; +} + +/** + * sep_mmap - maps the shared area to user space + * @filp: pointer to struct file + * @vma: pointer to vm_area_struct + * + * Called on an mmap of our space via the normal SEP device + */ +static int sep_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct sep_private_data * const private_data = filp->private_data; + struct sep_call_status *call_status = &private_data->call_status; + struct sep_device *sep = private_data->device; + struct sep_queue_info **my_queue_elem = &private_data->my_queue_elem; + dma_addr_t bus_addr; + unsigned long error = 0; + + dev_dbg(&sep->pdev->dev, "[PID%d] sep_mmap\n", current->pid); + + /* Set the transaction busy (own the device) */ + /* + * Problem for multithreaded applications is that here we're + * possibly going to sleep while holding a write lock on + * current->mm->mmap_sem, which will cause deadlock for ongoing + * transaction trying to create DMA tables + */ + error = sep_wait_transaction(sep); + if (error) + /* Interrupted by signal, don't clear transaction */ + goto end_function; + + /* Clear the message area to avoid next transaction reading + * sensitive results from previous transaction */ + memset(sep->shared_addr, 0, + SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES); + + /* + * Check that the size of the mapped range is as the size of the message + * shared area + */ + if ((vma->vm_end - vma->vm_start) > SEP_DRIVER_MMMAP_AREA_SIZE) { + error = -EINVAL; + goto end_function_with_error; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] shared_addr is %p\n", + current->pid, sep->shared_addr); + + /* Get bus address */ + bus_addr = sep->shared_bus; + + if (remap_pfn_range(vma, vma->vm_start, bus_addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(&sep->pdev->dev, "[PID%d] remap_page_range failed\n", + current->pid); + error = -EAGAIN; + goto end_function_with_error; + } + + /* Update call status */ + set_bit(SEP_LEGACY_MMAP_DONE_OFFSET, &call_status->status); + + goto end_function; + +end_function_with_error: + /* Clear our transaction */ + sep_end_transaction_handler(sep, NULL, call_status, + my_queue_elem); + +end_function: + return error; +} + +/** + * sep_poll - poll handler + * @filp: pointer to struct file + * @wait: pointer to poll_table + * + * Called by the OS when the kernel is asked to do a poll on + * a SEP file handle. + */ +static unsigned int sep_poll(struct file *filp, poll_table *wait) +{ + struct sep_private_data * const private_data = filp->private_data; + struct sep_call_status *call_status = &private_data->call_status; + struct sep_device *sep = private_data->device; + u32 mask = 0; + u32 retval = 0; + u32 retval2 = 0; + unsigned long lock_irq_flag; + + /* Am I the process that owns the transaction? */ + if (sep_check_transaction_owner(sep)) { + dev_dbg(&sep->pdev->dev, "[PID%d] poll pid not owner\n", + current->pid); + mask = POLLERR; + goto end_function; + } + + /* Check if send command or send_reply were activated previously */ + if (0 == test_bit(SEP_LEGACY_SENDMSG_DONE_OFFSET, + &call_status->status)) { + dev_warn(&sep->pdev->dev, "[PID%d] sendmsg not called\n", + current->pid); + mask = POLLERR; + goto end_function; + } + + + /* Add the event to the polling wait table */ + dev_dbg(&sep->pdev->dev, "[PID%d] poll: calling wait sep_event\n", + current->pid); + + poll_wait(filp, &sep->event_interrupt, wait); + + dev_dbg(&sep->pdev->dev, + "[PID%d] poll: send_ct is %lx reply ct is %lx\n", + current->pid, sep->send_ct, sep->reply_ct); + + /* Check if error occured during poll */ + retval2 = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR3_REG_ADDR); + if ((retval2 != 0x0) && (retval2 != 0x8)) { + dev_dbg(&sep->pdev->dev, "[PID%d] poll; poll error %x\n", + current->pid, retval2); + mask |= POLLERR; + goto end_function; + } + + spin_lock_irqsave(&sep->snd_rply_lck, lock_irq_flag); + + if (sep->send_ct == sep->reply_ct) { + spin_unlock_irqrestore(&sep->snd_rply_lck, lock_irq_flag); + retval = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR2_REG_ADDR); + dev_dbg(&sep->pdev->dev, + "[PID%d] poll: data ready check (GPR2) %x\n", + current->pid, retval); + + /* Check if printf request */ + if ((retval >> 30) & 0x1) { + dev_dbg(&sep->pdev->dev, + "[PID%d] poll: SEP printf request\n", + current->pid); + goto end_function; + } + + /* Check if the this is SEP reply or request */ + if (retval >> 31) { + dev_dbg(&sep->pdev->dev, + "[PID%d] poll: SEP request\n", + current->pid); + } else { + dev_dbg(&sep->pdev->dev, + "[PID%d] poll: normal return\n", + current->pid); + sep_dump_message(sep); + dev_dbg(&sep->pdev->dev, + "[PID%d] poll; SEP reply POLLIN|POLLRDNORM\n", + current->pid); + mask |= POLLIN | POLLRDNORM; + } + set_bit(SEP_LEGACY_POLL_DONE_OFFSET, &call_status->status); + } else { + spin_unlock_irqrestore(&sep->snd_rply_lck, lock_irq_flag); + dev_dbg(&sep->pdev->dev, + "[PID%d] poll; no reply; returning mask of 0\n", + current->pid); + mask = 0; + } + +end_function: + return mask; +} + +/** + * sep_time_address - address in SEP memory of time + * @sep: SEP device we want the address from + * + * Return the address of the two dwords in memory used for time + * setting. + */ +static u32 *sep_time_address(struct sep_device *sep) +{ + return sep->shared_addr + + SEP_DRIVER_SYSTEM_TIME_MEMORY_OFFSET_IN_BYTES; +} + +/** + * sep_set_time - set the SEP time + * @sep: the SEP we are setting the time for + * + * Calculates time and sets it at the predefined address. + * Called with the SEP mutex held. + */ +static unsigned long sep_set_time(struct sep_device *sep) +{ + struct timeval time; + u32 *time_addr; /* Address of time as seen by the kernel */ + + + do_gettimeofday(&time); + + /* Set value in the SYSTEM MEMORY offset */ + time_addr = sep_time_address(sep); + + time_addr[0] = SEP_TIME_VAL_TOKEN; + time_addr[1] = time.tv_sec; + + dev_dbg(&sep->pdev->dev, "[PID%d] time.tv_sec is %lu\n", + current->pid, time.tv_sec); + dev_dbg(&sep->pdev->dev, "[PID%d] time_addr is %p\n", + current->pid, time_addr); + dev_dbg(&sep->pdev->dev, "[PID%d] sep->shared_addr is %p\n", + current->pid, sep->shared_addr); + + return time.tv_sec; +} + +/** + * sep_send_command_handler - kick off a command + * @sep: SEP being signalled + * + * This function raises interrupt to SEP that signals that is has a new + * command from the host + * + * Note that this function does fall under the ioctl lock + */ +int sep_send_command_handler(struct sep_device *sep) +{ + unsigned long lock_irq_flag; + u32 *msg_pool; + int error = 0; + + /* Basic sanity check; set msg pool to start of shared area */ + msg_pool = (u32 *)sep->shared_addr; + msg_pool += 2; + + /* Look for start msg token */ + if (*msg_pool != SEP_START_MSG_TOKEN) { + dev_warn(&sep->pdev->dev, "start message token not present\n"); + error = -EPROTO; + goto end_function; + } + + /* Do we have a reasonable size? */ + msg_pool += 1; + if ((*msg_pool < 2) || + (*msg_pool > SEP_DRIVER_MAX_MESSAGE_SIZE_IN_BYTES)) { + + dev_warn(&sep->pdev->dev, "invalid message size\n"); + error = -EPROTO; + goto end_function; + } + + /* Does the command look reasonable? */ + msg_pool += 1; + if (*msg_pool < 2) { + dev_warn(&sep->pdev->dev, "invalid message opcode\n"); + error = -EPROTO; + goto end_function; + } + +#if defined(CONFIG_PM_RUNTIME) && defined(SEP_ENABLE_RUNTIME_PM) + dev_dbg(&sep->pdev->dev, "[PID%d] before pm sync status 0x%X\n", + current->pid, + sep->pdev->dev.power.runtime_status); + sep->in_use = 1; /* device is about to be used */ + pm_runtime_get_sync(&sep->pdev->dev); +#endif + + if (test_and_set_bit(SEP_WORKING_LOCK_BIT, &sep->in_use_flags)) { + error = -EPROTO; + goto end_function; + } + sep->in_use = 1; /* device is about to be used */ + sep_set_time(sep); + + sep_dump_message(sep); + + /* Update counter */ + spin_lock_irqsave(&sep->snd_rply_lck, lock_irq_flag); + sep->send_ct++; + spin_unlock_irqrestore(&sep->snd_rply_lck, lock_irq_flag); + + dev_dbg(&sep->pdev->dev, + "[PID%d] sep_send_command_handler send_ct %lx reply_ct %lx\n", + current->pid, sep->send_ct, sep->reply_ct); + + /* Send interrupt to SEP */ + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR0_REG_ADDR, 0x2); + +end_function: + return error; +} + +/** + * sep_crypto_dma - + * @sep: pointer to struct sep_device + * @sg: pointer to struct scatterlist + * @direction: + * @dma_maps: pointer to place a pointer to array of dma maps + * This is filled in; anything previous there will be lost + * The structure for dma maps is sep_dma_map + * @returns number of dma maps on success; negative on error + * + * This creates the dma table from the scatterlist + * It is used only for kernel crypto as it works with scatterlists + * representation of data buffers + * + */ +static int sep_crypto_dma( + struct sep_device *sep, + struct scatterlist *sg, + struct sep_dma_map **dma_maps, + enum dma_data_direction direction) +{ + struct scatterlist *temp_sg; + + u32 count_segment; + u32 count_mapped; + struct sep_dma_map *sep_dma; + int ct1; + + if (sg->length == 0) + return 0; + + /* Count the segments */ + temp_sg = sg; + count_segment = 0; + while (temp_sg) { + count_segment += 1; + temp_sg = scatterwalk_sg_next(temp_sg); + } + dev_dbg(&sep->pdev->dev, + "There are (hex) %x segments in sg\n", count_segment); + + /* DMA map segments */ + count_mapped = dma_map_sg(&sep->pdev->dev, sg, + count_segment, direction); + + dev_dbg(&sep->pdev->dev, + "There are (hex) %x maps in sg\n", count_mapped); + + if (count_mapped == 0) { + dev_dbg(&sep->pdev->dev, "Cannot dma_map_sg\n"); + return -ENOMEM; + } + + sep_dma = kmalloc(sizeof(struct sep_dma_map) * + count_mapped, GFP_ATOMIC); + + if (sep_dma == NULL) { + dev_dbg(&sep->pdev->dev, "Cannot allocate dma_maps\n"); + return -ENOMEM; + } + + for_each_sg(sg, temp_sg, count_mapped, ct1) { + sep_dma[ct1].dma_addr = sg_dma_address(temp_sg); + sep_dma[ct1].size = sg_dma_len(temp_sg); + dev_dbg(&sep->pdev->dev, "(all hex) map %x dma %lx len %lx\n", + ct1, (unsigned long)sep_dma[ct1].dma_addr, + (unsigned long)sep_dma[ct1].size); + } + + *dma_maps = sep_dma; + return count_mapped; + +} + +/** + * sep_crypto_lli - + * @sep: pointer to struct sep_device + * @sg: pointer to struct scatterlist + * @data_size: total data size + * @direction: + * @dma_maps: pointer to place a pointer to array of dma maps + * This is filled in; anything previous there will be lost + * The structure for dma maps is sep_dma_map + * @lli_maps: pointer to place a pointer to array of lli maps + * This is filled in; anything previous there will be lost + * The structure for dma maps is sep_dma_map + * @returns number of dma maps on success; negative on error + * + * This creates the LLI table from the scatterlist + * It is only used for kernel crypto as it works exclusively + * with scatterlists (struct scatterlist) representation of + * data buffers + */ +static int sep_crypto_lli( + struct sep_device *sep, + struct scatterlist *sg, + struct sep_dma_map **maps, + struct sep_lli_entry **llis, + u32 data_size, + enum dma_data_direction direction) +{ + + int ct1; + struct sep_lli_entry *sep_lli; + struct sep_dma_map *sep_map; + + int nbr_ents; + + nbr_ents = sep_crypto_dma(sep, sg, maps, direction); + if (nbr_ents <= 0) { + dev_dbg(&sep->pdev->dev, "crypto_dma failed %x\n", + nbr_ents); + return nbr_ents; + } + + sep_map = *maps; + + sep_lli = kmalloc(sizeof(struct sep_lli_entry) * nbr_ents, GFP_ATOMIC); + + if (sep_lli == NULL) { + dev_dbg(&sep->pdev->dev, "Cannot allocate lli_maps\n"); + + kfree(*maps); + *maps = NULL; + return -ENOMEM; + } + + for (ct1 = 0; ct1 < nbr_ents; ct1 += 1) { + sep_lli[ct1].bus_address = (u32)sep_map[ct1].dma_addr; + + /* Maximum for page is total data size */ + if (sep_map[ct1].size > data_size) + sep_map[ct1].size = data_size; + + sep_lli[ct1].block_size = (u32)sep_map[ct1].size; + } + + *llis = sep_lli; + return nbr_ents; +} + +/** + * sep_lock_kernel_pages - map kernel pages for DMA + * @sep: pointer to struct sep_device + * @kernel_virt_addr: address of data buffer in kernel + * @data_size: size of data + * @lli_array_ptr: lli array + * @in_out_flag: input into device or output from device + * + * This function locks all the physical pages of the kernel virtual buffer + * and construct a basic lli array, where each entry holds the physical + * page address and the size that application data holds in this page + * This function is used only during kernel crypto mod calls from within + * the kernel (when ioctl is not used) + * + * This is used only for kernel crypto. Kernel pages + * are handled differently as they are done via + * scatter gather lists (struct scatterlist) + */ +static int sep_lock_kernel_pages(struct sep_device *sep, + unsigned long kernel_virt_addr, + u32 data_size, + struct sep_lli_entry **lli_array_ptr, + int in_out_flag, + struct sep_dma_context *dma_ctx) + +{ + u32 num_pages; + struct scatterlist *sg; + + /* Array of lli */ + struct sep_lli_entry *lli_array; + /* Map array */ + struct sep_dma_map *map_array; + + enum dma_data_direction direction; + + lli_array = NULL; + map_array = NULL; + + if (in_out_flag == SEP_DRIVER_IN_FLAG) { + direction = DMA_TO_DEVICE; + sg = dma_ctx->src_sg; + } else { + direction = DMA_FROM_DEVICE; + sg = dma_ctx->dst_sg; + } + + num_pages = sep_crypto_lli(sep, sg, &map_array, &lli_array, + data_size, direction); + + if (num_pages <= 0) { + dev_dbg(&sep->pdev->dev, "sep_crypto_lli returned error %x\n", + num_pages); + return -ENOMEM; + } + + /* Put mapped kernel sg into kernel resource array */ + + /* Set output params acording to the in_out flag */ + if (in_out_flag == SEP_DRIVER_IN_FLAG) { + *lli_array_ptr = lli_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_num_pages = + num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_page_array = + NULL; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_map_array = + map_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_map_num_entries = + num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].src_sg = + dma_ctx->src_sg; + } else { + *lli_array_ptr = lli_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_num_pages = + num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_page_array = + NULL; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_map_array = + map_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat]. + out_map_num_entries = num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].dst_sg = + dma_ctx->dst_sg; + } + + return 0; +} + +/** + * sep_lock_user_pages - lock and map user pages for DMA + * @sep: pointer to struct sep_device + * @app_virt_addr: user memory data buffer + * @data_size: size of data buffer + * @lli_array_ptr: lli array + * @in_out_flag: input or output to device + * + * This function locks all the physical pages of the application + * virtual buffer and construct a basic lli array, where each entry + * holds the physical page address and the size that application + * data holds in this physical pages + */ +static int sep_lock_user_pages(struct sep_device *sep, + u32 app_virt_addr, + u32 data_size, + struct sep_lli_entry **lli_array_ptr, + int in_out_flag, + struct sep_dma_context *dma_ctx) + +{ + int error = 0; + u32 count; + int result; + /* The the page of the end address of the user space buffer */ + u32 end_page; + /* The page of the start address of the user space buffer */ + u32 start_page; + /* The range in pages */ + u32 num_pages; + /* Array of pointers to page */ + struct page **page_array; + /* Array of lli */ + struct sep_lli_entry *lli_array; + /* Map array */ + struct sep_dma_map *map_array; + + /* Set start and end pages and num pages */ + end_page = (app_virt_addr + data_size - 1) >> PAGE_SHIFT; + start_page = app_virt_addr >> PAGE_SHIFT; + num_pages = end_page - start_page + 1; + + dev_dbg(&sep->pdev->dev, + "[PID%d] lock user pages app_virt_addr is %x\n", + current->pid, app_virt_addr); + + dev_dbg(&sep->pdev->dev, "[PID%d] data_size is (hex) %x\n", + current->pid, data_size); + dev_dbg(&sep->pdev->dev, "[PID%d] start_page is (hex) %x\n", + current->pid, start_page); + dev_dbg(&sep->pdev->dev, "[PID%d] end_page is (hex) %x\n", + current->pid, end_page); + dev_dbg(&sep->pdev->dev, "[PID%d] num_pages is (hex) %x\n", + current->pid, num_pages); + + /* Allocate array of pages structure pointers */ + page_array = kmalloc(sizeof(struct page *) * num_pages, GFP_ATOMIC); + if (!page_array) { + error = -ENOMEM; + goto end_function; + } + map_array = kmalloc(sizeof(struct sep_dma_map) * num_pages, GFP_ATOMIC); + if (!map_array) { + dev_warn(&sep->pdev->dev, + "[PID%d] kmalloc for map_array failed\n", + current->pid); + error = -ENOMEM; + goto end_function_with_error1; + } + + lli_array = kmalloc(sizeof(struct sep_lli_entry) * num_pages, + GFP_ATOMIC); + + if (!lli_array) { + dev_warn(&sep->pdev->dev, + "[PID%d] kmalloc for lli_array failed\n", + current->pid); + error = -ENOMEM; + goto end_function_with_error2; + } + + /* Convert the application virtual address into a set of physical */ + down_read(¤t->mm->mmap_sem); + result = get_user_pages(current, current->mm, app_virt_addr, + num_pages, + ((in_out_flag == SEP_DRIVER_IN_FLAG) ? 0 : 1), + 0, page_array, NULL); + + up_read(¤t->mm->mmap_sem); + + /* Check the number of pages locked - if not all then exit with error */ + if (result != num_pages) { + dev_warn(&sep->pdev->dev, + "[PID%d] not all pages locked by get_user_pages, " + "result 0x%X, num_pages 0x%X\n", + current->pid, result, num_pages); + error = -ENOMEM; + goto end_function_with_error3; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] get_user_pages succeeded\n", + current->pid); + + /* + * Fill the array using page array data and + * map the pages - this action will also flush the cache as needed + */ + for (count = 0; count < num_pages; count++) { + /* Fill the map array */ + map_array[count].dma_addr = + dma_map_page(&sep->pdev->dev, page_array[count], + 0, PAGE_SIZE, DMA_BIDIRECTIONAL); + + map_array[count].size = PAGE_SIZE; + + /* Fill the lli array entry */ + lli_array[count].bus_address = (u32)map_array[count].dma_addr; + lli_array[count].block_size = PAGE_SIZE; + + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_array[%x].bus_address is %08lx, " + "lli_array[%x].block_size is (hex) %x\n", current->pid, + count, (unsigned long)lli_array[count].bus_address, + count, lli_array[count].block_size); + } + + /* Check the offset for the first page */ + lli_array[0].bus_address = + lli_array[0].bus_address + (app_virt_addr & (~PAGE_MASK)); + + /* Check that not all the data is in the first page only */ + if ((PAGE_SIZE - (app_virt_addr & (~PAGE_MASK))) >= data_size) + lli_array[0].block_size = data_size; + else + lli_array[0].block_size = + PAGE_SIZE - (app_virt_addr & (~PAGE_MASK)); + + dev_dbg(&sep->pdev->dev, + "[PID%d] After check if page 0 has all data\n", + current->pid); + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_array[0].bus_address is (hex) %08lx, " + "lli_array[0].block_size is (hex) %x\n", + current->pid, + (unsigned long)lli_array[0].bus_address, + lli_array[0].block_size); + + + /* Check the size of the last page */ + if (num_pages > 1) { + lli_array[num_pages - 1].block_size = + (app_virt_addr + data_size) & (~PAGE_MASK); + if (lli_array[num_pages - 1].block_size == 0) + lli_array[num_pages - 1].block_size = PAGE_SIZE; + + dev_dbg(&sep->pdev->dev, + "[PID%d] After last page size adjustment\n", + current->pid); + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_array[%x].bus_address is (hex) %08lx, " + "lli_array[%x].block_size is (hex) %x\n", + current->pid, + num_pages - 1, + (unsigned long)lli_array[num_pages - 1].bus_address, + num_pages - 1, + lli_array[num_pages - 1].block_size); + } + + /* Set output params acording to the in_out flag */ + if (in_out_flag == SEP_DRIVER_IN_FLAG) { + *lli_array_ptr = lli_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_num_pages = + num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_page_array = + page_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_map_array = + map_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_map_num_entries = + num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].src_sg = NULL; + } else { + *lli_array_ptr = lli_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_num_pages = + num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_page_array = + page_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_map_array = + map_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat]. + out_map_num_entries = num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].dst_sg = NULL; + } + goto end_function; + +end_function_with_error3: + /* Free lli array */ + kfree(lli_array); + +end_function_with_error2: + kfree(map_array); + +end_function_with_error1: + /* Free page array */ + kfree(page_array); + +end_function: + return error; +} + +/** + * sep_lli_table_secure_dma - get lli array for IMR addresses + * @sep: pointer to struct sep_device + * @app_virt_addr: user memory data buffer + * @data_size: size of data buffer + * @lli_array_ptr: lli array + * @in_out_flag: not used + * @dma_ctx: pointer to struct sep_dma_context + * + * This function creates lli tables for outputting data to + * IMR memory, which is memory that cannot be accessed by the + * the x86 processor. + */ +static int sep_lli_table_secure_dma(struct sep_device *sep, + u32 app_virt_addr, + u32 data_size, + struct sep_lli_entry **lli_array_ptr, + int in_out_flag, + struct sep_dma_context *dma_ctx) + +{ + int error = 0; + u32 count; + /* The the page of the end address of the user space buffer */ + u32 end_page; + /* The page of the start address of the user space buffer */ + u32 start_page; + /* The range in pages */ + u32 num_pages; + /* Array of lli */ + struct sep_lli_entry *lli_array; + + /* Set start and end pages and num pages */ + end_page = (app_virt_addr + data_size - 1) >> PAGE_SHIFT; + start_page = app_virt_addr >> PAGE_SHIFT; + num_pages = end_page - start_page + 1; + + dev_dbg(&sep->pdev->dev, "[PID%d] lock user pages" + " app_virt_addr is %x\n", current->pid, app_virt_addr); + + dev_dbg(&sep->pdev->dev, "[PID%d] data_size is (hex) %x\n", + current->pid, data_size); + dev_dbg(&sep->pdev->dev, "[PID%d] start_page is (hex) %x\n", + current->pid, start_page); + dev_dbg(&sep->pdev->dev, "[PID%d] end_page is (hex) %x\n", + current->pid, end_page); + dev_dbg(&sep->pdev->dev, "[PID%d] num_pages is (hex) %x\n", + current->pid, num_pages); + + lli_array = kmalloc(sizeof(struct sep_lli_entry) * num_pages, + GFP_ATOMIC); + + if (!lli_array) { + dev_warn(&sep->pdev->dev, + "[PID%d] kmalloc for lli_array failed\n", + current->pid); + return -ENOMEM; + } + + /* + * Fill the lli_array + */ + start_page = start_page << PAGE_SHIFT; + for (count = 0; count < num_pages; count++) { + /* Fill the lli array entry */ + lli_array[count].bus_address = start_page; + lli_array[count].block_size = PAGE_SIZE; + + start_page += PAGE_SIZE; + + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_array[%x].bus_address is %08lx, " + "lli_array[%x].block_size is (hex) %x\n", + current->pid, + count, (unsigned long)lli_array[count].bus_address, + count, lli_array[count].block_size); + } + + /* Check the offset for the first page */ + lli_array[0].bus_address = + lli_array[0].bus_address + (app_virt_addr & (~PAGE_MASK)); + + /* Check that not all the data is in the first page only */ + if ((PAGE_SIZE - (app_virt_addr & (~PAGE_MASK))) >= data_size) + lli_array[0].block_size = data_size; + else + lli_array[0].block_size = + PAGE_SIZE - (app_virt_addr & (~PAGE_MASK)); + + dev_dbg(&sep->pdev->dev, + "[PID%d] After check if page 0 has all data\n" + "lli_array[0].bus_address is (hex) %08lx, " + "lli_array[0].block_size is (hex) %x\n", + current->pid, + (unsigned long)lli_array[0].bus_address, + lli_array[0].block_size); + + /* Check the size of the last page */ + if (num_pages > 1) { + lli_array[num_pages - 1].block_size = + (app_virt_addr + data_size) & (~PAGE_MASK); + if (lli_array[num_pages - 1].block_size == 0) + lli_array[num_pages - 1].block_size = PAGE_SIZE; + + dev_dbg(&sep->pdev->dev, + "[PID%d] After last page size adjustment\n" + "lli_array[%x].bus_address is (hex) %08lx, " + "lli_array[%x].block_size is (hex) %x\n", + current->pid, num_pages - 1, + (unsigned long)lli_array[num_pages - 1].bus_address, + num_pages - 1, + lli_array[num_pages - 1].block_size); + } + *lli_array_ptr = lli_array; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_num_pages = num_pages; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_page_array = NULL; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_map_array = NULL; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_map_num_entries = 0; + + return error; +} + +/** + * sep_calculate_lli_table_max_size - size the LLI table + * @sep: pointer to struct sep_device + * @lli_in_array_ptr + * @num_array_entries + * @last_table_flag + * + * This function calculates the size of data that can be inserted into + * the lli table from this array, such that either the table is full + * (all entries are entered), or there are no more entries in the + * lli array + */ +static u32 sep_calculate_lli_table_max_size(struct sep_device *sep, + struct sep_lli_entry *lli_in_array_ptr, + u32 num_array_entries, + u32 *last_table_flag) +{ + u32 counter; + /* Table data size */ + u32 table_data_size = 0; + /* Data size for the next table */ + u32 next_table_data_size; + + *last_table_flag = 0; + + /* + * Calculate the data in the out lli table till we fill the whole + * table or till the data has ended + */ + for (counter = 0; + (counter < (SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP - 1)) && + (counter < num_array_entries); counter++) + table_data_size += lli_in_array_ptr[counter].block_size; + + /* + * Check if we reached the last entry, + * meaning this ia the last table to build, + * and no need to check the block alignment + */ + if (counter == num_array_entries) { + /* Set the last table flag */ + *last_table_flag = 1; + goto end_function; + } + + /* + * Calculate the data size of the next table. + * Stop if no entries left or if data size is more the DMA restriction + */ + next_table_data_size = 0; + for (; counter < num_array_entries; counter++) { + next_table_data_size += lli_in_array_ptr[counter].block_size; + if (next_table_data_size >= SEP_DRIVER_MIN_DATA_SIZE_PER_TABLE) + break; + } + + /* + * Check if the next table data size is less then DMA rstriction. + * if it is - recalculate the current table size, so that the next + * table data size will be adaquete for DMA + */ + if (next_table_data_size && + next_table_data_size < SEP_DRIVER_MIN_DATA_SIZE_PER_TABLE) + + table_data_size -= (SEP_DRIVER_MIN_DATA_SIZE_PER_TABLE - + next_table_data_size); + +end_function: + return table_data_size; +} + +/** + * sep_build_lli_table - build an lli array for the given table + * @sep: pointer to struct sep_device + * @lli_array_ptr: pointer to lli array + * @lli_table_ptr: pointer to lli table + * @num_processed_entries_ptr: pointer to number of entries + * @num_table_entries_ptr: pointer to number of tables + * @table_data_size: total data size + * + * Builds ant lli table from the lli_array according to + * the given size of data + */ +static void sep_build_lli_table(struct sep_device *sep, + struct sep_lli_entry *lli_array_ptr, + struct sep_lli_entry *lli_table_ptr, + u32 *num_processed_entries_ptr, + u32 *num_table_entries_ptr, + u32 table_data_size) +{ + /* Current table data size */ + u32 curr_table_data_size; + /* Counter of lli array entry */ + u32 array_counter; + + /* Init current table data size and lli array entry counter */ + curr_table_data_size = 0; + array_counter = 0; + *num_table_entries_ptr = 1; + + dev_dbg(&sep->pdev->dev, + "[PID%d] build lli table table_data_size: (hex) %x\n", + current->pid, table_data_size); + + /* Fill the table till table size reaches the needed amount */ + while (curr_table_data_size < table_data_size) { + /* Update the number of entries in table */ + (*num_table_entries_ptr)++; + + lli_table_ptr->bus_address = + cpu_to_le32(lli_array_ptr[array_counter].bus_address); + + lli_table_ptr->block_size = + cpu_to_le32(lli_array_ptr[array_counter].block_size); + + curr_table_data_size += lli_array_ptr[array_counter].block_size; + + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_table_ptr is %p\n", + current->pid, lli_table_ptr); + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_table_ptr->bus_address: %08lx\n", + current->pid, + (unsigned long)lli_table_ptr->bus_address); + + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_table_ptr->block_size is (hex) %x\n", + current->pid, lli_table_ptr->block_size); + + /* Check for overflow of the table data */ + if (curr_table_data_size > table_data_size) { + dev_dbg(&sep->pdev->dev, + "[PID%d] curr_table_data_size too large\n", + current->pid); + + /* Update the size of block in the table */ + lli_table_ptr->block_size = + cpu_to_le32(lli_table_ptr->block_size) - + (curr_table_data_size - table_data_size); + + /* Update the physical address in the lli array */ + lli_array_ptr[array_counter].bus_address += + cpu_to_le32(lli_table_ptr->block_size); + + /* Update the block size left in the lli array */ + lli_array_ptr[array_counter].block_size = + (curr_table_data_size - table_data_size); + } else + /* Advance to the next entry in the lli_array */ + array_counter++; + + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_table_ptr->bus_address is %08lx\n", + current->pid, + (unsigned long)lli_table_ptr->bus_address); + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_table_ptr->block_size is (hex) %x\n", + current->pid, + lli_table_ptr->block_size); + + /* Move to the next entry in table */ + lli_table_ptr++; + } + + /* Set the info entry to default */ + lli_table_ptr->bus_address = 0xffffffff; + lli_table_ptr->block_size = 0; + + /* Set the output parameter */ + *num_processed_entries_ptr += array_counter; + +} + +/** + * sep_shared_area_virt_to_bus - map shared area to bus address + * @sep: pointer to struct sep_device + * @virt_address: virtual address to convert + * + * This functions returns the physical address inside shared area according + * to the virtual address. It can be either on the externa RAM device + * (ioremapped), or on the system RAM + * This implementation is for the external RAM + */ +static dma_addr_t sep_shared_area_virt_to_bus(struct sep_device *sep, + void *virt_address) +{ + dev_dbg(&sep->pdev->dev, "[PID%d] sh virt to phys v %p\n", + current->pid, virt_address); + dev_dbg(&sep->pdev->dev, "[PID%d] sh virt to phys p %08lx\n", + current->pid, + (unsigned long) + sep->shared_bus + (virt_address - sep->shared_addr)); + + return sep->shared_bus + (size_t)(virt_address - sep->shared_addr); +} + +/** + * sep_shared_area_bus_to_virt - map shared area bus address to kernel + * @sep: pointer to struct sep_device + * @bus_address: bus address to convert + * + * This functions returns the virtual address inside shared area + * according to the physical address. It can be either on the + * externa RAM device (ioremapped), or on the system RAM + * This implementation is for the external RAM + */ +static void *sep_shared_area_bus_to_virt(struct sep_device *sep, + dma_addr_t bus_address) +{ + dev_dbg(&sep->pdev->dev, "[PID%d] shared bus to virt b=%lx v=%lx\n", + current->pid, + (unsigned long)bus_address, (unsigned long)(sep->shared_addr + + (size_t)(bus_address - sep->shared_bus))); + + return sep->shared_addr + (size_t)(bus_address - sep->shared_bus); +} + +/** + * sep_debug_print_lli_tables - dump LLI table + * @sep: pointer to struct sep_device + * @lli_table_ptr: pointer to sep_lli_entry + * @num_table_entries: number of entries + * @table_data_size: total data size + * + * Walk the the list of the print created tables and print all the data + */ +static void sep_debug_print_lli_tables(struct sep_device *sep, + struct sep_lli_entry *lli_table_ptr, + unsigned long num_table_entries, + unsigned long table_data_size) +{ +#ifdef DEBUG + unsigned long table_count = 1; + unsigned long entries_count = 0; + + dev_dbg(&sep->pdev->dev, "[PID%d] sep_debug_print_lli_tables start\n", + current->pid); + if (num_table_entries == 0) { + dev_dbg(&sep->pdev->dev, "[PID%d] no table to print\n", + current->pid); + return; + } + + while ((unsigned long) lli_table_ptr->bus_address != 0xffffffff) { + dev_dbg(&sep->pdev->dev, + "[PID%d] lli table %08lx, " + "table_data_size is (hex) %lx\n", + current->pid, table_count, table_data_size); + dev_dbg(&sep->pdev->dev, + "[PID%d] num_table_entries is (hex) %lx\n", + current->pid, num_table_entries); + + /* Print entries of the table (without info entry) */ + for (entries_count = 0; entries_count < num_table_entries; + entries_count++, lli_table_ptr++) { + + dev_dbg(&sep->pdev->dev, + "[PID%d] lli_table_ptr address is %08lx\n", + current->pid, + (unsigned long) lli_table_ptr); + + dev_dbg(&sep->pdev->dev, + "[PID%d] phys address is %08lx " + "block size is (hex) %x\n", current->pid, + (unsigned long)lli_table_ptr->bus_address, + lli_table_ptr->block_size); + } + + /* Point to the info entry */ + lli_table_ptr--; + + dev_dbg(&sep->pdev->dev, + "[PID%d] phys lli_table_ptr->block_size " + "is (hex) %x\n", + current->pid, + lli_table_ptr->block_size); + + dev_dbg(&sep->pdev->dev, + "[PID%d] phys lli_table_ptr->physical_address " + "is %08lx\n", + current->pid, + (unsigned long)lli_table_ptr->bus_address); + + + table_data_size = lli_table_ptr->block_size & 0xffffff; + num_table_entries = (lli_table_ptr->block_size >> 24) & 0xff; + + dev_dbg(&sep->pdev->dev, + "[PID%d] phys table_data_size is " + "(hex) %lx num_table_entries is" + " %lx bus_address is%lx\n", + current->pid, + table_data_size, + num_table_entries, + (unsigned long)lli_table_ptr->bus_address); + + if ((unsigned long)lli_table_ptr->bus_address != 0xffffffff) + lli_table_ptr = (struct sep_lli_entry *) + sep_shared_bus_to_virt(sep, + (unsigned long)lli_table_ptr->bus_address); + + table_count++; + } + dev_dbg(&sep->pdev->dev, "[PID%d] sep_debug_print_lli_tables end\n", + current->pid); +#endif +} + + +/** + * sep_prepare_empty_lli_table - create a blank LLI table + * @sep: pointer to struct sep_device + * @lli_table_addr_ptr: pointer to lli table + * @num_entries_ptr: pointer to number of entries + * @table_data_size_ptr: point to table data size + * @dmatables_region: Optional buffer for DMA tables + * @dma_ctx: DMA context + * + * This function creates empty lli tables when there is no data + */ +static void sep_prepare_empty_lli_table(struct sep_device *sep, + dma_addr_t *lli_table_addr_ptr, + u32 *num_entries_ptr, + u32 *table_data_size_ptr, + void **dmatables_region, + struct sep_dma_context *dma_ctx) +{ + struct sep_lli_entry *lli_table_ptr; + + /* Find the area for new table */ + lli_table_ptr = + (struct sep_lli_entry *)(sep->shared_addr + + SYNCHRONIC_DMA_TABLES_AREA_OFFSET_BYTES + + dma_ctx->num_lli_tables_created * sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP); + + if (dmatables_region && *dmatables_region) + lli_table_ptr = *dmatables_region; + + lli_table_ptr->bus_address = 0; + lli_table_ptr->block_size = 0; + + lli_table_ptr++; + lli_table_ptr->bus_address = 0xFFFFFFFF; + lli_table_ptr->block_size = 0; + + /* Set the output parameter value */ + *lli_table_addr_ptr = sep->shared_bus + + SYNCHRONIC_DMA_TABLES_AREA_OFFSET_BYTES + + dma_ctx->num_lli_tables_created * + sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + + /* Set the num of entries and table data size for empty table */ + *num_entries_ptr = 2; + *table_data_size_ptr = 0; + + /* Update the number of created tables */ + dma_ctx->num_lli_tables_created++; +} + +/** + * sep_prepare_input_dma_table - prepare input DMA mappings + * @sep: pointer to struct sep_device + * @data_size: + * @block_size: + * @lli_table_ptr: + * @num_entries_ptr: + * @table_data_size_ptr: + * @is_kva: set for kernel data (kernel cryptio call) + * + * This function prepares only input DMA table for synhronic symmetric + * operations (HASH) + * Note that all bus addresses that are passed to the SEP + * are in 32 bit format; the SEP is a 32 bit device + */ +static int sep_prepare_input_dma_table(struct sep_device *sep, + unsigned long app_virt_addr, + u32 data_size, + u32 block_size, + dma_addr_t *lli_table_ptr, + u32 *num_entries_ptr, + u32 *table_data_size_ptr, + bool is_kva, + void **dmatables_region, + struct sep_dma_context *dma_ctx +) +{ + int error = 0; + /* Pointer to the info entry of the table - the last entry */ + struct sep_lli_entry *info_entry_ptr; + /* Array of pointers to page */ + struct sep_lli_entry *lli_array_ptr; + /* Points to the first entry to be processed in the lli_in_array */ + u32 current_entry = 0; + /* Num entries in the virtual buffer */ + u32 sep_lli_entries = 0; + /* Lli table pointer */ + struct sep_lli_entry *in_lli_table_ptr; + /* The total data in one table */ + u32 table_data_size = 0; + /* Flag for last table */ + u32 last_table_flag = 0; + /* Number of entries in lli table */ + u32 num_entries_in_table = 0; + /* Next table address */ + void *lli_table_alloc_addr = NULL; + void *dma_lli_table_alloc_addr = NULL; + void *dma_in_lli_table_ptr = NULL; + + dev_dbg(&sep->pdev->dev, "[PID%d] prepare intput dma " + "tbl data size: (hex) %x\n", + current->pid, data_size); + + dev_dbg(&sep->pdev->dev, "[PID%d] block_size is (hex) %x\n", + current->pid, block_size); + + /* Initialize the pages pointers */ + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_page_array = NULL; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_num_pages = 0; + + /* Set the kernel address for first table to be allocated */ + lli_table_alloc_addr = (void *)(sep->shared_addr + + SYNCHRONIC_DMA_TABLES_AREA_OFFSET_BYTES + + dma_ctx->num_lli_tables_created * sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP); + + if (data_size == 0) { + if (dmatables_region) { + error = sep_allocate_dmatables_region(sep, + dmatables_region, + dma_ctx, + 1); + if (error) + return error; + } + /* Special case - create meptu table - 2 entries, zero data */ + sep_prepare_empty_lli_table(sep, lli_table_ptr, + num_entries_ptr, table_data_size_ptr, + dmatables_region, dma_ctx); + goto update_dcb_counter; + } + + /* Check if the pages are in Kernel Virtual Address layout */ + if (is_kva == true) + error = sep_lock_kernel_pages(sep, app_virt_addr, + data_size, &lli_array_ptr, SEP_DRIVER_IN_FLAG, + dma_ctx); + else + /* + * Lock the pages of the user buffer + * and translate them to pages + */ + error = sep_lock_user_pages(sep, app_virt_addr, + data_size, &lli_array_ptr, SEP_DRIVER_IN_FLAG, + dma_ctx); + + if (error) + goto end_function; + + dev_dbg(&sep->pdev->dev, + "[PID%d] output sep_in_num_pages is (hex) %x\n", + current->pid, + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_num_pages); + + current_entry = 0; + info_entry_ptr = NULL; + + sep_lli_entries = + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_num_pages; + + dma_lli_table_alloc_addr = lli_table_alloc_addr; + if (dmatables_region) { + error = sep_allocate_dmatables_region(sep, + dmatables_region, + dma_ctx, + sep_lli_entries); + if (error) + return error; + lli_table_alloc_addr = *dmatables_region; + } + + /* Loop till all the entries in in array are processed */ + while (current_entry < sep_lli_entries) { + + /* Set the new input and output tables */ + in_lli_table_ptr = + (struct sep_lli_entry *)lli_table_alloc_addr; + dma_in_lli_table_ptr = + (struct sep_lli_entry *)dma_lli_table_alloc_addr; + + lli_table_alloc_addr += sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + dma_lli_table_alloc_addr += sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + + if (dma_lli_table_alloc_addr > + ((void *)sep->shared_addr + + SYNCHRONIC_DMA_TABLES_AREA_OFFSET_BYTES + + SYNCHRONIC_DMA_TABLES_AREA_SIZE_BYTES)) { + + error = -ENOMEM; + goto end_function_error; + + } + + /* Update the number of created tables */ + dma_ctx->num_lli_tables_created++; + + /* Calculate the maximum size of data for input table */ + table_data_size = sep_calculate_lli_table_max_size(sep, + &lli_array_ptr[current_entry], + (sep_lli_entries - current_entry), + &last_table_flag); + + /* + * If this is not the last table - + * then allign it to the block size + */ + if (!last_table_flag) + table_data_size = + (table_data_size / block_size) * block_size; + + dev_dbg(&sep->pdev->dev, + "[PID%d] output table_data_size is (hex) %x\n", + current->pid, + table_data_size); + + /* Construct input lli table */ + sep_build_lli_table(sep, &lli_array_ptr[current_entry], + in_lli_table_ptr, + ¤t_entry, &num_entries_in_table, table_data_size); + + if (info_entry_ptr == NULL) { + + /* Set the output parameters to physical addresses */ + *lli_table_ptr = sep_shared_area_virt_to_bus(sep, + dma_in_lli_table_ptr); + *num_entries_ptr = num_entries_in_table; + *table_data_size_ptr = table_data_size; + + dev_dbg(&sep->pdev->dev, + "[PID%d] output lli_table_in_ptr is %08lx\n", + current->pid, + (unsigned long)*lli_table_ptr); + + } else { + /* Update the info entry of the previous in table */ + info_entry_ptr->bus_address = + sep_shared_area_virt_to_bus(sep, + dma_in_lli_table_ptr); + info_entry_ptr->block_size = + ((num_entries_in_table) << 24) | + (table_data_size); + } + /* Save the pointer to the info entry of the current tables */ + info_entry_ptr = in_lli_table_ptr + num_entries_in_table - 1; + } + /* Print input tables */ + if (!dmatables_region) { + sep_debug_print_lli_tables(sep, (struct sep_lli_entry *) + sep_shared_area_bus_to_virt(sep, *lli_table_ptr), + *num_entries_ptr, *table_data_size_ptr); + } + + /* The array of the pages */ + kfree(lli_array_ptr); + +update_dcb_counter: + /* Update DCB counter */ + dma_ctx->nr_dcb_creat++; + goto end_function; + +end_function_error: + /* Free all the allocated resources */ + kfree(dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_map_array); + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_map_array = NULL; + kfree(lli_array_ptr); + kfree(dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_page_array); + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_page_array = NULL; + +end_function: + return error; + +} + +/** + * sep_construct_dma_tables_from_lli - prepare AES/DES mappings + * @sep: pointer to struct sep_device + * @lli_in_array: + * @sep_in_lli_entries: + * @lli_out_array: + * @sep_out_lli_entries + * @block_size + * @lli_table_in_ptr + * @lli_table_out_ptr + * @in_num_entries_ptr + * @out_num_entries_ptr + * @table_data_size_ptr + * + * This function creates the input and output DMA tables for + * symmetric operations (AES/DES) according to the block + * size from LLI arays + * Note that all bus addresses that are passed to the SEP + * are in 32 bit format; the SEP is a 32 bit device + */ +static int sep_construct_dma_tables_from_lli( + struct sep_device *sep, + struct sep_lli_entry *lli_in_array, + u32 sep_in_lli_entries, + struct sep_lli_entry *lli_out_array, + u32 sep_out_lli_entries, + u32 block_size, + dma_addr_t *lli_table_in_ptr, + dma_addr_t *lli_table_out_ptr, + u32 *in_num_entries_ptr, + u32 *out_num_entries_ptr, + u32 *table_data_size_ptr, + void **dmatables_region, + struct sep_dma_context *dma_ctx) +{ + /* Points to the area where next lli table can be allocated */ + void *lli_table_alloc_addr = NULL; + /* + * Points to the area in shared region where next lli table + * can be allocated + */ + void *dma_lli_table_alloc_addr = NULL; + /* Input lli table in dmatables_region or shared region */ + struct sep_lli_entry *in_lli_table_ptr = NULL; + /* Input lli table location in the shared region */ + struct sep_lli_entry *dma_in_lli_table_ptr = NULL; + /* Output lli table in dmatables_region or shared region */ + struct sep_lli_entry *out_lli_table_ptr = NULL; + /* Output lli table location in the shared region */ + struct sep_lli_entry *dma_out_lli_table_ptr = NULL; + /* Pointer to the info entry of the table - the last entry */ + struct sep_lli_entry *info_in_entry_ptr = NULL; + /* Pointer to the info entry of the table - the last entry */ + struct sep_lli_entry *info_out_entry_ptr = NULL; + /* Points to the first entry to be processed in the lli_in_array */ + u32 current_in_entry = 0; + /* Points to the first entry to be processed in the lli_out_array */ + u32 current_out_entry = 0; + /* Max size of the input table */ + u32 in_table_data_size = 0; + /* Max size of the output table */ + u32 out_table_data_size = 0; + /* Flag te signifies if this is the last tables build */ + u32 last_table_flag = 0; + /* The data size that should be in table */ + u32 table_data_size = 0; + /* Number of etnries in the input table */ + u32 num_entries_in_table = 0; + /* Number of etnries in the output table */ + u32 num_entries_out_table = 0; + + if (!dma_ctx) { + dev_warn(&sep->pdev->dev, "DMA context uninitialized\n"); + return -EINVAL; + } + + /* Initiate to point after the message area */ + lli_table_alloc_addr = (void *)(sep->shared_addr + + SYNCHRONIC_DMA_TABLES_AREA_OFFSET_BYTES + + (dma_ctx->num_lli_tables_created * + (sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP))); + dma_lli_table_alloc_addr = lli_table_alloc_addr; + + if (dmatables_region) { + /* 2 for both in+out table */ + if (sep_allocate_dmatables_region(sep, + dmatables_region, + dma_ctx, + 2*sep_in_lli_entries)) + return -ENOMEM; + lli_table_alloc_addr = *dmatables_region; + } + + /* Loop till all the entries in in array are not processed */ + while (current_in_entry < sep_in_lli_entries) { + /* Set the new input and output tables */ + in_lli_table_ptr = + (struct sep_lli_entry *)lli_table_alloc_addr; + dma_in_lli_table_ptr = + (struct sep_lli_entry *)dma_lli_table_alloc_addr; + + lli_table_alloc_addr += sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + dma_lli_table_alloc_addr += sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + + /* Set the first output tables */ + out_lli_table_ptr = + (struct sep_lli_entry *)lli_table_alloc_addr; + dma_out_lli_table_ptr = + (struct sep_lli_entry *)dma_lli_table_alloc_addr; + + /* Check if the DMA table area limit was overrun */ + if ((dma_lli_table_alloc_addr + sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP) > + ((void *)sep->shared_addr + + SYNCHRONIC_DMA_TABLES_AREA_OFFSET_BYTES + + SYNCHRONIC_DMA_TABLES_AREA_SIZE_BYTES)) { + + dev_warn(&sep->pdev->dev, "dma table limit overrun\n"); + return -ENOMEM; + } + + /* Update the number of the lli tables created */ + dma_ctx->num_lli_tables_created += 2; + + lli_table_alloc_addr += sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + dma_lli_table_alloc_addr += sizeof(struct sep_lli_entry) * + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + + /* Calculate the maximum size of data for input table */ + in_table_data_size = + sep_calculate_lli_table_max_size(sep, + &lli_in_array[current_in_entry], + (sep_in_lli_entries - current_in_entry), + &last_table_flag); + + /* Calculate the maximum size of data for output table */ + out_table_data_size = + sep_calculate_lli_table_max_size(sep, + &lli_out_array[current_out_entry], + (sep_out_lli_entries - current_out_entry), + &last_table_flag); + + if (!last_table_flag) { + in_table_data_size = (in_table_data_size / + block_size) * block_size; + out_table_data_size = (out_table_data_size / + block_size) * block_size; + } + + table_data_size = in_table_data_size; + if (table_data_size > out_table_data_size) + table_data_size = out_table_data_size; + + dev_dbg(&sep->pdev->dev, + "[PID%d] construct tables from lli" + " in_table_data_size is (hex) %x\n", current->pid, + in_table_data_size); + + dev_dbg(&sep->pdev->dev, + "[PID%d] construct tables from lli" + "out_table_data_size is (hex) %x\n", current->pid, + out_table_data_size); + + /* Construct input lli table */ + sep_build_lli_table(sep, &lli_in_array[current_in_entry], + in_lli_table_ptr, + ¤t_in_entry, + &num_entries_in_table, + table_data_size); + + /* Construct output lli table */ + sep_build_lli_table(sep, &lli_out_array[current_out_entry], + out_lli_table_ptr, + ¤t_out_entry, + &num_entries_out_table, + table_data_size); + + /* If info entry is null - this is the first table built */ + if (info_in_entry_ptr == NULL) { + /* Set the output parameters to physical addresses */ + *lli_table_in_ptr = + sep_shared_area_virt_to_bus(sep, dma_in_lli_table_ptr); + + *in_num_entries_ptr = num_entries_in_table; + + *lli_table_out_ptr = + sep_shared_area_virt_to_bus(sep, + dma_out_lli_table_ptr); + + *out_num_entries_ptr = num_entries_out_table; + *table_data_size_ptr = table_data_size; + + dev_dbg(&sep->pdev->dev, + "[PID%d] output lli_table_in_ptr is %08lx\n", + current->pid, + (unsigned long)*lli_table_in_ptr); + dev_dbg(&sep->pdev->dev, + "[PID%d] output lli_table_out_ptr is %08lx\n", + current->pid, + (unsigned long)*lli_table_out_ptr); + } else { + /* Update the info entry of the previous in table */ + info_in_entry_ptr->bus_address = + sep_shared_area_virt_to_bus(sep, + dma_in_lli_table_ptr); + + info_in_entry_ptr->block_size = + ((num_entries_in_table) << 24) | + (table_data_size); + + /* Update the info entry of the previous in table */ + info_out_entry_ptr->bus_address = + sep_shared_area_virt_to_bus(sep, + dma_out_lli_table_ptr); + + info_out_entry_ptr->block_size = + ((num_entries_out_table) << 24) | + (table_data_size); + + dev_dbg(&sep->pdev->dev, + "[PID%d] output lli_table_in_ptr:%08lx %08x\n", + current->pid, + (unsigned long)info_in_entry_ptr->bus_address, + info_in_entry_ptr->block_size); + + dev_dbg(&sep->pdev->dev, + "[PID%d] output lli_table_out_ptr:" + "%08lx %08x\n", + current->pid, + (unsigned long)info_out_entry_ptr->bus_address, + info_out_entry_ptr->block_size); + } + + /* Save the pointer to the info entry of the current tables */ + info_in_entry_ptr = in_lli_table_ptr + + num_entries_in_table - 1; + info_out_entry_ptr = out_lli_table_ptr + + num_entries_out_table - 1; + + dev_dbg(&sep->pdev->dev, + "[PID%d] output num_entries_out_table is %x\n", + current->pid, + (u32)num_entries_out_table); + dev_dbg(&sep->pdev->dev, + "[PID%d] output info_in_entry_ptr is %lx\n", + current->pid, + (unsigned long)info_in_entry_ptr); + dev_dbg(&sep->pdev->dev, + "[PID%d] output info_out_entry_ptr is %lx\n", + current->pid, + (unsigned long)info_out_entry_ptr); + } + + /* Print input tables */ + if (!dmatables_region) { + sep_debug_print_lli_tables( + sep, + (struct sep_lli_entry *) + sep_shared_area_bus_to_virt(sep, *lli_table_in_ptr), + *in_num_entries_ptr, + *table_data_size_ptr); + } + + /* Print output tables */ + if (!dmatables_region) { + sep_debug_print_lli_tables( + sep, + (struct sep_lli_entry *) + sep_shared_area_bus_to_virt(sep, *lli_table_out_ptr), + *out_num_entries_ptr, + *table_data_size_ptr); + } + + return 0; +} + +/** + * sep_prepare_input_output_dma_table - prepare DMA I/O table + * @app_virt_in_addr: + * @app_virt_out_addr: + * @data_size: + * @block_size: + * @lli_table_in_ptr: + * @lli_table_out_ptr: + * @in_num_entries_ptr: + * @out_num_entries_ptr: + * @table_data_size_ptr: + * @is_kva: set for kernel data; used only for kernel crypto module + * + * This function builds input and output DMA tables for synhronic + * symmetric operations (AES, DES, HASH). It also checks that each table + * is of the modular block size + * Note that all bus addresses that are passed to the SEP + * are in 32 bit format; the SEP is a 32 bit device + */ +static int sep_prepare_input_output_dma_table(struct sep_device *sep, + unsigned long app_virt_in_addr, + unsigned long app_virt_out_addr, + u32 data_size, + u32 block_size, + dma_addr_t *lli_table_in_ptr, + dma_addr_t *lli_table_out_ptr, + u32 *in_num_entries_ptr, + u32 *out_num_entries_ptr, + u32 *table_data_size_ptr, + bool is_kva, + void **dmatables_region, + struct sep_dma_context *dma_ctx) + +{ + int error = 0; + /* Array of pointers of page */ + struct sep_lli_entry *lli_in_array; + /* Array of pointers of page */ + struct sep_lli_entry *lli_out_array; + + if (!dma_ctx) { + error = -EINVAL; + goto end_function; + } + + if (data_size == 0) { + /* Prepare empty table for input and output */ + if (dmatables_region) { + error = sep_allocate_dmatables_region( + sep, + dmatables_region, + dma_ctx, + 2); + if (error) + goto end_function; + } + sep_prepare_empty_lli_table(sep, lli_table_in_ptr, + in_num_entries_ptr, table_data_size_ptr, + dmatables_region, dma_ctx); + + sep_prepare_empty_lli_table(sep, lli_table_out_ptr, + out_num_entries_ptr, table_data_size_ptr, + dmatables_region, dma_ctx); + + goto update_dcb_counter; + } + + /* Initialize the pages pointers */ + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_page_array = NULL; + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_page_array = NULL; + + /* Lock the pages of the buffer and translate them to pages */ + if (is_kva == true) { + dev_dbg(&sep->pdev->dev, "[PID%d] Locking kernel input pages\n", + current->pid); + error = sep_lock_kernel_pages(sep, app_virt_in_addr, + data_size, &lli_in_array, SEP_DRIVER_IN_FLAG, + dma_ctx); + if (error) { + dev_warn(&sep->pdev->dev, + "[PID%d] sep_lock_kernel_pages for input " + "virtual buffer failed\n", current->pid); + + goto end_function; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] Locking kernel output pages\n", + current->pid); + error = sep_lock_kernel_pages(sep, app_virt_out_addr, + data_size, &lli_out_array, SEP_DRIVER_OUT_FLAG, + dma_ctx); + + if (error) { + dev_warn(&sep->pdev->dev, + "[PID%d] sep_lock_kernel_pages for output " + "virtual buffer failed\n", current->pid); + + goto end_function_free_lli_in; + } + + } + + else { + dev_dbg(&sep->pdev->dev, "[PID%d] Locking user input pages\n", + current->pid); + error = sep_lock_user_pages(sep, app_virt_in_addr, + data_size, &lli_in_array, SEP_DRIVER_IN_FLAG, + dma_ctx); + if (error) { + dev_warn(&sep->pdev->dev, + "[PID%d] sep_lock_user_pages for input " + "virtual buffer failed\n", current->pid); + + goto end_function; + } + + if (dma_ctx->secure_dma == true) { + /* secure_dma requires use of non accessible memory */ + dev_dbg(&sep->pdev->dev, "[PID%d] in secure_dma\n", + current->pid); + error = sep_lli_table_secure_dma(sep, + app_virt_out_addr, data_size, &lli_out_array, + SEP_DRIVER_OUT_FLAG, dma_ctx); + if (error) { + dev_warn(&sep->pdev->dev, + "[PID%d] secure dma table setup " + " for output virtual buffer failed\n", + current->pid); + + goto end_function_free_lli_in; + } + } else { + /* For normal, non-secure dma */ + dev_dbg(&sep->pdev->dev, "[PID%d] not in secure_dma\n", + current->pid); + + dev_dbg(&sep->pdev->dev, + "[PID%d] Locking user output pages\n", + current->pid); + + error = sep_lock_user_pages(sep, app_virt_out_addr, + data_size, &lli_out_array, SEP_DRIVER_OUT_FLAG, + dma_ctx); + + if (error) { + dev_warn(&sep->pdev->dev, + "[PID%d] sep_lock_user_pages" + " for output virtual buffer failed\n", + current->pid); + + goto end_function_free_lli_in; + } + } + } + + dev_dbg(&sep->pdev->dev, "[PID%d] After lock; prep input output dma " + "table sep_in_num_pages is (hex) %x\n", current->pid, + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_num_pages); + + dev_dbg(&sep->pdev->dev, "[PID%d] sep_out_num_pages is (hex) %x\n", + current->pid, + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_num_pages); + + dev_dbg(&sep->pdev->dev, "[PID%d] SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP" + " is (hex) %x\n", current->pid, + SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP); + + /* Call the fucntion that creates table from the lli arrays */ + dev_dbg(&sep->pdev->dev, "[PID%d] calling create table from lli\n", + current->pid); + error = sep_construct_dma_tables_from_lli( + sep, lli_in_array, + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat]. + in_num_pages, + lli_out_array, + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat]. + out_num_pages, + block_size, lli_table_in_ptr, lli_table_out_ptr, + in_num_entries_ptr, out_num_entries_ptr, + table_data_size_ptr, dmatables_region, dma_ctx); + + if (error) { + dev_warn(&sep->pdev->dev, + "[PID%d] sep_construct_dma_tables_from_lli failed\n", + current->pid); + goto end_function_with_error; + } + + kfree(lli_out_array); + kfree(lli_in_array); + +update_dcb_counter: + /* Update DCB counter */ + dma_ctx->nr_dcb_creat++; + + goto end_function; + +end_function_with_error: + kfree(dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_map_array); + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_map_array = NULL; + kfree(dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_page_array); + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].out_page_array = NULL; + kfree(lli_out_array); + + +end_function_free_lli_in: + kfree(dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_map_array); + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_map_array = NULL; + kfree(dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_page_array); + dma_ctx->dma_res_arr[dma_ctx->nr_dcb_creat].in_page_array = NULL; + kfree(lli_in_array); + +end_function: + + return error; + +} + +/** + * sep_prepare_input_output_dma_table_in_dcb - prepare control blocks + * @app_in_address: unsigned long; for data buffer in (user space) + * @app_out_address: unsigned long; for data buffer out (user space) + * @data_in_size: u32; for size of data + * @block_size: u32; for block size + * @tail_block_size: u32; for size of tail block + * @isapplet: bool; to indicate external app + * @is_kva: bool; kernel buffer; only used for kernel crypto module + * @secure_dma; indicates whether this is secure_dma using IMR + * + * This function prepares the linked DMA tables and puts the + * address for the linked list of tables inta a DCB (data control + * block) the address of which is known by the SEP hardware + * Note that all bus addresses that are passed to the SEP + * are in 32 bit format; the SEP is a 32 bit device + */ +int sep_prepare_input_output_dma_table_in_dcb(struct sep_device *sep, + unsigned long app_in_address, + unsigned long app_out_address, + u32 data_in_size, + u32 block_size, + u32 tail_block_size, + bool isapplet, + bool is_kva, + bool secure_dma, + struct sep_dcblock *dcb_region, + void **dmatables_region, + struct sep_dma_context **dma_ctx, + struct scatterlist *src_sg, + struct scatterlist *dst_sg) +{ + int error = 0; + /* Size of tail */ + u32 tail_size = 0; + /* Address of the created DCB table */ + struct sep_dcblock *dcb_table_ptr = NULL; + /* The physical address of the first input DMA table */ + dma_addr_t in_first_mlli_address = 0; + /* Number of entries in the first input DMA table */ + u32 in_first_num_entries = 0; + /* The physical address of the first output DMA table */ + dma_addr_t out_first_mlli_address = 0; + /* Number of entries in the first output DMA table */ + u32 out_first_num_entries = 0; + /* Data in the first input/output table */ + u32 first_data_size = 0; + + dev_dbg(&sep->pdev->dev, "[PID%d] app_in_address %lx\n", + current->pid, app_in_address); + + dev_dbg(&sep->pdev->dev, "[PID%d] app_out_address %lx\n", + current->pid, app_out_address); + + dev_dbg(&sep->pdev->dev, "[PID%d] data_in_size %x\n", + current->pid, data_in_size); + + dev_dbg(&sep->pdev->dev, "[PID%d] block_size %x\n", + current->pid, block_size); + + dev_dbg(&sep->pdev->dev, "[PID%d] tail_block_size %x\n", + current->pid, tail_block_size); + + dev_dbg(&sep->pdev->dev, "[PID%d] isapplet %x\n", + current->pid, isapplet); + + dev_dbg(&sep->pdev->dev, "[PID%d] is_kva %x\n", + current->pid, is_kva); + + dev_dbg(&sep->pdev->dev, "[PID%d] src_sg %p\n", + current->pid, src_sg); + + dev_dbg(&sep->pdev->dev, "[PID%d] dst_sg %p\n", + current->pid, dst_sg); + + if (!dma_ctx) { + dev_warn(&sep->pdev->dev, "[PID%d] no DMA context pointer\n", + current->pid); + error = -EINVAL; + goto end_function; + } + + if (*dma_ctx) { + /* In case there are multiple DCBs for this transaction */ + dev_dbg(&sep->pdev->dev, "[PID%d] DMA context already set\n", + current->pid); + } else { + *dma_ctx = kzalloc(sizeof(**dma_ctx), GFP_KERNEL); + if (!(*dma_ctx)) { + dev_dbg(&sep->pdev->dev, + "[PID%d] Not enough memory for DMA context\n", + current->pid); + error = -ENOMEM; + goto end_function; + } + dev_dbg(&sep->pdev->dev, + "[PID%d] Created DMA context addr at 0x%p\n", + current->pid, *dma_ctx); + } + + (*dma_ctx)->secure_dma = secure_dma; + + /* these are for kernel crypto only */ + (*dma_ctx)->src_sg = src_sg; + (*dma_ctx)->dst_sg = dst_sg; + + if ((*dma_ctx)->nr_dcb_creat == SEP_MAX_NUM_SYNC_DMA_OPS) { + /* No more DCBs to allocate */ + dev_dbg(&sep->pdev->dev, "[PID%d] no more DCBs available\n", + current->pid); + error = -ENOSPC; + goto end_function_error; + } + + /* Allocate new DCB */ + if (dcb_region) { + dcb_table_ptr = dcb_region; + } else { + dcb_table_ptr = (struct sep_dcblock *)(sep->shared_addr + + SEP_DRIVER_SYSTEM_DCB_MEMORY_OFFSET_IN_BYTES + + ((*dma_ctx)->nr_dcb_creat * + sizeof(struct sep_dcblock))); + } + + /* Set the default values in the DCB */ + dcb_table_ptr->input_mlli_address = 0; + dcb_table_ptr->input_mlli_num_entries = 0; + dcb_table_ptr->input_mlli_data_size = 0; + dcb_table_ptr->output_mlli_address = 0; + dcb_table_ptr->output_mlli_num_entries = 0; + dcb_table_ptr->output_mlli_data_size = 0; + dcb_table_ptr->tail_data_size = 0; + dcb_table_ptr->out_vr_tail_pt = 0; + + if (isapplet == true) { + + /* Check if there is enough data for DMA operation */ + if (data_in_size < SEP_DRIVER_MIN_DATA_SIZE_PER_TABLE) { + if (is_kva == true) { + error = -ENODEV; + goto end_function_error; + } else { + if (copy_from_user(dcb_table_ptr->tail_data, + (void __user *)app_in_address, + data_in_size)) { + error = -EFAULT; + goto end_function_error; + } + } + + dcb_table_ptr->tail_data_size = data_in_size; + + /* Set the output user-space address for mem2mem op */ + if (app_out_address) + dcb_table_ptr->out_vr_tail_pt = + (aligned_u64)app_out_address; + + /* + * Update both data length parameters in order to avoid + * second data copy and allow building of empty mlli + * tables + */ + tail_size = 0x0; + data_in_size = 0x0; + + } else { + if (!app_out_address) { + tail_size = data_in_size % block_size; + if (!tail_size) { + if (tail_block_size == block_size) + tail_size = block_size; + } + } else { + tail_size = 0; + } + } + if (tail_size) { + if (tail_size > sizeof(dcb_table_ptr->tail_data)) + return -EINVAL; + if (is_kva == true) { + error = -ENODEV; + goto end_function_error; + } else { + /* We have tail data - copy it to DCB */ + if (copy_from_user(dcb_table_ptr->tail_data, + (void __user *)(app_in_address + + data_in_size - tail_size), tail_size)) { + error = -EFAULT; + goto end_function_error; + } + } + if (app_out_address) + /* + * Calculate the output address + * according to tail data size + */ + dcb_table_ptr->out_vr_tail_pt = + (aligned_u64)app_out_address + + data_in_size - tail_size; + + /* Save the real tail data size */ + dcb_table_ptr->tail_data_size = tail_size; + /* + * Update the data size without the tail + * data size AKA data for the dma + */ + data_in_size = (data_in_size - tail_size); + } + } + /* Check if we need to build only input table or input/output */ + if (app_out_address) { + /* Prepare input/output tables */ + error = sep_prepare_input_output_dma_table(sep, + app_in_address, + app_out_address, + data_in_size, + block_size, + &in_first_mlli_address, + &out_first_mlli_address, + &in_first_num_entries, + &out_first_num_entries, + &first_data_size, + is_kva, + dmatables_region, + *dma_ctx); + } else { + /* Prepare input tables */ + error = sep_prepare_input_dma_table(sep, + app_in_address, + data_in_size, + block_size, + &in_first_mlli_address, + &in_first_num_entries, + &first_data_size, + is_kva, + dmatables_region, + *dma_ctx); + } + + if (error) { + dev_warn(&sep->pdev->dev, + "prepare DMA table call failed " + "from prepare DCB call\n"); + goto end_function_error; + } + + /* Set the DCB values */ + dcb_table_ptr->input_mlli_address = in_first_mlli_address; + dcb_table_ptr->input_mlli_num_entries = in_first_num_entries; + dcb_table_ptr->input_mlli_data_size = first_data_size; + dcb_table_ptr->output_mlli_address = out_first_mlli_address; + dcb_table_ptr->output_mlli_num_entries = out_first_num_entries; + dcb_table_ptr->output_mlli_data_size = first_data_size; + + goto end_function; + +end_function_error: + kfree(*dma_ctx); + *dma_ctx = NULL; + +end_function: + return error; + +} + + +/** + * sep_free_dma_tables_and_dcb - free DMA tables and DCBs + * @sep: pointer to struct sep_device + * @isapplet: indicates external application (used for kernel access) + * @is_kva: indicates kernel addresses (only used for kernel crypto) + * + * This function frees the DMA tables and DCB + */ +static int sep_free_dma_tables_and_dcb(struct sep_device *sep, bool isapplet, + bool is_kva, struct sep_dma_context **dma_ctx) +{ + struct sep_dcblock *dcb_table_ptr; + unsigned long pt_hold; + void *tail_pt; + + int i = 0; + int error = 0; + int error_temp = 0; + + dev_dbg(&sep->pdev->dev, "[PID%d] sep_free_dma_tables_and_dcb\n", + current->pid); + + if (((*dma_ctx)->secure_dma == false) && (isapplet == true)) { + dev_dbg(&sep->pdev->dev, "[PID%d] handling applet\n", + current->pid); + + /* Tail stuff is only for non secure_dma */ + /* Set pointer to first DCB table */ + dcb_table_ptr = (struct sep_dcblock *) + (sep->shared_addr + + SEP_DRIVER_SYSTEM_DCB_MEMORY_OFFSET_IN_BYTES); + + /** + * Go over each DCB and see if + * tail pointer must be updated + */ + for (i = 0; dma_ctx && *dma_ctx && + i < (*dma_ctx)->nr_dcb_creat; i++, dcb_table_ptr++) { + if (dcb_table_ptr->out_vr_tail_pt) { + pt_hold = (unsigned long)dcb_table_ptr-> + out_vr_tail_pt; + tail_pt = (void *)pt_hold; + if (is_kva == true) { + error = -ENODEV; + break; + } else { + error_temp = copy_to_user( + (void __user *)tail_pt, + dcb_table_ptr->tail_data, + dcb_table_ptr->tail_data_size); + } + if (error_temp) { + /* Release the DMA resource */ + error = -EFAULT; + break; + } + } + } + } + + /* Free the output pages, if any */ + sep_free_dma_table_data_handler(sep, dma_ctx); + + dev_dbg(&sep->pdev->dev, "[PID%d] sep_free_dma_tables_and_dcb end\n", + current->pid); + + return error; +} + +/** + * sep_prepare_dcb_handler - prepare a control block + * @sep: pointer to struct sep_device + * @arg: pointer to user parameters + * @secure_dma: indicate whether we are using secure_dma on IMR + * + * This function will retrieve the RAR buffer physical addresses, type + * & size corresponding to the RAR handles provided in the buffers vector. + */ +static int sep_prepare_dcb_handler(struct sep_device *sep, unsigned long arg, + bool secure_dma, + struct sep_dma_context **dma_ctx) +{ + int error; + /* Command arguments */ + static struct build_dcb_struct command_args; + + /* Get the command arguments */ + if (copy_from_user(&command_args, (void __user *)arg, + sizeof(struct build_dcb_struct))) { + error = -EFAULT; + goto end_function; + } + + dev_dbg(&sep->pdev->dev, + "[PID%d] prep dcb handler app_in_address is %08llx\n", + current->pid, command_args.app_in_address); + dev_dbg(&sep->pdev->dev, + "[PID%d] app_out_address is %08llx\n", + current->pid, command_args.app_out_address); + dev_dbg(&sep->pdev->dev, + "[PID%d] data_size is %x\n", + current->pid, command_args.data_in_size); + dev_dbg(&sep->pdev->dev, + "[PID%d] block_size is %x\n", + current->pid, command_args.block_size); + dev_dbg(&sep->pdev->dev, + "[PID%d] tail block_size is %x\n", + current->pid, command_args.tail_block_size); + dev_dbg(&sep->pdev->dev, + "[PID%d] is_applet is %x\n", + current->pid, command_args.is_applet); + + if (!command_args.app_in_address) { + dev_warn(&sep->pdev->dev, + "[PID%d] null app_in_address\n", current->pid); + error = -EINVAL; + goto end_function; + } + + error = sep_prepare_input_output_dma_table_in_dcb(sep, + (unsigned long)command_args.app_in_address, + (unsigned long)command_args.app_out_address, + command_args.data_in_size, command_args.block_size, + command_args.tail_block_size, + command_args.is_applet, false, + secure_dma, NULL, NULL, dma_ctx, NULL, NULL); + +end_function: + return error; + +} + +/** + * sep_free_dcb_handler - free control block resources + * @sep: pointer to struct sep_device + * + * This function frees the DCB resources and updates the needed + * user-space buffers. + */ +static int sep_free_dcb_handler(struct sep_device *sep, + struct sep_dma_context **dma_ctx) +{ + if (!dma_ctx || !(*dma_ctx)) { + dev_dbg(&sep->pdev->dev, + "[PID%d] no dma context defined, nothing to free\n", + current->pid); + return -EINVAL; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] free dcbs num of DCBs %x\n", + current->pid, + (*dma_ctx)->nr_dcb_creat); + + return sep_free_dma_tables_and_dcb(sep, false, false, dma_ctx); +} + +/** + * sep_ioctl - ioctl handler for sep device + * @filp: pointer to struct file + * @cmd: command + * @arg: pointer to argument structure + * + * Implement the ioctl methods availble on the SEP device. + */ +static long sep_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct sep_private_data * const private_data = filp->private_data; + struct sep_call_status *call_status = &private_data->call_status; + struct sep_device *sep = private_data->device; + struct sep_dma_context **dma_ctx = &private_data->dma_ctx; + struct sep_queue_info **my_queue_elem = &private_data->my_queue_elem; + int error = 0; + + dev_dbg(&sep->pdev->dev, "[PID%d] ioctl cmd 0x%x\n", + current->pid, cmd); + dev_dbg(&sep->pdev->dev, "[PID%d] dma context addr 0x%p\n", + current->pid, *dma_ctx); + + /* Make sure we own this device */ + error = sep_check_transaction_owner(sep); + if (error) { + dev_dbg(&sep->pdev->dev, "[PID%d] ioctl pid is not owner\n", + current->pid); + goto end_function; + } + + /* Check that sep_mmap has been called before */ + if (0 == test_bit(SEP_LEGACY_MMAP_DONE_OFFSET, + &call_status->status)) { + dev_dbg(&sep->pdev->dev, + "[PID%d] mmap not called\n", current->pid); + error = -EPROTO; + goto end_function; + } + + /* Check that the command is for SEP device */ + if (_IOC_TYPE(cmd) != SEP_IOC_MAGIC_NUMBER) { + error = -ENOTTY; + goto end_function; + } + + switch (cmd) { + case SEP_IOCSENDSEPCOMMAND: + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOCSENDSEPCOMMAND start\n", + current->pid); + if (1 == test_bit(SEP_LEGACY_SENDMSG_DONE_OFFSET, + &call_status->status)) { + dev_warn(&sep->pdev->dev, + "[PID%d] send msg already done\n", + current->pid); + error = -EPROTO; + goto end_function; + } + /* Send command to SEP */ + error = sep_send_command_handler(sep); + if (!error) + set_bit(SEP_LEGACY_SENDMSG_DONE_OFFSET, + &call_status->status); + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOCSENDSEPCOMMAND end\n", + current->pid); + break; + case SEP_IOCENDTRANSACTION: + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOCENDTRANSACTION start\n", + current->pid); + error = sep_end_transaction_handler(sep, dma_ctx, call_status, + my_queue_elem); + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOCENDTRANSACTION end\n", + current->pid); + break; + case SEP_IOCPREPAREDCB: + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOCPREPAREDCB start\n", + current->pid); + case SEP_IOCPREPAREDCB_SECURE_DMA: + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOCPREPAREDCB_SECURE_DMA start\n", + current->pid); + if (1 == test_bit(SEP_LEGACY_SENDMSG_DONE_OFFSET, + &call_status->status)) { + dev_warn(&sep->pdev->dev, + "[PID%d] dcb prep needed before send msg\n", + current->pid); + error = -EPROTO; + goto end_function; + } + + if (!arg) { + dev_warn(&sep->pdev->dev, + "[PID%d] dcb null arg\n", current->pid); + error = EINVAL; + goto end_function; + } + + if (cmd == SEP_IOCPREPAREDCB) { + /* No secure dma */ + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOCPREPAREDCB (no secure_dma)\n", + current->pid); + + error = sep_prepare_dcb_handler(sep, arg, false, + dma_ctx); + } else { + /* Secure dma */ + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOC_POC (with secure_dma)\n", + current->pid); + + error = sep_prepare_dcb_handler(sep, arg, true, + dma_ctx); + } + dev_dbg(&sep->pdev->dev, "[PID%d] dcb's end\n", + current->pid); + break; + case SEP_IOCFREEDCB: + dev_dbg(&sep->pdev->dev, "[PID%d] SEP_IOCFREEDCB start\n", + current->pid); + case SEP_IOCFREEDCB_SECURE_DMA: + dev_dbg(&sep->pdev->dev, + "[PID%d] SEP_IOCFREEDCB_SECURE_DMA start\n", + current->pid); + error = sep_free_dcb_handler(sep, dma_ctx); + dev_dbg(&sep->pdev->dev, "[PID%d] SEP_IOCFREEDCB end\n", + current->pid); + break; + default: + error = -ENOTTY; + dev_dbg(&sep->pdev->dev, "[PID%d] default end\n", + current->pid); + break; + } + +end_function: + dev_dbg(&sep->pdev->dev, "[PID%d] ioctl end\n", current->pid); + + return error; +} + +/** + * sep_inthandler - interrupt handler for sep device + * @irq: interrupt + * @dev_id: device id + */ +static irqreturn_t sep_inthandler(int irq, void *dev_id) +{ + unsigned long lock_irq_flag; + u32 reg_val, reg_val2 = 0; + struct sep_device *sep = dev_id; + irqreturn_t int_error = IRQ_HANDLED; + + /* Are we in power save? */ +#if defined(CONFIG_PM_RUNTIME) && defined(SEP_ENABLE_RUNTIME_PM) + if (sep->pdev->dev.power.runtime_status != RPM_ACTIVE) { + dev_dbg(&sep->pdev->dev, "interrupt during pwr save\n"); + return IRQ_NONE; + } +#endif + + if (test_bit(SEP_WORKING_LOCK_BIT, &sep->in_use_flags) == 0) { + dev_dbg(&sep->pdev->dev, "interrupt while nobody using sep\n"); + return IRQ_NONE; + } + + /* Read the IRR register to check if this is SEP interrupt */ + reg_val = sep_read_reg(sep, HW_HOST_IRR_REG_ADDR); + + dev_dbg(&sep->pdev->dev, "sep int: IRR REG val: %x\n", reg_val); + + if (reg_val & (0x1 << 13)) { + + /* Lock and update the counter of reply messages */ + spin_lock_irqsave(&sep->snd_rply_lck, lock_irq_flag); + sep->reply_ct++; + spin_unlock_irqrestore(&sep->snd_rply_lck, lock_irq_flag); + + dev_dbg(&sep->pdev->dev, "sep int: send_ct %lx reply_ct %lx\n", + sep->send_ct, sep->reply_ct); + + /* Is this a kernel client request */ + if (sep->in_kernel) { + tasklet_schedule(&sep->finish_tasklet); + goto finished_interrupt; + } + + /* Is this printf or daemon request? */ + reg_val2 = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR2_REG_ADDR); + dev_dbg(&sep->pdev->dev, + "SEP Interrupt - GPR2 is %08x\n", reg_val2); + + clear_bit(SEP_WORKING_LOCK_BIT, &sep->in_use_flags); + + if ((reg_val2 >> 30) & 0x1) { + dev_dbg(&sep->pdev->dev, "int: printf request\n"); + } else if (reg_val2 >> 31) { + dev_dbg(&sep->pdev->dev, "int: daemon request\n"); + } else { + dev_dbg(&sep->pdev->dev, "int: SEP reply\n"); + wake_up(&sep->event_interrupt); + } + } else { + dev_dbg(&sep->pdev->dev, "int: not SEP interrupt\n"); + int_error = IRQ_NONE; + } + +finished_interrupt: + + if (int_error == IRQ_HANDLED) + sep_write_reg(sep, HW_HOST_ICR_REG_ADDR, reg_val); + + return int_error; +} + +/** + * sep_reconfig_shared_area - reconfigure shared area + * @sep: pointer to struct sep_device + * + * Reconfig the shared area between HOST and SEP - needed in case + * the DX_CC_Init function was called before OS loading. + */ +static int sep_reconfig_shared_area(struct sep_device *sep) +{ + int ret_val; + + /* use to limit waiting for SEP */ + unsigned long end_time; + + /* Send the new SHARED MESSAGE AREA to the SEP */ + dev_dbg(&sep->pdev->dev, "reconfig shared; sending %08llx to sep\n", + (unsigned long long)sep->shared_bus); + + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR1_REG_ADDR, sep->shared_bus); + + /* Poll for SEP response */ + ret_val = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR1_REG_ADDR); + + end_time = jiffies + (WAIT_TIME * HZ); + + while ((time_before(jiffies, end_time)) && (ret_val != 0xffffffff) && + (ret_val != sep->shared_bus)) + ret_val = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR1_REG_ADDR); + + /* Check the return value (register) */ + if (ret_val != sep->shared_bus) { + dev_warn(&sep->pdev->dev, "could not reconfig shared area\n"); + dev_warn(&sep->pdev->dev, "result was %x\n", ret_val); + ret_val = -ENOMEM; + } else + ret_val = 0; + + dev_dbg(&sep->pdev->dev, "reconfig shared area end\n"); + + return ret_val; +} + +/** + * sep_activate_dcb_dmatables_context - Takes DCB & DMA tables + * contexts into use + * @sep: SEP device + * @dcb_region: DCB region copy + * @dmatables_region: MLLI/DMA tables copy + * @dma_ctx: DMA context for current transaction + */ +ssize_t sep_activate_dcb_dmatables_context(struct sep_device *sep, + struct sep_dcblock **dcb_region, + void **dmatables_region, + struct sep_dma_context *dma_ctx) +{ + void *dmaregion_free_start = NULL; + void *dmaregion_free_end = NULL; + void *dcbregion_free_start = NULL; + void *dcbregion_free_end = NULL; + ssize_t error = 0; + + dev_dbg(&sep->pdev->dev, "[PID%d] activating dcb/dma region\n", + current->pid); + + if (1 > dma_ctx->nr_dcb_creat) { + dev_warn(&sep->pdev->dev, + "[PID%d] invalid number of dcbs to activate 0x%08X\n", + current->pid, dma_ctx->nr_dcb_creat); + error = -EINVAL; + goto end_function; + } + + dmaregion_free_start = sep->shared_addr + + SYNCHRONIC_DMA_TABLES_AREA_OFFSET_BYTES; + dmaregion_free_end = dmaregion_free_start + + SYNCHRONIC_DMA_TABLES_AREA_SIZE_BYTES - 1; + + if (dmaregion_free_start + + dma_ctx->dmatables_len > dmaregion_free_end) { + error = -ENOMEM; + goto end_function; + } + memcpy(dmaregion_free_start, + *dmatables_region, + dma_ctx->dmatables_len); + /* Free MLLI table copy */ + kfree(*dmatables_region); + *dmatables_region = NULL; + + /* Copy thread's DCB table copy to DCB table region */ + dcbregion_free_start = sep->shared_addr + + SEP_DRIVER_SYSTEM_DCB_MEMORY_OFFSET_IN_BYTES; + dcbregion_free_end = dcbregion_free_start + + (SEP_MAX_NUM_SYNC_DMA_OPS * + sizeof(struct sep_dcblock)) - 1; + + if (dcbregion_free_start + + (dma_ctx->nr_dcb_creat * sizeof(struct sep_dcblock)) + > dcbregion_free_end) { + error = -ENOMEM; + goto end_function; + } + + memcpy(dcbregion_free_start, + *dcb_region, + dma_ctx->nr_dcb_creat * sizeof(struct sep_dcblock)); + + /* Print the tables */ + dev_dbg(&sep->pdev->dev, "activate: input table\n"); + sep_debug_print_lli_tables(sep, + (struct sep_lli_entry *)sep_shared_area_bus_to_virt(sep, + (*dcb_region)->input_mlli_address), + (*dcb_region)->input_mlli_num_entries, + (*dcb_region)->input_mlli_data_size); + + dev_dbg(&sep->pdev->dev, "activate: output table\n"); + sep_debug_print_lli_tables(sep, + (struct sep_lli_entry *)sep_shared_area_bus_to_virt(sep, + (*dcb_region)->output_mlli_address), + (*dcb_region)->output_mlli_num_entries, + (*dcb_region)->output_mlli_data_size); + + dev_dbg(&sep->pdev->dev, + "[PID%d] printing activated tables\n", current->pid); + +end_function: + kfree(*dmatables_region); + *dmatables_region = NULL; + + kfree(*dcb_region); + *dcb_region = NULL; + + return error; +} + +/** + * sep_create_dcb_dmatables_context - Creates DCB & MLLI/DMA table context + * @sep: SEP device + * @dcb_region: DCB region buf to create for current transaction + * @dmatables_region: MLLI/DMA tables buf to create for current transaction + * @dma_ctx: DMA context buf to create for current transaction + * @user_dcb_args: User arguments for DCB/MLLI creation + * @num_dcbs: Number of DCBs to create + * @secure_dma: Indicate use of IMR restricted memory secure dma + */ +static ssize_t sep_create_dcb_dmatables_context(struct sep_device *sep, + struct sep_dcblock **dcb_region, + void **dmatables_region, + struct sep_dma_context **dma_ctx, + const struct build_dcb_struct __user *user_dcb_args, + const u32 num_dcbs, bool secure_dma) +{ + int error = 0; + int i = 0; + struct build_dcb_struct *dcb_args = NULL; + + dev_dbg(&sep->pdev->dev, "[PID%d] creating dcb/dma region\n", + current->pid); + + if (!dcb_region || !dma_ctx || !dmatables_region || !user_dcb_args) { + error = -EINVAL; + goto end_function; + } + + if (SEP_MAX_NUM_SYNC_DMA_OPS < num_dcbs) { + dev_warn(&sep->pdev->dev, + "[PID%d] invalid number of dcbs 0x%08X\n", + current->pid, num_dcbs); + error = -EINVAL; + goto end_function; + } + + dcb_args = kzalloc(num_dcbs * sizeof(struct build_dcb_struct), + GFP_KERNEL); + if (!dcb_args) { + dev_warn(&sep->pdev->dev, "[PID%d] no memory for dcb args\n", + current->pid); + error = -ENOMEM; + goto end_function; + } + + if (copy_from_user(dcb_args, + user_dcb_args, + num_dcbs * sizeof(struct build_dcb_struct))) { + error = -EINVAL; + goto end_function; + } + + /* Allocate thread-specific memory for DCB */ + *dcb_region = kzalloc(num_dcbs * sizeof(struct sep_dcblock), + GFP_KERNEL); + if (!(*dcb_region)) { + error = -ENOMEM; + goto end_function; + } + + /* Prepare DCB and MLLI table into the allocated regions */ + for (i = 0; i < num_dcbs; i++) { + error = sep_prepare_input_output_dma_table_in_dcb(sep, + (unsigned long)dcb_args[i].app_in_address, + (unsigned long)dcb_args[i].app_out_address, + dcb_args[i].data_in_size, + dcb_args[i].block_size, + dcb_args[i].tail_block_size, + dcb_args[i].is_applet, + false, secure_dma, + *dcb_region, dmatables_region, + dma_ctx, + NULL, + NULL); + if (error) { + dev_warn(&sep->pdev->dev, + "[PID%d] dma table creation failed\n", + current->pid); + goto end_function; + } + + if (dcb_args[i].app_in_address != 0) + (*dma_ctx)->input_data_len += dcb_args[i].data_in_size; + } + +end_function: + kfree(dcb_args); + return error; + +} + +/** + * sep_create_dcb_dmatables_context_kernel - Creates DCB & MLLI/DMA table context + * for kernel crypto + * @sep: SEP device + * @dcb_region: DCB region buf to create for current transaction + * @dmatables_region: MLLI/DMA tables buf to create for current transaction + * @dma_ctx: DMA context buf to create for current transaction + * @user_dcb_args: User arguments for DCB/MLLI creation + * @num_dcbs: Number of DCBs to create + * This does that same thing as sep_create_dcb_dmatables_context + * except that it is used only for the kernel crypto operation. It is + * separate because there is no user data involved; the dcb data structure + * is specific for kernel crypto (build_dcb_struct_kernel) + */ +int sep_create_dcb_dmatables_context_kernel(struct sep_device *sep, + struct sep_dcblock **dcb_region, + void **dmatables_region, + struct sep_dma_context **dma_ctx, + const struct build_dcb_struct_kernel *dcb_data, + const u32 num_dcbs) +{ + int error = 0; + int i = 0; + + dev_dbg(&sep->pdev->dev, "[PID%d] creating dcb/dma region\n", + current->pid); + + if (!dcb_region || !dma_ctx || !dmatables_region || !dcb_data) { + error = -EINVAL; + goto end_function; + } + + if (SEP_MAX_NUM_SYNC_DMA_OPS < num_dcbs) { + dev_warn(&sep->pdev->dev, + "[PID%d] invalid number of dcbs 0x%08X\n", + current->pid, num_dcbs); + error = -EINVAL; + goto end_function; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] num_dcbs is %d\n", + current->pid, num_dcbs); + + /* Allocate thread-specific memory for DCB */ + *dcb_region = kzalloc(num_dcbs * sizeof(struct sep_dcblock), + GFP_KERNEL); + if (!(*dcb_region)) { + error = -ENOMEM; + goto end_function; + } + + /* Prepare DCB and MLLI table into the allocated regions */ + for (i = 0; i < num_dcbs; i++) { + error = sep_prepare_input_output_dma_table_in_dcb(sep, + (unsigned long)dcb_data->app_in_address, + (unsigned long)dcb_data->app_out_address, + dcb_data->data_in_size, + dcb_data->block_size, + dcb_data->tail_block_size, + dcb_data->is_applet, + true, + false, + *dcb_region, dmatables_region, + dma_ctx, + dcb_data->src_sg, + dcb_data->dst_sg); + if (error) { + dev_warn(&sep->pdev->dev, + "[PID%d] dma table creation failed\n", + current->pid); + goto end_function; + } + } + +end_function: + return error; + +} + +/** + * sep_activate_msgarea_context - Takes the message area context into use + * @sep: SEP device + * @msg_region: Message area context buf + * @msg_len: Message area context buffer size + */ +static ssize_t sep_activate_msgarea_context(struct sep_device *sep, + void **msg_region, + const size_t msg_len) +{ + dev_dbg(&sep->pdev->dev, "[PID%d] activating msg region\n", + current->pid); + + if (!msg_region || !(*msg_region) || + SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES < msg_len) { + dev_warn(&sep->pdev->dev, + "[PID%d] invalid act msgarea len 0x%08zX\n", + current->pid, msg_len); + return -EINVAL; + } + + memcpy(sep->shared_addr, *msg_region, msg_len); + + return 0; +} + +/** + * sep_create_msgarea_context - Creates message area context + * @sep: SEP device + * @msg_region: Msg area region buf to create for current transaction + * @msg_user: Content for msg area region from user + * @msg_len: Message area size + */ +static ssize_t sep_create_msgarea_context(struct sep_device *sep, + void **msg_region, + const void __user *msg_user, + const size_t msg_len) +{ + int error = 0; + + dev_dbg(&sep->pdev->dev, "[PID%d] creating msg region\n", + current->pid); + + if (!msg_region || + !msg_user || + SEP_DRIVER_MAX_MESSAGE_SIZE_IN_BYTES < msg_len || + SEP_DRIVER_MIN_MESSAGE_SIZE_IN_BYTES > msg_len) { + dev_warn(&sep->pdev->dev, + "[PID%d] invalid creat msgarea len 0x%08zX\n", + current->pid, msg_len); + error = -EINVAL; + goto end_function; + } + + /* Allocate thread-specific memory for message buffer */ + *msg_region = kzalloc(msg_len, GFP_KERNEL); + if (!(*msg_region)) { + dev_warn(&sep->pdev->dev, + "[PID%d] no mem for msgarea context\n", + current->pid); + error = -ENOMEM; + goto end_function; + } + + /* Copy input data to write() to allocated message buffer */ + if (copy_from_user(*msg_region, msg_user, msg_len)) { + error = -EINVAL; + goto end_function; + } + +end_function: + if (error && msg_region) { + kfree(*msg_region); + *msg_region = NULL; + } + + return error; +} + + +/** + * sep_read - Returns results of an operation for fastcall interface + * @filp: File pointer + * @buf_user: User buffer for storing results + * @count_user: User buffer size + * @offset: File offset, not supported + * + * The implementation does not support reading in chunks, all data must be + * consumed during a single read system call. + */ +static ssize_t sep_read(struct file *filp, + char __user *buf_user, size_t count_user, + loff_t *offset) +{ + struct sep_private_data * const private_data = filp->private_data; + struct sep_call_status *call_status = &private_data->call_status; + struct sep_device *sep = private_data->device; + struct sep_dma_context **dma_ctx = &private_data->dma_ctx; + struct sep_queue_info **my_queue_elem = &private_data->my_queue_elem; + ssize_t error = 0, error_tmp = 0; + + /* Am I the process that owns the transaction? */ + error = sep_check_transaction_owner(sep); + if (error) { + dev_dbg(&sep->pdev->dev, "[PID%d] read pid is not owner\n", + current->pid); + goto end_function; + } + + /* Checks that user has called necessarry apis */ + if (0 == test_bit(SEP_FASTCALL_WRITE_DONE_OFFSET, + &call_status->status)) { + dev_warn(&sep->pdev->dev, + "[PID%d] fastcall write not called\n", + current->pid); + error = -EPROTO; + goto end_function_error; + } + + if (!buf_user) { + dev_warn(&sep->pdev->dev, + "[PID%d] null user buffer\n", + current->pid); + error = -EINVAL; + goto end_function_error; + } + + + /* Wait for SEP to finish */ + wait_event(sep->event_interrupt, + test_bit(SEP_WORKING_LOCK_BIT, + &sep->in_use_flags) == 0); + + sep_dump_message(sep); + + dev_dbg(&sep->pdev->dev, "[PID%d] count_user = 0x%08zX\n", + current->pid, count_user); + + /* In case user has allocated bigger buffer */ + if (count_user > SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES) + count_user = SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES; + + if (copy_to_user(buf_user, sep->shared_addr, count_user)) { + error = -EFAULT; + goto end_function_error; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] read succeeded\n", current->pid); + error = count_user; + +end_function_error: + /* Copy possible tail data to user and free DCB and MLLIs */ + error_tmp = sep_free_dcb_handler(sep, dma_ctx); + if (error_tmp) + dev_warn(&sep->pdev->dev, "[PID%d] dcb free failed\n", + current->pid); + + /* End the transaction, wakeup pending ones */ + error_tmp = sep_end_transaction_handler(sep, dma_ctx, call_status, + my_queue_elem); + if (error_tmp) + dev_warn(&sep->pdev->dev, + "[PID%d] ending transaction failed\n", + current->pid); + +end_function: + return error; +} + +/** + * sep_fastcall_args_get - Gets fastcall params from user + * sep: SEP device + * @args: Parameters buffer + * @buf_user: User buffer for operation parameters + * @count_user: User buffer size + */ +static inline ssize_t sep_fastcall_args_get(struct sep_device *sep, + struct sep_fastcall_hdr *args, + const char __user *buf_user, + const size_t count_user) +{ + ssize_t error = 0; + size_t actual_count = 0; + + if (!buf_user) { + dev_warn(&sep->pdev->dev, + "[PID%d] null user buffer\n", + current->pid); + error = -EINVAL; + goto end_function; + } + + if (count_user < sizeof(struct sep_fastcall_hdr)) { + dev_warn(&sep->pdev->dev, + "[PID%d] too small message size 0x%08zX\n", + current->pid, count_user); + error = -EINVAL; + goto end_function; + } + + + if (copy_from_user(args, buf_user, sizeof(struct sep_fastcall_hdr))) { + error = -EFAULT; + goto end_function; + } + + if (SEP_FC_MAGIC != args->magic) { + dev_warn(&sep->pdev->dev, + "[PID%d] invalid fastcall magic 0x%08X\n", + current->pid, args->magic); + error = -EINVAL; + goto end_function; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] fastcall hdr num of DCBs 0x%08X\n", + current->pid, args->num_dcbs); + dev_dbg(&sep->pdev->dev, "[PID%d] fastcall hdr msg len 0x%08X\n", + current->pid, args->msg_len); + + if (SEP_DRIVER_MAX_MESSAGE_SIZE_IN_BYTES < args->msg_len || + SEP_DRIVER_MIN_MESSAGE_SIZE_IN_BYTES > args->msg_len) { + dev_warn(&sep->pdev->dev, + "[PID%d] invalid message length\n", + current->pid); + error = -EINVAL; + goto end_function; + } + + actual_count = sizeof(struct sep_fastcall_hdr) + + args->msg_len + + (args->num_dcbs * sizeof(struct build_dcb_struct)); + + if (actual_count != count_user) { + dev_warn(&sep->pdev->dev, + "[PID%d] inconsistent message " + "sizes 0x%08zX vs 0x%08zX\n", + current->pid, actual_count, count_user); + error = -EMSGSIZE; + goto end_function; + } + +end_function: + return error; +} + +/** + * sep_write - Starts an operation for fastcall interface + * @filp: File pointer + * @buf_user: User buffer for operation parameters + * @count_user: User buffer size + * @offset: File offset, not supported + * + * The implementation does not support writing in chunks, + * all data must be given during a single write system call. + */ +static ssize_t sep_write(struct file *filp, + const char __user *buf_user, size_t count_user, + loff_t *offset) +{ + struct sep_private_data * const private_data = filp->private_data; + struct sep_call_status *call_status = &private_data->call_status; + struct sep_device *sep = private_data->device; + struct sep_dma_context *dma_ctx = NULL; + struct sep_fastcall_hdr call_hdr = {0}; + void *msg_region = NULL; + void *dmatables_region = NULL; + struct sep_dcblock *dcb_region = NULL; + ssize_t error = 0; + struct sep_queue_info *my_queue_elem = NULL; + bool my_secure_dma; /* are we using secure_dma (IMR)? */ + + dev_dbg(&sep->pdev->dev, "[PID%d] sep dev is 0x%p\n", + current->pid, sep); + dev_dbg(&sep->pdev->dev, "[PID%d] private_data is 0x%p\n", + current->pid, private_data); + + error = sep_fastcall_args_get(sep, &call_hdr, buf_user, count_user); + if (error) + goto end_function; + + buf_user += sizeof(struct sep_fastcall_hdr); + + if (call_hdr.secure_dma == 0) + my_secure_dma = false; + else + my_secure_dma = true; + + /* + * Controlling driver memory usage by limiting amount of + * buffers created. Only SEP_DOUBLEBUF_USERS_LIMIT number + * of threads can progress further at a time + */ + dev_dbg(&sep->pdev->dev, "[PID%d] waiting for double buffering " + "region access\n", current->pid); + error = down_interruptible(&sep->sep_doublebuf); + dev_dbg(&sep->pdev->dev, "[PID%d] double buffering region start\n", + current->pid); + if (error) { + /* Signal received */ + goto end_function_error; + } + + + /* + * Prepare contents of the shared area regions for + * the operation into temporary buffers + */ + if (0 < call_hdr.num_dcbs) { + error = sep_create_dcb_dmatables_context(sep, + &dcb_region, + &dmatables_region, + &dma_ctx, + (const struct build_dcb_struct __user *) + buf_user, + call_hdr.num_dcbs, my_secure_dma); + if (error) + goto end_function_error_doublebuf; + + buf_user += call_hdr.num_dcbs * sizeof(struct build_dcb_struct); + } + + error = sep_create_msgarea_context(sep, + &msg_region, + buf_user, + call_hdr.msg_len); + if (error) + goto end_function_error_doublebuf; + + dev_dbg(&sep->pdev->dev, "[PID%d] updating queue status\n", + current->pid); + my_queue_elem = sep_queue_status_add(sep, + ((struct sep_msgarea_hdr *)msg_region)->opcode, + (dma_ctx) ? dma_ctx->input_data_len : 0, + current->pid, + current->comm, sizeof(current->comm)); + + if (!my_queue_elem) { + dev_dbg(&sep->pdev->dev, "[PID%d] updating queue" + "status error\n", current->pid); + error = -ENOMEM; + goto end_function_error_doublebuf; + } + + /* Wait until current process gets the transaction */ + error = sep_wait_transaction(sep); + + if (error) { + /* Interrupted by signal, don't clear transaction */ + dev_dbg(&sep->pdev->dev, "[PID%d] interrupted by signal\n", + current->pid); + sep_queue_status_remove(sep, &my_queue_elem); + goto end_function_error_doublebuf; + } + + dev_dbg(&sep->pdev->dev, "[PID%d] saving queue element\n", + current->pid); + private_data->my_queue_elem = my_queue_elem; + + /* Activate shared area regions for the transaction */ + error = sep_activate_msgarea_context(sep, &msg_region, + call_hdr.msg_len); + if (error) + goto end_function_error_clear_transact; + + sep_dump_message(sep); + + if (0 < call_hdr.num_dcbs) { + error = sep_activate_dcb_dmatables_context(sep, + &dcb_region, + &dmatables_region, + dma_ctx); + if (error) + goto end_function_error_clear_transact; + } + + /* Send command to SEP */ + error = sep_send_command_handler(sep); + if (error) + goto end_function_error_clear_transact; + + /* Store DMA context for the transaction */ + private_data->dma_ctx = dma_ctx; + /* Update call status */ + set_bit(SEP_FASTCALL_WRITE_DONE_OFFSET, &call_status->status); + error = count_user; + + up(&sep->sep_doublebuf); + dev_dbg(&sep->pdev->dev, "[PID%d] double buffering region end\n", + current->pid); + + goto end_function; + +end_function_error_clear_transact: + sep_end_transaction_handler(sep, &dma_ctx, call_status, + &private_data->my_queue_elem); + +end_function_error_doublebuf: + up(&sep->sep_doublebuf); + dev_dbg(&sep->pdev->dev, "[PID%d] double buffering region end\n", + current->pid); + +end_function_error: + if (dma_ctx) + sep_free_dma_table_data_handler(sep, &dma_ctx); + +end_function: + kfree(dcb_region); + kfree(dmatables_region); + kfree(msg_region); + + return error; +} +/** + * sep_seek - Handler for seek system call + * @filp: File pointer + * @offset: File offset + * @origin: Options for offset + * + * Fastcall interface does not support seeking, all reads + * and writes are from/to offset zero + */ +static loff_t sep_seek(struct file *filp, loff_t offset, int origin) +{ + return -ENOSYS; +} + + + +/** + * sep_file_operations - file operation on sep device + * @sep_ioctl: ioctl handler from user space call + * @sep_poll: poll handler + * @sep_open: handles sep device open request + * @sep_release:handles sep device release request + * @sep_mmap: handles memory mapping requests + * @sep_read: handles read request on sep device + * @sep_write: handles write request on sep device + * @sep_seek: handles seek request on sep device + */ +static const struct file_operations sep_file_operations = { + .owner = THIS_MODULE, + .unlocked_ioctl = sep_ioctl, + .poll = sep_poll, + .open = sep_open, + .release = sep_release, + .mmap = sep_mmap, + .read = sep_read, + .write = sep_write, + .llseek = sep_seek, +}; + +/** + * sep_sysfs_read - read sysfs entry per gives arguments + * @filp: file pointer + * @kobj: kobject pointer + * @attr: binary file attributes + * @buf: read to this buffer + * @pos: offset to read + * @count: amount of data to read + * + * This function is to read sysfs entries for sep driver per given arguments. + */ +static ssize_t +sep_sysfs_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t pos, size_t count) +{ + unsigned long lck_flags; + size_t nleft = count; + struct sep_device *sep = sep_dev; + struct sep_queue_info *queue_elem = NULL; + u32 queue_num = 0; + u32 i = 1; + + spin_lock_irqsave(&sep->sep_queue_lock, lck_flags); + + queue_num = sep->sep_queue_num; + if (queue_num > SEP_DOUBLEBUF_USERS_LIMIT) + queue_num = SEP_DOUBLEBUF_USERS_LIMIT; + + + if (count < sizeof(queue_num) + + (queue_num * sizeof(struct sep_queue_data))) { + spin_unlock_irqrestore(&sep->sep_queue_lock, lck_flags); + return -EINVAL; + } + + memcpy(buf, &queue_num, sizeof(queue_num)); + buf += sizeof(queue_num); + nleft -= sizeof(queue_num); + + list_for_each_entry(queue_elem, &sep->sep_queue_status, list) { + if (i++ > queue_num) + break; + + memcpy(buf, &queue_elem->data, sizeof(queue_elem->data)); + nleft -= sizeof(queue_elem->data); + buf += sizeof(queue_elem->data); + } + spin_unlock_irqrestore(&sep->sep_queue_lock, lck_flags); + + return count - nleft; +} + +/** + * bin_attributes - defines attributes for queue_status + * @attr: attributes (name & permissions) + * @read: function pointer to read this file + * @size: maxinum size of binary attribute + */ +static const struct bin_attribute queue_status = { + .attr = {.name = "queue_status", .mode = 0444}, + .read = sep_sysfs_read, + .size = sizeof(u32) + + (SEP_DOUBLEBUF_USERS_LIMIT * sizeof(struct sep_queue_data)), +}; + +/** + * sep_register_driver_with_fs - register misc devices + * @sep: pointer to struct sep_device + * + * This function registers the driver with the file system + */ +static int sep_register_driver_with_fs(struct sep_device *sep) +{ + int ret_val; + + sep->miscdev_sep.minor = MISC_DYNAMIC_MINOR; + sep->miscdev_sep.name = SEP_DEV_NAME; + sep->miscdev_sep.fops = &sep_file_operations; + + ret_val = misc_register(&sep->miscdev_sep); + if (ret_val) { + dev_warn(&sep->pdev->dev, "misc reg fails for SEP %x\n", + ret_val); + return ret_val; + } + + ret_val = device_create_bin_file(sep->miscdev_sep.this_device, + &queue_status); + if (ret_val) { + dev_warn(&sep->pdev->dev, "sysfs attribute1 fails for SEP %x\n", + ret_val); + return ret_val; + } + + return ret_val; +} + + +/** + *sep_probe - probe a matching PCI device + *@pdev: pci_device + *@ent: pci_device_id + * + *Attempt to set up and configure a SEP device that has been + *discovered by the PCI layer. Allocates all required resources. + */ +static int __devinit sep_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int error = 0; + struct sep_device *sep = NULL; + + if (sep_dev != NULL) { + dev_dbg(&pdev->dev, "only one SEP supported.\n"); + return -EBUSY; + } + + /* Enable the device */ + error = pci_enable_device(pdev); + if (error) { + dev_warn(&pdev->dev, "error enabling pci device\n"); + goto end_function; + } + + /* Allocate the sep_device structure for this device */ + sep_dev = kzalloc(sizeof(struct sep_device), GFP_ATOMIC); + if (sep_dev == NULL) { + dev_warn(&pdev->dev, + "can't kmalloc the sep_device structure\n"); + error = -ENOMEM; + goto end_function_disable_device; + } + + /* + * We're going to use another variable for actually + * working with the device; this way, if we have + * multiple devices in the future, it would be easier + * to make appropriate changes + */ + sep = sep_dev; + + sep->pdev = pci_dev_get(pdev); + + init_waitqueue_head(&sep->event_transactions); + init_waitqueue_head(&sep->event_interrupt); + spin_lock_init(&sep->snd_rply_lck); + spin_lock_init(&sep->sep_queue_lock); + sema_init(&sep->sep_doublebuf, SEP_DOUBLEBUF_USERS_LIMIT); + + INIT_LIST_HEAD(&sep->sep_queue_status); + + dev_dbg(&sep->pdev->dev, "sep probe: PCI obtained, " + "device being prepared\n"); + + /* Set up our register area */ + sep->reg_physical_addr = pci_resource_start(sep->pdev, 0); + if (!sep->reg_physical_addr) { + dev_warn(&sep->pdev->dev, "Error getting register start\n"); + error = -ENODEV; + goto end_function_free_sep_dev; + } + + sep->reg_physical_end = pci_resource_end(sep->pdev, 0); + if (!sep->reg_physical_end) { + dev_warn(&sep->pdev->dev, "Error getting register end\n"); + error = -ENODEV; + goto end_function_free_sep_dev; + } + + sep->reg_addr = ioremap_nocache(sep->reg_physical_addr, + (size_t)(sep->reg_physical_end - sep->reg_physical_addr + 1)); + if (!sep->reg_addr) { + dev_warn(&sep->pdev->dev, "Error getting register virtual\n"); + error = -ENODEV; + goto end_function_free_sep_dev; + } + + dev_dbg(&sep->pdev->dev, + "Register area start %llx end %llx virtual %p\n", + (unsigned long long)sep->reg_physical_addr, + (unsigned long long)sep->reg_physical_end, + sep->reg_addr); + + /* Allocate the shared area */ + sep->shared_size = SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES + + SYNCHRONIC_DMA_TABLES_AREA_SIZE_BYTES + + SEP_DRIVER_DATA_POOL_SHARED_AREA_SIZE_IN_BYTES + + SEP_DRIVER_STATIC_AREA_SIZE_IN_BYTES + + SEP_DRIVER_SYSTEM_DATA_MEMORY_SIZE_IN_BYTES; + + if (sep_map_and_alloc_shared_area(sep)) { + error = -ENOMEM; + /* Allocation failed */ + goto end_function_error; + } + + /* Clear ICR register */ + sep_write_reg(sep, HW_HOST_ICR_REG_ADDR, 0xFFFFFFFF); + + /* Set the IMR register - open only GPR 2 */ + sep_write_reg(sep, HW_HOST_IMR_REG_ADDR, (~(0x1 << 13))); + + /* Read send/receive counters from SEP */ + sep->reply_ct = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR2_REG_ADDR); + sep->reply_ct &= 0x3FFFFFFF; + sep->send_ct = sep->reply_ct; + + /* Get the interrupt line */ + error = request_irq(pdev->irq, sep_inthandler, IRQF_SHARED, + "sep_driver", sep); + + if (error) + goto end_function_deallocate_sep_shared_area; + + /* The new chip requires a shared area reconfigure */ + error = sep_reconfig_shared_area(sep); + if (error) + goto end_function_free_irq; + + sep->in_use = 1; + + /* Finally magic up the device nodes */ + /* Register driver with the fs */ + error = sep_register_driver_with_fs(sep); + + if (error) { + dev_err(&sep->pdev->dev, "error registering dev file\n"); + goto end_function_free_irq; + } + + sep->in_use = 0; /* through touching the device */ +#ifdef SEP_ENABLE_RUNTIME_PM + pm_runtime_put_noidle(&sep->pdev->dev); + pm_runtime_allow(&sep->pdev->dev); + pm_runtime_set_autosuspend_delay(&sep->pdev->dev, + SUSPEND_DELAY); + pm_runtime_use_autosuspend(&sep->pdev->dev); + pm_runtime_mark_last_busy(&sep->pdev->dev); + sep->power_save_setup = 1; +#endif + /* register kernel crypto driver */ +#if defined(CONFIG_CRYPTO) || defined(CONFIG_CRYPTO_MODULE) + error = sep_crypto_setup(); + if (error) { + dev_err(&sep->pdev->dev, "crypto setup failed\n"); + goto end_function_free_irq; + } +#endif + goto end_function; + +end_function_free_irq: + free_irq(pdev->irq, sep); + +end_function_deallocate_sep_shared_area: + /* De-allocate shared area */ + sep_unmap_and_free_shared_area(sep); + +end_function_error: + iounmap(sep->reg_addr); + +end_function_free_sep_dev: + pci_dev_put(sep_dev->pdev); + kfree(sep_dev); + sep_dev = NULL; + +end_function_disable_device: + pci_disable_device(pdev); + +end_function: + return error; +} + +/** + * sep_remove - handles removing device from pci subsystem + * @pdev: pointer to pci device + * + * This function will handle removing our sep device from pci subsystem on exit + * or unloading this module. It should free up all used resources, and unmap if + * any memory regions mapped. + */ +static void sep_remove(struct pci_dev *pdev) +{ + struct sep_device *sep = sep_dev; + + /* Unregister from fs */ + misc_deregister(&sep->miscdev_sep); + + /* Unregister from kernel crypto */ +#if defined(CONFIG_CRYPTO) || defined(CONFIG_CRYPTO_MODULE) + sep_crypto_takedown(); +#endif + /* Free the irq */ + free_irq(sep->pdev->irq, sep); + + /* Free the shared area */ + sep_unmap_and_free_shared_area(sep_dev); + iounmap(sep_dev->reg_addr); + +#ifdef SEP_ENABLE_RUNTIME_PM + if (sep->in_use) { + sep->in_use = 0; + pm_runtime_forbid(&sep->pdev->dev); + pm_runtime_get_noresume(&sep->pdev->dev); + } +#endif + pci_dev_put(sep_dev->pdev); + kfree(sep_dev); + sep_dev = NULL; +} + +/* Initialize struct pci_device_id for our driver */ +static DEFINE_PCI_DEVICE_TABLE(sep_pci_id_tbl) = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0826)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08e9)}, + {0} +}; + +/* Export our pci_device_id structure to user space */ +MODULE_DEVICE_TABLE(pci, sep_pci_id_tbl); + +#ifdef SEP_ENABLE_RUNTIME_PM + +/** + * sep_pm_resume - rsume routine while waking up from S3 state + * @dev: pointer to sep device + * + * This function is to be used to wake up sep driver while system awakes from S3 + * state i.e. suspend to ram. The RAM in intact. + * Notes - revisit with more understanding of pm, ICR/IMR & counters. + */ +static int sep_pci_resume(struct device *dev) +{ + struct sep_device *sep = sep_dev; + + dev_dbg(&sep->pdev->dev, "pci resume called\n"); + + if (sep->power_state == SEP_DRIVER_POWERON) + return 0; + + /* Clear ICR register */ + sep_write_reg(sep, HW_HOST_ICR_REG_ADDR, 0xFFFFFFFF); + + /* Set the IMR register - open only GPR 2 */ + sep_write_reg(sep, HW_HOST_IMR_REG_ADDR, (~(0x1 << 13))); + + /* Read send/receive counters from SEP */ + sep->reply_ct = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR2_REG_ADDR); + sep->reply_ct &= 0x3FFFFFFF; + sep->send_ct = sep->reply_ct; + + sep->power_state = SEP_DRIVER_POWERON; + + return 0; +} + +/** + * sep_pm_suspend - suspend routine while going to S3 state + * @dev: pointer to sep device + * + * This function is to be used to suspend sep driver while system goes to S3 + * state i.e. suspend to ram. The RAM in intact and ON during this suspend. + * Notes - revisit with more understanding of pm, ICR/IMR + */ +static int sep_pci_suspend(struct device *dev) +{ + struct sep_device *sep = sep_dev; + + dev_dbg(&sep->pdev->dev, "pci suspend called\n"); + if (sep->in_use == 1) + return -EAGAIN; + + sep->power_state = SEP_DRIVER_POWEROFF; + + /* Clear ICR register */ + sep_write_reg(sep, HW_HOST_ICR_REG_ADDR, 0xFFFFFFFF); + + /* Set the IMR to block all */ + sep_write_reg(sep, HW_HOST_IMR_REG_ADDR, 0xFFFFFFFF); + + return 0; +} + +/** + * sep_pm_runtime_resume - runtime resume routine + * @dev: pointer to sep device + * + * Notes - revisit with more understanding of pm, ICR/IMR & counters + */ +static int sep_pm_runtime_resume(struct device *dev) +{ + + u32 retval2; + u32 delay_count; + struct sep_device *sep = sep_dev; + + dev_dbg(&sep->pdev->dev, "pm runtime resume called\n"); + + /** + * Wait until the SCU boot is ready + * This is done by iterating SCU_DELAY_ITERATION (10 + * microseconds each) up to SCU_DELAY_MAX (50) times. + * This bit can be set in a random time that is less + * than 500 microseconds after each power resume + */ + retval2 = 0; + delay_count = 0; + while ((!retval2) && (delay_count < SCU_DELAY_MAX)) { + retval2 = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR3_REG_ADDR); + retval2 &= 0x00000008; + if (!retval2) { + udelay(SCU_DELAY_ITERATION); + delay_count += 1; + } + } + + if (!retval2) { + dev_warn(&sep->pdev->dev, "scu boot bit not set at resume\n"); + return -EINVAL; + } + + /* Clear ICR register */ + sep_write_reg(sep, HW_HOST_ICR_REG_ADDR, 0xFFFFFFFF); + + /* Set the IMR register - open only GPR 2 */ + sep_write_reg(sep, HW_HOST_IMR_REG_ADDR, (~(0x1 << 13))); + + /* Read send/receive counters from SEP */ + sep->reply_ct = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR2_REG_ADDR); + sep->reply_ct &= 0x3FFFFFFF; + sep->send_ct = sep->reply_ct; + + return 0; +} + +/** + * sep_pm_runtime_suspend - runtime suspend routine + * @dev: pointer to sep device + * + * Notes - revisit with more understanding of pm + */ +static int sep_pm_runtime_suspend(struct device *dev) +{ + struct sep_device *sep = sep_dev; + + dev_dbg(&sep->pdev->dev, "pm runtime suspend called\n"); + + /* Clear ICR register */ + sep_write_reg(sep, HW_HOST_ICR_REG_ADDR, 0xFFFFFFFF); + return 0; +} + +/** + * sep_pm - power management for sep driver + * @sep_pm_runtime_resume: resume- no communication with cpu & main memory + * @sep_pm_runtime_suspend: suspend- no communication with cpu & main memory + * @sep_pci_suspend: suspend - main memory is still ON + * @sep_pci_resume: resume - main meory is still ON + */ +static const struct dev_pm_ops sep_pm = { + .runtime_resume = sep_pm_runtime_resume, + .runtime_suspend = sep_pm_runtime_suspend, + .resume = sep_pci_resume, + .suspend = sep_pci_suspend, +}; +#endif /* SEP_ENABLE_RUNTIME_PM */ + +/** + * sep_pci_driver - registers this device with pci subsystem + * @name: name identifier for this driver + * @sep_pci_id_tbl: pointer to struct pci_device_id table + * @sep_probe: pointer to probe function in PCI driver + * @sep_remove: pointer to remove function in PCI driver + */ +static struct pci_driver sep_pci_driver = { +#ifdef SEP_ENABLE_RUNTIME_PM + .driver = { + .pm = &sep_pm, + }, +#endif + .name = "sep_sec_driver", + .id_table = sep_pci_id_tbl, + .probe = sep_probe, + .remove = sep_remove +}; + +/** + * sep_init - init function + * + * Module load time. Register the PCI device driver. + */ + +static int __init sep_init(void) +{ + return pci_register_driver(&sep_pci_driver); +} + + +/** + * sep_exit - called to unload driver + * + * Unregister the driver The device will perform all the cleanup required. + */ +static void __exit sep_exit(void) +{ + pci_unregister_driver(&sep_pci_driver); +} + + +module_init(sep_init); +module_exit(sep_exit); + +MODULE_LICENSE("GPL"); |