高通Android7.1 WIFI国家码问题

    xiaoxiao2023-10-08  139

    20:12:08.774420 [20:12:03.149290] [000000002A1ADFB1] [wpa_s] wlan: [I :HDD] hdd_driver_command: Received COUNTRY CN cmd from Wi-Fi GUI*** 20:12:08.774457 [20:12:03.149318] [000000002A1AE1BC] [wpa_s] wlan: [I :SME] sme_ChangeCountryCode: 5688: called 20:12:08.774490 [20:12:03.149334] [000000002A1AE2FD] [wpa_s] wlan: [I :SME] sme_ChangeCountryCode: 5728: returned 20:12:08.774520 [20:12:03.149476] [000000002A1AEDA0] [VosMC] wlan: [IH:VOS] vos_timer_stop: Timer Addr inside voss_stop : 0xbf3733d4 20:12:08.774550 [20:12:03.149494] [000000002A1AEF01] [VosMC] wlan: [I :WDA] <------ WDA_UpdateChReqCallback 20:12:08.774580 [20:12:03.149564] [000000002A1AF43B] [VosMC] wlan: [I :WDA] <------ WDA_UpdateScanParamsReqCallback, wdiStatus: 0 20:12:08.774609 [20:12:03.149574] [000000002A1AF4F8] [VosMC] wlan: [IH:VOS] Timer Addr inside voss_start : 0xbf3733d4 20:12:08.774639 [20:12:03.149598] [000000002A1AF6C1] [VosMC] wlan: [I :VOS] VosMCThread: Servicing the VOS SME MC Message queue 20:12:08.774668 [20:12:03.149616] [000000002A1AF819] [VosMC] wlan: [I :VOS] regdomain request 20:12:08.774943 [20:12:03.149624] [000000002A1AF8AB] [VosMC] wlan: [W :VOS] get country information from kernel db 20:12:08.774984 [20:12:03.443815] [000000002A71293C] [VosMC] wlan: [I :VOS] runtime country code : CN is found in kernel db 20:12:08.775014 [20:12:03.443894] [000000002A712F06] [wpa_s] wlan: [I :HDD] Exit:hdd_driver_command 20:12:08.775045 [20:12:03.443903] [000000002A712FA2] [VosMC] wlan: [IH:VOS] vos_timer_stop: Timer Addr inside voss_stop : 0xbf3733d4 20:12:08.775075 [20:12:03.443907] [000000002A712FE7] [wpa_s] wlan: [I :HDD] Exit:__hdd_ioctl 20:12:08.775106 [20:12:03.443920] [000000002A7130E9] [VosMC] wlan: [I :WDA] <------ WDA_UpdateScanParamsRespCallback 20:12:08.775136 [20:12:03.443937] [000000002A71322C] [VosMC] wlan: [IH:VOS] vos_list_remove_front: list empty 20:12:08.775165 [20:12:03.444199] [000000002A7145E2] [kwork] wlan: [I :VOS] cfg80211 reg notifier callback for country for initiator 1 20:12:08.775195 [20:12:03.444211] [000000002A7146B3] [kwork] wlan: [I :VOS] __wlan_hdd_linux_reg_notifier: Req initiator 1 CC=CN

    不同国家,WIFI使用的信道是不同的,2.4G一共有14个信道,中国使用1-13信道,美国则使用1-11信道。因此,我们需要指定WIFI的国家码,来确定WIFI在扫描和连接过程中,可以在哪些信道上进行。

    设置国家码有三种方法,我们逐一介绍:

    1.通过设置prop设置

    oem产商设置国家码在

    device/qcom/msm8909w/system.prop

    文件中设置一个prop就可以了。

    ro.boot.wificountrycode=CN

    设置好prop后,我们分析下国家码的设置过程。

    在SystemServer起来后,会加载WifiServiceImpl,即WIFI服务。在WIFI服务中,将获取设置的WIFI国家码属性。并作为参数传给WifiStateMachine的构造函数中。

    public WifiServiceImpl(Context context) { mCountryCode = new WifiCountryCode( WifiNative.getWlanNativeInterface(), SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE), mFacade.getStringSetting(mContext, Settings.Global.WIFI_COUNTRY_CODE), mContext.getResources().getBoolean( R.bool.config_wifi_revert_country_code_on_cellular_loss)); mWifiStateMachine = new WifiStateMachine(mContext, mFacade, wifiStateMachineThread.getLooper(), mUserManager, mWifiInjector, new BackupManagerProxy(), mCountryCode); }

    此处首先构造了WifiCountryCode对象,我们看构造方法:

    public WifiCountryCode( WifiNative wifiNative, String oemDefaultCountryCode, String persistentCountryCode, boolean revertCountryCodeOnCellularLoss) { mWifiNative = wifiNative; mRevertCountryCodeOnCellularLoss = revertCountryCodeOnCellularLoss; if (!TextUtils.isEmpty(persistentCountryCode)) { mDefaultCountryCode = persistentCountryCode.toUpperCase(); } else if (!TextUtils.isEmpty(oemDefaultCountryCode)) { mDefaultCountryCode = oemDefaultCountryCode.toUpperCase(); } else { if (mRevertCountryCodeOnCellularLoss) { Log.w(TAG, "config_wifi_revert_country_code_on_cellular_loss is set, " + "but there is no default country code."); mRevertCountryCodeOnCellularLoss = false; return; } } if (mRevertCountryCodeOnCellularLoss) { Log.d(TAG, "Country code will be reverted to " + mDefaultCountryCode + " on MCC loss"); } }

    oemDefaultCountryCode就是我们从属性设置的国家码。另外还有一个persistentCountryCode,这个是从设置中获取的国家码。从构造方法可以看到,从设置文件中取到的国家码如果存在,直接将其赋值给mDefaultCountryCode,否则将oemDefaultCountryCode赋值给mDefaultCountryCode。设置中的国家码优先级大于属性中的国家码。

    mCountryCode传递给WifiStateMachine中。WifiStateMachine在wpa_supplicant启动起来后,取设置国家码。

    public WifiStateMachine(Context context, FrameworkFacade facade, Looper looper, UserManager userManager, WifiInjector wifiInjector, BackupManagerProxy backupManagerProxy, WifiCountryCode countryCode) { mCountryCode = countryCode; } class SupplicantStartedState extends State { @Override public void enter() { /* Wifi is available as long as we have a connection to supplicant */ mNetworkInfo.setIsAvailable(true); if (mNetworkAgent != null) mNetworkAgent.sendNetworkInfo(mNetworkInfo); int defaultInterval = mContext.getResources().getInteger( R.integer.config_wifi_supplicant_scan_interval); mCountryCode.setReadyForChange(true); }

    此处mCountryCode.setReadyForChange()方法就是用来设置国家码的。我们大致看起实现方法:

    public synchronized void setReadyForChange(boolean ready) { if (DBG) Log.d(TAG, "Set ready: " + ready); mReady = ready; // We are ready to set country code now. // We need to post pending country code request. if (mReady) { updateCountryCode(); } } private void updateCountryCode() { String country = pickCountryCode(); if (country != null) { setCountryCodeNative(country); } } private String pickCountryCode() { if (mTelephonyCountryCode != null) { return mTelephonyCountryCode; } if (mDefaultCountryCode != null) { return mDefaultCountryCode; } // If there is no candidate country code we will return null. return null; }

    在pickCountryCode中我们首先选择使用那个CountryCode,如果mTelephonyCountryCode有设置,就使用mTelephonyCountryCode对应的code。mTelephonyCountryCode在检测电话卡后,通过WifiManager设置进来的。不插卡时,mTelephonyCountryCode为null。如果不插卡,mTelephonyCountryCode不会被pick到,则会返回mDefaultCountryCode。mDefaultCountryCode在WifiCountryCode构造是传入的。mDefaultCountryCode被设置成设置值或者属性值。我们假设没有设置值,只有OEM的属性值。则此处pick到我们属性中设置的属性值。

    在pick到国家码后,使用setCountryCodeNative进行设置。否则不做任何设置,不做设置是不插卡不在设置中设置国家码也没有属性的情况。属于第三种情况。

    public boolean setCountryCode(String countryCode) { if (countryCode != null) return doBooleanCommand("DRIVER COUNTRY " + countryCode.toUpperCase(Locale.ROOT)); else return doBooleanCommand("DRIVER COUNTRY"); }

    "DRIVER COUNTRY"通过JNI传递给wpa_supplicant处理。

    else if (os_strncmp(buf, "DRIVER ", 7) == 0) { reply_len = wpa_supplicant_driver_cmd(wpa_s, buf + 7, reply, reply_size); const struct wpa_driver_ops wpa_driver_nl80211_ops = { .driver_cmd = wpa_driver_nl80211_driver_cmd, } int wpa_driver_nl80211_driver_cmd(void *priv, char *cmd, char *buf, size_t buf_len ) { if ((ret = ioctl(drv->global->ioctl_sock, SIOCDEVPRIVATE + 1, &ifr)) < 0) { wpa_printf(MSG_ERROR, "%s: failed to issue private commands\n", __func__); } }

    通过ioctl发送指令给驱动程序。驱动程序在对应ioctl中接收。

    static struct net_device_ops wlan_drv_ops = { .ndo_open = hdd_open, .ndo_do_ioctl = hdd_ioctl, } int __hdd_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { switch (cmd) { case (SIOCDEVPRIVATE + 1): if (is_compat_task()) ret = hdd_driver_compat_ioctl(pAdapter, ifr); else ret = hdd_driver_ioctl(pAdapter, ifr); break; } static int hdd_driver_command(hdd_adapter_t *pAdapter, hdd_priv_data_t *ppriv_data) { else if ( strncasecmp(command, "COUNTRY", 7) == 0 ) { char *country_code; country_code = command + 8; INIT_COMPLETION(pAdapter->change_country_code); hdd_checkandupdate_dfssetting(pAdapter, country_code); #ifndef CONFIG_ENABLE_LINUX_REG hdd_checkandupdate_phymode(pAdapter, country_code); #endif ret = (int)sme_ChangeCountryCode(pHddCtx->hHal, (void *)(tSmeChangeCountryCallback) wlan_hdd_change_country_code_callback, country_code, pAdapter, pHddCtx->pvosContext, eSIR_TRUE, eSIR_TRUE); if (eHAL_STATUS_SUCCESS == ret) { ret = wait_for_completion_interruptible_timeout( &pAdapter->change_country_code, msecs_to_jiffies(WLAN_WAIT_TIME_COUNTRY)); if (0 >= ret) { hddLog(VOS_TRACE_LEVEL_ERROR, "%s: SME while setting country code timed out %d", __func__, ret); } } else { VOS_TRACE( VOS_MODULE_ID_VOSS, VOS_TRACE_LEVEL_FATAL, "%s: SME Change Country code fail ret=%d", __func__, ret); ret = -EINVAL; } } }

    sme_ChangeCountryCode函数将构造一个消息,消息中包括国家码信息,消息的队列ID,消息类型等内容,然后 将消息发送到VOSS中进一步处理。这正是高通驱动的消息基本处理方式。

    @/device/qcom/common/opensource/wlan/prima/CORE/SME/src/sme_common/sme_Api.c eHalStatus sme_ChangeCountryCode( tHalHandle hHal, tSmeChangeCountryCallback callback, tANI_U8 *pCountry, void *pContext, void* pVosContext, tAniBool countryFromUserSpace, tAniBool sendRegHint ) { vos_msg_t msg; tAniChangeCountryCodeReq *pMsg; 5709 pMsg->msgType = pal_cpu_to_be16((tANI_U16)eWNI_SME_CHANGE_COUNTRY_CODE); pMsg = vos_mem_malloc(sizeof(tAniChangeCountryCodeReq)); pMsg->msgLen = (tANI_U16)sizeof(tAniChangeCountryCodeReq); vos_mem_copy(pMsg->countryCode, pCountry, 3); pMsg->countryFromUserSpace = countryFromUserSpace; pMsg->sendRegHint = sendRegHint; pMsg->changeCCCallback = callback; pMsg->pDevContext = pContext; pMsg->pVosContext = pVosContext; msg.type = eWNI_SME_CHANGE_COUNTRY_CODE; msg.bodyptr = pMsg; msg.reserved = 0; if(VOS_STATUS_SUCCESS != vos_mq_post_message(VOS_MQ_ID_SME, &msg)) return (status); } VOS_STATUS vos_mq_post_message( VOS_MQ_ID msgQueueId, vos_msg_t *pMsg ) { switch (msgQueueId) { case VOS_MQ_ID_SME: { pTargetMq = &(gpVosContext->vosSched.smeMcMq); break; } } pMsgWrapper = vos_mq_get(&gpVosContext->freeVosMq); /* ** Copy the message now */ vos_mem_copy( (v_VOID_t*)pMsgWrapper->pVosMsg, (v_VOID_t*)pMsg, sizeof(vos_msg_t)); vos_mq_put(pTargetMq, pMsgWrapper); set_bit(MC_POST_EVENT, &gpVosContext->vosSched.mcEventFlag); wake_up_interruptible(&gpVosContext->vosSched.mcWaitQueue); }

    将消息拷贝到MsgWrapper->pVosMsg中,然后唤醒vosSched.mcWaitQueue。我们直接进入mcWaitQueue这个等待队列看唤醒后的操作。

    eHalStatus sme_ProcessMsg(tHalHandle hHal, vos_msg_t* pMsg) { case eWNI_SME_CHANGE_COUNTRY_CODE: if(pMsg->bodyptr) { status = sme_HandleChangeCountryCode((void *)pMac, pMsg->bodyptr); vos_mem_free(pMsg->bodyptr); } break; } eHalStatus sme_HandleChangeCountryCode(tpAniSirGlobal pMac, void *pMsgBuf) { v_REGDOMAIN_t domainIdIoctl; static uNvTables nvTables; pMsg = (tAniChangeCountryCodeReq *)pMsgBuf; /* Set Current Country code and Current Regulatory domain */ status = csrSetCountryCode(pMac, pMsg->countryCode, NULL); /* overwrite the defualt country code */ vos_mem_copy(pMac->scan.countryCodeDefault, pMac->scan.countryCodeCurrent, WNI_CFG_COUNTRY_CODE_LEN); /* Get Domain ID from country code */ status = csrGetRegulatoryDomainForCountry(pMac, pMac->scan.countryCodeCurrent, (v_REGDOMAIN_t *) &domainIdIoctl, COUNTRY_QUERY); status = WDA_SetRegDomain(pMac, domainIdIoctl, pMsg->sendRegHint); /* get the channels based on new cc */ status = csrInitGetChannels( pMac ); /* reset info based on new cc, and we are done */ csrResetCountryInformation(pMac, eANI_BOOLEAN_TRUE, eANI_BOOLEAN_TRUE); }

    sme_HandleChangeCountryCode设置好国家码,并通过国家码重新设置scan的信道。文章开始的log就是这里打印处理的。

    2.通过WifiManager提供的结构设置

    在安装上电话卡,Android设备可以从电话卡找到位置信息。然后会自动设置国家码。

    private static void setWifiCountryCodeFromMcc(Context context, int mcc) { String country = MccTable.countryCodeForMcc(mcc); Slog.d(LOG_TAG, "WIFI_COUNTRY_CODE set to " + country); WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); wM.setCountryCode(country, false); }

    这里通过WifiManager来设置国家码。参数中除了国家码外,还有一个是否固化国家码的boolean类型参数。固化为ture,我们就会把国家码写入到设置中固化下来,以后优先使用该国家码。我们这里不会固化。

    public void setCountryCode(String countryCode, boolean persist) { Slog.i(TAG, "WifiService trying to set country code to " + countryCode + " with persist set to " + persist); enforceConnectivityInternalPermission(); final long token = Binder.clearCallingIdentity(); try { if (mCountryCode.setCountryCode(countryCode, persist) && persist) { // Save this country code to persistent storage mFacade.setStringSetting(mContext, Settings.Global.WIFI_COUNTRY_CODE, countryCode); } } finally { Binder.restoreCallingIdentity(token); } }

    这里的countryCode将被吸入到mTelephonyCountryCode。前面已经提过,TelephonyCountryCode优先级高于mDefaultCountryCode。

    public synchronized boolean setCountryCode(String countryCode, boolean persist) { if (DBG) Log.d(TAG, "Receive set country code request: " + countryCode); // Ignore empty country code. if (TextUtils.isEmpty(countryCode)) { if (DBG) Log.d(TAG, "Ignore empty country code"); return false; } if (persist) { mDefaultCountryCode = countryCode; } mTelephonyCountryCode = countryCode.toUpperCase(); // If wpa_supplicant is ready we set the country code now, otherwise it will be // set once wpa_supplicant is ready. if (mReady) { updateCountryCode(); } return true; }

    剩下的内容updateCountryCode和1中设置方法相同了。

    3.从扫描结果过去国家码

    如果Android设备没有设置国家码prop,也没有插卡,则开机后设备是没有国家码的,这个时候将从扫描的WIFI结果中,寻找国家信息,自动设定一个国家码。这样设置可能会有误。

    驱动中打印的log如下:

    17:32:05.033088 [17:32:04.731383] [000000007E40829B] [VosMC] wlan: [I :SME] Scan received 3 unique BSS scan reason is 9 17:32:05.033129 [17:32:04.731430] [000000007E408625] [VosMC] wlan: [W :SME] csrMoveTempScanResultsToMainList: 3250: 11d AP Bssid 00:35:1a:db:f7:02 chan= 8, rssi = -63, countryCode CN 17:32:05.033171 [17:32:04.731474] [000000007E408978] [VosMC] wlan: [W :SME] csrMoveTempScanResultsToMainList: 3250: 11d AP Bssid 00:35:1a:db:f7:00 chan= 8, rssi = -63, countryCode CN 17:32:05.033211 [17:32:04.731510] [000000007E408C1A] [VosMC] wlan: [I :SME] Selected Country is CN With count 2 17:32:05.033283 [17:32:04.731521] [000000007E408CEE] [VosMC] wlan: [I :VOS] regdomain request 17:32:05.033323 [17:32:04.731528] [000000007E408D7F] [VosMC] wlan: [W :VOS] get country information from kernel db 17:32:05.033363 [17:32:04.731776] [000000007E40A023] [kwork] wlan: [I :VOS] cfg80211 reg notifier callback for country for initiator 1 17:32:05.033403 [17:32:04.731788] [000000007E40A101] [kwork] wlan: [I :VOS] __wlan_hdd_linux_reg_notifier: Req initiator 1 CC=CN 17:32:05.033443 [17:32:05.022912] [000000007E95EB75] [VosMC] wlan: [I :VOS] runtime country code : CN is found in kernel db

    基本调用流程就是从扫描结果中选取一个国家码,如果扫描结果中只有一个国家码,就设置这个国家码,如果有多个,就随机选择一个。

    csrScanComplete csrSaveScanResults csrMoveTempScanResultsToMainList csrElectedCountryInfo(pMac); csrLearnCountryInformation( pMac, NULL, NULL, eANI_BOOLEAN_TRUE );

    在csrElectedCountryInfo中选择国家码,在csrLearnCountryInformation设置国家码。

    tANI_BOOLEAN csrLearnCountryInformation( tpAniSirGlobal pMac, tSirBssDescription *pSirBssDesc,tDot11fBeaconIEs *pIes, tANI_BOOLEAN fForce) { if (eANI_BOOLEAN_FALSE == useVoting) pCountryCodeSelected = pIesLocal->Country.country; else pCountryCodeSelected = pMac->scan.countryCodeElected; status = csrGetRegulatoryDomainForCountry(pMac, pCountryCodeSelected, &domainId, COUNTRY_IE); /* updating 11d Country Code with Country code selected. */ vos_mem_copy(pMac->scan.countryCode11d, pCountryCodeSelected, WNI_CFG_COUNTRY_CODE_LEN); }

    在__wlan_hdd_linux_reg_notifier()方法中设置通过从scan结果选择的国家码。

    通常为了保证能够正确指定扫描的信道,需要OEM产商在porp中设置国家码,防止出现国家码设置异常,扫描不到某些热点的情况。

    最新回复(0)