通用接口层实现所在路径 —— zircon\system\dev\ethernet\ethernet\ethernet.cpp
通用接口层也是以驱动形式加载,挂接在发布ZX_PROTOCOL_ETHMAC协议的网卡驱动设备下。
通用接口层驱动加载流程:EthDev0::EthBind-->EthDev0::AddDevice
构造eth::EthDev0对象;调用AddDevice,获取mac、feature等信息,并添加通用网卡驱动层设备。
通用网卡接口层对外以设备节点方式提供ops方法。设备节点在
"/dev/class/ethernet/"
路径下,完整路径path如
"/dev/class/ethernet/000"
。
client端通过fdio open上面提到设备节点的path,获取通信句柄,通用接口层这边会对应调用EthDev0::DdkOpen函数。
EthDev0::DdkOpen-->EthDev::AddDevice
//添加设备实例并返回调用者,后续devfs操作基于此实例,并最终调用DdkMessage函数。
然后利用FIDL接口,通过IPC与驱动所在的devhost通信。devhost通过devfs处理外部IPC请求。devfs处理流程如下:
DevfsConnection::HandleRpc
devhost_fidl_handler --devmgr\devhost\rpc-server.cpp
conn->dev->MessageOp(msg, txn)
ops->message(zircon\system\ulib\ddktl\include\ddktl\device.h文件InitOp函数
EthDev::DdkMessage --zircon\system\dev\ethernet\ethernet\ethernet.cpp
fuchsia_hardware_ethernet_Device_dispatch
kOps.Start
//以start接口举例
MsgStartLocked
EthDev::StartLocked
判断tx/rx rings是否配置
TransmitThread
//启动线程调用TransmitThread函数
transmit_fifo_.read
transmit_fifo_.wait_one
Send
edev0_->TransmitInfoToNetbuf
edev0_->mac_.QueueTx
edev0_->TransmitEcho
PutTransmitInfo
TransmitFifoWrite
edev0_->mac_.Start
edev0_->list_idle_.erase
edev0_->list_active_.push_bac
receive_fifo_.signal_peer
上面举例中,已经说明了start流程。每个client端实例都会新启一个名为
"eth-tx-thread"
的线程,用于处理发送请求。完整的client端与通用接口层的通信初始化流程主要包含下面几个步骤:
1
.clint端open获取通信句柄(前面已经介绍了)
2
.fuchsia_hardware_ethernet_DeviceGetInfo,FIDL接口,获取网卡设备基本信息(mac、feature等)
3
.fuchsia_hardware_ethernet_DeviceGetFifos,通用网卡接口层收到此请求后,会调用zx_fifo_create分别为rx和tx通道创建数据包描述符fifo,自己保留fifo一端,另一端传回调用者(client)
4
.client端通常会根据传回来的fifo深度(rx_depth、tx_depth;描述符个数,也指数据buffer个数),申请一块vmo数据缓冲区,同时调用fuchsia_hardware_ethernet_DeviceSetIOBuffer,将vmo传递给通用网卡接口层。
5
.fuchsia_hardware_ethernet_DeviceSetClientName,设置client名字
6
.fuchsia_hardware_ethernet_DeviceStart,start流程。
7
.在fifo上等待(如VirtioNetLegacy会调用WaitOnFifos),处理收发数据。
下面以VirtioNetLegacy实现为例,说明从virtio后端到网卡驱动的整个收发流程。
先看一下VirtioNetLegacy构造函数
1
.调用VirtioInprocessDevice初始化父类
2
.rx_stream_(phys_mem, dispatcher, rx_queue(), rx_trace_flow_id(),&io_buf_),初始化rx stream
3
.tx_stream_(phys_mem, dispatcher, tx_queue(), tx_trace_flow_id(),&io_buf_),初始化tx stream
在rx stream和tx stream初始化构造时,会在对应queue上利用VirtioNetLegacy::Stream::OnQueueReady对queue_wait_进行初始化。queue_wait_是VirtioQueueWaiter类型,用于处理virtio queue的通知事件,当对应queue上有virtio消息通知时会调用回调函数,这里就会调用OnQueueReady函数。
发送
VirtioNetLegacy::Stream::OnQueueReady
ReadPacketInfo
//根据virtio desc获取包在vmo(virtio前后端共享vmo)中的offset和len
io_buf_->vmo().write
//将virtio前端发来的数据写到vmo(与网卡通用接口层共享的vmo)中,偏移为io_offset
fifo_entries_[fifo_num_entries_++] = {……}
//填充fifo_entries_(数据包描述符)
WaitOnFifoWritable
//等待fifo可写
当与网卡通用接口层的fifo可写时,会调用回调函数OnFifoWritable
VirtioNetLegacy::Stream::OnFifoWritable
zx_fifo_write
//将数据包描述符写入fifo
WaitOnQueue
//继续在queue上等待
数据包描述符写入到了与网卡通用接口层的fifo中,下面是通用接口层的处理流程:
通用接口层的发送线程在fifo上等到数据后流程如下:
TransmitThread
transmit_fifo_.read
Send
GetTransmitInfo
//获取发送info
edev0_->TransmitInfoToNetbuf
edev0_->mac_.QueueTx
OPI2MacDevice::EthmacQueueTx
//调用驱动发送函数
[edev0_->TransmitEcho
//回环测试场景]
[ethmac_request_count_ ++]
//驱动阻塞场景
PutTransmitInfo
TransmitFifoWrite
接收
物理网卡驱动收到数据后,会在中断处理中调用接收处理流程如下:
OPI2MacDevice::ProcRxBuffer
ethmac_client_.Recv
//调用网卡通用接口层的Recv函数
EthDev0::Recv
edev.RecvLocked
//这里会对list_active_的所有client实例调用接收函数
receive_fifo_.read
//从接收fifo中读取entry
memcpy
//选取最后一个entry,将接收到的数据拷贝到其中
receive_fifo_.write
//将有数据的entry重新写入fifo
VirtioNetLegacy端在检测到fifo可读之后会触发OnFifoReadable函数:
VirtioNetLegacy::Stream::OnFifoReadable
zx_fifo_read
//读fifo到entries
根据前面读取的entries,循环处理;eth_fifo_entry_t.cookie里存放着desc的head
ReadPacketInfo /根据cookie中virtio desc head获取包应该放在vmo(virtio前后端共享vmo)中的offset和len
io_buf_->vmo().read
//将vmo(与网卡通用接口层共享的vmo)中数据(偏移为io_offset),读到virtio前端vmo中
io_buf_->Free
//释放io buffer,以便后面的传输可以重复使用
queue_->Return
//更新vring(这里会涉及到中断注入,通知guest)
wait->Begin
//重启事件等待
链表管理分析
网卡通用接口层对每个试图连接物理网卡的client都建立了一个EthDev实例,并且用两个链表将它们管理了起来。
list_idle_
//空闲client链表
idle链表在如下几种情况下添加成员:
1
.client调用open来打开设备节点(会构造EthDev实例,并将此实例加入idle链表)
2
.client调用stop来停止网卡设备(会将client对应的EthDev实例,从active链表中删除,并添加都idle链表)
idle链表在如下几种情况下移除成员:
1
.通用设备层DdkUnbind时,会调用DestroyAllEthDev将所有active链表中实例转入idle,在将idle表中所有实例移除
2
.client调用close关闭设备实例时
3
.client调用start,启动设备时(会将client对应的EthDev实例,从idle链表中删除,并添加都active链表)
list_active_
//活跃client链表
active链表成员的添加和移除与idle是对立的,这里不赘述。
list_active_链表的关联使用
1
.多播场景,会扫描所有active实例
2
.set status,会对每个active实例调用receive_fifo_.signal_peer发信号
3
.收包场景,会将包发送到所有active实例
4
.loopback场景,搜索active链表中处于kStateTransmissionListen状态的实例
5
.start、stop、destroy和release场景(实例出入链表)
list_idle_链表的关联使用:start、stop、open、destroy和release场景(实例出入链表)
可以考虑在通用接口层做netswitch功能。这里的list_active_和list_idle_包含了所有试图连接物理网卡的client端实例。