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
3 changes: 2 additions & 1 deletion Documentation/zigux/phase10-virtio-net-slice.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ Current starter:
- `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
- `VirtioNetProbeLab.decideReceiveQueueRefillNotify()` now turns that bounded reservation plan into a queue-local notify decision that can trigger on empty-queue transitions or descriptor thresholds 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`
- `zig build phase10-virtio-net-tests --build-file zigux/tests/phase10_build.zig --summary all`
- the shared Phase 10 gate now includes `phase10-virtio-net-tests` beside the existing virtio core, ring, input, and survey checks

Non-goals:
Expand Down
61 changes: 61 additions & 0 deletions drivers/net/virtio_net.zig
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,31 @@ pub const ReceiveQueueRefillReservationPlan = struct {
total_allocation_bytes: u32,
};

pub const ReceiveQueueRefillNotifyRequest = struct {
queue_was_empty: bool,
notifications_enabled: bool = true,
notify_after_descriptors: u16 = 0,
};

pub const ReceiveQueueRefillNotifyDecision = struct {
anchor: []const u8,
planned_queue_pairs: u16,
rx_queue_count: u16,
queue_was_empty: bool,
queue_became_non_empty: bool,
notifications_enabled: bool,
notify_after_descriptors: 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,
reached_notify_threshold: bool,
should_notify: bool,
};

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

Expand Down Expand Up @@ -381,6 +406,42 @@ pub const VirtioNetProbeLab = struct {
};
}

pub fn decideReceiveQueueRefillNotify(
self: *Self,
reservation: ReceiveQueueRefillReservationPlan,
request: ReceiveQueueRefillNotifyRequest,
) ReceiveQueueRefillNotifyDecision {
_ = self;
const notify_after_descriptors = if (request.notify_after_descriptors == 0)
reservation.descriptors_per_buffer
else
request.notify_after_descriptors;
const queue_became_non_empty = request.queue_was_empty and reservation.refill_count != 0;
const reached_notify_threshold = reservation.descriptors_reserved != 0 and
reservation.descriptors_reserved >= notify_after_descriptors;

return .{
.anchor = reservation.anchor,
.planned_queue_pairs = reservation.planned_queue_pairs,
.rx_queue_count = reservation.rx_queue_count,
.queue_was_empty = request.queue_was_empty,
.queue_became_non_empty = queue_became_non_empty,
.notifications_enabled = request.notifications_enabled,
.notify_after_descriptors = notify_after_descriptors,
.refill_count = reservation.refill_count,
.descriptors_reserved = reservation.descriptors_reserved,
.buffers_after_reservation = reservation.buffers_after_reservation,
.buffers_left_pending = reservation.buffers_left_pending,
.descriptor_budget_exhausted = reservation.descriptor_budget_exhausted,
.queue_will_be_full = reservation.queue_will_be_full,
.refill_path = reservation.refill_path,
.reached_notify_threshold = reached_notify_threshold,
.should_notify = request.notifications_enabled and
reservation.descriptors_reserved != 0 and
(queue_became_non_empty or reached_notify_threshold),
};
}

fn checkedMulU16(lhs: u16, rhs: u16) !u16 {
const value = @as(u32, lhs) * rhs;
return std.math.cast(u16, value) orelse error.QueueCountOverflow;
Expand Down
83 changes: 83 additions & 0 deletions zigux/tests/phase10_virtio_net.zig
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,89 @@ test "phase10 virtio net reserves refill descriptors without widening into live
try std.testing.expectEqual(@as(u32, 25600), recycled_reservation.total_allocation_bytes);
}

test "phase10 virtio net makes a bounded notify decision from the queued refill reservation" {
var device = try virtio_net.VirtioNetProbeLab.init(&.{
virtio_net.feature_mergeable_rx_buffers,
virtio_net.feature_control_vq,
virtio_net.feature_multiqueue,
});
const snapshot = try 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 device.planMergeableReceiveBuffer(snapshot, .{
.header_len = 12,
.average_packet_len = 1500,
.min_buf_len = 512,
.headroom = 256,
.cache_line_size = 64,
.skb_shared_info_size = 320,
});

const reservation = try device.reserveReceiveQueueRefillDescriptors(.{
.queue_capacity = 256,
.buffers_posted = 192,
.batch_limit = 32,
.descriptors_available = 48,
.descriptors_per_buffer = 2,
});

const empty_transition = device.decideReceiveQueueRefillNotify(reservation, .{
.queue_was_empty = true,
.notify_after_descriptors = 64,
});
try std.testing.expectEqualStrings("drivers/net/virtio_net.c", empty_transition.anchor);
try std.testing.expect(empty_transition.queue_became_non_empty);
try std.testing.expectEqual(@as(u16, 64), empty_transition.notify_after_descriptors);
try std.testing.expect(!empty_transition.reached_notify_threshold);
try std.testing.expect(empty_transition.should_notify);

const threshold_only = device.decideReceiveQueueRefillNotify(reservation, .{
.queue_was_empty = false,
.notify_after_descriptors = 48,
});
try std.testing.expect(!threshold_only.queue_became_non_empty);
try std.testing.expect(threshold_only.reached_notify_threshold);
try std.testing.expect(threshold_only.should_notify);

const suppressed = device.decideReceiveQueueRefillNotify(reservation, .{
.queue_was_empty = true,
.notifications_enabled = false,
.notify_after_descriptors = 16,
});
try std.testing.expect(suppressed.queue_became_non_empty);
try std.testing.expect(suppressed.reached_notify_threshold);
try std.testing.expect(!suppressed.should_notify);

const no_threshold = device.decideReceiveQueueRefillNotify(reservation, .{
.queue_was_empty = false,
.notify_after_descriptors = 64,
});
try std.testing.expect(!no_threshold.queue_became_non_empty);
try std.testing.expect(!no_threshold.reached_notify_threshold);
try std.testing.expect(!no_threshold.should_notify);

const exhausted_reservation = try device.reserveReceiveQueueRefillDescriptors(.{
.queue_capacity = 256,
.buffers_posted = 192,
.batch_limit = 32,
.descriptors_available = 0,
.descriptors_per_buffer = 2,
});
const no_descriptors = device.decideReceiveQueueRefillNotify(exhausted_reservation, .{
.queue_was_empty = true,
});
try std.testing.expect(!no_descriptors.queue_became_non_empty);
try std.testing.expect(!no_descriptors.reached_notify_threshold);
try std.testing.expect(!no_descriptors.should_notify);
try std.testing.expect(no_descriptors.descriptor_budget_exhausted);
}

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());
Expand Down
Loading