Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/zigux/phase10-virtio-net-slice.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Current starter:
- the new helper records aligned room, requested receive-buffer length, requested allocation length, and whether the plan is using recycled room from a prior mergeable refill step
- `VirtioNetProbeLab.summarizeReceiveQueueRefill()` now turns the last bounded mergeable-buffer plan into an explicit refill-path summary so the lab slice can distinguish fresh mergeable allocation from recycled-room reuse without widening into page ownership or receive completion
- `VirtioNetProbeLab.planReceiveQueueRefillBatch()` now turns that same bounded refill summary into queue-slot and byte counts for one refill pass, including a clampable batch limit and a fail-closed overfill guard, without widening into DMA submission, kicks, or receive completion
- `VirtioNetProbeLab.reserveReceiveQueueRefillDescriptors()` now clamps one refill pass against the descriptors currently available on the receive queue, yielding one bounded reservation plan with pending-buffer carryover while still stopping short of live descriptor writes, DMA submission, or queue kicks

Validation:
- `zig build test --build-file zigux/tests/phase10_build.zig --summary all`
Expand Down
68 changes: 67 additions & 1 deletion drivers/net/virtio_net.zig
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,34 @@ pub const ReceiveQueueRefillBatchPlan = struct {
total_allocation_bytes: u32,
};

pub const ReceiveQueueRefillReservationRequest = struct {
queue_capacity: u16,
buffers_posted: u16,
batch_limit: u16 = 0,
descriptors_available: u16,
descriptors_per_buffer: u16 = 1,
};

pub const ReceiveQueueRefillReservationPlan = struct {
anchor: []const u8,
planned_queue_pairs: u16,
rx_queue_count: u16,
queue_capacity: u16,
buffers_posted: u16,
descriptors_available: u16,
descriptors_per_buffer: u16,
requested_refill_count: u16,
refill_count: u16,
descriptors_reserved: u16,
buffers_after_reservation: u16,
buffers_left_pending: u16,
descriptor_budget_exhausted: bool,
queue_will_be_full: bool,
refill_path: ReceiveQueueRefillPath,
total_posted_bytes: u32,
total_allocation_bytes: u32,
};

pub const VirtioNetProbeLab = struct {
const Self = @This();

Expand Down Expand Up @@ -315,6 +343,44 @@ pub const VirtioNetProbeLab = struct {
};
}

pub fn reserveReceiveQueueRefillDescriptors(
self: *Self,
request: ReceiveQueueRefillReservationRequest,
) !ReceiveQueueRefillReservationPlan {
if (request.descriptors_per_buffer == 0) return error.InvalidDescriptorsPerBuffer;

const summary = try self.summarizeReceiveQueueRefill();
const batch = try self.planReceiveQueueRefillBatch(.{
.queue_capacity = request.queue_capacity,
.buffers_posted = request.buffers_posted,
.batch_limit = request.batch_limit,
});
const descriptor_budget = request.descriptors_available / request.descriptors_per_buffer;
const refill_count = @min(batch.refill_count, descriptor_budget);
const descriptors_reserved = try checkedMulU16(refill_count, request.descriptors_per_buffer);
const buffers_after_reservation = try checkedAddU16(request.buffers_posted, refill_count);

return .{
.anchor = batch.anchor,
.planned_queue_pairs = batch.planned_queue_pairs,
.rx_queue_count = batch.rx_queue_count,
.queue_capacity = batch.queue_capacity,
.buffers_posted = batch.buffers_posted,
.descriptors_available = request.descriptors_available,
.descriptors_per_buffer = request.descriptors_per_buffer,
.requested_refill_count = batch.refill_count,
.refill_count = refill_count,
.descriptors_reserved = descriptors_reserved,
.buffers_after_reservation = buffers_after_reservation,
.buffers_left_pending = batch.refill_count - refill_count,
.descriptor_budget_exhausted = refill_count < batch.refill_count,
.queue_will_be_full = buffers_after_reservation == batch.queue_capacity,
.refill_path = batch.refill_path,
.total_posted_bytes = try checkedMulU32(summary.requested_len, refill_count),
.total_allocation_bytes = try checkedMulU32(summary.requested_alloc_len, refill_count),
};
}

fn checkedMulU16(lhs: u16, rhs: u16) !u16 {
const value = @as(u32, lhs) * rhs;
return std.math.cast(u16, value) orelse error.QueueCountOverflow;
Expand All @@ -339,4 +405,4 @@ pub const VirtioNetProbeLab = struct {
const widened = std.mem.alignForward(u64, value, alignment);
return std.math.cast(u32, widened) orelse error.BufferLengthOverflow;
}
};
}
91 changes: 91 additions & 0 deletions zigux/tests/phase10_virtio_net.zig
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,98 @@ test "phase10 virtio net plans bounded receive queue refill batches from the las
try std.testing.expectEqual(@as(u32, 25600), recycled_batch.total_allocation_bytes);
}

test "phase10 virtio net reserves refill descriptors without widening into live submission" {
var fresh_device = try virtio_net.VirtioNetProbeLab.init(&.{
virtio_net.feature_mergeable_rx_buffers,
virtio_net.feature_control_vq,
virtio_net.feature_multiqueue,
});
const fresh_snapshot = try fresh_device.captureProbeSnapshot(.{
.driver_feature_bits = &.{
virtio_net.feature_mergeable_rx_buffers,
virtio_net.feature_control_vq,
virtio_net.feature_multiqueue,
},
.requested_queue_pairs = 2,
.max_queue_pairs = 2,
});
_ = try fresh_device.planMergeableReceiveBuffer(fresh_snapshot, .{
.header_len = 12,
.average_packet_len = 1500,
.min_buf_len = 512,
.headroom = 256,
.cache_line_size = 64,
.skb_shared_info_size = 320,
});

const fresh_reservation = try fresh_device.reserveReceiveQueueRefillDescriptors(.{
.queue_capacity = 256,
.buffers_posted = 192,
.batch_limit = 32,
.descriptors_available = 48,
.descriptors_per_buffer = 2,
});
try std.testing.expectEqualStrings("drivers/net/virtio_net.c", fresh_reservation.anchor);
try std.testing.expectEqual(@as(u16, 32), fresh_reservation.requested_refill_count);
try std.testing.expectEqual(@as(u16, 24), fresh_reservation.refill_count);
try std.testing.expectEqual(@as(u16, 48), fresh_reservation.descriptors_reserved);
try std.testing.expectEqual(@as(u16, 216), fresh_reservation.buffers_after_reservation);
try std.testing.expectEqual(@as(u16, 8), fresh_reservation.buffers_left_pending);
try std.testing.expect(fresh_reservation.descriptor_budget_exhausted);
try std.testing.expect(!fresh_reservation.queue_will_be_full);
try std.testing.expectEqual(virtio_net.ReceiveQueueRefillPath.mergeable_allocation, fresh_reservation.refill_path);
try std.testing.expectEqual(@as(u32, 36864), fresh_reservation.total_posted_bytes);
try std.testing.expectEqual(@as(u32, 50688), fresh_reservation.total_allocation_bytes);

var recycled_device = try virtio_net.VirtioNetProbeLab.init(&.{
virtio_net.feature_mergeable_rx_buffers,
virtio_net.feature_control_vq,
});
const recycled_snapshot = try recycled_device.captureProbeSnapshot(.{
.driver_feature_bits = &.{
virtio_net.feature_mergeable_rx_buffers,
virtio_net.feature_control_vq,
},
.requested_queue_pairs = 1,
.max_queue_pairs = 1,
});
_ = try recycled_device.planMergeableReceiveBuffer(recycled_snapshot, .{
.header_len = 12,
.average_packet_len = 900,
.min_buf_len = 256,
.recycled_room = 896,
});

const recycled_reservation = try recycled_device.reserveReceiveQueueRefillDescriptors(.{
.queue_capacity = 128,
.buffers_posted = 120,
.descriptors_available = 16,
.descriptors_per_buffer = 2,
});
try std.testing.expectEqual(@as(u16, 8), recycled_reservation.requested_refill_count);
try std.testing.expectEqual(@as(u16, 8), recycled_reservation.refill_count);
try std.testing.expectEqual(@as(u16, 16), recycled_reservation.descriptors_reserved);
try std.testing.expectEqual(@as(u16, 128), recycled_reservation.buffers_after_reservation);
try std.testing.expectEqual(@as(u16, 0), recycled_reservation.buffers_left_pending);
try std.testing.expect(!recycled_reservation.descriptor_budget_exhausted);
try std.testing.expect(recycled_reservation.queue_will_be_full);
try std.testing.expectEqual(virtio_net.ReceiveQueueRefillPath.recycled_room, recycled_reservation.refill_path);
try std.testing.expectEqual(@as(u32, 25600), recycled_reservation.total_posted_bytes);
try std.testing.expectEqual(@as(u32, 25600), recycled_reservation.total_allocation_bytes);
}

test "phase10 virtio net rejects mergeable buffer plans that widen beyond the negotiated safe path" {
var untouched_device = try virtio_net.VirtioNetProbeLab.init(&.{virtio_net.feature_mergeable_rx_buffers});
try std.testing.expectError(error.MergeableBufferPlanUnavailable, untouched_device.summarizeReceiveQueueRefill());
try std.testing.expectError(error.MergeableBufferPlanUnavailable, untouched_device.planReceiveQueueRefillBatch(.{
.queue_capacity = 64,
.buffers_posted = 0,
}));
try std.testing.expectError(error.MergeableBufferPlanUnavailable, untouched_device.reserveReceiveQueueRefillDescriptors(.{
.queue_capacity = 64,
.buffers_posted = 0,
.descriptors_available = 4,
}));

var non_mergeable = try virtio_net.VirtioNetProbeLab.init(&.{virtio_net.feature_control_vq});
const non_mergeable_snapshot = try non_mergeable.captureProbeSnapshot(.{
Expand Down Expand Up @@ -310,4 +395,10 @@ test "phase10 virtio net rejects mergeable buffer plans that widen beyond the ne
.queue_capacity = 8,
.buffers_posted = 9,
}));
try std.testing.expectError(error.InvalidDescriptorsPerBuffer, device.reserveReceiveQueueRefillDescriptors(.{
.queue_capacity = 8,
.buffers_posted = 4,
.descriptors_available = 8,
.descriptors_per_buffer = 0,
}));
}
Loading