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, ®s); 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底层学习之路:
wifi详解: