1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.
4  * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
5  */
6 
7 #include <net/genetlink.h>
8 #ifdef CONFIG_CNSS_OUT_OF_TREE
9 #include "cnss_nl.h"
10 #else
11 #include <net/cnss_nl.h>
12 #endif
13 #include <linux/module.h>
14 #include <linux/of.h>
15 #include <linux/version.h>
16 
17 #define CLD80211_GENL_NAME "cld80211"
18 
19 #define CLD80211_MULTICAST_GROUP_SVC_MSGS       "svc_msgs"
20 #define CLD80211_MULTICAST_GROUP_HOST_LOGS      "host_logs"
21 #define CLD80211_MULTICAST_GROUP_FW_LOGS        "fw_logs"
22 #define CLD80211_MULTICAST_GROUP_PER_PKT_STATS  "per_pkt_stats"
23 #define CLD80211_MULTICAST_GROUP_DIAG_EVENTS    "diag_events"
24 #define CLD80211_MULTICAST_GROUP_FATAL_EVENTS   "fatal_events"
25 #define CLD80211_MULTICAST_GROUP_OEM_MSGS       "oem_msgs"
26 
27 static const struct genl_multicast_group nl_mcgrps[] = {
28 	[CLD80211_MCGRP_SVC_MSGS] = { .name =
29 			CLD80211_MULTICAST_GROUP_SVC_MSGS},
30 	[CLD80211_MCGRP_HOST_LOGS] = { .name =
31 			CLD80211_MULTICAST_GROUP_HOST_LOGS},
32 	[CLD80211_MCGRP_FW_LOGS] = { .name =
33 			CLD80211_MULTICAST_GROUP_FW_LOGS},
34 	[CLD80211_MCGRP_PER_PKT_STATS] = { .name =
35 			CLD80211_MULTICAST_GROUP_PER_PKT_STATS},
36 	[CLD80211_MCGRP_DIAG_EVENTS] = { .name =
37 			CLD80211_MULTICAST_GROUP_DIAG_EVENTS},
38 	[CLD80211_MCGRP_FATAL_EVENTS] = { .name =
39 			CLD80211_MULTICAST_GROUP_FATAL_EVENTS},
40 	[CLD80211_MCGRP_OEM_MSGS] = { .name =
41 			CLD80211_MULTICAST_GROUP_OEM_MSGS},
42 };
43 
44 struct cld_ops {
45 	cld80211_cb cb;
46 	void *cb_ctx;
47 };
48 
49 struct cld80211_nl_data {
50 	struct cld_ops cld_ops[CLD80211_MAX_COMMANDS];
51 };
52 
53 static struct cld80211_nl_data nl_data;
54 
get_local_ctx(void)55 static inline struct cld80211_nl_data *get_local_ctx(void)
56 {
57 	return &nl_data;
58 }
59 
60 static struct genl_ops nl_ops[CLD80211_MAX_COMMANDS];
61 
62 /* policy for the attributes */
63 static const struct nla_policy cld80211_policy[CLD80211_ATTR_MAX + 1] = {
64 	[CLD80211_ATTR_VENDOR_DATA] = { .type = NLA_NESTED },
65 	[CLD80211_ATTR_DATA] = { .type = NLA_BINARY,
66 				 .len = CLD80211_MAX_NL_DATA },
67 	[CLD80211_ATTR_META_DATA] = { .type = NLA_BINARY,
68 				 .len = CLD80211_MAX_NL_DATA },
69 	[CLD80211_ATTR_CMD] = { .type = NLA_U32 },
70 	[CLD80211_ATTR_CMD_TAG_DATA] = { .type = NLA_NESTED },
71 };
72 
73 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0))
cld80211_pre_doit(const struct genl_split_ops * ops,struct sk_buff * skb,struct genl_info * info)74 static int cld80211_pre_doit(const struct genl_split_ops *ops,
75 			     struct sk_buff *skb,
76 			     struct genl_info *info)
77 {
78 	u8 cmd_id = ops->cmd;
79 	struct cld80211_nl_data *nl = get_local_ctx();
80 
81 	if (cmd_id < 1 || cmd_id > CLD80211_MAX_COMMANDS) {
82 		pr_err("CLD80211: Command Not supported: %u\n", cmd_id);
83 		return -EOPNOTSUPP;
84 	}
85 	info->user_ptr[0] = nl->cld_ops[cmd_id - 1].cb;
86 	info->user_ptr[1] = nl->cld_ops[cmd_id - 1].cb_ctx;
87 
88 	return 0;
89 }
90 #else
cld80211_pre_doit(const struct genl_ops * ops,struct sk_buff * skb,struct genl_info * info)91 static int cld80211_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
92 			     struct genl_info *info)
93 {
94 	u8 cmd_id = ops->cmd;
95 	struct cld80211_nl_data *nl = get_local_ctx();
96 
97 	if (cmd_id < 1 || cmd_id > CLD80211_MAX_COMMANDS) {
98 		pr_err("CLD80211: Command Not supported: %u\n", cmd_id);
99 		return -EOPNOTSUPP;
100 	}
101 	info->user_ptr[0] = nl->cld_ops[cmd_id - 1].cb;
102 	info->user_ptr[1] = nl->cld_ops[cmd_id - 1].cb_ctx;
103 
104 	return 0;
105 }
106 #endif
107 /* The netlink family */
108 static struct genl_family cld80211_fam __ro_after_init = {
109 	.name = CLD80211_GENL_NAME,
110 	.hdrsize = 0,			/* no private header */
111 	.version = 1,			/* no particular meaning now */
112 	.maxattr = CLD80211_ATTR_MAX,
113 	.policy = cld80211_policy,
114 	.netnsok = true,
115 	.pre_doit = cld80211_pre_doit,
116 	.post_doit = NULL,
117 	.module = THIS_MODULE,
118 	.ops = nl_ops,
119 	.n_ops = ARRAY_SIZE(nl_ops),
120 	.mcgrps = nl_mcgrps,
121 	.n_mcgrps = ARRAY_SIZE(nl_mcgrps),
122 };
123 
register_cld_cmd_cb(u8 cmd_id,cld80211_cb func,void * cb_ctx)124 int register_cld_cmd_cb(u8 cmd_id, cld80211_cb func, void *cb_ctx)
125 {
126 	struct cld80211_nl_data *nl = get_local_ctx();
127 
128 	pr_debug("CLD80211: Registering command: %d\n", cmd_id);
129 	if (!cmd_id || cmd_id > CLD80211_MAX_COMMANDS) {
130 		pr_debug("CLD80211: invalid command: %d\n", cmd_id);
131 		return -EINVAL;
132 	}
133 
134 	nl->cld_ops[cmd_id - 1].cb = func;
135 	nl->cld_ops[cmd_id - 1].cb_ctx = cb_ctx;
136 
137 	return 0;
138 }
139 EXPORT_SYMBOL(register_cld_cmd_cb);
140 
deregister_cld_cmd_cb(u8 cmd_id)141 int deregister_cld_cmd_cb(u8 cmd_id)
142 {
143 	struct cld80211_nl_data *nl = get_local_ctx();
144 
145 	pr_debug("CLD80211: De-registering command: %d\n", cmd_id);
146 	if (!cmd_id || cmd_id > CLD80211_MAX_COMMANDS) {
147 		pr_debug("CLD80211: invalid command: %d\n", cmd_id);
148 		return -EINVAL;
149 	}
150 
151 	nl->cld_ops[cmd_id - 1].cb = NULL;
152 	nl->cld_ops[cmd_id - 1].cb_ctx = NULL;
153 
154 	return 0;
155 }
156 EXPORT_SYMBOL(deregister_cld_cmd_cb);
157 
cld80211_get_genl_family(void)158 struct genl_family *cld80211_get_genl_family(void)
159 {
160 	return &cld80211_fam;
161 }
162 EXPORT_SYMBOL(cld80211_get_genl_family);
163 
cld80211_doit(struct sk_buff * skb,struct genl_info * info)164 static int cld80211_doit(struct sk_buff *skb, struct genl_info *info)
165 {
166 	cld80211_cb cld_cb;
167 	void *cld_ctx;
168 
169 	cld_cb = info->user_ptr[0];
170 
171 	if (!cld_cb) {
172 		pr_err("CLD80211: Not supported\n");
173 		return -EOPNOTSUPP;
174 	}
175 	cld_ctx = info->user_ptr[1];
176 
177 	if (info->attrs[CLD80211_ATTR_VENDOR_DATA]) {
178 		cld_cb(nla_data(info->attrs[CLD80211_ATTR_VENDOR_DATA]),
179 		       nla_len(info->attrs[CLD80211_ATTR_VENDOR_DATA]),
180 		       cld_ctx, info->snd_portid);
181 	} else {
182 		pr_err("CLD80211: No CLD80211_ATTR_VENDOR_DATA\n");
183 		return -EINVAL;
184 	}
185 	return 0;
186 }
187 
__cld80211_init(void)188 static int __cld80211_init(void)
189 {
190 	int err, i;
191 
192 	memset(&nl_ops[0], 0, sizeof(nl_ops));
193 
194 	pr_info("CLD80211: Initializing\n");
195 	for (i = 0; i < CLD80211_MAX_COMMANDS; i++) {
196 		nl_ops[i].cmd = i + 1;
197 		nl_ops[i].doit = cld80211_doit;
198 		nl_ops[i].flags = GENL_ADMIN_PERM;
199 	}
200 
201 	err = genl_register_family(&cld80211_fam);
202 	if (err) {
203 		pr_err("CLD80211: Failed to register cld80211 family: %d\n",
204 		       err);
205 	}
206 
207 	return err;
208 }
209 
__cld80211_exit(void)210 static void __cld80211_exit(void)
211 {
212 	genl_unregister_family(&cld80211_fam);
213 }
214 
215 /**
216  * cld80211_is_valid_dt_node_found - Check if valid device tree node present
217  *
218  * Valid device tree node means a node with "qcom,wlan" property present and
219  * "status" property not disabled.
220  *
221  * Return: true if valid device tree node found, false if not found
222  */
cld80211_is_valid_dt_node_found(void)223 static bool cld80211_is_valid_dt_node_found(void)
224 {
225 	struct device_node *dn = NULL;
226 
227 	for_each_node_with_property(dn, "qcom,wlan") {
228 		if (of_device_is_available(dn))
229 			break;
230 	}
231 
232 	if (dn)
233 		return true;
234 
235 	return false;
236 }
237 
cld80211_init(void)238 static int __init cld80211_init(void)
239 {
240 	if (!cld80211_is_valid_dt_node_found())
241 		return -ENODEV;
242 
243 	return __cld80211_init();
244 }
245 
cld80211_exit(void)246 static void __exit cld80211_exit(void)
247 {
248 	__cld80211_exit();
249 }
250 
251 module_init(cld80211_init);
252 module_exit(cld80211_exit);
253 
254 MODULE_LICENSE("GPL v2");
255 MODULE_DESCRIPTION("CNSS generic netlink module");
256