| |
| |
| |
| |
| |
| |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/bits.h> |
| #include <linux/bitops.h> |
| #include <linux/bitfield.h> |
| #include <linux/io.h> |
| #include <linux/build_bug.h> |
| #include <linux/device.h> |
| #include <linux/dma-mapping.h> |
| |
| #include "ipa.h" |
| #include "ipa_version.h" |
| #include "ipa_endpoint.h" |
| #include "ipa_table.h" |
| #include "ipa_reg.h" |
| #include "ipa_mem.h" |
| #include "ipa_cmd.h" |
| #include "gsi.h" |
| #include "gsi_trans.h" |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #define IPA_TABLE_ALIGN 128 |
| |
| |
| #define IPA_ROUTE_MODEM_MIN 0 |
| #define IPA_ROUTE_MODEM_COUNT 8 |
| |
| #define IPA_ROUTE_AP_MIN IPA_ROUTE_MODEM_COUNT |
| #define IPA_ROUTE_AP_COUNT \ |
| <------><------>(IPA_ROUTE_COUNT_MAX - IPA_ROUTE_MODEM_COUNT) |
| |
| |
| |
| |
| |
| #define IPA_ZERO_RULE_SIZE (2 * sizeof(__le32)) |
| |
| #ifdef IPA_VALIDATE |
| |
| |
| static void ipa_table_validate_build(void) |
| { |
| <------> |
| <------> * referred to by entries in filter and route tables must be |
| <------> * aligned on 128-byte byte boundaries. The only rule address |
| <------> * ever use is the "zero rule", and it's aligned at the base |
| <------> * of a coherent DMA allocation. |
| <------> */ |
| <------>BUILD_BUG_ON(ARCH_DMA_MINALIGN % IPA_TABLE_ALIGN); |
| |
| <------> |
| <------> * filter or route rules. We use a fixed constant to represent |
| <------> * the size of either type of table entry. Code in ipa_table_init() |
| <------> * uses a pointer to __le64 to initialize table entriews. |
| <------> */ |
| <------>BUILD_BUG_ON(IPA_TABLE_ENTRY_SIZE != sizeof(dma_addr_t)); |
| <------>BUILD_BUG_ON(sizeof(dma_addr_t) != sizeof(__le64)); |
| |
| <------> |
| <------> * It is a 64-bit block of zeroed memory. Code in ipa_table_init() |
| <------> * assumes that it can be written using a pointer to __le64. |
| <------> */ |
| <------>BUILD_BUG_ON(IPA_ZERO_RULE_SIZE != sizeof(__le64)); |
| |
| <------> |
| <------>BUILD_BUG_ON(IPA_ROUTE_COUNT_MAX > 32); |
| <------> |
| <------>BUILD_BUG_ON(!IPA_ROUTE_MODEM_COUNT); |
| <------> |
| <------>BUILD_BUG_ON(IPA_ROUTE_MODEM_COUNT > IPA_ROUTE_COUNT_MAX); |
| |
| } |
| |
| static bool |
| ipa_table_valid_one(struct ipa *ipa, bool route, bool ipv6, bool hashed) |
| { |
| <------>struct device *dev = &ipa->pdev->dev; |
| <------>const struct ipa_mem *mem; |
| <------>u32 size; |
| |
| <------>if (route) { |
| <------><------>if (ipv6) |
| <------><------><------>mem = hashed ? &ipa->mem[IPA_MEM_V6_ROUTE_HASHED] |
| <------><------><------><------> : &ipa->mem[IPA_MEM_V6_ROUTE]; |
| <------><------>else |
| <------><------><------>mem = hashed ? &ipa->mem[IPA_MEM_V4_ROUTE_HASHED] |
| <------><------><------><------> : &ipa->mem[IPA_MEM_V4_ROUTE]; |
| <------><------>size = IPA_ROUTE_COUNT_MAX * IPA_TABLE_ENTRY_SIZE; |
| <------>} else { |
| <------><------>if (ipv6) |
| <------><------><------>mem = hashed ? &ipa->mem[IPA_MEM_V6_FILTER_HASHED] |
| <------><------><------><------> : &ipa->mem[IPA_MEM_V6_FILTER]; |
| <------><------>else |
| <------><------><------>mem = hashed ? &ipa->mem[IPA_MEM_V4_FILTER_HASHED] |
| <------><------><------><------> : &ipa->mem[IPA_MEM_V4_FILTER]; |
| <------><------>size = (1 + IPA_FILTER_COUNT_MAX) * IPA_TABLE_ENTRY_SIZE; |
| <------>} |
| |
| <------>if (!ipa_cmd_table_valid(ipa, mem, route, ipv6, hashed)) |
| <------><------>return false; |
| |
| <------> |
| <------>if (mem->size == size) |
| <------><------>return true; |
| |
| <------> |
| <------>if (hashed && !mem->size) |
| <------><------>return true; |
| |
| <------>dev_err(dev, "IPv%c %s%s table region size 0x%02x, expected 0x%02x\n", |
| <------><------>ipv6 ? '6' : '4', hashed ? "hashed " : "", |
| <------><------>route ? "route" : "filter", mem->size, size); |
| |
| <------>return false; |
| } |
| |
| |
| bool ipa_table_valid(struct ipa *ipa) |
| { |
| <------>bool valid = true; |
| |
| <------>valid = valid && ipa_table_valid_one(ipa, false, false, false); |
| <------>valid = valid && ipa_table_valid_one(ipa, false, false, true); |
| <------>valid = valid && ipa_table_valid_one(ipa, false, true, false); |
| <------>valid = valid && ipa_table_valid_one(ipa, false, true, true); |
| <------>valid = valid && ipa_table_valid_one(ipa, true, false, false); |
| <------>valid = valid && ipa_table_valid_one(ipa, true, false, true); |
| <------>valid = valid && ipa_table_valid_one(ipa, true, true, false); |
| <------>valid = valid && ipa_table_valid_one(ipa, true, true, true); |
| |
| <------>return valid; |
| } |
| |
| bool ipa_filter_map_valid(struct ipa *ipa, u32 filter_map) |
| { |
| <------>struct device *dev = &ipa->pdev->dev; |
| <------>u32 count; |
| |
| <------>if (!filter_map) { |
| <------><------>dev_err(dev, "at least one filtering endpoint is required\n"); |
| |
| <------><------>return false; |
| <------>} |
| |
| <------>count = hweight32(filter_map); |
| <------>if (count > IPA_FILTER_COUNT_MAX) { |
| <------><------>dev_err(dev, "too many filtering endpoints (%u, max %u)\n", |
| <------><------><------>count, IPA_FILTER_COUNT_MAX); |
| |
| <------><------>return false; |
| <------>} |
| |
| <------>return true; |
| } |
| |
| #else |
| static void ipa_table_validate_build(void) |
| |
| { |
| } |
| |
| #endif |
| |
| |
| static dma_addr_t ipa_table_addr(struct ipa *ipa, bool filter_mask, u16 count) |
| { |
| <------>u32 skip; |
| |
| <------>if (!count) |
| <------><------>return 0; |
| |
| |
| |
| <------> |
| <------>skip = filter_mask ? 1 : 2; |
| |
| <------>return ipa->table_addr + skip * sizeof(*ipa->table_virt); |
| } |
| |
| static void ipa_table_reset_add(struct gsi_trans *trans, bool filter, |
| <------><------><------><------>u16 first, u16 count, const struct ipa_mem *mem) |
| { |
| <------>struct ipa *ipa = container_of(trans->gsi, struct ipa, gsi); |
| <------>dma_addr_t addr; |
| <------>u32 offset; |
| <------>u16 size; |
| |
| <------> |
| <------>if (!mem->size) |
| <------><------>return; |
| |
| <------>if (filter) |
| <------><------>first++; |
| |
| <------>offset = mem->offset + first * IPA_TABLE_ENTRY_SIZE; |
| <------>size = count * IPA_TABLE_ENTRY_SIZE; |
| <------>addr = ipa_table_addr(ipa, false, count); |
| |
| <------>ipa_cmd_dma_shared_mem_add(trans, offset, size, addr, true); |
| } |
| |
| |
| |
| |
| |
| static int |
| ipa_filter_reset_table(struct ipa *ipa, const struct ipa_mem *mem, bool modem) |
| { |
| <------>u32 ep_mask = ipa->filter_map; |
| <------>u32 count = hweight32(ep_mask); |
| <------>struct gsi_trans *trans; |
| <------>enum gsi_ee_id ee_id; |
| |
| <------>if (!mem->size) |
| <------><------>return 0; |
| |
| <------>trans = ipa_cmd_trans_alloc(ipa, count); |
| <------>if (!trans) { |
| <------><------>dev_err(&ipa->pdev->dev, |
| <------><------><------>"no transaction for %s filter reset\n", |
| <------><------><------>modem ? "modem" : "AP"); |
| <------><------>return -EBUSY; |
| <------>} |
| |
| <------>ee_id = modem ? GSI_EE_MODEM : GSI_EE_AP; |
| <------>while (ep_mask) { |
| <------><------>u32 endpoint_id = __ffs(ep_mask); |
| <------><------>struct ipa_endpoint *endpoint; |
| |
| <------><------>ep_mask ^= BIT(endpoint_id); |
| |
| <------><------>endpoint = &ipa->endpoint[endpoint_id]; |
| <------><------>if (endpoint->ee_id != ee_id) |
| <------><------><------>continue; |
| |
| <------><------>ipa_table_reset_add(trans, true, endpoint_id, 1, mem); |
| <------>} |
| |
| <------>gsi_trans_commit_wait(trans); |
| |
| <------>return 0; |
| } |
| |
| |
| |
| |
| |
| static int ipa_filter_reset(struct ipa *ipa, bool modem) |
| { |
| <------>int ret; |
| |
| <------>ret = ipa_filter_reset_table(ipa, &ipa->mem[IPA_MEM_V4_FILTER], modem); |
| <------>if (ret) |
| <------><------>return ret; |
| |
| <------>ret = ipa_filter_reset_table(ipa, &ipa->mem[IPA_MEM_V4_FILTER_HASHED], |
| <------><------><------><------> modem); |
| <------>if (ret) |
| <------><------>return ret; |
| |
| <------>ret = ipa_filter_reset_table(ipa, &ipa->mem[IPA_MEM_V6_FILTER], modem); |
| <------>if (ret) |
| <------><------>return ret; |
| <------>ret = ipa_filter_reset_table(ipa, &ipa->mem[IPA_MEM_V6_FILTER_HASHED], |
| <------><------><------><------> modem); |
| |
| <------>return ret; |
| } |
| |
| |
| |
| |
| |
| static int ipa_route_reset(struct ipa *ipa, bool modem) |
| { |
| <------>struct gsi_trans *trans; |
| <------>u16 first; |
| <------>u16 count; |
| |
| <------>trans = ipa_cmd_trans_alloc(ipa, 4); |
| <------>if (!trans) { |
| <------><------>dev_err(&ipa->pdev->dev, |
| <------><------><------>"no transaction for %s route reset\n", |
| <------><------><------>modem ? "modem" : "AP"); |
| <------><------>return -EBUSY; |
| <------>} |
| |
| <------>if (modem) { |
| <------><------>first = IPA_ROUTE_MODEM_MIN; |
| <------><------>count = IPA_ROUTE_MODEM_COUNT; |
| <------>} else { |
| <------><------>first = IPA_ROUTE_AP_MIN; |
| <------><------>count = IPA_ROUTE_AP_COUNT; |
| <------>} |
| |
| <------>ipa_table_reset_add(trans, false, first, count, |
| <------><------><------> &ipa->mem[IPA_MEM_V4_ROUTE]); |
| <------>ipa_table_reset_add(trans, false, first, count, |
| <------><------><------> &ipa->mem[IPA_MEM_V4_ROUTE_HASHED]); |
| |
| <------>ipa_table_reset_add(trans, false, first, count, |
| <------><------><------> &ipa->mem[IPA_MEM_V6_ROUTE]); |
| <------>ipa_table_reset_add(trans, false, first, count, |
| <------><------><------> &ipa->mem[IPA_MEM_V6_ROUTE_HASHED]); |
| |
| <------>gsi_trans_commit_wait(trans); |
| |
| <------>return 0; |
| } |
| |
| void ipa_table_reset(struct ipa *ipa, bool modem) |
| { |
| <------>struct device *dev = &ipa->pdev->dev; |
| <------>const char *ee_name; |
| <------>int ret; |
| |
| <------>ee_name = modem ? "modem" : "AP"; |
| |
| <------> |
| <------>ret = ipa_filter_reset(ipa, modem); |
| <------>if (ret) |
| <------><------>dev_err(dev, "error %d resetting filter table for %s\n", |
| <------><------><------><------>ret, ee_name); |
| |
| <------>ret = ipa_route_reset(ipa, modem); |
| <------>if (ret) |
| <------><------>dev_err(dev, "error %d resetting route table for %s\n", |
| <------><------><------><------>ret, ee_name); |
| } |
| |
| int ipa_table_hash_flush(struct ipa *ipa) |
| { |
| <------>u32 offset = ipa_reg_filt_rout_hash_flush_offset(ipa->version); |
| <------>struct gsi_trans *trans; |
| <------>u32 val; |
| |
| <------> |
| <------>if (ipa->version == IPA_VERSION_4_2) |
| <------><------>return 0; |
| |
| <------>trans = ipa_cmd_trans_alloc(ipa, 1); |
| <------>if (!trans) { |
| <------><------>dev_err(&ipa->pdev->dev, "no transaction for hash flush\n"); |
| <------><------>return -EBUSY; |
| <------>} |
| |
| <------>val = IPV4_FILTER_HASH_FLUSH | IPV6_FILTER_HASH_FLUSH; |
| <------>val |= IPV6_ROUTER_HASH_FLUSH | IPV4_ROUTER_HASH_FLUSH; |
| |
| <------>ipa_cmd_register_write_add(trans, offset, val, val, false); |
| |
| <------>gsi_trans_commit_wait(trans); |
| |
| <------>return 0; |
| } |
| |
| static void ipa_table_init_add(struct gsi_trans *trans, bool filter, |
| <------><------><------> enum ipa_cmd_opcode opcode, |
| <------><------><------> const struct ipa_mem *mem, |
| <------><------><------> const struct ipa_mem *hash_mem) |
| { |
| <------>struct ipa *ipa = container_of(trans->gsi, struct ipa, gsi); |
| <------>dma_addr_t hash_addr; |
| <------>dma_addr_t addr; |
| <------>u16 hash_count; |
| <------>u16 hash_size; |
| <------>u16 count; |
| <------>u16 size; |
| |
| <------> |
| <------> * in the filter table. The hashed and non-hashed filter table |
| <------> * will have the same number of entries. The size of the route |
| <------> * table region determines the number of entries it has. |
| <------> */ |
| <------>if (filter) { |
| <------><------> |
| <------><------>count = 1 + hweight32(ipa->filter_map); |
| <------><------>hash_count = hash_mem->size ? count : 0; |
| <------>} else { |
| <------><------>count = mem->size / IPA_TABLE_ENTRY_SIZE; |
| <------><------>hash_count = hash_mem->size / IPA_TABLE_ENTRY_SIZE; |
| <------>} |
| <------>size = count * IPA_TABLE_ENTRY_SIZE; |
| <------>hash_size = hash_count * IPA_TABLE_ENTRY_SIZE; |
| |
| <------>addr = ipa_table_addr(ipa, filter, count); |
| <------>hash_addr = ipa_table_addr(ipa, filter, hash_count); |
| |
| <------>ipa_cmd_table_init_add(trans, opcode, size, mem->offset, addr, |
| <------><------><------> hash_size, hash_mem->offset, hash_addr); |
| } |
| |
| int ipa_table_setup(struct ipa *ipa) |
| { |
| <------>struct gsi_trans *trans; |
| |
| <------>trans = ipa_cmd_trans_alloc(ipa, 4); |
| <------>if (!trans) { |
| <------><------>dev_err(&ipa->pdev->dev, "no transaction for table setup\n"); |
| <------><------>return -EBUSY; |
| <------>} |
| |
| <------>ipa_table_init_add(trans, false, IPA_CMD_IP_V4_ROUTING_INIT, |
| <------><------><------> &ipa->mem[IPA_MEM_V4_ROUTE], |
| <------><------><------> &ipa->mem[IPA_MEM_V4_ROUTE_HASHED]); |
| |
| <------>ipa_table_init_add(trans, false, IPA_CMD_IP_V6_ROUTING_INIT, |
| <------><------><------> &ipa->mem[IPA_MEM_V6_ROUTE], |
| <------><------><------> &ipa->mem[IPA_MEM_V6_ROUTE_HASHED]); |
| |
| <------>ipa_table_init_add(trans, true, IPA_CMD_IP_V4_FILTER_INIT, |
| <------><------><------> &ipa->mem[IPA_MEM_V4_FILTER], |
| <------><------><------> &ipa->mem[IPA_MEM_V4_FILTER_HASHED]); |
| |
| <------>ipa_table_init_add(trans, true, IPA_CMD_IP_V6_FILTER_INIT, |
| <------><------><------> &ipa->mem[IPA_MEM_V6_FILTER], |
| <------><------><------> &ipa->mem[IPA_MEM_V6_FILTER_HASHED]); |
| |
| <------>gsi_trans_commit_wait(trans); |
| |
| <------>return 0; |
| } |
| |
| void ipa_table_teardown(struct ipa *ipa) |
| { |
| <------> |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| static void ipa_filter_tuple_zero(struct ipa_endpoint *endpoint) |
| { |
| <------>u32 endpoint_id = endpoint->endpoint_id; |
| <------>u32 offset; |
| <------>u32 val; |
| |
| <------>offset = IPA_REG_ENDP_FILTER_ROUTER_HSH_CFG_N_OFFSET(endpoint_id); |
| |
| <------>val = ioread32(endpoint->ipa->reg_virt + offset); |
| |
| <------> |
| <------>u32p_replace_bits(&val, 0, IPA_REG_ENDP_FILTER_HASH_MSK_ALL); |
| |
| <------>iowrite32(val, endpoint->ipa->reg_virt + offset); |
| } |
| |
| static void ipa_filter_config(struct ipa *ipa, bool modem) |
| { |
| <------>enum gsi_ee_id ee_id = modem ? GSI_EE_MODEM : GSI_EE_AP; |
| <------>u32 ep_mask = ipa->filter_map; |
| |
| <------> |
| <------>if (ipa->version == IPA_VERSION_4_2) |
| <------><------>return; |
| |
| <------>while (ep_mask) { |
| <------><------>u32 endpoint_id = __ffs(ep_mask); |
| <------><------>struct ipa_endpoint *endpoint; |
| |
| <------><------>ep_mask ^= BIT(endpoint_id); |
| |
| <------><------>endpoint = &ipa->endpoint[endpoint_id]; |
| <------><------>if (endpoint->ee_id == ee_id) |
| <------><------><------>ipa_filter_tuple_zero(endpoint); |
| <------>} |
| } |
| |
| static void ipa_filter_deconfig(struct ipa *ipa, bool modem) |
| { |
| <------> |
| } |
| |
| static bool ipa_route_id_modem(u32 route_id) |
| { |
| <------>return route_id >= IPA_ROUTE_MODEM_MIN && |
| <------><------>route_id <= IPA_ROUTE_MODEM_MIN + IPA_ROUTE_MODEM_COUNT - 1; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| static void ipa_route_tuple_zero(struct ipa *ipa, u32 route_id) |
| { |
| <------>u32 offset = IPA_REG_ENDP_FILTER_ROUTER_HSH_CFG_N_OFFSET(route_id); |
| <------>u32 val; |
| |
| <------>val = ioread32(ipa->reg_virt + offset); |
| |
| <------> |
| <------>u32p_replace_bits(&val, 0, IPA_REG_ENDP_ROUTER_HASH_MSK_ALL); |
| |
| <------>iowrite32(val, ipa->reg_virt + offset); |
| } |
| |
| static void ipa_route_config(struct ipa *ipa, bool modem) |
| { |
| <------>u32 route_id; |
| |
| <------> |
| <------>if (ipa->version == IPA_VERSION_4_2) |
| <------><------>return; |
| |
| <------>for (route_id = 0; route_id < IPA_ROUTE_COUNT_MAX; route_id++) |
| <------><------>if (ipa_route_id_modem(route_id) == modem) |
| <------><------><------>ipa_route_tuple_zero(ipa, route_id); |
| } |
| |
| static void ipa_route_deconfig(struct ipa *ipa, bool modem) |
| { |
| <------> |
| } |
| |
| void ipa_table_config(struct ipa *ipa) |
| { |
| <------>ipa_filter_config(ipa, false); |
| <------>ipa_filter_config(ipa, true); |
| <------>ipa_route_config(ipa, false); |
| <------>ipa_route_config(ipa, true); |
| } |
| |
| void ipa_table_deconfig(struct ipa *ipa) |
| { |
| <------>ipa_route_deconfig(ipa, true); |
| <------>ipa_route_deconfig(ipa, false); |
| <------>ipa_filter_deconfig(ipa, true); |
| <------>ipa_filter_deconfig(ipa, false); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int ipa_table_init(struct ipa *ipa) |
| { |
| <------>u32 count = max_t(u32, IPA_FILTER_COUNT_MAX, IPA_ROUTE_COUNT_MAX); |
| <------>struct device *dev = &ipa->pdev->dev; |
| <------>dma_addr_t addr; |
| <------>__le64 le_addr; |
| <------>__le64 *virt; |
| <------>size_t size; |
| |
| <------>ipa_table_validate_build(); |
| |
| <------>size = IPA_ZERO_RULE_SIZE + (1 + count) * IPA_TABLE_ENTRY_SIZE; |
| <------>virt = dma_alloc_coherent(dev, size, &addr, GFP_KERNEL); |
| <------>if (!virt) |
| <------><------>return -ENOMEM; |
| |
| <------>ipa->table_virt = virt; |
| <------>ipa->table_addr = addr; |
| |
| <------> |
| <------>*virt++ = 0; |
| |
| <------> |
| <------> * must be converted to the hardware representation by shifting |
| <------> * it left one position. (Bit 0 repesents global filtering, |
| <------> * which is possible but not used.) |
| <------> */ |
| <------>*virt++ = cpu_to_le64((u64)ipa->filter_map << 1); |
| |
| <------> |
| <------>le_addr = cpu_to_le64(addr); |
| <------>while (count--) |
| <------><------>*virt++ = le_addr; |
| |
| <------>return 0; |
| } |
| |
| void ipa_table_exit(struct ipa *ipa) |
| { |
| <------>u32 count = max_t(u32, 1 + IPA_FILTER_COUNT_MAX, IPA_ROUTE_COUNT_MAX); |
| <------>struct device *dev = &ipa->pdev->dev; |
| <------>size_t size; |
| |
| <------>size = IPA_ZERO_RULE_SIZE + (1 + count) * IPA_TABLE_ENTRY_SIZE; |
| |
| <------>dma_free_coherent(dev, size, ipa->table_virt, ipa->table_addr); |
| <------>ipa->table_addr = 0; |
| <------>ipa->table_virt = NULL; |
| } |
| |