上篇文章:https://blog.csdn.net/Phoenix_zxk/article/details/132917657
篇幅太大,所以分开写,接下来续上
设备必须忽略 GPR2 的位 0-31(从左边数)。这样可以使子通道 ID 的传递方式与现有的 I/O 指令传递方式保持一致。
设备可以在 GPR2 中返回一个 64 位的主机 cookie 以加速通知的执行。
对于每个通知,驱动程序应该使用 GPR4 来传递从前一个通知中在 GPR2 中接收到的主机 cookie。
注意:例如:
// 使用指定的虚拟队列(vq)索引和当前的 cookie 值(info->cookie)进行通知
info->cookie = do_notify(schid,
virtqueue_get_queue_index(vq),
info->cookie);
为了重置设备,驱动程序发送 CCW_CMD_VDEV_RESET
命令。
在virtio中,除了队列、配置空间和特性协商功能之外,还定义了多种设备类型。以下是用于标识不同类型的virtio设备的设备ID。其中,一些设备ID被保留,用于尚未在本标准中定义的设备。发现可用设备及其类型是依赖于总线的。
本文档中未明确定义一些设备,因为它们被视为不成熟或特别狭窄的设备。请注意,其中一些设备仅由唯一的现有实现规定;它们可能成为未来规范的一部分,也可能被完全放弃,或者继续存在于本标准之外。我们将不再深入讨论它们。
virtio网络设备是虚拟以太网卡,是到目前为止由virtio支持的设备中最复杂的设备。它迅速增强,并清晰地展示了如何向现有设备添加新功能支持。空缓冲区放置在一个virtqueue中用于接收数据包,而发送的数据包则按顺序排队到另一个virtqueue中进行传输。第三个命令队列用于控制高级过滤功能。
1
N的值为1(如果未协商VIRTIO_NET_F_MQ),否则N的值由max_virtqueue_pairs设置。
如果设置了VIRTIO_NET_F_CTRL_VQ,则存在控制队列。
某些网络特性位要求其他网络特性位(请参阅2.2.1):
目前定义了三个仅供驱动程序读取的配置字段。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位整数
};
驱动程序将执行典型的初始化程序如下:
数据包通过将它们放置在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
};
在使用遗留接口时,过渡设备和驱动程序必须根据客户机的本机字节序来格式化struct virtio_net_hdr中的字段,而不是(当不使用遗留接口时)小端字节序。
在遗留接口中,只有在协商了VIRTIO_NET_F_MRG_RXBUF时,驱动程序才会在struct virtio_net_hdr中呈现num_buffers字段;如果没有该功能,该结构将缩短2个字节。
在使用遗留接口时,驱动程序应该忽略传输队列和controlq队列的used长度。
注意:在历史上,某些设备将总描述符长度放在那里,尽管实际上没有写入任何数据。
传输单个数据包很简单,但取决于驱动程序协商的不同功能。
通常,驱动程序将抑制传输虚拟队列中的中断,并在随后的数据包传输路径中检查已使用的数据包。
在此中断处理程序中的正常行为是从虚拟队列中检索已使用的缓冲区并释放相应的标头和数据包。
通常情况下,尽可能使接收虚拟队列保持充分填充是个不错的主意:如果用完,网络性能将受到影响。
如果使用了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字节。
如果协商了VIRTIO_NET_F_MQ,那么每个将被使用的receiveq1…receiveqN都应该被填充上接收缓冲区。
设备必须将num_buffers设置为用于容纳传入数据包的描述符数。
如果没有协商VIRTIO_NET_F_MRG_RXBUF,则设备必须只使用一个描述符。
注意:这意味着如果没有协商VIRTIO_NET_F_MRG_RXBUF,则num_buffers始终为1。
当将数据包复制到接收队列中的缓冲区时,最佳路径是禁用接收队列的进一步已使用缓冲区通知,并处理数据包,直到没有更多数据包,然后重新启用它们。
处理传入数据包涉及:
如果已协商了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)来发送命令,以操纵设备的各种特性,这些特性不容易映射到配置空间中。所有的命令都具有以下形式:
这些字段由驱动程序设置,而设备设置 ack 字节。如果 ack 不是 VIRTIO_NET_OK,设备通常只能发出诊断消息。
如果已经协商了 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 // 不接收广播数据包
如果已经协商了 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_NOBCAST:用于抑制广播接收。命令特定数据是一个字节,包含 0(允许广播接收)或 1(抑制广播接收)。当抑制广播接收时,设备不应将广播数据包发送给驱动程序。即使 VIRTIO_NET_CTRL_RX_ALLMULTI 打开,这个过滤器也不应用于广播数据包。
如果未协商 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。
如果已经协商了 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地址。
mac
(或使用 VIRTIO_NET_CTRL_MAC_ADDR_SET 设置的MAC)和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。
在使用旧版接口时,过渡设备和驱动程序必须根据客户机的本机字节序(与旧版接口无关时)而不是小端格式来格式化 struct virtio_net_ctrl_mac 条目。
未协商 VIRTIO_NET_F_CTRL_MAC_ADDR 的旧版驱动程序在NIC接受传入的数据包时会更改配置空间中的mac。这些驱动程序始终将MAC值从第一个字节写到最后一个字节,因此在检测到这些驱动程序后,过渡设备可以选择推迟MAC更新,或者可以选择推迟处理传入的数据包,直到驱动程序写入配置空间中的mac的最后一个字节。
如果驱动程序协商了 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作为命令特定数据传递。
在使用旧版接口时,过渡设备和驱动程序必须根据客户机的本机字节序而不是小端格式来格式化VLAN id。
如果驱动程序协商了 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位。
处理此通知包括:
如果驱动程序协商了VIRTIO_NET_F_GUEST_ANNOUNCE,则应在注意到状态中的VIRTIO_NET_S_ANNOUNCE位后通知网络对等方其新位置。驱动程序必须通过命令队列发送一个带有类VIRTIO_NET_CTRL_ANNOUNCE和命令VIRTIO_NET_CTRL_ANNOUNCE_ACK的命令。
如果协商了VIRTIO_NET_F_GUEST_ANNOUNCE,设备必须在收到一个带有类VIRTIO_NET_CTRL_ANNOUNCE和命令VIRTIO_NET_CTRL_ANNOUNCE_ACK的命令缓冲区后,在将其标记为已使用之前清除状态中的VIRTIO_NET_S_ANNOUNCE位。
如果驱动程序协商了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来禁用多队列(这是默认值),并等待设备使用命令缓冲区。
驱动程序必须在使用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的传输队列上排队数据包。
设备必须在使用VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET命令放置在已使用的缓冲区中之前,仅在任何receiveq1上排队数据包。
设备不得在大于virtqueue_pairs的接收队列上排队数据包。
在使用传统接口时,过渡设备和驱动程序必须根据客户机的本地字节顺序(而不是(不一定是)小端字节顺序)格式化virtqueue_pairs。
如果已经协商了VIRTIO_NET_F_CTRL_GUEST_OFFLOADS功能,驱动程序可以发送用于动态卸载状态配置的控制命令。
要配置卸载状态,使用以下布局结构和定义:
// 定义 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值是一个位掩码,已设置的位定义要启用的卸载,已清除的位 - 要禁用的卸载。
对于每个卸载都有相应的设备功能。在特性协商期间,相应的卸载将被启用以保持向后兼容性。
驱动程序不得启用未协商适当特性的卸载。
在使用传统接口时,过渡设备和驱动程序必须根据客户机的本地字节顺序(而不是(不一定是)小端字节顺序)格式化卸载。
在使用传统接口时,未协商VIRTIO_F_ANY_LAYOUT的过渡驱动程序必须在传输和接收的struct virtio_net_hdr上使用单个描述符,其中网络数据位于以下描述符中。
此外,当使用控制virtqueue(参见5.1.6.5)时,未协商VIRTIO_F_ANY_LAYOUT的过渡驱动程序必须:
请参阅2.6.4。
virtio块设备是一个简单的虚拟块设备(即磁盘)。读取和写入请求(以及其他奇特的请求)被放置在队列中,并由设备进行服务(可能是无序的),除非另有说明。
2
0 requestq
设备的容量(以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]; // 未使用的字段
};
在使用传统接口时,过渡设备和驱动程序必须根据来宾的本机字节顺序(与传统接口不使用时一样)格式化struct virtio_blk_config中的字段。
如果驱动程序无法发送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。
设备应始终提供VIRTIO_BLK_F_FLUSH,如果提供VIRTIO_BLK_F_CONFIG_WCE,则必须提供它。
如果已协商VIRTIO_BLK_F_CONFIG_WCE但未协商VIRTIO_BLK_F_FLUSH,则设备必须将writeback初始化为0。
设备必须将未使用的填充字节unused0和unused1初始化为0。
因为传统设备没有FEATURES_OK,所以在使用传统接口时,过渡设备在特性协商周围的行为略有不同。特别是,在使用传统接口时:
驱动程序将请求排入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 时,个别段的状态是不确定的。一个段可能已经成功完成,失败,或者设备未对其进行处理。
使用传统接口时,过渡设备和驱动程序必须根据客户机的本机字节序而不是小端字节序(如果不使用传统接口的话)来格式化 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 包命令请求,并指示残余大小,计算方式为数据长度 - 实际传输的字节数。
在使用传统接口时,尚未协商 VIRTIO_F_ANY_LAYOUT 的过渡驱动程序必须:
virtio 控制台设备是用于数据输入和输出的简单设备。一个设备可以拥有一个或多个端口。每个端口都有一对输入和输出 virtqueue。此外,设备还有一对控制 IO virtqueue。控制 virtqueue 用于设备和驱动程序之间的信息通信,关于连接的两侧打开和关闭的端口,设备是否为特定端口是控制台端口,添加新端口,端口热插拔等,以及驱动程序关于是否成功添加了端口或设备,端口打开/关闭等。
对于数据 IO,将一个或多个空缓冲区放置在接收队列中,用于接收传入数据,而传出字符则放置在传输队列中。
3
0 接收队列(端口0)
1 传输队列(端口0)
2 控制接收队列
3 控制传输队列
4 接收队列(端口1) 5 传输队列(端口1)
…
端口0的接收和传输队列始终存在:其他队列只在设置 VIRTIO_CONSOLE_F_MULTIPORT 时存在。
VIRTIO_CONSOLE_F_SIZE(0)配置的列数和行数有效。
VIRTIO_CONSOLE_F_MULTIPORT(1)设备支持多个端口;max_nr_ports 有效,并且将使用控制 virtqueue。
VIRTIO_CONSOLE_F_EMERG_WRITE(2)设备支持紧急写入。配置字段 emerg_wr 有效。
如果设置了 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; // 紧急写入缓冲区大小
};
在使用传统接口时,过渡设备和驱动程序必须按照宿主系统的本机字节顺序而不是小端字节顺序(当不使用传统接口时可能是小端字节顺序)来格式化 struct virtio_console_config 中的字段。
设备必须允许对 emerg_wr 的写入,即使在未配置的设备上也是如此。
设备应该将写入 emerg_wr 的较低字节传输到适当的日志或输出方法。
驱动程序不得将可供设备读取的内容放入 receiveq 中。驱动程序不得将可供设备写入的缓冲区放入 transmitq 中。
如果驱动程序协商了 VIRTIO_CONSOLE_F_MULTIPORT 特性,则两个控制队列用于操作不同的控制台端口:控制接收队列用于从设备到驱动程序的消息,控制发送队列用于从驱动程序到设备的消息。控制消息的布局如下:
struct virtio_console_control {
le32 id; /* 端口号 */
le16 event; /* 控制事件类型 */
le16 value; /* 事件的附加信息 */
};
```c
在这里插入代码片
事件的值为:
``
struct virtio_console_resize {
le16 cols; /* 控制台列数 */
le16 rows; /* 控制台行数 */
};
virtio熵设备为客户机提供高质量的随机性以供使用。
4
0 请求队列
目前没有定义
目前没有定义
当驱动程序需要随机字节时,它将一个或多个缓冲区的描述符放入队列中。设备会使用随机数据完全填充它。
这是传统的气球设备。设备号13保留给了新的内存气球接口,其语义不同,预计将在标准的未来版本中出现。
传统virtio内存气球设备是管理客户机内存的基本设备:设备请求一定量的内存,驱动程序提供它(或者如果设备比它请求的内存多,则撤回它)。这允许客户机适应底层物理内存的允许变化。如果协商了该功能,设备还可以用于向主机传达客户机内存统计信息。
5
0 增加队列
1 释放队列
2 统计队列。
只有在设置了 VIRTIO_BALLON_F_STATS_VQ 时才存在 Virtqueue 2。
由于遗留接口没有优雅地报告特性协商失败的方式,因此在使用遗留接口时,过渡设备必须支持不协商 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性的客户机,并且如果未协商 VIRTIO_BALLOON_F_MUST_TELL_HOST,则应允许客户机在通知主机之前使用内存。
该配置的两个字段始终可用。5.5.4.0.0.1 遗留接口:设备配置布局:使用遗留接口时,过渡设备和驱动程序必须按照小端格式格式化 struct virtio_balloon_config 中的字段。注意:这与通常的惯例不同,通常情况下遗留设备字段是客户端字节序。
struct virtio_balloon_config {
le32 num_pages; // 分配的内存页数
le32 actual; // 实际分配的内存页数
};
设备初始化过程如下:
设备驱动通过接收配置更改通知或更改客户机内存需求来驱动设备,例如执行内存压缩或响应内存不足的情况。
如果设备检测到膨胀请求中的页面的物理编号并在使用 inflateq 描述符确认膨胀请求之前,设备可以修改气球中页面的内容。
如果已协商了 VIRTIO_BALLOON_F_MUST_TELL_HOST 特性,则设备可以在检测到膨胀请求中的页面的物理编号并在使用膨胀请求的 inflateq 描述符确认膨胀请求之前,以及在检测到紧缩请求中的页面的物理编号并在确认紧缩请求之前修改气球中页面的内容。
在使用遗留接口时,驱动程序应忽略已使用长度值。
注意:从历史上看,某些设备将总描述符长度放在那里,尽管实际上没有写入任何数据。
在使用遗留接口时,每次在配置空间中更新实际值时,驱动程序必须使用单个原子操作写出所有 4 个字节。
在使用遗留接口时,设备不应在驱动程序在配置空间中写入实际值的最后一个最重要的字节之前使用该值。
注意:从历史上看,设备使用实际值,尽管在使用 Virtio Over PCI Bus 时,不能保证设备特定的配置空间是原子的。在驱动程序更新过程中使用中间值最好避免,除非用于调试。
历史上,使用 Virtio Over PCI Bus 的驱动程序通过按从最不重要的值到最重要的值的顺序使用多个单字节写操作来写入实际值。
统计 virtqueue 不同寻常的地方在于通信是由设备(而不是驱动程序)驱动的。该通道在驱动程序初始化时变为活动状态,当驱动程序添加一个空缓冲区并通知设备时。内存统计的请求如下进行:
/*
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 - 分配大页页表项失败的次数
*/
本节中的规范性语句仅在已协商 VIRTIO_BALLOON_F_STATS_VQ 特性的情况下适用。
驱动程序在 statsq 中最多一次向设备提供一个缓冲区。
在初始化设备后,驱动程序必须在 statsq 中提供一个输出缓冲区。
在检测到设备已使用 statsq 中的缓冲区之前,驱动程序必须在 statsq 中提供一个输出缓冲区。
在 statsq 中提供输出缓冲区之前,驱动程序必须对其进行初始化,包括为其支持的每个统计信息提供一个 struct virtio_balloon_stat 条目。
驱动程序必须对提交给 statsq 的所有缓冲区使用多个 6 字节的输出缓冲区大小。
驱动程序可以以任何顺序在提交给 statsq 的输出缓冲区中提供 struct virtio_balloon_stat 条目,而不考虑标记值。
驱动程序可以在提交给 statsq 的输出缓冲区中提供所有统计信息的子集。
驱动程序必须在提交给 statsq 的所有缓冲区中提供相同的统计信息子集。
本节中的规范性语句仅在已协商 VIRTIO_BALLOON_F_STATS_VQ 特性的情况下适用。
在提交给 statsq 的输出缓冲区内,设备必须忽略其不识别的标记值的条目。
在提交给 statsq 的输出缓冲区内,设备必须接受 struct virtio_balloon_stat 条目,而不考虑标记值。
在使用遗留接口时,过渡设备和驱动程序必须根据客户机的本机字节顺序格式化 struct virtio_balloon_stat 中的字段,而不是(在不使用遗留接口时)按小尾数顺序。
在使用遗留接口时,驱动程序在设备初始化后在 statsq 提供的第一个缓冲区中忽略所有值。注意:从历史上看,驱动程序在第一个缓冲区中提供了一个未初始化的缓冲区。
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)客户端失败的巨型页面分配次数。
virtio SCSI 主机设备将一个或多个虚拟逻辑单元(例如磁盘)分组在一起,允许使用 SCSI 协议与它们通信。设备的一个实例表示一个 SCSI 主机,附加了许多目标和 LUN。
virtio SCSI 设备提供两种类型的请求:
• 逻辑单元的命令请求;
• 与逻辑单元、目标或命令相关的任务管理功能。
设备还能够发送关于添加和删除逻辑单元的通知。这些功能共同提供了一种使用 virtqueue 作为传输介质的 SCSI 传输协议。在传输协议中,virtio 驱动程序充当发起者,而 virtio SCSI 主机提供一个或多个接收和处理请求的目标。
本节依赖于 SAM 的定义。
8
0 控制队列
1 事件队列
2…n 请求队列
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 请求头中。
此配置的所有字段始终可用。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。
驱动程序不得写入除 sense_size 和 cdb_size 之外的设备配置字段。
驱动程序不得向一个 LUN 发送超过 cmd_per_lun 个关联命令,并且不得向一个 LUN 发送超过 virtqueue 大小数量的关联命令。
在重置时,设备必须将 sense_size 设置为 96,cdb_size 设置为 32。
在使用遗留接口时,过渡设备和驱动程序必须根据客户机的本机字节序而不是(不使用遗留接口时的必然)小端格式格式化 struct virtio_scsi_config 中的字段。
在初始化时,驱动程序应首先发现设备的 virtqueue。
如果驱动程序使用 eventq,则驱动程序应在 eventq 中放置至少一个缓冲区。
驱动程序可以立即发出请求或任务管理函数。
设备操作包括操作请求队列、控制队列和事件队列。
在使用遗留接口时,驱动程序应忽略已使用的长度值。
注意:从历史上看,设备在那里放置了总描述符长度,或者设备可写缓冲区的总长度,即使实际写入的只是部分缓冲区。
驱动程序将请求排队到任意请求队列,并且它们将由同一队列上的设备使用。由于它们将无序使用,因此驱动程序有责任确保在不同队列上放置的命令的严格请求顺序。
请求具有以下格式:
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。
设备必须将状态字节写为 SAM 中定义的状态码。
设备必须将响应字节写为以下之一:
task_attr、prio 和 crn 应该为零。
收到 VIRTIO_SCSI_S_TARGET_FAILURE 响应时,驱动程序不应在其他路径上重试请求。
在使用遗留接口时,过渡设备和驱动程序必须根据宿主的本机字节序而不是(在不使用遗留接口时也可能如此)小端字节序来格式化 struct virtio-scsi_req_cmd 中的字段。
控制队列用于其他 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_SUBSCRIBE
。lun
和 event_requested
由驱动程序编写。event_actual
和响应字段由设备编写。
响应字节中没有定义特定于命令的值。
在使用遗留接口时,过渡设备和驱动程序必须按照客户机的本地字节顺序,而不是(在不使用遗留接口时)小端字节顺序,格式化 struct virtio_scsi_ctrl、struct virtio_scsi_ctrl_tmf、struct virtio_scsi_ctrl_an 和 struct virtio_scsi_ctrl_an 中的字段。
事件队列由驱动程序为设备填充,以报告连接到设备的逻辑单元的信息。通常情况下,设备不会排队事件以适应空的事件队列,并且如果它找不到准备好的缓冲区,最终会丢弃事件。然而,当报告许多 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 设备事件类型:传输复位
// 表示 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_F_HOTPLUG 功能时发生;当发送到 LUN 0 时,它们可能适用于整个目标,因此驱动程序可以要求发起者重新扫描目标以检测到这一点。
事件还将通过 Sense 代码报告(这显然不适用于新出现的总线或目标,因为应用程序从未发现过它们):
检测传输重置的首选方法始终是使用事件,因为只有在驱动程序向逻辑单元或目标发送 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设备报告此事件。
驱动程序应该始终保持事件队列(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。
如果由于缺少缓冲区而丢失了事件,设备必须在事件的第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。
在使用遗留接口时,过渡设备和驱动程序必须根据客户端的本机字节序而不是(当未使用遗留接口时)小端字节序格式化struct virtio_scsi_event字段。
在使用遗留接口时,尚未协商VIRTIO_F_ANY_LAYOUT的过渡驱动程序必须为lun、id、task_attr、prio、crn和cdb字段使用单个描述符,并且只能为sense_len、residual、status_qualifier、status、response和sense字段使用单个描述符。
virtio-gpu是基于virtio的图形适配器。它可以在2D模式和3D(virgl)模式下运行。3D模式将渲染操作卸载到主机GPU上,因此需要主机机器上支持3D的GPU。尽管此处和那里提到了3D模式的一些细节,但本规范尚未涵盖3D模式。
在2D模式下,virtio-gpu设备支持ARGB硬件光标和多个扫描输出(又称为"heads")。
16
0 控制队列(controlq)- 用于发送控制命令的队列
1 光标队列(cursorq)- 用于发送光标更新的队列
这两个队列具有相同的格式。每个请求和每个响应都有一个固定的标头,后面是特定于命令的数据字段。独立的光标队列是用于光标命令(VIRTIO_GPU_CMD_UPDATE_CURSOR和VIRTIO_GPU_CMD_MOVE_CURSOR)的“快速通道”,因此它们可以在不受控制队列中耗时命令的延迟影响的情况下顺利进行。
VIRTIO_GPU_F_VIRGL(0)支持virgl 3D模式。
VIRTIO_GPU_F_EDID(1)支持EDID。
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; // 保留字段
};
驱动程序应该使用VIRTIO_GPU_CMD_GET_DISPLAY_INFO命令从设备查询显示信息,并将该信息用于初始扫描设置。如果没有可用的信息或者所有显示都已禁用,驱动程序可以选择使用备用方案,例如在显示0上使用1024x768。
virtio-gpu基于主机私有资源的概念,客户必须将数据传输到这些资源中。这是为了与未来的3D渲染进行接口设计的要求。在未加速的2D模式下,不支持从资源进行DMA传输,只支持传输到资源。
资源最初是简单的2D资源,包括宽度、高度和格式以及标识符。然后,客户必须将支持存储附加到资源中,以便进行DMA传输。这类似于实际GPU中的GART。
可以创建多个帧缓冲区,使用VIRTIO_GPU_CMD_SET_SCANOUT和VIRTIO_GPU_CMD_RESOURCE_FLUSH之间进行翻转,并使用VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D更新不可见帧缓冲区。
如果存在两个或更多显示器,有不同的配置方式:
设备可以异步处理控制队列(controlq)命令并在处理完成之前将其返回给驱动程序。如果驱动程序需要知道处理何时完成,它可以在请求中设置VIRTIO_GPU_FLAG_FENCE标志。设备必须在返回命令之前完成处理。
鼠标光标图像是一个普通资源,不过它必须是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。
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; // 填充字段
};
成功完成后,设备将返回VIRTIO_GPU_RESP_OK_NODATA,以表示没有有效数据。否则,type字段将指示有效负载的类型。
在错误情况下,设备将返回VIRTIO_GPU_RESP_ERR_*之一的错误代码。
对于任何给定的坐标,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; // 填充字段
};
这将分离资源的任何备份页面,以便在需要进行客户机交换或对象销毁时使用
这两个 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; // 填充字段
};
这是一个完整的鼠标指针更新操作。鼠标指针将从指定的 resource_id 载入,并移动到 pos 指定的位置。在执行此命令之前,驱动程序必须使用控制队列命令将鼠标指针传输到资源中,并确保对填充资源的命令已经得到处理(使用屏障)。
此命令将鼠标指针移动到 pos 指定的位置。其他字段将不被使用,设备将忽略它们。
仅适用于 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)。
virtio 输入设备可用于创建虚拟人机界面设备,如键盘、鼠标和平板电脑。virtio 设备的一个实例代表一个输入设备。设备行为与 Linux 的 evdev 层相对应,使得在 evdev 之上进行实现变得容易。
此规范定义了如何在 virtio 上传输 evdev 事件以及驱动程序如何发现支持的事件集。但是,它不定义输入事件的语义,因为这取决于特定的 evdev 实现。有关 Linux 输入设备使用的事件列表,请参阅 Linux 源代码树中的 include/uapi/linux/input-event-codes.h。
18
0 eventq
1 statusq
无
设备配置包含了客户机处理设备所需的所有信息,其中最重要的是支持的事件。要查询特定的信息,驱动程序相应地设置 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 输入设备事件结构体
struct virtio_input_event {
le16 type; // 事件类型
le16 code; // 事件代码
le32 value; // 事件值
};
虚拟加密设备既是虚拟的加密设备,也是虚拟的加密加速器。虚拟加密设备提供以下加密服务:CIPHER、MAC、HASH 和 AEAD。虚拟加密设备有一个控制队列和至少一个数据队列。加密操作请求被放置到数据队列中,并由设备处理。某些加密操作请求只在会话上下文中有效。控制队列的作用是促进控制操作请求。会话管理通过控制操作请求实现。
20
0 dataq1
…
N-1 dataqN
N controlq
N 由 max_dataqueues 设置。
一些加密功能位需要其他加密功能位(参见 2.2.1):