237 lines
5.2 KiB
C
Executable File
237 lines
5.2 KiB
C
Executable File
/*
|
|
* Copyright (C) 2012-2018 Samsung Electronics, Inc.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <asm/pgtable.h>
|
|
|
|
#include "tzdev.h"
|
|
#include "tz_iwio.h"
|
|
#include "tzlog.h"
|
|
|
|
struct iw_channel {
|
|
struct list_head list;
|
|
void *vaddr;
|
|
struct page **pages;
|
|
unsigned int num_pages;
|
|
};
|
|
|
|
static LIST_HEAD(iw_channel_list);
|
|
static DEFINE_SPINLOCK(iw_channel_list_lock);
|
|
|
|
#ifdef CONFIG_TZDEV_SK_MULTICORE
|
|
|
|
static DEFINE_PER_CPU(struct tz_iwio_aux_channel *, aux_channel);
|
|
|
|
#define aux_channel_get(ch) get_cpu_var(ch)
|
|
#define aux_channel_put(ch) put_cpu_var(ch)
|
|
#define aux_channel_init(ch, cpu) per_cpu(ch, cpu)
|
|
|
|
#else /* CONFIG_TZDEV_SK_MULTICORE */
|
|
|
|
static DEFINE_MUTEX(aux_channel_lock);
|
|
static struct tz_iwio_aux_channel *aux_channel[1];
|
|
|
|
static struct tz_iwio_aux_channel *aux_channel_get(struct tz_iwio_aux_channel *ch[])
|
|
{
|
|
mutex_lock(&aux_channel_lock);
|
|
return ch[0];
|
|
}
|
|
|
|
#define aux_channel_put(ch) mutex_unlock(&aux_channel_lock)
|
|
#define aux_channel_init(ch, cpu) ch[cpu]
|
|
|
|
#endif /* CONFIG_TZDEV_SK_MULTICORE */
|
|
|
|
struct tz_iwio_aux_channel *tz_iwio_get_aux_channel(void)
|
|
{
|
|
return aux_channel_get(aux_channel);
|
|
}
|
|
|
|
void tz_iwio_put_aux_channel(void)
|
|
{
|
|
aux_channel_put(aux_channel);
|
|
}
|
|
|
|
int tz_iwio_alloc_aux_channel(int cpu)
|
|
{
|
|
struct tz_iwio_aux_channel *ch;
|
|
struct page *page;
|
|
int ret;
|
|
|
|
page = alloc_page(GFP_KERNEL);
|
|
if (!page)
|
|
return -ENOMEM;
|
|
|
|
ch = page_address(page);
|
|
|
|
pr_debug("AUX Channel[%d] = 0x%pK\n", cpu, ch);
|
|
|
|
ret = tzdev_smc_connect_aux(page_to_pfn(page));
|
|
if (ret) {
|
|
__free_page(page);
|
|
return ret;
|
|
}
|
|
|
|
if (cpu_possible(cpu))
|
|
aux_channel_init(aux_channel, cpu) = ch;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void *tz_iwio_alloc_iw_channel(unsigned int mode, unsigned int num_pages,
|
|
tz_iwio_callback_t pre_callback, tz_iwio_callback_cleanup_t pre_callback_cleanup,
|
|
void *callback_data)
|
|
{
|
|
void *buffer;
|
|
struct page **pages;
|
|
sk_pfn_t *pfns;
|
|
struct tz_iwio_aux_channel *aux_ch;
|
|
struct iw_channel *new_ch;
|
|
unsigned int i, j;
|
|
unsigned long offset, num_pfns;
|
|
unsigned long pfns_in_buf = TZ_IWIO_AUX_BUF_SIZE / sizeof(sk_pfn_t);
|
|
int ret;
|
|
|
|
might_sleep();
|
|
|
|
new_ch = kmalloc(sizeof(struct iw_channel), GFP_KERNEL);
|
|
if (!new_ch) {
|
|
tzdev_print(0, "TZDev iw channel structure allocation failed\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
pages = kcalloc(num_pages, sizeof(struct page *), GFP_KERNEL);
|
|
if (!pages) {
|
|
tzdev_print(0, "TZDev IW buffer pages allocation failed\n");
|
|
ret = -ENOMEM;
|
|
goto free_iw_channel_structure;
|
|
}
|
|
|
|
pfns = kmalloc(num_pages * sizeof(sk_pfn_t), GFP_KERNEL);
|
|
if (!pfns) {
|
|
tzdev_print(0, "TZDev IW buffer pfns allocation failed\n");
|
|
ret = -ENOMEM;
|
|
goto free_pages_arr;
|
|
}
|
|
|
|
/* Allocate non-contiguous buffer to reduce page allocator pressure */
|
|
for (i = 0; i < num_pages; i++) {
|
|
pages[i] = alloc_page(GFP_KERNEL);
|
|
if (!pages[i]) {
|
|
tzdev_print(0, "TZDev IW buffer creation failed\n");
|
|
ret = -ENOMEM;
|
|
goto free_pfns_arr;
|
|
}
|
|
pfns[i] = page_to_pfn(pages[i]);
|
|
}
|
|
|
|
buffer = vmap(pages, num_pages, VM_MAP, PAGE_KERNEL);
|
|
if (!buffer) {
|
|
tzdev_print(0, "TZDev IW buffer mapping failed\n");
|
|
ret = -EINVAL;
|
|
goto free_pfns_arr;
|
|
}
|
|
|
|
/* Call pre-callback */
|
|
if (pre_callback &&
|
|
pre_callback(buffer, num_pages, callback_data)) {
|
|
ret = -EINVAL;
|
|
goto unmap_buffer;
|
|
}
|
|
|
|
/* Push PFNs list into aux channel */
|
|
aux_ch = tz_iwio_get_aux_channel();
|
|
|
|
for (offset = 0; offset < num_pages; offset += pfns_in_buf) {
|
|
num_pfns = min(pfns_in_buf, num_pages - offset);
|
|
|
|
memcpy(aux_ch->buffer, pfns + offset,
|
|
num_pfns * sizeof(sk_pfn_t));
|
|
|
|
if ((ret = tzdev_smc_connect(mode, num_pfns))) {
|
|
tz_iwio_put_aux_channel();
|
|
tzdev_print(0, "TZDev IW buffer registration failed\n");
|
|
goto pre_callback_cleanup;
|
|
}
|
|
}
|
|
|
|
new_ch->pages = pages;
|
|
new_ch->num_pages = num_pages;
|
|
new_ch->vaddr = buffer;
|
|
|
|
/* Add new channel to global list */
|
|
spin_lock(&iw_channel_list_lock);
|
|
list_add_tail(&new_ch->list, &iw_channel_list);
|
|
spin_unlock(&iw_channel_list_lock);
|
|
|
|
tz_iwio_put_aux_channel();
|
|
|
|
kfree(pfns);
|
|
|
|
return buffer;
|
|
|
|
pre_callback_cleanup:
|
|
if (pre_callback_cleanup)
|
|
pre_callback_cleanup(buffer, num_pages, callback_data);
|
|
|
|
unmap_buffer:
|
|
vunmap(buffer);
|
|
|
|
free_pfns_arr:
|
|
kfree(pfns);
|
|
|
|
for (j = 0; j < i; j++)
|
|
__free_page(pages[j]);
|
|
|
|
free_pages_arr:
|
|
kfree(pages);
|
|
|
|
free_iw_channel_structure:
|
|
kfree(new_ch);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
void tz_iwio_free_iw_channel(void *ch)
|
|
{
|
|
struct iw_channel *iw;
|
|
unsigned int i, found = 0;
|
|
|
|
spin_lock(&iw_channel_list_lock);
|
|
list_for_each_entry(iw, &iw_channel_list, list) {
|
|
if (iw->vaddr == ch) {
|
|
list_del(&iw->list);
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock(&iw_channel_list_lock);
|
|
|
|
if (!found)
|
|
return;
|
|
|
|
vunmap(ch);
|
|
|
|
for (i = 0; i < iw->num_pages; i++)
|
|
__free_page(iw->pages[i]);
|
|
|
|
kfree(iw->pages);
|
|
kfree(iw);
|
|
}
|