好记性不如铅笔头

操作系统

linux802.11fullmac代码流程简单解析

Linux中wifi驱动分为两种模式,FullMac模式和SoftMac模式,于此相对应,Linux也有两套对应的驱动和代码框架来实现,这里简单的笔记下FullMac的代码框架。

注:

1 本文基于Linux2.6.32.65代码实现。

2 作者在Linux wireless开发方面是新手一枚,对框架的理解上可能不太深刻,欢迎大家批评指正。

在Linux2.6.32目录中,可以发现在/net下有两个目录,mac80211和wireless.这里分别对应了softmac模式和fullmac实现模式.这里简单的笔记下wireless即fullmac模式下的功能实现.
cfg80211是fullmac下的linux的对应驱动,由于fullmac下大多数工作都放在了wifi芯片上,因此cfg80211没有做太多的wifi业务上的工作,而是作为一个框架用于和外部程序间(比如hostapd,wpa_supplicant等)进行沟通.
由于cfg80211等驱动是工作在Linux内核模式下,hostapd等程序是工作在Linux用户模式下,因此Linux用户态和内核态的交互方式是实现关键.最新的Linux是基于netlink方式来实现的,在Linux内核中这部分功能集中在nl80211中.这里以linux2.6.32源码为例子,简单的笔记下主要的流程.

\linux-2.6.32.65\net\wireless\nl80211.c
注册驱动侧genetlink的回调响应函数.

static struct genl_ops nl80211_ops[] = {
	{
		.cmd = NL80211_CMD_ASSOCIATE,
		.doit = nl80211_associate,
		.policy = nl80211_policy,
		.flags = GENL_ADMIN_PERM,
	},
......
......
};
/* nl80211初始化时注册genetlink */
int nl80211_init(void)
{
	int err;

	err = genl_register_family_with_ops(&nl80211_fam,
		nl80211_ops, ARRAY_SIZE(nl80211_ops));
	if (err)
		return err;
......
......
}

这里以连接流程为例,简单的笔记下函数调用流程.
当外部使用genetlink连接上之后,发送NL80211_CMD_ASSOCIATE命令,就会路由到nl80211_associate函数,我们跟着函数流程一级级看下去,最终可以看到调用了注册到cf80211内部的回调函数。

/* 连接回调函数 */
static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev; /* cfg80211_registered_device可以理解为是cfg802.11的管理结构体 */
	struct net_device *dev;/* 网络设备 */
......
......

	/* 获取对应的管理结构体指针 */
	err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
	if (err)
		goto unlock_rtnl;

/* 注册到cfg80211的回调函数必须定义 */
	if (!rdev->ops->assoc) {
		err = -EOPNOTSUPP;
		goto out;
	}
......
......

		err = cfg80211_mlme_assoc(rdev, dev, chan, bssid, prev_bssid,
					  ssid, ssid_len, ie, ie_len, use_mfp,
					  &crypto);
......
......
}
\linux-2.6.32.65\net\wireless\mlme.c

int cfg80211_mlme_assoc(struct cfg80211_registered_device *rdev,
			struct net_device *dev,
			struct ieee80211_channel *chan,
			const u8 *bssid, const u8 *prev_bssid,
			const u8 *ssid, int ssid_len,
			const u8 *ie, int ie_len, bool use_mfp,
			struct cfg80211_crypto_settings *crypt)
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
	int err;

	wdev_lock(wdev);
	err = __cfg80211_mlme_assoc(rdev, dev, chan, bssid, prev_bssid,
				    ssid, ssid_len, ie, ie_len, use_mfp, crypt);
	wdev_unlock(wdev);

	return err;
}

int __cfg80211_mlme_assoc(struct cfg80211_registered_device *rdev,
			  struct net_device *dev,
			  struct ieee80211_channel *chan,
			  const u8 *bssid, const u8 *prev_bssid,
			  const u8 *ssid, int ssid_len,
			  const u8 *ie, int ie_len, bool use_mfp,
			  struct cfg80211_crypto_settings *crypt)
{
......
......
	err = rdev->ops->assoc(&rdev->wiphy, dev, &req);
......
......
}

那么cfg80211中的回调函数是如何注册的呢?
在\linux-2.6.32.65\net\wireless\core.c中,可以看到有个函数为,该函数便是外部驱动注册回调函数结构体的位置,注意这里所谓的注册是将回调函数结构体包装为一个cfg80211_registered_device结构体,在整个802.11流程中使用.

struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv)
{
	static int wiphy_counter;

	/* 注册到linux内核中的802.11指针 */
	struct cfg80211_registered_device *rdev;
	int alloc_size;

	/* 传入函数指针校验 */
	WARN_ON(ops->add_key && (!ops->del_key || !ops->set_default_key));
	WARN_ON(ops->auth && (!ops->assoc || !ops->deauth || !ops->disassoc));
	WARN_ON(ops->connect && !ops->disconnect);
	WARN_ON(ops->join_ibss && !ops->leave_ibss);
	WARN_ON(ops->add_virtual_intf && !ops->del_virtual_intf);
	WARN_ON(ops->add_station && !ops->del_station);
	WARN_ON(ops->add_mpath && !ops->del_mpath);

	/* 计算malloc的大小 */
	alloc_size = sizeof(*rdev) + sizeof_priv;

	rdev = kzalloc(alloc_size, GFP_KERNEL);
	if (!rdev)
		return NULL;

	rdev->ops = ops;/* 保存ops指针 */

	mutex_lock(&cfg80211_mutex);

	rdev->wiphy_idx = wiphy_counter++;/* 可能有多个cfg80211被注册,这里计数硬件 */

	if (unlikely(!wiphy_idx_valid(rdev->wiphy_idx))) {
		wiphy_counter--;
		mutex_unlock(&cfg80211_mutex);
		/* ugh, wrapped! */
		kfree(rdev);
		return NULL;
	}

	mutex_unlock(&cfg80211_mutex);

	/* give it a proper name */
	dev_set_name(&rdev->wiphy.dev, PHY_NAME "%d", rdev->wiphy_idx);

	mutex_init(&rdev->mtx);
	mutex_init(&rdev->devlist_mtx);
	INIT_LIST_HEAD(&rdev->netdev_list);
	spin_lock_init(&rdev->bss_lock);
	INIT_LIST_HEAD(&rdev->bss_list);
	INIT_WORK(&rdev->scan_done_wk, __cfg80211_scan_done);

	device_initialize(&rdev->wiphy.dev);
	rdev->wiphy.dev.class = &ieee80211_class;
	rdev->wiphy.dev.platform_data = rdev;

	rdev->wiphy.ps_default = CONFIG_CFG80211_DEFAULT_PS_VALUE;

	wiphy_net_set(&rdev->wiphy, &init_net);

	rdev->rfkill_ops.set_block = cfg80211_rfkill_set_block;
	rdev->rfkill = rfkill_alloc(dev_name(&rdev->wiphy.dev),
				   &rdev->wiphy.dev, RFKILL_TYPE_WLAN,
				   &rdev->rfkill_ops, rdev);

	if (!rdev->rfkill) {
		kfree(rdev);
		return NULL;
	}

	INIT_WORK(&rdev->rfkill_sync, cfg80211_rfkill_sync_work);
	INIT_WORK(&rdev->conn_work, cfg80211_conn_work);
	INIT_WORK(&rdev->event_work, cfg80211_event_work);

	init_waitqueue_head(&rdev->dev_wait);

	/*
	 * Initialize wiphy parameters to IEEE 802.11 MIB default values.
	 * Fragmentation and RTS threshold are disabled by default with the
	 * special -1 value.
	 */
	rdev->wiphy.retry_short = 7;
	rdev->wiphy.retry_long = 4;
	rdev->wiphy.frag_threshold = (u32) -1;
	rdev->wiphy.rts_threshold = (u32) -1;

	return &rdev->wiphy;
}

 

Leave a Reply

11 + 2 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据