diff options
Diffstat (limited to 'drivers/staging/sep/sep_driver.c')
-rw-r--r-- | drivers/staging/sep/sep_driver.c | 2707 |
1 files changed, 2707 insertions, 0 deletions
diff --git a/drivers/staging/sep/sep_driver.c b/drivers/staging/sep/sep_driver.c new file mode 100644 index 00000000000..87f8a119276 --- /dev/null +++ b/drivers/staging/sep/sep_driver.c @@ -0,0 +1,2707 @@ +/* + * + * sep_driver.c - Security Processor Driver main group of functions + * + * Copyright(c) 2009 Intel Corporation. All rights reserved. + * Copyright(c) 2009 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; either version 2 of the License, or (at your option) + * any later version. + * + * 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 + * + * CHANGES: + * + * 2009.06.26 Initial publish + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/kdev_t.h> +#include <linux/mutex.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/wait.h> +#include <linux/pci.h> +#include <linux/firmware.h> +#include <asm/ioctl.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <linux/interrupt.h> +#include <linux/pagemap.h> +#include <asm/cacheflush.h> +#include "sep_driver_hw_defs.h" +#include "sep_driver_config.h" +#include "sep_driver_api.h" +#include "sep_dev.h" + +#if SEP_DRIVER_ARM_DEBUG_MODE + +#define CRYS_SEP_ROM_length 0x4000 +#define CRYS_SEP_ROM_start_address 0x8000C000UL +#define CRYS_SEP_ROM_start_address_offset 0xC000UL +#define SEP_ROM_BANK_register 0x80008420UL +#define SEP_ROM_BANK_register_offset 0x8420UL +#define SEP_RAR_IO_MEM_REGION_START_ADDRESS 0x82000000 + +/* + * THESE 2 definitions are specific to the board - must be + * defined during integration + */ +#define SEP_RAR_IO_MEM_REGION_START_ADDRESS 0xFF0D0000 + +/* 2M size */ + +static void sep_load_rom_code(struct sep_device *sep) +{ + /* Index variables */ + unsigned long i, k, j; + u32 reg; + u32 error; + u32 warning; + + /* Loading ROM from SEP_ROM_image.h file */ + k = sizeof(CRYS_SEP_ROM); + + edbg("SEP Driver: DX_CC_TST_SepRomLoader start\n"); + + edbg("SEP Driver: k is %lu\n", k); + edbg("SEP Driver: sep->reg_addr is %p\n", sep->reg_addr); + edbg("SEP Driver: CRYS_SEP_ROM_start_address_offset is %p\n", CRYS_SEP_ROM_start_address_offset); + + for (i = 0; i < 4; i++) { + /* write bank */ + sep_write_reg(sep, SEP_ROM_BANK_register_offset, i); + + for (j = 0; j < CRYS_SEP_ROM_length / 4; j++) { + sep_write_reg(sep, CRYS_SEP_ROM_start_address_offset + 4 * j, CRYS_SEP_ROM[i * 0x1000 + j]); + + k = k - 4; + + if (k == 0) { + j = CRYS_SEP_ROM_length; + i = 4; + } + } + } + + /* reset the SEP */ + sep_write_reg(sep, HW_HOST_SEP_SW_RST_REG_ADDR, 0x1); + + /* poll for SEP ROM boot finish */ + do + reg = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR3_REG_ADDR); + while (!reg); + + edbg("SEP Driver: ROM polling ended\n"); + + switch (reg) { + case 0x1: + /* fatal error - read erro status from GPRO */ + error = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR0_REG_ADDR); + edbg("SEP Driver: ROM polling case 1\n"); + break; + case 0x4: + /* Cold boot ended successfully */ + case 0x8: + /* Warmboot ended successfully */ + case 0x10: + /* ColdWarm boot ended successfully */ + error = 0; + case 0x2: + /* Boot First Phase ended */ + warning = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR0_REG_ADDR); + case 0x20: + edbg("SEP Driver: ROM polling case %d\n", reg); + break; + } + +} + +#else +static void sep_load_rom_code(struct sep_device *sep) { } +#endif /* SEP_DRIVER_ARM_DEBUG_MODE */ + + + +/*---------------------------------------- + DEFINES +-----------------------------------------*/ + +#define BASE_ADDRESS_FOR_SYSTEM 0xfffc0000 +#define SEP_RAR_IO_MEM_REGION_SIZE 0x40000 + +/*-------------------------------------------- + GLOBAL variables +--------------------------------------------*/ + +/* debug messages level */ +static int debug; +module_param(debug, int , 0); +MODULE_PARM_DESC(debug, "Flag to enable SEP debug messages"); + +/* Keep this a single static object for now to keep the conversion easy */ + +static struct sep_device sep_instance; +static struct sep_device *sep_dev = &sep_instance; + +/* + mutex for the access to the internals of the sep driver +*/ +static DEFINE_MUTEX(sep_mutex); + + +/* wait queue head (event) of the driver */ +static DECLARE_WAIT_QUEUE_HEAD(sep_event); + +/** + * sep_load_firmware - copy firmware cache/resident + * @sep: device we are loading + * + * This functions copies the cache and resident from their source + * location into destination shared memory. + */ + +static int sep_load_firmware(struct sep_device *sep) +{ + const struct firmware *fw; + char *cache_name = "cache.image.bin"; + char *res_name = "resident.image.bin"; + int error; + + edbg("SEP Driver:rar_virtual is %p\n", sep->rar_addr); + edbg("SEP Driver:rar_bus is %08llx\n", (unsigned long long)sep->rar_bus); + + /* load cache */ + error = request_firmware(&fw, cache_name, &sep->pdev->dev); + if (error) { + edbg("SEP Driver:cant request cache fw\n"); + return error; + } + edbg("SEP Driver:cache %08Zx@%p\n", fw->size, (void *) fw->data); + + memcpy(sep->rar_addr, (void *)fw->data, fw->size); + sep->cache_size = fw->size; + release_firmware(fw); + + sep->resident_bus = sep->rar_bus + sep->cache_size; + sep->resident_addr = sep->rar_addr + sep->cache_size; + + /* load resident */ + error = request_firmware(&fw, res_name, &sep->pdev->dev); + if (error) { + edbg("SEP Driver:cant request res fw\n"); + return error; + } + edbg("sep: res %08Zx@%p\n", fw->size, (void *)fw->data); + + memcpy(sep->resident_addr, (void *) fw->data, fw->size); + sep->resident_size = fw->size; + release_firmware(fw); + + edbg("sep: resident v %p b %08llx cache v %p b %08llx\n", + sep->resident_addr, (unsigned long long)sep->resident_bus, + sep->rar_addr, (unsigned long long)sep->rar_bus); + return 0; +} + +/** + * sep_map_and_alloc_shared_area - allocate shared block + * @sep: security processor + * @size: size of shared area + * + * Allocate a shared buffer in host memory that can be used by both the + * kernel and also the hardware interface via DMA. + */ + +static int sep_map_and_alloc_shared_area(struct sep_device *sep, + unsigned long size) +{ + /* shared_addr = ioremap_nocache(0xda00000,shared_area_size); */ + sep->shared_addr = dma_alloc_coherent(&sep->pdev->dev, size, + &sep->shared_bus, GFP_KERNEL); + + if (!sep->shared_addr) { + edbg("sep_driver :shared memory dma_alloc_coherent failed\n"); + return -ENOMEM; + } + /* set the bus address of the shared area */ + edbg("sep: shared_addr %ld bytes @%p (bus %08llx)\n", + size, sep->shared_addr, (unsigned long long)sep->shared_bus); + return 0; +} + +/** + * sep_unmap_and_free_shared_area - free shared block + * @sep: security processor + * + * Free the shared area allocated to the security processor. The + * processor must have finished with this and any final posted + * writes cleared before we do so. + */ +static void sep_unmap_and_free_shared_area(struct sep_device *sep, int size) +{ + dma_free_coherent(&sep->pdev->dev, size, + sep->shared_addr, sep->shared_bus); +} + +/** + * sep_shared_virt_to_bus - convert bus/virt addresses + * + * Returns the bus address inside the shared area according + * to the virtual address. + */ + +static dma_addr_t sep_shared_virt_to_bus(struct sep_device *sep, + void *virt_address) +{ + dma_addr_t pa = sep->shared_bus + (virt_address - sep->shared_addr); + edbg("sep: virt to bus b %08llx v %p\n", pa, virt_address); + return pa; +} + +/** + * sep_shared_bus_to_virt - convert bus/virt addresses + * + * 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); +} + + +/** + * sep_try_open - attempt to open a SEP device + * @sep: device to attempt to open + * + * Atomically attempt to get ownership of a SEP device. + * Returns 1 if the device was opened, 0 on failure. + */ + +static int sep_try_open(struct sep_device *sep) +{ + if (!test_and_set_bit(0, &sep->in_use)) + return 1; + return 0; +} + +/** + * 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. Must also release the memory data pool + * allocations. + * + * Returns zero on success otherwise an error code. + */ + +static int sep_open(struct inode *inode, struct file *filp) +{ + if (sep_dev == NULL) + return -ENODEV; + + /* check the blocking mode */ + if (filp->f_flags & O_NDELAY) { + if (sep_try_open(sep_dev) == 0) + return -EAGAIN; + } else + if (wait_event_interruptible(sep_event, sep_try_open(sep_dev)) < 0) + return -EINTR; + + /* Bind to the device, we only have one which makes it easy */ + filp->private_data = sep_dev; + /* release data pool allocations */ + sep_dev->data_pool_bytes_allocated = 0; + 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. As the open protects against + * multiple simultaenous opens that means this method is called when the + * final reference to the open handle is dropped. + */ + +static int sep_release(struct inode *inode, struct file *filp) +{ + struct sep_device *sep = filp->private_data; +#if 0 /*!SEP_DRIVER_POLLING_MODE */ + /* close IMR */ + sep_write_reg(sep, HW_HOST_IMR_REG_ADDR, 0x7FFF); + /* release IRQ line */ + free_irq(SEP_DIRVER_IRQ_NUM, sep); + +#endif + /* Ensure any blocked open progresses */ + clear_bit(0, &sep->in_use); + wake_up(&sep_event); + return 0; +} + +/*--------------------------------------------------------------- + map function - this functions maps the message shared area +-----------------------------------------------------------------*/ +static int sep_mmap(struct file *filp, struct vm_area_struct *vma) +{ + dma_addr_t bus_addr; + struct sep_device *sep = filp->private_data; + + dbg("-------->SEP Driver: mmap start\n"); + + /* 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) { + edbg("SEP Driver mmap requested size is more than allowed\n"); + printk(KERN_WARNING "SEP Driver mmap requested size is more \ + than allowed\n"); + printk(KERN_WARNING "SEP Driver vma->vm_end is %08lx\n", vma->vm_end); + printk(KERN_WARNING "SEP Driver vma->vm_end is %08lx\n", vma->vm_start); + return -EAGAIN; + } + + edbg("SEP Driver:sep->shared_addr is %p\n", sep->shared_addr); + + /* get bus address */ + bus_addr = sep->shared_bus; + + edbg("SEP Driver: phys_addr is %08llx\n", (unsigned long long)bus_addr); + + if (remap_pfn_range(vma, vma->vm_start, bus_addr >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + edbg("SEP Driver remap_page_range failed\n"); + printk(KERN_WARNING "SEP Driver remap_page_range failed\n"); + return -EAGAIN; + } + + dbg("SEP Driver:<-------- mmap end\n"); + + return 0; +} + + +/*----------------------------------------------- + poll function +*----------------------------------------------*/ +static unsigned int sep_poll(struct file *filp, poll_table * wait) +{ + unsigned long count; + unsigned int mask = 0; + unsigned long retval = 0; /* flow id */ + struct sep_device *sep = filp->private_data; + + dbg("---------->SEP Driver poll: start\n"); + + +#if SEP_DRIVER_POLLING_MODE + + while (sep->send_ct != (retval & 0x7FFFFFFF)) { + retval = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR2_REG_ADDR); + + for (count = 0; count < 10 * 4; count += 4) + edbg("Poll Debug Word %lu of the message is %lu\n", count, *((unsigned long *) (sep->shared_addr + SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES + count))); + } + + sep->reply_ct++; +#else + /* add the event to the polling wait table */ + poll_wait(filp, &sep_event, wait); + +#endif + + edbg("sep->send_ct is %lu\n", sep->send_ct); + edbg("sep->reply_ct is %lu\n", sep->reply_ct); + + /* check if the data is ready */ + if (sep->send_ct == sep->reply_ct) { + for (count = 0; count < 12 * 4; count += 4) + edbg("Sep Mesg Word %lu of the message is %lu\n", count, *((unsigned long *) (sep->shared_addr + count))); + + for (count = 0; count < 10 * 4; count += 4) + edbg("Debug Data Word %lu of the message is %lu\n", count, *((unsigned long *) (sep->shared_addr + 0x1800 + count))); + + retval = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR2_REG_ADDR); + edbg("retval is %lu\n", retval); + /* check if the this is sep reply or request */ + if (retval >> 31) { + edbg("SEP Driver: sep request in\n"); + /* request */ + mask |= POLLOUT | POLLWRNORM; + } else { + edbg("SEP Driver: sep reply in\n"); + mask |= POLLIN | POLLRDNORM; + } + } + dbg("SEP Driver:<-------- poll exit\n"); + 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 */ + + + dbg("sep:sep_set_time start\n"); + + 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; + + edbg("SEP Driver:time.tv_sec is %lu\n", time.tv_sec); + edbg("SEP Driver:time_addr is %p\n", time_addr); + edbg("SEP Driver:sep->shared_addr is %p\n", sep->shared_addr); + + return time.tv_sec; +} + +/** + * sep_dump_message - dump the message that is pending + * @sep: sep device + * + * Dump out the message pending in the shared message area + */ + +static void sep_dump_message(struct sep_device *sep) +{ + int count; + for (count = 0; count < 12 * 4; count += 4) + edbg("Word %d of the message is %u\n", count, *((u32 *) (sep->shared_addr + count))); +} + +/** + * 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 + */ + +static void sep_send_command_handler(struct sep_device *sep) +{ + dbg("sep:sep_send_command_handler start\n"); + + mutex_lock(&sep_mutex); + sep_set_time(sep); + + /* FIXME: flush cache */ + flush_cache_all(); + + sep_dump_message(sep); + /* update counter */ + sep->send_ct++; + /* send interrupt to SEP */ + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR0_REG_ADDR, 0x2); + dbg("SEP Driver:<-------- sep_send_command_handler end\n"); + mutex_unlock(&sep_mutex); + return; +} + +/** + * sep_send_reply_command_handler - kick off a command reply + * @sep: sep being signalled + * + * This function raises interrupt to SEP that signals that is has a new + * command from the host + */ + +static void sep_send_reply_command_handler(struct sep_device *sep) +{ + dbg("sep:sep_send_reply_command_handler start\n"); + + /* flash cache */ + flush_cache_all(); + + sep_dump_message(sep); + + mutex_lock(&sep_mutex); + sep->send_ct++; /* update counter */ + /* send the interrupt to SEP */ + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR2_REG_ADDR, sep->send_ct); + /* update both counters */ + sep->send_ct++; + sep->reply_ct++; + mutex_unlock(&sep_mutex); + dbg("sep: sep_send_reply_command_handler end\n"); +} + +/* + This function handles the allocate data pool memory request + This function returns calculates the bus address of the + allocated memory, and the offset of this area from the mapped address. + Therefore, the FVOs in user space can calculate the exact virtual + address of this allocated memory +*/ +static int sep_allocate_data_pool_memory_handler(struct sep_device *sep, + unsigned long arg) +{ + int error; + struct sep_driver_alloc_t command_args; + + dbg("SEP Driver:--------> sep_allocate_data_pool_memory_handler start\n"); + + error = copy_from_user(&command_args, (void *) arg, sizeof(struct sep_driver_alloc_t)); + if (error) + goto end_function; + + /* allocate memory */ + if ((sep->data_pool_bytes_allocated + command_args.num_bytes) > SEP_DRIVER_DATA_POOL_SHARED_AREA_SIZE_IN_BYTES) { + error = -ENOMEM; + goto end_function; + } + + /* set the virtual and bus address */ + command_args.offset = SEP_DRIVER_DATA_POOL_AREA_OFFSET_IN_BYTES + sep->data_pool_bytes_allocated; + command_args.phys_address = sep->shared_bus + SEP_DRIVER_DATA_POOL_AREA_OFFSET_IN_BYTES + sep->data_pool_bytes_allocated; + + /* write the memory back to the user space */ + error = copy_to_user((void *) arg, (void *) &command_args, sizeof(struct sep_driver_alloc_t)); + if (error) + goto end_function; + + /* set the allocation */ + sep->data_pool_bytes_allocated += command_args.num_bytes; + +end_function: + dbg("SEP Driver:<-------- sep_allocate_data_pool_memory_handler end\n"); + return error; +} + +/* + This function handles write into allocated data pool command +*/ +static int sep_write_into_data_pool_handler(struct sep_device *sep, unsigned long arg) +{ + int error; + void *virt_address; + unsigned long va; + unsigned long app_in_address; + unsigned long num_bytes; + void *data_pool_area_addr; + + dbg("SEP Driver:--------> sep_write_into_data_pool_handler start\n"); + + /* get the application address */ + error = get_user(app_in_address, &(((struct sep_driver_write_t *) arg)->app_address)); + if (error) + goto end_function; + + /* get the virtual kernel address address */ + error = get_user(va, &(((struct sep_driver_write_t *) arg)->datapool_address)); + if (error) + goto end_function; + virt_address = (void *)va; + + /* get the number of bytes */ + error = get_user(num_bytes, &(((struct sep_driver_write_t *) arg)->num_bytes)); + if (error) + goto end_function; + + /* calculate the start of the data pool */ + data_pool_area_addr = sep->shared_addr + SEP_DRIVER_DATA_POOL_AREA_OFFSET_IN_BYTES; + + + /* check that the range of the virtual kernel address is correct */ + if (virt_address < data_pool_area_addr || virt_address > (data_pool_area_addr + SEP_DRIVER_DATA_POOL_SHARED_AREA_SIZE_IN_BYTES)) { + error = -EINVAL; + goto end_function; + } + /* copy the application data */ + error = copy_from_user(virt_address, (void *) app_in_address, num_bytes); +end_function: + dbg("SEP Driver:<-------- sep_write_into_data_pool_handler end\n"); + return error; +} + +/* + this function handles the read from data pool command +*/ +static int sep_read_from_data_pool_handler(struct sep_device *sep, unsigned long arg) +{ + int error; + /* virtual address of dest application buffer */ + unsigned long app_out_address; + /* virtual address of the data pool */ + unsigned long va; + void *virt_address; + unsigned long num_bytes; + void *data_pool_area_addr; + + dbg("SEP Driver:--------> sep_read_from_data_pool_handler start\n"); + + /* get the application address */ + error = get_user(app_out_address, &(((struct sep_driver_write_t *) arg)->app_address)); + if (error) + goto end_function; + + /* get the virtual kernel address address */ + error = get_user(va, &(((struct sep_driver_write_t *) arg)->datapool_address)); + if (error) + goto end_function; + virt_address = (void *)va; + + /* get the number of bytes */ + error = get_user(num_bytes, &(((struct sep_driver_write_t *) arg)->num_bytes)); + if (error) + goto end_function; + + /* calculate the start of the data pool */ + data_pool_area_addr = sep->shared_addr + SEP_DRIVER_DATA_POOL_AREA_OFFSET_IN_BYTES; + + /* FIXME: These are incomplete all over the driver: what about + len + and when doing that also overflows */ + /* check that the range of the virtual kernel address is correct */ + if (virt_address < data_pool_area_addr || virt_address > data_pool_area_addr + SEP_DRIVER_DATA_POOL_SHARED_AREA_SIZE_IN_BYTES) { + error = -EINVAL; + goto end_function; + } + + /* copy the application data */ + error = copy_to_user((void *) app_out_address, virt_address, num_bytes); +end_function: + dbg("SEP Driver:<-------- sep_read_from_data_pool_handler end\n"); + return error; +} + +/* + This function releases all the application virtual buffer physical pages, + that were previously locked +*/ +static int sep_free_dma_pages(struct page **page_array_ptr, unsigned long num_pages, unsigned long dirtyFlag) +{ + unsigned long count; + + if (dirtyFlag) { + for (count = 0; count < num_pages; count++) { + /* the out array was written, therefore the data was changed */ + if (!PageReserved(page_array_ptr[count])) + SetPageDirty(page_array_ptr[count]); + page_cache_release(page_array_ptr[count]); + } + } else { + /* free in pages - the data was only read, therefore no update was done + on those pages */ + for (count = 0; count < num_pages; count++) + page_cache_release(page_array_ptr[count]); + } + + if (page_array_ptr) + /* free the array */ + kfree(page_array_ptr); + + return 0; +} + +/* + 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 physical pages +*/ +static int sep_lock_kernel_pages(struct sep_device *sep, + unsigned long kernel_virt_addr, + unsigned long data_size, + unsigned long *num_pages_ptr, + struct sep_lli_entry_t **lli_array_ptr, + struct page ***page_array_ptr) +{ + int error = 0; + /* the the page of the end address of the user space buffer */ + unsigned long end_page; + /* the page of the start address of the user space buffer */ + unsigned long start_page; + /* the range in pages */ + unsigned long num_pages; + struct sep_lli_entry_t *lli_array; + /* next kernel address to map */ + unsigned long next_kernel_address; + unsigned long count; + + dbg("SEP Driver:--------> sep_lock_kernel_pages start\n"); + + /* set start and end pages and num pages */ + end_page = (kernel_virt_addr + data_size - 1) >> PAGE_SHIFT; + start_page = kernel_virt_addr >> PAGE_SHIFT; + num_pages = end_page - start_page + 1; + + edbg("SEP Driver: kernel_virt_addr is %08lx\n", kernel_virt_addr); + edbg("SEP Driver: data_size is %lu\n", data_size); + edbg("SEP Driver: start_page is %lx\n", start_page); + edbg("SEP Driver: end_page is %lx\n", end_page); + edbg("SEP Driver: num_pages is %lu\n", num_pages); + + lli_array = kmalloc(sizeof(struct sep_lli_entry_t) * num_pages, GFP_ATOMIC); + if (!lli_array) { + edbg("SEP Driver: kmalloc for lli_array failed\n"); + error = -ENOMEM; + goto end_function; + } + + /* set the start address of the first page - app data may start not at + the beginning of the page */ + lli_array[0].physical_address = (unsigned long) virt_to_phys((unsigned long *) kernel_virt_addr); + + /* check that not all the data is in the first page only */ + if ((PAGE_SIZE - (kernel_virt_addr & (~PAGE_MASK))) >= data_size) + lli_array[0].block_size = data_size; + else + lli_array[0].block_size = PAGE_SIZE - (kernel_virt_addr & (~PAGE_MASK)); + + /* debug print */ + dbg("lli_array[0].physical_address is %08lx, lli_array[0].block_size is %lu\n", lli_array[0].physical_address, lli_array[0].block_size); + + /* advance the address to the start of the next page */ + next_kernel_address = (kernel_virt_addr & PAGE_MASK) + PAGE_SIZE; + + /* go from the second page to the prev before last */ + for (count = 1; count < (num_pages - 1); count++) { + lli_array[count].physical_address = (unsigned long) virt_to_phys((unsigned long *) next_kernel_address); + lli_array[count].block_size = PAGE_SIZE; + + edbg("lli_array[%lu].physical_address is %08lx, lli_array[%lu].block_size is %lu\n", count, lli_array[count].physical_address, count, lli_array[count].block_size); + next_kernel_address += PAGE_SIZE; + } + + /* if more then 1 pages locked - then update for the last page size needed */ + if (num_pages > 1) { + /* update the address of the last page */ + lli_array[count].physical_address = (unsigned long) virt_to_phys((unsigned long *) next_kernel_address); + + /* set the size of the last page */ + lli_array[count].block_size = (kernel_virt_addr + data_size) & (~PAGE_MASK); + + if (lli_array[count].block_size == 0) { + dbg("app_virt_addr is %08lx\n", kernel_virt_addr); + dbg("data_size is %lu\n", data_size); + while (1); + } + + edbg("lli_array[%lu].physical_address is %08lx, lli_array[%lu].block_size is %lu\n", count, lli_array[count].physical_address, count, lli_array[count].block_size); + } + /* set output params */ + *lli_array_ptr = lli_array; + *num_pages_ptr = num_pages; + *page_array_ptr = 0; +end_function: + dbg("SEP Driver:<-------- sep_lock_kernel_pages end\n"); + return 0; +} + +/* + 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, + unsigned long app_virt_addr, + unsigned long data_size, + unsigned long *num_pages_ptr, + struct sep_lli_entry_t **lli_array_ptr, + struct page ***page_array_ptr) +{ + int error = 0; + /* the the page of the end address of the user space buffer */ + unsigned long end_page; + /* the page of the start address of the user space buffer */ + unsigned long start_page; + /* the range in pages */ + unsigned long num_pages; + struct page **page_array; + struct sep_lli_entry_t *lli_array; + unsigned long count; + int result; + + dbg("SEP Driver:--------> sep_lock_user_pages start\n"); + + /* 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; + + edbg("SEP Driver: app_virt_addr is %08lx\n", app_virt_addr); + edbg("SEP Driver: data_size is %lu\n", data_size); + edbg("SEP Driver: start_page is %lu\n", start_page); + edbg("SEP Driver: end_page is %lu\n", end_page); + edbg("SEP Driver: num_pages is %lu\n", num_pages); + + /* allocate array of pages structure pointers */ + page_array = kmalloc(sizeof(struct page *) * num_pages, GFP_ATOMIC); + if (!page_array) { + edbg("SEP Driver: kmalloc for page_array failed\n"); + + error = -ENOMEM; + goto end_function; + } + + lli_array = kmalloc(sizeof(struct sep_lli_entry_t) * num_pages, GFP_ATOMIC); + if (!lli_array) { + edbg("SEP Driver: kmalloc for lli_array failed\n"); + + error = -ENOMEM; + goto end_function_with_error1; + } + + /* 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, 1, 0, page_array, 0); + up_read(¤t->mm->mmap_sem); + + /* check the number of pages locked - if not all then exit with error */ + if (result != num_pages) { + dbg("SEP Driver: not all pages locked by get_user_pages\n"); + + error = -ENOMEM; + goto end_function_with_error2; + } + + /* flush the cache */ + for (count = 0; count < num_pages; count++) + flush_dcache_page(page_array[count]); + + /* set the start address of the first page - app data may start not at + the beginning of the page */ + lli_array[0].physical_address = ((unsigned long) page_to_phys(page_array[0])) + (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)); + + /* debug print */ + dbg("lli_array[0].physical_address is %08lx, lli_array[0].block_size is %lu\n", lli_array[0].physical_address, lli_array[0].block_size); + + /* go from the second page to the prev before last */ + for (count = 1; count < (num_pages - 1); count++) { + lli_array[count].physical_address = (unsigned long) page_to_phys(page_array[count]); + lli_array[count].block_size = PAGE_SIZE; + + edbg("lli_array[%lu].physical_address is %08lx, lli_array[%lu].block_size is %lu\n", count, lli_array[count].physical_address, count, lli_array[count].block_size); + } + + /* if more then 1 pages locked - then update for the last page size needed */ + if (num_pages > 1) { + /* update the address of the last page */ + lli_array[count].physical_address = (unsigned long) page_to_phys(page_array[count]); + + /* set the size of the last page */ + lli_array[count].block_size = (app_virt_addr + data_size) & (~PAGE_MASK); + + if (lli_array[count].block_size == 0) { + dbg("app_virt_addr is %08lx\n", app_virt_addr); + dbg("data_size is %lu\n", data_size); + while (1); + } + edbg("lli_array[%lu].physical_address is %08lx, \ + lli_array[%lu].block_size is %lu\n", count, lli_array[count].physical_address, count, lli_array[count].block_size); + } + + /* set output params */ + *lli_array_ptr = lli_array; + *num_pages_ptr = num_pages; + *page_array_ptr = page_array; + goto end_function; + +end_function_with_error2: + /* release the cache */ + for (count = 0; count < num_pages; count++) + page_cache_release(page_array[count]); + kfree(lli_array); +end_function_with_error1: + kfree(page_array); +end_function: + dbg("SEP Driver:<-------- sep_lock_user_pages end\n"); + return 0; +} + + +/* + this function calculates the size of data that can be inserted into the lli + table from this array the condition is that either the table is full + (all etnries are entered), or there are no more entries in the lli array +*/ +static unsigned long sep_calculate_lli_table_max_size(struct sep_lli_entry_t *lli_in_array_ptr, unsigned long num_array_entries) +{ + unsigned long table_data_size = 0; + unsigned long counter; + + /* calculate the data in the out lli table if 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; + return table_data_size; +} + +/* + this functions builds ont lli table from the lli_array according to + the given size of data +*/ +static void sep_build_lli_table(struct sep_lli_entry_t *lli_array_ptr, struct sep_lli_entry_t *lli_table_ptr, unsigned long *num_processed_entries_ptr, unsigned long *num_table_entries_ptr, unsigned long table_data_size) +{ + unsigned long curr_table_data_size; + /* counter of lli array entry */ + unsigned long array_counter; + + dbg("SEP Driver:--------> sep_build_lli_table start\n"); + + /* init currrent table data size and lli array entry counter */ + curr_table_data_size = 0; + array_counter = 0; + *num_table_entries_ptr = 1; + + edbg("SEP Driver:table_data_size is %lu\n", 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->physical_address = lli_array_ptr[array_counter].physical_address; + lli_table_ptr->block_size = lli_array_ptr[array_counter].block_size; + curr_table_data_size += lli_table_ptr->block_size; + + edbg("SEP Driver:lli_table_ptr is %08lx\n", (unsigned long) lli_table_ptr); + edbg("SEP Driver:lli_table_ptr->physical_address is %08lx\n", lli_table_ptr->physical_address); + edbg("SEP Driver:lli_table_ptr->block_size is %lu\n", lli_table_ptr->block_size); + + /* check for overflow of the table data */ + if (curr_table_data_size > table_data_size) { + edbg("SEP Driver:curr_table_data_size > table_data_size\n"); + + /* update the size of block in the table */ + 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].physical_address += 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++; + + edbg("SEP Driver:lli_table_ptr->physical_address is %08lx\n", lli_table_ptr->physical_address); + edbg("SEP Driver:lli_table_ptr->block_size is %lu\n", lli_table_ptr->block_size); + + /* move to the next entry in table */ + lli_table_ptr++; + } + + /* set the info entry to default */ + lli_table_ptr->physical_address = 0xffffffff; + lli_table_ptr->block_size = 0; + + edbg("SEP Driver:lli_table_ptr is %08lx\n", (unsigned long) lli_table_ptr); + edbg("SEP Driver:lli_table_ptr->physical_address is %08lx\n", lli_table_ptr->physical_address); + edbg("SEP Driver:lli_table_ptr->block_size is %lu\n", lli_table_ptr->block_size); + + /* set the output parameter */ + *num_processed_entries_ptr += array_counter; + + edbg("SEP Driver:*num_processed_entries_ptr is %lu\n", *num_processed_entries_ptr); + dbg("SEP Driver:<-------- sep_build_lli_table end\n"); + return; +} + +/* + this function goes over the list of the print created tables and + prints all the data +*/ +static void sep_debug_print_lli_tables(struct sep_device *sep, struct sep_lli_entry_t *lli_table_ptr, unsigned long num_table_entries, unsigned long table_data_size) +{ + unsigned long table_count; + unsigned long entries_count; + + dbg("SEP Driver:--------> sep_debug_print_lli_tables start\n"); + + table_count = 1; + while ((unsigned long) lli_table_ptr != 0xffffffff) { + edbg("SEP Driver: lli table %08lx, table_data_size is %lu\n", table_count, table_data_size); + edbg("SEP Driver: num_table_entries is %lu\n", 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++) { + edbg("SEP Driver:lli_table_ptr address is %08lx\n", (unsigned long) lli_table_ptr); + edbg("SEP Driver:phys address is %08lx block size is %lu\n", lli_table_ptr->physical_address, lli_table_ptr->block_size); + } + + /* point to the info entry */ + lli_table_ptr--; + + edbg("SEP Driver:phys lli_table_ptr->block_size is %lu\n", lli_table_ptr->block_size); + edbg("SEP Driver:phys lli_table_ptr->physical_address is %08lx\n", lli_table_ptr->physical_address); + + + table_data_size = lli_table_ptr->block_size & 0xffffff; + num_table_entries = (lli_table_ptr->block_size >> 24) & 0xff; + lli_table_ptr = (struct sep_lli_entry_t *) + (lli_table_ptr->physical_address); + + edbg("SEP Driver:phys table_data_size is %lu num_table_entries is %lu lli_table_ptr is%lu\n", table_data_size, num_table_entries, (unsigned long) lli_table_ptr); + + if ((unsigned long) lli_table_ptr != 0xffffffff) + lli_table_ptr = (struct sep_lli_entry_t *) sep_shared_bus_to_virt(sep, (unsigned long) lli_table_ptr); + + table_count++; + } + dbg("SEP Driver:<-------- sep_debug_print_lli_tables end\n"); +} + + +/* + This function prepares only input DMA table for synhronic symmetric + operations (HASH) +*/ +static int sep_prepare_input_dma_table(struct sep_device *sep, + unsigned long app_virt_addr, + unsigned long data_size, + unsigned long block_size, + unsigned long *lli_table_ptr, + unsigned long *num_entries_ptr, + unsigned long *table_data_size_ptr, + bool isKernelVirtualAddress) +{ + /* pointer to the info entry of the table - the last entry */ + struct sep_lli_entry_t *info_entry_ptr; + /* array of pointers ot page */ + struct sep_lli_entry_t *lli_array_ptr; + /* points to the first entry to be processed in the lli_in_array */ + unsigned long current_entry; + /* num entries in the virtual buffer */ + unsigned long sep_lli_entries; + /* lli table pointer */ + struct sep_lli_entry_t *in_lli_table_ptr; + /* the total data in one table */ + unsigned long table_data_size; + /* number of entries in lli table */ + unsigned long num_entries_in_table; + /* next table address */ + void *lli_table_alloc_addr; + unsigned long result; + + dbg("SEP Driver:--------> sep_prepare_input_dma_table start\n"); + + edbg("SEP Driver:data_size is %lu\n", data_size); + edbg("SEP Driver:block_size is %lu\n", block_size); + + /* initialize the pages pointers */ + sep->in_page_array = 0; + sep->in_num_pages = 0; + + if (data_size == 0) { + /* special case - created 2 entries table with zero data */ + in_lli_table_ptr = (struct sep_lli_entry_t *) (sep->shared_addr + SEP_DRIVER_SYNCHRONIC_DMA_TABLES_AREA_OFFSET_IN_BYTES); + /* FIXME: Should the entry below not be for _bus */ + in_lli_table_ptr->physical_address = (unsigned long)sep->shared_addr + SEP_DRIVER_SYNCHRONIC_DMA_TABLES_AREA_OFFSET_IN_BYTES; + in_lli_table_ptr->block_size = 0; + + in_lli_table_ptr++; + in_lli_table_ptr->physical_address = 0xFFFFFFFF; + in_lli_table_ptr->block_size = 0; + + *lli_table_ptr = sep->shared_bus + SEP_DRIVER_SYNCHRONIC_DMA_TABLES_AREA_OFFSET_IN_BYTES; + *num_entries_ptr = 2; + *table_data_size_ptr = 0; + + goto end_function; + } + + /* check if the pages are in Kernel Virtual Address layout */ + if (isKernelVirtualAddress == true) + /* lock the pages of the kernel buffer and translate them to pages */ + result = sep_lock_kernel_pages(sep, app_virt_addr, data_size, &sep->in_num_pages, &lli_array_ptr, &sep->in_page_array); + else + /* lock the pages of the user buffer and translate them to pages */ + result = sep_lock_user_pages(sep, app_virt_addr, data_size, &sep->in_num_pages, &lli_array_ptr, &sep->in_page_array); + + if (result) + return result; + + edbg("SEP Driver:output sep->in_num_pages is %lu\n", sep->in_num_pages); + + current_entry = 0; + info_entry_ptr = 0; + sep_lli_entries = sep->in_num_pages; + + /* initiate to point after the message area */ + lli_table_alloc_addr = sep->shared_addr + SEP_DRIVER_SYNCHRONIC_DMA_TABLES_AREA_OFFSET_IN_BYTES; + + /* loop till all the entries in in array are not processed */ + while (current_entry < sep_lli_entries) { + /* set the new input and output tables */ + in_lli_table_ptr = (struct sep_lli_entry_t *) lli_table_alloc_addr; + + lli_table_alloc_addr += sizeof(struct sep_lli_entry_t) * SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + + /* calculate the maximum size of data for input table */ + table_data_size = sep_calculate_lli_table_max_size(&lli_array_ptr[current_entry], (sep_lli_entries - current_entry)); + + /* now calculate the table size so that it will be module block size */ + table_data_size = (table_data_size / block_size) * block_size; + + edbg("SEP Driver:output table_data_size is %lu\n", table_data_size); + + /* construct input lli table */ + sep_build_lli_table(&lli_array_ptr[current_entry], in_lli_table_ptr, ¤t_entry, &num_entries_in_table, table_data_size); + + if (info_entry_ptr == 0) { + /* set the output parameters to physical addresses */ + *lli_table_ptr = sep_shared_virt_to_bus(sep, in_lli_table_ptr); + *num_entries_ptr = num_entries_in_table; + *table_data_size_ptr = table_data_size; + + edbg("SEP Driver:output lli_table_in_ptr is %08lx\n", *lli_table_ptr); + } else { + /* update the info entry of the previous in table */ + info_entry_ptr->physical_address = sep_shared_virt_to_bus(sep, 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 */ + sep_debug_print_lli_tables(sep, (struct sep_lli_entry_t *) + sep_shared_bus_to_virt(sep, *lli_table_ptr), *num_entries_ptr, *table_data_size_ptr); + + /* the array of the pages */ + kfree(lli_array_ptr); +end_function: + dbg("SEP Driver:<-------- sep_prepare_input_dma_table end\n"); + return 0; + +} + +/* + This function creates the input and output dma tables for + symmetric operations (AES/DES) according to the block size from LLI arays +*/ +static int sep_construct_dma_tables_from_lli(struct sep_device *sep, + struct sep_lli_entry_t *lli_in_array, + unsigned long sep_in_lli_entries, + struct sep_lli_entry_t *lli_out_array, + unsigned long sep_out_lli_entries, + unsigned long block_size, unsigned long *lli_table_in_ptr, unsigned long *lli_table_out_ptr, unsigned long *in_num_entries_ptr, unsigned long *out_num_entries_ptr, unsigned long *table_data_size_ptr) +{ + /* points to the area where next lli table can be allocated: keep void * + as there is pointer scaling to fix otherwise */ + void *lli_table_alloc_addr; + /* input lli table */ + struct sep_lli_entry_t *in_lli_table_ptr; + /* output lli table */ + struct sep_lli_entry_t *out_lli_table_ptr; + /* pointer to the info entry of the table - the last entry */ + struct sep_lli_entry_t *info_in_entry_ptr; + /* pointer to the info entry of the table - the last entry */ + struct sep_lli_entry_t *info_out_entry_ptr; + /* points to the first entry to be processed in the lli_in_array */ + unsigned long current_in_entry; + /* points to the first entry to be processed in the lli_out_array */ + unsigned long current_out_entry; + /* max size of the input table */ + unsigned long in_table_data_size; + /* max size of the output table */ + unsigned long out_table_data_size; + /* flag te signifies if this is the first tables build from the arrays */ + unsigned long first_table_flag; + /* the data size that should be in table */ + unsigned long table_data_size; + /* number of etnries in the input table */ + unsigned long num_entries_in_table; + /* number of etnries in the output table */ + unsigned long num_entries_out_table; + + dbg("SEP Driver:--------> sep_construct_dma_tables_from_lli start\n"); + + /* initiate to pint after the message area */ + lli_table_alloc_addr = sep->shared_addr + SEP_DRIVER_SYNCHRONIC_DMA_TABLES_AREA_OFFSET_IN_BYTES; + + current_in_entry = 0; + current_out_entry = 0; + first_table_flag = 1; + info_in_entry_ptr = 0; + info_out_entry_ptr = 0; + + /* 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_t *) lli_table_alloc_addr; + + lli_table_alloc_addr += sizeof(struct sep_lli_entry_t) * SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP; + + /* set the first output tables */ + out_lli_table_ptr = (struct sep_lli_entry_t *) lli_table_alloc_addr; + + lli_table_alloc_addr += sizeof(struct sep_lli_entry_t) * 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(&lli_in_array[current_in_entry], (sep_in_lli_entries - current_in_entry)); + + /* calculate the maximum size of data for output table */ + out_table_data_size = sep_calculate_lli_table_max_size(&lli_out_array[current_out_entry], (sep_out_lli_entries - current_out_entry)); + + edbg("SEP Driver:in_table_data_size is %lu\n", in_table_data_size); + edbg("SEP Driver:out_table_data_size is %lu\n", out_table_data_size); + + /* check where the data is smallest */ + table_data_size = in_table_data_size; + if (table_data_size > out_table_data_size) + table_data_size = out_table_data_size; + + /* now calculate the table size so that it will be module block size */ + table_data_size = (table_data_size / block_size) * block_size; + + dbg("SEP Driver:table_data_size is %lu\n", table_data_size); + + /* construct input lli table */ + sep_build_lli_table(&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(&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 == 0) { + /* set the output parameters to physical addresses */ + *lli_table_in_ptr = sep_shared_virt_to_bus(sep, in_lli_table_ptr); + *in_num_entries_ptr = num_entries_in_table; + *lli_table_out_ptr = sep_shared_virt_to_bus(sep, out_lli_table_ptr); + *out_num_entries_ptr = num_entries_out_table; + *table_data_size_ptr = table_data_size; + + edbg("SEP Driver:output lli_table_in_ptr is %08lx\n", *lli_table_in_ptr); + edbg("SEP Driver:output lli_table_out_ptr is %08lx\n", *lli_table_out_ptr); + } else { + /* update the info entry of the previous in table */ + info_in_entry_ptr->physical_address = sep_shared_virt_to_bus(sep, 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->physical_address = sep_shared_virt_to_bus(sep, out_lli_table_ptr); + info_out_entry_ptr->block_size = ((num_entries_out_table) << 24) | (table_data_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; + + edbg("SEP Driver:output num_entries_out_table is %lu\n", (unsigned long) num_entries_out_table); + edbg("SEP Driver:output info_in_entry_ptr is %lu\n", (unsigned long) info_in_entry_ptr); + edbg("SEP Driver:output info_out_entry_ptr is %lu\n", (unsigned long) info_out_entry_ptr); + } + + /* print input tables */ + sep_debug_print_lli_tables(sep, (struct sep_lli_entry_t *) + sep_shared_bus_to_virt(sep, *lli_table_in_ptr), *in_num_entries_ptr, *table_data_size_ptr); + /* print output tables */ + sep_debug_print_lli_tables(sep, (struct sep_lli_entry_t *) + sep_shared_bus_to_virt(sep, *lli_table_out_ptr), *out_num_entries_ptr, *table_data_size_ptr); + dbg("SEP Driver:<-------- sep_construct_dma_tables_from_lli end\n"); + return 0; +} + + +/* + This function builds input and output DMA tables for synhronic + symmetric operations (AES, DES). It also checks that each table + is of the modular block size +*/ +static int sep_prepare_input_output_dma_table(struct sep_device *sep, + unsigned long app_virt_in_addr, + unsigned long app_virt_out_addr, + unsigned long data_size, + unsigned long block_size, + unsigned long *lli_table_in_ptr, unsigned long *lli_table_out_ptr, unsigned long *in_num_entries_ptr, unsigned long *out_num_entries_ptr, unsigned long *table_data_size_ptr, bool isKernelVirtualAddress) +{ + /* array of pointers of page */ + struct sep_lli_entry_t *lli_in_array; + /* array of pointers of page */ + struct sep_lli_entry_t *lli_out_array; + int result = 0; + + dbg("SEP Driver:--------> sep_prepare_input_output_dma_table start\n"); + + /* initialize the pages pointers */ + sep->in_page_array = 0; + sep->out_page_array = 0; + + /* check if the pages are in Kernel Virtual Address layout */ + if (isKernelVirtualAddress == true) { + /* lock the pages of the kernel buffer and translate them to pages */ + result = sep_lock_kernel_pages(sep, app_virt_in_addr, data_size, &sep->in_num_pages, &lli_in_array, &sep->in_page_array); + if (result) { + edbg("SEP Driver: sep_lock_kernel_pages for input virtual buffer failed\n"); + goto end_function; + } + } else { + /* lock the pages of the user buffer and translate them to pages */ + result = sep_lock_user_pages(sep, app_virt_in_addr, data_size, &sep->in_num_pages, &lli_in_array, &sep->in_page_array); + if (result) { + edbg("SEP Driver: sep_lock_user_pages for input virtual buffer failed\n"); + goto end_function; + } + } + + if (isKernelVirtualAddress == true) { + result = sep_lock_kernel_pages(sep, app_virt_out_addr, data_size, &sep->out_num_pages, &lli_out_array, &sep->out_page_array); + if (result) { + edbg("SEP Driver: sep_lock_kernel_pages for output virtual buffer failed\n"); + goto end_function_with_error1; + } + } else { + result = sep_lock_user_pages(sep, app_virt_out_addr, data_size, &sep->out_num_pages, &lli_out_array, &sep->out_page_array); + if (result) { + edbg("SEP Driver: sep_lock_user_pages for output virtual buffer failed\n"); + goto end_function_with_error1; + } + } + edbg("sep->in_num_pages is %lu\n", sep->in_num_pages); + edbg("sep->out_num_pages is %lu\n", sep->out_num_pages); + edbg("SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP is %x\n", SEP_DRIVER_ENTRIES_PER_TABLE_IN_SEP); + + + /* call the fucntion that creates table from the lli arrays */ + result = sep_construct_dma_tables_from_lli(sep, lli_in_array, sep->in_num_pages, lli_out_array, sep->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); + if (result) { + edbg("SEP Driver: sep_construct_dma_tables_from_lli failed\n"); + goto end_function_with_error2; + } + + /* fall through - free the lli entry arrays */ + dbg("in_num_entries_ptr is %08lx\n", *in_num_entries_ptr); + dbg("out_num_entries_ptr is %08lx\n", *out_num_entries_ptr); + dbg("table_data_size_ptr is %08lx\n", *table_data_size_ptr); +end_function_with_error2: + kfree(lli_out_array); +end_function_with_error1: + kfree(lli_in_array); +end_function: + dbg("SEP Driver:<-------- sep_prepare_input_output_dma_table end result = %d\n", (int) result); + return result; + +} + +/* + this function handles tha request for creation of the DMA table + for the synchronic symmetric operations (AES,DES) +*/ +static int sep_create_sync_dma_tables_handler(struct sep_device *sep, + unsigned long arg) +{ + int error; + /* command arguments */ + struct sep_driver_build_sync_table_t command_args; + + dbg("SEP Driver:--------> sep_create_sync_dma_tables_handler start\n"); + + error = copy_from_user(&command_args, (void *) arg, sizeof(struct sep_driver_build_sync_table_t)); + if (error) + goto end_function; + + edbg("app_in_address is %08lx\n", command_args.app_in_address); + edbg("app_out_address is %08lx\n", command_args.app_out_address); + edbg("data_size is %lu\n", command_args.data_in_size); + edbg("block_size is %lu\n", command_args.block_size); + + /* check if we need to build only input table or input/output */ + if (command_args.app_out_address) + /* prepare input and output tables */ + error = sep_prepare_input_output_dma_table(sep, + command_args.app_in_address, + command_args.app_out_address, + command_args.data_in_size, + command_args.block_size, + &command_args.in_table_address, + &command_args.out_table_address, &command_args.in_table_num_entries, &command_args.out_table_num_entries, &command_args.table_data_size, command_args.isKernelVirtualAddress); + else + /* prepare input tables */ + error = sep_prepare_input_dma_table(sep, + command_args.app_in_address, + command_args.data_in_size, command_args.block_size, &command_args.in_table_address, &command_args.in_table_num_entries, &command_args.table_data_size, command_args.isKernelVirtualAddress); + + if (error) + goto end_function; + /* copy to user */ + if (copy_to_user((void *) arg, (void *) &command_args, sizeof(struct sep_driver_build_sync_table_t))) + error = -EFAULT; +end_function: + dbg("SEP Driver:<-------- sep_create_sync_dma_tables_handler end\n"); + return error; +} + +/* + this function handles the request for freeing dma table for synhronic actions +*/ +static int sep_free_dma_table_data_handler(struct sep_device *sep) +{ + dbg("SEP Driver:--------> sep_free_dma_table_data_handler start\n"); + + /* free input pages array */ + sep_free_dma_pages(sep->in_page_array, sep->in_num_pages, 0); + + /* free output pages array if needed */ + if (sep->out_page_array) + sep_free_dma_pages(sep->out_page_array, sep->out_num_pages, 1); + + /* reset all the values */ + sep->in_page_array = 0; + sep->out_page_array = 0; + sep->in_num_pages = 0; + sep->out_num_pages = 0; + dbg("SEP Driver:<-------- sep_free_dma_table_data_handler end\n"); + return 0; +} + +/* + this function find a space for the new flow dma table +*/ +static int sep_find_free_flow_dma_table_space(struct sep_device *sep, + unsigned long **table_address_ptr) +{ + int error = 0; + /* pointer to the id field of the flow dma table */ + unsigned long *start_table_ptr; + /* Do not make start_addr unsigned long * unless fixing the offset + computations ! */ + void *flow_dma_area_start_addr; + unsigned long *flow_dma_area_end_addr; + /* maximum table size in words */ + unsigned long table_size_in_words; + + /* find the start address of the flow DMA table area */ + flow_dma_area_start_addr = sep->shared_addr + SEP_DRIVER_FLOW_DMA_TABLES_AREA_OFFSET_IN_BYTES; + + /* set end address of the flow table area */ + flow_dma_area_end_addr = flow_dma_area_start_addr + SEP_DRIVER_FLOW_DMA_TABLES_AREA_SIZE_IN_BYTES; + + /* set table size in words */ + table_size_in_words = SEP_DRIVER_MAX_FLOW_NUM_ENTRIES_IN_TABLE * (sizeof(struct sep_lli_entry_t) / sizeof(long)) + 2; + + /* set the pointer to the start address of DMA area */ + start_table_ptr = flow_dma_area_start_addr; + + /* find the space for the next table */ + while (((*start_table_ptr & 0x7FFFFFFF) != 0) && start_table_ptr < flow_dma_area_end_addr) + start_table_ptr += table_size_in_words; + + /* check if we reached the end of floa tables area */ + if (start_table_ptr >= flow_dma_area_end_addr) + error = -1; + else + *table_address_ptr = start_table_ptr; + + return error; +} + +/* + This function creates one DMA table for flow and returns its data, + and pointer to its info entry +*/ +static int sep_prepare_one_flow_dma_table(struct sep_device *sep, + unsigned long virt_buff_addr, + unsigned long virt_buff_size, + struct sep_lli_entry_t *table_data, + struct sep_lli_entry_t **info_entry_ptr, + struct sep_flow_context_t *flow_data_ptr, + bool isKernelVirtualAddress) +{ + int error; + /* the range in pages */ + unsigned long lli_array_size; + struct sep_lli_entry_t *lli_array; + struct sep_lli_entry_t *flow_dma_table_entry_ptr; + unsigned long *start_dma_table_ptr; + /* total table data counter */ + unsigned long dma_table_data_count; + /* pointer that will keep the pointer to the pages of the virtual buffer */ + struct page **page_array_ptr; + unsigned long entry_count; + + /* find the space for the new table */ + error = sep_find_free_flow_dma_table_space(sep, &start_dma_table_ptr); + if (error) + goto end_function; + + /* check if the pages are in Kernel Virtual Address layout */ + if (isKernelVirtualAddress == true) + /* lock kernel buffer in the memory */ + error = sep_lock_kernel_pages(sep, virt_buff_addr, virt_buff_size, &lli_array_size, &lli_array, &page_array_ptr); + else + /* lock user buffer in the memory */ + error = sep_lock_user_pages(sep, virt_buff_addr, virt_buff_size, &lli_array_size, &lli_array, &page_array_ptr); + + if (error) + goto end_function; + + /* set the pointer to page array at the beginning of table - this table is + now considered taken */ + *start_dma_table_ptr = lli_array_size; + + /* point to the place of the pages pointers of the table */ + start_dma_table_ptr++; + + /* set the pages pointer */ + *start_dma_table_ptr = (unsigned long) page_array_ptr; + + /* set the pointer to the first entry */ + flow_dma_table_entry_ptr = (struct sep_lli_entry_t *) (++start_dma_table_ptr); + + /* now create the entries for table */ + for (dma_table_data_count = entry_count = 0; entry_count < lli_array_size; entry_count++) { + flow_dma_table_entry_ptr->physical_address = lli_array[entry_count].physical_address; + + flow_dma_table_entry_ptr->block_size = lli_array[entry_count].block_size; + + /* set the total data of a table */ + dma_table_data_count += lli_array[entry_count].block_size; + + flow_dma_table_entry_ptr++; + } + + /* set the physical address */ + table_data->physical_address = virt_to_phys(start_dma_table_ptr); + + /* set the num_entries and total data size */ + table_data->block_size = ((lli_array_size + 1) << SEP_NUM_ENTRIES_OFFSET_IN_BITS) | (dma_table_data_count); + + /* set the info entry */ + flow_dma_table_entry_ptr->physical_address = 0xffffffff; + flow_dma_table_entry_ptr->block_size = 0; + + /* set the pointer to info entry */ + *info_entry_ptr = flow_dma_table_entry_ptr; + + /* the array of the lli entries */ + kfree(lli_array); +end_function: + return error; +} + + + +/* + This function creates a list of tables for flow and returns the data for + the first and last tables of the list +*/ +static int sep_prepare_flow_dma_tables(struct sep_device *sep, + unsigned long num_virtual_buffers, + unsigned long first_buff_addr, struct sep_flow_context_t *flow_data_ptr, struct sep_lli_entry_t *first_table_data_ptr, struct sep_lli_entry_t *last_table_data_ptr, bool isKernelVirtualAddress) +{ + int error; + unsigned long virt_buff_addr; + unsigned long virt_buff_size; + struct sep_lli_entry_t table_data; + struct sep_lli_entry_t *info_entry_ptr; + struct sep_lli_entry_t *prev_info_entry_ptr; + unsigned long i; + + /* init vars */ + error = 0; + prev_info_entry_ptr = 0; + + /* init the first table to default */ + table_data.physical_address = 0xffffffff; + first_table_data_ptr->physical_address = 0xffffffff; + table_data.block_size = 0; + + for (i = 0; i < num_virtual_buffers; i++) { + /* get the virtual buffer address */ + error = get_user(virt_buff_addr, &first_buff_addr); + if (error) + goto end_function; + + /* get the virtual buffer size */ + first_buff_addr++; + error = get_user(virt_buff_size, &first_buff_addr); + if (error) + goto end_function; + + /* advance the address to point to the next pair of address|size */ + first_buff_addr++; + + /* now prepare the one flow LLI table from the data */ + error = sep_prepare_one_flow_dma_table(sep, virt_buff_addr, virt_buff_size, &table_data, &info_entry_ptr, flow_data_ptr, isKernelVirtualAddress); + if (error) + goto end_function; + + if (i == 0) { + /* if this is the first table - save it to return to the user + application */ + *first_table_data_ptr = table_data; + + /* set the pointer to info entry */ + prev_info_entry_ptr = info_entry_ptr; + } else { + /* not first table - the previous table info entry should + be updated */ + prev_info_entry_ptr->block_size = (0x1 << SEP_INT_FLAG_OFFSET_IN_BITS) | (table_data.block_size); + + /* set the pointer to info entry */ + prev_info_entry_ptr = info_entry_ptr; + } + } + + /* set the last table data */ + *last_table_data_ptr = table_data; +end_function: + return error; +} + +/* + this function goes over all the flow tables connected to the given + table and deallocate them +*/ +static void sep_deallocated_flow_tables(struct sep_lli_entry_t *first_table_ptr) +{ + /* id pointer */ + unsigned long *table_ptr; + /* end address of the flow dma area */ + unsigned long num_entries; + unsigned long num_pages; + struct page **pages_ptr; + /* maximum table size in words */ + struct sep_lli_entry_t *info_entry_ptr; + + /* set the pointer to the first table */ + table_ptr = (unsigned long *) first_table_ptr->physical_address; + + /* set the num of entries */ + num_entries = (first_table_ptr->block_size >> SEP_NUM_ENTRIES_OFFSET_IN_BITS) + & SEP_NUM_ENTRIES_MASK; + + /* go over all the connected tables */ + while (*table_ptr != 0xffffffff) { + /* get number of pages */ + num_pages = *(table_ptr - 2); + + /* get the pointer to the pages */ + pages_ptr = (struct page **) (*(table_ptr - 1)); + + /* free the pages */ + sep_free_dma_pages(pages_ptr, num_pages, 1); + + /* goto to the info entry */ + info_entry_ptr = ((struct sep_lli_entry_t *) table_ptr) + (num_entries - 1); + + table_ptr = (unsigned long *) info_entry_ptr->physical_address; + num_entries = (info_entry_ptr->block_size >> SEP_NUM_ENTRIES_OFFSET_IN_BITS) & SEP_NUM_ENTRIES_MASK; + } + + return; +} + +/** + * sep_find_flow_context - find a flow + * @sep: the SEP we are working with + * @flow_id: flow identifier + * + * Returns a pointer the matching flow, or NULL if the flow does not + * exist. + */ + +static struct sep_flow_context_t *sep_find_flow_context(struct sep_device *sep, + unsigned long flow_id) +{ + int count; + /* + * always search for flow with id default first - in case we + * already started working on the flow there can be no situation + * when 2 flows are with default flag + */ + for (count = 0; count < SEP_DRIVER_NUM_FLOWS; count++) { + if (sep->flows[count].flow_id == flow_id) + return &sep->flows[count]; + } + return NULL; +} + + +/* + this function handles the request to create the DMA tables for flow +*/ +static int sep_create_flow_dma_tables_handler(struct sep_device *sep, + unsigned long arg) +{ + int error; + struct sep_driver_build_flow_table_t command_args; + /* first table - output */ + struct sep_lli_entry_t first_table_data; + /* dma table data */ + struct sep_lli_entry_t last_table_data; + /* pointer to the info entry of the previuos DMA table */ + struct sep_lli_entry_t *prev_info_entry_ptr; + /* pointer to the flow data strucutre */ + struct sep_flow_context_t *flow_context_ptr; + + dbg("SEP Driver:--------> sep_create_flow_dma_tables_handler start\n"); + + /* init variables */ + prev_info_entry_ptr = 0; + first_table_data.physical_address = 0xffffffff; + + /* find the free structure for flow data */ + flow_context_ptr = sep_find_flow_context(sep, SEP_FREE_FLOW_ID); + if (flow_context_ptr == NULL) + goto end_function; + + error = copy_from_user(&command_args, (void *) arg, sizeof(struct sep_driver_build_flow_table_t)); + if (error) + goto end_function; + + /* create flow tables */ + error = sep_prepare_flow_dma_tables(sep, command_args.num_virtual_buffers, command_args.virt_buff_data_addr, flow_context_ptr, &first_table_data, &last_table_data, command_args.isKernelVirtualAddress); + if (error) + goto end_function_with_error; + + /* check if flow is static */ + if (!command_args.flow_type) + /* point the info entry of the last to the info entry of the first */ + last_table_data = first_table_data; + + /* set output params */ + command_args.first_table_addr = first_table_data.physical_address; + command_args.first_table_num_entries = ((first_table_data.block_size >> SEP_NUM_ENTRIES_OFFSET_IN_BITS) & SEP_NUM_ENTRIES_MASK); + command_args.first_table_data_size = (first_table_data.block_size & SEP_TABLE_DATA_SIZE_MASK); + + /* send the parameters to user application */ + error = copy_to_user((void *) arg, &command_args, sizeof(struct sep_driver_build_flow_table_t)); + if (error) + goto end_function_with_error; + + /* all the flow created - update the flow entry with temp id */ + flow_context_ptr->flow_id = SEP_TEMP_FLOW_ID; + + /* set the processing tables data in the context */ + if (command_args.input_output_flag == SEP_DRIVER_IN_FLAG) + flow_context_ptr->input_tables_in_process = first_table_data; + else + flow_context_ptr->output_tables_in_process = first_table_data; + + goto end_function; + +end_function_with_error: + /* free the allocated tables */ + sep_deallocated_flow_tables(&first_table_data); +end_function: + dbg("SEP Driver:<-------- sep_create_flow_dma_tables_handler end\n"); + return error; +} + +/* + this function handles add tables to flow +*/ +static int sep_add_flow_tables_handler(struct sep_device *sep, unsigned long arg) +{ + int error; + unsigned long num_entries; + struct sep_driver_add_flow_table_t command_args; + struct sep_flow_context_t *flow_context_ptr; + /* first dma table data */ + struct sep_lli_entry_t first_table_data; + /* last dma table data */ + struct sep_lli_entry_t last_table_data; + /* pointer to the info entry of the current DMA table */ + struct sep_lli_entry_t *info_entry_ptr; + + dbg("SEP Driver:--------> sep_add_flow_tables_handler start\n"); + + /* get input parameters */ + error = copy_from_user(&command_args, (void *) arg, sizeof(struct sep_driver_add_flow_table_t)); + if (error) + goto end_function; + + /* find the flow structure for the flow id */ + flow_context_ptr = sep_find_flow_context(sep, command_args.flow_id); + if (flow_context_ptr == NULL) + goto end_function; + + /* prepare the flow dma tables */ + error = sep_prepare_flow_dma_tables(sep, command_args.num_virtual_buffers, command_args.virt_buff_data_addr, flow_context_ptr, &first_table_data, &last_table_data, command_args.isKernelVirtualAddress); + if (error) + goto end_function_with_error; + + /* now check if there is already an existing add table for this flow */ + if (command_args.inputOutputFlag == SEP_DRIVER_IN_FLAG) { + /* this buffer was for input buffers */ + if (flow_context_ptr->input_tables_flag) { + /* add table already exists - add the new tables to the end + of the previous */ + num_entries = (flow_context_ptr->last_input_table.block_size >> SEP_NUM_ENTRIES_OFFSET_IN_BITS) & SEP_NUM_ENTRIES_MASK; + + info_entry_ptr = (struct sep_lli_entry_t *) + (flow_context_ptr->last_input_table.physical_address + (sizeof(struct sep_lli_entry_t) * (num_entries - 1))); + + /* connect to list of tables */ + *info_entry_ptr = first_table_data; + + /* set the first table data */ + first_table_data = flow_context_ptr->first_input_table; + } else { + /* set the input flag */ + flow_context_ptr->input_tables_flag = 1; + + /* set the first table data */ + flow_context_ptr->first_input_table = first_table_data; + } + /* set the last table data */ + flow_context_ptr->last_input_table = last_table_data; + } else { /* this is output tables */ + + /* this buffer was for input buffers */ + if (flow_context_ptr->output_tables_flag) { + /* add table already exists - add the new tables to + the end of the previous */ + num_entries = (flow_context_ptr->last_output_table.block_size >> SEP_NUM_ENTRIES_OFFSET_IN_BITS) & SEP_NUM_ENTRIES_MASK; + + info_entry_ptr = (struct sep_lli_entry_t *) + (flow_context_ptr->last_output_table.physical_address + (sizeof(struct sep_lli_entry_t) * (num_entries - 1))); + + /* connect to list of tables */ + *info_entry_ptr = first_table_data; + + /* set the first table data */ + first_table_data = flow_context_ptr->first_output_table; + } else { + /* set the input flag */ + flow_context_ptr->output_tables_flag = 1; + + /* set the first table data */ + flow_context_ptr->first_output_table = first_table_data; + } + /* set the last table data */ + flow_context_ptr->last_output_table = last_table_data; + } + + /* set output params */ + command_args.first_table_addr = first_table_data.physical_address; + command_args.first_table_num_entries = ((first_table_data.block_size >> SEP_NUM_ENTRIES_OFFSET_IN_BITS) & SEP_NUM_ENTRIES_MASK); + command_args.first_table_data_size = (first_table_data.block_size & SEP_TABLE_DATA_SIZE_MASK); + + /* send the parameters to user application */ + error = copy_to_user((void *) arg, &command_args, sizeof(struct sep_driver_add_flow_table_t)); +end_function_with_error: + /* free the allocated tables */ + sep_deallocated_flow_tables(&first_table_data); +end_function: + dbg("SEP Driver:<-------- sep_add_flow_tables_handler end\n"); + return error; +} + +/* + this function add the flow add message to the specific flow +*/ +static int sep_add_flow_tables_message_handler(struct sep_device *sep, unsigned long arg) +{ + int error; + struct sep_driver_add_message_t command_args; + struct sep_flow_context_t *flow_context_ptr; + + dbg("SEP Driver:--------> sep_add_flow_tables_message_handler start\n"); + + error = copy_from_user(&command_args, (void *) arg, sizeof(struct sep_driver_add_message_t)); + if (error) + goto end_function; + + /* check input */ + if (command_args.message_size_in_bytes > SEP_MAX_ADD_MESSAGE_LENGTH_IN_BYTES) { + error = -ENOMEM; + goto end_function; + } + + /* find the flow context */ + flow_context_ptr = sep_find_flow_context(sep, command_args.flow_id); + if (flow_context_ptr == NULL) + goto end_function; + + /* copy the message into context */ + flow_context_ptr->message_size_in_bytes = command_args.message_size_in_bytes; + error = copy_from_user(flow_context_ptr->message, (void *) command_args.message_address, command_args.message_size_in_bytes); +end_function: + dbg("SEP Driver:<-------- sep_add_flow_tables_message_handler end\n"); + return error; +} + + +/* + this function returns the bus and virtual addresses of the static pool +*/ +static int sep_get_static_pool_addr_handler(struct sep_device *sep, unsigned long arg) +{ + int error; + struct sep_driver_static_pool_addr_t command_args; + + dbg("SEP Driver:--------> sep_get_static_pool_addr_handler start\n"); + + /*prepare the output parameters in the struct */ + command_args.physical_static_address = sep->shared_bus + SEP_DRIVER_STATIC_AREA_OFFSET_IN_BYTES; + command_args.virtual_static_address = (unsigned long)sep->shared_addr + SEP_DRIVER_STATIC_AREA_OFFSET_IN_BYTES; + + edbg("SEP Driver:bus_static_address is %08lx, virtual_static_address %08lx\n", command_args.physical_static_address, command_args.virtual_static_address); + + /* send the parameters to user application */ + error = copy_to_user((void *) arg, &command_args, sizeof(struct sep_driver_static_pool_addr_t)); + dbg("SEP Driver:<-------- sep_get_static_pool_addr_handler end\n"); + return error; +} + +/* + this address gets the offset of the physical address from the start + of the mapped area +*/ +static int sep_get_physical_mapped_offset_handler(struct sep_device *sep, unsigned long arg) +{ + int error; + struct sep_driver_get_mapped_offset_t command_args; + + dbg("SEP Driver:--------> sep_get_physical_mapped_offset_handler start\n"); + + error = copy_from_user(&command_args, (void *) arg, sizeof(struct sep_driver_get_mapped_offset_t)); + if (error) + goto end_function; + + if (command_args.physical_address < sep->shared_bus) { + error = -EINVAL; + goto end_function; + } + + /*prepare the output parameters in the struct */ + command_args.offset = command_args.physical_address - sep->shared_bus; + + edbg("SEP Driver:bus_address is %08lx, offset is %lu\n", command_args.physical_address, command_args.offset); + + /* send the parameters to user application */ + error = copy_to_user((void *) arg, &command_args, sizeof(struct sep_driver_get_mapped_offset_t)); +end_function: + dbg("SEP Driver:<-------- sep_get_physical_mapped_offset_handler end\n"); + return error; +} + + +/* + ? +*/ +static int sep_start_handler(struct sep_device *sep) +{ + unsigned long reg_val; + unsigned long error = 0; + + dbg("SEP Driver:--------> sep_start_handler start\n"); + + /* wait in polling for message from SEP */ + do + reg_val = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR3_REG_ADDR); + while (!reg_val); + + /* check the value */ + if (reg_val == 0x1) + /* fatal error - read error status from GPRO */ + error = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR0_REG_ADDR); + dbg("SEP Driver:<-------- sep_start_handler end\n"); + return error; +} + +/* + this function handles the request for SEP initialization +*/ +static int sep_init_handler(struct sep_device *sep, unsigned long arg) +{ + unsigned long message_word; + unsigned long *message_ptr; + struct sep_driver_init_t command_args; + unsigned long counter; + unsigned long error; + unsigned long reg_val; + + dbg("SEP Driver:--------> sep_init_handler start\n"); + error = 0; + + error = copy_from_user(&command_args, (void *) arg, sizeof(struct sep_driver_init_t)); + + dbg("SEP Driver:--------> sep_init_handler - finished copy_from_user \n"); + + if (error) + goto end_function; + + /* PATCH - configure the DMA to single -burst instead of multi-burst */ + /*sep_configure_dma_burst(); */ + + dbg("SEP Driver:--------> sep_init_handler - finished sep_configure_dma_burst \n"); + + message_ptr = (unsigned long *) command_args.message_addr; + + /* set the base address of the SRAM */ + sep_write_reg(sep, HW_SRAM_ADDR_REG_ADDR, HW_CC_SRAM_BASE_ADDRESS); + + for (counter = 0; counter < command_args.message_size_in_words; counter++, message_ptr++) { + get_user(message_word, message_ptr); + /* write data to SRAM */ + sep_write_reg(sep, HW_SRAM_DATA_REG_ADDR, message_word); + edbg("SEP Driver:message_word is %lu\n", message_word); + /* wait for write complete */ + sep_wait_sram_write(sep); + } + dbg("SEP Driver:--------> sep_init_handler - finished getting messages from user space\n"); + /* signal SEP */ + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR0_REG_ADDR, 0x1); + + do + reg_val = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR3_REG_ADDR); + while (!(reg_val & 0xFFFFFFFD)); + + dbg("SEP Driver:--------> sep_init_handler - finished waiting for reg_val & 0xFFFFFFFD \n"); + + /* check the value */ + if (reg_val == 0x1) { + edbg("SEP Driver:init failed\n"); + + error = sep_read_reg(sep, 0x8060); + edbg("SEP Driver:sw monitor is %lu\n", error); + + /* fatal error - read erro status from GPRO */ + error = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR0_REG_ADDR); + edbg("SEP Driver:error is %lu\n", error); + } +end_function: + dbg("SEP Driver:<-------- sep_init_handler end\n"); + return error; + +} + +/* + this function handles the request cache and resident reallocation +*/ +static int sep_realloc_cache_resident_handler(struct sep_device *sep, + unsigned long arg) +{ + struct sep_driver_realloc_cache_resident_t command_args; + int error; + + /* copy cache and resident to the their intended locations */ + error = sep_load_firmware(sep); + if (error) + return error; + + command_args.new_base_addr = sep->shared_bus; + + /* find the new base address according to the lowest address between + cache, resident and shared area */ + if (sep->resident_bus < command_args.new_base_addr) + command_args.new_base_addr = sep->resident_bus; + if (sep->rar_bus < command_args.new_base_addr) + command_args.new_base_addr = sep->rar_bus; + + /* set the return parameters */ + command_args.new_cache_addr = sep->rar_bus; + command_args.new_resident_addr = sep->resident_bus; + + /* set the new shared area */ + command_args.new_shared_area_addr = sep->shared_bus; + + edbg("SEP Driver:command_args.new_shared_addr is %08llx\n", command_args.new_shared_area_addr); + edbg("SEP Driver:command_args.new_base_addr is %08llx\n", command_args.new_base_addr); + edbg("SEP Driver:command_args.new_resident_addr is %08llx\n", command_args.new_resident_addr); + edbg("SEP Driver:command_args.new_rar_addr is %08llx\n", command_args.new_cache_addr); + + /* return to user */ + if (copy_to_user((void *) arg, &command_args, sizeof(struct sep_driver_realloc_cache_resident_t))) + return -EFAULT; + return 0; +} + +/** + * sep_get_time_handler - time request from user space + * @sep: sep we are to set the time for + * @arg: pointer to user space arg buffer + * + * This function reports back the time and the address in the SEP + * shared buffer at which it has been placed. (Do we really need this!!!) + */ + +static int sep_get_time_handler(struct sep_device *sep, unsigned long arg) +{ + struct sep_driver_get_time_t command_args; + + mutex_lock(&sep_mutex); + command_args.time_value = sep_set_time(sep); + command_args.time_physical_address = (unsigned long)sep_time_address(sep); + mutex_unlock(&sep_mutex); + if (copy_to_user((void __user *)arg, + &command_args, sizeof(struct sep_driver_get_time_t))) + return -EFAULT; + return 0; + +} + +/* + This API handles the end transaction request +*/ +static int sep_end_transaction_handler(struct sep_device *sep, unsigned long arg) +{ + dbg("SEP Driver:--------> sep_end_transaction_handler start\n"); + +#if 0 /*!SEP_DRIVER_POLLING_MODE */ + /* close IMR */ + sep_write_reg(sep, HW_HOST_IMR_REG_ADDR, 0x7FFF); + + /* release IRQ line */ + free_irq(SEP_DIRVER_IRQ_NUM, sep); + + /* lock the sep mutex */ + mutex_unlock(&sep_mutex); +#endif + + dbg("SEP Driver:<-------- sep_end_transaction_handler end\n"); + + return 0; +} + + +/** + * sep_set_flow_id_handler - handle flow setting + * @sep: the SEP we are configuring + * @flow_id: the flow we are setting + * + * This function handler the set flow id command + */ +static int sep_set_flow_id_handler(struct sep_device *sep, + unsigned long flow_id) +{ + int error = 0; + struct sep_flow_context_t *flow_data_ptr; + + /* find the flow data structure that was just used for creating new flow + - its id should be default */ + + mutex_lock(&sep_mutex); + flow_data_ptr = sep_find_flow_context(sep, SEP_TEMP_FLOW_ID); + if (flow_data_ptr) + flow_data_ptr->flow_id = flow_id; /* set flow id */ + else + error = -EINVAL; + mutex_unlock(&sep_mutex); + return error; +} + +static int sep_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + int error = 0; + struct sep_device *sep = filp->private_data; + + dbg("------------>SEP Driver: ioctl start\n"); + + edbg("SEP Driver: cmd is %x\n", cmd); + + switch (cmd) { + case SEP_IOCSENDSEPCOMMAND: + /* send command to SEP */ + sep_send_command_handler(sep); + edbg("SEP Driver: after sep_send_command_handler\n"); + break; + case SEP_IOCSENDSEPRPLYCOMMAND: + /* send reply command to SEP */ + sep_send_reply_command_handler(sep); + break; + case SEP_IOCALLOCDATAPOLL: + /* allocate data pool */ + error = sep_allocate_data_pool_memory_handler(sep, arg); + break; + case SEP_IOCWRITEDATAPOLL: + /* write data into memory pool */ + error = sep_write_into_data_pool_handler(sep, arg); + break; + case SEP_IOCREADDATAPOLL: + /* read data from data pool into application memory */ + error = sep_read_from_data_pool_handler(sep, arg); + break; + case SEP_IOCCREATESYMDMATABLE: + /* create dma table for synhronic operation */ + error = sep_create_sync_dma_tables_handler(sep, arg); + break; + case SEP_IOCCREATEFLOWDMATABLE: + /* create flow dma tables */ + error = sep_create_flow_dma_tables_handler(sep, arg); + break; + case SEP_IOCFREEDMATABLEDATA: + /* free the pages */ + error = sep_free_dma_table_data_handler(sep); + break; + case SEP_IOCSETFLOWID: + /* set flow id */ + error = sep_set_flow_id_handler(sep, (unsigned long)arg); + break; + case SEP_IOCADDFLOWTABLE: + /* add tables to the dynamic flow */ + error = sep_add_flow_tables_handler(sep, arg); + break; + case SEP_IOCADDFLOWMESSAGE: + /* add message of add tables to flow */ + error = sep_add_flow_tables_message_handler(sep, arg); + break; + case SEP_IOCSEPSTART: + /* start command to sep */ + error = sep_start_handler(sep); + break; + case SEP_IOCSEPINIT: + /* init command to sep */ + error = sep_init_handler(sep, arg); + break; + case SEP_IOCGETSTATICPOOLADDR: + /* get the physical and virtual addresses of the static pool */ + error = sep_get_static_pool_addr_handler(sep, arg); + break; + case SEP_IOCENDTRANSACTION: + error = sep_end_transaction_handler(sep, arg); + break; + case SEP_IOCREALLOCCACHERES: + error = sep_realloc_cache_resident_handler(sep, arg); + break; + case SEP_IOCGETMAPPEDADDROFFSET: + error = sep_get_physical_mapped_offset_handler(sep, arg); + break; + case SEP_IOCGETIME: + error = sep_get_time_handler(sep, arg); + break; + default: + error = -ENOTTY; + break; + } + dbg("SEP Driver:<-------- ioctl end\n"); + return error; +} + + + +#if !SEP_DRIVER_POLLING_MODE + +/* handler for flow done interrupt */ + +static void sep_flow_done_handler(struct work_struct *work) +{ + struct sep_flow_context_t *flow_data_ptr; + + /* obtain the mutex */ + mutex_lock(&sep_mutex); + + /* get the pointer to context */ + flow_data_ptr = (struct sep_flow_context_t *) work; + + /* free all the current input tables in sep */ + sep_deallocated_flow_tables(&flow_data_ptr->input_tables_in_process); + + /* free all the current tables output tables in SEP (if needed) */ + if (flow_data_ptr->output_tables_in_process.physical_address != 0xffffffff) + sep_deallocated_flow_tables(&flow_data_ptr->output_tables_in_process); + + /* check if we have additional tables to be sent to SEP only input + flag may be checked */ + if (flow_data_ptr->input_tables_flag) { + /* copy the message to the shared RAM and signal SEP */ + memcpy((void *) flow_data_ptr->message, (void *) sep->shared_addr, flow_data_ptr->message_size_in_bytes); + + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR2_REG_ADDR, 0x2); + } + mutex_unlock(&sep_mutex); +} +/* + interrupt handler function +*/ +static irqreturn_t sep_inthandler(int irq, void *dev_id) +{ + irqreturn_t int_error; + unsigned long reg_val; + unsigned long flow_id; + struct sep_flow_context_t *flow_context_ptr; + struct sep_device *sep = dev_id; + + int_error = IRQ_HANDLED; + + /* read the IRR register to check if this is SEP interrupt */ + reg_val = sep_read_reg(sep, HW_HOST_IRR_REG_ADDR); + edbg("SEP Interrupt - reg is %08lx\n", reg_val); + + /* check if this is the flow interrupt */ + if (0 /*reg_val & (0x1 << 11) */ ) { + /* read GPRO to find out the which flow is done */ + flow_id = sep_read_reg(sep, HW_HOST_IRR_REG_ADDR); + + /* find the contex of the flow */ + flow_context_ptr = sep_find_flow_context(sep, flow_id >> 28); + if (flow_context_ptr == NULL) + goto end_function_with_error; + + /* queue the work */ + INIT_WORK(&flow_context_ptr->flow_wq, sep_flow_done_handler); + queue_work(sep->flow_wq, &flow_context_ptr->flow_wq); + + } else { + /* check if this is reply interrupt from SEP */ + if (reg_val & (0x1 << 13)) { + /* update the counter of reply messages */ + sep->reply_ct++; + /* wake up the waiting process */ + wake_up(&sep_event); + } else { + int_error = IRQ_NONE; + goto end_function; + } + } +end_function_with_error: + /* clear the interrupt */ + sep_write_reg(sep, HW_HOST_ICR_REG_ADDR, reg_val); +end_function: + return int_error; +} + +#endif + + + +#if 0 + +static void sep_wait_busy(struct sep_device *sep) +{ + u32 reg; + + do { + reg = sep_read_reg(sep, HW_HOST_SEP_BUSY_REG_ADDR); + } while (reg); +} + +/* + PATCH for configuring the DMA to single burst instead of multi-burst +*/ +static void sep_configure_dma_burst(struct sep_device *sep) +{ +#define HW_AHB_RD_WR_BURSTS_REG_ADDR 0x0E10UL + + dbg("SEP Driver:<-------- sep_configure_dma_burst start \n"); + + /* request access to registers from SEP */ + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR0_REG_ADDR, 0x2); + + dbg("SEP Driver:<-------- sep_configure_dma_burst finished request access to registers from SEP (write reg) \n"); + + sep_wait_busy(sep); + + dbg("SEP Driver:<-------- sep_configure_dma_burst finished request access to registers from SEP (while(revVal) wait loop) \n"); + + /* set the DMA burst register to single burst */ + sep_write_reg(sep, HW_AHB_RD_WR_BURSTS_REG_ADDR, 0x0UL); + + /* release the sep busy */ + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR0_REG_ADDR, 0x0UL); + sep_wait_busy(sep); + + dbg("SEP Driver:<-------- sep_configure_dma_burst done \n"); + +} + +#endif + +/* + Function that is activaed on the succesful probe of the SEP device +*/ +static int __devinit sep_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int error = 0; + struct sep_device *sep; + int counter; + int size; /* size of memory for allocation */ + + edbg("Sep pci probe starting\n"); + if (sep_dev != NULL) { + dev_warn(&pdev->dev, "only one SEP supported.\n"); + return -EBUSY; + } + + /* enable the device */ + error = pci_enable_device(pdev); + if (error) { + edbg("error enabling pci device\n"); + goto end_function; + } + + /* set the pci dev pointer */ + sep_dev = &sep_instance; + sep = &sep_instance; + + edbg("sep->shared_addr = %p\n", sep->shared_addr); + /* transaction counter that coordinates the transactions between SEP + and HOST */ + sep->send_ct = 0; + /* counter for the messages from sep */ + sep->reply_ct = 0; + /* counter for the number of bytes allocated in the pool + for the current transaction */ + sep->data_pool_bytes_allocated = 0; + + /* calculate the total size for allocation */ + size = SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES + + SEP_DRIVER_SYNCHRONIC_DMA_TABLES_AREA_SIZE_IN_BYTES + SEP_DRIVER_DATA_POOL_SHARED_AREA_SIZE_IN_BYTES + SEP_DRIVER_FLOW_DMA_TABLES_AREA_SIZE_IN_BYTES + SEP_DRIVER_STATIC_AREA_SIZE_IN_BYTES + SEP_DRIVER_SYSTEM_DATA_MEMORY_SIZE_IN_BYTES; + + /* allocate the shared area */ + if (sep_map_and_alloc_shared_area(sep, size)) { + error = -ENOMEM; + /* allocation failed */ + goto end_function_error; + } + /* now set the memory regions */ +#if (SEP_DRIVER_RECONFIG_MESSAGE_AREA == 1) + /* Note: this test section will need moving before it could ever + work as the registers are not yet mapped ! */ + /* send the new SHARED MESSAGE AREA to the SEP */ + sep_write_reg(sep, HW_HOST_HOST_SEP_GPR1_REG_ADDR, sep->shared_bus); + + /* poll for SEP response */ + retval = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR1_REG_ADDR); + while (retval != 0xffffffff && retval != sep->shared_bus) + retval = sep_read_reg(sep, HW_HOST_SEP_HOST_GPR1_REG_ADDR); + + /* check the return value (register) */ + if (retval != sep->shared_bus) { + error = -ENOMEM; + goto end_function_deallocate_sep_shared_area; + } +#endif + /* init the flow contextes */ + for (counter = 0; counter < SEP_DRIVER_NUM_FLOWS; counter++) + sep->flows[counter].flow_id = SEP_FREE_FLOW_ID; + + sep->flow_wq = create_singlethread_workqueue("sepflowwq"); + if (sep->flow_wq == NULL) { + error = -ENOMEM; + edbg("sep_driver:flow queue creation failed\n"); + goto end_function_deallocate_sep_shared_area; + } + edbg("SEP Driver: create flow workqueue \n"); + sep->pdev = pci_dev_get(pdev); + + sep->reg_addr = pci_ioremap_bar(pdev, 0); + if (!sep->reg_addr) { + edbg("sep: ioremap of registers failed.\n"); + goto end_function_deallocate_sep_shared_area; + } + edbg("SEP Driver:reg_addr is %p\n", sep->reg_addr); + + /* load the rom code */ + sep_load_rom_code(sep); + + /* set up system base address and shared memory location */ + sep->rar_addr = dma_alloc_coherent(&sep->pdev->dev, + 2 * SEP_RAR_IO_MEM_REGION_SIZE, + &sep->rar_bus, GFP_KERNEL); + + if (!sep->rar_addr) { + edbg("SEP Driver:can't allocate rar\n"); + goto end_function_uniomap; + } + + + edbg("SEP Driver:rar_bus is %08llx\n", (unsigned long long)sep->rar_bus); + edbg("SEP Driver:rar_virtual is %p\n", sep->rar_addr); + +#if !SEP_DRIVER_POLLING_MODE + + edbg("SEP Driver: about to write IMR and ICR REG_ADDR\n"); + + /* 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))); + + edbg("SEP Driver: about to call request_irq\n"); + /* get the interrupt line */ + error = request_irq(pdev->irq, sep_inthandler, IRQF_SHARED, "sep_driver", sep); + if (error) + goto end_function_free_res; + return 0; + edbg("SEP Driver: about to write IMR REG_ADDR"); + + /* set the IMR register - open only GPR 2 */ + sep_write_reg(sep, HW_HOST_IMR_REG_ADDR, (~(0x1 << 13))); + +end_function_free_res: + dma_free_coherent(&sep->pdev->dev, 2 * SEP_RAR_IO_MEM_REGION_SIZE, + sep->rar_addr, sep->rar_bus); +#endif /* SEP_DRIVER_POLLING_MODE */ +end_function_uniomap: + iounmap(sep->reg_addr); +end_function_deallocate_sep_shared_area: + /* de-allocate shared area */ + sep_unmap_and_free_shared_area(sep, size); +end_function_error: + sep_dev = NULL; +end_function: + return error; +} + +static struct pci_device_id sep_pci_id_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080c)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, sep_pci_id_tbl); + +/* field for registering driver to PCI device */ +static struct pci_driver sep_pci_driver = { + .name = "sep_sec_driver", + .id_table = sep_pci_id_tbl, + .probe = sep_probe + /* FIXME: remove handler */ +}; + +/* major and minor device numbers */ +static dev_t sep_devno; + +/* the files operations structure of the driver */ +static struct file_operations sep_file_operations = { + .owner = THIS_MODULE, + .ioctl = sep_ioctl, + .poll = sep_poll, + .open = sep_open, + .release = sep_release, + .mmap = sep_mmap, +}; + + +/* cdev struct of the driver */ +static struct cdev sep_cdev; + +/* + this function registers the driver to the file system +*/ +static int sep_register_driver_to_fs(void) +{ + int ret_val = alloc_chrdev_region(&sep_devno, 0, 1, "sep_sec_driver"); + if (ret_val) { + edbg("sep: major number allocation failed, retval is %d\n", + ret_val); + return ret_val; + } + /* init cdev */ + cdev_init(&sep_cdev, &sep_file_operations); + sep_cdev.owner = THIS_MODULE; + + /* register the driver with the kernel */ + ret_val = cdev_add(&sep_cdev, sep_devno, 1); + if (ret_val) { + edbg("sep_driver:cdev_add failed, retval is %d\n", ret_val); + /* unregister dev numbers */ + unregister_chrdev_region(sep_devno, 1); + } + return ret_val; +} + + +/*-------------------------------------------------------------- + init function +----------------------------------------------------------------*/ +static int __init sep_init(void) +{ + int ret_val = 0; + dbg("SEP Driver:-------->Init start\n"); + /* FIXME: Probe can occur before we are ready to survive a probe */ + ret_val = pci_register_driver(&sep_pci_driver); + if (ret_val) { + edbg("sep_driver:sep_driver_to_device failed, ret_val is %d\n", ret_val); + goto end_function_unregister_from_fs; + } + /* register driver to fs */ + ret_val = sep_register_driver_to_fs(); + if (ret_val) + goto end_function_unregister_pci; + goto end_function; +end_function_unregister_pci: + pci_unregister_driver(&sep_pci_driver); +end_function_unregister_from_fs: + /* unregister from fs */ + cdev_del(&sep_cdev); + /* unregister dev numbers */ + unregister_chrdev_region(sep_devno, 1); +end_function: + dbg("SEP Driver:<-------- Init end\n"); + return ret_val; +} + + +/*------------------------------------------------------------- + exit function +--------------------------------------------------------------*/ +static void __exit sep_exit(void) +{ + int size; + + dbg("SEP Driver:--------> Exit start\n"); + + /* unregister from fs */ + cdev_del(&sep_cdev); + /* unregister dev numbers */ + unregister_chrdev_region(sep_devno, 1); + /* calculate the total size for de-allocation */ + size = SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES + + SEP_DRIVER_SYNCHRONIC_DMA_TABLES_AREA_SIZE_IN_BYTES + SEP_DRIVER_DATA_POOL_SHARED_AREA_SIZE_IN_BYTES + SEP_DRIVER_FLOW_DMA_TABLES_AREA_SIZE_IN_BYTES + SEP_DRIVER_STATIC_AREA_SIZE_IN_BYTES + SEP_DRIVER_SYSTEM_DATA_MEMORY_SIZE_IN_BYTES; + /* FIXME: We need to do this in the unload for the device */ + /* free shared area */ + if (sep_dev) { + sep_unmap_and_free_shared_area(sep_dev, size); + edbg("SEP Driver: free pages SEP SHARED AREA \n"); + iounmap((void *) sep_dev->reg_addr); + edbg("SEP Driver: iounmap \n"); + } + edbg("SEP Driver: release_mem_region \n"); + dbg("SEP Driver:<-------- Exit end\n"); +} + + +module_init(sep_init); +module_exit(sep_exit); + +MODULE_LICENSE("GPL"); |