在线时间94 小时
UID3617445
注册时间2020-2-7
NXP金币9
TA的每日心情 | 开心 2023-2-28 15:37 |
---|
签到天数: 42 天 [LV.5]常住居民I
版主
- 积分
- 1369
- 最后登录
- 2024-2-23
|
本帖最后由 y369369 于 2021-6-17 10:59 编辑
主题月刚好是wifi,所以今天我们来分析分析wifi。首先我们从USB设备这条线索开始。在分析之前,我们需要理解在整个wifi模块中,USB充当什么角色?它的作用是什么?实质上wifi模块上的数据传输有两端,一端是wifi芯片与wifi芯片之间,通过无线射频(RF)进行数据传输;另一端则是wifi芯片与CPU之间,通过USB进行数据传输。
了解Linux的USB驱动的读者都知道,USB驱动分为两种:一种是USB主机驱动;另一种是USB设备驱动。而我们的USB接口的wifi模块对于CPU(主机)来说,属于USB设备,因此采用USB设备驱动。
有了以上信息之后,我们先让Linux系统识别该USB接口的wifi模块,首先我们在驱动源码中大致添加以下几步工作:
(1)定义一个usb_driver结构体变量:
- struct usb_driver xxx_usb_wifi_driver;
复制代码 (2)填充该设备的usb_driver结构体成员变量:
- static struct usb_driver xxx_usb_wifi_driver = {
- .name = "XXX_USB_WIFI",
- .probe = xxx_init_wifi,
- .disconnect = xxx_remove,
- .suspend = xxx_suspend,
- .resume = xxx_resume,
- .id_table= xxx_table,
- };
复制代码 (3)将该驱动注册到USB子系统:
usb_register(&xxx_usb_wifi_driver);
简单完成以上几步工作,再加上板级文件(arch/mach-xxx.c)对USB设备的支持,Linux的USB子系统几乎可以挂载该wifi模块为USB设备了。但是这并不是我们最终想要的结果。我们还要让Linux系统知道它挂载的USB设备属于无线网络设备,同时能够访问它,利用它实施无线网络的工作。
我们都知道,若要让USB设备真正工作起来,需要对USB设备的4个层次(设备、配置、接口、端点)进行初始化。当然这四个层次并不是一定都要进行初始化,而是根据你的USB设备的功能进行选择的,大致初始化流程如下伪代码:
- static struct dvobj_priv *usb_dvobj_init(struct usb_interface *usb_intf)
- {
- int i;
- u8 val8;
- int status= _FAIL;
- struct dvobj_priv *pdvobjpriv;
- //设备
- struct usb_device *pusbd;
- struct usb_device_descriptor *pdev_desc;
- //配置
- struct usb_host_config *phost_conf;
- struct usb_config_descriptor *pconf_desc;
- //接口
- struct usb_host_interface *phost_iface;
- struct usb_interface_descriptor *piface_desc;
- //端点
- struct usb_host_endpoint *phost_endp;
- struct usb_endpoint_descriptor *pendp_desc;
- //设备的初始化
- pdvobjpriv->pusbintf = usb_intf ;
- pusbd =pdvobjpriv->pusbdev = interface_to_usbdev(usb_intf);
- usb_set_intfdata(usb_intf, pdvobjpriv);
- pdev_desc =&pusbd->descriptor;
- //配置的初始化
- phost_conf =pusbd->actconfig;
- pconf_desc =&phost_conf->desc;
- //接口的初始化
- phost_iface =&usb_intf->altsetting[0];
- piface_desc =&phost_iface->desc;
复制代码 端点的初始化,由于wifi模块属于网络设备,传输批量数据,因此需要初始化为批量端点,端点方向(输入、输出)等。同时,由于wifi驱动功能比较多,需要初始化几个输入输出端点。
- for (i = 0; i <pdvobjpriv->nr_endpoint; i++)
- {
- phost_endp = phost_iface->endpoint +i;
- if (phost_endp)
- {
- pendp_desc =&phost_endp->desc;
- //检查是否为输入端点
- usb_endpoint_is_bulk_in(pendp_desc);
- //检查是否为输出端点
- usb_endpoint_is_bulk_out(pendp_desc);
- }
- }
- usb_get_dev(pusbd);
- }
复制代码 完成以上的初始化工作之后,接下来我们需要理清一下USB接口的作用,它是wifi芯片内部的固件程序与主机上的Linux系统进行数据通信。USB设备通信不像普通字符设备那样采用I/O内存和I/O端口的访问,而是采用一种称为URB(USB Request Block)的USB请求块,URB在整个USB子系统中,相当于通电设备中的“电波”,USB主机与设备的通信,通过“电波”来传递。下面我们就来编写USB接口的读写操作函数,伪代码如下:
- void xxx_wifi_usb_intf_ops(struct _io_ops *pops)
- {
- //当需要进行简单数据的读取时,采用以下操作
- pops->_read8 = &usb_read8;
- pops->_read16 = &usb_read16;
- pops->_read32 = &usb_read32;
- //当需要进行批量数据的读取时,采用以下操作
- pops->_read_port = &usb_read_port;
- //当需要进行简单数据的写时,采用以下操作
- pops->_write8 = &usb_write8;
- pops->_write16 = &usb_write16;
- pops->_write32 = &usb_write32;
- pops->_writeN = &usb_writeN;
- //当需要进行批量数据的写时,采用以下操作
- pops->_write_port = &usb_write_port;
- //取消读写urb
- pops->_read_port_cancel = &usb_read_port_cancel;
- pops->_write_port_cancel = &usb_write_port_cancel;
- }
复制代码 在进行批量数据的读写时,如usb_read_port()和usb_write_port()函数,需要完成urb创建、初始化、提交、完成处理这个完整的流程。伪代码如下:
(1)批量读操作
- static u32 usb_read_port(struct intf_hdl *pintfhdl, u32 addr, u32 cnt, u8 *rmem)
- {
- int err;
- unsigned intpipe;
- PURB purb =NULL;
- structrecv_buf *precvbuf = (structrecv_buf *)rmem;
- structusb_device *pusbd = pdvobj->pusbdev;
- //创建urb,这里是在其它地方创建完成之后,传递过来
- purb =precvbuf->purb;
- //初始化批量urb
- usb_fill_bulk_urb(purb, pusbd, pipe,
- precvbuf->pbuf,
- MAX_RECVBUF_SZ,
- usb_read_port_complete,
- precvbuf);//contextis precvbuf
- //提交urb
- err =usb_submit_urb(purb, GFP_ATOMIC);
- }
复制代码 (2)批量写操作
- u32 usb_write_port(struct intf_hdl *pintfhdl, u32 addr, u32 cnt, u8 *wmem)
- {
- unsigned int pipe;
- intstatus;
- PURB purb = NULL;
- structxmit_priv *pxmitpriv =&padapter->xmitpriv;
- structxmit_buf *pxmitbuf = (struct xmit_buf *)wmem;
- structxmit_frame *pxmitframe = (struct xmit_frame *)pxmitbuf->priv_data;
- structusb_device *pusbd = pdvobj->pusbdev;
- structpkt_attrib *pattrib = &pxmitframe->attrib;
- //创建urb,这里是在其它地方创建完成之后,传递过来
- purb = pxmitbuf->pxmit_urb[0];
- //初始化批量urb
- usb_fill_bulk_urb(purb, pusbd, pipe,
- pxmitframe->buf_addr,//= pxmitbuf->pbuf
- cnt,
- usb_write_port_complete,
- pxmitbuf);//contextis pxmitbuf
- //提交urb
- status = usb_submit_urb(purb,GFP_ATOMIC);
- return ret;
- }
复制代码 完成以上批量数据的读写操作之后,大家可能会疑问:这不是一般USB设备驱动的操作流程吗?貌似和wifi没有半毛钱的关系啊!
写到这发现确实和wifi没有任何联系,但是以上只是一个铺垫。我们一直强调USB接口在wifi模块中充当什么角色,既然是接口,那么它就是为数据传输而生。所以,和wifi扯上关系的就在于usb_read_port()和usb_write_port()这两个函数。
1、首先需要明确wifi模块是USB设备,主控(CPU)端是USB主机;
2、USB主机若需要对wifi模块进行数据的读写时,就必须经过USB接口;
3、既然涉及到数据的读写操作,必然要用相应的读写函数,那么usb_read_port()和usb_write_port()即是它们的读写函数。
我们先从读数据开始进行分析,在分析之前,我们必须了解USB设备驱动的读数据过程。USB读取数据操作流程如下:
(1)通过usb_alloc_urb()函数创建并分配一个URB,作为传输USB数据的载体
(2)创建并分配DMA缓冲区,以DMA方式快速传输数据;
(3)初始化URB,根据wifi的传输数据量,我们需要初始化为批量URB,相应操作函数为usb_fill_bulk_urb();
(4)将URB提交到USB核心;
(5)提交成功后,URB的完成函数将被USB核心调用。
现在我们一步步地详细分析整个过程,所谓的创建和分配,实质上是对内存的分配。作为一名Linux驱动开发程序员,必须了解Linux内存管理相关知识及合理使用内存。
那么我们应该怎样合理地创建和分配URB和DMA缓冲区呢?很明显,我们应该在用的时候分配,在不用的时候释放。
那么问题来了……什么时候在用,又什么时候不用呢?问题很简单,就是主控端读数据时分配,读完后释放,而只有当wifi模块有数据可读时,主控端才能成功地读取数据。那么wifi模块什么时候有数据可读呢?——下面重点来了!wifi模块通过RF端接收到无线网络数据,然后缓存到wifi芯片的RAM中,此时,wifi模块就有数据可读了。
经过上面的分析,我们找到了一条USB接口与wifi模块扯上关系的线索,就是wifi模块的接收数据,会引发USB接口的读数据;
现在,我们转到wifi模块的接收函数中,看看是不是真的这样?
在wifi接收函数初始化中,我们可以看到usb_alloc_urb()创建一个中断URB。伪代码如下:
- int xxxwifi_init_recv(_adapter *padapter)
- {
- struct recv_priv *precvpriv = &padapter->recvpriv;
- int i, res = _SUCCESS;
- struct recv_buf *precvbuf;
-
- tasklet_init(&precvpriv->recv_tasklet, (void(*)(unsigned long))rtl8188eu_recv_tasklet, (unsigned long)padapter);
-
- precvpriv->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); //创建一个中断URB
-
- precvpriv->int_in_buf = rtw_zmalloc(INTERRUPT_MSG_FORMAT_LEN);
- //init recv_buf
- _rtw_init_queue(&precvpriv->free_recv_buf_queue);
- _rtw_init_queue(&precvpriv->recv_buf_pending_queue);
-
- precvpriv -> pallocated_recv_buf = rtw_zmalloc(NR_RECVBUFF *sizeof(struct recv_buf) + 4);
- precvbuf = (struct recv_buf*)precvpriv->precv_buf;
-
- for(i=0; i < NR_RECVBUFF ; i++)
- {
- _rtw_init_listhead(&precvbuf->list);
- _rtw_spinlock_init(&precvbuf->recvbuf_lock);
- precvbuf->alloc_sz = MAX_RECVBUF_SZ;
-
- res = rtw_os_recvbuf_resource_alloc(padapter, precvbuf);
-
- precvbuf->ref_cnt = 0;
- precvbuf->adapter =padapter;
- precvbuf++;
- }
- precvpriv->free_recv_buf_queue_cnt = NR_RECVBUFF;
-
- skb_queue_head_init(&precvpriv->rx_skb_queue);
-
- #ifdef CONFIG_PREALLOC_RECV_SKB
- {
- int i;
- SIZE_PTR tmpaddr=0;
- SIZE_PTR alignment=0;
- struct sk_buff *pskb=NULL;
- skb_queue_head_init(&precvpriv->free_recv_skb_queue);
- for(i=0; i<NR_PREALLOC_RECV_SKB; i++)
- {
- pskb = rtw_skb_alloc(MAX_RECVBUF_SZ + RECVBUFF_ALIGN_SZ);
- if(pskb)
- {
- pskb->dev = padapter->pnetdev;
- tmpaddr = (SIZE_PTR)pskb->data;
- alignment = tmpaddr & (RECVBUFF_ALIGN_SZ-1);
- skb_reserve(pskb, (RECVBUFF_ALIGN_SZ - alignment));
- skb_queue_tail(&precvpriv->free_recv_skb_queue, pskb);
- }
- pskb=NULL;
- }
- }
- #endif
- return res;
- }
复制代码 在rtw_os_recvbuf_resource_alloc函数中,创建一个批量URB和一个DMA缓冲区。伪代码如下:
- int rtw_os_recvbuf_resource_alloc(_adapter *padapter, struct recv_buf *precvbuf)
- {
- int res=_SUCCESS;
- struct dvobj_priv *pdvobjpriv = adapter_to_dvobj(padapter);
- struct usb_device *pusbd = pdvobjpriv->pusbdev;
-
- precvbuf->irp_pending = _FALSE;
- precvbuf->purb = usb_alloc_urb(0, GFP_KERNEL); //创建一个批量URB
-
- precvbuf->pskb = NULL;
- precvbuf->reuse = _FALSE;
- precvbuf->pallocated_buf = precvbuf->pbuf = NULL;
- precvbuf->pdata = precvbuf->phead = precvbuf->ptail = precvbuf->pend = NULL;
- precvbuf->transfer_len = 0;
- precvbuf->len = 0;
-
- #ifdef CONFIG_USE_USB_BUFFER_ALLOC_RX
- precvbuf->pallocated_buf = rtw_usb_buffer_alloc(pusbd, (size_t)precvbuf->alloc_sz, &precvbuf->dma_transfer_addr); //创建一个DMA缓冲区
- precvbuf->pbuf = precvbuf->pallocated_buf;
- if(precvbuf->pallocated_buf == NULL)
- return _FAIL;
- #endif //CONFIG_USE_USB_BUFFER_ALLOC_RX
-
- return res;
- }
复制代码 在usb_read_port()函数中,通过usb_fill_bulk_urb()初始化批量URB,并且提交给USB核心,也即USB读取数据操作流程的第3、4步。在usb_fill_bulk_urb()函数中,初始化URB的完成函数usb_read_port_complete(),只有当URB提交完成后,函数usb_read_port_complete()将被调用。伪代码如下:
- static u32 usb_read_port(struct intf_hdl *pintfhdl, u32 addr, u32 cnt, u8 *rmem)
- {
- struct recv_buf *precvbuf = (struct recv_buf *)rmem;
- _adapter *adapter = pintfhdl->padapter;
- struct dvobj_priv *pdvobj = adapter_to_dvobj(adapter);
- struct pwrctrl_priv *pwrctl = dvobj_to_pwrctl(pdvobj);
- struct recv_priv *precvpriv = &adapter->recvpriv;
- struct usb_device *pusbd = pdvobj->pusbdev;
-
- rtl8188eu_init_recvbuf(adapter, precvbuf);
-
- precvpriv->rx_pending_cnt++;
-
- purb = precvbuf->purb;
-
- //translate DMA FIFO addr to pipehandle
- pipe = ffaddr2pipehdl(pdvobj, addr);
-
- usb_fill_bulk_urb(purb, pusbd, pipe,
- precvbuf->pbuf,
- MAX_RECVBUF_SZ,
- usb_read_port_complete,
- precvbuf);//context is precvbuf
-
- err = usb_submit_urb(purb, GFP_ATOMIC);
-
- return ret;
- }
复制代码 通过上面的代码,我们可以得知在wifi模块为接收数据做初始化准备时,分配了URB和DMA缓冲区。而在usb_read_port()函数中初始化URB和提交URB。
。。。。。
好了,今天就分享到这,下次我们接着写,欢迎与坛友们交流。
|
|