/* Copyright (c) 2012, 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 }; /** * ipa_generate_hdr_hw_tbl() - generates the headers table * @mem: [out] buffer to put the header table * * Returns: 0 on success, negative on failure */ 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(NULL, 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. */ static int __ipa_commit_hdr(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 && mem->size > IPA_RAM_HDR_SIZE) { IPAERR("tbl too big, needed %d avail %d\n", mem->size, IPA_RAM_HDR_SIZE); goto fail_send_cmd; } cmd->hdr_table_addr = mem->phys_base; if (ipa_ctx->hdr_tbl_lcl) { cmd->size_hdr_table = mem->size; cmd->hdr_addr = IPA_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(NULL, mem->size, mem->base, mem->phys_base); } else { if (ipa_ctx->hdr_mem.phys_base) { dma_free_coherent(NULL, 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(NULL, mem->size, mem->base, mem->phys_base); fail_hw_tbl_gen: kfree(cmd); fail_alloc_cmd: kfree(mem); fail_alloc_mem: return -EPERM; } static int __ipa_add_hdr(struct ipa_hdr_add *hdr) { struct ipa_hdr_entry *entry; struct ipa_hdr_offset_entry *offset; struct ipa_tree_node *node; u32 bin; struct ipa_hdr_tbl *htbl = &ipa_ctx->hdr_tbl; if (hdr->hdr_len == 0) { IPAERR("bad parm\n"); goto error; } node = kmem_cache_zalloc(ipa_ctx->tree_node_cache, GFP_KERNEL); if (!node) { IPAERR("failed to alloc tree node object\n"); goto error; } entry = kmem_cache_zalloc(ipa_ctx->hdr_cache, GFP_KERNEL); if (!entry) { IPAERR("failed to alloc hdr object\n"); goto hdr_alloc_fail; } 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 { 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); hdr->hdr_hdl = (u32) entry; node->hdl = hdr->hdr_hdl; if (ipa_insert(&ipa_ctx->hdr_hdl_tree, node)) { IPAERR("failed to add to tree\n"); WARN_ON(1); } 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); hdr_alloc_fail: kmem_cache_free(ipa_ctx->tree_node_cache, node); error: return -EPERM; } int __ipa_del_hdr(u32 hdr_hdl) { struct ipa_hdr_entry *entry = (struct ipa_hdr_entry *)hdr_hdl; struct ipa_tree_node *node; struct ipa_hdr_tbl *htbl = &ipa_ctx->hdr_tbl; node = ipa_search(&ipa_ctx->hdr_hdl_tree, hdr_hdl); if (node == 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 */ rb_erase(&node->node, &ipa_ctx->hdr_hdl_tree); kmem_cache_free(ipa_ctx->tree_node_cache, node); 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); 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) { if (__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_commit_hdr()) { result = -EPERM; goto bail; } } result = 0; bail: mutex_unlock(&ipa_ctx->lock); return result; } EXPORT_SYMBOL(ipa_del_hdr); /** * ipa_dump_hdr() - prints all the headers in the header table in SW * * Note: Should not be called from atomic context */ void ipa_dump_hdr(void) { struct ipa_hdr_entry *entry; IPADBG("START\n"); mutex_lock(&ipa_ctx->lock); list_for_each_entry(entry, &ipa_ctx->hdr_tbl.head_hdr_entry_list, link) { IPADBG("hdr_len=%4d off=%4d bin=%4d\n", entry->hdr_len, entry->offset_entry->offset, entry->offset_entry->bin); } mutex_unlock(&ipa_ctx->lock); IPADBG("END\n"); } /** * 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_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; struct ipa_tree_node *node; 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_DFLT_HDR_NAME, IPA_RESOURCE_NAME_MAX)) continue; node = ipa_search(&ipa_ctx->hdr_hdl_tree, (u32) entry); if (node == NULL) WARN_ON(1); list_del(&entry->link); entry->cookie = 0; kmem_cache_free(ipa_ctx->hdr_cache, entry); /* remove the handle from the database */ rb_erase(&node->node, &ipa_ctx->hdr_hdl_tree); kmem_cache_free(ipa_ctx->tree_node_cache, node); } 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 = (uint32_t) entry; 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_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 = (struct ipa_hdr_entry *)hdr_hdl; struct ipa_tree_node *node; int result = -EFAULT; mutex_lock(&ipa_ctx->lock); node = ipa_search(&ipa_ctx->hdr_hdl_tree, hdr_hdl); if (node == 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);