前言

  本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 。主要是想对spi接口的wifi驱动框架有一个整体的把控,因此会忽略一些硬件上操作的系统,同时里面涉及到的一些驱动基础,比如数据结构、设备模式也不进行详细说明原理。如果有任何错误地方,请指出,谢谢!

分两步来分析:

第一步:spi接口驱动分析

第二部:基于spi接口的wifi驱动分析

spi接口驱动分析

在cm-x270.c中

static void __init cmx270_init_spi(void)
{
pxa2xx_set_spi_info(2, &cm_x270_spi_info);
spi_register_board_info(ARRAY_AND_SIZE(cm_x270_spi_devices));
} void __init cmx270_init(void)
{
pxa2xx_mfp_config(ARRAY_AND_SIZE(cmx270_pin_config)); #ifdef CONFIG_PM
pxa27x_set_pwrmode(PWRMODE_DEEPSLEEP);
#endif cmx270_init_rtc();
cmx270_init_mmc();
cmx270_init_ohci();
cmx270_init_2700G();
cmx270_init_spi();
}

pxa2xx_set_spi_info实际上是向平台总线注册spi控制器设备。在drivers/spi/pxa2xx_spi.c中有对应的spi控制器的驱动注册:

static int __init pxa2xx_spi_init(void)
{
return platform_driver_probe(&driver, pxa2xx_spi_probe);
}

这样匹配成功后导致pxa2xx_spi_probe的执行,在pxa2xx_spi_probe中,主要是根据cm_x270_spi_info实例化一个spi控制器对象,然后初始化控制器,申请资源,最后调用spi_register_master将该spi控制器实例注册到设备模型上去。里面核心的操作就是scan_boardinfo(master)它会扫描注册的spi设备。这个就是之前函数

spi_register_board_info(ARRAY_AND_SIZE(cm_x270_spi_devices));

调用的结果,它会将cm_x270_spi_devices信息添加到一个全局的链表(中间会wrap一层)。

scan_boardinfo会将全局链表里面总线号和自己匹配的设备都进行注册,即调用spi_new_device,将设备注册到spi总线上去。于是,spi控制器和spi设备(wifi) 都已经准备完毕,剩下的就等spi接口的wifi驱动注册了。

基于spi接口的wifi驱动分析

因为分析的spi接口的wifi驱动是Marvell公司的,所以spi接口的wifi驱动部分看:

linux/drivers/net/wireless/libertas/if_spi.c ,它在

static int __init if_spi_init_module(void)
{
int ret = 0;
lbs_deb_enter(LBS_DEB_SPI);
printk(KERN_INFO "libertas_spi: Libertas SPI driver\n");
ret = spi_register_driver(&libertas_spi_driver);
lbs_deb_leave(LBS_DEB_SPI);
return ret;
}

中调用了spi_register_driver向spi总线注册驱动,这会导致if_spi_probe的调用,它内部会创建一个对象来描述该spi接口的wifi,即struct if_spi_card *card;。同时,也会装载固件到wifi设备中,它是通过if_spi_prog_helper_firmwareif_spi_prog_main_firmware实现的。

  关于固件的主要作用

   Firmware是在Wi-Fi设备硬件中执行的一段程序,系统上电后由WLAN驱动将其下载到Wi-Fi模块中,实现Wi-Fi硬件接口控制、数据缓冲、802.11与802.3帧类型转换、802.1l MAC层管理、WLAN MAC中断管理以及硬件控制等功能。发送数据时,Host驱动程序将从上层接收到的标准802.3帧发送给Firmware,Firmware将收到的数据帧转换成802.11帧,再通过无线连接将数据传输出去;接收数据时,Firmware将接收到的所有802.11帧转换成802.3帧后,通过SPI口发送给Host驱动。由此可见,Wi-Fi无线网卡设备在Linux中是被当作普通的以太网设备对待的,在Wi-Fi驱动程序中无需实现802.11帧与802.3帧之间的类型转换。

然后会将刚申请并初始化完成的card注册到libertas中,即调用:lbs_add_card(card, &spi->dev); lbs_add_card的主要作用是让card成为net device功能,它调用alloc_netdev申请了一个网络设备,需要注意的是赋予了网络子系统将来会用到的操作集lbs_netdev_ops

static const struct net_device_ops lbs_netdev_ops = {
.ndo_open = lbs_dev_open,
.ndo_stop = lbs_eth_stop,
.ndo_start_xmit = lbs_hard_start_xmit,
.ndo_set_mac_address = lbs_set_mac_address,
.ndo_tx_timeout = lbs_tx_timeout,
.ndo_set_multicast_list = lbs_set_multicast_list,
.ndo_change_mtu = eth_change_mtu,
.ndo_validate_addr = eth_validate_addr,
};

同时注意下面的调用,后面会基于这些通信:

        init_waitqueue_head(&priv->waitq);
priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");
if (IS_ERR(priv->main_thread)) {
lbs_deb_thread("Error creating main thread.\n");
goto err_ndev;
} priv->work_thread = create_singlethread_workqueue("lbs_worker");
INIT_DELAYED_WORK(&priv->assoc_work, lbs_association_worker);
INIT_DELAYED_WORK(&priv->scan_work, lbs_scan_worker);
INIT_WORK(&priv->mcast_work, lbs_set_mcast_worker);

接着if_spi_probe里面会调用:

 priv->hw_host_to_card = if_spi_host_to_card;

它会在Libertas层收到网络子系统数据的时候调用

 card->spi_thread = kthread_run(lbs_spi_thread, card, "lbs_spi_thread");

负责数据包的接收和命令的响应处理线程

 err = request_irq(spi->irq, if_spi_host_interrupt,
IRQF_TRIGGER_FALLING, "libertas_spi", card);

用于处理spi接口网卡上的中断

 err = lbs_start_card(priv);

它的调用链是

lbs_cfg_register -> wiphy_register ->register_netdev

,这样之后网络子系统就能够使用该网卡驱动了。

下面分几个情景分析上面这些核心函数的作用

情景分析

情景1:上层网络子系统有数据发送

lbs_hard_start_xmit函数会被调用,这个函数属于上文说过的网络子系统将来会用到的操作集lbs_netdev_opslbs_hard_start_xmit首先将数据包拷贝到自己的buf里面,然后唤醒其他线程然后退出,priv是libertas层维护的对象,是在上文中lbs_add_card的时候分配的。

调用层次

lbs_add_card -> lbs_cfg_alloc -> wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private)); , 属于wdev额外分配的一段内存,可以通过priv = wdev_priv(wdev);获取。 wake_up(&priv->waitq);实际唤醒的是lbs_thread线程,由于是发送数据包,会调用lbs_execute_next_command,接着会导致lbs_submit_command -> priv->hw_host_to_card

priv->hw_host_to_card是上文中说过的if_spi_host_to_card,因为是通过spi接口发送数据,所以调用spi驱动probe时注册的函数是理所当然的。

if_spi_host_to_card最终会导致spi控制器实例的master->transfer会被调用,它是在第一步中的pxa2xx_spi_probe里注册master->transfer = transfer;,transfer实际上就是最终的硬件操作了,但它实际上也是异步的,它呼叫一个工作线程去完成这些任务

if (drv_data->run == QUEUE_RUNNING && !drv_data->busy)
queue_work(drv_data->workqueue, &drv_data->pump_messages);

drv_data->workqueue也是在pxa2xx_spi_probe完成的:

init_queue
-> tasklet_init(&drv_data->pump_transfers,
pump_transfers, (unsigned long)drv_data);
-> INIT_WORK(&drv_data->pump_messages, pump_messages);
-> drv_data->workqueue = create_singlethread_workqueue(
dev_name(drv_data->master->dev.parent));

最终pump_messages会被调用. 它最后会导致pump_transfers来完成硬件数据传送。

这个过程确实非常绕,下面分析下为什么需要这些过程:

首先是lbs_thread,它的存在不仅仅是处理数据的发送,同时还要处理设备返回的事件和命令的响应。上层数据包发送的时候,我们应该尽可能快的完成处理并返回,不然网络子系统就会延迟了,但这并不是最主要的, 将一类事件异步统一到一个线程里面处理也是有很多优势的。然后是priv->hw_host_to_card的回调问题,网卡驱动的libertas层实际上是和具体的硬件接口分离的,不然它还得关心是spi接口、sdio接口、或者是usb接口的wifi等等,所以采用回调的方式,让具体的接口处理部分注册回调函数。

if_spi_host_to_card调用spi控制器实例的master->transfer很容易理解,具体的数据传送当然的操作控制器。

对于控制器里面的实现,即master->transfe的transfer 它采用工作线程的方式来统一处理所有的发送数据包,这样就可以和lbs_thread实现异步,各种更容易处理各自的队列等。

情景二:设备接收到数据

Spi接口的wifi有数据过来的时候,会通过spi中断,所以理所当然的进入注册的spi irq的中断处理函数if_spi_host_interrupt,它的实现很简单,异步处理,自己本身不进行任何处理(毕竟在中断,处理spi有可能sleep,那就玩玩了,呵呵),即调用up(&card->spi_ready)唤醒其他线程来做事情。

睡在这信号量上的线程是:lbs_spi_thread,这个是上文第一步中的if_spi_probe中创建的,这个好理解啦。它的处理函数lbs_spi_thread,读状态寄存器,根据状态来决定是什么事件。

/* Read the host interrupt status register to see what we
* can do. */
err = spu_read_u16(card, IF_SPI_HOST_INT_STATUS_REG,
&hiStatus);

数据的处理就在:

 if (hiStatus & IF_SPI_HIST_RX_UPLOAD_RDY)
err = if_spi_c2h_data(card);
if (err)
goto err;

if_spi_c2h_data的处理和其他网卡的处理类似,分配sk_buff 然后读到分配的buff里面(读可以直接读,也可以用dma),然后调用lbs_process_rxed_packet经过一些处理调用netif_rx来传给网络子系统。

情景三:命令的响应处理

和情景二类似,都是在lbs_spi_thread中,不过是进入了另一个判断里面:

if (hiStatus & IF_SPI_HIST_CMD_UPLOAD_RDY)
err = if_spi_c2h_cmd(card);
if (err)
goto err;

if_spi_c2h_cmd命令的响应当然没必要让网络子系统知道,因为对于网络子系统,spi wifi就是一个网络设备而已,不知道这些具体的命令的含义。我们这个理所当然的是让libertas层的lbs_thread线程处理,这个上文 情景1:上层网络子系统有数据发送 已经有过分析。它会进入到

if (priv->resp_len[resp_idx]) {
spin_unlock_irq(&priv->driver_lock);
lbs_process_command_response(priv,
priv->resp_buf[resp_idx],
priv->resp_len[resp_idx]);
spin_lock_irq(&priv->driver_lock);
priv->resp_len[resp_idx] = 0;
}

最终调用lbs_process_command_response来处理命令的响应。

完!

2014年5月

linux驱动基础系列--Linux下Spi接口Wifi驱动分析的更多相关文章

  1. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

  2. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  3. Linux 下wifi 驱动开发(四)—— USB接口WiFi驱动浅析

    源: Linux 下wifi 驱动开发(四)—— USB接口WiFi驱动浅析

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

    SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈.可以实现用户主平台数据通过SDIO口到无线网络之间的转 ...

  5. linux驱动基础系列--Linux mmc sd sdio驱动分析

    前言 主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.块设备驱动.设备模型等也不进行详细说明原 ...

  6. linux驱动基础系列--Linux 串口、usb转串口驱动分析

    前言 主要是想对Linux 串口.usb转串口驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如字符设备驱动.平台驱动等也不进行详细说明原理.如果有任何错误地方,请指出, ...

  7. linux驱动基础系列--Linux I2c驱动分析

    前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...

  8. linux驱动基础系列--linux rtc子系统

    前言 linux驱动子系统太多了,连时钟也搞了个子系统,这导致一般的时钟芯片的驱动也会涉及到至少2个子系统,一个是时钟芯片接口子系统(比如I2c接口的时钟芯片),一个是内核给所有时钟芯片提供的rtc子 ...

  9. linux驱动由浅入深系列:高通sensor架构实例分析之二(驱动代码结构)【转】

    本文转载自:https://blog.csdn.net/radianceblau/article/details/73498303 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ...

随机推荐

  1. Visual Stdio 2015打包安装项目的方法(使用Visual Studio Installer)

    首先在官网下载VS2015的Visual Studio Installer 1.创建安装项目 里面最左侧的框框有三个文件夹 1.“应用程序文件夹”即"Application Folder&q ...

  2. Android 字母导航条实现

    在Activity中进行功能的实现,需要用到第三方jar包:pinyin4j.jar,此jar包用于将汉字转换为汉语拼音. 首先,设置右侧边栏索引列表(A-Z),并且设置列表点击,Touch事件,点击 ...

  3. php写错命名空间 导致catch不到异常

    写的微信回调接口出错了, 由于手里的调试工具(包括微信官方的开发者接口调试工具)不能把HTTP错误的详情dump出来,只会显示空白,所以打算在程序里加上try catch 捕获错误直接输出.重新测试, ...

  4. 信息工程学院技能大赛 计算机程序设计(Java)大赛试题

    前期准备与后期上传工作: (1)必须先建立项目和包,项目名为"JavaContest",包结构为:"contest.c+序号+姓名",其中序号为两位为本人大赛报 ...

  5. React Render Callback Pattern(渲染回调模式)

    React Render Callback Pattern,渲染回调模式,其实是将this.props.children当做函数来调用. 例如: 要根据user参数确定渲染Loading还是Profi ...

  6. [bzoj 1594]猜数游戏

    主要是怎么处理矛盾 矛盾的条件有$2$种: 第一种是当把所有相等的$a$都全部找到后,他们并没有全联通,所以矛盾,因为没有两个是相同的 第二种是在2组$(l,r,a)$,$(l1,r1,a1)$中,$ ...

  7. Redis、Memcache

    ★ Redis redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sor ...

  8. 【逆序对相关/数学】【P1966】【NOIP2013D1T2】 火柴排队

    传送门 Description 涵涵有两盒火柴,每盒装有 $n$ 根火柴,每根火柴都有一个高度. 现在将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为:$ \sum ...

  9. Linux系统上的popen()库函数

    popen可以是系统命令,也可以是自己写的程序a.out. 假如a.out就是打印 “hello world“ 在代码中,想获取什么,都可以通过popen获取. 比如获取ls的信息, 比如获取自己写的 ...

  10. 数据添加到solr索引库后前台如何搜索

    主要结构: 查询 Dao: package com.taotao.search.dao.impl; import java.util.ArrayList; import java.util.List; ...