Skip to content

Commit 3d5be44

Browse files
committed
Refactor mock ring accessor API; add typed accessors
- Introduces the RingAccess trait for endianness conversion in mock ring elements - Adds typed accessor methods (e.g., load_flags, store_ring) to SplitQueueRing - Refactors test/mocks to use the new accessors instead of raw Ref/ArrayRef and manual conversion - Adds minimal test coverage for flag and event accessors - Fixes #123 Signed-off-by: Nelson Wong <[email protected]>
1 parent d3795a5 commit 3d5be44

File tree

5 files changed

+163
-23
lines changed

5 files changed

+163
-23
lines changed

virtio-queue/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
## Added
44

5+
- Introduced `RingAccess` trait to handle endianness conversion of ring elements in mock tests.
6+
- Added typed accessor methods to `SplitQueueRing` for safer and more readable load/store of ring fields (e.g., `load_idx`, `store_ring_entry`).
7+
58
## Changed
69

10+
- Refactored tests and mock infrastructure to use typed accessors instead of raw `Ref`/`ArrayRef` and manual endianness conversion.
11+
- Made `SplitQueueRing` generic over `RingAccess` types.
12+
713
## Fixed
814

915
# v0.16.0

virtio-queue/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ following ones:
151151
* `AvailIter` - is a consuming iterator over all available descriptor chain
152152
heads in the queue.
153153

154+
### Queue helper methods
155+
156+
To help with directly manipulating the memory backing a queue, the test utilities expose a set of convenience methods:
157+
158+
* `load_idx` / `store_idx` – read or write the ring `idx` field.
159+
* `load_ring_entry` / `store_ring_entry` – read or write individual ring entries.
160+
* `load_flags` / `store_flags` and `load_event` / `store_event` – access the auxiliary fields of the rings.
161+
* `start` / `end` – get the guest address range covered by the ring.
162+
163+
These helpers are available on the objects returned by the `avail()` and `used()` accessors provided by `MockSplitQueue` and make it easy to set up a queue in tests or simple examples.
164+
154165
## Save/Restore Queue
155166

156167
The `Queue` allows saving the state through the `state` function which returns

virtio-queue/src/desc/split.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ use virtio_bindings::bindings::virtio_ring::{
3737
/// # let desc = RawDescriptor::from(SplitDescriptor::new(0x2000, 0x1000, VRING_DESC_F_WRITE as u16, 0));
3838
/// # vq.desc_table().store(1, desc);
3939
/// #
40-
/// # vq.avail().ring().ref_at(0).unwrap().store(u16::to_le(0));
41-
/// # vq.avail().idx().store(u16::to_le(1));
40+
/// # vq.avail().store_ring_entry(0, 0).unwrap();
41+
/// # vq.avail().store_idx(1);
4242
/// # q
4343
/// # }
4444
/// let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();

virtio-queue/src/mock.rs

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,37 @@ impl<'a, M: GuestMemory, T: ByteValued> ArrayRef<'a, M, T> {
117117
}
118118
}
119119

120+
/// Trait for converting queue values to and from little-endian representation.
121+
pub trait RingAccess: ByteValued + Copy {
122+
/// Convert from host to little-endian.
123+
fn to_le(self) -> Self;
124+
/// Convert from little-endian to host.
125+
fn from_le(val: Self) -> Self;
126+
}
127+
128+
impl RingAccess for u16 {
129+
fn to_le(self) -> Self {
130+
u16::to_le(self)
131+
}
132+
133+
fn from_le(val: Self) -> Self {
134+
u16::from_le(val)
135+
}
136+
}
137+
138+
impl RingAccess for VirtqUsedElem {
139+
fn to_le(self) -> Self {
140+
self
141+
}
142+
143+
fn from_le(val: Self) -> Self {
144+
val
145+
}
146+
}
147+
120148
/// Represents a virtio queue ring. The only difference between the used and available rings,
121149
/// is the ring element type.
122-
pub struct SplitQueueRing<'a, M, T: ByteValued> {
150+
pub struct SplitQueueRing<'a, M, T: RingAccess> {
123151
flags: Ref<'a, M, u16>,
124152
// The value stored here should more precisely be a `Wrapping<u16>`, but that would require a
125153
// `ByteValued` impl for this type, which is not provided in vm-memory. Implementing the trait
@@ -131,7 +159,7 @@ pub struct SplitQueueRing<'a, M, T: ByteValued> {
131159
event: Ref<'a, M, u16>,
132160
}
133161

134-
impl<'a, M: GuestMemory, T: ByteValued> SplitQueueRing<'a, M, T> {
162+
impl<'a, M: GuestMemory, T: RingAccess> SplitQueueRing<'a, M, T> {
135163
/// Create a new `SplitQueueRing` instance
136164
pub fn new(mem: &'a M, base: GuestAddress, len: u16) -> Self {
137165
let event_addr = base
@@ -165,14 +193,44 @@ impl<'a, M: GuestMemory, T: ByteValued> SplitQueueRing<'a, M, T> {
165193
.unwrap()
166194
}
167195

168-
/// Return a reference to the idx field.
169-
pub fn idx(&self) -> &Ref<'a, M, u16> {
170-
&self.idx
196+
/// Load the value of the `flags` field.
197+
pub fn load_flags(&self) -> u16 {
198+
u16::from_le(self.flags.load())
199+
}
200+
201+
/// Store the `flags` field.
202+
pub fn store_flags(&self, val: u16) {
203+
self.flags.store(u16::to_le(val))
171204
}
172205

173-
/// Return a reference to the ring field.
174-
pub fn ring(&self) -> &ArrayRef<'a, M, T> {
175-
&self.ring
206+
/// Load the value of the `idx` field.
207+
pub fn load_idx(&self) -> u16 {
208+
u16::from_le(self.idx.load())
209+
}
210+
211+
/// Store the `idx` field.
212+
pub fn store_idx(&self, val: u16) {
213+
self.idx.store(u16::to_le(val))
214+
}
215+
216+
/// Load a ring entry at `index`.
217+
pub fn load_ring_entry(&self, index: usize) -> Result<T, MockError> {
218+
self.ring.ref_at(index).map(|r| T::from_le(r.load()))
219+
}
220+
221+
/// Store a ring entry at `index`.
222+
pub fn store_ring_entry(&self, index: usize, val: T) -> Result<(), MockError> {
223+
self.ring.ref_at(index).map(|r| r.store(val.to_le()))
224+
}
225+
226+
/// Load the value of the event field.
227+
pub fn load_event(&self) -> u16 {
228+
u16::from_le(self.event.load())
229+
}
230+
231+
/// Store the event field.
232+
pub fn store_event(&self, val: u16) {
233+
self.event.store(u16::to_le(val))
176234
}
177235
}
178236

@@ -523,3 +581,68 @@ impl<'a, M: GuestMemory> MockSplitQueue<'a, M> {
523581
Ok(())
524582
}
525583
}
584+
585+
#[cfg(test)]
586+
mod tests {
587+
use super::*;
588+
use vm_memory::{GuestAddress, GuestMemoryMmap};
589+
590+
// SplitQueueRing load/store API coverage for AvailRing (u16)
591+
#[test]
592+
fn test_avail_ring_load_store() {
593+
let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
594+
let len = 8u16;
595+
let base = GuestAddress(0x1000);
596+
let ring: AvailRing<_> = AvailRing::new(mem, base, len);
597+
598+
// flags
599+
ring.store_flags(0x55aa);
600+
assert_eq!(ring.load_flags(), 0x55aa);
601+
602+
// idx
603+
ring.store_idx(7);
604+
assert_eq!(ring.load_idx(), 7);
605+
606+
// ring entry
607+
ring.store_ring_entry(3, 0xbeef).unwrap();
608+
assert_eq!(ring.load_ring_entry(3).unwrap(), 0xbeef);
609+
610+
// event field
611+
ring.store_event(0x1234);
612+
assert_eq!(ring.load_event(), 0x1234);
613+
614+
// out-of-bounds must error
615+
assert!(matches!(
616+
ring.store_ring_entry(len as usize, 0).unwrap_err(),
617+
MockError::InvalidIndex
618+
));
619+
}
620+
621+
// SplitQueueRing load/store API coverage for UsedRing (VirtqUsedElem)
622+
#[test]
623+
fn test_used_ring_load_store() {
624+
let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x20000)]).unwrap();
625+
let len = 8u16;
626+
let base = GuestAddress(0x3000);
627+
let ring: UsedRing<_> = UsedRing::new(mem, base, len);
628+
629+
// flags
630+
ring.store_flags(0xccdd);
631+
assert_eq!(ring.load_flags(), 0xccdd);
632+
633+
// idx
634+
ring.store_idx(2);
635+
assert_eq!(ring.load_idx(), 2);
636+
637+
// ring entry
638+
let elem = VirtqUsedElem::new(42, 0x1000);
639+
ring.store_ring_entry(0, elem).unwrap();
640+
let read = ring.load_ring_entry(0).unwrap();
641+
assert_eq!(read.id(), 42);
642+
assert_eq!(read.len(), 0x1000);
643+
644+
// event field
645+
ring.store_event(0xdead);
646+
assert_eq!(ring.load_event(), 0xdead);
647+
}
648+
}

virtio-queue/src/queue.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -871,19 +871,19 @@ mod tests {
871871
let mut q: Queue = vq.create_queue().unwrap();
872872

873873
assert_eq!(q.used_idx(mem, Ordering::Acquire).unwrap(), Wrapping(0));
874-
assert_eq!(u16::from_le(vq.used().idx().load()), 0);
874+
assert_eq!(vq.used().load_idx(), 0);
875875

876876
// index too large
877877
assert!(q.add_used(mem, 16, 0x1000).is_err());
878-
assert_eq!(u16::from_le(vq.used().idx().load()), 0);
878+
assert_eq!(vq.used().load_idx(), 0);
879879

880880
// should be ok
881881
q.add_used(mem, 1, 0x1000).unwrap();
882882
assert_eq!(q.next_used, Wrapping(1));
883883
assert_eq!(q.used_idx(mem, Ordering::Acquire).unwrap(), Wrapping(1));
884-
assert_eq!(u16::from_le(vq.used().idx().load()), 1);
884+
assert_eq!(vq.used().load_idx(), 1);
885885

886-
let x = vq.used().ring().ref_at(0).unwrap().load();
886+
let x = vq.used().load_ring_entry(0).unwrap();
887887
assert_eq!(x.id(), 1);
888888
assert_eq!(x.len(), 0x1000);
889889
}
@@ -1075,7 +1075,7 @@ mod tests {
10751075
// Update the index of the chain that can be consumed to not be the last one.
10761076
// This enables us to consume chains in multiple iterations as opposed to consuming
10771077
// all the driver written chains at once.
1078-
vq.avail().idx().store(u16::to_le(2));
1078+
vq.avail().store_idx(2);
10791079
// No descriptor chains are consumed at this point.
10801080
assert_eq!(q.next_avail(), 0);
10811081

@@ -1108,7 +1108,7 @@ mod tests {
11081108
assert_eq!(q.next_avail(), 2);
11091109
assert_eq!(q.next_used(), 2);
11101110
// Let the device know it can consume one more chain.
1111-
vq.avail().idx().store(u16::to_le(3));
1111+
vq.avail().store_idx(3);
11121112
i = 0;
11131113

11141114
loop {
@@ -1132,7 +1132,7 @@ mod tests {
11321132
// ring. Ideally this should be done on a separate thread.
11331133
// Because of this update, the loop should be iterated again to consume the new
11341134
// available descriptor chains.
1135-
vq.avail().idx().store(u16::to_le(4));
1135+
vq.avail().store_idx(4);
11361136
if !q.enable_notification(mem).unwrap() {
11371137
break;
11381138
}
@@ -1144,7 +1144,7 @@ mod tests {
11441144

11451145
// Set an `idx` that is bigger than the number of entries added in the ring.
11461146
// This is an allowed scenario, but the indexes of the chain will have unexpected values.
1147-
vq.avail().idx().store(u16::to_le(7));
1147+
vq.avail().store_idx(7);
11481148
loop {
11491149
q.disable_notification(mem).unwrap();
11501150

@@ -1199,7 +1199,7 @@ mod tests {
11991199

12001200
vq.add_desc_chains(&descs, 0).unwrap();
12011201
// Let the device know it can consume chains with the index < 2.
1202-
vq.avail().idx().store(u16::to_le(3));
1202+
vq.avail().store_idx(3);
12031203
// No descriptor chains are consumed at this point.
12041204
assert_eq!(q.next_avail(), 0);
12051205
assert_eq!(q.next_used(), 0);
@@ -1232,7 +1232,7 @@ mod tests {
12321232

12331233
// Decrement `idx` which should be forbidden. We don't enforce this thing, but we should
12341234
// test that we don't panic in case the driver decrements it.
1235-
vq.avail().idx().store(u16::to_le(1));
1235+
vq.avail().store_idx(1);
12361236
// Invalid available ring index
12371237
assert!(q.iter(mem).is_err());
12381238
}
@@ -1269,16 +1269,16 @@ mod tests {
12691269
// When the number of chains exposed by the driver is equal to or less than the queue
12701270
// size, the available ring index is valid and constructs an iterator successfully.
12711271
let avail_idx = Wrapping(q.next_avail()) + Wrapping(queue_size);
1272-
vq.avail().idx().store(u16::to_le(avail_idx.0));
1272+
vq.avail().store_idx(avail_idx.0);
12731273
assert!(q.iter(mem).is_ok());
12741274
let avail_idx = Wrapping(q.next_avail()) + Wrapping(queue_size - 1);
1275-
vq.avail().idx().store(u16::to_le(avail_idx.0));
1275+
vq.avail().store_idx(avail_idx.0);
12761276
assert!(q.iter(mem).is_ok());
12771277

12781278
// When the number of chains exposed by the driver is larger than the queue size, the
12791279
// available ring index is invalid and produces an error from constructing an iterator.
12801280
let avail_idx = Wrapping(q.next_avail()) + Wrapping(queue_size + 1);
1281-
vq.avail().idx().store(u16::to_le(avail_idx.0));
1281+
vq.avail().store_idx(avail_idx.0);
12821282
assert!(q.iter(mem).is_err());
12831283
}
12841284

0 commit comments

Comments
 (0)