nexus5 博通芯片WIFI详解 (4)
1 IOCTL的调用逻辑
之所以要分析这个,是因为上层wpa_supplicant和WIFI驱动打交道的方式,多半是通过ioctl的方式进行的,所以看看它的调用逻辑(这里只列出其主要的调用逻辑):
上面便是用户ioctl调用的流程图,它最终分为两条线即有两种支持,选择那一条或两条都选(个人感觉最好选第2条线,因为它最后也是会调用到相应的函数的,而且还有其它更多的命令支持),从实际的代码来看,如果dev->netdev_ops
->ndo_do_ioctl被初始化了,那么它一定会被调用,是否被初始化,在前面选择对net结构变量的初始化方式中有讨论过。
下面来具体看看该调用流程,首先说明下,上面的流程主要实现在kernel/net/wireless/wext_core.c文件中,这是wireless的协议层实现,恰好我们在wpa_supplicant中通常选择的驱动类型也是wext,它的入口函数是wext_ioctl_dispatch:
/* entry point from dev ioctl*/
static int wext_ioctl_dispatch(struct net *net, struct ifreq*ifr,
unsigned int cmd, struct iw_request_info *info,
wext_ioctl_func standard,
wext_ioctl_funcprivate)
{
int ret = wext_permission_check(cmd);
if (ret)
return ret;
dev_load(net, ifr->ifr_name);
rtnl_lock();
ret = wireless_process_ioctl(net, ifr, cmd, info, standard,private);
rtnl_unlock();
return ret;
}
它其实就是wireless_process_ioctl的封装函数,除了进行许可权限的确认,没有做什么其它内容,这里有standard和private两个函数指针的传递,其实就是两个回调函数,在后面会用到,它是由wext_handle_ioctl函数传递过来的:
int wext_handle_ioctl(structnet *net, struct ifreq *ifr, unsigned int cmd,
void __user *arg)
{
struct iw_request_info info = { .cmd =cmd, .flags = 0 };
int ret;
ret = wext_ioctl_dispatch(net, ifr, cmd, &info,
ioctl_standard_call,
ioctl_private_call); //这两个回调函数的定义之后再讨论,这里暂不理论
if (ret >= 0 &&
IW_IS_GET(cmd) &&
copy_to_user(arg, ifr, sizeof(structiwreq)))
return -EFAULT;
return ret;
}
实际上传递的就是ioctl_standard_call和ioctl_private_call两个函数,在看看wireless_process_ioctl函数,这个函数很重要,下面做重点分析:
static intwireless_process_ioctl(struct net *net, struct ifreq *ifr,
unsigned int cmd,
structiw_request_info *info,
wext_ioctl_func standard,
wext_ioctl_func private)
{
struct iwreq *iwr = (struct iwreq *)ifr;
struct net_device *dev;
iw_handler handler;
/* Permissions are already checked indev_ioctl() before calling us.
* The copy_to/from_user() of ifr isalso dealt with in there */
/* Make sure the device exist */
if ((dev = __dev_get_by_name(net, ifr->ifr_name)) == NULL) //通过网络接口名获取net_device设备
return -ENODEV;
/* A bunch of special cases, then thegeneric case...
* Note that 'cmd' is already filteredin dev_ioctl() with
* (cmd >= SIOCIWFIRST &&cmd <= SIOCIWLAST) */
if (cmd == SIOCGIWSTATS)
returnstandard(dev, iwr, cmd, info,
&iw_handler_get_iwstats); //如果是状态查询命令,调用该函数(回调函数中的一个)
#ifdef CONFIG_WEXT_PRIV
if (cmd == SIOCGIWPRIV && dev->wireless_handlers)
returnstandard(dev, iwr, cmd, info,
iw_handler_get_private); //如果是专有命令,调用回调函数,同上
#endif
/* Basic check */
if (!netif_device_present(dev))
return -ENODEV;
/* New driver API : try to find thehandler */
handler = get_handler(dev, cmd); //根据cmd参数,从dev成员中查询相应的处理函数
if (handler) {
/* Standard and private are notthe same */
if (cmd < SIOCIWFIRSTPRIV)
return standard(dev, iwr, cmd, info, handler); //调用相应命令的处理函数
else if (private)
return private(dev, iwr, cmd, info, handler); //同上
}
/* Old driver API : call driver ioctlhandler */
if(dev->netdev_ops->ndo_do_ioctl)
return dev->netdev_ops->ndo_do_ioctl(dev,ifr, cmd); //如果被设置就调用该函数
return -EOPNOTSUPP;
}
该函数的大意是,通过网络接口名称获得一个网络设备,然后根据命令的类型调用相应的处理函数,特别的是当dev->netdev_ops->ndo_do_ioctl或dev->wireless_handlers被设置时,则会查找执行对应的处理函数。Get_handle函数用于查询处理函数使用:
static iw_handlerget_handler(struct net_device *dev, unsigned int cmd)
{
/* Don't "optimise" thefollowing variable, it will crash */
unsigned int index; /* *MUST* be unsigned */
const struct iw_handler_def *handlers = NULL;
#ifdef CONFIG_CFG80211_WEXT
if (dev->ieee80211_ptr &&dev->ieee80211_ptr->wiphy)
handlers =dev->ieee80211_ptr->wiphy->wext; //初始化默认的处理函数
#endif
#ifdef CONFIG_WIRELESS_EXT
if (dev->wireless_handlers)
handlers= dev->wireless_handlers; //这里的dev->wireless_handlers在net初始化时被作为扩张功能选择性的设置,前面有提到过
#endif
if (!handlers)
return NULL;
/* Try as a standard command */
index = IW_IOCTL_IDX(cmd);
if (index <handlers->num_standard)
returnhandlers->standard[index]; //返回对应的标准函数
#ifdef CONFIG_WEXT_PRIV
/* Try as a private command */
index = cmd - SIOCIWFIRSTPRIV;
if (index <handlers->num_private)
return handlers->private[index]; //返回对应的专有函数
#endif
/* Not found */
return NULL;
}
那么这个dev->wireless_handlers究竟是什么,这里来揭开它的神秘面纱,在bcm4329源码src/wl/sys/wl_iw.c中,有它的定义:
static const iw_handler wl_iw_handler[]=
{
(iw_handler) wl_iw_config_commit,
(iw_handler) wl_iw_get_name,
(iw_handler) NULL,
......
}
static const iw_handler wl_iw_priv_handler[]= {
NULL,
(iw_handler)wl_iw_set_active_scan,
NULL,
(iw_handler)wl_iw_get_rssi,
......
}
const struct iw_handler_def wl_iw_handler_def =
{
.num_standard =ARRAYSIZE(wl_iw_handler),
.standard = (iw_handler *) wl_iw_handler,
.num_private = ARRAYSIZE(wl_iw_priv_handler),
.num_private_args =ARRAY_SIZE(wl_iw_priv_args),
.private = (iw_handler *)wl_iw_priv_handler,
.private_args = (void *)wl_iw_priv_args,
#if WIRELESS_EXT >= 19
get_wireless_stats:dhd_get_wireless_stats,
#endif
};
#endif
在net初始化的时候,这里把dev->wireless_handlers和dev->netdev_ops的初始化代码再贴出来:
int
dhd_net_attach(dhd_pub_t*dhdp, int ifidx)
{
……
#if (LINUX_VERSION_CODE <KERNEL_VERSION(2, 6, 31))
ASSERT(!net->open);
net->get_stats = dhd_get_stats;
net->do_ioctl =dhd_ioctl_entry;
net->hard_start_xmit = dhd_start_xmit;
net->set_mac_address = dhd_set_mac_address;
net->set_multicast_list = dhd_set_multicast_list;
net->open =net->stop = NULL;
#else
ASSERT(!net->netdev_ops);
net->netdev_ops = &dhd_ops_virt;
#endif
……
#if WIRELESS_EXT > 12
net->wireless_handlers = (struct iw_handler_def*)&wl_iw_handler_def; //这里的初始化工作很重要,之后的ioctl流程会涉及到对它的使用
#endif /* WIRELESS_EXT > 12*/
……
}
看到这里,应该可以明白相应的命令最终会在wl_iw.c中被执行,这些处理函数也是在该文件中实现。上面已经获取了命令的处理函数,那么它是如何被执行的呢?这里wireless_process_ioctl里有standard和private的回调函数的调用:
static intioctl_standard_call(struct net_device * dev,
structiwreq *iwr,
unsigned int cmd,
structiw_request_info *info,
iw_handler handler)
{
const struct iw_ioctl_description* descr;
int ret = -EINVAL;
/* Get the description of the IOCTL */
if (IW_IOCTL_IDX(cmd) >=standard_ioctl_num)
return -EOPNOTSUPP;
descr =&(standard_ioctl[IW_IOCTL_IDX(cmd)]);
/* Check if we have a pointer to userspace data or not */
if (descr->header_type !=IW_HEADER_TYPE_POINT) {
/* No extra arguments. Trivialto handle */
ret = handler(dev, info, &(iwr->u),NULL);
/* Generate an event to notifylisteners of the change */
if ((descr->flags &IW_DESCR_FLAG_EVENT) &&
((ret == 0) || (ret ==-EIWCOMMIT)))
wireless_send_event(dev, cmd, &(iwr->u),NULL);
} else {
ret =ioctl_standard_iw_point(&iwr->u.data, cmd, descr,
handler, dev, info);
}
/* Call commit handler if needed anddefined */
if (ret == -EIWCOMMIT)
ret =call_commit_handler(dev);
/* Here, we will generate theappropriate event if needed */
return ret;
}
回调函数中对传递过来的handler函数指针进行呼叫,对应的处理函数就会被执行,当然用户传送的命令还不止这些,所以才会有net->netdev_ops的存在的必要性。下面来就来看看执行到:
return dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd); //wireless_process_ioctl的最后一句
就会调用dhd_ioctl函数,这是wlan驱动对ioctl调用的处理函数,就是根据用户传递过来的cmd,给它找一个最合适最合理的“归宿”。
static int
dhd_ioctl_entry(structnet_device *net, struct ifreq *ifr, int cmd)
{
......#ifdefined(CONFIG_WIRELESS_EXT)
/* linux wireless extensions */
if ((cmd >= SIOCIWFIRST) &&(cmd <= SIOCIWLAST)) {
/* may recurse, do NOT lock */
ret = wl_iw_ioctl(net, ifr, cmd);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return ret;
}
#endif /*defined(CONFIG_WIRELESS_EXT) */
#if LINUX_VERSION_CODE >KERNEL_VERSION(2, 4, 2)
if (cmd == SIOCETHTOOL) {
ret = dhd_ethtool(dhd,(void*)ifr->ifr_data);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return ret;
}
#endif /* LINUX_VERSION_CODE> KERNEL_VERSION(2, 4, 2) */
if (cmd == SIOCDEVPRIVATE+1) {
ret = wl_android_priv_cmd(net, ifr, cmd);
dhd_check_hang(net,&dhd->pub, ret);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return ret;
}
if (cmd != SIOCDEVPRIVATE) {
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return -EOPNOTSUPP;
}
memset(&ioc, 0, sizeof(ioc));
......
bcmerror = dhd_wl_ioctl(&dhd->pub, ifidx, (wl_ioctl_t*)&ioc, buf, buflen);
......
}
限于篇幅,该函数处理过程不再详述,大致的命令处理方法相似,wl_iw.c中的系列处理函数只是其中的一部分,wl_android中和dhd_linux.c也有相应的处理函数。
2 数据的传送
2.1 数据传送过程简述
传送指的是通过一个网络连接发送一个报文的行为.。无论何时内核需要传送一个数据报文, 它都必须调用驱动的 hard_start_xmit 方法将数据放在外出队列上。
2.2 Bcm4329芯片wlan驱动数据传送
dhd_start_xmit(struct sk_buff *skb,struct net_device *net)
if (!(pktbuf =PKTFRMNATIVE(dhd->pub.osh, skb))) {
DHD_ERROR(("%s:PKTFRMNATIVE failed\n",
dhd_ifname(&dhd->pub, ifidx)));
dev_kfree_skb_any(skb); //转换成功,释放skb,在通常处理中,会在中断中做该操作
if (htsfdlystat_sz &&PKTLEN(dhd->pub.osh, pktbuf) >= ETHER_ADDR_LEN) {
uint8 *pktdata = (uint8*)PKTDATA(dhd->pub.osh, pktbuf);
struct ether_header *eh =(struct ether_header *)pktdata;
if(!ETHER_ISMULTI(eh->ether_dhost) &&
(ntoh16(eh->ether_type) == ETHER_TYPE_IP)) {
eh->ether_type =hton16(ETHER_TYPE_BRCM_PKTDLYSTATS);
ret = dhd_sendpkt(&dhd->pub, ifidx,pktbuf); //发送pktbuf
dhd_sendpkt(dhd_pub_t *dhdp, intifidx, void *pktbuf)
if (dhdp->wlfc_state &&((athost_wl_status_info_t*)dhdp->wlfc_state)->proptxstatus_mode
ret =dhd_wlfc_enque_sendq(dhdp->wlfc_state, DHD_PKTTAG_FIFO(PKTTAG(pktbuf)),
dhd_wlfc_commit_packets(dhdp->wlfc_state, (f_commitpkt_t)dhd_bus_txdata,
if(((athost_wl_status_info_t*)dhdp->wlfc_state)->toggle_host_if) {
((athost_wl_status_info_t*)dhdp->wlfc_state)->toggle_host_if= 0;
ret = dhd_bus_txdata(dhdp->bus, pktbuf); //在SDIO总线上传输
ret = dhd_bus_txdata(dhdp->bus, pktbuf);
传输结束后,会产生一个中断,即传输结束中断,一般的网络驱动程序都会有这个中断的注册,但还有一种轮询方式,这在后面的数据的接收部分会有介绍,而sk_buf就在这个中断处理函数中被释放。
2.3 传输超时
与真实硬件打交道的大部分驱动不得不预备处理硬件偶尔不能响应。接口可能忘记它们在做什么,或者系统可能丢失中断。
当发生传送超时, 驱动必须在接口统计量中标记这个错误, 并安排设备被复位到一个干净的能发送新报文的状态,一般驱动会调用netif_wake_queue函数重新启动传输队列。
3 数据的接收
3.1 数据接收的方式和过程
从网络上接收报文比发送它要难一些,因为必须分配一个 sk_buff 并从一个原子性上下文中递交给上层。网络驱动可以实现 2 种报文接收的模式:中断驱动和查询,大部分驱动采用中断驱动技术。
大部分硬件接口通过一个中断处理来控制,硬件中断处理器来发出 2 种可能的信号:一个新报文到了或者一个外出报文的发送完成了。网络接口也能够产生中断来指示错误, 例如状态改变, 等等。
注意:这里的对设备数据的操作是在锁得保护下完成的,做一最后还要释放掉锁。
3.2 选择哪种接收模式
3.3 Bcm4329芯片wlan驱动数据传送
下面来看看这两种方式的初始化(在dhd_attach.c):
/* Set up the bottom halfhandler */
PROC_START(dhd_dpc_thread, dhd,&dhd->thr_dpc_ctl, 0);
tasklet_init(&dhd->tasklet, dhd_dpc,(ulong)dhd);
tsk_ctl_t *tsk = (tsk_ctl_t *)data;
dhd_info_t *dhd = (dhd_info_t*)tsk->parent;
/* This thread doesn't need anyuser-level access,
* so get rid of all our resources
param.sched_priority =(dhd_dpc_prio < MAX_RT_PRIO)?dhd_dpc_prio:(MAX_RT_PRIO-1);
setScheduler(current, SCHED_FIFO,¶m);
/* DHD_OS_WAKE_LOCK is called indhd_sched_dpc[dhd_linux.c] down below */
/* signal: thread has started */
/* Run until signal received */
if (down_interruptible(&tsk->sema)== 0) {
/* Call bus dpc unlessit indicated down (then clean stop) */
if (dhd->pub.busstate!= DHD_BUS_DOWN) {
if (dhd_bus_dpc(dhd->pub.bus)) {
DHD_OS_WAKE_UNLOCK(&dhd->pub);
dhd_bus_stop(dhd->pub.bus, TRUE);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
complete_and_exit(&tsk->completed, 0);
这里是一个永真循环,直到接收到终止信号才停止,该线程就是通过不断调用dhd_bus_dpc函数调用实现轮询的,它的调用逻辑如下所示:
dhdsdio_probe(uint16 venid, uint16devid, uint16 bus_no, uint16 slot,
uint16 func, uint bustype, void*regsva, osl_t * osh, void *sdh)
/* Register interrupt callback,but mask it (not operational yet). */
DHD_INTR(("%s: disableSDIO interrupts (not interested yet)\n", __FUNCTION__));
bcmsdh_intr_disable(sdh); //首先禁止SDIO中断,再注册中断
if ((ret= bcmsdh_intr_reg(sdh, dhdsdio_isr, bus)) != 0) {
DHD_ERROR(("%s:FAILED: bcmsdh_intr_reg returned %d\n",
DHD_INTR(("%s: registeredSDIO interrupt function ok\n", __FUNCTION__));
DHD_INFO(("%s: SDIOinterrupt function is NOT registered due to polling mode\n",
看看Dhdsdio_isr这个中断处理函数干了什么?在函数的最后部分是:
DHD_TRACE(("Calling dhdsdio_dpc()from %s\n", __FUNCTION__));
Dhd_sched_dpc函数在最后被调用(上面的while循环调用dhdsdio_dpc,其实和下面的这个调用函数最后的作用是一样的,就不予详述),这个函数的代码如下:
dhd_sched_dpc(dhd_pub_t *dhdp)
dhd_info_t *dhd = (dhd_info_t*)dhdp->info;
if (dhd->thr_dpc_ctl.thr_pid >=0) {
tasklet_schedule(&dhd->tasklet);
就是触发一个中断的下半部tasklet,让cpu选择在一个合适的时候调用dhd_dpc函数,这个函数会调用dhd_bus_dpc,然后进入上面流程图的调用逻辑。
4 电源管理相关的调用逻辑
电源管理始终是手机等移动设备最重要的一个功能,尤其对于Android这种智能手机或者说手机电脑化的设备,电源管理更显得十分重要。
对于android平台上整个系统是如何一步一步进入休眠的,我这里不做详细介绍,只作出它的大致流程图:
dhd_attach(osl_t *osh, structdhd_bus *bus, uint bus_hdrlen)
#ifdef CONFIG_HAS_EARLYSUSPEND
dhd->early_suspend.level =EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 20;
dhd->early_suspend.suspend = dhd_early_suspend;
dhd->early_suspend.resume = dhd_late_resume;
register_early_suspend(&dhd->early_suspend);
dhd_state |= DHD_ATTACH_STATE_EARLYSUSPEND_DONE;
红色区域初始化了dhd结构的两个early_suspend函数,并将其注册到电源管理系统。early_suspend的休眠函数的代码如下:
static void dhd_early_suspend(structearly_suspend *h)
struct dhd_info *dhd = container_of(h,struct dhd_info, early_suspend);
DHD_TRACE(("%s: enter\n",__FUNCTION__));
dhd_suspend_resume_helper(dhd, 1);
调用dhd_suspend_resume_helper函数,别看函数名中有resume单词,其实early_suspend和late_resume都是通过这个函数实现功能的:
static void dhd_suspend_resume_helper(structdhd_info *dhd, int val)
/* Set flag when early suspend wascalled */
if ((!dhdp->suspend_disable_flag)&& (dhd_check_ap_wfd_mode_set(dhdp) == FALSE))
static int dhd_set_suspend(intvalue, dhd_pub_t *dhd)
if(value && dhd->in_suspend) { //early_suspend
DHD_ERROR(("%s: force extra Suspend setting \n",__FUNCTION__));
dhd_wl_ioctl_cmd(dhd,WLC_SET_PM, (char *)&power_mode,
/* Enablepacket filter, only allow unicast packet to send up */
dhd_set_packet_filter(1, dhd);
/* If DTIM skipis set up as default, force it to wake
* each thirdDTIM for better power savings. Note that
* one sideeffect is a chance to miss BC/MC packet.
bcn_li_dtim =dhd_get_dtim_skip(dhd);
bcm_mkiovar("bcn_li_dtim",(char *)&bcn_li_dtim,
dhd_wl_ioctl_cmd(dhd,WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
/* Disable firmwareroaming during suspend */
bcm_mkiovar("roam_off", (char *)&roamvar, 4,
dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
DHD_TRACE(("%s: Remove extra suspend setting \n",__FUNCTION__));
dhd_wl_ioctl_cmd(dhd, WLC_SET_PM, (char *)&power_mode,
/* restorepre-suspend setting for dtim_skip */
bcm_mkiovar("bcn_li_dtim", (char *)&dhd->dtim_skip,
dhd_wl_ioctl_cmd(dhd,WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
bcm_mkiovar("roam_off", (char *)&roamvar, 4, iovbuf,
dhd_wl_ioctl_cmd(dhd,WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
static struct platform_driverwifi_device = {
static struct platform_driverwifi_device_legacy = {
DHD_TRACE(("## Callingplatform_driver_register\n"));
platform_driver_register(&wifi_device);
platform_driver_register(&wifi_device_legacy);
Wifi_suspend和wifi_resume随着wifi_device设备的注册而注册,这样当系统进入suspend流程后,就可以调用每个设备上的电源管理函数来使设备进入休眠状态了。
static int wifi_suspend(structplatform_device *pdev, pm_message_t state)
DHD_TRACE(("##> %s\n",__FUNCTION__));
#if (LINUX_VERSION_CODE <=KERNEL_VERSION(2, 6, 39)) && defined(OOB_INTR_ONLY)
static int wifi_resume(structplatform_device *pdev)
DHD_TRACE(("##> %s\n",__FUNCTION__));
#if (LINUX_VERSION_CODE <=KERNEL_VERSION(2, 6, 39)) && defined(OOB_INTR_ONLY)
if (dhd_os_check_if_up(bcmsdh_get_drvdata()))
PATH:bcmsdio/sys/bcmsdh_linux.c
#if defined(OOB_INTR_ONLY) //该中断的使用需要配置
void bcmsdh_oob_intr_set(bool enable)
spin_lock_irqsave(&sdhcinfo->irq_lock, flags);
enable_irq(sdhcinfo->oob_irq);
disable_irq_nosync(sdhcinfo->oob_irq);
spin_unlock_irqrestore(&sdhcinfo->irq_lock, flags);
dhd_open(struct net_device *net)
if (dhd->pub.busstate !=DHD_BUS_DATA) {
if ((ret = dhd_bus_start(&dhd->pub)) !=0) {
DHD_ERROR(("%s: failed with code %d\n", __FUNCTION__, ret));
dhd_bus_start(dhd_pub_t *dhdp)
/* Host registration for OOB interrupt*/
if(bcmsdh_register_oob_intr(dhdp)) {
nexus5 博通芯片WIFI详解 (4)相关推荐
- nexus5 博通芯片WIFI详解 (3)
1 WLAN驱动结构介绍 1.1 SDIO驱动 在drivers/mmc下面是mmc卡,SD卡和SDIO卡驱动部分,其中包括host驱动,card驱动和core部分,由于网络接 ...
- nexus5 博通芯片WIFI详解 (1)
1 WLAN技术 WLAN是英文WirelessLAN的缩写,就是无线局域网的意思.无线以太网技术是一种基于无线传输的局域网技术,与有线网络技术相比,具有灵活.建网迅速.个人化等特点.将 ...
- nexus5 博通芯片WIFI详解 (2)
1 Wifi模块解析和启动流程 1.1 框架分析 WIFI整体框架如图所示: 首先,用户程序使用WifiManager类来管理Wifi模块,它能够获得Wifi模块的状态,配置和 ...
- 国产GPU的发展历程及芯片性能详解
一.国产GPU的发展历程 二.国产GPU进口代替的紧迫性 三.景嘉微:具有完全自主知识产权,打破国外GPU长期垄断 四.景嘉微国产GPU芯片概述 五.景嘉微国产GPU芯片性能详解 六.景嘉微国产GPU ...
- 利用和讯博客赚钱步骤详解
利用和讯博客赚钱步骤详解 来自朋友邻居大唐 玩博客的时间也不短了,原来一直在博客天下做,现在搬来和讯了.感觉这里很好,准备坚持做下去.踏进和讯的大门,发现这里的朋友都在玩Google AdSense, ...
- android wifi驱动_OTT盒子WiFi方案首选:博通2T2R WiFi模块
IT6356 WiFi模组简介 WiFi/BT/FM 3合1模块: 此模块为 2.4G + 5G 双频道,支持802.11a/b/g/n/AC,支持蓝牙 4.0:2T2R 双天线. 具有以下优势: 1 ...
- 通过源码详解 Servlet
Servlet 结构 1.Servlet Servlet 该接口定义了5个方法. init(),初始化 servlet 对象,完成一些初始化工作.它是由 servlet 容器控制的,该方法只能被调用一 ...
- linux中关于wifi函数,wifi详解(五)
user interface Android WiFiService WPA_Supplicant DHD Driver Dongle Binary BCM43xxHardware 蓝色:需要修改 黑 ...
- [转载]Android Wi-Fi 设置country code的调用流程(博通芯片为base)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/xiaoxiangyuhai/artic ...
最新文章
- js 获取sessionid_百战卓越班学员学习经验分享:页面js代码
- 20返回指针的函数与指向函数的指针
- java中静态代码块的用法 static用法详解(转)
- reactNative 打包那些事儿
- java se 动态添加视图组件_博为峰Java技术题 ——JavaSE Java Swing在顶层容器中添加菜单栏Ⅰ...
- c mysql 插入大量数据_C++操作MySQL大量数据插入效率低下的解决方法
- guns使用注意问题
- Linux下的/etc/ssh/ssh_config文件配置详解SSH配置文件相关参数详细说明
- ASPack压缩可执行文件
- 【思维导图】LAMPer技能树
- windows10 禁用Device/Credential Guard解决方案
- Scratch 3.0建站指南(一)
- MCC(移动国家码)和 MNC(移动网络码)
- VS发布网站时,报错提示:“未能将文件xxx复制到xxx,未能找到文件xx”三种解决方案!...
- NLTK读书笔记 — 分类与标注
- 【MP4格式转换成MP3教程】
- 我的 keylogger 终于搞定了。。
- 在manjaro中开启多VLAN,配置永久静态IP,使用systemd-networkd配置
- 浅谈---免费ARP 【Gratuitous ARP】
- 本市天气(百度定位与车联网之天气查询)