<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    wifi詳解(四)

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    1        IOCTL的調用邏輯

    之所以要分析這個,是因為上層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:

    /* 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;

    }

          

    它其實就是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;

     

            /* 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)             //通過網絡接口名獲取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);   //如果是狀態查詢命令,調用該函數(回調函數中的一個)

     

    #ifdef CONFIG_WEXT_PRIV

            if (cmd == SIOCGIWPRIV && dev->wireless_handlers)

                    returnstandard(dev, iwr, cmd, info,

                                   iw_handler_get_private);      //如果是專有命令,調用回調函數,同上

    #endif

     

            /* Basic check */

            if (!netif_device_present(dev))

                    return -ENODEV;

     

            /* New driver API : try to find thehandler */

            handler = get_handler(dev, cmd);              //根據cmd參數,從dev成員中查詢相應的處理函數

            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;

    }

    該函數的大意是,通過網絡接口名稱獲得一個網絡設備,然后根據命令的類型調用相應的處理函數,特別的是當dev->netdev_ops->ndo_do_ioctl或dev->wireless_handlers被設置時,則會查找執行對應的處理函數。Get_handle函數用于查詢處理函數使用:

    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初始化時被作為擴張功能選擇性的設置,前面有提到過

    #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];        //返回對應的專有函數

    #endif

     

            /* Not found */

            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 <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流程會涉及到對它的使用

    #endif /* WIRELESS_EXT > 12*/

     

     

    ……

    }

    看到這里,應該可以明白相應的命令最終會在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;

     

            /* 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;

    }

    回調函數中對傳遞過來的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)

            /* 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);

    ......

    }

    限于篇幅,該函數處理過程不再詳述,大致的命令處理方法相似,wl_iw.c中的系列處理函數只是其中的一部分,wl_android中和dhd_linux.c也有相應的處理函數。

    2        數據的傳送

    2.1       數據傳送過程簡述

    傳送指的是通過一個網絡連接發送一個報文的行為.。無論何時內核需要傳送一個數據報文, 它都必須調用驅動的 hard_start_xmit 方法將數據放在外出隊列上。

    每個內核處理的報文都包含在一個 socket緩存結構( 結構 sk_buff )里, 定義見<linux/skbuff.h>。這個結構從 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)

    {

    ......

            /* 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,在通常處理中,會在中斷中做該操作

                    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

                    /* non-proptxstatus way */

            ret = dhd_bus_txdata(dhdp->bus, pktbuf);         //在SDIO總線上傳輸

    #else

            ret = dhd_bus_txdata(dhdp->bus, pktbuf);

    #endif /* PROP_TXST

    ......

    }

    傳輸結束后,會產生一個中斷,即傳輸結束中斷,一般的網絡驅動程序都會有這個中斷的注冊,但還有一種輪詢方式,這在后面的數據的接收部分會有介紹,而sk_buf就在這個中斷處理函數中被釋放。

    但是,實際情況還是比較復雜,當硬件偶爾出現問題不能響應驅動時,就不能完成驅動的功能。在網絡接口發送數據時也會發生一些不可預知的不響應動作,比如當網絡介質因阻塞造成的沖突,而使發送報文的動作不能得到響應,但硬件通常不需要做此類的檢測,需要驅動用軟件的方法來實現,這就是超時傳輸機制。

    2.3       傳輸超時

    與真實硬件打交道的大部分驅動不得不預備處理硬件偶爾不能響應。接口可能忘記它們在做什么,或者系統可能丟失中斷。

    許多驅動通過設置定時器來處理這個問題; 如果在定時器到期時操作還沒結束, 有什么不對了,網絡系統, 本質上是一個復雜的由大量定時器控制的狀態機的組合體。因此, 網絡代碼是一個合適的位置來檢測發送超時, 作為它正常操作的一部分。網絡驅動不需要擔心自己去檢測這樣的問題,相反, 它們只需要設置一個超時值, 在net_device 結構的 watchdog_timeo 成員。這個超時值, 以 jiffy 計, 應當足夠長以容納正常的發送延遲(例如網絡媒介擁塞引起的沖突)。

    如果當前系統時間超過設備的 trans_start 時間至少 time-out 值, 網絡層最終調用驅動的 tx_timeout方法。這個方法的工作是是進行清除問題需要的工作并且保證任何已經開始的發送正確地完成。特別地, 驅動沒有丟失追蹤任何網絡代碼委托給它的 socket 緩存。

    當發生傳送超時, 驅動必須在接口統計量中標記這個錯誤, 并安排設備被復位到一個干凈的能發送新報文的狀態,一般驅動會調用netif_wake_queue函數重新啟動傳輸隊列。

    3        數據的接收

    3.1       數據接收的方式和過程

    從網絡上接收報文比發送它要難一些,因為必須分配一個 sk_buff 并從一個原子性上下文中遞交給上層。網絡驅動可以實現 2 種報文接收的模式:中斷驅動和查詢,大部分驅動采用中斷驅動技術。

    大部分硬件接口通過一個中斷處理來控制,硬件中斷處理器來發出 2 種可能的信號:一個新報文到了或者一個外出報文的發送完成了。網絡接口也能夠產生中斷來指示錯誤, 例如狀態改變, 等等。

    通常的中斷過程能夠告知新報文到達中斷和發送完成通知的區別,通過檢查物理設備中的狀態寄存器,來判斷是那一種中斷,對于發送完成中斷更新狀態信息,釋放skb內存。而對于接收數據中斷,從數據隊列中抽取一包數據,并把它傳遞給接收函數。

    注意:這里的對設備數據的操作是在鎖得保護下完成的,做一最后還要釋放掉鎖。

    3.2       選擇哪種接收模式

    那么,既然后兩種方式來處理網絡接口發來的數據,選擇那一種呢?一般認為中斷是比較好的一種方式,不過,如果接口接收數據太頻繁,甚至一秒中接收上千包數據,那么系統的中斷次數就非常多,這回嚴重影響系統的性能。所以,在頻繁接收數據的情況下,也可以考慮使用輪詢的方式。

    這樣,為了提高linux在寬帶系統上的性能,網絡子系統開發者創建了一種基于輪詢方式的接口NAPI,它雖然在很多情況下,并不被看好,但處理高流量的高速接口時,用這種NAPI輪詢技術處理到達的每一個數據包就足夠了,前提是網絡設備必須能支持這種模式,就是說一個網絡接口必須能保存多個數據包,而且中斷能夠禁止中斷并能在傳輸和其他事件上打開中斷。

    3.3      Bcm4329芯片wlan驅動數據傳送

    在bcm4329芯片Wlan驅動中,在函數dhd_attach被調用時,會初始化一個內核線程或一個tasklet中斷的下半部。其實這兩種方式就是之前的中斷和輪詢方式的實現版,如果使用輪詢,驅動初始化一個內核線程dhd_dpc_thread輪詢網絡接口接收的數據,中斷下半部是中斷處理程序的延續,用于處理比較復雜費時的操作,這樣就能早點從中斷中解放出來,防止拖累系統的性能。

    下面來看看這兩種方式的初始化(在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,&param);

            }

     

            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);

    }

    這里是一個永真循環,直到接收到終止信號才停止,該線程就是通過不斷調用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) {

                    /* 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這個中斷處理函數干了什么?在函數的最后部分是:

    #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循環調用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 /* DHDTHREAD */

     

            tasklet_schedule(&dhd->tasklet);

    }

    就是觸發一個中斷的下半部tasklet,讓cpu選擇在一個合適的時候調用dhd_dpc函數,這個函數會調用dhd_bus_dpc,然后進入上面流程圖的調用邏輯。

    詳細的數據處理過程不詳細敘述,可以參考源碼來具體分析。

     

    4        電源管理相關的調用邏輯

    電源管理始終是手機等移動設備最重要的一個功能,尤其對于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: enter\n",__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);

            /* 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       ,則系統會使用這部分代碼,其實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

     

                                    /* 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

        具體做什么內容,可以不用過多理會,一般只是會對該模塊做些最基本的低功耗設置,其實真正的低功耗設置時在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_register\n"));

            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(("##> %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;

    }

           上面的兩個電源管理函數都調用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) {

     

                            /* 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)) {

    ......

    }

    在系統進入suspend狀態后,wifi設備進入禁止中斷狀態,不再接收處理網絡發來的數據,系統進入sleep狀態,當然還有很多cpu在suspend之后進入sleep狀態,但此時系統clock中斷并沒有被禁止,而且pmu還正常工作,以期對power鍵和充電器連接的檢測。

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全