/* * Copyright (C) 2016 Spreadtrum Communications Inc. * * Authors : * star.liu * yifei.li * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "rx_msg.h" #include "wl_core.h" #include "msg.h" #include "sprdwl.h" #include "txrx.h" #include "intf.h" #include "wl_intf.h" #include "tx_msg.h" #include #include "debug.h" #include "tcp_ack.h" #include #if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 6, 0)) #include #endif #ifdef RX_HW_CSUM bool mh_ipv6_ext_hdr(unsigned char nexthdr) { return (nexthdr == NEXTHDR_HOP) || (nexthdr == NEXTHDR_ROUTING) || (nexthdr == NEXTHDR_DEST); } int ipv6_csum(void *data, __wsum csum) { int ret = 0; struct rx_msdu_desc *msdu_desc = (struct rx_msdu_desc *)data; struct ethhdr *eth = (struct ethhdr *)(data + msdu_desc->msdu_offset); struct ipv6hdr *ip6h = NULL; struct ipv6_opt_hdr *hp = NULL; unsigned short dataoff = ETH_HLEN; unsigned short nexthdr = 0; wl_debug("%s: eth_type: 0x%x\n", __func__, eth->h_proto); if (eth->h_proto == cpu_to_be16(ETH_P_IPV6)) { data += msdu_desc->msdu_offset; ip6h = data + dataoff; nexthdr = ip6h->nexthdr; dataoff += sizeof(*ip6h); while (mh_ipv6_ext_hdr(nexthdr)) { wl_debug("%s: nexthdr: %d\n", __func__, nexthdr); hp = (struct ipv6_opt_hdr *)(data + dataoff); dataoff += ipv6_optlen(hp); nexthdr = hp->nexthdr; } wl_debug("%s: nexthdr: %d, dataoff: %d, len: %d\n", __func__, nexthdr, dataoff, (msdu_desc->msdu_len - dataoff)); if (!csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, (msdu_desc->msdu_len - dataoff), nexthdr, csum)) { ret = 1; } else { ret = -1; } wl_debug("%s: ret: %d\n", __func__, ret); } return ret; } unsigned short get_sdio_data_csum(void *entry, void *data) { unsigned short csum = 0; struct sdiohal_puh *puh = (struct sdiohal_puh *)data; struct rx_msdu_desc *msdu_desc = (struct rx_msdu_desc *)(data + sizeof(*puh)); unsigned int csum_offset = msdu_total_len(msdu_desc) + sizeof(*puh); struct sprdwl_intf *intf = (struct sprdwl_intf *)entry; wl_debug("%s: check_sum: %d\n", __func__, puh->check_sum); if ((intf->priv->hw_type == SPRDWL_HW_SDIO) && puh->check_sum) { memcpy(&csum, (void *)(data + csum_offset), sizeof(csum)); wl_debug("%s: csum: 0x%x\n", __func__, csum); } return csum; } unsigned short get_pcie_data_csum(void *entry, void *data) { unsigned short csum = 0; struct rx_mh_desc *mh_desc = (struct rx_mh_desc *)data; struct sprdwl_intf *intf = (struct sprdwl_intf *)entry; if (intf->priv->hw_type == SPRDWL_HW_PCIE) { if (mh_desc->tcp_checksum_en) csum = mh_desc->tcp_hw_checksum; } return csum; } inline int fill_skb_csum(struct sk_buff *skb, unsigned short csum) { int ret = 0; if (csum) { ret = ipv6_csum(skb->data, (__force __wsum)csum); if (!ret) { skb->ip_summed = CHECKSUM_COMPLETE; skb->csum = (__force __wsum)csum; } else if (ret > 0) { skb->ip_summed = CHECKSUM_UNNECESSARY; } } else { skb->ip_summed = CHECKSUM_NONE; } return ret; } #endif void sprdwl_rx_send_cmd(struct sprdwl_intf *intf, void *data, int len, unsigned char id, unsigned char ctx_id) { struct sprdwl_priv *priv = intf->priv; sprdwl_rx_send_cmd_process(priv, data, len, id, ctx_id); } #ifdef SPRD_RX_THREAD /* seam for rx_thread */ void rx_down(struct sprdwl_rx_if *rx_if) { wait_for_completion_interruptible(&rx_if->rx_completed); } void rx_up(struct sprdwl_rx_if *rx_if) { complete(&rx_if->rx_completed); } #endif void sprdwl_rx_process(struct sprdwl_rx_if *rx_if, struct sk_buff *pskb) { struct sk_buff *reorder_skb = NULL, *skb = NULL; /* TODO: Add rx mh data process */ reorder_data_process(&rx_if->ba_entry, pskb); reorder_skb = reorder_get_skb_list(&rx_if->ba_entry); while (reorder_skb) { SPRDWL_GET_FIRST_SKB(skb, reorder_skb); skb = defrag_data_process(&rx_if->defrag_entry, skb); if (skb) skb_queue_tail(&rx_if->net_rx_list, skb); } if (!skb_queue_empty(&rx_if->net_rx_list)) { #if defined RX_NAPI if (rx_if->napi_rx_enable) napi_schedule(&rx_if->napi_rx); #elif defined SPLIT_STACK if (!work_pending(&rx_if->rx_net_work)) queue_work(rx_if->rx_net_workq, &rx_if->rx_net_work); #else/*SPLIT_STACK*/ while (!skb_queue_empty(&rx_if->net_rx_list)) { skb = skb_dequeue(&rx_if->net_rx_list); if (skb) sprdwl_rx_skb_process(rx_if->intf->priv, skb); } #endif/*SPLIT_STACK*/ } } static inline void sprdwl_rx_mh_data_process(struct sprdwl_rx_if *rx_if, void *data, int len, int buffer_type) { mm_mh_data_process(&rx_if->mm_entry, data, len, buffer_type); } static void sprdwl_rx_mh_addr_process(struct sprdwl_rx_if *rx_if, void *data, int len, int buffer_type) { struct sprdwl_intf *intf = rx_if->intf; struct sprdwl_common_hdr *hdr = (struct sprdwl_common_hdr *)(data + intf->hif_offset); if (hdr->reserv) { mm_mh_data_event_process(&rx_if->mm_entry, data, len, buffer_type); } else { /* TODO: Add TX complete code here */ sprdwl_tx_free_pcie_data(intf, (unsigned char *)data, len); wl_err("%s: Add TX complete code here\n", __func__); } } #ifdef SPLIT_STACK void sprdwl_rx_net_work_queue(struct work_struct *work) { struct sprdwl_rx_if *rx_if; struct sprdwl_priv *priv; struct sk_buff *skb = NULL; rx_if = container_of(work, struct sprdwl_rx_if, rx_net_work); priv = rx_if->intf->priv; while (!skb_queue_empty(&rx_if->net_rx_list)) { skb = skb_dequeue(&rx_if->net_rx_list); if (skb) sprdwl_rx_skb_process(priv, skb); } } #endif #ifdef SPRD_RX_THREAD static int sprdwl_rx_work_queue(void *arg) #else static void sprdwl_rx_work_queue(struct work_struct *work) #endif { struct sprdwl_msg_buf *msg; struct sprdwl_priv *priv; struct sprdwl_rx_if *rx_if; struct sprdwl_intf *intf; void *pos = NULL, *data = NULL, *tran_data = NULL; int len = 0, num = 0; /*struct sprdwl_vif *vif; struct sprdwl_cmd_hdr *hdr;*/ #ifdef SPRD_RX_THREAD struct sched_param param; rx_if = (struct sprdwl_rx_if *)arg; #else rx_if = container_of(work, struct sprdwl_rx_if, rx_work); #endif intf = rx_if->intf; priv = intf->priv; #ifdef SPRD_RX_THREAD param.sched_priority = 1; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)) sched_setscheduler_nocheck(current, SCHED_FIFO, ¶m); #else sched_setscheduler(current, SCHED_FIFO, ¶m); #endif while(1) { rx_down(rx_if); if(intf->exit) break; #endif if (!intf->exit && !sprdwl_peek_msg_buf(&rx_if->rx_list)) sprdwl_rx_process(rx_if, NULL); while ((msg = sprdwl_peek_msg_buf(&rx_if->rx_list))) { if (intf->exit) goto next; pos = msg->tran_data; for (num = msg->len; num > 0; num--) { pos = sprdwl_get_rx_data(intf, pos, &data, &tran_data, &len, intf->hif_offset); wl_debug("%s: rx type:%d, num = %d\n", __func__, SPRDWL_HEAD_GET_TYPE(data), num); /* len in mbuf_t just means buffer len in ADMA, * so need to get data len in sdiohal_puh */ if (sprdwl_debug_level >= L_DBG) { int print_len = ((struct sdiohal_puh *)tran_data)->len; if (print_len > 100) print_len = 100; sprdwl_hex_dump("rx data", (unsigned char *)data, print_len); } #if 0 /* to check is the rsp_cnt from CP2 * eqaul to rsp_cnt count on driver side. * if not equal, must be lost on SDIOHAL/PCIE. * assert to warn CP2 */ hdr = (struct sprdwl_cmd_hdr *)data; vif = ctx_id_to_vif(priv, hdr->common.ctx_id); if ((SPRDWL_HEAD_GET_TYPE(data) == SPRDWL_TYPE_CMD || SPRDWL_HEAD_GET_TYPE(data) == SPRDWL_TYPE_EVENT)) { if (rx_if->rsp_event_cnt != hdr->rsp_cnt) { wl_info("%s, %d, rsp_event_cnt=%d, hdr->cnt=%d\n", __func__, __LINE__, rx_if->rsp_event_cnt, hdr->rsp_cnt); if (hdr->rsp_cnt == 0) { rx_if->rsp_event_cnt = 0; wl_info("%s reset rsp_event_cnt", __func__); } /* hdr->rsp_cnt=0 means it's a old version CP2, * so do not assert. * vif=NULL means driver not init ok, * send cmd may cause crash */ if (vif != NULL && hdr->rsp_cnt != 0) { wl_err("cmd_id:%d resp count = %d error\n", hdr->cmd_id, hdr->rsp_cnt); sprdwl_send_assert_cmd(vif, hdr->cmd_id, RSP_CNT_ERROR); } } rx_if->rsp_event_cnt++; } sprdwl_put_vif(vif); #endif if (unlikely(priv->wakeup_tracer.resume_flag)) trace_rx_wakeup(&priv->wakeup_tracer, data, tran_data + intf->hif_offset); switch (SPRDWL_HEAD_GET_TYPE(data)) { case SPRDWL_TYPE_DATA: #if defined FPGA_LOOPBACK_TEST if (intf->loopback_n < 500) { unsigned char *r_buf; r_buf = (unsigned char *)data; sprdwl_intf_tx_data_fpga_test(intf, r_buf, len); } #else if (msg->len > SPRDWL_MAX_DATA_RXLEN) wl_err("err rx data too long:%d > %d\n", len, SPRDWL_MAX_DATA_RXLEN); sprdwl_rx_data_process(priv, data); #endif /* FPGA_LOOPBACK_TEST */ break; case SPRDWL_TYPE_CMD: if (msg->len > SPRDWL_MAX_CMD_RXLEN) wl_err("err rx cmd too long:%d > %d\n", len, SPRDWL_MAX_CMD_RXLEN); sprdwl_rx_rsp_process(priv, data); break; case SPRDWL_TYPE_PKT_LOG: if (sprdwl_pkt_log_save(intf, data) == 1) wl_err("%s: pkt log file open or create failed!\n", __func__); break; case SPRDWL_TYPE_EVENT: if (msg->len > SPRDWL_MAX_CMD_RXLEN) wl_err("err rx event too long:%d > %d\n", len, SPRDWL_MAX_CMD_RXLEN); sprdwl_rx_event_process(priv, data); break; case SPRDWL_TYPE_DATA_SPECIAL: debug_ts_leave(RX_SDIO_PORT); debug_ts_enter(RX_SDIO_PORT); if (msg->len > SPRDWL_MAX_DATA_RXLEN) wl_err("err data trans too long:%d > %d\n", len, SPRDWL_MAX_CMD_RXLEN); sprdwl_rx_mh_data_process(rx_if, tran_data, len, msg->buffer_type); tran_data = NULL; data = NULL; break; case SPRDWL_TYPE_DATA_PCIE_ADDR: if (intf->priv->hw_type != SPRDWL_HW_PCIE) { wl_err("error get pcie addr data!\n"); break; } if (msg->len > SPRDWL_MAX_CMD_RXLEN) wl_err("err rx mh data too long:%d > %d\n", len, SPRDWL_MAX_DATA_RXLEN); sprdwl_rx_mh_addr_process(rx_if, tran_data, len, msg->buffer_type); tran_data = NULL; data = NULL; break; default: wl_err("rx unknown type:%d\n", SPRDWL_HEAD_GET_TYPE(data)); break; } /* Marlin3 should release buffer by ourself */ if (tran_data) sprdwl_free_data(tran_data, msg->buffer_type); if (!pos) { wl_debug("%s no mbuf\n", __func__); break; } } next: /* TODO: Should we free mbuf one by one? */ sprdwl_free_rx_data(intf, msg->fifo_id, msg->tran_data, msg->data, msg->len); sprdwl_dequeue_msg_buf(msg, &rx_if->rx_list); } #ifdef SPRD_RX_THREAD } for (;;) { set_current_state(TASK_INTERRUPTIBLE); if (kthread_should_stop()) break; schedule(); } __set_current_state(TASK_RUNNING); return 0; #endif } /* * data_len:length of data recv by driver once time * pkt_len:length of one type of packet in data * pkt_line_num:line num in pkt log file */ int sprdwl_pkt_log_save(struct sprdwl_intf *intf, void *data) { int i, j, temp, data_len, pkt_line_num, temp_pkt_line_num, pkt_len, m = 0; mm_segment_t fs; /*for pkt log space key and enter key*/ char temp_space, temp_enter; /*for pkt log txt line number and write pkt log into file*/ char temphdr[6], tempdata[3]; intf->pfile = filp_open( "storage/sdcard0/Download/sprdwl_pkt_log.txt", O_CREAT | O_RDWR, 0); if (IS_ERR(intf->pfile)) { wl_err("file create/open fail %s, %d\n", __func__, __LINE__); return 1; } fs = get_fs(); set_fs(KERNEL_DS); pkt_len = ((struct sprdwl_pktlog_hdr *)(data))->plen; data += sizeof(struct sprdwl_pktlog_hdr); while (m < pkt_len) { data_len = *((unsigned char *)(data + 2)) + 4; m += data_len; temp_space = ' '; temp_enter = '\n'; temp_pkt_line_num = 0; pkt_line_num = 0; for (j = 0; j < 6; j++) { temphdr[j] = '0'; } vfs_write(intf->pfile, temphdr, 6, &intf->lp); vfs_write(intf->pfile, &temp_space, 1, &intf->lp); memset(tempdata, 0x00, 3); for (i = 0; i < data_len; i++) { sprintf(tempdata, "%02x", *(unsigned char *)data); vfs_write(intf->pfile, tempdata, 2, &intf->lp); memset(tempdata, 0x00, 3); if ((i != 0) && ((i + 1)%16 == 0)) { if (i < (data_len - 1)) { vfs_write(intf->pfile, &temp_enter, sizeof(temp_enter), &intf->lp); pkt_line_num += 16; temp_pkt_line_num = pkt_line_num; for (j = 0; j < 6; j++) { temp = (temp_pkt_line_num >> (j*4)) & 0xf; temphdr[5 - j] = (temp < 10) ? (temp + '0') : (temp - 10 + 'a'); } vfs_write(intf->pfile, temphdr, 6, &intf->lp); vfs_write(intf->pfile, &temp_space, 1, &intf->lp); } } else { vfs_write(intf->pfile, &temp_space, sizeof(temp_space), &intf->lp); } data++; } vfs_write(intf->pfile, &temp_enter, sizeof(temp_enter), &intf->lp); memset(temphdr, 0x00, 6); } filp_close(intf->pfile, NULL); set_fs(fs); return 0; } #ifdef RX_NAPI static int sprdwl_netdev_poll_rx(struct napi_struct *napi, int budget) { struct sprdwl_rx_if *rx_if; struct sprdwl_priv *priv; struct sk_buff *skb = NULL; int work_done = 0; rx_if = container_of(napi, struct sprdwl_rx_if, napi_rx); priv = rx_if->intf->priv; while ((work_done < budget) && (!skb_queue_empty(&rx_if->net_rx_list))) { skb = skb_dequeue(&rx_if->net_rx_list); if (skb) sprdwl_rx_skb_process(priv, skb); work_done++; } if (work_done < budget) { napi_complete(napi); if ((rx_if->napi_rx_enable) && (!skb_queue_empty(&rx_if->net_rx_list))) napi_schedule(napi); } return work_done; } void sprdwl_rx_napi_init(struct net_device *ndev, struct sprdwl_intf *intf) { struct sprdwl_rx_if *rx_if = (struct sprdwl_rx_if *)intf->sprdwl_rx; netif_napi_add(ndev, &rx_if->napi_rx, sprdwl_netdev_poll_rx, 128); napi_enable(&rx_if->napi_rx); rx_if->napi_rx_enable = true; } void sprdwl_rx_napi_deinit(struct sprdwl_intf *intf) { struct sprdwl_rx_if *rx_if = (struct sprdwl_rx_if *)intf->sprdwl_rx; rx_if->napi_rx_enable = false; napi_disable(&rx_if->napi_rx); netif_napi_del(&rx_if->napi_rx); } #endif/*RX_NAPI*/ #define RX_THREAD_NAME "SPRD_RX_THREAD" int sprdwl_rx_init(struct sprdwl_intf *intf) { int ret = 0; struct sprdwl_rx_if *rx_if = NULL; rx_if = kzalloc(sizeof(*rx_if), GFP_KERNEL); if (!rx_if) { ret = -ENOMEM; goto err_rx_if; } /* init rx_list */ ret = sprdwl_msg_init(SPRDWL_RX_MSG_NUM, &rx_if->rx_list); if (ret) { wl_err("%s tx_buf create failed: %d\n", __func__, ret); goto err_rx_list; } #ifdef SPRD_RX_THREAD /* init rx_work thread */ rx_if->rx_thread = kthread_create(sprdwl_rx_work_queue, (void*)rx_if, RX_THREAD_NAME); if (IS_ERR_OR_NULL(rx_if->rx_thread)) { wl_err("%s SPRDWL_RX_THREAD create failed\n", __func__); ret = -ENOMEM; goto err_rx_work; } #else /* init rx_work */ rx_if->rx_queue = alloc_ordered_workqueue("SPRDWL_RX_QUEUE", WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_CPU_INTENSIVE); if (!rx_if->rx_queue) { wl_err("%s SPRDWL_RX_QUEUE create failed\n", __func__); ret = -ENOMEM; goto err_rx_work; } /*init rx_queue*/ INIT_WORK(&rx_if->rx_work, sprdwl_rx_work_queue); #endif #ifdef SPLIT_STACK rx_if->rx_net_workq = alloc_ordered_workqueue("SPRDWL_RX_NET_QUEUE", WQ_HIGHPRI | WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM); if (!rx_if->rx_net_workq) { wl_err("%s SPRDWL_RX_NET_QUEUE create failed\n", __func__); ret = -ENOMEM; goto err_rx_net_work; } /*init rx_queue*/ INIT_WORK(&rx_if->rx_net_work, sprdwl_rx_net_work_queue); #endif ret = sprdwl_defrag_init(&rx_if->defrag_entry); if (ret) { wl_err("%s init defrag fail: %d\n", __func__, ret); goto err_rx_defrag; } ret = sprdwl_mm_init(&rx_if->mm_entry, (void *)intf); if (ret) { wl_err("%s init mm fail: %d\n", __func__, ret); goto err_rx_mm; } sprdwl_reorder_init(&rx_if->ba_entry); skb_queue_head_init(&rx_if->net_rx_list); intf->lp = 0; intf->sprdwl_rx = (void *)rx_if; rx_if->intf = intf; #ifdef SPRD_RX_THREAD init_completion(&rx_if->rx_completed); wake_up_process(rx_if->rx_thread); #endif return ret; err_rx_mm: sprdwl_mm_deinit(&rx_if->mm_entry, intf); err_rx_defrag: #ifdef SPLIT_STACK destroy_workqueue(rx_if->rx_net_workq); err_rx_net_work: #endif #ifndef SPRD_RX_THREAD destroy_workqueue(rx_if->rx_queue); #else kthread_stop(rx_if->rx_thread); rx_if->rx_thread = NULL; #endif err_rx_work: sprdwl_msg_deinit(&rx_if->rx_list); err_rx_list: kfree(rx_if); err_rx_if: return ret; } int sprdwl_rx_deinit(struct sprdwl_intf *intf) { struct sprdwl_rx_if *rx_if = (struct sprdwl_rx_if *)intf->sprdwl_rx; struct sk_buff *skb; #ifdef SPRD_RX_THREAD if (rx_if->rx_thread) { rx_up(rx_if); if (!strncmp(rx_if->rx_thread->comm, RX_THREAD_NAME, strlen(RX_THREAD_NAME))) { kthread_stop(rx_if->rx_thread); rx_if->rx_thread = NULL; } else { wl_err("rx thread name is : %s\n", rx_if->rx_thread->comm); } } #else flush_workqueue(rx_if->rx_queue); destroy_workqueue(rx_if->rx_queue); #endif #ifdef SPLIT_STACK flush_workqueue(rx_if->rx_net_workq); destroy_workqueue(rx_if->rx_net_workq); #endif sprdwl_msg_deinit(&rx_if->rx_list); while ((skb = skb_dequeue(&rx_if->net_rx_list)) != NULL) kfree_skb(skb); sprdwl_defrag_deinit(&rx_if->defrag_entry); sprdwl_mm_deinit(&rx_if->mm_entry, intf); sprdwl_reorder_deinit(&rx_if->ba_entry); kfree(rx_if); intf->sprdwl_rx = NULL; return 0; }