MTK启动无线热点的过程

MTK有一个和其他android不一样的无线热点的实现。现在让我们从最顶层的界面到最底层的驱动来看看这个启动过程是如何的吧。

1. 界面上启动无线热点(应用程序部分)

我使用的界面是联想的个人热点(Personal Hotspot)的界面。源代码是反编译得到的。代码位于WifiApEnabler这个类里

public class WifiApEnabler {
  public void setSoftapEnabled(boolean enable) {
  }
}

第一步是停用wifi

        final ContentResolver cr = mContext.getContentResolver();
        /**
         * Disable Wifi if enabling tethering
         */
        int wifiState = mWifiManager.getWifiState();
        if (enable && ((wifiState == WifiManager.WIFI_STATE_ENABLING) ||
                    (wifiState == WifiManager.WIFI_STATE_ENABLED))) {
            mWifiManager.setWifiEnabled(false);
            Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1);
        }

这一步有两个问题。wifi_saved_state是干什么的。另外一个是this.mWifiManager哪里来的

this.mWifiManager = ((WifiManager)paramTetherSettings.getSystemService("wifi"));
// public class TetherSettings extends PreferenceActivity
// 等价于 Context.getSystemService(Context.WIFI_SERVICE)

paramTetherSettings是一个TetherSettings的实例,所以有系统提供的getSystemService。

而WIFI_SAVED_STATE是用来保存在热点开启之前,wifi是开的还是关,以便在热点关闭之后决定是不是要重新把wifi打开。

第二步是开启热点

    this.mWifiManagerEx.setRealObject(this.mWifiManager);
    if (this.mWifiManagerEx.setWifiApEnabled(null, enable)) {
      this.mCheckBox.setEnabled(false);
    }

这一步是是把wifiManager的实例传递给WifiManagerEx,然后通过WifiManagerEx用反射去调用wifiManager的私有api,setWifiApEnabled。如果开启成功则把启用热点的checkbox禁用掉,防止重新启用。注意到setWifiApEnabled并没有把configuration做为参数传递过去。因为之前已经通过setWifiApConfiguration设置过了。

2. WifiManager 的 setWifiApEnabled 的实现 (框架的Java部分)

这个不是官方API的一部分。但是好像官方的实现里就有这个函数:

    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
        try {
            mService.setWifiApEnabled(wifiConfig, enabled);
            return true;
        } catch (RemoteException e) {
            return false;
        }
    }

那么mService是什么呢?是getSystemService(Context.WIFI_SERVICE)

    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
        enforceChangePermission();
        mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled);
    }

那mWifiStateMachine是什么呢?就是WifiStateMachine。

    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) {
        mLastApEnableUid.set(Binder.getCallingUid());
        if (enable) {
            /* Argument is the state that is entered prior to load */
            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_AP_STATE_ENABLING, 0));
            sendMessage(obtainMessage(CMD_START_AP, wifiConfig));
        } else {
            sendMessage(CMD_STOP_AP);
            /* Argument is the state that is entered upon success */
            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_DISABLED, 0));
        }
    }

看代码还是很清晰的。连发了两个message,一个加载驱动。一个是启动热点。那么是谁在监听这两个发出去的消息呢?

关于第一个CMD_LOAD_DRIVER这个命令,找到的实现是没有进行任何处理。

            case CMD_START_AP:
                    transitionTo(mSoftApStartingState);
                    break;
                    

那么应该就是调用了SoftApStartingState的enter方法了

class SoftApStartingState extends State {
        @Override
        public void enter() {
            if (DBG) log(getName() + "\n");
            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());

            final Message message = getCurrentMessage();
            if (message.what == CMD_START_AP) {
                final WifiConfiguration config = (WifiConfiguration) message.obj;

                if (config == null) {
                    mWifiApConfigChannel.sendMessage(CMD_REQUEST_AP_CONFIG);
                } else {
                    mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);
                    startSoftApWithConfig(config);
                }
            } else {
                throw new RuntimeException("Illegal transition to SoftApStartingState: " + message);
            }
        }
}
        

居然又转了一道手,这次是交给了mWifiApConfigChannel。这个是什么?

private AsyncChannel mWifiApConfigChannel = new AsyncChannel();
mWifiApConfigChannel.connectSync(mContext, getHandler(), wifiApConfigStore.getMessenger());

那这消息过去到底是谁接收的啊?还是当前这个state machine接收的,因为getHandler返回的还是自己的handler。

mSmHandler = new SmHandler(looper, this);

那在softApStartingState这个状态下是如何处理CMD_REQUEST_AP_CONFIG的?我们来看一下:

case WifiStateMachine.CMD_REQUEST_AP_CONFIG:
                    mReplyChannel.replyToMessage(message,
                            WifiStateMachine.CMD_RESPONSE_AP_CONFIG, mWifiApConfig);
                    break;
                    

挺简单的WifiApConfigStore监听了这个消息,然后把之前setWifiApConfiguration存着的mWifiApConfig用消息发回去了。

                case WifiStateMachine.CMD_RESPONSE_AP_CONFIG:
                    WifiConfiguration config = (WifiConfiguration) message.obj;
                    if (config != null) {
                        startSoftApWithConfig(config);
                    } else {
                        loge("Softap config is null!");
                        sendMessage(CMD_START_AP_FAILURE);
                    }
                    break;
                    

这次接收消息的是SoftApStartingState这个类,不过还是再次转手:

    private void startSoftApWithConfig(final WifiConfiguration config) {
        // start hostapd on a seperate thread
        new Thread(new Runnable() {
            public void run() {
                if (DBG) Xlog.d(TAG, "startSoftApWithConfig, config:" + config);
                try {
                    mNwService.startAccessPoint(config, mInterfaceName, SOFTAP_IFACE);
                } catch (Exception e) {
                    loge("Exception in softap start " + e);
                    try {
                        mNwService.stopAccessPoint(mInterfaceName);
                        mNwService.startAccessPoint(config, mInterfaceName, SOFTAP_IFACE);
                    } catch (Exception e1) {
                        loge("Exception in softap re-start " + e1);
                        sendMessage(CMD_START_AP_FAILURE);
                        return;
                    }
                }
                if (DBG) log("Soft AP start successful");
                sendMessage(CMD_START_AP_SUCCESS);
            }
        }, "startSoftApThread").start();
    }
    

mNwService是NetworkManagementService的实例,继续:

public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface)
             throws IllegalStateException {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
        mStartRequest = true;
        mStopRequest = false;
        try {
            //wifiFirmwareReload(wlanIface, "AP");
            mConnector.doCommand(String.format("softap start " + wlanIface));
            if (wifiConfig == null) {
                mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface));
            } else {
                /**
                 * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8]
                 * argv1 - wlan interface
                 * argv2 - softap interface
                 * argv3 - SSID
                 * argv4 - Security
                 * argv5 - Key
                 * argv6 - Channel
                 * argv7 - Channel Width
                 * argv8 - Preamble
                 * argv9 - Max SCB
                 */
                 String str = String.format("softap set " + wlanIface + " " + softapIface +
                                       " %s %s %s %s %s", convertQuotedString(wifiConfig.SSID),
                                       getSecurityType(wifiConfig),
                                       convertQuotedString(wifiConfig.preSharedKey),
                                       wifiConfig.channel,
                                       wifiConfig.channelWidth
                                       );
                mConnector.doCommand(str);
            }
            mConnector.doCommand(String.format("softap startap"));
        } catch (NativeDaemonConnectorException e) {
            throw new IllegalStateException("Error communicating to native daemon to start softap", e);
        }
    }

可以看到wifiFirmwareReload被注释掉了,看来不需要切换无线网卡的固件就可以启动AP。然后转交给了mConnector,这又是什么?

/**
     * connector object for communicating with netd
     */
    private NativeDaemonConnector mConnector;
    

看来是给netd了。看看怎么给的:

            socket = new LocalSocket();
            LocalSocketAddress address = new LocalSocketAddress(“netd”,
                    LocalSocketAddress.Namespace.RESERVED);

            socket.connect(address);
            mOutputStream = socket.getOutputStream();

command是通过这个socket写出去的,结果也是在这个socket的输出里读出来的。看来搞了半天就是开了一个socket连上了netd,然后给netd发了一条softap start的命令啊。然后又发了一条softap set命令做设置。

3. netd的softap start/softap set的实现 (框架的C++部分)

在netd的CommandListener.cpp里找到了命令派发的实现

if (!strcmp(argv[1], "start")) {
        rc = sSoftapCtrl->startDriver(argv[2]);
        } else if (!strcmp(argv[1], "set")) {
        rc = sSoftapCtrl->setSoftap(argc, argv);
        }

找到SoftApController.cpp:

int SoftapController::startDriver(char *iface)
{
    int connectTries = 0;

    //  load p2p driver,
    // WifiStateMachine will load wifi driver before starting softAP
    ::wifi_hotspot_load_driver();

    //  start the p2p_supplicant
    LOGD("start the p2p_supplicant");
#if 0
#else
    if (::wifi_hotspot_start_supplicant() < 0) {
        LOGE("Softap driver start - failed to start p2p_supplicant");
        return -1;
    }
#endif

    //  connect to the p2p_supplicant
    while (true) {
        LOGD("try to connect to p2p_supplicant");
        if (::wifi_hotspot_connect_to_supplicant() == 0) {
            LOGD("connect to p2p_supplicant");
        ::wifi_hotspot_close_supplicant_connection_no_wait();
            return 0;
        }
		//maximum delay 12s
        if (connectTries++ < 40) {
			sched_yield();
			LOGD("softap sleep %d us\n", AP_CONNECT_TO_SUPPLICANT_DELAY);
            usleep(AP_CONNECT_TO_SUPPLICANT_DELAY);
        } else {
            break;
        }
    }

    LOGD("Softap driver start - failed to connect to p2p_supplicant");

    return -1;
}

wifi_hotspot_load_driver是由libhardware_legacy提供的函数实现是:

int wifi_hotspot_load_driver()
{
    property_get(
        rHotspotSuppPara.acIfPropName,
        rHotspotSuppInfo.acIfName,
        rHotspotSuppPara.acIfDefName);
    return halDoCommand("load hotspot");
}

看来又转了一道手给hald,怎么转的?

int halDoCommand(const char *cmd)
{
    int sock;
    char *final_cmd;

    if ((sock = socket_local_client(HAL_DAEMON_NAME,
                                     ANDROID_SOCKET_NAMESPACE_RESERVED,
                                     SOCK_STREAM)) < 0) {
        LOGE("Error connecting (%s)", strerror(errno));
        //exit(4);
        /*return error if hald is not existing*/
        return errno;
    }

    asprintf(&final_cmd, "%s %s", HAL_DAEMON_CMD, cmd);

    LOGD("Hal cmd: \"%s\"", final_cmd);

    if (write(sock, final_cmd, strlen(final_cmd) + 1) < 0) {
        free(final_cmd);
    close(sock);
        LOGE("Hal cmd error: \"%s\"", final_cmd);
        return errno;
    }
    free(final_cmd);
    return halDoMonitor(sock);
}

又是一个socket,实现挺简单的。hald收到了命令之后呢?先找到派发命令的代码:

if (!strcmp(argv[1], "load")) {
        pthread_mutex_lock(&mLock);
        rc = sHaldCtrl->loadDriver(argv[2]);
        pthread_mutex_unlock(&mLock);
}

这个loadDriver的实现还挺长的:

int HaldController::loadDriver(const char *ifname){
#ifdef CFG_ENABLE_NVRAM_CHECK
    int nvram_restore_ready_retry = 0;
    char nvram_init_val[32];
    static bool fg_is_nvram_chk_failed = false;

    LOGD("Check NvRAM status.");
    while(nvram_restore_ready_retry < MAX_NVRAM_RESTORE_READY_RETRY_NUM) {
        nvram_restore_ready_retry++;
        property_get("nvram_init", nvram_init_val, NULL);
        if(strcmp(nvram_init_val, "Ready") == 0) {
            LOGD("NvRAM is READY!");
			fg_is_nvram_chk_failed = false;
            break;
        } else if (fg_is_nvram_chk_failed){
			LOGE("NvRAM status check is still failed! NvRAM content may be WRONG!");
			break;
        } else {
            usleep(NVRAM_RESTORE_POLLING_TIME_USEC);
		}
    }
    if(nvram_restore_ready_retry >= MAX_NVRAM_RESTORE_READY_RETRY_NUM) {
		fg_is_nvram_chk_failed = true;
        LOGE("NvRAM status check timeout(%dus)! NvRAM content may be WRONG!", MAX_NVRAM_RESTORE_READY_RETRY_NUM * NVRAM_RESTORE_POLLING_TIME_USEC);
    }
#endif

#ifdef MTK_SDIORETRY_SUPPORT
	//write the sdio retry setting. add by mtk80743
	{

		sdio_retry = NVM_GetFileDesc(iFileSDIO_RETRYLID, &rec_size, &rec_num, true);
		if(read(sdio_retry, &sdioRetrySetting, rec_num*rec_size) < 0){
					LOGD("read iFileSDIO_RETRYLID failed %s\n", strerror(errno));
		}else{
			if(sdioRetrySetting.retry_flag == 0 && sdioRetrySetting.sdio.clk_src == 0){
				needReadSetting = 1;
			}else{
				needReadSetting = 0;
				writeRetrySetting();
			}
		}
		NVM_CloseFileDesc(sdio_retry);
	}
#endif
    /*LOAD WIFI*/
    if (!strcmp(ifname, "wifi")) {
        /*if wifi or its sub function is on, no need to load wifi again*/
        if(isHotspotActive || isP2pActive || isWifiActive) {
            /*do nothing*/
            LOGE("Wifi driver is already loaded, no need to load again.");
            isWifiActive = true;
            return 0;
        /*load wifi driver*/
        } else {
            LOGD("Start load wifi driver.");
            /*load wifi driver*/
            sDriverCtrl->load(NETWORK_IFACE_WIFI);
            /*turn on wifi power*/
            powerOn();
            /*set flag*/
            isWifiActive = true;
            return 0;
        }
    /*LOAD HOTSPOT*/
    } else if (!strcmp(ifname, "hotspot")) {
        if(isHotspotActive) {
            LOGE("Hotspot driver is already loaded, no need to load again.");
            return 0;
        }
		/*if wifi is not on, MUST load wifi and turn on wifi power first*/
		if(!isWifiActive){
			sDriverCtrl->load(NETWORK_IFACE_WIFI);
			powerOn();
		}
		/*if p2p is on, unload p2p driver*/
		if(isP2pActive) {
            LOGE("Unload P2P driver first.");
            sDriverCtrl->unload(NETWORK_IFACE_P2P);
            isP2pActive = false;
        }
        /*load hotspot driver*/
        LOGD("Start load hotspot driver.");
        sDriverCtrl->load(NETWORK_IFACE_HOTSPOT);
        isHotspotActive = true;
#ifdef CFG_ENABLE_RFKILL_IF_FOR_CFG80211
		/*enable hotspot rfkill interface for cfg80211*/
		sRfkillCtrl->setAllState(1);
#endif
        return 0;
    /*LOAD P2P*/
    } else if (!strcmp(ifname, "p2p")) {
        if(isP2pActive) {
            LOGE("P2P driver is already loaded, no need to load again.");
            return 0;
        }
        /*if wifi is not on, MUST load wifi and turn on wifi power first*/
        if(!isWifiActive){
            sDriverCtrl->load(NETWORK_IFACE_WIFI);
            powerOn();
        }
		/*if hotspot is on, unload hotspot driver*/
		if(isHotspotActive) {
            LOGE("Unload Hotspot driver first.");
            sDriverCtrl->unload(NETWORK_IFACE_HOTSPOT);
            isHotspotActive = false;
		}
        /*load p2p driver*/
        LOGD("Start load P2P driver.");
        sDriverCtrl->load(NETWORK_IFACE_P2P);
        isP2pActive = true;
#ifdef CFG_ENABLE_RFKILL_IF_FOR_CFG80211
		/*enable p2p rfkill interface for cfg80211*/
		sRfkillCtrl->setAllState(1);
#endif
        return 0;
    }
    return -1;
}

反正最后就是按照hotspot模式加载了驱动。最终调用的命令是insmod,rmmod。

回过头去看加载完驱动之后SoftApController还干什么事情:

if (::wifi_hotspot_start_supplicant() < 0) {
        LOGE("Softap driver start - failed to start p2p_supplicant");
        return -1;
    }

这个命令也是libhardware_legacy提供的,实现是:

int wifi_hotspot_start_supplicant()
{
   return start_supplicant(&rHotspotSuppPara, rHotspotSuppInfo.acIfName);
}
int start_supplicant(const struct _SUPPLICANT_PARA_T * prTar, const char *pcIface)
{
    char daemon_cmd[PROPERTY_VALUE_MAX];
    char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
    int count = 200; /* wait at most 20 seconds for completion */
#ifdef HAVE_LIBC_SYSTEM_PROPERTIES
    const prop_info *pi;
    unsigned serial = 0;
#endif

    /* Check whether already running */
    if (property_get(prTar->acSuppPropName, supp_status, NULL)
            && strcmp(supp_status, "running") == 0) {
        return 0;
    }

    /* Before starting the daemon, make sure its config file exists */
    if (ensure_config_file_exists(prTar->acSuppConfigFile, prTar->acSuppConfigTemplate) < 0) {
        LOGE("[%s] %s will not be enabled", pcIface, prTar->acSuppName);
        return -1;
    }

    /*Set interface UP (ifconfig "iface" up)*/
    if(set_iface(pcIface, 1) < 0 ) {
        /*If interface up failed, skip the following actions*/
        return -1;
    }

    /* Clear out any stale socket files that might be left over. */
    LOGD("[%s] clear out stale sockets with prefix \"%s\" in %s", pcIface, prTar->acSuppCtrlPrefix, CONFIG_CTRL_IFACE_CLIENT_DIR);
    ctrl_cleanup(prTar->acSuppCtrlPrefix);

    LOGI("[%s] start %s", pcIface, prTar->acSuppName);
#ifdef HAVE_LIBC_SYSTEM_PROPERTIES
    /*
     * Get a reference to the status property, so we can distinguish
     * the case where it goes stopped => running => stopped (i.e.,
     * it start up, but fails right away) from the case in which
     * it starts in the stopped state and never manages to start
     * running at all.
     */
    pi = __system_property_find(prTar->acSuppPropName);
    if (pi != NULL) {
        serial = pi->serial;
    }
#endif
    property_get(prTar->acIfPropName, (char *)pcIface, prTar->acIfDefName);
    snprintf(daemon_cmd, PROPERTY_VALUE_MAX, prTar->acSuppDeamonCmd, prTar->acSuppName, pcIface, prTar->acSuppConfigFile);
    property_set("ctl.start", daemon_cmd);
    LOGD("[%s] supplicant start command: \"%s\"", pcIface, daemon_cmd);
    //property_set("ctl.start", prTar->acSuppName);
    sched_yield();

    while (count-- > 0) {
#ifdef HAVE_LIBC_SYSTEM_PROPERTIES
        if (pi == NULL) {
            pi = __system_property_find(prTar->acSuppPropName);
        }
        if (pi != NULL) {
            __system_property_read(pi, NULL, supp_status);
            if (strcmp(supp_status, "running") == 0) {
                return 0;
            } else if (pi->serial != serial &&
                    strcmp(supp_status, "stopped") == 0) {
                return -1;
            }
        }
#else
        if (property_get(prTar->acSuppPropName, supp_status, NULL)) {
            if (strcmp(supp_status, "running") == 0)
                return 0;
        }
#endif
        usleep(100000);
    }
    return -1;
}

最终调用的命令是什么得看下面这个结构体的内容:

static const SUPPLICANT_PARA_T rHotspotSuppPara = {
    "ap0",
    "wifi.tethering.interface",
    "/data/misc/p2p_supplicant",
    "p2p_supplicant",
    "init.svc.p2p_supplicant",
    "/system/etc/wifi/p2p_supplicant.conf",
    "/data/misc/wifi/p2p_supplicant.conf",
    "%s:-Dnl80211 -i%s -c%s -dd",
#ifdef CFG_SUPPORT_CONCURRENT_NETWORK
    "p2p_ctrl"
#else
    "wpa_ctrl"
#endif
};
typedef struct _SUPPLICANT_PARA_T {
    char acIfDefName[PARA_LENGTH];              //Default interface name
    char acIfPropName[PARA_LENGTH];             //Interface property name
    char acIfDir[PARA_LENGTH];                  //Supplicant path
    char acSuppName[PARA_LENGTH];               //Supplicant name
    char acSuppPropName[PARA_LENGTH];           //Supplicant property name
    char acSuppConfigTemplate[PARA_LENGTH];     //Supplicant config template file path
    char acSuppConfigFile[PARA_LENGTH];         //Supplicant config file path
    char acSuppDeamonCmd[PROPERTY_VALUE_MAX];   //Supplicant deamon start command
    char acSuppCtrlPrefix[PARA_LENGTH];         //Ctrl socket prefix
} SUPPLICANT_PARA_T, *P_SUPPLICANT_PARA_T; 

还有这个:

    static SUPPLICANT_INFO_T rHotspotSuppInfo = {
    "ap0",
    NULL,
    NULL,
#ifdef CFG_SUPPORT_CONCURRENT_NETWORK
    {
        p2p_ctrl_open,
        p2p_ctrl_close,
        p2p_ctrl_request,
        p2p_ctrl_recv,
        p2p_ctrl_attach
    },
#else
    {   //Supplicant ctrl API
        wpa_ctrl_open,
        wpa_ctrl_close,
        wpa_ctrl_request,
        wpa_ctrl_recv,
        wpa_ctrl_attach
    },
#endif

    {                           //exit socket pair
        -1,
        -1
    }
};
typedef struct _SUPPLICANT_INFO_T {
    char acIfName[PROPERTY_VALUE_MAX];          //Interface name for using
    P_WPA_CTRL_T prCtrlConn;                    //Supplicant ctrl connection
    P_WPA_CTRL_T prMonitorConn;                 //Supplicant monitor connection
    SUPPLICANT_CTRL_T rSuppCtrl;                //Supplicant ctrl api
    int aucExitSockets[2];                      /* socket pair used to exit from a blocking read */
} SUPPLICANT_INFO_T, *P_SUPPLICANT_INFO_T;
    

最终是启动了一个wpa_supplicant进程。SoftApController会在启动之后等待一段时间,直到wpa_supplicant的socket可以连上了。检查wpa_supplicant连接的代码有:

int connect_to_supplicant(const struct _SUPPLICANT_PARA_T *prTar, P_SUPPLICANT_INFO_T prTarInfo)
{
    char ifname[256];
    char supp_status[PROPERTY_VALUE_MAX] = {'\0'};

    /* Make sure supplicant is running */
    if (!property_get(prTar->acSuppPropName, supp_status, NULL)
            || strcmp(supp_status, "running") != 0) {
        LOGE("[%s] %s not running, cannot connect", prTarInfo->acIfName, prTar->acSuppName);
        return -1;
    }

    LOGI("[%s] Connect to %s", prTarInfo->acIfName, prTar->acSuppName);

    snprintf(ifname, sizeof(ifname), "%s/%s", prTar->acIfDir, prTarInfo->acIfName);
    LOGD("[%s] ctrl_open %s", prTarInfo->acIfName, ifname);
    {
        int count = CONNECT_TO_SUPP_POLLING_LOOP;
        while (count-- > 0) {
            prTarInfo->prCtrlConn = prTarInfo->rSuppCtrl.ctrl_open(ifname);
            if (prTarInfo->prCtrlConn) {
                LOGD("[%s] Open control connection to %s successfully\n", prTarInfo->acIfName, ifname);
                break;
            }
            LOGD("[%s] Open control connection fail (%s). Sleep %dus\n", prTarInfo->acIfName, ifname, CONNECT_TO_SUPP_DELAY);
            sched_yield();
            usleep(CONNECT_TO_SUPP_DELAY);
        }
    }

    if (prTarInfo->prCtrlConn == NULL) {
        LOGE("[%s] Unable to open connection to supplicant on \"%s\": %s"
            , prTarInfo->acIfName
            , ifname
            , strerror(errno));
        return -1;
    }
    prTarInfo->prMonitorConn = prTarInfo->rSuppCtrl.ctrl_open(ifname);
    if (prTarInfo->prMonitorConn == NULL) {
        prTarInfo->rSuppCtrl.ctrl_close(prTarInfo->prCtrlConn);
        prTarInfo->prCtrlConn = NULL;
        return -1;
    }
    if (prTarInfo->rSuppCtrl.ctrl_attach(prTarInfo->prMonitorConn) != 0) {
        prTarInfo->rSuppCtrl.ctrl_close(prTarInfo->prMonitorConn);
        prTarInfo->rSuppCtrl.ctrl_close(prTarInfo->prCtrlConn);
        prTarInfo->prCtrlConn = prTarInfo->prMonitorConn = NULL;
        return -1;
    }
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, prTarInfo->aucExitSockets) == -1) {
        prTarInfo->rSuppCtrl.ctrl_close(prTarInfo->prMonitorConn);
        prTarInfo->rSuppCtrl.ctrl_close(prTarInfo->prCtrlConn);
        prTarInfo->prCtrlConn = prTarInfo->prMonitorConn = NULL;
        return -1;
    }

    LOGD("[%s] Connect_to_supplicant %s successfully.\n", prTarInfo->acIfName, ifname);

    return 0;
}

最终还是要调用wpa_ctrl_open或者p2p_ctrl_open的。这两个函数只有so文件,没有源代码:libwpa_client.so和libp2p_client.so。

再回过头去看setSoftAp的实现:

/*
 * Arguments:
 *      argv[2] - wlan interface
 *      argv[3] - softap interface
 *      argv[4] - SSID
 *    argv[5] - Security
 *	argv[6] - Key
 *	argv[7] - Channel
 *	argv[8] - Preamble
 *	argv[9] - Max SCB
 */
int SoftapController::setSoftap(int argc, char *argv[]) {
    char psk_str[2*SHA256_DIGEST_LENGTH+1];
    int ret = 0, i = 0, fd;
    char *ssid, *iface;

    if (mSock < 0) {
        LOGE("Softap set - failed to open socket");
        return -1;
    }
    if (argc < 4) {
        LOGE("Softap set - missing arguments");
        return -1;
    }

    strncpy(mIface, argv[3], sizeof(mIface));
    iface = argv[2];
    
    /* Create command line */
    i = addParam(i, "ASCII_CMD", "AP_CFG");
    if (argc > 4) {
        ssid = argv[4];
    } else {
        ssid = (char *)"AndroidAP";
    }
    i = addParam(i, "SSID", ssid);
    if (argc > 5) {
        i = addParam(i, "SEC", argv[5]);
    } else {
        i = addParam(i, "SEC", "open");
    }
    if (argc > 6) {
        generatePsk(ssid, argv[6], psk_str);
        i = addParam(i, "KEY", psk_str);
    } else {
        i = addParam(i, "KEY", "12345678");
    }
    if (argc > 7) {
        i = addParam(i, "CHANNEL", argv[7]);
    } else {
        i = addParam(i, "CHANNEL", "6");
    }
    if (argc > 8) {
        i = addParam(i, "PREAMBLE", argv[8]);
    } else {
        i = addParam(i, "PREAMBLE", "0");
    }
    if (argc > 9) {
        i = addParam(i, "MAX_SCB", argv[9]);
    } else {
        i = addParam(i, "MAX_SCB", "8");
    }
    if ((i < 0) || ((unsigned)(i + 4) >= sizeof(mBuf))) {
        LOGE("Softap set - command is too big");
        return i;
    }
    sprintf(&mBuf[i], "END");

    /* system("iwpriv eth0 WL_AP_CFG ASCII_CMD=AP_CFG,SSID=\"AndroidAP\",SEC=\"open\",KEY=12345,CHANNEL=1,PREAMBLE=0,MAX_SCB=8,END"); */
    ret = setCommand(iface, "AP_SET_CFG");
    if (ret) {
        LOGE("Softap set - failed: %d", ret);
    }
    else {
        LOGD("Softap set - Ok");
        usleep(AP_SET_CFG_DELAY);
    }
    return ret;
}

什么是setCommand?这个非常有意思了:

int SoftapController::setCommand(char *iface, const char *fname, unsigned buflen) {
#ifdef HAVE_HOSTAPD
    return 0;
#else
    char tBuf[SOFTAP_MAX_BUFFER_SIZE];
    struct iwreq wrq;
    struct iw_priv_args *priv_ptr;
    int i, j, ret;
    int cmd = 0, sub_cmd = 0;

    strncpy(wrq.ifr_name, iface, sizeof(wrq.ifr_name));
    wrq.u.data.pointer = tBuf;
    wrq.u.data.length = sizeof(tBuf) / sizeof(struct iw_priv_args);
    wrq.u.data.flags = 0;
    if ((ret = ioctl(mSock, SIOCGIWPRIV, &wrq)) < 0) {
        LOGE("SIOCGIPRIV failed: %d", ret);
        return ret;
    }

    priv_ptr = (struct iw_priv_args *)wrq.u.data.pointer;
    for(i=0; i < wrq.u.data.length;i++) {
        if (strcmp(priv_ptr[i].name, fname) == 0) {
            cmd = priv_ptr[i].cmd;
            break;
        }
    }

    if (i == wrq.u.data.length) {
        LOGE("iface:%s, fname: %s - function not supported", iface, fname);
        return -1;
    }

    if (cmd < SIOCDEVPRIVATE) {
        for(j=0; j < i; j++) {
            if ((priv_ptr[j].set_args == priv_ptr[i].set_args) &&
                (priv_ptr[j].get_args == priv_ptr[i].get_args) &&
                (priv_ptr[j].name[0] == '\0'))
                break;
        }
        if (j == i) {
            LOGE("iface:%s, fname: %s - invalid private ioctl", iface, fname);
            return -1;
        }
        sub_cmd = cmd;
        cmd = priv_ptr[j].cmd;
    }

    strncpy(wrq.ifr_name, iface, sizeof(wrq.ifr_name));
    if ((buflen == 0) && (*mBuf != 0))
        wrq.u.data.length = strlen(mBuf) + 1;
    else
        wrq.u.data.length = buflen;
    wrq.u.data.pointer = mBuf;
    wrq.u.data.flags = sub_cmd;
    ret = ioctl(mSock, cmd, &wrq);
    return ret;
#endif
}

其实就是一个对ioctl的封装,标准的wireless extension(wext)的private command实现方式。

再来看startSoftAp:

int SoftapController::startSoftap() {
    pid_t pid = 1;
    int ret = 0;

    if (mPid) {
        LOGE("Softap already started");
        return 0;
    }
    if (mSock < 0) {
        LOGE("Softap startap - failed to open socket");
        return -1;
    }
    if (!pid) {
        LOGE("Should never get here!");
        return -1;
    } else {
        *mBuf = 0;
        ret = setCommand(mIface, "AP_BSS_START");
        if (ret) {
            LOGE("Softap startap - failed: %d", ret);
        }
        else {
           mPid = pid;
           LOGD("Softap startap - Ok");
           usleep(AP_BSS_START_DELAY);
        }
    }
    return ret;

}

同样是一个private command发给驱动,至此启动热点过程结束。

驱动没有源代码就没有办法分析下去了。我们可以断定的是驱动内部实现了一个类似hostapd的东西,处理了AP模式。


原文转自:http://fqrouter.tumblr.com/post/44145769603/mtk

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值