第二篇------Virtual I/O Device (VIRTIO) Version 1.1

上篇文章:https://blog.csdn.net/Phoenix_zxk/article/details/132917657

篇幅太大,所以分开写,接下来续上

4.3.3.2.1 设备要求:Guest->Host 通知

设备必须忽略 GPR2 的位 0-31(从左边数)。这样可以使子通道 ID 的传递方式与现有的 I/O 指令传递方式保持一致。
设备可以在 GPR2 中返回一个 64 位的主机 cookie 以加速通知的执行。

4.3.3.2.2 驱动程序要求:Guest->Host 通知

对于每个通知,驱动程序应该使用 GPR4 来传递从前一个通知中在 GPR2 中接收到的主机 cookie。

注意:例如:

// 使用指定的虚拟队列(vq)索引和当前的 cookie 值(info->cookie)进行通知
info->cookie = do_notify(schid,
                        virtqueue_get_queue_index(vq),
                        info->cookie);

4.3.3.3 重置设备

为了重置设备,驱动程序发送 CCW_CMD_VDEV_RESET 命令。

5 设备类型

在virtio中,除了队列、配置空间和特性协商功能之外,还定义了多种设备类型。以下是用于标识不同类型的virtio设备的设备ID。其中,一些设备ID被保留,用于尚未在本标准中定义的设备。发现可用设备及其类型是依赖于总线的。

第二篇------Virtual I/O Device (VIRTIO) Version 1.1_第1张图片

5 设备类型

本文档中未明确定义一些设备,因为它们被视为不成熟或特别狭窄的设备。请注意,其中一些设备仅由唯一的现有实现规定;它们可能成为未来规范的一部分,也可能被完全放弃,或者继续存在于本标准之外。我们将不再深入讨论它们。

5.1 网络设备

virtio网络设备是虚拟以太网卡,是到目前为止由virtio支持的设备中最复杂的设备。它迅速增强,并清晰地展示了如何向现有设备添加新功能支持。空缓冲区放置在一个virtqueue中用于接收数据包,而发送的数据包则按顺序排队到另一个virtqueue中进行传输。第三个命令队列用于控制高级过滤功能。

5.1.1 设备ID

1

5.1.2 Virtqueues

  • 0 接收队列1
  • 1 发送队列1
  • 2(N-1) 接收队列N
  • 2(N-1)+1 发送队列N
  • 2N 控制队列

N的值为1(如果未协商VIRTIO_NET_F_MQ),否则N的值由max_virtqueue_pairs设置。

如果设置了VIRTIO_NET_F_CTRL_VQ,则存在控制队列。

5.1.3 特性位

  • VIRTIO_NET_F_CSUM (0):设备处理带有部分校验和的数据包。这种“校验和卸载”是现代网络卡的常见功能。
  • VIRTIO_NET_F_GUEST_CSUM (1):驱动程序处理带有部分校验和的数据包。
  • VIRTIO_NET_F_CTRL_GUEST_OFFLOADS (2):控制通道卸载重新配置支持。
  • VIRTIO_NET_F_MTU (3):支持设备的最大MTU报告。如果设备提供了此功能,设备会通知驱动程序有关其最大MTU值的值。如果协商成功,驱动程序将使用mtu作为最大MTU值。
  • VIRTIO_NET_F_MAC (5):设备具有给定的MAC地址。
  • VIRTIO_NET_F_GUEST_TSO4 (7):驱动程序可以接收TSOv4。
  • VIRTIO_NET_F_GUEST_TSO6 (8):驱动程序可以接收TSOv6。
  • VIRTIO_NET_F_GUEST_ECN (9):驱动程序可以接收带有ECN的TSO。
  • VIRTIO_NET_F_GUEST_UFO (10):驱动程序可以接收UFO。
  • VIRTIO_NET_F_HOST_TSO4 (11):设备可以接收TSOv4。
  • VIRTIO_NET_F_HOST_TSO6 (12):设备可以接收TSOv6。
  • VIRTIO_NET_F_HOST_ECN (13):设备可以接收带有ECN的TSO。
  • VIRTIO_NET_F_HOST_UFO (14):设备可以接收UFO。
  • VIRTIO_NET_F_MRG_RXBUF (15):驱动程序可以合并接收缓冲区。
  • VIRTIO_NET_F_STATUS (16):可用配置状态字段。
  • VIRTIO_NET_F_CTRL_VQ (17):可用控制通道。
  • VIRTIO_NET_F_CTRL_RX (18):支持控制通道RX模式。
  • VIRTIO_NET_F_CTRL_VLAN (19):支持控制通道VLAN过滤。
  • VIRTIO_NET_F_GUEST_ANNOUNCE (21):驱动程序可以发送免费数据包。
  • VIRTIO_NET_F_MQ (22):设备支持自动接收转向的多队列。
  • VIRTIO_NET_F_CTRL_MAC_ADDR (23):通过控制通道设置MAC地址。
  • VIRTIO_NET_F_RSC_EXT (61):设备可以处理重复的ACK并报告合并的段数和重复的ACK数。
  • VIRTIO_NET_F_STANDBY (62):设备可以充当具有相同MAC地址的主设备的备用设备。

5.1.3.1 特性位要求

某些网络特性位要求其他网络特性位(请参阅2.2.1):

  • VIRTIO_NET_F_GUEST_TSO4 需要 VIRTIO_NET_F_GUEST_CSUM。
  • VIRTIO_NET_F_GUEST_TSO6 需要 VIRTIO_NET_F_GUEST_CSUM。
  • VIRTIO_NET_F_GUEST_ECN 需要 VIRTIO_NET_F_GUEST_TSO4 或 VIRTIO_NET_F_GUEST_TSO6。
  • VIRTIO_NET_F_GUEST_UFO 需要 VIRTIO_NET_F_GUEST_CSUM。
  • VIRTIO_NET_F_HOST_TSO4 需要 VIRTIO_NET_F_CSUM。
  • VIRTIO_NET_F_HOST_TSO6 需要 VIRTIO_NET_F_CSUM。
  • VIRTIO_NET_F_HOST_ECN 需要 VIRTIO_NET_F_HOST_TSO4 或 VIRTIO_NET_F_HOST_TSO6。
  • VIRTIO_NET_F_HOST_UFO 需要 VIRTIO_NET_F_CSUM。
  • VIRTIO_NET_F_CTRL_RX 需要 VIRTIO_NET_F_CTRL_VQ。
  • VIRTIO_NET_F_CTRL_VLAN 需要 VIRTIO_NET_F_CTRL_VQ。
  • VIRTIO_NET_F_GUEST_ANNOUNCE 需要 VIRTIO_NET_F_CTRL_VQ。
  • VIRTIO_NET_F_MQ 需要 VIRTIO_NET_F_CTRL_VQ。
  • VIRTIO_NET_F_CTRL_MAC_ADDR 需要 VIRTIO_NET_F_CTRL_VQ。
  • VIRTIO_NET_F_RSC_EXT 需要 VIRTIO_NET_F_HOST_TSO4 或 VIRTIO_NET_F_HOST_TSO6。

5.1.3.2 Legacy界面:特性位

  • VIRTIO_NET_F_GSO (6):设备处理具有任何GSO类型的数据包。这原本是指分段卸载支持,但进一步调查后明确需要多个位。
  • VIRTIO_NET_F_GUEST_RSC4 (41):设备合并TCPIP v4数据包。这是为了认证目的而实施的,当前的Windows驱动程序依赖于它。如果virtio-net设备报告此特性,则不会起作用。
  • VIRTIO_NET_F_GUEST_RSC6 (42):设备合并TCPIP v6数据包。类似于VIRTIO_NET_F_GUEST_RSC4。

5.1.4 设备配置布局

目前定义了三个仅供驱动程序读取的配置字段。MAC地址字段始终存在(但仅在设置了VIRTIO_NET_F_MAC时有效),状态字段仅在设置了VIRTIO_NET_F_STATUS时存在。目前为状态字段定义了两个只读位(供驱动程序使用):VIRTIO_NET_S_LINK_UP和VIRTIO_NET_S_ANNOUNCE。

// 定义 Virtio 网络设备状态:连接已建立或链路已上线
#define VIRTIO_NET_S_LINK_UP 1

// 定义 Virtio 网络设备状态:将要进行链路通告
#define VIRTIO_NET_S_ANNOUNCE 2

驱动程序只读字段

如果设置了VIRTIO_NET_F_MQ,则max_virtqueue_pairs字段存在。此字段指定了在协商了VIRTIO_NET_F_MQ后可以配置的传输和接收virtqueue(分别为receiveq1…receiveqN和transmitq1…transmitqN)的最大数量。

如果设置了VIRTIO_NET_F_MTU,则mtu字段存在。此字段指定了驱动程序可以使用的最大MTU。

struct virtio_net_config {
    u8 mac[6];                // MAC 地址,占用 6 个字节
    le16 status;              // 设备状态,Little-endian 16位整数
    le16 max_virtqueue_pairs; // 最大虚拟队列对数,Little-endian 16位整数
    le16 mtu;                 // 最大传输单元 (MTU),Little-endian 16位整数
};

5.1.4.1 设备要求:设备配置布局

  • 如果设备提供VIRTIO_NET_F_MQ,则设备必须将max_virtqueue_pairs设置为1到0x8000之间(包括0x8000)。
  • 如果设备提供VIRTIO_NET_F_MTU,则设备必须将mtu设置为68到65535之间(包括68和65535)。
  • 如果设备提供VIRTIO_NET_F_MTU,则设备应该将mtu设置为至少1280。
  • 一旦设置,设备不得修改mtu。
  • 一旦成功协商了VIRTIO_NET_F_MTU,设备不得传递超过mtu(加上低级以太网标头长度)大小的接收数据包,其中gso_type为NONE或ECN。
  • 一旦成功协商了VIRTIO_NET_F_MTU,设备必须在不进行分段的情况下将大小不超过mtu(加上低级以太网标头长度)的传输数据包(gso_type为NONE或ECN)转发。

5.1.4.2 驱动程序要求:设备配置布局

  • 如果设备提供VIRTIO_NET_F_MAC,则驱动程序应该协商VIRTIO_NET_F_MAC。如果驱动程序协商了VIRTIO_NET_F_MAC功能,则驱动程序必须设置NIC的物理地址为mac。否则,它应该使用本地管理的MAC地址(请参阅IEEE 802,“9.2 48位通用LAN MAC地址”)。
  • 如果驱动程序没有协商VIRTIO_NET_F_STATUS功能,则应该假设连接处于活动状态,否则应该从status的最低位中读取链接状态。
  • 如果设备提供VIRTIO_NET_F_MTU,则驱动程序应该协商VIRTIO_NET_F_MTU功能。
  • 如果驱动程序协商了VIRTIO_NET_F_MTU,则它必须提供足够的接收缓冲区,以接收至少一个大小为mtu(加上低级以太网标头长度)且gso_type为NONE或ECN的接收数据包。
  • 如果驱动程序协商了VIRTIO_NET_F_MTU,则它不得传输大小超过mtu(加上低级以太网标头长度)值且gso_type为NONE或ECN的数据包。
  • 如果设备提供VIRTIO_NET_F_STANDBY功能,并且驱动程序支持它,则驱动程序应该协商VIRTIO_NET_F_STANDBY功能。

5.1.4.3 遗留接口:设备配置布局

  • 在使用遗留接口时,过渡设备和驱动程序必须根据客户机的本机字节序(而不是必须在不使用遗留接口时)对struct virtio_net_config中的status和max_virtqueue_pairs进行格式化。

5.1.5 设备初始化

驱动程序将执行典型的初始化程序如下:

  1. 识别和初始化接收和传输virtqueue,最多N个每种类型。如果协商了VIRTIO_NET_F_MQ功能位,则N=max_virtqueue_pairs,否则N=1。
  2. 如果协商了VIRTIO_NET_F_CTRL_VQ功能位,则识别控制virtqueue。
  3. 使用缓冲区填充接收队列:参见5.1.6.3。
  4. 即使在VIRTIO_NET_F_MQ的情况下,默认只使用receiveq1、transmitq1和controlq。驱动程序将发送VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET命令,指定要使用的传输和接收队列的数量。
  5. 如果设置了VIRTIO_NET_F_MAC功能位,则配置空间中的mac条目指示网络卡的“物理”地址,否则驱动程序通常会生成一个随机的本地MAC地址。
  6. 如果协商了VIRTIO_NET_F_STATUS功能位,则链接状态来自status的最低位。否则,驱动程序假定链接处于活动状态。
  7. 一个高性能的驱动程序会通过协商VIRTIO_NET_F_CSUM功能来指示它将生成无校验和的数据包。
  8. 如果协商了该功能,驱动程序可以通过协商VIRTIO_NET_F_HOST_TSO4(IPv4 TCP)、VIRTIO_NET_F_HOST_TSO6(IPv6 TCP)和VIRTIO_NET_F_HOST_UFO(UDP分片)功能来使用TCP或UDP分段卸载。
  9. 反之亦然:驱动程序可以通过协商这些功能来为虚拟设备节省一些工作。
    注意:例如,如果两个位于同一系统上的虚拟机之间传输网络数据包,如果两个虚拟机都支持,那么可能根本不需要进行校验和或分段。VIRTIO_NET_F_GUEST_CSUM功能表示可以接收部分校验和的数据包,如果可以接收这样的数据包,则VIRTIO_NET_F_GUEST_TSO4、VIRTIO_NET_F_GUEST_TSO6、VIRTIO_NET_F_GUEST_UFO和VIRTIO_NET_F_GUEST_ECN等功能是上述功能的输入等效功能。请参见5.1.6.3设置接收缓冲区和5.1.6.4处理传入数据包。
    一个真正最小化的驱动程序只会接受VIRTIO_NET_F_MAC,并忽略其他一切。

5.1.6 设备操作

数据包通过将它们放置在transmitq1…transmitqN中进行传输,而用于传入数据包的缓冲区则放置在receiveq1…receiveqN中。在每种情况下,数据包本身之前都会有一个标头:

struct virtio_net_hdr {
    u8 flags;                // 标志位,用于表示头部属性
    u8 gso_type;             // GSO(Generic Segmentation Offload)类型
    le16 hdr_len;            // 头部长度,Little-endian 16位整数
    le16 gso_size;           // GSO 大小,Little-endian 16位整数
    le16 csum_start;         // 校验和开始位置,Little-endian 16位整数
    le16 csum_offset;        // 校验和偏移量,Little-endian 16位整数
    le16 num_buffers;        // 缓冲区数量,Little-endian 16位整数
    
    // 头部属性的标志位
    #define VIRTIO_NET_HDR_F_NEEDS_CSUM 1
    #define VIRTIO_NET_HDR_F_DATA_VALID 2
    #define VIRTIO_NET_HDR_F_RSC_INFO 4

    // GSO(Generic Segmentation Offload)类型
    #define VIRTIO_NET_HDR_GSO_NONE 0
    #define VIRTIO_NET_HDR_GSO_TCPV4 1
    #define VIRTIO_NET_HDR_GSO_UDP 3
    #define VIRTIO_NET_HDR_GSO_TCPV6 4
    #define VIRTIO_NET_HDR_GSO_ECN 0x80
};

5.1.6.1 遗留接口:设备操作

在使用遗留接口时,过渡设备和驱动程序必须根据客户机的本机字节序来格式化struct virtio_net_hdr中的字段,而不是(当不使用遗留接口时)小端字节序。

在遗留接口中,只有在协商了VIRTIO_NET_F_MRG_RXBUF时,驱动程序才会在struct virtio_net_hdr中呈现num_buffers字段;如果没有该功能,该结构将缩短2个字节。

在使用遗留接口时,驱动程序应该忽略传输队列和controlq队列的used长度。
注意:在历史上,某些设备将总描述符长度放在那里,尽管实际上没有写入任何数据。

5.1.6.2 数据包传输

传输单个数据包很简单,但取决于驱动程序协商的不同功能。

  1. 驱动程序可以发送完全校验过的数据包。在这种情况下,标志将为零,gso_type将为VIRTIO_NET_HDR_GSO_NONE。
  2. 如果驱动程序协商了VIRTIO_NET_F_CSUM,则可以跳过对数据包进行校验和:
    • 标志设置为具有VIRTIO_NET_HDR_F_NEEDS_CSUM标志,
    • csum_start设置为数据包内开始校验和的偏移量,并且
    • csum_offset指示设备放置的新(16位一补数)校验和在csum_start之后的多少字节。
    • 数据包中的TCP校验和字段设置为TCP伪标头的和,以便将其替换为TCP标头和正文的一补数校验和将得到正确的结果。
      注意:例如,考虑一个部分校验和的TCP(IPv4)数据包。它具有14字节的以太网标头,后跟20字节的IP标头,然后是TCP标头(TCP校验和字段在该标头的16字节处)。csum_start将为14+20 = 34(TCP校验和包括标头),csum_offset将为16。
  3. 如果驱动程序协商了VIRTIO_NET_F_HOST_TSO4、TSO6或UFO,并且数据包需要进行TCP分段或UDP分段,则gso_type将设置为VIRTIO_NET_HDR_GSO_TCPV4、TCPV6或UDP。 (否则,设置为VIRTIO_NET_HDR_GSO_NONE)。在这种情况下,可以传输大于1514字节的数据包:元数据指示如何复制数据包标头以将其切割成较小的数据包。还设置了其他gso字段:
    • hdr_len是对设备的提示,指示需要保留多少标头以复制到每个数据包中,通常设置为包括传输标头在内的标头的长度。
    • gso_size是每个数据包在该标头之后的最大大小(即MSS)。如果驱动程序协商了VIRTIO_NET_F_HOST_ECN功能,则gso_type中的VIRTIO_NET_HDR_GSO_-
      ECN位表示TCP数据包设置了ECN位。
  4. num_buffers设置为零。此字段在传输的数据包上未使用。
  5. 将标头和数据包作为一个输出描述符添加到传输队列,并通知设备新条目的到来(参见5.1.5 设备初始化)。
5.1.6.2.1 驱动程序要求:数据包传输
  • 驱动程序必须将num_buffers设置为零。
  • 如果没有协商VIRTIO_NET_F_CSUM,则驱动程序必须将flags设置为零,并且应向设备提供完全校验过的数据包。
  • 如果协商了VIRTIO_NET_F_HOST_TSO4,则驱动程序可以将gso_type设置为VIRTIO_NET_HDR_GSO_TCPV4以请求TCPv4分段,否则驱动程序不得将gso_type设置为VIRTIO_NET_-
    HDR_GSO_TCPV4。
  • 如果协商了VIRTIO_NET_F_HOST_TSO6,则驱动程序可以将gso_type设置为VIRTIO_NET_HDR_GSO_TCPV6以请求TCPv6分段,否则驱动程序不得将gso_type设置为VIRTIO_NET_-
    HDR_GSO_TCPV6。
  • 如果协商了VIRTIO_NET_F_HOST_UFO,则驱动程序可以将gso_type设置为VIRTIO_NET_HDR_GSO_UDP以请求UDP分段,否则驱动程序不得将gso_type设置为VIRTIO_NET_HDR_-
    GSO_UDP。
  • 驱动程序不应将需要分段卸载的TCP数据包发送给带有显式拥塞通知位设置的设备,除非协商了VIRTIO_NET_F_HOST_ECN功能,在这种情况下,gso_type中必须设置VIRTIO_NET_HDR_GSO_-
    ECN位。
  • 如果协商了VIRTIO_NET_F_CSUM功能,驱动程序可以在flags中设置VIRTIO_NET_HDR_F_NEEDS_CSUM位,如果是这样:
    1. 驱动程序必须验证从csum_start偏移量开始的数据包校验和,以及所有先前的偏移量;
    2. 驱动程序必须将存储在缓冲区中的数据包校验和设置为TCP/UDP伪标头;
    3. 驱动程序必须设置csum_start和csum_offset,以便从csum_start开始直到数据包结束计算一补数校验和,并将结果存储在csum_start偏移量处。
  • 如果没有协商VIRTIO_NET_F_HOST_TSO4、TSO6或UFO选项之一,则驱动程序必须将gso_type设置为VIRTIO_NET_HDR_GSO_NONE。
  • 如果gso_type与VIRTIO_NET_HDR_GSO_NONE不同,则驱动程序还必须在flags中设置VIRTIO_NET_HDR_F_NEEDS_CSUM位,并且必须设置gso_size以指示所需的MSS。
  • 如果协商了VIRTIO_NET_F_HOST_TSO4、TSO6或UFO选项之一,则驱动程序应将hdr_len设置为不小于标头的长度,包括传输标头。
  • 驱动程序不得在flags中设置VIRTIO_NET_HDR_F_DATA_VALID和VIRTIO_NET_HDR_F_RSC_INFO位。
5.1.6.2.2 设备要求:数据包传输
  • 设备必须忽略它不认识的标志位。
  • 如果在flags中没有设置VIRTIO_NET_HDR_F_NEEDS_CSUM位,则设备不得使用csum_start和csum_offset。如果协商了VIRTIO_NET_F_HOST_TSO4、TSO6或UFO选项之一,则设备只能将hdr_len用作有关传输标头大小的提示。设备不得依赖hdr_len的正确性。
    注意:这是由于各种实现中的各种错误。
  • 如果没有设置VIRTIO_NET_HDR_F_NEEDS_CSUM,则设备不得依赖数据包的校验和是正确的。
5.1.6.2.3 数据包传输中断

通常,驱动程序将抑制传输虚拟队列中的中断,并在随后的数据包传输路径中检查已使用的数据包。

在此中断处理程序中的正常行为是从虚拟队列中检索已使用的缓冲区并释放相应的标头和数据包。

5.1.6.3 设置接收缓冲区

通常情况下,尽可能使接收虚拟队列保持充分填充是个不错的主意:如果用完,网络性能将受到影响。

如果使用了VIRTIO_NET_F_GUEST_TSO4、VIRTIO_NET_F_GUEST_TSO6或VIRTIO_NET_F_GUEST_UFO功能,则最大的传入数据包将长达65550字节(TCP或UDP数据包的最大大小,再加上14字节的以太网标头),否则为1514字节。这之前会有12字节的struct virtio_net_hdr。这样总共就是65562字节或1526字节。

5.1.6.3.1 驱动程序要求:设置接收缓冲区
  • 如果没有协商VIRTIO_NET_F_MRG_RXBUF:
    • 如果协商了VIRTIO_NET_F_GUEST_TSO4、VIRTIO_NET_F_GUEST_TSO6或VIRTIO_NET_F_GUEST_-
      UFO,则驱动程序应该用至少65562字节的缓冲区填充接收队列。
    • 否则,驱动程序应该用至少1526字节的缓冲区填充接收队列。
  • 如果协商了VIRTIO_NET_F_MRG_RXBUF,则每个缓冲区必须至少具有struct virtio_net_hdr的大小。
    注意:显然,每个缓冲区可以跨越多个描述符元素。

如果协商了VIRTIO_NET_F_MQ,那么每个将被使用的receiveq1…receiveqN都应该被填充上接收缓冲区。

5.1.6.3.2 设备要求:设置接收缓冲区

设备必须将num_buffers设置为用于容纳传入数据包的描述符数。

如果没有协商VIRTIO_NET_F_MRG_RXBUF,则设备必须只使用一个描述符。
注意:这意味着如果没有协商VIRTIO_NET_F_MRG_RXBUF,则num_buffers始终为1。

5.1.6.4 处理传入数据包

当将数据包复制到接收队列中的缓冲区时,最佳路径是禁用接收队列的进一步已使用缓冲区通知,并处理数据包,直到没有更多数据包,然后重新启用它们。

处理传入数据包涉及:

  1. num_buffers指示此数据包分布在多少个描述符上(包括此描述符):如果没有协商VIRTIO_NET_F_MRG_RXBUF,则始终为1。这允许接收大数据包而无需分配大缓冲区:不适合单个缓冲区的数据包可以流经下一个缓冲区,依此类推。在这种情况下,virtqueue中将至少有num_buffers个已使用的缓冲区,设备将它们链接在一起以形成单个数据包,方式类似于它如何将其存储在单个跨多个描述符的缓冲区中。其他缓冲区不会以struct virtio_net_hdr开头。
  2. 如果num_buffers为1,则整个数据包将包含在此缓冲区中,紧随在struct virtio_net_hdr之后。
  3. 如果已协商VIRTIO_NET_F_GUEST_CSUM功能,则flags中的VIRTIO_NET_HDR_F_DATA_VALID位可以设置:如果是这样,设备已验证数据包的校验和。在多层封装协议的情况下,已验证了一层校验和。
  4. 另外,VIRTIO_NET_F_GUEST_CSUM、TSO4、TSO6、UDP和ECN功能启用了接收校验和、大接收卸载和ECN支持,这些是与传输校验和、传输分段卸载和ECN功能对应的输入功能,如5.1.6.2中所述:
    1. 如果已协商了VIRTIO_NET_F_GUEST_TSO4、TSO6或UFO选项之一,则gso_type可能是VIRTIO_NET_HDR_GSO_NONE之外的其他值,而gso_size字段指示所需的MSS(请参见数据包传输中的第2点)。
    2. 如果已协商了VIRTIO_NET_F_RSC_EXT选项(这意味着已协商了VIRTIO_NET_F_GUEST_-
      TSO4、TSO6之一),则设备还会处理重复的ACK段,在csum_start字段中报告合并的TCP段数,在csum_offset字段中报告重复的ACK段数,并在flags中设置了VIRTIO_NET_HDR_F_RSC_INFO位。
    3. 如果已协商了VIRTIO_NET_F_GUEST_CSUM功能,则flags中的VIRTIO_NET_HDR_F_NEEDS_CSUM位可以设置:如果是这样,已验证了从csum_start到csum_offset偏移处的数据包校验和以及任何先前的校验和。数据包上的校验和是不完整的,如果在flags中未设置VIRTIO_NET_HDR_F_RSC_INFO位,则csum_start和csum_offset将指示如何计算它(请参见数据包传输中的第1点)。
5.1.6.4.1 设备要求:处理传入数据包
  • 如果没有协商VIRTIO_NET_F_MRG_RXBUF,则设备必须将num_buffers设置为1。
  • 如果协商了VIRTIO_NET_F_MRG_RXBUF,则设备必须设置num_buffers以指示数据包(包括标头)分布在多少个缓冲区上。
  • 如果接收数据包分布在多个缓冲区上,则设备必须使用除最后一个之外的所有缓冲区(即前num_buffers−1个缓冲区),直到完全使用由驱动程序提供的每个缓冲区的整个长度。
  • 设备必须将单个接收数据包中使用的所有缓冲区一起使用,以至少让驱动程序观察到num_buffers个已使用的缓冲区。
  • 如果没有协商VIRTIO_NET_F_GUEST_CSUM,设备必须将flags设置为零,并应该向驱动程序提供完全校验和的数据包。
  • 如果没有协商VIRTIO_NET_F_GUEST_TSO4,则设备不得将gso_type设置为VIRTIO_NET_-
    HDR_GSO_TCPV4。
  • 如果没有协商VIRTIO_NET_F_GUEST_UDP,则设备不得将gso_type设置为VIRTIO_NET_-
    HDR_GSO_UDP。
  • 如果没有协商VIRTIO_NET_F_GUEST_TSO6,则设备不得将gso_type设置为VIRTIO_NET_-
    HDR_GSO_TCPV6。
  • 设备不应该发送需要分段卸载的TCP数据包,其显式拥塞通知位设置,除非已协商了VIRTIO_NET_F_GUEST_ECN功能,此时设备必须设置gso_type中的VIRTIO_NET_HDR_GSO_ECN位。如果已协商了VIRTIO_NET_F_GUEST_CSUM功能,设备可以在flags中设置VIRTIO_NET_HDR_F_NEEDS_CSUM位,如果如此:
    1. 设备必须验证从csum_start到csum_offset偏移的数据包校验和,以及所有前面的偏移;
    2. 设备必须设置存储在接收缓冲区中的数据包校验和为TCP/UDP伪标头;
    3. 设备必须设置csum_start和csum_offset,以便从csum_start到数据包末尾计算一的补码校验和,并将结果存储在csum_start偏移处的csum_offset中,以得到完全校验和的数据包;
  • 如果没有协商VIRTIO_NET_F_GUEST_TSO4、TSO6或UFO选项,设备必须将gso_type设置为VIRTIO_NET_HDR_GSO_NONE。
  • 如果gso_type与VIRTIO_NET_HDR_GSO_NONE不同,则设备还必须设置flags中的VIRTIO_NET_HDR_F_NEEDS_CSUM位,并设置gso_size以指示所需的MSS。
  • 如果协商了VIRTIO_NET_F_RSC_EXT,设备必须还设置flags中的VIRTIO_NET_HDR_F_RSC_INFO位,设置csum_start以表示合并的TCP段数量,以及设置csum_offset以表示接收到的重复的ACK段数量。
  • 如果没有协商VIRTIO_NET_F_RSC_EXT,设备不得在flags中设置VIRTIO_NET_HDR_F_RSC_INFO位。
  • 如果已协商了VIRTIO_NET_F_GUEST_TSO4、TSO6或UFO选项之一,设备应该将hdr_len设置为至少不小于标头的长度,包括传输标头。
  • 如果已协商了VIRTIO_NET_F_GUEST_CSUM功能,设备可以在flags中设置VIRTIO_NET_HDR_F_DATA_VALID位,如果如此,设备必须验证数据包的校验和(在多层封装协议的情况下,已验证了一层校验和)。
5.1.6.4.2 驱动程序要求:处理传入数据包
  • 驱动程序必须忽略它不认识的标志位。
  • 如果在flags中没有设置VIRTIO_NET_HDR_F_NEEDS_CSUM位,或者如果在flags中设置了VIRTIO_NET_HDR_F_RSC_INFO位,驱动程序不得使用csum_start和csum_offset。
  • 如果协商了VIRTIO_NET_F_GUEST_TSO4、TSO6或UFO选项之一,驱动程序只能将hdr_len用作有关传输标头大小的提示。驱动程序不得依赖hdr_len的正确性。
    注意:这是由于各种实现中的各种错误。
  • 如果既没有设置VIRTIO_NET_HDR_F_NEEDS_CSUM也没有设置VIRTIO_NET_HDR_F_DATA_VALID,驱动程序不得依赖数据包的校验和是正确的。

5.1.6.5 控制虚拟队列

如果已协商了VIRTIO_NET_F_CTRL_VQ,则驱动程序使用控制虚拟队列来发送命令以操作设备的各种功能,这些功能不容易映射到配置空间中。
所有命令都采用以下形式:

struct virtio_net_ctrl {
    u8 class;                    // 控制消息的类别
    u8 command;                  // 控制消息的命令
    u8 command-specific-data[];  // 命令特定数据(可变长度)
    u8 ack;                      // 确认标志
};

/* ack 值 */
#define VIRTIO_NET_OK 0   // 控制消息确认成功
#define VIRTIO_NET_ERR 1  // 控制消息确认失败

驱动程序使用控制 virtqueue(如果已经协商了 VIRTIO_NET_F_CTRL_VQ)来发送命令,以操纵设备的各种特性,这些特性不容易映射到配置空间中。所有的命令都具有以下形式:

  • class:表示命令的类型。
  • command:表示要执行的特定命令。
  • command-specific-data:包含与命令相关的特定数据。

这些字段由驱动程序设置,而设备设置 ack 字节。如果 ack 不是 VIRTIO_NET_OK,设备通常只能发出诊断消息。

5.1.6.5.1 数据包接收过滤

如果已经协商了 VIRTIO_NET_F_CTRL_RX 和 VIRTIO_NET_F_CTRL_RX_EXTRA 特性,驱动程序可以发送控制命令来启用或禁用混杂模式、多播、单播和广播数据包的接收。

注意:总的来说,这些命令是尽力而为的:仍然可能会收到不需要的数据包。

// 定义 Virtio 网络设备控制消息的类别
#define VIRTIO_NET_CTRL_RX 0

// 定义 Virtio 网络设备控制消息的命令,用于配置接收行为
#define VIRTIO_NET_CTRL_RX_PROMISC 0    // 混杂模式,接收所有数据包
#define VIRTIO_NET_CTRL_RX_ALLMULTI 1    // 接收所有多播数据包
#define VIRTIO_NET_CTRL_RX_ALLUNI 2      // 接收所有单播数据包
#define VIRTIO_NET_CTRL_RX_NOMULTI 3     // 不接收多播数据包
#define VIRTIO_NET_CTRL_RX_NOUNI 4       // 不接收单播数据包
#define VIRTIO_NET_CTRL_RX_NOBCAST 5     // 不接收广播数据包

5.1.6.5.1.1 设备要求:数据包接收过滤

如果已经协商了 VIRTIO_NET_F_CTRL_RX 特性,设备必须支持以下 VIR-
TIO_NET_CTRL_RX 类命令:

  • VIRTIO_NET_CTRL_RX_PROMISC:用于启用和禁用混杂模式。命令特定数据是一个字节,包含 0(关闭)或 1(打开)。如果混杂模式打开,设备应该接收所有传入的数据包。即使已经通过 VIRTIO_NET_CTRL_RX 类命令中的其他模式之一打开,这也应该生效。

  • VIRTIO_NET_CTRL_RX_ALLMULTI:用于启用和禁用全多播接收。命令特定数据是一个字节,包含 0(关闭)或 1(打开)。当全多播接收打开时,设备应该允许所有传入的多播数据包。

如果已经协商了 VIRTIO_NET_F_CTRL_RX_EXTRA 特性,设备必须支持以下 VIR-
TIO_NET_CTRL_RX 类命令:

  • VIRTIO_NET_CTRL_RX_ALLUNI:用于启用和禁用全单播接收。命令特定数据是一个字节,包含 0(关闭)或 1(打开)。当全单播接收打开时,设备应该允许所有传入的单播数据包。

  • VIRTIO_NET_CTRL_RX_NOMULTI:用于抑制多播接收。命令特定数据是一个字节,包含 0(允许多播接收)或 1(抑制多播接收)。当抑制多播接收时,设备不应将多播数据包发送给驱动程序。即使 VIRTIO_NET_CTRL_RX_ALLMULTI 打开,这个过滤器也不应用于广播数据包。

    • VIRTIO_NET_CTRL_RX_NOUNI:用于抑制单播接收。命令特定数据是一个字节,包含 0(允许单播接收)或 1(抑制单播接收)。当抑制单播接收时,设备不应将单播数据包发送给驱动程序。即使 VIRTIO_NET_CTRL_RX_ALLUNI 打开,这个过滤器也不应用于广播数据包。
  • VIRTIO_NET_CTRL_RX_NOBCAST:用于抑制广播接收。命令特定数据是一个字节,包含 0(允许广播接收)或 1(抑制广播接收)。当抑制广播接收时,设备不应将广播数据包发送给驱动程序。即使 VIRTIO_NET_CTRL_RX_ALLMULTI 打开,这个过滤器也不应用于广播数据包。

5.1.6.5.1.2 驱动程序要求:数据包接收过滤

如果未协商 VIRTIO_NET_F_CTRL_RX 特性,驱动程序不得发出命令 VIRTIO_NET_CTRL_RX_PROMISC 或 VIRTIO_NET_CTRL_RX_ALLMULTI。如果未协商 VIRTIO_NET_F_CTRL_RX_EXTRA 特性,驱动程序不得发出
命令 VIRTIO_NET_CTRL_RX_ALLUNI、VIRTIO_NET_CTRL_RX_NOMULTI、VIRTIO_NET_CTRL_RX_NOUNI 或 VIRTIO_NET_CTRL_RX_NOBCAST。

5.1.6.5.2 设置MAC地址过滤

如果已经协商了 VIRTIO_NET_F_CTRL_RX 特性,驱动程序可以发送用于MAC地址过滤的控制命令。

struct virtio_net_ctrl_mac {
    le32 entries;      // MAC 地址表的条目数量,Little-endian 32位整数
    u8 macs[entries][6]; // MAC 地址表,包含多个 MAC 地址,每个地址占用 6 个字节
};

// 定义 Virtio 网络设备控制消息的类别
#define VIRTIO_NET_CTRL_MAC 1

// 定义 Virtio 网络设备控制消息的命令
#define VIRTIO_NET_CTRL_MAC_TABLE_SET 0  // 设置 MAC 地址表
#define VIRTIO_NET_CTRL_MAC_ADDR_SET 1   // 设置单个 MAC 地址

设备可以根据任意数量的目标MAC地址过滤传入的数据包 3。此表使用 VIRTIO_NET_CTRL_MAC 类和 VIRTIO_NET_CTRL_MAC_TABLE_SET 命令进行设置。命令特定数据是两个可变长度的表格,包含 6 字节的MAC地址(如 struct virtio_net_ctrl_mac 中所述)。第一个表格包含单播地址,第二个包含多播地址。
VIRTIO_NET_CTRL_MAC_ADDR_SET 命令用于设置接收过滤接受的默认MAC地址(如果已经协商了 VIRTIO_NET_F_MAC_ADDR 特性,则会在配置空间的 mac 中反映出来)。
VIRTIO_NET_CTRL_MAC_ADDR_SET 的命令特定数据是 6 字节的MAC地址。

5.1.6.5.2.1 设备要求:设置MAC地址过滤
  • 设备在重置时必须具有一个空的MAC过滤表。
  • 设备在处理 VIRTIO_NET_CTRL_MAC_TABLE_SET 命令之前必须更新MAC过滤表。
  • 如果已经协商了 VIRTIO_NET_F_MAC_ADDR 特性,设备在处理 VIRTIO_NET_CTRL_MAC_ADDR_SET 命令之前必须更新配置空间中的mac。
  • 如果目标MAC与 mac(或使用 VIRTIO_NET_CTRL_MAC_ADDR_SET 设置的MAC)和MAC过滤表都不匹配,设备应该丢弃传入的数据包。
5.1.6.5.2.2 驱动程序要求:设置MAC地址过滤

如果未协商 VIRTIO_NET_F_CTRL_RX,驱动程序不得发出 VIRTIO_NET_CTRL_MAC 类命令。
如果已经协商了 VIRTIO_NET_F_CTRL_RX,且默认的MAC与 mac 不同,驱动程序应该发出 VIRTIO_NET_CTRL_MAC_ADDR_SET 来设置默认的MAC。
驱动程序必须在 VIRTIO_NET_CTRL_MAC_TABLE_SET 命令后附加 le32 数字,然后是该数量的非多播MAC地址,然后是另一个 le32 数字,然后是该数量的多播地址。两个数字都可以为0。

5.1.6.5.2.3 旧版接口:设置MAC地址过滤

在使用旧版接口时,过渡设备和驱动程序必须根据客户机的本机字节序(与旧版接口无关时)而不是小端格式来格式化 struct virtio_net_ctrl_mac 条目。
未协商 VIRTIO_NET_F_CTRL_MAC_ADDR 的旧版驱动程序在NIC接受传入的数据包时会更改配置空间中的mac。这些驱动程序始终将MAC值从第一个字节写到最后一个字节,因此在检测到这些驱动程序后,过渡设备可以选择推迟MAC更新,或者可以选择推迟处理传入的数据包,直到驱动程序写入配置空间中的mac的最后一个字节。

5.1.6.5.3 VLAN过滤

如果驱动程序协商了 VIRTION_NET_F_CTRL_VLAN 特性,它可以控制设备中的VLAN过滤表。

// 定义 Virtio 网络设备控制消息的类别,用于配置 VLAN 相关操作
#define VIRTIO_NET_CTRL_VLAN 2

// 定义 Virtio 网络设备控制消息的命令
#define VIRTIO_NET_CTRL_VLAN_ADD 0  // 添加 VLAN
#define VIRTIO_NET_CTRL_VLAN_DEL 1  // 删除 VLAN

"VIRTIO_NET_CTRL_VLAN_ADD"和"VIRTIO_NET_CTRL_VLAN_DEL"命令都将小端序的16位VLAN ID作为命令特定数据传递。

5.1.6.5.3.1 旧版接口:VLAN过滤

在使用旧版接口时,过渡设备和驱动程序必须根据客户机的本机字节序而不是小端格式来格式化VLAN id。

5.1.6.5.4 Gratuitous Packet Sending

如果驱动程序协商了 VIRTIO_NET_F_GUEST_ANNOUNCE(取决于 VIRTIO_NET_F_CTRL_VQ),设备可以要求驱动程序发送 gratuitous 数据包;通常在客户机已经在物理上迁移,并且需要在新的网络链路上宣布其存在时执行此操作(由于虚拟机监视器不了解客户机的网络配置(例如,标记的 VLAN),因此这种方式最简单)。

// 定义 Virtio 网络设备控制消息的类别,用于通告相关操作
#define VIRTIO_NET_CTRL_ANNOUNCE 3

// 定义 Virtio 网络设备控制消息的命令,表示通告确认
#define VIRTIO_NET_CTRL_ANNOUNCE_ACK 0

驱动程序在注意到设备配置更改时,会检查设备配置状态字段中的VIRTIO_NET_S_ANNOUNCE位。命令VIRTIO_NET_CTRL_ANNOUNCE_ACK用于表示驱动程序已收到通知,设备会在状态中清除VIRTIO_NET_S_ANNOUNCE位。
处理此通知包括:

  1. 发送自愿数据包(例如ARP)或标记有待发送的自愿数据包,并允许延迟例程发送它们。
  2. 通过控制队列发送VIRTIO_NET_CTRL_ANNOUNCE_ACK命令。
5.1.6.5.4.1 驱动程序要求:自愿数据包发送

如果驱动程序协商了VIRTIO_NET_F_GUEST_ANNOUNCE,则应在注意到状态中的VIRTIO_NET_S_ANNOUNCE位后通知网络对等方其新位置。驱动程序必须通过命令队列发送一个带有类VIRTIO_NET_CTRL_ANNOUNCE和命令VIRTIO_NET_CTRL_ANNOUNCE_ACK的命令。

5.1.6.5.4.2 设备要求:自愿数据包发送

如果协商了VIRTIO_NET_F_GUEST_ANNOUNCE,设备必须在收到一个带有类VIRTIO_NET_CTRL_ANNOUNCE和命令VIRTIO_NET_CTRL_ANNOUNCE_ACK的命令缓冲区后,在将其标记为已使用之前清除状态中的VIRTIO_NET_S_ANNOUNCE位。

5.1.6.5.5 多队列模式中的自动接收定向

如果驱动程序协商了VIRTIO_NET_F_MQ功能位(取决于VIRTIO_NET_F_CTRL_VQ),则可以将传出数据包传输到多个transmitq1…transmitqN中的一个,并要求设备根据数据包流将传入数据包排入多个receiveq1…receiveqN中的一个。

// 定义 Virtio 网络设备控制消息的类别,用于多队列(Multi-Queue)配置
#define VIRTIO_NET_CTRL_MQ 4

// 定义 Virtio 网络设备控制消息的命令
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 0    // 设置队列对数
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN 1    // 最小队列对数
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX 0x8000  // 最大队列对数

// 定义 Virtio 网络设备多队列配置的结构体
struct virtio_net_ctrl_mq {
    le16 virtqueue_pairs;  // 队列对数,Little-endian 16位整数
};

默认情况下,多队列是禁用的。驱动程序通过执行VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET命令来启用多队列,指定要使用的传输队列和接收队列的数量,最多为max_virtqueue_pairs;随后,transmitq1…transmitqn和receiveq1…receiveqn(其中n=virtqueue_pairs)可以被使用。

启用多队列后,设备必须基于数据包流使用自动接收定向。接收定向分类器的编程是隐式的。驱动程序在传输了流上的数据包后,设备应该导致该流的传入数据包被定向到receiveqX。对于单向协议,或者尚未传输数据包的情况,设备可以将数据包定向到指定的receiveq1…receiveqn队列中的一个。

通过将virtqueue_pairs设置为1来禁用多队列(这是默认值),并等待设备使用命令缓冲区。

5.1.6.5.5.1 驱动程序要求:多队列模式中的自动接收定向

驱动程序必须在使用VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET命令启用它们之前配置virtqueues。
驱动程序不得在设备配置空间中请求virtqueue_pairs为0或大于max_virtqueue_pairs。
驱动程序必须在将VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET命令放置在可用环中之前仅在任何传输队列上排队数据包transmitq1。
一旦将VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET命令放置在可用环中,驱动程序不得在大于virtqueue_pairs的传输队列上排队数据包。

5.1.6.5.5.2 设备要求:多队列模式中的自动接收定向

设备必须在使用VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET命令放置在已使用的缓冲区中之前,仅在任何receiveq1上排队数据包。
设备不得在大于virtqueue_pairs的接收队列上排队数据包。

5.1.6.5.5.3 Legacy接口:多队列模式中的自动接收定向

在使用传统接口时,过渡设备和驱动程序必须根据客户机的本地字节顺序(而不是(不一定是)小端字节顺序)格式化virtqueue_pairs。

5.1.6.5.6 网络卸载状态配置

如果已经协商了VIRTIO_NET_F_CTRL_GUEST_OFFLOADS功能,驱动程序可以发送用于动态卸载状态配置的控制命令。

5.1.6.5.6.1 设置卸载状态

要配置卸载状态,使用以下布局结构和定义:

// 定义 Virtio 网络设备特性标志,用于表示设备支持的特性
#define VIRTIO_NET_F_GUEST_CSUM 1   // 支持客户端计算校验和
#define VIRTIO_NET_F_GUEST_TSO4 7   // 支持客户端 TCP Segmentation Offload (TSO) IPv4
#define VIRTIO_NET_F_GUEST_TSO6 8   // 支持客户端 TCP Segmentation Offload (TSO) IPv6
#define VIRTIO_NET_F_GUEST_ECN 9    // 支持客户端 ECN (Explicit Congestion Notification)
#define VIRTIO_NET_F_GUEST_UFO 10   // 支持客户端 UDP Fragmentation Offload (UFO)

// 定义 Virtio 网络设备控制消息的类别,用于客户端特性标志的配置
#define VIRTIO_NET_CTRL_GUEST_OFFLOADS 5

// 定义 Virtio 网络设备控制消息的命令,用于设置客户端特性标志
#define VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET 0

// 表示设备支持的特性的位掩码
le64 offloads;

VIRTIO_NET_CTRL_GUEST_OFFLOADS类有一个命令:VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET,应用新的卸载配置。
作为命令数据传递的le64值是一个位掩码,已设置的位定义要启用的卸载,已清除的位 - 要禁用的卸载。
对于每个卸载都有相应的设备功能。在特性协商期间,相应的卸载将被启用以保持向后兼容性。

5.1.6.5.6.2 驱动程序要求:设置卸载状态

驱动程序不得启用未协商适当特性的卸载。

5.1.6.5.6.3 Legacy接口:设置卸载状态

在使用传统接口时,过渡设备和驱动程序必须根据客户机的本地字节顺序(而不是(不一定是)小端字节顺序)格式化卸载。

5.1.6.6 Legacy接口:帧格式要求

在使用传统接口时,未协商VIRTIO_F_ANY_LAYOUT的过渡驱动程序必须在传输和接收的struct virtio_net_hdr上使用单个描述符,其中网络数据位于以下描述符中。
此外,当使用控制virtqueue(参见5.1.6.5)时,未协商VIRTIO_F_ANY_LAYOUT的过渡驱动程序必须:

  • 对于所有命令,使用一个包括第一个两个字段的2字节描述符:class和command。
  • 对于除了VIRTIO_NET_CTRL_MAC_TABLE_SET之外的所有命令,使用一个包括无填充的command-specific-data的单个描述符。
  • 对于VIRTIO_NET_CTRL_MAC_TABLE_SET命令,使用确切包括无填充的command-specific-data的两个描述符:第一个描述符必须包括用于单播地址的virtio_net_ctrl_mac表结构,第二个描述符必须包括用于多播地址的virtio_net_ctrl_mac表结构。
  • 对于所有命令,使用一个1字节描述符用于ack字段。

请参阅2.6.4。

5.2 块设备

virtio块设备是一个简单的虚拟块设备(即磁盘)。读取和写入请求(以及其他奇特的请求)被放置在队列中,并由设备进行服务(可能是无序的),除非另有说明。

5.2.1 设备ID

2

5.2.2 Virtqueues

0 requestq

5.2.3 特性位

  • VIRTIO_BLK_F_SIZE_MAX(1):任何单个段的最大大小在size_max中。
  • VIRTIO_BLK_F_SEG_MAX(2):请求中段的最大数量在seg_max中。
  • VIRTIO_BLK_F_GEOMETRY(4):在geometry中指定的磁盘样式几何。
  • VIRTIO_BLK_F_RO(5):设备是只读的。
  • VIRTIO_BLK_F_BLK_SIZE(6):磁盘的块大小在blk_size中。
  • VIRTIO_BLK_F_FLUSH(9):支持缓存刷新命令。
  • VIRTIO_BLK_F_TOPOLOGY(10):设备导出有关最佳I/O对齐的信息。
  • VIRTIO_BLK_F_CONFIG_WCE(11):设备可以在写回模式和写穿模式之间切换其缓存。
  • VIRTIO_BLK_F_DISCARD(13):设备支持丢弃命令,最大丢弃扇区大小在max_discard_sectors中,最大丢弃段号在max_discard_seg中。
  • VIRTIO_BLK_F_WRITE_ZEROES(14):设备支持写零命令,最大写零扇区大小在max_write_zeroes_sectors中,最大写零段号在max_write_zeroes_seg中。

5.2.3.1 Legacy接口:特性位

  • VIRTIO_BLK_F_BARRIER(0):设备支持请求障碍。
  • VIRTIO_BLK_F_SCSI(7):设备支持SCSI数据包命令。
    注意:在传统接口中,VIRTIO_BLK_F_FLUSH也被称为VIRTIO_BLK_F_WCE。

5.2.4 设备配置布局

设备的容量(以512字节扇区表示)始终存在。其他参数的可用性都取决于上面指示的各种特性位。
如果已协商VIRTIO_BLK_F_DISCARD特性位,则设备配置空间中的max_discard_sectors和discard_sector_alignment参数以512字节单位表示。如果已协商VIRTIO_BLK_F_WRITE_ZEROES特性位,则max_write_zeroes_sectors以512字节单位表示。

struct virtio_blk_config {
    le64 capacity;  // 设备容量,Little-endian 64位整数
    le32 size_max;  // 最大请求大小,Little-endian 32位整数
    le32 seg_max;   // 最大分段请求大小,Little-endian 32位整数

    // 块设备几何信息
    struct virtio_blk_geometry {
        le16 cylinders;  // 圆柱数量,Little-endian 16位整数
        u8 heads;        // 磁头数
        u8 sectors;      // 每磁道扇区数
    } geometry;

    le32 blk_size;  // 块大小,Little-endian 32位整数

    // 块设备拓扑信息
    struct virtio_blk_topology {
        u8 physical_block_exp;  // 每个物理块的逻辑块数(以2为底的对数)
        u8 alignment_offset;    // 第一个对齐的逻辑块的偏移量
        le16 min_io_size;       // 建议的最小I/O大小(以块为单位),Little-endian 16位整数
        le32 opt_io_size;       // 建议的最大I/O大小(以块为单位),Little-endian 32位整数
    } topology;

    u8 writeback;  // 写回缓存标志
    u8 unused0[3]; // 未使用的字段

    le32 max_discard_sectors;        // 最大丢弃(trim)扇区数,Little-endian 32位整数
    le32 max_discard_seg;            // 最大丢弃分段数,Little-endian 32位整数
    le32 discard_sector_alignment;   // 丢弃(trim)扇区对齐要求,Little-endian 32位整数
    le32 max_write_zeroes_sectors;   // 最大写零扇区数,Little-endian 32位整数
    le32 max_write_zeroes_seg;       // 最大写零分段数,Little-endian 32位整数
    u8 write_zeroes_may_unmap;       // 写零操作可能会执行解除映射(unmap)操作标志
    u8 unused1[3];                   // 未使用的字段
};

5.2.4.1 Legacy接口:设备配置布局

在使用传统接口时,过渡设备和驱动程序必须根据来宾的本机字节顺序(与传统接口不使用时一样)格式化struct virtio_blk_config中的字段。

5.2.5 设备初始化

  1. 设备的大小可以从capacity中读取。
  2. 如果已协商VIRTIO_BLK_F_BLK_SIZE特性位,则可以从blk_size中读取以确定驱动程序要使用的最佳扇区大小。这不会影响协议中使用的单位(始终为512字节),但正确值的意识可以影响性能。
  3. 如果设备设置了VIRTIO_BLK_F_RO特性位,任何写请求都将失败。
  4. 如果已协商VIRTIO_BLK_F_TOPOLOGY特性位,则可以从拓扑结构中的字段中读取以确定驱动程序要使用的物理块大小和最佳I/O长度。这也不会影响协议中的单位,只会影响性能。
  5. 如果已协商VIRTIO_BLK_F_CONFIG_WCE特性位,则可以通过writeback字段读取或设置缓存模式。0对应于写透模式,1对应于写回模式。复位后的缓存模式可以是写回模式或写透模式。可以通过在特性协商后读取writeback来确定实际模式。
  6. 如果已协商VIRTIO_BLK_F_DISCARD特性位,则可以从max_discard_sectors和max_discard_seg中读取以确定块驱动程序要使用的最大丢弃扇区数和最大丢弃段数。discard_sector_alignment可以用于在基于对齐方式拆分请求时供操作系统使用。
  7. 如果已协商VIRTIO_BLK_F_WRITE_ZEROES特性位,则可以从max_write_zeroes_sectors和max_write_zeroes_seg中读取以确定块驱动程序要使用的最大写零扇区数和最大写零段数。

5.2.5.1 驱动程序要求:设备初始化

如果驱动程序无法发送VIRTIO_BLK_T_FLUSH命令,则不应协商VIRTIO_BLK_F_FLUSH。
如果未协商VIRTIO_BLK_F_CONFIG_WCE或VIRTIO_BLK_F_FLUSH,则驱动程序可以推断写透缓存的存在。如果未协商VIRTIO_BLK_F_CONFIG_WCE但已协商VIRTIO_BLK_F_FLUSH,则驱动程序应假定存在写回缓存。
驱动程序在设置FEATURES_OK设备状态位之前,不能读取writeback。

5.2.5.2 设备要求:设备初始化

设备应始终提供VIRTIO_BLK_F_FLUSH,如果提供VIRTIO_BLK_F_CONFIG_WCE,则必须提供它。
如果已协商VIRTIO_BLK_F_CONFIG_WCE但未协商VIRTIO_BLK_F_FLUSH,则设备必须将writeback初始化为0。
设备必须将未使用的填充字节unused0和unused1初始化为0。

5.2.5.3 Legacy接口:设备初始化

因为传统设备没有FEATURES_OK,所以在使用传统接口时,过渡设备在特性协商周围的行为略有不同。特别是,在使用传统接口时:

  • 在设置DRIVER或DRIVER_OK设备状态位之前,驱动程序可以读取或写入writeback。
  • 除非正在设置DRIVER_OK位并且驱动程序没有设置VIRTIO_BLK_F_CONFIG_WCE驱动程序特性位,否则设备不能修改缓存模式(和writeback)作为驱动程序设置状态位的结果。
  • 设备不能修改缓存模式(和writeback)作为驱动程序修改驱动程序特性位的结果,例如,如果驱动程序设置了VIRTIO_BLK_F_CONFIG_WCE驱动程序特性位但没有设置VIRTIO_BLK_F_FLUSH位。

5.2.6 设备操作

驱动程序将请求排入virtqueue,并由设备使用(不一定按顺序)。每个请求的形式如下:

struct virtio_blk_req {
    le32 type;     // 请求类型,Little-endian 32位整数
    le32 reserved; // 保留字段,Little-endian 32位整数
    le64 sector;   // 扇区号,Little-endian 64位整数
    u8 data[];     // 数据数组,用于存储请求的数据
    u8 status;     // 请求的状态信息
};

请求的类型可以是读取(VIRTIO_BLK_T_IN)、写入(VIRTIO_BLK_T_OUT)、丢弃(VIRTIO_BLK_T_DISCARD)、写零(VIRTIO_BLK_T_WRITE_ZEROES)或刷新(VIRTIO_BLK_T_FLUSH)之一。

// 定义 Virtio 块设备请求类型
#define VIRTIO_BLK_T_IN           0   // 读取请求
#define VIRTIO_BLK_T_OUT          1   // 写入请求
#define VIRTIO_BLK_T_FLUSH        4   // 刷新请求
#define VIRTIO_BLK_T_DISCARD      11  // 丢弃(trim)请求
#define VIRTIO_BLK_T_WRITE_ZEROES 13  // 写零请求

扇区号指示读取或写入将发生的偏移量(乘以512)。对于除读取或写入之外的命令,此字段未使用并设置为0。
VIRTIO_BLK_T_IN请求使用数据填充,其中包含从块设备读取的扇区内容(以512字节的倍数)。VIRTIO_BLK_T_OUT请求将数据的内容写入块设备(以512字节的倍数)。
用于丢弃或写零命令的数据由一个或多个段组成。丢弃命令的最大段数为max_discard_seg,写零命令的最大段数为max_write_zeroes_seg。每个段的形式如下:

struct virtio_blk_discard_write_zeroes {
    le64 sector;      // 起始扇区号,Little-endian 64位整数
    le32 num_sectors; // 扇区数量,Little-endian 32位整数

    // 请求标志位
    struct {
        le32 unmap:1;     // 解除映射标志位,Little-endian 32位整数中的最低位
        le32 reserved:31; // 保留字段,Little-endian 32位整数中的高31位
    } flags;
};

sector表示段的起始偏移量(以512字节为单位),而num_sectors表示每个丢弃范围中的扇区数。unmap仅在写零命令中使用,它允许设备丢弃指定的范围,前提是随后的读取将返回零。
最终的状态字节由设备编写:VIRTIO_BLK_S_OK表示成功,VIRTIO_BLK_S_IOERR表示设备或驱动程序错误,VIRTIO_BLK_S_UNSUPP表示设备不支持的请求:

// 定义 Virtio 块设备请求的不同状态
#define VIRTIO_BLK_S_OK      0  // 请求执行成功
#define VIRTIO_BLK_S_IOERR   1  // 请求执行出错
#define VIRTIO_BLK_S_UNSUPP  2  // 不支持的请求类型或操作

#个别段的状态
当丢弃或写零命令导致 VIR-TIO_BLK_S_IOERR 时,个别段的状态是不确定的。一个段可能已经成功完成,失败,或者设备未对其进行处理。

5.2.6.1 驱动程序要求:设备操作

  • 驱动程序绝不能提交会导致读取或写入超出容量的请求。
  • 驱动程序应该在提供时接受 VIRTIO_BLK_F_RO 特性。
  • 驱动程序必须在 VIRTIO_BLK_T_FLUSH 请求中将扇区设置为 0。驱动程序不应该在 VIRTIO_BLK_T_FLUSH 请求中包含任何数据。
  • 对于 VIRTIO_BLK_T_IN 和 VIRTIO_BLK_T_OUT 请求,数据的长度必须是 512 字节的倍数。
  • 对于 VIRTIO_BLK_T_DISCARD 和 VIRTIO_BLK_T_WRITE_ZEROES 请求,数据的长度必须是 struct virtio_blk_discard_write_zeroes 的大小的倍数。
  • VIRTIO_BLK_T_DISCARD 请求在数据中不能包含超过 max_discard_seg 个 struct virtio_blk_discard_write_zeroes 段。
  • VIRTIO_BLK_T_WRITE_ZEROES 请求在数据中不能包含超过 max_write_zeroes_seg 个 struct virtio_blk_discard_write_zeroes 段。
  • 如果已协商 VIRTIO_BLK_F_CONFIG_WCE 特性,则驱动程序可以通过分别将 0 和 1 写入 writeback 字段来切换到写透和写回模式。在写入 0 到 writeback 后,驱动程序绝不能假设已将任何易失性写入提交到持久设备后端存储。
  • 丢弃命令的 unmap 位必须为零。驱动程序在一系列扇区已被丢弃后不应假设读取请求返回的数据的任何内容。
  • 如果请求失败并显示 VIRTIO_BLK_S_IOERR,则驱动程序不得假设多段 VIRTIO_BLK_T_DISCARD 或 VIRTIO_BLK_T_WRITE_ZEROES 请求的各个段已成功完成,失败或者设备是否对其进行了处理。

5.2.6.2 设备要求:设备操作

  • 如果提供了 VIRTIO_BLK_F_RO 特性,则设备必须在写请求时将状态字节设置为 VIRTIO_BLK_S_IOERR,并且不得写入任何数据。
  • 如果设置了任何未知标志,则设备必须在丢弃和写零命令时将状态字节设置为 VIRTIO_BLK_S_UNSUPP。此外,如果 unmap 标志被设置,设备必须在丢弃命令时将状态字节设置为 VIRTIO_BLK_S_UNSUPP。
  • 对于丢弃命令,设备可以在设备后端存储中释放指定范围的扇区。
  • 对于写零命令,如果设置了 unmap,则设备可以在设备后端存储中释放指定范围的扇区,就像发送了丢弃命令一样。在写零命令完成后,指定范围的扇区必须返回零值。这是独立于 unmap 是否被设置的情况。
  • 如果写零请求不能导致释放一个或多个扇区,则设备必须根据情况清除 virtio 配置空间的 write_zeroes_may_unmap 字段。设备可以在设备运行过程中更改该字段的内容;发生这种情况时,设备必须触发配置更改通知。
  • 在提交时,写被视为易失性。在写完成并满足以下一个或多个条件之一时,写被视为稳定:
    1. 未协商 VIRTIO_BLK_F_CONFIG_WCE 特性和 VIRTIO_BLK_F_FLUSH 特性,但设备提供了 VIRTIO_BLK_F_FLUSH;
    2. 已协商 VIRTIO_BLK_F_CONFIG_WCE 特性,并且在提交写操作和其完成之间的所有时间内,配置空间中的 writeback 字段始终为 0;
    3. 在写操作完成后发送了 VIRTIO_BLK_T_FLUSH 请求,并且该请求本身也已完成。
  • 如果设备由持久性存储支持,则设备必须确保在报告写操作完成(情况 1 和 2)或刷新操作完成(情况 3)之前,已稳定的写入已提交给设备。否则,在崩溃时可能会导致数据丢失。
  • 如果驱动程序在写操作提交和其完成之间更改 writeback,则在报告写操作完成时,写操作可能是易失性的或稳定的;换句话说,确切的行为是未定义的。
  • 如果设备未提供 VIRTIO_BLK_F_FLUSH(第 5 部分),则设备也可以在报告其完成之前将写入提交到持久性设备后端存储。但与情况 1 不同,这不是规范的绝对要求。
    注意:不提供 VIRTIO_BLK_F_FLUSH 并且不提交已完成写入的实现将在崩溃时对数据丢失不具备弹性。不提供 VIRTIO_BLK_F_FLUSH 对于不希望安全防范此类数据丢失的实现来说并不是绝对要求。

5.2.6.3 传统接口:设备操作

使用传统接口时,过渡设备和驱动程序必须根据客户机的本机字节序而不是小端字节序(如果不使用传统接口的话)来格式化 struct virtio_blk_req 中的字段。
在使用传统接口时,过渡驱动程序应忽略已使用的长度值。
注意:历史上,一些设备在实际只写入状态字节时,即使只实际写入状态字节,也将总描述符长度或可写入设备缓冲区的总长度放在这里。保留字段以前称为 ioprio。ioprio 是关于请求到设备的相对优先级的提示:较高的数字表示更重要的请求。

// 定义 Virtio 块设备请求类型
#define VIRTIO_BLK_T_FLUSH_OUT  5  // 刷新并写入请求

命令 VIRTIO_BLK_T_FLUSH_OUT 是 VIRTIO_BLK_T_FLUSH 的同义词;驱动程序必须将其视为 VIRTIO_BLK_T_FLUSH 命令。

// 定义 Virtio 块设备请求类型的特殊标志位
#define VIRTIO_BLK_T_BARRIER  0x80000000

如果设备具有 VIRTIO_BLK_F_BARRIER 特性,则高位(VIRTIO_BLK_T_BARRIER)表示此请求充当障碍,要求在此之前的所有请求应该已经完成,而且在此之后的所有请求不应该开始直到此请求完成。
注意:障碍不会刷新主机中底层后端设备的缓存,因此不会作为数据一致性保证。只有 VIRTIO_BLK_T_FLUSH 请求才能实现这一点。
一些较旧的传统设备在提供但未协商 VIRTIO_BLK_F_FLUSH 时未提交已完成的写入到持久性设备后端存储。为了解决这个问题,驱动程序可以将 writeback 设置为 0(如果可用),或者可以在每次完成写入后发送显式的刷新请求。
如果设备具有 VIRTIO_BLK_F_SCSI 特性,它还可以支持 scsi 包命令请求,每个这样的请求的形式如下:
(接下来的内容请提供原文,以便进行翻译。)

struct virtio_scsi_pc_req {
    u32 type;         // 命令类型
    u32 ioprio;       // I/O 优先级
    u64 sector;       // 扇区号
    u8 cmd[];         // 命令数据
    u8 data[][512];   // 数据块数组
    #define SCSI_SENSE_BUFFERSIZE 96
    u8 sense[SCSI_SENSE_BUFFERSIZE]; // SCSI 识别信息缓冲区
    u32 errors;       // 错误信息
    u32 data_len;     // 数据长度
    u32 sense_len;    // 识别信息长度
    u32 residual;     // 剩余数据长度
    u8 status;        // 命令执行状态
};

请求类型还可以是 SCSI 包命令(VIRTIO_BLK_T_SCSI_CMD 或 VIRTIO_BLK_T_SCSI_CMD_OUT)。这两种类型是等效的,设备不区分它们:

// 定义 Virtio 块设备请求类型中的 SCSI 命令类型
#define VIRTIO_BLK_T_SCSI_CMD     2  // SCSI 命令请求
#define VIRTIO_BLK_T_SCSI_CMD_OUT 3  // 输出 SCSI 命令请求

cmd 字段仅适用于 SCSI 包命令请求,并指示要执行的命令。此字段必须位于单独的、可供设备读取的缓冲区中;命令长度可以从此缓冲区的长度派生出来。
请注意,这前三个字段(对于 SCSI 包命令来说是前四个字段)始终可供设备读取:数据要么可供设备读取,要么可供设备写入,具体取决于请求。读取或写入的大小可以从请求缓冲区的总大小派生出来。
sense 仅适用于 SCSI 包命令请求,并指示用于 SCSI 感知数据的缓冲区。
data_len 仅适用于 SCSI 包命令请求,此字段已不推荐使用,驱动程序应忽略它。在历史上,设备在此字段中复制数据长度。
sense_len 仅适用于 SCSI 包命令请求,指示实际写入感知缓冲区的字节数。
residual 字段仅适用于 SCSI 包命令请求,并指示残余大小,计算方式为数据长度 - 实际传输的字节数。

5.2.6.4 传统接口:帧格式要求

在使用传统接口时,尚未协商 VIRTIO_F_ANY_LAYOUT 的过渡驱动程序必须:

  • 必须使用一个单独的 8 字节描述符,包含类型、保留和扇区字段,然后是数据的描述符,最后是一个单独的 1 字节描述符用于状态。
  • 对于 SCSI 命令,还有其他约束。sense 必须位于一个单独的、96 字节大小的设备可写描述符中,而 errors、data_len、sense_len 和 residual 必须位于一个单独的、可写的设备描述符中。
    请参阅2.6.4。

5.3 控制台设备

virtio 控制台设备是用于数据输入和输出的简单设备。一个设备可以拥有一个或多个端口。每个端口都有一对输入和输出 virtqueue。此外,设备还有一对控制 IO virtqueue。控制 virtqueue 用于设备和驱动程序之间的信息通信,关于连接的两侧打开和关闭的端口,设备是否为特定端口是控制台端口,添加新端口,端口热插拔等,以及驱动程序关于是否成功添加了端口或设备,端口打开/关闭等。
对于数据 IO,将一个或多个空缓冲区放置在接收队列中,用于接收传入数据,而传出字符则放置在传输队列中。

5.3.1 设备 ID

3

5.3.2 Virtqueues

0 接收队列(端口0)
1 传输队列(端口0)
2 控制接收队列
3 控制传输队列
4 接收队列(端口1) 5 传输队列(端口1)

端口0的接收和传输队列始终存在:其他队列只在设置 VIRTIO_CONSOLE_F_MULTIPORT 时存在。

5.3.3 特性位

VIRTIO_CONSOLE_F_SIZE(0)配置的列数和行数有效。
VIRTIO_CONSOLE_F_MULTIPORT(1)设备支持多个端口;max_nr_ports 有效,并且将使用控制 virtqueue。
VIRTIO_CONSOLE_F_EMERG_WRITE(2)设备支持紧急写入。配置字段 emerg_wr 有效。

5.3.4 设备配置布局

如果设置了 VIRTIO_CONSOLE_F_SIZE 特性,则在配置空间中提供了控制台的大小。此外,如果设置了 VIRTIO_CONSOLE_F_MULTIPORT 特性,则可以获取设备支持的最大端口数量。
如果设置了 VIRTIO_CONSOLE_F_EMERG_WRITE,则驱动程序可以使用紧急写入来输出单个字符,而无需初始化 virtio 队列,甚至无需确认特性。

struct virtio_console_config {
    le16 cols;           // 控制台列数
    le16 rows;           // 控制台行数
    le32 max_nr_ports;   // 最大端口数
    le32 emerg_wr;       // 紧急写入缓冲区大小
};

5.3.4.1 传统接口:设备配置布局

在使用传统接口时,过渡设备和驱动程序必须按照宿主系统的本机字节顺序而不是小端字节顺序(当不使用传统接口时可能是小端字节顺序)来格式化 struct virtio_console_config 中的字段。

5.3.5 设备初始化

  1. 如果提供了 VIRTIO_CONSOLE_F_EMERG_WRITE 特性,则随时可以写入配置的 emerg_wr 字段。因此,它适用于非常早期的引导调试输出以及灾难性的操作系统故障(例如 virtio 环形队列损坏)。
  2. 如果已协商 VIRTIO_CONSOLE_F_SIZE 特性,则驱动程序可以从 cols 和 rows 中读取控制台的尺寸。
  3. 如果已协商 VIRTIO_CONSOLE_F_MULTIPORT 特性,则驱动程序可以生成多个端口,其中不一定都连接到控制台。某些可能是通用端口。在这种情况下,启用控制 virtqueues,并根据 max_nr_ports 创建适当数量的 virtqueues。发送一个指示驱动程序准备就绪的控制消息给设备。然后,设备可以发送控制消息来添加新的端口到设备中。在创建和初始化每个端口之后,将发送一个 VIRTIO_CONSOLE_PORT_READY 控制消息给该端口的设备,以便设备可以通知驱动程序有关为该端口设置的任何其他配置选项。
  4. 每个端口的 receiveq 将被填充为一个或多个接收缓冲区。

5.3.5.1 设备要求:设备初始化

设备必须允许对 emerg_wr 的写入,即使在未配置的设备上也是如此。
设备应该将写入 emerg_wr 的较低字节传输到适当的日志或输出方法。

5.3.6 设备操作

  1. 对于输出,包含字符的缓冲区被放置在端口的 transmitq 中。
  2. 当在 receiveq 中使用缓冲区(通过已使用的缓冲区通知表示)时,内容将作为与收到通知的 virtqueue 关联的端口的输入。
  3. 如果驱动程序协商了 VIRTIO_CONSOLE_F_SIZE 特性,则配置更改通知指示可以从配置字段中读取更新后的尺寸。此尺寸仅适用于端口 0。
  4. 如果驱动程序协商了 VIRTIO_CONSOLE_F_MULTIPORT 特性,则设备将使用 VIRTIO_CONSOLE_PORT_ADD 控制消息宣布活动端口。相同的消息也用于热插拔端口。

5.3.6.1 驱动程序要求:设备操作

驱动程序不得将可供设备读取的内容放入 receiveq 中。驱动程序不得将可供设备写入的缓冲区放入 transmitq 中。

5.3.6.2 多端口设备操作

如果驱动程序协商了 VIRTIO_CONSOLE_F_MULTIPORT 特性,则两个控制队列用于操作不同的控制台端口:控制接收队列用于从设备到驱动程序的消息,控制发送队列用于从驱动程序到设备的消息。控制消息的布局如下:

struct virtio_console_control {
    le32 id;     /* 端口号 */
    le16 event;  /* 控制事件类型 */
    le16 value;  /* 事件的附加信息 */
};


```c
在这里插入代码片

事件的值为:

  • VIRTIO_CONSOLE_DEVICE_READY (0):由驱动程序在初始化时发送,表示准备好接收控制消息。值为 1 表示成功,值为 0 表示失败。端口号 id 未使用。
  • VIRTIO_CONSOLE_DEVICE_ADD (1):由设备发送,用于创建新端口。值未使用。
  • VIRTIO_CONSOLE_DEVICE_REMOVE (2):由设备发送,用于删除现有端口。值未使用。
  • VIRTIO_CONSOLE_PORT_READY (3):由驱动程序响应设备的 VIRTIO_CONSOLE_PORT_ADD 消息而发送,表示该端口已准备就绪。值为 1 表示成功,值为 0 表示失败。
  • VIRTIO_CONSOLE_CONSOLE_PORT (4):由设备发送,指定一个端口作为控制台端口。可以存在多个控制台端口。
  • VIRTIO_CONSOLE_RESIZE (5):由设备发送,指示控制台尺寸更改。值未使用。
    缓冲区后跟列数和行数的数量:

``

struct virtio_console_resize {
    le16 cols;  /* 控制台列数 */
    le16 rows;  /* 控制台行数 */
};

  • VIRTIO_CONSOLE_PORT_OPEN (6):此消息由设备和驱动程序都发送。value 表示状态:0(端口关闭)或 1(端口打开)。这允许端口被直接用于客户机和主机进程以应用程序定义的方式进行通信。
  • VIRTIO_CONSOLE_PORT_NAME (7):由设备发送,为端口分配一个标签。此控制命令紧接着是端口的 UTF-8 名称,用于在客户机内进行标识(不包含空字符终止符)。
5.3.6.2.1 设备要求:多端口设备操作
  • 设备不得指定在 VIRTIO_CONSOLE_DEVICE_ADD 消息中存在的端口,也不得指定等于或大于 max_nr_ports 的端口。
  • 设备不得在 VIRTIO_CONSOLE_DEVICE_REMOVE 中指定未经过前一个 VIRTIO_CONSOLE_DEVICE_ADD 创建的端口。
5.3.6.2.2 驱动程序要求:多端口设备操作
  • 如果已经协商了 VIRTIO_CONSOLE_F_MULTIPORT,则驱动程序必须发送一个 VIRTIO_CONSOLE_DEVICE_READY 消息。
  • 在接收到 VIRTIO_CONSOLE_CONSOLE_PORT 消息时,驱动程序应该以适合文本控制台访问的方式处理端口,并且必须以 VIRTIO_CONSOLE_PORT_OPEN 消息进行响应,该消息的 value 必须设置为 1。

5.3.6.3 遗留接口:设备操作

  • 在使用遗留接口时,过渡设备和驱动程序必须按照客户机的本机字节序格式化 struct virtio_console_control 中的字段,而不是小端格式(除非不使用遗留接口)。
  • 在使用遗留接口时,驱动程序应该忽略传输队列和控制传输队列的已使用长度值。
  • 注:从历史上看,有些设备将总描述符长度放在那里,尽管实际上没有写入任何数据。

5.3.6.4 遗留接口:帧格式要求

  • 在使用遗留接口时,尚未协商 VIRTIO_F_ANY_LAYOUT 的过渡驱动程序必须仅使用单个描述符来处理控制接收队列和控制传输队列中的所有缓冲区。

5.4 熵设备

virtio熵设备为客户机提供高质量的随机性以供使用。

5.4.1 设备ID

4

5.4.2 Virtqueues

0 请求队列

5.4.3 特性位

目前没有定义

5.4.4 设备配置布局

目前没有定义

5.4.5 设备初始化

  1. 初始化虚拟队列

5.4.6 设备操作

当驱动程序需要随机字节时,它将一个或多个缓冲区的描述符放入队列中。设备会使用随机数据完全填充它。

5.4.6.1 驱动程序要求:设备操作

  • 驱动程序不得将驱动程序可读的缓冲区放入队列中。
  • 驱动程序必须检查设备写入的长度,以确定接收到了多少个随机字节。

5.4.6.2 设备要求:设备操作

  • 设备必须将一个或多个随机字节放入缓冲区,但可以使用小于整个缓冲区长度的字节。

5.5 传统内存气球设备

这是传统的气球设备。设备号13保留给了新的内存气球接口,其语义不同,预计将在标准的未来版本中出现。
传统virtio内存气球设备是管理客户机内存的基本设备:设备请求一定量的内存,驱动程序提供它(或者如果设备比它请求的内存多,则撤回它)。这允许客户机适应底层物理内存的允许变化。如果协商了该功能,设备还可以用于向主机传达客户机内存统计信息。

5.5.1 设备ID

5

5.5.2 Virtqueues

0 增加队列
1 释放队列
2 统计队列。
只有在设置了 VIRTIO_BALLON_F_STATS_VQ 时才存在 Virtqueue 2。

5.5.3 特性位

  • VIRTIO_BALLOON_F_MUST_TELL_HOST (0):必须在使用气球的页面之前通知主机。
  • VIRTIO_BALLOON_F_STATS_VQ (1):存在用于报告客户机内存统计信息的虚拟队列。
  • VIRTIO_BALLOON_F_DEFLATE_ON_OOM (2):在客户机内存不足的情况下减小气球。

5.5.3.1 驱动程序要求:特性位

  • 如果设备提供了 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性位,则驱动程序应接受它。

5.5.3.2 设备要求:特性位

  • 如果设备提供了 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性位,且驱动程序未接受此特性位,则设备可以通过在驱动程序写入 FEATURES_OK 设备状态位时未设置来发出失败信号。

5.5.3.2.0.1 遗留接口:特性位

由于遗留接口没有优雅地报告特性协商失败的方式,因此在使用遗留接口时,过渡设备必须支持不协商 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性的客户机,并且如果未协商 VIRTIO_BALLOON_F_MUST_TELL_HOST,则应允许客户机在通知主机之前使用内存。

5.5.4 设备配置布局

该配置的两个字段始终可用。5.5.4.0.0.1 遗留接口:设备配置布局:使用遗留接口时,过渡设备和驱动程序必须按照小端格式格式化 struct virtio_balloon_config 中的字段。注意:这与通常的惯例不同,通常情况下遗留设备字段是客户端字节序。

struct virtio_balloon_config {
    le32 num_pages;  // 分配的内存页数
    le32 actual;     // 实际分配的内存页数
};

5.5.5 设备初始化

设备初始化过程如下:

  1. 识别 inflate 和 deflate 虚拟队列。
  2. 如果已协商了 VIRTIO_BALLOON_F_STATS_VQ 特性位:
    (a) 识别统计虚拟队列。
    (b) 向统计虚拟队列添加一个空缓冲区。
    © 设置 DRIVER_OK:设备操作开始。
    (d) 通知设备有关统计虚拟队列缓冲区的信息。

5.5.6 设备操作

设备驱动通过接收配置更改通知或更改客户机内存需求来驱动设备,例如执行内存压缩或响应内存不足的情况。

  1. 检查 num_pages 配置字段。如果它大于实际页面数,则气球需要更多来自客户机的内存。如果小于实际值,则气球不需要全部。
  2. 为了提供内存给气球(即增加内存):
    (a) 驱动程序构建一个未使用内存页面地址的数组。这些地址除以 4096 并将描述符添加到 inflateq 中,以描述生成的 32 位数组。
  3. 从气球中移除内存(即减小内存):
    (a) 驱动程序构建一个描述之前提供给气球的内存页面地址的数组,如上所述。此描述符添加到 deflateq 中。
    (b) 如果已协商了 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性,则客户机在使用这些页面之前通知设备。
    © 否则,客户机可以在设备确认其撤回之前重用之前提供给气球的页面。
  4. 在任何情况下,设备通过使用描述符来确认增加或减小请求。
  5. 一旦设备确认了膨胀或紧缩,驱动程序就会更新实际值,以反映气球中的新页面数。

5.5.6.1 驱动程序要求:设备操作

  • 当 num_pages 大于气球中的实际页面数时,驱动程序应向气球提供页面。
  • 当 num_pages 小于气球中的实际页面数时,驱动程序可以使用气球中的页面。
  • 当 num_pages 大于或等于气球中的实际页面数时,驱动程序可以向气球提供页面。
  • 如果未协商 VIRTIO_BALLOON_F_DEFLATE_ON_OOM 特性,则当 num_pages 小于或等于气球中的实际页面数时,驱动程序不得使用气球中的页面。
  • 如果已协商 VIRTIO_BALLOON_F_DEFLATE_ON_OOM 特性,并且出于系统稳定性的需要(例如,应用程序在客户机内运行需要内存)需要在 num_pages 小于或等于气球中的实际页面数时使用气球中的页面,驱动程序必须使用 deflateq 向设备通知要使用的页面。
  • 如果已协商 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性,驱动程序不得在设备确认紧缩请求之前使用气球中的页面。
  • 否则,如果未协商 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性,驱动程序可以在设备确认紧缩请求之前开始重用之前提供给气球的页面。
  • 在任何情况下,驱动程序在更改气球中的页面数量后必须更新实际值。

5.5.6.2 设备要求:设备操作

如果设备检测到膨胀请求中的页面的物理编号并在使用 inflateq 描述符确认膨胀请求之前,设备可以修改气球中页面的内容。
如果已协商了 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性,则设备可以在检测到膨胀请求中的页面的物理编号并在使用膨胀请求的 inflateq 描述符确认膨胀请求之前,以及在检测到紧缩请求中的页面的物理编号并在确认紧缩请求之前修改气球中页面的内容。

5.5.6.2.1 遗留接口:设备操作

在使用遗留接口时,驱动程序应忽略已使用长度值。
注意:从历史上看,某些设备将总描述符长度放在那里,尽管实际上没有写入任何数据。
在使用遗留接口时,每次在配置空间中更新实际值时,驱动程序必须使用单个原子操作写出所有 4 个字节。
在使用遗留接口时,设备不应在驱动程序在配置空间中写入实际值的最后一个最重要的字节之前使用该值。
注意:从历史上看,设备使用实际值,尽管在使用 Virtio Over PCI Bus 时,不能保证设备特定的配置空间是原子的。在驱动程序更新过程中使用中间值最好避免,除非用于调试。
历史上,使用 Virtio Over PCI Bus 的驱动程序通过按从最不重要的值到最重要的值的顺序使用多个单字节写操作来写入实际值。

5.5.6.3 内存统计

统计 virtqueue 不同寻常的地方在于通信是由设备(而不是驱动程序)驱动的。该通道在驱动程序初始化时变为活动状态,当驱动程序添加一个空缓冲区并通知设备时。内存统计的请求如下进行:

  1. 设备使用缓冲区并发送已使用的缓冲区通知。
  2. 驱动程序弹出已使用的缓冲区并将其丢弃。
  3. 驱动程序收集内存统计信息并将其写入新的缓冲区。
  4. 驱动程序将缓冲区添加到 virtqueue 并通知设备。
  5. 设备弹出缓冲区(保留以启动后续请求)并消耗统计信息。在缓冲区内,统计信息是 6 字节条目的数组。每个统计信息由 16 位标签和 64 位值组成。所有统计信息都是可选的,驱动程序可以选择提供哪些统计信息。为了确保向后兼容性,设备会省略不支持的统计信息。
/* 
   virtio_balloon_stat 结构体用于表示 Virtio Balloon 设备的不同统计信息。
   这些统计信息可用于监控 Virtio Balloon 设备的性能和内存管理情况。
*/

struct virtio_balloon_stat {
    le16 tag;    // 统计信息标签,标识不同类型的统计数据
    le64 val;    // 统计信息值,存储相关统计数据的具体数值
} __attribute__((packed));

/*
   下面是一些可能出现在 tag 字段中的统计信息标签,每个标签对应不同的统计数据类型:
   VIRTIO_BALLOON_S_SWAP_IN - 交换入的次数
   VIRTIO_BALLOON_S_SWAP_OUT - 交换出的次数
   VIRTIO_BALLOON_S_MAJFLT - 主要页错误的次数
   VIRTIO_BALLOON_S_MINFLT - 次要页错误的次数
   VIRTIO_BALLOON_S_MEMFREE - 空闲内存的大小
   VIRTIO_BALLOON_S_MEMTOT - 总内存的大小
   VIRTIO_BALLOON_S_AVAIL - 可用内存的大小
   VIRTIO_BALLOON_S_CACHES - 缓存的内存大小
   VIRTIO_BALLOON_S_HTLB_PGALLOC - 为大页分配的页表项数量
   VIRTIO_BALLOON_S_HTLB_PGFAIL - 分配大页页表项失败的次数
*/

5.5.6.3.1 驱动程序要求:内存统计

本节中的规范性语句仅在已协商 VIRTIO_BALLOON_F_STATS_VQ 特性的情况下适用。
驱动程序在 statsq 中最多一次向设备提供一个缓冲区。
在初始化设备后,驱动程序必须在 statsq 中提供一个输出缓冲区。
在检测到设备已使用 statsq 中的缓冲区之前,驱动程序必须在 statsq 中提供一个输出缓冲区。
在 statsq 中提供输出缓冲区之前,驱动程序必须对其进行初始化,包括为其支持的每个统计信息提供一个 struct virtio_balloon_stat 条目。
驱动程序必须对提交给 statsq 的所有缓冲区使用多个 6 字节的输出缓冲区大小。
驱动程序可以以任何顺序在提交给 statsq 的输出缓冲区中提供 struct virtio_balloon_stat 条目,而不考虑标记值。
驱动程序可以在提交给 statsq 的输出缓冲区中提供所有统计信息的子集。
驱动程序必须在提交给 statsq 的所有缓冲区中提供相同的统计信息子集。

5.5.6.3.2 设备要求:内存统计

本节中的规范性语句仅在已协商 VIRTIO_BALLOON_F_STATS_VQ 特性的情况下适用。
在提交给 statsq 的输出缓冲区内,设备必须忽略其不识别的标记值的条目。
在提交给 statsq 的输出缓冲区内,设备必须接受 struct virtio_balloon_stat 条目,而不考虑标记值。

5.5.6.3.3 遗留接口:内存统计

在使用遗留接口时,过渡设备和驱动程序必须根据客户机的本机字节顺序格式化 struct virtio_balloon_stat 中的字段,而不是(在不使用遗留接口时)按小尾数顺序。
在使用遗留接口时,驱动程序在设备初始化后在 statsq 提供的第一个缓冲区中忽略所有值。注意:从历史上看,驱动程序在第一个缓冲区中提供了一个未初始化的缓冲区。

5.5.6.4 内存统计标签

VIRTIO_BALLOON_S_SWAP_IN(0)已交换的内存量(以字节为单位)。
VIRTIO_BALLOON_S_SWAP_OUT(1)已交换到磁盘的内存量(以字节为单位)。
VIRTIO_BALLOON_S_MAJFLT(2)已发生的主要页面错误次数。
VIRTIO_BALLOON_S_MINFLT(3)已发生的次要页面错误次数。
VIRTIO_BALLOON_S_MEMFREE(4)未用于任何目的的内存量(以字节为单位)。
VIRTIO_BALLOON_S_MEMTOT(5)可用内存的总量(以字节为单位)。
VIRTIO_BALLOON_S_AVAIL(6)可用于启动新应用程序而不会推动系统进行交换的内存的估算量(以字节为单位)。
VIRTIO_BALLOON_S_CACHES(7)可以快速回收而无需额外 I/O 的内存量,通常用于从磁盘缓存文件。
VIRTIO_BALLOON_S_HTLB_PGALLOC(8)客户端成功的巨型页面分配次数。
VIRTIO_BALLOON_S_HTLB_PGFAIL(9)客户端失败的巨型页面分配次数。

5.6 SCSI 主机设备

virtio SCSI 主机设备将一个或多个虚拟逻辑单元(例如磁盘)分组在一起,允许使用 SCSI 协议与它们通信。设备的一个实例表示一个 SCSI 主机,附加了许多目标和 LUN。
virtio SCSI 设备提供两种类型的请求:
• 逻辑单元的命令请求;
• 与逻辑单元、目标或命令相关的任务管理功能。
设备还能够发送关于添加和删除逻辑单元的通知。这些功能共同提供了一种使用 virtqueue 作为传输介质的 SCSI 传输协议。在传输协议中,virtio 驱动程序充当发起者,而 virtio SCSI 主机提供一个或多个接收和处理请求的目标。
本节依赖于 SAM 的定义。

5.6.1 设备 ID

8

5.6.2 Virtqueues

0 控制队列
1 事件队列
2…n 请求队列

5.6.3 特性位

VIRTIO_SCSI_F_INOUT(0)一个单一的请求可以包含既可由设备读取又可由设备写入的数据缓冲区。
VIRTIO_SCSI_F_HOTPLUG(1)主机应该启用报告 SCSI 总线上的 LUN 和目标的热插拔事件。客户机应该处理热插拔事件。
VIRTIO_SCSI_F_CHANGE(2)主机将通过 VIRTIO_SCSI_T_PARAM_CHANGE 事件报告 LUN 参数的更改;客户机应该处理这些更改。
VIRTIO_SCSI_F_T10_PI(3)T10 保护信息(DIF/DIX)的扩展字段包含在 SCSI 请求头中。

5.6.4 设备配置布局

此配置的所有字段始终可用。num_queues 是设备公开的请求 virtqueue 的总数。驱动程序可以只使用一个请求队列,也可以使用多个以获得更好的性能。

struct virtio_scsi_config {
    le32 num_queues;        // 支持的队列数目
    le32 seg_max;           // 每个请求的最大段数
    le32 max_sectors;       // 每个请求的最大扇区数
    le32 cmd_per_lun;       // 每个逻辑单元 (LUN) 支持的最大命令数
    le32 event_info_size;   // 事件信息缓冲区的大小
    le32 sense_size;        // SENSE 数据缓冲区的大小
    le32 cdb_size;          // CDB (命令描述块) 缓冲区的大小
    le16 max_channel;       // 最大通道数
    le16 max_target;        // 每个通道支持的最大目标设备数
    le32 max_lun;           // 每个目标设备支持的最大逻辑单元数
};

seg_max 是命令中可能存在的最大段数。双向命令可以包括 seg_max 输入段和 seg_max 输出段。
max_sectors 是关于要使用的最大传输大小的提示。
cmd_per_lun 告诉驱动程序可以发送到一个 LUN 的最大关联命令数量。
event_info_size 是设备将填充的驱动程序放置在 eventq 中的缓冲区的最大大小。它由设备根据已协商的特性集写入。
sense_size 是设备将写入的感知数据的最大大小。默认值由设备写入并且必须为 96,但驱动程序可以修改它。在设备重置时,它将恢复到默认值。
cdb_size 是驱动程序将要写入的 CDB 的最大大小。默认值由设备写入并且必须为 32,但驱动程序也可以修改它。在设备重置时,它将恢复到默认值。
max_channel、max_target 和 max_lun 可以作为提示供驱动程序使用,以限制扫描主机上的逻辑单元到通道/目标/逻辑单元号小于或等于字段值的范围。max_channel 应该为零。max_target 应该小于或等于 255。max_lun 应该小于或等于 16383。

5.6.4.1 驱动程序要求:设备配置布局

驱动程序不得写入除 sense_size 和 cdb_size 之外的设备配置字段。
驱动程序不得向一个 LUN 发送超过 cmd_per_lun 个关联命令,并且不得向一个 LUN 发送超过 virtqueue 大小数量的关联命令。

5.6.4.2 设备要求:设备配置布局

在重置时,设备必须将 sense_size 设置为 96,cdb_size 设置为 32。

5.6.4.3 遗留接口:设备配置布局

在使用遗留接口时,过渡设备和驱动程序必须根据客户机的本机字节序而不是(不使用遗留接口时的必然)小端格式格式化 struct virtio_scsi_config 中的字段。

5.6.5 设备要求:设备初始化

在初始化时,驱动程序应首先发现设备的 virtqueue。
如果驱动程序使用 eventq,则驱动程序应在 eventq 中放置至少一个缓冲区。
驱动程序可以立即发出请求或任务管理函数。

5.6.6 设备操作

设备操作包括操作请求队列、控制队列和事件队列。

5.6.6.0.1 遗留接口:设备操作

在使用遗留接口时,驱动程序应忽略已使用的长度值。
注意:从历史上看,设备在那里放置了总描述符长度,或者设备可写缓冲区的总长度,即使实际写入的只是部分缓冲区。

5.6.6.1 设备操作:请求队列

驱动程序将请求排队到任意请求队列,并且它们将由同一队列上的设备使用。由于它们将无序使用,因此驱动程序有责任确保在不同队列上放置的命令的严格请求顺序。
请求具有以下格式:

struct virtio_scsi_req_cmd {
    // 设备可读部分
    u8 lun[8];               // 逻辑单元号 (LUN),表示目标设备上的逻辑单元
    le64 id;                 // 命令的唯一标识符
    u8 task_attr;            // 任务属性,指示命令的执行方式
    u8 prio;                 // 命令优先级
    u8 crn;                  // 命令请求标识
    u8 cdb[cdb_size];        // CDB (命令描述块) 数据,存储 SCSI 命令内容

    // 下面的三个字段仅在协商了 VIRTIO_SCSI_F_T10_PI 特性时存在
    le32 pi_bytesout;        // 输出 PI (Protection Information) 数据的字节数
    le32 pi_bytesin;         // 输入 PI 数据的字节数
    u8 pi_out[pi_bytesout];  // 输出 PI 数据
    u8 dataout[];            // 数据输出区域

    // 设备可写部分
    le32 sense_len;          // SENSE 数据的长度
    le32 residual;           // 剩余数据的长度
    le16 status_qualifier;   // 状态限定符
    u8 status;               // 命令执行状态
    u8 response;             // 命令响应

    u8 sense[sense_size];    // SENSE 数据,存储 SCSI 命令的状态信息

    // 下面的字段仅在协商了 VIRTIO_SCSI_F_T10_PI 特性时存在
    u8 pi_in[pi_bytesin];    // 输入 PI 数据
    u8 datain[];             // 数据输入区域
};

/* 命令特定的响应值 */
#define VIRTIO_SCSI_S_OK 0                   // 命令执行成功
#define VIRTIO_SCSI_S_OVERRUN 1              // 数据溢出
#define VIRTIO_SCSI_S_ABORTED 2              // 命令被中止
#define VIRTIO_SCSI_S_BAD_TARGET 3           // 无效的目标设备
#define VIRTIO_SCSI_S_RESET 4                // 命令执行时发生重置
#define VIRTIO_SCSI_S_BUSY 5                 // 目标设备忙
#define VIRTIO_SCSI_S_TRANSPORT_FAILURE 6    // 传输失败
#define VIRTIO_SCSI_S_TARGET_FAILURE 7       // 目标设备失败
#define VIRTIO_SCSI_S_NEXUS_FAILURE 8        // 连接失败
#define VIRTIO_SCSI_S_FAILURE 9              // 命令执行失败

/* 任务属性 */
#define VIRTIO_SCSI_S_SIMPLE 0   // 简单任务
#define VIRTIO_SCSI_S_ORDERED 1  // 有序任务
#define VIRTIO_SCSI_S_HEAD 2     // 头任务
#define VIRTIO_SCSI_S_ACA 3      // ACA 任务

lun 用于寻址 REPORT LUNS 常用逻辑单元,或 virtio-scsi 设备的 SCSI 域中的一个目标和逻辑单元。当用于寻址 REPORT LUNS 常用逻辑单元时,lun 为 0xC1,0x01,后跟六个零字节。virtio-scsi 设备应该实现 REPORT LUNS 常用逻辑单元。
当用于寻址目标和逻辑单元时,lun 的唯一支持格式是:第一个字节设置为 1,第二个字节设置为目标,第三和第四个字节表示单级 LUN 结构,然后是四个零字节。使用此表示法,virtio-scsi 设备每个目标最多可以为 256 个目标和每个目标的 16384 个 LUN 提供服务。设备也可以支持在第三和第四个字节中有一个常用逻辑单元。
id 是命令标识符(“标签”)。
task_attr 定义任务属性,如上表所示,但设备可以将所有任务属性映射为 SIMPLE。某些命令由 SCSI 标准定义为“隐式队列头”;对于这样的命令,设备还可以将所有任务属性映射为 HEAD OF QUEUE。驱动程序和应用程序不应在命令具有隐式 HEAD OF QUEUE 属性的情况下发送具有 ORDERED 任务属性的命令,因为 ORDERED 任务属性是否受到尊重是特定于供应商的。
crn 也可以由客户端提供,但通常预期为 0。由于 CRN 存储在 8 位整数中,协议定义的最大 CRN 值为 255。
CDB 包含在 cdb 中,其大小 cdb_size 取自配置空间。
所有这些字段都在 SAM 中定义,并始终可由设备读取。
pi_bytesout 确定 pi_out 字段的输出大小(以字节为单位)。如果不为零,pi_out 字段包含写操作的出站保护信息。pi_bytesin 确定设备可写部分中 pi_in 字段的大小,以字节为单位。只有在已经协商了 VIRTIO_SCSI_F_T10_PI 时才会存在这三个字段。
可读部分的其余部分是数据输出缓冲区,dataout。
sense 和随后的字段始终是设备可写的。sense_len 指示实际写入 sense 缓冲区的字节数。
residual 指示剩余大小,计算为“data_length - number_of_transferred_bytes”,用于读取或写入操作。对于双向命令,number_of_transferred_bytes 包括读取和写入的字节。小于 datain 大小的剩余值意味着 dataout 已完全处理。大于 datain 大小的剩余值意味着 dataout 部分处理且 datain 未被处理。如果 pi_bytesin 不为零,则 pi_in 字段包含读取操作的入站保护信息。只有在已经协商了 VIRTIO_SCSI_F_T10_PI 时才会存在 pi_in。
可写部分的其余部分是数据输入缓冲区,datain。

5.6.6.1.1 设备要求:设备操作:请求队列

设备必须将状态字节写为 SAM 中定义的状态码。
设备必须将响应字节写为以下之一:

  • VIRTIO_SCSI_S_OK 当请求已完成且状态字节填充有 SCSI 状态码(不一定是“GOOD”)时。
  • VIRTIO_SCSI_S_OVERRUN 如果 CDB 的内容(如分配长度、参数长度或传输大小)需要比数据输入和数据输出缓冲区中可用的数据更多时。
  • VIRTIO_SCSI_S_ABORTED 如果请求因 ABORT TASK 或 ABORT TASK SET 任务管理功能而被取消时。
  • VIRTIO_SCSI_S_BAD_TARGET 如果请求从未被处理,因为 lun 指示的目标不存在时。
  • VIRTIO_SCSI_S_RESET 如果请求因总线或设备复位而被取消(包括任务管理功能)时。
  • VIRTIO_SCSI_S_TRANSPORT_FAILURE 如果请求由于主机和目标之间的连接问题(断开连接)而失败时。
  • VIRTIO_SCSI_S_TARGET_FAILURE 如果目标遭受故障并且要告诉驱动程序不要在其他路径上重试时。
  • VIRTIO_SCSI_S_NEXUS_FAILURE 如果虚拟机遭受故障但在其他路径上重试可能会产生不同的结果时。
  • VIRTIO_SCSI_S_BUSY 如果请求失败但在相同路径上重试可能会成功时。
  • 对于其他主机或驱动程序错误的情况,响应为 VIRTIO_SCSI_S_FAILURE。特别是,如果 dataout 和 datain 都不为空,并且未协商 VIRTIO_SCSI_F_INOUT 功能,则该请求将立即返回响应等于 VIRTIO_SCSI_S_FAILURE。
    所有命令必须在重置或拔出 virtio-scsi 设备之前完成。设备可以选择取消它们,或者如果不这样做,则必须选择 VIRTIO_SCSI_S_FAILURE 响应。
5.6.6.1.2 驱动程序要求:设备操作:请求队列

task_attr、prio 和 crn 应该为零。
收到 VIRTIO_SCSI_S_TARGET_FAILURE 响应时,驱动程序不应在其他路径上重试请求。

5.6.6.1.3 遗留接口:设备操作:请求队列

在使用遗留接口时,过渡设备和驱动程序必须根据宿主的本机字节序而不是(在不使用遗留接口时也可能如此)小端字节序来格式化 struct virtio-scsi_req_cmd 中的字段。

5.6.6.2 设备操作:控制队列

控制队列用于其他 SCSI 传输操作。请求具有以下格式:

struct virtio_scsi_ctrl {
    le32 type;       // 控制类型,指示控制命令的类型
    // ... 其他字段 ...
    u8 response;     // 控制命令的响应
};

/* 适用于所有控制命令的响应值 */
#define VIRTIO_SCSI_S_OK 0                     // 命令执行成功
#define VIRTIO_SCSI_S_BAD_TARGET 3              // 无效的目标设备
#define VIRTIO_SCSI_S_BUSY 5                   // 目标设备忙
#define VIRTIO_SCSI_S_TRANSPORT_FAILURE 6       // 传输失败
#define VIRTIO_SCSI_S_TARGET_FAILURE 7          // 目标设备失败
#define VIRTIO_SCSI_S_NEXUS_FAILURE 8           // 连接失败
#define VIRTIO_SCSI_S_FAILURE 9                 // 命令执行失败
#define VIRTIO_SCSI_S_INCORRECT_LUN 12          // 无效的逻辑单元号

类型标识了其余字段
定义了以下命令:
• 任务管理函数

struct virtio_scsi_ctrl_tmf {
    // 设备可读部分
    le32 type;           // 控制类型,指示控制命令的类型
    le32 subtype;        // 控制子类型,用于进一步指示控制命令的具体子命令
    u8 lun[8];           // 逻辑单元号(LUN)
    le64 id;             // 命令标识符

    // 设备可写部分
    u8 response;         // 控制命令的响应
};

/* 特定命令的响应值 */
#define VIRTIO_SCSI_S_FUNCTION_COMPLETE 0        // 命令功能已完成
#define VIRTIO_SCSI_S_FUNCTION_SUCCEEDED 10       // 命令功能成功
#define VIRTIO_SCSI_S_FUNCTION_REJECTED 11        // 命令功能被拒绝

类型为 VIRTIO_SCSI_T_TMF;subtype 定义了哪个任务管理函数。除了响应字段外,所有字段都由驱动程序填充。

对于所请求的 TMF 不相关的其他字段将被忽略,但它们仍然存在。lun 的格式与请求队列指定的格式相同;当任务管理函数寻址整个 I_T nexus 时,将忽略单层 LUN。在相关时,id 的值与传递给请求队列的 id 值进行匹配。
任务管理函数可以影响请求队列中尚未完成的命令的响应值。例如,设备必须在收到“逻辑单元复位”或“I_T nexus 复位”TMF时,完成逻辑单元或目标上的所有活动命令(可能使用 VIRTIO_SCSI_S_RESET 响应代码)。类似地,设备必须在收到“中止任务”或“中止任务集”TMF时,完成所选命令(可能使用 VIRTIO_SCSI_S_ABORTED 响应代码)。此类效果必须在 TMF 本身成功完成之前发生,并且设备必须适当地使用内存屏障,以确保驱动程序以正确的顺序看到这些写入。
• 异步通知查询。

struct virtio_scsi_ctrl_an {
    // 设备可读部分
    le32 type;               // 控制类型,指示控制命令的类型
    u8 lun[8];               // 逻辑单元号(LUN)
    le32 event_requested;    // 请求的事件标志位

    // 设备可写部分
    le32 event_actual;       // 实际发生的事件标志位
    u8 response;             // 控制命令的响应
};

/* 异步通知事件标志位 */
#define VIRTIO_SCSI_EVT_ASYNC_OPERATIONAL_CHANGE 2       // 操作状态变更
#define VIRTIO_SCSI_EVT_ASYNC_POWER_MGMT 4               // 电源管理
#define VIRTIO_SCSI_EVT_ASYNC_EXTERNAL_REQUEST 8          // 外部请求
#define VIRTIO_SCSI_EVT_ASYNC_MEDIA_CHANGE 16             // 媒体更换
#define VIRTIO_SCSI_EVT_ASYNC_MULTI_HOST 32               // 多主机
#define VIRTIO_SCSI_EVT_ASYNC_DEVICE_BUSY 64              // 设备繁忙

通过发送此命令,驱动程序询问设备给定的 LUN 可以报告哪些事件,如 SCSI MMC 的第 6.6 和 A.6 段所述。驱动程序将其感兴趣的事件写入 event_requested;设备会通过将其支持的事件写入 event_actual 来响应。
类型是 VIRTIO_SCSI_T_AN_QUERY。lun 和 event_requested 由驱动程序编写。event_actual 和响应字段由设备编写。
响应字节没有定义命令特定的值。
• 异步通知订阅。

#define VIRTIO_SCSI_T_AN_SUBSCRIBE 2

struct virtio_scsi_ctrl_an {
    // Device-readable part 设备可读部分
    le32 type;             // 控制命令的类型,这里为订阅异步通知的命令类型
    u8 lun[8];             // 逻辑单元号,表示应用于的 SCSI 设备的标识
    le32 event_requested;  // 请求的事件标志位,指定要订阅的异步事件类型,可以包含多个事件类型
    // Device-writable part 设备可写部分
    le32 event_actual;     // 实际发生的事件标志位,当设备发生订阅的事件时,将其写入这个字段
    u8 response;           // 控制命令的响应,根据命令执行结果包含不同的响应值,指示命令是否成功执行
};

通过发送此命令,驱动程序要求指定的 LUN 报告其物理接口的事件,方式与 SCSI MMC 中描述的相同。驱动程序将其感兴趣的事件写入 event_requested;设备会通过将其支持的事件写入 event_actual 来响应。

事件类型与异步通知查询消息相同。

类型为 VIRTIO_SCSI_T_AN_SUBSCRIBElunevent_requested 由驱动程序编写。event_actual 和响应字段由设备编写。

响应字节中没有定义特定于命令的值。

5.6.6.2.1 遗留接口:设备操作:控制队列

在使用遗留接口时,过渡设备和驱动程序必须按照客户机的本地字节顺序,而不是(在不使用遗留接口时)小端字节顺序,格式化 struct virtio_scsi_ctrl、struct virtio_scsi_ctrl_tmf、struct virtio_scsi_ctrl_an 和 struct virtio_scsi_ctrl_an 中的字段。

5.6.6.3 设备操作:事件队列

事件队列由驱动程序为设备填充,以报告连接到设备的逻辑单元的信息。通常情况下,设备不会排队事件以适应空的事件队列,并且如果它找不到准备好的缓冲区,最终会丢弃事件。然而,当报告许多 LUN 的事件时(例如,当整个目标消失时),设备可以限制事件以避免丢弃它们。因此,在事件队列上放置 10 到 15 个缓冲区就足够了。

设备在事件队列上返回的缓冲区将在本节的其余部分中称为“事件”。

#define VIRTIO_SCSI_T_EVENTS_MISSED 0x80000000

struct virtio_scsi_event {
    // Device-writable part 设备可写部分
    le32 event;       // 发生的事件类型,描述了异步事件的种类
    u8 lun[8];        // 逻辑单元号,表示事件相关的 SCSI 设备的标识
    le32 reason;      // 事件的原因或详细信息,提供有关事件发生的额外信息
};

设备将事件中的第 31 位设置为报告由于缺少缓冲区而丢失的事件。

原因的含义取决于事件的内容。以下是已定义的事件:

  • 无事件。
#define VIRTIO_SCSI_T_NO_EVENT 0

  • 发生以下情况时触发此事件:

    • 当设备在事件队列中检测到一个比配置字段中指示的长度更短的缓冲区时,它可以立即使用该缓冲区,并在事件中放置此虚拟值。良好编写的驱动程序不应该遇到这种情况。
    • 当事件被丢弃时,设备可以在驱动程序提供缓冲区后立即发出此事件,以请求驱动程序采取措施。在这种情况下,当然,此事件将以 VIRTIO_SCSI_T_EVENTS_MISSED 标志报告。
  • 传输重置

// Virtio SCSI 设备事件类型:传输复位
// 表示 SCSI 设备的传输复位事件类型。
#define VIRTIO_SCSI_EVT_RESET_HARD 0 // 硬复位
#define VIRTIO_SCSI_EVT_RESET_RESCAN 1 // 重新扫描
#define VIRTIO_SCSI_EVT_RESET_REMOVED 2 // 被移除

通过发送此事件,设备表示已重置目标上的逻辑单元,包括出现或消失在总线上的新设备的情况。设备填写所有字段。事件设置为 VIRTIO_SCSI_T_TRANSPORT_RESET。lun 寻址 SCSI 主机中的逻辑单元。

原因值是上面三个 #define 值之一:

  • VIRTIO_SCSI_EVT_RESET_REMOVED(“LUN/target removed”)用于目标或逻辑单元不再能够接收命令的情况。
  • VIRTIO_SCSI_EVT_RESET_HARD(“LUN hard reset”)用于已重置逻辑单元但仍存在的情况。
  • VIRTIO_SCSI_EVT_RESET_RESCAN(“rescan LUN/target”)用于设备上刚刚出现目标或逻辑单元的情况。

“已删除”和“重新扫描”事件可能会在协商了 VIRTIO_SCSI_F_HOTPLUG 功能时发生;当发送到 LUN 0 时,它们可能适用于整个目标,因此驱动程序可以要求发起者重新扫描目标以检测到这一点。

事件还将通过 Sense 代码报告(这显然不适用于新出现的总线或目标,因为应用程序从未发现过它们):

  • “LUN/target removed” 映射到 Sense 键 ILLEGAL REQUEST、asc 0x25、ascq 0x00(LOGICAL UNIT NOT SUPPORTED)
  • “LUN hard reset” 映射到 Sense 键 UNIT ATTENTION、asc 0x29(POWER ON, RESET OR BUS DEVICE RESET OCCURRED)
  • “rescan LUN/target” 映射到 Sense 键 UNIT ATTENTION、asc 0x3f、ascq 0x0e(REPORTED LUNS DATA HAS CHANGED)

检测传输重置的首选方法始终是使用事件,因为只有在驱动程序向逻辑单元或目标发送 SCSI 命令时,驱动程序才会看到 Sense 代码。但是,如果事件被丢弃,在驱动程序要求发起者重新扫描 SCSI 总线时,发起者仍然可以与控制器的实际状态同步。在重新扫描期间,发起者将能够观察到上述 Sense 代码,并将其处理为驱动程序接收到等效事件的情况。

  • 异步通知
#define VIRTIO_SCSI_T_ASYNC_NOTIFY 2

// Virtio SCSI 设备事件类型:异步通知
// 表示 SCSI 设备的异步通知事件类型。

通过发送此事件,设备通知逻辑单元(LUN)的参数发生了变化。所有字段都由设备填写。事件设置为VIRTIO_SCSI_T_ASYNC_NOTIFY。LUN用于寻址SCSI主机中的逻辑单元。原因字段包含了驱动程序通过“异步通知订阅”命令订阅的事件的子集。

这种事件通知允许驱动程序了解到与逻辑单元相关的参数发生了变化,例如容量、状态或其他关键信息。驱动程序可以相应地更新其状态或采取必要的操作来适应这些变化,以确保系统的正常运行。

LUN参数变更

#define VIRTIO_SCSI_T_PARAM_CHANGE 3

// Virtio SCSI 设备事件类型:参数变更
// 表示 SCSI 设备的参数变更事件类型。

通过发送此事件,设备通知逻辑单元(LUN)的配置参数发生了变化,例如容量或缓存模式。事件设置为VIRTIO_SCSI_T_PARAM_CHANGE。LUN用于寻址SCSI主机中的逻辑单元。

同样的事件应该作为单元关注条件进行报告。原因字段包含了附加的感知代码和附加的感知代码限定符,分别在位0…7和8…15中。
注意:例如,容量的更改将被报告为asc 0x2a,ascq 0x09(容量数据发生了变化)。

对于MMC设备(查询类型5),此事件与异步通知事件之间可能会有一些重叠,因此为了简化起见,主机不会为MMC设备报告此事件。

5.6.6.3.1 驱动程序要求: 设备操作: 事件队列

驱动程序应该始终保持事件队列(eventq)中有可用的缓冲区。这些缓冲区必须是设备可写的,并且应该至少有event_info_size字节长,必须至少有struct virtio_scsi_event大小。
如果事件(event)的第31位被设置,驱动程序应该轮询逻辑单元(logical units)以检查单元关注条件(unit attention conditions),或者执行适合于客户操作系统的总线扫描,还应该使用SCSI命令手动轮询异步事件。
当接收到一个原因(reason)设置为VIRTIO_SCSI_EVT_RESET_REMOVED或VIRTIO_SCSI_EVT_RESET_RESCAN的VIRTIO_SCSI_T_TRANSPORT_RESET消息时,驱动程序应该请求发起者(initiator)重新扫描目标(target),以便检测整个目标是否已出现或消失,前提是LUN为0。

5.6.6.3.2 设备要求: 设备操作: 事件队列

如果由于缺少缓冲区而丢失了事件,设备必须在事件的第31位设置位31,并且它可以使用VIRTIO_SCSI_T_NO_EVENT事件来报告这一情况。除非已经协商了VIRTIO_SCSI_F_HOTPLUG,设备不得发送原因设置为VIRTIO_SCSI_EVT_RESET_REMOVED或VIRTIO_SCSI_EVT_RESET_RESCAN的VIRTIO_SCSI_T_TRANSPORT_RESET消息。
对于MMC设备,设备不得报告VIRTIO_SCSI_T_PARAM_CHANGE。

5.6.6.3.3 遗留接口: 设备操作: 事件队列

在使用遗留接口时,过渡设备和驱动程序必须根据客户端的本机字节序而不是(当未使用遗留接口时)小端字节序格式化struct virtio_scsi_event字段。

5.6.6.4 遗留接口: 帧格式要求

在使用遗留接口时,尚未协商VIRTIO_F_ANY_LAYOUT的过渡驱动程序必须为lun、id、task_attr、prio、crn和cdb字段使用单个描述符,并且只能为sense_len、residual、status_qualifier、status、response和sense字段使用单个描述符。

5.7 GPU设备

virtio-gpu是基于virtio的图形适配器。它可以在2D模式和3D(virgl)模式下运行。3D模式将渲染操作卸载到主机GPU上,因此需要主机机器上支持3D的GPU。尽管此处和那里提到了3D模式的一些细节,但本规范尚未涵盖3D模式。
在2D模式下,virtio-gpu设备支持ARGB硬件光标和多个扫描输出(又称为"heads")。

5.7.1 设备ID

16

5.7.2 Virtqueues

0 控制队列(controlq)- 用于发送控制命令的队列
1 光标队列(cursorq)- 用于发送光标更新的队列
这两个队列具有相同的格式。每个请求和每个响应都有一个固定的标头,后面是特定于命令的数据字段。独立的光标队列是用于光标命令(VIRTIO_GPU_CMD_UPDATE_CURSOR和VIRTIO_GPU_CMD_MOVE_CURSOR)的“快速通道”,因此它们可以在不受控制队列中耗时命令的延迟影响的情况下顺利进行。

5.7.3 功能位

VIRTIO_GPU_F_VIRGL(0)支持virgl 3D模式。
VIRTIO_GPU_F_EDID(1)支持EDID。

5.7.4 设备配置布局

GPU设备配置使用以下布局结构和定义:

#define VIRTIO_GPU_EVENT_DISPLAY (1 << 0)

// Virtio GPU 配置结构体
struct virtio_gpu_config {
    le32 events_read;    // 用于读取事件标志
    le32 events_clear;   // 用于清除事件标志
    le32 num_scanouts;   // 扫描输出的数量
    le32 reserved;       // 保留字段
};

5.7.4.1 设备配置字段

  • events_read:用于向驱动程序信号挂起的事件。驱动程序不得写入此字段。
  • events_clear:用于清除设备中的挂起事件。将“1”写入位将清除events_read中的相应位,模仿写入清除行为。
  • num_scanouts:指定设备支持的最大扫描次数。最小值为1,最大值为16。

5.7.4.2 事件

  • VIRTIO_GPU_EVENT_DISPLAY:显示配置已更改。驱动程序应该使用VIRTIO_GPU_CMD_GET_DISPLAY_INFO命令从设备中获取信息。

5.7.5 设备要求: 设备初始化

驱动程序应该使用VIRTIO_GPU_CMD_GET_DISPLAY_INFO命令从设备查询显示信息,并将该信息用于初始扫描设置。如果没有可用的信息或者所有显示都已禁用,驱动程序可以选择使用备用方案,例如在显示0上使用1024x768。

5.7.6 设备操作

virtio-gpu基于主机私有资源的概念,客户必须将数据传输到这些资源中。这是为了与未来的3D渲染进行接口设计的要求。在未加速的2D模式下,不支持从资源进行DMA传输,只支持传输到资源。

资源最初是简单的2D资源,包括宽度、高度和格式以及标识符。然后,客户必须将支持存储附加到资源中,以便进行DMA传输。这类似于实际GPU中的GART。

5.7.6.1 设备操作: 创建帧缓冲区并配置扫描

  • 使用VIRTIO_GPU_CMD_RESOURCE_CREATE_2D创建主机资源。
  • 从客户端内存分配一个帧缓冲区,并将其附加为刚创建的资源的支持存储,使用VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING。支持分散列表,因此帧缓冲区不需要在客户物理内存中是连续的。
  • 使用VIRTIO_GPU_CMD_SET_SCANOUT将帧缓冲区链接到显示扫描。

5.7.6.2 设备操作: 更新帧缓冲区和扫描

  • 渲染到帧缓冲区内存。
  • 使用VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D从客户端内存更新主机资源。
  • 使用VIRTIO_GPU_CMD_RESOURCE_FLUSH将更新的资源刷新到显示。

5.7.6.3 设备操作: 使用页面翻转**

可以创建多个帧缓冲区,使用VIRTIO_GPU_CMD_SET_SCANOUT和VIRTIO_GPU_CMD_RESOURCE_FLUSH之间进行翻转,并使用VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D更新不可见帧缓冲区。

5.7.6.4 设备操作: 多显示器设置**

如果存在两个或更多显示器,有不同的配置方式:

  • 创建单个帧缓冲区,将其链接到所有显示器(镜像)。
  • 为每个显示器创建一个帧缓冲区。
  • 创建一个大帧缓冲区,配置扫描以在帧缓冲区的每个部分显示不同的矩形。

5.7.6.5 设备要求: 设备操作: 命令生命周期和围栏**

设备可以异步处理控制队列(controlq)命令并在处理完成之前将其返回给驱动程序。如果驱动程序需要知道处理何时完成,它可以在请求中设置VIRTIO_GPU_FLAG_FENCE标志。设备必须在返回命令之前完成处理。

5.7.6.6 设备操作: 配置鼠标光标

鼠标光标图像是一个普通资源,不过它必须是64x64大小。驱动程序必须创建和填充资源(使用通常的VIRTIO_GPU_CMD_RESOURCE_CREATE_2D、VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING和VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D控制队列命令),并确保它们已完成(使用VIRTIO_GPU_FLAG_FENCE)。
然后,可以将VIRTIO_GPU_CMD_UPDATE_CURSOR发送到cursorq以设置指针形状和位置。要移动指针而不更新形状,使用VIRTIO_GPU_CMD_MOVE_CURSOR。

5.7.6.7 设备操作: 请求头

virt队列上的所有请求和响应都具有一个固定的头部,使用以下布局结构和定义:每个请求中的固定头部struct virtio_gpu_ctrl_hdr包括以下字段:

enum virtio_gpu_ctrl_type {
    /* 2D命令 */
    VIRTIO_GPU_CMD_GET_DISPLAY_INFO = 0x0100,
    VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
    VIRTIO_GPU_CMD_RESOURCE_UNREF,
    VIRTIO_GPU_CMD_SET_SCANOUT,
    VIRTIO_GPU_CMD_RESOURCE_FLUSH,
    VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
    VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
    VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING,
    VIRTIO_GPU_CMD_GET_CAPSET_INFO,
    VIRTIO_GPU_CMD_GET_CAPSET,
    VIRTIO_GPU_CMD_GET_EDID,
    /* 光标命令 */
    VIRTIO_GPU_CMD_UPDATE_CURSOR = 0x0300,
    VIRTIO_GPU_CMD_MOVE_CURSOR,
    /* 成功响应 */
    VIRTIO_GPU_RESP_OK_NODATA = 0x1100,
    VIRTIO_GPU_RESP_OK_DISPLAY_INFO,
    VIRTIO_GPU_RESP_OK_CAPSET_INFO,
    VIRTIO_GPU_RESP_OK_CAPSET,
    VIRTIO_GPU_RESP_OK_EDID,
    /* 错误响应 */
    VIRTIO_GPU_RESP_ERR_UNSPEC = 0x1200,
    VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY,
    VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
    VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID,
    VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID,
    VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER,
};

#define VIRTIO_GPU_FLAG_FENCE (1 << 0)

// Virtio GPU 控制头部结构体
struct virtio_gpu_ctrl_hdr {
    le32 type;      // 控制类型
    le32 flags;     // 标志位
    le64 fence_id;  // 围栏标识
    le32 ctx_id;    // 上下文标识
    le32 padding;   // 填充字段
};

  • type:指定驱动程序请求(VIRTIO_GPU_CMD_)或设备响应(VIRTIO_GPU_RESP_)的类型。
  • flags:请求/响应标志。
  • fence_id:如果驱动程序在请求标志字段中设置了VIRTIO_GPU_FLAG_FENCE位,则设备必须
    • 在响应中设置VIRTIO_GPU_FLAG_FENCE位。
    • 将请求中的fence_id字段的内容复制到响应中。
    • 仅在命令处理完成后发送响应。
  • ctx_id:渲染上下文(仅在3D模式下使用)。

成功完成后,设备将返回VIRTIO_GPU_RESP_OK_NODATA,以表示没有有效数据。否则,type字段将指示有效负载的类型。
在错误情况下,设备将返回VIRTIO_GPU_RESP_ERR_*之一的错误代码。

5.7.6.8 设备操作: controlq

对于任何给定的坐标,0,0代表左上角,更大的x值向右移动,更大的y值向下移动。
VIRTIO_GPU_CMD_GET_DISPLAY_INFO 获取当前的输出配置。没有请求数据(只有裸露的struct virtio_gpu_ctrl_hdr)。响应类型是VIRTIO_GPU_RESP_OK_DISPLAY_INFO,响应数据是struct virtio_gpu_resp_display_info。

#define VIRTIO_GPU_MAX_SCANOUTS 16

// 矩形结构体,用于表示矩形区域的位置和大小
struct virtio_gpu_rect {
    le32 x;      // 左上角 x 坐标
    le32 y;      // 左上角 y 坐标
    le32 width;  // 宽度
    le32 height; // 高度
};

// Virtio GPU 响应显示信息的结构体
struct virtio_gpu_resp_display_info {
    struct virtio_gpu_ctrl_hdr hdr;  // 控制头部结构体
    struct virtio_gpu_display_one {
        struct virtio_gpu_rect r;   // 矩形区域
        le32 enabled;               // 是否启用
        le32 flags;                 // 标志位
    } pmodes[VIRTIO_GPU_MAX_SCANOUTS];  // 支持的显示模式
};

响应包含每个扫描输出信息的列表。信息包括扫描输出是否已启用以及其首选位置和大小。
大小(width 和 height 字段)类似于 EDID 显示信息中的原生面板分辨率,但在虚拟机情况下,当表示宿主显示的客户端窗口大小发生变化时,大小可以更改。
位置(x 和 y 字段)描述了显示屏的排列方式(例如,哪个是左边的显示屏)。
enabled 字段在用户启用显示时设置为 true。它大致等同于物理显示连接器的连接状态。

VIRTIO_GPU_CMD_GET_EDID 获取指定扫描输出的EDID数据:
请求数据是 struct virtio_gpu_get_edid。响应类型是 VIRTIO_GPU_RESP_OK_EDID,响应数据是 struct virtio_gpu_resp_edid。支持是可选的,并通过协商使用 VIRTIO_GPU_F_EDID 功能标志。

// Virtio GPU 获取 EDID 命令结构体
struct virtio_gpu_get_edid {
    struct virtio_gpu_ctrl_hdr hdr; // 控制头部结构体
    le32 scanout;                   // 扫描输出 ID
    le32 padding;                   // 填充字段
};

// Virtio GPU 响应 EDID 命令结构体
struct virtio_gpu_resp_edid {
    struct virtio_gpu_ctrl_hdr hdr; // 控制头部结构体
    le32 size;                      // EDID 数据的大小
    le32 padding;                   // 填充字段
    u8 edid[1024];                  // EDID 数据
};

Response Content for EDID Display Data:
响应包含扫描输出的EDID显示数据块(如VESA规定)。

VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 创建主机上的2D资源:
请求数据是 struct virtio_gpu_resource_create_2d。响应类型是 VIRTIO_GPU_RESP_OK_NODATA。

// Virtio GPU 支持的像素格式枚举
enum virtio_gpu_formats {
    VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM = 1,
    VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM = 2,
    VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM = 3,
    VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM = 4,
    VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM = 67,
    VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM = 68,
    VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM = 121,
    VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM = 134,
};

// Virtio GPU 创建 2D 资源的命令结构体
struct virtio_gpu_resource_create_2d {
    struct virtio_gpu_ctrl_hdr hdr;  // 控制头部结构体
    le32 resource_id;               // 资源 ID
    le32 format;                    // 像素格式
    le32 width;                     // 宽度
    le32 height;                    // 高度
};

Creating a 2D Resource:
这将在主机上创建一个具有指定宽度、高度和格式的2D资源。资源ID由客户机生成。

VIRTIO_GPU_CMD_RESOURCE_UNREF 销毁资源:
请求数据是 struct virtio_gpu_resource_unref。响应类型是 VIRTIO_GPU_RESP_OK_NODATA。

// Virtio GPU 释放资源的命令结构体
struct virtio_gpu_resource_unref {
    struct virtio_gpu_ctrl_hdr hdr;  // 控制头部结构体
    le32 resource_id;               // 资源 ID
    le32 padding;                   // 填充字段
};

这通知主机不再需要由客户端使用的资源。

VIRTIO_GPU_CMD_SET_SCANOUT 设置单个输出的扫描参数。请求数据是 struct virtio_gpu_set_scanout。响应类型是 VIRTIO_GPU_RESP_OK_NODATA。

// Virtio GPU 设置扫描输出的命令结构体
struct virtio_gpu_set_scanout {
    struct virtio_gpu_ctrl_hdr hdr;  // 控制头部结构体
    struct virtio_gpu_rect r;       // 矩形区域信息
    le32 scanout_id;                // 扫描输出的 ID
    le32 resource_id;               // 资源 ID
};

这设置了单个扫描的扫描参数。resource_id 是要扫描的资源,以及一个矩形。

扫描矩形必须完全覆盖基础资源。允许重叠(或相同)的扫描,典型用例是屏幕镜像。

驱动程序可以使用 resource_id = 0 来禁用扫描。

VIRTIO_GPU_CMD_RESOURCE_FLUSH 刷新扫描资源。请求数据是 struct virtio_gpu_resource_flush。响应类型是 VIRTIO_GPU_RESP_OK_NODATA

// Virtio GPU 刷新资源命令结构体
struct virtio_gpu_resource_flush {
    struct virtio_gpu_ctrl_hdr hdr;   // 控制头部结构体
    struct virtio_gpu_rect r;        // 矩形区域信息
    le32 resource_id;                // 资源 ID
    le32 padding;                    // 填充字段
};

这将资源刷新到屏幕。它接受一个矩形和一个资源ID,并刷新资源正在使用的任何扫描。

VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 从客户内存传输到主机资源。请求数据是 struct virtio_gpu_transfer_to_host_2d。响应类型是 VIRTIO_GPU_RESP_OK_NODATA

// Virtio GPU 2D 数据传输到主机的命令结构体
struct virtio_gpu_transfer_to_host_2d {
    struct virtio_gpu_ctrl_hdr hdr; // 控制头部结构体
    struct virtio_gpu_rect r;      // 矩形区域信息
    le64 offset;                   // 数据偏移量
    le32 resource_id;              // 资源 ID
    le32 padding;                  // 填充字段
};

这将获取一个资源ID,以及资源的目标偏移量,以及要传输到资源的主机后备的框。
VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 分配资源的后备页面。请求数据是 struct virtio_gpu_resource_attach_backing,后跟 struct virtio_gpu_mem_entry 条目。响应类型是 VIRTIO_GPU_RESP_OK_NODATA。

// Virtio GPU 资源附加后备存储命令结构体
struct virtio_gpu_resource_attach_backing {
    struct virtio_gpu_ctrl_hdr hdr;  // 控制头部结构体
    le32 resource_id;                // 资源 ID
    le32 nr_entries;                 // 内存条目数量
};

// Virtio GPU 内存条目结构体
struct virtio_gpu_mem_entry {
    le64 addr;   // 地址
    le32 length; // 长度
    le32 padding; // 填充字段
};
这段代码定义了 Virtio GPU 设备中用于附加资源后备存储的命令结构体以及相关的内存条目结构体。结构体中的字段含义在注释中有详细解释。

这将分配一个包含宿主备份存储的数组。这些页面随后用于该资源的传输操作

VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING 从资源分离备份页面。请求数据是 struct virtio_gpu_resource_detach_backing。响应类型是 VIRTIO_GPU_RESP_OK_NODATA。

// Virtio GPU 资源分离后备存储命令结构体
struct virtio_gpu_resource_detach_backing {
    struct virtio_gpu_ctrl_hdr hdr;  // 控制头部结构体
    le32 resource_id;                // 资源 ID
    le32 padding;                    // 填充字段
};

这将分离资源的任何备份页面,以便在需要进行客户机交换或对象销毁时使用

5.7.6.9 设备操作:cursorq

这两个 cursorq 命令使用相同的命令结构。

// Virtio GPU 鼠标光标位置结构体
struct virtio_gpu_cursor_pos {
    le32 scanout_id;  // 扫描输出 ID
    le32 x;           // X 坐标
    le32 y;           // Y 坐标
    le32 padding;     // 填充字段
};

// Virtio GPU 更新鼠标光标命令结构体
struct virtio_gpu_update_cursor {
    struct virtio_gpu_ctrl_hdr hdr;  // 控制头部结构体
    struct virtio_gpu_cursor_pos pos; // 光标位置信息
    le32 resource_id;                // 资源 ID
    le32 hot_x;                      // 光标热点 X 坐标
    le32 hot_y;                      // 光标热点 Y 坐标
    le32 padding;                    // 填充字段
};

  • VIRTIO_GPU_CMD_UPDATE_CURSOR 更新鼠标指针。请求数据为 struct virtio_gpu_update_cursor。响应类型为 VIRTIO_GPU_RESP_OK_NODATA

这是一个完整的鼠标指针更新操作。鼠标指针将从指定的 resource_id 载入,并移动到 pos 指定的位置。在执行此命令之前,驱动程序必须使用控制队列命令将鼠标指针传输到资源中,并确保对填充资源的命令已经得到处理(使用屏障)。

  • VIRTIO_GPU_CMD_MOVE_CURSOR 移动鼠标指针。请求数据同样为 struct virtio_gpu_update_cursor。响应类型也是 VIRTIO_GPU_RESP_OK_NODATA

此命令将鼠标指针移动到 pos 指定的位置。其他字段将不被使用,设备将忽略它们。

5.7.7 VGA Compatibility (VGA 兼容性)

仅适用于 Virtio Over PCI。GPU 设备可以具备 VGA 兼容性,也可以没有。PCI 类别应该为 DISPLAY_VGA(如果存在 VGA 兼容性),否则为 DISPLAY_OTHER。

VGA 兼容性: PCI 区域 0 包含线性帧缓冲区,标准 VGA 寄存器也存在。配置扫描输出(VIRTIO_GPU_CMD_SET_SCANOUT)将设备从 VGA 兼容性模式切换到本机 virtio 模式。重置将其切换回 VGA 兼容性模式。
注意:QEMU 实现还提供了 Bochs Dispi 接口 IO 端口和位于 PCI 区域 1 的 MMIO 条,因此与 QEMU 标准 VGA 完全兼容(请参阅 QEMU 源代码树中的 docs/specs/standard-vga.txt)。

5.8 输入设备

virtio 输入设备可用于创建虚拟人机界面设备,如键盘、鼠标和平板电脑。virtio 设备的一个实例代表一个输入设备。设备行为与 Linux 的 evdev 层相对应,使得在 evdev 之上进行实现变得容易。

此规范定义了如何在 virtio 上传输 evdev 事件以及驱动程序如何发现支持的事件集。但是,它不定义输入事件的语义,因为这取决于特定的 evdev 实现。有关 Linux 输入设备使用的事件列表,请参阅 Linux 源代码树中的 include/uapi/linux/input-event-codes.h。

5.8.1 设备标识符

18

5.8.2 Virtqueues

0 eventq
1 statusq

5.8.3 特性位

5.8.4 设备配置布局

设备配置包含了客户机处理设备所需的所有信息,其中最重要的是支持的事件。要查询特定的信息,驱动程序相应地设置 select 和 subsel,然后检查 size 以查看有多少信息可用。如果没有信息可用,size 可以为零。字符串不包括空终止符。提供了相关的 evdev ioctl 名称以供参考。

// Virtio 输入设备配置选择枚举
enum virtio_input_config_select {
    VIRTIO_INPUT_CFG_UNSET = 0x00,    // 未设置
    VIRTIO_INPUT_CFG_ID_NAME = 0x01,  // ID 名称
    VIRTIO_INPUT_CFG_ID_SERIAL = 0x02,  // ID 序列号
    VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03,  // ID 设备信息
    VIRTIO_INPUT_CFG_PROP_BITS = 0x10,  // 属性位图
    VIRTIO_INPUT_CFG_EV_BITS = 0x11,    // 事件位图
    VIRTIO_INPUT_CFG_ABS_INFO = 0x12,   // 绝对信息
};

// Virtio 输入设备绝对信息结构体
struct virtio_input_absinfo {
    le32 min;   // 最小值
    le32 max;   // 最大值
    le32 fuzz;  // 模糊度
    le32 flat;  // 平坦度
    le32 res;   // 保留字段
};

// Virtio 输入设备设备信息结构体
struct virtio_input_devids {
    le16 bustype;  // 总线类型
    le16 vendor;   // 厂商 ID
    le16 product;  // 产品 ID
    le16 version;  // 版本信息
};

// Virtio 输入设备配置信息结构体
struct virtio_input_config {
    u8 select;              // 选择字段
    u8 subsel;              // 子选择字段
    u8 size;                // 数据大小
    u8 reserved[5];         // 保留字段
    union {
        char string[128];               // 字符串数据
        u8 bitmap[128];                // 位图数据
        struct virtio_input_absinfo abs; // 绝对信息数据
        struct virtio_input_devids ids; // 设备信息数据
    } u; // 联合体字段
};

  • VIRTIO_INPUT_CFG_ID_NAME:subsel 为零。返回设备的名称,存储在 u.string 中。类似于 Linux evdev 设备的 EVIOCGNAME ioctl。
  • VIRTIO_INPUT_CFG_ID_SERIAL:subsel 为零。返回设备的序列号,存储在 u.string 中。
  • VIRTIO_INPUT_CFG_ID_DEVIDS:subsel 为零。返回设备的 ID 信息,存储在 u.ids 中。类似于 Linux evdev 设备的 EVIOCGID ioctl。
  • VIRTIO_INPUT_CFG_PROP_BITS:subsel 为零。返回设备的输入属性,存储在 u.bitmap 中。位图中的各位对应于底层 evdev 实现中使用的 INPUT_PROP_* 常量。类似于 Linux evdev 设备的 EVIOCGPROP ioctl。
  • VIRTIO_INPUT_CFG_EV_BITS:subsel 使用底层 evdev 实现中的 EV_* 常量指定事件类型。如果 size 不为零,则支持该事件类型,并在 u.bitmap 中返回支持的事件代码的位图。位图中的各位对应于实现定义的输入事件代码,例如键盘按键或指针设备轴。类似于 Linux evdev 设备的 EVIOCGBIT ioctl。
  • VIRTIO_INPUT_CFG_ABS_INFO:subsel 使用底层 evdev 实现中的 ABS_* 常量指定绝对轴。关于轴的信息将在 u.abs 中返回。类似于 Linux evdev 设备的 EVIOCGABS ioctl。

5.8.5 设备初始化

  1. 查询设备支持的事件类型和代码。
  2. 使用接收缓冲区填充 eventq。

5.8.5.1 驱动程序要求:设备初始化

  • 驱动程序在查询设备配置时必须同时设置 select 和 subsel,可以按任意顺序设置。
  • 驱动程序不得写入配置字段,除了 select 和 subsel 之外的字段。
  • 驱动程序在访问配置信息之前应该检查 size 字段

5.8.5.2 设备要求:设备初始化

  • 如果设备不支持给定的 select 和 subsel 组合,设备必须将 size 字段设置为零

5.8.6 设备操作

  1. 从设备发送输入事件,例如按键和按钮的按下和释放事件,以及指针设备的移动事件,通过 eventq 发送到驱动程序。
  2. 从驱动程序发送键盘 LED 更新等状态反馈到设备,通过 statusq 发送。
  3. 两个队列都使用相同的 virtio_input_event 结构。type、code 和 value 根据 Linux 输入层(evdev)接口进行填充,不同之处在于字段采用小端字节顺序,而 evdev ioctl 接口使用本地字节顺序。
// Virtio 输入设备事件结构体
struct virtio_input_event {
    le16 type;   // 事件类型
    le16 code;   // 事件代码
    le32 value;  // 事件值
};

5.8.6.1 驱动程序要求:设备操作

  • 驱动程序应该保持事件队列(eventq)始终填满缓冲区。这些缓冲区必须可由设备写入,并且必须至少具有 struct virtio_input_event 大小
  • 驱动程序放置到 statusq 的缓冲区必须至少具有 struct virtio_input_event 大小
  • 驱动程序应该忽略它不认识的 eventq 输入事件。请注意,evdev 设备通常通过发送冗余事件并依赖消费方只使用它理解的事件并忽略其余事件来保持向后兼容性。

5.8.6.2 设备要求:设备操作

  • 如果 eventq 没有足够的可用缓冲区,设备可以丢弃输入事件。但如果它们是组成一个输入设备更新的序列的一部分,则不应该丢弃单个输入事件。例如,指针设备更新通常由多个输入事件组成,每个事件代表一个轴,以及一个终止的 EV_SYN 事件。设备应该要么缓冲整个序列,要么丢弃整个序列

5.9 加密设备

虚拟加密设备既是虚拟的加密设备,也是虚拟的加密加速器。虚拟加密设备提供以下加密服务:CIPHER、MAC、HASH 和 AEAD。虚拟加密设备有一个控制队列和至少一个数据队列。加密操作请求被放置到数据队列中,并由设备处理。某些加密操作请求只在会话上下文中有效。控制队列的作用是促进控制操作请求。会话管理通过控制操作请求实现。

5.9.1 设备ID

20

5.9.2 虚拟队列

0 dataq1

N-1 dataqN
N controlq
N 由 max_dataqueues 设置。

5.9.3 功能位

  • VIRTIO_CRYPTO_F_REVISION_1 (0) 版本 1。版本 1 具有特定的请求格式和其他增强功能(导致了一些额外的要求)。
  • VIRTIO_CRYPTO_F_CIPHER_STATELESS_MODE (1) 支持无状态模式请求的 CIPHER 服务。
  • VIRTIO_CRYPTO_F_HASH_STATELESS_MODE(2) 支持无状态模式请求的 HASH 服务。
  • VIRTIO_CRYPTO_F_MAC_STATELESS_MODE (3) 支持无状态模式请求的 MAC 服务。
  • VIRTIO_CRYPTO_F_AEAD_STATELESS_MODE(4) 支持无状态模式请求的 AEAD 服务。

5.9.3.1 功能位要求

一些加密功能位需要其他加密功能位(参见 2.2.1):

  • VIRTIO_CRYPTO_F_CIPHER_STATELESS_MODE 需要 VIRTIO_CRYPTO_F_REVISION_1。
  • VIRTIO_CRYPTO_F_HASH_STATELESS_MODE 需要 VIRTIO_CRYPTO_F_REVISION_1。
  • VIRTIO_CRYPTO_F_MAC_STATELESS_MODE 需要 VIRTIO_CRYPTO_F_REVISION_1。
  • VIRTIO_CRYPTO_F_AEAD_STATELESS_MODE 需要 VIRTIO_CRYPTO_F_REVISION_1。

你可能感兴趣的:(网络虚拟化,信息与通信)