http://blog.csdn.net/wh_19910525/article/details/7392518
1、sdio接口层解析
SDIO总线
SDIO总线 和 USB总线 类似,SDIO也有两端,其中一端是HOST端,另一端是device端。所有的 通信 都是 由HOST端 发送 命令 开始的,Device端只要能解析命令,就可以相互通信。
CLK信号:HOST给DEVICE的 时钟信号,每个时钟周期传输一个命令。
CMD信号:双向 的信号,用于传送 命令 和 反应。
DAT0-DAT3 信号:四条用于传送的数据线。
VDD信号:电源信号。
VSS1,VSS2:电源地信号。
SDIO热插拔原理:
方法:设置一个 定时器检查 或 插拔中断检测
硬件:假如GPG10(EINT18)用于SD卡检测
GPG10 为高电平 即没有插入SD卡
GPG10为低电平 即插入了SD卡
SDIO命令
SDIO总线上都是HOST端发起请求,然后DEVICE端回应请求。sdio命令由6个字节组成。
1. Command:用于开始传输的命令,是由HOST端发往DEVICE端的。其中命令是通过CMD信号线传送的。
2. Response:回应是DEVICE返回的HOST的命令,作为Command的回应。也是通过CMD线传送的。
3. 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同时会返回一个响应。
sd命令格式
以IO_SEND_OP_COND命令为例包含以下部分:
S(开始位) 总为0
D(方向位) 1 从host到 device (0 从device到host)
命令索引: 通过值000101B来
填充位 0
IO_OCR 运转条件寄存器所支持的VDD的最小值和最大值
CRC7 7位CRC校验数据
E(结束位) 总为1
MMC命令总共有40多个,分为class0 ~class7共8类,class0的所有卡必须支持。驱动程序通过发送cmd1、cmd41来区分sd卡和mmc卡,如果发送cmd1返回成功,则为mmc卡,否则发送cmd41返回成功,则为sd卡。
cmd0 初始化mmc卡
--------------------------------------
Sdio接口驱动
首先我们来探讨几个重要的数据结构:该结果位于core核心层,主要用于 核心层与主机驱动层 的数据交换处理。/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层驱动,可以参考S3C24XX的HOST驱动程序 /drivers/mmc/host/s3cmci.c
static struct platform_driver s3cmci_driver = {
.driver = {
.name = "s3c-sdi", //名称和平台设备定义中的对应
.owner = THIS_MODULE,
.pm = s3cmci_pm_ops,
},
.id_table = s3cmci_driver_ids,
.probe = s3cmci_probe, //平台设备探测接口函数
.remove = __devexit_p(s3cmci_remove),
.shutdown = s3cmci_shutdown,
};
s3cmci_probe(struct platform_device *pdev)
{
//....
struct mmc_host *mmc;
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); //分配mmc_host结构体
//.....
}
/*注册中断处理函数s3cmci_irq,来处理数据收发过程引起的各种中断*/
request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host) //注册中断处理函数s3cmci_irq
/*注册中断处理s3cmci_irq_cd函数,来处理热拨插引起的中断,中断触发的形式为上升沿、下降沿触发*/
request_irq(host->irq_cd, s3cmci_irq_cd,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, DRIVER_NAME, host)
mmc_add_host(mmc); //initialise host hardware //向MMC core注册host驱动
----> device_add(&host->class_dev); //添加设备到mmc_bus_type总线上的设备链表中
----> mmc_start_host(host); //启动mmc host
/*MMC drivers should call this when they detect a card has been inserted or removed.检测sd卡是否插上或移除*/
---->mmc_detect_change(host, 0);
/*Schedule delayed work in the MMC work queue.调度延时工作队列*/
mmc_schedule_delayed_work(&host->detect, delay);
搜索host->detected得到以下信息:
/drivers/mmc/core/host.c
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc_rescan(struct work_struct *work)
---->mmc_bus_put(host);//card 从bus上移除时,释放它占有的总线空间
/*判断当前mmc host控制器是否被占用,当前mmc控制器如果被占用,那么 host->claimed = 1;否则为0
*如果为1,那么会在while(1)循环中调用schedule切换出自己,当占用mmc控制器的操作完成之后,执行 *mmc_release_host()的时候,会激活登记到等待队列&host->wq中的其他程序获得mmc主控制器的使用权
*/
mmc_claim_host(host);
mmc_rescan_try_freq(host, max(freqs[i], host->f_min);
/**/
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
…………………………………………..
/* Order's important: probe SDIO, then SD, then MMC */
if (!mmc_attach_sdio(host))
return 0;
if (!mmc_attach_sd(host))
return 0;
if (!mmc_attach_mmc(host))
return 0;
………………………………………….
}
mmc_attach_sdio(struct mmc_host *host) //匹配sdio接口卡
--->mmc_attach_bus(host, &mmc_sdio_ops);
/*当card与总线上的驱动匹配,就初始化card*/
mmc_sdio_init_card(host, host->ocr, NULL, 0);
--->card = mmc_alloc_card(host, NULL);//分配一个card结构体
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); //设置mmc_bus的工作模式
struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; //SDIO functions (devices)
sdio_init_func(host->card, i + 1);
--->func = sdio_alloc_func(card); //分配struct sdio_fun(sdio功能设备)结构体
mmc_io_rw_direct();
card->sdio_func[fn - 1] = func;
mmc_add_card(host->card); //将具体的sdio设备挂载到mmc_bus_types 总线
sdio_add_func(host->card->sdio_func[i]); //将sdio功能设备挂载到sdio_bus_types总线
附:mmc_card结构体中sdio_func[]赋值为分配的sdio_fun结构体,通过fun结构体与wireless驱动层建立联系
--------------------------------------------------------------------------------
mmc_attach_sd(struct mmc_host *host) //匹配sd卡
---> mmc_sd_attach_bus_ops(host);
mmc_sd_init_card(host, host->ocr, NULL);//检测、初始化sd卡
mmc_add_card(struct mmc_card *card) // Register a new MMC card with the driver model.以驱动模式注册一个新mmc卡
mmc_claim_host(host);
mmc_attach_mmc(struct mmc_host *host) //匹配mmc卡
---> mmc_attach_bus_ops(host);
mmc_init_card(host, host->ocr, NULL);//检测、初始化mmc
mmc_add_card(host->card);
mmc_claim_host(host);
struct mmc_request {
struct mmc_command *cmd;
struct mmc_data *data;
struct mmc_command *stop;
void *done_data; /* completion data */
void (*done)(struct mmc_request *);/* completion function */
};
/*将val写到addr地址*/
#define writel(val, addr) outl((val), (unsigned long)(addr))
/*从addr地址处,获取信息*/
#define readl(addr) inl((unsigned long)(addr))
/*发送请求*/
s3cmci_send_request(struct mmc_host *mmc)
---->struct mmc_request *mrq = host->mrq;
/* Clear command, data and fifo status registers
*Fifo clear only necessary on 2440, but doesn't hurt on 2410
*/清除相应状态寄存器
writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT);
writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA);
writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA);
---->s3cmci_setup_data(host, cmd->data);
writel(0, host->base + S3C2410_SDIDCON); //写SDIDCON寄存器
/* add to IMASK register */ 添加相应中断
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
enable_imask(host, imsk); //使能中断
writel(0x0000FFFF, host->base + S3C2410_SDITIMER); //设置sditimer寄存器的状态
writel(0xFF, host->base + S3C2410_SDIPRE); //设置sdipre SDI预分频寄存器的状态
---->if (s3cmci_host_usedma(host)) //判断host的传送模式:dma(直接内存访问模式) 或 pio(CPU执行I/O端口指令来进行数据的读写的数据交换模式)
res = s3cmci_prepare_dma(host, cmd->data);
else
res = s3cmci_prepare_pio(host, cmd->data);
---->s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW); //设置dma模式的属性
s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op) //对dma传输模式的进行相关控制函数
s3c2410_dma_start(chan); //开始
s3c2410_dma_dostop(chan); //停止
s3c2410_dma_flush(chan); //刷新
s3c2410_dma_started(chan);
---->s3cmci_prepare_pio(host, cmd->data);
do_pio_write(host);
enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); //使能Tx FIFO half interrupt 中断
---------------------------------------------------------------------------------------
/*host控制器发送命令*/
s3cmci_send_command(struct s3cmci_host *host,struct mmc_command *cmd)
---->writel(cmd->arg, host->base + S3C2410_SDICMDARG); //将命令写到sdicarg寄存器中
writel(ccon, host->base + S3C2410_SDICMDCON); //将ccon写到sdiccon(sdi控制寄存器)中
s3cmci_irq(int irq, void *dev_id) //设置sdi传输的相关中断处理
---->mmc_signal_sdio_irq(host->mmc); //sdio 中断信号
wake_up_process(host->sdio_irq_thread); //唤醒
当插拔SDIO设备,会触发中断通知到CPU,然后执行卡检测中断处理函数在这个中断服务函数中,mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay), INIT_DELAYED_WORK(&host->detect, mmc_rescan)会调度mmc_rescan函数延时调度工作队列,这样也会触发SDIO设备的初始化流程,检测到有效的SDIO设备后,会将它注册到系统中去。
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
........
mmc_detect_change(host->mmc, msecs_to_jiffies(500));
return IRQ_HANDLED;
}
2、wifi驱动解析
Drivers/net/wireless/libertas/if_sdio.c
Sdio设备的驱动由sdio_driver结构体定义,sdio_register_driver函数将该设备驱动挂载到sdio_bus_type总线上。
/* SDIO function device driver*/
struct sdio_driver {
char *name; //设备名
const struct sdio_device_id *id_table; //设备驱动ID
int (*probe)(struct sdio_func *, const struct sdio_device_id *);//匹配函数
void (*remove)(struct sdio_func *);
struct device_driver drv;
};
/*if_sdio.c*/
static struct sdio_driver if_sdio_driver = {
.name = "libertas_sdio",
.id_table = if_sdio_ids, //用于设备与驱动的匹配
.probe = if_sdio_probe,
.remove = if_sdio_remove,
.drv = {
.pm = &if_sdio_pm_ops,
},
};
/**
* sdio_register_driver - register a function driver
* @drv: SDIO function driver
*/
int sdio_register_driver(struct sdio_driver *drv)
{
drv->drv.name = drv->name;
drv->drv.bus = &sdio_bus_type; //设置driver的bus为sdio_bus_type
return driver_register(&drv->drv);
}
static struct bus_type sdio_bus_type = {
.name = "sdio",
.dev_attrs = sdio_dev_attrs,
.match = sdio_bus_match,
.uevent = sdio_bus_uevent,
.probe = sdio_bus_probe,
.remove = sdio_bus_remove,
.pm = SDIO_PM_OPS_PTR,
};
注意:设备或者驱动注册到系统中的过程中,都会调用相应bus上的匹配函数来进行匹配合适的驱动或者设备,对于sdio设备的匹配是由sdio_bus_match和sdio_bus_probe函数来完成。
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct sdio_driver *sdrv = to_sdio_driver(drv);
if (sdio_match_device(func, sdrv))
return 1;
return 0;
}
static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
struct sdio_driver *sdrv)
{
const struct sdio_device_id *ids;
ids = sdrv->id_table;
if (sdio_match_one(func, ids))
return ids;
}
由以上匹配过程来看,通过匹配id_table 和 sdio_driver设备驱动中id,来匹配合适的驱动或设备。最终会调用.probe函数,来完成相关操作。
If_sdio_probe函数分析
Linux网络设备驱动中的重要数据结构:struct net_device 和 struct net_device_ops
sdio_register_driver(&if_sdio_driver); //注册sdio_driver结构体
card->workqueue = create_workqueue("libertas_sdio"); //创建工作队列
INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
card->model == fw_table[i].model //检测是否支持wifi卡
sdio_claim_host(func);
ret = sdio_enable_func(func); // enables a SDIO function for usage
使能sdio功能设备
/* claim the IRQ for a SDIO function 注册中断*/
ret = sdio_claim_irq(func, if_sdio_interrupt);
priv = lbs_add_card(card, &func->dev);
----> wdev = lbs_cfg_alloc(dmdev); // 分配一个无线网络设备结构体,并初始化
lbs_init_adapter(priv) // initialize adapter structure 初始化网络适配器
dev = alloc_netdev(0, "wlan%d", ether_setup); //分配一个net_device结构体,并对其成员赋值
SET_NETDEV_DEV(dev, dmdev);
wdev->netdev = dev;
priv->dev = dev;
dev->netdev_ops = &lbs_netdev_ops;
init_waitqueue_head(&priv->waitq); //初始化等待队列头
priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");//创建内核线程lbs_thread,它的重要功能为:It handles all events generated by firmware, RX data received from firmware and TX data sent from kernel.即通过固件产生收发事件,从固件接受数据包,发送数据包给kernel。
ret = lbs_start_card(priv);
/* gets the HW spec from the firmware and sets some basic parameters.*/
---->ret = lbs_setup_firmware(priv);// determined the firmware capabities.
设置fireware固件的功能
lbs_cfg_register(priv)
----> ret = register_netdev(priv->dev);//注册net_device网络设备
priv->card = card;
priv->hw_host_to_card = if_sdio_host_to_card; //发送数据
priv->enter_deep_sleep = if_sdio_enter_deep_sleep;
priv->exit_deep_sleep = if_sdio_exit_deep_sleep;
priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup;
发送数据包
if_sdio_host_to_card(struct lbs_private *priv,u8 type, u8 *buf, u16 nb)
----> packet = kzalloc(sizeof(struct if_sdio_packet) + size,GFP_ATOMIC);
struct if_sdio_packet {
struct if_sdio_packet *next;
u16 nb;
u8 buffer[0] __attribute__((aligned(4))); //缓冲区为4字节对齐
};
memcpy(packet->buffer + 4, buf, nb); //复制buf的数据到packet
queue_work(card->workqueue, &card->packet_worker);
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,
};
lbs_hard_start_xmit()//检测发送条件 和 启动数据包的发送
----> netif_stop_queue(priv->mesh_dev); //当发送队列为满或因其他原因来不及发送当前上层传送下来的数据包,则调用此函数阻止上层继续向网络设备驱动传递数据包。当数据包被发送完成后Tx结束的中断处理中,应该调用netif_wake_queue(priv->mesh_dev);唤醒被阻塞的上层,以启动继续向网络设备驱动传送数据包。
netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
……………………………………
netif_stop_queue(priv->dev);
if (priv->tx_pending_len) {
/* This can happen if packets come in on the mesh and eth
device simultaneously -- there's no mutual exclusion on
hard_start_xmit() calls between devices. */
lbs_deb_tx("Packet on %s while busy\n", dev->name);
ret = NETDEV_TX_BUSY;
goto unlock;
}
…………………………….
txpd = (void *)priv->tx_pending_buf; //将发送buf的地址赋给txpd
/*将要发送的数据拷贝到priv->tx_pending_buf缓冲区*/
memcpy(&txpd[1], p802x_hdr, le16_to_cpu(txpd->tx_packet_length));
unlock:
wake_up(&priv->waitq); //唤醒等待队列
}
由以上函数得知,当发送队列为满或因其他原因来不及发送当前上层传递下来的数据包,就会调用netif_stop_queue阻止上层继续向网络设备驱动发送数据包。priv->tx_pending_len为skb中要发送数据的长度,当它不为0时,就会调用wake_up(&priv->waitq)来唤醒等待队列头&priv->waitq。
搜索priv->waitq得知,priv->waitq等待队列头(drivers\net\wireless\libertas\main.c)
init_waitqueue_head(&priv->waitq);
priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main"); //创建内核线程lbs_thread
lbs_thread(void *data) //kernel thread
---->add_wait_queue(&priv->waitq, &wait); //将等待队列添加到wait等待队列中
/* Execute the next command */
if (!priv->dnld_sent && !priv->cur_cmd)
lbs_execute_next_command(priv);
priv->hw_host_to_card(priv, MVMS_DAT,priv->tx_pending_buf,priv->tx_pending_len);
注意:hw_host_to_card函数将带txpd头的packet通过sdio接口发送到wifi芯片。搜索priv->hw_host_to_card得到drivers\net\wireless\libertas\If_sdio.c中的
priv->hw_host_to_card = if_sdio_host_to_card;
if_sdio_host_to_card(struct lbs_private *priv,u8 type, u8 *buf, u16 nb)
----> packet = kzalloc(sizeof(struct if_sdio_packet) + size,GFP_ATOMIC); //分配套接字缓冲区
memcpy(packet->buffer + 4, buf, nb); //复制数据到网络缓冲区(skb)
queue_work(card->workqueue, &card->packet_worker); //工作队列
搜索card->packet_worker得到:card->workqueue = create_workqueue("libertas_sdio");
INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
if_sdio_host_to_card_worker(struct work_struct *work)
----> sdio_writesb(card->func, card->ioport,packet->buffer, packet->nb); //将io端口、sk_buff的数据包信息写到card->func中。
----> sdio_io_rw_ext_helper(func, 1, addr, 0, src, count); //function: Split an arbitrarily sized data transfer into several IO_RW_EXTENDED commands. 一个任意大小的数据传输分裂成几IO_RW_EXTENDED命令。
----> mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize);
mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
----> struct mmc_request mrq;
struct mmc_command cmd;
struct mmc_data data;
mmc_wait_for_req(card->host, &mrq); //mmc_host等待响应请求
----> mmc_start_request(host, mrq); //开始执行请求
----> host->ops->request(host, mrq);
wait_for_completion(&complete); //等待请求的完成
注意:struct mmc_host *host,struct mmc_host_ops *ops; void (*request)(struct mmc_host *host, struct mmc_request *req);因为请求已经通过sdio接口发送到card上,所以我们可以联想到s3cmci.c函数中的struct mmc_host_ops s3cmci_ops 结构体中.request = s3cmci_request成员。
static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct s3cmci_host *host = mmc_priv(mmc);
host->status = "mmc request";
host->cmd_is_stop = 0;
host->mrq = mrq;
/*发出一个请求前先要检测一下卡是否存在,否则提交到块设备层*/
if (s3cmci_card_present(mmc) == 0) {
dbg(host, dbg_err, "%s: no medium present\n", __func__);
host->mrq->cmd->error = -ENOMEDIUM;
mmc_request_done(mmc, mrq); //若卡不存在,马上结束请求
} else
s3cmci_send_request(mmc);
}
由函数得知,s3cmci_card_present(mmc)判断卡是否存在,若存在则发送命令请求。
s3cmci_send_request(struct mmc_host *mmc)
/* Clear command, data and fifo status registersFifo clear only necessary on 2440, but doesn't hurt on 2410*/清空sdi命令状态寄存器、数据状态寄存器和fifo状态寄存器。
---->writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT);
writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA);
writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA);
s3cmci_setup_data(host, cmd->data); //进行数据请求处理设置,主要是数据控制寄存器
----> writel(dcon, host->base + S3C2410_SDIDCON);//将dcon写到sdidcon 数据控制寄存器中
s3cmci_host_usedma(host) //判断发送模式是dma还是pri
mmc_request_done(mmc, mrq); //请求处理完成
/*开始发送命令*/
s3cmci_send_command(host, cmd);
----> writel(cmd->arg, host->base + S3C2410_SDICMDARG); //写sdi command argument register寄存器
writel(ccon, host->base + S3C2410_SDICMDCON); //写sdicmdcon命令控制寄存器
数据包接收
网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断的类型,如果为接收中断,则读取接收到的数据,分配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()函数,进行接收数据。
#define S3C2410_SDIIMSK_SDIOIRQ (1<<12) 由s3c2410芯片手册,sd host receives SDIO interrupter from the card (for SDIO) ,0 = disable 1 = interrupt enable.
/*中断处理函数,来处理数据收发过程引起的各种中断*/
s3cmci_irq()
----> mmc_signal_sdio_irq(host->mmc); //中断信号
----> wake_up_process(host->sdio_irq_thread); //唤醒线程sdio_irq_thread
搜索sdio_irq_thread可知,Sdio_irq.c (drivers\mmc\core)
host->sdio_irq_thread = kthread_run(sdio_irq_thread, host, "ksdioirqd/%s",mmc_hostname(host));
sdio_irq_thread()线程处理函数
----> process_sdio_pending_irqs(host->card); //挂起中断
----> if (func->irq_handler) {func->irq_handler(func);}
if_sdio_interrupt(struct sdio_func *func) //中断处理函数
----> if_sdio_card_to_host(card); //host 控制器从card接收数据
static int if_sdio_card_to_host(struct if_sdio_card *card)
{
size = if_sdio_read_rx_len(card, &ret);
ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY);
type = card->buffer[2] | (card->buffer[3] << 8);
………………………………………………………………
switch (type) {
case MVMS_CMD:
ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4);
case MVMS_DAT:
ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);
case MVMS_EVENT:
ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);
}
…………………………………………………….
}
该函数主要完成向上层上交命令、数据、事件。
/*向上层上交数据包*/
if_sdio_handle_data(struct if_sdio_card *card,u8 *buffer, unsigned size)
----> skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + NET_IP_ALIGN); 分配sk_buff套接字缓冲区
lbs_process_rxed_packet(card->priv, skb);
----> skb->protocol = eth_type_trans(skb, dev); //获取网络协议类型
netif_rx(skb); //根据网络协议类型将数据包交给上层处理
-------------------------------------------------------------------------------------------
当sdio卡拔除时,驱动会调用该函数,完成相应操作。如释放占有的资源,禁止func功能函数,释放host。
if_sdio_remove(struct sdio_func *func)
---->lbs_stop_card(card->priv);
lbs_remove_card(card->priv);
---->kthread_stop(priv->main_thread); //终止内核线程
lbs_free_adapter(priv);
lbs_cfg_free(priv);
free_netdev(dev);
flush_workqueue(card->workqueue); //刷新工作队列
destroy_workqueue(card->workqueue);
sdio_claim_host(func);
sdio_release_irq(func);
sdio_disable_func(func);
sdio_release_host(func);
----------------------------------------------------------------------------------
android 平台USB wifi驱动移植及使用 SDIOwifi
http://blog.csdn.net/wh_19910525/article/details/7389890
----------ok-------------
但是命令发到wpa_supplicant后的流程网上提到的资料就非常少了,不过由于wpa_supplicant是一个标准的开源项目,已经被移植到很多平台上,它中间的过程我暂时还没有去细看。比较关心的是wpa_supplicant在 接收 到上层的命令后 是怎么将命令发给DRIVER的,DRIVER在接收到命令后的解析的动作 以及之后 调用驱动功能函数 的流程以及驱动对寄存器控制的细节。
以下是一张wpa_supplicant的标准结构框图:
重点关注框图的下半部分,即wpa_supplicant是如何与DRIVER进行联系的。整个过程暂以APP发出SCAN命令为主线。由于现在大部分WIFI DRIVER都支持wext, 所以就假设我们的设备走的是wext这条线,其实用ndis也一样,流程感觉差不多。
首先要说的是,在Driver.h文件中有个结构体wpa_driver_ops:
这个结构体在Driver.c中被声明为:
#ifdef CONFIG_DRIVER_WEXT
extern struct wpa_driver_ops wpa_driver_wext_ops;/* driver_wext.c */
然后在driver_wext.c填写了结构体的成员,
const struct wpa_driver_ops wpa_driver_wext_ops = {
.name = "wext",
.desc = "Linux wireless extensions (generic)",
.get_bssid = wpa_driver_wext_get_bssid,
.get_ssid = wpa_driver_wext_get_ssid,
.set_wpa = wpa_driver_wext_set_wpa,
.set_key = wpa_driver_wext_set_key,
.set_countermeasures = wpa_driver_wext_set_countermeasures,
.set_drop_unencrypted = wpa_driver_wext_set_drop_unencrypted,
.scan = wpa_driver_wext_scan,
.combo_scan = wpa_driver_wext_combo_scan,
.get_scan_results2 = wpa_driver_wext_get_scan_results,
.deauthenticate = wpa_driver_wext_deauthenticate,
.disassociate = wpa_driver_wext_disassociate,
.set_mode = wpa_driver_wext_set_mode,
.associate = wpa_driver_wext_associate,
.set_auth_alg = wpa_driver_wext_set_auth_alg,
.init = wpa_driver_wext_init,
.deinit = wpa_driver_wext_deinit,
.add_pmkid = wpa_driver_wext_add_pmkid,
.remove_pmkid = wpa_driver_wext_remove_pmkid,
.flush_pmkid = wpa_driver_wext_flush_pmkid,
.get_capa = wpa_driver_wext_get_capa,
.set_operstate = wpa_driver_wext_set_operstate,
#ifdef ANDROID
.driver_cmd = wpa_driver_priv_driver_cmd,
#endif
};
这些成员其实都是驱动 和 wpa_supplicant 的 接口,以SCAN为例:
int wpa_driver_wext_scan(void *priv, const u8 *ssid, size_t ssid_len)
中的LINE1174:if (ioctl(drv->ioctl_sock, SIOCSIWSCAN, &iwr) < 0)从这里可以看出 wpa_cupplicant是通过IOCTL来调用SOCKET与DRIVER进行通信的,并给DRIVER下达SIOCSIWSCAN这个命令。
这样,一个命令从APP到FRAMEWORK到C++本地库再到wpa_supplicant适配层,再由wpa_supplicant下CMD给DRIVER的路线就打通了。
-------------------------------------------
由于在这个项目中,WIFI模块 是 采用SDIO总线 来 控制 的,Client Driver 的SDIO部分分为三层:SdioDrv、SdioAdapter、SdioBusDrv。其中SdioBusDrv是Client Driver中SDIO与WIFI模块的接口,SdioAdapter是SdioDrv和SdioBusDrv之间的适配层,SdioDrv是Client Driver中SDIO与LINUX KERNEL中的MMC SDIO的接口。这三部分只需要关注一下SdioDrv就可以了,另外两层都只是对它的封装。
在SdioDrv中提供了这几个功能:
(1)static structsdio_driver tiwlan_sdio_drv = {
.probe = tiwlan_sdio_probe,
.remove = tiwlan_sdio_remove,
.name = "sdio_tiwlan",
.id_table = tiwl12xx_devices,
};
(2)int sdioDrv_EnableFunction(unsigned int uFunc)
(3)int sdioDrv_EnableInterrupt(unsigned int uFunc)
(4)SDIO的读写,实际是调用了MMC\Core中的 static int mmc_io_rw_direct_host()功能。
SDIO功能部分简单了解下就可以,一般HOST部分芯片厂商都会做好。我们的主要任务还是WIFI模块。
首先从WIFI模块的入口函数wlanDrvIf_ModuleInit()看起,这里调用了wlanDrvIf_Create()。
代码主体部分:
static int wlanDrvIf_Create (void)
{
TWlanDrvIfObj *drv; //这个结构体为代表设备,包含LINUX网络设备结构体net_device
pDrvStaticHandle = drv; /* save for module destroy */
drv->pWorkQueue = create_singlethread_workqueue (TIWLAN_DRV_NAME);//创建了工作队列
/* Setup driver network interface. */
rc = wlanDrvIf_SetupNetif (drv); //这个函数超级重要,后面详细的看
drv->wl_sock = netlink_kernel_create( NETLINK_USERSOCK, 0, NULL, NULL, THIS_MODULE );
// 创建了接受wpa_supplicant的SOCKET接口
/* Create all driver modules and link their handles */
rc = drvMain_Create (drv,
&drv->tCommon.hDrvMain,
&drv->tCommon.hCmdHndlr,
&drv->tCommon.hContext,
&drv->tCommon.hTxDataQ,
&drv->tCommon.hTxMgmtQ,
&drv->tCommon.hTxCtrl,
&drv->tCommon.hTWD,
&drv->tCommon.hEvHandler,
&drv->tCommon.hCmdDispatch,
&drv->tCommon.hReport,
&drv->tCommon.hPwrState);
/*
* Initialize interrupts (or polling mode for debug):
*/
/* Normal mode: Interrupts (the default mode) */
rc = hPlatform_initInterrupt (drv, (void*)wlanDrvIf_HandleInterrupt);
return 0;
}
在调用完wlanDrvIf_Create()这个函数后,实际上WIFI模块的初始化就结束了,下面分析如何初始化的。先看wlanDrvIf_SetupNetif (drv)这个函数的主体,
static int wlanDrvIf_SetupNetif(TWlanDrvIfObj *drv)
{
struct net_device *dev;
int res;
/* Allocate network interface structure for the driver */
dev = alloc_etherdev (0);//申请LINUX网络设备
if (dev == NULL)
/* 申请失败 的结果 */
/* Setup the network interface */
ether_setup (dev);//建立网络接口 ,这两个都是LINUX网络设备驱动的标准函数
dev->netdev_ops = &wlan_netdev_ops;
/* Initialize Wireless Extensions interface (WEXT) */
wlanDrvWext_Init (dev);
res = register_netdev (dev);
/* Setup power-management callbacks */
hPlatform_SetupPm(wlanDrvIf_Suspend, wlanDrvIf_Resume, pDrvStaticHandle);
}
注意,在这里初始化了wlanDrvWext_Inti(dev),这就说明wpa_supplicant与Driver直接的联系是走的WEXT这条路。也就是说event的接收,处理也应该是在WEXT部分来做的,确定这个,剩下的工作量顿减三分之一。后面还注册了网络设备dev。而在wlan_netdev_ops中定义的功能如下:
static const struct net_device_ops wlan_netdev_ops = {
.ndo_open = wlanDrvIf_Open,
.ndo_stop = wlanDrvIf_Release,
.ndo_do_ioctl = NULL,
.ndo_start_xmit = wlanDrvIf_Xmit,
.ndo_get_stats = wlanDrvIf_NetGetStat,
.ndo_validate_addr = NULL,
};
功能一看名字就知道了,这几个对应的都是LINUX网络设备驱动 都有的命令字,详见《LINUX设备驱动开发详解》第十六章。
在这之后,又调用了rc =drvMain_CreateI。在这个函数里完成了相关模块的初始化工作。接下来就是等待Android上层发送来的事件了。
二、linux内核配置
在原有android内核支持情况下,增加wifi内核配置,具体配置如下:
1. Networkingsupport --->Wireless下增加802.11 协议栈的支持
2. USB 支持WIFI的配置
USB 支持WIFI 的配置选项位于Device Drivers>USB support 配置菜单下USB Wireless Device Management support。
3. 用户空间的mdev 和firmware 支持配置
进入Device Drivers > Generic Driver Options 配置菜单,按照下图所示配置用户空间
的mdev 和firmware支持。
4. WIFI 设备支持配置
Device Drivers ---> Network device support ---> Wireless LAN ---> Ralink driver support--->Ralink rt2800 (USB) support (EXPERIMENTAL) --->rt2800usb - Include support for rt30xx (USB) devices
以及Wireless LAN 目录里IEEE 802.11 for Host AP (Prism2/2.5/3 andWEP/TKIP/CCMP)都选择上,目的是打开CONFIG_WIRELESS_EXT=y CONFIG_WEXT_PRIV=y
1. 修改驱动SDK包中的配置文件
1.1 修改env.mk,将RT28xx_DIR 设为当前目录,RT28xx_DIR = $(shell pwd)。
1.2 修改makefile中对应的kernel与交叉编译器路径
1.3 修改os/linux目录下config.mk中gcc 与 ld变量
1.4 打开os/linux目录下config.mk中HAS_WPA_SUPPLICANT与HAS_NATIVE_WPA_SUPPLICANT_SUPPORT宏
2. 修改驱动SDK包中的驱动源码
2.1 将rt_linux.h中的RTUSB_URB_ALLOC_BUFFER与RTUSB_URB_FREE_BUFFER宏修改,定义如下
#define RTUSB_URB_ALLOC_BUFFER(pUsb_Dev,BufSize, pDma_addr) usb_alloc_coherent(pUsb_Dev,BufSize, GFP_ATOMIC, pDma_addr)
#defineRTUSB_URB_FREE_BUFFER(pUsb_Dev, BufSize, pTransferBuf, Dma_addr) usb_free_coherent(pUsb_Dev, BufSize,pTransferBuf, Dma_addr)
2.2 修改rt_main_dev.c,直接将MainVirtualIF_close函数放空,return 0,解决不能反复关闭wifi问题。
2.3 修改rt_linux.c里RtmpOSNetDevAttach函数里增加devname赋值。strcpy( pNetDev->name, "mlan0");注:(此处所用的名字要与上层使用的节点名保持一致,在此说明一下上层主要有这几处用到节点名:
1,\frameworks\base\wifi\java\android\net\wifiWifiStateTracker.java
2,init.rc启动wpa_supplicant守护进程里面与启动dhcpcd服务
3,dhcpcd服务配置文件,dhcpcd.conf里面
4,init.rc设置setprop wifi.interface "mlan0")
3. 编译方法
Source env.mk;make;即可,驱动是在的路径为os/linux下的rt3070sta.ko。
此处所用的驱动名字应与HAL层wifi.c所指定驱动名保持一致
四、wap_supplicant相关配置
3.1 rootfs-src/external/wpa_supplicant_6/wpa_supplicant.conf配置文件的修改
ctrl_interface=DIR=/data/system/wpa_supplicantGROUP=wifi #这个路径在wifi.c中用到
3.2 整个环境必须要让wext类型相关代码进行编译。也就是要打开wext相关的宏CONFIG_DRIVER_WEXT。 即在device/hisi/Hi3716C/BoardConfig.mk中添加:
BOARD_HAVE_WIFI := true
BOARD_WPA_SUPPLICANT_DRIVER := WEXT
该配置的作用是使external/wpa_supplicant/Android.mk设置WPA_BUILD_SUPPLICANT为true。
3.3 在init.rc里面增加启动wpa_supplicant守护进程及dhcpcd进程
3.4 在init.rc里面增加wifi相关文件的权限设定,设置如下:
chmod 0771 /system/etc/wifi
chmod 0660/system/etc/wifi/wpa_supplicant.conf
chown wifi wifi /system/etc/wifi/wpa_supplicant.conf #wifi的原始配置文件
#wpa_supplicantsocket
mkdir/data/system/wpa_supplicant 0770 wifi wifi
chmod 0771/data/system/wpa_supplicant #放置wifiinterface的地方
mkdir/data/misc/wifi 0770 wifi wifi
chmod 0771/data/misc/wifi
chmod 0660 /data/misc/wifi/wpa_supplicant.conf #wifi的配置文件,将由wpa_supplicant根据实际配置写入该文件
chown wifiwifi /data/misc/wifi
chown wifiwifi /data/misc/wifi/wpa_supplicant.conf
mkdir/data/misc/wifi/sockets 0770 wifi wifi #与上层通过socket通信的路径
cp/system/etc/wifi/wpa_supplicant.conf /data/misc/wifi/
mkdir/data/misc/dhcp 0777 dhcp dhcp
chown dhcpdhcp /data/misc/dhcp
# Preparefor wifi
setpropwifi.interface "mlan0"
setprop wlan.driver.status "ok"
3.5 启动wpa_supplicant守护进程与dhcpcd服务
在init.rc里面添加wpa_supplicant启动:
service wpa_supplicant /system/bin/logwrapper /system/bin/wpa_supplicant -Dwext -imlan0 -c /data/misc/wifi/wpa_supplicant.conf -dd
user root
group system wifi inet
socket wpa_mlan0 dgram 660 wifi wifi
disable
oneshot
在init.rc里面添加dhcpcd启动:
service dhcpcd /system/bin/logwrapper/system/bin/dhcpcd -d -B wlan0
disabled
oneshot
3.6 在init.godbox.rc里增加dns设置
Setprop net.dns1 192.168.10.247
Setprop net.dns2 192.168.10.248
4.1 添加wifi的wpa_supplicant.conf配置文件
放置目录与hardware/libhardware_legacy/wifi/wifi.c中的目录保持一致
4.2 添加驱动的配置文件
在system/etc/Wireless/RT2870STA目录放置配置文件RT2870STA.dat,与rt_linux.h中配置文件的路径保持一致。
4.3 添加dhcpcd启动配置文件
设置/system/etc/dhcpcd/dhcpcd.conf的配置为:
interface mlan0
option subnet_mask, routers,domain_name_servers
6.1 内核
内核的修改如上述第二大点内核配置
6.2 Wpa_supplicant
将wpa_supplicant_6编译打开
Wpa_supplicant 主要是在device/hisi/Hi3716C/BoardConfig.mk中添加:
BOARD_HAVE_WIFI := true
BOARD_WPA_SUPPLICANT_DRIVER := WEXT
以及在wpa_supplicant_6 里面的.config增加ANDROID=y
----------ok-------------
但是命令发到wpa_supplicant后的流程网上提到的资料就非常少了,不过由于wpa_supplicant是一个标准的开源项目,已经被移植到很多平台上,它中间的过程我暂时还没有去细看。比较关心的是wpa_supplicant在 接收 到上层的命令后 是怎么将命令发给DRIVER的,DRIVER在接收到命令后的解析的动作 以及之后 调用驱动功能函数 的流程以及驱动对寄存器控制的细节。
以下是一张wpa_supplicant的标准结构框图:
重点关注框图的下半部分,即wpa_supplicant是如何与DRIVER进行联系的。整个过程暂以APP发出SCAN命令为主线。由于现在大部分WIFI DRIVER都支持wext, 所以就假设我们的设备走的是wext这条线,其实用ndis也一样,流程感觉差不多。
首先要说的是,在Driver.h文件中有个结构体wpa_driver_ops:
这个结构体在Driver.c中被声明为:
#ifdef CONFIG_DRIVER_WEXT
extern struct wpa_driver_ops wpa_driver_wext_ops;/* driver_wext.c */
然后在driver_wext.c填写了结构体的成员,
const struct wpa_driver_ops wpa_driver_wext_ops = {
.name = "wext",
.desc = "Linux wireless extensions (generic)",
.get_bssid = wpa_driver_wext_get_bssid,
.get_ssid = wpa_driver_wext_get_ssid,
.set_wpa = wpa_driver_wext_set_wpa,
.set_key = wpa_driver_wext_set_key,
.set_countermeasures = wpa_driver_wext_set_countermeasures,
.set_drop_unencrypted = wpa_driver_wext_set_drop_unencrypted,
.scan = wpa_driver_wext_scan,
.combo_scan = wpa_driver_wext_combo_scan,
.get_scan_results2 = wpa_driver_wext_get_scan_results,
.deauthenticate = wpa_driver_wext_deauthenticate,
.disassociate = wpa_driver_wext_disassociate,
.set_mode = wpa_driver_wext_set_mode,
.associate = wpa_driver_wext_associate,
.set_auth_alg = wpa_driver_wext_set_auth_alg,
.init = wpa_driver_wext_init,
.deinit = wpa_driver_wext_deinit,
.add_pmkid = wpa_driver_wext_add_pmkid,
.remove_pmkid = wpa_driver_wext_remove_pmkid,
.flush_pmkid = wpa_driver_wext_flush_pmkid,
.get_capa = wpa_driver_wext_get_capa,
.set_operstate = wpa_driver_wext_set_operstate,
#ifdef ANDROID
.driver_cmd = wpa_driver_priv_driver_cmd,
#endif
};
这些成员其实都是驱动 和 wpa_supplicant 的 接口,以SCAN为例:
int wpa_driver_wext_scan(void *priv, const u8 *ssid, size_t ssid_len)
中的LINE1174:if (ioctl(drv->ioctl_sock, SIOCSIWSCAN, &iwr) < 0)从这里可以看出 wpa_cupplicant是通过IOCTL来调用SOCKET与DRIVER进行通信的,并给DRIVER下达SIOCSIWSCAN这个命令。
这样,一个命令从APP到FRAMEWORK到C++本地库再到wpa_supplicant适配层,再由wpa_supplicant下CMD给DRIVER的路线就打通了。
-------------------------------------------
由于在这个项目中,WIFI模块 是 采用SDIO总线 来 控制 的,Client Driver 的SDIO部分分为三层:SdioDrv、SdioAdapter、SdioBusDrv。其中SdioBusDrv是Client Driver中SDIO与WIFI模块的接口,SdioAdapter是SdioDrv和SdioBusDrv之间的适配层,SdioDrv是Client Driver中SDIO与LINUX KERNEL中的MMC SDIO的接口。这三部分只需要关注一下SdioDrv就可以了,另外两层都只是对它的封装。
在SdioDrv中提供了这几个功能:
(1)static structsdio_driver tiwlan_sdio_drv = {
.probe = tiwlan_sdio_probe,
.remove = tiwlan_sdio_remove,
.name = "sdio_tiwlan",
.id_table = tiwl12xx_devices,
};
(2)int sdioDrv_EnableFunction(unsigned int uFunc)
(3)int sdioDrv_EnableInterrupt(unsigned int uFunc)
(4)SDIO的读写,实际是调用了MMC\Core中的 static int mmc_io_rw_direct_host()功能。
SDIO功能部分简单了解下就可以,一般HOST部分芯片厂商都会做好。我们的主要任务还是WIFI模块。
首先从WIFI模块的入口函数wlanDrvIf_ModuleInit()看起,这里调用了wlanDrvIf_Create()。
代码主体部分:
static int wlanDrvIf_Create (void)
{
TWlanDrvIfObj *drv; //这个结构体为代表设备,包含LINUX网络设备结构体net_device
pDrvStaticHandle = drv; /* save for module destroy */
drv->pWorkQueue = create_singlethread_workqueue (TIWLAN_DRV_NAME);//创建了工作队列
/* Setup driver network interface. */
rc = wlanDrvIf_SetupNetif (drv); //这个函数超级重要,后面详细的看
drv->wl_sock = netlink_kernel_create( NETLINK_USERSOCK, 0, NULL, NULL, THIS_MODULE );
// 创建了接受wpa_supplicant的SOCKET接口
/* Create all driver modules and link their handles */
rc = drvMain_Create (drv,
&drv->tCommon.hDrvMain,
&drv->tCommon.hCmdHndlr,
&drv->tCommon.hContext,
&drv->tCommon.hTxDataQ,
&drv->tCommon.hTxMgmtQ,
&drv->tCommon.hTxCtrl,
&drv->tCommon.hTWD,
&drv->tCommon.hEvHandler,
&drv->tCommon.hCmdDispatch,
&drv->tCommon.hReport,
&drv->tCommon.hPwrState);
/*
* Initialize interrupts (or polling mode for debug):
*/
/* Normal mode: Interrupts (the default mode) */
rc = hPlatform_initInterrupt (drv, (void*)wlanDrvIf_HandleInterrupt);
return 0;
}
在调用完wlanDrvIf_Create()这个函数后,实际上WIFI模块的初始化就结束了,下面分析如何初始化的。先看wlanDrvIf_SetupNetif (drv)这个函数的主体,
static int wlanDrvIf_SetupNetif(TWlanDrvIfObj *drv)
{
struct net_device *dev;
int res;
/* Allocate network interface structure for the driver */
dev = alloc_etherdev (0);//申请LINUX网络设备
if (dev == NULL)
/* 申请失败 的结果 */
/* Setup the network interface */
ether_setup (dev);//建立网络接口 ,这两个都是LINUX网络设备驱动的标准函数
dev->netdev_ops = &wlan_netdev_ops;
/* Initialize Wireless Extensions interface (WEXT) */
wlanDrvWext_Init (dev);
res = register_netdev (dev);
/* Setup power-management callbacks */
hPlatform_SetupPm(wlanDrvIf_Suspend, wlanDrvIf_Resume, pDrvStaticHandle);
}
注意,在这里初始化了wlanDrvWext_Inti(dev),这就说明wpa_supplicant与Driver直接的联系是走的WEXT这条路。也就是说event的接收,处理也应该是在WEXT部分来做的,确定这个,剩下的工作量顿减三分之一。后面还注册了网络设备dev。而在wlan_netdev_ops中定义的功能如下:
static const struct net_device_ops wlan_netdev_ops = {
.ndo_open = wlanDrvIf_Open,
.ndo_stop = wlanDrvIf_Release,
.ndo_do_ioctl = NULL,
.ndo_start_xmit = wlanDrvIf_Xmit,
.ndo_get_stats = wlanDrvIf_NetGetStat,
.ndo_validate_addr = NULL,
};
功能一看名字就知道了,这几个对应的都是LINUX网络设备驱动 都有的命令字,详见《LINUX设备驱动开发详解》第十六章。
在这之后,又调用了rc =drvMain_CreateI。在这个函数里完成了相关模块的初始化工作。接下来就是等待Android上层发送来的事件了。