| /* | 
 |  * This is the new netlink-based wireless configuration interface. | 
 |  * | 
 |  * Copyright 2006, 2007	Johannes Berg <johannes@sipsolutions.net> | 
 |  */ | 
 |  | 
 | #include <linux/if.h> | 
 | #include <linux/module.h> | 
 | #include <linux/err.h> | 
 | #include <linux/mutex.h> | 
 | #include <linux/list.h> | 
 | #include <linux/if_ether.h> | 
 | #include <linux/ieee80211.h> | 
 | #include <linux/nl80211.h> | 
 | #include <linux/rtnetlink.h> | 
 | #include <linux/netlink.h> | 
 | #include <net/genetlink.h> | 
 | #include <net/cfg80211.h> | 
 | #include "core.h" | 
 | #include "nl80211.h" | 
 |  | 
 | /* the netlink family */ | 
 | static struct genl_family nl80211_fam = { | 
 | 	.id = GENL_ID_GENERATE,	/* don't bother with a hardcoded ID */ | 
 | 	.name = "nl80211",	/* have users key off the name instead */ | 
 | 	.hdrsize = 0,		/* no private header */ | 
 | 	.version = 1,		/* no particular meaning now */ | 
 | 	.maxattr = NL80211_ATTR_MAX, | 
 | }; | 
 |  | 
 | /* internal helper: get drv and dev */ | 
 | static int get_drv_dev_by_info_ifindex(struct genl_info *info, | 
 | 				       struct cfg80211_registered_device **drv, | 
 | 				       struct net_device **dev) | 
 | { | 
 | 	int ifindex; | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_IFINDEX]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	ifindex = nla_get_u32(info->attrs[NL80211_ATTR_IFINDEX]); | 
 | 	*dev = dev_get_by_index(&init_net, ifindex); | 
 | 	if (!*dev) | 
 | 		return -ENODEV; | 
 |  | 
 | 	*drv = cfg80211_get_dev_from_ifindex(ifindex); | 
 | 	if (IS_ERR(*drv)) { | 
 | 		dev_put(*dev); | 
 | 		return PTR_ERR(*drv); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* policy for the attributes */ | 
 | static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { | 
 | 	[NL80211_ATTR_WIPHY] = { .type = NLA_U32 }, | 
 | 	[NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING, | 
 | 				      .len = BUS_ID_SIZE-1 }, | 
 |  | 
 | 	[NL80211_ATTR_IFTYPE] = { .type = NLA_U32 }, | 
 | 	[NL80211_ATTR_IFINDEX] = { .type = NLA_U32 }, | 
 | 	[NL80211_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 }, | 
 |  | 
 | 	[NL80211_ATTR_MAC] = { .type = NLA_BINARY, .len = ETH_ALEN }, | 
 |  | 
 | 	[NL80211_ATTR_KEY_DATA] = { .type = NLA_BINARY, | 
 | 				    .len = WLAN_MAX_KEY_LEN }, | 
 | 	[NL80211_ATTR_KEY_IDX] = { .type = NLA_U8 }, | 
 | 	[NL80211_ATTR_KEY_CIPHER] = { .type = NLA_U32 }, | 
 | 	[NL80211_ATTR_KEY_DEFAULT] = { .type = NLA_FLAG }, | 
 |  | 
 | 	[NL80211_ATTR_BEACON_INTERVAL] = { .type = NLA_U32 }, | 
 | 	[NL80211_ATTR_DTIM_PERIOD] = { .type = NLA_U32 }, | 
 | 	[NL80211_ATTR_BEACON_HEAD] = { .type = NLA_BINARY, | 
 | 				       .len = IEEE80211_MAX_DATA_LEN }, | 
 | 	[NL80211_ATTR_BEACON_TAIL] = { .type = NLA_BINARY, | 
 | 				       .len = IEEE80211_MAX_DATA_LEN }, | 
 | 	[NL80211_ATTR_STA_AID] = { .type = NLA_U16 }, | 
 | 	[NL80211_ATTR_STA_FLAGS] = { .type = NLA_NESTED }, | 
 | 	[NL80211_ATTR_STA_LISTEN_INTERVAL] = { .type = NLA_U16 }, | 
 | 	[NL80211_ATTR_STA_SUPPORTED_RATES] = { .type = NLA_BINARY, | 
 | 					       .len = NL80211_MAX_SUPP_RATES }, | 
 | 	[NL80211_ATTR_STA_VLAN] = { .type = NLA_U32 }, | 
 | }; | 
 |  | 
 | /* message building helper */ | 
 | static inline void *nl80211hdr_put(struct sk_buff *skb, u32 pid, u32 seq, | 
 | 				   int flags, u8 cmd) | 
 | { | 
 | 	/* since there is no private header just add the generic one */ | 
 | 	return genlmsg_put(skb, pid, seq, &nl80211_fam, flags, cmd); | 
 | } | 
 |  | 
 | /* netlink command implementations */ | 
 |  | 
 | static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, | 
 | 			      struct cfg80211_registered_device *dev) | 
 | { | 
 | 	void *hdr; | 
 |  | 
 | 	hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_WIPHY); | 
 | 	if (!hdr) | 
 | 		return -1; | 
 |  | 
 | 	NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx); | 
 | 	NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy)); | 
 | 	return genlmsg_end(msg, hdr); | 
 |  | 
 |  nla_put_failure: | 
 | 	return genlmsg_cancel(msg, hdr); | 
 | } | 
 |  | 
 | static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb) | 
 | { | 
 | 	int idx = 0; | 
 | 	int start = cb->args[0]; | 
 | 	struct cfg80211_registered_device *dev; | 
 |  | 
 | 	mutex_lock(&cfg80211_drv_mutex); | 
 | 	list_for_each_entry(dev, &cfg80211_drv_list, list) { | 
 | 		if (++idx < start) | 
 | 			continue; | 
 | 		if (nl80211_send_wiphy(skb, NETLINK_CB(cb->skb).pid, | 
 | 				       cb->nlh->nlmsg_seq, NLM_F_MULTI, | 
 | 				       dev) < 0) | 
 | 			break; | 
 | 	} | 
 | 	mutex_unlock(&cfg80211_drv_mutex); | 
 |  | 
 | 	cb->args[0] = idx; | 
 |  | 
 | 	return skb->len; | 
 | } | 
 |  | 
 | static int nl80211_get_wiphy(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct sk_buff *msg; | 
 | 	struct cfg80211_registered_device *dev; | 
 |  | 
 | 	dev = cfg80211_get_dev_from_info(info); | 
 | 	if (IS_ERR(dev)) | 
 | 		return PTR_ERR(dev); | 
 |  | 
 | 	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | 
 | 	if (!msg) | 
 | 		goto out_err; | 
 |  | 
 | 	if (nl80211_send_wiphy(msg, info->snd_pid, info->snd_seq, 0, dev) < 0) | 
 | 		goto out_free; | 
 |  | 
 | 	cfg80211_put_dev(dev); | 
 |  | 
 | 	return genlmsg_unicast(msg, info->snd_pid); | 
 |  | 
 |  out_free: | 
 | 	nlmsg_free(msg); | 
 |  out_err: | 
 | 	cfg80211_put_dev(dev); | 
 | 	return -ENOBUFS; | 
 | } | 
 |  | 
 | static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *rdev; | 
 | 	int result; | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_WIPHY_NAME]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	rdev = cfg80211_get_dev_from_info(info); | 
 | 	if (IS_ERR(rdev)) | 
 | 		return PTR_ERR(rdev); | 
 |  | 
 | 	result = cfg80211_dev_rename(rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME])); | 
 |  | 
 | 	cfg80211_put_dev(rdev); | 
 | 	return result; | 
 | } | 
 |  | 
 |  | 
 | static int nl80211_send_iface(struct sk_buff *msg, u32 pid, u32 seq, int flags, | 
 | 			      struct net_device *dev) | 
 | { | 
 | 	void *hdr; | 
 |  | 
 | 	hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_INTERFACE); | 
 | 	if (!hdr) | 
 | 		return -1; | 
 |  | 
 | 	NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex); | 
 | 	NLA_PUT_STRING(msg, NL80211_ATTR_IFNAME, dev->name); | 
 | 	/* TODO: interface type */ | 
 | 	return genlmsg_end(msg, hdr); | 
 |  | 
 |  nla_put_failure: | 
 | 	return genlmsg_cancel(msg, hdr); | 
 | } | 
 |  | 
 | static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *cb) | 
 | { | 
 | 	int wp_idx = 0; | 
 | 	int if_idx = 0; | 
 | 	int wp_start = cb->args[0]; | 
 | 	int if_start = cb->args[1]; | 
 | 	struct cfg80211_registered_device *dev; | 
 | 	struct wireless_dev *wdev; | 
 |  | 
 | 	mutex_lock(&cfg80211_drv_mutex); | 
 | 	list_for_each_entry(dev, &cfg80211_drv_list, list) { | 
 | 		if (++wp_idx < wp_start) | 
 | 			continue; | 
 | 		if_idx = 0; | 
 |  | 
 | 		mutex_lock(&dev->devlist_mtx); | 
 | 		list_for_each_entry(wdev, &dev->netdev_list, list) { | 
 | 			if (++if_idx < if_start) | 
 | 				continue; | 
 | 			if (nl80211_send_iface(skb, NETLINK_CB(cb->skb).pid, | 
 | 					       cb->nlh->nlmsg_seq, NLM_F_MULTI, | 
 | 					       wdev->netdev) < 0) | 
 | 				break; | 
 | 		} | 
 | 		mutex_unlock(&dev->devlist_mtx); | 
 | 	} | 
 | 	mutex_unlock(&cfg80211_drv_mutex); | 
 |  | 
 | 	cb->args[0] = wp_idx; | 
 | 	cb->args[1] = if_idx; | 
 |  | 
 | 	return skb->len; | 
 | } | 
 |  | 
 | static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct sk_buff *msg; | 
 | 	struct cfg80211_registered_device *dev; | 
 | 	struct net_device *netdev; | 
 | 	int err; | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &dev, &netdev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | 
 | 	if (!msg) | 
 | 		goto out_err; | 
 |  | 
 | 	if (nl80211_send_iface(msg, info->snd_pid, info->snd_seq, 0, netdev) < 0) | 
 | 		goto out_free; | 
 |  | 
 | 	dev_put(netdev); | 
 | 	cfg80211_put_dev(dev); | 
 |  | 
 | 	return genlmsg_unicast(msg, info->snd_pid); | 
 |  | 
 |  out_free: | 
 | 	nlmsg_free(msg); | 
 |  out_err: | 
 | 	dev_put(netdev); | 
 | 	cfg80211_put_dev(dev); | 
 | 	return -ENOBUFS; | 
 | } | 
 |  | 
 | static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err, ifindex; | 
 | 	enum nl80211_iftype type; | 
 | 	struct net_device *dev; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_IFTYPE]) { | 
 | 		type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]); | 
 | 		if (type > NL80211_IFTYPE_MAX) | 
 | 			return -EINVAL; | 
 | 	} else | 
 | 		return -EINVAL; | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 | 	ifindex = dev->ifindex; | 
 | 	dev_put(dev); | 
 |  | 
 | 	if (!drv->ops->change_virtual_intf) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto unlock; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->change_virtual_intf(&drv->wiphy, ifindex, type); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  unlock: | 
 | 	cfg80211_put_dev(drv); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED; | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_IFNAME]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_IFTYPE]) { | 
 | 		type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]); | 
 | 		if (type > NL80211_IFTYPE_MAX) | 
 | 			return -EINVAL; | 
 | 	} | 
 |  | 
 | 	drv = cfg80211_get_dev_from_info(info); | 
 | 	if (IS_ERR(drv)) | 
 | 		return PTR_ERR(drv); | 
 |  | 
 | 	if (!drv->ops->add_virtual_intf) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto unlock; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->add_virtual_intf(&drv->wiphy, | 
 | 		nla_data(info->attrs[NL80211_ATTR_IFNAME]), type); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  unlock: | 
 | 	cfg80211_put_dev(drv); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int ifindex, err; | 
 | 	struct net_device *dev; | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 | 	ifindex = dev->ifindex; | 
 | 	dev_put(dev); | 
 |  | 
 | 	if (!drv->ops->del_virtual_intf) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->del_virtual_intf(&drv->wiphy, ifindex); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	return err; | 
 | } | 
 |  | 
 | struct get_key_cookie { | 
 | 	struct sk_buff *msg; | 
 | 	int error; | 
 | }; | 
 |  | 
 | static void get_key_callback(void *c, struct key_params *params) | 
 | { | 
 | 	struct get_key_cookie *cookie = c; | 
 |  | 
 | 	if (params->key) | 
 | 		NLA_PUT(cookie->msg, NL80211_ATTR_KEY_DATA, | 
 | 			params->key_len, params->key); | 
 |  | 
 | 	if (params->seq) | 
 | 		NLA_PUT(cookie->msg, NL80211_ATTR_KEY_SEQ, | 
 | 			params->seq_len, params->seq); | 
 |  | 
 | 	if (params->cipher) | 
 | 		NLA_PUT_U32(cookie->msg, NL80211_ATTR_KEY_CIPHER, | 
 | 			    params->cipher); | 
 |  | 
 | 	return; | 
 |  nla_put_failure: | 
 | 	cookie->error = 1; | 
 | } | 
 |  | 
 | static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	u8 key_idx = 0; | 
 | 	u8 *mac_addr = NULL; | 
 | 	struct get_key_cookie cookie = { | 
 | 		.error = 0, | 
 | 	}; | 
 | 	void *hdr; | 
 | 	struct sk_buff *msg; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_KEY_IDX]) | 
 | 		key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]); | 
 |  | 
 | 	if (key_idx > 3) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_MAC]) | 
 | 		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	if (!drv->ops->get_key) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | 
 | 	if (!msg) { | 
 | 		err = -ENOMEM; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0, | 
 | 			     NL80211_CMD_NEW_KEY); | 
 |  | 
 | 	if (IS_ERR(hdr)) { | 
 | 		err = PTR_ERR(hdr); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	cookie.msg = msg; | 
 |  | 
 | 	NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex); | 
 | 	NLA_PUT_U8(msg, NL80211_ATTR_KEY_IDX, key_idx); | 
 | 	if (mac_addr) | 
 | 		NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->get_key(&drv->wiphy, dev, key_idx, mac_addr, | 
 | 				&cookie, get_key_callback); | 
 | 	rtnl_unlock(); | 
 |  | 
 | 	if (err) | 
 | 		goto out; | 
 |  | 
 | 	if (cookie.error) | 
 | 		goto nla_put_failure; | 
 |  | 
 | 	genlmsg_end(msg, hdr); | 
 | 	err = genlmsg_unicast(msg, info->snd_pid); | 
 | 	goto out; | 
 |  | 
 |  nla_put_failure: | 
 | 	err = -ENOBUFS; | 
 | 	nlmsg_free(msg); | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	u8 key_idx; | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_KEY_IDX]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]); | 
 |  | 
 | 	if (key_idx > 3) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* currently only support setting default key */ | 
 | 	if (!info->attrs[NL80211_ATTR_KEY_DEFAULT]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	if (!drv->ops->set_default_key) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->set_default_key(&drv->wiphy, dev, key_idx); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	struct key_params params; | 
 | 	u8 key_idx = 0; | 
 | 	u8 *mac_addr = NULL; | 
 |  | 
 | 	memset(¶ms, 0, sizeof(params)); | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_KEY_CIPHER]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_KEY_DATA]) { | 
 | 		params.key = nla_data(info->attrs[NL80211_ATTR_KEY_DATA]); | 
 | 		params.key_len = nla_len(info->attrs[NL80211_ATTR_KEY_DATA]); | 
 | 	} | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_KEY_IDX]) | 
 | 		key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]); | 
 |  | 
 | 	params.cipher = nla_get_u32(info->attrs[NL80211_ATTR_KEY_CIPHER]); | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_MAC]) | 
 | 		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); | 
 |  | 
 | 	if (key_idx > 3) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* | 
 | 	 * Disallow pairwise keys with non-zero index unless it's WEP | 
 | 	 * (because current deployments use pairwise WEP keys with | 
 | 	 * non-zero indizes but 802.11i clearly specifies to use zero) | 
 | 	 */ | 
 | 	if (mac_addr && key_idx && | 
 | 	    params.cipher != WLAN_CIPHER_SUITE_WEP40 && | 
 | 	    params.cipher != WLAN_CIPHER_SUITE_WEP104) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* TODO: add definitions for the lengths to linux/ieee80211.h */ | 
 | 	switch (params.cipher) { | 
 | 	case WLAN_CIPHER_SUITE_WEP40: | 
 | 		if (params.key_len != 5) | 
 | 			return -EINVAL; | 
 | 		break; | 
 | 	case WLAN_CIPHER_SUITE_TKIP: | 
 | 		if (params.key_len != 32) | 
 | 			return -EINVAL; | 
 | 		break; | 
 | 	case WLAN_CIPHER_SUITE_CCMP: | 
 | 		if (params.key_len != 16) | 
 | 			return -EINVAL; | 
 | 		break; | 
 | 	case WLAN_CIPHER_SUITE_WEP104: | 
 | 		if (params.key_len != 13) | 
 | 			return -EINVAL; | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	if (!drv->ops->add_key) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->add_key(&drv->wiphy, dev, key_idx, mac_addr, ¶ms); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	u8 key_idx = 0; | 
 | 	u8 *mac_addr = NULL; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_KEY_IDX]) | 
 | 		key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]); | 
 |  | 
 | 	if (key_idx > 3) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_MAC]) | 
 | 		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	if (!drv->ops->del_key) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->del_key(&drv->wiphy, dev, key_idx, mac_addr); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 |         int (*call)(struct wiphy *wiphy, struct net_device *dev, | 
 | 		    struct beacon_parameters *info); | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	struct beacon_parameters params; | 
 | 	int haveinfo = 0; | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	switch (info->genlhdr->cmd) { | 
 | 	case NL80211_CMD_NEW_BEACON: | 
 | 		/* these are required for NEW_BEACON */ | 
 | 		if (!info->attrs[NL80211_ATTR_BEACON_INTERVAL] || | 
 | 		    !info->attrs[NL80211_ATTR_DTIM_PERIOD] || | 
 | 		    !info->attrs[NL80211_ATTR_BEACON_HEAD]) { | 
 | 			err = -EINVAL; | 
 | 			goto out; | 
 | 		} | 
 |  | 
 | 		call = drv->ops->add_beacon; | 
 | 		break; | 
 | 	case NL80211_CMD_SET_BEACON: | 
 | 		call = drv->ops->set_beacon; | 
 | 		break; | 
 | 	default: | 
 | 		WARN_ON(1); | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	if (!call) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	memset(¶ms, 0, sizeof(params)); | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_BEACON_INTERVAL]) { | 
 | 		params.interval = | 
 | 		    nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]); | 
 | 		haveinfo = 1; | 
 | 	} | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_DTIM_PERIOD]) { | 
 | 		params.dtim_period = | 
 | 		    nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]); | 
 | 		haveinfo = 1; | 
 | 	} | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_BEACON_HEAD]) { | 
 | 		params.head = nla_data(info->attrs[NL80211_ATTR_BEACON_HEAD]); | 
 | 		params.head_len = | 
 | 		    nla_len(info->attrs[NL80211_ATTR_BEACON_HEAD]); | 
 | 		haveinfo = 1; | 
 | 	} | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_BEACON_TAIL]) { | 
 | 		params.tail = nla_data(info->attrs[NL80211_ATTR_BEACON_TAIL]); | 
 | 		params.tail_len = | 
 | 		    nla_len(info->attrs[NL80211_ATTR_BEACON_TAIL]); | 
 | 		haveinfo = 1; | 
 | 	} | 
 |  | 
 | 	if (!haveinfo) { | 
 | 		err = -EINVAL; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = call(&drv->wiphy, dev, ¶ms); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_del_beacon(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	if (!drv->ops->del_beacon) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->del_beacon(&drv->wiphy, dev); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static const struct nla_policy sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = { | 
 | 	[NL80211_STA_FLAG_AUTHORIZED] = { .type = NLA_FLAG }, | 
 | 	[NL80211_STA_FLAG_SHORT_PREAMBLE] = { .type = NLA_FLAG }, | 
 | 	[NL80211_STA_FLAG_WME] = { .type = NLA_FLAG }, | 
 | }; | 
 |  | 
 | static int parse_station_flags(struct nlattr *nla, u32 *staflags) | 
 | { | 
 | 	struct nlattr *flags[NL80211_STA_FLAG_MAX + 1]; | 
 | 	int flag; | 
 |  | 
 | 	*staflags = 0; | 
 |  | 
 | 	if (!nla) | 
 | 		return 0; | 
 |  | 
 | 	if (nla_parse_nested(flags, NL80211_STA_FLAG_MAX, | 
 | 			     nla, sta_flags_policy)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	*staflags = STATION_FLAG_CHANGED; | 
 |  | 
 | 	for (flag = 1; flag <= NL80211_STA_FLAG_MAX; flag++) | 
 | 		if (flags[flag]) | 
 | 			*staflags |= (1<<flag); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int nl80211_send_station(struct sk_buff *msg, u32 pid, u32 seq, | 
 | 				int flags, struct net_device *dev, | 
 | 				u8 *mac_addr, struct station_stats *stats) | 
 | { | 
 | 	void *hdr; | 
 | 	struct nlattr *statsattr; | 
 |  | 
 | 	hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_STATION); | 
 | 	if (!hdr) | 
 | 		return -1; | 
 |  | 
 | 	NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex); | 
 | 	NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); | 
 |  | 
 | 	statsattr = nla_nest_start(msg, NL80211_ATTR_STA_STATS); | 
 | 	if (!statsattr) | 
 | 		goto nla_put_failure; | 
 | 	if (stats->filled & STATION_STAT_INACTIVE_TIME) | 
 | 		NLA_PUT_U32(msg, NL80211_STA_STAT_INACTIVE_TIME, | 
 | 			    stats->inactive_time); | 
 | 	if (stats->filled & STATION_STAT_RX_BYTES) | 
 | 		NLA_PUT_U32(msg, NL80211_STA_STAT_RX_BYTES, | 
 | 			    stats->rx_bytes); | 
 | 	if (stats->filled & STATION_STAT_TX_BYTES) | 
 | 		NLA_PUT_U32(msg, NL80211_STA_STAT_TX_BYTES, | 
 | 			    stats->tx_bytes); | 
 |  | 
 | 	nla_nest_end(msg, statsattr); | 
 |  | 
 | 	return genlmsg_end(msg, hdr); | 
 |  | 
 |  nla_put_failure: | 
 | 	return genlmsg_cancel(msg, hdr); | 
 | } | 
 |  | 
 |  | 
 | static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	struct station_stats stats; | 
 | 	struct sk_buff *msg; | 
 | 	u8 *mac_addr = NULL; | 
 |  | 
 | 	memset(&stats, 0, sizeof(stats)); | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_MAC]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	if (!drv->ops->get_station) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->get_station(&drv->wiphy, dev, mac_addr, &stats); | 
 | 	rtnl_unlock(); | 
 |  | 
 | 	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | 
 | 	if (!msg) | 
 | 		goto out; | 
 |  | 
 | 	if (nl80211_send_station(msg, info->snd_pid, info->snd_seq, 0, | 
 | 				 dev, mac_addr, &stats) < 0) | 
 | 		goto out_free; | 
 |  | 
 | 	err = genlmsg_unicast(msg, info->snd_pid); | 
 | 	goto out; | 
 |  | 
 |  out_free: | 
 | 	nlmsg_free(msg); | 
 |  | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | /* | 
 |  * Get vlan interface making sure it is on the right wiphy. | 
 |  */ | 
 | static int get_vlan(struct nlattr *vlanattr, | 
 | 		    struct cfg80211_registered_device *rdev, | 
 | 		    struct net_device **vlan) | 
 | { | 
 | 	*vlan = NULL; | 
 |  | 
 | 	if (vlanattr) { | 
 | 		*vlan = dev_get_by_index(&init_net, nla_get_u32(vlanattr)); | 
 | 		if (!*vlan) | 
 | 			return -ENODEV; | 
 | 		if (!(*vlan)->ieee80211_ptr) | 
 | 			return -EINVAL; | 
 | 		if ((*vlan)->ieee80211_ptr->wiphy != &rdev->wiphy) | 
 | 			return -EINVAL; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	struct station_parameters params; | 
 | 	u8 *mac_addr = NULL; | 
 |  | 
 | 	memset(¶ms, 0, sizeof(params)); | 
 |  | 
 | 	params.listen_interval = -1; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_STA_AID]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_MAC]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]) { | 
 | 		params.supported_rates = | 
 | 			nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]); | 
 | 		params.supported_rates_len = | 
 | 			nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]); | 
 | 	} | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]) | 
 | 		params.listen_interval = | 
 | 		    nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]); | 
 |  | 
 | 	if (parse_station_flags(info->attrs[NL80211_ATTR_STA_FLAGS], | 
 | 				¶ms.station_flags)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, ¶ms.vlan); | 
 | 	if (err) | 
 | 		goto out; | 
 |  | 
 | 	if (!drv->ops->change_station) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->change_station(&drv->wiphy, dev, mac_addr, ¶ms); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	if (params.vlan) | 
 | 		dev_put(params.vlan); | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	struct station_parameters params; | 
 | 	u8 *mac_addr = NULL; | 
 |  | 
 | 	memset(¶ms, 0, sizeof(params)); | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_MAC]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_STA_AID]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (!info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); | 
 | 	params.supported_rates = | 
 | 		nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]); | 
 | 	params.supported_rates_len = | 
 | 		nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]); | 
 | 	params.listen_interval = | 
 | 		nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]); | 
 | 	params.listen_interval = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]); | 
 |  | 
 | 	if (parse_station_flags(info->attrs[NL80211_ATTR_STA_FLAGS], | 
 | 				¶ms.station_flags)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, ¶ms.vlan); | 
 | 	if (err) | 
 | 		goto out; | 
 |  | 
 | 	if (!drv->ops->add_station) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->add_station(&drv->wiphy, dev, mac_addr, ¶ms); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	if (params.vlan) | 
 | 		dev_put(params.vlan); | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info) | 
 | { | 
 | 	struct cfg80211_registered_device *drv; | 
 | 	int err; | 
 | 	struct net_device *dev; | 
 | 	u8 *mac_addr = NULL; | 
 |  | 
 | 	if (info->attrs[NL80211_ATTR_MAC]) | 
 | 		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); | 
 |  | 
 | 	err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	if (!drv->ops->del_station) { | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	rtnl_lock(); | 
 | 	err = drv->ops->del_station(&drv->wiphy, dev, mac_addr); | 
 | 	rtnl_unlock(); | 
 |  | 
 |  out: | 
 | 	cfg80211_put_dev(drv); | 
 | 	dev_put(dev); | 
 | 	return err; | 
 | } | 
 |  | 
 | static struct genl_ops nl80211_ops[] = { | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_GET_WIPHY, | 
 | 		.doit = nl80211_get_wiphy, | 
 | 		.dumpit = nl80211_dump_wiphy, | 
 | 		.policy = nl80211_policy, | 
 | 		/* can be retrieved by unprivileged users */ | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_SET_WIPHY, | 
 | 		.doit = nl80211_set_wiphy, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_GET_INTERFACE, | 
 | 		.doit = nl80211_get_interface, | 
 | 		.dumpit = nl80211_dump_interface, | 
 | 		.policy = nl80211_policy, | 
 | 		/* can be retrieved by unprivileged users */ | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_SET_INTERFACE, | 
 | 		.doit = nl80211_set_interface, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_NEW_INTERFACE, | 
 | 		.doit = nl80211_new_interface, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_DEL_INTERFACE, | 
 | 		.doit = nl80211_del_interface, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_GET_KEY, | 
 | 		.doit = nl80211_get_key, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_SET_KEY, | 
 | 		.doit = nl80211_set_key, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_NEW_KEY, | 
 | 		.doit = nl80211_new_key, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_DEL_KEY, | 
 | 		.doit = nl80211_del_key, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_SET_BEACON, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 		.doit = nl80211_addset_beacon, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_NEW_BEACON, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 		.doit = nl80211_addset_beacon, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_DEL_BEACON, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 		.doit = nl80211_del_beacon, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_GET_STATION, | 
 | 		.doit = nl80211_get_station, | 
 | 		/* TODO: implement dumpit */ | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_SET_STATION, | 
 | 		.doit = nl80211_set_station, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_NEW_STATION, | 
 | 		.doit = nl80211_new_station, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | 	{ | 
 | 		.cmd = NL80211_CMD_DEL_STATION, | 
 | 		.doit = nl80211_del_station, | 
 | 		.policy = nl80211_policy, | 
 | 		.flags = GENL_ADMIN_PERM, | 
 | 	}, | 
 | }; | 
 |  | 
 | /* multicast groups */ | 
 | static struct genl_multicast_group nl80211_config_mcgrp = { | 
 | 	.name = "config", | 
 | }; | 
 |  | 
 | /* notification functions */ | 
 |  | 
 | void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev) | 
 | { | 
 | 	struct sk_buff *msg; | 
 |  | 
 | 	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | 
 | 	if (!msg) | 
 | 		return; | 
 |  | 
 | 	if (nl80211_send_wiphy(msg, 0, 0, 0, rdev) < 0) { | 
 | 		nlmsg_free(msg); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL); | 
 | } | 
 |  | 
 | /* initialisation/exit functions */ | 
 |  | 
 | int nl80211_init(void) | 
 | { | 
 | 	int err, i; | 
 |  | 
 | 	err = genl_register_family(&nl80211_fam); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	for (i = 0; i < ARRAY_SIZE(nl80211_ops); i++) { | 
 | 		err = genl_register_ops(&nl80211_fam, &nl80211_ops[i]); | 
 | 		if (err) | 
 | 			goto err_out; | 
 | 	} | 
 |  | 
 | 	err = genl_register_mc_group(&nl80211_fam, &nl80211_config_mcgrp); | 
 | 	if (err) | 
 | 		goto err_out; | 
 |  | 
 | 	return 0; | 
 |  err_out: | 
 | 	genl_unregister_family(&nl80211_fam); | 
 | 	return err; | 
 | } | 
 |  | 
 | void nl80211_exit(void) | 
 | { | 
 | 	genl_unregister_family(&nl80211_fam); | 
 | } |