VirtioNet
======在vmm中启动和初始化
vmm/main.cc中main函数启动网络设备时,会优先判断legacy_net是否使能,在legacy_net不使能的情况下会进入VirtioNet初始化分支,下面具体分析。
1.在virtio设备初始化之前先把pci总线初始化起来(virtio设备都挂载pci总线上):
1.1PciBus构造函数:
PciBus bus(&guest, &interrupt_controller)
对guest_和interrupt_controller_等成员对象进行赋值初始化
1.2PciBus::Init
将root_complex_的bar0的size设置为16,trap_type设置为MMIO_SYNC
PciBus::Connect //设置root_complex_设备
以PAGE_SIZE为对齐单位为每个bar分配mmio空间
设置command寄存器为io和mem enable,并为设备分配中断号
SetupBarTraps
guest->CreateMapping //将bar的地址空间加入到guest的IoMappingList中,并设置访问trap为ZX_GUEST_TRAP_MEM(陷出).同时设置IoHandler为PciBar(实现Read和Write函数)
guest_->CreateMapping // Setup ECAM trap for a single bus
TODO:补充PciEcam知识!
2.VirtioNet初始化
2.1VirtioNet构造函数
VirtioNet()
VirtioComponentDevice //调用父类构造,传入ConfigureQueue和Ready作为virtio后端的hook函数
VirtioDevice //父类构造
zx::event::create(0, &event_) //创建event对象
设置wait_等待对象为event_,并设置triger事件为ZX_USER_SIGNAL_ALL(所有用户信号)
2.2初始化VirtioNet的PCI设备
bus.Connect(net.pci_device(),device_loop.dispatcher(), true) //设置virtionet的PciDevice参数,内部实现同1.2节
2.3启动VirtioNet设备
net.Start
launcher->CreateComponent //创建virtio_net.cmx进程
services.ConnectToService(net_.NewRequest()) //连接virtio_net.cmx进程服务端,net_中存储客户端句柄
PrepStart
wait_.Begin //启动事件(event_)等待
this->pci_.bar(kVirtioPciNotifyBar) //设置virtio的pcibar为bar1(notify)--TODO:补充virtio pci bar相关知识
start_info->trap = {……} //设置notify bar的trap区域
guest.duplicate //复制guest写和传输权限
event().duplicate //复制event传输和信号权限
this->phys_mem_.vmo().duplicate //物理内存的vmo复制
net_->Start //调用服务端Start函数(见后面分析)
设置virtio的config_寄存器mac地址为固定的kGuestMacAddress(02:1A:11:00:01:00),将virtionet的link状态设置为UP。
VirtioComponentDevice::OnInterrupt //事件(event_)等待回调函数
event_.signal(signal->trigger, 0) //清除触发事件的信号
VirtioDevice::Interrupt //调用父类中断处理函数
根据actions类型(SET_QUEUE、SET_CONFIG、TRY_INTERRUPT),分别调用VirtioPci设备的add_isr_flags或Interrupt方法
wait->Begin //重新启动事件等待
======================================================================virtio_net.cmx进程
vmm/device/virtio_net.cc主函数中实例化了VirtioNetImpl类,构造函数如下:
VirtioNetImpl
DeviceBase //父类构造
netstack_ = context_.ConnectToEnvironmentService
Start函数:初始化VirtioNet设备的event及trap信息、调用协议栈接口添加虚拟网卡设备,并对rx_stream_和tx_stream_进行初始化。
PrepStart //利用start_info中保存的信息恢复相关配置,是2.3中的PrepStart的反过程。
构造config(利用"/dev/class/ethernet/virtio"作为interface,"ethv0"为interface名,设置ip地址为10.0.0.1/24) //TODO:interface是否存在
AddEthernetDevice
netstack_->AddEthernetDevice
SetInterfaceAddress
netstack_->SetInterfaceAddress
netstack_->SetInterfaceStatus
rx_stream_.Init、tx_stream_.Init
初始化guest_ethernet_和phys_mem_成员
StreamBase::Init
queue_.set_phys_mem
queue_.set_interrupt
NotifyQueue函数:
根据入参queue值,分别调用rx_stream_或tx_stream_的Notify函数
RxStream::Notify(遍历packet_queue_)
循环条件:packet_queue_不为空且接收queue的avail ring还有可用desc
packet_queue_.front() //取出第一个包
chain_.NextDescriptor //获取chain的下一个desc
header = static_cast
设置header,不用VIRTIO_NET_F_MRG_RXBUF、VIRTIO_NET_F_GUEST_CSUM、VIRTIO_NET_F_GUEST_TSO4, TSO6 and UFO
memcpy //拷贝pkt到desc指向的buffer
*chain_.Used() = pkt.length + sizeof(*header) //更新chain的used为desc_.len
guest_ethernet_->Complete //写tx_fifo_,告诉协议栈包已经发送到guest
chain_.Return //更新used ring(用avail的idx填充used<同avail指向同一个desc>,且idx+1[,注中断])
TxStream::Notify
循环条件:遍历avail ring中所有可用desc
chain_.NextDescriptor //取出desc
如果desc的has_next有值,则不符合规范(一次只能发一个包?),continue
guest_ethernet_->Send
chain_.Return
ConfigureQueue函数:
根据入参queue值,分别调用rx_stream_或tx_stream_的Configure函数(都调用基类StreamBase的Configure函数)
Ready函数:
配置negotiated_features_,并调用传入的cb。
Receive函数:当协议栈要发送一个包给guest时,GuestEthernet调用其来通知设备。
rx_stream_.Receive
packet_queue_.push //包入队列
RxStream::Notify
================================================涉及的其他类
TODO:async::GuestBellTrapMethod;分析guest notify过程!
class GuestEthernet
继承自fuchsia::hardware::ethernet::Device类,虚拟出网卡的大部分操作,如下:
Send
rx_fifo_.read //读rxfifo获取rx entry数
memcpy(reinterpret_cast
rx_fifo_.write //将entry写入rxfifo,协议栈就可以读取了
OnTxFifoReadable
tx_fifo_.read //获取txfifo数
[tx_fifo_wait_.Begin //发送等待?]
receiver_->Receive //调用VirtioNetImpl::Receive
Complete //写txfifo,完成发送
GetInfo //将固定mac地址02:1A:11:00:01:00返回
GetFifos //系统调用创建rx和tx fifo(收发都是256个),并将fifo的另一端传入调用者的cb
SetIOBuffer //todo,协议栈谁来调用这个接口
zx::vmar::root_self()->map //映射vmo到本地io_addr_(模拟io基址?)
Start
tx_fifo_.signal(0, ZX_USER_SIGNAL_0) //发信号给协议栈,bring the link up
tx_fifo_wait_.xxx //设置ZX_SOCKET_READABLE等待
Stop
ListenStart
ListenStop
SetClientName
GetStatus //返回DEVICE_STATUS_ONLINE
SetPromiscuousMode
ConfigMulticastAddMac
ConfigMulticastDeleteMac
ConfigMulticastSetPromiscuousMode
ConfigMulticastTestFilter
DumpRegisters
class StreamBase
Init
queue_.set_phys_mem
queue_.set_interrupt //这里设置guest中断注入hook,在VirtioQueue::Return会调用
Configure
queue_.Configure
Used
chain_.Used
class DeviceBase
OnQueueNotify //将设备trap转化为queue notify
static_cast
PrepStart //设置event_、phys_mem_、trap_
trap_.SetTrap
async_set_guest_bell_trap
set_guest_bell_trap
Interrupt
event_.signal //设备中断通过发信号?谁来捕获?!
说明:由于PrepStart中将pcibar的地址设置为了belltrap,当guest访问virtiopci相关寄存器时就会陷出,并会调用DeviceBase::OnQueueNotify方法处理。就像其函数注释的一样,将一个设备trap转化为queue notifications。
struct GuestEthernetReceiver //Interface for GuestEthernet to send a packet to the guest.
struct GuestEthernetReceiver {
virtual void Receive(uintptr_t addr, size_t length, const eth_fifo_entry_t& entry) = 0;
};