SimpleKernel 1.17.0
Loading...
Searching...
No Matches
split.hpp
Go to the documentation of this file.
1
5#pragma once
6
7#include <utility>
8
9#include "expected.hpp"
10#include "io_buffer.hpp"
13
14namespace virtio {
15
38class SplitVirtqueue final : public VirtqueueBase {
39 public:
44 enum class DescFlags : uint16_t {
46 kDescFNext = 1,
48 kDescFWrite = 2,
51 };
52
57 enum class AvailFlags : uint16_t {
60 };
61
66 enum class UsedFlags : uint16_t {
69 };
70
80 struct [[gnu::packed]] Desc {
82 static constexpr size_t kAlign = 16;
84 uint64_t addr;
86 uint32_t len;
88 uint16_t flags;
90 uint16_t next;
91 };
92
103 struct [[gnu::packed]] Avail {
105 static constexpr size_t kAlign = 2;
107 uint16_t flags;
109 uint16_t idx;
111#pragma GCC diagnostic push
112#pragma GCC diagnostic ignored "-Wpedantic"
113 uint16_t ring[];
114#pragma GCC diagnostic pop
115
123 [[nodiscard]] auto used_event(uint16_t queue_size) volatile
124 -> volatile uint16_t* {
125 return ring + queue_size;
126 }
127
128 [[nodiscard]] auto used_event(uint16_t queue_size) const volatile -> const
129 volatile uint16_t* {
130 return ring + queue_size;
131 }
132 };
133
141 struct [[gnu::packed]] UsedElem {
143 uint32_t id;
145 uint32_t len;
146 };
147
158 struct [[gnu::packed]] Used {
160 static constexpr size_t kAlign = 4;
162 uint16_t flags;
164 uint16_t idx;
166#pragma GCC diagnostic push
167#pragma GCC diagnostic ignored "-Wpedantic"
168 UsedElem ring[];
169#pragma GCC diagnostic pop
170
178 [[nodiscard]] auto avail_event(uint16_t queue_size) volatile
179 -> volatile uint16_t* {
180 // avail_event 位于 ring[queue_size] 之后,需要先转换为字节指针计算偏移
181 auto* byte_ptr = reinterpret_cast<volatile uint8_t*>(ring);
182 return reinterpret_cast<volatile uint16_t*>(byte_ptr + sizeof(UsedElem) *
183 queue_size);
184 }
185
186 [[nodiscard]] auto avail_event(uint16_t queue_size) const volatile -> const
187 volatile uint16_t* {
188 const auto* byte_ptr = reinterpret_cast<const volatile uint8_t*>(ring);
189 return reinterpret_cast<const volatile uint16_t*>(
190 byte_ptr + sizeof(UsedElem) * queue_size);
191 }
192 };
193
203 [[nodiscard]] static constexpr auto CalcSize(uint16_t queue_size,
204 bool event_idx = true,
205 size_t used_align = Used::kAlign)
206 -> size_t {
207 // Descriptor Table: sizeof(Desc) * queue_size
208 size_t desc_total = static_cast<size_t>(sizeof(Desc)) * queue_size;
209
210 // Available Ring: flags(2) + idx(2) + ring[N](2*N) + used_event(2, 可选)
211 size_t avail_total = sizeof(uint16_t) * (2 + queue_size);
212 if (event_idx) {
213 avail_total += sizeof(uint16_t);
214 }
215
216 // Used Ring: flags(2) + idx(2) + ring[N](sizeof(UsedElem)*N) +
217 // avail_event(2, 可选)
218 size_t used_total = sizeof(uint16_t) * 2 + sizeof(UsedElem) * queue_size;
219 if (event_idx) {
220 used_total += sizeof(uint16_t);
221 }
222
223 // 按对齐要求排列
224 size_t avail_off = AlignUp(desc_total, Avail::kAlign);
225 size_t used_off = AlignUp(avail_off + avail_total, used_align);
226
227 return used_off + used_total;
228 }
229
240 SplitVirtqueue(const DmaRegion& dma, uint16_t queue_size, bool event_idx,
241 size_t used_align = Used::kAlign)
242 : queue_size_(queue_size),
243 phys_base_(dma.phys),
244 event_idx_enabled_(event_idx) {
245 if (!dma.IsValid() || !IsPowerOfTwo(queue_size)) {
246 return;
247 }
248
249 size_t desc_total = static_cast<size_t>(sizeof(Desc)) * queue_size;
250 size_t avail_total = sizeof(uint16_t) * (2 + queue_size);
251 if (event_idx) {
252 avail_total += sizeof(uint16_t);
253 }
254
255 desc_offset_ = 0;
256 avail_offset_ = AlignUp(desc_total, Avail::kAlign);
257 size_t used_total = sizeof(uint16_t) * 2 + sizeof(UsedElem) * queue_size;
258 if (event_idx) {
259 used_total += sizeof(uint16_t);
260 }
261 used_offset_ = AlignUp(avail_offset_ + avail_total, used_align);
262
263 auto* base = dma.Data();
264 desc_ = reinterpret_cast<volatile Desc*>(base + desc_offset_);
265 avail_ = reinterpret_cast<volatile Avail*>(base + avail_offset_);
266 used_ = reinterpret_cast<volatile Used*>(base + used_offset_);
267
268 for (uint16_t i = 0; i < queue_size; ++i) {
269 desc_[i].next = static_cast<uint16_t>(i + 1);
270 }
271 // 末尾描述符使用 sentinel 值,避免越界索引
272 desc_[queue_size - 1].next = 0xFFFF;
273 free_head_ = 0;
274 num_free_ = queue_size;
275 last_used_idx_ = 0;
276
277 is_valid_ = true;
278 }
279
283 [[nodiscard]] auto IsValid() const -> bool { return is_valid_; }
284
298 [[nodiscard]] auto AllocDesc() -> Expected<uint16_t> {
299 if (num_free_ == 0) {
300 return std::unexpected(Error{ErrorCode::kNoFreeDescriptors});
301 }
302
303 uint16_t idx = free_head_;
305 --num_free_;
306
307 return idx;
308 }
309
322 auto FreeDesc(uint16_t idx) -> Expected<void> {
323 if (idx >= queue_size_) {
324 return std::unexpected(Error{ErrorCode::kInvalidDescriptor});
325 }
326 desc_[idx].next = free_head_;
327 free_head_ = idx;
328 ++num_free_;
329 return {};
330 }
331
342 [[nodiscard]] auto GetDesc(uint16_t idx) -> Expected<volatile Desc*> {
343 if (idx >= queue_size_) {
344 return std::unexpected(Error{ErrorCode::kInvalidDescriptor});
345 }
346 return &desc_[idx];
347 }
348
355 [[nodiscard]] auto GetDesc(uint16_t idx) const
357 if (idx >= queue_size_) {
358 return std::unexpected(Error{ErrorCode::kInvalidDescriptor});
359 }
360 return &desc_[idx];
361 }
362
380 auto Submit(uint16_t head) -> void {
381 uint16_t idx = avail_->idx;
382 avail_->ring[idx % queue_size_] = head;
383
384 // 写屏障:确保 ring 写入在 idx 更新之前对设备可见
385 cpu_io::Wmb();
386
387 avail_->idx = idx + 1;
388 }
389
399 [[nodiscard]] auto HasUsed() const -> bool {
400 return last_used_idx_ != used_->idx;
401 }
402
417 [[nodiscard]] auto PopUsed() -> Expected<UsedElem> {
418 if (!HasUsed()) {
419 return std::unexpected(Error{ErrorCode::kNoUsedBuffers});
420 }
421
422 uint16_t idx = last_used_idx_ % queue_size_;
423 UsedElem elem;
424 elem.id = used_->ring[idx].id;
425 elem.len = used_->ring[idx].len;
426
428
429 return elem;
430 }
431
460 [[nodiscard]] auto SubmitChain(const IoVec* readable, size_t readable_count,
461 const IoVec* writable, size_t writable_count)
463 size_t total = readable_count + writable_count;
464 if (total == 0) {
465 return std::unexpected(Error{ErrorCode::kInvalidArgument});
466 }
467 if (num_free_ < static_cast<uint16_t>(total)) {
468 return std::unexpected(Error{ErrorCode::kNoFreeDescriptors});
469 }
470
471 uint16_t head = free_head_;
472 uint16_t prev_idx = 0xFFFF;
473
474 for (size_t i = 0; i < readable_count; ++i) {
475 uint16_t idx = free_head_;
477 --num_free_;
478
479 desc_[idx].addr = readable[i].phys_addr;
480 desc_[idx].len = static_cast<uint32_t>(readable[i].len);
481 desc_[idx].flags = std::to_underlying(DescFlags::kDescFNext);
482
483 if (prev_idx != 0xFFFF) {
484 desc_[prev_idx].next = idx;
485 }
486 prev_idx = idx;
487 }
488
489 for (size_t i = 0; i < writable_count; ++i) {
490 uint16_t idx = free_head_;
492 --num_free_;
493
494 desc_[idx].addr = writable[i].phys_addr;
495 desc_[idx].len = static_cast<uint32_t>(writable[i].len);
496 desc_[idx].flags = std::to_underlying(DescFlags::kDescFNext) |
497 std::to_underlying(DescFlags::kDescFWrite);
498 if (prev_idx != 0xFFFF) {
499 desc_[prev_idx].next = idx;
500 }
501 prev_idx = idx;
502 }
503
504 desc_[prev_idx].flags =
505 desc_[prev_idx].flags & ~std::to_underlying(DescFlags::kDescFNext);
506
507 // 写屏障:确保描述符写入在 Available Ring 更新之前对设备可见
508 cpu_io::Wmb();
509
510 Submit(head);
511
512 return head;
513 }
514
530 auto FreeChain(uint16_t head) -> Expected<void> {
531 if (head >= queue_size_) {
532 return std::unexpected(Error{ErrorCode::kInvalidDescriptor});
533 }
534
535 uint16_t idx = head;
536 while (true) {
537 if (idx >= queue_size_) {
538 return std::unexpected(Error{ErrorCode::kInvalidDescriptor});
539 }
540
541 uint16_t next = desc_[idx].next;
542 bool has_next =
543 (desc_[idx].flags & std::to_underlying(DescFlags::kDescFNext)) != 0;
544
545 desc_[idx].next = free_head_;
546 free_head_ = idx;
547 ++num_free_;
548
549 if (!has_next) {
550 break;
551 }
552 idx = next;
553 }
554
555 return {};
556 }
557
562 [[nodiscard]] auto DescPhys() const -> uint64_t {
563 return phys_base_ + desc_offset_;
564 }
565
570 [[nodiscard]] auto AvailPhys() const -> uint64_t {
571 return phys_base_ + avail_offset_;
572 }
573
578 [[nodiscard]] auto UsedPhys() const -> uint64_t {
579 return phys_base_ + used_offset_;
580 }
581
585 [[nodiscard]] auto Size() const -> uint16_t { return queue_size_; }
586
590 [[nodiscard]] auto NumFree() const -> uint16_t { return num_free_; }
591
598 [[nodiscard]] auto AvailUsedEvent() -> volatile uint16_t* {
599 return event_idx_enabled_ ? avail_->used_event(queue_size_) : nullptr;
600 }
601
602 [[nodiscard]] auto AvailUsedEvent() const -> const volatile uint16_t* {
603 return event_idx_enabled_ ? avail_->used_event(queue_size_) : nullptr;
604 }
605
612 [[nodiscard]] auto UsedAvailEvent() -> volatile uint16_t* {
613 return event_idx_enabled_ ? used_->avail_event(queue_size_) : nullptr;
614 }
615
616 [[nodiscard]] auto UsedAvailEvent() const -> const volatile uint16_t* {
617 return event_idx_enabled_ ? used_->avail_event(queue_size_) : nullptr;
618 }
619
623 [[nodiscard]] auto EventIdxEnabled() const -> bool {
624 return event_idx_enabled_;
625 }
626
630 [[nodiscard]] auto AvailIdx() const -> uint16_t { return avail_->idx; }
631
635 [[nodiscard]] auto LastUsedIdx() const -> uint16_t { return last_used_idx_; }
636
639 SplitVirtqueue() = delete;
641 auto operator=(const SplitVirtqueue&) -> SplitVirtqueue& = delete;
644 : VirtqueueBase(std::move(other)),
645 desc_(other.desc_),
646 avail_(other.avail_),
647 used_(other.used_),
648 queue_size_(other.queue_size_),
649 free_head_(other.free_head_),
650 num_free_(other.num_free_),
651 last_used_idx_(other.last_used_idx_),
652 phys_base_(other.phys_base_),
653 desc_offset_(other.desc_offset_),
654 avail_offset_(other.avail_offset_),
655 used_offset_(other.used_offset_),
656 event_idx_enabled_(other.event_idx_enabled_),
657 is_valid_(other.is_valid_) {
658 other.is_valid_ = false;
659 other.desc_ = nullptr;
660 other.avail_ = nullptr;
661 other.used_ = nullptr;
662 }
663 ~SplitVirtqueue() = default;
665
666 private:
668 volatile Desc* desc_{nullptr};
670 volatile Avail* avail_{nullptr};
672 volatile Used* used_{nullptr};
673
675 uint16_t queue_size_{0};
677 uint16_t free_head_{0};
679 uint16_t num_free_{0};
681 uint16_t last_used_idx_{0};
682
684 uint64_t phys_base_{0};
686 size_t desc_offset_{0};
688 size_t avail_offset_{0};
690 size_t used_offset_{0};
694 bool is_valid_{false};
695};
696
697} // namespace virtio
Split Virtqueue 管理类
Definition split.hpp:38
size_t avail_offset_
Available Ring 在 DMA 内存中的偏移量(字节)
Definition split.hpp:688
auto EventIdxEnabled() const -> bool
检查是否启用了 VIRTIO_F_EVENT_IDX 特性
Definition split.hpp:623
SplitVirtqueue(const SplitVirtqueue &)=delete
size_t used_offset_
Used Ring 在 DMA 内存中的偏移量(字节)
Definition split.hpp:690
auto AvailUsedEvent() -> volatile uint16_t *
获取 Available Ring 的 used_event 字段
Definition split.hpp:598
volatile Avail * avail_
Available Ring 指针(指向 DMA 内存)
Definition split.hpp:670
uint16_t queue_size_
队列大小(描述符数量,必须为 2 的幂)
Definition split.hpp:675
auto SubmitChain(const IoVec *readable, size_t readable_count, const IoVec *writable, size_t writable_count) -> Expected< uint16_t >
提交 Scatter-Gather 描述符链
Definition split.hpp:460
UsedFlags
Used Ring Flags.
Definition split.hpp:66
@ kUsedFNoNotify
驱动不需要通知
static constexpr auto CalcSize(uint16_t queue_size, bool event_idx=true, size_t used_align=Used::kAlign) -> size_t
计算给定队列大小所需的 DMA 内存字节数
Definition split.hpp:203
auto FreeDesc(uint16_t idx) -> Expected< void >
归还描述符到空闲链表
Definition split.hpp:322
auto GetDesc(uint16_t idx) -> Expected< volatile Desc * >
获取描述符的可变引用
Definition split.hpp:342
auto GetDesc(uint16_t idx) const -> Expected< const volatile Desc * >
获取描述符的只读引用
Definition split.hpp:355
auto AvailIdx() const -> uint16_t
获取当前 Available Ring 索引
Definition split.hpp:630
auto LastUsedIdx() const -> uint16_t
获取驱动程序上次处理到的 Used Ring 索引
Definition split.hpp:635
volatile Used * used_
Used Ring 指针(指向 DMA 内存)
Definition split.hpp:672
SplitVirtqueue(SplitVirtqueue &&other) noexcept
Definition split.hpp:643
auto AllocDesc() -> Expected< uint16_t >
从空闲链表分配一个描述符
Definition split.hpp:298
auto operator=(SplitVirtqueue &&) -> SplitVirtqueue &=delete
uint16_t num_free_
空闲描述符数量
Definition split.hpp:679
auto IsValid() const -> bool
检查 virtqueue 是否成功初始化
Definition split.hpp:283
auto DescPhys() const -> uint64_t
获取描述符表的物理地址
Definition split.hpp:562
SplitVirtqueue(const DmaRegion &dma, uint16_t queue_size, bool event_idx, size_t used_align=Used::kAlign)
从预分配的 DMA 缓冲区构造 SplitVirtqueue
Definition split.hpp:240
size_t desc_offset_
描述符表在 DMA 内存中的偏移量(字节)
Definition split.hpp:686
bool event_idx_enabled_
是否启用 VIRTIO_F_EVENT_IDX 特性
Definition split.hpp:692
DescFlags
Descriptor Flags.
Definition split.hpp:44
@ kDescFNext
标记缓冲区通过 next 字段继续
@ kDescFWrite
标记缓冲区为设备只写(否则为设备只读)
@ kDescFIndirect
标记缓冲区包含描述符列表(间接描述符)
auto Size() const -> uint16_t
获取队列大小
Definition split.hpp:585
volatile Desc * desc_
描述符表指针(指向 DMA 内存)
Definition split.hpp:668
auto operator=(const SplitVirtqueue &) -> SplitVirtqueue &=delete
auto HasUsed() const -> bool
检查 Used Ring 中是否有已完成的缓冲区
Definition split.hpp:399
AvailFlags
Available Ring Flags.
Definition split.hpp:57
@ kAvailFNoInterrupt
设备应该不发送中断
bool is_valid_
初始化是否成功
Definition split.hpp:694
auto UsedAvailEvent() -> volatile uint16_t *
获取 Used Ring 的 avail_event 字段
Definition split.hpp:612
uint64_t phys_base_
DMA 内存物理基地址(客户机物理地址)
Definition split.hpp:684
auto NumFree() const -> uint16_t
获取当前空闲描述符数量
Definition split.hpp:590
auto UsedPhys() const -> uint64_t
获取 Used Ring 的物理地址
Definition split.hpp:578
auto UsedAvailEvent() const -> const volatile uint16_t *
Definition split.hpp:616
uint16_t free_head_
空闲描述符链表头索引
Definition split.hpp:677
auto Submit(uint16_t head) -> void
将描述符链提交到 Available Ring
Definition split.hpp:380
auto PopUsed() -> Expected< UsedElem >
从 Used Ring 弹出一个已完成的元素
Definition split.hpp:417
uint16_t last_used_idx_
上次处理到的 Used Ring 索引(用于 PopUsed)
Definition split.hpp:681
auto FreeChain(uint16_t head) -> Expected< void >
释放整条描述符链
Definition split.hpp:530
auto AvailUsedEvent() const -> const volatile uint16_t *
Definition split.hpp:602
auto AvailPhys() const -> uint64_t
获取 Available Ring 的物理地址
Definition split.hpp:570
Virtqueue 基类(C++23 Deducing this 编译期多态)
@ kInvalidArgument
@ kNoFreeDescriptors
没有空闲描述符
@ kInvalidDescriptor
无效的描述符索引
@ kNoUsedBuffers
没有已使用的缓冲区可回收
std::expected< T, Error > Expected
std::expected 别名模板
Definition expected.hpp:365
void Wmb()
Definition cpu_io.h:58
Definition defs.h:9
constexpr auto IsPowerOfTwo(size_t value) -> bool
检查值是否为 2 的幂
Definition misc.hpp:32
constexpr auto AlignUp(size_t value, size_t align) -> size_t
将值向上对齐到指定边界
Definition misc.hpp:20
DMA 可访问内存区域的非拥有描述符
Definition io_buffer.hpp:37
auto Data() const -> uint8_t *
获取虚拟基地址的类型化指针
Definition io_buffer.hpp:52
auto IsValid() const -> bool
检查区域是否有效(非空指针且大小非零)
Definition io_buffer.hpp:46
错误类型,用于 std::expected
Definition expected.hpp:343
Scatter-Gather IO 物理内存向量
Definition misc.hpp:43
Virtqueue Available Ring.
Definition split.hpp:103
uint16_t ring[]
可用描述符头索引数组 ring[queue_size] (little-endian)
Definition split.hpp:113
static constexpr size_t kAlign
Available Ring 对齐要求(字节)
Definition split.hpp:105
auto used_event(uint16_t queue_size) volatile -> volatile uint16_t *
获取 used_event 字段的指针
Definition split.hpp:123
uint16_t flags
标志位: AvailFlags (little-endian)
Definition split.hpp:107
uint16_t idx
驱动程序将下一个描述符条目放入环中的位置(模 queue_size) (little-endian)
Definition split.hpp:109
auto used_event(uint16_t queue_size) const volatile -> const volatile uint16_t *
Definition split.hpp:128
Virtqueue 描述符表条目
Definition split.hpp:80
uint64_t addr
缓冲区的客户机物理地址 (little-endian)
Definition split.hpp:84
uint16_t flags
标志位: DescFlags (little-endian)
Definition split.hpp:88
uint16_t next
下一个描述符的索引(当 flags & kDescFNext 时有效) (little-endian)
Definition split.hpp:90
uint32_t len
缓冲区长度(字节) (little-endian)
Definition split.hpp:86
Virtqueue Used Ring 元素
Definition split.hpp:141
uint32_t len
设备写入描述符链的总字节数 (little-endian)
Definition split.hpp:145
uint32_t id
描述符链头的索引 (little-endian)
Definition split.hpp:143
Virtqueue Used Ring.
Definition split.hpp:158
static constexpr size_t kAlign
Used Ring 对齐要求(字节)
Definition split.hpp:160
UsedElem ring[]
已用描述符元素数组 ring[queue_size]
Definition split.hpp:168
auto avail_event(uint16_t queue_size) const volatile -> const volatile uint16_t *
Definition split.hpp:186
uint16_t idx
设备将下一个描述符条目放入环中的位置(模 queue_size) (little-endian)
Definition split.hpp:164
auto avail_event(uint16_t queue_size) volatile -> volatile uint16_t *
获取 avail_event 字段的指针
Definition split.hpp:178
uint16_t flags
标志位: UsedFlags (little-endian)
Definition split.hpp:162