blob: c7b2baf6d1bf9f5d3a22578e84da20565854326a [file] [log] [blame]
/*
* Copyright (c) 2012-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
/******************************************************************************
* wlan_ptt_sock_svc.c
*
******************************************************************************/
#ifdef PTT_SOCK_SVC_ENABLE
#include <wlan_nlink_srv.h>
#include <halTypes.h>
#include <vos_status.h>
#include <wlan_hdd_includes.h>
#include <vos_trace.h>
#include <wlan_nlink_common.h>
#include <wlan_ptt_sock_svc.h>
#include <vos_types.h>
#include <vos_trace.h>
#include <wlan_hdd_ftm.h>
#ifdef CNSS_GENL
#include <net/cnss_nl.h>
#else
static struct hdd_context_s *hdd_ctx_handle;
#endif
#define PTT_SOCK_DEBUG
#ifdef PTT_SOCK_DEBUG
#define PTT_TRACE(level, args...) VOS_TRACE( VOS_MODULE_ID_HDD, level, ## args)
#else
#define PTT_TRACE(level, args...)
#endif
// Global variables
#ifdef PTT_SOCK_DEBUG_VERBOSE
//Utility function to perform a hex dump
static void ptt_sock_dump_buf(const unsigned char * pbuf, int cnt)
{
int i;
for (i = 0; i < cnt ; i++) {
if ((i%16)==0)
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO,"\n%pK:", pbuf);
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO," %02X", *pbuf);
pbuf++;
}
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO,"\n");
}
#endif
/**
* nl_srv_ucast_ptt() - Wrapper function to send ucast msgs to PTT
* @skb: sk buffer pointer
* @dst_pid: Destination PID
* @flag: flags
*
* Sends the ucast message to PTT with generic nl socket if CNSS_GENL
* is enabled. Else, use the legacy netlink socket to send.
*
* Return: zero on success, error code otherwise
*/
static int nl_srv_ucast_ptt(struct sk_buff *skb, int dst_pid, int flag)
{
#ifdef CNSS_GENL
return nl_srv_ucast(skb, dst_pid, flag, ANI_NL_MSG_PUMAC,
CLD80211_MCGRP_DIAG_EVENTS);
#else
return nl_srv_ucast(skb, dst_pid, flag);
#endif
}
/**
* nl_srv_bcast_ptt() - Wrapper function to send bcast msgs to DIAG mcast group
* @skb: sk buffer pointer
*
* Sends the bcast message to DIAG multicast group with generic nl socket
* if CNSS_GENL is enabled. Else, use the legacy netlink socket to send.
*
* Return: zero on success, error code otherwise
*/
static int nl_srv_bcast_ptt(struct sk_buff *skb)
{
#ifdef CNSS_GENL
return nl_srv_bcast(skb, CLD80211_MCGRP_DIAG_EVENTS, ANI_NL_MSG_PUMAC);
#else
return nl_srv_bcast(skb);
#endif
}
//Utility function to send a netlink message to an application in user space
int ptt_sock_send_msg_to_app(tAniHdr *wmsg, int radio, int src_mod, int pid)
{
int err = -1;
int payload_len;
int tot_msg_len;
tAniNlHdr *wnl;
struct sk_buff *skb;
struct nlmsghdr *nlh;
int wmsg_length = be16_to_cpu(wmsg->length);
static int nlmsg_seq;
if (radio < 0 || radio > ANI_MAX_RADIOS) {
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "%s: invalid radio id [%d]\n",
__func__, radio);
return -EINVAL;
}
payload_len = wmsg_length + sizeof(wnl->radio) + sizeof(tAniHdr);
tot_msg_len = NLMSG_SPACE(payload_len);
if ((skb = dev_alloc_skb(tot_msg_len)) == NULL) {
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "%s: dev_alloc_skb() failed for msg size[%d]\n",
__func__, tot_msg_len);
return -ENOMEM;
}
nlh = nlmsg_put(skb, pid, nlmsg_seq++, src_mod, payload_len, NLM_F_REQUEST);
if (NULL == nlh) {
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "%s: nlmsg_put() failed for msg size[%d]\n",
__func__, tot_msg_len);
kfree_skb(skb);
return -ENOMEM;
}
wnl = (tAniNlHdr *) nlh;
wnl->radio = radio;
memcpy(&wnl->wmsg, wmsg, wmsg_length);
#ifdef PTT_SOCK_DEBUG_VERBOSE
ptt_sock_dump_buf((const unsigned char *)skb->data, skb->len);
#endif
if (pid != -1) {
err = nl_srv_ucast_ptt(skb, pid, MSG_DONTWAIT);
} else {
err = nl_srv_bcast_ptt(skb);
}
if (err) {
PTT_TRACE(VOS_TRACE_LEVEL_INFO,
"%s:Failed sending Msg Type [0x%X] to pid[%d]\n",
__func__, be16_to_cpu(wmsg->type), pid);
}
return err;
}
#ifndef CNSS_GENL
/*
* Process tregisteration request and send registration response messages
* to the PTT Socket App in user space
*/
static void ptt_sock_proc_reg_req(tAniHdr *wmsg, int radio)
{
tAniNlAppRegReq *reg_req;
tAniNlAppRegRsp rspmsg;
reg_req = (tAniNlAppRegReq *)(wmsg + 1);
memset((char *)&rspmsg, 0, sizeof(rspmsg));
/* send reg response message to the application */
rspmsg.ret = ANI_NL_MSG_OK;
rspmsg.regReq.type = reg_req->type;
/* Save the pid */
hdd_ctx_handle->ptt_pid = reg_req->pid;
rspmsg.regReq.pid= reg_req->pid;
rspmsg.wniHdr.type = cpu_to_be16(ANI_MSG_APP_REG_RSP);
rspmsg.wniHdr.length = cpu_to_be16(sizeof(rspmsg.wniHdr));
if (ptt_sock_send_msg_to_app((tAniHdr *)&rspmsg.wniHdr, radio,
ANI_NL_MSG_PUMAC, reg_req->pid) < 0)
{
PTT_TRACE(VOS_TRACE_LEVEL_INFO, "%s: Error sending ANI_MSG_APP_REG_RSP to pid[%d]\n",
__func__, reg_req->pid);
}
}
/*
* Process all the messages from the PTT Socket App in user space
*/
static void ptt_proc_pumac_msg(struct sk_buff * skb, tAniHdr *wmsg, int radio)
{
u16 ani_msg_type = be16_to_cpu(wmsg->type);
switch(ani_msg_type)
{
case ANI_MSG_APP_REG_REQ:
PTT_TRACE(VOS_TRACE_LEVEL_INFO, "%s: Received ANI_MSG_APP_REG_REQ [0x%X]\n",
__func__, ani_msg_type);
ptt_sock_proc_reg_req(wmsg, radio);
break;
default:
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "%s: Received Unknown Msg Type[0x%X]\n",
__func__, ani_msg_type);
break;
}
}
/*
* Process all the Netlink messages from PTT Socket app in user space
*/
static int ptt_sock_rx_nlink_msg (struct sk_buff * skb)
{
tAniNlHdr *wnl;
int radio;
int type;
wnl = (tAniNlHdr *) skb->data;
radio = wnl->radio;
type = wnl->nlh.nlmsg_type;
switch (type) {
case ANI_NL_MSG_PUMAC: // Message from the PTT socket APP
PTT_TRACE(VOS_TRACE_LEVEL_INFO, "%s: Received ANI_NL_MSG_PUMAC Msg [0x%X]\n",
__func__, type);
ptt_proc_pumac_msg(skb, &wnl->wmsg, radio);
break;
default:
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "%s: Unknown NL Msg [0x%X]\n",__func__, type);
break;
}
return 0;
}
#endif
#ifdef CNSS_GENL
/**
* ptt_cmd_handler() - Handler function for PTT commands
* @data: Data to be parsed
* @data_len: Length of the data received
* @ctx: Registered context reference
* @pid: Process id of the user space application
*
* This function handles the command from PTT user space application
*
* Return: None
*/
static void ptt_cmd_handler(const void *data, int data_len, void *ctx, int pid)
{
uint16_t length;
ptt_app_reg_req *payload;
struct nlattr *tb[CLD80211_ATTR_MAX + 1];
/*
* audit note: it is ok to pass a NULL policy here since a
* length check on the data is added later already
*/
if (nla_parse(tb, CLD80211_ATTR_MAX, data, data_len, NULL)) {
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "Invalid ATTR");
return;
}
if (!tb[CLD80211_ATTR_DATA]) {
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "attr ATTR_DATA failed");
return;
}
if (nla_len(tb[CLD80211_ATTR_DATA]) < sizeof(struct ptt_app_reg_req)) {
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "%s:attr length check fails\n",
__func__);
return;
}
payload = (ptt_app_reg_req *)(nla_data(tb[CLD80211_ATTR_DATA]));
length = be16_to_cpu(payload->wmsg.length);
if ((USHRT_MAX - length) < (sizeof(payload->radio) + sizeof(tAniHdr))) {
PTT_TRACE(QDF_TRACE_LEVEL_ERROR,
"u16 overflow length %d %zu %zu",
length,
sizeof(payload->radio),
sizeof(tAniHdr));
return;
}
if (nla_len(tb[CLD80211_ATTR_DATA]) < (length +
sizeof(payload->radio) +
sizeof(tAniHdr))) {
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "ATTR_DATA len check failed");
return;
}
switch (payload->wmsg.type) {
case ANI_MSG_APP_REG_REQ:
ptt_sock_send_msg_to_app(&payload->wmsg, payload->radio,
ANI_NL_MSG_PUMAC, pid);
break;
default:
PTT_TRACE(VOS_TRACE_LEVEL_ERROR, "Unknown msg type %d",
payload->wmsg.type);
break;
}
}
/**
* ptt_sock_activate_svc() - API to register PTT/PUMAC command handler
* @pAdapter: Pointer to adapter
*
* API to register the PTT/PUMAC command handlers. Argument @pAdapter
* is sent for prototype compatibility between new genl and legacy
* implementation
*
* Return: 0
*/
int ptt_sock_activate_svc(void *hdd_ctx)
{
register_cld_cmd_cb(ANI_NL_MSG_PUMAC, ptt_cmd_handler, NULL);
register_cld_cmd_cb(ANI_NL_MSG_PTT, ptt_cmd_handler, NULL);
return 0;
}
#else
int ptt_sock_activate_svc(void *hdd_ctx)
{
hdd_ctx_handle = (struct hdd_context_s *)hdd_ctx;
hdd_ctx_handle->ptt_pid = INVALID_PID;
nl_srv_register(ANI_NL_MSG_PUMAC, ptt_sock_rx_nlink_msg);
nl_srv_register(ANI_NL_MSG_PTT, ptt_sock_rx_nlink_msg);
return 0;
}
#endif
#endif // PTT_SOCK_SVC_ENABLE