/* * Copyright (C) 2015 Spreadtrum Communications Inc. * * Authors : * Xianwei.Zhao * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include "sprdwl.h" #include "npi.h" static int sprdwl_nl_send_generic(struct genl_info *info, u8 attr, u8 cmd, u32 len, u8 *data); static struct genl_family sprdwl_nl_genl_family; static int sprdwl_get_flag(void) { struct file *fp = NULL; mm_segment_t fs; loff_t *pos; int flag = 0; char file_data[2]; unsigned long long tmp; fp = filp_open(SPRDWL_PSM_PATH, O_RDONLY, 0); if (IS_ERR(fp)) { wl_err("open file:%s failed\n", SPRDWL_PSM_PATH); return PTR_ERR(fp); } fs = get_fs(); set_fs(KERNEL_DS); pos = &fp->f_pos; vfs_read(fp, file_data, 1, pos); filp_close(fp, NULL); set_fs(fs); file_data[1] = 0; if (kstrtoull(file_data, 10, &tmp)) { wl_err("%s: get value invald\n", __func__); return flag; } if (tmp) flag = SPRDWL_STA_GC_EN_SLEEP; else flag = SPRDWL_STA_GC_NO_SLEEP; return flag; } static int sprdwl_cmd_set_psm_cap(struct sprdwl_vif *vif) { struct sprdwl_priv *priv = NULL; struct sprdwl_npi_cmd_hdr *msg; unsigned char r_buf[512] = {0}, s_buf[8]; unsigned short r_len = 512; int s_len, flag, ret; if (!vif) { wl_err("%s: parameters invalid\n", __func__); return -EINVAL; } priv = vif->priv; flag = sprdwl_get_flag(); if (flag < 0) { wl_err("%s: get flag failed\n", __func__); return 0; } msg = (struct sprdwl_npi_cmd_hdr *)s_buf; msg->type = SPRDWL_HT2CP_CMD; msg->subtype = SPRDWL_NPI_CMD_SET_WLAN_CAP; msg->len = sizeof(flag); s_len = msg->len + sizeof(*msg); memcpy(s_buf + sizeof(*msg), &flag, sizeof(flag)); ret = sprdwl_npi_send_recv(priv, vif->ctx_id, s_buf, s_len, r_buf, &r_len); wl_info("[%s psm is:%s]\n", __func__, flag ? "normal mode" : "rf mode"); return ret; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) static int sprdwl_npi_pre_doit(const struct genl_ops *ops, struct sk_buff *skb, struct genl_info *info) #else static int sprdwl_npi_pre_doit(struct genl_ops *ops, struct sk_buff *skb, struct genl_info *info) #endif { struct net_device *ndev; struct sprdwl_vif *vif; struct sprdwl_priv *priv; int ifindex; if (!info) { wl_err("%s NULL info!\n", __func__); return -EINVAL; } if (info->attrs[SPRDWL_NL_ATTR_IFINDEX]) { ifindex = nla_get_u32(info->attrs[SPRDWL_NL_ATTR_IFINDEX]); ndev = dev_get_by_index(genl_info_net(info), ifindex); if (!ndev) { wl_err("NPI: Could not find ndev\n"); return -EFAULT; } vif = netdev_priv(ndev); priv = vif->priv; info->user_ptr[0] = ndev; info->user_ptr[1] = priv; } else { wl_err("nl80211_pre_doit: Not have attr_ifindex\n"); return -EFAULT; } return 0; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) static void sprdwl_npi_post_doit(const struct genl_ops *ops, struct sk_buff *skb, struct genl_info *info) #else static void sprdwl_npi_post_doit(struct genl_ops *ops, struct sk_buff *skb, struct genl_info *info) #endif { if (info->user_ptr[0]) dev_put(info->user_ptr[0]); } static bool sprdwl_npi_cmd_is_start(void *buf) { struct sprdwl_npi_cmd_hdr *msg; msg = (struct sprdwl_npi_cmd_hdr *)buf; if ((msg->type == SPRDWL_HT2CP_CMD) && (msg->subtype == SPRDWL_NPI_CMD_START)) return true; else return false; } static bool sta_or_p2p_is_opened(void) { return false; } static int sprdwl_nl_npi_handler(struct sk_buff *skb_2, struct genl_info *info) { struct net_device *ndev = NULL; struct sprdwl_vif *vif = NULL; struct sprdwl_priv *priv = NULL; struct sprdwl_npi_cmd_hdr *hdr = NULL; unsigned short r_len = 1024, s_len; unsigned char *s_buf = NULL, *r_buf = NULL; unsigned char dbgstr[64] = { 0 }; int err = -100, ret = 0; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) const char *id_name = NULL; unsigned char status = 0; #endif ndev = info->user_ptr[0]; vif = netdev_priv(ndev); priv = info->user_ptr[1]; if (!info->attrs[SPRDWL_NL_ATTR_AP2CP]) { wl_err("%s: invalid content\n", __func__); return -EPERM; } r_buf = kmalloc(1024, GFP_KERNEL); if (!r_buf) return -ENOMEM; s_buf = nla_data(info->attrs[SPRDWL_NL_ATTR_AP2CP]); s_len = nla_len(info->attrs[SPRDWL_NL_ATTR_AP2CP]); if (sprdwl_npi_cmd_is_start(s_buf) && sta_or_p2p_is_opened()) { hdr = kzalloc(sizeof(*hdr), GFP_KERNEL); if (!hdr) { wl_err("%s: failed to alloc hdr!\n", __func__); kfree(r_buf); return -ENOMEM; } hdr->type = SPRDWL_CP2HT_REPLY; hdr->subtype = SPRDWL_NPI_CMD_START; hdr->len = sizeof(err); r_len = sizeof(*hdr) + hdr->len; memcpy(r_buf, hdr, sizeof(*hdr)); memcpy(r_buf + sizeof(*hdr), &err, hdr->len); ret = sprdwl_nl_send_generic(info, SPRDWL_NL_ATTR_CP2AP, SPRDWL_NL_CMD_NPI, r_len, r_buf); kfree(hdr); kfree(r_buf); return ret; } sprintf(dbgstr, "[iwnpi][SEND][%d]:", s_len); hdr = (struct sprdwl_npi_cmd_hdr *)s_buf; wl_err("%s type is %d, subtype %d\n", dbgstr, hdr->type, hdr->subtype); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) if (hdr->subtype == SPRDWL_NPI_CMD_GET_CHIPID) { id_name = wcn_get_chip_name(); sprintf(r_buf, "%d", status); strcat(r_buf, id_name); r_len = strlen(r_buf); wl_err("r_len = %d, %s\n", r_len, __func__); } else { sprdwl_npi_send_recv(priv, vif->ctx_id, s_buf, s_len, r_buf, &r_len); sprintf(dbgstr, "[iwnpi][RECV][%d]:", r_len); hdr = (struct sprdwl_npi_cmd_hdr *)r_buf; wl_err("%s type is %d, subtype %d\n", dbgstr, hdr->type, hdr->subtype); } #else sprdwl_npi_send_recv(priv, vif->ctx_id, s_buf, s_len, r_buf, &r_len); sprintf(dbgstr, "[iwnpi][RECV][%d]:", r_len); hdr = (struct sprdwl_npi_cmd_hdr *)r_buf; wl_err("%s type is %d, subtype %d\n", dbgstr, hdr->type, hdr->subtype); #endif ret = sprdwl_nl_send_generic(info, SPRDWL_NL_ATTR_CP2AP, SPRDWL_NL_CMD_NPI, r_len, r_buf); if (sprdwl_npi_cmd_is_start(s_buf)) { msleep(100); ret = sprdwl_cmd_set_psm_cap(vif); } kfree(r_buf); return ret; } static int sprdwl_nl_get_info_handler(struct sk_buff *skb_2, struct genl_info *info) { struct net_device *ndev = info->user_ptr[0]; struct sprdwl_vif *vif = netdev_priv(ndev); unsigned char r_buf[64] = { 0 }; unsigned short r_len = 0; int ret = 0; if (vif) { ether_addr_copy(r_buf, vif->ndev->dev_addr); sprdwl_put_vif(vif); r_len = 6; ret = sprdwl_nl_send_generic(info, SPRDWL_NL_ATTR_CP2AP, SPRDWL_NL_CMD_GET_INFO, r_len, r_buf); } else { wl_err("%s NULL vif!\n", __func__); ret = -1; } return ret; } #if KERNEL_VERSION(5, 2, 0) > LINUX_VERSION_CODE static struct nla_policy sprdwl_genl_policy[SPRDWL_NL_ATTR_MAX + 1] = { [SPRDWL_NL_ATTR_AP2CP] = {.type = NLA_BINARY, .len = 1024}, [SPRDWL_NL_ATTR_CP2AP] = {.type = NLA_BINARY, .len = 1024} }; #endif static struct genl_ops sprdwl_nl_ops[] = { { .cmd = SPRDWL_NL_CMD_NPI, #if KERNEL_VERSION(5, 2, 0) > LINUX_VERSION_CODE .policy = sprdwl_genl_policy, #endif .doit = sprdwl_nl_npi_handler, }, { .cmd = SPRDWL_NL_CMD_GET_INFO, #if KERNEL_VERSION(5, 2, 0) > LINUX_VERSION_CODE .policy = sprdwl_genl_policy, #endif .doit = sprdwl_nl_get_info_handler, } }; static struct genl_family sprdwl_nl_genl_family = { .id = SPRDWL_NL_GENERAL_SOCK_ID, .hdrsize = 0, .name = "SPRDWL_NL", .version = 1, .maxattr = SPRDWL_NL_ATTR_MAX, .pre_doit = sprdwl_npi_pre_doit, .post_doit = sprdwl_npi_post_doit, #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) .module = THIS_MODULE, .n_ops = ARRAY_SIZE(sprdwl_nl_ops), .ops = sprdwl_nl_ops, #endif }; static int sprdwl_nl_send_generic(struct genl_info *info, u8 attr, u8 cmd, u32 len, u8 *data) { struct sk_buff *skb; void *hdr; int ret; skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); if (!skb) return -ENOMEM; hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, &sprdwl_nl_genl_family, 0, cmd); if (IS_ERR(hdr)) { ret = PTR_ERR(hdr); goto err_put; } if (nla_put(skb, attr, len, data)) { ret = -1; goto err_put; } genlmsg_end(skb, hdr); return genlmsg_reply(skb, info); err_put: nlmsg_free(skb); return ret; } void sprdwl_init_npi(void) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) int ret = genl_register_family(&sprdwl_nl_genl_family); if (ret) wl_err("genl_register_family error: %d\n", ret); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) int ret = genl_register_family_with_ops(&sprdwl_nl_genl_family, sprdwl_nl_ops); #else int ret = genl_register_family_with_ops(&sprdwl_nl_genl_family, sprdwl_nl_ops, ARRAY_SIZE(sprdwl_nl_ops)); #endif if (ret) wl_err("genl_register_family_with_ops error: %d\n", ret); #endif } void sprdwl_deinit_npi(void) { int ret = genl_unregister_family(&sprdwl_nl_genl_family); if (ret) wl_err("genl_unregister_family error:%d\n", ret); }