Skip to content
Open
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
7 changes: 6 additions & 1 deletion arch/x64/exceptions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ unsigned interrupt_descriptor_table::register_interrupt_handler(
}
}
}
abort();
// No free IDT vector. Vectors 0..31 are CPU exceptions and are never
// returned here, so 0 is an unambiguous "exhausted" sentinel. Returning it
// rather than aborting lets MSI-X allocation (msix_vector) fail gracefully
// and lets a device fall back to fewer queues instead of crashing the boot
// when many multiqueue devices exhaust the 224 usable vectors.
return 0;
}

void interrupt_descriptor_table::unregister_handler(unsigned vector)
Expand Down
17 changes: 15 additions & 2 deletions drivers/msi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ msix_vector::msix_vector(pci::function* dev)

msix_vector::~msix_vector()
{
idt.unregister_handler(_vector);
// _vector == 0 means IDT vector allocation failed (see the ctor); there is
// nothing to unregister in that case.
if (_vector) {
idt.unregister_handler(_vector);
}
}

pci::function* msix_vector::get_pci_function(void)
Expand Down Expand Up @@ -198,7 +202,16 @@ std::vector<msix_vector*> interrupt_manager::request_vectors(unsigned num_vector
auto num = std::min(num_vectors, num_entries);

for (unsigned i = 0; i < num; ++i) {
results.push_back(new msix_vector(_dev));
auto* vec = new msix_vector(_dev);
// A zero vector means the global IDT vector pool is exhausted. Stop
// here and return however many we did get; easy_register() treats a
// short result as failure so the caller can fall back to fewer queues
// rather than crash.
if (!vec->get_vector()) {
delete vec;
break;
}
results.push_back(vec);
}

return (results);
Expand Down
14 changes: 14 additions & 0 deletions drivers/virtio-blk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,20 @@ blk::blk(virtio_device& virtio_dev)
// base class did not set up.
_num_queues = virtio_driver::_num_queues;

// Cap the number of queues we arm with an interrupt against the shared
// MSI-X vector budget. Each queue uses one vector, and the x86-64 IDT has
// only 224 usable vectors shared by every device; a high-vCPU guest with
// several multiqueue virtio devices can otherwise exhaust them at boot.
// Extra probed virtqueues beyond this cap are simply left unused - I/O is
// directed only to queues 0.._num_queues-1, all of which have a serviced
// interrupt.
unsigned granted = reserve_msix_vectors(_num_queues);
if (granted < (unsigned)_num_queues) {
virtio_i("virtio-blk: capping active queues from %d to %d (MSI-X budget)\n",
_num_queues, granted);
_num_queues = granted;
}

// Resize per-queue lock vector now that _num_queues is known.
_queue_locks = std::vector<mutex>(_num_queues);

Expand Down
34 changes: 34 additions & 0 deletions drivers/virtio.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/

#include <string.h>
#include <atomic>
#include <algorithm>

#include "drivers/virtio.hh"
#include "virtio-vring.hh"
Expand All @@ -19,6 +21,38 @@ namespace virtio {

int virtio_driver::_disk_idx = 0;

// Process-wide MSI-X vector budget shared by all virtio devices. The x86-64
// IDT exposes 224 usable vectors (32..255); GSI/legacy IRQs, the APIC timer
// and a few IPIs also consume some, so we hand out at most a conservative
// fraction to per-queue MSI-X and leave headroom. Each multiqueue device caps
// its interrupt-bearing queue count against what remains here so that a
// high-vCPU guest with several such devices still boots. This is a soft cap:
// it only limits how many queues a device arms with a distinct interrupt, not
// how many virtqueues exist.
static std::atomic<int> msix_vector_budget{192};

unsigned virtio_driver::reserve_msix_vectors(unsigned want)
{
if (want == 0) {
return 0;
}
int remaining = msix_vector_budget.load(std::memory_order_relaxed);
while (true) {
if (remaining <= 0) {
// Pool empty: still allow a single vector so the device is usable
// (it will run single-queue / shared-interrupt).
return 1;
}
unsigned grant = std::min<unsigned>(want, (unsigned)remaining);
if (msix_vector_budget.compare_exchange_weak(
remaining, remaining - (int)grant,
std::memory_order_relaxed)) {
return grant;
}
// remaining reloaded by compare_exchange_weak; retry.
}
}

virtio_driver::virtio_driver(virtio_device& dev)
: hw_driver()
, _dev(dev)
Expand Down
10 changes: 10 additions & 0 deletions drivers/virtio.hh
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ public:

size_t get_vring_alignment() { return _dev.get_vring_alignment();}

// Reserve up to `want` MSI-X vectors from a process-wide budget and return
// how many are actually available. The x86-64 IDT has only 224 usable
// vectors (32..255) shared by every device, so a high-vCPU guest with
// several multiqueue virtio devices can exhaust them and crash at boot.
// Drivers that scale their queue count with the vCPU count (for example
// virtio-blk multiqueue) call this to cap the number of queues they arm
// with an interrupt, so the total stays within the budget and every device
// still gets at least one vector. Returns 0 only if the pool is empty.
static unsigned reserve_msix_vectors(unsigned want);

protected:
// Actual drivers should implement this on top of the basic ring features
virtual u64 get_driver_features() { return 1 << VIRTIO_RING_F_INDIRECT_DESC | 1 << VIRTIO_RING_F_EVENT_IDX; }
Expand Down