精华内容
下载资源
问答
  • 介绍am33xx+android平台,bcm4334模块的驱动框架,收悉wifi驱动工作流程
  • WiFi驱动(1)框架解析

    千次阅读 2017-02-11 16:00:14
    本文从硬件结构到软件实现探究LinuxWiFi驱动框架,WiFi模块采用SDIO接口进行通信。
    Linux:3.10
    WiFi芯片:RTL8723
    接口:SDIO

    本文从硬件结构到软件实现探究Linux中WiFi驱动的框架。如下图:


    硬件角度:

    • CPU
    • WiFi芯片,以rtl8723为例
    • 总线SDIO
    软件角度:
    • 电源、GPIO:负责WiFi模组的电源管理、IO管理
    • SDIO:数据通道
    • WiFi驱动:负责WiFi规范实现
    • Sysfs:/sys/文件系统中提供访问驱动接口


    Linux有非常好的模块化机制,所以这几部分作为各自独立的模块进行注册,下面从代码示例的方式看下。

    一、电源、GPIO管理模块

    该模块完成CPU对WiFi模组电源、引脚的初始化、控制等功能。由于Linux采用设备树(Device Tree)方式管理硬件设置,所以第一步就是解析dts文件中的设置项并进行赋值、初始化操作,如:

    static struct of_device_id wlan_platdata_of_match[] = {
        { .compatible = "wlan-platdata" },
        { }
    };
    MODULE_DEVICE_TABLE(of, wlan_platdata_of_match);
    
    static int wlan_platdata_parse_dt(struct device *dev, struct wifi_moudle *data)
    {
    	struct device_node *node = dev->of_node;
        ret = of_property_read_string(node, "wifi_chip_type", &strings);
    	ret = of_property_read_u32(node, "sdio_vref", &value);
        of_find_property(node, "keep_wifi_power_on", NULL)
        of_find_property(node, "vref_ctrl_enable", NULL)
    	of_find_property(node, "power_ctrl_by_pmu", NULL);
    	of_get_named_gpio_flags(node, "WIFI,poweren_gpio", 0, &flags);
        of_get_named_gpio_flags(node, "WIFI,reset_gpio", 0, &flags);
        of_get_named_gpio_flags(node, "WIFI,host_wake_irq", 0, &flags);
    
        return 0;
    }


    驱动以平台驱动的方式进行注册:

    static struct platform_driver wlan_driver = {
    	.probe = wlan_probe,
    	.remove = wlan_remove,
        .suspend = wlan_suspend,
        .resume = wlan_resume,
    	.driver = {
    		.name = "wlan-platdata",
    		.owner = THIS_MODULE,
            .of_match_table = of_match_ptr(wlan_platdata_of_match),
    	},
    };
    
    static int wlan_probe(struct platform_device *pdev)
    {
    	......
    	wlan_platdata_parse_dt(&pdev->dev, pdata);
    	......
    }
    
    static int __init wlan_init(void)
    {
        LOG("Enter %s\n", __func__);
    	return platform_driver_register(&wlan_driver);
    }
    
    module_init(wlan_init);
    module_exit(wlan_exit);

    二、SDIO Host端

    CPU集成有SDIO控制器,所以Host端就是对CPU上SDIO控制器的驱动实现,然后把函数指针通过mmc_add_host()赋予Core层。这部分代码存在于drivers\mmc\host\目录。操作接口比如:

    static const struct mmc_host_ops dw_mci_ops = {
    	.request		= dw_mci_request,
    	.pre_req		= dw_mci_pre_req,
    	.post_req		= dw_mci_post_req,
    	.set_ios		= dw_mci_set_ios,
    	.get_ro			= dw_mci_get_ro,
    	.get_cd			= dw_mci_get_cd,
    	.set_sdio_status	= dw_mci_set_sdio_status,
    	.hw_reset		= dw_mci_hw_reset,
    	.enable_sdio_irq	= dw_mci_enable_sdio_irq,
    	.execute_tuning		= dw_mci_execute_tuning,
            .post_tmo		= dw_mci_post_tmo,
    	#ifdef CONFIG_MMC_SWITCH_VOLTAGE
            .start_signal_voltage_switch
    				= dw_mci_start_signal_voltage_switch,
            .card_busy		= dw_mci_card_busy,
            #endif
    };


    添加Host:

    struct mmc_host *mmc;
    mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev);
    
    mmc->ops = &dw_mci_ops;
    mmc_add_host(mmc);

    三、SDIO Client端

    WiFi模组自身集成有SDIO控制器,所以这部分完成WiFi模组上SDIO控制器的驱动实现。针对RTL8723,SDIO Client的代码实现在其驱动源码里。如下:

    rtl8723:
    static struct sdio_drv_priv sdio_drvpriv = {
    	.r871xs_drv.probe = rtw_drv_init,
    	.r871xs_drv.remove = rtw_dev_remove,
    	.r871xs_drv.name = (char*)DRV_NAME,
    	.r871xs_drv.id_table = sdio_ids,
    	#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) 
    	.r871xs_drv.drv = {
    		.pm = &rtw_sdio_pm_ops,
    	}
    	#endif
    };
    
    static int rtw_drv_entry(void)
    {
    	sdio_drvpriv.drv_registered = _TRUE;
    	// sdio_register_driver: kernel\drivers\mmc\core\sdio_bus.c
    
    	ret = sdio_register_driver(&sdio_drvpriv.r871xs_drv);
    	return ret;
    }	
    
    int rtl8723_wifi_init_module(void)
    {
    	return rtw_drv_entry();
    }
    
    late_initcall(rtl8723_wifi_init_module);

    四、Sysfs

    通过sys文件系统,用户可以读写驱动信息。

    static ssize_t wifi_chip_read(struct class *cls, struct class_attribute *attr, char *_buf)
    {
    	ssize_t count = sprintf(_buf, "%s", "RTL8723BS");
    	printk("Current WiFi chip is RTL8723BS.\n");
    	return count;
    }
    
    static ssize_t wifi_power_write(struct class *cls, struct class_attribute *attr, const char *_buf, size_t _count)
    {
    	int poweren = 0;
        poweren = simple_strtol(_buf, NULL, 10);
        if(poweren > 0) {
            wifi_power(1);
        } else {
            wifi_power(0);
        }
    	return _count;
    }
    
    static struct class *wifi_class = NULL;
    // 生成class_attr_chip
    static CLASS_ATTR(chip, 0664, wifi_chip_read, NULL);
    // 生成class_attr_power
    static CLASS_ATTR(power, 0660, NULL, wifi_power_write);
    
    int wifi_sysif_init(void)
    {
        int ret;
        wifi_class = class_create(THIS_MODULE, "rtlwifi");
        
        ret = class_create_file(wifi_class, &class_attr_chip);
        ret = class_create_file(wifi_class, &class_attr_power);
        return 0;
    }
    
    void wifi_sysif_exit(void)
    {
        // need to remove the sys files and class
        class_remove_file(wifi_class, &class_attr_chip);
        class_remove_file(wifi_class, &class_attr_power);
        
        
        wifi_class = NULL;
    }
    
    module_init(wifi_sysif_init);
    module_exit(wifi_sysif_exit);
    该模块注册后,将出现/sys/class/rtlwifi目录,且目录下含有/sys/class/rtlwifi/chip、/sys/class/rtlwifi/power两个文件,cat chip文件将会得到RTL8723BS,而读写power文件能够获取、设置WiFi模组供电状态。

    展开全文
  • 我们在上篇文章中给大家介绍了android中的wifi框架,也介绍了手动其wpa进程来测试wifi的方式。 在本篇文章中会和大家介绍从上层(java)层,也就是一般用界面上的wifi开关打开wifi的流程 二、JAVA层框架 1、界面开关 ...

    一、引言

    我们在上篇文章中给大家介绍了android中的wifi框架,也介绍了手动其wpa进程来测试wifi的方式。
    在本篇文章中会和大家介绍从上层(java)层,也就是一般用界面上的wifi开关打开wifi的流程

    二、JAVA层框架

    1、界面开关

    WifiManager中提供了接口用来控制wifi开关,setWifiEnabled,参数true表示开启、false表示关闭。

    public boolean setWifiEnabled(boolean enabled) {
        try {
            return mService.setWifiEnabled(enabled);
        } catch (RemoteException e) {
            return false;
        }
    }
    

    该方法调用到WifiService中的setWifiEnabled。

    setWifiEnabled

    public synchronized boolean setWifiEnabled(boolean enable) {
        enforceChangePermission();
        Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid());
        if (DBG) {
            Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
        }
    
        /*
        * Caller might not have WRITE_SECURE_SETTINGS,
        * only CHANGE_WIFI_STATE is enforced
        */
    
        long ident = Binder.clearCallingIdentity();
        try {
            if (! mSettingsStore.handleWifiToggled(enable)) {
                // Nothing to do if wifi cannot be toggled
                return true;
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    
        mWifiController.sendMessage(CMD_WIFI_TOGGLED);
        return true;
    }
    

    enforceChangePermission 判断调用的进程是否有权限。想要开关wifi需要CHANGE_WIFI_STATE 权限。

    handleWifiToggled 判断飞行模式、保存wifi 操作的状态。

    最后向WifiController发送消息。CMD_WIFI_TOGGLED

    WifiController

    WifiController在WIfiService的构造函数中初始化、并开始运行。
    WifiController继承了StateMachine,是一个状态机。其构造函数如下

    WifiController(Context context, WifiService service, Looper looper) {
      super(TAG, looper);
    ......
      addState(mDefaultState);
          addState(mApStaDisabledState, mDefaultState);
          addState(mStaEnabledState, mDefaultState);
              addState(mDeviceActiveState, mStaEnabledState);
              addState(mDeviceInactiveState, mStaEnabledState);
                  addState(mScanOnlyLockHeldState, mDeviceInactiveState);
                  addState(mFullLockHeldState, mDeviceInactiveState);
                  addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
                  addState(mNoLockHeldState, mDeviceInactiveState);
          addState(mStaDisabledWithScanState, mDefaultState);
          addState(mApEnabledState, mDefaultState);
          addState(mEcmState, mDefaultState);
      if (mSettingsStore.isScanAlwaysAvailable()) {
          setInitialState(mApStaDisabledState);
      } else {
          setInitialState(mApStaDisabledState);
      }
    }
    

    上述函数中addState的格式,可以看出各状态之间的关系。
    然后通过wifi是否可以一直扫描(isScanAlwaysAvailable)设置状态机初始状态。
    主要通过调用 ApStaDisabledState类来处理CMD_WIFI_TOGGLED消息
    下面来看 ApStaDisabledState类中几个重要的成员函数

    class StaEnabledState extends State {  
        @Override  
        public void enter() {  
            mWifiStateMachine.setSupplicantRunning(true);  
        }  
        ...  
        }  
    }  
    

    mWifiStateMachine.setSupplicantRunning(true),WifiStateMachine发送Message--CMD_STAET_SUPPLICANT。发出CMD_STAET_SUPPLICANT消息

    JNI层关系最为密切的一部分,消息处理函数

    class InitialState extends State {
        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                case CMD_START_SUPPLICANT:
                    //加载Wifi驱动
                    if (mWifiNative.loadDriver()) {
                        try {//加载固件
                            mNwService.wifiFirmwareReload(mInterfaceName, "STA");
                        } catch (Exception e) {
                        }
    
                        try {
                            // A runtime crash can leave the interface up and
                            // this affects connectivity when supplicant starts up.
                            // Ensure interface is down before a supplicant start.
                            mNwService.setInterfaceDown(mInterfaceName);
                            // Set privacy extensions
                            mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
    
                           // IPv6 is enabled only as long as access point is connected since:
                           // - IPv6 addresses and routes stick around after disconnection
                           // - kernel is unaware when connected and fails to start IPv6 negotiation
                           // - kernel can start autoconfiguration when 802.1x is not complete
                            mNwService.disableIpv6(mInterfaceName);
                        } catch (RemoteException re) {
                            loge("Unable to change interface settings: " + re);
                        } catch (IllegalStateException ie) {
                            loge("Unable to change interface settings: " + ie);
                        }
    
                       /* Stop a running supplicant after a runtime restart
                        * Avoids issues with drivers that do not handle interface down
                        * on a running supplicant properly.
                        */
                        mWifiNative.killSupplicant(mP2pSupported);
                        //开启wpa_supplicant
                        if(mWifiNative.startSupplicant(mP2pSupported)) {
                            setWifiState(WIFI_STATE_ENABLING);
                            if (DBG) log("Supplicant start successful");
                            mWifiMonitor.startMonitoring();
                            transitionTo(mSupplicantStartingState);
                        } else {
                            loge("Failed to start supplicant!");
                        }
                    } else {
                        loge("Failed to load driver");
                    }
                    break;
    //.......
    }
    
    1、WifiNative.loadDriver():找到并加载Wifi驱动

    WifiNative.loadDriver()
    ---->android_net_wifi_wifi.cpp (android_net_wifi_loadDriver)
    --------->wifi.c(wifi_load_driver)

    2、开启wpa supplicant 进程

    mWifiNative.startSupplicant(mP2pSupported) 先保证没有运行的wpa supplicant,然后开启wpa supplicant,其中参数mP2pSupported表示是否支持wifi 直连。
    WifiNative.startSupplicant()
    ---->android_net_wifi_wifi.cpp(android_net_wifi_startSupplicant)
    --------->wifi.c(wifi_start_supplicant)。

    执行到这时,系统会去找对应的网络设备节点,如果上一步没有找到对应的wifi驱动,即么没有产生对应的设备节点,这一步就会报错

    其他

    接下去的流程几乎所有平台的框架都相似,无需修改就能使用,下面就这两部分,详细和大家讲解在JNI中对应的接口及实现方式

    三、JNI层

    JNI层的代码一般在frameworks/base/core/jni/android_net_wifi_Wifi.cpp中
    我们来看一下相关源码

    static jboolean android_net_wifi_loadDriver(JNIEnv* env, jobject)
    {
        return (::wifi_load_driver() == 0);
    }
    
    static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject, jboolean p2pSupported)
    {
        return (::wifi_start_supplicant(p2pSupported) == 0);
    }
    
    /*
     * JNI registration.
     */
    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", "(Z)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 },
        { "getWifiChipTypeNative", "()Ljava/lang/String;", (void*)android_net_wifi_getWifiChipType },
        { "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 },
    };
    

    可以看出其实JNI结构体中的接口都只是又将Wifi Hardware层中的接口封装了一层,核心还是Wifi Hardware层提供的接口

    四、Wifi Hardware层

    一般源码位于:hardware/libhardware_legacy/wifi/wifi.c
    下面分析几个重要的接口,一般移植时需要手动修改

    1、加载驱动

    int wifi_load_driver()
    {
        int ret = 0;
    #ifdef WIFI_DRIVER_MODULE_PATH
        ret = load_driver();
    #else
        if (property_get("wifi.driver.path", DRIVER_MODULE_PATH, NULL)) {
            property_get("wifi.driver.arg", DRIVER_MODULE_ARG, NULL);
            ret = load_driver();
        } else {
            property_set(DRIVER_PROP_NAME, "ok");
        }
    #endif
        return ret;
    }
    

    这里有两种方式加载驱动
    1、直接在wpa中指定驱动的名称、位置等相关信息

    2、去 以"wifi.driver.xx"为名称的共享内存区中获取驱动的相关信息,一般写在Boardconfig.mk文件中,由envsetup.sh来export到环境变量中,这样我们就能在其他地方应用其中的变量,相关的驱动信息格式如下

     WPA_SUPPLICANT_VERSION := VER_0_8_X
     BOARD_WPA_SUPPLICANT_DRIVER := NL80211
     CONFIG_DRIVER_WEXT :=y
     BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_rtl
     BOARD_HOSTAPD_DRIVER := NL80211
     BOARD_HOSTAPD_PRIVATE_LIB := lib_driver_cmd_rtl
     BOARD_WLAN_DEVICE := xxxxx
     WIFI_DRIVER_MODULE_NAME := "wlan"
     WIFI_DRIVER_MODULE_PATH := "/system/lib/modules/wlan.ko"
     WIFI_DRIVER_MODULE_ARG := "ifname=wlan0 if2name=p2p0"
     WIFI_FIRMWARE_LOADER := ""
     WIFI_DRIVER_FW_PATH_STA := ""
     WIFI_DRIVER_FW_PATH_AP := ""
     WIFI_DRIVER_FW_PATH_P2P := ""
     WIFI_DRIVER_FW_PATH_PARAM := ""
    endif
    

    2、启动wpa supplicant

    int wifi_start_supplicant(int p2p_supported)
    {
        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, i;
    #endif
    
        if (p2p_supported) {
            property_get("wifi.supplicant", supplicant_name, P2P_SUPPLICANT_NAME);
            snprintf(supplicant_prop_name, PROPERTY_KEY_MAX, P2P_PROP_NAME, supplicant_name);
        } else {
            property_get("wifi.supplicant", supplicant_name, SUPPLICANT_NAME);
            snprintf(supplicant_prop_name, PROPERTY_KEY_MAX, SUPP_PROP_NAME, supplicant_name);
        }
    
    //    wifi_stop_supplicant(p2p_supported);
    //    wifi_close_supplicant_connection(NULL);
    //    wifi_close_supplicant_connection("sec");
    
        /* Check whether already running */
        if (property_get(supplicant_prop_name, supp_status, NULL)
                && strcmp(supp_status, "running") == 0) {
            return 0;
        }
    
        /* Before starting the daemon, make sure its config file exists */
        if (p2p_supported) {
            if (ensure_config_file_exists(P2P_CONFIG_FILE) < 0) {
                ALOGE("Failed to create a p2p config file");
                return -1;
            }
        }
        if (ensure_config_file_exists(SUPP_CONFIG_FILE) < 0) {
            ALOGE("Wi-Fi will not be enabled");
            return -1;
        }
    
        if (ensure_entropy_file_exists() < 0) {
            ALOGE("Wi-Fi entropy file was not created");
        }
    
        /* Clear out any stale socket files that might be left over. */
        wpa_ctrl_cleanup();
    
        /* Reset sockets used for exiting from hung state */
        exit_sockets[0] = exit_sockets[1] = -1;
    
    #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(supplicant_prop_name);
        if (pi != NULL) {
            serial = __system_property_serial(pi);
        }
    #endif
    
        /* Check the interface exist*/
        if (p2p_supported) {
            int count = 10; /* wait at most 1 seconds for completion */
            while (wifi_ifname(SECONDARY) == NULL && count-- > 0) {
                usleep(100000);
            }
            if (wifi_ifname(SECONDARY) == NULL) {
                ALOGE("%s get wifi_ifname(SECONDARY) fail", __func__);
                return -1;
            }
        }
        if(wifi_ifname(PRIMARY) == NULL) {
            ALOGE("%s get wifi_ifname(PRIMARY) fail", __func__);
            return -1;
        }
    
        property_set("ctl.start", supplicant_name);
        sched_yield();
    
        while (count-- > 0) {
    #ifdef HAVE_LIBC_SYSTEM_PROPERTIES
            if (pi == NULL) {
                pi = __system_property_find(supplicant_prop_name);
            }
            if (pi != NULL) {
                __system_property_read(pi, NULL, supp_status);
                if (strcmp(supp_status, "running") == 0) {
                    return 0;
                } else if (__system_property_serial(pi) != serial &&
                        strcmp(supp_status, "stopped") == 0) {
                    return -1;
                }
            }
    #else
            if (property_get(supplicant_prop_name, supp_status, NULL)) {
                if (strcmp(supp_status, "running") == 0)
                    return 0;
            }
    #endif
            usleep(100000);
        }
        return -1;
    }
    

    四、 移植遇到的问题

    1、测试驱动

    在进行wpa及上层的移植前,首先应该测试wifi芯片驱动的正确性
    手动insmod后,使用如下命令开启无线网卡,扫描并连接无线网

    //开启无线网卡
    ifconfig  wlan0 up 
    
    // 查看网卡相关信息
    # ifconfig
    lo        Link encap:Local Loopback
              inet addr:127.0.0.1  Mask:255.0.0.0
              inet6 addr: ::1/128 Scope:Host
              UP LOOPBACK RUNNING  MTU:16436  Metric:1
              RX packets:40 errors:0 dropped:0 overruns:0 frame:0
              TX packets:40 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0
              RX bytes:3208 (3.1 KiB)  TX bytes:3208 (3.1 KiB)
    
    wlan0     Link encap:Ethernet  HWaddr 7C:DD:90:78:81:51
              inet6 addr: fe80::7edd:90ff:fe78:8151/64 Scope:Link
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:5748079 (5.4 MiB)  TX bytes:267896 (261.6 KiB)
    
    //扫描可用的无线网络
     # iwlist wlan0 scan | grep ESSID
                   ESSID:"xxxx"
                   ESSID:"xxxxx"
                   ESSID:"xxxx"
               
    //选择要连接的无线网络
    # iwconfig wlan0 essid "xxxx"
    
    //输入该网络的安全密码
    # iwconfig wlan0 key 123456
    
    //连接到指定的AP(无线路由)
    root@sabresd_6dq:/ # iwconfig wlan0 ap auto
    
    //设置无线网卡的IP地址
    root@sabresd_6dq:/ # ifconfig wlan0 192.168.111.111
    
    //使用 ping 命令检测无线网连通状况
    

    测试成功后再开始后面的移植工作

    2、加载驱动不成功 wifi_ifname(PRIMARY) fail

    如果驱动没有加载成功,在开启wpa supplicant时会出现找不到设备节点的错误

    wifi_start_supplicantget wifi_ifname(PRIMARY) fail
    

    需要在wpa中指定使用wifi芯片匹配的驱动相关信息,或者在Boardconfig.mk中指定

    3、找不到驱动

    wifi_start_supplicantget wifi_ifname(SECONDARY) fail
    

    一般是Boardconfig.mk中驱动名称和位置与真实的信息不匹配,或者驱动已加载造成,需要进一步核对。

    4、加载节点失败 wifi_ifname(SECONDARY) fail

    wifi_start_supplicantget wifi_ifname(SECONDARY) fail
    

    是因为没有 创建p2p0设备节点,导致,
    可以手动insmod创建相应的节点,查看是否解决

    insmod wlan.ko ifname=wlan0 if2name=p2p0
    

    成功后在Boardconfig.mk中的WIFI_DRIVER_MODULE_ARG参数指定 "ifname=wlan0 if2name=p2p0"即可

    5、supplicant启动失败

    E/WifiStateMachine(1783): Failed to start supplicant!
    

    这是framework层中的报错,是由于wap_supplicant没有正确开启所导致的。主要还是在wifi_realtek.c中的int wifi_start_supplicant(int p2p_supported)中找问题。
    将wifi_realtek.c中的如下语句注释掉即可

    wifi_stop_supplicant(p2p_supported);
    wifi_close_supplicant_connection(NULL);
    wifi_close_supplicant_connection("sec");
    
    展开全文
  • 主要是想对spi接口的wifi驱动框架有一个整体的把控,因此会忽略一些硬件上操作的系统,同时里面涉及到的一些驱动基础,比如数据结构、设备模式也不进行详细说明原理。如果有任何错误地方,请指出,谢谢! 分两步来...

    前言

      本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 。主要是想对spi接口的wifi驱动框架有一个整体的把控,因此会忽略一些硬件上操作的系统,同时里面涉及到的一些驱动基础,比如数据结构、设备模式也不进行详细说明原理。如果有任何错误地方,请指出,谢谢!

    分两步来分析:
    第一步:spi接口驱动分析
    第二部:基于spi接口的wifi驱动分析

    spi接口驱动分析

    在cm-x270.c中

    static void __init cmx270_init_spi(void)
    {
        pxa2xx_set_spi_info(2, &cm_x270_spi_info);
        spi_register_board_info(ARRAY_AND_SIZE(cm_x270_spi_devices));
    }
    
    void __init cmx270_init(void)
    {
        pxa2xx_mfp_config(ARRAY_AND_SIZE(cmx270_pin_config));
    
    #ifdef CONFIG_PM
        pxa27x_set_pwrmode(PWRMODE_DEEPSLEEP);
    #endif
    
        cmx270_init_rtc();
        cmx270_init_mmc();
        cmx270_init_ohci();
        cmx270_init_2700G();
        cmx270_init_spi();
    }

    pxa2xx_set_spi_info实际上是向平台总线注册spi控制器设备。在drivers/spi/pxa2xx_spi.c中有对应的spi控制器的驱动注册:

    static int __init pxa2xx_spi_init(void)
    {
        return platform_driver_probe(&driver, pxa2xx_spi_probe);
    }

    这样匹配成功后导致pxa2xx_spi_probe的执行,在pxa2xx_spi_probe中,主要是根据cm_x270_spi_info实例化一个spi控制器对象,然后初始化控制器,申请资源,最后调用spi_register_master将该spi控制器实例注册到设备模型上去。里面核心的操作就是scan_boardinfo(master)它会扫描注册的spi设备。这个就是之前函数

    spi_register_board_info(ARRAY_AND_SIZE(cm_x270_spi_devices));

    调用的结果,它会将cm_x270_spi_devices信息添加到一个全局的链表(中间会wrap一层)。

    scan_boardinfo会将全局链表里面总线号和自己匹配的设备都进行注册,即调用spi_new_device,将设备注册到spi总线上去。于是,spi控制器和spi设备(wifi) 都已经准备完毕,剩下的就等spi接口的wifi驱动注册了。

    基于spi接口的wifi驱动分析

    因为分析的spi接口的wifi驱动是Marvell公司的,所以spi接口的wifi驱动部分看:

    linux/drivers/net/wireless/libertas/if_spi.c ,它在

    static int __init if_spi_init_module(void)
    {
        int ret = 0;
        lbs_deb_enter(LBS_DEB_SPI);
        printk(KERN_INFO "libertas_spi: Libertas SPI driver\n");
        ret = spi_register_driver(&libertas_spi_driver);
        lbs_deb_leave(LBS_DEB_SPI);
        return ret;
    }

    中调用了spi_register_driver向spi总线注册驱动,这会导致if_spi_probe的调用,它内部会创建一个对象来描述该spi接口的wifi,即struct if_spi_card *card;。同时,也会装载固件到wifi设备中,它是通过if_spi_prog_helper_firmwareif_spi_prog_main_firmware实现的。

      关于固件的主要作用
       Firmware是在Wi-Fi设备硬件中执行的一段程序,系统上电后由WLAN驱动将其下载到Wi-Fi模块中,实现Wi-Fi硬件接口控制、数据缓冲、802.11与802.3帧类型转换、802.1l MAC层管理、WLAN MAC中断管理以及硬件控制等功能。发送数据时,Host驱动程序将从上层接收到的标准802.3帧发送给Firmware,Firmware将收到的数据帧转换成802.11帧,再通过无线连接将数据传输出去;接收数据时,Firmware将接收到的所有802.11帧转换成802.3帧后,通过SPI口发送给Host驱动。由此可见,Wi-Fi无线网卡设备在Linux中是被当作普通的以太网设备对待的,在Wi-Fi驱动程序中无需实现802.11帧与802.3帧之间的类型转换。

    然后会将刚申请并初始化完成的card注册到libertas中,即调用:lbs_add_card(card, &spi->dev); lbs_add_card的主要作用是让card成为net device功能,它调用alloc_netdev申请了一个网络设备,需要注意的是赋予了网络子系统将来会用到的操作集lbs_netdev_ops

    static const struct net_device_ops lbs_netdev_ops = {
        .ndo_open       = lbs_dev_open,
        .ndo_stop       = lbs_eth_stop,
        .ndo_start_xmit     = lbs_hard_start_xmit,
        .ndo_set_mac_address    = lbs_set_mac_address,
        .ndo_tx_timeout     = lbs_tx_timeout,
        .ndo_set_multicast_list = lbs_set_multicast_list,
        .ndo_change_mtu     = eth_change_mtu,
        .ndo_validate_addr  = eth_validate_addr,
    };

    同时注意下面的调用,后面会基于这些通信:

            init_waitqueue_head(&priv->waitq);
            priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");
        if (IS_ERR(priv->main_thread)) {
            lbs_deb_thread("Error creating main thread.\n");
            goto err_ndev;
        }
    
        priv->work_thread = create_singlethread_workqueue("lbs_worker");
        INIT_DELAYED_WORK(&priv->assoc_work, lbs_association_worker);
        INIT_DELAYED_WORK(&priv->scan_work, lbs_scan_worker);
        INIT_WORK(&priv->mcast_work, lbs_set_mcast_worker);

    接着if_spi_probe里面会调用:

     priv->hw_host_to_card = if_spi_host_to_card; 

    它会在Libertas层收到网络子系统数据的时候调用

     card->spi_thread = kthread_run(lbs_spi_thread, card, "lbs_spi_thread"); 

    负责数据包的接收和命令的响应处理线程

     err = request_irq(spi->irq, if_spi_host_interrupt,
                                IRQF_TRIGGER_FALLING, "libertas_spi", card); 

    用于处理spi接口网卡上的中断

     err = lbs_start_card(priv); 

    它的调用链是
    lbs_cfg_register -> wiphy_register ->register_netdev
    ,这样之后网络子系统就能够使用该网卡驱动了。

    下面分几个情景分析上面这些核心函数的作用

    情景分析

    情景1:上层网络子系统有数据发送

    lbs_hard_start_xmit函数会被调用,这个函数属于上文说过的网络子系统将来会用到的操作集lbs_netdev_opslbs_hard_start_xmit首先将数据包拷贝到自己的buf里面,然后唤醒其他线程然后退出,priv是libertas层维护的对象,是在上文中lbs_add_card的时候分配的。

    调用层次
    lbs_add_card -> lbs_cfg_alloc -> wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private));, 属于wdev额外分配的一段内存,可以通过priv = wdev_priv(wdev);获取。 wake_up(&priv->waitq);实际唤醒的是lbs_thread线程,由于是发送数据包,会调用lbs_execute_next_command,接着会导致lbs_submit_command -> priv->hw_host_to_card

    priv->hw_host_to_card是上文中说过的if_spi_host_to_card,因为是通过spi接口发送数据,所以调用spi驱动probe时注册的函数是理所当然的。

    if_spi_host_to_card最终会导致spi控制器实例的master->transfer会被调用,它是在第一步中的pxa2xx_spi_probe里注册master->transfer = transfer;,transfer实际上就是最终的硬件操作了,但它实际上也是异步的,它呼叫一个工作线程去完成这些任务

    if (drv_data->run == QUEUE_RUNNING && !drv_data->busy)
            queue_work(drv_data->workqueue, &drv_data->pump_messages);

    drv_data->workqueue也是在pxa2xx_spi_probe完成的:

    init_queue
            -> tasklet_init(&drv_data->pump_transfers,
                pump_transfers, (unsigned long)drv_data);
            -> INIT_WORK(&drv_data->pump_messages, pump_messages);
            -> drv_data->workqueue = create_singlethread_workqueue(
                    dev_name(drv_data->master->dev.parent));

    最终pump_messages会被调用. 它最后会导致pump_transfers来完成硬件数据传送。

    这个过程确实非常绕,下面分析下为什么需要这些过程:
    首先是lbs_thread,它的存在不仅仅是处理数据的发送,同时还要处理设备返回的事件和命令的响应。上层数据包发送的时候,我们应该尽可能快的完成处理并返回,不然网络子系统就会延迟了,但这并不是最主要的, 将一类事件异步统一到一个线程里面处理也是有很多优势的。然后是priv->hw_host_to_card的回调问题,网卡驱动的libertas层实际上是和具体的硬件接口分离的,不然它还得关心是spi接口、sdio接口、或者是usb接口的wifi等等,所以采用回调的方式,让具体的接口处理部分注册回调函数。

    if_spi_host_to_card调用spi控制器实例的master->transfer很容易理解,具体的数据传送当然的操作控制器。

    对于控制器里面的实现,即master->transfe的transfer 它采用工作线程的方式来统一处理所有的发送数据包,这样就可以和lbs_thread实现异步,各种更容易处理各自的队列等。

    ### 情景二:设备接收到数据

    Spi接口的wifi有数据过来的时候,会通过spi中断,所以理所当然的进入注册的spi irq的中断处理函数if_spi_host_interrupt,它的实现很简单,异步处理,自己本身不进行任何处理(毕竟在中断,处理spi有可能sleep,那就玩玩了,呵呵),即调用up(&card->spi_ready)唤醒其他线程来做事情。

    睡在这信号量上的线程是:lbs_spi_thread,这个是上文第一步中的if_spi_probe中创建的,这个好理解啦。它的处理函数lbs_spi_thread,读状态寄存器,根据状态来决定是什么事件。

    /* Read the host interrupt status register to see what we
     * can do. */
    err = spu_read_u16(card, IF_SPI_HOST_INT_STATUS_REG,
                       &hiStatus); 

    数据的处理就在:

     if (hiStatus & IF_SPI_HIST_RX_UPLOAD_RDY)
                 err = if_spi_c2h_data(card);
                 if (err)
                            goto err; 

    if_spi_c2h_data的处理和其他网卡的处理类似,分配sk_buff然后读到分配的buff里面(读可以直接读,也可以用dma),然后调用lbs_process_rxed_packet经过一些处理调用netif_rx来传给网络子系统。

    情景三:命令的响应处理

    和情景二类似,都是在lbs_spi_thread中,不过是进入了另一个判断里面:

    if (hiStatus & IF_SPI_HIST_CMD_UPLOAD_RDY)
                  err = if_spi_c2h_cmd(card);
                  if (err)
                             goto err;

    if_spi_c2h_cmd命令的响应当然没必要让网络子系统知道,因为对于网络子系统,spi wifi就是一个网络设备而已,不知道这些具体的命令的含义。我们这个理所当然的是让libertas层的lbs_thread线程处理,这个上文 情景1:上层网络子系统有数据发送 已经有过分析。它会进入到

    if (priv->resp_len[resp_idx]) {
                 spin_unlock_irq(&priv->driver_lock);
                 lbs_process_command_response(priv,
                             priv->resp_buf[resp_idx],
                             priv->resp_len[resp_idx]);
                 spin_lock_irq(&priv->driver_lock);
                 priv->resp_len[resp_idx] = 0;
    }

    最终调用lbs_process_command_response来处理命令的响应。

    完!
    2014年5月

    转载于:https://www.cnblogs.com/rongpmcu/p/7662269.html

    展开全文
  • 本篇文章来跟大家一起了解一下Android中的wifi框架 二、WIFI框架 整体框架图 1、Java应用层 原生机目录:packages/apps/Settings/src/com/android/settings/wifi/ 主要的类: WifiSettings.java 负责显示Wifi的设置...

    一、引言

    本篇文章来跟大家一起了解一下Android中的wifi框架

    二、WIFI框架

    整体框架图

    在这里插入图片描述
    1、Java应用层
    原生机目录:packages/apps/Settings/src/com/android/settings/wifi/
    主要的类:
    WifiSettings.java 负责显示Wifi的设置界面
    WifiEnabler.java 负责Wifi的开关逻辑
    WifiDialog.java 负责Wifi的对话框
    WifiInfo.java 表示Wifi的相关配置信息
    本文不详细描述

    2、Wifi Framework层
    位于: frameworks/base/wifi/Java/android/net/wifi/
    本文不详细描述

    3、Wifi JNI层
    位于:frameworks/base/core/jni/android_net_wifi_Wifi.cpp
    android_net_wifi_Wifi.cpp就是典型jni接口,通过它可以直接调用Wifi的硬件抽象层。

    4、Wifi Hardware层 (wifi管理库)
    hardware/libhardware_legacy/wifi/wifi.c
    Wifi Hardware层也叫wpa_supplicant适配层,是通用wpa_supplicant的封装。wpa_supplicant适配层起着承上启下的作用,主要用于与wpa_supplicant守护进程的通信,以供给Wifi框架层使用。

    wifi.c和wpa_supplicant直接交互
    在wifi.c中,声明了如下和wpa_supplicant交互的函数:

    void wpa_ctrl_cleanup(void) {}
    struct wpa_ctrl *wpa_ctrl_open(const char *ctrl_path) { return NULL; }
    void wpa_ctrl_close(struct wpa_ctrl *ctrl) {}
    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))
        { return 0; }
    int wpa_ctrl_attach(struct wpa_ctrl *ctrl) { return 0; }
    int wpa_ctrl_detach(struct wpa_ctrl *ctrl) { return 0; }
    int wpa_ctrl_recv(struct wpa_ctrl *ctrl, char *reply, size_t *reply_len)
        { return 0; }
    int wpa_ctrl_get_fd(struct wpa_ctrl *ctrl) { return 0; }
    

    5、wpa_supplicant层(wifi tool)
    wpa_supplicant是一个开源项目,已经移植到Linux、Windows以及其它嵌入式系统上。它是WPA(WiFi Protected Access的缩写,中文含义为WiFi网络安全存取)的应用层认证客户端,负责完成认证相关的登录、加密等工作。 该层是Wifi FrameWork层的基石,也叫Wifi服务层。
    源代码目录:external/wpa_supplicant/
    经过编译后主要结果是生成动态库libwpa_client.so和可执行程序wpa_supplicant。
    (1) wpa_client (生成库libwpaclient.so)
    external/wpa_supplicant_8/wpa_supplicant/src/common/wpa_ctrl.c
    (2) wpa_server (生成守护进程wpa_supplicant)
    external/wpa_supplicant_8/wpa_supplicant/main.c

    6、Wifi kernel层

    内核根目录:drivers/net/wireless

    wpa_supplicant

    wpa_supplicant是开源项目源码,主要是用来支持WEP,WPA/WPA2和WAPI无线协议和加密认证的,而实际上的工作内容是通过socket(不管是wpa_supplicant与上层还是wpa_supplicant与驱动都采用socket通讯)与驱动交互上报数据给用户,而用户可以通过socket发送命令给wpa_supplicant调动驱动来对WiFi芯片操作。
    总结wpa_supplicant的功能:

    1、WiFi驱动和用户的中转站
    2、对协议和加密认证的支持。

    wpa_supplicant 进程实现的入口函数为:external/wpa_supplicant_8/wpa_supplicant/main.c中的main函数, 此程序在hardware/libhardware_legacy/wifi/wifi.c中的wifi_start_supplicant_common中被启动(property_set(“ctl.start”, daemon_cmd))。

    三、Android中的WIFI框架

    Android WiFi系统引入了wpa_supplicant,它的整个WiFi系统以wpa_supplicant为核心来定义上层用户接口和下层驱动接口。整个WiFi系统架构如下图所示:
    在这里插入图片描述
    本篇文章只介绍wpa及底层相关框架,不涉及上层
    流程分析

    1、加载驱动

    使用wpa之前,需要先加载(编入内核/insmod)对应的wifi驱动,所以第一步先将对应的wifi芯片的驱动加载入系统。
    加载入系统后会产生对应的节点(wlan0,p2p0)
    部分网卡有p2p功能,在insmod时需要加上对应的参数,不然州wpa调用时会出错

    2、wpa_supplicant

    加载好驱动后,需要启动wpa_supplicant 进程,一般会在开机脚本中启动该服务,如果没有将驱动加载近系统,则需要手动加载驱动后手动开启

    当然,该进程也可由上层java,我们打开对应的wifi开关后开启

    static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject, jboolean p2pSupported)
    {
        return (::wifi_start_supplicant(p2pSupported) == 0);
    }
    

    核心也是调用 Wifi Hardware层的’wifi_start_supplicant’ 开启该进程。

    3、WifiService

    由上层创建,负责wifi的相关动作
    具体如下

    1. Wifi启动
    2. 开始扫描
    3. 显示扫描的AP
    4. 配置AP
    5. 连接AP
    6. 获取IP地址
    7. 上网

    四、wpa_supplicant介绍

    wpa_supplicant是Android平台使用的用来管理wifi的应用程序,它可以支持WEP,WPA/WPA2和WAPI无线协议和加密认证。wpa_supplicant会操作驱动程序和wifi网卡交互,它同时是一个服务器,通过socket,我们可以和它建立连接,然后就可以通过命令和它交互,让它帮我们实现wifi连接断开等操作。

    在android系统中,当我们打开wifi后,wpa_supplicant就启动了,
    我们不需要手动来启动。这点可以通过ps | grep wpa_supplicant来查看,结果如下:

    wifi 2955 2947 4932 2596 poll_sched b6e22138 S wpa_supplicant 
    

    与wpa_supplicant同时存在的是wpl_cli,它是一个客户端,它实现了和wap_supplicant连接等功能,我们可以通过它和wap_supplicant交互。
    要使用wpa_cli,首先得过得root权限,没有root权限的话连接会失败。
    使用wap_cli的第一步就是启动wpa_cli:
    如果有可以用的wifi,则会成功连接
    成功连接后,可以通过以下顺序来测试wifi功能

    1、scan

    /> scan 
    OK
    

    2、查看扫描结果

    /> scan_results 
    bssid / frequency / signal level / flags / ssid 
    20:f4:1b:83:40:e4 2462 -63 [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS] ROC 
    80:89:17:a1:a8:ee 2437 -64 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] samuel 
    80:89:17:a1:a8:e4 2437 -67 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] TV-WIFI 
    80:89:17:9f:d3:ba 2412 -73 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] xrr 
    f4:ec:38:7b:2c:96 2437 -75 [WPA-PSK-CCMP][WPA2-PSK-CCMP][WPS][ESS] TP-LINK_7B2C96 
    1c:fa:68:50:8a:76 2462 -71 [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS] RDSPEED100M 
    d0:c7:c0:fd:86:64 2437 -73 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] yingyunguanli-huiyishi 
    4e:e0:10:c2:44:57 2462 -59 [WPA2-PSK-CCMP][ESS] hellowifi 
    4e:e0:10:c2:48:5d 2462 -70 [WPA2-PSK-CCMP][ESS] ADESKTOP-93DNJ6F 
    ac:e0:10:c2:63:bc 2412 -66 [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS] wifi 
    cc:34:29:2c:7f:e8 2412 -76 [WPA-PSK-TKIP][WPA2-PSK-TKIP][ESS] dlink
    

    3、add_network

    添加网络
    通过查看扫描的结果,我们选择一个要连接的wifi,比如hellowifi
    这个时候,我们首先要添加一个网络。

    /> add_network 
    2 
    

    这个2是wpa_supplicant给我们返回的一个值,代表了这个网络,以后我们需要操作这个网络就要使用这个值。

    4、设置ssid和密码

    ssid就是热点的名字,这里就是hellowifi.

    /> set_network 2 ssid “hellowifi” 
    OK 
    /> set_network 2 psk “11223344” 
    OK 
    psk是WPA2-PSK模式下标示wifi密码的名字。如果是没有加密: 
    />set_network 2 key_mgmt NONE 
    如果是WEP安全模式: 
    />set_network 2 wep_key0 “your ap passwork”
    

    5、enable_network

    使能wifi

    /> select_network 2 
    OK 
    选择网络是可选的,如果当前已经连接了一个wifi,你应该重新选择一个网络。 
    /> enable_network 2 
    OK
    

    上层调试

    如果上述步骤都能成功调试,则说明底层驱动以及wpa层没有问题,接下来应该结合上层,如UI界面,wifi开关等功能进行最后的调试

    展开全文
  • 本文从硬件结构到软件实现探究LinuxWiFi驱动框架。如下图: 硬件角度: CPUWiFi芯片,以rtl8723为例总线SDIO软件角度: 电源、GPIO:负责WiFi模组的电源管理、IO管理SDIO:数据通道WiFi驱动:负责WiFi规范实现...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 135
精华内容 54
关键字:

linuxwifi驱动框架

linux 订阅