From: Zhirun Liu Add the cfg80211 FullMAC core, regulatory and configuration paths, TX/RX data path, SDIO and chipset integration, advanced wireless features, diagnostics and retained downstream compatibility interfaces. This is the complete untrimmed RFC implementation. Private interfaces, the copied regulatory database and INI configuration are included for design review and are not claimed to be merge-ready. Signed-off-by: Zhirun Liu Signed-off-by: Yanli Yang --- .../wireless/aic/aic8800_fdrv/aic_br_ext.c | 1598 +++++ .../wireless/aic/aic8800_fdrv/aic_br_ext.h | 72 + .../wireless/aic/aic8800_fdrv/aic_btsdio.c | 1164 ++++ .../wireless/aic/aic8800_fdrv/aic_btsdio.h | 472 ++ .../wireless/aic/aic8800_fdrv/aic_priv_cmd.c | 1967 ++++++ .../wireless/aic/aic8800_fdrv/aic_priv_cmd.h | 127 + .../wireless/aic/aic8800_fdrv/aic_vendor.c | 950 +++ .../wireless/aic/aic8800_fdrv/aic_vendor.h | 370 ++ .../aic/aic8800_fdrv/aicwf_chip_8800d80.c | 291 + .../aic/aic8800_fdrv/aicwf_chip_8800dc.c | 300 + .../aic/aic8800_fdrv/aicwf_chip_8801.c | 279 + .../aic/aic8800_fdrv/aicwf_chip_ops.c | 267 + .../aic/aic8800_fdrv/aicwf_chip_ops.h | 186 + .../aic/aic8800_fdrv/aicwf_compat_8800d80.c | 96 + .../aic/aic8800_fdrv/aicwf_compat_8800d80.h | 16 + .../aic/aic8800_fdrv/aicwf_compat_8800dc.c | 505 ++ .../aic/aic8800_fdrv/aicwf_compat_8800dc.h | 22 + .../wireless/aic/aic8800_fdrv/aicwf_genl.c | 932 +++ .../wireless/aic/aic8800_fdrv/aicwf_genl.h | 112 + .../aic/aic8800_fdrv/aicwf_rx_prealloc.c | 102 + .../aic/aic8800_fdrv/aicwf_rx_prealloc.h | 32 + .../wireless/aic/aic8800_fdrv/aicwf_sdio.c | 2813 +++++++++ .../wireless/aic/aic8800_fdrv/aicwf_sdio.h | 264 + .../wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c | 572 ++ .../wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h | 104 + .../wireless/aic/aic8800_fdrv/aicwf_txrxif.c | 753 +++ .../wireless/aic/aic8800_fdrv/aicwf_txrxif.h | 217 + .../aic/aic8800_fdrv/aicwf_wext_linux.c | 1137 ++++ .../aic/aic8800_fdrv/aicwf_wext_linux.h | 15 + drivers/net/wireless/aic/aic8800_fdrv/regdb.c | 2770 +++++++++ .../wireless/aic/aic8800_fdrv/rwnx_bfmer.c | 93 + .../wireless/aic/aic8800_fdrv/rwnx_bfmer.h | 100 + .../wireless/aic/aic8800_fdrv/rwnx_cfgfile.c | 265 + .../wireless/aic/aic8800_fdrv/rwnx_cfgfile.h | 33 + .../wireless/aic/aic8800_fdrv/rwnx_debugfs.c | 2585 ++++++++ .../wireless/aic/aic8800_fdrv/rwnx_debugfs.h | 134 + .../wireless/aic/aic8800_fdrv/rwnx_fw_trace.c | 50 + .../wireless/aic/aic8800_fdrv/rwnx_fw_trace.h | 28 + .../net/wireless/aic/aic8800_fdrv/rwnx_gki.c | 406 ++ .../net/wireless/aic/aic8800_fdrv/rwnx_gki.h | 46 + .../net/wireless/aic/aic8800_fdrv/rwnx_main.c | 5406 +++++++++++++++++ .../net/wireless/aic/aic8800_fdrv/rwnx_main.h | 34 + .../net/wireless/aic/aic8800_fdrv/rwnx_mesh.c | 33 + .../net/wireless/aic/aic8800_fdrv/rwnx_mesh.h | 35 + .../aic/aic8800_fdrv/rwnx_mod_params.c | 1295 ++++ .../aic/aic8800_fdrv/rwnx_mod_params.h | 70 + .../wireless/aic/aic8800_fdrv/rwnx_mu_group.c | 646 ++ .../wireless/aic/aic8800_fdrv/rwnx_mu_group.h | 159 + .../wireless/aic/aic8800_fdrv/rwnx_radar.c | 1965 ++++++ .../wireless/aic/aic8800_fdrv/rwnx_radar.h | 278 + .../net/wireless/aic/aic8800_fdrv/rwnx_rx.c | 2443 ++++++++ .../net/wireless/aic/aic8800_fdrv/rwnx_rx.h | 358 ++ .../net/wireless/aic/aic8800_fdrv/rwnx_tdls.c | 776 +++ .../net/wireless/aic/aic8800_fdrv/rwnx_tdls.h | 57 + .../wireless/aic/aic8800_fdrv/rwnx_testmode.c | 217 + .../wireless/aic/aic8800_fdrv/rwnx_testmode.h | 68 + .../wireless/aic/aic8800_fdrv/rwnx_trace.c | 5 + .../net/wireless/aic/aic8800_fdrv/rwnx_tx.c | 1805 ++++++ .../net/wireless/aic/aic8800_fdrv/rwnx_tx.h | 193 + .../net/wireless/aic/aic8800_fdrv/rwnx_txq.c | 1379 +++++ .../net/wireless/aic/aic8800_fdrv/rwnx_txq.h | 372 ++ .../net/wireless/aic/aic8800_fdrv/sdio_host.c | 132 + .../net/wireless/aic/aic8800_fdrv/sdio_host.h | 39 + 63 files changed, 40010 insertions(+) create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800d80.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8800dc.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_8801.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800d80.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_compat_8800dc.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/regdb.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_bfmer.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_cfgfile.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mesh.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_testmode.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_trace.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.h create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/sdio_host.c create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/sdio_host.h diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.c b/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.c new file mode 100644 index 0000000000000..4dc33b367cac3 --- /dev/null +++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.c @@ -0,0 +1,1598 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + ***************************************************************************** + * + * Copyright (C) 2020 AIC semiconductor. + * + * @brief bridge function definitions + * + **************************************************************************** + */ + +#define _AIC_BR_EXT_C_ + +#ifdef __KERNEL__ +#include "rwnx_defs.h" +#include +#include +#include +#include +#include +#include +#endif + +#ifdef CL_IPV6_PASS +#ifdef __KERNEL__ +#include +#include +#include +#include +#endif +#endif + +#ifdef CONFIG_AIC8800_BR_SUPPORT + +/* #define BR_SUPPORT_DEBUG */ + +#define NAT25_IPV4 01 +#define NAT25_IPV6 02 +#define NAT25_IPX 03 +#define NAT25_APPLE 04 +#define NAT25_PPPOE 05 + +#define RTL_RELAY_TAG_LEN (ETH_ALEN) +#define TAG_HDR_LEN 4 + +#define MAGIC_CODE 0x8186 +#define MAGIC_CODE_LEN 2 +#define WAIT_TIME_PPPOE 5 /* waiting time for pppoe server in sec */ + +/*----------------------------------------------------------------- + * How database records network address: + * 0 1 2 3 4 5 6 7 8 9 10 + * |----|----|----|----|----|----|----|----|----|----|----| + * IPv4 |type| | IP addr | + * IPX |type| Net addr | Node addr | + * IPX |type| Net addr |Sckt addr| + * Apple |type| Network |node| + * PPPoE |type| SID | AC MAC | + *----------------------------------------------------------------- + */ + +/* Find a tag in pppoe frame and return the pointer */ +static inline unsigned char *__nat25_find_pppoe_tag(struct pppoe_hdr *ph, + unsigned short type) +{ + unsigned char *cur_ptr, *start_ptr; + unsigned short tag_len, tag_type; + + cur_ptr = (unsigned char *)ph->tag; + start_ptr = cur_ptr; + while ((cur_ptr - start_ptr) < ntohs(ph->length)) { + /* prevent un-alignment access */ + tag_type = (unsigned short)((cur_ptr[0] << 8) + cur_ptr[1]); + tag_len = (unsigned short)((cur_ptr[2] << 8) + cur_ptr[3]); + if (tag_type == type) + return cur_ptr; + cur_ptr = cur_ptr + TAG_HDR_LEN + tag_len; + } + return 0; +} + +static inline int __nat25_add_pppoe_tag(struct sk_buff *skb, + struct pppoe_tag *tag) +{ + struct pppoe_hdr *ph = (struct pppoe_hdr *)(skb->data + ETH_HLEN); + int data_len; + + data_len = tag->tag_len + TAG_HDR_LEN; + if (skb_tailroom(skb) < data_len) { + pr_err("skb_tailroom() failed in add SID tag!\n"); + return -1; + } + + skb_put(skb, data_len); + /* have a room for new tag */ + memmove(((unsigned char *)ph->tag + data_len), (unsigned char *)ph->tag, + ntohs(ph->length)); + ph->length = htons(ntohs(ph->length) + data_len); + memcpy((unsigned char *)ph->tag, tag, data_len); + return data_len; +} + +static int skb_pull_and_merge(struct sk_buff *skb, unsigned char *src, int len) +{ + int tail_len; + unsigned long end, tail; + + if ((src + len) > skb_tail_pointer(skb) || skb->len < len) + return -1; + + tail = (unsigned long)skb_tail_pointer(skb); + end = (unsigned long)src + len; + if (tail < end) + return -1; + + tail_len = (int)(tail - end); + if (tail_len > 0) + memmove(src, src + len, tail_len); + + skb_trim(skb, skb->len - len); + return 0; +} + +static inline unsigned long __nat25_timeout(struct rwnx_vif *vif) +{ + unsigned long timeout; + + timeout = jiffies - NAT25_AGEING_TIME * HZ; + + return timeout; +} + +static inline int __nat25_has_expired(struct rwnx_vif *vif, + struct nat25_network_db_entry *fdb) +{ + if (time_before_eq(fdb->ageing_timer, __nat25_timeout(vif))) + return 1; + + return 0; +} + +static inline void +__nat25_generate_ipv4_network_addr(unsigned char *network_addr, + unsigned int *ip_addr) +{ + memset(network_addr, 0, MAX_NETWORK_ADDR_LEN); + + network_addr[0] = NAT25_IPV4; + memcpy(network_addr + 7, (unsigned char *)ip_addr, 4); +} + +static inline void +__nat25_generate_ipx_network_addr_with_node(unsigned char *network_addr, + unsigned int *ipx_net_addr, + unsigned char *ipx_node_addr) +{ + memset(network_addr, 0, MAX_NETWORK_ADDR_LEN); + + network_addr[0] = NAT25_IPX; + memcpy(network_addr + 1, (unsigned char *)ipx_net_addr, 4); + memcpy(network_addr + 5, ipx_node_addr, 6); +} + +static inline void +__nat25_generate_ipx_network_addr_with_socket(unsigned char *network_addr, + unsigned int *ipx_net_addr, + unsigned short *ipx_socket_addr) +{ + memset(network_addr, 0, MAX_NETWORK_ADDR_LEN); + + network_addr[0] = NAT25_IPX; + memcpy(network_addr + 1, (unsigned char *)ipx_net_addr, 4); + memcpy(network_addr + 5, (unsigned char *)ipx_socket_addr, 2); +} + +static inline void __nat25_generate_apple_network_addr(unsigned char *network_addr, + unsigned short *network, + unsigned char *node) +{ + memset(network_addr, 0, MAX_NETWORK_ADDR_LEN); + + network_addr[0] = NAT25_APPLE; + memcpy(network_addr + 1, (unsigned char *)network, 2); + network_addr[3] = *node; +} + +static inline void +__nat25_generate_pppoe_network_addr(unsigned char *network_addr, + unsigned char *ac_mac, unsigned short *sid) +{ + memset(network_addr, 0, MAX_NETWORK_ADDR_LEN); + + network_addr[0] = NAT25_PPPOE; + memcpy(network_addr + 1, (unsigned char *)sid, 2); + memcpy(network_addr + 3, (unsigned char *)ac_mac, 6); +} + +#ifdef CL_IPV6_PASS +static void __nat25_generate_ipv6_network_addr(unsigned char *network_addr, + unsigned int *ip_addr) +{ + memset(network_addr, 0, MAX_NETWORK_ADDR_LEN); + + network_addr[0] = NAT25_IPV6; + memcpy(network_addr + 1, (unsigned char *)ip_addr, 16); +} + +static unsigned char *scan_tlv(unsigned char *data, int len, unsigned char tag, + unsigned char len8b) +{ + while (len > 0) { + if (*data == tag && *(data + 1) == len8b && len >= len8b * 8) + return data + 2; + + len -= (*(data + 1)) * 8; + data += (*(data + 1)) * 8; + } + return NULL; +} + +static int update_nd_link_layer_addr(unsigned char *data, int len, + unsigned char *replace_mac) +{ + struct icmp6hdr *icmphdr = (struct icmp6hdr *)data; + unsigned char *mac; + + if (icmphdr->icmp6_type == NDISC_ROUTER_SOLICITATION) { + if (len >= 8) { + mac = scan_tlv(&data[8], len - 8, 1, 1); + if (mac) { + pr_info("Router Solicitation, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + replace_mac[0], replace_mac[1], replace_mac[2], + replace_mac[3], replace_mac[4], replace_mac[5]); + memcpy(mac, replace_mac, 6); + return 1; + } + } + } else if (icmphdr->icmp6_type == NDISC_ROUTER_ADVERTISEMENT) { + if (len >= 16) { + mac = scan_tlv(&data[16], len - 16, 1, 1); + if (mac) { + pr_info("Router Advertisement, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + replace_mac[0], replace_mac[1], replace_mac[2], + replace_mac[3], replace_mac[4], replace_mac[5]); + memcpy(mac, replace_mac, 6); + return 1; + } + } + } else if (icmphdr->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) { + if (len >= 24) { + mac = scan_tlv(&data[24], len - 24, 1, 1); + if (mac) { + pr_info("Neighbor Solicitation, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + replace_mac[0], replace_mac[1], replace_mac[2], + replace_mac[3], replace_mac[4], replace_mac[5]); + memcpy(mac, replace_mac, 6); + return 1; + } + } + } else if (icmphdr->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) { + if (len >= 24) { + mac = scan_tlv(&data[24], len - 24, 2, 1); + if (mac) { + pr_info("Neighbor Advertisement, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + replace_mac[0], replace_mac[1], replace_mac[2], + replace_mac[3], replace_mac[4], replace_mac[5]); + memcpy(mac, replace_mac, 6); + return 1; + } + } + } else if (icmphdr->icmp6_type == NDISC_REDIRECT) { + if (len >= 40) { + mac = scan_tlv(&data[40], len - 40, 2, 1); + if (mac) { + pr_info("Redirect, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + replace_mac[0], replace_mac[1], replace_mac[2], + replace_mac[3], replace_mac[4], replace_mac[5]); + memcpy(mac, replace_mac, 6); + return 1; + } + } + } + return 0; +} + +#ifdef SUPPORT_RX_UNI2MCAST +static void convert_ipv6_mac_to_mc(struct sk_buff *skb) +{ + struct ipv6hdr *iph = (struct ipv6hdr *)(skb->data + ETH_HLEN); + unsigned char *dst_mac = skb->data; + + /*modified by qinjunjie,ipv6 multicast address ix 0x33-33-xx-xx-xx-xx */ + dst_mac[0] = 0x33; + dst_mac[1] = 0x33; + memcpy(&dst_mac[2], &iph->daddr.s6_addr32[3], 4); +#if defined(__LINUX_2_6__) + /*modified by qinjunjie,warning:should not remove next line */ + skb->pkt_type = PACKET_MULTICAST; +#endif +} +#endif /* CL_IPV6_PASS */ +#endif /* SUPPORT_RX_UNI2MCAST */ + +static inline int __nat25_network_hash(unsigned char *network_addr) +{ + if (network_addr[0] == NAT25_IPV4) { + unsigned long x; + + x = network_addr[7] ^ network_addr[8] ^ network_addr[9] ^ network_addr[10]; + + return x & (NAT25_HASH_SIZE - 1); + } else if (network_addr[0] == NAT25_IPX) { + unsigned long x; + + x = network_addr[1] ^ network_addr[2] ^ network_addr[3] ^ network_addr[4] ^ + network_addr[5] ^ network_addr[6] ^ network_addr[7] ^ network_addr[8] ^ + network_addr[9] ^ network_addr[10]; + + return x & (NAT25_HASH_SIZE - 1); + } else if (network_addr[0] == NAT25_APPLE) { + unsigned long x; + + x = network_addr[1] ^ network_addr[2] ^ network_addr[3]; + + return x & (NAT25_HASH_SIZE - 1); + } else if (network_addr[0] == NAT25_PPPOE) { + unsigned long x; + + x = network_addr[0] ^ network_addr[1] ^ network_addr[2] ^ network_addr[3] ^ + network_addr[4] ^ network_addr[5] ^ network_addr[6] ^ network_addr[7] ^ + network_addr[8]; + + return x & (NAT25_HASH_SIZE - 1); +#ifdef CL_IPV6_PASS + } else if (network_addr[0] == NAT25_IPV6) { + unsigned long x; + + x = network_addr[1] ^ network_addr[2] ^ network_addr[3] ^ network_addr[4] ^ + network_addr[5] ^ network_addr[6] ^ network_addr[7] ^ network_addr[8] ^ + network_addr[9] ^ network_addr[10] ^ network_addr[11] ^ + network_addr[12] ^ network_addr[13] ^ network_addr[14] ^ + network_addr[15] ^ network_addr[16]; + + return x & (NAT25_HASH_SIZE - 1); +#endif + } else { + unsigned long x = 0; + int i; + + for (i = 0; i < MAX_NETWORK_ADDR_LEN; i++) + x ^= network_addr[i]; + + return x & (NAT25_HASH_SIZE - 1); + } +} + +static inline void __network_hash_link(struct rwnx_vif *vif, + struct nat25_network_db_entry *ent, + int hash) +{ + /* Caller must _enter_critical_bh already! */ + /* _irqL irqL; */ + /* _enter_critical_bh(&priv->br_ext_lock, &irqL); */ + + ent->next_hash = vif->nethash[hash]; + if (ent->next_hash) + ent->next_hash->pprev_hash = &ent->next_hash; + vif->nethash[hash] = ent; + ent->pprev_hash = &vif->nethash[hash]; + + /* _exit_critical_bh(&priv->br_ext_lock, &irqL); */ +} + +static inline void __network_hash_unlink(struct nat25_network_db_entry *ent) +{ + /* Caller must _enter_critical_bh already! */ + /* _irqL irqL; */ + /* _enter_critical_bh(&priv->br_ext_lock, &irqL); */ + + *ent->pprev_hash = ent->next_hash; + if (ent->next_hash) + ent->next_hash->pprev_hash = ent->pprev_hash; + ent->next_hash = NULL; + ent->pprev_hash = NULL; + + /* _exit_critical_bh(&priv->br_ext_lock, &irqL); */ +} + +static int __nat25_db_network_lookup_and_replace(struct rwnx_vif *vif, + struct sk_buff *skb, + unsigned char *network_addr) +{ + struct nat25_network_db_entry *db; + + spin_lock_bh(&vif->br_ext_lock); + + db = vif->nethash[__nat25_network_hash(network_addr)]; + while (db) { + if (!memcmp(db->network_addr, network_addr, MAX_NETWORK_ADDR_LEN)) { + if (!__nat25_has_expired(vif, db)) { + /* replace the destination mac address */ + memcpy(skb->data, db->mac_addr, ETH_ALEN); + atomic_inc(&db->use_count); + +#ifdef CL_IPV6_PASS + pr_info("NAT25: Lookup M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + db->mac_addr[0], db->mac_addr[1], db->mac_addr[2], + db->mac_addr[3], db->mac_addr[4], db->mac_addr[5], + db->network_addr[0], db->network_addr[1], + db->network_addr[2], db->network_addr[3], + db->network_addr[4], db->network_addr[5], + db->network_addr[6], db->network_addr[7], + db->network_addr[8], db->network_addr[9], + db->network_addr[10], db->network_addr[11], + db->network_addr[12], db->network_addr[13], + db->network_addr[14], db->network_addr[15], + db->network_addr[16]); +#else + pr_info("NAT25: Lookup M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + db->mac_addr[0], db->mac_addr[1], db->mac_addr[2], + db->mac_addr[3], db->mac_addr[4], db->mac_addr[5], + db->network_addr[0], db->network_addr[1], + db->network_addr[2], db->network_addr[3], + db->network_addr[4], db->network_addr[5], + db->network_addr[6], db->network_addr[7], + db->network_addr[8], db->network_addr[9], + db->network_addr[10]); +#endif + } + spin_unlock_bh(&vif->br_ext_lock); + return 1; + } + + db = db->next_hash; + } + + spin_unlock_bh(&vif->br_ext_lock); + return 0; +} + +static void __nat25_db_network_insert(struct rwnx_vif *vif, + unsigned char *mac_addr, + unsigned char *network_addr) +{ + struct nat25_network_db_entry *db; + int hash; + + spin_lock_bh(&vif->br_ext_lock); + + hash = __nat25_network_hash(network_addr); + db = vif->nethash[hash]; + + while (db) { + if (!memcmp(db->network_addr, network_addr, MAX_NETWORK_ADDR_LEN)) { + memcpy(db->mac_addr, mac_addr, ETH_ALEN); + db->ageing_timer = jiffies; + spin_unlock_bh(&vif->br_ext_lock); + return; + } + + db = db->next_hash; + } + + db = kmalloc(sizeof(*db), in_interrupt() ? GFP_ATOMIC : GFP_KERNEL); + if (!db) { + spin_unlock_bh(&vif->br_ext_lock); + return; + } + + memcpy(db->network_addr, network_addr, MAX_NETWORK_ADDR_LEN); + memcpy(db->mac_addr, mac_addr, ETH_ALEN); + atomic_set(&db->use_count, 1); + db->ageing_timer = jiffies; + + __network_hash_link(vif, db, hash); + + spin_unlock_bh(&vif->br_ext_lock); +} + +static void __nat25_db_print(struct rwnx_vif *vif) +{ + spin_lock_bh(&vif->br_ext_lock); + +#ifdef BR_SUPPORT_DEBUG + static int counter; + int i, j; + struct nat25_network_db_entry *db; + + counter++; + if ((counter % 16) != 0) + return; + + for (i = 0, j = 0; i < NAT25_HASH_SIZE; i++) { + db = vif->nethash[i]; + + while (db) { +#ifdef CL_IPV6_PASS + pr_info("NAT25: DB(%d) H(%02d) C(%d) M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + j, i, atomic_read(&db->use_count), db->mac_addr[0], + db->mac_addr[1], db->mac_addr[2], db->mac_addr[3], db->mac_addr[4], + db->mac_addr[5], db->network_addr[0], db->network_addr[1], + db->network_addr[2], db->network_addr[3], db->network_addr[4], + db->network_addr[5], db->network_addr[6], db->network_addr[7], + db->network_addr[8], db->network_addr[9], db->network_addr[10], + db->network_addr[11], db->network_addr[12], db->network_addr[13], + db->network_addr[14], db->network_addr[15], db->network_addr[16]); +#else + pr_info("NAT25: DB(%d) H(%02d) C(%d) M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + j, i, atomic_read(&db->use_count), db->mac_addr[0], + db->mac_addr[1], db->mac_addr[2], db->mac_addr[3], + db->mac_addr[4], db->mac_addr[5], db->network_addr[0], + db->network_addr[1], db->network_addr[2], db->network_addr[3], + db->network_addr[4], db->network_addr[5], db->network_addr[6], + db->network_addr[7], db->network_addr[8], db->network_addr[9], + db->network_addr[10]); +#endif + j++; + + db = db->next_hash; + } + } +#endif + + spin_unlock_bh(&vif->br_ext_lock); +} + +/* + * NAT2.5 interface + */ + +void nat25_db_cleanup(struct rwnx_vif *vif) +{ + int i; + + spin_lock_bh(&vif->br_ext_lock); + + for (i = 0; i < NAT25_HASH_SIZE; i++) { + struct nat25_network_db_entry *f; + + f = vif->nethash[i]; + while (f) { + struct nat25_network_db_entry *g; + + g = f->next_hash; + if (vif->scdb_entry == f) { + memset(vif->scdb_mac, 0, ETH_ALEN); + memset(vif->scdb_ip, 0, 4); + vif->scdb_entry = NULL; + } + __network_hash_unlink(f); + kfree(f); + + f = g; + } + } + + spin_unlock_bh(&vif->br_ext_lock); +} + +static inline void clear_scdb_entry(struct rwnx_vif *vif, + struct nat25_network_db_entry *f) +{ + if (vif->scdb_entry == f) { + memset(vif->scdb_mac, 0, ETH_ALEN); + memset(vif->scdb_ip, 0, 4); + vif->scdb_entry = NULL; + } +} + +void nat25_db_expire(struct rwnx_vif *vif) +{ + int i; + + spin_lock_bh(&vif->br_ext_lock); + + /* if(!priv->eth_br_ext_info.nat25_disable) */ + { + for (i = 0; i < NAT25_HASH_SIZE; i++) { + struct nat25_network_db_entry *f; + + f = vif->nethash[i]; + + while (f) { + struct nat25_network_db_entry *g; + + g = f->next_hash; + + if (__nat25_has_expired(vif, f)) { + if (atomic_dec_and_test(&f->use_count)) { +#ifdef BR_SUPPORT_DEBUG +#ifdef CL_IPV6_PASS + panic_printk("NAT25 Expire H(%02d) M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + i, f->mac_addr[0], f->mac_addr[1], + f->mac_addr[2], f->mac_addr[3], + f->mac_addr[4], f->mac_addr[5], + f->network_addr[0], f->network_addr[1], + f->network_addr[2], f->network_addr[3], + f->network_addr[4], f->network_addr[5], + f->network_addr[6], f->network_addr[7], + f->network_addr[8], f->network_addr[9], + f->network_addr[10], + f->network_addr[11], + f->network_addr[12], + f->network_addr[13], + f->network_addr[14], + f->network_addr[15], + f->network_addr[16]); +#else + + panic_printk("NAT25 Expire H(%02d) M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + i, f->mac_addr[0], f->mac_addr[1], + f->mac_addr[2], f->mac_addr[3], + f->mac_addr[4], f->mac_addr[5], + f->network_addr[0], f->network_addr[1], + f->network_addr[2], f->network_addr[3], + f->network_addr[4], f->network_addr[5], + f->network_addr[6], f->network_addr[7], + f->network_addr[8], f->network_addr[9], + f->network_addr[10]); +#endif +#endif + clear_scdb_entry(vif, f); + __network_hash_unlink(f); + kfree(f); + } + } + + f = g; + } + } + } + + spin_unlock_bh(&vif->br_ext_lock); +} + +#ifdef SUPPORT_TX_MCAST2UNI +static int check_ip_mc_and_replace(struct rwnx_vif *vif, struct sk_buff *skb, + unsigned int *dst_ip) +{ + struct stat_info *pstat; + struct list_head *phead, *plist; + int i; + + phead = &vif->asoc_list; + plist = phead->next; + + while (plist != phead) { + pstat = list_entry(plist, struct stat_info, asoc_list); + plist = plist->next; + + if (pstat->ipmc_num == 0) + continue; + + for (i = 0; i < MAX_IP_MC_ENTRY; i++) { + if (pstat->ipmc[i].used && + !memcmp(&pstat->ipmc[i].mcmac[3], ((unsigned char *)dst_ip) + 1, + 3)) { + memcpy(skb->data, pstat->ipmc[i].mcmac, ETH_ALEN); + return 1; + } + } + } + return 0; +} +#endif + +static inline void check_and_reinit_br_mac(struct rwnx_vif *vif) +{ + if ((*(u32 *)vif->br_mac) == 0 && + (*(u16 *)(vif->br_mac + 4)) == 0) { + pr_info("Re-init netdev_br_init() due to br_mac==0!\n"); + netdev_br_init(vif->ndev); + } +} + +static int nat25_handle_pppoe_packet(struct rwnx_vif *vif, + struct pppoe_hdr *ph, + struct sk_buff *skb, + unsigned char network_addr[MAX_NETWORK_ADDR_LEN]) +{ + struct pppoe_tag *tag, *old_tag; + unsigned char tag_buf[40]; + int old_tag_len = 0; + unsigned short *p_magic; + + if (ph->sid == 0) { + /* Discovery phase */ + if (ph->code != PADI_CODE && ph->code != PADR_CODE) + return -EINVAL; + + if (vif->eth_br_ext_info.add_pppoe_tag) { + tag = (struct pppoe_tag *)tag_buf; + old_tag = __nat25_find_pppoe_tag(ph, + ntohs(PTT_RELAY_SID)); + + if (old_tag) { + /* Copy old value and delete it */ + old_tag_len = ntohs(old_tag->tag_len); + + if (old_tag_len + TAG_HDR_LEN + + MAGIC_CODE_LEN + RTL_RELAY_TAG_LEN > + sizeof(tag_buf)) { + pr_err("SID tag length too long!\n"); + return -EINVAL; + } + + memcpy(tag->tag_data + MAGIC_CODE_LEN + + RTL_RELAY_TAG_LEN, + old_tag->tag_data, old_tag_len); + + if (skb_pull_and_merge(skb, + (unsigned char *)old_tag, + TAG_HDR_LEN + old_tag_len) + < 0) { + pr_err("skb_pull_and_merge() failed in PADI/R packet!\n"); + return -EINVAL; + } + + ph->length = htons(ntohs(ph->length) - + TAG_HDR_LEN - old_tag_len); + } + + tag->tag_type = PTT_RELAY_SID; + tag->tag_len = htons(MAGIC_CODE_LEN + + RTL_RELAY_TAG_LEN + old_tag_len); + + /* Insert magic_code + client MAC */ + p_magic = (unsigned short *)tag->tag_data; + *p_magic = htons(MAGIC_CODE); + memcpy(tag->tag_data + MAGIC_CODE_LEN, + skb->data + ETH_ALEN, ETH_ALEN); + + if (__nat25_add_pppoe_tag(skb, tag) < 0) + return -EINVAL; + + pr_info("NAT25: Insert PPPoE, forward %s packet\n", + (ph->code == PADI_CODE) ? "PADI" : "PADR"); + + } else { + /* Not adding relay tag */ + if (vif->pppoe_connection_in_progress && + memcmp(skb->data + ETH_ALEN, + vif->pppoe_addr, ETH_ALEN)) { + pr_info("Discard PPPoE packet: another connection in progress!\n"); + return -EBUSY; + } + + if (vif->pppoe_connection_in_progress == 0) + memcpy(vif->pppoe_addr, skb->data + ETH_ALEN, + ETH_ALEN); + + vif->pppoe_connection_in_progress = WAIT_TIME_PPPOE; + } + + } else { + /* Session phase */ + pr_info("NAT25: Insert PPPoE session packet to %s\n", + skb->dev->name); + + __nat25_generate_pppoe_network_addr(network_addr, + skb->data, &ph->sid); + + __nat25_db_network_insert(vif, skb->data + ETH_ALEN, + network_addr); + + __nat25_db_print(vif); + + if (!vif->eth_br_ext_info.add_pppoe_tag && + vif->pppoe_connection_in_progress && + !memcmp(skb->data + ETH_ALEN, + vif->pppoe_addr, ETH_ALEN)) + vif->pppoe_connection_in_progress = 0; + } + + return 0; +} + +int nat25_db_handle(struct rwnx_vif *vif, struct sk_buff *skb, int method) +{ + unsigned short protocol; + unsigned char network_addr[MAX_NETWORK_ADDR_LEN]; + + if (!skb) + return -1; + + if (method <= NAT25_MIN || method >= NAT25_MAX) + return -1; + + protocol = *((unsigned short *)(skb->data + 2 * ETH_ALEN)); + + /*---------------------------------------------------*/ + /* Handle IP frame */ + /*---------------------------------------------------*/ + if (protocol == htons(ETH_P_IP)) { + struct iphdr *iph = (struct iphdr *)(skb->data + ETH_HLEN); + + if (((unsigned char *)(iph) + (iph->ihl << 2)) >= + (skb->data + ETH_HLEN + skb->len)) { + pr_err("NAT25: malformed IP packet !\n"); + return -1; + } + + switch (method) { + case NAT25_CHECK: + return -1; + + case NAT25_INSERT: { + /* some multicast with source IP is all zero, maybe other case is + * illegal + */ + /* in class A, B, C, host address is all zero or all one is illegal + */ + if (iph->saddr == 0) + return 0; + pr_info("NAT25: Insert IP, SA=%08x, DA=%08x\n", iph->saddr, + iph->daddr); + __nat25_generate_ipv4_network_addr(network_addr, &iph->saddr); + /* record source IP address and , source mac address into db */ + __nat25_db_network_insert(vif, skb->data + ETH_ALEN, network_addr); + + __nat25_db_print(vif); + } + return 0; + + case NAT25_LOOKUP: { + pr_info("NAT25: Lookup IP, SA=%08x, DA=%08x\n", iph->saddr, + iph->daddr); +#ifdef SUPPORT_TX_MCAST2UNI + if (vif->pshare->rf_ft_var.mc2u_disable || + ((((OPMODE & (WIFI_STATION_STATE | WIFI_ASOC_STATE)) == + (WIFI_STATION_STATE | WIFI_ASOC_STATE)) && + !check_ip_mc_and_replace(vif, skb, &iph->daddr)) || + (OPMODE & WIFI_ADHOC_STATE))) +#endif + { + __nat25_generate_ipv4_network_addr(network_addr, &iph->daddr); + + if (!__nat25_db_network_lookup_and_replace(vif, skb, + network_addr)) { + if (*((unsigned char *)&iph->daddr + 3) == 0xff) { + /* L2 is unicast but L3 is broadcast, make L2 bacome + * broadcast + */ + pr_info("NAT25: Set DA as broadcast\n"); + memset(skb->data, 0xff, ETH_ALEN); + } else { + /* forward unknown IP packet to upper TCP/IP */ + pr_info("NAT25: Replace DA with BR's MAC\n"); + check_and_reinit_br_mac(vif); + memcpy(skb->data, vif->br_mac, ETH_ALEN); + } + } + } + } + return 0; + + default: + return -1; + } + } + + /*---------------------------------------------------*/ + /* Handle ARP frame */ + /*---------------------------------------------------*/ + else if (protocol == htons(ETH_P_ARP)) { + struct arphdr *arp = (struct arphdr *)(skb->data + ETH_HLEN); + unsigned char *arp_ptr = (unsigned char *)(arp + 1); + unsigned int *sender, *target; + + if (arp->ar_pro != htons(ETH_P_IP)) { + pr_err("NAT25: arp protocol unknown (%4x)!\n", htons(arp->ar_pro)); + return -1; + } + + switch (method) { + case NAT25_CHECK: + return 0; /* skb_copy for all ARP frame */ + + case NAT25_INSERT: { + pr_info("NAT25: Insert ARP, MAC=%02x%02x%02x%02x%02x%02x\n", + arp_ptr[0], arp_ptr[1], arp_ptr[2], arp_ptr[3], arp_ptr[4], + arp_ptr[5]); + + /* change to ARP sender mac address to wlan STA address */ + memcpy(arp_ptr, vif->ndev->dev_addr, ETH_ALEN); + + arp_ptr += arp->ar_hln; + sender = (unsigned int *)arp_ptr; + + __nat25_generate_ipv4_network_addr(network_addr, sender); + + __nat25_db_network_insert(vif, skb->data + ETH_ALEN, network_addr); + + __nat25_db_print(vif); + } + return 0; + + case NAT25_LOOKUP: { + pr_info("NAT25: Lookup ARP\n"); + + arp_ptr += arp->ar_hln; + sender = (unsigned int *)arp_ptr; + arp_ptr += (arp->ar_hln + arp->ar_pln); + target = (unsigned int *)arp_ptr; + + __nat25_generate_ipv4_network_addr(network_addr, target); + + __nat25_db_network_lookup_and_replace(vif, skb, network_addr); + + /* change to ARP target mac address to Lookup result */ + arp_ptr = (unsigned char *)(arp + 1); + arp_ptr += (arp->ar_hln + arp->ar_pln); + memcpy(arp_ptr, skb->data, ETH_ALEN); + } + return 0; + + default: + return -1; + } + } + + /*---------------------------------------------------*/ + /* Handle IPX and Apple Talk frame */ + /*---------------------------------------------------*/ + else if ((protocol == htons(ETH_P_IPX)) || + (protocol == htons(ETH_P_ATALK)) || + (protocol == htons(ETH_P_AARP))) { + unsigned char ipx_header[2] = {0xFF, 0xFF}; + struct ipxhdr *ipx = NULL; + struct elapaarp *ea = NULL; + struct ddpehdr *ddp = NULL; + unsigned char *frame_ptr = skb->data + ETH_HLEN; + + if (protocol == htons(ETH_P_IPX)) { + pr_info("NAT25: Protocol=IPX (Ethernet II)\n"); + ipx = (struct ipxhdr *)frame_ptr; + } else { /* if(protocol <= __constant_htons(ETH_FRAME_LEN)) */ + if (!memcmp(ipx_header, frame_ptr, 2)) { + pr_info("NAT25: Protocol=IPX (Ethernet 802.3)\n"); + ipx = (struct ipxhdr *)frame_ptr; + } else { + unsigned char ipx_8022_type = 0xE0; + unsigned char snap_8022_type = 0xAA; + + if (*frame_ptr == snap_8022_type) { + unsigned char ipx_snap_id[5] = {0x0, 0x0, 0x0, 0x81, + 0x37}; /* IPX SNAP ID */ + unsigned char aarp_snap_id[5] = { + 0x00, 0x00, 0x00, 0x80, + 0xF3}; /* Apple Talk AARP SNAP ID */ + unsigned char ddp_snap_id[5] = { + 0x08, 0x00, 0x07, 0x80, + 0x9B}; /* Apple Talk DDP SNAP ID */ + + frame_ptr += 3; /* eliminate the 802.2 header */ + + if (!memcmp(ipx_snap_id, frame_ptr, 5)) { + frame_ptr += 5; /* eliminate the SNAP header */ + + pr_info("NAT25: Protocol=IPX (Ethernet SNAP)\n"); + ipx = (struct ipxhdr *)frame_ptr; + } else if (!memcmp(aarp_snap_id, frame_ptr, 5)) { + frame_ptr += 5; /* eliminate the SNAP header */ + + ea = (struct elapaarp *)frame_ptr; + } else if (!memcmp(ddp_snap_id, frame_ptr, 5)) { + frame_ptr += 5; /* eliminate the SNAP header */ + + ddp = (struct ddpehdr *)frame_ptr; + } else { + pr_info("NAT25: Protocol=Ethernet SNAP %02x%02x%02x%02x%02x\n", + frame_ptr[0], frame_ptr[1], frame_ptr[2], + frame_ptr[3], frame_ptr[4]); + return -1; + } + } else if (*frame_ptr == ipx_8022_type) { + frame_ptr += 3; /* eliminate the 802.2 header */ + + if (!memcmp(ipx_header, frame_ptr, 2)) { + pr_info("NAT25: Protocol=IPX (Ethernet 802.2)\n"); + ipx = (struct ipxhdr *)frame_ptr; + } else { + return -1; + } + } + } + } + + /* IPX */ + if (ipx) { + unsigned int *ipx_net = NULL; + unsigned short *ipx_socket = NULL; + + switch (method) { + case NAT25_CHECK: + if (!memcmp(skb->data + ETH_ALEN, ipx->ipx_source.node, + ETH_ALEN)) { + pr_info("NAT25: Check IPX skb_copy\n"); + return 0; + } + return -1; + + case NAT25_INSERT: { + pr_info("NAT25: Insert IPX, Dest=%08x,%02x%02x%02x%02x%02x%02x,%04x Source=%08x,%02x%02x%02x%02x%02x%02x,%04x\n", + ipx->ipx_dest.net, ipx->ipx_dest.node[0], + ipx->ipx_dest.node[1], ipx->ipx_dest.node[2], + ipx->ipx_dest.node[3], ipx->ipx_dest.node[4], + ipx->ipx_dest.node[5], ipx->ipx_dest.sock, + ipx->ipx_source.net, ipx->ipx_source.node[0], + ipx->ipx_source.node[1], ipx->ipx_source.node[2], + ipx->ipx_source.node[3], ipx->ipx_source.node[4], + ipx->ipx_source.node[5], ipx->ipx_source.sock); + + if (!memcmp(skb->data + ETH_ALEN, ipx->ipx_source.node, + ETH_ALEN)) { + unsigned int *ipx_net = NULL; + unsigned short *ipx_socket = NULL; + + pr_info("NAT25: Use IPX Net, and Socket as network addr\n"); + + ipx_net = &ipx->ipx_source.net; + ipx_socket = &ipx->ipx_source.sock; + __nat25_generate_ipx_network_addr_with_socket(network_addr, + ipx_net, + ipx_socket); + + /* change IPX source node addr to wlan STA address */ + memcpy(ipx->ipx_source.node, vif->ndev->dev_addr, ETH_ALEN); + } else { + unsigned int *ipx_net = NULL; + unsigned char *ipx_node = NULL; + + ipx_net = &ipx->ipx_source.net; + ipx_node = ipx->ipx_source.node; + __nat25_generate_ipx_network_addr_with_node(network_addr, + ipx_net, + ipx_node); + } + + __nat25_db_network_insert(vif, skb->data + ETH_ALEN, + network_addr); + + __nat25_db_print(vif); + } + return 0; + + case NAT25_LOOKUP: { + if (!memcmp(vif->ndev->dev_addr, ipx->ipx_dest.node, + ETH_ALEN)) { + unsigned int *ipx_net = &ipx->ipx_dest.net; + unsigned short *ipx_socket = &ipx->ipx_dest.sock; + + pr_info("NAT25: Lookup IPX, Modify Destination IPX Node addr\n"); + + __nat25_generate_ipx_network_addr_with_socket(network_addr, + ipx_net, + ipx_socket); + + __nat25_db_network_lookup_and_replace(vif, skb, + network_addr); + + /* replace IPX destination node addr with Lookup destination + * MAC addr + */ + memcpy(ipx->ipx_dest.node, skb->data, ETH_ALEN); + } else { + unsigned int *ipx_net = &ipx->ipx_dest.net; + unsigned char *ipx_node = ipx->ipx_dest.node; + + __nat25_generate_ipx_network_addr_with_node(network_addr, + ipx_net, + ipx_node); + + __nat25_db_network_lookup_and_replace(vif, skb, + network_addr); + } + } + return 0; + + default: + return -1; + } + } + + /* AARP */ + else if (ea) { + /* Sanity check fields. */ + if (ea->hw_len != ETH_ALEN || ea->pa_len != AARP_PA_ALEN) { + pr_err("NAT25: Appletalk AARP Sanity check fail!\n"); + return -1; + } + + switch (method) { + case NAT25_CHECK: + return 0; + + case NAT25_INSERT: { + /* change to AARP source mac address to wlan STA address */ + memcpy(ea->hw_src, vif->ndev->dev_addr, ETH_ALEN); + + pr_info("NAT25: Insert AARP, Source=%d,%d Destination=%d,%d\n", + ea->pa_src_net, ea->pa_src_node, ea->pa_dst_net, + ea->pa_dst_node); + + __nat25_generate_apple_network_addr(network_addr, + &ea->pa_src_net, + &ea->pa_src_node); + + __nat25_db_network_insert(vif, skb->data + ETH_ALEN, + network_addr); + + __nat25_db_print(vif); + } + return 0; + + case NAT25_LOOKUP: { + pr_info("NAT25: Lookup AARP, Source=%d,%d Destination=%d,%d\n", + ea->pa_src_net, ea->pa_src_node, ea->pa_dst_net, + ea->pa_dst_node); + + __nat25_generate_apple_network_addr(network_addr, + &ea->pa_dst_net, + &ea->pa_dst_node); + + __nat25_db_network_lookup_and_replace(vif, skb, network_addr); + + /* change to AARP destination mac address to Lookup result */ + memcpy(ea->hw_dst, skb->data, ETH_ALEN); + } + return 0; + + default: + return -1; + } + } + + /* DDP */ + else if (ddp) { + switch (method) { + case NAT25_CHECK: + return -1; + + case NAT25_INSERT: { + pr_info("NAT25: Insert DDP, Source=%d,%d Destination=%d,%d\n", + ddp->deh_snet, ddp->deh_snode, ddp->deh_dnet, + ddp->deh_dnode); + + __nat25_generate_apple_network_addr(network_addr, &ddp->deh_snet, + &ddp->deh_snode); + + __nat25_db_network_insert(vif, skb->data + ETH_ALEN, + network_addr); + + __nat25_db_print(vif); + } + return 0; + + case NAT25_LOOKUP: { + pr_info("NAT25: Lookup DDP, Source=%d,%d Destination=%d,%d\n", + ddp->deh_snet, ddp->deh_snode, ddp->deh_dnet, + ddp->deh_dnode); + + __nat25_generate_apple_network_addr(network_addr, &ddp->deh_dnet, + &ddp->deh_dnode); + + __nat25_db_network_lookup_and_replace(vif, skb, network_addr); + } + return 0; + + default: + return -1; + } + } + + return -1; + } + + /*---------------------------------------------------*/ + /* Handle PPPoE frame */ + /*---------------------------------------------------*/ + else if ((protocol == htons(ETH_P_PPP_DISC)) || + (protocol == htons(ETH_P_PPP_SES))) { + struct pppoe_hdr *ph = (struct pppoe_hdr *)(skb->data + ETH_HLEN); + unsigned short *pmagic; + + switch (method) { + case NAT25_CHECK: + if (ph->sid == 0) + return 0; + return 1; + + case NAT25_INSERT: + return nat25_handle_pppoe_packet(vif, ph, skb, + network_addr); + + case NAT25_LOOKUP: + if (ph->code == PADO_CODE || ph->code == PADS_CODE) { + if (vif->eth_br_ext_info.add_pppoe_tag) { + struct pppoe_tag *tag; + unsigned char *ptr; + unsigned short tag_type, tag_len; + int offset = 0; + + ptr = __nat25_find_pppoe_tag(ph, ntohs(PTT_RELAY_SID)); + if (ptr == 0) { + pr_err("Fail to find PTT_RELAY_SID in FADO!\n"); + return -1; + } + + tag = (struct pppoe_tag *)ptr; + tag_type = (unsigned short)((ptr[0] << 8) + ptr[1]); + tag_len = (unsigned short)((ptr[2] << 8) + ptr[3]); + + if (tag_type != ntohs(PTT_RELAY_SID) || + (tag_len < (MAGIC_CODE_LEN + RTL_RELAY_TAG_LEN))) { + pr_err("Invalid PTT_RELAY_SID tag length [%d]!\n", + tag_len); + return -1; + } + + pmagic = (unsigned short *)tag->tag_data; + if (ntohs(*pmagic) != MAGIC_CODE) { + pr_err("Can't find MAGIC_CODE in %s packet!\n", + (ph->code == PADO_CODE ? "PADO" : "PADS")); + return -1; + } + + memcpy(skb->data, tag->tag_data + MAGIC_CODE_LEN, ETH_ALEN); + + if (tag_len > MAGIC_CODE_LEN + RTL_RELAY_TAG_LEN) + offset = TAG_HDR_LEN; + + if (skb_pull_and_merge(skb, ptr + offset, + TAG_HDR_LEN + MAGIC_CODE_LEN + + RTL_RELAY_TAG_LEN - offset) < + 0) { + pr_err("call skb_pull_and_merge() failed in PADO packet!\n"); + return -1; + } + ph->length = htons(ntohs(ph->length) - + (TAG_HDR_LEN + MAGIC_CODE_LEN + + RTL_RELAY_TAG_LEN - offset)); + if (offset > 0) + tag->tag_len = + htons(tag_len - MAGIC_CODE_LEN - + RTL_RELAY_TAG_LEN); + + pr_info("NAT25: Lookup PPPoE, forward %s Packet from %s\n", + (ph->code == PADO_CODE ? "PADO" : "PADS"), + skb->dev->name); + } else { /* not add relay tag */ + if (!vif->pppoe_connection_in_progress) { + pr_err("Discard PPPoE packet due to no connection in progress!\n"); + return -1; + } + memcpy(skb->data, vif->pppoe_addr, ETH_ALEN); + vif->pppoe_connection_in_progress = WAIT_TIME_PPPOE; + } + } else { + if (ph->sid != 0) { + pr_info("NAT25: Lookup PPPoE, lookup session packet from %s\n", + skb->dev->name); + __nat25_generate_pppoe_network_addr(network_addr, + skb->data + + ETH_ALEN, + &ph->sid); + + __nat25_db_network_lookup_and_replace(vif, skb, + network_addr); + + __nat25_db_print(vif); + } else { + return -1; + } + } + return 0; + + default: + return -1; + } + } + + /*---------------------------------------------------*/ + /* Handle EAP frame */ + /*---------------------------------------------------*/ + else if (protocol == htons(0x888e)) { + switch (method) { + case NAT25_CHECK: + return -1; + + case NAT25_INSERT: + return 0; + + case NAT25_LOOKUP: + return 0; + + default: + return -1; + } + } + + /*---------------------------------------------------*/ + /* Handle C-Media proprietary frame */ + /*---------------------------------------------------*/ + else if ((protocol == htons(0xe2ae)) || + (protocol == htons(0xe2af))) { + switch (method) { + case NAT25_CHECK: + return -1; + + case NAT25_INSERT: + return 0; + + case NAT25_LOOKUP: + return 0; + + default: + return -1; + } + } + + /*---------------------------------------------------*/ + /* Handle IPV6 frame */ + /*---------------------------------------------------*/ +#ifdef CL_IPV6_PASS + else if (protocol == htons(ETH_P_IPV6)) { + struct ipv6hdr *iph = (struct ipv6hdr *)(skb->data + ETH_HLEN); + + if (sizeof(*iph) >= (skb->len - ETH_HLEN)) { + pr_err("NAT25: malformed IPv6 packet !\n"); + return -1; + } + + switch (method) { + case NAT25_CHECK: + if (skb->data[0] & 1) + return 0; + return -1; + + case NAT25_INSERT: { + pr_info("NAT25: Insert IP, SA=%4x:%4x:%4x:%4x:%4x:%4x:%4x:%4x, DA=%4x:%4x:%4x:%4x:%4x:%4x:%4x:%4x\n", + iph->saddr.s6_addr16[0], iph->saddr.s6_addr16[1], + iph->saddr.s6_addr16[2], iph->saddr.s6_addr16[3], + iph->saddr.s6_addr16[4], iph->saddr.s6_addr16[5], + iph->saddr.s6_addr16[6], iph->saddr.s6_addr16[7], + iph->daddr.s6_addr16[0], iph->daddr.s6_addr16[1], + iph->daddr.s6_addr16[2], iph->daddr.s6_addr16[3], + iph->daddr.s6_addr16[4], iph->daddr.s6_addr16[5], + iph->daddr.s6_addr16[6], iph->daddr.s6_addr16[7]); + + if (memcmp(&iph->saddr, + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", + 16)) { + __nat25_generate_ipv6_network_addr(network_addr, + (unsigned int *)&iph->saddr); + __nat25_db_network_insert(vif, skb->data + ETH_ALEN, + network_addr); + __nat25_db_print(vif); + + if (iph->nexthdr == IPPROTO_ICMPV6 && + skb->len > (ETH_HLEN + sizeof(*iph) + 4)) { + if (update_nd_link_layer_addr(skb->data + + ETH_HLEN + + sizeof(*iph), + skb->len - + ETH_HLEN - + sizeof(*iph), + vif->ndev->dev_addr)) { + struct icmp6hdr *hdr = + (struct icmp6hdr *)(skb->data + ETH_HLEN + + sizeof(*iph)); + hdr->icmp6_cksum = 0; + hdr->icmp6_cksum = + csum_ipv6_magic(&iph->saddr, &iph->daddr, + iph->payload_len, IPPROTO_ICMPV6, + csum_partial((__u8 *)hdr, + iph->payload_len, + 0)); + } + } + } + } + return 0; + + case NAT25_LOOKUP: + pr_info("NAT25: Lookup IP, SA=%4x:%4x:%4x:%4x:%4x:%4x:%4x:%4x, DA=%4x:%4x:%4x:%4x:%4x:%4x:%4x:%4x\n", + iph->saddr.s6_addr16[0], iph->saddr.s6_addr16[1], + iph->saddr.s6_addr16[2], iph->saddr.s6_addr16[3], + iph->saddr.s6_addr16[4], iph->saddr.s6_addr16[5], + iph->saddr.s6_addr16[6], iph->saddr.s6_addr16[7], + iph->daddr.s6_addr16[0], iph->daddr.s6_addr16[1], + iph->daddr.s6_addr16[2], iph->daddr.s6_addr16[3], + iph->daddr.s6_addr16[4], iph->daddr.s6_addr16[5], + iph->daddr.s6_addr16[6], iph->daddr.s6_addr16[7]); + + __nat25_generate_ipv6_network_addr(network_addr, + (unsigned int *)&iph->daddr); + if (!__nat25_db_network_lookup_and_replace(vif, skb, network_addr)) { +#ifdef SUPPORT_RX_UNI2MCAST + if (iph->daddr.s6_addr[0] == 0xff) + convert_ipv6_mac_to_mc(skb); +#endif + } + return 0; + + default: + return -1; + } + } +#endif /* CL_IPV6_PASS */ + + return -1; +} + +int nat25_handle_frame(struct rwnx_vif *vif, struct sk_buff *skb) +{ + // printk("%s : vif_type=%d\n",__func__,RWNX_VIF_TYPE(vif)); +#ifdef BR_SUPPORT_DEBUG + if (!vif->eth_br_ext_info.nat25_disable && (!(skb->data[0] & 1))) { + pr_info("NAT25: Input Frame: DA=%02x%02x%02x%02x%02x%02x SA=%02x%02x%02x%02x%02x%02x\n", + skb->data[0], skb->data[1], skb->data[2], skb->data[3], + skb->data[4], skb->data[5], skb->data[6], skb->data[7], + skb->data[8], skb->data[9], skb->data[10], skb->data[11]); + } +#endif + + if (!(skb->data[0] & 1)) { + int is_vlan_tag = 0, i, retval = 0; + unsigned short vlan_hdr = 0; + + if (*((unsigned short *)(skb->data + ETH_ALEN * 2)) == + htons(ETH_P_8021Q)) { + is_vlan_tag = 1; + vlan_hdr = *((unsigned short *)(skb->data + ETH_ALEN * 2 + 2)); + for (i = 0; i < 6; i++) + *((unsigned short *)(skb->data + ETH_ALEN * 2 + 2 - i * 2)) = + *((unsigned short *)(skb->data + ETH_ALEN * 2 - 2 - i * 2)); + skb_pull(skb, 4); + } + + if (!vif->eth_br_ext_info.nat25_disable) { + unsigned long irqL; + + spin_lock_bh(&vif->br_ext_lock); + /* + * This function look up the destination network address from + * the NAT2.5 database. Return value = -1 means that the + * corresponding network protocol is NOT support. + */ + if (!vif->eth_br_ext_info.nat25sc_disable && + (*((unsigned short *)(skb->data + ETH_ALEN * 2)) == + htons(ETH_P_IP)) && + !memcmp(vif->scdb_ip, skb->data + ETH_HLEN + 16, 4)) { + memcpy(skb->data, vif->scdb_mac, ETH_ALEN); + + spin_unlock_bh(&vif->br_ext_lock); + } else { + spin_unlock_bh(&vif->br_ext_lock); + + retval = nat25_db_handle(vif, skb, NAT25_LOOKUP); + } + } else { + if (((*((unsigned short *)(skb->data + ETH_ALEN * 2)) == + htons(ETH_P_IP)) && + !memcmp(vif->br_ip, skb->data + ETH_HLEN + 16, 4)) || + ((*((unsigned short *)(skb->data + ETH_ALEN * 2)) == + htons(ETH_P_ARP)) && + !memcmp(vif->br_ip, skb->data + ETH_HLEN + 24, 4))) { + /* for traffic to upper TCP/IP */ + retval = nat25_db_handle(vif, skb, NAT25_LOOKUP); + } + } + + if (is_vlan_tag) { + skb_push(skb, 4); + for (i = 0; i < 6; i++) + *((unsigned short *)(skb->data + i * 2)) = + *((unsigned short *)(skb->data + 4 + i * 2)); + *((unsigned short *)(skb->data + ETH_ALEN * 2)) = + htons(ETH_P_8021Q); + *((unsigned short *)(skb->data + ETH_ALEN * 2 + 2)) = vlan_hdr; + } + + if (retval == -1) { + /* DEBUG_ERR("NAT25: Lookup fail!\n"); */ + return -1; + } + } + + return 0; +} + +#define SERVER_PORT 67 +#define CLIENT_PORT 68 +#define DHCP_MAGIC 0x63825363 +#define BROADCAST_FLAG 0x8000 + +struct dhcp_message { + u8 op; + u8 htype; + u8 hlen; + u8 hops; + u32 xid; + u16 secs; + u16 flags; + u32 ciaddr; + u32 yiaddr; + u32 siaddr; + u32 giaddr; + u8 chaddr[16]; + u8 sname[64]; + u8 file[128]; + u32 cookie; + u8 options[308]; /* 312 - cookie */ +}; + +static inline void dhcp_fix_broadcast_flag(struct dhcp_message *dhcph, + struct udphdr *udph) +{ + int sum; + + if (dhcph->cookie == __constant_htonl(DHCP_MAGIC)) { + /* match magic word */ + if (!(dhcph->flags & htons(BROADCAST_FLAG))) { + /* if not broadcast */ + pr_info("DHCP: change flag of DHCP request to broadcast.\n"); + + /* OR BROADCAST flag */ + dhcph->flags |= htons(BROADCAST_FLAG); + + /* recalculate checksum */ + sum = ~(udph->check) & 0xffff; + sum += dhcph->flags; + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + udph->check = ~sum; + } + } +} + +void dhcp_flag_bcast(struct rwnx_vif *vif, struct sk_buff *skb) +{ + if (!skb) + return; + // debug trace + // print_hex_dump(KERN_ERR, "SKB DUMP: SKB->DATA== ", DUMP_PREFIX_NONE, 32, + // 1, skb->data, 64,false); + if (!vif->eth_br_ext_info.dhcp_bcst_disable) { + unsigned short protocol = + *((unsigned short *)(skb->data + 2 * ETH_ALEN)); + pr_info("%s protocol: %04x\n", __func__, protocol); + + if (protocol == htons(ETH_P_IP)) { /* IP */ + struct iphdr *iph = (struct iphdr *)(skb->data + ETH_HLEN); + + if (iph->protocol == IPPROTO_UDP) { /* UDP */ + struct udphdr *udph = + (struct udphdr *)((u8 *)iph + (iph->ihl << 2)); + + if (udph->source == htons(CLIENT_PORT) && + udph->dest == + htons(SERVER_PORT)) { /* DHCP request */ + struct dhcp_message *dhcph = + (struct dhcp_message *)((u8 *)udph + + sizeof(struct udphdr)); + dhcp_fix_broadcast_flag(dhcph, udph); + } + } + } + } +} + +void *scdb_find_entry(struct rwnx_vif *vif, unsigned char *mac_addr, + unsigned char *ip_addr) +{ + unsigned char network_addr[MAX_NETWORK_ADDR_LEN]; + struct nat25_network_db_entry *db; + int hash; + + __nat25_generate_ipv4_network_addr(network_addr, (unsigned int *)ip_addr); + hash = __nat25_network_hash(network_addr); + db = vif->nethash[hash]; + while (db) { + if (!memcmp(db->network_addr, network_addr, MAX_NETWORK_ADDR_LEN)) + return (void *)db; + + db = db->next_hash; + } + + return NULL; +} + +#endif /* CONFIG_AIC8800_BR_SUPPORT */ diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h b/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h new file mode 100644 index 0000000000000..f64a7592cbff2 --- /dev/null +++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/** + **************************************************************************** + * + * Copyright (C) 2020 AIC semiconductor. + * + * @brief bridge function definitions + * + **************************************************************************** + */ + +#ifndef _AIC_BR_EXT_H_ +#define _AIC_BR_EXT_H_ + +#define CL_IPV6_PASS 1 +#define MACADDRLEN 6 +#define WLAN_ETHHDR_LEN 14 + +#define NAT25_HASH_BITS 4 +#define NAT25_HASH_SIZE BIT(NAT25_HASH_BITS) +#define NAT25_AGEING_TIME 300 + +#define NDEV_FMT "%s" +#define NDEV_ARG(ndev) ((ndev)->name) +#define ADPT_FMT "%s" +// #define ADPT_ARG(adapter) (adapter->pnetdev ? adapter->pnetdev->name : NULL) +#define FUNC_NDEV_FMT "%s(%s)" +#define FUNC_NDEV_ARG(ndev) (__func__, (ndev)->name) +#define FUNC_ADPT_FMT "%s(%s)" +// #define FUNC_ADPT_ARG(adapter) __func__, (adapter->pnetdev ? +// adapter->pnetdev->name : NULL) +#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" + +#ifdef CL_IPV6_PASS +#define MAX_NETWORK_ADDR_LEN 17 +#else +#define MAX_NETWORK_ADDR_LEN 11 +#endif + +struct nat25_network_db_entry { + struct nat25_network_db_entry *next_hash; + struct nat25_network_db_entry **pprev_hash; + atomic_t use_count; + unsigned char mac_addr[6]; + unsigned long ageing_timer; + unsigned char network_addr[MAX_NETWORK_ADDR_LEN]; +}; + +enum NAT25_METHOD { + NAT25_MIN, + NAT25_CHECK, + NAT25_INSERT, + NAT25_LOOKUP, + NAT25_PARSE, + NAT25_MAX +}; + +struct br_ext_info { + unsigned int nat25_disable; + unsigned int macclone_enable; + unsigned int dhcp_bcst_disable; + int add_pppoe_tag; /* 1: Add PPPoE relay-SID, 0: disable */ + unsigned char nat25_dmz_mac[MACADDRLEN]; + unsigned int nat25sc_disable; +}; + +void nat25_db_cleanup(struct rwnx_vif *vif); +int nat25_handle_frame(struct rwnx_vif *vif, struct sk_buff *skb); +int aic_br_client_tx(struct rwnx_vif *vif, struct sk_buff **pskb); +void netdev_br_init(struct net_device *netdev); + +#endif /* _AIC_BR_EXT_H_ */ diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c b/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c new file mode 100644 index 0000000000000..5c9ed79c7618f --- /dev/null +++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c @@ -0,0 +1,1164 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + **************************************************************************** + * + * Copyright (C) 2020 AIC semiconductor. + * + * @brief bt uses the sdio interface function definitions + * + **************************************************************************** + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "aic_btsdio.h" +#include "rwnx_msg_tx.h" + +static spinlock_t queue_lock; + +static inline struct sk_buff *bt_skb_alloc(unsigned int len, gfp_t how) +{ + struct sk_buff *skb; + + skb = alloc_skb(len + BT_SKB_RESERVE, how); + if (skb) { + skb_reserve(skb, BT_SKB_RESERVE); + bt_cb(skb)->incoming = 0; + } + return skb; +} + +static spinlock_t queue_lock; +static spinlock_t dlfw_lock; +static u16 dlfw_dis_state; + +/* Global parameters for bt usb char driver */ +#define BT_CHAR_DEVICE_NAME "aicbt_dev" +/* mutex for btchr */ +struct mutex btchr_mutex; +static struct sk_buff_head btchr_readq; +static wait_queue_head_t btchr_read_wait; +static wait_queue_head_t bt_dlfw_wait; +static int bt_char_dev_registered; +static dev_t bt_devid; /* bt char device number */ +static struct cdev bt_char_dev; /* bt character device structure */ +static struct class *bt_char_class; /* device class for usb char driver */ +static int bt_reset; +/* HCI device & lock */ +DEFINE_RWLOCK(hci_dev_lock); + +struct hci_dev *ghdev; + +static struct sk_buff *aic_skb_queue[QUEUE_SIZE]; +static int aic_skb_queue_front; +static int aic_skb_queue_rear; + +static inline int check_set_dlfw_state_value(uint16_t change_value) +{ + spin_lock(&dlfw_lock); + if (!dlfw_dis_state) + dlfw_dis_state = change_value; + spin_unlock(&dlfw_lock); + return dlfw_dis_state; +} + +static inline void set_dlfw_state_value(uint16_t change_value) +{ + spin_lock(&dlfw_lock); + dlfw_dis_state = change_value; + spin_unlock(&dlfw_lock); +} + +static void print_acl(struct sk_buff *skb, int direction) +{ +#if PRINT_ACL_DATA + u16 *handle = (u16 *)(skb->data); + u16 len = *(handle + 1); + + AICBT_INFO("aic %s: direction %d, handle %04x, len %d", __func__, direction, + *handle, len); +#endif +} + +static void print_sco(struct sk_buff *skb, int direction) +{ +#if PRINT_SCO_DATA + uint wlength = skb->len; + u16 *handle = (u16 *)(skb->data); + u8 len = *(u8 *)(handle + 1); + + AICBT_INFO("aic %s: direction %d, handle %04x, len %d,wlength %d", __func__, + direction, *handle, len, wlength); +#endif +} + +int bt_bypass_event(struct sk_buff *skb) +{ + int ret = 0; + u8 *opcode = (u8 *)(skb->data); + // debug trace + // printk("bypass_event + // %x,%x,%x,%x,%x\r\n",opcode[0],opcode[1],opcode[2],opcode[3],opcode[4]); + + switch (opcode[1]) { + case HCI_EV_LE_Meta: { + u8 subevent_code; + + subevent_code = opcode[3]; + switch (subevent_code) { + case HCI_BLE_ADV_PKT_RPT_EVT: + case HCI_LE_EXTENDED_ADVERTISING_REPORT_EVT: { + if (aic_queue_cnt() > (QUEUE_SIZE - 490)) { + pr_warn("more adv report bypass\r\n"); + ret = 1; + } + } break; + } + } break; + default: + break; + } + return ret; +} + +int bt_sdio_recv(u8 *data, u32 data_len) +{ + struct sk_buff *skb; + int type = data[0]; + struct hci_dev *hdev; + u32 len = data_len; + + hdev = hci_dev_get(0); + if (!hdev) { + AICWFDBG(LOGERROR, "%s: Failed to get hci dev[NULL]", __func__); + return -ENODEV; + } + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) + AICWFDBG(LOGERROR, "alloc skb fail %s\n", __func__); + memcpy(skb_put(skb, len), data, len); + if (bt_bypass_event(skb)) { + kfree_skb(skb); + return 0; + } + // bt_data_dump("bt_skb", skb, skb->len); //debug trace + + if (aic_enqueue(skb) < 0) { + kfree_skb(skb); + } else { + // printk("wake up\n"); //debug trace + wake_up_interruptible(&btchr_read_wait); + } + return 0; +} + +static int bypass_event(struct sk_buff *skb) +{ + int ret = 0; + u8 *opcode = (u8 *)(skb->data); + + switch (*opcode) { +#ifdef CONFIG_SUPPORT_VENDOR_APCF + case HCI_EV_CMD_COMPLETE: { + u16 sub_opcpde; + + sub_opcpde = ((u16)opcode[3] | (u16)(opcode[4]) << 8); + if (sub_opcpde == 0xfd57) { + if (vendor_apcf_sent_done) { + vendor_apcf_sent_done--; + pr_warn("apcf bypass\r\n"); + ret = 1; + } + } + } break; +#endif // CONFIG_SUPPORT_VENDOR_APCF + case HCI_EV_LE_Meta: { + u8 subevent_code; + + subevent_code = opcode[2]; + switch (subevent_code) { + case HCI_BLE_ADV_PKT_RPT_EVT: + case HCI_LE_EXTENDED_ADVERTISING_REPORT_EVT: { + if (aic_queue_cnt() > (QUEUE_SIZE - 100)) { + pr_warn("more adv report bypass\r\n"); + ret = 1; + } + } break; + } + } break; + default: + break; + } + return ret; +} + +static void print_event(struct sk_buff *skb) +{ +#if PRINT_CMD_EVENT + u8 *opcode = (u8 *)(skb->data); + + pr_info("aic %s ", __func__); + switch (*opcode) { + case HCI_EV_INQUIRY_COMPLETE: + pr_info("HCI_EV_INQUIRY_COMPLETE"); + break; + case HCI_EV_INQUIRY_RESULT: + pr_info("HCI_EV_INQUIRY_RESULT"); + break; + case HCI_EV_CONN_COMPLETE: + pr_info("HCI_EV_CONN_COMPLETE"); + break; + case HCI_EV_CONN_REQUEST: + pr_info("HCI_EV_CONN_REQUEST"); + break; + case HCI_EV_DISCONN_COMPLETE: + pr_info("HCI_EV_DISCONN_COMPLETE"); + break; + case HCI_EV_AUTH_COMPLETE: + pr_info("HCI_EV_AUTH_COMPLETE"); + break; + case HCI_EV_REMOTE_NAME: + pr_info("HCI_EV_REMOTE_NAME"); + break; + case HCI_EV_ENCRYPT_CHANGE: + pr_info("HCI_EV_ENCRYPT_CHANGE"); + break; + case HCI_EV_CHANGE_LINK_KEY_COMPLETE: + pr_info("HCI_EV_CHANGE_LINK_KEY_COMPLETE"); + break; + case HCI_EV_REMOTE_FEATURES: + pr_info("HCI_EV_REMOTE_FEATURES"); + break; + case HCI_EV_REMOTE_VERSION: + pr_info("HCI_EV_REMOTE_VERSION"); + break; + case HCI_EV_QOS_SETUP_COMPLETE: + pr_info("HCI_EV_QOS_SETUP_COMPLETE"); + break; + case HCI_EV_CMD_COMPLETE: + pr_info("HCI_EV_CMD_COMPLETE"); + break; + case HCI_EV_CMD_STATUS: + pr_info("HCI_EV_CMD_STATUS"); + break; + case HCI_EV_ROLE_CHANGE: + pr_info("HCI_EV_ROLE_CHANGE"); + break; + case HCI_EV_NUM_COMP_PKTS: + pr_info("HCI_EV_NUM_COMP_PKTS"); + break; + case HCI_EV_MODE_CHANGE: + pr_info("HCI_EV_MODE_CHANGE"); + break; + case HCI_EV_PIN_CODE_REQ: + pr_info("HCI_EV_PIN_CODE_REQ"); + break; + case HCI_EV_LINK_KEY_REQ: + pr_info("HCI_EV_LINK_KEY_REQ"); + break; + case HCI_EV_LINK_KEY_NOTIFY: + pr_info("HCI_EV_LINK_KEY_NOTIFY"); + break; + case HCI_EV_CLOCK_OFFSET: + pr_info("HCI_EV_CLOCK_OFFSET"); + break; + case HCI_EV_PKT_TYPE_CHANGE: + pr_info("HCI_EV_PKT_TYPE_CHANGE"); + break; + case HCI_EV_PSCAN_REP_MODE: + pr_info("HCI_EV_PSCAN_REP_MODE"); + break; + case HCI_EV_INQUIRY_RESULT_WITH_RSSI: + pr_info("HCI_EV_INQUIRY_RESULT_WITH_RSSI"); + break; + case HCI_EV_REMOTE_EXT_FEATURES: + pr_info("HCI_EV_REMOTE_EXT_FEATURES"); + break; + case HCI_EV_SYNC_CONN_COMPLETE: + pr_info("HCI_EV_SYNC_CONN_COMPLETE"); + break; + case HCI_EV_SYNC_CONN_CHANGED: + pr_info("HCI_EV_SYNC_CONN_CHANGED"); + break; + case HCI_EV_SNIFF_SUBRATE: + pr_info("HCI_EV_SNIFF_SUBRATE"); + break; + case HCI_EV_EXTENDED_INQUIRY_RESULT: + pr_info("HCI_EV_EXTENDED_INQUIRY_RESULT"); + break; + case HCI_EV_IO_CAPA_REQUEST: + pr_info("HCI_EV_IO_CAPA_REQUEST"); + break; + case HCI_EV_SIMPLE_PAIR_COMPLETE: + pr_info("HCI_EV_SIMPLE_PAIR_COMPLETE"); + break; + case HCI_EV_REMOTE_HOST_FEATURES: + pr_info("HCI_EV_REMOTE_HOST_FEATURES"); + break; + default: + pr_info("unknown event"); + break; + } + pr_info("\n"); +#endif +} + +static inline ssize_t sdio_put_user(struct sk_buff *skb, char __user *buf, + int count) +{ + char __user *ptr = buf; + int len = min_t(unsigned int, skb->len, count); + + if (copy_to_user(ptr, skb->data, len)) + return -EFAULT; + + return len; +} + +int aic_enqueue(struct sk_buff *skb) +{ + unsigned long flags = 0; + int ret = 0; + + spin_lock_irqsave(&queue_lock, flags); + if (aic_skb_queue_front == (aic_skb_queue_rear + 1) % QUEUE_SIZE) { + /* + * If queue is full, current solution is to drop + * the following entries. + */ + AICBT_WARN("%s: Queue is full, entry will be dropped", __func__); + ret = -1; + } else { + aic_skb_queue[aic_skb_queue_rear] = skb; + + aic_skb_queue_rear++; + aic_skb_queue_rear %= QUEUE_SIZE; + } + spin_unlock_irqrestore(&queue_lock, flags); + return ret; +} + +static struct sk_buff *aic_dequeue_try(unsigned int deq_len) +{ + struct sk_buff *skb; + struct sk_buff *skb_copy; + unsigned long flags = 0; + + spin_lock_irqsave(&queue_lock, flags); + if (aic_skb_queue_front == aic_skb_queue_rear) { + AICBT_WARN("%s: Queue is empty", __func__); + spin_unlock_irqrestore(&queue_lock, flags); + return NULL; + } + + skb = aic_skb_queue[aic_skb_queue_front]; + if (deq_len >= skb->len) { + aic_skb_queue_front++; + aic_skb_queue_front %= QUEUE_SIZE; + + /* + * Return skb addr to be dequeued, and the caller + * should free the skb eventually. + */ + spin_unlock_irqrestore(&queue_lock, flags); + return skb; + } + skb_copy = pskb_copy(skb, GFP_ATOMIC); + skb_pull(skb, deq_len); + /* Return its copy to be freed */ + spin_unlock_irqrestore(&queue_lock, flags); + return skb_copy; +} + +static inline int is_queue_empty(void) +{ + return (aic_skb_queue_front == aic_skb_queue_rear) ? 1 : 0; +} + +void aic_clear_queue(void) +{ + struct sk_buff *skb; + unsigned long flags = 0; + + spin_lock_irqsave(&queue_lock, flags); + while (!is_queue_empty()) { + skb = aic_skb_queue[aic_skb_queue_front]; + aic_skb_queue[aic_skb_queue_front] = NULL; + aic_skb_queue_front++; + aic_skb_queue_front %= QUEUE_SIZE; + if (skb) + kfree_skb(skb); + } + spin_unlock_irqrestore(&queue_lock, flags); +} + +int aic_queue_cnt(void) +{ + int ret_cnt = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&queue_lock, flags); + if (is_queue_empty()) { + ret_cnt = 0; + } else { + if (aic_skb_queue_rear > aic_skb_queue_front) + ret_cnt = aic_skb_queue_rear - aic_skb_queue_front; + else + ret_cnt = aic_skb_queue_rear + QUEUE_SIZE - aic_skb_queue_front; + } + spin_unlock_irqrestore(&queue_lock, flags); + return ret_cnt; +} + +/* + * AicSemi - Integrate from hci_core.c + */ + +/* Get HCI device by index. + * Device is held on return. + */ +struct hci_dev *hci_dev_get(int index) +{ + if (index != 0) + return NULL; + + return ghdev; +} + +/* ---- HCI ioctl helpers ---- */ +static int hci_dev_open(__u16 dev) +{ + struct hci_dev *hdev; + int ret = 0; + + AICBT_DBG("%s: dev %d", __func__, dev); + + hdev = hci_dev_get(dev); + if (!hdev) { + AICBT_ERR("%s: Failed to get hci dev[Null]", __func__); + return -ENODEV; + } + +done: + return ret; +} + +static int hci_dev_do_close(struct hci_dev *hdev) +{ + /* Clear flags */ + hdev->flags = 0; + return 0; +} + +static int hci_dev_close(__u16 dev) +{ + struct hci_dev *hdev; + int err; + + hdev = hci_dev_get(dev); + if (!hdev) { + AICBT_ERR("%s: failed to get hci dev[Null]", __func__); + return -ENODEV; + } + + err = hci_dev_do_close(hdev); + + return err; +} + +#if CONFIG_BLUEDROID +static struct hci_dev *hci_alloc_dev(void) +{ + struct hci_dev *hdev; + + hdev = kzalloc_obj(*hdev, GFP_KERNEL); + if (!hdev) + return NULL; + + return hdev; +} + +/* Free HCI device */ +static void hci_free_dev(struct hci_dev *hdev) +{ + kfree(hdev); +} + +/* Register HCI device */ +static int hci_register_dev(struct hci_dev *hdev) +{ + int i, id; + + AICBT_DBG("%s: %p name %s bus %d", __func__, hdev, hdev->name, hdev->bus); + /* Do not allow HCI_AMP devices to register at index 0, + * so the index can be used as the AMP controller ID. + */ + id = (hdev->dev_type == HCI_BREDR) ? 0 : 1; + + write_lock(&hci_dev_lock); + + sprintf(hdev->name, "hci%d", id); + hdev->id = id; + hdev->flags = 0; + hdev->dev_flags = 0; + mutex_init(&hdev->lock); + + AICBT_DBG("%s: id %d, name %s", __func__, hdev->id, hdev->name); + + for (i = 0; i < NUM_REASSEMBLY; i++) + hdev->reassembly[i] = NULL; + + memset(&hdev->stat, 0, sizeof(struct hci_dev_stats)); + atomic_set(&hdev->promisc, 0); + + if (ghdev) { + write_unlock(&hci_dev_lock); + AICBT_ERR("%s: Hci device has been registered already", __func__); + return -1; + } + ghdev = hdev; + + write_unlock(&hci_dev_lock); + + return id; +} + +/* Unregister HCI device */ +static void hci_unregister_dev(struct hci_dev *hdev) +{ + int i; + + AICBT_DBG("%s: hdev %p name %s bus %d", __func__, hdev, hdev->name, + hdev->bus); + set_bit(HCI_UNREGISTER, &hdev->dev_flags); + + write_lock(&hci_dev_lock); + ghdev = NULL; + write_unlock(&hci_dev_lock); + + hci_dev_do_close(hdev); + for (i = 0; i < NUM_REASSEMBLY; i++) + kfree_skb(hdev->reassembly[i]); +} + +static void hci_send_to_stack(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct sk_buff *aic_skb_copy = NULL; + + if (!hdev) { + AICBT_ERR("%s: Frame for unknown HCI device", __func__); + return; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) { + AICBT_ERR("%s: HCI not running", __func__); + return; + } + + aic_skb_copy = pskb_copy(skb, GFP_ATOMIC); + if (!aic_skb_copy) { + AICBT_ERR("%s: Copy skb error", __func__); + return; + } + + memcpy(skb_push(aic_skb_copy, 1), &bt_cb(skb)->pkt_type, 1); + aic_enqueue(aic_skb_copy); + + /* Make sure bt char device existing before wakeup read queue */ + hdev = hci_dev_get(0); + if (hdev) + wake_up_interruptible(&btchr_read_wait); +} + +/* Receive frame from HCI drivers */ +static int hci_recv_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *)skb->dev; + + if (!hdev || (!test_bit(HCI_UP, &hdev->flags) && + !test_bit(HCI_INIT, &hdev->flags))) { + kfree_skb(skb); + return -ENXIO; + } + + /* Incoming skb */ + bt_cb(skb)->incoming = 1; + + /* Time stamp */ + __net_timestamp(skb); + + if (atomic_read(&hdev->promisc)) { +#ifdef CONFIG_SCO_OVER_HCI + if (bt_cb(skb)->pkt_type == HCI_SCODATA_PKT) { + hci_send_to_alsa_ringbuffer(hdev, skb); + } else { + if (bt_cb(skb)->pkt_type == HCI_EVENT_PKT) { + if (bypass_event(skb)) { + kfree_skb(skb); + return 0; + } + } + hci_send_to_stack(hdev, skb); + } +#else + if (bt_cb(skb)->pkt_type == HCI_EVENT_PKT) { + if (bypass_event(skb)) { + kfree_skb(skb); + return 0; + } + } + /* Send copy to the sockets */ + hci_send_to_stack(hdev, skb); +#endif + } + + kfree_skb(skb); + return 0; +} + +static int hci_reassembly(struct hci_dev *hdev, int type, void *data, int count, + __u8 index) +{ + int len = 0; + int hlen = 0; + int remain = count; + struct sk_buff *skb; + struct bt_skb_cb *scb; + + if ((type < HCI_ACLDATA_PKT || type > HCI_EVENT_PKT) || + index >= NUM_REASSEMBLY) + return -EILSEQ; + + skb = hdev->reassembly[index]; + + if (!skb) { + switch (type) { + case HCI_ACLDATA_PKT: + len = HCI_MAX_FRAME_SIZE; + hlen = HCI_ACL_HDR_SIZE; + break; + case HCI_EVENT_PKT: + len = HCI_MAX_EVENT_SIZE; + hlen = HCI_EVENT_HDR_SIZE; + break; + case HCI_SCODATA_PKT: + len = HCI_MAX_SCO_SIZE; + hlen = HCI_SCO_HDR_SIZE; + break; + } + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + scb = (void *)skb->cb; + scb->expect = hlen; + scb->pkt_type = type; + + skb->dev = (void *)hdev; + hdev->reassembly[index] = skb; + } + + while (count) { + scb = (void *)skb->cb; + len = min_t(uint, scb->expect, count); + + memcpy(skb_put(skb, len), data, len); + + count -= len; + data += len; + scb->expect -= len; + remain = count; + + switch (type) { + case HCI_EVENT_PKT: + if (skb->len == HCI_EVENT_HDR_SIZE) { + struct hci_event_hdr *h = hci_event_hdr(skb); + + scb->expect = h->plen; + + if (skb_tailroom(skb) < scb->expect) { + kfree_skb(skb); + hdev->reassembly[index] = NULL; + return -ENOMEM; + } + } + break; + + case HCI_ACLDATA_PKT: + if (skb->len == HCI_ACL_HDR_SIZE) { + struct hci_acl_hdr *h = hci_acl_hdr(skb); + + scb->expect = __le16_to_cpu(h->dlen); + + if (skb_tailroom(skb) < scb->expect) { + kfree_skb(skb); + hdev->reassembly[index] = NULL; + return -ENOMEM; + } + } + break; + + case HCI_SCODATA_PKT: + if (skb->len == HCI_SCO_HDR_SIZE) { + struct hci_sco_hdr *h = hci_sco_hdr(skb); + + scb->expect = h->dlen; + + if (skb_tailroom(skb) < scb->expect) { + kfree_skb(skb); + hdev->reassembly[index] = NULL; + return -ENOMEM; + } + } + break; + } + + if (scb->expect == 0) { + /* Complete frame */ + if (type == HCI_ACLDATA_PKT) + print_acl(skb, 0); + if (type == HCI_SCODATA_PKT) + print_sco(skb, 0); + if (type == HCI_EVENT_PKT) + print_event(skb); + + bt_cb(skb)->pkt_type = type; + hci_recv_frame(skb); + + hdev->reassembly[index] = NULL; + return remain; + } + } + + return remain; +} + +int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count) +{ + int rem = 0; + + if (type < HCI_ACLDATA_PKT || type > HCI_EVENT_PKT) + return -EILSEQ; + + while (count) { + rem = hci_reassembly(hdev, type, data, count, type - 1); + if (rem < 0) + return rem; + + data += (count - rem); + count = rem; + } + + return rem; +} +#endif // CONFIG_BLUEDROID + +static int btchr_open(struct inode *inode_p, struct file *file_p) +{ + struct btusb_data *data; + struct hci_dev *hdev; + + AICBT_DBG("%s: BT sdio char device is opening", __func__); + + hdev = hci_dev_get(0); + if (!hdev) { + AICBT_DBG("%s: Failed to get hci dev[NULL]", __func__); + return -ENODEV; + } + data = GET_DRV_DATA(hdev); + + atomic_inc(&hdev->promisc); + /* + * As bt device is not re-opened when hotplugged out, we cannot + * trust on file's private data(may be null) when other file ops + * are invoked. + */ + file_p->private_data = data; + + mutex_lock(&btchr_mutex); + hci_dev_open(0); + mutex_unlock(&btchr_mutex); + + aic_clear_queue(); + return nonseekable_open(inode_p, file_p); +} + +static int btchr_close(struct inode *inode_p, struct file *file_p) +{ + struct btusb_data *data; + struct hci_dev *hdev; + + AICBT_INFO("%s: BT sdio char device is closing", __func__); + + data = file_p->private_data; + file_p->private_data = NULL; + +#if CONFIG_BLUEDROID + /* + * If the upper layer closes bt char interfaces, no reset + * action required even bt device hotplugged out. + */ + bt_reset = 0; +#endif + + hdev = hci_dev_get(0); + if (hdev) { + atomic_set(&hdev->promisc, 0); + mutex_lock(&btchr_mutex); + hci_dev_close(0); + mutex_unlock(&btchr_mutex); + } + + return 0; +} + +void bt_data_dump(char *tag, void *data, unsigned long len) +{ + unsigned long i = 0; + u8 *data_ = (uint8_t *)data; + + pr_info("%s %s len:(%lu)\r\n", __func__, tag, len); + + for (i = 0; i < len; i += 16) { + pr_info("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\r\n", + data_[0 + i], data_[1 + i], data_[2 + i], data_[3 + i], + data_[4 + i], data_[5 + i], data_[6 + i], data_[7 + i], + data_[8 + i], data_[9 + i], data_[10 + i], data_[11 + i], + data_[12 + i], data_[13 + i], data_[14 + i], data_[15 + i]); + } +} + +static ssize_t btchr_read(struct file *file_p, char __user *buf_p, size_t count, + loff_t *pos_p) +{ + struct hci_dev *hdev; + struct sk_buff *skb; + ssize_t ret = 0; + + while (count) { + hdev = hci_dev_get(0); + if (!hdev) { + /* + * Note: Only when BT device hotplugged out, we wil get + * into such situation. In order to keep the upper layer + * stack alive (blocking the read), we should never return + * EFAULT or break the loop. + */ + AICBT_ERR("%s: Failed to get hci dev[Null]", __func__); + } + + ret = wait_event_interruptible(btchr_read_wait, !is_queue_empty()); + if (ret < 0) { + AICBT_ERR("%s: wait event is signaled %d", __func__, (int)ret); + break; + } + + skb = aic_dequeue_try(count); + + if (skb) { + ret = sdio_put_user(skb, buf_p, count); + if (ret < 0) + AICBT_ERR("%s: Failed to put data to user space", __func__); + kfree_skb(skb); + break; + } + } + + return ret; +} + +#ifdef CONFIG_SUPPORT_VENDOR_APCF +void btchr_external_write(char *buff, int len) +{ + struct hci_dev *hdev; + struct sk_buff *skb; + int i; + struct btusb_data *data; + + AICBT_INFO("%s \r\n", __func__); + for (i = 0; i < len; i++) + pr_info("0x%x ", (u8)buff[i]); + pr_info("\r\n"); + hdev = hci_dev_get(0); + if (!hdev) { + AICBT_WARN("%s: Failed to get hci dev[Null]", __func__); + return; + } + /* Never trust on btusb_data, as bt device may be hotplugged out */ + data = GET_DRV_DATA(hdev); + if (!data) { + AICBT_WARN("%s: Failed to get bt sdio driver data[Null]", __func__); + return; + } + vendor_apcf_sent_done++; + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) + return; + skb_reserve(skb, -1); // Add this line + skb->dev = (void *)hdev; + memcpy((__u8 *)skb->data, (__u8 *)buff, len); + skb_put(skb, len); + bt_cb(skb)->pkt_type = *((__u8 *)skb->data); + skb_pull(skb, 1); + data->hdev->send(skb); +} +EXPORT_SYMBOL_GPL(btchr_external_write); +#endif // CONFIG_SUPPORT_VENDOR_APCF + +static ssize_t btchr_write(struct file *file_p, const char __user *buf_p, + size_t count, loff_t *pos_p) +{ + struct btusb_data *data = file_p->private_data; + struct hci_dev *hdev; + struct sk_buff *skb; + int err = 0; + + hdev = hci_dev_get(0); + if (!hdev) { + AICBT_WARN("%s: Failed to get hci dev[Null]", __func__); + /* + * Note: we bypass the data from the upper layer if bt device + * is hotplugged out. Fortunatelly, H4 or H5 HCI stack does + * NOT check btchr_write's return value. However, returning + * count instead of EFAULT is preferable. + */ + /* return -EFAULT; */ + return count; + } + + if (count > HCI_MAX_FRAME_SIZE) + return -EINVAL; + + skb = bt_skb_alloc(count, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + skb_reserve(skb, -1); + + if (copy_from_user(skb_put(skb, count), buf_p, count)) { + AICBT_ERR("%s: Failed to get data from user space", __func__); + kfree_skb(skb); + return -EFAULT; + } + + skb->dev = (void *)hdev; + bt_cb(skb)->pkt_type = *((__u8 *)skb->data); + + err = rwnx_sdio_bt_send_req(g_rwnx_plat->sdiodev->rwnx_hw, skb->len, skb); + if (err < 0) + pr_err("%s rwnx_sdio_bt_send_req error %d", __func__, err); + + kfree_skb(skb); + return count; +} + +static unsigned int btchr_poll(struct file *file_p, poll_table *wait) +{ + struct btusb_data *data = file_p->private_data; + struct hci_dev *hdev; + + poll_wait(file_p, &btchr_read_wait, wait); + + hdev = hci_dev_get(0); + if (!hdev) { + AICBT_ERR("%s: Failed to get hci dev[Null]", __func__); + // mdelay(URB_CANCELING_DELAY_MS); + return POLLERR | POLLHUP; + return POLLOUT | POLLWRNORM; + } + + if (!is_queue_empty()) + return POLLIN | POLLRDNORM; + + return POLLOUT | POLLWRNORM; +} + +static long btchr_ioctl(struct file *file_p, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct hci_dev *hdev; + struct btusb_data *data; + + pr_info("%s cmd support %d\n", __func__, cmd); + + if (check_set_dlfw_state_value(1) != 1) { + AICBT_ERR("%s bt controller is disconnecting!", __func__); + return 0; + } + + hdev = hci_dev_get(0); + if (!hdev) { + AICBT_ERR("%s device is NULL!", __func__); + set_dlfw_state_value(0); + return 0; + } + + AICBT_INFO(" %s DOWN_FW_CFG with Cmd:%d", __func__, cmd); + switch (cmd) { + case DOWN_FW_CFG: + AICBT_INFO(" %s DOWN_FW_CFG", __func__); + set_bit(HCI_UP, &hdev->flags); + set_dlfw_state_value(0); + wake_up_interruptible(&bt_dlfw_wait); + return 1; + case DWFW_CMPLT: + AICBT_INFO(" %s DWFW_CMPLT", __func__); + case SET_ISO_CFG: + AICBT_INFO("%s SET_ISO_CFG", __func__); + if (copy_from_user(&hdev->voice_setting, (__u16 *)arg, + sizeof(__u16))) { + AICBT_INFO(" voice settings err"); + } + AICBT_INFO(" voice settings = %d", hdev->voice_setting); + case GET_USB_INFO: + AICBT_INFO(" %s GET_USB_INFO", __func__); + set_bit(HCI_UP, &hdev->flags); + set_dlfw_state_value(0); + wake_up_interruptible(&bt_dlfw_wait); + return 1; + case RESET_CONTROLLER: + AICBT_INFO(" %s RESET_CONTROLLER", __func__); + return 1; + default: + AICBT_ERR("%s:Failed with wrong Cmd:%d", __func__, cmd); + goto failed; + } +failed: + set_dlfw_state_value(0); + wake_up_interruptible(&bt_dlfw_wait); + return ret; +} + +typedef u32 compat_uptr_t; +static inline void __user *compat_ptr(compat_uptr_t uptr) +{ + return (void __user *)(unsigned long)uptr; +} + +#ifdef CONFIG_COMPAT +static long compat_btchr_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + return btchr_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#endif +static const struct file_operations bt_chrdev_ops = { + .open = btchr_open, + .release = btchr_close, + .read = btchr_read, + .write = btchr_write, + .poll = btchr_poll, + .unlocked_ioctl = btchr_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = compat_btchr_ioctl, +#endif +}; + +int btchr_init(void) +{ + int res = 0; + struct device *dev; + + AICBT_INFO("Register sdio char device interface for BT driver"); + /* + * btchr mutex is used to sync between + * 1) downloading patch and opening bt char driver + * 2) the file operations of bt char driver + */ + mutex_init(&btchr_mutex); + + skb_queue_head_init(&btchr_readq); + init_waitqueue_head(&btchr_read_wait); + init_waitqueue_head(&bt_dlfw_wait); + + bt_char_class = class_create(THIS_MODULE, BT_CHAR_DEVICE_NAME); + if (IS_ERR(bt_char_class)) { + AICBT_ERR("Failed to create bt char class"); + return PTR_ERR(bt_char_class); + } + + res = alloc_chrdev_region(&bt_devid, 0, 1, BT_CHAR_DEVICE_NAME); + if (res < 0) { + AICBT_ERR("Failed to allocate bt char device"); + goto err_alloc; + } + + dev = + device_create(bt_char_class, NULL, bt_devid, NULL, BT_CHAR_DEVICE_NAME); + if (IS_ERR(dev)) { + AICBT_ERR("Failed to create bt char device"); + res = PTR_ERR(dev); + goto err_create; + } + + cdev_init(&bt_char_dev, &bt_chrdev_ops); + res = cdev_add(&bt_char_dev, bt_devid, 1); + if (res < 0) { + AICBT_ERR("Failed to add bt char device"); + goto err_add; + } + + return 0; + +err_add: + device_destroy(bt_char_class, bt_devid); +err_create: + unregister_chrdev_region(bt_devid, 1); +err_alloc: + class_destroy(bt_char_class); + return res; +} + +void btchr_exit(void) +{ + AICBT_INFO("Unregister sdio char device interface for BT driver"); + + device_destroy(bt_char_class, bt_devid); + cdev_del(&bt_char_dev); + unregister_chrdev_region(bt_devid, 1); + class_destroy(bt_char_class); +} + +int hdev_init(void) +{ + struct hci_dev *hdev; + int err = 0; + + hdev = hci_alloc_dev(); + + err = hci_register_dev(hdev); + if (err < 0) { + hci_free_dev(hdev); + hdev = NULL; + return err; + } + + spin_lock_init(&queue_lock); + + return 0; +} + +void hdev_exit(void) +{ + struct hci_dev *hdev; + + hdev = ghdev; + hci_unregister_dev(hdev); + hci_free_dev(hdev); +} diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h b/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h new file mode 100644 index 0000000000000..81f0f05957566 --- /dev/null +++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h @@ -0,0 +1,472 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _AICWF_SDIO_BT_H_ +#define _AICWF_SDIO_BT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define CONFIG_BLUEDROID 1 /* bleuz 0, bluedroid 1 */ + +#define PRINT_CMD_EVENT 1 +#define PRINT_ACL_DATA 1 +#define PRINT_SCO_DATA 1 + +#define AICBT_DBG_FLAG 1 + +#if AICBT_DBG_FLAG +#define AICBT_DBG(fmt, arg...) pr_debug("aic_btsdio: " fmt "\n", ##arg) +#else +#define AICBT_DBG(fmt, arg...) +#endif + +#define AICBT_INFO(fmt, arg...) pr_info("aic_btsdio: " fmt "\n", ##arg) +#define AICBT_WARN(fmt, arg...) pr_warn("aic_btsdio: " fmt "\n", ##arg) +#define AICBT_ERR(fmt, arg...) pr_err("aic_btsdio: " fmt "\n", ##arg) + +#define GET_DRV_DATA(x) hci_get_drvdata(x) + +struct btusb_data { + struct hci_dev *hdev; + /* lock for btusb */ + spinlock_t lock; + + unsigned long flags; + + struct work_struct work; + struct work_struct waker; + + int tx_in_flight; + /* lock for tx */ + spinlock_t txlock; + +#if (CONFIG_BLUEDROID == 0) + /* lock for tx */ + spinlock_t rxlock; + struct sk_buff *evt_skb; + struct sk_buff *acl_skb; + struct sk_buff *sco_skb; +#endif + + __u8 cmdreq_type; + + unsigned int sco_num; + int isoc_altsetting; + int suspend_count; + u16 sco_handle; + +#if (CONFIG_BLUEDROID == 0) + int (*recv_bulk)(struct btusb_data *data, void *buffer, int count); +#endif + struct notifier_block pm_notifier; + struct notifier_block reboot_notifier; + +#ifdef CONFIG_SCO_OVER_HCI + AIC_sco_card_t *sco_snd; +#endif +}; + +#define QUEUE_SIZE 500 + +/* Reserv for core and drivers use */ +#define BT_SKB_RESERVE 8 + +/* BD Address */ +struct bdaddr_t { + __u8 b[6]; +} __packed; + +/* Skb helpers */ +struct bt_skb_cb { + __u8 pkt_type; + __u8 incoming; + __u16 expect; + __u16 tx_seq; + __u8 retries; + __u8 sar; + __u8 force_active; +}; + +#define bt_cb(skb) ((struct bt_skb_cb *)((skb)->cb)) + +#define HCI_MAX_ACL_SIZE 1024 +#define HCI_MAX_SCO_SIZE 255 +#define HCI_MAX_EVENT_SIZE 260 +#define HCI_MAX_FRAME_SIZE (HCI_MAX_ACL_SIZE + 4) + +/* HCI bus types */ +#define HCI_VIRTUAL 0 +#define HCI_USB 1 +#define HCI_PCCARD 2 +#define HCI_UART 3 +#define HCI_RS232 4 +#define HCI_PCI 5 +#define HCI_SDIO 6 + +/* HCI controller types */ +#define HCI_BREDR 0x00 +#define HCI_AMP 0x01 + +/* HCI device flags */ +enum { + HCI_UP, + HCI_INIT, + HCI_RUNNING, + + HCI_PSCAN, + HCI_ISCAN, + HCI_AUTH, + HCI_ENCRYPT, + HCI_INQUIRY, + + HCI_RAW, + + HCI_RESET, +}; + +/* + * BR/EDR and/or LE controller flags: the flags defined here should represent + * states from the controller. + */ +enum { + HCI_SETUP, + HCI_AUTO_OFF, + HCI_MGMT, + HCI_PAIRABLE, + HCI_SERVICE_CACHE, + HCI_LINK_KEYS, + HCI_DEBUG_KEYS, + HCI_UNREGISTER, + + HCI_LE_SCAN, + HCI_SSP_ENABLED, + HCI_HS_ENABLED, + HCI_LE_ENABLED, + HCI_CONNECTABLE, + HCI_DISCOVERABLE, + HCI_LINK_SECURITY, + HCI_PENDING_CLASS, +}; + +/* HCI data types */ +#define HCI_COMMAND_PKT 0x01 +#define HCI_ACLDATA_PKT 0x02 +#define HCI_SCODATA_PKT 0x03 +#define HCI_EVENT_PKT 0x04 +#define HCI_VENDOR_PKT 0xff + +#define HCI_MAX_NAME_LENGTH 248 +#define HCI_MAX_EIR_LENGTH 240 + +#define HCI_OP_READ_LOCAL_VERSION 0x1001 +struct hci_rp_read_local_version { + __u8 status; + __u8 hci_ver; + __le16 hci_rev; + __u8 lmp_ver; + __le16 manufacturer; + __le16 lmp_subver; +} __packed; + +#define HCI_EV_CMD_COMPLETE 0x0e +struct hci_ev_cmd_complete { + __u8 ncmd; + __le16 opcode; +} __packed; + +/* ---- HCI Packet structures ---- */ +#define HCI_COMMAND_HDR_SIZE 3 +#define HCI_EVENT_HDR_SIZE 2 +#define HCI_ACL_HDR_SIZE 4 +#define HCI_SCO_HDR_SIZE 3 + +struct hci_command_hdr { + __le16 opcode; /* OCF & OGF */ + __u8 plen; +} __packed; + +struct hci_event_hdr { + __u8 evt; + __u8 plen; +} __packed; + +struct hci_acl_hdr { + __le16 handle; /* Handle & Flags(PB, BC) */ + __le16 dlen; +} __packed; + +struct hci_sco_hdr { + __le16 handle; + __u8 dlen; +} __packed; + +static inline struct hci_event_hdr *hci_event_hdr(const struct sk_buff *skb) +{ + return (struct hci_event_hdr *)skb->data; +} + +static inline struct hci_acl_hdr *hci_acl_hdr(const struct sk_buff *skb) +{ + return (struct hci_acl_hdr *)skb->data; +} + +static inline struct hci_sco_hdr *hci_sco_hdr(const struct sk_buff *skb) +{ + return (struct hci_sco_hdr *)skb->data; +} + +/* ---- HCI Ioctl requests structures ---- */ +struct hci_dev_stats { + __u32 err_rx; + __u32 err_tx; + __u32 cmd_tx; + __u32 evt_rx; + __u32 acl_tx; + __u32 acl_rx; + __u32 sco_tx; + __u32 sco_rx; + __u32 byte_rx; + __u32 byte_tx; +}; + +struct hci_conn_hash { + struct list_head list; + unsigned int acl_num; + unsigned int sco_num; + unsigned int le_num; +}; + +#define HCI_MAX_SHORT_NAME_LENGTH 10 + +#define NUM_REASSEMBLY 4 +struct hci_dev { + /* mutex for hic dev */ + struct mutex lock; + + char name[8]; + unsigned long flags; + __u16 id; + __u8 bus; + __u8 dev_type; + + struct sk_buff *reassembly[NUM_REASSEMBLY]; + + struct hci_conn_hash conn_hash; + + struct hci_dev_stats stat; + + atomic_t promisc; + + struct device *parent; + struct device dev; + + unsigned long dev_flags; + + int (*open)(struct hci_dev *hdev); + int (*close)(struct hci_dev *hdev); + int (*flush)(struct hci_dev *hdev); + int (*send)(struct sk_buff *skb); + __u16 voice_setting; + void (*notify)(struct hci_dev *hdev, unsigned int evt); + int (*ioctl)(struct hci_dev *hdev, unsigned int cmd, unsigned long arg); + u8 *align_data; +}; + +static inline void *hci_get_drvdata(struct hci_dev *hdev) +{ + return dev_get_drvdata(&hdev->dev); +} + +static inline void hci_set_drvdata(struct hci_dev *hdev, void *data) +{ + dev_set_drvdata(&hdev->dev, data); +} + +#define SET_HCIDEV_DEV(hdev, pdev) ((hdev)->parent = (pdev)) + +/* ---- HCI Packet structures ---- */ +#define HCI_COMMAND_HDR_SIZE 3 +#define HCI_EVENT_HDR_SIZE 2 +#define HCI_ACL_HDR_SIZE 4 +#define HCI_SCO_HDR_SIZE 3 + +/* ----- HCI Commands ---- */ +#define HCI_OP_INQUIRY 0x0401 +#define HCI_OP_INQUIRY_CANCEL 0x0402 +#define HCI_OP_EXIT_PERIODIC_INQ 0x0404 +#define HCI_OP_CREATE_CONN 0x0405 +#define HCI_OP_DISCONNECT 0x0406 +#define HCI_OP_ADD_SCO 0x0407 +#define HCI_OP_CREATE_CONN_CANCEL 0x0408 +#define HCI_OP_ACCEPT_CONN_REQ 0x0409 +#define HCI_OP_REJECT_CONN_REQ 0x040a +#define HCI_OP_LINK_KEY_REPLY 0x040b +#define HCI_OP_LINK_KEY_NEG_REPLY 0x040c +#define HCI_OP_PIN_CODE_REPLY 0x040d +#define HCI_OP_PIN_CODE_NEG_REPLY 0x040e +#define HCI_OP_CHANGE_CONN_PTYPE 0x040f +#define HCI_OP_AUTH_REQUESTED 0x0411 +#define HCI_OP_SET_CONN_ENCRYPT 0x0413 +#define HCI_OP_CHANGE_CONN_LINK_KEY 0x0415 +#define HCI_OP_REMOTE_NAME_REQ 0x0419 +#define HCI_OP_REMOTE_NAME_REQ_CANCEL 0x041a +#define HCI_OP_READ_REMOTE_FEATURES 0x041b +#define HCI_OP_READ_REMOTE_EXT_FEATURES 0x041c +#define HCI_OP_READ_REMOTE_VERSION 0x041d +#define HCI_OP_SETUP_SYNC_CONN 0x0428 +#define HCI_OP_ACCEPT_SYNC_CONN_REQ 0x0429 +#define HCI_OP_REJECT_SYNC_CONN_REQ 0x042a +#define HCI_OP_SNIFF_MODE 0x0803 +#define HCI_OP_EXIT_SNIFF_MODE 0x0804 +#define HCI_OP_ROLE_DISCOVERY 0x0809 +#define HCI_OP_SWITCH_ROLE 0x080b +#define HCI_OP_READ_LINK_POLICY 0x080c +#define HCI_OP_WRITE_LINK_POLICY 0x080d +#define HCI_OP_READ_DEF_LINK_POLICY 0x080e +#define HCI_OP_WRITE_DEF_LINK_POLICY 0x080f +#define HCI_OP_SNIFF_SUBRATE 0x0811 +#define HCI_OP_Write_Link_Policy_Settings 0x080d +#define HCI_OP_SET_EVENT_MASK 0x0c01 +#define HCI_OP_RESET 0x0c03 +#define HCI_OP_SET_EVENT_FLT 0x0c05 +#define HCI_OP_Write_Extended_Inquiry_Response 0x0c52 +#define HCI_OP_Write_Simple_Pairing_Mode 0x0c56 +#define HCI_OP_Read_Buffer_Size 0x1005 +#define HCI_OP_Host_Buffer_Size 0x0c33 +#define HCI_OP_Read_Local_Version_Information 0x1001 +#define HCI_OP_Read_BD_ADDR 0x1009 +#define HCI_OP_Read_Local_Supported_Commands 0x1002 +#define HCI_OP_Write_Scan_Enable 0x0c1a +#define HCI_OP_Write_Current_IAC_LAP 0x0c3a +#define HCI_OP_Write_Inquiry_Scan_Activity 0x0c1e +#define HCI_OP_Write_Class_of_Device 0x0c24 +#define HCI_OP_LE_Rand 0x2018 +#define HCI_OP_LE_Set_Random_Address 0x2005 +#define HCI_OP_LE_Set_Extended_Scan_Enable 0x2042 +#define HCI_OP_LE_Set_Extended_Scan_Parameters 0x2041 +#define HCI_OP_Set_Event_Filter 0x0c05 +#define HCI_OP_Write_Voice_Setting 0x0c26 +#define HCI_OP_Change_Local_Name 0x0c13 +#define HCI_OP_Read_Local_Name 0x0c14 +#define HCI_OP_Wirte_Page_Timeout 0x0c18 +#define HCI_OP_LE_Clear_Resolving_List 0x0c29 +#define HCI_OP_LE_Set_Addres_Resolution_Enable_Command 0x0c2e +#define HCI_OP_Write_Inquiry_mode 0x0c45 +#define HCI_OP_Write_Page_Scan_Type 0x0c47 +#define HCI_OP_Write_Inquiry_Scan_Type 0x0c43 + +#define HCI_OP_Delete_Stored_Link_Key 0x0c12 +#define HCI_OP_LE_Read_Local_Resolvable_Address 0x202d +#define HCI_OP_LE_Extended_Create_Connection 0x2043 +#define HCI_OP_Read_Remote_Version_Information 0x041d +#define HCI_OP_LE_Start_Encryption 0x2019 +#define HCI_OP_LE_Add_Device_to_Resolving_List 0x2027 +#define HCI_OP_LE_Set_Privacy_Mode 0x204e +#define HCI_OP_LE_Connection_Update 0x2013 + +/* ----- HCI events---- */ +#define HCI_OP_DISCONNECT 0x0406 +#define HCI_EV_INQUIRY_COMPLETE 0x01 +#define HCI_EV_INQUIRY_RESULT 0x02 +#define HCI_EV_CONN_COMPLETE 0x03 +#define HCI_EV_CONN_REQUEST 0x04 +#define HCI_EV_DISCONN_COMPLETE 0x05 +#define HCI_EV_AUTH_COMPLETE 0x06 +#define HCI_EV_REMOTE_NAME 0x07 +#define HCI_EV_ENCRYPT_CHANGE 0x08 +#define HCI_EV_CHANGE_LINK_KEY_COMPLETE 0x09 + +#define HCI_EV_REMOTE_FEATURES 0x0b +#define HCI_EV_REMOTE_VERSION 0x0c +#define HCI_EV_QOS_SETUP_COMPLETE 0x0d +#define HCI_EV_CMD_COMPLETE 0x0e +#define HCI_EV_CMD_STATUS 0x0f + +#define HCI_EV_ROLE_CHANGE 0x12 +#define HCI_EV_NUM_COMP_PKTS 0x13 +#define HCI_EV_MODE_CHANGE 0x14 +#define HCI_EV_PIN_CODE_REQ 0x16 +#define HCI_EV_LINK_KEY_REQ 0x17 +#define HCI_EV_LINK_KEY_NOTIFY 0x18 +#define HCI_EV_CLOCK_OFFSET 0x1c +#define HCI_EV_PKT_TYPE_CHANGE 0x1d +#define HCI_EV_PSCAN_REP_MODE 0x20 + +#define HCI_EV_INQUIRY_RESULT_WITH_RSSI 0x22 +#define HCI_EV_REMOTE_EXT_FEATURES 0x23 +#define HCI_EV_SYNC_CONN_COMPLETE 0x2c +#define HCI_EV_SYNC_CONN_CHANGED 0x2d +#define HCI_EV_SNIFF_SUBRATE 0x2e +#define HCI_EV_EXTENDED_INQUIRY_RESULT 0x2f +#define HCI_EV_IO_CAPA_REQUEST 0x31 +#define HCI_EV_SIMPLE_PAIR_COMPLETE 0x36 +#define HCI_EV_REMOTE_HOST_FEATURES 0x3d +#define HCI_EV_LE_Meta 0x3e + +/* ULP Event sub code */ +#define HCI_BLE_CONN_COMPLETE_EVT 0x01 +#define HCI_BLE_ADV_PKT_RPT_EVT 0x02 +#define HCI_BLE_LL_CONN_PARAM_UPD_EVT 0x03 +#define HCI_BLE_READ_REMOTE_FEAT_CMPL_EVT 0x04 +#define HCI_BLE_LTK_REQ_EVT 0x05 +#define HCI_BLE_RC_PARAM_REQ_EVT 0x06 +#define HCI_BLE_DATA_LENGTH_CHANGE_EVT 0x07 +#define HCI_BLE_ENHANCED_CONN_COMPLETE_EVT 0x0a +#define HCI_BLE_DIRECT_ADV_EVT 0x0b +#define HCI_BLE_PHY_UPDATE_COMPLETE_EVT 0x0c +#define HCI_LE_EXTENDED_ADVERTISING_REPORT_EVT 0x0D +#define HCI_BLE_PERIODIC_ADV_SYNC_EST_EVT 0x0E +#define HCI_BLE_PERIODIC_ADV_REPORT_EVT 0x0F +#define HCI_BLE_PERIODIC_ADV_SYNC_LOST_EVT 0x10 +#define HCI_BLE_SCAN_TIMEOUT_EVT 0x11 +#define HCI_LE_ADVERTISING_SET_TERMINATED_EVT 0x12 +#define HCI_BLE_SCAN_REQ_RX_EVT 0x13 +#define HCI_BLE_CIS_EST_EVT 0x19 +#define HCI_BLE_CIS_REQ_EVT 0x1a +#define HCI_BLE_CREATE_BIG_CPL_EVT 0x1b +#define HCI_BLE_TERM_BIG_CPL_EVT 0x1c +#define HCI_BLE_BIG_SYNC_EST_EVT 0x1d +#define HCI_BLE_BIG_SYNC_LOST_EVT 0x1e +#define HCI_BLE_REQ_PEER_SCA_CPL_EVT 0x1f + +#define HCI_VENDOR_SPECIFIC_EVT 0xFF /* Vendor specific events */ + +#define CONFIG_MAC_OFFSET_GEN_1_2 \ + (0x3C) // MAC's OFFSET in config/efuse for aic generation 1~2 bluetooth chip +#define CONFIG_MAC_OFFSET_GEN_3PLUS \ + (0x44) // MAC's OFFSET in config/efuse for aic generation 3+ bluetooth chip + +// Define ioctl cmd the same as HCIDEVUP in the kernel +#define DOWN_FW_CFG _IOW('E', 176, int) +#define SET_ISO_CFG _IOW('E', 177, int) +#define RESET_CONTROLLER _IOW('E', 178, int) +#define DWFW_CMPLT _IOW('E', 179, int) + +#define GET_USB_INFO _IOR('E', 180, int) + +void bt_data_dump(char *tag, void *data, unsigned long len); +int aic_enqueue(struct sk_buff *skb); +int aic_queue_cnt(void); +int bt_sdio_recv(u8 *data, u32 data_len); + +int btchr_init(void); +void btchr_exit(void); +int hdev_init(void); +void hdev_exit(void); + +struct hci_dev *hci_dev_get(int index); +int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count); + +#endif //_AICWF_SDIO_BT_H_ diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c b/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c new file mode 100644 index 0000000000000..7e47a1529a872 --- /dev/null +++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c @@ -0,0 +1,1967 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + ****************************************************************************** + * + * Copyright (C) 2020 AIC semiconductor. + * + * @brief private command definition + * + ****************************************************************************** + */ + +#include "aic_priv_cmd.h" +#include "aicwf_genl.h" +#include "aicwf_sdio.h" +#include "rwnx_debugfs.h" +#include "rwnx_defs.h" +#include "rwnx_main.h" +#include "rwnx_mod_params.h" +#include "rwnx_msg_tx.h" +#include "rwnx_platform.h" +#include +#include +#include +#ifdef CONFIG_AIC8800_POWER_LIMIT +#include "aicwf_compat_8800d80.h" +#endif + +static void print_help(const char *cmd); +struct dbg_rftest_cmd_cfm cfm = {{ + 0, +}}; + +struct cmd_ef_usrdata { + u8_l func; + u8_l cnt; + u8_l reserved[2]; + u32_l usrdata[3]; +}; + +#define CMD_MAXARGS 224 + +static int parse_line(char *line, char *argv[]) +{ + int nargs = 0; + + while (nargs < CMD_MAXARGS) { + /* skip any white space */ + while ((*line == ' ') || (*line == '\t')) + ++line; + + if (*line == '\0') { /* end of line, no more args */ + argv[nargs] = 0; + return nargs; + } + + /* Argument include space should be bracketed by quotation mark */ + if (*line == '\"') { + /* Skip quotation mark */ + line++; + + /* Begin of argument string */ + argv[nargs++] = line; + + /* Until end of argument */ + while (*line && (*line != '\"')) + ++line; + } else { + argv[nargs++] = line; /* begin of argument string */ + + /* find end of string */ + while (*line && (*line != ' ') && (*line != '\t')) + ++line; + } + + if (*line == '\0') { /* end of line, no more args */ + argv[nargs] = 0; + return nargs; + } + + *line++ = '\0'; /* terminate current arg */ + } + + pr_info("** Too many args (max. %d) **\n", CMD_MAXARGS); + + return nargs; +} + +unsigned int command_strtoul(const char *cp, char **endp, unsigned int base) +{ + unsigned int result = 0, value, is_neg = 0; + + if (*cp == '0') { + cp++; + if ((*cp == 'x') && isxdigit(cp[1])) { + base = 16; + cp++; + } + if (!base) + base = 8; + } + if (!base) + base = 10; + if (*cp == '-') { + is_neg = 1; + cp++; + } + while (isxdigit(*cp) && + (value = isdigit(*cp) ? *cp - '0' + : (islower(*cp) ? toupper(*cp) : *cp) - 'A' + + 10) < base) { + result = result * base + value; + cp++; + } + if (is_neg) + result = (unsigned int)((int)result * (-1)); + + if (endp) + *endp = (char *)cp; + return result; +} + +/* + * aic_priv_cmd handers. + */ +static int aic_priv_cmd_set_tx(struct rwnx_hw *rwnx_hw, int argc, char *argv[], + char *command) +{ + u8_l enable = 0; + struct cmd_rf_settx settx_param; +#ifdef CONFIG_AIC8800_POWER_LIMIT + s8 max_pwr; + struct txpwr_loss_conf txpwr_loss_tmp; + struct txpwr_loss_conf *txpwr_loss; + + txpwr_loss = &txpwr_loss_tmp; +#endif + + if (argc < 6) + return -EINVAL; + + settx_param.chan = command_strtoul(argv[1], NULL, 10); + settx_param.bw = command_strtoul(argv[2], NULL, 10); + settx_param.mode = command_strtoul(argv[3], NULL, 10); + settx_param.rate = command_strtoul(argv[4], NULL, 10); + settx_param.length = command_strtoul(argv[5], NULL, 10); + if (argc > 6) + settx_param.tx_intv_us = command_strtoul(argv[6], NULL, 10); + else + settx_param.tx_intv_us = 10000; // set default val 10ms + settx_param.max_pwr = POWER_LEVEL_INVALID_VAL; + AICWFDBG(LOGINFO, "txparam:%d,%d,%d,%d,%d,%d\n", settx_param.chan, + settx_param.bw, settx_param.mode, settx_param.rate, + settx_param.length, settx_param.tx_intv_us); + +#ifdef CONFIG_AIC8800_POWER_LIMIT + txpwr_loss = &txpwr_loss_tmp; + get_userconfig_txpwr_loss(txpwr_loss); + if (txpwr_loss->loss_enable_2g4 == 1) + AICWFDBG(LOGINFO, "%s:loss_value_2g4: %d\r\n", __func__, + txpwr_loss->loss_value_2g4); + if (txpwr_loss->loss_enable_5g == 1) + AICWFDBG(LOGINFO, "%s:loss_value_5g: %d\r\n", __func__, + txpwr_loss->loss_value_5g); + max_pwr = get_powerlimit_by_chnum(settx_param.chan, &enable); + if (settx_param.chan >= 36) { + if (txpwr_loss->loss_enable_5g == 1) + max_pwr -= txpwr_loss->loss_value_5g; + } else { + if (txpwr_loss->loss_enable_2g4 == 1) + max_pwr -= txpwr_loss->loss_value_2g4; + } + if (enable) + settx_param.max_pwr = max_pwr; + AICWFDBG(LOGINFO, "max_pwr:%d\n", settx_param.max_pwr); +#endif + + rwnx_send_rftest_req(rwnx_hw, SET_TX, sizeof(struct cmd_rf_settx), + (u8_l *)&settx_param, NULL); + return 0; +} + +static int aic_priv_cmd_set_txstop(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + rwnx_send_rftest_req(rwnx_hw, SET_TXSTOP, 0, NULL, NULL); + return 0; +} + +static int aic_priv_cmd_set_rx(struct rwnx_hw *rwnx_hw, int argc, char *argv[], + char *command) +{ + struct cmd_rf_rx setrx_param; + + if (argc < 3) + return -EINVAL; + setrx_param.chan = command_strtoul(argv[1], NULL, 10); + setrx_param.bw = command_strtoul(argv[2], NULL, 10); + rwnx_send_rftest_req(rwnx_hw, SET_RX, sizeof(struct cmd_rf_rx), + (u8_l *)&setrx_param, NULL); + return 0; +} + +static int aic_priv_cmd_get_rx_result(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + rwnx_send_rftest_req(rwnx_hw, GET_RX_RESULT, 0, NULL, &cfm); + memcpy(command, &cfm.rftest_result[0], 8); + return 8; +} + +static int aic_priv_cmd_set_rxstop(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + rwnx_send_rftest_req(rwnx_hw, SET_RXSTOP, 0, NULL, NULL); + return 0; +} + +static int aic_priv_cmd_set_tx_tone(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + u8_l buf[2]; + s8_l freq_ = 0; + + AICWFDBG(LOGINFO, "%s argc:%d\n", argv[0], argc); + if (argc == 2 || argc == 3) { + AICWFDBG(LOGINFO, "argv 1:%s\n", argv[1]); + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (argc == 3) { + AICWFDBG(LOGINFO, "argv 2:%s\n", argv[2]); + freq_ = (u8_l)command_strtoul(argv[2], NULL, 10); + } else { + freq_ = 0; + }; + buf[0] = func; + buf[1] = (u8_l)freq_; + rwnx_send_rftest_req(rwnx_hw, SET_TXTONE, argc - 1, buf, NULL); + } else { + return -EINVAL; + } + return 0; +} + +static int aic_priv_cmd_set_rx_meter(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + s8_l freq = 0; + + freq = (int)command_strtoul(argv[1], NULL, 10); + rwnx_send_rftest_req(rwnx_hw, SET_RX_METER, sizeof(freq), (u8_l *)&freq, + NULL); + return 0; +} + +static int aic_priv_cmd_set_set_power(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + return aic_chip_priv_cmd_set_power(rwnx_hw, argc, argv, command); +} + +static int aic_priv_cmd_set_xtal_cap(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l xtal_cap; + + if (argc < 2) + return -EINVAL; + + xtal_cap = command_strtoul(argv[1], NULL, 10); + AICWFDBG(LOGINFO, "xtal_cap =%x\r\n", xtal_cap); + rwnx_send_rftest_req(rwnx_hw, SET_XTAL_CAP, sizeof(xtal_cap), + (u8_l *)&xtal_cap, &cfm); + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_set_xtal_cap_fine(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l xtal_cap_fine; + + if (argc < 2) + return -EINVAL; + + xtal_cap_fine = command_strtoul(argv[1], NULL, 10); + AICWFDBG(LOGINFO, "xtal_cap_fine =%x\r\n", xtal_cap_fine); + rwnx_send_rftest_req(rwnx_hw, SET_XTAL_CAP_FINE, sizeof(xtal_cap_fine), + (u8_l *)&xtal_cap_fine, &cfm); + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_get_efuse_block(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + struct cmd_rf_getefuse getefuse_param; + + if (argc < 2) + return -EINVAL; + + getefuse_param.block = command_strtoul(argv[1], NULL, 10); + rwnx_send_rftest_req(rwnx_hw, GET_EFUSE_BLOCK, sizeof(struct cmd_rf_getefuse), + (u8_l *)&getefuse_param, &cfm); + AICWFDBG(LOGINFO, "get val=%x\r\n", cfm.rftest_result[0]); + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_set_freq_cal(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + struct cmd_rf_setfreq cmd_setfreq; + + if (argc < 2) + return -EINVAL; + + cmd_setfreq.val = command_strtoul(argv[1], NULL, 16); + AICWFDBG(LOGINFO, "param:%x\r\n", cmd_setfreq.val); + rwnx_send_rftest_req(rwnx_hw, SET_FREQ_CAL, sizeof(struct cmd_rf_setfreq), + (u8_l *)&cmd_setfreq, &cfm); + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_set_freq_cal_fine(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + struct cmd_rf_setfreq cmd_setfreq; + + if (argc < 2) + return -EINVAL; + + cmd_setfreq.val = command_strtoul(argv[1], NULL, 16); + AICWFDBG(LOGINFO, "param:%x\r\n", cmd_setfreq.val); + rwnx_send_rftest_req(rwnx_hw, SET_FREQ_CAL_FINE, sizeof(struct cmd_rf_setfreq), + (u8_l *)&cmd_setfreq, &cfm); + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + aic_chip_priv_cmd_get_freq_cal(rwnx_hw, argc, argv, command); + return 4; +} + +static int aic_priv_cmd_set_mac_addr(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l mac_addr[6]; + + if (argc < 7) + return -EINVAL; + + mac_addr[5] = command_strtoul(argv[1], NULL, 16); + mac_addr[4] = command_strtoul(argv[2], NULL, 16); + mac_addr[3] = command_strtoul(argv[3], NULL, 16); + mac_addr[2] = command_strtoul(argv[4], NULL, 16); + mac_addr[1] = command_strtoul(argv[5], NULL, 16); + mac_addr[0] = command_strtoul(argv[6], NULL, 16); + AICWFDBG(LOGINFO, "set macaddr:%x,%x,%x,%x,%x,%x\n", mac_addr[5], + mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]); + rwnx_send_rftest_req(rwnx_hw, SET_MAC_ADDR, sizeof(mac_addr), + (u8_l *)&mac_addr, NULL); + return 0; +} + +static int aic_priv_cmd_get_mac_addr(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + aic_chip_priv_cmd_get_mac_addr(rwnx_hw, argc, argv, command); + return 8; +} + +static int aic_priv_cmd_set_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l mac_addr[6]; + + if (argc < 7) + return -EINVAL; + + mac_addr[5] = command_strtoul(argv[1], NULL, 16); + mac_addr[4] = command_strtoul(argv[2], NULL, 16); + mac_addr[3] = command_strtoul(argv[3], NULL, 16); + mac_addr[2] = command_strtoul(argv[4], NULL, 16); + mac_addr[1] = command_strtoul(argv[5], NULL, 16); + mac_addr[0] = command_strtoul(argv[6], NULL, 16); + AICWFDBG(LOGINFO, "set bt macaddr:%x,%x,%x,%x,%x,%x\n", mac_addr[5], + mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]); + rwnx_send_rftest_req(rwnx_hw, SET_BT_MAC_ADDR, sizeof(mac_addr), + (u8_l *)&mac_addr, NULL); + return 0; +} + +static int aic_priv_cmd_get_bt_mac_addr(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + aic_chip_priv_cmd_get_bt_mac_addr(rwnx_hw, argc, argv, command); + return 8; +} + +static int aic_priv_cmd_set_vendor_info(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + return aic_chip_priv_cmd_set_vendor_info(rwnx_hw, argc, argv, command); +} + +static int aic_priv_cmd_get_vendor_info(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + return aic_chip_priv_cmd_get_vendor_info(rwnx_hw, argc, argv, command); +} + +static int aic_priv_cmd_rdwr_pwrmm(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + if (argc <= 1) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_PWRMM, 0, NULL, &cfm); + } else { // write + u8_l pwrmm = (u8_l)command_strtoul(argv[1], NULL, 16); + + pwrmm = (pwrmm) ? 1 : 0; + AICWFDBG(LOGINFO, "set pwrmm = %x\r\n", pwrmm); + rwnx_send_rftest_req(rwnx_hw, RDWR_PWRMM, sizeof(pwrmm), (u8_l *)&pwrmm, + &cfm); + } + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_rdwr_pwridx(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (!rwnx_hw->chip_ops->support_priv_cmd_rdwr_pwridx) { + AICWFDBG(LOGERROR, "unsupported cmd\n"); + return -EINVAL; + } + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_PWRIDX, 0, NULL, &cfm); + } else if (func <= 2) { // write 2.4g/5g pwr idx + if (argc > 3) { + u8_l type = (u8_l)command_strtoul(argv[2], NULL, 16); + u8_l pwridx = (u8_l)command_strtoul(argv[3], NULL, 10); + u8_l buf[3] = {func, type, pwridx}; + + AICWFDBG(LOGINFO, "set pwridx:[%x][%x]=%x\r\n", func, type, pwridx); + rwnx_send_rftest_req(rwnx_hw, RDWR_PWRIDX, sizeof(buf), buf, &cfm); + } else { + return -EINVAL; + } + } else { + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 9); + return 9; +} + +static int aic_priv_cmd_rdwr_pwrlvl(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (!rwnx_hw->chip_ops->support_priv_cmd_rdwr_pwrlvl) { + AICWFDBG(LOGERROR, "unsupported cmd\n"); + return -EINVAL; + } + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_PWRLVL, 0, NULL, &cfm); + } else if (func <= 2) { // write 2.4g/5g pwr lvl + if (argc > 4) { + u8_l grp = (u8_l)command_strtoul(argv[2], NULL, 16); + u8_l idx, size; + u8_l buf[14] = { + func, + grp, + }; + if (argc > 12) { // set all grp + AICWFDBG(LOGINFO, + "set pwrlvl %s:\n" + " [%x] =", + (func == 1) ? "2.4g" : "5g", grp); + if (grp == 1) { // TXPWR_LVL_GRP_11N_11AC + size = 10; + } else { + size = 12; + } + for (idx = 0; idx < size; idx++) { + s8_l pwrlvl = + (s8_l)command_strtoul(argv[3 + idx], NULL, 10); + buf[2 + idx] = (u8_l)pwrlvl; + if (idx && !(idx & 0x3)) + AICWFDBG(LOGINFO, " "); + AICWFDBG(LOGINFO, " %2d", pwrlvl); + } + AICWFDBG(LOGINFO, "\n"); + size += 2; + } else { // set grp[idx] + u8_l idx = (u8_l)command_strtoul(argv[3], NULL, 10); + s8_l pwrlvl = (s8_l)command_strtoul(argv[4], NULL, 10); + + buf[2] = idx; + buf[3] = (u8_l)pwrlvl; + size = 4; + AICWFDBG(LOGINFO, + "set pwrlvl %s:\n" + " [%x][%d] = %d\n", + (func == 1) ? "2.4g" : "5g", grp, idx, pwrlvl); + } + rwnx_send_rftest_req(rwnx_hw, RDWR_PWRLVL, size, buf, &cfm); + } else { + AICWFDBG(LOGERROR, "wrong args\n"); + return -EINVAL; + } + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + + memcpy(command, &cfm.rftest_result[0], (rwnx_hw->chip_ops->pwrlvl_result_copy_len) * 12); + return ((rwnx_hw->chip_ops->pwrlvl_result_copy_len) * 12); +} + +static int aic_priv_cmd_rdwr_pwrofst(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + return aic_chip_priv_cmd_rdwr_pwrofst(rwnx_hw, argc, argv, command); +} + +static int aic_priv_cmd_rdwr_pwrofstfine(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFSTFINE, 0, NULL, &cfm); + } else if (func <= 2) { // write 2.4g/5g pwr ofst + if (argc > 3) { + u8_l chgrp = (u8_l)command_strtoul(argv[2], NULL, 16); + s8_l pwrofst = (u8_l)command_strtoul(argv[3], NULL, 10); + u8_l buf[3] = {func, chgrp, (u8_l)pwrofst}; + + AICWFDBG(LOGINFO, "set pwrofstfine:[%x][%x]=%d\r\n", func, chgrp, + pwrofst); + rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFSTFINE, sizeof(buf), buf, + &cfm); + } else { + AICWFDBG(LOGERROR, "wrong args\n"); + return -EINVAL; + } + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 7); + return 7; +} + +static int aic_priv_cmd_rdwr_drvibit(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_DRVIBIT, 0, NULL, &cfm); + } else if (func == 1) { // write 2.4g pa drv_ibit + if (argc > 2) { + u8_l ibit = (u8_l)command_strtoul(argv[2], NULL, 16); + u8_l buf[2] = {func, ibit}; + + AICWFDBG(LOGINFO, "set drvibit:[%x]=%x\r\n", func, ibit); + rwnx_send_rftest_req(rwnx_hw, RDWR_DRVIBIT, sizeof(buf), buf, &cfm); + } else { + AICWFDBG(LOGERROR, "wrong args\n"); + return -EINVAL; + } + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 16); + return 16; +} + +static int aic_priv_cmd_rdwr_efuse_pwrofst(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + return aic_chip_priv_cmd_rdwr_efuse_pwrofst(rwnx_hw, argc, argv, command); +} + +static int aic_priv_cmd_rdwr_efuse_pwrofstfine(struct rwnx_hw *rwnx_hw, + int argc, char *argv[], + char *command) +{ + u8_l func = 0; + + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFSTFINE, 0, NULL, &cfm); + } else if (func <= 2) { // write 2.4g/5g pwr ofst + if (argc > 3) { + u8_l chgrp = (u8_l)command_strtoul(argv[2], NULL, 16); + s8_l pwrofst = (u8_l)command_strtoul(argv[3], NULL, 10); + u8_l buf[3] = {func, chgrp, (u8_l)pwrofst}; + + AICWFDBG(LOGINFO, "set pwrofstfine:[%x][%x]=%d\r\n", func, chgrp, + pwrofst); + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFSTFINE, sizeof(buf), + buf, &cfm); + } else { + AICWFDBG(LOGERROR, "wrong args\n"); + return -EINVAL; + } + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 7); + return 7; +} + +static int aic_priv_cmd_rdwr_efuse_drvibit(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_DRVIBIT, 0, NULL, &cfm); + } else if (func == 1) { // write 2.4g pa drv_ibit + if (argc > 2) { + u8_l ibit = (u8_l)command_strtoul(argv[2], NULL, 16); + u8_l buf[2] = {func, ibit}; + + AICWFDBG(LOGINFO, "set efuse drvibit:[%x]=%x\r\n", func, ibit); + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_DRVIBIT, sizeof(buf), buf, + &cfm); + } else { + AICWFDBG(LOGERROR, "wrong args\n"); + return -EINVAL; + } + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_rdwr_efuse_usrdata(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + struct cmd_ef_usrdata cmd_ef_usrdata; + + if (argc <= 1) { // read all + cmd_ef_usrdata.func = 0; + cmd_ef_usrdata.cnt = 3; + } else if (argc >= 2) { // read/write + cmd_ef_usrdata.func = (u8_l)command_strtoul(argv[1], NULL, 10); + cmd_ef_usrdata.cnt = (u8_l)command_strtoul(argv[2], NULL, 10); + if (cmd_ef_usrdata.func == 1) { + int idx; + + for (idx = 0; idx < cmd_ef_usrdata.cnt; idx++) { + cmd_ef_usrdata.usrdata[idx] = + (u32_l)command_strtoul(argv[3 + idx], NULL, 16); + } + } + } else { + AICWFDBG(LOGERROR, "wrong argc: %x\n", argc); + return -EINVAL; + } + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_USRDATA, sizeof(cmd_ef_usrdata), + (u8_l *)&cmd_ef_usrdata, &cfm); + memcpy(command, &cfm.rftest_result[0], 12); + return 12; +} + +static int aic_priv_cmd_rdwr_efuse_sdiocfg(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_SDIOCFG, 0, NULL, &cfm); + } else if (func == 1) { // write sdiocfg + if (argc > 2) { + u8_l ibit = (u8_l)command_strtoul(argv[2], NULL, 16); + u8_l buf[2] = {func, ibit}; + + AICWFDBG(LOGINFO, "set efuse sdiocfg:[%x]=%x\r\n", func, ibit); + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_SDIOCFG, sizeof(buf), buf, + &cfm); + } else { + AICWFDBG(LOGERROR, "wrong args\n"); + return -EINVAL; + } + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_rdwr_efuse_usbvidpid(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + AICWFDBG(LOGINFO, "read/write usb vid/pid into efuse\n"); + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 16); + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_USBVIDPID, 0, NULL, &cfm); + } else if (func == 1) { // write USB vid+pid + if (argc > 2) { + u32_l usb_id = (u32_l)command_strtoul(argv[2], NULL, 16); + u8_l buf[5] = {func, (u8_l)usb_id, (u8_l)(usb_id >> 8), + (u8_l)(usb_id >> 16), (u8_l)(usb_id >> 24)}; + AICWFDBG(LOGINFO, "set efuse usb vid/pid:[%x]=%x\r\n", func, + usb_id); + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_USBVIDPID, sizeof(buf), + buf, &cfm); + } else { + AICWFDBG(LOGERROR, "wrong args\n"); + return -EINVAL; + } + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 4); + return 4; +} + +static int aic_priv_cmd_rdwr_efuse_he_off(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + func = command_strtoul(argv[1], NULL, 10); + AICWFDBG(LOGINFO, "set he off: %d\n", func); + if (func == 1 || func == 0) { + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_HE_OFF, sizeof(func), + (u8_l *)&func, &cfm); + AICWFDBG(LOGINFO, "he_off cfm: %d\n", cfm.rftest_result[0]); + memcpy(command, &cfm.rftest_result[0], 4); + return 4; + } + return 0; +} + +static int aic_priv_cmd_set_cal_xtal(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + rwnx_send_rftest_req(rwnx_hw, SET_CAL_XTAL, 0, NULL, NULL); + return 0; +} + +static int aic_priv_cmd_get_cal_xtal_res(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + rwnx_send_rftest_req(rwnx_hw, GET_CAL_XTAL_RES, 0, NULL, &cfm); + memcpy(command, &cfm.rftest_result[0], 4); + AICWFDBG(LOGINFO, "cap=0x%x, cap_fine=0x%x\n", + cfm.rftest_result[0] & 0x0000ffff, + (cfm.rftest_result[0] >> 16) & 0x0000ffff); + return 4; +} + +static int aic_priv_cmd_set_cob_cal(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + struct cmd_rf_setcobcal setcob_cal; + + if (argc < 3) + return -EINVAL; + setcob_cal.dutid = command_strtoul(argv[1], NULL, 10); + setcob_cal.chip_num = command_strtoul(argv[2], NULL, 10); + setcob_cal.dis_xtal = command_strtoul(argv[3], NULL, 10); + rwnx_send_rftest_req(rwnx_hw, SET_COB_CAL, sizeof(struct cmd_rf_setcobcal), + (u8_l *)&setcob_cal, NULL); + return 0; +} + +static int aic_priv_cmd_get_cob_cal_res(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l state; + struct cob_result_ptr *cob_result_ptr; + + rwnx_send_rftest_req(rwnx_hw, GET_COB_CAL_RES, 0, NULL, &cfm); + state = (cfm.rftest_result[0] >> 16) & 0x000000ff; + if (!state) { + AICWFDBG(LOGINFO, "cap= 0x%x, cap_fine= 0x%x, freq_ofst= %d Hz\n", + cfm.rftest_result[0] & 0x000000ff, + (cfm.rftest_result[0] >> 8) & 0x000000ff, + cfm.rftest_result[1]); + cob_result_ptr = (struct cob_result_ptr *)&cfm.rftest_result[2]; + AICWFDBG(LOGINFO, + "golden_rcv_dut= %d , tx_rssi= %d dBm, snr = %d dB\ndut_rcv_godlden= %d , rx_rssi= %d dBm", + cob_result_ptr->golden_rcv_dut_num, + cob_result_ptr->rssi_static, cob_result_ptr->snr_static, + cob_result_ptr->dut_rcv_golden_num, + cob_result_ptr->dut_rssi_static); + memcpy(command, &cfm.rftest_result, 16); + return 16; + } + AICWFDBG(LOGERROR, "cob not idle\n"); + return -EINVAL; +} + +static int aic_priv_cmd_do_cob_test(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l state; + struct cmd_rf_setcobcal setcob_cal; + struct cob_result_ptr *cob_result_ptr; + + setcob_cal.dutid = 1; + setcob_cal.chip_num = 1; + setcob_cal.dis_xtal = 0; + if (argc > 1) + setcob_cal.dis_xtal = command_strtoul(argv[1], NULL, 10); + rwnx_send_rftest_req(rwnx_hw, SET_COB_CAL, sizeof(struct cmd_rf_setcobcal), + (u8_l *)&setcob_cal, NULL); + msleep(2000); + rwnx_send_rftest_req(rwnx_hw, GET_COB_CAL_RES, 0, NULL, &cfm); + state = (cfm.rftest_result[0] >> 16) & 0x000000ff; + if (!state) { + AICWFDBG(LOGINFO, "cap= 0x%x, cap_fine= 0x%x, freq_ofst= %d Hz\n", + cfm.rftest_result[0] & 0x000000ff, + (cfm.rftest_result[0] >> 8) & 0x000000ff, + cfm.rftest_result[1]); + cob_result_ptr = (struct cob_result_ptr *)&cfm.rftest_result[2]; + AICWFDBG(LOGINFO, + "golden_rcv_dut= %d , tx_rssi= %d dBm, snr = %d dB\ndut_rcv_godlden= %d , rx_rssi= %d dBm", + cob_result_ptr->golden_rcv_dut_num, + cob_result_ptr->rssi_static, cob_result_ptr->snr_static, + cob_result_ptr->dut_rcv_golden_num, + cob_result_ptr->dut_rssi_static); + memcpy(command, &cfm.rftest_result, 16); + return 16; + } + AICWFDBG(LOGERROR, "cob not idle\n"); + return -EINVAL; +} + +static int aic_priv_cmd_set_papr(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (argc > 1) { + func = command_strtoul(argv[1], NULL, 10); + AICWFDBG(LOGINFO, "papr %d\r\n", func); + rwnx_send_rftest_req(rwnx_hw, SET_PAPR, sizeof(func), (u8_l *)&func, + NULL); + } else { + return -EINVAL; + } + return 0; +} + +static int aic_priv_cmd_set_notch(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (argc > 1) { + func = command_strtoul(argv[1], NULL, 10); + AICWFDBG(LOGINFO, "notch %d\r\n", func); + rwnx_send_rftest_req(rwnx_hw, SET_NOTCH, sizeof(func), (u8_l *)&func, + NULL); + } else { + return -EINVAL; + } + return 0; +} + +static int aic_priv_cmd_set_srrc(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + + if (argc > 1) { + func = command_strtoul(argv[1], NULL, 10); + AICWFDBG(LOGINFO, "srrc %d\r\n", func); + rwnx_send_rftest_req(rwnx_hw, SET_SRRC, sizeof(func), (u8_l *)&func, + NULL); + } else { + return -EINVAL; + } + return 0; +} + +static int aic_priv_cmd_set_fss(struct rwnx_hw *rwnx_hw, int argc, char *argv[], + char *command) +{ + u8_l func = 0; + + if (argc > 1) { + func = command_strtoul(argv[1], NULL, 10); + AICWFDBG(LOGINFO, "fss %d\r\n", func); + rwnx_send_rftest_req(rwnx_hw, SET_FSS, sizeof(func), (u8_l *)&func, + NULL); + } else { + return -EINVAL; + } + return 0; +} + +static int aic_priv_cmd_set_usb_off(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + rwnx_send_rftest_req(rwnx_hw, SET_USB_OFF, 0, NULL, NULL); + return 0; +} + +static int aic_priv_cmd_set_pll_test(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0, tx_pwr = 0xc; + s8_l freq = 0; + + if (argc > 1) + func = command_strtoul(argv[1], NULL, 16); + if (argc > 3) { + freq = (s8_l)command_strtoul(argv[2], NULL, 10); + tx_pwr = command_strtoul(argv[3], NULL, 16); + } + if (func <= 1) { + u8_l buf[3] = {func, (u8_l)freq, tx_pwr}; + + AICWFDBG(LOGINFO, "set pll_test %d: freq=%d, tx_pwr=0x%x\n", func, freq, + tx_pwr); + rwnx_send_rftest_req(rwnx_hw, SET_PLL_TEST, sizeof(buf), buf, &cfm); + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + return 0; +} + +static int aic_priv_cmd_get_txpwr(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + s8_l power = 0; + + power = get_txpwr_max(power); + memcpy(command, &power, 1); + return 1; +} + +static int aic_priv_cmd_set_txpwr_loss(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + s8_l func; + + if (argc > 1) { + func = (s8_l)command_strtoul(argv[1], NULL, 10); + pr_info("set txpwr loss: %d\n", func); + if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->support_priv_cmd_set_txpwr_loss) { + set_txpwr_loss_ofst(func); + rwnx_send_txpwr_lvl_v3_req(g_rwnx_plat->sdiodev->rwnx_hw, 0); + } else { + AICWFDBG(LOGINFO, "error:don't support ,now only support D40 D80"); + } + } else { + pr_info("wrong args\n"); + return -EINVAL; + } + return 0; +} + +static int aic_priv_cmd_rdwr_pwradd2x(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + u8_l buf[2]; + s8 pwradd2x_in = 0; + + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 10); + + if (func > 0 && argc > 2) + pwradd2x_in = (int8_t)command_strtoul(argv[2], NULL, 10); + + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_PWRADD2X, 0, NULL, &cfm); + } else if (func == 1 || func == 2) { // write pwradd2x + AICWFDBG(LOGINFO, "set pwradd2x_%s %d\r\n", + (func == 1) ? "2g4" : "5g", pwradd2x_in); + if (pwradd2x_in < -15 || pwradd2x_in > 15) { + AICWFDBG(LOGERROR, + "wrong params %d, pwradd2x: -15 ~ 15\n", + pwradd2x_in); + return -EINVAL; + } + + buf[0] = func; + buf[1] = (u8_l)pwradd2x_in; + rwnx_send_rftest_req(rwnx_hw, RDWR_PWRADD2X, + sizeof(buf), buf, &cfm); + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 2); + return 2; +} + +static int aic_priv_cmd_rdwr_efuse_pwradd2x(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + u8_l buf[2]; + s8 pwradd2x_in = 0; + + if (argc > 1) + func = (u8_l)command_strtoul(argv[1], NULL, 10); + + if (func > 0 && argc > 2) + pwradd2x_in = (int8_t)command_strtoul(argv[2], NULL, 10); + + if (func == 0) { // read cur + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWRADD2X, 0, NULL, &cfm); + } else if (func == 1 || func == 2) { // write pwradd2x + AICWFDBG(LOGINFO, "set efuse pwradd2x_%s %d\r\n", + (func == 1) ? "2g4" : "5g", pwradd2x_in); + if (pwradd2x_in < -15 || pwradd2x_in > 15) { + AICWFDBG(LOGERROR, + "wrong params %d, pwradd2x: -15 ~ 15\n", + pwradd2x_in); + return -EINVAL; + } + + buf[0] = func; + buf[1] = (u8_l)pwradd2x_in; + rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWRADD2X, + sizeof(buf), buf, &cfm); + } else { + AICWFDBG(LOGERROR, "wrong func: %x\n", func); + return -EINVAL; + } + memcpy(command, &cfm.rftest_result[0], 3); + return 3; +} + +#ifdef CONFIG_AIC8800_AUTO_CUSTREG +static int aic_priv_cmd_country_set(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + int ret = 0; + + if (argc < 2) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + + AICWFDBG(LOGINFO, "cmd country_set: %s\n", argv[1]); + if (strncmp(argv[1], "AUTO", 4) == 0) { + rwnx_hw->ccode.auto_set = true; + rwnx_hw->ccode.ccode_set = false; + rwnx_hw->ccode.ccode_cnt = 0; + rwnx_hw->ccode.ccode_rssi = -100; + } else if (strncmp(argv[1], "MANUAL", 6) == 0) { + rwnx_hw->ccode.auto_set = false; + rwnx_hw->ccode.ccode_set = true; + } else { + rwnx_hw->ccode.ccode_set = true; + ret = regulatory_set_wiphy_regd(rwnx_hw->wiphy, + get_regdomain_from_rwnx_db(rwnx_hw->wiphy, + argv[1])); + memcpy(rwnx_hw->country_abbr, argv[1], 2); + rwnx_hw->ccode.ccode_cnt = 0; +#ifdef CONFIG_AIC8800_REGION_PW + rwnx_send_txpwr_lvl_v3_req(rwnx_hw, get_ccode_region(rwnx_hw->country_abbr)); +#endif +#ifdef CONFIG_AIC8800_POWER_LIMIT + aic_chip_powerlimit_load(rwnx_hw); + if (!rwnx_hw->testmode) + rwnx_send_me_chan_config_req(rwnx_hw); +#endif + } + return ret; +} + +static int aic_priv_cmd_country_get(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l buf[3]; + int bytes_written = 0; + + buf[0] = rwnx_hw->wiphy->regd->alpha2[0]; + buf[1] = rwnx_hw->wiphy->regd->alpha2[1]; + buf[2] = rwnx_hw->ccode.auto_set; + memcpy(command, &buf[0], 3); + bytes_written = 3; + AICWFDBG(LOGINFO, "cmd country_get: %c%c\n", command[0], command[1]); + + return bytes_written; +} +#endif + +#ifdef CONFIG_AIC8800_TEMP_CONTROL +static int aic_priv_cmd_temp_ctrl_sw(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + if (argc < 2) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + + if (command_strtoul(argv[1], NULL, 10) == 0) { + AICWFDBG(LOGINFO, "tp to off\n"); + rwnx_hw->sdiodev->tp_ctrl.on_off = false; + rwnx_hw->sdiodev->tp_ctrl.get_level = 0; + spin_lock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock); + rwnx_hw->sdiodev->tp_ctrl.tm_start = 0; + if (timer_pending(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer)) + //del_timer_sync(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer); + timer_delete_sync(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer); + spin_unlock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock); + } else if (command_strtoul(argv[1], NULL, 10) == 1) { + AICWFDBG(LOGINFO, "tp to on\n"); + rwnx_hw->sdiodev->tp_ctrl.on_off = true; + spin_lock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock); + rwnx_hw->sdiodev->tp_ctrl.tm_start = 1; + mod_timer(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer, + jiffies + msecs_to_jiffies(TEMP_GET_INTERVAL)); + spin_unlock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock); + } else { + AICWFDBG(LOGINFO, "tp err param\n"); + return -1; + } + + return 0; +} + +static int aic_priv_cmd_temp_sget(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + int bytes_written = 0; + s8 tp_res[4]; + + if (argc < 2) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + + func = (u8_l)command_strtoul(argv[1], NULL, 10); + if (func == 0) { // get + if (rwnx_hw->sdiodev->tp_ctrl.on_off) { // on + tp_res[0] = 1; + if (rwnx_hw->sdiodev->tp_ctrl.set_level == 0) + tp_res[1] = rwnx_hw->sdiodev->tp_ctrl.get_level; + else + tp_res[1] = rwnx_hw->sdiodev->tp_ctrl.set_level; + AICWFDBG(LOGINFO, "tp_get on-off: %d, ctrl-level: %d\n", tp_res[0], + tp_res[1]); + memcpy(command, &tp_res[0], 2); + bytes_written = 2; + } else { // off + tp_res[0] = 0; + AICWFDBG(LOGINFO, "tp_get on-off: %d\n", tp_res[0]); + memcpy(command, &tp_res[0], 1); + bytes_written = 1; + } + } else if (func == 1) { // set + if (!rwnx_hw->sdiodev->tp_ctrl.on_off) { + AICWFDBG(LOGINFO, "tp_set sw is off, return\n"); + tp_res[0] = 0; + memcpy(command, &tp_res[0], 1); + bytes_written = 1; + } else { + if (argc < 3) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + rwnx_hw->sdiodev->tp_ctrl.set_level = + command_strtoul(argv[2], NULL, 10); + if (rwnx_hw->sdiodev->tp_ctrl.set_level < 0 || + rwnx_hw->sdiodev->tp_ctrl.set_level > 2) { + AICWFDBG(LOGINFO, "set_level out of range\n"); + rwnx_hw->sdiodev->tp_ctrl.set_level = 0; + } + rwnx_hw->sdiodev->tp_ctrl.get_level = 0; + tp_res[0] = 1; + tp_res[1] = rwnx_hw->sdiodev->tp_ctrl.set_level; + AICWFDBG(LOGINFO, "tp_set ctrl-level: %d\n", + rwnx_hw->sdiodev->tp_ctrl.set_level); + memcpy(command, &tp_res[0], 2); + bytes_written = 2; + + if (rwnx_hw->sdiodev->tp_ctrl.set_level != 0) { + spin_lock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock); + rwnx_hw->sdiodev->tp_ctrl.tm_start = 0; + if (timer_pending(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer)) + //del_timer_sync(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer); + timer_delete_sync(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer); + spin_unlock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock); + } else if (rwnx_hw->sdiodev->tp_ctrl.set_level == 0) { + spin_lock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock); + rwnx_hw->sdiodev->tp_ctrl.tm_start = 1; + mod_timer(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer, + jiffies + msecs_to_jiffies(TEMP_GET_INTERVAL)); + spin_unlock_bh(&rwnx_hw->sdiodev->tp_ctrl.tm_lock); + } + } + } else { + AICWFDBG(LOGINFO, "tp command err\n"); + return -1; + } + + return bytes_written; +} + +static int aic_priv_cmd_set_tmr_intval(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + int bytes_written = 0; + + if (argc < 3) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + + func = (u8_l)command_strtoul(argv[1], NULL, 10); + if (func == 1) { + rwnx_hw->sdiodev->tp_ctrl.interval_t1 = + command_strtoul(argv[2], NULL, 10); + AICWFDBG(LOGDEBUG, "set tmr_intval_1: %d\n", + rwnx_hw->sdiodev->tp_ctrl.interval_t1); + memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.interval_t1, 4); + bytes_written = 4; + } else if (func == 2) { + rwnx_hw->sdiodev->tp_ctrl.interval_t2 = + command_strtoul(argv[2], NULL, 10); + AICWFDBG(LOGDEBUG, "set tmr_intval_2: %d\n", + rwnx_hw->sdiodev->tp_ctrl.interval_t2); + memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.interval_t2, 4); + bytes_written = 4; + } else { + AICWFDBG(LOGERROR, "%s command err\n", __func__); + return -1; + } + + return bytes_written; +} + +static int aic_priv_cmd_get_tmr_intval(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + int bytes_written = 0; + + if (argc < 2) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + func = (u8_l)command_strtoul(argv[1], NULL, 10); + if (func == 1) { + AICWFDBG(LOGDEBUG, "get tmr_intval_1: %d\n", + rwnx_hw->sdiodev->tp_ctrl.interval_t1); + memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.interval_t1, 4); + bytes_written = 4; + } else if (func == 2) { + AICWFDBG(LOGDEBUG, "get tmr_intval_1: %d\n", + rwnx_hw->sdiodev->tp_ctrl.interval_t2); + memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.interval_t2, 4); + bytes_written = 4; + } else { + AICWFDBG(LOGERROR, "%s command err\n", __func__); + return -1; + } + + return bytes_written; +} + +static int aic_priv_cmd_temp_get(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + int bytes_written = 0; + struct mm_set_vendor_swconfig_cfm tp_cfm; + + if (timer_pending(&rwnx_hw->sdiodev->tp_ctrl.tp_ctrl_timer)) { + if (jiffies_to_msecs(jiffies - rwnx_hw->started_jiffies) < 5000) { + AICWFDBG(LOGINFO, "tp_get temp_1: %d\n", rwnx_hw->temp); + memcpy(command, &rwnx_hw->temp, 1); + } else { + if (rwnx_send_get_temp_req(rwnx_hw, &tp_cfm)) + return -1; + AICWFDBG(LOGINFO, "tp_get temp_2: %d\n", + tp_cfm.temp_comp_get_cfm.degree); + rwnx_hw->sdiodev->tp_ctrl.cur_temp = + tp_cfm.temp_comp_get_cfm.degree; + memcpy(command, &tp_cfm.temp_comp_get_cfm.degree, 1); + } + } else { + if (rwnx_send_get_temp_req(rwnx_hw, &tp_cfm)) + return -1; + AICWFDBG(LOGINFO, "tp_get temp_3: %d\n", + tp_cfm.temp_comp_get_cfm.degree); + memcpy(command, &tp_cfm.temp_comp_get_cfm.degree, 1); + } + bytes_written = 1; + + return bytes_written; +} + +static int aic_priv_cmd_tp_thd_set(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + int bytes_written = 0; + + if (argc < 3) { + AICWFDBG(LOGERROR, "%s param err\n", __func__); + return -1; + } + func = (u8_l)command_strtoul(argv[1], NULL, 10); + + if (func == 1) { + rwnx_hw->sdiodev->tp_ctrl.tp_thd_1 = command_strtoul(argv[2], NULL, 10); + AICWFDBG(LOGINFO, "set tp_thd_1: %d\n", + rwnx_hw->sdiodev->tp_ctrl.tp_thd_1); + memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.tp_thd_1, 1); + bytes_written = 1; + } else if (func == 2) { + rwnx_hw->sdiodev->tp_ctrl.tp_thd_2 = command_strtoul(argv[2], NULL, 10); + AICWFDBG(LOGINFO, "set tp_thd_2: %d\n", + rwnx_hw->sdiodev->tp_ctrl.tp_thd_2); + memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.tp_thd_2, 1); + bytes_written = 1; + } else { + AICWFDBG(LOGERROR, "%s command err\n", __func__); + return -1; + } + return bytes_written; +} + +static int aic_priv_cmd_tp_thd_get(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + int bytes_written = 0; + + if (argc < 2) { + AICWFDBG(LOGERROR, "%s param err\n", __func__); + return -1; + } + func = (u8_l)command_strtoul(argv[1], NULL, 10); + + if (func == 1) { + AICWFDBG(LOGINFO, "get tp_thd_1: %d\n", + rwnx_hw->sdiodev->tp_ctrl.tp_thd_1); + memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.tp_thd_1, 1); + bytes_written = 1; + } else if (func == 2) { + AICWFDBG(LOGINFO, "set tp_thd_2: %d\n", + rwnx_hw->sdiodev->tp_ctrl.tp_thd_2); + memcpy(command, &rwnx_hw->sdiodev->tp_ctrl.tp_thd_2, 1); + bytes_written = 1; + } else { + AICWFDBG(LOGERROR, "%s command err\n", __func__); + return -1; + } + return bytes_written; +} + +#endif +#ifdef CONFIG_AIC8800_GENL +static int aic_priv_cmd_pktft_set(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + int ret = 0; + + if (argc < 6) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + ret = cmd_pktfilter_set(rwnx_hw, &argv[1], argc - 1, NULL, NULL); + return ret; +} + +static int aic_priv_cmd_pktft_del(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + int ret = 0; + + if (argc < 2) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + ret = cmd_pktfilter_del(rwnx_hw, &argv[1], argc - 1, NULL, NULL); + return ret; +} + +static int aic_priv_cmd_pktft_delall(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + int ret = 0; + + ret = cmd_pktfilter_delall(rwnx_hw, argv, 0, NULL, NULL); + return ret; +} + +static int aic_priv_cmd_pktft_enable(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + int ret; + + if (argc < 3) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + ret = cmd_pktfilter_enable(rwnx_hw, &argv[1], argc - 1, NULL, NULL); + return ret; +} + +static int aic_priv_cmd_pktft_list(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + unsigned long r_len = 0; + int bytes_written = 0; + + if (argc < 2) { + AICWFDBG(LOGINFO, "%s param err\n", __func__); + return -1; + } + cmd_pktfilter_list(rwnx_hw, &argv[1], argc - 1, command, &r_len); + bytes_written = r_len; + + return bytes_written; +} +#endif + +static int aic_priv_cmd_set_suspend(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + u8_l func = 0; + s8_l err = -1; + int ret = 0; + + if (argc < 2 || rwnx_hw->testmode != 0) { + AICWFDBG(LOGERROR, "%s param err or in rf_test mode\n", __func__); + return -1; + } + func = (u8_l)command_strtoul(argv[1], NULL, 10); + +#ifndef CONFIG_AIC8800_AUTO_POWERSAVE + if (func == 0) { + AICWFDBG(LOGINFO, "priv_cmd suspend 0\n"); + ret = rwnx_send_me_set_lp_level(rwnx_hw, 0, 1); + } else if (func == 1) { + AICWFDBG(LOGINFO, "priv_cmd suspend 1\n"); + ret = rwnx_send_me_set_lp_level(rwnx_hw, 1, 0); + if (rwnx_hw->scan_request && rwnx_hw->scanning) { + pr_info("AICWF enter suspend, stop scan\n"); + ret = rwnx_send_scanu_cancel_req(rwnx_hw, NULL); + /* make sure fw take effect */ + msleep(50); + if (ret) { + pr_info("AICWF %s scanu_cancel fail\n", __func__); + return ret; + } + } + } else { + AICWFDBG(LOGERROR, "param err\n"); + ret = -1; + } +#else + func = 15; +#endif + + if (ret == 0) { + memcpy(command, &func, 1); + return 1; + } + memcpy(command, &err, 1); + return 1; +} + +static int aic_priv_cmd_get_version(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + int bytes_written = 0; + + AICWFDBG(LOGINFO, "Firmware Version: %s\n", rwnx_hw->fw_version); + memcpy(command, rwnx_hw->fw_version, 32); + bytes_written = 32; + return bytes_written; +} + +static int aic_priv_cmd_get_link_status(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + struct rwnx_vif *rwnx_vif = NULL; + struct rwnx_vif *rwnx_vif_st = NULL; + struct wf_bss_info bi; + + bi.length = 0; + list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) { + if (rwnx_vif && rwnx_vif->up && + (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION)) + rwnx_vif_st = rwnx_vif; + } + if (!rwnx_vif_st) { + AICWFDBG(LOGINFO, "rwnx_vif_st is NULL\n"); + memcpy(command, &bi, 4); + return 4; + } + if (atomic_read(&rwnx_vif_st->drv_conn_state) != + (int)RWNX_DRV_STATUS_CONNECTED) { + AICWFDBG(LOGINFO, "rwnx_vif_st is not conncet\n"); + memcpy(command, &bi, 4); + return 4; + } + + bi.ssid_len = rwnx_vif_st->sta.ssid_len; + bi.band = rwnx_vif_st->sta.ap->band; + bi.width = rwnx_vif_st->sta.ap->width; + bi.center_freq = rwnx_vif_st->sta.ap->center_freq; + bi.center_freq1 = rwnx_vif_st->sta.ap->center_freq1; + bi.center_freq2 = rwnx_vif_st->sta.ap->center_freq2; + bi.ht = rwnx_vif_st->sta.ap->ht; + bi.vht = rwnx_vif_st->sta.ap->vht; + bi.chan = ieee80211_frequency_to_channel(bi.center_freq); + memcpy(bi.bssid, rwnx_vif_st->sta.bssid, ETH_ALEN); + memset(bi.ssid, 0, sizeof(bi.ssid)); + memcpy(bi.ssid, rwnx_vif_st->sta.ssid, rwnx_vif_st->sta.ssid_len); + bi.length = sizeof(bi); + + memcpy(command, &bi, bi.length); + return bi.length; +} + +static int aic_priv_cmd_get_auth_type(struct rwnx_hw *rwnx_hw, int argc, + char *argv[], char *command) +{ + struct rwnx_vif *rwnx_vif = NULL; + s32_l val = -1; + + list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) { + if (rwnx_vif && rwnx_vif->up && + (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION) && + atomic_read(&rwnx_vif->drv_conn_state) == + (int)RWNX_DRV_STATUS_CONNECTED) + val = rwnx_vif->sta.auth_type; + } + + memcpy(command, &val, 4); + return 4; +} + +static int aic_priv_cmd_help(struct rwnx_hw *rwnx_hw, int argc, char *argv[], + char *command) +{ + print_help(argc > 0 ? argv[0] : NULL); + return 0; +} + +struct aic_priv_cmd { + const char *cmd; + int (*handler)(struct rwnx_hw *rwnx_hw, int argc, char *argv[], + char *command); + const char *usage; +}; + +static const struct aic_priv_cmd aic_priv_commands[] = { + {"set_tx", aic_priv_cmd_set_tx, + " "}, + {"set_txstop", aic_priv_cmd_set_txstop, "= stop tx "}, + {"set_rx", aic_priv_cmd_set_rx, " "}, + {"get_rx_result", aic_priv_cmd_get_rx_result, + "= display rx fcsok/total pkt num"}, + {"set_rxstop", aic_priv_cmd_set_rxstop, "= stop rx "}, + {"set_txtone", aic_priv_cmd_set_tx_tone, " val = 0/off"}, + {"set_rx_meter", aic_priv_cmd_set_rx_meter, "= set rx meter "}, + {"set_power", aic_priv_cmd_set_set_power, " "}, + {"set_xtal_cap", aic_priv_cmd_set_xtal_cap, " [0 ~ 31]"}, + {"set_xtal_cap_fine", aic_priv_cmd_set_xtal_cap_fine, " [0 ~ 63]"}, + {"get_efuse_block", aic_priv_cmd_get_efuse_block, ""}, + {"set_freq_cal", aic_priv_cmd_set_freq_cal, ""}, + {"set_freq_cal_fine", aic_priv_cmd_set_freq_cal_fine, ""}, + {"get_freq_cal", aic_priv_cmd_get_freq_cal, "= display cap & cap fine"}, + {"set_mac_addr", aic_priv_cmd_set_mac_addr, + "= write WiFi MAC into efuse or flash is limited to a maximum of two times" + }, + {"get_mac_addr", aic_priv_cmd_get_mac_addr, + "= display WiFi MAC stored in efuse or flash"}, + {"set_bt_mac_addr", aic_priv_cmd_set_bt_mac_addr, + "= write BT MAC into efuse or flash is limited to a maximum of two times"}, + {"get_bt_mac_addr", aic_priv_cmd_get_bt_mac_addr, + "= display BT MAC stored in efuse or flash"}, + {"set_vendor_info", aic_priv_cmd_set_vendor_info, + "= write vendor info into efuse or flash is allowed only once"}, + {"get_vendor_info", aic_priv_cmd_get_vendor_info, + "= display vendor info stored in efuse or flash"}, + {"rdwr_pwrmm", aic_priv_cmd_rdwr_pwrmm, + " = 0/rdwr_pwrlvl, 1/set_power = read/write txpwr manul mode"}, + {"rdwr_pwridx", aic_priv_cmd_rdwr_pwridx, " "}, + {"rdwr_pwrlvl", aic_priv_cmd_rdwr_pwrlvl, " "}, + {"rdwr_pwrofst", aic_priv_cmd_rdwr_pwrofst, " "}, + {"rdwr_pwrofstfine", aic_priv_cmd_rdwr_pwrofstfine, + " "}, + {"rdwr_drvibit", aic_priv_cmd_rdwr_drvibit, + " read/write 8800D pa drvibit"}, + {"set_cal_xtal", aic_priv_cmd_set_cal_xtal, "= set cal xtal"}, + {"get_cal_xtal_res", aic_priv_cmd_get_cal_xtal_res, + "= get cal xtal result cap & cap_fine"}, + {"set_cob_cal", aic_priv_cmd_set_cob_cal, + " = dut cob test"}, + {"get_cob_cal_res", aic_priv_cmd_get_cob_cal_res, "= get cob cal result"}, + {"do_cob_test", aic_priv_cmd_do_cob_test, + " = 0/xtal, 1/dis_xtal, 2/only_xtal"}, + {"rdwr_efuse_pwrofst", aic_priv_cmd_rdwr_efuse_pwrofst, + " limited to a maximum of two times"}, + {"rdwr_efuse_pwrofstfine", aic_priv_cmd_rdwr_efuse_pwrofstfine, + " limited to a maximum of two times"}, + {"rdwr_efuse_drvibit", aic_priv_cmd_rdwr_efuse_drvibit, + " = read/write 8800D efuse pa drvibitis allowed only once"}, + {"rdwr_efuse_usrdata", aic_priv_cmd_rdwr_efuse_usrdata, + " = read/write efuse usrdata"}, + {"rdwr_efuse_sdiocfg", aic_priv_cmd_rdwr_efuse_sdiocfg, + " = read/write sdiocfg_bit into efuse"}, + {"rdwr_efuse_usbvidpid", aic_priv_cmd_rdwr_efuse_usbvidpid, + " = read/write usb vid/pid into efuse"}, + {"rdwr_efuse_he_off", aic_priv_cmd_rdwr_efuse_he_off, + " = read/write he_off into efuse"}, + {"set_papr", aic_priv_cmd_set_papr, + " = configure papr filter to optimize sideband suppression"}, + {"set_notch", aic_priv_cmd_set_notch, + " = configure filter to optimize sideband suppression"}, + {"set_srrc", aic_priv_cmd_set_srrc, + " = disable/enable sideband suppression for SRRC"}, + {"set_fss", aic_priv_cmd_set_fss, + " = disable/enable treatment of spurious emissions and burrs"}, + {"set_usb_off", aic_priv_cmd_set_usb_off, + "= off usb configure before usb disconnect"}, + {"set_pll_test", aic_priv_cmd_set_pll_test, + " = use pll test to measure saturation power"}, + {"get_txpwr", aic_priv_cmd_get_txpwr, "= get userconfig max txpwr"}, + {"set_txpwr_loss", aic_priv_cmd_set_txpwr_loss, + " = txpwr will change ,val can be negative"}, + {"rdwr_pwradd2x", aic_priv_cmd_rdwr_pwradd2x, + "a value is added for both 2.4G and 5G to achieve overall power adjustment of the band"}, + {"rdwr_efuse_pwradd2x", aic_priv_cmd_rdwr_efuse_pwradd2x, + "add power offset for both 2.4G and 5G, write to efuse"}, + + /* The following is not an RF cmd */ +#ifdef CONFIG_AIC8800_AUTO_CUSTREG + {"country_set", aic_priv_cmd_country_set, ""}, + {"country_get", aic_priv_cmd_country_get, "no param"}, +#endif +#ifdef CONFIG_AIC8800_TEMP_CONTROL + {"TEMP_CTRL_SW", aic_priv_cmd_temp_ctrl_sw, " 1--open, 0--close"}, + {"TEMP_CTRL_SET_GET", aic_priv_cmd_temp_sget, + "