 95c9617472
			
		
	
	
	95c9617472
	
	
	
		
			
			Use of "unsigned int" is preferred to bare "unsigned" in net tree. Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
		
			
				
	
	
		
			635 lines
		
	
	
	
		
			18 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			635 lines
		
	
	
	
		
			18 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Linux WiMAX
 | |
|  * Initialization, addition and removal of wimax devices
 | |
|  *
 | |
|  *
 | |
|  * Copyright (C) 2005-2006 Intel Corporation <linux-wimax@intel.com>
 | |
|  * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU General Public License version
 | |
|  * 2 as published by the Free Software Foundation.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program; if not, write to the Free Software
 | |
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | |
|  * 02110-1301, USA.
 | |
|  *
 | |
|  *
 | |
|  * This implements:
 | |
|  *
 | |
|  *   - basic life cycle of 'struct wimax_dev' [wimax_dev_*()]; on
 | |
|  *     addition/registration initialize all subfields and allocate
 | |
|  *     generic netlink resources for user space communication. On
 | |
|  *     removal/unregistration, undo all that.
 | |
|  *
 | |
|  *   - device state machine [wimax_state_change()] and support to send
 | |
|  *     reports to user space when the state changes
 | |
|  *     [wimax_gnl_re_state_change*()].
 | |
|  *
 | |
|  * See include/net/wimax.h for rationales and design.
 | |
|  *
 | |
|  * ROADMAP
 | |
|  *
 | |
|  * [__]wimax_state_change()     Called by drivers to update device's state
 | |
|  *   wimax_gnl_re_state_change_alloc()
 | |
|  *   wimax_gnl_re_state_change_send()
 | |
|  *
 | |
|  * wimax_dev_init()	        Init a device
 | |
|  * wimax_dev_add()              Register
 | |
|  *   wimax_rfkill_add()
 | |
|  *   wimax_gnl_add()            Register all the generic netlink resources.
 | |
|  *   wimax_id_table_add()
 | |
|  * wimax_dev_rm()               Unregister
 | |
|  *   wimax_id_table_rm()
 | |
|  *   wimax_gnl_rm()
 | |
|  *   wimax_rfkill_rm()
 | |
|  */
 | |
| #include <linux/device.h>
 | |
| #include <linux/gfp.h>
 | |
| #include <net/genetlink.h>
 | |
| #include <linux/netdevice.h>
 | |
| #include <linux/wimax.h>
 | |
| #include <linux/module.h>
 | |
| #include "wimax-internal.h"
 | |
| 
 | |
| 
 | |
| #define D_SUBMODULE stack
 | |
| #include "debug-levels.h"
 | |
| 
 | |
| static char wimax_debug_params[128];
 | |
| module_param_string(debug, wimax_debug_params, sizeof(wimax_debug_params),
 | |
| 		    0644);
 | |
| MODULE_PARM_DESC(debug,
 | |
| 		 "String of space-separated NAME:VALUE pairs, where NAMEs "
 | |
| 		 "are the different debug submodules and VALUE are the "
 | |
| 		 "initial debug value to set.");
 | |
| 
 | |
| /*
 | |
|  * Authoritative source for the RE_STATE_CHANGE attribute policy
 | |
|  *
 | |
|  * We don't really use it here, but /me likes to keep the definition
 | |
|  * close to where the data is generated.
 | |
|  */
 | |
| /*
 | |
| static const struct nla_policy wimax_gnl_re_status_change[WIMAX_GNL_ATTR_MAX + 1] = {
 | |
| 	[WIMAX_GNL_STCH_STATE_OLD] = { .type = NLA_U8 },
 | |
| 	[WIMAX_GNL_STCH_STATE_NEW] = { .type = NLA_U8 },
 | |
| };
 | |
| */
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Allocate a Report State Change message
 | |
|  *
 | |
|  * @header: save it, you need it for _send()
 | |
|  *
 | |
|  * Creates and fills a basic state change message; different code
 | |
|  * paths can then add more attributes to the message as needed.
 | |
|  *
 | |
|  * Use wimax_gnl_re_state_change_send() to send the returned skb.
 | |
|  *
 | |
|  * Returns: skb with the genl message if ok, IS_ERR() ptr on error
 | |
|  *     with an errno code.
 | |
|  */
 | |
| static
 | |
| struct sk_buff *wimax_gnl_re_state_change_alloc(
 | |
| 	struct wimax_dev *wimax_dev,
 | |
| 	enum wimax_st new_state, enum wimax_st old_state,
 | |
| 	void **header)
 | |
| {
 | |
| 	int result;
 | |
| 	struct device *dev = wimax_dev_to_dev(wimax_dev);
 | |
| 	void *data;
 | |
| 	struct sk_buff *report_skb;
 | |
| 
 | |
| 	d_fnstart(3, dev, "(wimax_dev %p new_state %u old_state %u)\n",
 | |
| 		  wimax_dev, new_state, old_state);
 | |
| 	result = -ENOMEM;
 | |
| 	report_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
 | |
| 	if (report_skb == NULL) {
 | |
| 		dev_err(dev, "RE_STCH: can't create message\n");
 | |
| 		goto error_new;
 | |
| 	}
 | |
| 	data = genlmsg_put(report_skb, 0, wimax_gnl_mcg.id, &wimax_gnl_family,
 | |
| 			   0, WIMAX_GNL_RE_STATE_CHANGE);
 | |
| 	if (data == NULL) {
 | |
| 		dev_err(dev, "RE_STCH: can't put data into message\n");
 | |
| 		goto error_put;
 | |
| 	}
 | |
| 	*header = data;
 | |
| 
 | |
| 	result = nla_put_u8(report_skb, WIMAX_GNL_STCH_STATE_OLD, old_state);
 | |
| 	if (result < 0) {
 | |
| 		dev_err(dev, "RE_STCH: Error adding OLD attr: %d\n", result);
 | |
| 		goto error_put;
 | |
| 	}
 | |
| 	result = nla_put_u8(report_skb, WIMAX_GNL_STCH_STATE_NEW, new_state);
 | |
| 	if (result < 0) {
 | |
| 		dev_err(dev, "RE_STCH: Error adding NEW attr: %d\n", result);
 | |
| 		goto error_put;
 | |
| 	}
 | |
| 	result = nla_put_u32(report_skb, WIMAX_GNL_STCH_IFIDX,
 | |
| 			     wimax_dev->net_dev->ifindex);
 | |
| 	if (result < 0) {
 | |
| 		dev_err(dev, "RE_STCH: Error adding IFINDEX attribute\n");
 | |
| 		goto error_put;
 | |
| 	}
 | |
| 	d_fnend(3, dev, "(wimax_dev %p new_state %u old_state %u) = %p\n",
 | |
| 		wimax_dev, new_state, old_state, report_skb);
 | |
| 	return report_skb;
 | |
| 
 | |
| error_put:
 | |
| 	nlmsg_free(report_skb);
 | |
| error_new:
 | |
| 	d_fnend(3, dev, "(wimax_dev %p new_state %u old_state %u) = %d\n",
 | |
| 		wimax_dev, new_state, old_state, result);
 | |
| 	return ERR_PTR(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Send a Report State Change message (as created with _alloc).
 | |
|  *
 | |
|  * @report_skb: as returned by wimax_gnl_re_state_change_alloc()
 | |
|  * @header: as returned by wimax_gnl_re_state_change_alloc()
 | |
|  *
 | |
|  * Returns: 0 if ok, < 0 errno code on error.
 | |
|  *
 | |
|  * If the message is  NULL, pretend it didn't happen.
 | |
|  */
 | |
| static
 | |
| int wimax_gnl_re_state_change_send(
 | |
| 	struct wimax_dev *wimax_dev, struct sk_buff *report_skb,
 | |
| 	void *header)
 | |
| {
 | |
| 	int result = 0;
 | |
| 	struct device *dev = wimax_dev_to_dev(wimax_dev);
 | |
| 	d_fnstart(3, dev, "(wimax_dev %p report_skb %p)\n",
 | |
| 		  wimax_dev, report_skb);
 | |
| 	if (report_skb == NULL) {
 | |
| 		result = -ENOMEM;
 | |
| 		goto out;
 | |
| 	}
 | |
| 	genlmsg_end(report_skb, header);
 | |
| 	genlmsg_multicast(report_skb, 0, wimax_gnl_mcg.id, GFP_KERNEL);
 | |
| out:
 | |
| 	d_fnend(3, dev, "(wimax_dev %p report_skb %p) = %d\n",
 | |
| 		wimax_dev, report_skb, result);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| static
 | |
| void __check_new_state(enum wimax_st old_state, enum wimax_st new_state,
 | |
| 		       unsigned int allowed_states_bm)
 | |
| {
 | |
| 	if (WARN_ON(((1 << new_state) & allowed_states_bm) == 0)) {
 | |
| 		printk(KERN_ERR "SW BUG! Forbidden state change %u -> %u\n",
 | |
| 			old_state, new_state);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Set the current state of a WiMAX device [unlocking version of
 | |
|  * wimax_state_change().
 | |
|  */
 | |
| void __wimax_state_change(struct wimax_dev *wimax_dev, enum wimax_st new_state)
 | |
| {
 | |
| 	struct device *dev = wimax_dev_to_dev(wimax_dev);
 | |
| 	enum wimax_st old_state = wimax_dev->state;
 | |
| 	struct sk_buff *stch_skb;
 | |
| 	void *header;
 | |
| 
 | |
| 	d_fnstart(3, dev, "(wimax_dev %p new_state %u [old %u])\n",
 | |
| 		  wimax_dev, new_state, old_state);
 | |
| 
 | |
| 	if (WARN_ON(new_state >= __WIMAX_ST_INVALID)) {
 | |
| 		dev_err(dev, "SW BUG: requesting invalid state %u\n",
 | |
| 			new_state);
 | |
| 		goto out;
 | |
| 	}
 | |
| 	if (old_state == new_state)
 | |
| 		goto out;
 | |
| 	header = NULL;	/* gcc complains? can't grok why */
 | |
| 	stch_skb = wimax_gnl_re_state_change_alloc(
 | |
| 		wimax_dev, new_state, old_state, &header);
 | |
| 
 | |
| 	/* Verify the state transition and do exit-from-state actions */
 | |
| 	switch (old_state) {
 | |
| 	case __WIMAX_ST_NULL:
 | |
| 		__check_new_state(old_state, new_state,
 | |
| 				  1 << WIMAX_ST_DOWN);
 | |
| 		break;
 | |
| 	case WIMAX_ST_DOWN:
 | |
| 		__check_new_state(old_state, new_state,
 | |
| 				  1 << __WIMAX_ST_QUIESCING
 | |
| 				  | 1 << WIMAX_ST_UNINITIALIZED
 | |
| 				  | 1 << WIMAX_ST_RADIO_OFF);
 | |
| 		break;
 | |
| 	case __WIMAX_ST_QUIESCING:
 | |
| 		__check_new_state(old_state, new_state, 1 << WIMAX_ST_DOWN);
 | |
| 		break;
 | |
| 	case WIMAX_ST_UNINITIALIZED:
 | |
| 		__check_new_state(old_state, new_state,
 | |
| 				  1 << __WIMAX_ST_QUIESCING
 | |
| 				  | 1 << WIMAX_ST_RADIO_OFF);
 | |
| 		break;
 | |
| 	case WIMAX_ST_RADIO_OFF:
 | |
| 		__check_new_state(old_state, new_state,
 | |
| 				  1 << __WIMAX_ST_QUIESCING
 | |
| 				  | 1 << WIMAX_ST_READY);
 | |
| 		break;
 | |
| 	case WIMAX_ST_READY:
 | |
| 		__check_new_state(old_state, new_state,
 | |
| 				  1 << __WIMAX_ST_QUIESCING
 | |
| 				  | 1 << WIMAX_ST_RADIO_OFF
 | |
| 				  | 1 << WIMAX_ST_SCANNING
 | |
| 				  | 1 << WIMAX_ST_CONNECTING
 | |
| 				  | 1 << WIMAX_ST_CONNECTED);
 | |
| 		break;
 | |
| 	case WIMAX_ST_SCANNING:
 | |
| 		__check_new_state(old_state, new_state,
 | |
| 				  1 << __WIMAX_ST_QUIESCING
 | |
| 				  | 1 << WIMAX_ST_RADIO_OFF
 | |
| 				  | 1 << WIMAX_ST_READY
 | |
| 				  | 1 << WIMAX_ST_CONNECTING
 | |
| 				  | 1 << WIMAX_ST_CONNECTED);
 | |
| 		break;
 | |
| 	case WIMAX_ST_CONNECTING:
 | |
| 		__check_new_state(old_state, new_state,
 | |
| 				  1 << __WIMAX_ST_QUIESCING
 | |
| 				  | 1 << WIMAX_ST_RADIO_OFF
 | |
| 				  | 1 << WIMAX_ST_READY
 | |
| 				  | 1 << WIMAX_ST_SCANNING
 | |
| 				  | 1 << WIMAX_ST_CONNECTED);
 | |
| 		break;
 | |
| 	case WIMAX_ST_CONNECTED:
 | |
| 		__check_new_state(old_state, new_state,
 | |
| 				  1 << __WIMAX_ST_QUIESCING
 | |
| 				  | 1 << WIMAX_ST_RADIO_OFF
 | |
| 				  | 1 << WIMAX_ST_READY);
 | |
| 		netif_tx_disable(wimax_dev->net_dev);
 | |
| 		netif_carrier_off(wimax_dev->net_dev);
 | |
| 		break;
 | |
| 	case __WIMAX_ST_INVALID:
 | |
| 	default:
 | |
| 		dev_err(dev, "SW BUG: wimax_dev %p is in unknown state %u\n",
 | |
| 			wimax_dev, wimax_dev->state);
 | |
| 		WARN_ON(1);
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	/* Execute the actions of entry to the new state */
 | |
| 	switch (new_state) {
 | |
| 	case __WIMAX_ST_NULL:
 | |
| 		dev_err(dev, "SW BUG: wimax_dev %p entering NULL state "
 | |
| 			"from %u\n", wimax_dev, wimax_dev->state);
 | |
| 		WARN_ON(1);		/* Nobody can enter this state */
 | |
| 		break;
 | |
| 	case WIMAX_ST_DOWN:
 | |
| 		break;
 | |
| 	case __WIMAX_ST_QUIESCING:
 | |
| 		break;
 | |
| 	case WIMAX_ST_UNINITIALIZED:
 | |
| 		break;
 | |
| 	case WIMAX_ST_RADIO_OFF:
 | |
| 		break;
 | |
| 	case WIMAX_ST_READY:
 | |
| 		break;
 | |
| 	case WIMAX_ST_SCANNING:
 | |
| 		break;
 | |
| 	case WIMAX_ST_CONNECTING:
 | |
| 		break;
 | |
| 	case WIMAX_ST_CONNECTED:
 | |
| 		netif_carrier_on(wimax_dev->net_dev);
 | |
| 		netif_wake_queue(wimax_dev->net_dev);
 | |
| 		break;
 | |
| 	case __WIMAX_ST_INVALID:
 | |
| 	default:
 | |
| 		BUG();
 | |
| 	}
 | |
| 	__wimax_state_set(wimax_dev, new_state);
 | |
| 	if (!IS_ERR(stch_skb))
 | |
| 		wimax_gnl_re_state_change_send(wimax_dev, stch_skb, header);
 | |
| out:
 | |
| 	d_fnend(3, dev, "(wimax_dev %p new_state %u [old %u]) = void\n",
 | |
| 		wimax_dev, new_state, old_state);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * wimax_state_change - Set the current state of a WiMAX device
 | |
|  *
 | |
|  * @wimax_dev: WiMAX device descriptor (properly referenced)
 | |
|  * @new_state: New state to switch to
 | |
|  *
 | |
|  * This implements the state changes for the wimax devices. It will
 | |
|  *
 | |
|  * - verify that the state transition is legal (for now it'll just
 | |
|  *   print a warning if not) according to the table in
 | |
|  *   linux/wimax.h's documentation for 'enum wimax_st'.
 | |
|  *
 | |
|  * - perform the actions needed for leaving the current state and
 | |
|  *   whichever are needed for entering the new state.
 | |
|  *
 | |
|  * - issue a report to user space indicating the new state (and an
 | |
|  *   optional payload with information about the new state).
 | |
|  *
 | |
|  * NOTE: @wimax_dev must be locked
 | |
|  */
 | |
| void wimax_state_change(struct wimax_dev *wimax_dev, enum wimax_st new_state)
 | |
| {
 | |
| 	/*
 | |
| 	 * A driver cannot take the wimax_dev out of the
 | |
| 	 * __WIMAX_ST_NULL state unless by calling wimax_dev_add(). If
 | |
| 	 * the wimax_dev's state is still NULL, we ignore any request
 | |
| 	 * to change its state because it means it hasn't been yet
 | |
| 	 * registered.
 | |
| 	 *
 | |
| 	 * There is no need to complain about it, as routines that
 | |
| 	 * call this might be shared from different code paths that
 | |
| 	 * are called before or after wimax_dev_add() has done its
 | |
| 	 * job.
 | |
| 	 */
 | |
| 	mutex_lock(&wimax_dev->mutex);
 | |
| 	if (wimax_dev->state > __WIMAX_ST_NULL)
 | |
| 		__wimax_state_change(wimax_dev, new_state);
 | |
| 	mutex_unlock(&wimax_dev->mutex);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(wimax_state_change);
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * wimax_state_get() - Return the current state of a WiMAX device
 | |
|  *
 | |
|  * @wimax_dev: WiMAX device descriptor
 | |
|  *
 | |
|  * Returns: Current state of the device according to its driver.
 | |
|  */
 | |
| enum wimax_st wimax_state_get(struct wimax_dev *wimax_dev)
 | |
| {
 | |
| 	enum wimax_st state;
 | |
| 	mutex_lock(&wimax_dev->mutex);
 | |
| 	state = wimax_dev->state;
 | |
| 	mutex_unlock(&wimax_dev->mutex);
 | |
| 	return state;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(wimax_state_get);
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * wimax_dev_init - initialize a newly allocated instance
 | |
|  *
 | |
|  * @wimax_dev: WiMAX device descriptor to initialize.
 | |
|  *
 | |
|  * Initializes fields of a freshly allocated @wimax_dev instance. This
 | |
|  * function assumes that after allocation, the memory occupied by
 | |
|  * @wimax_dev was zeroed.
 | |
|  */
 | |
| void wimax_dev_init(struct wimax_dev *wimax_dev)
 | |
| {
 | |
| 	INIT_LIST_HEAD(&wimax_dev->id_table_node);
 | |
| 	__wimax_state_set(wimax_dev, __WIMAX_ST_NULL);
 | |
| 	mutex_init(&wimax_dev->mutex);
 | |
| 	mutex_init(&wimax_dev->mutex_reset);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(wimax_dev_init);
 | |
| 
 | |
| /*
 | |
|  * This extern is declared here because it's easier to keep track --
 | |
|  * both declarations are a list of the same
 | |
|  */
 | |
| extern struct genl_ops
 | |
| 	wimax_gnl_msg_from_user,
 | |
| 	wimax_gnl_reset,
 | |
| 	wimax_gnl_rfkill,
 | |
| 	wimax_gnl_state_get;
 | |
| 
 | |
| static
 | |
| struct genl_ops *wimax_gnl_ops[] = {
 | |
| 	&wimax_gnl_msg_from_user,
 | |
| 	&wimax_gnl_reset,
 | |
| 	&wimax_gnl_rfkill,
 | |
| 	&wimax_gnl_state_get,
 | |
| };
 | |
| 
 | |
| 
 | |
| static
 | |
| size_t wimax_addr_scnprint(char *addr_str, size_t addr_str_size,
 | |
| 			   unsigned char *addr, size_t addr_len)
 | |
| {
 | |
| 	unsigned int cnt, total;
 | |
| 
 | |
| 	for (total = cnt = 0; cnt < addr_len; cnt++)
 | |
| 		total += scnprintf(addr_str + total, addr_str_size - total,
 | |
| 				   "%02x%c", addr[cnt],
 | |
| 				   cnt == addr_len - 1 ? '\0' : ':');
 | |
| 	return total;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * wimax_dev_add - Register a new WiMAX device
 | |
|  *
 | |
|  * @wimax_dev: WiMAX device descriptor (as embedded in your @net_dev's
 | |
|  *     priv data). You must have called wimax_dev_init() on it before.
 | |
|  *
 | |
|  * @net_dev: net device the @wimax_dev is associated with. The
 | |
|  *     function expects SET_NETDEV_DEV() and register_netdev() were
 | |
|  *     already called on it.
 | |
|  *
 | |
|  * Registers the new WiMAX device, sets up the user-kernel control
 | |
|  * interface (generic netlink) and common WiMAX infrastructure.
 | |
|  *
 | |
|  * Note that the parts that will allow interaction with user space are
 | |
|  * setup at the very end, when the rest is in place, as once that
 | |
|  * happens, the driver might get user space control requests via
 | |
|  * netlink or from debugfs that might translate into calls into
 | |
|  * wimax_dev->op_*().
 | |
|  */
 | |
| int wimax_dev_add(struct wimax_dev *wimax_dev, struct net_device *net_dev)
 | |
| {
 | |
| 	int result;
 | |
| 	struct device *dev = net_dev->dev.parent;
 | |
| 	char addr_str[32];
 | |
| 
 | |
| 	d_fnstart(3, dev, "(wimax_dev %p net_dev %p)\n", wimax_dev, net_dev);
 | |
| 
 | |
| 	/* Do the RFKILL setup before locking, as RFKILL will call
 | |
| 	 * into our functions. */
 | |
| 	wimax_dev->net_dev = net_dev;
 | |
| 	result = wimax_rfkill_add(wimax_dev);
 | |
| 	if (result < 0)
 | |
| 		goto error_rfkill_add;
 | |
| 
 | |
| 	/* Set up user-space interaction */
 | |
| 	mutex_lock(&wimax_dev->mutex);
 | |
| 	wimax_id_table_add(wimax_dev);
 | |
| 	result = wimax_debugfs_add(wimax_dev);
 | |
| 	if (result < 0) {
 | |
| 		dev_err(dev, "cannot initialize debugfs: %d\n",
 | |
| 			result);
 | |
| 		goto error_debugfs_add;
 | |
| 	}
 | |
| 
 | |
| 	__wimax_state_set(wimax_dev, WIMAX_ST_DOWN);
 | |
| 	mutex_unlock(&wimax_dev->mutex);
 | |
| 
 | |
| 	wimax_addr_scnprint(addr_str, sizeof(addr_str),
 | |
| 			    net_dev->dev_addr, net_dev->addr_len);
 | |
| 	dev_err(dev, "WiMAX interface %s (%s) ready\n",
 | |
| 		net_dev->name, addr_str);
 | |
| 	d_fnend(3, dev, "(wimax_dev %p net_dev %p) = 0\n", wimax_dev, net_dev);
 | |
| 	return 0;
 | |
| 
 | |
| error_debugfs_add:
 | |
| 	wimax_id_table_rm(wimax_dev);
 | |
| 	mutex_unlock(&wimax_dev->mutex);
 | |
| 	wimax_rfkill_rm(wimax_dev);
 | |
| error_rfkill_add:
 | |
| 	d_fnend(3, dev, "(wimax_dev %p net_dev %p) = %d\n",
 | |
| 		wimax_dev, net_dev, result);
 | |
| 	return result;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(wimax_dev_add);
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * wimax_dev_rm - Unregister an existing WiMAX device
 | |
|  *
 | |
|  * @wimax_dev: WiMAX device descriptor
 | |
|  *
 | |
|  * Unregisters a WiMAX device previously registered for use with
 | |
|  * wimax_add_rm().
 | |
|  *
 | |
|  * IMPORTANT! Must call before calling unregister_netdev().
 | |
|  *
 | |
|  * After this function returns, you will not get any more user space
 | |
|  * control requests (via netlink or debugfs) and thus to wimax_dev->ops.
 | |
|  *
 | |
|  * Reentrancy control is ensured by setting the state to
 | |
|  * %__WIMAX_ST_QUIESCING. rfkill operations coming through
 | |
|  * wimax_*rfkill*() will be stopped by the quiescing state; ops coming
 | |
|  * from the rfkill subsystem will be stopped by the support being
 | |
|  * removed by wimax_rfkill_rm().
 | |
|  */
 | |
| void wimax_dev_rm(struct wimax_dev *wimax_dev)
 | |
| {
 | |
| 	d_fnstart(3, NULL, "(wimax_dev %p)\n", wimax_dev);
 | |
| 
 | |
| 	mutex_lock(&wimax_dev->mutex);
 | |
| 	__wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING);
 | |
| 	wimax_debugfs_rm(wimax_dev);
 | |
| 	wimax_id_table_rm(wimax_dev);
 | |
| 	__wimax_state_change(wimax_dev, WIMAX_ST_DOWN);
 | |
| 	mutex_unlock(&wimax_dev->mutex);
 | |
| 	wimax_rfkill_rm(wimax_dev);
 | |
| 	d_fnend(3, NULL, "(wimax_dev %p) = void\n", wimax_dev);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(wimax_dev_rm);
 | |
| 
 | |
| 
 | |
| /* Debug framework control of debug levels */
 | |
| struct d_level D_LEVEL[] = {
 | |
| 	D_SUBMODULE_DEFINE(debugfs),
 | |
| 	D_SUBMODULE_DEFINE(id_table),
 | |
| 	D_SUBMODULE_DEFINE(op_msg),
 | |
| 	D_SUBMODULE_DEFINE(op_reset),
 | |
| 	D_SUBMODULE_DEFINE(op_rfkill),
 | |
| 	D_SUBMODULE_DEFINE(op_state_get),
 | |
| 	D_SUBMODULE_DEFINE(stack),
 | |
| };
 | |
| size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
 | |
| 
 | |
| 
 | |
| struct genl_family wimax_gnl_family = {
 | |
| 	.id = GENL_ID_GENERATE,
 | |
| 	.name = "WiMAX",
 | |
| 	.version = WIMAX_GNL_VERSION,
 | |
| 	.hdrsize = 0,
 | |
| 	.maxattr = WIMAX_GNL_ATTR_MAX,
 | |
| };
 | |
| 
 | |
| struct genl_multicast_group wimax_gnl_mcg = {
 | |
| 	.name = "msg",
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /* Shutdown the wimax stack */
 | |
| static
 | |
| int __init wimax_subsys_init(void)
 | |
| {
 | |
| 	int result, cnt;
 | |
| 
 | |
| 	d_fnstart(4, NULL, "()\n");
 | |
| 	d_parse_params(D_LEVEL, D_LEVEL_SIZE, wimax_debug_params,
 | |
| 		       "wimax.debug");
 | |
| 
 | |
| 	snprintf(wimax_gnl_family.name, sizeof(wimax_gnl_family.name),
 | |
| 		 "WiMAX");
 | |
| 	result = genl_register_family(&wimax_gnl_family);
 | |
| 	if (unlikely(result < 0)) {
 | |
| 		printk(KERN_ERR "cannot register generic netlink family: %d\n",
 | |
| 		       result);
 | |
| 		goto error_register_family;
 | |
| 	}
 | |
| 
 | |
| 	for (cnt = 0; cnt < ARRAY_SIZE(wimax_gnl_ops); cnt++) {
 | |
| 		result = genl_register_ops(&wimax_gnl_family,
 | |
| 					   wimax_gnl_ops[cnt]);
 | |
| 		d_printf(4, NULL, "registering generic netlink op code "
 | |
| 			 "%u: %d\n", wimax_gnl_ops[cnt]->cmd, result);
 | |
| 		if (unlikely(result < 0)) {
 | |
| 			printk(KERN_ERR "cannot register generic netlink op "
 | |
| 			       "code %u: %d\n",
 | |
| 			       wimax_gnl_ops[cnt]->cmd, result);
 | |
| 			goto error_register_ops;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	result = genl_register_mc_group(&wimax_gnl_family, &wimax_gnl_mcg);
 | |
| 	if (result < 0)
 | |
| 		goto error_mc_group;
 | |
| 	d_fnend(4, NULL, "() = 0\n");
 | |
| 	return 0;
 | |
| 
 | |
| error_mc_group:
 | |
| error_register_ops:
 | |
| 	for (cnt--; cnt >= 0; cnt--)
 | |
| 		genl_unregister_ops(&wimax_gnl_family,
 | |
| 				    wimax_gnl_ops[cnt]);
 | |
| 	genl_unregister_family(&wimax_gnl_family);
 | |
| error_register_family:
 | |
| 	d_fnend(4, NULL, "() = %d\n", result);
 | |
| 	return result;
 | |
| 
 | |
| }
 | |
| module_init(wimax_subsys_init);
 | |
| 
 | |
| 
 | |
| /* Shutdown the wimax stack */
 | |
| static
 | |
| void __exit wimax_subsys_exit(void)
 | |
| {
 | |
| 	int cnt;
 | |
| 	wimax_id_table_release();
 | |
| 	genl_unregister_mc_group(&wimax_gnl_family, &wimax_gnl_mcg);
 | |
| 	for (cnt = ARRAY_SIZE(wimax_gnl_ops) - 1; cnt >= 0; cnt--)
 | |
| 		genl_unregister_ops(&wimax_gnl_family,
 | |
| 				    wimax_gnl_ops[cnt]);
 | |
| 	genl_unregister_family(&wimax_gnl_family);
 | |
| }
 | |
| module_exit(wimax_subsys_exit);
 | |
| 
 | |
| MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>");
 | |
| MODULE_DESCRIPTION("Linux WiMAX stack");
 | |
| MODULE_LICENSE("GPL");
 | |
| 
 |