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