1 IOCTL的調(diào)用邏輯之所以要分析這個(gè),是因?yàn)樯蠈觲pa_supplicant和WIFI驅(qū)動(dòng)打交道的方式,多半是通過ioctl的方式進(jìn)行的,所以看看它的調(diào)用邏輯(這里只列出其主要的調(diào)用邏輯): 上面便是用戶ioctl調(diào)用的流程圖,它最終分為兩條線即有兩種支持,選擇那一條或兩條都選(個(gè)人感覺最好選第2條線,因?yàn)樗詈笠彩菚?huì)調(diào)用到相應(yīng)的函數(shù)的,而且還有其它更多的命令支持),從實(shí)際的代碼來看,如果dev->netdev_ops ->ndo_do_ioctl被初始化了,那么它一定會(huì)被調(diào)用,是否被初始化,在前面選擇對(duì)net結(jié)構(gòu)變量的初始化方式中有討論過。 下面來具體看看該調(diào)用流程,首先說明下,上面的流程主要實(shí)現(xiàn)在kernel/net/wireless/wext_core.c文件中,這是wireless的協(xié)議層實(shí)現(xiàn),恰好我們在wpa_supplicant中通常選擇的驅(qū)動(dòng)類型也是wext,它的入口函數(shù)是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的封裝函數(shù),除了進(jìn)行許可權(quán)限的確認(rèn),沒有做什么其它內(nèi)容,這里有standard和private兩個(gè)函數(shù)指針的傳遞,其實(shí)就是兩個(gè)回調(diào)函數(shù),在后面會(huì)用到,它是由wext_handle_ioctl函數(shù)傳遞過來的: 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è)回調(diào)函數(shù)的定義之后再討論,這里暫不理論 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è)函數(shù),在看看wireless_process_ioctl函數(shù),這個(gè)函數(shù)很重要,下面做重點(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) //通過網(wǎng)絡(luò)接口名獲取net_device設(shè)備 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)查詢命令,調(diào)用該函數(shù)(回調(diào)函數(shù)中的一個(gè))
#ifdef CONFIG_WEXT_PRIV if (cmd == SIOCGIWPRIV && dev->wireless_handlers) returnstandard(dev, iwr, cmd, info, iw_handler_get_private); //如果是專有命令,調(diào)用回調(diào)函數(shù),同上 #endif
/* Basic check */ if (!netif_device_present(dev)) return -ENODEV;
/* New driver API : try to find thehandler */ handler = get_handler(dev, cmd); //根據(jù)cmd參數(shù),從dev成員中查詢相應(yīng)的處理函數(shù) if (handler) { /* Standard and private are notthe same */ if (cmd < SIOCIWFIRSTPRIV) return standard(dev, iwr, cmd, info, handler); //調(diào)用相應(yīng)命令的處理函數(shù) 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); //如果被設(shè)置就調(diào)用該函數(shù) return -EOPNOTSUPP; } 該函數(shù)的大意是,通過網(wǎng)絡(luò)接口名稱獲得一個(gè)網(wǎng)絡(luò)設(shè)備,然后根據(jù)命令的類型調(diào)用相應(yīng)的處理函數(shù),特別的是當(dāng)dev->netdev_ops->ndo_do_ioctl或dev->wireless_handlers被設(shè)置時(shí),則會(huì)查找執(zhí)行對(duì)應(yīng)的處理函數(shù)。Get_handle函數(shù)用于查詢處理函數(shù)使用: 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; //初始化默認(rèn)的處理函數(shù) #endif #ifdef CONFIG_WIRELESS_EXT if (dev->wireless_handlers) handlers= dev->wireless_handlers; //這里的dev->wireless_handlers在net初始化時(shí)被作為擴(kuò)張功能選擇性的設(shè)置,前面有提到過 #endif
if (!handlers) return NULL;
/* Try as a standard command */ index = IW_IOCTL_IDX(cmd); if (index <handlers->num_standard) returnhandlers->standard[index]; //返回對(duì)應(yīng)的標(biāo)準(zhǔn)函數(shù)
#ifdef CONFIG_WEXT_PRIV /* Try as a private command */ index = cmd - SIOCIWFIRSTPRIV; if (index <handlers->num_private) return handlers->private[index]; //返回對(duì)應(yīng)的專有函數(shù) #endif
/* Not found */ return NULL; } 那么這個(gè)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初始化的時(shí)候,這里把dev->wireless_handlers和dev->netdev_ops的初始化代碼再貼出來: 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ì)涉及到對(duì)它的使用 #endif /* WIRELESS_EXT > 12*/
…… } 看到這里,應(yīng)該可以明白相應(yīng)的命令最終會(huì)在wl_iw.c中被執(zhí)行,這些處理函數(shù)也是在該文件中實(shí)現(xiàn)。上面已經(jīng)獲取了命令的處理函數(shù),那么它是如何被執(zhí)行的呢?這里wireless_process_ioctl里有standard和private的回調(diào)函數(shù)的調(diào)用: 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; } 回調(diào)函數(shù)中對(duì)傳遞過來的handler函數(shù)指針進(jìn)行呼叫,對(duì)應(yīng)的處理函數(shù)就會(huì)被執(zhí)行,當(dāng)然用戶傳送的命令還不止這些,所以才會(huì)有net->netdev_ops的存在的必要性。下面來就來看看執(zhí)行到: return dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd); //wireless_process_ioctl的最后一句 就會(huì)調(diào)用dhd_ioctl函數(shù),這是wlan驅(qū)動(dòng)對(duì)ioctl調(diào)用的處理函數(shù),就是根據(jù)用戶傳遞過來的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); ...... } 限于篇幅,該函數(shù)處理過程不再詳述,大致的命令處理方法相似,wl_iw.c中的系列處理函數(shù)只是其中的一部分,wl_android中和dhd_linux.c也有相應(yīng)的處理函數(shù)。 2 數(shù)據(jù)的傳送2.1 數(shù)據(jù)傳送過程簡述傳送指的是通過一個(gè)網(wǎng)絡(luò)連接發(fā)送一個(gè)報(bào)文的行為.。無論何時(shí)內(nèi)核需要傳送一個(gè)數(shù)據(jù)報(bào)文, 它都必須調(diào)用驅(qū)動(dòng)的 hard_start_xmit 方法將數(shù)據(jù)放在外出隊(duì)列上。 每個(gè)內(nèi)核處理的報(bào)文都包含在一個(gè) socket緩存結(jié)構(gòu)( 結(jié)構(gòu) sk_buff )里, 定義見<linux/skbuff.h>。這個(gè)結(jié)構(gòu)從 Unix 抽象中得名, 用來代表一個(gè)網(wǎng)絡(luò)連接socket.。對(duì)于接口來說, 一個(gè) socket 緩存只是一個(gè)報(bào)文。 傳給 hard_start_xmit 的socket 緩存包含物理報(bào)文, 它應(yīng)當(dāng)出現(xiàn)在媒介上, 以傳輸層的頭部結(jié)束。接口不需要修改要傳送的數(shù)據(jù).。skb->data指向要傳送的報(bào)文,skb->len 是以字節(jié)計(jì)的長度。傳送下來的sk_buff中的數(shù)據(jù)已經(jīng)包含硬件需要的幀頭(這是通過hard_header函數(shù)將傳遞進(jìn)入的信息,組織成設(shè)備特有的硬件頭),所以在發(fā)送方法里不需要再填充硬件幀頭,數(shù)據(jù)可以直接提 交給硬件發(fā)送。sk_buff是被鎖住的(locked),確保其他程序不會(huì)存取它。 所有的網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序都必須有這個(gè)發(fā)送方法。在系統(tǒng)調(diào)用驅(qū)動(dòng)程序的xmit時(shí),發(fā)送的數(shù)據(jù)放在一個(gè)sk_buff 結(jié)構(gòu)中。一般的驅(qū)動(dòng)程序把數(shù)據(jù)傳給硬件發(fā)出去。也有一些特殊的設(shè)備比如loopback把數(shù)據(jù)組成一個(gè)接收數(shù)據(jù)再回送給系統(tǒng),或者dummy設(shè)備直接丟棄 數(shù)據(jù)。如果發(fā)送成功,hard_start_xmit方法里釋放sk_buff,返回0(發(fā)送成功)。 2.2 Bcm4329芯片wlan驅(qū)動(dòng)數(shù)據(jù)傳送當(dāng)上層傳送過來報(bào)文,調(diào)用hard_start_xmit函數(shù)(該方法主用于初始化數(shù)據(jù)包的傳輸),該函數(shù)主要用于轉(zhuǎn)換sk_buf,將其組織成pktbuf數(shù)據(jù)格式,然后調(diào)用dhd_sendpkt函數(shù)將pktbuf通過dhd bus發(fā)送到wifi芯片,最后硬件wifi芯片將報(bào)文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); //轉(zhuǎn)換成功,釋放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總線上傳輸 #else ret = dhd_bus_txdata(dhdp->bus, pktbuf); #endif /* PROP_TXST ...... } 傳輸結(jié)束后,會(huì)產(chǎn)生一個(gè)中斷,即傳輸結(jié)束中斷,一般的網(wǎng)絡(luò)驅(qū)動(dòng)程序都會(huì)有這個(gè)中斷的注冊,但還有一種輪詢方式,這在后面的數(shù)據(jù)的接收部分會(huì)有介紹,而sk_buf就在這個(gè)中斷處理函數(shù)中被釋放。 但是,實(shí)際情況還是比較復(fù)雜,當(dāng)硬件偶爾出現(xiàn)問題不能響應(yīng)驅(qū)動(dòng)時(shí),就不能完成驅(qū)動(dòng)的功能。在網(wǎng)絡(luò)接口發(fā)送數(shù)據(jù)時(shí)也會(huì)發(fā)生一些不可預(yù)知的不響應(yīng)動(dòng)作,比如當(dāng)網(wǎng)絡(luò)介質(zhì)因阻塞造成的沖突,而使發(fā)送報(bào)文的動(dòng)作不能得到響應(yīng),但硬件通常不需要做此類的檢測,需要驅(qū)動(dòng)用軟件的方法來實(shí)現(xiàn),這就是超時(shí)傳輸機(jī)制。 2.3 傳輸超時(shí)與真實(shí)硬件打交道的大部分驅(qū)動(dòng)不得不預(yù)備處理硬件偶爾不能響應(yīng)。接口可能忘記它們在做什么,或者系統(tǒng)可能丟失中斷。 許多驅(qū)動(dòng)通過設(shè)置定時(shí)器來處理這個(gè)問題; 如果在定時(shí)器到期時(shí)操作還沒結(jié)束, 有什么不對(duì)了,網(wǎng)絡(luò)系統(tǒng), 本質(zhì)上是一個(gè)復(fù)雜的由大量定時(shí)器控制的狀態(tài)機(jī)的組合體。因此, 網(wǎng)絡(luò)代碼是一個(gè)合適的位置來檢測發(fā)送超時(shí), 作為它正常操作的一部分。網(wǎng)絡(luò)驅(qū)動(dòng)不需要擔(dān)心自己去檢測這樣的問題,相反, 它們只需要設(shè)置一個(gè)超時(shí)值, 在net_device 結(jié)構(gòu)的 watchdog_timeo 成員。這個(gè)超時(shí)值, 以 jiffy 計(jì), 應(yīng)當(dāng)足夠長以容納正常的發(fā)送延遲(例如網(wǎng)絡(luò)媒介擁塞引起的沖突)。 如果當(dāng)前系統(tǒng)時(shí)間超過設(shè)備的 trans_start 時(shí)間至少 time-out 值, 網(wǎng)絡(luò)層最終調(diào)用驅(qū)動(dòng)的 tx_timeout方法。這個(gè)方法的工作是是進(jìn)行清除問題需要的工作并且保證任何已經(jīng)開始的發(fā)送正確地完成。特別地, 驅(qū)動(dòng)沒有丟失追蹤任何網(wǎng)絡(luò)代碼委托給它的 socket 緩存。 當(dāng)發(fā)生傳送超時(shí), 驅(qū)動(dòng)必須在接口統(tǒng)計(jì)量中標(biāo)記這個(gè)錯(cuò)誤, 并安排設(shè)備被復(fù)位到一個(gè)干凈的能發(fā)送新報(bào)文的狀態(tài),一般驅(qū)動(dòng)會(huì)調(diào)用netif_wake_queue函數(shù)重新啟動(dòng)傳輸隊(duì)列。 3 數(shù)據(jù)的接收3.1 數(shù)據(jù)接收的方式和過程從網(wǎng)絡(luò)上接收?qǐng)?bào)文比發(fā)送它要難一些,因?yàn)楸仨毞峙湟粋€(gè) sk_buff 并從一個(gè)原子性上下文中遞交給上層。網(wǎng)絡(luò)驅(qū)動(dòng)可以實(shí)現(xiàn) 2 種報(bào)文接收的模式:中斷驅(qū)動(dòng)和查詢,大部分驅(qū)動(dòng)采用中斷驅(qū)動(dòng)技術(shù)。 大部分硬件接口通過一個(gè)中斷處理來控制,硬件中斷處理器來發(fā)出 2 種可能的信號(hào):一個(gè)新報(bào)文到了或者一個(gè)外出報(bào)文的發(fā)送完成了。網(wǎng)絡(luò)接口也能夠產(chǎn)生中斷來指示錯(cuò)誤, 例如狀態(tài)改變, 等等。 通常的中斷過程能夠告知新報(bào)文到達(dá)中斷和發(fā)送完成通知的區(qū)別,通過檢查物理設(shè)備中的狀態(tài)寄存器,來判斷是那一種中斷,對(duì)于發(fā)送完成中斷更新狀態(tài)信息,釋放skb內(nèi)存。而對(duì)于接收數(shù)據(jù)中斷,從數(shù)據(jù)隊(duì)列中抽取一包數(shù)據(jù),并把它傳遞給接收函數(shù)。 注意:這里的對(duì)設(shè)備數(shù)據(jù)的操作是在鎖得保護(hù)下完成的,做一最后還要釋放掉鎖。 3.2 選擇哪種接收模式那么,既然后兩種方式來處理網(wǎng)絡(luò)接口發(fā)來的數(shù)據(jù),選擇那一種呢?一般認(rèn)為中斷是比較好的一種方式,不過,如果接口接收數(shù)據(jù)太頻繁,甚至一秒中接收上千包數(shù)據(jù),那么系統(tǒng)的中斷次數(shù)就非常多,這回嚴(yán)重影響系統(tǒng)的性能。所以,在頻繁接收數(shù)據(jù)的情況下,也可以考慮使用輪詢的方式。 這樣,為了提高linux在寬帶系統(tǒng)上的性能,網(wǎng)絡(luò)子系統(tǒng)開發(fā)者創(chuàng)建了一種基于輪詢方式的接口NAPI,它雖然在很多情況下,并不被看好,但處理高流量的高速接口時(shí),用這種NAPI輪詢技術(shù)處理到達(dá)的每一個(gè)數(shù)據(jù)包就足夠了,前提是網(wǎng)絡(luò)設(shè)備必須能支持這種模式,就是說一個(gè)網(wǎng)絡(luò)接口必須能保存多個(gè)數(shù)據(jù)包,而且中斷能夠禁止中斷并能在傳輸和其他事件上打開中斷。 3.3 Bcm4329芯片wlan驅(qū)動(dòng)數(shù)據(jù)傳送在bcm4329芯片Wlan驅(qū)動(dòng)中,在函數(shù)dhd_attach被調(diào)用時(shí),會(huì)初始化一個(gè)內(nèi)核線程或一個(gè)tasklet中斷的下半部。其實(shí)這兩種方式就是之前的中斷和輪詢方式的實(shí)現(xiàn)版,如果使用輪詢,驅(qū)動(dòng)初始化一個(gè)內(nèi)核線程dhd_dpc_thread輪詢網(wǎng)絡(luò)接口接收的數(shù)據(jù),中斷下半部是中斷處理程序的延續(xù),用于處理比較復(fù)雜費(fèi)時(shí)的操作,這樣就能早點(diǎn)從中斷中解放出來,防止拖累系統(tǒng)的性能。 下面來看看這兩種方式的初始化(在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; } 首先來看看輪詢方式的過程: 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),直到接收到終止信號(hào)才停止,該線程就是通過不斷調(diào)用dhd_bus_dpc函數(shù)調(diào)用實(shí)現(xiàn)輪詢的,它的調(diào)用邏輯如下所示: 上面是dhd_dpc_thread的調(diào)用邏輯,最后通過netif_rx將數(shù)據(jù)提交到上層協(xié)議,那么,還有一種中斷方式時(shí)如何實(shí)現(xiàn)的呢?上面只看到驅(qū)動(dòng)初始化了一個(gè)tasklet,一個(gè)中斷下半部的實(shí)例。其實(shí)在dhdsdh_probe函數(shù)中已經(jīng)注冊了這個(gè)中斷處理函數(shù): 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è)中斷處理函數(shù)干了什么?在函數(shù)的最后部分是: #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函數(shù)在最后被調(diào)用(上面的while循環(huán)調(diào)用dhdsdio_dpc,其實(shí)和下面的這個(gè)調(diào)用函數(shù)最后的作用是一樣的,就不予詳述),這個(gè)函數(shù)的代碼如下: 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í)候調(diào)用dhd_dpc函數(shù),這個(gè)函數(shù)會(huì)調(diào)用dhd_bus_dpc,然后進(jìn)入上面流程圖的調(diào)用邏輯。 詳細(xì)的數(shù)據(jù)處理過程不詳細(xì)敘述,可以參考源碼來具體分析。
4 電源管理相關(guān)的調(diào)用邏輯電源管理始終是手機(jī)等移動(dòng)設(shè)備最重要的一個(gè)功能,尤其對(duì)于Android這種智能手機(jī)或者說手機(jī)電腦化的設(shè)備,電源管理更顯得十分重要。 Linux一直在傳統(tǒng)的PC和服務(wù)器市場上有很好的應(yīng)用,也有了比較好的電源管理框架,但是對(duì)于智能手機(jī)等嵌入式設(shè)備來說,Linux標(biāo)準(zhǔn)的電源管理就顯得不是很適用了,有許多需要改進(jìn)的地方。Android在這方面做了一些比較好的嘗試,添加了一些新的特性,包括wake_lock,early_supend等。這里對(duì)wake_lock不做介紹,只介紹WIFI模塊在系統(tǒng)將要或正在進(jìn)入休眠的一些動(dòng)作,感興趣的話可以自己查閱android的電源管理相關(guān)文章。 在介紹實(shí)質(zhì)內(nèi)容之前,先來看看android的電源管理的實(shí)現(xiàn)基礎(chǔ):Linux系統(tǒng)的電源管理Suspend框架跟Linux系統(tǒng)的驅(qū)動(dòng)模型(Linux DriverModel)是相關(guān)的,也是基于Linux的驅(qū)動(dòng)模型來實(shí)現(xiàn)的,下面的圖描述了Linux系統(tǒng)電源管理的Suspend系統(tǒng)框架,Linux的Suspend系統(tǒng)分為兩部分,一部分是平臺(tái)無關(guān)的核心層,另一個(gè)是平臺(tái)相關(guān)的平臺(tái)層。操作接口都在平臺(tái)無關(guān)的核心層里了,平臺(tái)相關(guān)部分會(huì)使用Suspend API將自己的操作函數(shù)注冊進(jìn)Suspend核心層里。 根據(jù)Linux系統(tǒng)驅(qū)動(dòng)模型,Device結(jié)構(gòu)描述了一個(gè)設(shè)備,device_driver是設(shè)備的驅(qū)動(dòng),而class、type和bus分別描述了設(shè)備所屬的類別、類型和總線。而設(shè)備的電源管理也根據(jù)此模型分為class級(jí)的、type級(jí)的、bus級(jí)的和驅(qū)動(dòng)級(jí)的。如果一個(gè)設(shè)備的class或者bus確切的知道如何管理一個(gè)設(shè)備的電源的時(shí)候,驅(qū)動(dòng)級(jí)別的suspend/resume就可以為空了。這極大的提高了電源管理的高效性和靈活性。 對(duì)于android平臺(tái)上整個(gè)系統(tǒng)是如何一步一步進(jìn)入休眠的,我這里不做詳細(xì)介紹,只作出它的大致流程圖: 此流程圖顯示了系統(tǒng)的休眠的全過程,對(duì)WIFI模塊來說,我們主要關(guān)注early_suspend和suspend以及相應(yīng)的喚醒過程。當(dāng)系統(tǒng)屏幕超時(shí)或用戶(亮屏?xí)r)按power鍵,系統(tǒng)進(jìn)入休眠流程(這里不討論可能的中途退出休眠的其它因素),即在沒有進(jìn)程持有wakelock情況下,首先進(jìn)入early_suspend流程。 Early_suspend流程的實(shí)現(xiàn)基礎(chǔ)是:android電源管理系統(tǒng)中定義了一個(gè)early_suspend結(jié)構(gòu)鏈表,里面存放了所有系統(tǒng)中注冊了的early_suspend實(shí)例,即如果一個(gè)模塊要在系統(tǒng)進(jìn)入early_suspend狀態(tài)有所動(dòng)作,就必須注冊一個(gè)early_suspend實(shí)例。在WIFI驅(qū)動(dòng)模塊中,當(dāng)驅(qū)動(dòng)流程走到dhd_attach函數(shù)時(shí),有相應(yīng)的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 ...... } 紅色區(qū)域初始化了dhd結(jié)構(gòu)的兩個(gè)early_suspend函數(shù),并將其注冊到電源管理系統(tǒng)。early_suspend的休眠函數(shù)的代碼如下: 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); } 調(diào)用dhd_suspend_resume_helper函數(shù),別看函數(shù)名中有resume單詞,其實(shí)early_suspend和late_resume都是通過這個(gè)函數(shù)實(shí)現(xiàn)功能的: 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) //看這里,如果系統(tǒng)配置了EARLYSUSPEND ,則系統(tǒng)會(huì)使用這部分代碼,其實(shí)early_suspend是android對(duì)linux內(nèi)核的電源管理的優(yōu)化,所以如果你使用的是android平臺(tái),一定要配置該選項(xiàng) 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 具體做什么內(nèi)容,可以不用過多理會(huì),一般只是會(huì)對(duì)該模塊做些最基本的低功耗設(shè)置,其實(shí)真正的低功耗設(shè)置時(shí)在suspend中完成的。一般的模塊也不需要注冊early_suspend實(shí)例,但是背光燈,鍵盤LED和LCD屏是一定要在注冊的。 Early_suspend注冊成功后,會(huì)被掛接到電源管理系統(tǒng)中的一個(gè)鏈表上,當(dāng)系統(tǒng)進(jìn)入early_suspend流程時(shí),會(huì)逐一調(diào)用該鏈表中的每一個(gè)實(shí)例的early_suspend回調(diào)函數(shù),使設(shè)備進(jìn)入相應(yīng)的狀態(tài)。在完成early_suspend流程后,系統(tǒng)檢測wake_lock(也是被鏈表管理,其實(shí)不止一個(gè)),如果沒有進(jìn)程持有wake_lock包括main_wake_lock,系統(tǒng)進(jìn)入suspend流程。 同樣,suspend流程的實(shí)施也是需要系統(tǒng)支持的,需要實(shí)現(xiàn)電源管理的模塊需要實(shí)現(xiàn)suspend和resume兩個(gè)函數(shù),并注冊到系統(tǒng)中,對(duì)于WIFI設(shè)備的電源管理函數(shù)的注冊在調(diào)用wifi_add_dev函數(shù)時(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隨著wifi_device設(shè)備的注冊而注冊,這樣當(dāng)系統(tǒng)進(jìn)入suspend流程后,就可以調(diào)用每個(gè)設(shè)備上的電源管理函數(shù)來使設(shè)備進(jìn)入休眠狀態(tài)了。 Wifi設(shè)備的休眠: 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è)電源管理函數(shù)都調(diào)用bcmsdh_oob_intr_set函數(shù),但是傳遞的參數(shù)不同,在wifi_suspend函數(shù)中傳遞0,表示禁止wifi設(shè)備對(duì)應(yīng)的oob中斷,而wifi_resume的作用恰恰相反。 Bcmsdh_oob_intr_set函數(shù)的定義如下: 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網(wǎng)絡(luò)設(shè)備的時(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)) { ...... } 在系統(tǒng)進(jìn)入suspend狀態(tài)后,wifi設(shè)備進(jìn)入禁止中斷狀態(tài),不再接收處理網(wǎng)絡(luò)發(fā)來的數(shù)據(jù),系統(tǒng)進(jìn)入sleep狀態(tài),當(dāng)然還有很多cpu在suspend之后進(jìn)入sleep狀態(tài),但此時(shí)系統(tǒng)clock中斷并沒有被禁止,而且pmu還正常工作,以期對(duì)power鍵和充電器連接的檢測。 |
|