1. 程式人生 > >Android WiFi 架構總覽(模組及介面)

Android WiFi 架構總覽(模組及介面)

Android WiFi 架構總覽

本文介紹Android原始碼專案(AOSP)中WiFi功能的軟體架構及各個模組(可執行檔案、動態連結庫)間的介面。

SDK API

Android SDK為開發者提供了WiFi程式設計介面,使用起來非常方便。

相關包:
android.net.wifi(寫App時只需import該包,即可使用WiFi相關功能)

主要相關類:
* WifiManager WIFI程式設計入口,WIFI的多數功能都以該類的方法的形式提供
* WifiInfo 用於描述WIFI連線的狀態
* ScanResult 用於描述一個AP,如SSID,訊號強度,安全方式等

Overview

下圖基展示了Android系統WIFI模組的架構(當然,這只是軟體的控制命令部分,資料部分直接通過kernel與網路子系統、socket API互動)。
Android WiFi 相關編譯模組
(PS:一圖勝千言,雖然用ppt畫起來費勁)
WifiManager是管理所有Wifi連線的基本API,可以通過:
android.content.Context.getSystemService(Context.WIFI_SERVICE)
得到它的例項。

具體 IPC(Inter-Process communication)

App & system_server(WifiManager & WifiService)

如果說Binder是連線 App 和 system_server 的橋樑,那麼WifiManager和WiFiService就是橋樑的兩頭。

framework程式碼上和wifi相關的package位於:
frameworks/base/wifi/java(WIFI相關的一些包)
frameworks/base/services/java(各種Service,WIFI相關包為:com.android.server.wifi)

frameworks程式碼中,和wifi相關的幾個類的關係如下:

  • WifiService繼承自IWifiManager.Stub;
  • IWifiManager.Stub又繼承自Binder,同時實現了IWifiManager介面;
  • WifiManager.Stu.proxy也實現了IWifiManager介面;

如圖:
WiFi相關的class

其中,IWifiManager, IWifiManager.Stub, IWifiManager.Stub.Proxy都由IWifiManger.aidl生成;
aidl自動生成相關的java程式碼,簡化了用Binder實現RPC的過程。
IWifiManager.Stub.Proxy,WifiManager,BinberProxy用於客戶端(App程序);
而IWifiManager.Stub,WifiService,Binder用於服務端(SystemServer程序)。

App 與 system_server 通過Binder通訊,但Binder本身只實現了IPC,即類似socket通訊的能力。而App端的WifiManager和system_server端的WifiService及Binder等類共同實現了RPC(remote procedure call)。

WifiManager只是系統為app提供的介面。Context.getSystemService(Context.WIFI_SERVICE)
返回的實際物件型別是IWifiManager.Stub.Proxy
IWifiManager.Stub.Proxy的例項是位於App端的一個代理,代理象IWifiManager.Stub.Proxy
將WifiManager方法的引數序列化到Parcel,再經Binder傳送給system_server程序。

system_server內的WifiService收App傳來的WifiManager呼叫,完成實際工作。
這樣,實際和下層通訊的工作就由App轉移到了system_server程序(WifiService物件)。

WifiStateMachine

另外,可以看到 WifiStateMachine 是wifi功能的樞紐,幾種不同模式下的控制流程都經它流下。
當WIFI處在STA模式(或P2P模式)時,通過WifiNative與wpa_supplicant互動。
WifiNative定義了幾個Native方法:

    public native static boolean setMaxTxPower(int txpower, boolean sapRunning);

    public native static boolean loadDriver();

    public native static boolean isDriverLoaded();

    public native static boolean unloadDriver();

    public native static boolean startSupplicant(boolean p2pSupported, int firstScanDelay);

    /* Sends a kill signal to supplicant. To be used when we have lost connection
       or when the supplicant is hung */
    public native static boolean killSupplicant(boolean p2pSupported);

    private native boolean connectToSupplicantNative();

    private native void closeSupplicantConnectionNative();

    /**
     * Wait for the supplicant to send an event, returning the event string.
     * @return the event string sent by the supplicant.
     */
    private native String waitForEventNative();

    private native boolean doBooleanCommandNative(String command);

    private native int doIntCommandNative(String command);

    private native String doStringCommandNative(String command);

當WIFI處在AP模式。通過NetworkManagementService與netd互動,具體是通過LocalSocket(Framework封裝的UNIX域socket)與netd程序通訊。
比如,NetworkManagementService.startAccessPoint方法:

    @Override
    public void startAccessPoint(
            WifiConfiguration wifiConfig, String wlanIface) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        try {
            wifiFirmwareReload(wlanIface, "AP");
            if (wifiConfig == null) {
                mConnector.execute("softap", "set", wlanIface); // 向 netd 傳送控制命令
            } else {
                mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
                                   wifiConfig.hiddenSSID ? "hidden" : "broadcast",
                                   "1", getSecurityType(wifiConfig),
                                   new SensitiveArg(wifiConfig.preSharedKey));
            }
            mConnector.execute("softap", "startap");
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    }

WifiNative

從功能上來說,WifiNative是system_server 和 wpa_supplicant 對話的視窗,實際上主要依靠wpa_supplicant專案編譯出來的動態庫libwpa_client.so。

WifiNative的幾個native方法的具體實現在:
frameworks/base/core/jni/android_net_wifi_WifiNative.cpp

WifiNative的native方法的實現:

static JNINativeMethod gWifiMethods[] = {
    /* name, signature, funcPtr */
    { "loadDriver", "()Z",  (void *)android_net_wifi_loadDriver },
    { "isDriverLoaded", "()Z",  (void *)android_net_wifi_isDriverLoaded },
    { "unloadDriver", "()Z",  (void *)android_net_wifi_unloadDriver },
    { "startSupplicant", "(ZI)Z",  (void *)android_net_wifi_startSupplicant },
    { "killSupplicant", "(Z)Z",  (void *)android_net_wifi_killSupplicant },
    { "connectToSupplicantNative", "()Z", (void *)android_net_wifi_connectToSupplicant },
    { "closeSupplicantConnectionNative", "()V", (void *)android_net_wifi_closeSupplicantConnection },
    { "waitForEventNative", "()Ljava/lang/String;", (void*)android_net_wifi_waitForEvent },
    { "doBooleanCommandNative", "(Ljava/lang/String;)Z", (void*)android_net_wifi_doBooleanCommand },
    { "doIntCommandNative", "(Ljava/lang/String;)I", (void*)android_net_wifi_doIntCommand },
    { "doStringCommandNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*) android_net_wifi_doStringCommand },
    { "setMaxTxPower", "(IZ)Z",  (void *)android_net_wifi_setMaxTxPower },
};

android_net_wifi_WifiNative.cpp 並沒有做多少實際工作,大多是直接呼叫 wifi.h wifi_maxtxpower.h 定義的函式:
wifi.h 的具體實現在 hardware/libhardware_legacy/wifi/wifi.c (會被編譯為 libhardware_legacy.so)
wifi_maxtxpower.h 的具體實現在 hardware/qcom/wlan/libmaxtxpower/wifi_maxtxpower.c (會被編譯為 libmaxtxpower.so)
所以native程式碼依賴libhardware_legacy.so模組、libmaxtxpower.so模組。

WIFI HAL

實現SystemServer與wpa_supplicant(hostapd)通訊的,即Wifi HAL
Wifi HAL封裝了UNIX域socket,SystemServer通過UNIX域socket與wpa_supplicant(hostapd)
通訊;SystemServer傳送的訊息和wpa_supplicant響應的訊息都是為ASCII字串。

Wifi HAL程式碼主要分佈在:
hardware/libhardware_legacy/wifi
hardware/qcom/wlan/libmaxtxpower

分別被編譯為:
hardware/libhardware_legacy/wifi -> libhardware_legacy.so
hardware/qcom/wlan/libmaxtxpower -> libmaxtxpower.so

wifi.h定義了WIFI HAL的介面,具體函式有:

// 驅動相關:
int wifi_load_driver();
int wifi_unload_driver();
int is_wifi_driver_loaded();

// supplicant相關:
int wifi_start_supplicant(int p2pSupported, int first_scan_delay);
int wifi_stop_supplicant(int p2pSupported);
int wifi_connect_to_supplicant();
void wifi_close_supplicant_connection();

// 等待WIFI事發生,該函式會阻塞當前呼叫,直到有wifi事件發生時,返回一個表示wifi事件的字元
int wifi_wait_for_event(char *buf, size_t len);

// 向wifi驅動發一個命,(多數功能經該函式向下層發命令)
int wifi_command(const char *command, char *reply, size_t *reply_len);

// 發起一dhcp請求
int do_dhcp_request(int *ipaddr, int *gateway, int *mask,
                   int *dns1, int *dns2, int *server, int *lease);

// 返回一個do_dhcp_request()的錯誤字串
const char *get_dhcp_error_string();

#define WIFI_GET_FW_PATH_STA    0
#define WIFI_GET_FW_PATH_AP     1
#define WIFI_GET_FW_PATH_P2P    2

// 返一個請的firmware路徑
const char *wifi_get_fw_path(int fw_type);

// 為wlan驅動改變firmware路徑
int wifi_change_fw_path(const char *fwpath);

#define WIFI_ENTROPY_FILE   "/data/misc/wifi/entropy.bin"

int ensure_entropy_file_exists();

wifi_maxtxpower.h 只定義了一個函式:

int set_max_tx_power(int power, int sap_running);

android_net_wifi_WifiNative.cpp 呼叫了這些函式。
wifi.c呼叫了wpa_ctrl.h定義的一些函式,而wpa_ctrl.h中的函式
在external/wpa_supplicant_8 專案實現,並被編譯為libwpa_client.so,
詳見external/wpa_supplicant_8/Android.mk。

wpa_supplicant(hostapd)

程式碼位於:
external/wpa_supplicant_8
該專案內包含兩個互相相關的開源專案wpa_supplicant和hostapd,它們將會生成兩個可執行檔案:
wpa_supplicant和hostapd,分別為STA模式和AP模式時的守護程序。

除此之外,還會生成用於測試的wpa_cli,hostapd_cli,
以及WIFI HAL依賴的wpa_client.so,具體可以到Android.mk中找到。

wpa_supplicant原始碼結構和hostapd類似,下面只介紹wpa_supplicant。

wpa_ctrl.h 定義了與wpa_supplicant(或hostapd)程序通訊的介面:

struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path);

// Close a control interface to wpa_supplicant/hostapd
void wpa_ctrl_close(struct wpa_ctrl *ctrl);

// Send a command to wpa_supplicant/hostapd
int wpa_ctrl_request(struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len,
             char *reply, size_t *reply_len,
             void (*msg_cb)(char *msg, size_t len));

// Register as an event monitor for the control interface
int wpa_ctrl_attach(struct wpa_ctrl *ctrl);

// Unregister event monitor from the control interface
int wpa_ctrl_detach(struct wpa_ctrl *ctrl);

// Receive a pending control interface message
int wpa_ctrl_recv(struct wpa_ctrl *ctrl, char *reply, size_t *reply_len);

// Check whether there are pending event messages
int wpa_ctrl_pending(struct wpa_ctrl *ctrl);

// Get file descriptor used by the control interface
int wpa_ctrl_get_fd(struct wpa_ctrl *ctrl);

char * wpa_ctrl_get_remote_ifname(struct wpa_ctrl *ctrl);

wpa_ctrl_open 建立一個UNIX域socket 與 wpa_supplicant(或hostapd)程序相連,
wpa_ctrl_close 用於關閉wpa_ctrl_open建立的連線,
wpa_ctrl_request 用於向wpa_supplicant/hostapd傳送控制命令,並阻塞,
直到wpa_supplicant/hostapd返回命令響應。控制命令和相應都是ASCII字串。

wpa_ctrl.h除了聲明瞭這些函式外,還定義了wpa_supplicant/hostapd的一些訊息,這裡沒有詳細列出。

原始碼中wpa_supplicant_global_ctrl_iface_receive
負責分派上層發來的控制命令,進而呼叫具體處理函式:

 "ATTACH" -> wpa_supplicant_ctrl_iface_attach
 "DETACH" -> wpa_supplicant_ctrl_iface_detach
  else -> wpa_supplicant_global_ctrl_iface_process:
    "IFNAME="(prefix) // 多數控制命令以IFNAME=開頭
        -> wpas_global_ctrl_iface_ifname
            -> wpa_supplicant_ctrl_iface_process # "IFNAME="開頭的命令的處理
    wpas_global_ctrl_iface_redir 
        -> wpas_global_ctrl_iface_redir_p2p
            -> wpa_supplicant_ctrl_iface_process # "IFNAME="開頭的命令的處理
        -> wpas_global_ctrl_iface_redir_wfd
            -> wpa_supplicant_ctrl_iface_process # "IFNAME="開頭的命令的處理
    "PING" -> "PONG"
    "INTERFACE_ADD" -> wpa_supplicant_global_iface_add
    "INTERFACE_REMOVE" -> wpa_supplicant_global_iface_remove
    "INTERFACE_LIST" -> wpa_supplicant_global_iface_list
    "INTERFACES" -> wpa_supplicant_global_iface_interfaces
    "TERMINATE" -> wpa_supplicant_terminate_proc
    "SUSPEND" -> wpas_notify_suspend
    "RESUME" -> wpas_notify_resume
    "SET" -> wpas_global_ctrl_iface_set
    "SAVE_CONFIG" -> wpas_global_ctrl_iface_save_config
    "STATUS" -> wpas_global_ctrl_iface_status

該函式在wpa_supplicant目錄下的
ctrl_iface_unix.cctrl_iface_udp.c內都有實現,可由該目錄下的android.config切換
(android.config被Android.mk包含,具體參見Android.mk)

wpa_supplicant與核心通訊

wpa_supplicant的執行模型是單程序單執行緒的 Reactor(IO multiplexing)。wpa_supplicant通過NETLINK socket與核心通訊。

wpa_supplicant專案支援多種驅動程式設計介面,在Android上使用的是nl80211;

nl80211是新的802.11netlink介面公共頭,與cfg80211一同組成了Wireless-Extensions的替代方案。
cfg80211是Linux 802.11配置API, nl80211用於配置cfg80211裝置,同時用於核心到使用者空間的通訊。

實際使用nl80211時,只需要在程式中包含標頭檔案

wireless module(in kernel)

程式碼位於:
kernel/net/wireless

nl80211.c 中的 nl80211_init 使用genl_register_family_with_ops 註冊了響應應用程式的
struct genl_ops nl80211_ops[], 該陣列定義了響應NETLINK訊息的函式。

而 nl80211_init 在 cfg80211_init 內被呼叫,cfg80211_init是被subsys_initcall註冊的
子系統初始化程式,被編譯為cfg80211.ko。

nl80211_ops[] 節選:

static struct genl_ops nl80211_ops[] = {
    // ...
    {
        .cmd = NL80211_CMD_TRIGGER_SCAN,
        .doit = nl80211_trigger_scan,
        .policy = nl80211_policy,
        .flags = GENL_ADMIN_PERM,
        .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
                  NL80211_FLAG_NEED_RTNL,
    },
    {
        .cmd = NL80211_CMD_GET_SCAN,
        .policy = nl80211_policy,
        .dumpit = nl80211_dump_scan,
    },
    {
        .cmd = NL80211_CMD_START_SCHED_SCAN,
        .doit = nl80211_start_sched_scan,
        .policy = nl80211_policy,
        .flags = GENL_ADMIN_PERM,
        .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
                  NL80211_FLAG_NEED_RTNL,
    },
    {
        .cmd = NL80211_CMD_STOP_SCHED_SCAN,
        .doit = nl80211_stop_sched_scan,
        .policy = nl80211_policy,
        .flags = GENL_ADMIN_PERM,
        .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
                  NL80211_FLAG_NEED_RTNL,
    },

    // ...
};

wlan driver

程式碼位於:
vendor/qcom/opensource/wlan/prima

模組初始化(module_init),模組退出(module_exit):
CORE/HDD/src/wlan_hdd_main.c

模型:
多執行緒 + 佇列

建立核心執行緒:
CORE/VOSS/src/vos_sched.c 的 vos_sched_open()

執行緒任務(vos_sched.c):
* VosMcThread() - The VOSS Main Controller thread
* VosWdThread() - The VOSS Watchdog thread
* VosTXThread() - The VOSS Main Tx thread
* VosRXThread() - The VOSS Main Rx thread

執行緒環境(context) (vos_sched.h):

typedef struct _VosSchedContext
{
  /* Place holder to the VOSS Context */ 
   v_PVOID_t           pVContext; 

  /* WDA Message queue on the Main thread*/
   VosMqType           wdaMcMq;

   /* PE Message queue on the Main thread*/
   VosMqType           peMcMq;

   /* SME Message queue on the Main thread*/
   VosMqType           smeMcMq;

   /* TL Message queue on the Main thread */
   VosMqType           tlMcMq;

   /* SYS Message queue on the Main thread */
   VosMqType           sysMcMq;

  /* WDI Message queue on the Main thread*/
   VosMqType           wdiMcMq;

   /* WDI Message queue on the Tx Thread*/
   VosMqType           wdiTxMq;

   /* WDI Message queue on the Rx Thread*/
   VosMqType           wdiRxMq;

   /* TL Message queue on the Tx thread */
   VosMqType           tlTxMq;

   /* TL Message queue on the Rx thread */
   VosMqType           tlRxMq;

   /* SYS Message queue on the Tx thread */
   VosMqType           sysTxMq;

   VosMqType           sysRxMq;

    // ...

   struct task_struct* McThread;

   /* TX Thread handle */

   struct task_struct*   TxThread;

   /* RX Thread handle */
   struct task_struct*   RxThread;

    // ...
} VosSchedContext, *pVosSchedContext;

高通資料:
80-Y0513-1_G_QCA_WCN36x0_Software_Architecture.pdf
chapter: Android WLAN Host Software Architecture

SCAN過程跟蹤

App

WifiManager wifiManager = (WifiManager)Context.getService(Contex.WIFI_SERVICE);
wifiManager.startScan();

//wifiManager --> IWifiManager.Stub.Proxy

IWifiManager.Stub.Proxy implements android.net.wifi.IWifiManager

wifiManager.startScan() 
-> IWifiManager.Stub.Proxy.startScan(WorkSource=null);
-> BinderProxy.transact(Stub.TRANSACTION_startScan, _data, _reply, 0);

systerm_server

wifi –> WifiService

WifiService extends IWifiManager.Stub

IWifiManager.Stub extends android.os.Binder 
    implements android.net.wifi.IWifiManager

-> IWifiManager.Stub.onTransact(int code, Parcel data, Parcel reply, int flags);
    case TRANSACTION_startScan:
-> WifiService.startScan(WorkSource workSource);
-> WifiStateMachine.startScan(int callingUid, WorkSource workSource);
-> StateMachine.sendMessage(CMD_START_SCAN, callingUid, 0, workSource);
    case CMD_START_SCAN:
->  handleScanRequest(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, message);
->  startScanNative(type, freqs)
-> WifiNative.scan(type, freqs)
->  doBooleanCommand("SCAN ..."); // AF_UNIX socket, send to wpa_supplicant

wpa_supplicant

external/wpa_supplicant_8/wpa_supplicant$ grep -nr "\"SCAN " .
./ChangeLog:197:      - "SCAN freq=<freq list>" can be used to specify which channels are
./ChangeLog:199:      - "SCAN passive=1" can be used to request a passive scan (no Probe
./ChangeLog:201:      - "SCAN use_id" can be used to request a scan id to be returned and
./ChangeLog:203:      - "SCAN only_new=1" can be used to request the driver/cfg80211 to
./ctrl_iface.c:6986:    } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
./src/drivers/driver_test.c:1289:   ret = os_snprintf(pos, end - pos, "SCAN " MACSTR,
./src/drivers/driver_test.c:1994:   } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
./ctrl_iface.c:6986:    } else if (os_strncmp(buf, "SCAN ", 5) == 0) {

refer to ./ctrl_iface.c:6986

    } else if (os_strcmp(buf, "SCAN") == 0) {
        wpas_ctrl_scan(wpa_s, NULL, reply, reply_size, &reply_len);
    } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
        wpas_ctrl_scan(wpa_s, buf + 5, reply, reply_size, &reply_len);

wpas_ctrl_scan -> wpa_supplicant_req_scan

    int res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,
                    NULL);
    if (res == 1) {
        wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d.%06d sec",
            sec, usec);
    }

wpa_supplicant_scan -> wpa_supplicant_trigger_scan

-> radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx)

wpas_trigger_scan_cb -> wpa_drv_scan

static inline int wpa_drv_scan(struct wpa_supplicant *wpa_s,
                   struct wpa_driver_scan_params *params)
{
    if (wpa_s->driver->scan2) // callback
        return wpa_s->driver->scan2(wpa_s->drv_priv, params);
    return -1;
}

grep scan2 callback

external/wpa_supplicant_8/wpa_supplicant$ grep -nr "scan2\s*=" .
./src/drivers/driver_wext.c:2401:   .scan2 = wpa_driver_wext_scan,
./src/drivers/driver_privsep.c:726: .scan2 = wpa_driver_privsep_scan,
./src/drivers/driver_test.c:2677:   .scan2 = wpa_driver_test_scan,
./src/drivers/driver_bsd.c:1618:    .scan2          = wpa_driver_bsd_scan,
./src/drivers/driver_nl80211.c:12612:   .scan2 = driver_nl80211_scan2,
./src/drivers/driver_ndis.c:3217:   wpa_driver_ndis_ops.scan2 = wpa_driver_ndis_scan;

refer to ./src/drivers/driver_nl80211.c:12612

driver_nl80211_scan2 -> wpa_driver_nl80211_scan

    msg = nl80211_scan_common(drv, NL80211_CMD_TRIGGER_SCAN, params,
                  bss->wdev_id_set ? &bss->wdev_id : NULL);
    if (!msg)
        return -1;

use NL80211_CMD_TRIGGER_SCAN talk with kernel(cfg80211.ko)

kernel

grep NL80211_CMD_TRIGGER_SCAN in kernel source:

kernel$ cgrep NL80211_CMD_TRIGGER_SCAN
./net/wireless/nl80211.c:9053:      .cmd = NL80211_CMD_TRIGGER_SCAN,
./net/wireless/nl80211.c:9605:                NL80211_CMD_TRIGGER_SCAN) < 0) {
./include/uapi/linux/nl80211.h:255: *   option to specify additional IEs in NL80211_CMD_TRIGGER_SCAN,
./include/uapi/linux/nl80211.h:260: * @NL80211_CMD_TRIGGER_SCAN: trigger a new scan with the given parameters
./include/uapi/linux/nl80211.h:759: NL80211_CMD_TRIGGER_SCAN,
./include/uapi/linux/nl80211.h:1362: *  This attribute is used with %NL80211_CMD_TRIGGER_SCAN and
./include/uapi/linux/nl80211.h:3863: * of NL80211_CMD_TRIGGER_SCAN and NL80211_CMD_START_SCHED_SCAN

refer to net/wireless/nl80211.c:9053

static struct genl_ops nl80211_ops[] = {

// ... ...

    {
        .cmd = NL80211_CMD_TRIGGER_SCAN,
        .doit = nl80211_trigger_scan,
        .policy = nl80211_policy,
        .flags = GENL_ADMIN_PERM,
        .internal_flags = NL80211_FLAG_NEED_WDEV_UP |
                  NL80211_FLAG_NEED_RTNL,
    },

// ... ...
};

nl80211_trigger_scan -> rdev_scan(rdev, request);

static inline int rdev_scan(struct cfg80211_registered_device *rdev,
                struct cfg80211_scan_request *request)
{
    int ret;
    trace_rdev_scan(&rdev->wiphy, request);
    ret = rdev->ops->scan(&rdev->wiphy, request); // callback
    trace_rdev_return_int(&rdev->wiphy, ret);
    return ret;
}

driver

參考高通文件