diff --git a/SUMMARY.md b/SUMMARY.md index 3d46422c5200d34c32de1bced67ff130030aac6b..ac7fee5e96c5059d4519a1b6994fec5cbfcff01d 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -22,7 +22,7 @@ - [中断系统](programing-manual/interrupt/irq.md) - [延迟队列](programing-manual/interrupt/delay_queue.md) - [定时器](programing-manual/timer/timer.md) - + - 内核移植 - [riscv架构移植](programing-manual/port/riscv/riscv.md) @@ -47,5 +47,9 @@ - [菜单配置](tutorial/menuconfig.md) - [工具链](tutorial/toolchains.md) - [单元测试](tutorial/utest.md) + + - 设备驱动 + - [VirtIO驱动](programing-manual/driver/virtio/virtio.md) + - 更多 - [文档生成](tutorial/doc-generation.md) \ No newline at end of file diff --git a/programing-manual/driver/virtio/appendix.md b/programing-manual/driver/virtio/appendix.md new file mode 100644 index 0000000000000000000000000000000000000000..5b4d7c142f07c890d4188d82dca6fe8f9f556a52 --- /dev/null +++ b/programing-manual/driver/virtio/appendix.md @@ -0,0 +1,45 @@ +# 附录 + +[返回主页](./virtio.md) + +## VirtIO驱动内核配置 + +### 主要配置 + +|配置名|说明|默认值| +|-|-|-| +|NX_DRIVER_ENABLE_VIRTIO|启用VirtIO功能|启用| +|NX_DRIVER_VIRTIO_SPLIT_QUEUE_SIZE|VirtIO队列元素数量,必须为2的幂。**小于一定门限**的值会队列占用越少的内存空间(详见说明),越大的值会减少突发传输时或无法及时处理时的丢包情况。|8| +|NX_DRIVER_VIRTIO_QUEUE_USED_ALIGNMENT|Used Ring的内存对齐字节数,须为2的幂。越小的值浪费的内存空间越少。Legacy(目前不支持)设备要求必须为4096,Modern设备则要求至少为4|4| +|NX_DRIVER_VIRTIO_USE_DELAY_QUEUE_FOR_ISR|是否启用中断队列进行处理,目前尚未观察到有明显的性能影响|禁用| + +> 说明:在Used Ring对齐值为4的情况,队列元素数量在1~128之间均占用1整个页,而当对齐值为4096时,则占用2个页。故元素数量设置为1和128之间任何2的幂都会占用相同的内存空间,在实际使用时倾向于设置为128。默认值8旨在方便Debug,并不建议在**使用**VirtIO驱动的情况下将其值保留为8。 + +### 通讯协议配置 + +|配置名|说明|默认值| +|-|-|-| +|NX_DRIVER_VIRTIO_PCI|启用VirtIO Over PCI Bus,通常用于x86和ARM平台|启用| +|NX_DRIVER_VIRTIO_MMIO|启用VirtIO Over MMIO,通常用于RISC-V、ARM等平台|启用| + +> MMIO无法自动发现设备,故需要内核中其他模块(如内核启动参数、设备树等)调用相关的注册方法进行设备注册。目前此部分还在实现中。 +> +> QEMU的microvm平台在x86架构下可以使用MMIO。 + +### 设备配置 + +过于显然且无需额外说明的配置在此省略 +|配置名|说明|默认值| +|-|-|-| +|NX_DRIVER_VIRTIO_SOUND_EVENT_BUFFER_SIZE|Sound设备事件队列大小|64| +|NX_DRIVER_VIRTIO_INPUT_EVENT_BUFFER_SIZE|Input设备事件队列大小|64| + +> 设备事件队列大小单位为**个**,不是字节。 + +## 使用QEMU直接启动使用MultiBoot1规范的内核 + +QEMU可以通过`-kernel`参数直接启动符合MultiBoot1规范的ELF内核文件。通过这项功能可以减少Debug中反复创建启动磁盘的步骤,降低Debug时间。 + +目前MultiBoot1规范仅在x86平台上可用,若要将内核编译为MultiBoot1格式的ELF文件,请启用Kconfig中的`Arch/Using MultiBoot version 1`,并重新编译内核。 + +使用如下的QEMU启动命令直接启动编译后的内核:`qemu-system-i386 -m 512m -kernel src/platform/i386/NXOS.elf -nographic` \ No newline at end of file diff --git a/programing-manual/driver/virtio/images/1.svg b/programing-manual/driver/virtio/images/1.svg new file mode 100644 index 0000000000000000000000000000000000000000..8d097286fa0e0d180745f34a10992d3c05a1ad52 --- /dev/null +++ b/programing-manual/driver/virtio/images/1.svg @@ -0,0 +1,3 @@ + + +
控制信息
PCI/MMIO
控制信息 PCI/MMIO
数据信息
共享内存
数据信息 共享内存
VirtIO Ring
VirtIO Ring
VirtIO 后端设备
VirtIO 后端设备
QEMU
QEMU
VirtIO 前端驱动
VirtIO 前端驱动
src/drivers/virtio/virtio_pci.c
src/drivers/virtio/virtio_mmio.c
src/drivers/virtio/virtio_...
NXOS
NXOS
src/drivers/virtio/virtio_queue.c
src/drivers/virtio/virtio_...
src/drivers/virtio/devices/*
src/drivers/virtio/devices...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/programing-manual/driver/virtio/implementation.md b/programing-manual/driver/virtio/implementation.md new file mode 100644 index 0000000000000000000000000000000000000000..ef60cff5fed504b4c674a53b143eef6c3970b7fe --- /dev/null +++ b/programing-manual/driver/virtio/implementation.md @@ -0,0 +1,106 @@ +# VirtIO驱动实现简介 + +[返回主页](./virtio.md) + +VirtIO通过共享内存和已有的通讯接口来实现数据和控制信息的交换。共享内存通过VirtIO环形队列(即VirtIO Ring)实现,控制信息则通过已有的硬件通讯接口(如PCI、MMIO、Channel I/O)实现。 + +具体的VirtIO实现细节请参考[VirtIO 1.2规范文档](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html)。本小节仅介绍在NXOS的驱动框架下的VirtIO实现。 + +> 注:由于所有VirtIO驱动代码均位于`src/drivers/virtio`内,在本章节内所有的源代码路径均省略该路径前缀。 + +## 总览 + +VirtIO在NXOS中的实现分为三个组成部分: + +1. VirtIO协议层 + - 协议层是独立于架构和通讯方法的VirtIO基础协议的实现,目前单纯指VirtIO Queue的实现。 + - 包含文件:`virtio_queue.c`、`virtio_queue.h` +2. VirtIO接口层 + - 接口层向下对接实现了不同通讯方法的通讯层,使其平台无关;向上对接NXOS的驱动接口。同时也负责注册、初始化等驱动操作。 + - 包含文件:`virtio.c`、`virtio.h` +3. VirtIO通讯层 + - 通讯层负责实现不同的VirtIO通讯方法([相关文档](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-1140004)),负责控制信息的交换。 + - 包含文件:`virtio_pci.c`、`virtio_pci.h`、`virtio_pci_private.h`、`virtio_mmio.c`、`virtio_mmio.h`、`virtio_mmio_private.h` +4. VirtIO设备层 + - 设备层负责实现各个VirtIO设备,即利用VirtIO接口层和协议层实现具体的VirtIO驱动。 + - 设备层包含`devices`文件夹下的所有源码,其中`virtio_template.c`是一个通用的模板代码,用于在后期添加新的设备实现,`virtio_devices.h`是设备表注册函数的声明头文件,添加任何新的设备都需要在此添加相应的条目。 + +> 注:因QEMU后端驱动实现不标准(相关Issue:[QEMU#1794](https://gitlab.com/qemu-project/qemu/-/issues/1794)),驱动中对于GPU驱动的硬件光标部分实现与标准文档有所出入。 + +## VirtIO驱动框架 + +由于NXOS提供的驱动框架较为扁平,为了实现VirtIO驱动的一个框架结构,在NXOS提供的驱动框架的基础上本系列驱动通过“驱动表”的形式实现了一个简单的二级驱动结构。 + +任何前端设备都需要定义一个全局的`VirtioDriverTableEntity`类型的驱动表表项,然后定义一个返回该表项指针的函数,并将该函数添加到`devices/virtio_devices.h`中。如: + +```C +// virtio_template.c +NX_PRIVATE VirtioDriverTableEntity virtioTemplateDriverTableEntity = { + .deviceId = VIRTIO_DEVICE_ID_TEMPLATE, + .driverName = "virtio-template", + .deviceNameTemplate = "virtio_template_%d", + .deviceType = NX_DEVICE_TYPE_TEMPLATE, + .driverOps = &virtioTemplateDriverOps, + .initHandler = VirtioTemplateInit, + .cleanUpHandler = VirtioTemplateCleanUp +}; + +VirtioDriverTableEntity *VirtioDriverTableEntityForVirtioTemplate(void) +{ + return &virtioTemplateDriverTableEntity; +} + +// devices/virtio_devices.h +VirtioDriverTableEntity *VirtioDriverTableEntityForVirtioTemplate(void); // virtio_template.c + +#define VIRTIO_DRIVERS_TABLE_ENTITY_CREATOR_FUNCS \ +{ \ + VirtioDriverTableEntityForVirtioTemplate, \ +} + +``` + +VirtIO大类驱动会遍历该表项然后初始化相应的设备和子驱动,如果发现有VirtIO设备没有注册相关的驱动,则不会进行初始化。 + +## PCI总线通讯 + +由于PCI支持设备自动发现,所以不需要手动注册设备,在VirtIO大类驱动初始化的时候会自动发现所有的基于PCI的VirtIO设备。 + +使用PCI进行通讯则直接调用了NXOS的PCI支持,采用BAR和PCI中断的方法来互相传输控制信息数据(如设备配置变动、Queue状态变动等)。 + +## MMIO通讯 + +MMIO不支持设备自动发现,所以基于MMIO通讯方法的VirtIO设备需要在VirtIO大类驱动初始化之前由其他内核模块调用注册函数添加设备信息。 + +MMIO的设备信息很简单,即内存基址、内存大小、中断号。通过调用`NX_Error VirtioMMIORegisterDevice(VirtioMMIODeviceInfo *info)`将信息传输给VirtIO MMIO模块,并在之后的初始化中使用。 + +例: + +```C +void register_test() { + // TODO: MMIO Device Info should be passed through kernel parameters or device tree. + // None of these we have. So we just define it in source code for test purpose only. + // We use qemu-system-i386 -M microvm to test with MMIO, Layout: + // phyAddr: 0xfeb0e00(offset=phy-0xfeb00000) and decrease, memSize=512, interrupt=5+offset/512. + /* + VirtioMMIODeviceInfo deviceInfo = { + .phyAddr = 0xfeb00e00, + .memSize = 512, + .interrupt = 12, + .interruptParent = 0 + }; + VirtioMMIORegisterDevice(&deviceInfo); */ + VirtioMMIODeviceInfo deviceInfo[] = { + {.phyAddr = 0x10008000, .memSize = 0x1000, .interrupt = 0x08, .interruptParent = 0x03}, + {.phyAddr = 0x10007000, .memSize = 0x1000, .interrupt = 0x07, .interruptParent = 0x03}, + {.phyAddr = 0x10006000, .memSize = 0x1000, .interrupt = 0x06, .interruptParent = 0x03}, + {.phyAddr = 0x10005000, .memSize = 0x1000, .interrupt = 0x05, .interruptParent = 0x03}, + {.phyAddr = 0x10004000, .memSize = 0x1000, .interrupt = 0x04, .interruptParent = 0x03}, + {.phyAddr = 0x10003000, .memSize = 0x1000, .interrupt = 0x03, .interruptParent = 0x03}, + {.phyAddr = 0x10002000, .memSize = 0x1000, .interrupt = 0x02, .interruptParent = 0x03}, + {.phyAddr = 0x10001000, .memSize = 0x1000, .interrupt = 0x01, .interruptParent = 0x03}, + }; + for(int i = 0; i < NX_ARRAY_SIZE(deviceInfo); i++) + VirtioMMIORegisterDevice(&deviceInfo[i]); +} +``` diff --git a/programing-manual/driver/virtio/interface.md b/programing-manual/driver/virtio/interface.md new file mode 100644 index 0000000000000000000000000000000000000000..c505686e9d4d4ca9bac4f707716504021ce647a6 --- /dev/null +++ b/programing-manual/driver/virtio/interface.md @@ -0,0 +1,109 @@ +# VirtIO驱动接口 + +[返回主页](./virtio.md) + +VirtIO设备提供的接口是经典的“万物皆文件”类型的接口,也是NXOS标准的驱动接口。对于每个设备,驱动程序都会创建一个设备文件,对该文件进行普通的文件操作既可控制设备,具体接口方法请参考其他部分的文档。 + +> VirtIO Net驱动的文件读写接口仅供开发使用,后期可能会直接在内核内部进行网络栈的对接并取消设备文件的读写功能。 + +## VirtIO Block 块设备 + +对一个VirtIO Block设备的文件操作和对其他磁盘设备文件的操作相同。 + +需要值得注意的是,由于VirtIO Block的最小操作单位是大小为512字节的块,所以**建议**对块设备的读写在大小和偏移值上对齐512字节。当然驱动程序内部也对不对齐的读写操作进行了相应的兼容,但是会消耗额外的内存空间和操作周期。 + +## VirtIO Entropy 熵池设备 + +对一个VirtIO Entropy设备的读操作将会使传入的Buffer被随机字节填充。而写操作不会有任何作用。 + +## VirtIO GPU 图形显示设备 + +VirtIO GPU是相对比较复杂的部分。对其的读写操作会被转发到**当前选中的**输出(即“显示器”)的Framebuffer上。因为支持部分刷新,所以读操作并不确保会严格读到显示的内容,而仅仅会读取存放在Framebuffer中的数据。同样,写入操作也需要进行刷新才能被显示到屏幕上。驱动程序也提供了内存映射,可以将Framebuffer映射到用户态可以访问的地址上,提高数据读写效率。 + +大部分的VirtIO GPU操作集中在控制函数内,可用的命令和说明如下: + +
+ +控制命令 + +|命令|参数|说明| +|-|-|-| +|NX_FRAMEBUFFER_CMD_GETINFO|NX_FramebufferInfo*|获取当前选中的输出的Framebuffer信息| +|NX_FRAMEBUFFER_CMD_PRESENT|NULL或NX_Region*|全部刷新或者局部刷新Framebuffer中的区域到当前选中的输出上(即VIRTIO_GPU_CMD_FRAMEBUFFER_TRANSFER和VIRTIO_GPU_CMD_FRAMEBUFFER_FLUSH结合使用)| +|VIRTIO_GPU_CMD_SELECT_SCANOUT|VirtioGPUControlArgument*|选中参数内的scanoutId指向的输出| +|VIRTIO_GPU_CMD_QUERY_DISPLAY_INFO|VirtioGPUControlArgument*|填充参数内的data为VirtioGPUDisplayInfo类型的显示信息| +|VIRTIO_GPU_CMD_QUERY_EDID|VirtioGPUControlArgument*|填充参数内的data为字节数组类型的EDID信息| +|VIRTIO_GPU_CMD_QUERY_CAPSET_INFO|VirtioGPUControlArgument*|填充参数内的data为VirtioGPUCapsetInfo类型的CapsetInfo信息,Capset由`arg.queryCapsetInfo.capsetId`指定| +|VIRTIO_GPU_CMD_QUERY_CAPSET|VirtioGPUControlArgument*|填充参数内的data为字节数组类型的Capset数据,Capset由`arg.queryCapset.capsetId`指定、CapsetVersion由`arg.queryCapset.capsetVer`指定| +|VIRTIO_GPU_CMD_FRAMEBUFFER_TRANSFER|NULL或VirtioGPUControlArgument*|将Framebuffer中的全部或部分区域传输到宿主机,可以通过参数指定待传送的局部Framebuffer的偏移(`arg.fb_offset`)、矩形的区域(`rect`)| +|VIRTIO_GPU_CMD_FRAMEBUFFER_FLUSH|NULL或VirtioGPUControlArgument*|刷新已经被传送到宿主机的Framebuffer。可以通过参数指定刷新的区域(`rect`)| +|VIRTIO_GPU_CMD_CURSOR_UPDATE|VirtioGPUControlArgument*|设置GPU硬件光标的图像资源,必须为64x64的位图,即`dataSize`必须为`64*64*4`。通过`format`指定格式,`data`存放光标位图的地址。`arg.cursorUpdate.hotX/hotY`存放硬件光标的“热点”(即判定点)。`arg.cursorUpdate.x/y`存放光标在屏幕上的位置。| +|VIRTIO_GPU_CMD_CURSOR_MOVE|VirtioGPUControlArgument*|移动硬件光标,`arg.cursorMove.x/y`存放光标目标位置。| +|VIRTIO_GPU_CMD_SETUP_SCANOUT|VirtioGPUControlArgument*|使用`rect`的大小和`format`的格式重新设置显示输出。| + +
+ +## VirtIO Sound 音频设备 + +VirtIO Sound设备仅支持音频输出流,即PCM流的播放。 + +一个Sound设备有如下几个状态:已初始化、已打开、已就绪、播放中、已停止、已释放、已关闭(等同于已初始化),这也构成了Sound设备的环状生命周期。设备在被打开之前会由驱动进行初始化,并设置相关的参数、初始化事件队列等。 + +在设备进入播放中的状态时,可以对设备进行读写操作。写操作为写入PCM片段;读操作为读取播放结果(可以使用Poll来进行阻塞查询),读操作仅能一次性读取一个`VirtioSoundPCMIOStatus`类型的数据。读写操作均作用于已选择的PCM流。写入操作完成后设备会立刻播放PCM片段,**播放结束后**将播放结果放置于队列中等待被读取。 + +对Sound设备的控制操作同样集中在控制函数内,可用的命令和说明如下: + +
+ +控制命令 + +|命令|参数|说明| +|-|-|-| +|VIRTIO_SOUND_CMD_PCM_SELECT_STREAM|32位整型的流ID|选择PCM流| +|VIRTIO_SOUND_CMD_PCM_SET_PARAM|VirtioSoundCmdPCMSetParam*|仅能在设备在进入播放状态前使用,设置待播放的PCM流的参数。| +|VIRTIO_SOUND_CMD_PCM_PREPARE|NULL|仅能在设备不再播放中或者已停止的状态下使用,准备PCM流,并进入已就绪状态| +|VIRTIO_SOUND_CMD_PCM_START|NULL|开始PCM流的播放| +|VIRTIO_SOUND_CMD_PCM_STOP|NULL|停止PCM流的播放| +|VIRTIO_SOUND_CMD_PCM_RELEASE|NULL|释放PCM流| +|VIRTIO_SOUND_CMD_PCM_INFO|VirtioSoundCmdGetPCMInfo*|获取选中的PCM流的信息| + +
+ +## VirtIO Input 输入设备 + +VirtIO Input设备为通用输入设备,无论是键盘还是鼠标都可以通过这个设备与虚拟机链接。Input设备的事件代码等静态定义采用Linux中的[uapi/linux/input-event-codes.h](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/input-event-codes.h)定义,与NXOS的事件代码有些许出入,还请注意。 + +对Input设备的写操作是非法的,而读操作一次读取一个输入事件`VirtioInputEvent`,同样,可以使用Poll查询等待。 + +对于Input设备的控制方法,可用的命令和说明如下: + +
+ +控制命令 + +|命令|参数|说明| +|-|-|-| +|VIRTIO_INPUT_CMD_RESET|NULL|清空输入事件队列| +|VIRTIO_INPUT_CMD_SET_STATUS|VirtioInputEvent*|控制输入设备的某些状态(如指示灯等)| +|VIRTIO_INPUT_CMD_GET_NAME|VirtioInputGetRequest*|获取输入设备名称| +|VIRTIO_INPUT_CMD_GET_SERIAL|VirtioInputGetRequest*|获取输入设备序列号| +|VIRTIO_INPUT_CMD_GET_PROP_BITMAP|VirtioInputGetRequest*|获取参数Bitmap| +|VIRTIO_INPUT_CMD_GET_DEVIDS|VirtioInputGetRequest*|获取DevIds| +|VIRTIO_INPUT_CMD_GET_EV_BITMAP|VirtioInputGetRequest*|获取事件Bitmap| +|VIRTIO_INPUT_CMD_GET_ABS_INFO|VirtioInputGetRequest*|获取设备绝对座标| + +
+ +## VirtIO Net 网络设备 + +VirtIO Net设备在NXOS中实现的驱动并非旨在提供一个文件接口供用户访问,由于NXOS缺乏对事件驱动模型的支持,使用设备文件接口收包将会变得开销巨大。目前Net设备提供的设备文件接口仅供测试开发使用。 + +对于Net设备的读写均以包为单位进行。目前尚未支持高级功能的情况下,一次最多只能读写个1514字节的MAC帧包(即6字节目的地址、6字节源地址、2字节类型、1500字节数据Payload,不需要4字节的FCS)。可以使用Poll进行查询等待收包。 + +Net设备的控制方法仅有`VIRTIO_NET_CMD_GET_MAC`和`VIRTIO_NET_CMD_FLUSH`,前者获取网卡的MAC地址,后者清空接受队列。 + +## VirtIO Console 控制台设备 + +VirtIO Console设备是一个控制台设备,即同时具备字符输入和输出的一个多通道终端设备。 + +对Console设备的读写即为终端的输入输出,使用Poll方法对终端的输入进行查询等待。 diff --git a/programing-manual/driver/virtio/internal_interface.md b/programing-manual/driver/virtio/internal_interface.md new file mode 100644 index 0000000000000000000000000000000000000000..769966209d0f1da0dcdd2e5132d2549f7557cbb1 --- /dev/null +++ b/programing-manual/driver/virtio/internal_interface.md @@ -0,0 +1,265 @@ +# VirtIO驱动框架接口 + +[返回主页](./virtio.md) + +本节主要列举可以在VirtIO前端驱动中使用的通用VirtIO驱动框架接口。当需要实现新的VirtIO设备时,必须使用这些接口实现的VirtIO协议。 + +## 函数方法 + +### virtio.h + +```C +/* Common Config */ +void VirtioCommonConfigGet(NX_Device *device, VirtioCommonConfigItem item, void *value); +void VirtioCommonConfigSet(NX_Device *device, VirtioCommonConfigItem item, void *value); +/* Device Config */ +void *VirtioGetDeviceConfig(NX_Device *device); +void VirtioReadDeviceConfig(NX_Device *device, NX_Offset offset, void *buf, NX_Size len); +/* Notification Config, VIRTIO_F_NOTIFICATION_DATA should not be negotiated */ +// From Device, notifying Driver about Used +void VirtioWaitNotification(NX_Device *device, NX_U16 queue_index, NX_U32 desc_id); +// From Driver, notifying Device about Avail +void VirtioSendNotification(NX_Device *device, NX_U16 queue_index, NX_U32 desc_id); +/* Generic Interrupt Service Routine */ +NX_Error VirtioISR(VirtioISRType type, NX_Device *device); +NX_Error VirtioSetupISR(NX_Device *device); +NX_Error VirtioRemoveISR(NX_Device *device); +/* Generic Initialization */ +NX_Error VirtioDeviceInitialize(NX_Device *device, NX_U32 *driverFeatures, NX_Size driverFeaturesCount); +NX_Error VirtioDeviceDriverOK(NX_Device *device); +/* Generic Cleanup */ +NX_Error VirtioDeviceReset(NX_Device *device); +void VirtioCleanUpDevice(NX_Device *device); + +NX_INLINE void *VirtioVirt2Phy(void *buf); +NX_INLINE NX_Addr VirtioVirtualAddressAllocate(NX_Addr phyAddr); +``` + +### virtio_queue.h + +```C +void VirtioQueueInit(NX_Device *device, NX_U16 queue_index, NX_U16 ring_size); +void VirtioQueueFree(NX_Device *device); +void VirtioQueueAlloc(NX_Device *device, int queue_num); +void VirtioQueueDestroy(NX_Device *device, NX_U16 queue_index); +VirtioQueue *VirtioQueueGet(NX_Device *device, NX_U16 queue_index); + +NX_U32 VirtioQueueDescAlloc(VirtioQueue *queue); +NX_Error VirtioQueueDescAllocMany(VirtioQueue *queue, int num, NX_U32 *allocated_ids); +void VirtioQueueDescFree(VirtioQueue *queue, NX_U32 id); +void VirtioQueueDescFreeMany(VirtioQueue *queue, NX_U32 id); +VirtioQueueDesc *VirtioQueueDescGet(VirtioQueue *queue, NX_U32 id); + +void VirtioQueueDescFill(VirtioQueue *queue, NX_U32 desc_id, NX_U64 addr, NX_U32 len, NX_U16 flags, NX_U16 next); +void VirtioQueuePutIntoAvailRing(VirtioQueue *queue, NX_U32 desc_id); + +void VirtioQueueRegisterNotifier(VirtioQueue *queue, VirtioQueueCompletedHandler handler); +void VirtioQueueUnregisterNotifier(VirtioQueue *queue, VirtioQueueCompletedHandler handler); +``` + +## 宏定义 + +### virtio.h +```C +/* VIRTIO Generic Definitions */ +#define VIRTIO_STATUS_RESET 0 // Write 0 to Status reset the device + +#define VIRTIO_STATUS_ACKNOWLEDGE 1 +#define VIRTIO_STATUS_DRIVER 2 +#define VIRTIO_STATUS_DRIVER_OK 4 +#define VIRTIO_STATUS_FEATURES_OK 8 +#define VIRTIO_STATUS_NEEDS_RESET 64 +#define VIRTIO_STATUS_FAILED 128 + +// Device-independent features bits (modern) +// 0 ~ 31 +#define VIRTIO_FEATURE_INDIRECT_DESC (1<<28) +#define VIRTIO_FEATURE_EVENT_IDX (1<<29) +// 32 ~ 63 +#define VIRTIO_FEATURE_VERSION_1 (1<<(32 - 32)) +#define VIRTIO_FEATURE_ACCESS_PLATFORM (1<<(33 - 32)) +#define VIRTIO_FEATURE_RING_PACKED (1<<(34 - 32)) +#define VIRTIO_FEATURE_IN_ORDER (1<<(35 - 32)) +#define VIRTIO_FEATURE_ORDER_PLATFORM (1<<(36 - 32)) +#define VIRTIO_FEATURE_SR_IOV (1<<(37 - 32)) +#define VIRTIO_FEATURE_NOTIFICATION_DATA (1<<(38 - 32)) +#define VIRTIO_FEATURE_NOTIF_CONFIG_DATA (1<<(39 - 32)) +#define VIRTIO_FEATURE_RING_RESET (1<<(40 - 32)) + +// Device-independent features bits (legacy) +#define VIRTIO_FEATURE_NOTIFY_ON_EMPTY (1<<24) +#define VIRTIO_FEATURE_ANY_LAYOUT (1<<27) +#define VIRTIO_FEATURE_UNUSED (1<<30) + +#define VIRTIO_PAGE_SIZE 4096 + +#define VirtioGetExtension(device) ((VirtioExtension*)(device)->extension) +#define VirtioGetBusExtension(device, type) ((type*)(VirtioGetExtension(device)->busExt)) +#define VirtioGetDevExtension(device, type) ((type*)(VirtioGetExtension(device)->devExt)) + +// For VIRTIO_COMMON_CFG_QUEUE_ENABLE +#define VIRTIO_COMMON_CFG_QUEUE_ENABLE_ENABLE 1 +#define VIRTIO_COMMON_CFG_QUEUE_ENABLE_DISABLE 0 + +// For VirtioVirtualAddressAllocate +#define VIRTIO_VADDR_MASK 0xF0000000; +``` + +### virtio_queue.h + +```C +#define VIRTIO_QUEUE_DESC_FLAG_NEXT 1 // marks a buffer as continuing via the next field +#define VIRTIO_QUEUE_DESC_FLAG_WRITE 2 // marks a buffer as write-only +#define VIRTIO_QUEUE_DESC_FLAG_INDIRECT 4 // the buffer contains a list of buffer descriptors + +#define VIRTIO_QUEUE_USED_FLAG_NO_NOTIFY 1 // no notify when add a buffer (unreliable) +#define VIRTIO_QUEUE_AVAIL_FLAG_NO_INTERRUPT 1 // no interrupt then consume a buffer (unreliable) + +#define VIRTIO_QUEUE_DESC_TOTAL_SIZE(ring_size) (sizeof(VirtioQueueDesc) * (ring_size)) +#define VIRTIO_QUEUE_AVAIL_TOTAL_SIZE(ring_size) (sizeof(VirtioQueueAvail) + (ring_size) * sizeof(NX_U16)) +#define VIRTIO_QUEUE_USED_TOTAL_SIZE(ring_size) (sizeof(VirtioQueueUsed) + (ring_size) * sizeof(VirtioQueueUsedElement)) + +#define VIRTIO_QUEUE_DESC_INVALID_ID (0xFFFFFFFF) +``` + +## 结构体定义 + +### virtio.h + +```C +// VirtioExtension +typedef void (*VirtioConfigChangeHandler)(NX_Device *device); + +struct VirtioExtension +{ + VirtioQueue *queue; + int queue_num; + VirtioBusType busType; + void *busExt; + void *devExt; + NX_Device *device; // For reverse lookup + NX_U32 flags; + NX_List list; + NX_IRQ_DelayWork *irqWorkQueue; + NX_IRQ_DelayWork *irqWorkConfig; + VirtioConfigChangeHandler configChangeNotifier; +}; +typedef struct VirtioExtension VirtioExtension; + +// VirtioDriverTableEntity + +typedef NX_Error (*VirtioDriverInitHandler)(NX_Driver *driver, NX_Device *device); +typedef void (*VirtioDriverCleanUpHandler)(NX_Device *device); +struct VirtioDriverTableEntity +{ + const VirtioDeviceId deviceId; /* Static, Driver provide */ + const char *driverName; /* Static, Driver provide */ + const char *deviceNameTemplate; /* Static, Driver provide */ + const NX_DeviceType deviceType; /* Static, Driver provide */ + NX_DriverOps *driverOps; /* Static, Driver provide */ + VirtioDriverInitHandler initHandler; /* Static, Driver provide */ + VirtioDriverCleanUpHandler cleanUpHandler; /* Static, Driver provide */ + NX_Driver *driver; /* Runtime */ + int deviceCount; /* Runtime */ + NX_List deviceListHead; /* Runtime */ +}; +typedef struct VirtioDriverTableEntity VirtioDriverTableEntity; + +// VirtioFeature + +struct VirtioFeature +{ + NX_U32 selector; + NX_U32 features; +}; +typedef struct VirtioFeature VirtioFeature; + +``` + +### virtio_queue.h + +```C +/* Virtqueue descriptors: 16 bytes. + * These can chain together via "next". */ +struct VirtioQueueDesc +{ + /* Address (guest-physical). */ + NX_U64 addr; + /* Length. */ + NX_U32 len; + /* The flags as indicated above. */ + NX_U16 flags; + /* We chain unused descriptors via this, too */ + NX_U16 next; +}; +typedef struct VirtioQueueDesc VirtioQueueDesc; + +struct VirtioQueueAvail +{ + NX_U16 flags; + NX_U16 idx; + NX_U16 ring[]; + /* Only if VIRTIO_F_EVENT_IDX: NX_U16 used_event; */ +}; +typedef struct VirtioQueueAvail VirtioQueueAvail; + +/* NX_U32 is used here for ids for padding reasons. */ +struct VirtioQueueUsedElement +{ + /* Index of start of used descriptor chain. */ + NX_U32 id; + /* Total length of the descriptor chain which was written to. */ + NX_U32 len; +}; +typedef struct VirtioQueueUsedElement VirtioQueueUsedElement; + +struct VirtioQueueUsed +{ + NX_U16 flags; + NX_U16 idx; + struct VirtioQueueUsedElement ring[]; + /* Only if VIRTIO_F_EVENT_IDX: NX_U16 avail_event; */ +}; +typedef struct VirtioQueueUsed VirtioQueueUsed; + +struct VirtioQueueTrace +{ + /* + * For synchronous waiting + */ + NX_Bool waiting; // Driver is expecting this idx + NX_Bool ok; // Interrupt has been made + NX_Semaphore lock; + /* + * For asynchronous completed notifying + */ + NX_Bool needNotify; +}; +typedef struct VirtioQueueTrace VirtioQueueTrace; + +typedef struct VirtioQueue VirtioQueue; + +typedef NX_Error(*VirtioQueueCompletedHandler)(NX_Device *device, VirtioQueue *queue, NX_U32 idx); + +struct VirtioQueue +{ + NX_U16 queue_index; + unsigned int ring_size; // be power of 2 + NX_U32 used_idx; + + VirtioQueueDesc *desc; // alignment of 16 + VirtioQueueAvail *avail;// alignment of 2 + VirtioQueueUsed *used; // alignment of 4 + + NX_Bool *free; // consider using bitset to reduce memory usage + unsigned int free_count; + VirtioQueueTrace *trace; + + VirtioQueueCompletedHandler notifier; + + NX_Mutex freeLock; + NX_Mutex availRingLock; + NX_Spin usedRingLock; +}; + +``` \ No newline at end of file diff --git a/programing-manual/driver/virtio/usage.md b/programing-manual/driver/virtio/usage.md new file mode 100644 index 0000000000000000000000000000000000000000..409a8bd2523c3180d254f6f146577f6ec79f2843 --- /dev/null +++ b/programing-manual/driver/virtio/usage.md @@ -0,0 +1,98 @@ +# VirtIO驱动使用 + +[返回主页](./virtio.md) + +## 内核配置 + +在使用VirtIO之前,请确保内核在编译时启用了VirtIO相关的功能,有关VirtIO的Kconfig全部位于`Device/VirtIO`下,下文不再复述该Kconfig路径前缀。 + +`Enable virtio driver`为VirtIO驱动的总开关,若要使用VirtIO功能则必须开启这个开关。此外,`VirtIO Communication Protocol`的子项中至少有一个通讯方法是启用的。目前受支持的通讯方法有PCI及MMIO,同时启用两个通讯方法不会造成功能上的异常。有关Kconfig中其他的可配置项请参见[附录:VirtIO内核配置](./appendix.md#VirtIO驱动内核配置) + +> 在理论上同时使用两种通讯方法也是可行的,但是没有经过测试。 + +若意图使用某个特定的VirtIO设备,则须启用`VirtIO Devices`的子项中对应的开关。针对Sound和Input设备提供了一个Event Buffer Size的选项用以配置事件缓冲区的大小(以事件个数为单位),这两个设备的驱动会将接受到的事件拷贝到Event Buffer的队列中。 + +目前所有支持的设备均在下一小节中对QEMU启动参数的介绍中列出。目前部分设备的高级功能尚未实现。此外,GPU的3D图形部分与Sound的输入部分尚未实现。 + + + +## QEMU中VirtIO设备的使用 + +本小节主要罗列启用各个VirtIO设备所使用的QEMU的命令行参数和部分注意事项,仅供参考,具体说明以QEMU文档为准。本小节假设读者已掌握基本的QEMU命令行使用。 + +特别需要注意的是,由于内核中的VirtIO驱动仅实现了对Modern设备的支持,所以不管使用何种方式启用VirtIO设备,都需要注意确保设备为Modern设备,当前版本的QEMU会**优先选择Legacy设备**,故须用显式声明的方法禁用掉QEMU后端的Legacy设备。 + + + +## VirtIO Over PCI Bus + +- Block 块设备 + - `-drive file=rootfs.cpio,if=none,format=raw,id=x0 -device virtio-blk-pci,drive=x0,disable-legacy=on` +- Entropy 熵池设备 + - `-device virtio-rng-pci,disable-legacy=on` +- GPU 图形显示设备 + - `-display sdl -vga virtio` + - `-display sdl`为QEMU的显示后端选择,需根据宿主机情况自行调整。 +- Sound 音频设备 + - `-audio driver=sdl,model=virtio-sound -device virtio-sound-pci,disable-legacy=on` + - `-audio driver=sdl`为QEMU的音频后端选择,需根据宿主机情况自行调整。 +- Input 输入设备 + - `-device virtio-keyboard-pci,serial=virtio-keyboard` + - `-device virtio-mouse-pci,serial=virtio-mouse` +- Net 网络设备 + - `-netdev tap,ifname=TAP,id=mynet0 -device virtio-net-pci,netdev=mynet0,disable-legacy=on` + - 上述命令行参数使用TAP设备建立网络接口,可以十分方便地使用Wireshark等工具进行抓包。`netdev`的其他接口和TAP设备的使用请阅读相关的文档介绍。 +- Console 控制台设备 + - `-chardev socket,host=127.0.0.1,port=4321,server=on,wait=on,telnet=on,id=charconsole0 -device virtserialport,chardev=charconsole0,id=console0 -device virtio-serial-pci,id=virtio-console0,disable-legacy=on` + - 上述命令行参数建立一个Socket终端,宿主机可以通过`netcat`等工具链接。 + +> 所有VirtIO Over PCI Bus设备均可以通过`-device virtio-xxx-pci`使用。 +> +> 为了禁用QEMU选择默认的Legacy设备,需要显式声明`disable-legacy=on`。虽然理论上可以通过`-global virtio-pci.disable-legacy=on`来一次性禁用所有设备的Legacy支持,但是由于Net设备被拆成了两个所以无法使用这种方式,只能重复在每个设备参数上手动添加。 + +## VirtIO Over MMIO + +使用MMIO进行通信就可以直接使用`-global virtio-mmio.force-legacy=false`来一次性禁用所有设备的Legacy支持。请确保将其用于QEMU的命令行参数内。 + +部分与VirtIO Over PCI Bus相同的的参数在此不做说明。 + +- Block 块设备 + - `-drive file=rootfs.cpio,if=none,format=raw,id=x1 -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.0` +- Entropy 熵池设备 + - `-device virtio-rng-device,bus=virtio-mmio-bus.0` +- GPU 图形显示设备 + - `-display sdl -device virtio-gpu-device,bus=virtio-mmio-bus.0` +- Sound 音频设备 + - `-audiodev driver=sdl,id=mysnd0 -device virtio-sound-device,audiodev=mysnd0,bus=virtio-mmio-bus.0` +- Input 输入设备 + - `-device virtio-keyboard-device,serial=virtio-keyboard,bus=virtio-mmio-bus.0` + - `-device virtio-mouse-device,serial=virtio-mouse,bus=virtio-mmio-bus.0` +- Net 网络设备 + - `-netdev tap,ifname=TAP,id=mynet0 -device virtio-net-device,netdev=mynet0,bus=virtio-mmio-bus.0` +- Console 控制台设备 + - `-chardev socket,host=127.0.0.1,port=4321,server=on,wait=on,telnet=on,id=charconsole0 -device virtserialport,chardev=charconsole0,id=console0 -device virtio-serial-device,id=virtio-console0,bus=virtio-mmio-bus.0` + +需要注意的是,每一个MMIO设备都需要**手动**指定一个独立的MMIO Bus地址,即`bus=virtio-mmio-bus.N`(N为从零开始的数字)。MMIO不支持自动发现,需要内核其他部分手动于VirtIO驱动初始化之前调用VirtIO MMIO设备注册方法进行注册,具体说明位于[VirtIO驱动实现简介](./implementation.md#MMIO通讯)中的MMIO小节内。 + +## Tips: 在QEMU Monitor中查看VirtIO设备状态 + +使用`info`命令可以查看QEMU中组件的信息及状态,对于VirtIO设备,有如下的`info`子命令可供查看。更多详细用法请参考QEMU文档及手册。 + +- `info virtio`:查看所有可用的VirtIO设备及其路径。 +- `info virtio-status `:给定一个设备路径``,输出设备状态信息。 +- `info virtio-queue-status `:给定一个设备路径``和队列标示``,输出VirtIO队列状态。 +- `info virtio-queue-element [index]`:参数同上,可以查看具体到队列中某个元素的内容。 + +在GDB链接QEMU后,可以在GDB控制台内使用`monitor xxxx`调用QEMU Monitor的指令,如`monitor info virtio`。 diff --git a/programing-manual/driver/virtio/virtio.md b/programing-manual/driver/virtio/virtio.md new file mode 100644 index 0000000000000000000000000000000000000000..7ae3b2e643650deda234ea0909609d42a4902550 --- /dev/null +++ b/programing-manual/driver/virtio/virtio.md @@ -0,0 +1,30 @@ +# VirtIO 设备驱动 + +## 目录 + +- [主页](./virtio.md) + - [VirtIO驱动使用](./usage.md) + - [VirtIO驱动接口](./interface.md) + - [VirtIO驱动实现简介](./implementation.md) + - [VirtIO驱动框架接口](./internal_interface.md) + - [附录](./appendix.md) + - [VirtIO驱动内核配置](./appendix.md#VirtIO驱动内核配置) + - [使用QEMU直接启动使用MultiBoot1规范的内核](./appendix.md#使用QEMU直接启动使用MultiBoot1规范的内核) + +## 简介 + +VirtIO是一个开放的虚拟化设备通讯接口。旨在降低主客机之间的设备通讯成本,提高虚拟化设备通讯效率。较传统仿真设备而言,基于VirtIO通讯协议的虚拟化设备是一种半虚拟化设备,即客机知晓自己运行于虚拟机中,同时也需要特殊的驱动程序,即本文档描述的VirtIO设备驱动程序。 + +VirtIO设备通过VirtIO通讯协议规定的方法于主客机中进行通讯。一般来说,VirtIO利用已有的设备接口(如PCI、MMIO Bus等)进行设备间控制信息的交换、利用共享内存进行设备间数据信息的交换。 + +VirtIO的架构层次如下图所示: + +![VirtIO架构层次](./images/1.svg) + +本文档描述的是在NXOS内核中的前端驱动实现,不涉及后端驱动的实现。如无特殊说明,在本文档中出现的所有驱动均为VirtIO前端驱动,例:*GPU驱动*指*VirtIO GPU驱动*而并非传统GPU驱动。 + +NXOS内核中VirtIO设备驱动基于VirtIO 1.2规范,**提供且仅提供**对Modern设备的支持。在NXOS的VirtIO设备驱动中使用的VirtIO-Ring方案为传统的Split VirtIO-Ring。 + +目前,VirtIO已实现VirtIO Over PCI Bus及VirtIO Over MMIO两种通讯方法。其中PCI Bus通讯方法已于x86平台上完成测试;MMIO通讯方法已于x86、risc-v平台上完成测试。 + +需要注意的是,部分设备驱动尚未实现全部功能(如GPU驱动的3D部分、Sound驱动的音频输入部分)。此外,VirtIO Sound于QEMU-8.2中才引入设备支持,使用时请注意QEMU版本。