AP6256驅動分析


硬件:IMX6Q

系統:Linux 4.1.15

 

一.驅動初始化

Dhd_linux.c (drivers\net\wireless\bcmdhd) 

dhd_module_init調用dhd_wifi_platform_register_drv再到wifi_ctrlfunc_register_drv

wifi_ctrlfunc_register_drv:

 
static int wifi_ctrlfunc_register_drv(void)
{
    wifi_adapter_info_t *adapter;
    /* multi-chip support not enabled, build one adapter information for
     * DHD (either SDIO, USB or PCIe)
     */
    adapter = kzalloc(sizeof(wifi_adapter_info_t), GFP_KERNEL);
    if (adapter == NULL) {
        DHD_ERROR(("%s:adapter alloc failed", __FUNCTION__));
        return -ENOMEM;
    }
    //分配初始化一個adapter
    adapter->name = "DHD generic adapter";
    adapter->bus_type = -1;
    adapter->bus_num = -1;
    adapter->slot_num = -1;
    adapter->irq_num = -1;
    is_power_on = FALSE;
    wifi_plat_dev_probe_ret = 0;
    dhd_wifi_platdata = kzalloc(sizeof(bcmdhd_wifi_platdata_t), GFP_KERNEL);
    dhd_wifi_platdata->num_adapters = 1;
    dhd_wifi_platdata->adapters = adapter;
    init_waitqueue_head(&adapter->status_event);
#if !defined(CONFIG_DTS)
    if (dts_enabled) {
        struct resource *resource;
        adapter->wifi_plat_data = (void *)&dhd_wlan_control; //操作結構體
        resource = &dhd_wlan_resources;
#ifdef CUSTOMER_HW
        /*調用dhd_wlan_init_gpio獲取DTS里面的gpio_wl_reg_on和gpio_wl_host_wake引腳
        bcmdhd {
            compatible = "android,bcmdhd_wlan";
 
            gpio_wl_reg_on = <&gpio2 24 GPIO_ACTIVE_HIGH>;
            gpio_wl_host_wake = <&gpio2 23 GPIO_ACTIVE_HIGH>;
        }; */
        wifi_plat_dev_probe_ret = dhd_wlan_init_plat_data();
        if (wifi_plat_dev_probe_ret)
            return wifi_plat_dev_probe_ret;
#endif
        adapter->irq_num = resource->start;
        adapter->intr_flags = resource->flags & IRQF_TRIGGER_MASK;
        //對於不同接口,包括usb,sdio,pcie的wifi進行加載,單獨分析1
        wifi_plat_dev_probe_ret = dhd_wifi_platform_load();
    }
#endif /* !defined(CONFIG_DTS) */
    /* return probe function's return value if registeration succeeded */
    return wifi_plat_dev_probe_ret;
}

 

 
單獨分析1
/* 對於不同接口,包括usb,sdio,pcie的wifi進行加載 */
 static int dhd_wifi_platform_load()
{
    //Netlink初始化
    wl_android_init();
    if ((err = dhd_wifi_platform_load_usb()))
        goto end;
    else if ((err = dhd_wifi_platform_load_sdio()))
        goto end;
    else
        err = dhd_wifi_platform_load_pcie();
 
    return err;
}

 

我們這里只分析sdio的dhd_wifi_platform_load_sdio
主要是給所有adapters上電,然后匹配func
static int dhd_wifi_platform_load_sdio(void)
{
    /* power up all adapters 給所有adapter上電*/
    for (i = 0; i < dhd_wifi_platdata->num_adapters; i++) {
        bool chip_up = FALSE;
        int retry = POWERUP_MAX_RETRY;
        struct semaphore dhd_chipup_sem;
 
        adapter = &dhd_wifi_platdata->adapters[i];
        do {
            sema_init(&dhd_chipup_sem, 0);
            /* 注冊一個虛擬的SDIO客戶端驅動程序,以便收到新的SDIO設備的通知
            *  里面會注sdio_register_driver(&dummy_sdmmc_driver),注冊一個dummy_sdmmc_driver
            *  會匹配func,如果匹配不成功不會執行dummy_probe,會初始化失敗,所以要
            *  保證sdio host讀取到了設備,添加了func。
            */
            err = dhd_bus_reg_sdio_notify(&dhd_chipup_sem);
            //給wifi上電
            err = wifi_platform_set_power(adapter, TRUE, WIFI_TURNON_DELAY);
            if (err) {
            } else {
                wifi_platform_bus_enumerate(adapter, TRUE);
            }
            /* 等待dhd_bus_reg_sdio_notify注冊成功,里面會up(notify_semaphore)
             * 然后執行chip_up = TRUE,跳出循環
             */
            if (down_timeout(&dhd_chipup_sem, msecs_to_jiffies(POWERUP_WAIT_MS)) == 0) {
                printk("cxw dhd_wifi_platform_load_sdio down_timeout TRUE ......\n");
                dhd_bus_unreg_sdio_notify();
                chip_up = TRUE;
                break;
            }
        }
    }
    //注冊sdio_register_driver(&bcmsdh_sdmmc_driver),單獨分析2
    err = dhd_bus_register(); 
 
    return err;
}

 

 
單獨分析2
dhd_bus_register主要是注冊sdio驅動sdio_register_driver(&dummy_sdmmc_driver);
/* devices we support, null terminated */
static const struct sdio_device_id bcmsdh_sdmmc_ids[] = {
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_DEFAULT) },
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM4362_CHIP_ID) },
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43751_CHIP_ID) },
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43752_CHIP_ID) },
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43012_CHIP_ID) },
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_CHIP_ID) },
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N_ID) },
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N2G_ID) },
    { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N5G_ID) },
    /* { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_ANY_ID) }, */
    { SDIO_DEVICE_CLASS(SDIO_CLASS_NONE)        },
     /* end: all zeroes */
    { 0, 0, 0, 0},
};
static struct sdio_driver dummy_sdmmc_driver = {
    .probe        = dummy_probe,
    .remove        = dummy_remove,
    .name        = "dummy_sdmmc",
    .id_table    = bcmsdh_sdmmc_ids,
    };
 
通過總線驅動sdio_bus_match匹配,主要匹配三個參數ids->class || ids->vendor || ids->device。
匹配成功后調用bcmsdh_sdmmc_probe 》 sdioh_probe 》 bcmsdh_probe
void* bcmsdh_probe(osl_t *osh, void *dev, void *sdioh, void *adapter_info, uint bus_type,
    uint bus_num, uint slot_num)
{
    /* 將BCMSDH層附加到SDIO主機控制器驅動程序 */
    bcmsdh = bcmsdh_attach(osh, sdioh, &regs);
    if (bcmsdh == NULL) {
        SDLX_ERR(("%s: bcmsdh_attach failed\n", __FUNCTION__));
        goto err;
    }
    bcmsdh_osinfo = MALLOC(osh, sizeof(bcmsdh_os_info_t));
    if (bcmsdh_osinfo == NULL) {
        SDLX_ERR(("%s: failed to allocate bcmsdh_os_info_t\n", __FUNCTION__));
        goto err;
    }
    bzero((char *)bcmsdh_osinfo, sizeof(bcmsdh_os_info_t));
    bcmsdh->os_cxt = bcmsdh_osinfo;
    bcmsdh_osinfo->sdioh = sdioh;
    bcmsdh_osinfo->dev = dev;
    osl_set_bus_handle(osh, bcmsdh);
#if defined(OOB_INTR_ONLY)
    spin_lock_init(&bcmsdh_osinfo->oob_irq_spinlock);
    /* Get customer specific OOB IRQ parametres: IRQ number as IRQ type */
    bcmsdh_osinfo->oob_irq_num = wifi_platform_get_irq_number(adapter_info,
        &bcmsdh_osinfo->oob_irq_flags);
    if  (bcmsdh_osinfo->oob_irq_num < 0) {
        SDLX_ERR(("%s: Host OOB irq is not defined\n", __FUNCTION__));
        goto err;
    }
#endif /* defined(BCMLXSDMMC) */
    /* Read the vendor/device ID from the CIS */
    vendevid = bcmsdh_query_device(bcmsdh);
    /* try to attach to the target device */
    bcmsdh_osinfo->context = drvinfo.probe((vendevid >> 16), (vendevid & 0xFFFF), bus_num,
        slot_num, 0, bus_type, (void *)regs, osh, bcmsdh);
    if (bcmsdh_osinfo->context == NULL) {
        SDLX_ERR(("%s: device attach failed\n", __FUNCTION__));
        goto err;
    }
    return bcmsdh;
}
 
這里會調用drvinfo.probe,也就是dhdsdio_probe,前面有賦值
這里是重要部分了,涉及網絡相關的內容了
dhdsdio_probe(uint16 venid, uint16 devid, uint16 bus_no, uint16 slot,
    uint16 func, uint bustype, void *regsva, osl_t * osh, void *sdh)
{
    /* attempt to attach to the dongle 嘗試連接到 dongle, 
     * 設置是poll還是中斷模式,默認使用中斷 */
    if (!(dhdsdio_probe_attach(bus, osh, sdh, regsva, devid))) {
        DHD_ERROR(("%s: dhdsdio_probe_attach failed\n", __FUNCTION__));
        goto fail;
    }
    /* Attach to the dhd/OS/network interface 綁定到dhd/OS/網口 */
    if (!(bus->dhd = dhd_attach(osh, bus, SDPCM_RESERVE))) {
        DHD_ERROR(("%s: dhd_attach failed\n", __FUNCTION__));
        goto fail;
    }
    /* Allocate buffers 分配SDIO用的BUF */
    if (!(dhdsdio_probe_malloc(bus, osh, sdh))) {
        DHD_ERROR(("%s: dhdsdio_probe_malloc failed\n", __FUNCTION__));
        goto fail;
    }
    /* SDIO的一些特行初始化 */
    if (!(dhdsdio_probe_init(bus, osh, sdh))) {
        DHD_ERROR(("%s: dhdsdio_probe_init failed\n", __FUNCTION__));
        goto fail;
    }
    if (bus->intr) {
        /* Register interrupt callback, but mask it (not operational yet). */
        DHD_INTR(("%s: disable SDIO interrupts (not interested yet)\n", __FUNCTION__));
        bcmsdh_intr_disable(sdh);
        /* 設置中斷,當有中斷來的時候調用dhdsdio_isr */
        if ((ret = bcmsdh_intr_reg(sdh, dhdsdio_isr, bus)) != 0) {
            DHD_ERROR(("%s: FAILED: bcmsdh_intr_reg returned %d\n",
                       __FUNCTION__, ret));
            goto fail;
        }
        DHD_INTR(("%s: registered SDIO interrupt function ok\n", __FUNCTION__));
    } else {
        DHD_INFO(("%s: SDIO interrupt function is NOT registered due to polling mode\n",
                   __FUNCTION__));
    }
    DHD_INFO(("%s: completed!!\n", __FUNCTION__));
    /* Ok, have the per-port tell the stack we're open for business 告訴我這個網絡棧,可以工作了 */
    if (dhd_attach_net(bus->dhd, TRUE) != 0)
    {
        DHD_ERROR(("%s: Net attach failed!!\n", __FUNCTION__));
        goto fail;
    }
    return NULL;
}
  
 
我們先分析下dhd_attach
對DHD管理的每個硬件(狗)實例調用一次
dhd_attach(osl_t *osh, struct dhd_bus *bus, uint bus_hdrlen
#ifdef BCMDBUS
    , void *data
#endif
)
{
    /* Passing NULL to dngl_name to ensure host gets if_name in dngl_name member 
     * ifp->net = alloc_etherdev(DHD_DEV_PRIV_SIZE)分配etherdev,包括私有結構空間
     * 取消注冊並釋放iflist和iflist中現有的net_device接口(如果有的話)分配一個新的。
     * 槽位被重用。此函數不注冊Linux內核的新接口。Dhd_register_if負責這項工作
     */
    net = dhd_allocate_if(&dhd->pub, 0, if_name, NULL, 0, TRUE, NULL);
    if (net == NULL) {
        goto fail;
    } 
 
#if defined(RXFRAME_THREAD)
    dhd->rxthread_enabled = TRUE;
#endif /* defined(RXFRAME_THREAD) */
    /* Attach and link in the protocol */
    if (dhd_prot_attach(&dhd->pub) != 0) {
        DHD_ERROR(("dhd_prot_attach failed\n"));
        goto fail;
    }
    dhd_state |= DHD_ATTACH_STATE_PROT_ATTACH;
#ifdef WL_CFG80211
    spin_lock_init(&dhd->pub.up_lock);
    /* Attach and link in the cfg80211 把設備注冊到cfg80211,
     * 操作函數是wl_cfg80211_ops, 注冊到rfkill*/
    if (unlikely(wl_cfg80211_attach(net, &dhd->pub))) {
        DHD_ERROR(("wl_cfg80211_attach failed\n"));
        goto fail;
    }
#if defined(WL_WIRELESS_EXT)
    /* Attach and link in the iw  加入到iw里面*/
    if (wl_iw_attach(net, &dhd->pub) != 0) {
        DHD_ERROR(("wl_iw_attach failed\n"));
        goto fail;
    }
    dhd_state |= DHD_ATTACH_STATE_WL_ATTACH;
#endif /* defined(WL_WIRELESS_EXT) */
    /* Set up the bottom half handler */
    if (dhd_dpc_prio >= 0) {
        /* Initialize DPC thread 
         * Deferred Procedure Call 延遲函數,也就是中斷后半部    
         */
        PROC_START(dhd_dpc_thread, dhd, &dhd->thr_dpc_ctl, 0, "dhd_dpc");
        if (dhd->thr_dpc_ctl.thr_pid < 0) {
            goto fail;
        }
    } else {
        /*  use tasklet for dpc */
        tasklet_init(&dhd->tasklet, dhd_dpc, (ulong)dhd);
        dhd->thr_dpc_ctl.thr_pid = -1; 
    }
    if (dhd->rxthread_enabled) {
        bzero(&dhd->pub.skbbuf[0], sizeof(void *) * MAXSKBPEND);
        /* Initialize RXF thread 網絡包接收線程 */
        PROC_START(dhd_rxf_thread, dhd, &dhd->thr_rxf_ctl, 0, "dhd_rxf");
        if (dhd->thr_rxf_ctl.thr_pid < 0) {
            goto fail;
        }
    }
#endif /* !BCMDBUS */
    return &dhd->pub;
}

 

 
我們再來分析一下dhd_attach_net
dhd_attach_net(dhd_pub_t *dhdp, bool need_rtnl_lock)
{
    struct net_device *primary_ndev;
    /* Register primary net device , 這里的need_rtnl_lock=true */
    if (dhd_register_if(dhdp, 0, need_rtnl_lock) != 0) {
        return BCME_ERROR;
    }
#if defined(WL_CFG80211)
    primary_ndev =  dhd_linux_get_primary_netdev(dhdp);
    /* 加入到cfg80211里面,cfg80211是Linux 802.11用於管理配置的一套API,
     * 它是用戶和驅動之間的橋梁,替代了WEXT,提供和802.11相關的功能*/
    if (wl_cfg80211_net_attach(primary_ndev) < 0) {
        /* fail the init */
        dhd_remove_if(dhdp, 0, TRUE);
        return BCME_ERROR;
    }
#endif /* WL_CFG80211 */
    return BCME_OK;
}

 

 
重點是dhd_register_if
dhd_register_if(dhd_pub_t *dhdp, int ifidx, bool need_rtnl_lock)
{
    ifp = dhd->iflist[ifidx]; 
     /* 首先從剛才添加的接口列表中取出net,然后進行下面的系列初始化工作*/
    net = ifp->net; 
    /
    net->netdev_ops = &dhd_ops_virt;
    /* Ok, link into the network layer... */
    if (ifidx == 0) {
        /*
         * device functions for the primary interface only
         * 網絡設備注冊,啟用,停止,發送數據幀,選擇網卡隊列
         *(對於支持網卡多隊列的),設置網絡設備mac地址
         */
        net->netdev_ops = &dhd_ops_pri;
        if (!ETHER_ISNULLADDR(dhd->pub.mac.octet))
            memcpy(temp_addr, dhd->pub.mac.octet, ETHER_ADDR_LEN);
    } else {
        /*
         * We have to use the primary MAC for virtual interfaces
         */
        memcpy(temp_addr, ifp->mac_addr, ETHER_ADDR_LEN);
        /*
         * Android sets the locally administered bit to indicate that this is a
         * portable hotspot.  This will not work in simultaneous AP/STA mode,
         * nor with P2P.  Need to set the Donlge's MAC address, and then use that.
         */
        if (!memcmp(temp_addr, dhd->iflist[0]->mac_addr,
            ETHER_ADDR_LEN)) {
            DHD_ERROR(("%s interface [%s]: set locally administered bit in MAC\n",
            __func__, net->name));
            temp_addr[0] |= 0x02;
        }
    }
    net->hard_header_len = ETH_HLEN + dhd->pub.hdrlen;
    /* ethtool操作函數,ethtool 是用於查詢及設置網卡參數的命令。*/
    net->ethtool_ops = &dhd_ethtool_ops;
#if defined(WL_WIRELESS_EXT)
#if WIRELESS_EXT < 19
    net->get_wireless_stats = dhd_get_wireless_stats;
#endif /* WIRELESS_EXT < 19 */
#if WIRELESS_EXT > 12
    /* 這里的初始化工作很重要,之后的ioctl流程會涉及到對它的使用 */
    net->wireless_handlers = &wl_iw_handler_def;
#endif /* WIRELESS_EXT > 12 */
#endif /* defined(WL_WIRELESS_EXT) */
    dhd->pub.rxsz = DBUS_RX_BUFFER_SIZE_DHD(net);
    memcpy(net->dev_addr, temp_addr, ETHER_ADDR_LEN);
    if (ifidx == 0)
        printf("%s\n", dhd_version);
    else {
#ifdef WL_EXT_IAPSTA
        wl_ext_iapsta_update_net_device(net, ifidx);
#endif /* WL_EXT_IAPSTA */
        if (dhd->pub.up == 1) {
            /* 設置mac地址 */
            if (_dhd_set_mac_address(dhd, ifidx, net->dev_addr, FALSE) == 0)
                DHD_INFO(("%s: MACID is overwritten\n", __FUNCTION__));
            else
                DHD_ERROR(("%s: _dhd_set_mac_address() failed\n", __FUNCTION__));
        }
    }
    if (need_rtnl_lock)
        /* 注冊net設備*/
        err = register_netdev(net);
    else
        err = register_netdevice(net);
    printf("Register interface [%s]  MAC: "MACDBG"\n\n", net->name,
        MAC2STRDBG(net->dev_addr));
    net->netdev_ops = NULL;
    return err;
}
 
 
參考:
wifi底層學習之路:
wifi詳解:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM