概述
之所以要分析这个,是因为上层wpa_supplicant和WIFI驱动打交道的方式,多半是通过ioctl的方式进行的,所以看看它的调用逻辑(这里只列出其主要的调用逻辑):
上面便是用户ioctl调用的流程图,它最终分为两条线即有两种支持,选择那一条或两条都选(个人感觉最好选第2条线,因为它最后也是会调用到相应的函数的,而且还有其它更多的命令支持),从实际的代码来看,如果dev->netdev_ops
->ndo_do_ioctl被初始化了,那么它一定会被调用,是否被初始化,在前面选择对net结构变量的初始化方式中有讨论过。
下面来具体看看该调用流程,首先说明下,上面的流程主要实现在kernel/net/wireless/wext_core.c文件中,这是wireless的协议层实现,恰好我们在wpa_supplicant中通常选择的驱动类型也是wext,它的入口函数是wext_ioctl_dispatch:
static int
wext_ioctl_dispatch(struct net *net, struct
ifreq*ifr,
unsigned
int cmd, struct iw_request_info *info,
wext_ioctl_func
standard,
wext_ioctl_funcprivate)
{
int ret = wext_permission_check(cmd);
if (ret)
return ret;
dev_load(net, ifr->ifr_name);
rtnl_lock();
ret
= wireless_process_ioctl(net, ifr, cmd, info,
standard,private);
rtnl_unlock();
return ret;
}
它其实就是wireless_process_ioctl的封装函数,除了进行许可权限的确认,没有做什么其它内容,这里有standard和private两个函数指针的传递,其实就是两个回调函数,在后面会用到,它是由wext_handle_ioctl函数传递过来的:
int wext_handle_ioctl(structnet *net, struct ifreq *ifr, unsigned
int cmd,
void __user *arg)
{
struct iw_request_info info = { .cmd =cmd, .flags = 0 };
int ret;
ret
= wext_ioctl_dispatch(net, ifr, cmd,
&info,
ioctl_standard_call,
ioctl_private_call); //这两个回调函数的定义之后再讨论,这里暂不理论
if (ret >= 0 &&
IW_IS_GET(cmd) &&
copy_to_user(arg, ifr, sizeof(structiwreq)))
return -EFAULT;
return ret;
}
实际上传递的就是ioctl_standard_call和ioctl_private_call两个函数,在看看wireless_process_ioctl函数,这个函数很重要,下面做重点分析:
static intwireless_process_ioctl(struct net *net, struct ifreq
*ifr,
unsigned
int cmd,
structiw_request_info *info,
wext_ioctl_func
standard,
wext_ioctl_func
private)
{
struct iwreq *iwr = (struct iwreq *)ifr;
struct net_device *dev;
iw_handler handler;
if
((dev = __dev_get_by_name(net, ifr->ifr_name)) ==
NULL)//通过网络接口名获取net_device设备
return -ENODEV;
if
(cmd == SIOCGIWSTATS)
returnstandard(dev,
iwr, cmd, info,
&iw_handler_get_iwstats); //如果是状态查询命令,调用该函数(回调函数中的一个)
#ifdef CONFIG_WEXT_PRIV
if
(cmd == SIOCGIWPRIV &&
dev->wireless_handlers)
returnstandard(dev,
iwr, cmd, info,
iw_handler_get_private); //如果是专有命令,调用回调函数,同上
#endif
if (!netif_device_present(dev))
return -ENODEV;
handler
= get_handler(dev,
cmd);//根据cmd参数,从dev成员中查询相应的处理函数
if (handler) {
if (cmd < SIOCIWFIRSTPRIV)
return
standard(dev, iwr, cmd, info,
handler);//调用相应命令的处理函数
else if (private)
return
private(dev, iwr, cmd, info,
handler);//同上
}
if(dev->netdev_ops->ndo_do_ioctl)
return
dev->netdev_ops->ndo_do_ioctl(dev,ifr,
cmd);//如果被设置就调用该函数
return -EOPNOTSUPP;
}
该函数的大意是,通过网络接口名称获得一个网络设备,然后根据命令的类型调用相应的处理函数,特别的是当dev->netdev_ops->ndo_do_ioctl或dev->wireless_handlers被设置时,则会查找执行对应的处理函数。Get_handle函数用于查询处理函数使用:
static iw_handlerget_handler(struct net_device *dev, unsigned int
cmd)
{
unsigned int index;
const
struct iw_handler_def *handlers = NULL;
#ifdef CONFIG_CFG80211_WEXT
if (dev->ieee80211_ptr
&&dev->ieee80211_ptr->wiphy)
handlers
=dev->ieee80211_ptr->wiphy->wext;//初始化默认的处理函数
#endif
#ifdef CONFIG_WIRELESS_EXT
if (dev->wireless_handlers)
handlers=
dev->wireless_handlers; //这里的dev->wireless_handlers在net初始化时被作为扩张功能选择性的设置,前面有提到过
#endif
if (!handlers)
return NULL;
index = IW_IOCTL_IDX(cmd);
if (index num_standard)
returnhandlers->standard[index]; //返回对应的标准函数
#ifdef CONFIG_WEXT_PRIV
index = cmd - SIOCIWFIRSTPRIV;
if (index num_private)
return
handlers->private[index];//返回对应的专有函数
#endif
return NULL;
}
那么这个dev->wireless_handlers究竟是什么,这里来揭开它的神秘面纱,在bcm4329源码src/wl/sys/wl_iw.c中,有它的定义:
static const iw_handler wl_iw_handler[]=
{
(iw_handler) wl_iw_config_commit,
(iw_handler) wl_iw_get_name,
(iw_handler) NULL,
......
}
static const iw_handler wl_iw_priv_handler[]= {
NULL,
(iw_handler)wl_iw_set_active_scan,
NULL,
(iw_handler)wl_iw_get_rssi,
......
}
const struct iw_handler_def wl_iw_handler_def =
{
.num_standard =ARRAYSIZE(wl_iw_handler),
.standard
= (iw_handler *) wl_iw_handler,
.num_private = ARRAYSIZE(wl_iw_priv_handler),
.num_private_args =ARRAY_SIZE(wl_iw_priv_args),
.private
= (iw_handler *)wl_iw_priv_handler,
.private_args = (void *)wl_iw_priv_args,
#if WIRELESS_EXT >= 19
get_wireless_stats:dhd_get_wireless_stats,
#endif
};
#endif
在net初始化的时候,这里把dev->wireless_handlers和dev->netdev_ops的初始化代码再贴出来:
int
dhd_net_attach(dhd_pub_t*dhdp, int ifidx)
{
……
#if (LINUX_VERSION_CODE
ASSERT(!net->open);
net->get_stats
= dhd_get_stats;
net->do_ioctl
=dhd_ioctl_entry;
net->hard_start_xmit
= dhd_start_xmit;
net->set_mac_address
= dhd_set_mac_address;
net->set_multicast_list
= dhd_set_multicast_list;
net->open
=net->stop = NULL;
#else
ASSERT(!net->netdev_ops);
net->netdev_ops
= &dhd_ops_virt;
#endif
……
#if WIRELESS_EXT > 12
net->wireless_handlers
= (struct
iw_handler_def*)&wl_iw_handler_def;//这里的初始化工作很重要,之后的ioctl流程会涉及到对它的使用
#endif
……
}
看到这里,应该可以明白相应的命令最终会在wl_iw.c中被执行,这些处理函数也是在该文件中实现。上面已经获取了命令的处理函数,那么它是如何被执行的呢?这里wireless_process_ioctl里有standard和private的回调函数的调用:
static intioctl_standard_call(struct net_device
* dev,
structiwreq *iwr,
unsigned
int cmd,
structiw_request_info *info,
iw_handler handler)
{
const struct
iw_ioctl_description* descr;
int ret
= -EINVAL;
if (IW_IOCTL_IDX(cmd) >=standard_ioctl_num)
return -EOPNOTSUPP;
descr =&(standard_ioctl[IW_IOCTL_IDX(cmd)]);
if (descr->header_type !=IW_HEADER_TYPE_POINT) {
ret
= handler(dev, info,
&(iwr->u),NULL);
if ((descr->flags
&IW_DESCR_FLAG_EVENT)
&&
((ret == 0) || (ret ==-EIWCOMMIT)))
wireless_send_event(dev,
cmd,
&(iwr->u),NULL);
} else {
ret
=ioctl_standard_iw_point(&iwr->u.data,
cmd, descr,
handler,
dev, info);
}
if (ret == -EIWCOMMIT)
ret
=call_commit_handler(dev);
return ret;
}
回调函数中对传递过来的handler函数指针进行呼叫,对应的处理函数就会被执行,当然用户传送的命令还不止这些,所以才会有net->netdev_ops的存在的必要性。下面来就来看看执行到:
return
dev->netdev_ops->ndo_do_ioctl(dev,
ifr,
cmd);//wireless_process_ioctl的最后一句
就会调用dhd_ioctl函数,这是wlan驱动对ioctl调用的处理函数,就是根据用户传递过来的cmd,给它找一个最合适最合理的“归宿”。
static int
dhd_ioctl_entry(structnet_device *net, struct ifreq *ifr, int
cmd)
{
......#ifdefined(CONFIG_WIRELESS_EXT)
if ((cmd >= SIOCIWFIRST)
&&(cmd <=
SIOCIWLAST)) {
ret
= wl_iw_ioctl(net, ifr, cmd);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return ret;
}
#endif
#if LINUX_VERSION_CODE >KERNEL_VERSION(2, 4, 2)
if (cmd == SIOCETHTOOL) {
ret
=
dhd_ethtool(dhd,(void*)ifr->ifr_data);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return ret;
}
#endif
if (cmd == SIOCDEVPRIVATE+1) {
ret
= wl_android_priv_cmd(net, ifr, cmd);
dhd_check_hang(net,&dhd->pub,
ret);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return ret;
}
if (cmd != SIOCDEVPRIVATE) {
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return -EOPNOTSUPP;
}
memset(&ioc, 0, sizeof(ioc));
......
bcmerror =
dhd_wl_ioctl(&dhd->pub, ifidx,
(wl_ioctl_t*)&ioc, buf,
buflen);
......
}
限于篇幅,该函数处理过程不再详述,大致的命令处理方法相似,wl_iw.c中的系列处理函数只是其中的一部分,wl_android中和dhd_linux.c也有相应的处理函数。
2 数据的传送
2.1 数据传送过程简述
传送指的是通过一个网络连接发送一个报文的行为.。无论何时内核需要传送一个数据报文, 它都必须调用驱动的
hard_start_xmit 方法将数据放在外出队列上。
每个内核处理的报文都包含在一个 socket缓存结构( 结构 sk_buff )里,
定义见。这个结构从 Unix 抽象中得名, 用来代表一个网络连接socket.。对于接口来说, 一个 socket
缓存只是一个报文。
传给 hard_start_xmit 的socket 缓存包含物理报文, 它应当出现在媒介上,
以传输层的头部结束。接口不需要修改要传送的数据.。skb->data指向要传送的报文,skb->len
是以字节计的长度。传送下来的sk_buff中的数据已经包含硬件需要的帧头(这是通过hard_header函数将传递进入的信息,组织成设备特有的硬件头),所以在发送方法里不需要再填充硬件帧头,数据可以直接提
交给硬件发送。sk_buff是被锁住的(locked),确保其他程序不会存取它。
所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit时,发送的数据放在一个sk_buff
结构中。一般的驱动程序把数据传给硬件发出去。也有一些特殊的设备比如loopback把数据组成一个接收数据再回送给系统,或者dummy设备直接丢弃
数据。如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。
2.2 Bcm4329芯片wlan驱动数据传送
当上层传送过来报文,调用hard_start_xmit函数(该方法主用于初始化数据包的传输),该函数主要用于转换sk_buf,将其组织成pktbuf数据格式,然后调用dhd_sendpkt函数将pktbuf通过dhd
bus发送到wifi芯片,最后硬件wifi芯片将报文radio发送到网络上。
int
dhd_start_xmit(struct sk_buff
*skb,struct net_device *net)
{
......
if (!(pktbuf =PKTFRMNATIVE(dhd->pub.osh, skb)))
{
DHD_ERROR(("%s:PKTFRMNATIVE failedn",
dhd_ifname(&dhd->pub,
ifidx)));
dev_kfree_skb_any(skb);//转换成功,释放skb,在通常处理中,会在中断中做该操作
ret = -ENOMEM;
goto done;
}
#ifdef WLMEDIA_HTSF
if (htsfdlystat_sz
&&PKTLEN(dhd->pub.osh,
pktbuf) >= ETHER_ADDR_LEN) {
uint8 *pktdata = (uint8*)PKTDATA(dhd->pub.osh,
pktbuf);
struct ether_header *eh =(struct ether_header *)pktdata;
if(!ETHER_ISMULTI(eh->ether_dhost)
&&
(ntoh16(eh->ether_type)
== ETHER_TYPE_IP)) {
eh->ether_type
=hton16(ETHER_TYPE_BRCM_PKTDLYSTATS);
}
}
#endif
ret = dhd_sendpkt(&dhd->pub,
ifidx,pktbuf); //发送pktbuf
......
}
int
dhd_sendpkt(dhd_pub_t *dhdp,
intifidx, void *pktbuf)
{
......
#ifdef PROP_TXSTATUS
if (dhdp->wlfc_state
&&((athost_wl_status_info_t*)dhdp->wlfc_state)->proptxstatus_mode
!= WLFC_FCMODE_NONE) {
dhd_os_wlfc_block(dhdp);
ret =dhd_wlfc_enque_sendq(dhdp->wlfc_state,
DHD_PKTTAG_FIFO(PKTTAG(pktbuf)),
pktbuf);
dhd_wlfc_commit_packets(dhdp->wlfc_state, (f_commitpkt_t)dhd_bus_txdata,
dhdp->bus);
if(((athost_wl_status_info_t*)dhdp->wlfc_state)->toggle_host_if)
{
((athost_wl_status_info_t*)dhdp->wlfc_state)->toggle_host_if=
0;
}
dhd_os_wlfc_unblock(dhdp);
}
else
ret = dhd_bus_txdata(dhdp->bus,
pktbuf);//在SDIO总线上传输
#else
ret = dhd_bus_txdata(dhdp->bus,
pktbuf);
#endif
if (dhd_dpc_prio >= 0) {
PROC_START(dhd_dpc_thread,
dhd,&dhd->thr_dpc_ctl,
0);
} else {
tasklet_init(&dhd->tasklet,
dhd_dpc,(ulong)dhd);
dhd->thr_dpc_ctl.thr_pid =-1;
}
首先来看看轮询方式的过程:
dhd_dpc_thread(void *data)
{
tsk_ctl_t *tsk = (tsk_ctl_t *)data;
dhd_info_t *dhd = (dhd_info_t*)tsk->parent;
if (dhd_dpc_prio > 0)
{
struct sched_param param;
param.sched_priority =(dhd_dpc_prio <
MAX_RT_PRIO)?dhd_dpc_prio:(MAX_RT_PRIO-1);
setScheduler(current,
SCHED_FIFO,¶m);
}
DAEMONIZE("dhd_dpc");
complete(&tsk->completed);
while (1) {
if (down_interruptible(&tsk->sema)==
0) {
SMP_RD_BARRIER_DEPENDS();
if (tsk->terminated){
break;
}
if (dhd->pub.busstate!=
DHD_BUS_DOWN) {
if
(dhd_bus_dpc(dhd->pub.bus)) {
up(&tsk->sema);
}
else {
DHD_OS_WAKE_UNLOCK(&dhd->pub);
}
} else {
if
(dhd->pub.up)
dhd_bus_stop(dhd->pub.bus,
TRUE);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
}
}
else
break;
}
complete_and_exit(&tsk->completed,
0);
}
这里是一个永真循环,直到接收到终止信号才停止,该线程就是通过不断调用dhd_bus_dpc函数调用实现轮询的,它的调用逻辑如下所示:
上面是dhd_dpc_thread的调用逻辑,最后通过netif_rx将数据提交到上层协议,那么,还有一种中断方式时如何实现的呢?上面只看到驱动初始化了一个tasklet,一个中断下半部的实例。其实在dhdsdh_probe函数中已经注册了这个中断处理函数:
static void *
dhdsdio_probe(uint16 venid,
uint16devid, uint16 bus_no, uint16 slot,
uint16 func, uint bustype, void*regsva, osl_t * osh, void *sdh)
{
......
if (bus->intr) {
DHD_INTR(("%s: disableSDIO interrupts (not interested yet)n",
__FUNCTION__));
bcmsdh_intr_disable(sdh); //首先禁止SDIO中断,再注册中断
if
((ret= bcmsdh_intr_reg(sdh, dhdsdio_isr, bus)) != 0) {
DHD_ERROR(("%s:FAILED: bcmsdh_intr_reg returned %dn",
__FUNCTION__,
ret));
goto fail;
}
DHD_INTR(("%s: registeredSDIO interrupt function okn",
__FUNCTION__));
} else {
DHD_INFO(("%s: SDIOinterrupt function is NOT registered due to
polling moden",
__FUNCTION__));
}
......
}
看看Dhdsdio_isr这个中断处理函数干了什么?在函数的最后部分是:
#if defined(SDIO_ISR_THREAD)
DHD_TRACE(("Calling dhdsdio_dpc()from %sn", __FUNCTION__));
DHD_OS_WAKE_LOCK(bus->dhd);
while
(dhdsdio_dpc(bus));
DHD_OS_WAKE_UNLOCK(bus->dhd);
#else
bus->dpc_sched = TRUE;
dhd_sched_dpc(bus->dhd);
#endif
Dhd_sched_dpc函数在最后被调用(上面的while循环调用dhdsdio_dpc,其实和下面的这个调用函数最后的作用是一样的,就不予详述),这个函数的代码如下:
void
dhd_sched_dpc(dhd_pub_t *dhdp)
{
dhd_info_t *dhd = (dhd_info_t*)dhdp->info;
DHD_OS_WAKE_LOCK(dhdp);
#ifdef DHDTHREAD
if (dhd->thr_dpc_ctl.thr_pid >=0)
{
up(&dhd->thr_dpc_ctl.sema);
return;
}
#endif
tasklet_schedule(&dhd->tasklet);
}
就是触发一个中断的下半部tasklet,让cpu选择在一个合适的时候调用dhd_dpc函数,这个函数会调用dhd_bus_dpc,然后进入上面流程图的调用逻辑。
详细的数据处理过程不详细叙述,可以参考源码来具体分析。
电源管理始终是手机等移动设备最重要的一个功能,尤其对于Android这种智能手机或者说手机电脑化的设备,电源管理更显得十分重要。
Linux一直在传统的PC和服务器市场上有很好的应用,也有了比较好的电源管理框架,但是对于智能手机等嵌入式设备来说,Linux标准的电源管理就显得不是很适用了,有许多需要改进的地方。Android在这方面做了一些比较好的尝试,添加了一些新的特性,包括wake_lock,early_supend等。这里对wake_lock不做介绍,只介绍WIFI模块在系统将要或正在进入休眠的一些动作,感兴趣的话可以自己查阅android的电源管理相关文章。
在介绍实质内容之前,先来看看android的电源管理的实现基础:Linux系统的电源管理Suspend框架跟Linux系统的驱动模型(Linux
DriverModel)是相关的,也是基于Linux的驱动模型来实现的,下面的图描述了Linux系统电源管理的Suspend系统框架,Linux的Suspend系统分为两部分,一部分是平台无关的核心层,另一个是平台相关的平台层。操作接口都在平台无关的核心层里了,平台相关部分会使用Suspend
API将自己的操作函数注册进Suspend核心层里。
根据Linux系统驱动模型,Device结构描述了一个设备,device_driver是设备的驱动,而class、type和bus分别描述了设备所属的类别、类型和总线。而设备的电源管理也根据此模型分为class级的、type级的、bus级的和驱动级的。如果一个设备的class或者bus确切的知道如何管理一个设备的电源的时候,驱动级别的suspend/resume就可以为空了。这极大的提高了电源管理的高效性和灵活性。
对于android平台上整个系统是如何一步一步进入休眠的,我这里不做详细介绍,只作出它的大致流程图:
此流程图显示了系统的休眠的全过程,对WIFI模块来说,我们主要关注early_suspend和suspend以及相应的唤醒过程。当系统屏幕超时或用户(亮屏时)按power键,系统进入休眠流程(这里不讨论可能的中途退出休眠的其它因素),即在没有进程持有wakelock情况下,首先进入early_suspend流程。
Early_suspend流程的实现基础是:android电源管理系统中定义了一个early_suspend结构链表,里面存放了所有系统中注册了的early_suspend实例,即如果一个模块要在系统进入early_suspend状态有所动作,就必须注册一个early_suspend实例。在WIFI驱动模块中,当驱动流程走到dhd_attach函数时,有相应的early_suspend注册代码:
Path: dhd/sys/dhd_linux.c
dhd_pub_t *
dhd_attach(osl_t *osh, structdhd_bus *bus, uint bus_hdrlen)
{
......
#ifdef CONFIG_HAS_EARLYSUSPEND
dhd->early_suspend.level
=EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 20;
dhd->early_suspend.suspend
= dhd_early_suspend;
dhd->early_suspend.resume
= dhd_late_resume;
register_early_suspend(&dhd->early_suspend);
dhd_state |= DHD_ATTACH_STATE_EARLYSUSPEND_DONE;
#endif
......
}
红色区域初始化了dhd结构的两个early_suspend函数,并将其注册到电源管理系统。early_suspend的休眠函数的代码如下:
static void dhd_early_suspend(structearly_suspend
*h)
{
struct dhd_info *dhd = container_of(h,struct dhd_info,
early_suspend);
DHD_TRACE(("%s:
entern",__FUNCTION__));
if (dhd)
dhd_suspend_resume_helper(dhd,
1);
}
调用dhd_suspend_resume_helper函数,别看函数名中有resume单词,其实early_suspend和late_resume都是通过这个函数实现功能的:
static void dhd_suspend_resume_helper(structdhd_info
*dhd, int val)
{
dhd_pub_t *dhdp = &dhd->pub;
DHD_OS_WAKE_LOCK(dhdp);
dhdp->in_suspend = val;
if
((!dhdp->suspend_disable_flag)&&
(dhd_check_ap_wfd_mode_set(dhdp) == FALSE))
dhd_set_suspend(val,
dhdp);
DHD_OS_WAKE_UNLOCK(dhdp);
}
#if
defined(CONFIG_HAS_EARLYSUSPEND) //看这里,如果系统配置了EARLYSUSPEND ,则系统会使用这部分代码,其实early_suspend是android对linux内核的电源管理的优化,所以如果你使用的是android平台,一定要配置该选项
static int dhd_set_suspend(intvalue, dhd_pub_t
*dhd)
{
......
if (dhd && dhd->up)
{
if(value
&& dhd->in_suspend)
{ //early_suspend
DHD_ERROR(("%s:
force extra Suspend setting n",__FUNCTION__));
dhd_wl_ioctl_cmd(dhd,WLC_SET_PM,
(char *)&power_mode,
sizeof(power_mode),
TRUE, 0);
dhd_set_packet_filter(1,
dhd);
bcn_li_dtim =dhd_get_dtim_skip(dhd);
bcm_mkiovar("bcn_li_dtim",(char *)&bcn_li_dtim,
4,iovbuf, sizeof(iovbuf));
dhd_wl_ioctl_cmd(dhd,WLC_SET_VAR,
iovbuf, sizeof(iovbuf), TRUE, 0);
bcm_mkiovar("roam_off",
(char *)&roamvar, 4,
iovbuf,sizeof(iovbuf));
dhd_wl_ioctl_cmd(dhd,
WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
}
else
{ //late_resume
DHD_TRACE(("%s:
Remove extra suspend setting n",__FUNCTION__));
power_mode =PM_FAST;
dhd_wl_ioctl_cmd(dhd,
WLC_SET_PM, (char *)&power_mode,
sizeof(power_mode),
TRUE, 0);
dhd_set_packet_filter(0,dhd);
bcm_mkiovar("bcn_li_dtim",
(char *)&dhd->dtim_skip,
4, iovbuf, sizeof(iovbuf));
dhd_wl_ioctl_cmd(dhd,WLC_SET_VAR,
iovbuf, sizeof(iovbuf), TRUE, 0);
roamvar =dhd_roam_disable;
bcm_mkiovar("roam_off",
(char *)&roamvar, 4, iovbuf,
sizeof(iovbuf));
dhd_wl_ioctl_cmd(dhd,WLC_SET_VAR,
iovbuf, sizeof(iovbuf), TRUE, 0);
}
}
return 0;
}
#endif
具体做什么内容,可以不用过多理会,一般只是会对该模块做些最基本的低功耗设置,其实真正的低功耗设置时在suspend中完成的。一般的模块也不需要注册early_suspend实例,但是背光灯,键盘LED和LCD屏是一定要在注册的。
Early_suspend注册成功后,会被挂接到电源管理系统中的一个链表上,当系统进入early_suspend流程时,会逐一调用该链表中的每一个实例的early_suspend回调函数,使设备进入相应的状态。在完成early_suspend流程后,系统检测wake_lock(也是被链表管理,其实不止一个),如果没有进程持有wake_lock包括main_wake_lock,系统进入suspend流程。
同样,suspend流程的实施也是需要系统支持的,需要实现电源管理的模块需要实现suspend和resume两个函数,并注册到系统中,对于WIFI设备的电源管理函数的注册在调用wifi_add_dev函数时被注册:
Path:wl/sys/wl_android.c
static struct platform_driverwifi_device = {
.probe = wifi_probe,
.remove=wifi_remove,
.suspend = wifi_suspend,
.resume = wifi_resume,
.driver = {
.name = "bcmdhd_wlan",
}
};
static struct platform_driverwifi_device_legacy = {
.probe = wifi_probe,
.remove = wifi_remove,
.suspend=wifi_suspend,
.resume = wifi_resume,
.driver = {
.name = "bcm4329_wlan",
}
};
static int wifi_add_dev(void)
{
DHD_TRACE(("## Callingplatform_driver_registern"));
platform_driver_register(&wifi_device);
platform_driver_register(&wifi_device_legacy);
return 0;
}
Wifi_suspend和wifi_resume随着wifi_device设备的注册而注册,这样当系统进入suspend流程后,就可以调用每个设备上的电源管理函数来使设备进入休眠状态了。
Wifi设备的休眠:
static int wifi_suspend(structplatform_device
*pdev, pm_message_t state)
{
DHD_TRACE(("##> %sn",__FUNCTION__));
#if (LINUX_VERSION_CODE <=KERNEL_VERSION(2, 6, 39))
&& defined(OOB_INTR_ONLY)
bcmsdh_oob_intr_set(0);
#endif
return 0;
}
static int wifi_resume(structplatform_device
*pdev)
{
DHD_TRACE(("##> %sn",__FUNCTION__));
#if (LINUX_VERSION_CODE <=KERNEL_VERSION(2, 6, 39))
&& defined(OOB_INTR_ONLY)
if (dhd_os_check_if_up(bcmsdh_get_drvdata()))
bcmsdh_oob_intr_set(1);
#endif
return 0;
}
上面的两个电源管理函数都调用bcmsdh_oob_intr_set函数,但是传递的参数不同,在wifi_suspend函数中传递0,表示禁止wifi设备对应的oob中断,而wifi_resume的作用恰恰相反。
Bcmsdh_oob_intr_set函数的定义如下:
PATH:bcmsdio/sys/bcmsdh_linux.c
#if
defined(OOB_INTR_ONLY)//该中断的使用需要配置
void bcmsdh_oob_intr_set(bool
enable)
{
static bool curstate = 1;
unsigned long flags;
spin_lock_irqsave(&sdhcinfo->irq_lock,
flags);
if
(curstate != enable) {
if
(enable)
enable_irq(sdhcinfo->oob_irq);
else
disable_irq_nosync(sdhcinfo->oob_irq);
curstate
=enable;
}
spin_unlock_irqrestore(&sdhcinfo->irq_lock,
flags);
}
此中断是在打开wifi网络设备的时候被注册的,流程如下:
static int
dhd_open(struct net_device
*net)
{
......
if (dhd->pub.busstate !=DHD_BUS_DATA) {
if
((ret = dhd_bus_start(&dhd->pub))
!=0) {
DHD_ERROR(("%s:
failed with code %dn", __FUNCTION__, ret));
ret = -1;
goto exit;
}
}
......
}
dhd_bus_start(dhd_pub_t
*dhdp)
{
......
#if defined(OOB_INTR_ONLY)
if(bcmsdh_register_oob_intr(dhdp))
{
......
}
在系统进入suspend状态后,wifi设备进入禁止中断状态,不再接收处理网络发来的数据,系统进入sleep状态,当然还有很多cpu在suspend之后进入sleep状态,但此时系统clock中断并没有被禁止,而且pmu还正常工作,以期对power键和充电器连接的检测。
最后
以上就是美满小鸭子为你收集整理的Linux wifi wpa_sup,wifi详解(四)zz的全部内容,希望文章能够帮你解决Linux wifi wpa_sup,wifi详解(四)zz所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复