blob: bf62634fcc6bff97d09bd03c5b0f1a79248d60ce [file] [log] [blame]
/* Copyright (c) 2012-2014, The Linux Foundation. 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 version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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 "ipa_i.h"
static const u32 ipa_hdr_bin_sz[IPA_HDR_BIN_MAX] = { 8, 16, 24, 36, 58};
/**
* ipa_generate_hdr_hw_tbl() - generates the headers table
* @mem: [out] buffer to put the header table
*
* Returns: 0 on success, negative on failure
*/
static int ipa_generate_hdr_hw_tbl(struct ipa_mem_buffer *mem)
{
struct ipa_hdr_entry *entry;
mem->size = ipa_ctx->hdr_tbl.end;
if (mem->size == 0) {
IPAERR("hdr tbl empty\n");
return -EPERM;
}
IPADBG("tbl_sz=%d\n", ipa_ctx->hdr_tbl.end);
mem->base = dma_alloc_coherent(ipa_ctx->pdev, mem->size,
&mem->phys_base, GFP_KERNEL);
if (!mem->base) {
IPAERR("fail to alloc DMA buff of size %d\n", mem->size);
return -ENOMEM;
}
memset(mem->base, 0, mem->size);
list_for_each_entry(entry, &ipa_ctx->hdr_tbl.head_hdr_entry_list,
link) {
IPADBG("hdr of len %d ofst=%d\n", entry->hdr_len,
entry->offset_entry->offset);
memcpy(mem->base + entry->offset_entry->offset, entry->hdr,
entry->hdr_len);
}
return 0;
}
/*
* __ipa_commit_hdr() commits hdr to hardware
* This function needs to be called with a locked mutex.
*/
int __ipa_commit_hdr_v1(void)
{
struct ipa_desc desc = { 0 };
struct ipa_mem_buffer *mem;
struct ipa_hdr_init_local *cmd;
u16 len;
mem = kmalloc(sizeof(struct ipa_mem_buffer), GFP_KERNEL);
if (!mem) {
IPAERR("failed to alloc memory object\n");
goto fail_alloc_mem;
}
/* the immediate command param size is same for both local and system */
len = sizeof(struct ipa_hdr_init_local);
/*
* we can use init_local ptr for init_system due to layout of the
* struct
*/
cmd = kmalloc(len, GFP_KERNEL);
if (!cmd) {
IPAERR("failed to alloc immediate command object\n");
goto fail_alloc_cmd;
}
if (ipa_generate_hdr_hw_tbl(mem)) {
IPAERR("fail to generate HDR HW TBL\n");
goto fail_hw_tbl_gen;
}
if (ipa_ctx->hdr_tbl_lcl) {
if (mem->size > IPA_v1_RAM_HDR_SIZE) {
IPAERR("tbl too big, needed %d avail %d\n", mem->size,
IPA_v1_RAM_HDR_SIZE);
goto fail_send_cmd;
}
} else {
if (mem->size > IPA_RAM_HDR_SIZE_DDR) {
IPAERR("tbl too big, needed %d avail %d\n", mem->size,
IPA_RAM_HDR_SIZE_DDR);
goto fail_send_cmd;
}
}
cmd->hdr_table_src_addr = mem->phys_base;
if (ipa_ctx->hdr_tbl_lcl) {
cmd->size_hdr_table = mem->size;
cmd->hdr_table_dst_addr = IPA_v1_RAM_HDR_OFST;
desc.opcode = IPA_HDR_INIT_LOCAL;
} else {
desc.opcode = IPA_HDR_INIT_SYSTEM;
}
desc.pyld = cmd;
desc.len = sizeof(struct ipa_hdr_init_local);
desc.type = IPA_IMM_CMD_DESC;
IPA_DUMP_BUFF(mem->base, mem->phys_base, mem->size);
if (ipa_send_cmd(1, &desc)) {
IPAERR("fail to send immediate command\n");
goto fail_send_cmd;
}
if (ipa_ctx->hdr_tbl_lcl) {
dma_free_coherent(ipa_ctx->pdev, mem->size, mem->base,
mem->phys_base);
} else {
if (ipa_ctx->hdr_mem.phys_base) {
dma_free_coherent(ipa_ctx->pdev, ipa_ctx->hdr_mem.size,
ipa_ctx->hdr_mem.base,
ipa_ctx->hdr_mem.phys_base);
}
ipa_ctx->hdr_mem = *mem;
}
kfree(cmd);
kfree(mem);
return 0;
fail_send_cmd:
if (mem->base)
dma_free_coherent(ipa_ctx->pdev, mem->size, mem->base,
mem->phys_base);
fail_hw_tbl_gen:
kfree(cmd);
fail_alloc_cmd:
kfree(mem);
fail_alloc_mem:
return -EPERM;
}
int __ipa_commit_hdr_v2(void)
{
struct ipa_desc desc = { 0 };
struct ipa_mem_buffer mem;
struct ipa_hdr_init_system cmd;
struct ipa_hw_imm_cmd_dma_shared_mem dma_cmd;
int rc = -EFAULT;
if (ipa_generate_hdr_hw_tbl(&mem)) {
IPAERR("fail to generate HDR HW TBL\n");
goto end;
}
if (ipa_ctx->hdr_tbl_lcl) {
if (mem.size > IPA_v2_RAM_APPS_HDR_SIZE) {
IPAERR("tbl too big, needed %d avail %d\n", mem.size,
IPA_v2_RAM_APPS_HDR_SIZE);
goto end;
} else {
dma_cmd.system_addr = mem.phys_base;
dma_cmd.size = mem.size;
dma_cmd.local_addr = ipa_ctx->smem_restricted_bytes +
IPA_v2_RAM_APPS_HDR_OFST;
desc.opcode = IPA_DMA_SHARED_MEM;
desc.pyld = &dma_cmd;
desc.len =
sizeof(struct ipa_hw_imm_cmd_dma_shared_mem);
}
} else {
if (mem.size > IPA_RAM_HDR_SIZE_DDR) {
IPAERR("tbl too big, needed %d avail %d\n", mem.size,
IPA_RAM_HDR_SIZE_DDR);
goto end;
} else {
cmd.hdr_table_addr = mem.phys_base;
desc.opcode = IPA_HDR_INIT_SYSTEM;
desc.pyld = &cmd;
desc.len = sizeof(struct ipa_hdr_init_system);
}
}
desc.type = IPA_IMM_CMD_DESC;
IPA_DUMP_BUFF(mem.base, mem.phys_base, mem.size);
if (ipa_send_cmd(1, &desc))
IPAERR("fail to send immediate command\n");
else
rc = 0;
if (ipa_ctx->hdr_tbl_lcl) {
dma_free_coherent(ipa_ctx->pdev, mem.size, mem.base,
mem.phys_base);
} else {
if (!rc && ipa_ctx->hdr_mem.phys_base) {
dma_free_coherent(ipa_ctx->pdev, ipa_ctx->hdr_mem.size,
ipa_ctx->hdr_mem.base,
ipa_ctx->hdr_mem.phys_base);
ipa_ctx->hdr_mem = mem;
}
}
end:
return rc;
}
static int __ipa_add_hdr(struct ipa_hdr_add *hdr)
{
struct ipa_hdr_entry *entry;
struct ipa_hdr_offset_entry *offset;
u32 bin;
struct ipa_hdr_tbl *htbl = &ipa_ctx->hdr_tbl;
int id;
if (hdr->hdr_len == 0) {
IPAERR("bad parm\n");
goto error;
}
entry = kmem_cache_zalloc(ipa_ctx->hdr_cache, GFP_KERNEL);
if (!entry) {
IPAERR("failed to alloc hdr object\n");
goto error;
}
INIT_LIST_HEAD(&entry->link);
memcpy(entry->hdr, hdr->hdr, hdr->hdr_len);
entry->hdr_len = hdr->hdr_len;
strlcpy(entry->name, hdr->name, IPA_RESOURCE_NAME_MAX);
entry->is_partial = hdr->is_partial;
entry->cookie = IPA_COOKIE;
if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN0])
bin = IPA_HDR_BIN0;
else if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN1])
bin = IPA_HDR_BIN1;
else if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN2])
bin = IPA_HDR_BIN2;
else if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN3])
bin = IPA_HDR_BIN3;
else if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN4])
bin = IPA_HDR_BIN4;
else {
IPAERR("unexpected hdr len %d\n", hdr->hdr_len);
goto bad_hdr_len;
}
if (list_empty(&htbl->head_free_offset_list[bin])) {
offset = kmem_cache_zalloc(ipa_ctx->hdr_offset_cache,
GFP_KERNEL);
if (!offset) {
IPAERR("failed to alloc hdr offset object\n");
goto ofst_alloc_fail;
}
INIT_LIST_HEAD(&offset->link);
/*
* for a first item grow, set the bin and offset which are set
* in stone
*/
offset->offset = htbl->end;
offset->bin = bin;
htbl->end += ipa_hdr_bin_sz[bin];
list_add(&offset->link,
&htbl->head_offset_list[bin]);
} else {
/* get the first free slot */
offset =
list_first_entry(&htbl->head_free_offset_list[bin],
struct ipa_hdr_offset_entry, link);
list_move(&offset->link, &htbl->head_offset_list[bin]);
}
entry->offset_entry = offset;
list_add(&entry->link, &htbl->head_hdr_entry_list);
htbl->hdr_cnt++;
IPADBG("add hdr of sz=%d hdr_cnt=%d ofst=%d\n", hdr->hdr_len,
htbl->hdr_cnt, offset->offset);
id = ipa_id_alloc(entry);
if (id < 0) {
IPAERR("failed to alloc id\n");
WARN_ON(1);
}
entry->id = id;
hdr->hdr_hdl = id;
entry->ref_cnt++;
return 0;
ofst_alloc_fail:
kmem_cache_free(ipa_ctx->hdr_offset_cache, offset);
bad_hdr_len:
entry->cookie = 0;
kmem_cache_free(ipa_ctx->hdr_cache, entry);
error:
return -EPERM;
}
int __ipa_del_hdr(u32 hdr_hdl)
{
struct ipa_hdr_entry *entry;
struct ipa_hdr_tbl *htbl = &ipa_ctx->hdr_tbl;
entry = ipa_id_find(hdr_hdl);
if (entry == NULL) {
IPAERR("lookup failed\n");
return -EINVAL;
}
if (!entry || (entry->cookie != IPA_COOKIE)) {
IPAERR("bad parm\n");
return -EINVAL;
}
IPADBG("del hdr of sz=%d hdr_cnt=%d ofst=%d\n", entry->hdr_len,
htbl->hdr_cnt, entry->offset_entry->offset);
if (--entry->ref_cnt) {
IPADBG("hdr_hdl %x ref_cnt %d\n", hdr_hdl, entry->ref_cnt);
return 0;
}
/* move the offset entry to appropriate free list */
list_move(&entry->offset_entry->link,
&htbl->head_free_offset_list[entry->offset_entry->bin]);
list_del(&entry->link);
htbl->hdr_cnt--;
entry->cookie = 0;
kmem_cache_free(ipa_ctx->hdr_cache, entry);
/* remove the handle from the database */
ipa_id_remove(hdr_hdl);
return 0;
}
/**
* ipa_add_hdr() - add the specified headers to SW and optionally commit them to
* IPA HW
* @hdrs: [inout] set of headers to add
*
* Returns: 0 on success, negative on failure
*
* Note: Should not be called from atomic context
*/
int ipa_add_hdr(struct ipa_ioc_add_hdr *hdrs)
{
int i;
int result = -EFAULT;
if (hdrs == NULL || hdrs->num_hdrs == 0) {
IPAERR("bad parm\n");
return -EINVAL;
}
mutex_lock(&ipa_ctx->lock);
IPADBG("adding %d headers to IPA driver internal data struct\n",
hdrs->num_hdrs);
for (i = 0; i < hdrs->num_hdrs; i++) {
if (__ipa_add_hdr(&hdrs->hdr[i])) {
IPAERR("failed to add hdr %d\n", i);
hdrs->hdr[i].status = -1;
} else {
hdrs->hdr[i].status = 0;
}
}
if (hdrs->commit) {
IPADBG("committing all headers to IPA core");
if (ipa_ctx->ctrl->ipa_commit_hdr()) {
result = -EPERM;
goto bail;
}
}
result = 0;
bail:
mutex_unlock(&ipa_ctx->lock);
return result;
}
EXPORT_SYMBOL(ipa_add_hdr);
/**
* ipa_del_hdr() - Remove the specified headers from SW and optionally commit them
* to IPA HW
* @hdls: [inout] set of headers to delete
*
* Returns: 0 on success, negative on failure
*
* Note: Should not be called from atomic context
*/
int ipa_del_hdr(struct ipa_ioc_del_hdr *hdls)
{
int i;
int result = -EFAULT;
if (hdls == NULL || hdls->num_hdls == 0) {
IPAERR("bad parm\n");
return -EINVAL;
}
mutex_lock(&ipa_ctx->lock);
for (i = 0; i < hdls->num_hdls; i++) {
if (__ipa_del_hdr(hdls->hdl[i].hdl)) {
IPAERR("failed to del hdr %i\n", i);
hdls->hdl[i].status = -1;
} else {
hdls->hdl[i].status = 0;
}
}
if (hdls->commit) {
if (ipa_ctx->ctrl->ipa_commit_hdr()) {
result = -EPERM;
goto bail;
}
}
result = 0;
bail:
mutex_unlock(&ipa_ctx->lock);
return result;
}
EXPORT_SYMBOL(ipa_del_hdr);
/**
* ipa_commit_hdr() - commit to IPA HW the current header table in SW
*
* Returns: 0 on success, negative on failure
*
* Note: Should not be called from atomic context
*/
int ipa_commit_hdr(void)
{
int result = -EFAULT;
/*
* issue a commit on the routing module since routing rules point to
* header table entries
*/
if (ipa_commit_rt(IPA_IP_v4))
return -EPERM;
if (ipa_commit_rt(IPA_IP_v6))
return -EPERM;
mutex_lock(&ipa_ctx->lock);
if (ipa_ctx->ctrl->ipa_commit_hdr()) {
result = -EPERM;
goto bail;
}
result = 0;
bail:
mutex_unlock(&ipa_ctx->lock);
return result;
}
EXPORT_SYMBOL(ipa_commit_hdr);
/**
* ipa_reset_hdr() - reset the current header table in SW (does not commit to
* HW)
*
* Returns: 0 on success, negative on failure
*
* Note: Should not be called from atomic context
*/
int ipa_reset_hdr(void)
{
struct ipa_hdr_entry *entry;
struct ipa_hdr_entry *next;
struct ipa_hdr_offset_entry *off_entry;
struct ipa_hdr_offset_entry *off_next;
int i;
/*
* issue a reset on the routing module since routing rules point to
* header table entries
*/
if (ipa_reset_rt(IPA_IP_v4))
IPAERR("fail to reset v4 rt\n");
if (ipa_reset_rt(IPA_IP_v6))
IPAERR("fail to reset v4 rt\n");
mutex_lock(&ipa_ctx->lock);
IPADBG("reset hdr\n");
list_for_each_entry_safe(entry, next,
&ipa_ctx->hdr_tbl.head_hdr_entry_list, link) {
/* do not remove the default exception header */
if (!strncmp(entry->name, IPA_A5_MUX_HDR_NAME,
IPA_RESOURCE_NAME_MAX))
continue;
if (ipa_id_find(entry->id) == NULL) {
WARN_ON(1);
mutex_unlock(&ipa_ctx->lock);
return -EFAULT;
}
list_del(&entry->link);
entry->cookie = 0;
kmem_cache_free(ipa_ctx->hdr_cache, entry);
/* remove the handle from the database */
ipa_id_remove(entry->id);
}
for (i = 0; i < IPA_HDR_BIN_MAX; i++) {
list_for_each_entry_safe(off_entry, off_next,
&ipa_ctx->hdr_tbl.head_offset_list[i],
link) {
/*
* do not remove the default exception header which is
* at offset 0
*/
if (off_entry->offset == 0)
continue;
list_del(&off_entry->link);
kmem_cache_free(ipa_ctx->hdr_offset_cache, off_entry);
}
list_for_each_entry_safe(off_entry, off_next,
&ipa_ctx->hdr_tbl.head_free_offset_list[i],
link) {
list_del(&off_entry->link);
kmem_cache_free(ipa_ctx->hdr_offset_cache, off_entry);
}
}
/* there is one header of size 8 */
ipa_ctx->hdr_tbl.end = 8;
ipa_ctx->hdr_tbl.hdr_cnt = 1;
mutex_unlock(&ipa_ctx->lock);
return 0;
}
EXPORT_SYMBOL(ipa_reset_hdr);
static struct ipa_hdr_entry *__ipa_find_hdr(const char *name)
{
struct ipa_hdr_entry *entry;
list_for_each_entry(entry, &ipa_ctx->hdr_tbl.head_hdr_entry_list,
link) {
if (!strncmp(name, entry->name, IPA_RESOURCE_NAME_MAX))
return entry;
}
return NULL;
}
/**
* ipa_get_hdr() - Lookup the specified header resource
* @lookup: [inout] header to lookup and its handle
*
* lookup the specified header resource and return handle if it exists
*
* Returns: 0 on success, negative on failure
*
* Note: Should not be called from atomic context
* Caller should call ipa_put_hdr later if this function succeeds
*/
int ipa_get_hdr(struct ipa_ioc_get_hdr *lookup)
{
struct ipa_hdr_entry *entry;
int result = -1;
if (lookup == NULL) {
IPAERR("bad parm\n");
return -EINVAL;
}
mutex_lock(&ipa_ctx->lock);
entry = __ipa_find_hdr(lookup->name);
if (entry) {
lookup->hdl = entry->id;
result = 0;
}
mutex_unlock(&ipa_ctx->lock);
return result;
}
EXPORT_SYMBOL(ipa_get_hdr);
/**
* __ipa_release_hdr() - drop reference to header and cause
* deletion if reference count permits
* @hdr_hdl: [in] handle of header to be released
*
* Returns: 0 on success, negative on failure
*/
int __ipa_release_hdr(u32 hdr_hdl)
{
int result = 0;
if (__ipa_del_hdr(hdr_hdl)) {
IPADBG("fail to del hdr %x\n", hdr_hdl);
result = -EFAULT;
goto bail;
}
/* commit for put */
if (ipa_ctx->ctrl->ipa_commit_hdr()) {
IPAERR("fail to commit hdr\n");
result = -EFAULT;
goto bail;
}
bail:
return result;
}
/**
* ipa_put_hdr() - Release the specified header handle
* @hdr_hdl: [in] the header handle to release
*
* Returns: 0 on success, negative on failure
*
* Note: Should not be called from atomic context
*/
int ipa_put_hdr(u32 hdr_hdl)
{
struct ipa_hdr_entry *entry;
int result = -EFAULT;
mutex_lock(&ipa_ctx->lock);
entry = ipa_id_find(hdr_hdl);
if (entry == NULL) {
IPAERR("lookup failed\n");
result = -EINVAL;
goto bail;
}
if (entry == NULL || entry->cookie != IPA_COOKIE) {
IPAERR("bad params\n");
result = -EINVAL;
goto bail;
}
result = 0;
bail:
mutex_unlock(&ipa_ctx->lock);
return result;
}
EXPORT_SYMBOL(ipa_put_hdr);
/**
* ipa_copy_hdr() - Lookup the specified header resource and return a copy of it
* @copy: [inout] header to lookup and its copy
*
* lookup the specified header resource and return a copy of it (along with its
* attributes) if it exists, this would be called for partial headers
*
* Returns: 0 on success, negative on failure
*
* Note: Should not be called from atomic context
*/
int ipa_copy_hdr(struct ipa_ioc_copy_hdr *copy)
{
struct ipa_hdr_entry *entry;
int result = -EFAULT;
if (copy == NULL) {
IPAERR("bad parm\n");
return -EINVAL;
}
mutex_lock(&ipa_ctx->lock);
entry = __ipa_find_hdr(copy->name);
if (entry) {
memcpy(copy->hdr, entry->hdr, entry->hdr_len);
copy->hdr_len = entry->hdr_len;
copy->is_partial = entry->is_partial;
result = 0;
}
mutex_unlock(&ipa_ctx->lock);
return result;
}
EXPORT_SYMBOL(ipa_copy_hdr);