Linux 下wifi 驱动开发(三)—— SDIO接口WiFi驱动浅析

SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈,能够实现用户主平台数据通过SDIO口到无线网络之间的转换。SDIO具有传输数据快,兼容SD、MMC接口等特点。

     对于SDIO接口的wifi,首先,它是一个sdio的卡的设备,然后具备了wifi的功能,所以,注册的时候还是先以sdio的卡的设备去注册的。然后检测到卡之后就要驱动他的wifi功能了,显然,他是用sdio的协议,通过发命令和数据来控制的。下面先简单回顾一下SDIO的相关知识:

一、SDIO相关基础知识解析

1、SDIO接口

       SDIO 故名思义,就是 SD 的 I/O 接口(interface)的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO。

       所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 接脚来连接外部外围,并且透过 SD 上的 I/O 数据接位与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得 SDIO 外围(我们称为SDIO 卡)的开发与应用变得相当热门。

       现在已经有非常多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device 而制定),而且许多 SDIO 外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO 外围(SDIO 卡)有:

· Wi-Fi card(无线网络卡) 

· CMOS sensor card(照相模块) 

· GPS card 

· GSM/GPRS modem card 

· Bluetooth card 

        SDIO 的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO 式的 SPI 接口。


2、SDIO总线

      SDIO总线 和 USB总线 类似,SDIO也有两端,其中一端是HOST端,另一端是device端。所有的通信都是由HOST端 发送 命令 开始的,Device端只要能解析命令,就可以相互通信

CLK信号:HOST给DEVICE的 时钟信号,每个时钟周期传输一个命令。

CMD信号:双向 的信号,用于传送 命令 和 反应。

DAT0-DAT3 信号:四条用于传送的数据线。

VDD信号:电源信号。

VSS1,VSS2:电源地信号。


3、SDIO热插拔原理

方法:设置一个 定时器检查 或 插拔中断检测

硬件:假如GPG10(EINT18)用于SD卡检测

GPG10 为高电平 即没有插入SD卡

GPG10为低电平  即插入了SD卡


4、SDIO命令

      SDIO总线上都是HOST端发起请求,然后DEVICE端回应请求。sdio命令由6个字节组成。

a -- Command:用于开始传输的命令,是由HOST端发往DEVICE端的。其中命令是通过CMD信号线传送的。

b -- Response:回应是DEVICE返回的HOST的命令,作为Command的回应。也是通过CMD线传送的。

c -- Data:数据是双向的传送的。可以设置为1线模式,也可以设置为4线模式。数据是通过DAT0-DAT3信号线传输的。

      SDIO的每次操作都是由HOST在CMD线上发起一个CMD,对于有的CMD,DEVICE需要返回Response,有的则不需要。

     对于读命令,首先HOST会向DEVICE发送命令,紧接着DEVICE会返回一个握手信号,此时,当HOST收到回应的握手信号后,会将数据放在4位的数据线上,在传送数据的同时会跟随着CRC校验码。当整个读传送完毕后,HOST会再次发送一个命令,通知DEVICE操作完毕,DEVICE同时会返回一个响应。

    对于写命令,首先HOST会向DEVICE发送命令,紧接着DEVICE会返回一个握手信号,此时,当HOST收到回应的握手信号后,会将数据放在4位的数据线上,在传送数据的同时会跟随着CRC校验码。当整个写传送完毕后,HOST会再次发送一个命令,通知DEVICE操作完毕,DEVICE同时会返回一个响应。


二、SDIO接口驱动

        前面讲到,SDIO接口的wifi,首先,它是一个sdio的卡的设备,然后具备了wifi的功能,所以SDIO接口的WiFi驱动就是在wifi驱动外面套上了一个SDIO驱动的外壳,SDIO驱动仍然符合设备驱动的分层与分离思想


     设备驱动层(wifi 设备)

                      |

核心层(向上向下提供接口)

                      |

主机驱动层 (实现SDIO驱动)


        下面先分析SDIO接口驱动的实现,看几个重要的数据结构(用于核心层与主机驱动层 的数据交换处理)。

[ /include/linux/mmc/host.h ]

struct mmc_host     用来描述卡控制器

struct mmc_card     用来描述卡

struct mmc_driver  用来描述 mmc 卡驱动

struct sdio_func      用来描述 功能设备

struct mmc_host_ops   用来描述卡控制器操作接口函数功能,用于从 主机控制器层向 core 层注册操作函数,从而将core 层与具体的主机控制器隔离。也就是说 core 要操作主机控制器,就用这个 ops 当中给的函数指针操作,不能直接调用具体主控制器的函数。

      HOST层驱动分析在 前面的系列文章中 Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇 有详细阐述,下面只简单回顾一下一些重要函数处理

1、编写Host层驱动

     这里参考的是S3C24XX的HOST驱动程序   /drivers/mmc/host/s3cmci.c 

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static struct platform_driver s3cmci_driver = {  
  2.      .driver  = {  
  3.          .name    = "s3c-sdi",  //名称和平台设备定义中的对应  
  4.          .owner   = THIS_MODULE,  
  5.          .pm  = s3cmci_pm_ops,  
  6.      },  
  7.      .id_table = s3cmci_driver_ids,  
  8.      .probe        = s3cmci_probe,  //平台设备探测接口函数  
  9.      .remove       = __devexit_p(s3cmci_remove),  
  10.      .shutdown = s3cmci_shutdown,  
  11. };  
  12.   
  13. s3cmci_probe(struct platform_device *pdev)  
  14. {  
  15.     //....  
  16.     struct mmc_host *mmc;  
  17.     mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);  //分配mmc_host结构体  
  18.   
  19.     //.....  
  20. }  
  21.   
  22. /*注册中断处理函数s3cmci_irq,来处理数据收发过程引起的各种中断*/  
  23. request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host) //注册中断处理函数s3cmci_irq  
  24.   
  25. /*注册中断处理s3cmci_irq_cd函数,来处理热拨插引起的中断,中断触发的形式为上升沿、下降沿触发*/  
  26. request_irq(host->irq_cd, s3cmci_irq_cd,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, DRIVER_NAME, host)  
  27.   
  28. mmc_add_host(mmc);  //initialise host hardware //向MMC core注册host驱动  
  29. ----> device_add(&host->class_dev); //添加设备到mmc_bus_type总线上的设备链表中  
  30. ----> mmc_start_host(host); //启动mmc host  
  31.   
  32. /*MMC drivers should call this when they detect a card has been inserted or removed.检测sd卡是否插上或移除*/  
  33.  ---->mmc_detect_change(host, 0);  
  34.   
  35. /*Schedule delayed work in the MMC work queue.调度延时工作队列*/  
  36.  mmc_schedule_delayed_work(&host->detect, delay);  

搜索host->detected得到以下信息:

[/drivers/mmc/core/host.c]

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. NIT_DELAYED_WORK(&host->detect, mmc_rescan);  
  2.   
  3. mmc_rescan(struct work_struct *work)  
  4. ---->mmc_bus_put(host);//card 从bus上移除时,释放它占有的总线空间  
  5.   
  6. /*判断当前mmc host控制器是否被占用,当前mmc控制器如果被占用,那么  host->claimed = 1;否则为0 
  7.  *如果为1,那么会在while(1)循环中调用schedule切换出自己,当占用mmc控制器的操作完成之后,执行 *mmc_release_host()的时候,会激活登记到等待队列&host->wq中的其他 程序获得mmc主控制器的使用权 
  8.  */  
  9. mmc_claim_host(host);  
  10.      mmc_rescan_try_freq(host, max(freqs[i], host->f_min);  
  11.   
  12. static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)  
  13. {  
  14.      …  
  15.      /* Order's important: probe SDIO, then SD, then MMC */  
  16.      if (!mmc_attach_sdio(host))  
  17.           return 0;  
  18.      if (!mmc_attach_sd(host))  
  19.          return 0;  
  20.      if (!mmc_attach_mmc(host))  
  21.          return 0;  
  22.         ….  
  23. }  
  24.   
  25. mmc_attach_sdio(struct mmc_host *host)  //匹配sdio接口卡  
  26.      --->mmc_attach_bus(host, &mmc_sdio_ops);  
  27.   
  28. /*当card与总线上的驱动匹配,就初始化card*/  
  29. mmc_sdio_init_card(host, host->ocr, NULL, 0);   
  30.     --->card = mmc_alloc_card(host, NULL);//分配一个card结构体  
  31.           mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); //设置mmc_bus的工作模式  
  32.   
  33. struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; //SDIO functions (devices)  
  34.   
  35. sdio_init_func(host->card, i + 1);  
  36.     --->func = sdio_alloc_func(card); //分配struct sdio_fun(sdio功能设备)结构体  
  37.           mmc_io_rw_direct();  
  38.           card->sdio_func[fn - 1] = func;  
  39.   
  40. mmc_add_card(host->card);  //将具体的sdio设备挂载到mmc_bus_types 总线  
  41. sdio_add_func(host->card->sdio_func[i]); //将sdio功能设备挂载到sdio_bus_types总线  

这里一系列函数调用在前面的SD驱动蚊帐中已经阐述过了,不再详细阐述


2、SDIO设备的热插拔

      当插拔SDIO设备,会触发中断通知到CPU,然后执行卡检测中断处理函数在这个中断服务函数中,mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay), INIT_DELAYED_WORK(&host->detect, mmc_rescan)会调度mmc_rescan函数延时调度工作队列,这样也会触发SDIO设备的初始化流程,检测到有效的SDIO设备后,会将它注册到系统中去。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)  
  2. {  
  3.      struct s3cmci_host *host = (struct s3cmci_host *)dev_id;  
  4.      ........  
  5.      mmc_detect_change(host->mmc, msecs_to_jiffies(500));  
  6.   
  7.      return IRQ_HANDLED;  
  8. }  


三、wifi 驱动部分解析

wifi驱动的通用的软件架构

1. 分为两部分,上面为主机端驱动,下面是我们之前所说的firmware

2. 其中固件部分的主要工作是:因为天线接受和发送回来的都是802.11帧的帧,而主机接受和传送出来的数据都必须是802.3的帧,所以必须由firmware来负责802.3的帧和802.11帧之间的转换

3. 当天线收到数据,并被firmware处理好后会放在一个buffer里,并产生一个中断,主机在收到中断后就去读这个buffer。

      

     SDIO设备的驱动由sdio_driver结构体定义,sdio_driver其实是driver的封装。通过sdio_register_driver函数将SDIO设备驱动加载进内核,其实就是挂载到sdio_bus_type总线上去。

1、设备驱动的注册与匹配

[Drivers/net/wireless/libertas/if_sdio.c]

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. /* SDIO function device driver*/  
  2.   
  3. struct sdio_driver {  
  4.      char *name;  //设备名  
  5.      const struct sdio_device_id *id_table; //设备驱动ID  
  6.      int (*probe)(struct sdio_func *, const struct sdio_device_id *);//匹配函数  
  7.      void (*remove)(struct sdio_func *);  
  8.      struct device_driver drv;  
  9. };  

下面是具体函数的填充:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. /*if_sdio.c*/  
  2.   
  3. static struct sdio_driver if_sdio_driver = {  
  4.      .name         = "libertas_sdio",  
  5.      .id_table = if_sdio_ids,  //用于设备与驱动的匹配  
  6.      .probe        = if_sdio_probe,  
  7.      .remove       = if_sdio_remove,  
  8.      .drv = {  
  9.          .pm = &if_sdio_pm_ops,  
  10.          }  
  11. };  

设备注册函数

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  *   sdio_register_driver - register a function driver 
  3.  *   @drv: SDIO function driver 
  4.  */  
  5.   
  6. int sdio_register_driver(struct sdio_driver *drv)  
  7. {  
  8.      drv->drv.name = drv->name;  
  9.      drv->drv.bus = &sdio_bus_type;  //设置driver的bus为sdio_bus_type  
  10.      return driver_register(&drv->drv);  
  11. }  

总线函数

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static struct bus_type sdio_bus_type = {  
  2.      .name         = "sdio",  
  3.      .dev_attrs    = sdio_dev_attrs,  
  4.      .match        = sdio_bus_match,  
  5.      .uevent       = sdio_bus_uevent,  
  6.      .probe        = sdio_bus_probe,  
  7.      .remove       = sdio_bus_remove,  
  8.      .pm      = SDIO_PM_OPS_PTR,  
  9. };  

注意:设备或者驱动注册到系统中的过程中,都会调用相应bus上的匹配函数来进行匹配合适的驱动或者设备,对于sdio设备的匹配是由sdio_bus_matchsdio_bus_probe函数来完成。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static int sdio_bus_match(struct device *dev, struct device_driver *drv)  
  2. {  
  3.      struct sdio_func *func = dev_to_sdio_func(dev);  
  4.      struct sdio_driver *sdrv = to_sdio_driver(drv);   
  5.      if (sdio_match_device(func, sdrv))  
  6.          return 1;   
  7.   
  8.      return 0;  
  9. }  
  10.   
  11. static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,  
  12.      struct sdio_driver *sdrv)  
  13. {  
  14.      const struct sdio_device_id *ids;  
  15.      ids = sdrv->id_table;             
  16.   
  17.     if (sdio_match_one(func, ids))  
  18.                    return ids;  
  19. }  

由以上匹配过程来看,通过匹配id_table 和 sdio_driver设备驱动中id,来匹配合适的驱动或设备。最终会调用.probe函数,来完成相关操作。


2、If_sdio_probe函数

    当检测到sdio卡插入了之后就会调用If_sdio_probe,而当卡被移除后就会调用If_sdio_remove



下面先看下If_sdio_probet函数,if_sdio_prob 函数 主要做了两件事  

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static struct sdio_driver if_sdio_driver = {  
  2.  .name  = "libertas_sdio",  
  3.  .id_table = if_sdio_ids,   //用于设备和驱动的匹配  
  4.  .probe  = if_sdio_probe,  
  5.  .remove  = if_sdio_remove,  
  6.  .drv = {  
  7.   .pm = &if_sdio_pm_ops,  
  8.  },  
  9. };  
  10.    
  11.   
  12. //定义一个 if_sdio  card的结构体  
  13.  struct if_sdio_card *card;  
  14.  struct if_sdio_packet *packet;  //sdio 包的结构体   
  15.  struct mmc_host *host = func->card->host;  
  16.   
  17.  // 查询是否有指定的功能寄存器在mmc  
  18.    //_sdio_card中  
  19.  for (i = 0;i < func->card->num_info;i++) {  
  20.   if (sscanf(func->card->info[i],  
  21.     "802.11 SDIO ID: %x", &model) == 1)  
  22.    
  23. //在这里进行片选  选择到我们使用的marvell 8686 的设备  
  24. case MODEL_8686:  
  25.   card->scratch_reg = IF_SDIO_SCRATCH;  
  26.    
  27.    
  28. //创建sdio 的工作队列   
  29. card->workqueue = create_workqueue("libertas_sdio");  
  30. //调用下面的函数  
  31. INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);  
  32.   
  33.   
  34. //主机到卡的工作队列  
  35. static void if_sdio_host_to_card_worker(struct work_struct *work)  
  36.   
  37.  /* Check if we support this card  选择我们所支持的卡的类型*/  
  38.   //赋值为sd8686_helper.bin   sd8686.bin  
  39. /*fw_table 中的  MODEL_8686, "sd8686_helper.bin""sd8686.bin" },?/  
  40.  for (i = 0; i < ARRAY_SIZE(fw_table); i++) {  
  41.       if (card->model == fw_table[i].model)  
  42.            break;  
  43.  }  
  44.  { MODEL_8688, "libertas/sd8688_helper.bin""libertas/sd8688.bin" },  
  45.    
  46.   
  47. //申请一个host  
  48. sdio_claim_host(func);  
  49. //使能sdio 的功能 寄存器  
  50. ret = sdio_enable_func(func);  
  51. if (ret)  
  52.   goto release;  
  53.   
  54. 2//申请 sdio 的中断  当有数据  ,命令 或者是事件 的时间执行中断  
  55. ret = sdio_claim_irq(func, if_sdio_interrupt);  
  56. ret = if_sdio_card_to_host(card);  //从无线网卡接收到数据 或者说是上报数据  
  57. ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);   //接收数据的处理   
  58. ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4);   //处理申请的命令中断  
  59. ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);//处理申请的事件中断  
  60.   
  61.   
  62. //添加网络结构体  分配设备并注册  
  63. priv = lbs_add_card(card, &func->dev);  
  64.   
  65. //分配Ethernet设备并注册   
  66.  wdev = lbs_cfg_alloc(dmdev);  
  67. //802无线网的具体的操作函数   
  68. wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private));  
  69.   
  70.    
  71. //分配网络设备是整个网络部分操作的  
  72. //的核心结构体  
  73. dev = alloc_netdev(0, "wlan%d", ether_setup);  //实例化wlan0的属性  
  74. dev->ieee80211_ptr = wdev;  
  75.  dev->ml_priv = priv;  
  76.  //设置设备的物理地址   
  77.  SET_NETDEV_DEV(dev, dmdev);  
  78.  wdev->netdev = dev;  
  79.  priv->dev = dev;  
  80.    //初始化网络设备 ops.  看门狗    
  81.   dev->netdev_ops = &lbs_netdev_ops;    //网络设备的具体的操作函数   
  82.  dev->watchdog_timeo = 5 * HZ;  
  83.  dev->ethtool_ops = &lbs_ethtool_ops;     
  84.  dev->flags |= IFF_BROADCAST | IFF_MULTICAST;  //广播或者多播  
  85.    
  86.    
  87.    
  88.  //启动一个内核线程来管理这个网络设备的数据发送,事件的处理(卡的拔出)和一些命令的处理   
  89.  priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");  
  90. //初始化相关的工作队列  
  91.  priv->work_thread = create_singlethread_workqueue("lbs_worker");  
  92.  INIT_WORK(&priv->mcast_work, lbs_set_mcast_worker);  
  93.  priv->wol_criteria = EHS_REMOVE_WAKEUP;  
  94.  priv->wol_gpio = 0xff;  
  95.  priv->wol_gap = 20;  
  96.  priv->ehs_remove_supported = true;  
  97.    
  98.    
  99.  //设置私有变量   
  100. //设置主机发送数据到卡  
  101.  priv->hw_host_to_card = if_sdio_host_to_card;  
  102.  priv->enter_deep_sleep = if_sdio_enter_deep_sleep;  
  103.  priv->exit_deep_sleep = if_sdio_exit_deep_sleep;  
  104.  priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup;  
  105.  sdio_claim_host(func);    
  106.   
  107.   //启动卡设备   
  108.  ret = lbs_start_card(priv);  
  109.  if (lbs_cfg_register(priv))   
  110.   
  111.  ret = register_netdev(priv->dev);  
  112.  err = register_netdevice(dev);  
  113.   
  114.    
  115. //具体的wifi设备驱动功能   
  116. //网络设备操作的具体函数   
  117. static const struct net_device_ops lbs_netdev_ops = {  
  118.  .ndo_open   = lbs_dev_open,   //打开  
  119.  .ndo_stop  = lbs_eth_stop,  //停止  
  120.  .ndo_start_xmit  = lbs_hard_start_xmit,   //开始发送数据  
  121.  .ndo_set_mac_address = lbs_set_mac_address,   //设置mac地址   
  122.  .ndo_tx_timeout  = lbs_tx_timeout,    //发送超时  
  123.  .ndo_set_multicast_list = lbs_set_multicast_list,   //多播地址  
  124.  .ndo_change_mtu  = eth_change_mtu,  //最大传输单元  
  125.  .ndo_validate_addr = eth_validate_addr,  //判断地址的有效性   

3、数据的接收,通过中断的方式来解决

     网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断的类型,如果为接收中断,则读取接收到的数据,分配sk_buff数据结构和数据缓冲区,并将接收的数据复制到数据缓存区,并调用netif_rx()函数将sk_buff传递给上层协议。

    搜索if_sdio_interrupt,可知道它是在if_sdio.c文件中if_sdio_probe()函数中sdio_claim_irq(func, if_sdio_interrupt) ,func->irq_handler = if_sdio_interrupt。当s3cmci_irq中断处理函数的S3C2410_SDIIMSK_SDIOIRQ 中断被触发时将调用if_sdio_interrupt()函数,进行接收数据。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static void if_sdio_interrupt(struct sdio_func *func)  
  2.   
  3. ret = if_sdio_card_to_host(card);  //从无线网卡接收到数据 或者说是上报数据  
  4. //读取端口上的数据 ,放到card的buffer中   
  5.  ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);  
  6. 1.在这里一方面处理中断  还有2   
  7.  switch (type) {   //处理cmd   data  event的请求   
  8.  case MVMS_CMD:  
  9.   ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4);   //处理申请的命令中断  
  10.   if (ret)  
  11.    goto out;  
  12.   break;  
  13.  case MVMS_DAT:  
  14.   ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);//处理申请的数据中断   
  15.   if (ret)  
  16.    goto out;  
  17.   break;  
  18.  case MVMS_EVENT:  
  19.   ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);//处理申请的事件中断  
  20.    
  21. //读取包的过程   
  22.  lbs_process_rxed_packet(card->priv, skb);  
  23.    
  24.  //如果是中断 ,就把skb这个包提交给协议层,这个函数是  
  25.  //协议层提供的  netif_rx(skb)  
  26.  if (in_interrupt())  
  27.   netif_rx(skb);    //提交给协议层   
  28.    
  29.    
  30. 2//读取端口上的数据 ,放到card的buffer中   
  31.  ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);  
  32. //读取地址,目的地址,数量 等  
  33. int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, int count)  
  34.   
  35.          return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);  
  36.   
  37.                 ret = mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize);  
  38.                          cmd.arg = write ? 0x80000000 : 0x00000000;  
  39.                                   
  40.                     //wait for  request    
  41.                      mmc_wait_for_req(card->host, &mrq);  
  42.                         开始应答   
  43.                          mmc_start_request(host, mrq);  
  44.                          wait_for_completion(&complete);  
  45.                                       
  46.                              host->ops->request(host, mrq);  

4、 数据发送

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. //IP层通过dev_queue_xmit()将数据交给网络设备协议接口层,网络接口层通过netdevice中的注册函数的数据发送函数  
  2. int dev_queue_xmit(struct sk_buff *skb)  
  3.   
  4.     if (!netif_tx_queue_stopped(txq)) {  
  5.     __this_cpu_inc(xmit_recursion);  
  6.    //设备硬件开始发送    
  7.     rc = dev_hard_start_xmit(skb, dev, txq);  
  8.   //调用wifi网络中的ops   
  9.   
  10.   rc = ops->ndo_start_xmit(skb, dev);  
  11.   
  12.   dev->netdev_ops = &lbs_netdev_ops;    //设备的操作函数   
  13.   
  14.  //处理sdio firware数据和内核的数据main_thread 主线程    
  15.  priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");  
  16.   
  17.    //调用host_to_card   即if_sdio_card_to_host函数。   
  18.    int ret = priv->hw_host_to_card(priv, MVMS_DAT,priv->tx_pending_buf,priv->tx_pending_len);  
  19. 为什么是if_sdio_to_host呢 ?因为在prob函数中定义了这一个  
  20. //设置主机发送数据到卡  
  21.  priv->hw_host_to_card = if_sdio_host_to_card;  
  22.      
  23. static int if_sdio_host_to_card(struct lbs_private *priv,u8 type, u8 *buf, u16 nb)  
  24.       //把buf中的数据 copy到sdio 包中,在对sdio 的包进行处理  
  25.          memcpy(packet->buffer + 4, buf, nb);  
  26. //创建工作队列    
  27.          queue_work(card->workqueue, &card->packet_worker);  
  28.  //初始化队列    
  29.  INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);  
  30.   
  31.  //sdio的写数据     
  32.    ret = sdio_writesb(card->func, card->ioport, packet->buffer, packet->nb);  
  33.          //mmc写扩展口   
  34.                ret = mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize);  
  35.   
  36.                     //wait for  request    
  37.                                  mmc_wait_for_req(card->host, &mrq);  
  38.                                     
  39.                              mrq->done_data = &complete;  
  40.                              mrq->done = mmc_wait_done;  
  41.                              mmc_start_request(host, mrq);  
  42.                                 //完成等待 写数据结束   
  43.                              wait_for_completion(&complete);  
  44.    
  45.    
  46.                              host->ops->request(host, mrq);  
  47.    //到底结束  发送数据    


5、移除函数

       当sdio卡拔除时,驱动会调用该函数,完成相应操作。如释放占有的资源,禁止func功能函数,释放host。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. if_sdio_remove(struct sdio_func *func)  
  2. ---->lbs_stop_card(card->priv);  
  3.      lbs_remove_card(card->priv);  
  4.      ---->kthread_stop(priv->main_thread);  //终止内核线程  
  5.   
  6.          lbs_free_adapter(priv);  
  7.          lbs_cfg_free(priv);  
  8.           free_netdev(dev);  
  9.   
  10.      flush_workqueue(card->workqueue);  //刷新工作队列  
  11.      destroy_workqueue(card->workqueue);  
  12.      sdio_claim_host(func);  
  13.      sdio_release_irq(func);  
  14.      sdio_disable_func(func);  
  15.       sdio_release_host(func);  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Erlang并发机制 –进程调度

2019独角兽企业重金招聘Python工程师标准>>> Erlang调度器主要完成对Erlang进程的调度&#xff0c;它是Erlang实现软件实时和进程之间公平使用CPU的关键。Erlang运行时&#xff0c;有4种任务需要被调度&#xff1a;进程&#xff0c;Port&#xff0c;Linked-in drive…

Linux 下wifi 驱动开发(二)—— WiFi模块浅析

一、什么是wifi 模块 百度百科上这样定义&#xff1a; Wi-Fi模块又名串口Wi-Fi模块&#xff0c;属于物联网传输层&#xff0c;功能是将串口或TTL电平转为符合Wi-Fi无线网络通信标准的嵌入式模块&#xff0c;内置无线网络协议IEEE802.11b.g.n协议栈以及TCP/IP协议栈。传统的硬件…

Linux 下wifi 驱动开发(一)—— WiFi基础知识解析

一、WiFi相关基础概念 1、什么是wifi 我们看一下百度百科是如何定义的&#xff1a; Wi-Fi是一种可以将个人电脑、手持设备&#xff08;如pad、手机&#xff09;等终端以无线方式互相连接的技术&#xff0c;事实上它是一个高频无线电信号。[1] 无线保真是一个无线网络通信技术…

Linux 网络设备驱动开发(一) —— linux内核网络分层结构

Linux内核对网络驱动程序使用统一的接口&#xff0c;并且对于网络设备采用面向对象的思想设计。 Linux内核采用分层结构处理网络数据包。分层结构与网络协议的结构匹配&#xff0c;既能简化数据包处理流程&#xff0c;又便于扩展和维护。 一、内核网络结构 在Linux内核中&#…

Linux 网络设备驱动开发(二) —— Linux 网络栈剖析

一、协议简介 虽然对于网络的正式介绍一般都参考了 OSI&#xff08;Open Systems Interconnection&#xff09;模型&#xff0c;但是本文对 Linux 中基本网络栈的介绍分为四层的 Internet 模型&#xff08;如图 1 所示&#xff09;。 图 1. 网络栈的 Internet 模型 这个栈的最底…

Linux 网络设备驱动开发(三) —— 网络设备驱动基本原理和框架

一、协议栈层次对比 二、Linux网络子系统 Linux网络子系统的顶部是系统调用接口层。它为用户空间提供的应用程序提供了一种访问内核网络子系统的方法&#xff08;socket&#xff09;。位于其下面是一个协议无关层&#xff0c;它提供一种通用的方法来使用传输层协议。然后是具…

国产杀毒软件也开始支持虚拟化

今天不小心看到一个较老的“新”闻&#xff0c;国产安全软件厂商瑞星已经推出支持VMware vshield Endpoint技术的防病毒产品&#xff0c;也就是说如果客户今天使用的是VMware的虚拟化软件&#xff0c;上面运行的所有虚拟机就不需要安装传统的防病毒软件程序&#xff0c;而可以直…

VRP平台基本操作

一、显示系统信息 <Huawei>display version 图上所示可以知道VRP平台信息&#xff0c;运行的版本&#xff0c;运行的时间 二、修改和查看设备系统时间参数 1.查看时间 <Huawei>display clock 2.修改系统日期和时间 三、进入系统视图界面 <Huawei>system-view…

Android中获取应用程序(包)的信息-----PackageManager的使用(一)

本节内容是如何获取Android系统中应用程序的信息&#xff0c;主要包括packagename、label、icon、占用大小等。具体分为两个 部分&#xff0c;计划如下&#xff1a; 第一部分&#xff1a; 获取应用程序的packagename、label、icon等 &#xff1b; 第二部分&#xff1a; 获取应用…

VRP平台总体介绍及基础配置

前言 1、VRP软件系统基础 VRP系统在启动时需要加载“系统软件”和“配置文件”两部分&#xff0c;这与其它品牌网络交换机的操作系统是一样的。如果指定了下次启动的补丁文件&#xff0c;还需加载补丁文件。修改VRP系统启动的场景一般有以下几种&#xff1a; a-- 对交换机进行升…

小强的HTML5移动开发之路(43)——JqueryMobile页眉、工具栏和标签栏导航

一、页眉1、添加页眉和页脚<div data-role"header"><h1>第 1 页</h1></div><div data-role"footer"><h4>页面脚注</h4></div>默认的页眉在屏幕的顶部边缘显示&#xff0c;而且在在屏幕滚动时&#xff0c;…

交换机开发(二)—— 三层交换机报文转发过程

如图所示&#xff0c;假如主机A想访问主机B&#xff0c;首先主机A会将自己的IP地址和子网掩码做与操作,得出网路地址(如:Host-A的IP地址100.1.1.2与自身掩码255.255.255.0做与操作后,得到的网络号是100.1.1.0).然后判断目的IP地址(即Host-B的IP地址)与自己的网络地址是不是在同…

分布式搜索elasticsearch配置文件详解

2019独角兽企业重金招聘Python工程师标准>>> elasticsearch的config文件夹里面有两个配置文件&#xff1a;elasticsearch.yml和logging.yml&#xff0c;第一个是es的基本配置文件&#xff0c;第二个是日志配置文件&#xff0c;es也是使用log4j来记录日志的&#xff…

交换机开发(三)—— 深入分析三层网络交换机的原理和设计

引言传统路由器在网络中起到隔离网络、隔离广播、路由转发以及防火墙的作业&#xff0c;并且随着网络的不断发展&#xff0c;路由器的负荷也在迅速增长。其中一个重要原因是出于安全和管理方便等方面的考虑&#xff0c;VLAN(虚拟局域网)技术在网络中大量应用。VLAN技术可以逻辑…

XML 命名空间(XML Namespaces)

为什么80%的码农都做不了架构师&#xff1f;>>> XML 应用程序 XML CDATA XML 命名空间提供避免元素命名冲突的方法。 命名冲突 在 XML 中&#xff0c;元素名称是由开发者定义的&#xff0c;当两个不同的文档使用相同的元素名时&#xff0c;就会发生命名冲突。 这个…

Linux 下挂载新硬盘方法

Linux的硬盘识别: 一般使用”fdisk -l”命令可以列出系统中当前连接的硬盘 设备和分区信息.新硬盘没有分区信息,则只显示硬盘大小信息. 1.关闭服务器加上新硬盘 2.启动服务器&#xff0c;以root用户登录 3.查看硬盘信息 #fdisk -l [cpp] view plaincopy Disk /dev/sda: 42.9 GB…

C++ 学习基础篇(一)—— C++与C 的区别

编程的学习学无止境&#xff0c;只掌握一门语言是远远不够的&#xff0c;现在我们开始C的学习之路&#xff0c;下面先看下C 与C 的区别 一、C概述 1、发展历史 1980年&#xff0c;Bjarne Stroustrup博士开始着手创建一种模拟语言&#xff0c;能够具有面向对象的程序设计特色。在…

C++学习基础篇 —— 引用()的用法和应用

一、引用简介 引用就是某一变量&#xff08;目标&#xff09;的一个别名&#xff0c;对引用的操作与对变量直接操作完全一样。 引用的声明方法&#xff1a;类型标识符&引用名目标变量名&#xff1b; 【例1】&#xff1a; [cpp] view plaincopy int a; int &raa; //定义…

C++基础知识(二)—— 变量和数据类型

你可能觉得这个“Hellow World”程序用处不大。我们写了好几行代码&#xff0c;编译&#xff0c;然后执行生成的程序只是为了在屏幕上看到一句话。的确&#xff0c;我们直接在屏幕上打出这句话会更快。但是编程并不仅限于在屏幕上打出文字这么简单的工作。为了能够进一步写出可…

C++基础知识(四)—— 操作符/运算符

前面已经学习了变量和常量&#xff0c;我们可以开始对它们进行操作&#xff0c;这就要用到C的操作符。有些语言&#xff0c;很多操作符都是一些关键字&#xff0c; 比如add, equals等等。C的操作符主要是由符号组成的。这些符号不在字母表中&#xff0c;但是在所有键盘上都可以…