之所以要分析這個(gè),是因為上層wpa_supplicant和WIFI驅動(dòng)打交道的方式,多半是通過(guò)ioctl的方式進(jìn)行的,所以看看它的調用邏輯(這里只列出其主要的調用邏輯):
上面便是用戶(hù)ioctl調用的流程圖,它最終分為兩條線(xiàn)即有兩種支持,選擇那一條或兩條都選(個(gè)人感覺(jué)最好選第2條線(xiàn),因為它最后也是會(huì )調用到相應的函數的,而且還有其它更多的命令支持),從實(shí)際的代碼來(lái)看,如果dev->netdev_ops
->ndo_do_ioctl被初始化了,那么它一定會(huì )被調用,是否被初始化,在前面選擇對net結構變量的初始化方式中有討論過(guò)。
下面來(lái)具體看看該調用流程,首先說(shuō)明下,上面的流程主要實(shí)現在kernel/net/wireless/wext_core.c文件中,這是wireless的協(xié)議層實(shí)現,恰好我們在wpa_supplicant中通常選擇的驅動(dòng)類(lèi)型也是wext,它的入口函數是wext_ioctl_dispatch:
/* entry point from dev ioctl*/
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;
}
它其實(shí)就是wireless_process_ioctl的封裝函數,除了進(jìn)行許可權限的確認,沒(méi)有做什么其它內容,這里有standard和private兩個(gè)函數指針的傳遞,其實(shí)就是兩個(gè)回調函數,在后面會(huì )用到,它是由wext_handle_ioctl函數傳遞過(guò)來(lái)的:
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); //這兩個(gè)回調函數的定義之后再討論,這里暫不理論
if (ret >= 0 &&
IW_IS_GET(cmd) &&
copy_to_user(arg, ifr, sizeof(structiwreq)))
return -EFAULT;
return ret;
}
實(shí)際上傳遞的就是ioctl_standard_call和ioctl_private_call兩個(gè)函數,在看看wireless_process_ioctl函數,這個(gè)函數很重要,下面做重點(diǎn)分析:
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;
/* Permissions are already checked indev_ioctl() before calling us.
* The copy_to/from_user() of ifr isalso dealt with in there */
/* Make sure the device exist */
if ((dev = __dev_get_by_name(net, ifr->ifr_name)) == NULL) //通過(guò)網(wǎng)絡(luò )接口名獲取net_device設備
return -ENODEV;
/* A bunch of special cases, then thegeneric case...
* Note that 'cmd' is already filteredin dev_ioctl() with
* (cmd >= SIOCIWFIRST &&cmd <= SIOCIWLAST) */
if (cmd == SIOCGIWSTATS)
returnstandard(dev, iwr, cmd, info,
&iw_handler_get_iwstats); //如果是狀態(tài)查詢(xún)命令,調用該函數(回調函數中的一個(gè))
#ifdef CONFIG_WEXT_PRIV
if (cmd == SIOCGIWPRIV && dev->wireless_handlers)
returnstandard(dev, iwr, cmd, info,
iw_handler_get_private); //如果是專(zhuān)有命令,調用回調函數,同上
#endif
/* Basic check */
if (!netif_device_present(dev))
return -ENODEV;
/* New driver API : try to find thehandler */
handler = get_handler(dev, cmd); //根據cmd參數,從dev成員中查詢(xún)相應的處理函數
if (handler) {
/* Standard and private are notthe same */
if (cmd < SIOCIWFIRSTPRIV)
return standard(dev, iwr, cmd, info, handler); //調用相應命令的處理函數
else if (private)
return private(dev, iwr, cmd, info, handler); //同上
}
/* Old driver API : call driver ioctlhandler */
if(dev->netdev_ops->ndo_do_ioctl)
return dev->netdev_ops->ndo_do_ioctl(dev,ifr, cmd); //如果被設置就調用該函數
return -EOPNOTSUPP;
}
該函數的大意是,通過(guò)網(wǎng)絡(luò )接口名稱(chēng)獲得一個(gè)網(wǎng)絡(luò )設備,然后根據命令的類(lèi)型調用相應的處理函數,特別的是當dev->netdev_ops->ndo_do_ioctl或dev->wireless_handlers被設置時(shí),則會(huì )查找執行對應的處理函數。Get_handle函數用于查詢(xún)處理函數使用:
static iw_handlerget_handler(struct net_device *dev, unsigned int cmd)
{
/* Don't "optimise" thefollowing variable, it will crash */
unsigned int index; /* *MUST* be unsigned */
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初始化時(shí)被作為擴張功能選擇性的設置,前面有提到過(guò)
#endif
if (!handlers)
return NULL;
/* Try as a standard command */
index = IW_IOCTL_IDX(cmd);
if (index <handlers->num_standard)
returnhandlers->standard[index]; //返回對應的標準函數
#ifdef CONFIG_WEXT_PRIV
/* Try as a private command */
index = cmd - SIOCIWFIRSTPRIV;
if (index <handlers->num_private)
return handlers->private[index]; //返回對應的專(zhuān)有函數
#endif
/* Not found */
return NULL;
}
那么這個(gè)dev->wireless_handlers究竟是什么,這里來(lái)揭開(kāi)它的神秘面紗,在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初始化的時(shí)候,這里把dev->wireless_handlers和dev->netdev_ops的初始化代碼再貼出來(lái):
int
dhd_net_attach(dhd_pub_t*dhdp, int ifidx)
{
……
#if (LINUX_VERSION_CODE <KERNEL_VERSION(2, 6, 31))
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流程會(huì )涉及到對它的使用
#endif /* WIRELESS_EXT > 12*/
……
}
看到這里,應該可以明白相應的命令最終會(huì )在wl_iw.c中被執行,這些處理函數也是在該文件中實(shí)現。上面已經(jīng)獲取了命令的處理函數,那么它是如何被執行的呢?這里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;
/* Get the description of the IOCTL */
if (IW_IOCTL_IDX(cmd) >=standard_ioctl_num)
return -EOPNOTSUPP;
descr =&(standard_ioctl[IW_IOCTL_IDX(cmd)]);
/* Check if we have a pointer to userspace data or not */
if (descr->header_type !=IW_HEADER_TYPE_POINT) {
/* No extra arguments. Trivialto handle */
ret = handler(dev, info, &(iwr->u),NULL);
/* Generate an event to notifylisteners of the change */
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);
}
/* Call commit handler if needed anddefined */
if (ret == -EIWCOMMIT)
ret =call_commit_handler(dev);
/* Here, we will generate theappropriate event if needed */
return ret;
}
回調函數中對傳遞過(guò)來(lái)的handler函數指針進(jìn)行呼叫,對應的處理函數就會(huì )被執行,當然用戶(hù)傳送的命令還不止這些,所以才會(huì )有net->netdev_ops的存在的必要性。下面來(lái)就來(lái)看看執行到:
return dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd); //wireless_process_ioctl的最后一句
就會(huì )調用dhd_ioctl函數,這是wlan驅動(dòng)對ioctl調用的處理函數,就是根據用戶(hù)傳遞過(guò)來(lái)的cmd,給它找一個(gè)最合適最合理的“歸宿”。
static int
dhd_ioctl_entry(structnet_device *net, struct ifreq *ifr, int cmd)
{
......#ifdefined(CONFIG_WIRELESS_EXT)
/* linux wireless extensions */
if ((cmd >= SIOCIWFIRST) &&(cmd <= SIOCIWLAST)) {
/* may recurse, do NOT lock */
ret = wl_iw_ioctl(net, ifr, cmd);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return ret;
}
#endif /*defined(CONFIG_WIRELESS_EXT) */
#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 /* LINUX_VERSION_CODE> KERNEL_VERSION(2, 4, 2) */
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);
......
}
限于篇幅,該函數處理過(guò)程不再詳述,大致的命令處理方法相似,wl_iw.c中的系列處理函數只是其中的一部分,wl_android中和dhd_linux.c也有相應的處理函數。
傳送指的是通過(guò)一個(gè)網(wǎng)絡(luò )連接發(fā)送一個(gè)報文的行為.。無(wú)論何時(shí)內核需要傳送一個(gè)數據報文, 它都必須調用驅動(dòng)的 hard_start_xmit 方法將數據放在外出隊列上。
每個(gè)內核處理的報文都包含在一個(gè) socket緩存結構( 結構 sk_buff )里, 定義見(jiàn)<linux/skbuff.h>。這個(gè)結構從 Unix 抽象中得名, 用來(lái)代表一個(gè)網(wǎng)絡(luò )連接socket.。對于接口來(lái)說(shuō), 一個(gè) socket 緩存只是一個(gè)報文。
傳給 hard_start_xmit 的socket 緩存包含物理報文, 它應當出現在媒介上, 以傳輸層的頭部結束。接口不需要修改要傳送的數據.。skb->data指向要傳送的報文,skb->len 是以字節計的長(cháng)度。傳送下來(lái)的sk_buff中的數據已經(jīng)包含硬件需要的幀頭(這是通過(guò)hard_header函數將傳遞進(jìn)入的信息,組織成設備特有的硬件頭),所以在發(fā)送方法里不需要再填充硬件幀頭,數據可以直接提 交給硬件發(fā)送。sk_buff是被鎖住的(locked),確保其他程序不會(huì )存取它。
所有的網(wǎng)絡(luò )設備驅動(dòng)程序都必須有這個(gè)發(fā)送方法。在系統調用驅動(dòng)程序的xmit時(shí),發(fā)送的數據放在一個(gè)sk_buff 結構中。一般的驅動(dòng)程序把數據傳給硬件發(fā)出去。也有一些特殊的設備比如loopback把數據組成一個(gè)接收數據再回送給系統,或者dummy設備直接丟棄 數據。如果發(fā)送成功,hard_start_xmit方法里釋放sk_buff,返回0(發(fā)送成功)。
當上層傳送過(guò)來(lái)報文,調用hard_start_xmit函數(該方法主用于初始化數據包的傳輸),該函數主要用于轉換sk_buf,將其組織成pktbuf數據格式,然后調用dhd_sendpkt函數將pktbuf通過(guò)dhd bus發(fā)送到wifi芯片,最后硬件wifi芯片將報文radio發(fā)送到網(wǎng)絡(luò )上。
int
dhd_start_xmit(struct sk_buff *skb,struct net_device *net)
{
......
/* Convert to packet */
if (!(pktbuf =PKTFRMNATIVE(dhd->pub.osh, skb))) {
DHD_ERROR(("%s:PKTFRMNATIVE failed\n",
dhd_ifname(&dhd->pub, ifidx)));
dev_kfree_skb_any(skb); //轉換成功,釋放skb,在通常處理中,會(huì )在中斷中做該操作
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); //發(fā)送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
/* non-proptxstatus way */
ret = dhd_bus_txdata(dhdp->bus, pktbuf); //在SDIO總線(xiàn)上傳輸
#else
ret = dhd_bus_txdata(dhdp->bus, pktbuf);
#endif /* PROP_TXST
......
}
傳輸結束后,會(huì )產(chǎn)生一個(gè)中斷,即傳輸結束中斷,一般的網(wǎng)絡(luò )驅動(dòng)程序都會(huì )有這個(gè)中斷的注冊,但還有一種輪詢(xún)方式,這在后面的數據的接收部分會(huì )有介紹,而sk_buf就在這個(gè)中斷處理函數中被釋放。
但是,實(shí)際情況還是比較復雜,當硬件偶爾出現問(wèn)題不能響應驅動(dòng)時(shí),就不能完成驅動(dòng)的功能。在網(wǎng)絡(luò )接口發(fā)送數據時(shí)也會(huì )發(fā)生一些不可預知的不響應動(dòng)作,比如當網(wǎng)絡(luò )介質(zhì)因阻塞造成的沖突,而使發(fā)送報文的動(dòng)作不能得到響應,但硬件通常不需要做此類(lèi)的檢測,需要驅動(dòng)用軟件的方法來(lái)實(shí)現,這就是超時(shí)傳輸機制。
與真實(shí)硬件打交道的大部分驅動(dòng)不得不預備處理硬件偶爾不能響應。接口可能忘記它們在做什么,或者系統可能丟失中斷。
許多驅動(dòng)通過(guò)設置定時(shí)器來(lái)處理這個(gè)問(wèn)題; 如果在定時(shí)器到期時(shí)操作還沒(méi)結束, 有什么不對了,網(wǎng)絡(luò )系統, 本質(zhì)上是一個(gè)復雜的由大量定時(shí)器控制的狀態(tài)機的組合體。因此, 網(wǎng)絡(luò )代碼是一個(gè)合適的位置來(lái)檢測發(fā)送超時(shí), 作為它正常操作的一部分。網(wǎng)絡(luò )驅動(dòng)不需要擔心自己去檢測這樣的問(wèn)題,相反, 它們只需要設置一個(gè)超時(shí)值, 在net_device 結構的 watchdog_timeo 成員。這個(gè)超時(shí)值, 以 jiffy 計, 應當足夠長(cháng)以容納正常的發(fā)送延遲(例如網(wǎng)絡(luò )媒介擁塞引起的沖突)。
如果當前系統時(shí)間超過(guò)設備的 trans_start 時(shí)間至少 time-out 值, 網(wǎng)絡(luò )層最終調用驅動(dòng)的 tx_timeout方法。這個(gè)方法的工作是是進(jìn)行清除問(wèn)題需要的工作并且保證任何已經(jīng)開(kāi)始的發(fā)送正確地完成。特別地, 驅動(dòng)沒(méi)有丟失追蹤任何網(wǎng)絡(luò )代碼委托給它的 socket 緩存。
當發(fā)生傳送超時(shí), 驅動(dòng)必須在接口統計量中標記這個(gè)錯誤, 并安排設備被復位到一個(gè)干凈的能發(fā)送新報文的狀態(tài),一般驅動(dòng)會(huì )調用netif_wake_queue函數重新啟動(dòng)傳輸隊列。
從網(wǎng)絡(luò )上接收報文比發(fā)送它要難一些,因為必須分配一個(gè) sk_buff 并從一個(gè)原子性上下文中遞交給上層。網(wǎng)絡(luò )驅動(dòng)可以實(shí)現 2 種報文接收的模式:中斷驅動(dòng)和查詢(xún),大部分驅動(dòng)采用中斷驅動(dòng)技術(shù)。
大部分硬件接口通過(guò)一個(gè)中斷處理來(lái)控制,硬件中斷處理器來(lái)發(fā)出 2 種可能的信號:一個(gè)新報文到了或者一個(gè)外出報文的發(fā)送完成了。網(wǎng)絡(luò )接口也能夠產(chǎn)生中斷來(lái)指示錯誤, 例如狀態(tài)改變, 等等。
通常的中斷過(guò)程能夠告知新報文到達中斷和發(fā)送完成通知的區別,通過(guò)檢查物理設備中的狀態(tài)寄存器,來(lái)判斷是那一種中斷,對于發(fā)送完成中斷更新?tīng)顟B(tài)信息,釋放skb內存。而對于接收數據中斷,從數據隊列中抽取一包數據,并把它傳遞給接收函數。
注意:這里的對設備數據的操作是在鎖得保護下完成的,做一最后還要釋放掉鎖。
那么,既然后兩種方式來(lái)處理網(wǎng)絡(luò )接口發(fā)來(lái)的數據,選擇那一種呢?一般認為中斷是比較好的一種方式,不過(guò),如果接口接收數據太頻繁,甚至一秒中接收上千包數據,那么系統的中斷次數就非常多,這回嚴重影響系統的性能。所以,在頻繁接收數據的情況下,也可以考慮使用輪詢(xún)的方式。
這樣,為了提高linux在寬帶系統上的性能,網(wǎng)絡(luò )子系統開(kāi)發(fā)者創(chuàng )建了一種基于輪詢(xún)方式的接口NAPI,它雖然在很多情況下,并不被看好,但處理高流量的高速接口時(shí),用這種NAPI輪詢(xún)技術(shù)處理到達的每一個(gè)數據包就足夠了,前提是網(wǎng)絡(luò )設備必須能支持這種模式,就是說(shuō)一個(gè)網(wǎng)絡(luò )接口必須能保存多個(gè)數據包,而且中斷能夠禁止中斷并能在傳輸和其他事件上打開(kāi)中斷。
在bcm4329芯片Wlan驅動(dòng)中,在函數dhd_attach被調用時(shí),會(huì )初始化一個(gè)內核線(xiàn)程或一個(gè)tasklet中斷的下半部。其實(shí)這兩種方式就是之前的中斷和輪詢(xún)方式的實(shí)現版,如果使用輪詢(xún),驅動(dòng)初始化一個(gè)內核線(xiàn)程dhd_dpc_thread輪詢(xún)網(wǎng)絡(luò )接口接收的數據,中斷下半部是中斷處理程序的延續,用于處理比較復雜費時(shí)的操作,這樣就能早點(diǎn)從中斷中解放出來(lái),防止拖累系統的性能。
下面來(lái)看看這兩種方式的初始化(在dhd_attach.c):
/* Set up the bottom halfhandler */
if (dhd_dpc_prio >= 0) {
/* Initialize DPC thread */
PROC_START(dhd_dpc_thread, dhd,&dhd->thr_dpc_ctl, 0);
} else {
/* use tasklet for dpc */
tasklet_init(&dhd->tasklet, dhd_dpc,(ulong)dhd);
dhd->thr_dpc_ctl.thr_pid =-1;
}
首先來(lái)看看輪詢(xún)方式的過(guò)程:
dhd_dpc_thread(void *data)
{
tsk_ctl_t *tsk = (tsk_ctl_t *)data;
dhd_info_t *dhd = (dhd_info_t*)tsk->parent;
/* This thread doesn't need anyuser-level access,
* so get rid of all our resources
*/
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");
/* DHD_OS_WAKE_LOCK is called indhd_sched_dpc[dhd_linux.c] down below */
/* signal: thread has started */
complete(&tsk->completed);
/* Run until signal received */
while (1) {
if (down_interruptible(&tsk->sema)== 0) {
SMP_RD_BARRIER_DEPENDS();
if (tsk->terminated){
break;
}
/* Call bus dpc unlessit indicated down (then clean stop) */
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);
}
這里是一個(gè)永真循環(huán),直到接收到終止信號才停止,該線(xiàn)程就是通過(guò)不斷調用dhd_bus_dpc函數調用實(shí)現輪詢(xún)的,它的調用邏輯如下所示:
上面是dhd_dpc_thread的調用邏輯,最后通過(guò)netif_rx將數據提交到上層協(xié)議,那么,還有一種中斷方式時(shí)如何實(shí)現的呢?上面只看到驅動(dòng)初始化了一個(gè)tasklet,一個(gè)中斷下半部的實(shí)例。其實(shí)在dhdsdh_probe函數中已經(jīng)注冊了這個(gè)中斷處理函數:
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) {
/* Register interrupt callback,but mask it (not operational yet). */
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 %d\n",
__FUNCTION__, ret));
goto fail;
}
DHD_INTR(("%s: registeredSDIO interrupt function ok\n", __FUNCTION__));
} else {
DHD_INFO(("%s: SDIOinterrupt function is NOT registered due to polling mode\n",
__FUNCTION__));
}
......
}
看看Dhdsdio_isr這個(gè)中斷處理函數干了什么?在函數的最后部分是:
#if defined(SDIO_ISR_THREAD)
DHD_TRACE(("Calling dhdsdio_dpc()from %s\n", __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循環(huán)調用dhdsdio_dpc,其實(shí)和下面的這個(gè)調用函數最后的作用是一樣的,就不予詳述),這個(gè)函數的代碼如下:
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 /* DHDTHREAD */
tasklet_schedule(&dhd->tasklet);
}
就是觸發(fā)一個(gè)中斷的下半部tasklet,讓cpu選擇在一個(gè)合適的時(shí)候調用dhd_dpc函數,這個(gè)函數會(huì )調用dhd_bus_dpc,然后進(jìn)入上面流程圖的調用邏輯。
詳細的數據處理過(guò)程不詳細敘述,可以參考源碼來(lái)具體分析。
電源管理始終是手機等移動(dòng)設備最重要的一個(gè)功能,尤其對于A(yíng)ndroid這種智能手機或者說(shuō)手機電腦化的設備,電源管理更顯得十分重要。
Linux一直在傳統的PC和服務(wù)器市場(chǎng)上有很好的應用,也有了比較好的電源管理框架,但是對于智能手機等嵌入式設備來(lái)說(shuō),Linux標準的電源管理就顯得不是很適用了,有許多需要改進(jìn)的地方。Android在這方面做了一些比較好的嘗試,添加了一些新的特性,包括wake_lock,early_supend等。這里對wake_lock不做介紹,只介紹WIFI模塊在系統將要或正在進(jìn)入休眠的一些動(dòng)作,感興趣的話(huà)可以自己查閱android的電源管理相關(guān)文章。
在介紹實(shí)質(zhì)內容之前,先來(lái)看看android的電源管理的實(shí)現基礎:Linux系統的電源管理Suspend框架跟Linux系統的驅動(dòng)模型(Linux DriverModel)是相關(guān)的,也是基于Linux的驅動(dòng)模型來(lái)實(shí)現的,下面的圖描述了Linux系統電源管理的Suspend系統框架,Linux的Suspend系統分為兩部分,一部分是平臺無(wú)關(guān)的核心層,另一個(gè)是平臺相關(guān)的平臺層。操作接口都在平臺無(wú)關(guān)的核心層里了,平臺相關(guān)部分會(huì )使用Suspend API將自己的操作函數注冊進(jìn)Suspend核心層里。
根據Linux系統驅動(dòng)模型,Device結構描述了一個(gè)設備,device_driver是設備的驅動(dòng),而class、type和bus分別描述了設備所屬的類(lèi)別、類(lèi)型和總線(xiàn)。而設備的電源管理也根據此模型分為class級的、type級的、bus級的和驅動(dòng)級的。如果一個(gè)設備的class或者bus確切的知道如何管理一個(gè)設備的電源的時(shí)候,驅動(dòng)級別的suspend/resume就可以為空了。這極大的提高了電源管理的高效性和靈活性。
對于android平臺上整個(gè)系統是如何一步一步進(jìn)入休眠的,我這里不做詳細介紹,只作出它的大致流程圖:
此流程圖顯示了系統的休眠的全過(guò)程,對WIFI模塊來(lái)說(shuō),我們主要關(guān)注early_suspend和suspend以及相應的喚醒過(guò)程。當系統屏幕超時(shí)或用戶(hù)(亮屏時(shí))按power鍵,系統進(jìn)入休眠流程(這里不討論可能的中途退出休眠的其它因素),即在沒(méi)有進(jìn)程持有wakelock情況下,首先進(jìn)入early_suspend流程。
Early_suspend流程的實(shí)現基礎是:android電源管理系統中定義了一個(gè)early_suspend結構鏈表,里面存放了所有系統中注冊了的early_suspend實(shí)例,即如果一個(gè)模塊要在系統進(jìn)入early_suspend狀態(tài)有所動(dòng)作,就必須注冊一個(gè)early_suspend實(shí)例。在WIFI驅動(dòng)模塊中,當驅動(dòng)流程走到dhd_attach函數時(shí),有相應的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結構的兩個(gè)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: enter\n",__FUNCTION__));
if (dhd)
dhd_suspend_resume_helper(dhd, 1);
}
調用dhd_suspend_resume_helper函數,別看函數名中有resume單詞,其實(shí)early_suspend和late_resume都是通過(guò)這個(gè)函數實(shí)現功能的:
static void dhd_suspend_resume_helper(structdhd_info *dhd, int val)
{
dhd_pub_t *dhdp = &dhd->pub;
DHD_OS_WAKE_LOCK(dhdp);
/* Set flag when early suspend wascalled */
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 ,則系統會(huì )使用這部分代碼,其實(shí)early_suspend是android對linux內核的電源管理的優(yōu)化,所以如果你使用的是android平臺,一定要配置該選項
static int dhd_set_suspend(intvalue, dhd_pub_t *dhd)
{
......
if (dhd && dhd->up) {
if(value && dhd->in_suspend) { //early_suspend
/* Kernelsuspended */
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);
/* Enablepacket filter, only allow unicast packet to send up */
dhd_set_packet_filter(1, dhd);
/* If DTIM skipis set up as default, force it to wake
* each thirdDTIM for better power savings. Note that
* one sideeffect is a chance to miss BC/MC packet.
*/
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);
/* Disable firmwareroaming during suspend */
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
/* Kernelresumed */
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);
/* disable pktfilter */
dhd_set_packet_filter(0,dhd);
/* restorepre-suspend setting for dtim_skip */
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
具體做什么內容,可以不用過(guò)多理會(huì ),一般只是會(huì )對該模塊做些最基本的低功耗設置,其實(shí)真正的低功耗設置時(shí)在suspend中完成的。一般的模塊也不需要注冊early_suspend實(shí)例,但是背光燈,鍵盤(pán)LED和LCD屏是一定要在注冊的。
Early_suspend注冊成功后,會(huì )被掛接到電源管理系統中的一個(gè)鏈表上,當系統進(jìn)入early_suspend流程時(shí),會(huì )逐一調用該鏈表中的每一個(gè)實(shí)例的early_suspend回調函數,使設備進(jìn)入相應的狀態(tài)。在完成early_suspend流程后,系統檢測wake_lock(也是被鏈表管理,其實(shí)不止一個(gè)),如果沒(méi)有進(jìn)程持有wake_lock包括main_wake_lock,系統進(jìn)入suspend流程。
同樣,suspend流程的實(shí)施也是需要系統支持的,需要實(shí)現電源管理的模塊需要實(shí)現suspend和resume兩個(gè)函數,并注冊到系統中,對于WIFI設備的電源管理函數的注冊在調用wifi_add_dev函數時(shí)被注冊:
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_register\n"));
platform_driver_register(&wifi_device);
platform_driver_register(&wifi_device_legacy);
return 0;
}
Wifi_suspend和wifi_resume隨著(zhù)wifi_device設備的注冊而注冊,這樣當系統進(jìn)入suspend流程后,就可以調用每個(gè)設備上的電源管理函數來(lái)使設備進(jìn)入休眠狀態(tài)了。
Wifi設備的休眠:
static int wifi_suspend(structplatform_device *pdev, pm_message_t state)
{
DHD_TRACE(("##> %s\n",__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(("##> %s\n",__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;
}
上面的兩個(gè)電源管理函數都調用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);
}
此中斷是在打開(kāi)wifi網(wǎng)絡(luò )設備的時(shí)候被注冊的,流程如下:
static int
dhd_open(struct net_device *net)
{
......
if (dhd->pub.busstate !=DHD_BUS_DATA) {
/* try to bring up bus*/
if ((ret = dhd_bus_start(&dhd->pub)) !=0) {
DHD_ERROR(("%s: failed with code %d\n", __FUNCTION__, ret));
ret = -1;
goto exit;
}
}
......
}
dhd_bus_start(dhd_pub_t *dhdp)
{
......
#if defined(OOB_INTR_ONLY)
/* Host registration for OOB interrupt*/
if(bcmsdh_register_oob_intr(dhdp)) {
......
}
在系統進(jìn)入suspend狀態(tài)后,wifi設備進(jìn)入禁止中斷狀態(tài),不再接收處理網(wǎng)絡(luò )發(fā)來(lái)的數據,系統進(jìn)入sleep狀態(tài),當然還有很多cpu在suspend之后進(jìn)入sleep狀態(tài),但此時(shí)系統clock中斷并沒(méi)有被禁止,而且pmu還正常工作,以期對power鍵和充電器連接的檢測。
聯(lián)系客服