// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 Samsung Electronics Co., Ltd. * http://www.samsung.com/ * * EXYNOS DIT(Direct IP Translator) Driver support * */ #include #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_CPU_IDLE) #include #endif #if IS_ENABLED(CONFIG_EXYNOS_S2MPU) #include #endif #include "link_device.h" #include "dit_common.h" #include "dit_net.h" #include "dit_hal.h" static struct dit_ctrl_t *dc; static struct dit_snapshot_t snapshot[DIT_DIR_MAX][DIT_DESC_RING_MAX] = { { { .name = "tx_int_dst0", .head = -1, .tail = -1 }, { .name = "tx_int_dst1", .head = -1, .tail = -1 }, { .name = "tx_int_dst2", .head = -1, .tail = -1 }, { .name = "tx_kick_src", .head = -1, .tail = -1 }, }, { { .name = "rx_int_dst0", .head = -1, .tail = -1 }, { .name = "rx_int_dst1", .head = -1, .tail = -1 }, { .name = "rx_int_dst2", .head = -1, .tail = -1 }, { .name = "rx_kick_src", .head = -1, .tail = -1 }, }, }; static void dit_set_snapshot(enum dit_direction dir, enum dit_desc_ring ring_num, int head, int tail, u64 packets) { if (dir < 0 || dir >= DIT_DIR_MAX) return; if (ring_num < 0 || ring_num >= DIT_DESC_RING_MAX) return; if (head >= 0) snapshot[dir][ring_num].head = head; if (tail >= 0) snapshot[dir][ring_num].tail = tail; snapshot[dir][ring_num].packets = packets; snapshot[dir][ring_num].total_packets += packets; }; static int dit_get_snapshot_head(enum dit_direction dir, enum dit_desc_ring ring_num) { if (dir < 0 || dir >= DIT_DIR_MAX) return 0; if (ring_num < 0 || ring_num >= DIT_DESC_RING_MAX) return 0; if (snapshot[dir][ring_num].head >= 0) return snapshot[dir][ring_num].head; return 0; } static int dit_get_snapshot_tail(enum dit_direction dir, enum dit_desc_ring ring_num) { if (dir < 0 || dir >= DIT_DIR_MAX) return 0; if (ring_num < 0 || ring_num >= DIT_DESC_RING_MAX) return 0; if (snapshot[dir][ring_num].tail >= 0) return snapshot[dir][ring_num].tail; return 0; } static int dit_get_snapshot_next_head(enum dit_direction dir, enum dit_desc_ring ring_num, unsigned int qlen) { if (dir < 0 || dir >= DIT_DIR_MAX) return 0; if (ring_num < 0 || ring_num >= DIT_DESC_RING_MAX) return 0; if (snapshot[dir][ring_num].tail >= 0) return circ_new_ptr(qlen, snapshot[dir][ring_num].tail, 1); return 0; } static bool dit_hw_capa_matched(u32 mask) { if (dc->hw_capabilities & mask) return true; return false; } static void dit_print_dump(enum dit_direction dir, u32 dump_bits) { u16 ring_num; u32 i; if (cpif_check_bit(dump_bits, DIT_DUMP_SNAPSHOT_BIT)) { mif_err("---- SNAPSHOT[dir:%d] ----\n", dir); for (ring_num = 0; ring_num < DIT_DESC_RING_MAX; ring_num++) { pr_info("%s head:%d,tail:%d\n", snapshot[dir][ring_num].name, snapshot[dir][ring_num].head, snapshot[dir][ring_num].tail); } } if (cpif_check_bit(dump_bits, DIT_DUMP_DESC_BIT)) { struct dit_desc_info *desc_info = &dc->desc_info[dir]; struct dit_src_desc *src_desc = NULL; struct dit_dst_desc *dst_desc = NULL; src_desc = desc_info->src_desc_ring; mif_err("---- SRC RING[dir:%d] wp:%u,rp:%u ----\n", dir, desc_info->src_wp, desc_info->src_rp); for (i = 0; i < desc_info->src_desc_ring_len; i++) { if (!(src_desc[i].control & DIT_SRC_KICK_CONTROL_MASK)) continue; pr_info("src[%06d] ctrl:0x%02X,stat:0x%02X,ch_id:%03u\n", i, src_desc[i].control, src_desc[i].status, src_desc[i].ch_id); } for (ring_num = DIT_DST_DESC_RING_0; ring_num < DIT_DST_DESC_RING_MAX; ring_num++) { dst_desc = desc_info->dst_desc_ring[ring_num]; mif_err("---- DST RING%d[dir:%d] wp:%u,rp:%u ----\n", ring_num, dir, desc_info->dst_wp[ring_num], desc_info->dst_rp[ring_num]); for (i = 0; i < desc_info->dst_desc_ring_len; i++) { if (!dst_desc[i].control && !dst_desc[i].status) continue; pr_info("dst[%d][%06d] ctrl:0x%02X,stat:0x%02X,p_info:0x%03X\n", ring_num, i, dst_desc[i].control, dst_desc[i].status, dst_desc[i].packet_info); } } } if (cpif_check_bit(dump_bits, DIT_DUMP_PORT_TABLE_BIT) && dir == DIT_DIR_RX) { struct nat_local_port local_port; u16 reply_port_dst, reply_port_dst_h, reply_port_dst_l; u16 origin_port_src; mif_err("---- PORT TABLE[dir:%d] ----\n", dir); for (i = 0; i < DIT_REG_NAT_LOCAL_PORT_MAX; i++) { local_port.hw_val = READ_REG_VALUE(dc, DIT_REG_NAT_RX_PORT_TABLE_SLOT + (i * DIT_REG_NAT_LOCAL_INTERVAL)); if (!local_port.enable) continue; reply_port_dst_h = (u16)((local_port.reply_port_dst_h & 0xFF) << 8); reply_port_dst_l = (u16)(i & 0x7FF); /* read operation could return an invalid value if hw is running */ if (((reply_port_dst_h >> 8) & 0x7) != (reply_port_dst_l >> 8)) continue; reply_port_dst = (reply_port_dst_h | reply_port_dst_l); origin_port_src = local_port.origin_port_src; if (dit_hw_capa_matched(DIT_CAP_MASK_PORT_BIG_ENDIAN)) { reply_port_dst = htons(reply_port_dst); origin_port_src = htons(origin_port_src); } pr_info("[%04d] en:%d,o_port:%5d,r_port:%5d,addr_idx:%02d,dst:%d,udp:%d\n", i, local_port.enable, origin_port_src, reply_port_dst, local_port.addr_index, local_port.dst_ring, local_port.is_udp); } } } bool dit_is_kicked_any(void) { unsigned int dir; for (dir = 0; dir < DIT_DIR_MAX; dir++) { if (dc->kicked[dir]) return true; } return false; } static inline int dit_check_ring_space( unsigned int qlen, unsigned int wp, unsigned int rp) { unsigned int space; if (!circ_valid(qlen, wp, rp)) { mif_err_limited("DIRTY (qlen:%d wp:%d rp:%d)\n", qlen, wp, rp); return -EIO; } space = circ_get_space(qlen, wp, rp); if (unlikely(space < 1)) { mif_err_limited("NOSPC (qlen:%d wp:%d rp:%d)\n", qlen, wp, rp); return -ENOSPC; } return space; } #if defined(DIT_DEBUG_LOW) static void dit_debug_out_of_order(enum dit_direction dir, enum dit_desc_ring ring, u8 *data) { struct modem_ctl *mc; struct udphdr *uh; unsigned int off; unsigned int *seq_p; unsigned int seq; u16 port; static unsigned int last_seq[DIT_DIR_MAX][DIT_DESC_RING_MAX]; static unsigned int out_count[DIT_DIR_MAX][DIT_DESC_RING_MAX]; static u16 target_port[DIT_DIR_MAX][DIT_DESC_RING_MAX]; if (!dc->pktgen_ch) return; switch (data[0] & 0xF0) { case 0x40: off = sizeof(struct iphdr); break; case 0x60: off = sizeof(struct ipv6hdr); break; default: return; } uh = (struct udphdr *)(data + off); off += sizeof(struct udphdr); switch (dir) { case DIT_DIR_TX: port = uh->source; break; case DIT_DIR_RX: port = uh->dest; break; default: return; } if (!target_port[dir][ring]) { mif_info("check dir[%d] out of order at ring[%d] for port:%u\n", dir, ring, ntohs(port)); /* ntohs() is not needed */ target_port[dir][ring] = port; } /* check the first detected port only */ if (port != target_port[dir][ring]) return; seq_p = (unsigned int *)&data[off]; seq = ntohl(*seq_p); if (seq < last_seq[dir][ring]) { mif_err("dir[%d] out of order at ring[%d] seq:0x%08x last:0x%08x\n", dir, ring, seq, last_seq[dir][ring]); if (++out_count[dir][ring] > 5) { dit_print_dump(dir, DIT_DUMP_ALL); if ((dc->ld) && (dc->ld->mc)) { mc = dc->ld->mc; mc->ops.trigger_cp_crash(mc); } } } last_seq[dir][ring] = seq; } #endif int dit_check_dst_ready(enum dit_direction dir, enum dit_desc_ring ring_num) { struct dit_desc_info *desc_info; if (!dc) return -EPERM; if (ring_num < DIT_DST_DESC_RING_0 || ring_num >= DIT_DST_DESC_RING_MAX) return -EINVAL; /* DST0 is always ready */ if (ring_num == DIT_DST_DESC_RING_0) return 0; switch (dir) { case DIT_DIR_TX: desc_info = &dc->desc_info[dir]; if (!desc_info->dst_desc_ring[ring_num]) return -ENODEV; break; case DIT_DIR_RX: desc_info = &dc->desc_info[dir]; if (!desc_info->dst_skb_buf[ring_num] || !dit_hal_get_dst_netdev(ring_num)) return -ENODEV; break; default: return -EINVAL; } return 0; } static inline bool dit_check_queues_empty(enum dit_direction dir) { struct dit_desc_info *desc_info = &dc->desc_info[dir]; unsigned int ring_num; if (!circ_empty(desc_info->src_wp, desc_info->src_rp)) return false; for (ring_num = DIT_DST_DESC_RING_0; ring_num < DIT_DST_DESC_RING_MAX; ring_num++) { if (!circ_empty(desc_info->dst_wp[ring_num], desc_info->dst_rp[ring_num])) return false; } return true; } static bool dit_is_reg_value_valid(u32 value, u32 offset) { struct nat_local_port local_port; int ret = 0; if (offset >= DIT_REG_NAT_RX_PORT_TABLE_SLOT) { local_port.hw_val = value; ret = dit_check_dst_ready(DIT_DIR_RX, local_port.dst_ring); if (ret) goto exit; } exit: if (ret) { mif_err("reg value 0x%08X at 0x%08X is not valid. ret :%d\n", value, offset, ret); return false; } return true; } /* queue reg value writing if dit is running */ int dit_enqueue_reg_value_with_ext_lock(u32 value, u32 offset) { struct dit_reg_value_item *reg_item; if (dit_is_kicked_any() || !dc->init_done || !list_empty(&dc->reg_value_q)) { reg_item = kvzalloc(sizeof(struct dit_reg_value_item), GFP_ATOMIC); if (!reg_item) { mif_err("set reg value 0x%08X at 0x%08X enqueue failed\n", value, offset); return -ENOMEM; } reg_item->value = value; reg_item->offset = offset; list_add_tail(®_item->list, &dc->reg_value_q); } else { if (dit_is_reg_value_valid(value, offset)) WRITE_REG_VALUE(dc, value, offset); } return 0; } EXPORT_SYMBOL(dit_enqueue_reg_value_with_ext_lock); int dit_enqueue_reg_value(u32 value, u32 offset) { unsigned long flags; int ret; spin_lock_irqsave(&dc->src_lock, flags); ret = dit_enqueue_reg_value_with_ext_lock(value, offset); spin_unlock_irqrestore(&dc->src_lock, flags); return ret; } EXPORT_SYMBOL(dit_enqueue_reg_value); static void dit_clean_reg_value_with_ext_lock(void) { struct dit_reg_value_item *reg_item; while (!list_empty(&dc->reg_value_q)) { reg_item = list_first_entry(&dc->reg_value_q, struct dit_reg_value_item, list); if (dit_is_reg_value_valid(reg_item->value, reg_item->offset)) WRITE_REG_VALUE(dc, reg_item->value, reg_item->offset); list_del(®_item->list); kvfree(reg_item); } } static void dit_set_dst_skb_header(struct sk_buff *skb) { /* for tcpdump with any interface */ skb->protocol = htons(ETH_P_ALL); skb_reset_transport_header(skb); skb_reset_network_header(skb); skb_reset_mac_header(skb); } static void dit_update_stat(struct sk_buff *skb) { /* remove link layer header size */ unsigned int len = (skb->len - sizeof(struct ethhdr)); /* update upstream stat */ struct net_device *netdev = dit_hal_get_dst_netdev(DIT_DST_DESC_RING_0); if (netdev) { #if IS_ENABLED(CONFIG_CPIF_TP_MONITOR) struct mem_link_device *mld = to_mem_link_device(dc->ld); skb_set_network_header(skb, sizeof(struct ethhdr)); mld->tpmon->add_rx_bytes(skb); #endif netdev->stats.rx_packets++; netdev->stats.rx_bytes += len; } dit_hal_add_data_bytes(len, 0); } static inline void dit_set_skb_checksum(struct dit_dst_desc *dst_desc, enum dit_desc_ring ring_num, struct sk_buff *skb) { if ((ring_num == DIT_DST_DESC_RING_0) && dst_desc->pre_csum) { skb->ip_summed = CHECKSUM_UNNECESSARY; return; } if (dst_desc->status & DIT_CHECKSUM_FAILED_STATUS_MASK) return; if (cpif_check_bit(dst_desc->status, DIT_DESC_S_TCPC) && cpif_check_bit(dst_desc->status, DIT_DESC_S_IPCS)) skb->ip_summed = CHECKSUM_UNNECESSARY; } static inline void dit_set_skb_udp_csum_zero(struct dit_dst_desc *dst_desc, enum dit_desc_ring ring_num, struct sk_buff *skb) { struct udphdr *uh; unsigned int off; if (ring_num == DIT_DST_DESC_RING_0) return; /* every packets on DST1/2 are IPv4 NATed */ if (!cpif_check_bit(dst_desc->packet_info, DIT_PACKET_INFO_IPV4_BIT) || !cpif_check_bit(dst_desc->packet_info, DIT_PACKET_INFO_UDP_BIT)) return; off = sizeof(struct ethhdr) + sizeof(struct iphdr); uh = (struct udphdr *)(skb->data + off); /* set to 0 if csum was 0 from SRC. * set to CSUM_MANGLED_0 if csum is 0 after the hw csum magic. */ if (dst_desc->udp_csum_zero) uh->check = 0; else if (!uh->check) uh->check = CSUM_MANGLED_0; } static int dit_pass_to_net(enum dit_desc_ring ring_num, struct sk_buff *skb) { struct mem_link_device *mld; int ret = 0; #if defined(DIT_DEBUG_LOW) dit_debug_out_of_order(DIT_DIR_RX, ring_num, skb->data); #endif switch (ring_num) { case DIT_DST_DESC_RING_0: mld = to_mem_link_device(dc->ld); /* this function check iod and ch inside * an error means further calling is not necessary */ return mld->pass_skb_to_net(mld, skb); case DIT_DST_DESC_RING_1: case DIT_DST_DESC_RING_2: dit_update_stat(skb); skb->dev = dit_hal_get_dst_netdev(ring_num); if (!skb->dev || !netif_running(skb->dev) || !netif_carrier_ok(skb->dev)) { mif_err_limited("invalid netdev!! ring_num: %d\n", ring_num); dev_kfree_skb_any(skb); break; } dit_set_dst_skb_header(skb); ret = dev_queue_xmit(skb); if (ret == NET_XMIT_DROP) mif_err_limited("drop!! ring_num: %d\n", ring_num); break; default: break; } return 0; } static inline void dit_reset_src_desc_kick_control(struct dit_src_desc *src_desc) { u8 mask = DIT_SRC_KICK_CONTROL_MASK; if (!src_desc) return; src_desc->control &= ~mask; } static inline void dit_set_src_desc_udp_csum_zero(struct dit_src_desc *src_desc, u8 *src) { const struct iphdr *iph = (struct iphdr *)src; struct udphdr *uh; unsigned int off; /* check IPv4 UDP only */ if (((src[0] & 0xFF) != 0x45) || (iph->protocol != IPPROTO_UDP)) return; off = sizeof(*iph); uh = (struct udphdr *)(src + off); if (uh->check == 0) src_desc->udp_csum_zero = 1; } static void dit_set_src_desc_kick_range(enum dit_direction dir, unsigned int src_wp, unsigned int src_rp) { struct dit_desc_info *desc_info = &dc->desc_info[dir]; struct dit_src_desc *src_desc; phys_addr_t p_desc; unsigned int head; unsigned int tail; u32 offset_lo = 0, offset_hi = 0, offset_en = 0; /* reset previous kick */ head = dit_get_snapshot_head(dir, DIT_SRC_DESC_RING); src_desc = &desc_info->src_desc_ring[head]; dit_reset_src_desc_kick_control(src_desc); tail = dit_get_snapshot_tail(dir, DIT_SRC_DESC_RING); src_desc = &desc_info->src_desc_ring[tail]; dit_reset_src_desc_kick_control(src_desc); barrier(); /* set current kick */ head = src_rp; src_desc = &desc_info->src_desc_ring[head]; cpif_set_bit(src_desc->control, DIT_DESC_C_HEAD); p_desc = desc_info->src_desc_ring_daddr + (sizeof(struct dit_src_desc) * head); if (dir == DIT_DIR_TX) { offset_lo = DIT_REG_NAT_TX_DESC_ADDR_0_SRC; offset_hi = DIT_REG_NAT_TX_DESC_ADDR_1_SRC; offset_en = DIT_REG_NAT_TX_DESC_ADDR_EN_SRC; } else { offset_lo = DIT_REG_NAT_RX_DESC_ADDR_0_SRC; offset_hi = DIT_REG_NAT_RX_DESC_ADDR_1_SRC; offset_en = DIT_REG_NAT_RX_DESC_ADDR_EN_SRC; } WRITE_REG_PADDR_LO(dc, p_desc, offset_lo); WRITE_REG_PADDR_HI(dc, p_desc, offset_hi); WRITE_REG_VALUE(dc, 0x1, offset_en); tail = circ_prev_ptr(desc_info->src_desc_ring_len, src_wp, 1); src_desc = &desc_info->src_desc_ring[tail]; cpif_set_bit(src_desc->control, DIT_DESC_C_TAIL); cpif_set_bit(src_desc->control, DIT_DESC_C_INT); DIT_INDIRECT_CALL(dc, set_src_desc_tail, dir, desc_info, tail); src_desc = &desc_info->src_desc_ring[desc_info->src_desc_ring_len - 1]; cpif_set_bit(src_desc->control, DIT_DESC_C_RINGEND); dit_set_snapshot(dir, DIT_SRC_DESC_RING, head, tail, circ_get_usage(desc_info->src_desc_ring_len, tail, head) + 1); } static void dit_set_dst_desc_int_range(enum dit_direction dir, enum dit_desc_ring ring_num) { struct dit_desc_info *desc_info = &dc->desc_info[dir]; struct dit_dst_desc *dst_desc; phys_addr_t p_desc; unsigned int dst_wp_pos; u32 offset_lo = 0, offset_hi = 0; dst_desc = desc_info->dst_desc_ring[ring_num]; dst_wp_pos = desc_info->dst_wp[ring_num]; p_desc = desc_info->dst_desc_ring_daddr[ring_num] + (sizeof(struct dit_dst_desc) * dst_wp_pos); switch (ring_num) { case DIT_DST_DESC_RING_0: if (dir == DIT_DIR_TX) { offset_lo = DIT_REG_NAT_TX_DESC_ADDR_0_DST0; offset_hi = DIT_REG_NAT_TX_DESC_ADDR_1_DST0; } else { offset_lo = DIT_REG_NAT_RX_DESC_ADDR_0_DST0; offset_hi = DIT_REG_NAT_RX_DESC_ADDR_1_DST0; } break; case DIT_DST_DESC_RING_1: if (dir == DIT_DIR_TX) { offset_lo = DIT_REG_NAT_TX_DESC_ADDR_0_DST1; offset_hi = DIT_REG_NAT_TX_DESC_ADDR_1_DST1; } else { offset_lo = DIT_REG_NAT_RX_DESC_ADDR_0_DST1; offset_hi = DIT_REG_NAT_RX_DESC_ADDR_1_DST1; } break; case DIT_DST_DESC_RING_2: if (dir == DIT_DIR_TX) { offset_lo = DIT_REG_NAT_TX_DESC_ADDR_0_DST2; offset_hi = DIT_REG_NAT_TX_DESC_ADDR_1_DST2; } else { offset_lo = DIT_REG_NAT_RX_DESC_ADDR_0_DST2; offset_hi = DIT_REG_NAT_RX_DESC_ADDR_1_DST2; } break; default: break; } if (offset_lo && offset_hi && (desc_info->dst_desc_ring_len > 0)) { WRITE_REG_PADDR_LO(dc, p_desc, offset_lo); WRITE_REG_PADDR_HI(dc, p_desc, offset_hi); cpif_set_bit(dst_desc[desc_info->dst_desc_ring_len - 1].control, DIT_DESC_C_RINGEND); } } static int dit_enqueue_src_desc_ring_internal(enum dit_direction dir, u8 *src, unsigned long src_paddr, u16 len, u8 ch_id, bool csum) { struct dit_desc_info *desc_info; struct dit_src_desc *src_desc; int remain; int src_wp = 0; bool is_upstream_pkt = false; #if defined(DIT_DEBUG) static unsigned int overflow; static unsigned int last_max_overflow; #endif #if defined(DIT_DEBUG_LOW) u32 usage; #endif if (!dc) return -EPERM; desc_info = &dc->desc_info[dir]; remain = dit_check_ring_space(desc_info->src_desc_ring_len, desc_info->src_wp, desc_info->src_rp); if (unlikely(remain < 1)) { #if defined(DIT_DEBUG) if (remain == -ENOSPC) overflow++; if (overflow > last_max_overflow) { last_max_overflow = overflow; mif_err("enqueue overflow new max: %d", last_max_overflow); } #endif return remain; } #if defined(DIT_DEBUG) overflow = 0; #endif #if defined(DIT_DEBUG_LOW) dit_debug_out_of_order(dir, DIT_SRC_DESC_RING, src); #endif src_wp = (int) desc_info->src_wp; src_desc = &desc_info->src_desc_ring[src_wp]; if (src_paddr) src_desc->src_addr = src_paddr; else src_desc->src_addr = virt_to_phys(src); src_desc->length = len; src_desc->ch_id = ch_id; src_desc->pre_csum = csum; src_desc->udp_csum_zero = 0; src_desc->control = 0; if (src_wp == (desc_info->src_desc_ring_len - 1)) cpif_set_bit(src_desc->control, DIT_DESC_C_RINGEND); src_desc->status = 0; DIT_INDIRECT_CALL(dc, set_desc_filter_bypass, dir, src_desc, src, &is_upstream_pkt); if (is_upstream_pkt) dit_set_src_desc_udp_csum_zero(src_desc, src); if (dc->use_dma_map && dir == DIT_DIR_TX) { dma_addr_t daddr; daddr = dma_map_single(dc->dev, src, len, DMA_TO_DEVICE); if (dma_mapping_error(dc->dev, daddr)) { mif_err("dit dir[%d] src skb[%d] dma_map_single failed\n", dir, src_wp); return -ENOMEM; } dma_unmap_single(dc->dev, daddr, len, DMA_TO_DEVICE); } barrier(); desc_info->src_wp = circ_new_ptr(desc_info->src_desc_ring_len, src_wp, 1); /* ensure the src_wp ordering */ smp_mb(); #if defined(DIT_DEBUG_LOW) usage = circ_get_usage(desc_info->src_desc_ring_len, desc_info->src_wp, desc_info->src_rp); if (usage > snapshot[dir][DIT_SRC_DESC_RING].max_usage) snapshot[dir][DIT_SRC_DESC_RING].max_usage = usage; #endif return src_wp; } int dit_enqueue_src_desc_ring(enum dit_direction dir, u8 *src, unsigned long src_paddr, u16 len, u8 ch_id, bool csum) { return dit_enqueue_src_desc_ring_internal( dir, src, src_paddr, len, ch_id, csum); } EXPORT_SYMBOL(dit_enqueue_src_desc_ring); int dit_enqueue_src_desc_ring_skb(enum dit_direction dir, struct sk_buff *skb) { int src_wp; src_wp = dit_enqueue_src_desc_ring_internal(dir, skb->data, virt_to_phys(skb->data), skb->len, skbpriv(skb)->sipc_ch, (skb->ip_summed == CHECKSUM_UNNECESSARY)); if (src_wp >= 0) dc->desc_info[dir].src_skb_buf[src_wp] = skb; return src_wp; } EXPORT_SYMBOL(dit_enqueue_src_desc_ring_skb); static int dit_fill_tx_dst_data_buffer(enum dit_desc_ring ring_num, unsigned int read) { struct dit_desc_info *desc_info; struct dit_dst_desc *dst_desc; unsigned int dst_rp_pos; unsigned int i; if (!dc) return -EPERM; if (!read) return 0; desc_info = &dc->desc_info[DIT_DIR_TX]; if (unlikely(!desc_info->pktproc_pbase)) return -EACCES; dst_desc = desc_info->dst_desc_ring[ring_num]; dst_rp_pos = desc_info->dst_rp[ring_num]; for (i = 0; i < read; i++) { dst_desc[dst_rp_pos].dst_addr = desc_info->pktproc_pbase + (dst_rp_pos * desc_info->buf_size); dst_rp_pos = circ_new_ptr(desc_info->dst_desc_ring_len, dst_rp_pos, 1); } return 0; } static int dit_fill_rx_dst_data_buffer(enum dit_desc_ring ring_num, unsigned int read, bool initial) { struct dit_desc_info *desc_info; struct dit_dst_desc *dst_desc; struct sk_buff **dst_skb; unsigned int dst_rp_pos; gfp_t gfp_mask; int i; if (!dc) return -EPERM; if (!read) return 0; spin_lock(&dc->rx_buf_lock); desc_info = &dc->desc_info[DIT_DIR_RX]; if (initial && desc_info->dst_skb_buf_filled[ring_num]) { spin_unlock(&dc->rx_buf_lock); return 0; } if (unlikely(!desc_info->dst_skb_buf[ring_num])) { unsigned int buf_size = sizeof(struct sk_buff *) * desc_info->dst_desc_ring_len; desc_info->dst_skb_buf[ring_num] = kvzalloc(buf_size, GFP_KERNEL); if (!desc_info->dst_skb_buf[ring_num]) { mif_err("dit dst[%d] skb container alloc failed\n", ring_num); spin_unlock(&dc->rx_buf_lock); return -ENOMEM; } } if (dc->use_dma_map && unlikely(!desc_info->dst_skb_buf_daddr[ring_num])) { unsigned int buf_size = sizeof(dma_addr_t) * desc_info->dst_desc_ring_len; desc_info->dst_skb_buf_daddr[ring_num] = kvzalloc(buf_size, GFP_KERNEL); if (!desc_info->dst_skb_buf_daddr[ring_num]) { mif_err("dit dst[%d] skb dma addr container alloc failed\n", ring_num); spin_unlock(&dc->rx_buf_lock); return -ENOMEM; } } dst_desc = desc_info->dst_desc_ring[ring_num]; dst_skb = desc_info->dst_skb_buf[ring_num]; dst_rp_pos = desc_info->dst_rp[ring_num]; /* fill free space */ for (i = 0; i < read; i++) { if (dst_desc[dst_rp_pos].dst_addr) goto next; if (unlikely(dst_skb[dst_rp_pos])) goto dma_map; if (desc_info->dst_page_pool[ring_num]) { void *data; bool used_tmp_alloc; u16 len = SKB_DATA_ALIGN(dc->desc_info[DIT_DIR_RX].buf_size); len += SKB_DATA_ALIGN(dc->page_recycling_skb_padding); len += SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); data = cpif_page_alloc(desc_info->dst_page_pool[ring_num], len, &used_tmp_alloc); if (!data) { mif_err("dit dst[%d] skb[%d] recycle pg alloc failed\n", ring_num, dst_rp_pos); spin_unlock(&dc->rx_buf_lock); return -ENOMEM; } dst_skb[dst_rp_pos] = build_skb(data, len); } else if (initial) { gfp_mask = GFP_KERNEL; if (ring_num == DIT_DST_DESC_RING_0) gfp_mask = GFP_ATOMIC; dst_skb[dst_rp_pos] = __netdev_alloc_skb_ip_align(dc->netdev, desc_info->buf_size, gfp_mask); } else { dst_skb[dst_rp_pos] = napi_alloc_skb(&dc->napi, desc_info->buf_size); } if (unlikely(!dst_skb[dst_rp_pos])) { mif_err("dit dst[%d] skb[%d] build failed\n", ring_num, dst_rp_pos); spin_unlock(&dc->rx_buf_lock); return -ENOMEM; } if (desc_info->dst_page_pool[ring_num]) skb_reserve(dst_skb[dst_rp_pos], dc->page_recycling_skb_padding); #if defined(DIT_DEBUG_LOW) snapshot[DIT_DIR_RX][ring_num].alloc_skbs++; #endif dma_map: if (dc->use_dma_map && !desc_info->dst_skb_buf_daddr[ring_num][dst_rp_pos]) { dma_addr_t daddr; daddr = dma_map_single(dc->dev, dst_skb[dst_rp_pos]->data, desc_info->buf_size, DMA_FROM_DEVICE); if (dma_mapping_error(dc->dev, daddr)) { mif_err("dit dst[%d] skb[%d] dma_map_single failed\n", ring_num, dst_rp_pos); spin_unlock(&dc->rx_buf_lock); return -ENOMEM; } desc_info->dst_skb_buf_daddr[ring_num][dst_rp_pos] = daddr; #if defined(DIT_DEBUG_LOW) snapshot[DIT_DIR_RX][ring_num].dma_maps++; #endif } dst_desc[dst_rp_pos].dst_addr = virt_to_phys(dst_skb[dst_rp_pos]->data); next: dst_rp_pos = circ_new_ptr(desc_info->dst_desc_ring_len, dst_rp_pos, 1); } if (initial) desc_info->dst_skb_buf_filled[ring_num] = true; spin_unlock(&dc->rx_buf_lock); return 0; } static int dit_free_dst_data_buffer(enum dit_direction dir, enum dit_desc_ring ring_num) { struct dit_desc_info *desc_info; struct dit_dst_desc *dst_desc; struct sk_buff **dst_skb; int i; if (!dc) return -EPERM; spin_lock(&dc->rx_buf_lock); desc_info = &dc->desc_info[dir]; if (unlikely(!desc_info->dst_skb_buf[ring_num])) { spin_unlock(&dc->rx_buf_lock); return -EINVAL; } if (!circ_empty(desc_info->dst_wp[ring_num], desc_info->dst_rp[ring_num])) { mif_err("skip free. dst[%d] is processing. wp:%d rp:%d\n", ring_num, desc_info->dst_wp[ring_num], desc_info->dst_rp[ring_num]); spin_unlock(&dc->rx_buf_lock); return -EBUSY; } dst_desc = desc_info->dst_desc_ring[ring_num]; dst_skb = desc_info->dst_skb_buf[ring_num]; /* don't free dst_skb_buf if there are skbs will be handled in napi poll */ for (i = 0; i < desc_info->dst_desc_ring_len; i++) { if (!dst_desc[i].dst_addr) { spin_unlock(&dc->rx_buf_lock); return -EFAULT; } } for (i = 0; i < desc_info->dst_desc_ring_len; i++) { if (dst_skb[i]) { if (dc->use_dma_map && desc_info->dst_skb_buf_daddr[ring_num] && desc_info->dst_skb_buf_daddr[ring_num][i]) { #if defined(DIT_DEBUG_LOW) snapshot[DIT_DIR_RX][ring_num].dma_maps--; #endif dma_unmap_single(dc->dev, desc_info->dst_skb_buf_daddr[ring_num][i], desc_info->buf_size, DMA_FROM_DEVICE); } #if defined(DIT_DEBUG_LOW) snapshot[dir][ring_num].alloc_skbs--; #endif dev_kfree_skb_any(dst_skb[i]); } dst_desc[i].dst_addr = 0; } mif_info("free dst[%d] skb buffers\n", ring_num); if (dc->use_dma_map) { kvfree(desc_info->dst_skb_buf_daddr[ring_num]); desc_info->dst_skb_buf_daddr[ring_num] = NULL; } kvfree(dst_skb); desc_info->dst_skb_buf[ring_num] = NULL; desc_info->dst_skb_buf_filled[ring_num] = false; spin_unlock(&dc->rx_buf_lock); return 0; } /* ToDo: Tx does not have dst_skb_buf, might need another flag */ static int dit_get_dst_data_buffer_free_space(enum dit_direction dir) { struct dit_desc_info *desc_info = &dc->desc_info[dir]; unsigned int min = desc_info->dst_desc_ring_len; unsigned int space; int ring_num; for (ring_num = DIT_DST_DESC_RING_0; ring_num < DIT_DST_DESC_RING_MAX; ring_num++) { if (!desc_info->dst_skb_buf[ring_num]) continue; space = circ_get_space(desc_info->dst_desc_ring_len, desc_info->dst_wp[ring_num], desc_info->dst_rp[ring_num]); if (min > space) min = space; } return min; } int dit_manage_rx_dst_data_buffers(bool fill) { int ring_num; int ret = 0; /* ToDo: need to update dst wp and rp? */ for (ring_num = DIT_DST_DESC_RING_1; ring_num < DIT_DST_DESC_RING_MAX; ring_num++) { if (fill) { ret = dit_fill_rx_dst_data_buffer(ring_num, dc->desc_info[DIT_DIR_RX].dst_desc_ring_len, true); if (ret) break; mif_info("dst[%d] filled with wp[%d] rp[%d]\n", ring_num, dc->desc_info[DIT_DIR_RX].dst_wp[ring_num], dc->desc_info[DIT_DIR_RX].dst_rp[ring_num]); dit_set_dst_desc_int_range(DIT_DIR_RX, ring_num); } else ret = dit_free_dst_data_buffer(DIT_DIR_RX, ring_num); } return ret; } EXPORT_SYMBOL(dit_manage_rx_dst_data_buffers); int dit_read_rx_dst_poll(struct napi_struct *napi, int budget) { struct dit_desc_info *desc_info = &dc->desc_info[DIT_DIR_RX]; struct dit_dst_desc *dst_desc; struct sk_buff *skb; unsigned int rcvd_total = 0; unsigned int usage; unsigned int dst_rp_pos; unsigned int ring_num; int i, ret; #if IS_ENABLED(CONFIG_CPIF_TP_MONITOR) struct mem_link_device *mld = to_mem_link_device(dc->ld); #endif for (ring_num = DIT_DST_DESC_RING_0; ring_num < DIT_DST_DESC_RING_MAX; ring_num++) { /* read from rp to wp */ usage = circ_get_usage(desc_info->dst_desc_ring_len, desc_info->dst_wp[ring_num], desc_info->dst_rp[ring_num]); for (i = 0; i < usage; i++) { if (rcvd_total >= budget) break; dst_rp_pos = desc_info->dst_rp[ring_num]; /* get dst desc and skb */ dst_desc = &desc_info->dst_desc_ring[ring_num][dst_rp_pos]; if (dc->use_dma_map) { dma_addr_t daddr = desc_info->dst_skb_buf_daddr[ring_num][dst_rp_pos]; if (daddr) { #if defined(DIT_DEBUG_LOW) snapshot[DIT_DIR_RX][ring_num].dma_maps--; #endif dma_unmap_single(dc->dev, daddr, desc_info->buf_size, DMA_FROM_DEVICE); desc_info->dst_skb_buf_daddr[ring_num][dst_rp_pos] = 0; } } skb = desc_info->dst_skb_buf[ring_num][dst_rp_pos]; /* try to fill dst data buffers */ desc_info->dst_skb_buf[ring_num][dst_rp_pos] = NULL; ret = dit_fill_rx_dst_data_buffer(ring_num, 1, false); if (ret) { desc_info->dst_skb_buf[ring_num][dst_rp_pos] = skb; break; } /* set skb */ skb_put(skb, dst_desc->length); skbpriv(skb)->lnk_hdr = 0; skbpriv(skb)->sipc_ch = dst_desc->ch_id; skbpriv(skb)->iod = link_get_iod_with_channel(dc->ld, skbpriv(skb)->sipc_ch); skbpriv(skb)->ld = dc->ld; skbpriv(skb)->napi = napi; /* clat */ if (cpif_check_bit(dst_desc->packet_info, DIT_PACKET_INFO_IPV6_BIT) && ((skb->data[0] & 0xFF) == 0x45)) { skbpriv(skb)->rx_clat = 1; snapshot[DIT_DIR_RX][ring_num].clat_packets++; } /* hw checksum */ dit_set_skb_checksum(dst_desc, ring_num, skb); /* adjust udp zero checksum */ dit_set_skb_udp_csum_zero(dst_desc, ring_num, skb); dst_desc->packet_info = 0; dst_desc->control = 0; if (dst_rp_pos == desc_info->dst_desc_ring_len - 1) cpif_set_bit(dst_desc->control, DIT_DESC_C_RINGEND); dst_desc->status = 0; ret = dit_pass_to_net(ring_num, skb); /* update dst rp after dit_pass_to_net */ desc_info->dst_rp[ring_num] = circ_new_ptr(desc_info->dst_desc_ring_len, dst_rp_pos, 1); rcvd_total++; #if defined(DIT_DEBUG_LOW) snapshot[DIT_DIR_RX][ring_num].alloc_skbs--; #endif if (ret < 0) break; } } #if IS_ENABLED(CONFIG_CPIF_TP_MONITOR) if (rcvd_total) mld->tpmon->start(); #endif if (atomic_read(&dc->stop_napi_poll)) { atomic_set(&dc->stop_napi_poll, 0); napi_complete(napi); /* kick can be reserved if dst buffer was not enough */ dit_kick(DIT_DIR_RX, true); return 0; } if (rcvd_total < budget) { napi_complete_done(napi, rcvd_total); /* kick can be reserved if dst buffer was not enough */ dit_kick(DIT_DIR_RX, true); } return rcvd_total; } EXPORT_SYMBOL(dit_read_rx_dst_poll); static void dit_update_dst_desc_pos(enum dit_direction dir, enum dit_desc_ring ring_num) { struct dit_desc_info *desc_info = &dc->desc_info[dir]; unsigned int last_dst_wp = desc_info->dst_wp[ring_num]; struct dit_dst_desc *dst_desc; struct mem_link_device *mld = to_mem_link_device(dc->ld); u64 packets = 0; #if defined(DIT_DEBUG_LOW) u32 usage; #endif do { dst_desc = &desc_info->dst_desc_ring[ring_num][desc_info->dst_wp[ring_num]]; if (!cpif_check_bit(dst_desc->status, DIT_DESC_S_DONE)) break; /* update dst */ cpif_clear_bit(dst_desc->status, DIT_DESC_S_DONE); /* tx does not use status field */ if (dir == DIT_DIR_TX) dst_desc->status = 0; if (desc_info->dst_skb_buf[ring_num]) dst_desc->dst_addr = 0; desc_info->dst_wp[ring_num] = circ_new_ptr(desc_info->dst_desc_ring_len, desc_info->dst_wp[ring_num], 1); /* update src * after a DST interrupt, reset all of src buf */ if (desc_info->src_skb_buf[desc_info->src_rp]) { dev_consume_skb_any(desc_info->src_skb_buf[desc_info->src_rp]); desc_info->src_skb_buf[desc_info->src_rp] = NULL; } desc_info->src_rp = circ_new_ptr(desc_info->src_desc_ring_len, desc_info->src_rp, 1); packets++; #if defined(DIT_DEBUG) if (desc_info->dst_wp[ring_num] == desc_info->dst_rp[ring_num]) { mif_err("dst[%d] wp[%d] would overwrite rp (dir:%d)\n", ring_num, desc_info->dst_wp[ring_num], dir); } #endif #if defined(DIT_DEBUG_LOW) usage = circ_get_usage(desc_info->dst_desc_ring_len, desc_info->dst_wp[ring_num], desc_info->dst_rp[ring_num]); if (usage > snapshot[dir][ring_num].max_usage) snapshot[dir][ring_num].max_usage = usage; #endif } while (1); if (packets > 0) { u32 qnum = desc_info->pktproc_queue_num; dit_set_dst_desc_int_range(dir, ring_num); dit_set_snapshot(dir, ring_num, last_dst_wp, circ_prev_ptr(desc_info->dst_desc_ring_len, desc_info->dst_wp[ring_num], 1), packets); /* update pktproc fore pointer */ switch (dir) { case DIT_DIR_TX: desc_info->dst_rp[ring_num] = circ_new_ptr(desc_info->dst_desc_ring_len, desc_info->dst_rp[ring_num], packets); #if IS_ENABLED(CONFIG_CP_PKTPROC_UL) mld->pktproc_ul.q[qnum]->update_fore_ptr(mld->pktproc_ul.q[qnum], packets); #endif break; case DIT_DIR_RX: mld->pktproc.q[qnum]->update_fore_ptr(mld->pktproc.q[qnum], packets); break; default: mif_err_limited("dir error:%d\n", dir); break; } } } irqreturn_t dit_irq_handler(int irq, void *arg) { int pending_bit = *((int *)(arg)); enum dit_desc_ring ring_num; struct mem_link_device *mld; struct modem_ctl *mc; enum dit_direction dir = DIT_DIR_MAX; u32 pending_mask = DIT_ALL_INT_PENDING_MASK; unsigned long flags; switch (pending_bit) { case RX_DST0_INT_PENDING_BIT: case TX_DST0_INT_PENDING_BIT: ring_num = DIT_DST_DESC_RING_0; break; case RX_DST1_INT_PENDING_BIT: ring_num = DIT_DST_DESC_RING_1; break; case RX_DST2_INT_PENDING_BIT: ring_num = DIT_DST_DESC_RING_2; break; default: break; } switch (pending_bit) { case RX_DST0_INT_PENDING_BIT: case RX_DST1_INT_PENDING_BIT: case RX_DST2_INT_PENDING_BIT: dir = DIT_DIR_RX; pending_mask = DIT_RX_INT_PENDING_MASK; dit_update_dst_desc_pos(DIT_DIR_RX, ring_num); if (napi_schedule_prep(&dc->napi)) __napi_schedule(&dc->napi); break; case TX_DST0_INT_PENDING_BIT: dir = DIT_DIR_TX; pending_mask = DIT_TX_INT_PENDING_MASK; mld = ld_to_mem_link_device(dc->ld); mc = dc->ld->mc; dit_update_dst_desc_pos(DIT_DIR_TX, ring_num); spin_lock_irqsave(&mc->lock, flags); if (ipc_active(mld)) send_ipc_irq(mld, mask2int(MASK_SEND_DATA)); spin_unlock_irqrestore(&mc->lock, flags); break; case ERR_INT_PENDING_BIT: /* nothing to do when ERR interrupt */ mif_err_limited("ERR interrupt!! int_pending: 0x%X\n", READ_REG_VALUE(dc, DIT_REG_INT_PENDING)); break; default: break; } spin_lock(&dc->src_lock); /* do not clear ERR for debugging */ if (pending_bit != ERR_INT_PENDING_BIT) WRITE_REG_VALUE(dc, BIT(pending_bit), DIT_REG_INT_PENDING); if ((READ_REG_VALUE(dc, DIT_REG_INT_PENDING) & pending_mask) == 0) { if (dir < DIT_DIR_MAX) dc->kicked[dir] = false; if (!dit_is_kicked_any()) { #if IS_ENABLED(CONFIG_CPU_IDLE) exynos_update_ip_idle_status(dc->idle_ip_index, DIT_IDLE_IP_IDLE); #endif dit_clean_reg_value_with_ext_lock(); } } spin_unlock(&dc->src_lock); /* try init and kick again */ dit_init(NULL, DIT_INIT_RETRY, DIT_STORE_NONE); if (dir < DIT_DIR_MAX) dit_kick(dir, true); return IRQ_HANDLED; } bool dit_is_busy(enum dit_direction dir) { u32 status_bits = 0; u32 status_mask = 0; u32 pending_bits = 0; u32 pending_mask = 0; switch (dir) { case DIT_DIR_TX: status_mask = TX_STATUS_MASK; pending_mask = DIT_TX_INT_PENDING_MASK; break; case DIT_DIR_RX: status_mask = RX_STATUS_MASK; pending_mask = DIT_RX_INT_PENDING_MASK; break; default: break; } status_bits = READ_REG_VALUE(dc, DIT_REG_STATUS); if (status_bits & status_mask) { mif_err("status = 0x%02X\n", status_bits); return true; } pending_bits = READ_REG_VALUE(dc, DIT_REG_INT_PENDING); if (pending_bits & pending_mask) { mif_err("pending = 0x%02X\n", pending_bits); return true; } return false; } int dit_kick(enum dit_direction dir, bool retry) { int ret = 0; unsigned long flags; struct dit_desc_info *desc_info; u32 kick_mask = 0; unsigned int src_wp; unsigned int src_rp; if (unlikely(!dc)) return -EPERM; spin_lock_irqsave(&dc->src_lock, flags); if (retry && !dc->kick_reserved[dir]) { ret = -EAGAIN; goto exit; } if (dc->kicked[dir] || !dc->init_done) { dc->kick_reserved[dir] = true; ret = -EAGAIN; goto exit; } if (dit_is_busy(dir)) { dc->kick_reserved[dir] = true; mif_err_limited("busy\n"); ret = -EBUSY; goto exit; } desc_info = &dc->desc_info[dir]; /* save src_wp and src_rp to prevent dst overflow */ src_wp = desc_info->src_wp; src_rp = dit_get_snapshot_next_head(dir, DIT_SRC_DESC_RING, desc_info->src_desc_ring_len); if (circ_empty(src_wp, src_rp)) { ret = -ENODATA; goto exit; } /* check dst buffer space */ if (circ_get_usage(desc_info->src_desc_ring_len, src_wp, src_rp) > dit_get_dst_data_buffer_free_space(dir)) { dc->kick_reserved[dir] = true; mif_err_limited("not enough dst data buffer (dir:%d)\n", dir); ret = -ENOSPC; goto exit; } switch (dir) { case DIT_DIR_TX: cpif_set_bit(kick_mask, TX_COMMAND_BIT); break; case DIT_DIR_RX: cpif_set_bit(kick_mask, RX_COMMAND_BIT); break; default: break; } dc->kicked[dir] = true; dc->kick_reserved[dir] = false; exit: spin_unlock_irqrestore(&dc->src_lock, flags); if (ret) return ret; dit_set_src_desc_kick_range(dir, src_wp, src_rp); #if IS_ENABLED(CONFIG_CPU_IDLE) exynos_update_ip_idle_status(dc->idle_ip_index, DIT_IDLE_IP_ACTIVE); #endif WRITE_REG_VALUE(dc, kick_mask, DIT_REG_SW_COMMAND); return 0; } EXPORT_SYMBOL(dit_kick); static bool dit_check_nat_enabled(void) { unsigned int ring_num; for (ring_num = DIT_DST_DESC_RING_1; ring_num < DIT_DST_DESC_RING_MAX; ring_num++) { if (dit_check_dst_ready(DIT_DIR_RX, ring_num) == 0) return true; } return false; } static void dit_check_clat_enabled_internal(struct io_device *iod, void *args) { bool *enabled = (bool *)args; if (*enabled || !dc->ld->is_ps_ch(iod->ch)) return; if (iod->clat_ndev) *enabled = true; } static bool dit_check_clat_enabled(void) { bool enabled = false; if (unlikely(!dc->ld)) return false; iodevs_for_each(dc->ld->msd, dit_check_clat_enabled_internal, &enabled); return enabled; } static int dit_reg_backup_restore_internal(bool backup, const u16 *offset, const u16 *size, void **buf, const unsigned int arr_len) { unsigned int i; int ret = 0; for (i = 0; i < arr_len; i++) { if (!buf[i]) { buf[i] = kvzalloc(size[i], GFP_KERNEL); if (!buf[i]) { ret = -ENOMEM; goto exit; } } if (backup) BACKUP_REG_VALUE(dc, buf[i], offset[i], size[i]); else RESTORE_REG_VALUE(dc, buf[i], offset[i], size[i]); } exit: /* reset buffer if failed to backup */ if (unlikely(ret && backup)) { for (i = 0; i < arr_len; i++) { if (buf[i]) memset(buf[i], 0, size[i]); } } return ret; } static int dit_reg_backup_restore(bool backup) { /* NAT */ static const u16 nat_offset[] = { DIT_REG_NAT_LOCAL_ADDR, DIT_REG_NAT_ETHERNET_DST_MAC_ADDR_0, DIT_REG_NAT_RX_PORT_TABLE_SLOT, }; static const u16 nat_size[] = { (DIT_REG_NAT_LOCAL_ADDR_MAX * DIT_REG_NAT_LOCAL_INTERVAL), (DIT_REG_NAT_LOCAL_ADDR_MAX * DIT_REG_ETHERNET_MAC_INTERVAL), (DIT_REG_NAT_LOCAL_PORT_MAX * DIT_REG_NAT_LOCAL_INTERVAL), }; static const unsigned int nat_len = ARRAY_SIZE(nat_offset); static void *nat_buf[ARRAY_SIZE(nat_offset)]; /* CLAT */ static const u16 clat_offset[] = { DIT_REG_CLAT_TX_FILTER, DIT_REG_CLAT_TX_PLAT_PREFIX_0, DIT_REG_CLAT_TX_CLAT_SRC_0, }; static const u16 clat_size[] = { (DIT_REG_CLAT_ADDR_MAX * DIT_REG_CLAT_TX_FILTER_INTERVAL), (DIT_REG_CLAT_ADDR_MAX * DIT_REG_CLAT_TX_PLAT_PREFIX_INTERVAL), (DIT_REG_CLAT_ADDR_MAX * DIT_REG_CLAT_TX_CLAT_SRC_INTERVAL), }; static const unsigned int clat_len = ARRAY_SIZE(clat_offset); static void *clat_buf[ARRAY_SIZE(clat_offset)]; int ret = 0; if (unlikely(!dc)) return -EPERM; /* NAT */ if (dit_check_nat_enabled()) { ret = dit_reg_backup_restore_internal(backup, nat_offset, nat_size, nat_buf, nat_len); if (ret) goto error; } /* CLAT */ if (dit_check_clat_enabled()) { ret = dit_reg_backup_restore_internal(backup, clat_offset, clat_size, clat_buf, clat_len); if (ret) goto error; } return 0; error: mif_err("backup/restore failed is_backup:%d, ret:%d\n", backup, ret); return ret; } #define POOL_PAGE_SIZE 32768 static int dit_init_page_pool(enum dit_direction dir, enum dit_desc_ring ring_num) { struct dit_desc_info *desc_info; u64 total_page_count; u64 num_pkt_per_page; u64 max_pkt_size; if (!dc->use_page_recycling_rx) return 0; /* Support Rx DST0 only */ if (dir != DIT_DIR_RX || ring_num != DIT_DST_DESC_RING_0) return 0; desc_info = &dc->desc_info[dir]; if (desc_info->dst_page_pool[ring_num]) return 0; max_pkt_size = desc_info->buf_size + dc->page_recycling_skb_padding + sizeof(struct skb_shared_info); num_pkt_per_page = POOL_PAGE_SIZE / max_pkt_size; total_page_count = desc_info->dst_desc_ring_len / num_pkt_per_page; desc_info->dst_page_pool[ring_num] = cpif_page_pool_create(total_page_count, POOL_PAGE_SIZE); if (unlikely(!desc_info->dst_page_pool[ring_num])) return -ENOMEM; cpif_page_init_tmp_page(desc_info->dst_page_pool[ring_num]); return 0; } static int dit_init_hw(void) { unsigned int dir; unsigned int count = 0; const u16 port_offset_start[DIT_DIR_MAX] = { DIT_REG_NAT_TX_PORT_INIT_START, DIT_REG_NAT_RX_PORT_INIT_START }; const u16 port_offset_done[DIT_DIR_MAX] = { DIT_REG_NAT_TX_PORT_INIT_DONE, DIT_REG_NAT_RX_PORT_INIT_DONE }; /* set Tx/Rx port table to all zero * it requires 20us at 100MHz until DONE. */ for (dir = 0; dir < DIT_DIR_MAX; dir++) { WRITE_REG_VALUE(dc, 0x0, port_offset_done[dir]); WRITE_REG_VALUE(dc, 0x1, port_offset_start[dir]); while (++count < 100) { udelay(20); if (READ_REG_VALUE(dc, port_offset_done[dir])) { break; } } if (count >= 100) { mif_err("PORT_INIT_DONE failed dir:%d\n", dir); return -EIO; } } WRITE_REG_VALUE(dc, 0x4020, DIT_REG_DMA_INIT_DATA); WRITE_REG_VALUE(dc, BIT(DMA_INIT_COMMAND_BIT), DIT_REG_SW_COMMAND); WRITE_REG_VALUE(dc, 0x0, DIT_REG_DMA_CHKSUM_OFF); WRITE_REG_VALUE(dc, 0xF, DIT_REG_NAT_ZERO_CHK_OFF); WRITE_REG_VALUE(dc, BIT(RX_ETHERNET_EN_BIT), DIT_REG_NAT_ETHERNET_EN); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_TX_DESC_CTRL_SRC); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_TX_DESC_CTRL_DST); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_TX_HEAD_CTRL); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_TX_MOD_HD_CTRL); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_TX_PKT_CTRL); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_TX_CHKSUM_CTRL); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_RX_DESC_CTRL_SRC); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_RX_DESC_CTRL_DST); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_RX_HEAD_CTRL); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_RX_MOD_HD_CTRL); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_RX_PKT_CTRL); WRITE_REG_VALUE(dc, DIT_RX_BURST_16BEAT, DIT_REG_RX_CHKSUM_CTRL); WRITE_REG_VALUE(dc, DIT_INT_ENABLE_MASK, DIT_REG_INT_ENABLE); WRITE_REG_VALUE(dc, DIT_INT_MASK_MASK, DIT_REG_INT_MASK); WRITE_REG_VALUE(dc, DIT_ALL_INT_PENDING_MASK, DIT_REG_INT_PENDING); WRITE_REG_VALUE(dc, 0x0, DIT_REG_CLK_GT_OFF); DIT_INDIRECT_CALL(dc, do_init_hw); if (!dc->reg_version) DIT_INDIRECT_CALL(dc, get_reg_version, &dc->reg_version); WRITE_SHR_VALUE(dc, dc->sharability_value); return 0; } static int dit_init_desc(enum dit_direction dir) { struct dit_desc_info *desc_info = &dc->desc_info[dir]; void *buf = NULL; unsigned int buf_size; phys_addr_t p_desc; int ret = 0, ring_num; u32 offset_lo = 0, offset_hi = 0; if (!desc_info->src_desc_ring) { buf_size = sizeof(struct dit_src_desc) * (desc_info->src_desc_ring_len + DIT_SRC_DESC_RING_LEN_PADDING); if (dc->use_dma_map) { buf = dma_alloc_coherent(dc->dev, buf_size, &desc_info->src_desc_ring_daddr, GFP_KERNEL); } else { buf = devm_kzalloc(dc->dev, buf_size, GFP_KERNEL); } if (!buf) { mif_err("dit dir[%d] src desc alloc failed\n", dir); return -ENOMEM; } desc_info->src_desc_ring = buf; if (!dc->use_dma_map) desc_info->src_desc_ring_daddr = virt_to_phys(buf); } p_desc = desc_info->src_desc_ring_daddr; if (dir == DIT_DIR_TX) { offset_lo = DIT_REG_TX_RING_START_ADDR_0_SRC; offset_hi = DIT_REG_TX_RING_START_ADDR_1_SRC; } else { offset_lo = DIT_REG_RX_RING_START_ADDR_0_SRC; offset_hi = DIT_REG_RX_RING_START_ADDR_1_SRC; } WRITE_REG_PADDR_LO(dc, p_desc, offset_lo); WRITE_REG_PADDR_HI(dc, p_desc, offset_hi); if (!desc_info->src_skb_buf) { buf_size = sizeof(struct sk_buff *) * desc_info->src_desc_ring_len; buf = kvzalloc(buf_size, GFP_KERNEL); if (!buf) { mif_err("dit dir[%d] src skb container alloc failed\n", dir); return -ENOMEM; } desc_info->src_skb_buf = buf; } for (ring_num = DIT_DST_DESC_RING_0; ring_num < DIT_DST_DESC_RING_MAX; ring_num++) { offset_lo = 0; offset_hi = 0; if (!desc_info->dst_desc_ring[ring_num]) { buf_size = sizeof(struct dit_dst_desc) * (desc_info->dst_desc_ring_len + DIT_DST_DESC_RING_LEN_PADDING); if (dc->use_dma_map) { buf = dma_alloc_coherent(dc->dev, buf_size, &desc_info->dst_desc_ring_daddr[ring_num], GFP_KERNEL); } else { buf = devm_kzalloc(dc->dev, buf_size, GFP_KERNEL); } if (!buf) { mif_err("dit dir[%d] dst desc[%d] alloc failed\n", dir, ring_num); return -ENOMEM; } desc_info->dst_desc_ring[ring_num] = buf; if (!dc->use_dma_map) desc_info->dst_desc_ring_daddr[ring_num] = virt_to_phys(buf); } ret = dit_init_page_pool(dir, ring_num); if (ret) { mif_err("dit dir[%d] dst desc[%d] page pool init failed\n", dir, ring_num); return -ENOMEM; } p_desc = desc_info->dst_desc_ring_daddr[ring_num]; switch (ring_num) { case DIT_DST_DESC_RING_0: if (dir == DIT_DIR_TX) { offset_lo = DIT_REG_TX_RING_START_ADDR_0_DST0; offset_hi = DIT_REG_TX_RING_START_ADDR_1_DST0; ret = dit_fill_tx_dst_data_buffer(ring_num, desc_info->dst_desc_ring_len); } else { offset_lo = DIT_REG_RX_RING_START_ADDR_0_DST0; offset_hi = DIT_REG_RX_RING_START_ADDR_1_DST0; ret = dit_fill_rx_dst_data_buffer(ring_num, desc_info->dst_desc_ring_len, true); } if (ret) { mif_err("dit dir[%d] dst desc[%d] buffer fill failed\n", dir, ring_num); return -ENOMEM; } break; case DIT_DST_DESC_RING_1: if (dir == DIT_DIR_TX) { offset_lo = DIT_REG_TX_RING_START_ADDR_0_DST1; offset_hi = DIT_REG_TX_RING_START_ADDR_1_DST1; } else { offset_lo = DIT_REG_RX_RING_START_ADDR_0_DST1; offset_hi = DIT_REG_RX_RING_START_ADDR_1_DST1; } break; case DIT_DST_DESC_RING_2: if (dir == DIT_DIR_TX) { offset_lo = DIT_REG_TX_RING_START_ADDR_0_DST2; offset_hi = DIT_REG_TX_RING_START_ADDR_1_DST2; } else { offset_lo = DIT_REG_RX_RING_START_ADDR_0_DST2; offset_hi = DIT_REG_RX_RING_START_ADDR_1_DST2; } break; default: break; } if (offset_lo && offset_hi) { WRITE_REG_PADDR_LO(dc, p_desc, offset_lo); WRITE_REG_PADDR_HI(dc, p_desc, offset_hi); } dit_set_dst_desc_int_range(dir, ring_num); } DIT_INDIRECT_CALL(dc, do_init_desc, dir); mif_info("dir:%d src_len:%d dst_len:%d\n", dir, desc_info->src_desc_ring_len, desc_info->dst_desc_ring_len); return 0; } int dit_init(struct link_device *ld, enum dit_init_type type, enum dit_store_type store) { unsigned long flags; unsigned int dir; int ret = 0; if (unlikely(!dc)) { mif_err("dit not created\n"); return -EPERM; } /* ld can be null if it is set before */ if (!ld && !dc->ld) { mif_err("link device set failed\n"); return -EINVAL; } spin_lock_irqsave(&dc->src_lock, flags); if (type == DIT_INIT_RETRY && !dc->init_reserved) { spin_unlock_irqrestore(&dc->src_lock, flags); return -EAGAIN; } if (dit_is_kicked_any()) { if (type != DIT_INIT_DEINIT) dc->init_reserved = true; spin_unlock_irqrestore(&dc->src_lock, flags); return -EEXIST; } if (atomic_inc_return(&dc->init_running) > 1) { spin_unlock_irqrestore(&dc->src_lock, flags); ret = -EBUSY; goto exit; } dc->init_done = false; spin_unlock_irqrestore(&dc->src_lock, flags); if (store == DIT_STORE_BACKUP) { ret = dit_reg_backup_restore(true); if (ret) goto exit; } if (type == DIT_INIT_DEINIT) goto exit; for (dir = 0; dir < DIT_DIR_MAX; dir++) { ret = dit_init_desc(dir); if (ret) { mif_err("dit desc init failed\n"); goto exit; } } ret = dit_init_hw(); if (ret) { mif_err("dit hw init failed\n"); goto exit; } if (store == DIT_STORE_RESTORE) { ret = dit_reg_backup_restore(false); if (ret) goto exit; } ret = dit_net_init(dc); if (ret) { mif_err("dit net init failed\n"); goto exit; } if (ld) dc->ld = ld; spin_lock_irqsave(&dc->src_lock, flags); dc->init_done = true; dc->init_reserved = false; dit_clean_reg_value_with_ext_lock(); spin_unlock_irqrestore(&dc->src_lock, flags); mif_info("dit init done. hw_ver:0x%08X\n", dc->hw_version); exit: atomic_dec(&dc->init_running); if (ret || type == DIT_INIT_DEINIT) return ret; dit_kick(DIT_DIR_TX, true); dit_kick(DIT_DIR_RX, true); return 0; } EXPORT_SYMBOL(dit_init); static int dit_register_irq(struct platform_device *pdev) { struct device *dev = &pdev->dev; int ret = 0; int i; if (!dc->irq_len) { mif_err("dit irq not defined\n"); return -ENODEV; } dc->irq_buf = devm_kzalloc(dev, sizeof(int) * dc->irq_len, GFP_KERNEL); if (!dc->irq_buf) { mif_err("dit irq buf alloc failed\n"); ret = -ENOMEM; goto error; } for (i = 0; i < dc->irq_len; i++) { int irq_num; irq_num = platform_get_irq_byname(pdev, dc->irq_name[i]); ret = devm_request_irq(dev, irq_num, dit_irq_handler, 0, dc->irq_name[i], &dc->irq_pending_bit[i]); if (ret) { mif_err("failed to request irq: %d, ret: %d\n", i, ret); ret = -EIO; goto error; } if (dc->irq_pending_bit[i] == TX_DST0_INT_PENDING_BIT) dc->irq_num_tx = irq_num; dc->irq_buf[i] = irq_num; } return 0; error: if (dc->irq_buf) { devm_kfree(dev, dc->irq_buf); dc = NULL; } return ret; } static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dit_desc_info *desc_info; ssize_t count = 0; unsigned int wp, rp, desc_len; unsigned int dir, ring_num; count += scnprintf(&buf[count], PAGE_SIZE - count, "hw_ver:0x%08X reg_ver:0x%X\n", dc->hw_version, dc->reg_version); count += scnprintf(&buf[count], PAGE_SIZE - count, "use tx:%d rx:%d(stop:%d) clat:%d page_recycle:%d\n", dc->use_dir[DIT_DIR_TX], dc->use_dir[DIT_DIR_RX], dc->stop_enqueue[DIT_DIR_RX], dc->use_clat, dc->use_page_recycling_rx); for (dir = 0; dir < DIT_DIR_MAX; dir++) { desc_info = &dc->desc_info[dir]; for (ring_num = 0; ring_num < DIT_DESC_RING_MAX; ring_num++) { if (ring_num == DIT_SRC_DESC_RING) { wp = desc_info->src_wp; rp = desc_info->src_rp; desc_len = desc_info->src_desc_ring_len; } else { wp = desc_info->dst_wp[ring_num]; rp = desc_info->dst_rp[ring_num]; desc_len = desc_info->dst_desc_ring_len; } count += scnprintf(&buf[count], PAGE_SIZE - count, "%s max_usage(d)/alloc(d)/map(d)/total: %u/%u/%u/%u\n", snapshot[dir][ring_num].name, snapshot[dir][ring_num].max_usage, snapshot[dir][ring_num].alloc_skbs, snapshot[dir][ring_num].dma_maps, desc_len); count += scnprintf(&buf[count], PAGE_SIZE - count, " wp: %u, rp: %u\n", wp, rp); count += scnprintf(&buf[count], PAGE_SIZE - count, " kicked head: %d, tail: %d, packets: %llu\n", snapshot[dir][ring_num].head, snapshot[dir][ring_num].tail, snapshot[dir][ring_num].packets); count += scnprintf(&buf[count], PAGE_SIZE - count, " total packets: %llu, clat: %llu\n", snapshot[dir][ring_num].total_packets, snapshot[dir][ring_num].clat_packets); } } return count; } static ssize_t register_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t count = 0; int i = 0; count += scnprintf(&buf[count], PAGE_SIZE - count, "INT_PENDING: 0x%X\n", READ_REG_VALUE(dc, DIT_REG_INT_PENDING)); count += scnprintf(&buf[count], PAGE_SIZE - count, "STATUS: 0x%X\n", READ_REG_VALUE(dc, DIT_REG_STATUS)); count += scnprintf(&buf[count], PAGE_SIZE - count, "NAT Local Address\n"); for (i = 0; i < DIT_REG_NAT_LOCAL_ADDR_MAX; i++) { count += scnprintf(&buf[count], PAGE_SIZE - count, " [%02d] src:0x%08X%04X, dst:0x%08X/0x%08X%04X\n", i, ntohl(READ_REG_VALUE(dc, DIT_REG_NAT_ETHERNET_SRC_MAC_ADDR_0 + (i * DIT_REG_ETHERNET_MAC_INTERVAL))), ntohs(READ_REG_VALUE(dc, DIT_REG_NAT_ETHERNET_SRC_MAC_ADDR_1 + (i * DIT_REG_ETHERNET_MAC_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_NAT_LOCAL_ADDR + (i * DIT_REG_NAT_LOCAL_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_NAT_ETHERNET_DST_MAC_ADDR_0 + (i * DIT_REG_ETHERNET_MAC_INTERVAL))), ntohs(READ_REG_VALUE(dc, DIT_REG_NAT_ETHERNET_DST_MAC_ADDR_1 + (i * DIT_REG_ETHERNET_MAC_INTERVAL)))); } count += scnprintf(&buf[count], PAGE_SIZE - count, "CLAT Address\n"); for (i = 0; i < DIT_REG_CLAT_ADDR_MAX; i++) { count += scnprintf(&buf[count], PAGE_SIZE - count, " [%02d] v4:0x%08X, v6:0x%08X%08X%08X%08X, prx:0x%08X%08X%08X\n", i, ntohl(READ_REG_VALUE(dc, DIT_REG_CLAT_TX_FILTER + (i * DIT_REG_CLAT_TX_FILTER_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_CLAT_TX_CLAT_SRC_0 + (i * DIT_REG_CLAT_TX_CLAT_SRC_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_CLAT_TX_CLAT_SRC_1 + (i * DIT_REG_CLAT_TX_CLAT_SRC_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_CLAT_TX_CLAT_SRC_2 + (i * DIT_REG_CLAT_TX_CLAT_SRC_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_CLAT_TX_CLAT_SRC_3 + (i * DIT_REG_CLAT_TX_CLAT_SRC_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_CLAT_TX_PLAT_PREFIX_0 + (i * DIT_REG_CLAT_TX_PLAT_PREFIX_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_CLAT_TX_PLAT_PREFIX_1 + (i * DIT_REG_CLAT_TX_PLAT_PREFIX_INTERVAL))), ntohl(READ_REG_VALUE(dc, DIT_REG_CLAT_TX_PLAT_PREFIX_2 + (i * DIT_REG_CLAT_TX_PLAT_PREFIX_INTERVAL)))); } count += scnprintf(&buf[count], PAGE_SIZE - count, "check logs for port table\n"); dit_print_dump(DIT_DIR_TX, DIT_DUMP_ALL); dit_print_dump(DIT_DIR_RX, DIT_DUMP_ALL); return count; } #if defined(DIT_DEBUG) static ssize_t debug_set_rx_port_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct nat_local_port local_port; unsigned int index; int ret; ret = sscanf(buf, "%u %x", &index, &local_port.hw_val); if (ret < 1) return -EINVAL; if (index >= DIT_REG_NAT_LOCAL_PORT_MAX) return -EINVAL; dit_enqueue_reg_value(local_port.hw_val, DIT_REG_NAT_RX_PORT_TABLE_SLOT + (index * DIT_REG_NAT_LOCAL_INTERVAL)); return count; } static ssize_t debug_set_local_addr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { u8 eth_src_str[(ETH_ALEN * 2) + 1]; u8 eth_dst_str[(ETH_ALEN * 2) + 1]; u64 eth_src_addr; u64 eth_dst_addr; u32 ip_addr; unsigned int index; unsigned long flags; int ret; /* for example, "0 D6CFEB352CF4 C0A82A5D 2AAD159CDE96" is for packets * from D6CFEB352CF4(rndis0) to 192.168.42.93/2AAD159CDE96(neigh) */ ret = sscanf(buf, "%u %12s %x %12s", &index, eth_src_str, &ip_addr, eth_dst_str); if (ret < 1) return -EINVAL; ret = kstrtou64(eth_src_str, 16, ð_src_addr); if (ret) return ret; ret = kstrtou64(eth_dst_str, 16, ð_dst_addr); if (ret) return ret; if (index >= DIT_REG_NAT_LOCAL_ADDR_MAX) return -EINVAL; spin_lock_irqsave(&dc->src_lock, flags); dit_enqueue_reg_value_with_ext_lock(htonl(eth_src_addr >> 16), DIT_REG_NAT_ETHERNET_SRC_MAC_ADDR_0 + (index * DIT_REG_ETHERNET_MAC_INTERVAL)); dit_enqueue_reg_value_with_ext_lock(htons(eth_src_addr & 0xFFFF), DIT_REG_NAT_ETHERNET_SRC_MAC_ADDR_1 + (index * DIT_REG_ETHERNET_MAC_INTERVAL)); dit_enqueue_reg_value_with_ext_lock(htonl(ip_addr), DIT_REG_NAT_LOCAL_ADDR + (index * DIT_REG_NAT_LOCAL_INTERVAL)); dit_enqueue_reg_value_with_ext_lock(htonl(eth_dst_addr >> 16), DIT_REG_NAT_ETHERNET_DST_MAC_ADDR_0 + (index * DIT_REG_ETHERNET_MAC_INTERVAL)); dit_enqueue_reg_value_with_ext_lock(htons(eth_dst_addr & 0xFFFF), DIT_REG_NAT_ETHERNET_DST_MAC_ADDR_1 + (index * DIT_REG_ETHERNET_MAC_INTERVAL)); dit_enqueue_reg_value_with_ext_lock(htons(ETH_P_IP), DIT_REG_NAT_ETHERNET_TYPE + (index * DIT_REG_ETHERNET_MAC_INTERVAL)); spin_unlock_irqrestore(&dc->src_lock, flags); return count; } static ssize_t debug_reset_usage_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned int dir, ring_num; unsigned int reset_ring; int ret; ret = sscanf(buf, "%u %u", &dir, &reset_ring); if (ret < 1) return -EINVAL; if (dir >= DIT_DIR_MAX) return -EINVAL; if (reset_ring > DIT_DESC_RING_MAX) return -EINVAL; for (ring_num = DIT_DST_DESC_RING_0; ring_num < DIT_DESC_RING_MAX; ring_num++) { if ((ring_num == reset_ring) || (reset_ring == DIT_DESC_RING_MAX)) snapshot[dir][ring_num].max_usage = 0; } return count; } static ssize_t debug_use_tx_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int flag; int ret; ret = kstrtoint(buf, 0, &flag); if (ret) return -EINVAL; dc->use_dir[DIT_DIR_TX] = (flag > 0 ? true : false); return count; } static ssize_t debug_use_tx_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "use_tx: %d\n", dc->use_dir[DIT_DIR_TX]); } static ssize_t debug_use_rx_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned int flag; int ret; ret = kstrtoint(buf, 0, &flag); if (ret) return -EINVAL; dc->use_dir[DIT_DIR_RX] = (flag > 0 ? true : false); return count; } static ssize_t debug_use_rx_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "use_rx: %d\n", dc->use_dir[DIT_DIR_RX]); } static ssize_t debug_use_clat_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct clat_info clat; unsigned int i; unsigned int flag; int ret; struct mem_link_device *mld = ld_to_mem_link_device(dc->ld); ret = kstrtoint(buf, 0, &flag); if (ret) return -EINVAL; if (!flag) { memset(&clat, 0, sizeof(clat)); for (i = 0; i < DIT_REG_CLAT_ADDR_MAX; i++) { clat.clat_index = i; scnprintf(clat.ipv6_iface, IFNAMSIZ, "rmnet%d", i); dit_hal_set_clat_info(mld, &clat); } } dc->use_clat = (flag > 0 ? true : false); return count; } static ssize_t debug_use_clat_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "use_clat: %d\n", dc->use_clat); } static ssize_t debug_hal_support_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned int flag; int ret; ret = kstrtoint(buf, 0, &flag); if (ret) return -EINVAL; dc->hal_support = (flag > 0 ? true : false); return count; } static ssize_t debug_hal_support_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "hal_support: %d\n", dc->hal_support); } #endif #if defined(DIT_DEBUG_LOW) static ssize_t debug_pktgen_ch_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "pktgen ch: %d\n", dc->pktgen_ch); } static ssize_t debug_pktgen_ch_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct io_device *iod; int ch; int ret; ret = kstrtoint(buf, 0, &ch); if (ret) return -EINVAL; dc->pktgen_ch = ch; mif_info("dc->pktgen_ch = %d, 0x%x\n", dc->pktgen_ch, dc->pktgen_ch); if (!dc->ld) goto out; iod = link_get_iod_with_channel(dc->ld, dc->pktgen_ch); if (iod) DIT_INDIRECT_CALL(dc, set_reg_upstream, iod->ndev); else DIT_INDIRECT_CALL(dc, set_reg_upstream, NULL); out: return count; } static ssize_t debug_set_force_bypass_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int bypass; int ret; ret = kstrtoint(buf, 0, &bypass); if (ret) return -EINVAL; dc->force_bypass = bypass; return count; } #endif static DEVICE_ATTR_RO(status); static DEVICE_ATTR_RO(register); #if defined(DIT_DEBUG) static DEVICE_ATTR_WO(debug_set_rx_port); static DEVICE_ATTR_WO(debug_set_local_addr); static DEVICE_ATTR_WO(debug_reset_usage); static DEVICE_ATTR_RW(debug_use_tx); static DEVICE_ATTR_RW(debug_use_rx); static DEVICE_ATTR_RW(debug_use_clat); static DEVICE_ATTR_RW(debug_hal_support); #endif #if defined(DIT_DEBUG_LOW) static DEVICE_ATTR_RW(debug_pktgen_ch); static DEVICE_ATTR_WO(debug_set_force_bypass); #endif static struct attribute *dit_attrs[] = { &dev_attr_status.attr, &dev_attr_register.attr, #if defined(DIT_DEBUG) &dev_attr_debug_set_rx_port.attr, &dev_attr_debug_set_local_addr.attr, &dev_attr_debug_reset_usage.attr, &dev_attr_debug_use_tx.attr, &dev_attr_debug_use_rx.attr, &dev_attr_debug_use_clat.attr, &dev_attr_debug_hal_support.attr, #endif #if defined(DIT_DEBUG_LOW) &dev_attr_debug_pktgen_ch.attr, &dev_attr_debug_set_force_bypass.attr, #endif NULL, }; ATTRIBUTE_GROUPS(dit); bool dit_check_dir_use_queue(enum dit_direction dir, unsigned int queue_num) { struct dit_desc_info *desc_info; if (!dc) return false; desc_info = &dc->desc_info[dir]; if (!dc->use_dir[dir] || queue_num != desc_info->pktproc_queue_num) return false; if (dc->stop_enqueue[dir] && dit_check_queues_empty(dir)) return false; return true; } EXPORT_SYMBOL(dit_check_dir_use_queue); int dit_get_irq_affinity(void) { if (!dc) return -EPERM; return dc->irq_affinity; } EXPORT_SYMBOL(dit_get_irq_affinity); int dit_set_irq_affinity(int affinity) { int i; int num_cpu; if (IS_ENABLED(CONFIG_IRQ_SBALANCE)) return 0; if (!dc) return -EPERM; #if defined(CONFIG_VENDOR_NR_CPUS) num_cpu = CONFIG_VENDOR_NR_CPUS; #else num_cpu = 8; #endif if (affinity >= num_cpu) { mif_err("affinity:%d error. cpu max:%d\n", affinity, num_cpu); return -EINVAL; } dc->irq_affinity = affinity; for (i = 0; i < dc->irq_len; i++) { int val = dc->irq_affinity; if (dc->irq_buf[i] == dc->irq_num_tx) val = dc->irq_affinity_tx; mif_debug("num:%d affinity:%d\n", dc->irq_buf[i], val); irq_set_affinity_hint(dc->irq_buf[i], cpumask_of(val)); } return 0; } EXPORT_SYMBOL(dit_set_irq_affinity); int dit_set_pktproc_queue_num(enum dit_direction dir, u32 queue_num) { struct dit_desc_info *desc_info; if (!dc) return -EPERM; desc_info = &dc->desc_info[dir]; desc_info->pktproc_queue_num = queue_num; mif_info("dir:%d queue_num:%d\n", dir, desc_info->pktproc_queue_num); return 0; } EXPORT_SYMBOL(dit_set_pktproc_queue_num); int dit_set_buf_size(enum dit_direction dir, u32 size) { struct dit_desc_info *desc_info = NULL; if (!dc) return -EPERM; desc_info = &dc->desc_info[dir]; desc_info->buf_size = size; mif_info("dir:%d size:%d\n", dir, desc_info->buf_size); return 0; } EXPORT_SYMBOL(dit_set_buf_size); int dit_set_pktproc_base(enum dit_direction dir, phys_addr_t base) { struct dit_desc_info *desc_info = NULL; if (!dc) return -EPERM; desc_info = &dc->desc_info[dir]; desc_info->pktproc_pbase = base; mif_info("dir:%d base:%pap\n", dir, &desc_info->pktproc_pbase); return 0; } EXPORT_SYMBOL(dit_set_pktproc_base); int dit_set_desc_ring_len(enum dit_direction dir, u32 len) { struct dit_desc_info *desc_info = NULL; if (!dc) return -EPERM; desc_info = &dc->desc_info[dir]; desc_info->src_desc_ring_len = len; desc_info->dst_desc_ring_len = len; if (dir == DIT_DIR_RX) { desc_info->src_desc_ring_len += dc->rx_extra_desc_ring_len; desc_info->dst_desc_ring_len += dc->rx_extra_desc_ring_len; } mif_info("dir:%d len:%d src_len:%d dst_len:%d\n", dir, len, desc_info->src_desc_ring_len, desc_info->dst_desc_ring_len); return 0; } EXPORT_SYMBOL(dit_set_desc_ring_len); int dit_get_src_usage(enum dit_direction dir, u32 *usage) { struct dit_desc_info *desc_info = NULL; if (!dc) return -EPERM; desc_info = &dc->desc_info[dir]; *usage = circ_get_usage(desc_info->src_desc_ring_len, desc_info->src_wp, desc_info->src_rp); mif_debug("dir:%d usage:%d\n", dir, *usage); return 0; } EXPORT_SYMBOL(dit_get_src_usage); int dit_reset_dst_wp_rp(enum dit_direction dir) { struct dit_desc_info *desc_info = NULL; int ring_num; if (!dc) return -EPERM; desc_info = &dc->desc_info[dir]; for (ring_num = DIT_DST_DESC_RING_0; ring_num < DIT_DST_DESC_RING_MAX; ring_num++) { desc_info->dst_wp[ring_num] = 0; desc_info->dst_rp[ring_num] = 0; dit_set_dst_desc_int_range(dir, ring_num); } return 0; } EXPORT_SYMBOL(dit_reset_dst_wp_rp); struct net_device *dit_get_netdev(void) { if (!dc) return NULL; return dc->netdev; } EXPORT_SYMBOL(dit_get_netdev); int dit_stop_napi_poll(void) { if (!dc) return -EPERM; atomic_set(&dc->stop_napi_poll, 1); return 0; } EXPORT_SYMBOL(dit_stop_napi_poll); bool dit_support_clat(void) { if (!dc) return false; return dc->use_clat; } EXPORT_SYMBOL(dit_support_clat); #if IS_ENABLED(CONFIG_EXYNOS_S2MPU) static int s2mpu_notifier_callback(struct s2mpu_notifier_block *nb, struct s2mpu_notifier_info *ni) { dit_print_dump(DIT_DIR_TX, DIT_DUMP_ALL); dit_print_dump(DIT_DIR_RX, DIT_DUMP_ALL); return S2MPU_NOTIFY_BAD; } #endif #if IS_ENABLED(CONFIG_EXYNOS_ITMON) static int itmon_notifier_callback(struct notifier_block *nb, unsigned long action, void *nb_data) { struct itmon_notifier *itmon_data = nb_data; if (IS_ERR_OR_NULL(itmon_data)) return NOTIFY_DONE; if (itmon_data->port && !strncmp("DIT", itmon_data->port, sizeof("DIT") - 1)) { dit_print_dump(DIT_DIR_TX, DIT_DUMP_ALL); dit_print_dump(DIT_DIR_RX, DIT_DUMP_ALL); return NOTIFY_BAD; } return NOTIFY_DONE; } #endif static void dit_set_hw_specific(void) { #if defined(CONFIG_EXYNOS_DIT_VERSION) dc->hw_version = CONFIG_EXYNOS_DIT_VERSION; #else dc->hw_version = DIT_VERSION(2, 1, 0); #endif switch (exynos_soc_info.product_id) { #if defined(EXYNOS2100_SOC_ID) case EXYNOS2100_SOC_ID: dc->hw_capabilities |= DIT_CAP_MASK_PORT_BIG_ENDIAN; if (exynos_soc_info.revision >= 0x11) dc->hw_capabilities &= ~DIT_CAP_MASK_PORT_BIG_ENDIAN; break; #endif #if defined(S5E9815_SOC_ID) case S5E9815_SOC_ID: dc->hw_capabilities |= DIT_CAP_MASK_PORT_BIG_ENDIAN; if (exynos_soc_info.revision >= 0x1) dc->hw_capabilities &= ~DIT_CAP_MASK_PORT_BIG_ENDIAN; break; #endif default: break; } } static int dit_read_dt(struct device_node *np) { if (!IS_ERR_OR_NULL(dc->sharability_base)) { mif_dt_read_u32(np, "dit_sharability_offset", dc->sharability_offset); mif_dt_read_u32(np, "dit_sharability_value", dc->sharability_value); } mif_dt_read_u32(np, "dit_hw_capabilities", dc->hw_capabilities); mif_dt_read_bool(np, "dit_use_tx", dc->use_dir[DIT_DIR_TX]); mif_dt_read_bool(np, "dit_use_rx", dc->use_dir[DIT_DIR_RX]); mif_dt_read_bool(np, "dit_use_clat", dc->use_clat); mif_dt_read_bool(np, "dit_use_recycling", dc->use_page_recycling_rx); mif_dt_read_bool(np, "dit_hal_support", dc->hal_support); if (dc->hal_support) { mif_dt_read_bool(np, "dit_hal_enqueue_rx", dc->hal_enqueue_rx); if (dc->hal_enqueue_rx) dc->stop_enqueue[DIT_DIR_RX] = true; } mif_dt_read_u32(np, "dit_rx_extra_desc_ring_len", dc->rx_extra_desc_ring_len); mif_dt_read_u32(np, "dit_irq_affinity", dc->irq_affinity); dc->irq_affinity_tx = dc->irq_affinity; return 0; } int dit_create(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; #if IS_ENABLED(CONFIG_EXYNOS_S2MPU) struct s2mpu_notifier_block *s2mpu_nb = NULL; #endif #if IS_ENABLED(CONFIG_EXYNOS_ITMON) struct notifier_block *itmon_nb = NULL; #endif int ret; if (!np) { mif_err("of_node is null\n"); ret = -EINVAL; goto error; } dc = devm_kzalloc(dev, sizeof(struct dit_ctrl_t), GFP_KERNEL); if (!dc) { mif_err("dit ctrl alloc failed\n"); ret = -ENOMEM; goto error; } dc->dev = dev; dc->register_base = devm_platform_ioremap_resource_byname(pdev, "dit"); if (IS_ERR_OR_NULL(dc->register_base)) { mif_err("register devm_ioremap error\n"); ret = -EFAULT; goto error; } dc->sharability_base = devm_platform_ioremap_resource_byname(pdev, "sysreg"); if (IS_ERR_OR_NULL(dc->sharability_base)) { mif_err("sharability devm_ioremap error. use dma map.\n"); dc->use_dma_map = true; } ret = dit_read_dt(np); if (ret) { mif_err("read dt error\n"); goto error; } dit_set_hw_specific(); ret = dit_ver_create(dc); if (ret) { mif_err("dit versioning failed\n"); goto error; } dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); ret = dit_register_irq(pdev); if (ret) { mif_err("register irq error\n"); goto error; } spin_lock_init(&dc->src_lock); INIT_LIST_HEAD(&dc->reg_value_q); atomic_set(&dc->init_running, 0); atomic_set(&dc->stop_napi_poll, 0); spin_lock_init(&dc->rx_buf_lock); dit_set_irq_affinity(dc->irq_affinity); dev_set_drvdata(dev, dc); #if IS_ENABLED(CONFIG_CPU_IDLE) dc->idle_ip_index = exynos_get_idle_ip_index(dev_name(&pdev->dev), 1); if (dc->idle_ip_index < 0) { mif_err("%s idle ip registration failed, ret: %d\n", dev_name(&pdev->dev), dc->idle_ip_index); goto error; } exynos_update_ip_idle_status(dc->idle_ip_index, DIT_IDLE_IP_IDLE); #endif #if IS_ENABLED(CONFIG_EXYNOS_S2MPU) s2mpu_nb = devm_kzalloc(dev, sizeof(struct s2mpu_notifier_block), GFP_KERNEL); if (!s2mpu_nb) { mif_err("s2mpu notifier block alloc failed\n"); goto error; } s2mpu_nb->subsystem = "DIT"; s2mpu_nb->notifier_call = s2mpu_notifier_callback; exynos_s2mpu_notifier_call_register(s2mpu_nb); #endif #if IS_ENABLED(CONFIG_EXYNOS_ITMON) itmon_nb = devm_kzalloc(dev, sizeof(struct notifier_block), GFP_KERNEL); if (!itmon_nb) { mif_err("itmon notifier block alloc failed\n"); goto error; } itmon_nb->notifier_call = itmon_notifier_callback; itmon_notifier_chain_register(itmon_nb); #endif ret = sysfs_create_groups(&dev->kobj, dit_groups); if (ret != 0) { mif_err("sysfs_create_group() error %d\n", ret); goto error; } ret = dit_hal_create(dc); if (ret) { mif_err("dit hal create failed\n"); goto error; } if (dc->use_page_recycling_rx) dc->page_recycling_skb_padding = NET_SKB_PAD + NET_IP_ALIGN; mif_info("dit created. hw_ver:0x%08X tx:%d rx:%d clat:%d hal:%d ext:%d irq:%d pg_r:%d\n", dc->hw_version, dc->use_dir[DIT_DIR_TX], dc->use_dir[DIT_DIR_RX], dc->use_clat, dc->hal_support, dc->rx_extra_desc_ring_len, dc->irq_affinity, dc->use_page_recycling_rx); return 0; error: #if IS_ENABLED(CONFIG_EXYNOS_S2MPU) if (s2mpu_nb) devm_kfree(dev, s2mpu_nb); #endif if (!IS_ERR_OR_NULL(dc->sharability_base)) devm_iounmap(dev, dc->sharability_base); if (!IS_ERR_OR_NULL(dc->register_base)) devm_iounmap(dev, dc->register_base); if (dc) { devm_kfree(dev, dc); dc = NULL; } panic("DIT driver probe failed\n"); return ret; } static int dit_probe(struct platform_device *pdev) { return dit_create(pdev); } static int dit_remove(struct platform_device *pdev) { return 0; } static int dit_suspend(struct device *dev) { struct dit_ctrl_t *dc = dev_get_drvdata(dev); int ret; if (unlikely(!dc) || unlikely(!dc->ld)) return 0; ret = dit_init(NULL, DIT_INIT_DEINIT, DIT_STORE_BACKUP); if (ret) { mif_err("deinit failed ret:%d\n", ret); return ret; } return 0; } static int dit_resume(struct device *dev) { struct dit_ctrl_t *dc = dev_get_drvdata(dev); int ret; if (unlikely(!dc) || unlikely(!dc->ld)) return 0; dit_set_irq_affinity(dc->irq_affinity); ret = dit_init(NULL, DIT_INIT_NORMAL, DIT_STORE_RESTORE); if (ret) { unsigned int dir; mif_err("init failed ret:%d\n", ret); for (dir = 0; dir < DIT_DIR_MAX; dir++) { if (dit_is_busy(dir)) mif_err("busy (dir:%d)\n", dir); } return ret; } return 0; } static const struct dev_pm_ops dit_pm_ops = { SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(dit_suspend, dit_resume) }; static const struct of_device_id dit_dt_match[] = { { .compatible = "samsung,exynos-dit", }, {}, }; MODULE_DEVICE_TABLE(of, dit_dt_match); static struct platform_driver dit_driver = { .probe = dit_probe, .remove = dit_remove, .driver = { .name = "dit", .owner = THIS_MODULE, .of_match_table = of_match_ptr(dit_dt_match), .pm = &dit_pm_ops, .suppress_bind_attrs = true, }, }; module_platform_driver(dit_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Samsung DIT Driver");