1/*
2 * Copyright (c) 2014 Broadcom Corporation
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
11 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
13 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include <linux/vmalloc.h>
18#include <net/cfg80211.h>
19#include <net/netlink.h>
20
21#include <brcmu_wifi.h>
22#include "fwil_types.h"
23#include "core.h"
24#include "p2p.h"
25#include "debug.h"
26#include "cfg80211.h"
27#include "vendor.h"
28#include "fwil.h"
29
30static int brcmf_cfg80211_vndr_cmds_dcmd_handler(struct wiphy *wiphy,
31						 struct wireless_dev *wdev,
32						 const void *data, int len)
33{
34	struct brcmf_cfg80211_vif *vif;
35	struct brcmf_if *ifp;
36	const struct brcmf_vndr_dcmd_hdr *cmdhdr = data;
37	struct sk_buff *reply;
38	int ret, payload, ret_len;
39	void *dcmd_buf = NULL, *wr_pointer;
40	u16 msglen, maxmsglen = PAGE_SIZE - 0x100;
41
42	if (len < sizeof(*cmdhdr)) {
43		brcmf_err("vendor command too short: %d\n", len);
44		return -EINVAL;
45	}
46
47	vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev);
48	ifp = vif->ifp;
49
50	brcmf_dbg(TRACE, "ifidx=%d, cmd=%d\n", ifp->ifidx, cmdhdr->cmd);
51
52	if (cmdhdr->offset > len) {
53		brcmf_err("bad buffer offset %d > %d\n", cmdhdr->offset, len);
54		return -EINVAL;
55	}
56
57	len -= cmdhdr->offset;
58	ret_len = cmdhdr->len;
59	if (ret_len > 0 || len > 0) {
60		if (len > BRCMF_DCMD_MAXLEN) {
61			brcmf_err("oversize input buffer %d\n", len);
62			len = BRCMF_DCMD_MAXLEN;
63		}
64		if (ret_len > BRCMF_DCMD_MAXLEN) {
65			brcmf_err("oversize return buffer %d\n", ret_len);
66			ret_len = BRCMF_DCMD_MAXLEN;
67		}
68		payload = max(ret_len, len) + 1;
69		dcmd_buf = vzalloc(payload);
70		if (NULL == dcmd_buf)
71			return -ENOMEM;
72
73		memcpy(dcmd_buf, (void *)cmdhdr + cmdhdr->offset, len);
74		*(char *)(dcmd_buf + len)  = '\0';
75	}
76
77	if (cmdhdr->set)
78		ret = brcmf_fil_cmd_data_set(ifp, cmdhdr->cmd, dcmd_buf,
79					     ret_len);
80	else
81		ret = brcmf_fil_cmd_data_get(ifp, cmdhdr->cmd, dcmd_buf,
82					     ret_len);
83	if (ret != 0)
84		goto exit;
85
86	wr_pointer = dcmd_buf;
87	while (ret_len > 0) {
88		msglen = ret_len > maxmsglen ? maxmsglen : ret_len;
89		ret_len -= msglen;
90		payload = msglen + sizeof(msglen);
91		reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, payload);
92		if (NULL == reply) {
93			ret = -ENOMEM;
94			break;
95		}
96
97		if (nla_put(reply, BRCMF_NLATTR_DATA, msglen, wr_pointer) ||
98		    nla_put_u16(reply, BRCMF_NLATTR_LEN, msglen)) {
99			kfree_skb(reply);
100			ret = -ENOBUFS;
101			break;
102		}
103
104		ret = cfg80211_vendor_cmd_reply(reply);
105		if (ret)
106			break;
107
108		wr_pointer += msglen;
109	}
110
111exit:
112	vfree(dcmd_buf);
113
114	return ret;
115}
116
117const struct wiphy_vendor_command brcmf_vendor_cmds[] = {
118	{
119		{
120			.vendor_id = BROADCOM_OUI,
121			.subcmd = BRCMF_VNDR_CMDS_DCMD
122		},
123		.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
124			 WIPHY_VENDOR_CMD_NEED_NETDEV,
125		.doit = brcmf_cfg80211_vndr_cmds_dcmd_handler
126	},
127};
128