Skip to content
Snippets Groups Projects
core.c 45 KiB
Newer Older
	if (wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) {
		struct regulatory_request request;

		request.wiphy_idx = get_wiphy_idx(wiphy);
		request.initiator = NL80211_REGDOM_SET_BY_DRIVER;
		request.alpha2[0] = '9';
		request.alpha2[1] = '9';

		nl80211_send_reg_change_event(&request);
	}

	/* Check that nobody globally advertises any capabilities they do not
	 * advertise on all possible interface types.
	 */
	if (wiphy->extended_capabilities_len &&
	    wiphy->num_iftype_ext_capab &&
	    wiphy->iftype_ext_capab) {
		u8 supported_on_all, j;
		const struct wiphy_iftype_ext_capab *capab;

		capab = wiphy->iftype_ext_capab;
		for (j = 0; j < wiphy->extended_capabilities_len; j++) {
			if (capab[0].extended_capabilities_len > j)
				supported_on_all =
					capab[0].extended_capabilities[j];
			else
				supported_on_all = 0x00;
			for (i = 1; i < wiphy->num_iftype_ext_capab; i++) {
				if (j >= capab[i].extended_capabilities_len) {
					supported_on_all = 0x00;
					break;
				}
				supported_on_all &=
					capab[i].extended_capabilities[j];
			}
			if (WARN_ON(wiphy->extended_capabilities[j] &
				    ~supported_on_all))
				break;
		}
	}

	rdev->wiphy.registered = true;
	rtnl_unlock();
	res = rfkill_register(rdev->wiphy.rfkill);
		rfkill_destroy(rdev->wiphy.rfkill);
		rdev->wiphy.rfkill = NULL;
		wiphy_unregister(&rdev->wiphy);
		return res;
	}

void wiphy_rfkill_start_polling(struct wiphy *wiphy)
{
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
	if (!rdev->ops->rfkill_poll)
		return;
	rdev->rfkill_ops.poll = cfg80211_rfkill_poll;
	rfkill_resume_polling(wiphy->rfkill);
}
EXPORT_SYMBOL(wiphy_rfkill_start_polling);

void cfg80211_process_wiphy_works(struct cfg80211_registered_device *rdev)
{
	unsigned int runaway_limit = 100;
	unsigned long flags;

	lockdep_assert_held(&rdev->wiphy.mtx);

	spin_lock_irqsave(&rdev->wiphy_work_lock, flags);
	while (!list_empty(&rdev->wiphy_work_list)) {
		struct wiphy_work *wk;

		wk = list_first_entry(&rdev->wiphy_work_list,
				      struct wiphy_work, entry);
		list_del_init(&wk->entry);
		spin_unlock_irqrestore(&rdev->wiphy_work_lock, flags);

		wk->func(&rdev->wiphy, wk);

		spin_lock_irqsave(&rdev->wiphy_work_lock, flags);
		if (WARN_ON(--runaway_limit == 0))
			INIT_LIST_HEAD(&rdev->wiphy_work_list);
	}
	spin_unlock_irqrestore(&rdev->wiphy_work_lock, flags);
}

void wiphy_unregister(struct wiphy *wiphy)
{
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
	wait_event(rdev->dev_wait, ({
		int __count;
		__count = rdev->opencount;
		wiphy_unlock(&rdev->wiphy);
	if (rdev->wiphy.rfkill)
		rfkill_unregister(rdev->wiphy.rfkill);
	rtnl_lock();
	nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
	rdev->wiphy.registered = false;

	WARN_ON(!list_empty(&rdev->wiphy.wdev_list));

	/*
	 * First remove the hardware from everywhere, this makes
	 * it impossible to find from userspace.
	 */
	debugfs_remove_recursive(rdev->wiphy.debugfsdir);
	list_del_rcu(&rdev->list);
	synchronize_rcu();
	/*
	 * If this device got a regulatory hint tell core its
	 * free to listen now to a new shiny device regulatory hint
	 */
	wiphy_regulatory_deregister(wiphy);
	cfg80211_rdev_list_generation++;
	device_del(&rdev->wiphy.dev);
#ifdef CONFIG_PM
	if (rdev->wiphy.wowlan_config && rdev->ops->set_wakeup)
		rdev_set_wakeup(rdev, false);
#endif

	/* surely nothing is reachable now, clean up work */
	cfg80211_process_wiphy_works(rdev);
	wiphy_unlock(&rdev->wiphy);
	rtnl_unlock();
	/* this has nothing to do now but make sure it's gone */
	cancel_work_sync(&rdev->wiphy_work);

	flush_work(&rdev->scan_done_wk);
	cancel_work_sync(&rdev->conn_work);
	flush_work(&rdev->event_work);
	cancel_delayed_work_sync(&rdev->dfs_update_channels_wk);
	cancel_delayed_work_sync(&rdev->background_cac_done_wk);
	flush_work(&rdev->destroy_work);
	flush_work(&rdev->sched_scan_stop_wk);
	flush_work(&rdev->propagate_radar_detect_wk);
	flush_work(&rdev->propagate_cac_done_wk);
	flush_work(&rdev->mgmt_registrations_update_wk);
	cfg80211_rdev_free_coalesce(rdev);
}
EXPORT_SYMBOL(wiphy_unregister);

void cfg80211_dev_free(struct cfg80211_registered_device *rdev)
	struct cfg80211_internal_bss *scan, *tmp;
	struct cfg80211_beacon_registration *reg, *treg;
	rfkill_destroy(rdev->wiphy.rfkill);
	list_for_each_entry_safe(reg, treg, &rdev->beacon_registrations, list) {
		list_del(&reg->list);
		kfree(reg);
	}
	list_for_each_entry_safe(scan, tmp, &rdev->bss_list, list)
		cfg80211_put_bss(&rdev->wiphy, &scan->pub);
	mutex_destroy(&rdev->wiphy.mtx);

	/*
	 * The 'regd' can only be non-NULL if we never finished
	 * initializing the wiphy and thus never went through the
	 * unregister path - e.g. in failure scenarios. Thus, it
	 * cannot have been visible to anyone if non-NULL, so we
	 * can just free it here.
	 */
	kfree(rcu_dereference_raw(rdev->wiphy.regd));

}

void wiphy_free(struct wiphy *wiphy)
{
	put_device(&wiphy->dev);
}
EXPORT_SYMBOL(wiphy_free);

void wiphy_rfkill_set_hw_state_reason(struct wiphy *wiphy, bool blocked,
				      enum rfkill_hard_block_reasons reason)
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
	if (rfkill_set_hw_state_reason(wiphy->rfkill, blocked, reason))
		schedule_work(&rdev->rfkill_block);
EXPORT_SYMBOL(wiphy_rfkill_set_hw_state_reason);
void cfg80211_cqm_config_free(struct wireless_dev *wdev)
{
	kfree(wdev->cqm_config);
	wdev->cqm_config = NULL;
}

static void _cfg80211_unregister_wdev(struct wireless_dev *wdev,
				      bool unregister_netdev)
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
	lockdep_assert_held(&rdev->wiphy.mtx);
	nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE);

	wdev->registered = false;

	if (wdev->netdev) {
		sysfs_remove_link(&wdev->netdev->dev.kobj, "phy80211");
		if (unregister_netdev)
			unregister_netdevice(wdev->netdev);
	}

	list_del_rcu(&wdev->list);
	rdev->devlist_generation++;

	cfg80211_mlme_purge_registrations(wdev);

	switch (wdev->iftype) {
	case NL80211_IFTYPE_P2P_DEVICE:
		cfg80211_stop_p2p_device(rdev, wdev);
	case NL80211_IFTYPE_NAN:
		cfg80211_stop_nan(rdev, wdev);
		break;
#ifdef CONFIG_CFG80211_WEXT
	kfree_sensitive(wdev->wext.keys);
	cfg80211_cqm_config_free(wdev);

	/*
	 * Ensure that all events have been processed and
	 * freed.
	 */
	cfg80211_process_wdev_events(wdev);

	if (wdev->iftype == NL80211_IFTYPE_STATION ||
	    wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) {
		for (link_id = 0; link_id < ARRAY_SIZE(wdev->links); link_id++) {
			struct cfg80211_internal_bss *curbss;

			curbss = wdev->links[link_id].client.current_bss;

			if (WARN_ON(curbss)) {
				cfg80211_unhold_bss(curbss);
				cfg80211_put_bss(wdev->wiphy, &curbss->pub);
				wdev->links[link_id].client.current_bss = NULL;
			}
		}

void cfg80211_unregister_wdev(struct wireless_dev *wdev)
{
	_cfg80211_unregister_wdev(wdev, true);
EXPORT_SYMBOL(cfg80211_unregister_wdev);

static const struct device_type wiphy_type = {
void cfg80211_update_iface_num(struct cfg80211_registered_device *rdev,
			       enum nl80211_iftype iftype, int num)
{
	lockdep_assert_held(&rdev->wiphy.mtx);

	rdev->num_running_ifaces += num;
	if (iftype == NL80211_IFTYPE_MONITOR)
		rdev->num_running_monitor_ifaces += num;
}

void __cfg80211_leave(struct cfg80211_registered_device *rdev,
		      struct wireless_dev *wdev)
{
	struct net_device *dev = wdev->netdev;
	struct cfg80211_sched_scan_request *pos, *tmp;
	lockdep_assert_held(&rdev->wiphy.mtx);
	ASSERT_WDEV_LOCK(wdev);
	cfg80211_pmsr_wdev_down(wdev);

	switch (wdev->iftype) {
	case NL80211_IFTYPE_ADHOC:
		__cfg80211_leave_ibss(rdev, dev, true);
		break;
	case NL80211_IFTYPE_P2P_CLIENT:
	case NL80211_IFTYPE_STATION:
		list_for_each_entry_safe(pos, tmp, &rdev->sched_scan_req_list,
					 list) {
			if (dev == pos->dev)
				cfg80211_stop_sched_scan_req(rdev, pos, false);
		}

#ifdef CONFIG_CFG80211_WEXT
		kfree(wdev->wext.ie);
		wdev->wext.ie = NULL;
		wdev->wext.ie_len = 0;
		wdev->wext.connect.auth_type = NL80211_AUTHTYPE_AUTOMATIC;
#endif
		cfg80211_disconnect(rdev, dev,
				    WLAN_REASON_DEAUTH_LEAVING, true);
		break;
	case NL80211_IFTYPE_MESH_POINT:
		__cfg80211_leave_mesh(rdev, dev);
		break;
	case NL80211_IFTYPE_AP:
	case NL80211_IFTYPE_P2P_GO:
		__cfg80211_stop_ap(rdev, dev, -1, true);
	case NL80211_IFTYPE_OCB:
		__cfg80211_leave_ocb(rdev, dev);
		break;
	case NL80211_IFTYPE_P2P_DEVICE:
	case NL80211_IFTYPE_NAN:
		/* cannot happen, has no netdev */
		break;
	case NL80211_IFTYPE_AP_VLAN:
	case NL80211_IFTYPE_MONITOR:
		/* nothing to do */
		break;
	case NL80211_IFTYPE_UNSPECIFIED:
	case NL80211_IFTYPE_WDS:
	case NUM_NL80211_IFTYPES:
		/* invalid */
void cfg80211_leave(struct cfg80211_registered_device *rdev,
		    struct wireless_dev *wdev)
{
	wdev_lock(wdev);
	__cfg80211_leave(rdev, wdev);
	wdev_unlock(wdev);
}

void cfg80211_stop_iface(struct wiphy *wiphy, struct wireless_dev *wdev,
			 gfp_t gfp)
{
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
	struct cfg80211_event *ev;
	unsigned long flags;

	trace_cfg80211_stop_iface(wiphy, wdev);

	ev = kzalloc(sizeof(*ev), gfp);
	if (!ev)
		return;

	ev->type = EVENT_STOPPED;

	spin_lock_irqsave(&wdev->event_lock, flags);
	list_add_tail(&ev->list, &wdev->event_list);
	spin_unlock_irqrestore(&wdev->event_lock, flags);
	queue_work(cfg80211_wq, &rdev->event_work);
}
EXPORT_SYMBOL(cfg80211_stop_iface);

void cfg80211_init_wdev(struct wireless_dev *wdev)
{
	mutex_init(&wdev->mtx);
	INIT_LIST_HEAD(&wdev->event_list);
	spin_lock_init(&wdev->event_lock);
	INIT_LIST_HEAD(&wdev->mgmt_registrations);
	INIT_LIST_HEAD(&wdev->pmsr_list);
	spin_lock_init(&wdev->pmsr_lock);
	INIT_WORK(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk);
#ifdef CONFIG_CFG80211_WEXT
	wdev->wext.default_key = -1;
	wdev->wext.default_mgmt_key = -1;
	wdev->wext.connect.auth_type = NL80211_AUTHTYPE_AUTOMATIC;
#endif

	if (wdev->wiphy->flags & WIPHY_FLAG_PS_ON_BY_DEFAULT)
		wdev->ps = true;
	else
		wdev->ps = false;
	/* allow mac80211 to determine the timeout */
	wdev->ps_timeout = -1;

	if ((wdev->iftype == NL80211_IFTYPE_STATION ||
	     wdev->iftype == NL80211_IFTYPE_P2P_CLIENT ||
	     wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr)
		wdev->netdev->priv_flags |= IFF_DONT_BRIDGE;

	INIT_WORK(&wdev->disconnect_wk, cfg80211_autodisconnect_wk);
}

void cfg80211_register_wdev(struct cfg80211_registered_device *rdev,
			    struct wireless_dev *wdev)
{
	ASSERT_RTNL();
	lockdep_assert_held(&rdev->wiphy.mtx);

	/*
	 * We get here also when the interface changes network namespaces,
	 * as it's registered into the new one, but we don't want it to
	 * change ID in that case. Checking if the ID is already assigned
	 * works, because 0 isn't considered a valid ID and the memory is
	 * 0-initialized.
	 */
	if (!wdev->identifier)
		wdev->identifier = ++rdev->wdev_id;
	list_add_rcu(&wdev->list, &rdev->wiphy.wdev_list);
	rdev->devlist_generation++;
	if (wdev->netdev &&
	    sysfs_create_link(&wdev->netdev->dev.kobj, &rdev->wiphy.dev.kobj,
			      "phy80211"))
		pr_err("failed to add phy80211 symlink to netdev!\n");

	nl80211_notify_iface(rdev, wdev, NL80211_CMD_NEW_INTERFACE);
int cfg80211_register_netdevice(struct net_device *dev)
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
	struct cfg80211_registered_device *rdev;
	int ret;

	ASSERT_RTNL();

	if (WARN_ON(!wdev))
		return -EINVAL;

	rdev = wiphy_to_rdev(wdev->wiphy);

	lockdep_assert_held(&rdev->wiphy.mtx);

	/* we'll take care of this */
	wdev->registered = true;
	wdev->registering = true;
	ret = register_netdevice(dev);
	if (ret)
		goto out;

	cfg80211_register_wdev(rdev, wdev);
	ret = 0;
out:
	wdev->registering = false;
	if (ret)
		wdev->registered = false;
	return ret;
}
EXPORT_SYMBOL(cfg80211_register_netdevice);

static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
					 unsigned long state, void *ptr)
	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
	struct wireless_dev *wdev = dev->ieee80211_ptr;
	struct cfg80211_registered_device *rdev;
	struct cfg80211_sched_scan_request *pos, *tmp;
		return NOTIFY_DONE;
	rdev = wiphy_to_rdev(wdev->wiphy);
	WARN_ON(wdev->iftype == NL80211_IFTYPE_UNSPECIFIED);
	case NETDEV_POST_INIT:
		SET_NETDEV_DEVTYPE(dev, &wiphy_type);
		wdev->netdev = dev;
		/* can only change netns with wiphy */
		dev->features |= NETIF_F_NETNS_LOCAL;

		cfg80211_init_wdev(wdev);
		if (!wdev->registered) {
			wiphy_lock(&rdev->wiphy);
			cfg80211_register_wdev(rdev, wdev);
Johannes Berg's avatar
Johannes Berg committed
		/*
		 * It is possible to get NETDEV_UNREGISTER multiple times,
		 * so check wdev->registered.
Johannes Berg's avatar
Johannes Berg committed
		 */
		if (wdev->registered && !wdev->registering) {
			_cfg80211_unregister_wdev(wdev, false);
	case NETDEV_GOING_DOWN:
		cfg80211_leave(rdev, wdev);
		cfg80211_remove_links(wdev);
		wiphy_unlock(&rdev->wiphy);
		/* since we just did cfg80211_leave() nothing to do there */
		cancel_work_sync(&wdev->disconnect_wk);
		cancel_work_sync(&wdev->pmsr_free_wk);
		break;
	case NETDEV_DOWN:
		cfg80211_update_iface_num(rdev, wdev->iftype, -1);
		if (rdev->scan_req && rdev->scan_req->wdev == wdev) {
			if (WARN_ON(!rdev->scan_req->notified &&
				    (!rdev->int_scan_req ||
				     !rdev->int_scan_req->notified)))
				rdev->scan_req->info.aborted = true;
			___cfg80211_scan_done(rdev, false);
		list_for_each_entry_safe(pos, tmp,
					 &rdev->sched_scan_req_list, list) {
				cfg80211_stop_sched_scan_req(rdev, pos, false);
		}

		rdev->opencount--;
		wiphy_unlock(&rdev->wiphy);
		wake_up(&rdev->dev_wait);
		break;
	case NETDEV_UP:
		cfg80211_update_iface_num(rdev, wdev->iftype, 1);
Johannes Berg's avatar
Johannes Berg committed
		wdev_lock(wdev);
		switch (wdev->iftype) {
#ifdef CONFIG_CFG80211_WEXT
		case NL80211_IFTYPE_ADHOC:
			cfg80211_ibss_wext_join(rdev, wdev);
		case NL80211_IFTYPE_STATION:
			cfg80211_mgd_wext_connect(rdev, wdev);
		case NL80211_IFTYPE_MESH_POINT:
			{
				/* backward compat code... */
				struct mesh_setup setup;
				memcpy(&setup, &default_mesh_setup,
						sizeof(setup));
				 /* back compat only needed for mesh_id */
				setup.mesh_id = wdev->u.mesh.id;
				setup.mesh_id_len = wdev->u.mesh.id_up_len;
				if (wdev->u.mesh.id_up_len)
					__cfg80211_join_mesh(rdev, dev,
							&setup,
							&default_mesh_config);
				break;
			}
#endif
Johannes Berg's avatar
Johannes Berg committed
		wdev_unlock(wdev);
		rdev->opencount++;

		/*
		 * Configure power management to the driver here so that its
		 * correctly set also after interface type changes etc.
		 */
		if ((wdev->iftype == NL80211_IFTYPE_STATION ||
		     wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) &&
		    rdev->ops->set_power_mgmt &&
		    rdev_set_power_mgmt(rdev, dev, wdev->ps,
					wdev->ps_timeout)) {
			/* assume this means it's off */
			wdev->ps = false;
		}
		wiphy_unlock(&rdev->wiphy);
	case NETDEV_PRE_UP:
		if (!cfg80211_iftype_allowed(wdev->wiphy, wdev->iftype,
					     wdev->use_4addr, 0))
			return notifier_from_errno(-EOPNOTSUPP);
		if (rfkill_blocked(rdev->wiphy.rfkill))
			return notifier_from_errno(-ERFKILL);
		break;
	default:
		return NOTIFY_DONE;
	wireless_nlevent_flush();

}

static struct notifier_block cfg80211_netdev_notifier = {
	.notifier_call = cfg80211_netdev_notifier_call,
};

static void __net_exit cfg80211_pernet_exit(struct net *net)
{
	struct cfg80211_registered_device *rdev;

	rtnl_lock();
	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
		if (net_eq(wiphy_net(&rdev->wiphy), net))
			WARN_ON(cfg80211_switch_netns(rdev, &init_net));
	}
	rtnl_unlock();
}

static struct pernet_operations cfg80211_pernet_ops = {
	.exit = cfg80211_pernet_exit,
};

void wiphy_work_queue(struct wiphy *wiphy, struct wiphy_work *work)
{
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
	unsigned long flags;

	spin_lock_irqsave(&rdev->wiphy_work_lock, flags);
	if (list_empty(&work->entry))
		list_add_tail(&work->entry, &rdev->wiphy_work_list);
	spin_unlock_irqrestore(&rdev->wiphy_work_lock, flags);

	schedule_work(&rdev->wiphy_work);
}
EXPORT_SYMBOL_GPL(wiphy_work_queue);

void wiphy_work_cancel(struct wiphy *wiphy, struct wiphy_work *work)
{
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
	unsigned long flags;

	lockdep_assert_held(&wiphy->mtx);

	spin_lock_irqsave(&rdev->wiphy_work_lock, flags);
	if (!list_empty(&work->entry))
		list_del_init(&work->entry);
	spin_unlock_irqrestore(&rdev->wiphy_work_lock, flags);
}
EXPORT_SYMBOL_GPL(wiphy_work_cancel);

void wiphy_delayed_work_timer(struct timer_list *t)
{
	struct wiphy_delayed_work *dwork = from_timer(dwork, t, timer);

	wiphy_work_queue(dwork->wiphy, &dwork->work);
}
EXPORT_SYMBOL(wiphy_delayed_work_timer);

void wiphy_delayed_work_queue(struct wiphy *wiphy,
			      struct wiphy_delayed_work *dwork,
			      unsigned long delay)
{
	if (!delay) {
		wiphy_work_queue(wiphy, &dwork->work);
		return;
	}

	dwork->wiphy = wiphy;
	mod_timer(&dwork->timer, jiffies + delay);
}
EXPORT_SYMBOL_GPL(wiphy_delayed_work_queue);

void wiphy_delayed_work_cancel(struct wiphy *wiphy,
			       struct wiphy_delayed_work *dwork)
{
	lockdep_assert_held(&wiphy->mtx);

	del_timer_sync(&dwork->timer);
	wiphy_work_cancel(wiphy, &dwork->work);
}
EXPORT_SYMBOL_GPL(wiphy_delayed_work_cancel);

static int __init cfg80211_init(void)
	err = register_pernet_device(&cfg80211_pernet_ops);
	if (err)
		goto out_fail_pernet;

	err = wiphy_sysfs_init();
	if (err)
		goto out_fail_sysfs;

	err = register_netdevice_notifier(&cfg80211_netdev_notifier);
	if (err)
		goto out_fail_notifier;

	err = nl80211_init();
	if (err)
		goto out_fail_nl80211;

	ieee80211_debugfs_dir = debugfs_create_dir("ieee80211", NULL);

	err = regulatory_init();
	if (err)
		goto out_fail_reg;

	cfg80211_wq = alloc_ordered_workqueue("cfg80211", WQ_MEM_RECLAIM);
	if (!cfg80211_wq) {
		err = -ENOMEM;
out_fail_wq:
	regulatory_exit();
out_fail_reg:
	debugfs_remove(ieee80211_debugfs_dir);
	nl80211_exit();
out_fail_nl80211:
	unregister_netdevice_notifier(&cfg80211_netdev_notifier);
out_fail_notifier:
	wiphy_sysfs_exit();
out_fail_sysfs:
	unregister_pernet_device(&cfg80211_pernet_ops);
out_fail_pernet:
static void __exit cfg80211_exit(void)
{
	debugfs_remove(ieee80211_debugfs_dir);
	unregister_netdevice_notifier(&cfg80211_netdev_notifier);
	wiphy_sysfs_exit();
	unregister_pernet_device(&cfg80211_pernet_ops);
	destroy_workqueue(cfg80211_wq);
}
module_exit(cfg80211_exit);