/* SPDX-License-Identifier: GPL-2.0 */ #include #include #include #include #include #include #include "sprdwl.h" #include "intf_ops.h" #include "tcp_ack.h" #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) static void sprdwl_tcp_ack_timeout(struct timer_list *t) #else static void sprdwl_tcp_ack_timeout(unsigned long data) #endif { struct sprdwl_tcp_ack_info *ack_info; struct sprdwl_msg_buf *msg; struct sprdwl_tcp_ack_manage *ack_m = NULL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) ack_info = (struct sprdwl_tcp_ack_info *)from_timer(ack_info, t, timer); #else ack_info = (struct sprdwl_tcp_ack_info *)data; #endif ack_m = container_of(ack_info, struct sprdwl_tcp_ack_manage, ack_info[ack_info->ack_info_num]); write_seqlock_bh(&ack_info->seqlock); msg = ack_info->msgbuf; if (ack_info->busy && msg && !ack_info->in_send_msg) { ack_info->msgbuf = NULL; ack_info->drop_cnt = 0; ack_info->in_send_msg = msg; write_sequnlock_bh(&ack_info->seqlock); sprdwl_intf_tx(ack_m->priv, msg); return; } write_sequnlock_bh(&ack_info->seqlock); } void sprdwl_tcp_ack_init(struct sprdwl_priv *priv) { int i; struct sprdwl_tcp_ack_info *ack_info; struct sprdwl_tcp_ack_manage *ack_m = &priv->ack_m; memset(ack_m, 0, sizeof(struct sprdwl_tcp_ack_manage)); ack_m->priv = priv; spin_lock_init(&ack_m->lock); atomic_set(&ack_m->max_drop_cnt, SPRDWL_TCP_ACK_DROP_CNT); ack_m->last_time = jiffies; ack_m->timeout = msecs_to_jiffies(SPRDWL_ACK_OLD_TIME); for (i = 0; i < SPRDWL_TCP_ACK_NUM; i++) { ack_info = &ack_m->ack_info[i]; ack_info->ack_info_num = i; seqlock_init(&ack_info->seqlock); ack_info->last_time = jiffies; ack_info->timeout = msecs_to_jiffies(SPRDWL_ACK_OLD_TIME); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) timer_setup(&ack_info->timer, sprdwl_tcp_ack_timeout, 0); #else setup_timer(&ack_info->timer, sprdwl_tcp_ack_timeout, (unsigned long)ack_info); #endif } atomic_set(&ack_m->enable, 1); ack_m->ack_winsize = MIN_WIN; } void sprdwl_tcp_ack_deinit(struct sprdwl_priv *priv) { int i; struct sprdwl_tcp_ack_manage *ack_m = &priv->ack_m; struct sprdwl_msg_buf *drop_msg = NULL; atomic_set(&ack_m->enable, 0); for (i = 0; i < SPRDWL_TCP_ACK_NUM; i++) { drop_msg = NULL; write_seqlock_bh(&ack_m->ack_info[i].seqlock); del_timer(&ack_m->ack_info[i].timer); drop_msg = ack_m->ack_info[i].msgbuf; ack_m->ack_info[i].msgbuf = NULL; write_sequnlock_bh(&ack_m->ack_info[i].seqlock); if (drop_msg) sprdwl_intf_tcp_drop_msg(priv, drop_msg); } } static int sprdwl_tcp_check_quick_ack(unsigned char *buf, struct sprdwl_tcp_ack_msg *msg) { int ip_hdr_len; unsigned char *temp; struct ethhdr *ethhdr; struct iphdr *iphdr; struct tcphdr *tcphdr; ethhdr = (struct ethhdr *)buf; if (ethhdr->h_proto != htons(ETH_P_IP)) return 0; iphdr = (struct iphdr *)(ethhdr + 1); if (iphdr->version != 4 || iphdr->protocol != IPPROTO_TCP) return 0; ip_hdr_len = iphdr->ihl * 4; temp = (unsigned char *)(iphdr) + ip_hdr_len; tcphdr = (struct tcphdr *)temp; /* TCP_FLAG_ACK */ if (!(temp[13] & 0x10)) return 0; if (temp[13] & 0x8) { msg->saddr = iphdr->daddr; msg->daddr = iphdr->saddr; msg->source = tcphdr->dest; msg->dest = tcphdr->source; msg->seq = ntohl(tcphdr->seq); return 1; } return 0; } static int is_drop_tcp_ack(struct tcphdr *tcphdr, int tcp_tot_len, unsigned short *win_scale) { int drop = 1; int len = tcphdr->doff * 4; unsigned char *ptr; if (tcp_tot_len > len) { drop = 0; } else { len -= sizeof(struct tcphdr); ptr = (unsigned char *)(tcphdr + 1); while ((len > 0) && drop) { int opcode = *ptr++; int opsize; switch (opcode) { case TCPOPT_EOL: break; case TCPOPT_NOP: len--; continue; default: opsize = *ptr++; if (opsize < 2) break; if (opsize > len) break; switch (opcode) { /* TODO: Add other ignore opt */ case TCPOPT_TIMESTAMP: break; case TCPOPT_WINDOW: if (*ptr < 15) *win_scale = (1 << (*ptr)); break; default: drop = 2; } ptr += opsize - 2; len -= opsize; } } } return drop; } /* flag:0 for not tcp ack * 1 for ack which can be drop * 2 for other ack whith more info */ static int sprdwl_tcp_check_ack(unsigned char *buf, struct sprdwl_tcp_ack_msg *msg, unsigned short *win_scale) { int ret; int ip_hdr_len; int tcp_tot_len; unsigned char *temp; struct ethhdr *ethhdr; struct iphdr *iphdr; struct tcphdr *tcphdr; ethhdr = (struct ethhdr *)buf; if (ethhdr->h_proto != htons(ETH_P_IP)) return 0; iphdr = (struct iphdr *)(ethhdr + 1); if (iphdr->version != 4 || iphdr->protocol != IPPROTO_TCP) return 0; ip_hdr_len = iphdr->ihl * 4; temp = (unsigned char *)(iphdr) + ip_hdr_len; tcphdr = (struct tcphdr *)temp; /* TCP_FLAG_ACK */ if (!(temp[13] & 0x10)) return 0; tcp_tot_len = ntohs(iphdr->tot_len) - ip_hdr_len; ret = is_drop_tcp_ack(tcphdr, tcp_tot_len, win_scale); if (ret > 0) { msg->saddr = iphdr->saddr; msg->daddr = iphdr->daddr; msg->source = tcphdr->source; msg->dest = tcphdr->dest; msg->seq = ntohl(tcphdr->ack_seq); msg->win = ntohs(tcphdr->window); } return ret; } /* return val: -1 for not match, others for match */ static int sprdwl_tcp_ack_match(struct sprdwl_tcp_ack_manage *ack_m, struct sprdwl_tcp_ack_msg *ack_msg) { int i, ret = -1; unsigned start; struct sprdwl_tcp_ack_info *ack_info; struct sprdwl_tcp_ack_msg *ack; for (i = 0; ((ret < 0) && (i < SPRDWL_TCP_ACK_NUM)); i++) { ack_info = &ack_m->ack_info[i]; do { start = read_seqbegin(&ack_info->seqlock); ret = -1; ack = &ack_info->ack_msg; if (ack_info->busy && ack->dest == ack_msg->dest && ack->source == ack_msg->source && ack->saddr == ack_msg->saddr && ack->daddr == ack_msg->daddr) ret = i; } while (read_seqretry(&ack_info->seqlock, start)); } return ret; } static void sprdwl_tcp_ack_update(struct sprdwl_tcp_ack_manage *ack_m) { int i; struct sprdwl_tcp_ack_info *ack_info; if (time_after(jiffies, ack_m->last_time + ack_m->timeout)) { spin_lock_bh(&ack_m->lock); ack_m->last_time = jiffies; for (i = SPRDWL_TCP_ACK_NUM - 1; i >= 0; i--) { ack_info = &ack_m->ack_info[i]; write_seqlock_bh(&ack_info->seqlock); if (ack_info->busy && time_after(jiffies, ack_info->last_time + ack_info->timeout)) { ack_m->free_index = i; ack_m->max_num--; ack_info->busy = 0; } write_sequnlock_bh(&ack_info->seqlock); } spin_unlock_bh(&ack_m->lock); } } /* return val: -1 for no index, others for index */ static int sprdwl_tcp_ack_alloc_index(struct sprdwl_tcp_ack_manage *ack_m) { int i, ret = -1; struct sprdwl_tcp_ack_info *ack_info; unsigned start; spin_lock_bh(&ack_m->lock); if (ack_m->max_num == SPRDWL_TCP_ACK_NUM) { spin_unlock_bh(&ack_m->lock); return -1; } if (ack_m->free_index >= 0) { i = ack_m->free_index; ack_m->free_index = -1; ack_m->max_num++; spin_unlock_bh(&ack_m->lock); return i; } for (i = 0; ((ret < 0) && (i < SPRDWL_TCP_ACK_NUM)); i++) { ack_info = &ack_m->ack_info[i]; do { start = read_seqbegin(&ack_info->seqlock); ret = -1; if (!ack_info->busy) { ack_m->free_index = -1; ack_m->max_num++; ret = i; } } while (read_seqretry(&ack_info->seqlock, start)); } spin_unlock_bh(&ack_m->lock); return ret; } /* return val: 0 for not handle tx, 1 for handle tx */ int sprdwl_tcp_ack_handle(struct sprdwl_msg_buf *new_msgbuf, struct sprdwl_tcp_ack_manage *ack_m, struct sprdwl_tcp_ack_info *ack_info, struct sprdwl_tcp_ack_msg *ack_msg, int type) { int quick_ack = 0; struct sprdwl_tcp_ack_msg *ack; int ret = 0; struct sprdwl_msg_buf *drop_msg = NULL; write_seqlock_bh(&ack_info->seqlock); ack_info->last_time = jiffies; ack = &ack_info->ack_msg; if (type == 2) { if (SPRDWL_U32_BEFORE(ack->seq, ack_msg->seq)) { ack->seq = ack_msg->seq; if (ack_info->psh_flag && !SPRDWL_U32_BEFORE(ack_msg->seq, ack_info->psh_seq)) { ack_info->psh_flag = 0; } if (ack_info->msgbuf) { drop_msg = ack_info->msgbuf; ack_info->msgbuf = NULL; del_timer(&ack_info->timer); } ack_info->in_send_msg = NULL; ack_info->drop_cnt = atomic_read(&ack_m->max_drop_cnt); } else { wl_err("%s before abnormal ack: %d, %d\n", __func__, ack->seq, ack_msg->seq); drop_msg = new_msgbuf; ret = 1; } } else if (SPRDWL_U32_BEFORE(ack->seq, ack_msg->seq)) { if (ack_info->msgbuf) { drop_msg = ack_info->msgbuf; ack_info->msgbuf = NULL; } if (ack_info->psh_flag && !SPRDWL_U32_BEFORE(ack_msg->seq, ack_info->psh_seq)) { ack_info->psh_flag = 0; quick_ack = 1; } else { ack_info->drop_cnt++; } ack->seq = ack_msg->seq; if (quick_ack || (!ack_info->in_send_msg && (ack_info->drop_cnt >= atomic_read(&ack_m->max_drop_cnt)))) { ack_info->drop_cnt = 0; ack_info->in_send_msg = new_msgbuf; del_timer(&ack_info->timer); } else { ret = 1; ack_info->msgbuf = new_msgbuf; if (!timer_pending(&ack_info->timer)) mod_timer(&ack_info->timer, (jiffies + msecs_to_jiffies(5))); } } else { wl_err("%s before ack: %d, %d\n", __func__, ack->seq, ack_msg->seq); drop_msg = new_msgbuf; ret = 1; } write_sequnlock_bh(&ack_info->seqlock); if (drop_msg) sprdwl_intf_tcp_drop_msg(ack_m->priv, drop_msg); return ret; } void sprdwl_filter_rx_tcp_ack(struct sprdwl_priv *priv, unsigned char *buf, unsigned plen) { int index; struct sprdwl_tcp_ack_msg ack_msg; struct sprdwl_tcp_ack_info *ack_info; struct sprdwl_tcp_ack_manage *ack_m = &priv->ack_m; if (!atomic_read(&ack_m->enable)) return; if ((plen > MAX_TCP_ACK) || !sprdwl_tcp_check_quick_ack(buf, &ack_msg)) return; index = sprdwl_tcp_ack_match(ack_m, &ack_msg); if (index >= 0) { ack_info = ack_m->ack_info + index; write_seqlock_bh(&ack_info->seqlock); ack_info->psh_flag = 1; ack_info->psh_seq = ack_msg.seq; write_sequnlock_bh(&ack_info->seqlock); } } /* return val: 0 for not filter, 1 for filter */ int sprdwl_filter_send_tcp_ack(struct sprdwl_priv *priv, struct sprdwl_msg_buf *msgbuf, unsigned char *buf, unsigned int plen) { int ret = 0; int index, drop; unsigned short win_scale = 0; unsigned int win = 0; struct sprdwl_tcp_ack_msg ack_msg; struct sprdwl_tcp_ack_msg *ack; struct sprdwl_tcp_ack_info *ack_info; struct sprdwl_tcp_ack_manage *ack_m = &priv->ack_m; if (plen > MAX_TCP_ACK) return 0; sprdwl_tcp_ack_update(ack_m); drop = sprdwl_tcp_check_ack(buf, &ack_msg, &win_scale); if (!drop && (0 == win_scale)) return 0; index = sprdwl_tcp_ack_match(ack_m, &ack_msg); if (index >= 0) { ack_info = ack_m->ack_info + index; if ((0 != win_scale) && (ack_info->win_scale != win_scale)) { write_seqlock_bh(&ack_info->seqlock); ack_info->win_scale = win_scale; write_sequnlock_bh(&ack_info->seqlock); } if (drop > 0 && atomic_read(&ack_m->enable)) {/*lint !e506 */ win = ack_info->win_scale * ack_msg.win; if (win < (ack_m->ack_winsize * SIZE_KB)) drop = 2; ret = sprdwl_tcp_ack_handle(msgbuf, ack_m, ack_info, &ack_msg, drop); } goto out; } index = sprdwl_tcp_ack_alloc_index(ack_m); if (index >= 0) { write_seqlock_bh(&ack_m->ack_info[index].seqlock); ack_m->ack_info[index].busy = 1; ack_m->ack_info[index].psh_flag = 0; ack_m->ack_info[index].last_time = jiffies; ack_m->ack_info[index].drop_cnt = atomic_read(&ack_m->max_drop_cnt); ack_m->ack_info[index].win_scale = (win_scale != 0) ? win_scale : 1; ack = &ack_m->ack_info[index].ack_msg; ack->dest = ack_msg.dest; ack->source = ack_msg.source; ack->saddr = ack_msg.saddr; ack->daddr = ack_msg.daddr; ack->seq = ack_msg.seq; write_sequnlock_bh(&ack_m->ack_info[index].seqlock); } out: return ret; } void sprdwl_move_tcpack_msg(struct sprdwl_priv *priv, struct sprdwl_msg_buf *msg) { struct sprdwl_tcp_ack_info *ack_info; struct sprdwl_tcp_ack_manage *ack_m = &priv->ack_m; int i = 0; if (!atomic_read(&ack_m->enable)) return; if (msg->len > MAX_TCP_ACK) return; for (i = 0; i < SPRDWL_TCP_ACK_NUM; i++) { ack_info = &ack_m->ack_info[i]; write_seqlock_bh(&ack_info->seqlock); if (ack_info->busy && (ack_info->in_send_msg == msg)) ack_info->in_send_msg = NULL; write_sequnlock_bh(&ack_info->seqlock); } } void enable_tcp_ack_delay(char *buf, unsigned char offset) { int enable = buf[offset] - '0'; int i; struct sprdwl_tcp_ack_manage *ack_m = NULL; struct sprdwl_msg_buf *drop_msg = NULL; if (!g_sprdwl_priv) return ; ack_m = &g_sprdwl_priv->ack_m; if (enable == 0 && atomic_read(&ack_m->enable)) { /*lint !e506 */ atomic_set(&ack_m->enable, 0); wl_err("%s, %d, disable DROPTCPAK\n", __func__, __LINE__); for (i = 0; i < SPRDWL_TCP_ACK_NUM; i++) { drop_msg = NULL; write_seqlock_bh(&ack_m->ack_info[i].seqlock); drop_msg = ack_m->ack_info[i].msgbuf; ack_m->ack_info[i].msgbuf = NULL; del_timer(&ack_m->ack_info[i].timer); write_sequnlock_bh(&ack_m->ack_info[i].seqlock); if (drop_msg) sprdwl_intf_tcp_drop_msg(g_sprdwl_priv, drop_msg); } } else if (enable == 1 && !atomic_read(&ack_m->enable)){ atomic_set(&ack_m->enable, 1); wl_err("%s, %d, enable DROPTCPAK\n", __func__, __LINE__); } } void adjust_tcp_ack_delay(char *buf, unsigned char offset) { #define MAX_LEN 2 unsigned int cnt = 0; unsigned int i = 0; struct sprdwl_tcp_ack_manage *ack_m = NULL; if (!g_sprdwl_priv) return ; for (i = 0; i < MAX_LEN; (cnt *= 10), i++) { if ((buf[offset + i] >= '0') && (buf[offset + i] <= '9')) { cnt += (buf[offset + i] - '0'); } else { cnt /= 10; break; } } ack_m = &g_sprdwl_priv->ack_m; wl_err("cnt: %d\n", cnt); if (cnt >= 100) cnt = SPRDWL_TCP_ACK_DROP_CNT; atomic_set(&ack_m->max_drop_cnt, cnt); wl_err("drop time: %d, atomic drop time: %d\n", cnt, atomic_read(&ack_m->max_drop_cnt)); #undef MAX_LEN } void adjust_tcp_ack_delay_win(char *buf, unsigned char offset) { unsigned int value = 0; unsigned int i = 0; unsigned int len = strlen(buf) - strlen("tcpack_delay_win="); struct sprdwl_tcp_ack_manage *ack_m = NULL; if (!g_sprdwl_priv) return; for (i = 0; i < len; (value *= 10), i++) { if ((buf[offset + i] >= '0') && (buf[offset + i] <= '9')) { value += (buf[offset + i] - '0'); } else { value /= 10; break; } } ack_m = &g_sprdwl_priv->ack_m; ack_m->ack_winsize = value; wl_err("%s, change tcpack_delay_win to %dKB\n", __func__, value); }