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详解:
posted @ 2022-04-04 19:44  luoyuna  阅读(2216)  评论(1编辑  收藏  举报