ieee80211

  802.11协议簇是国际电工电子工程学会(IEEE)为无线局域网络制定的标准。

概述

  • nl80211: 用于对无线设备进行配置管理,它是一个基本Netlink的用户态协议(User态)
  • cfg80211: 用于对无线设备进行配置管理。与FullMAC, mac80211和nl80211一起工作。(Kernel态)
  • mac80211: 是一个driver开发者可用于为SoftMAC无线设备写驱动的框架 (Kernel态)。
  • WNIC : Wireless Network Interface Controller, 它总是指望硬件执行协议(如IEEE802.11)描述的功能。
  • MLME: 即MAC(Media Access Control ) Layer Management Entity,它管理物理层MAC状态机。
  • SoftMAC: 其MLME由软件实现,mac80211为SoftMAC实现提供了一个driver API。 即:SoftMAC设备允许对硬件执行更好地控制,允许用软件实现对802.11的帧管理,包括解析和产生802.11无线帧。目前大多数802.11设备为SoftMAC,而FullMAC设备较少。
  • FullMAC: 其MLME由硬件管理,当写FullMAC无线驱动时,不需要使用mac80211。
  • wpa_supplicant: 是用户空间一个应用程序,主要发起MLME命令,然后处理相关结果。

关联

  cfg80211是Linux 802.11配置API。cfg80211用于代码wext(Wireless-Extensions),nl80211用于配置一个cfg80211设备,且用于kernel与userspace间的通信。wext现处理维护状态,没有新的功能被增加,只是修改bug。如果需要通过wext操作,则需要定义CONFIG_CFG80211_WEXT。

  • cfg80211 and nl80211: 基于消息机制,使用netlink接口
  • wext: 基于ioctl机制
  • struct ieee80211_hw: 表示硬件信息和状态
  • ieee80211_alloc_hw:每个driver调用ieee80211_alloc_hw分配ieee80211_hw,且以ieee80211_ops为参数
  • ieee80211_register_hw:每个driver调用ieee80211_register_hw创建wlan0和 wmaster0,并进行各种初始化。
  • struct ieee80211_ops:每个driver实现它的成员函数,且它的成员函数都以struct ieee80211_hw做为第一个参数。在struct ieee80211_ops中定义了24个方法,以下7个方法必须实现:
    tx,start,stop,add_interface,remove_interface,config和configure_filter。

mac80211它是一个driver开发者可用于为SoftMAC无线设备写驱动的框架,mac80211为SoftMAC设备实现了cfg80211回调函数,且mac80211通过cfg80211实现了向网络子系统注册和配置。

数据传递过程

  mac80211和wifi设备驱动程序息息相关,但是它是内核抽象出来的设备无关层,每一个注册进来的wifi设备,它都帮助我们注册一个 net_device,并且使用统一的 ops 函数。那么网络协议上层传递过来的数据包首先经过mac80211层,然后才会传递到设备驱动层,再由硬件发送出去。
  

ath9k向mac80211的注册过程

  
  当 usb host 识别出 usb 设备为 ath9k 时,会调用到驱动程序中的 probe 函数。

static int ath9k_hif_usb_probe(struct usb_interface *interface,const struct usb_device_id *id)
{struct usb_device *udev = interface_to_usbdev(interface);struct hif_device_usb *hif_dev;int ret = 0;hif_dev = kzalloc(sizeof(struct hif_device_usb), GFP_KERNEL);usb_get_dev(udev);hif_dev->udev = udev;hif_dev->interface = interface;hif_dev->usb_device_id = id;//dev_set_drvdata(&intf->dev, data);usb_set_intfdata(interface, hif_dev);init_completion(&hif_dev->fw_done);/* Find out which firmware to load */if (IS_AR7010_DEVICE(id->driver_info))hif_dev->fw_name = FIRMWARE_AR7010_1_1;elsehif_dev->fw_name = FIRMWARE_AR9271;//加载固件,成功后调用 ath9k_hif_usb_firmware_cbret = request_firmware_nowait(THIS_MODULE, true, hif_dev->fw_name,&hif_dev->udev->dev, GFP_KERNEL,hif_dev, ath9k_hif_usb_firmware_cb);return 0;
}
static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context)
{struct hif_device_usb *hif_dev = context;int ret;//hif_dev->htc_handle->target->hif = hif_usbhif_dev->htc_handle = ath9k_htc_hw_alloc(hif_dev, &hif_usb, &hif_dev->udev->dev);hif_dev->firmware = fw;ret = ath9k_hif_usb_dev_init(hif_dev);ret = ath9k_htc_hw_init(hif_dev->htc_handle,&hif_dev->interface->dev,hif_dev->usb_device_id->idProduct,hif_dev->udev->product,hif_dev->usb_device_id->driver_info);complete(&hif_dev->fw_done);return;
}
static int ath9k_hif_usb_dev_init(struct hif_device_usb *hif_dev)
{struct usb_host_interface *alt = &hif_dev->interface->altsetting[0];struct usb_endpoint_descriptor *endp;int ret, idx;ret = ath9k_hif_usb_download_fw(hif_dev);ret = ath9k_hif_usb_alloc_urbs(hif_dev);return 0;
}
static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev)
{int transfer, err;const void *data = hif_dev->firmware->data;size_t len = hif_dev->firmware->size;u32 addr = AR9271_FIRMWARE;u8 *buf = kzalloc(4096, GFP_KERNEL);u32 firm_offset;if (!buf)return -ENOMEM;while (len) {transfer = min_t(size_t, len, 4096);memcpy(buf, data, transfer);//通过控制传输将固件传递给usb设备//FIRMWARE_DOWNLOAD并非标准的request,可能是厂家自己定义的//我们构造urb时无需关心端点的最大包大小,虽然确实是由多次传输构成的//比如主机请求一个长度16字节的描述符,设备收到请求之后会分两次发送给主机err = usb_control_msg(hif_dev->udev,usb_sndctrlpipe(hif_dev->udev, 0),FIRMWARE_DOWNLOAD, 0x40 | USB_DIR_OUT,addr >> 8, 0, buf, transfer, HZ);len -= transfer;data += transfer;addr += transfer;}kfree(buf);if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))firm_offset = AR7010_FIRMWARE_TEXT;elsefirm_offset = AR9271_FIRMWARE_TEXT;//通知设备固件下载完成err = usb_control_msg(hif_dev->udev, usb_sndctrlpipe(hif_dev->udev, 0),FIRMWARE_DOWNLOAD_COMP,0x40 | USB_DIR_OUT,firm_offset >> 8, 0, NULL, 0, HZ);return 0;
}
//数据传输4个同的端点
#define USB_WLAN_TX_PIPE  1
#define USB_WLAN_RX_PIPE  2
#define USB_REG_IN_PIPE   3
#define USB_REG_OUT_PIPE  4
static int ath9k_hif_usb_alloc_urbs(struct hif_device_usb *hif_dev)
{init_usb_anchor(&hif_dev->regout_submitted);//分配一个tx_buf绑定一个urb,把tx_buf挂入hif_dev->tx.tx_buf链表ath9k_hif_usb_alloc_tx_urbs(hif_dev)ath9k_hif_usb_alloc_rx_urbs(hif_dev)ath9k_hif_usb_alloc_reg_in_urbs(hif_dev)return 0;
}

数据urb,有数据到来调用 ath9k_hif_usb_rx_cb

static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
{struct urb *urb = NULL;struct sk_buff *skb = NULL;int i, ret;init_usb_anchor(&hif_dev->rx_submitted);spin_lock_init(&hif_dev->rx_lock);for (i = 0; i < MAX_RX_URB_NUM; i++) {urb = usb_alloc_urb(0, GFP_KERNEL);skb = alloc_skb(MAX_RX_BUF_SIZE, GFP_KERNEL);//提交urb 有数据到来时调用 ath9k_hif_usb_rx_cbusb_fill_bulk_urb(urb, hif_dev->udev,usb_rcvbulkpipe(hif_dev->udev,USB_WLAN_RX_PIPE),skb->data, MAX_RX_BUF_SIZE,ath9k_hif_usb_rx_cb, skb);usb_anchor_urb(urb, &hif_dev->rx_submitted);ret = usb_submit_urb(urb, GFP_KERNEL);//当urb的引用计数为0时释放内存usb_free_urb(urb);}return 0;
}

控制数据的urb ?,有控制数据到来调用 ath9k_hif_usb_reg_in_cb

static int ath9k_hif_usb_alloc_reg_in_urbs(struct hif_device_usb *hif_dev)
{struct urb *urb = NULL;struct sk_buff *skb = NULL;int i, ret;init_usb_anchor(&hif_dev->reg_in_submitted);for (i = 0; i < MAX_REG_IN_URB_NUM; i++) {urb = usb_alloc_urb(0, GFP_KERNEL);skb = alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_KERNEL);usb_fill_bulk_urb(urb, hif_dev->udev,usb_rcvbulkpipe(hif_dev->udev,USB_REG_IN_PIPE),skb->data, MAX_REG_IN_BUF_SIZE,ath9k_hif_usb_reg_in_cb, skb);usb_anchor_urb(urb, &hif_dev->reg_in_submitted);ret = usb_submit_urb(urb, GFP_KERNEL);usb_free_urb(urb);}return 0;
}
int ath9k_htc_hw_init(struct htc_target *target,struct device *dev, u16 devid,char *product, u32 drv_info)
{if (ath9k_htc_probe_device(target, dev, devid, product, drv_info)) {pr_err("Failed to initialize the device\n");return -ENODEV;}return 0;
}
int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev,u16 devid, char *product, u32 drv_info)
{struct ieee80211_hw *hw;struct ath9k_htc_priv *priv;int ret;//分配一个无线设备结构,绑定一个操作函数集hw = ieee80211_alloc_hw(sizeof(struct ath9k_htc_priv), &ath9k_htc_ops);priv = hw->priv;priv->hw = hw;priv->htc = htc_handle;priv->dev = dev;htc_handle->drv_priv = priv;SET_IEEE80211_DEV(hw, priv->dev);ret = ath9k_htc_wait_for_target(priv);priv->wmi = ath9k_init_wmi(priv);ret = ath9k_init_htc_services(priv, devid, drv_info);ret = ath9k_init_device(priv, devid, product, drv_info);return 0;
}
ath9k_htc_probe_device(target, dev, devid, product, drv_info))//在内核中wiphy(包括local)来描述一个线设备,在这里构造一个,并指定操作函数ieee80211_alloc_hw(sizeof(struct ath9k_htc_priv), &ath9k_htc_ops);struct wiphy * wiphy = wiphy_new(&mac80211_config_ops, priv_size);struct ieee80211_local *local = wiphy_priv(wiphy);local->ops = ops;tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending,(unsigned long)local);tasklet_init(&local->tasklet,ieee80211_tasklet_handler,(unsigned long) local);ath9k_init_device(priv, devid, product, drv_info);ath9k_init_priv(priv, devid, product, drv_info);tasklet_init(&priv->rx_tasklet, ath9k_rx_tasklet, (unsigned long)priv);     ieee80211_register_hw(hw);  //硬件无关,无线子系统帮我们构造了一个netdevice提供统一的ops:ieee80211_dataif_opswiphy_register(local->hw.wiphy);ieee80211_if_add(local, "wlan%d", NULL,NL80211_IFTYPE_STATION, NULL);alloc_netdev_mqs(sizeof(*sdata) + local->hw.vif_data_size,name, ieee80211_if_setup, txqs, 1);ieee80211_setup_sdata(sdata, type);dev->netdev_ops = &ieee80211_dataif_ops;register_netdevice(ndev);
struct ieee80211_ops ath9k_htc_ops = {.tx                 = ath9k_htc_tx,.start              = ath9k_htc_start,.stop               = ath9k_htc_stop,.add_interface      = ath9k_htc_add_interface,.remove_interface   = ath9k_htc_remove_interface,.config             = ath9k_htc_config,.configure_filter   = ath9k_htc_configure_filter,.sta_add            = ath9k_htc_sta_add,.sta_remove         = ath9k_htc_sta_remove,.conf_tx            = ath9k_htc_conf_tx,.bss_info_changed   = ath9k_htc_bss_info_changed,.set_key            = ath9k_htc_set_key,.get_tsf            = ath9k_htc_get_tsf,.set_tsf            = ath9k_htc_set_tsf,.reset_tsf          = ath9k_htc_reset_tsf,.ampdu_action       = ath9k_htc_ampdu_action,.sw_scan_start      = ath9k_htc_sw_scan_start,.sw_scan_complete   = ath9k_htc_sw_scan_complete,.set_rts_threshold  = ath9k_htc_set_rts_threshold,.rfkill_poll        = ath9k_htc_rfkill_poll_state,.set_coverage_class = ath9k_htc_set_coverage_class,.set_bitrate_mask   = ath9k_htc_set_bitrate_mask,.get_stats      = ath9k_htc_get_stats,
};

数据包发送过程

协议上层发送数据包过来,首先是到无线子系统帮我们注册的netdevice这里,调用
static const struct net_device_ops ieee80211_dataif_ops = {.ndo_open       = ieee80211_open,.ndo_stop       = ieee80211_stop,.ndo_uninit     = ieee80211_teardown_sdata,.ndo_start_xmit = ieee80211_subif_start_xmit,.ndo_set_rx_mode    = ieee80211_set_multicast_list,.ndo_change_mtu     = ieee80211_change_mtu,.ndo_set_mac_address    = ieee80211_change_mac,.ndo_select_queue   = ieee80211_netdev_select_queue,
};
中的 ieee80211_subif_start_xmit 
//硬件无关
ieee80211_xmit => ieee80211_tx
        => __ieee80211_tx
            => ieee80211_tx_frags
                => drv_tx
    local->ops->tx(&local->hw, skb); => ath9k_htc_tx
//硬件相关
static void ath9k_htc_tx(struct ieee80211_hw *hw, struct sk_buff *skb)ret = ath9k_htc_tx_start(priv, skb, slot, false);//判断是数据还是管理包,至于如何分辨的需要分析sk_buff,暂时保留if (ieee80211_is_data(hdr->frame_control))  ath9k_htc_tx_data(priv, vif, skb, sta_idx, vif_idx, slot, is_cab);elseath9k_htc_tx_mgmt(priv, avp, skb, sta_idx, vif_idx, slot);htc_send(priv->htc, skb);htc_issue_send(target, skb, skb->len, 0, tx_ctl->epid);hif_usb_send//根据数据包的类型,发送给对应的端点ret = hif_usb_send_tx(hif_dev, skb);    ret = hif_usb_send_regout(hif_dev, skb);usb_fill_bulk_urb(urb, hif_dev->udev, usb_sndbulkpipe(hif_dev->udev, USB_REG_OUT_PIPE),skb->data, skb->len, hif_usb_regout_cb, cmd);

数据包接收过程

  当一个数据包在空中被无线设备捕捉到后,硬件将会向内核发出一个中断(大部分 PCI 接口的设备这样做),或则通过轮询机制判断是否有数据到来(如,使用了 USB 接口)。
前者,中断将会引发中断处理程序的执行,后者促使特定的接收函数将被调用。

  一般设备驱动层的回调函数不会做太多关于接收数据包的操作,仅仅做数据校验,为 mac80211 填充接收描述符,然后把数据包推给 mac80211,由 mac80211 来做之后的工作(直接或间接将数据包放入接收队列)。
数据进入 mac80211 后,将会调用 ieee80211_rx 或者其他变种接收函数。在这里数据路径和管理路径也将分开进行。
  如果收到的帧是数据,它将被转换成 802.3 数据帧(通过 __ieee80211_data_to8023 实现),然后该数据帧将通过 netif_receive_skb 交付到网络协议栈。在协议栈中,各层网络协议将会对数据进行解析,识别协议首部。
  如果接收到的是控制帧,数据将会由 ieee80211_sta_rx_queued_mgmt 处理。部分控制帧在 mac80211 层就终止,另外一些将会通过 cfg80211 发送到用户空间下的管理程序。
  例如,身份认证控制帧被 cfg80211_rx_mlme_mgmt 处理,然后通过 nl80211_send_rx_auth 发送到用户空间下的 wpa_supplicant ; 相应的关联响应控制帧被 cfg80211_rx_assoc_resp 处理,并由 nl80211_send_rx_assoc 发送到用户空间。

//硬件相关
ath9k_hif_usb_alloc_urbs(hif_dev);static int ath9k_hif_usb_alloc_reg_in_urbs(struct hif_device_usb *hif_dev)usb_fill_bulk_urb(urb, hif_dev->udev, usb_rcvbulkpipe(hif_dev->udev, USB_REG_IN_PIPE), skb->data, MAX_REG_IN_BUF_SIZE,ath9k_hif_usb_reg_in_cb, skb);static void ath9k_hif_usb_reg_in_cb(struct urb *urb)ath9k_htc_rx_msg(hif_dev->htc_handle, skb, skb->len, USB_REG_IN_PIPE);endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv, skb, epid);static inline int ath9k_htc_connect_svc(struct ath9k_htc_priv *priv,req.ep_callbacks.rx = ath9k_htc_rxep;void ath9k_htc_rxep(void *drv_priv, struct sk_buff *skb, enum htc_endpoint_id ep_id)tasklet_schedule(&priv->rx_tasklet);void ath9k_rx_tasklet(unsigned long data)ieee80211_rx(priv->hw, skb);
//硬件无关
/** This is the receive path handler. It is called by a low level driver when an* 802.11 MPDU is received from the hardware.*/
void ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb)static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,struct sk_buff *skb)static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,struct sk_buff *skb, bool consume)static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx)static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx)
static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx)
{   CALL_RXH(ieee80211_rx_h_decrypt)CALL_RXH(ieee80211_rx_h_check_more_data)CALL_RXH(ieee80211_rx_h_uapsd_and_pspoll)CALL_RXH(ieee80211_rx_h_sta_process)CALL_RXH(ieee80211_rx_h_defragment)CALL_RXH(ieee80211_rx_h_michael_mic_verify)/* must be after MMIC verify so header is counted in MPDU mic */
#ifdef CONFIG_MAC80211_MESHif (ieee80211_vif_is_mesh(&rx->sdata->vif))CALL_RXH(ieee80211_rx_h_mesh_fwding);
#endifCALL_RXH(ieee80211_rx_h_amsdu)CALL_RXH(ieee80211_rx_h_data)       //数据CALL_RXH(ieee80211_rx_h_ctrl);      //控制CALL_RXH(ieee80211_rx_h_mgmt_check)CALL_RXH(ieee80211_rx_h_action)CALL_RXH(ieee80211_rx_h_userspace_mgmt)CALL_RXH(ieee80211_rx_h_action_return)CALL_RXH(ieee80211_rx_h_mgmt)       //管理
}
以数据为例:
ieee80211_rx_h_data(struct ieee80211_rx_data *rx)err = __ieee80211_data_to_8023(rx, &port_control);ieee80211_deliver_skb(rx);      dev_queue_xmit(xmit_skb);netif_receive_skb(skb);

管理过程

  理论上,我们可以像数据路径一样在用户空间下通过套接字发送控制帧。但是目前有很多开发得十分完善的用户层管理工具能完成这样的工作。
特别是 wpa_supplicant 和 host_apd 。wpa_supplicant 控制客户端 STA 模式下无线网络的连接,如扫描发现网络、身份认证、关联等。
而 host_apd 可以做 AP 。说白了前者就是用来连接热点,后者用来发射热点。这些用户层工具通过 netlink 套接字与内核通信。
内核中相关的回调接口是 cfg80211 中的 nl80211 。用户层的工具通过 netlink 提供的库(如, NL80211_CMD_TRIGGER_SCAN )将命令发送到内核。
在内核中,由 nl80211 接收应用层发出的命令。如下代码展示了对应绑定情况。

static int __init cfg80211_init(void)
{err = register_pernet_device(&cfg80211_pernet_ops);err = wiphy_sysfs_init();err = register_netdevice_notifier(&cfg80211_netdev_notifier);err = nl80211_init();
}
int nl80211_init(void)
{err = genl_register_family_with_ops(&nl80211_fam, nl80211_ops, ARRAY_SIZE(nl80211_ops));...
}
static struct genl_ops nl80211_ops[] = {{.cmd = NL80211_CMD_GET_WIPHY,.doit = nl80211_get_wiphy,.dumpit = nl80211_dump_wiphy,.policy = nl80211_policy,/* can be retrieved by unprivileged users */.internal_flags = NL80211_FLAG_NEED_WIPHY,},{.cmd = NL80211_CMD_TRIGGER_SCAN,.doit = nl80211_trigger_scan,.policy = nl80211_policy,.flags = GENL_ADMIN_PERM,.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |NL80211_FLAG_NEED_RTNL,},...
}
static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
{struct cfg80211_registered_device *rdev = info->user_ptr[0];err = rdev->ops->scan(&rdev->wiphy, dev, request);
}
在 mac80211 中, ieee80211_scan 将会具体去实现扫描发现网络的具体细节,最终还是会调到驱动层的操作函数
local->ops->sw_scan_start(&local->hw);

参考:

Linux Wireless架构总结
浅谈 Linux 内核无线子系统

ath9k usb wifi 网卡驱动浅析相关推荐

  1. USB WiFi网卡驱动分析--经典

    初始化: 在Linux下,驱动大多都是module,可以被自由的装载卸载.Rt73 usb wireless驱动也不例外,是一个可独立编译的module.Module一般都是由module_init入 ...

  2. USB WIFI网卡在S5PV210上的移植和使用最全攻略(1)

    目录 一.本季文章将会带来那些干货 1.MT7601网卡驱动的移植 2.无线网卡的配置和使用 3.在自己定制的rootfs中移植USB WIFI 4.在自己定制的rootf中添加dhcp支持 二.项目 ...

  3. win10安装wifi网卡驱动显示“这个 INF 中的服务安装段落无效“

    win10安装wifi网卡驱动显示"这个 INF 中的服务安装段落无效" 日志显示:Netwtw08.INF中的服务安装段落无效. 和控制管理器的显示一样 看了好多方案都不行,因为 ...

  4. win10下安装华为Atals USB虚拟网卡驱动

    1. 用USB连接电脑和HUAWEI Atlas 200 进入设备管理器,查看设备驱动安装情况 2. 安装驱动 (1) 在RNDIS上点击右键,选择更新驱动程序 (2) 接下来选择浏览我的电脑以查找驱 ...

  5. USB 3G网卡驱动流程

    USB 3G网卡驱动流程 简介 首先介绍一下linux下的整体驱动模式: 本文基于的linux kernel版本为2.6.36 (并且华为EM770W驱动,是由FriendlyARM公司定制的. 所以 ...

  6. USB WIFI网卡在S5PV210上的移植和使用最全攻略(2)

    目录 一.在自己制作的rootfs中移植网卡 1.确认自己制作的rootfs可正常工作并启动 2.需要的工具集确认 3.交叉编译iwconfig 二.移植wpa_supplicant并制作镜像 1.交 ...

  7. USB无线网卡-----MT7601 无线wifi网卡驱动移植

    备注:次博客为转载,原作者地址请点击此处 注意:本博客来源于朱老师项目积木-usb wifi移植部分,本博客仅供学习和交流 一.准备工作 1.MT7601驱动下载 点击下载 2.插入usb WiFi ...

  8. Linux驱动:在imx6ull上适配RT3070 USB WiFi网卡

    1.说明 如图,这是一款基于Ralink Technology RT3070芯片的USB Wi-Fi无线网卡和IEEE 802.11b/g/n标准,支持WEP 64/128,WPA,WPA2,TKIP ...

  9. Centos7.4.1708 安装usb无限网卡驱动

    今天总结一下前几天折腾的usb无线网卡驱动. 一.确定网卡驱动 实验机器内核版本信息如下: [root@localhost ~]# uname -a Linux localhost.localdoma ...

  10. MT7601 无线wifi网卡驱动移植

    注意:本博客来源于朱老师项目积木-usb wifi移植部分,本博客仅供学习和交流 一.准备工作 1.MT7601驱动下载 点击下载 2.插入usb WiFi 启动开发板linux,lsusb查看usb ...

最新文章

  1. 智能车竞赛技术报告 | 智能车视觉 - 首都师范大学 - 首师智能视觉
  2. python祝福祖国代码_国际文交所:9月17日-10月15日《祝福祖国信卡》《澳门爱与祝愿套票》《北京精神封》3个提货转仓公告...
  3. Vue中使用vue-croper插件实现图片上传裁剪并传到SpringBoot后台接口
  4. IIS+ASP+MySQL8.0+中文乱码解决方案(2019.7)
  5. Ring3下实现进程保护,不用hook
  6. 操作系统课设之Linux 进程间通信
  7. java设计模式2-观察者模式
  8. python抓包与解包_python 抓包与解包
  9. CSS去除链接虚线(兼容IE6、IE7)
  10. Flash Remoting+ Visual Studio .NET学习总结
  11. HDOJ(HDU) 1563 Find your present!(异或)
  12. 下两个网段转发的路由设置_一台路由器下如何实现多个网络互通
  13. NNDL 作业7:第五章课后题(1×1 卷积核 | CNN BP)
  14. 云端卫士实战录 React + Redux 前端项目实践
  15. 【HTML/CSS】从放弃到入门-笔记1
  16. “豪”秀上演——莱佛士学生作品精彩亮相施华蔻发布会
  17. 软件工程毕业设计课题(34)基于JAVA毕业设计JAVA医院预约挂号系统毕设作品项目
  18. 一张图解读小米公司的商业模式
  19. word插入标题之后自动跳到下一页怎么解决?
  20. mysql数据库显示unknown option '-d' 错误的处理办法

热门文章

  1. noi2018还没想好记
  2. linux 多核 双系统,Linux GRUB实现双系统引导教程
  3. 中国 省市区 code码
  4. 计算机保研面试之机器学习
  5. 用一台笔记本跑ROVIO
  6. Shopee优选卖家真的重要吗?
  7. ssh登录极路由后台_从浏览器如何进入路由器后台【详细介绍】
  8. 教你炒股票13:不带套的操作不是好操作!
  9. 通过gitbub桌面工具同步
  10. vue音频wavesurfer波形图