18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Linux WiMAX
48c2ecf20Sopenharmony_ci * Initialization, addition and removal of wimax devices
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright (C) 2005-2006 Intel Corporation <linux-wimax@intel.com>
78c2ecf20Sopenharmony_ci * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * This implements:
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci *   - basic life cycle of 'struct wimax_dev' [wimax_dev_*()]; on
128c2ecf20Sopenharmony_ci *     addition/registration initialize all subfields and allocate
138c2ecf20Sopenharmony_ci *     generic netlink resources for user space communication. On
148c2ecf20Sopenharmony_ci *     removal/unregistration, undo all that.
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci *   - device state machine [wimax_state_change()] and support to send
178c2ecf20Sopenharmony_ci *     reports to user space when the state changes
188c2ecf20Sopenharmony_ci *     [wimax_gnl_re_state_change*()].
198c2ecf20Sopenharmony_ci *
208c2ecf20Sopenharmony_ci * See include/net/wimax.h for rationales and design.
218c2ecf20Sopenharmony_ci *
228c2ecf20Sopenharmony_ci * ROADMAP
238c2ecf20Sopenharmony_ci *
248c2ecf20Sopenharmony_ci * [__]wimax_state_change()     Called by drivers to update device's state
258c2ecf20Sopenharmony_ci *   wimax_gnl_re_state_change_alloc()
268c2ecf20Sopenharmony_ci *   wimax_gnl_re_state_change_send()
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci * wimax_dev_init()	        Init a device
298c2ecf20Sopenharmony_ci * wimax_dev_add()              Register
308c2ecf20Sopenharmony_ci *   wimax_rfkill_add()
318c2ecf20Sopenharmony_ci *   wimax_gnl_add()            Register all the generic netlink resources.
328c2ecf20Sopenharmony_ci *   wimax_id_table_add()
338c2ecf20Sopenharmony_ci * wimax_dev_rm()               Unregister
348c2ecf20Sopenharmony_ci *   wimax_id_table_rm()
358c2ecf20Sopenharmony_ci *   wimax_gnl_rm()
368c2ecf20Sopenharmony_ci *   wimax_rfkill_rm()
378c2ecf20Sopenharmony_ci */
388c2ecf20Sopenharmony_ci#include <linux/device.h>
398c2ecf20Sopenharmony_ci#include <linux/gfp.h>
408c2ecf20Sopenharmony_ci#include <net/genetlink.h>
418c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
428c2ecf20Sopenharmony_ci#include <linux/wimax.h>
438c2ecf20Sopenharmony_ci#include <linux/module.h>
448c2ecf20Sopenharmony_ci#include "wimax-internal.h"
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#define D_SUBMODULE stack
488c2ecf20Sopenharmony_ci#include "debug-levels.h"
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic char wimax_debug_params[128];
518c2ecf20Sopenharmony_cimodule_param_string(debug, wimax_debug_params, sizeof(wimax_debug_params),
528c2ecf20Sopenharmony_ci		    0644);
538c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug,
548c2ecf20Sopenharmony_ci		 "String of space-separated NAME:VALUE pairs, where NAMEs "
558c2ecf20Sopenharmony_ci		 "are the different debug submodules and VALUE are the "
568c2ecf20Sopenharmony_ci		 "initial debug value to set.");
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci/*
598c2ecf20Sopenharmony_ci * Authoritative source for the RE_STATE_CHANGE attribute policy
608c2ecf20Sopenharmony_ci *
618c2ecf20Sopenharmony_ci * We don't really use it here, but /me likes to keep the definition
628c2ecf20Sopenharmony_ci * close to where the data is generated.
638c2ecf20Sopenharmony_ci */
648c2ecf20Sopenharmony_ci/*
658c2ecf20Sopenharmony_cistatic const struct nla_policy wimax_gnl_re_status_change[WIMAX_GNL_ATTR_MAX + 1] = {
668c2ecf20Sopenharmony_ci	[WIMAX_GNL_STCH_STATE_OLD] = { .type = NLA_U8 },
678c2ecf20Sopenharmony_ci	[WIMAX_GNL_STCH_STATE_NEW] = { .type = NLA_U8 },
688c2ecf20Sopenharmony_ci};
698c2ecf20Sopenharmony_ci*/
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci/*
738c2ecf20Sopenharmony_ci * Allocate a Report State Change message
748c2ecf20Sopenharmony_ci *
758c2ecf20Sopenharmony_ci * @header: save it, you need it for _send()
768c2ecf20Sopenharmony_ci *
778c2ecf20Sopenharmony_ci * Creates and fills a basic state change message; different code
788c2ecf20Sopenharmony_ci * paths can then add more attributes to the message as needed.
798c2ecf20Sopenharmony_ci *
808c2ecf20Sopenharmony_ci * Use wimax_gnl_re_state_change_send() to send the returned skb.
818c2ecf20Sopenharmony_ci *
828c2ecf20Sopenharmony_ci * Returns: skb with the genl message if ok, IS_ERR() ptr on error
838c2ecf20Sopenharmony_ci *     with an errno code.
848c2ecf20Sopenharmony_ci */
858c2ecf20Sopenharmony_cistatic
868c2ecf20Sopenharmony_cistruct sk_buff *wimax_gnl_re_state_change_alloc(
878c2ecf20Sopenharmony_ci	struct wimax_dev *wimax_dev,
888c2ecf20Sopenharmony_ci	enum wimax_st new_state, enum wimax_st old_state,
898c2ecf20Sopenharmony_ci	void **header)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	int result;
928c2ecf20Sopenharmony_ci	struct device *dev = wimax_dev_to_dev(wimax_dev);
938c2ecf20Sopenharmony_ci	void *data;
948c2ecf20Sopenharmony_ci	struct sk_buff *report_skb;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	d_fnstart(3, dev, "(wimax_dev %p new_state %u old_state %u)\n",
978c2ecf20Sopenharmony_ci		  wimax_dev, new_state, old_state);
988c2ecf20Sopenharmony_ci	result = -ENOMEM;
998c2ecf20Sopenharmony_ci	report_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
1008c2ecf20Sopenharmony_ci	if (report_skb == NULL) {
1018c2ecf20Sopenharmony_ci		dev_err(dev, "RE_STCH: can't create message\n");
1028c2ecf20Sopenharmony_ci		goto error_new;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci	/* FIXME: sending a group ID as the seq is wrong */
1058c2ecf20Sopenharmony_ci	data = genlmsg_put(report_skb, 0, wimax_gnl_family.mcgrp_offset,
1068c2ecf20Sopenharmony_ci			   &wimax_gnl_family, 0, WIMAX_GNL_RE_STATE_CHANGE);
1078c2ecf20Sopenharmony_ci	if (data == NULL) {
1088c2ecf20Sopenharmony_ci		dev_err(dev, "RE_STCH: can't put data into message\n");
1098c2ecf20Sopenharmony_ci		goto error_put;
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci	*header = data;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	result = nla_put_u8(report_skb, WIMAX_GNL_STCH_STATE_OLD, old_state);
1148c2ecf20Sopenharmony_ci	if (result < 0) {
1158c2ecf20Sopenharmony_ci		dev_err(dev, "RE_STCH: Error adding OLD attr: %d\n", result);
1168c2ecf20Sopenharmony_ci		goto error_put;
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci	result = nla_put_u8(report_skb, WIMAX_GNL_STCH_STATE_NEW, new_state);
1198c2ecf20Sopenharmony_ci	if (result < 0) {
1208c2ecf20Sopenharmony_ci		dev_err(dev, "RE_STCH: Error adding NEW attr: %d\n", result);
1218c2ecf20Sopenharmony_ci		goto error_put;
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci	result = nla_put_u32(report_skb, WIMAX_GNL_STCH_IFIDX,
1248c2ecf20Sopenharmony_ci			     wimax_dev->net_dev->ifindex);
1258c2ecf20Sopenharmony_ci	if (result < 0) {
1268c2ecf20Sopenharmony_ci		dev_err(dev, "RE_STCH: Error adding IFINDEX attribute\n");
1278c2ecf20Sopenharmony_ci		goto error_put;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci	d_fnend(3, dev, "(wimax_dev %p new_state %u old_state %u) = %p\n",
1308c2ecf20Sopenharmony_ci		wimax_dev, new_state, old_state, report_skb);
1318c2ecf20Sopenharmony_ci	return report_skb;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cierror_put:
1348c2ecf20Sopenharmony_ci	nlmsg_free(report_skb);
1358c2ecf20Sopenharmony_cierror_new:
1368c2ecf20Sopenharmony_ci	d_fnend(3, dev, "(wimax_dev %p new_state %u old_state %u) = %d\n",
1378c2ecf20Sopenharmony_ci		wimax_dev, new_state, old_state, result);
1388c2ecf20Sopenharmony_ci	return ERR_PTR(result);
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci/*
1438c2ecf20Sopenharmony_ci * Send a Report State Change message (as created with _alloc).
1448c2ecf20Sopenharmony_ci *
1458c2ecf20Sopenharmony_ci * @report_skb: as returned by wimax_gnl_re_state_change_alloc()
1468c2ecf20Sopenharmony_ci * @header: as returned by wimax_gnl_re_state_change_alloc()
1478c2ecf20Sopenharmony_ci *
1488c2ecf20Sopenharmony_ci * Returns: 0 if ok, < 0 errno code on error.
1498c2ecf20Sopenharmony_ci *
1508c2ecf20Sopenharmony_ci * If the message is  NULL, pretend it didn't happen.
1518c2ecf20Sopenharmony_ci */
1528c2ecf20Sopenharmony_cistatic
1538c2ecf20Sopenharmony_ciint wimax_gnl_re_state_change_send(
1548c2ecf20Sopenharmony_ci	struct wimax_dev *wimax_dev, struct sk_buff *report_skb,
1558c2ecf20Sopenharmony_ci	void *header)
1568c2ecf20Sopenharmony_ci{
1578c2ecf20Sopenharmony_ci	int result = 0;
1588c2ecf20Sopenharmony_ci	struct device *dev = wimax_dev_to_dev(wimax_dev);
1598c2ecf20Sopenharmony_ci	d_fnstart(3, dev, "(wimax_dev %p report_skb %p)\n",
1608c2ecf20Sopenharmony_ci		  wimax_dev, report_skb);
1618c2ecf20Sopenharmony_ci	if (report_skb == NULL) {
1628c2ecf20Sopenharmony_ci		result = -ENOMEM;
1638c2ecf20Sopenharmony_ci		goto out;
1648c2ecf20Sopenharmony_ci	}
1658c2ecf20Sopenharmony_ci	genlmsg_end(report_skb, header);
1668c2ecf20Sopenharmony_ci	genlmsg_multicast(&wimax_gnl_family, report_skb, 0, 0, GFP_KERNEL);
1678c2ecf20Sopenharmony_ciout:
1688c2ecf20Sopenharmony_ci	d_fnend(3, dev, "(wimax_dev %p report_skb %p) = %d\n",
1698c2ecf20Sopenharmony_ci		wimax_dev, report_skb, result);
1708c2ecf20Sopenharmony_ci	return result;
1718c2ecf20Sopenharmony_ci}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic
1758c2ecf20Sopenharmony_civoid __check_new_state(enum wimax_st old_state, enum wimax_st new_state,
1768c2ecf20Sopenharmony_ci		       unsigned int allowed_states_bm)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	if (WARN_ON(((1 << new_state) & allowed_states_bm) == 0)) {
1798c2ecf20Sopenharmony_ci		pr_err("SW BUG! Forbidden state change %u -> %u\n",
1808c2ecf20Sopenharmony_ci		       old_state, new_state);
1818c2ecf20Sopenharmony_ci	}
1828c2ecf20Sopenharmony_ci}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci/*
1868c2ecf20Sopenharmony_ci * Set the current state of a WiMAX device [unlocking version of
1878c2ecf20Sopenharmony_ci * wimax_state_change().
1888c2ecf20Sopenharmony_ci */
1898c2ecf20Sopenharmony_civoid __wimax_state_change(struct wimax_dev *wimax_dev, enum wimax_st new_state)
1908c2ecf20Sopenharmony_ci{
1918c2ecf20Sopenharmony_ci	struct device *dev = wimax_dev_to_dev(wimax_dev);
1928c2ecf20Sopenharmony_ci	enum wimax_st old_state = wimax_dev->state;
1938c2ecf20Sopenharmony_ci	struct sk_buff *stch_skb;
1948c2ecf20Sopenharmony_ci	void *header;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	d_fnstart(3, dev, "(wimax_dev %p new_state %u [old %u])\n",
1978c2ecf20Sopenharmony_ci		  wimax_dev, new_state, old_state);
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	if (WARN_ON(new_state >= __WIMAX_ST_INVALID)) {
2008c2ecf20Sopenharmony_ci		dev_err(dev, "SW BUG: requesting invalid state %u\n",
2018c2ecf20Sopenharmony_ci			new_state);
2028c2ecf20Sopenharmony_ci		goto out;
2038c2ecf20Sopenharmony_ci	}
2048c2ecf20Sopenharmony_ci	if (old_state == new_state)
2058c2ecf20Sopenharmony_ci		goto out;
2068c2ecf20Sopenharmony_ci	header = NULL;	/* gcc complains? can't grok why */
2078c2ecf20Sopenharmony_ci	stch_skb = wimax_gnl_re_state_change_alloc(
2088c2ecf20Sopenharmony_ci		wimax_dev, new_state, old_state, &header);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	/* Verify the state transition and do exit-from-state actions */
2118c2ecf20Sopenharmony_ci	switch (old_state) {
2128c2ecf20Sopenharmony_ci	case __WIMAX_ST_NULL:
2138c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state,
2148c2ecf20Sopenharmony_ci				  1 << WIMAX_ST_DOWN);
2158c2ecf20Sopenharmony_ci		break;
2168c2ecf20Sopenharmony_ci	case WIMAX_ST_DOWN:
2178c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state,
2188c2ecf20Sopenharmony_ci				  1 << __WIMAX_ST_QUIESCING
2198c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_UNINITIALIZED
2208c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_RADIO_OFF);
2218c2ecf20Sopenharmony_ci		break;
2228c2ecf20Sopenharmony_ci	case __WIMAX_ST_QUIESCING:
2238c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state, 1 << WIMAX_ST_DOWN);
2248c2ecf20Sopenharmony_ci		break;
2258c2ecf20Sopenharmony_ci	case WIMAX_ST_UNINITIALIZED:
2268c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state,
2278c2ecf20Sopenharmony_ci				  1 << __WIMAX_ST_QUIESCING
2288c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_RADIO_OFF);
2298c2ecf20Sopenharmony_ci		break;
2308c2ecf20Sopenharmony_ci	case WIMAX_ST_RADIO_OFF:
2318c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state,
2328c2ecf20Sopenharmony_ci				  1 << __WIMAX_ST_QUIESCING
2338c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_READY);
2348c2ecf20Sopenharmony_ci		break;
2358c2ecf20Sopenharmony_ci	case WIMAX_ST_READY:
2368c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state,
2378c2ecf20Sopenharmony_ci				  1 << __WIMAX_ST_QUIESCING
2388c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_RADIO_OFF
2398c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_SCANNING
2408c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_CONNECTING
2418c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_CONNECTED);
2428c2ecf20Sopenharmony_ci		break;
2438c2ecf20Sopenharmony_ci	case WIMAX_ST_SCANNING:
2448c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state,
2458c2ecf20Sopenharmony_ci				  1 << __WIMAX_ST_QUIESCING
2468c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_RADIO_OFF
2478c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_READY
2488c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_CONNECTING
2498c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_CONNECTED);
2508c2ecf20Sopenharmony_ci		break;
2518c2ecf20Sopenharmony_ci	case WIMAX_ST_CONNECTING:
2528c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state,
2538c2ecf20Sopenharmony_ci				  1 << __WIMAX_ST_QUIESCING
2548c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_RADIO_OFF
2558c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_READY
2568c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_SCANNING
2578c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_CONNECTED);
2588c2ecf20Sopenharmony_ci		break;
2598c2ecf20Sopenharmony_ci	case WIMAX_ST_CONNECTED:
2608c2ecf20Sopenharmony_ci		__check_new_state(old_state, new_state,
2618c2ecf20Sopenharmony_ci				  1 << __WIMAX_ST_QUIESCING
2628c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_RADIO_OFF
2638c2ecf20Sopenharmony_ci				  | 1 << WIMAX_ST_READY);
2648c2ecf20Sopenharmony_ci		netif_tx_disable(wimax_dev->net_dev);
2658c2ecf20Sopenharmony_ci		netif_carrier_off(wimax_dev->net_dev);
2668c2ecf20Sopenharmony_ci		break;
2678c2ecf20Sopenharmony_ci	case __WIMAX_ST_INVALID:
2688c2ecf20Sopenharmony_ci	default:
2698c2ecf20Sopenharmony_ci		dev_err(dev, "SW BUG: wimax_dev %p is in unknown state %u\n",
2708c2ecf20Sopenharmony_ci			wimax_dev, wimax_dev->state);
2718c2ecf20Sopenharmony_ci		WARN_ON(1);
2728c2ecf20Sopenharmony_ci		goto out;
2738c2ecf20Sopenharmony_ci	}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	/* Execute the actions of entry to the new state */
2768c2ecf20Sopenharmony_ci	switch (new_state) {
2778c2ecf20Sopenharmony_ci	case __WIMAX_ST_NULL:
2788c2ecf20Sopenharmony_ci		dev_err(dev, "SW BUG: wimax_dev %p entering NULL state "
2798c2ecf20Sopenharmony_ci			"from %u\n", wimax_dev, wimax_dev->state);
2808c2ecf20Sopenharmony_ci		WARN_ON(1);		/* Nobody can enter this state */
2818c2ecf20Sopenharmony_ci		break;
2828c2ecf20Sopenharmony_ci	case WIMAX_ST_DOWN:
2838c2ecf20Sopenharmony_ci		break;
2848c2ecf20Sopenharmony_ci	case __WIMAX_ST_QUIESCING:
2858c2ecf20Sopenharmony_ci		break;
2868c2ecf20Sopenharmony_ci	case WIMAX_ST_UNINITIALIZED:
2878c2ecf20Sopenharmony_ci		break;
2888c2ecf20Sopenharmony_ci	case WIMAX_ST_RADIO_OFF:
2898c2ecf20Sopenharmony_ci		break;
2908c2ecf20Sopenharmony_ci	case WIMAX_ST_READY:
2918c2ecf20Sopenharmony_ci		break;
2928c2ecf20Sopenharmony_ci	case WIMAX_ST_SCANNING:
2938c2ecf20Sopenharmony_ci		break;
2948c2ecf20Sopenharmony_ci	case WIMAX_ST_CONNECTING:
2958c2ecf20Sopenharmony_ci		break;
2968c2ecf20Sopenharmony_ci	case WIMAX_ST_CONNECTED:
2978c2ecf20Sopenharmony_ci		netif_carrier_on(wimax_dev->net_dev);
2988c2ecf20Sopenharmony_ci		netif_wake_queue(wimax_dev->net_dev);
2998c2ecf20Sopenharmony_ci		break;
3008c2ecf20Sopenharmony_ci	case __WIMAX_ST_INVALID:
3018c2ecf20Sopenharmony_ci	default:
3028c2ecf20Sopenharmony_ci		BUG();
3038c2ecf20Sopenharmony_ci	}
3048c2ecf20Sopenharmony_ci	__wimax_state_set(wimax_dev, new_state);
3058c2ecf20Sopenharmony_ci	if (!IS_ERR(stch_skb))
3068c2ecf20Sopenharmony_ci		wimax_gnl_re_state_change_send(wimax_dev, stch_skb, header);
3078c2ecf20Sopenharmony_ciout:
3088c2ecf20Sopenharmony_ci	d_fnend(3, dev, "(wimax_dev %p new_state %u [old %u]) = void\n",
3098c2ecf20Sopenharmony_ci		wimax_dev, new_state, old_state);
3108c2ecf20Sopenharmony_ci}
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci/**
3148c2ecf20Sopenharmony_ci * wimax_state_change - Set the current state of a WiMAX device
3158c2ecf20Sopenharmony_ci *
3168c2ecf20Sopenharmony_ci * @wimax_dev: WiMAX device descriptor (properly referenced)
3178c2ecf20Sopenharmony_ci * @new_state: New state to switch to
3188c2ecf20Sopenharmony_ci *
3198c2ecf20Sopenharmony_ci * This implements the state changes for the wimax devices. It will
3208c2ecf20Sopenharmony_ci *
3218c2ecf20Sopenharmony_ci * - verify that the state transition is legal (for now it'll just
3228c2ecf20Sopenharmony_ci *   print a warning if not) according to the table in
3238c2ecf20Sopenharmony_ci *   linux/wimax.h's documentation for 'enum wimax_st'.
3248c2ecf20Sopenharmony_ci *
3258c2ecf20Sopenharmony_ci * - perform the actions needed for leaving the current state and
3268c2ecf20Sopenharmony_ci *   whichever are needed for entering the new state.
3278c2ecf20Sopenharmony_ci *
3288c2ecf20Sopenharmony_ci * - issue a report to user space indicating the new state (and an
3298c2ecf20Sopenharmony_ci *   optional payload with information about the new state).
3308c2ecf20Sopenharmony_ci *
3318c2ecf20Sopenharmony_ci * NOTE: @wimax_dev must be locked
3328c2ecf20Sopenharmony_ci */
3338c2ecf20Sopenharmony_civoid wimax_state_change(struct wimax_dev *wimax_dev, enum wimax_st new_state)
3348c2ecf20Sopenharmony_ci{
3358c2ecf20Sopenharmony_ci	/*
3368c2ecf20Sopenharmony_ci	 * A driver cannot take the wimax_dev out of the
3378c2ecf20Sopenharmony_ci	 * __WIMAX_ST_NULL state unless by calling wimax_dev_add(). If
3388c2ecf20Sopenharmony_ci	 * the wimax_dev's state is still NULL, we ignore any request
3398c2ecf20Sopenharmony_ci	 * to change its state because it means it hasn't been yet
3408c2ecf20Sopenharmony_ci	 * registered.
3418c2ecf20Sopenharmony_ci	 *
3428c2ecf20Sopenharmony_ci	 * There is no need to complain about it, as routines that
3438c2ecf20Sopenharmony_ci	 * call this might be shared from different code paths that
3448c2ecf20Sopenharmony_ci	 * are called before or after wimax_dev_add() has done its
3458c2ecf20Sopenharmony_ci	 * job.
3468c2ecf20Sopenharmony_ci	 */
3478c2ecf20Sopenharmony_ci	mutex_lock(&wimax_dev->mutex);
3488c2ecf20Sopenharmony_ci	if (wimax_dev->state > __WIMAX_ST_NULL)
3498c2ecf20Sopenharmony_ci		__wimax_state_change(wimax_dev, new_state);
3508c2ecf20Sopenharmony_ci	mutex_unlock(&wimax_dev->mutex);
3518c2ecf20Sopenharmony_ci}
3528c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wimax_state_change);
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci/**
3568c2ecf20Sopenharmony_ci * wimax_state_get() - Return the current state of a WiMAX device
3578c2ecf20Sopenharmony_ci *
3588c2ecf20Sopenharmony_ci * @wimax_dev: WiMAX device descriptor
3598c2ecf20Sopenharmony_ci *
3608c2ecf20Sopenharmony_ci * Returns: Current state of the device according to its driver.
3618c2ecf20Sopenharmony_ci */
3628c2ecf20Sopenharmony_cienum wimax_st wimax_state_get(struct wimax_dev *wimax_dev)
3638c2ecf20Sopenharmony_ci{
3648c2ecf20Sopenharmony_ci	enum wimax_st state;
3658c2ecf20Sopenharmony_ci	mutex_lock(&wimax_dev->mutex);
3668c2ecf20Sopenharmony_ci	state = wimax_dev->state;
3678c2ecf20Sopenharmony_ci	mutex_unlock(&wimax_dev->mutex);
3688c2ecf20Sopenharmony_ci	return state;
3698c2ecf20Sopenharmony_ci}
3708c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wimax_state_get);
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci/**
3748c2ecf20Sopenharmony_ci * wimax_dev_init - initialize a newly allocated instance
3758c2ecf20Sopenharmony_ci *
3768c2ecf20Sopenharmony_ci * @wimax_dev: WiMAX device descriptor to initialize.
3778c2ecf20Sopenharmony_ci *
3788c2ecf20Sopenharmony_ci * Initializes fields of a freshly allocated @wimax_dev instance. This
3798c2ecf20Sopenharmony_ci * function assumes that after allocation, the memory occupied by
3808c2ecf20Sopenharmony_ci * @wimax_dev was zeroed.
3818c2ecf20Sopenharmony_ci */
3828c2ecf20Sopenharmony_civoid wimax_dev_init(struct wimax_dev *wimax_dev)
3838c2ecf20Sopenharmony_ci{
3848c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&wimax_dev->id_table_node);
3858c2ecf20Sopenharmony_ci	__wimax_state_set(wimax_dev, __WIMAX_ST_NULL);
3868c2ecf20Sopenharmony_ci	mutex_init(&wimax_dev->mutex);
3878c2ecf20Sopenharmony_ci	mutex_init(&wimax_dev->mutex_reset);
3888c2ecf20Sopenharmony_ci}
3898c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wimax_dev_init);
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_cistatic const struct nla_policy wimax_gnl_policy[WIMAX_GNL_ATTR_MAX + 1] = {
3928c2ecf20Sopenharmony_ci	[WIMAX_GNL_RESET_IFIDX] = { .type = NLA_U32, },
3938c2ecf20Sopenharmony_ci	[WIMAX_GNL_RFKILL_IFIDX] = { .type = NLA_U32, },
3948c2ecf20Sopenharmony_ci	[WIMAX_GNL_RFKILL_STATE] = {
3958c2ecf20Sopenharmony_ci		.type = NLA_U32		/* enum wimax_rf_state */
3968c2ecf20Sopenharmony_ci	},
3978c2ecf20Sopenharmony_ci	[WIMAX_GNL_STGET_IFIDX] = { .type = NLA_U32, },
3988c2ecf20Sopenharmony_ci	[WIMAX_GNL_MSG_IFIDX] = { .type = NLA_U32, },
3998c2ecf20Sopenharmony_ci	[WIMAX_GNL_MSG_DATA] = {
4008c2ecf20Sopenharmony_ci		.type = NLA_UNSPEC,	/* libnl doesn't grok BINARY yet */
4018c2ecf20Sopenharmony_ci	},
4028c2ecf20Sopenharmony_ci};
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic const struct genl_small_ops wimax_gnl_ops[] = {
4058c2ecf20Sopenharmony_ci	{
4068c2ecf20Sopenharmony_ci		.cmd = WIMAX_GNL_OP_MSG_FROM_USER,
4078c2ecf20Sopenharmony_ci		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
4088c2ecf20Sopenharmony_ci		.flags = GENL_ADMIN_PERM,
4098c2ecf20Sopenharmony_ci		.doit = wimax_gnl_doit_msg_from_user,
4108c2ecf20Sopenharmony_ci	},
4118c2ecf20Sopenharmony_ci	{
4128c2ecf20Sopenharmony_ci		.cmd = WIMAX_GNL_OP_RESET,
4138c2ecf20Sopenharmony_ci		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
4148c2ecf20Sopenharmony_ci		.flags = GENL_ADMIN_PERM,
4158c2ecf20Sopenharmony_ci		.doit = wimax_gnl_doit_reset,
4168c2ecf20Sopenharmony_ci	},
4178c2ecf20Sopenharmony_ci	{
4188c2ecf20Sopenharmony_ci		.cmd = WIMAX_GNL_OP_RFKILL,
4198c2ecf20Sopenharmony_ci		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
4208c2ecf20Sopenharmony_ci		.flags = GENL_ADMIN_PERM,
4218c2ecf20Sopenharmony_ci		.doit = wimax_gnl_doit_rfkill,
4228c2ecf20Sopenharmony_ci	},
4238c2ecf20Sopenharmony_ci	{
4248c2ecf20Sopenharmony_ci		.cmd = WIMAX_GNL_OP_STATE_GET,
4258c2ecf20Sopenharmony_ci		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
4268c2ecf20Sopenharmony_ci		.flags = GENL_ADMIN_PERM,
4278c2ecf20Sopenharmony_ci		.doit = wimax_gnl_doit_state_get,
4288c2ecf20Sopenharmony_ci	},
4298c2ecf20Sopenharmony_ci};
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_cistatic
4338c2ecf20Sopenharmony_cisize_t wimax_addr_scnprint(char *addr_str, size_t addr_str_size,
4348c2ecf20Sopenharmony_ci			   unsigned char *addr, size_t addr_len)
4358c2ecf20Sopenharmony_ci{
4368c2ecf20Sopenharmony_ci	unsigned int cnt, total;
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci	for (total = cnt = 0; cnt < addr_len; cnt++)
4398c2ecf20Sopenharmony_ci		total += scnprintf(addr_str + total, addr_str_size - total,
4408c2ecf20Sopenharmony_ci				   "%02x%c", addr[cnt],
4418c2ecf20Sopenharmony_ci				   cnt == addr_len - 1 ? '\0' : ':');
4428c2ecf20Sopenharmony_ci	return total;
4438c2ecf20Sopenharmony_ci}
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci/**
4478c2ecf20Sopenharmony_ci * wimax_dev_add - Register a new WiMAX device
4488c2ecf20Sopenharmony_ci *
4498c2ecf20Sopenharmony_ci * @wimax_dev: WiMAX device descriptor (as embedded in your @net_dev's
4508c2ecf20Sopenharmony_ci *     priv data). You must have called wimax_dev_init() on it before.
4518c2ecf20Sopenharmony_ci *
4528c2ecf20Sopenharmony_ci * @net_dev: net device the @wimax_dev is associated with. The
4538c2ecf20Sopenharmony_ci *     function expects SET_NETDEV_DEV() and register_netdev() were
4548c2ecf20Sopenharmony_ci *     already called on it.
4558c2ecf20Sopenharmony_ci *
4568c2ecf20Sopenharmony_ci * Registers the new WiMAX device, sets up the user-kernel control
4578c2ecf20Sopenharmony_ci * interface (generic netlink) and common WiMAX infrastructure.
4588c2ecf20Sopenharmony_ci *
4598c2ecf20Sopenharmony_ci * Note that the parts that will allow interaction with user space are
4608c2ecf20Sopenharmony_ci * setup at the very end, when the rest is in place, as once that
4618c2ecf20Sopenharmony_ci * happens, the driver might get user space control requests via
4628c2ecf20Sopenharmony_ci * netlink or from debugfs that might translate into calls into
4638c2ecf20Sopenharmony_ci * wimax_dev->op_*().
4648c2ecf20Sopenharmony_ci */
4658c2ecf20Sopenharmony_ciint wimax_dev_add(struct wimax_dev *wimax_dev, struct net_device *net_dev)
4668c2ecf20Sopenharmony_ci{
4678c2ecf20Sopenharmony_ci	int result;
4688c2ecf20Sopenharmony_ci	struct device *dev = net_dev->dev.parent;
4698c2ecf20Sopenharmony_ci	char addr_str[32];
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	d_fnstart(3, dev, "(wimax_dev %p net_dev %p)\n", wimax_dev, net_dev);
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	/* Do the RFKILL setup before locking, as RFKILL will call
4748c2ecf20Sopenharmony_ci	 * into our functions.
4758c2ecf20Sopenharmony_ci	 */
4768c2ecf20Sopenharmony_ci	wimax_dev->net_dev = net_dev;
4778c2ecf20Sopenharmony_ci	result = wimax_rfkill_add(wimax_dev);
4788c2ecf20Sopenharmony_ci	if (result < 0)
4798c2ecf20Sopenharmony_ci		goto error_rfkill_add;
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci	/* Set up user-space interaction */
4828c2ecf20Sopenharmony_ci	mutex_lock(&wimax_dev->mutex);
4838c2ecf20Sopenharmony_ci	wimax_id_table_add(wimax_dev);
4848c2ecf20Sopenharmony_ci	wimax_debugfs_add(wimax_dev);
4858c2ecf20Sopenharmony_ci
4868c2ecf20Sopenharmony_ci	__wimax_state_set(wimax_dev, WIMAX_ST_DOWN);
4878c2ecf20Sopenharmony_ci	mutex_unlock(&wimax_dev->mutex);
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	wimax_addr_scnprint(addr_str, sizeof(addr_str),
4908c2ecf20Sopenharmony_ci			    net_dev->dev_addr, net_dev->addr_len);
4918c2ecf20Sopenharmony_ci	dev_err(dev, "WiMAX interface %s (%s) ready\n",
4928c2ecf20Sopenharmony_ci		net_dev->name, addr_str);
4938c2ecf20Sopenharmony_ci	d_fnend(3, dev, "(wimax_dev %p net_dev %p) = 0\n", wimax_dev, net_dev);
4948c2ecf20Sopenharmony_ci	return 0;
4958c2ecf20Sopenharmony_ci
4968c2ecf20Sopenharmony_cierror_rfkill_add:
4978c2ecf20Sopenharmony_ci	d_fnend(3, dev, "(wimax_dev %p net_dev %p) = %d\n",
4988c2ecf20Sopenharmony_ci		wimax_dev, net_dev, result);
4998c2ecf20Sopenharmony_ci	return result;
5008c2ecf20Sopenharmony_ci}
5018c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wimax_dev_add);
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci/**
5058c2ecf20Sopenharmony_ci * wimax_dev_rm - Unregister an existing WiMAX device
5068c2ecf20Sopenharmony_ci *
5078c2ecf20Sopenharmony_ci * @wimax_dev: WiMAX device descriptor
5088c2ecf20Sopenharmony_ci *
5098c2ecf20Sopenharmony_ci * Unregisters a WiMAX device previously registered for use with
5108c2ecf20Sopenharmony_ci * wimax_add_rm().
5118c2ecf20Sopenharmony_ci *
5128c2ecf20Sopenharmony_ci * IMPORTANT! Must call before calling unregister_netdev().
5138c2ecf20Sopenharmony_ci *
5148c2ecf20Sopenharmony_ci * After this function returns, you will not get any more user space
5158c2ecf20Sopenharmony_ci * control requests (via netlink or debugfs) and thus to wimax_dev->ops.
5168c2ecf20Sopenharmony_ci *
5178c2ecf20Sopenharmony_ci * Reentrancy control is ensured by setting the state to
5188c2ecf20Sopenharmony_ci * %__WIMAX_ST_QUIESCING. rfkill operations coming through
5198c2ecf20Sopenharmony_ci * wimax_*rfkill*() will be stopped by the quiescing state; ops coming
5208c2ecf20Sopenharmony_ci * from the rfkill subsystem will be stopped by the support being
5218c2ecf20Sopenharmony_ci * removed by wimax_rfkill_rm().
5228c2ecf20Sopenharmony_ci */
5238c2ecf20Sopenharmony_civoid wimax_dev_rm(struct wimax_dev *wimax_dev)
5248c2ecf20Sopenharmony_ci{
5258c2ecf20Sopenharmony_ci	d_fnstart(3, NULL, "(wimax_dev %p)\n", wimax_dev);
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	mutex_lock(&wimax_dev->mutex);
5288c2ecf20Sopenharmony_ci	__wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING);
5298c2ecf20Sopenharmony_ci	wimax_debugfs_rm(wimax_dev);
5308c2ecf20Sopenharmony_ci	wimax_id_table_rm(wimax_dev);
5318c2ecf20Sopenharmony_ci	__wimax_state_change(wimax_dev, WIMAX_ST_DOWN);
5328c2ecf20Sopenharmony_ci	mutex_unlock(&wimax_dev->mutex);
5338c2ecf20Sopenharmony_ci	wimax_rfkill_rm(wimax_dev);
5348c2ecf20Sopenharmony_ci	d_fnend(3, NULL, "(wimax_dev %p) = void\n", wimax_dev);
5358c2ecf20Sopenharmony_ci}
5368c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wimax_dev_rm);
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci/* Debug framework control of debug levels */
5408c2ecf20Sopenharmony_cistruct d_level D_LEVEL[] = {
5418c2ecf20Sopenharmony_ci	D_SUBMODULE_DEFINE(debugfs),
5428c2ecf20Sopenharmony_ci	D_SUBMODULE_DEFINE(id_table),
5438c2ecf20Sopenharmony_ci	D_SUBMODULE_DEFINE(op_msg),
5448c2ecf20Sopenharmony_ci	D_SUBMODULE_DEFINE(op_reset),
5458c2ecf20Sopenharmony_ci	D_SUBMODULE_DEFINE(op_rfkill),
5468c2ecf20Sopenharmony_ci	D_SUBMODULE_DEFINE(op_state_get),
5478c2ecf20Sopenharmony_ci	D_SUBMODULE_DEFINE(stack),
5488c2ecf20Sopenharmony_ci};
5498c2ecf20Sopenharmony_cisize_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_cistatic const struct genl_multicast_group wimax_gnl_mcgrps[] = {
5538c2ecf20Sopenharmony_ci	{ .name = "msg", },
5548c2ecf20Sopenharmony_ci};
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_cistruct genl_family wimax_gnl_family __ro_after_init = {
5578c2ecf20Sopenharmony_ci	.name = "WiMAX",
5588c2ecf20Sopenharmony_ci	.version = WIMAX_GNL_VERSION,
5598c2ecf20Sopenharmony_ci	.hdrsize = 0,
5608c2ecf20Sopenharmony_ci	.maxattr = WIMAX_GNL_ATTR_MAX,
5618c2ecf20Sopenharmony_ci	.policy = wimax_gnl_policy,
5628c2ecf20Sopenharmony_ci	.module = THIS_MODULE,
5638c2ecf20Sopenharmony_ci	.small_ops = wimax_gnl_ops,
5648c2ecf20Sopenharmony_ci	.n_small_ops = ARRAY_SIZE(wimax_gnl_ops),
5658c2ecf20Sopenharmony_ci	.mcgrps = wimax_gnl_mcgrps,
5668c2ecf20Sopenharmony_ci	.n_mcgrps = ARRAY_SIZE(wimax_gnl_mcgrps),
5678c2ecf20Sopenharmony_ci};
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci/* Shutdown the wimax stack */
5728c2ecf20Sopenharmony_cistatic
5738c2ecf20Sopenharmony_ciint __init wimax_subsys_init(void)
5748c2ecf20Sopenharmony_ci{
5758c2ecf20Sopenharmony_ci	int result;
5768c2ecf20Sopenharmony_ci
5778c2ecf20Sopenharmony_ci	d_fnstart(4, NULL, "()\n");
5788c2ecf20Sopenharmony_ci	d_parse_params(D_LEVEL, D_LEVEL_SIZE, wimax_debug_params,
5798c2ecf20Sopenharmony_ci		       "wimax.debug");
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci	result = genl_register_family(&wimax_gnl_family);
5828c2ecf20Sopenharmony_ci	if (unlikely(result < 0)) {
5838c2ecf20Sopenharmony_ci		pr_err("cannot register generic netlink family: %d\n", result);
5848c2ecf20Sopenharmony_ci		goto error_register_family;
5858c2ecf20Sopenharmony_ci	}
5868c2ecf20Sopenharmony_ci
5878c2ecf20Sopenharmony_ci	d_fnend(4, NULL, "() = 0\n");
5888c2ecf20Sopenharmony_ci	return 0;
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_cierror_register_family:
5918c2ecf20Sopenharmony_ci	d_fnend(4, NULL, "() = %d\n", result);
5928c2ecf20Sopenharmony_ci	return result;
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ci}
5958c2ecf20Sopenharmony_cimodule_init(wimax_subsys_init);
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ci/* Shutdown the wimax stack */
5998c2ecf20Sopenharmony_cistatic
6008c2ecf20Sopenharmony_civoid __exit wimax_subsys_exit(void)
6018c2ecf20Sopenharmony_ci{
6028c2ecf20Sopenharmony_ci	wimax_id_table_release();
6038c2ecf20Sopenharmony_ci	genl_unregister_family(&wimax_gnl_family);
6048c2ecf20Sopenharmony_ci}
6058c2ecf20Sopenharmony_cimodule_exit(wimax_subsys_exit);
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ciMODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>");
6088c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Linux WiMAX stack");
6098c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
610