• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /******************************************************************************
2  *
3  * Copyright(c) 2007 - 2020 Realtek Corporation.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of version 2 of the GNU General Public License as
7  * published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12  * more details.
13  *
14  *****************************************************************************/
15 #define _RTW_NLRTW_C_
16 
17 #include <drv_types.h>
18 #include "nlrtw.h"
19 
20 #ifdef CONFIG_RTW_NLRTW
21 
22 #include <net/netlink.h>
23 #include <net/genetlink.h>
24 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0))
25 #include <uapi/linux/netlink.h>
26 #endif
27 
28 
29 enum nlrtw_cmds {
30 	NLRTW_CMD_UNSPEC,
31 
32 	NLRTW_CMD_CHANNEL_UTILIZATION,
33 	NLRTW_CMD_REG_CHANGE,
34 	NLRTW_CMD_REG_BEACON_HINT,
35 	NLRTW_CMD_RADAR_EVENT,
36 	NLRTW_CMD_RADIO_OPMODE,
37 
38 	__NLRTW_CMD_AFTER_LAST,
39 	NLRTW_CMD_MAX = __NLRTW_CMD_AFTER_LAST - 1
40 };
41 
42 enum nlrtw_attrs {
43 	NLRTW_ATTR_UNSPEC,
44 
45 	NLRTW_ATTR_WIPHY_NAME,
46 	NLRTW_ATTR_CHANNEL_UTILIZATIONS,
47 	NLRTW_ATTR_CHANNEL_UTILIZATION_THRESHOLD,
48 	NLRTW_ATTR_CHANNEL_CENTER,
49 	NLRTW_ATTR_CHANNEL_WIDTH,
50 	NLRTW_ATTR_RADAR_EVENT,
51 	NLRTW_ATTR_OP_CLASS,
52 	NLRTW_ATTR_OP_CHANNEL,
53 	NLRTW_ATTR_OP_TXPWR_MAX,
54 	NLRTW_ATTR_IF_OPMODES,
55 
56 	__NLRTW_ATTR_AFTER_LAST,
57 	NUM_NLRTW_ATTR = __NLRTW_ATTR_AFTER_LAST,
58 	NLRTW_ATTR_MAX = __NLRTW_ATTR_AFTER_LAST - 1
59 };
60 
61 enum nlrtw_ch_util_attrs {
62 	__NLRTW_ATTR_CHANNEL_UTILIZATION_INVALID,
63 
64 	NLRTW_ATTR_CHANNEL_UTILIZATION_VALUE,
65 	NLRTW_ATTR_CHANNEL_UTILIZATION_BSSID,
66 
67 	__NLRTW_ATTR_CHANNEL_UTILIZATION_AFTER_LAST,
68 	NUM_NLRTW_ATTR_CHANNEL_UTILIZATION = __NLRTW_ATTR_CHANNEL_UTILIZATION_AFTER_LAST,
69 	NLRTW_ATTR_CHANNEL_UTILIZATION_MAX = __NLRTW_ATTR_CHANNEL_UTILIZATION_AFTER_LAST - 1
70 };
71 
72 enum nlrtw_radar_event {
73 	NLRTW_RADAR_DETECTED,
74 	NLRTW_RADAR_CAC_FINISHED,
75 	NLRTW_RADAR_CAC_ABORTED,
76 	NLRTW_RADAR_NOP_FINISHED,
77 	NLRTW_RADAR_NOP_STARTED, /* NON_OCP started not by local radar detection */
78 };
79 
80 enum nlrtw_if_opmode_attrs {
81 	NLRTW_IF_OPMODE_UNSPEC,
82 
83 	NLRTW_IF_OPMODE_MACADDR,
84 	NLRTW_IF_OPMODE_OP_CLASS,
85 	NLRTW_IF_OPMODE_OP_CHANNEL,
86 
87 	__NLRTW_IF_OPMODE_ATTR_AFTER_LAST,
88 	NUM_NLRTW_IF_OPMODE_ATTR = __NLRTW_IF_OPMODE_ATTR_AFTER_LAST,
89 	NLRTW_IF_OPMODE_ATTR_MAX = __NLRTW_IF_OPMODE_ATTR_AFTER_LAST - 1
90 };
91 
nlrtw_ch_util_set(struct sk_buff * skb,struct genl_info * info)92 static int nlrtw_ch_util_set(struct sk_buff *skb, struct genl_info *info)
93 {
94 	unsigned int msg;
95 
96 	if (!info->attrs[NLRTW_ATTR_CHANNEL_UTILIZATION_THRESHOLD])
97 		return -EINVAL;
98 	msg = nla_get_u8(info->attrs[NLRTW_ATTR_CHANNEL_UTILIZATION_THRESHOLD]);
99 
100 	return 0;
101 }
102 
103 static struct nla_policy nlrtw_genl_policy[NUM_NLRTW_ATTR] = {
104 	[NLRTW_ATTR_CHANNEL_UTILIZATION_THRESHOLD] = { .type = NLA_U8 },
105 };
106 
107 static struct genl_ops nlrtw_genl_ops[] = {
108 	{
109 		.cmd = NLRTW_CMD_CHANNEL_UTILIZATION,
110 		.flags = 0,
111 #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
112 		.policy = nlrtw_genl_policy,
113 #endif
114 		.doit = nlrtw_ch_util_set,
115 		.dumpit = NULL,
116 	},
117 };
118 
119 enum nlrtw_multicast_groups {
120 	NLRTW_MCGRP_DEFAULT,
121 };
122 static struct genl_multicast_group nlrtw_genl_mcgrp[] = {
123 	[NLRTW_MCGRP_DEFAULT] = { .name = "nlrtw_default" },
124 };
125 
126 /* family definition */
127 static struct genl_family nlrtw_genl_family = {
128 	.hdrsize = 0,
129 	.name = "nlrtw_"DRV_NAME,
130 	.version = 1,
131 	.maxattr = NLRTW_ATTR_MAX,
132 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0)
133 	.policy = nlrtw_genl_policy,
134 #endif
135 	.netnsok = true,
136 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 12)
137 	.module = THIS_MODULE,
138 #endif
139 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
140 	.ops = nlrtw_genl_ops,
141 	.n_ops = ARRAY_SIZE(nlrtw_genl_ops),
142 	.mcgrps = nlrtw_genl_mcgrp,
143 	.n_mcgrps = ARRAY_SIZE(nlrtw_genl_mcgrp),
144 #endif
145 };
146 
nlrtw_multicast(const struct genl_family * family,struct sk_buff * skb,u32 portid,unsigned int group,gfp_t flags)147 static inline int nlrtw_multicast(const struct genl_family *family,
148 				  struct sk_buff *skb, u32 portid,
149 				  unsigned int group, gfp_t flags)
150 {
151 	int ret;
152 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
153 	ret = genlmsg_multicast(&nlrtw_genl_family, skb, portid, group, flags);
154 #else
155 	ret = genlmsg_multicast(skb, portid, nlrtw_genl_mcgrp[group].id, flags);
156 #endif
157 	return ret;
158 }
159 
rtw_nlrtw_ch_util_rpt(_adapter * adapter,u8 n_rpts,u8 * val,u8 ** mac_addr)160 int rtw_nlrtw_ch_util_rpt(_adapter *adapter, u8 n_rpts, u8 *val, u8 **mac_addr)
161 {
162 	struct sk_buff *skb = NULL;
163 	void *msg_header = NULL;
164 	struct nlattr *nl_ch_util, *nl_ch_utils;
165 	struct wiphy *wiphy;
166 	u8 i;
167 	int ret;
168 
169 	wiphy = adapter_to_wiphy(adapter);
170 	if (!wiphy)
171 		return -EINVAL;
172 
173 	/* allocate memory */
174 	skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
175 	if (!skb) {
176 		nlmsg_free(skb);
177 		return -ENOMEM;
178 	}
179 
180 	/* create the message headers */
181 	msg_header = genlmsg_put(skb, 0, 0, &nlrtw_genl_family, 0,
182 				 NLRTW_CMD_CHANNEL_UTILIZATION);
183 	if (!msg_header) {
184 		ret = -ENOMEM;
185 		goto err_out;
186 	}
187 
188 	/* add attributes */
189 	ret = nla_put_string(skb, NLRTW_ATTR_WIPHY_NAME, wiphy_name(wiphy));
190 
191 	nl_ch_utils = nla_nest_start(skb, NLRTW_ATTR_CHANNEL_UTILIZATIONS);
192 	if (!nl_ch_utils) {
193 		ret = -EMSGSIZE;
194 		goto err_out;
195 	}
196 
197 	for (i = 0; i < n_rpts; i++) {
198 		nl_ch_util = nla_nest_start(skb, i);
199 		if (!nl_ch_util) {
200 			ret = -EMSGSIZE;
201 			goto err_out;
202 		}
203 
204 		ret = nla_put(skb, NLRTW_ATTR_CHANNEL_UTILIZATION_BSSID, ETH_ALEN, *(mac_addr + i));
205 		if (ret != 0)
206 			goto err_out;
207 
208 		ret = nla_put_u8(skb, NLRTW_ATTR_CHANNEL_UTILIZATION_VALUE, *(val + i));
209 		if (ret != 0)
210 			goto err_out;
211 
212 		nla_nest_end(skb, nl_ch_util);
213 	}
214 
215 	nla_nest_end(skb, nl_ch_utils);
216 
217 	/* finalize the message */
218 	genlmsg_end(skb, msg_header);
219 
220 	ret = nlrtw_multicast(&nlrtw_genl_family, skb, 0, NLRTW_MCGRP_DEFAULT, GFP_KERNEL);
221 	if (ret == -ESRCH) {
222 		RTW_INFO("[%s] return ESRCH(No such process)."
223 			 " Maybe no process waits for this msg\n", __func__);
224 		return ret;
225 	} else if (ret != 0) {
226 		RTW_INFO("[%s] ret = %d\n", __func__, ret);
227 		return ret;
228 	}
229 
230 	return 0;
231 err_out:
232 	nlmsg_free(skb);
233 	return ret;
234 }
235 
rtw_nlrtw_reg_change_event(_adapter * adapter)236 int rtw_nlrtw_reg_change_event(_adapter *adapter)
237 {
238 	struct sk_buff *skb = NULL;
239 	void *msg_header = NULL;
240 	struct wiphy *wiphy;
241 	u8 i;
242 	int ret;
243 
244 	wiphy = adapter_to_wiphy(adapter);
245 	if (!wiphy) {
246 		ret = -EINVAL;
247 		goto err_out;
248 	}
249 
250 	/* allocate memory */
251 	skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
252 	if (!skb) {
253 		ret = -ENOMEM;
254 		goto err_out;
255 	}
256 
257 	/* create the message headers */
258 	msg_header = genlmsg_put(skb, 0, 0, &nlrtw_genl_family, 0, NLRTW_CMD_REG_CHANGE);
259 	if (!msg_header) {
260 		ret = -ENOMEM;
261 		goto err_out;
262 	}
263 
264 	/* add attributes */
265 	ret = nla_put_string(skb, NLRTW_ATTR_WIPHY_NAME, wiphy_name(wiphy));
266 	if (ret)
267 		goto err_out;
268 
269 	/* finalize the message */
270 	genlmsg_end(skb, msg_header);
271 
272 	ret = nlrtw_multicast(&nlrtw_genl_family, skb, 0, NLRTW_MCGRP_DEFAULT, GFP_KERNEL);
273 	if (ret == -ESRCH) {
274 		RTW_DBG(FUNC_WIPHY_FMT" return -ESRCH(No such process)."
275 			 " Maybe no process waits for this msg\n", FUNC_WIPHY_ARG(wiphy));
276 		return ret;
277 	} else if (ret != 0) {
278 		RTW_WARN(FUNC_WIPHY_FMT" return %d\n", FUNC_WIPHY_ARG(wiphy), ret);
279 		return ret;
280 	}
281 
282 	return 0;
283 
284 err_out:
285 	if (skb)
286 		nlmsg_free(skb);
287 	return ret;
288 }
289 
rtw_nlrtw_reg_beacon_hint_event(_adapter * adapter)290 int rtw_nlrtw_reg_beacon_hint_event(_adapter *adapter)
291 {
292 	struct sk_buff *skb = NULL;
293 	void *msg_header = NULL;
294 	struct wiphy *wiphy;
295 	u8 i;
296 	int ret;
297 
298 	wiphy = adapter_to_wiphy(adapter);
299 	if (!wiphy) {
300 		ret = -EINVAL;
301 		goto err_out;
302 	}
303 
304 	/* allocate memory */
305 	skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
306 	if (!skb) {
307 		ret = -ENOMEM;
308 		goto err_out;
309 	}
310 
311 	/* create the message headers */
312 	msg_header = genlmsg_put(skb, 0, 0, &nlrtw_genl_family, 0, NLRTW_CMD_REG_BEACON_HINT);
313 	if (!msg_header) {
314 		ret = -ENOMEM;
315 		goto err_out;
316 	}
317 
318 	/* add attributes */
319 	ret = nla_put_string(skb, NLRTW_ATTR_WIPHY_NAME, wiphy_name(wiphy));
320 	if (ret)
321 		goto err_out;
322 
323 	/* finalize the message */
324 	genlmsg_end(skb, msg_header);
325 
326 	ret = nlrtw_multicast(&nlrtw_genl_family, skb, 0, NLRTW_MCGRP_DEFAULT, GFP_KERNEL);
327 	if (ret == -ESRCH) {
328 		RTW_DBG(FUNC_WIPHY_FMT" return -ESRCH(No such process)."
329 			 " Maybe no process waits for this msg\n", FUNC_WIPHY_ARG(wiphy));
330 		return ret;
331 	} else if (ret != 0) {
332 		RTW_WARN(FUNC_WIPHY_FMT" return %d\n", FUNC_WIPHY_ARG(wiphy), ret);
333 		return ret;
334 	}
335 
336 	return 0;
337 
338 err_out:
339 	if (skb)
340 		nlmsg_free(skb);
341 	return ret;
342 }
343 
344 #ifdef CONFIG_DFS_MASTER
_rtw_nlrtw_radar_event(_adapter * adapter,enum nlrtw_radar_event evt_type,u8 cch,u8 bw)345 static int _rtw_nlrtw_radar_event(_adapter *adapter, enum nlrtw_radar_event evt_type, u8 cch, u8 bw)
346 {
347 	struct sk_buff *skb = NULL;
348 	void *msg_header = NULL;
349 	struct wiphy *wiphy;
350 	u8 i;
351 	int ret;
352 
353 	wiphy = adapter_to_wiphy(adapter);
354 	if (!wiphy) {
355 		ret = -EINVAL;
356 		goto err_out;
357 	}
358 
359 	/* allocate memory */
360 	skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
361 	if (!skb) {
362 		ret = -ENOMEM;
363 		goto err_out;
364 	}
365 
366 	/* create the message headers */
367 	msg_header = genlmsg_put(skb, 0, 0, &nlrtw_genl_family, 0, NLRTW_CMD_RADAR_EVENT);
368 	if (!msg_header) {
369 		ret = -ENOMEM;
370 		goto err_out;
371 	}
372 
373 	/* add attributes */
374 	ret = nla_put_string(skb, NLRTW_ATTR_WIPHY_NAME, wiphy_name(wiphy));
375 	if (ret)
376 		goto err_out;
377 
378 	ret = nla_put_u8(skb, NLRTW_ATTR_RADAR_EVENT, (uint8_t)evt_type);
379 	if (ret != 0)
380 		goto err_out;
381 
382 	ret = nla_put_u8(skb, NLRTW_ATTR_CHANNEL_CENTER, cch);
383 	if (ret != 0)
384 		goto err_out;
385 
386 	ret = nla_put_u8(skb, NLRTW_ATTR_CHANNEL_WIDTH, bw);
387 	if (ret != 0)
388 		goto err_out;
389 
390 	/* finalize the message */
391 	genlmsg_end(skb, msg_header);
392 
393 	ret = nlrtw_multicast(&nlrtw_genl_family, skb, 0, NLRTW_MCGRP_DEFAULT, GFP_KERNEL);
394 	if (ret == -ESRCH) {
395 		RTW_DBG(FUNC_WIPHY_FMT" return -ESRCH(No such process)."
396 			 " Maybe no process waits for this msg\n", FUNC_WIPHY_ARG(wiphy));
397 		return ret;
398 	} else if (ret != 0) {
399 		RTW_WARN(FUNC_WIPHY_FMT" return %d\n", FUNC_WIPHY_ARG(wiphy), ret);
400 		return ret;
401 	}
402 
403 	return 0;
404 
405 err_out:
406 	if (skb)
407 		nlmsg_free(skb);
408 	return ret;
409 }
410 
rtw_nlrtw_radar_detect_event(_adapter * adapter,u8 cch,u8 bw)411 int rtw_nlrtw_radar_detect_event(_adapter *adapter, u8 cch, u8 bw)
412 {
413 	return _rtw_nlrtw_radar_event(adapter, NLRTW_RADAR_DETECTED, cch, bw);
414 }
415 
rtw_nlrtw_cac_finish_event(_adapter * adapter,u8 cch,u8 bw)416 int rtw_nlrtw_cac_finish_event(_adapter *adapter, u8 cch, u8 bw)
417 {
418 	return _rtw_nlrtw_radar_event(adapter, NLRTW_RADAR_CAC_FINISHED, cch, bw);
419 }
420 
rtw_nlrtw_cac_abort_event(_adapter * adapter,u8 cch,u8 bw)421 int rtw_nlrtw_cac_abort_event(_adapter *adapter, u8 cch, u8 bw)
422 {
423 	return _rtw_nlrtw_radar_event(adapter, NLRTW_RADAR_CAC_ABORTED, cch, bw);
424 }
425 
rtw_nlrtw_nop_finish_event(_adapter * adapter,u8 cch,u8 bw)426 int rtw_nlrtw_nop_finish_event(_adapter *adapter, u8 cch, u8 bw)
427 {
428 	return _rtw_nlrtw_radar_event(adapter, NLRTW_RADAR_NOP_FINISHED, cch, bw);
429 }
430 
rtw_nlrtw_nop_start_event(_adapter * adapter,u8 cch,u8 bw)431 int rtw_nlrtw_nop_start_event(_adapter *adapter, u8 cch, u8 bw)
432 {
433 	return _rtw_nlrtw_radar_event(adapter, NLRTW_RADAR_NOP_STARTED, cch, bw);
434 }
435 #endif /* CONFIG_DFS_MASTER */
436 
rtw_nlrtw_radio_opmode_notify(struct rf_ctl_t * rfctl)437 int rtw_nlrtw_radio_opmode_notify(struct rf_ctl_t *rfctl)
438 {
439 	struct dvobj_priv *dvobj = rfctl_to_dvobj(rfctl);
440 	_adapter *iface;
441 	struct sk_buff *skb = NULL;
442 	void *msg_header = NULL;
443 	struct nlattr *nl_if_opmodes, *nl_if_opmode;
444 	struct wiphy *wiphy;
445 	u16 op_txpwr_max_u16;
446 	u8 i;
447 	int ret;
448 
449 	wiphy = dvobj_to_wiphy(dvobj);
450 	if (!wiphy) {
451 		ret = -EINVAL;
452 		goto err_out;
453 	}
454 
455 	/* allocate memory */
456 	skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
457 	if (!skb) {
458 		ret = -ENOMEM;
459 		goto err_out;
460 	}
461 
462 	/* create the message headers */
463 	msg_header = genlmsg_put(skb, 0, 0, &nlrtw_genl_family, 0, NLRTW_CMD_RADIO_OPMODE);
464 	if (!msg_header) {
465 		ret = -ENOBUFS;
466 		goto err_out;
467 	}
468 
469 	/* add attributes */
470 	ret = nla_put_string(skb, NLRTW_ATTR_WIPHY_NAME, wiphy_name(wiphy));
471 	if (ret)
472 		goto err_out;
473 
474 	ret = nla_put_u8(skb, NLRTW_ATTR_OP_CLASS, rfctl->op_class);
475 	if (ret != 0)
476 		goto err_out;
477 
478 	ret = nla_put_u8(skb, NLRTW_ATTR_OP_CHANNEL, rfctl->op_ch);
479 	if (ret != 0)
480 		goto err_out;
481 
482 	*((s16 *)&op_txpwr_max_u16) = rfctl->op_txpwr_max;
483 	ret = nla_put_u16(skb, NLRTW_ATTR_OP_TXPWR_MAX, op_txpwr_max_u16);
484 	if (ret != 0)
485 		goto err_out;
486 
487 	if (0)
488 		RTW_INFO("radio: %u,%u %d\n", rfctl->op_class, rfctl->op_ch, rfctl->op_txpwr_max);
489 
490 	nl_if_opmodes = nla_nest_start(skb, NLRTW_ATTR_IF_OPMODES);
491 	if (!nl_if_opmodes) {
492 		ret = -ENOBUFS;
493 		goto err_out;
494 	}
495 
496 	for (i = 0; i < dvobj->iface_nums; i++) {
497 		if (!dvobj->padapters[i])
498 			continue;
499 		iface = dvobj->padapters[i];
500 
501 		if (!rfctl->if_op_class[i] || !rfctl->if_op_ch[i])
502 			continue;
503 
504 		if (0)
505 			RTW_INFO(ADPT_FMT": %u,%u\n", ADPT_ARG(iface), rfctl->if_op_class[i], rfctl->if_op_ch[i]);
506 
507 		nl_if_opmode = nla_nest_start(skb, i + 1);
508 		if (!nl_if_opmode) {
509 			ret = -ENOBUFS;
510 			goto err_out;
511 		}
512 
513 		ret = nla_put(skb, NLRTW_IF_OPMODE_MACADDR, ETH_ALEN, adapter_mac_addr(iface));
514 		if (ret != 0)
515 			goto err_out;
516 
517 		ret = nla_put_u8(skb, NLRTW_IF_OPMODE_OP_CLASS, rfctl->if_op_class[i]);
518 		if (ret != 0)
519 			goto err_out;
520 
521 		ret = nla_put_u8(skb, NLRTW_IF_OPMODE_OP_CHANNEL, rfctl->if_op_ch[i]);
522 		if (ret != 0)
523 			goto err_out;
524 
525 		nla_nest_end(skb, nl_if_opmode);
526 	}
527 
528 	nla_nest_end(skb, nl_if_opmodes);
529 
530 	/* finalize the message */
531 	genlmsg_end(skb, msg_header);
532 
533 	ret = nlrtw_multicast(&nlrtw_genl_family, skb, 0, NLRTW_MCGRP_DEFAULT, GFP_KERNEL);
534 	if (ret == -ESRCH) {
535 		RTW_DBG(FUNC_WIPHY_FMT" return -ESRCH(No such process)."
536 			 " Maybe no process waits for this msg\n", FUNC_WIPHY_ARG(wiphy));
537 		return ret;
538 	} else if (ret != 0) {
539 		RTW_WARN(FUNC_WIPHY_FMT" return %d\n", FUNC_WIPHY_ARG(wiphy), ret);
540 		return ret;
541 	}
542 
543 	return 0;
544 
545 err_out:
546 	if (skb)
547 		nlmsg_free(skb);
548 	return ret;
549 }
550 
rtw_nlrtw_init(void)551 int rtw_nlrtw_init(void)
552 {
553 	int err;
554 
555 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
556 	err = genl_register_family(&nlrtw_genl_family);
557 	if (err)
558 		return err;
559 #else
560 	err = genl_register_family_with_ops(&nlrtw_genl_family, nlrtw_genl_ops, ARRAY_SIZE(nlrtw_genl_ops));
561 	if (err)
562 		return err;
563 
564 	err = genl_register_mc_group(&nlrtw_genl_family, &nlrtw_genl_mcgrp[0]);
565 	if (err) {
566 		genl_unregister_family(&nlrtw_genl_family);
567 		return err;
568 	}
569 #endif
570 	RTW_INFO("[%s] %s\n", __func__, nlrtw_genl_family.name);
571 	return 0;
572 }
573 
rtw_nlrtw_deinit(void)574 int rtw_nlrtw_deinit(void)
575 {
576 	int err;
577 
578 	err = genl_unregister_family(&nlrtw_genl_family);
579 	RTW_INFO("[%s] err = %d\n", __func__, err);
580 
581 	return err;
582 }
583 #endif /* CONFIG_RTW_NLRTW */
584