/* * 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" #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); } void sprdwl_rx_process(struct sprdwl_rx_if *rx_if, struct sk_buff *pskb) { #ifndef SPLIT_STACK struct sprdwl_priv *priv = rx_if->intf->priv; struct sk_buff *reorder_skb = NULL, *skb = NULL; #endif /* TODO: Add rx mh data process */ #ifdef SPLIT_STACK reorder_data_process(&rx_if->ba_entry, pskb); if (!work_pending(&rx_if->rx_net_work)) queue_work(rx_if->rx_net_workq, &rx_if->rx_net_work); #else reorder_skb = reorder_data_process(&rx_if->ba_entry, pskb); while (reorder_skb) { SPRDWL_GET_FIRST_SKB(skb, reorder_skb); skb = defrag_data_process(&rx_if->defrag_entry, skb); if (skb) sprdwl_rx_skb_process(priv, skb); } #endif } 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 *reorder_skb = NULL, *skb = NULL; rx_if = container_of(work, struct sprdwl_rx_if, rx_net_work); priv = rx_if->intf->priv; 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) sprdwl_rx_skb_process(priv, skb); } } #endif static void sprdwl_rx_work_queue(struct work_struct *work) { 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;*/ rx_if = container_of(work, struct sprdwl_rx_if, rx_work); intf = rx_if->intf; priv = intf->priv; #ifndef RX_NAPI if (!intf->exit && !sprdwl_peek_msg_buf(&rx_if->rx_list)) sprdwl_rx_process(rx_if, NULL); #endif 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); } } /* * 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; #ifdef setfs mm_segment_t fs; #endif /*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; } #ifdef setfs fs = get_fs(); set_fs(KERNEL_DS); #endif 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'; } kernel_write(intf->pfile, temphdr, 6, &intf->lp); kernel_write(intf->pfile, &temp_space, 1, &intf->lp); memset(tempdata, 0x00, 2); for (i = 0; i < data_len; i++) { sprintf(tempdata, "%02x", *(unsigned char *)data); kernel_write(intf->pfile, tempdata, 2, &intf->lp); memset(tempdata, 0x00, 2); if ((i != 0) && ((i + 1)%16 == 0)) { if (i < (data_len - 1)) { kernel_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'); } kernel_write(intf->pfile, temphdr, 6, &intf->lp); kernel_write(intf->pfile, &temp_space, 1, &intf->lp); } } else { kernel_write(intf->pfile, &temp_space, sizeof(temp_space), &intf->lp); } data++; } kernel_write(intf->pfile, &temp_enter, sizeof(temp_enter), &intf->lp); memset(temphdr, 0x00, 6); } filp_close(intf->pfile, NULL); #ifdef setfs set_fs(fs); #endif return 0; } #ifdef RX_NAPI static int sprdwl_netdev_poll_rx(struct napi_struct *napi, int budget) { 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; int print_len; int quota = budget; int done; rx_if = container_of(napi, struct sprdwl_rx_if, napi_rx); intf = rx_if->intf; priv = intf->priv; if (!intf->exit && !sprdwl_peek_msg_buf(&rx_if->rx_data_list)) sprdwl_rx_process(rx_if, NULL); while (quota && (msg = sprdwl_peek_msg_buf(&rx_if->rx_data_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_info("%s: rx type:%d\n", __func__, SPRDWL_HEAD_GET_TYPE(data)); /* len in mbuf_t just means buffer len in ADMA, * so need to get data len in sdiohal_puh */ if (((struct sdiohal_puh *)tran_data)->len > 100) print_len = 100; else print_len = ((struct sdiohal_puh *) tran_data)->len; sprdwl_hex_dump("rx data", (unsigned char *)data, print_len); if (sprdwl_sdio_process_credit(intf, data)) goto free; switch (SPRDWL_HEAD_GET_TYPE(data)) { case SPRDWL_TYPE_DATA_SPECIAL: 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 (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; } free: /* 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_data_list); quota--; } done = budget - quota; if (done <= 1) napi_complete(napi); return 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, 16); napi_enable(&rx_if->napi_rx); } #endif 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 RX_NAPI ret = sprdwl_msg_init(SPRDWL_RX_MSG_NUM, &rx_if->rx_data_list); if (ret) { wl_err("%s tx_buf create failed: %d\n", __func__, ret); goto err_rx_data_list; } #endif /* 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); #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); intf->lp = 0; intf->sprdwl_rx = (void *)rx_if; rx_if->intf = intf; 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 destroy_workqueue(rx_if->rx_queue); err_rx_work: #ifdef RX_NAPI sprdwl_msg_deinit(&rx_if->rx_data_list); err_rx_data_list: #endif 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; flush_workqueue(rx_if->rx_queue); destroy_workqueue(rx_if->rx_queue); #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); #ifdef RX_NAPI sprdwl_msg_deinit(&rx_if->rx_data_list); napi_disable(&rx_if->napi_rx); #endif 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; }