DISCLAIMER:
The following has been written with the aid of ChatGPT. The mechanisms
have been ideated, curated and revised by a human being before their
submission for your attention.
Please don't make this the occasion for a flamewar, I know that AI as
well as modern PCI architecture are nasty topics.
I post it here also because I think it is good information material.
I don't plan to implement this in the immediate future. Please feel
free and encouraged to ignore this message if it doesn't interest you.
END OF DISCLAIMER

GNU Mach currently exposes hardware interrupts to userspace through
the `irq` device. This interface is primarily designed around legacy
IRQs and I/O APIC Global System Interrupts (GSIs). An interrupt is
identified by an integer IRQ number, GNU Mach controls its routing and
masking, and userspace receives `DEVICE_INTR_NOTIFY` notifications
through a Mach port.

This model does not map naturally to PCI MSI-X. MSI-X devices generate
interrupts by issuing messages directly to a Local APIC, with each
MSI-X table entry specifying an interrupt vector and destination
processor. There is no I/O APIC interrupt line corresponding to such
an interrupt.

This RFC proposes a capability-based interrupt-vector facility for GNU
Mach intended to provide proper MSI-X support while preserving a
microkernel-oriented division of responsibilities. GNU Mach retains
control of the IDT, Local APIC interrupt entry, LAPIC EOI handling,
and protection of reserved interrupt vectors. Userspace controls
interrupt allocation policy, processor affinity, MSI-X configuration,
and delegation to device drivers.

Interrupt-vector allocation authority is represented by a recursively
delegatable Mach capability, `interrupt_vector_space_t`. A vector
space describes a subset of the available `(processor, vector)`
namespace. Any holder of such a capability may subdivide its remaining
authority and delegate a subset to another task.

An eventual device driver may therefore choose the processor and APIC
vector itself, because it may have the best knowledge of device
queues, interrupt affinity requirements, and hardware topology.

Allocating one `(processor, vector)` tuple from an
`interrupt_vector_space_t` produces an `interrupt_vector_t` capability
representing that concrete interrupt endpoint.

This design naturally supports hierarchical PCI arbitration and sub-Hurds:

```text
GNU Mach
    ↓
root PCI arbiter
    ↓
nested PCI arbiter
    ↓
sub-Hurd PCI arbiter
    ↓
device driver
```

At every level, authority may be further subdivided. The
PCI-arbitration hierarchy is not involved in the interrupt-delivery
fast path.

---

# 1. Motivation

GNU Mach's current userspace interrupt interface is centered around
the `irq` device and integer IRQ identifiers.

Conceptually, the existing path is:

```text
PCI INTx
   │
   ▼
I/O APIC GSI
   │
   ▼
GNU Mach IRQ
   │
   ▼
user_irq_handler()
   │
   ▼
DEVICE_INTR_NOTIFY
   │
   ▼
userspace driver
```

Registration is performed through an interface equivalent to:

```c
device_intr_register(
        irq_device,
        irq_number,
        flags,
        notification_port);
```

GNU Mach owns interrupt routing and masks the IRQ while userspace
handles it. Userspace subsequently acknowledges the notification with
`device_intr_ack()`, allowing GNU Mach to unmask the interrupt.

MSI-X has a fundamentally different hardware model:

```text
PCI device
   │
   │ MSI-X memory write
   ▼
Local APIC
   │
   │ vector V
   ▼
IDT[V]
   │
   ▼
interrupt handler
```

There is no GSI or I/O APIC pin corresponding to an MSI-X interrupt.

Furthermore, modern PCI devices commonly associate MSI-X vectors with
independent hardware queues. An NVMe controller, xHCI controller, or
high-performance network interface may require multiple vectors with
carefully selected processor affinity.

The component with the best information for making those choices is
often the device driver itself rather than a generic PCI arbiter.

Treating MSI-X vectors as artificial GSIs would unnecessarily preserve
assumptions inherited from legacy interrupt hardware.

The proposed facility instead exposes the primitive that GNU Mach
actually needs to provide:

> protected allocation and delivery of hardware interrupts identified by a 
> processor and APIC vector.

PCI-specific mechanisms remain outside the kernel.

---

# 2. Design Principles

The design follows six principles.

### 2.1 GNU Mach retains architectural control

GNU Mach retains exclusive control over:

* the IDT;
* interrupt entry stubs;
* Local APIC state required by interrupt handling;
* LAPIC EOI;
* architectural and kernel-reserved vectors;
* validation and protection of externally allocatable vectors.

Userspace is never permitted to install arbitrary IDT entries.

### 2.2 Allocation policy remains in userspace

Userspace is responsible for:

* MSI/MSI-X capability discovery;
* MSI-X table mapping;
* MSI-X table programming;
* interrupt-vector allocation policy;
* processor affinity;
* association of vectors with device queues.

GNU Mach protects the vector namespace but does not decide how a
device should distribute its interrupts.

### 2.3 Drivers may choose processor and vector

A device driver may know more about the device's queue structure and
affinity requirements than its parent PCI arbiter.

Consequently, the arbiter need not select a concrete `(processor,
vector)` tuple before delegation.

Instead, it delegates a subset of the interrupt-vector namespace from
which the driver may allocate.

### 2.4 Interrupt resources are capabilities

Processors and interrupt resources are represented by Mach objects
rather than global integer handles wherever practical.

Processors are named by the existing:

```c
processor_name_t
```

while interrupt allocation authority is represented by:

```c
interrupt_vector_space_t
```

and a concrete allocated interrupt by:

```c
interrupt_vector_t
```

### 2.5 Delegation is recursive

Every `interrupt_vector_space_t` is itself delegatable.

There is no distinction between a special root allocator,
nested-arbiter capability, and driver allocator capability at the
interface level.

Authority can therefore be subdivided recursively:

```text
vector space
    ↓
smaller vector space
    ↓
smaller vector space
    ↓
allocated interrupt vector
```

### 2.6 Delegation may only reduce authority

A child vector space must always be a subset of its parent.

A holder can delegate less authority, but can never manufacture
additional authority.

---

# 3. Interrupt Vector Namespace

The fundamental resource managed by this interface is the pair:

```text
(processor, APIC vector)
```

The complete externally usable namespace can therefore be represented as:

```text
Processors × ExternalVectors
```

For example:

```text
CPU0 × {0x50 ... 0xdf}
CPU1 × {0x50 ... 0xdf}
CPU2 × {0x50 ... 0xdf}
CPU3 × {0x50 ... 0xdf}
```

The same numerical APIC vector may be independently allocated on
different processors if the architecture and GNU Mach interrupt
implementation permit it.

For example:

```text
CPU0 : vector 0x91 → NIC RX queue 0
CPU1 : vector 0x91 → NIC RX queue 1
CPU2 : vector 0x91 → NVMe CQ 0
CPU3 : vector 0x91 → xHCI interrupter 0
```

GNU Mach must therefore track allocations per processor rather than
assuming one global vector namespace.

---

# 4. Externally Allocatable IDT Range

GNU Mach reserves a range of vectors for externally managed hardware interrupts.

For illustration:

```text
0x00 ─────────────── architectural exceptions
 ...
0x20 ─────────────── kernel/legacy interrupt vectors
 ...
0x50 ─┐
      │
      │ externally allocatable vectors
      │
0xdf ─┘
0xe0 ─────────────── kernel LAPIC/IPI vectors
 ...
0xff
```

The exact boundaries must be chosen according to GNU Mach's existing
IDT and interrupt-vector layout.

GNU Mach remains authoritative over which vectors are eligible for
external allocation.

No vector-space capability can include a vector outside this permitted
namespace.

---

# 5. Interrupt Vector Spaces

A new Mach object represents allocation authority over a subset of the
interrupt-vector namespace:

```c
typedef mach_port_t interrupt_vector_space_t;
```

Conceptually, an interrupt vector space contains:

```text
set of allowed processors
×
set of allowed vectors
-
already allocated/delegated resources
```

An internal representation might resemble:

```c
struct interrupt_vector_space {
        ipc_port_t port;

        struct processor_set allowed_processors;
        struct vector_set allowed_vectors;

        struct interrupt_vector_space *parent;

        queue_head_t children;
        queue_head_t allocated_vectors;

        bool revoked;
};
```

The exact representation need not expose ranges directly. Bitmaps may
be preferable because the available vector set is small.

An `interrupt_vector_space_t` represents allocation authority, not a
concrete hardware interrupt.

---

# 6. Root Vector Space

GNU Mach creates an initial vector space representing all externally
delegatable interrupt resources.

Conceptually:

```text
processors:
    all processors available for external interrupt routing

vectors:
    USER_VECTOR_FIRST ... USER_VECTOR_LAST
```

This root capability is supplied to the root PCI arbiter or another
privileged hardware-resource manager.

The root arbiter does not gain authority over:

* exceptions;
* system-call vectors;
* LAPIC timer vectors;
* IPIs;
* spurious-interrupt vectors;
* other vectors reserved by GNU Mach.

The distinction between kernel-reserved and externally allocatable
resources remains enforced by GNU Mach.

---

# 7. Recursive Delegation

Any holder of an `interrupt_vector_space_t` may delegate a subset of
its available authority.

A possible interface is:

```c
kern_return_t
interrupt_vector_space_delegate(
        interrupt_vector_space_t parent,
        processor_name_array_t processors,
        vector_set_t vectors,
        interrupt_vector_space_t *child);
```

The kernel validates:

```text
child.processors ⊆ parent.available_processors

child.vectors ⊆ parent.available_vectors
```

and removes the delegated resources from the parent's directly allocatable set.

The new child capability has exactly the same semantics as its parent.

It may therefore itself call:

```c
interrupt_vector_space_delegate();
```

without any special nested-arbiter interface.

This provides recursive delegation by construction.

---

# 8. Exclusive Delegation

Delegation should be exclusive by default.

Consider a parent holding:

```text
vectors 0x80 ... 0x9f
```

Before delegation:

```text
0x80 ───────────────────────── 0x9f
[             parent               ]
```

If it delegates:

```text
0x88 ... 0x8f
```

the resulting authority becomes:

```text
parent:

0x80 ... 0x87       0x90 ... 0x9f
[    owned    ]     [    owned    ]


child:

0x88 ... 0x8f
[    owned    ]
```

The parent may retain revocation authority over the child object, but
it may not independently allocate an interrupt from the delegated
subset while the delegation remains valid.

This prevents two descendants from allocating the same:

```text
(processor, vector)
```

tuple.

---

# 9. Delegating Processor Authority

The processor dimension of the namespace is delegated in the same
manner as the vector dimension.

For example, a root arbiter may own:

```text
processors:
    P0 P1 P2 P3

vectors:
    0x50 ... 0xdf
```

and delegate to a sub-Hurd:

```text
processors:
    P2 P3

vectors:
    0x80 ... 0x9f
```

The sub-Hurd may then further delegate to a driver:

```text
processors:
    P3

vectors:
    0x88 ... 0x8f
```

The driver is free to choose any `(processor, vector)` tuple permitted
by its capability.

Processor identity is expressed using:

```c
processor_name_t
```

rather than an integer CPU index.

This avoids creating an additional userspace processor namespace.

---

# 10. Allocating a Concrete Interrupt Vector

A leaf holder may allocate a concrete interrupt from its vector space.

A possible interface is:

```c
kern_return_t
interrupt_vector_allocate(
        interrupt_vector_space_t space,
        processor_name_t processor,
        unsigned int vector_number,
        interrupt_vector_t *vector);
```

For example:

```c
interrupt_vector_t cq_irq;

interrupt_vector_allocate(
        driver_space,
        cpu3,
        0x8b,
        &cq_irq);
```

GNU Mach verifies that:

1. `cpu3` belongs to the processors delegated to `driver_space`;
2. `0x8b` belongs to its delegated vector set;
3. `(cpu3, 0x8b)` has not already been allocated;
4. the vector is still valid and not revoked.

If successful, that tuple is removed from the space's remaining
allocatable resources.

The returned:

```c
interrupt_vector_t
```

represents one concrete hardware interrupt endpoint.

---

# 11. Interrupt Vector Objects

An allocated interrupt vector may internally be represented as:

```c
struct interrupt_vector {
        ipc_port_t port;

        struct interrupt_vector_space *space;

        processor_t processor;
        unsigned int vector;

        ipc_port_t notification_port;
        uint32_t cookie;

        bool bound;
        bool revoked;
};
```

The distinction is:

```text
interrupt_vector_space_t
        │
        │ allocate(processor, vector)
        ▼
interrupt_vector_t
```

An `interrupt_vector_space_t` is an allocator capability.

An `interrupt_vector_t` is one reserved hardware interrupt.

---

# 12. Driver Choice of Processor and Vector

The eventual driver is intentionally permitted to choose both the
processor and vector.

For example, an NVMe driver might decide:

```c
struct nvme_irq_assignment {
        processor_name_t cpu;
        unsigned int vector;
};

struct nvme_irq_assignment irqmap[] = {
        { cpu0, 0x90 },
        { cpu1, 0x90 },
        { cpu2, 0x90 },
        { cpu3, 0x90 }
};
```

It can then allocate:

```c
for (unsigned q = 0; q < nr_queues; q++) {
        interrupt_vector_allocate(
                irq_space,
                irqmap[q].cpu,
                irqmap[q].vector,
                &irq[q]);
}
```

This permits the driver to account for:

* hardware queue topology;
* queue-to-thread affinity;
* NUMA considerations;
* interrupt moderation strategy;
* device-specific constraints;
* processor-local polling and interrupt fallback.

The parent arbiter controls which choices are permitted but does not
make those choices on behalf of the driver.

---

# 13. IDT Handling

GNU Mach need not dynamically modify the IDT whenever a vector is allocated.

Instead, all externally allocatable vectors can receive generic
interrupt stubs during processor initialization.

Conceptually:

```c
for (unsigned vector = USER_VECTOR_FIRST;
     vector <= USER_VECTOR_LAST;
     vector++)
        install_user_vector_gate(vector,
                                 user_vector_stubs[vector]);
```

A generated stub records the vector before entering a common interrupt path:

```asm
user_vector_91:
        pushl $0x91
        jmp user_vector_common
```

The IDT can consequently remain static after processor initialization.

Vector allocation modifies only kernel dispatch state.

---

# 14. Per-Processor Dispatch State

GNU Mach maintains a per-processor mapping:

```text
(processor, vector) → interrupt_vector object
```

Conceptually:

```c
struct interrupt_vector *
vector_table[MAX_CPUS][256];
```

or an equivalent per-processor structure.

Allocating:

```text
CPU3, vector 0x8b
```

installs:

```c
vector_table[cpu3_slot][0x8b] = iv;
```

Freeing or revoking the vector removes this association.

---

# 15. Binding an Interrupt to a Notification Port

After allocating an interrupt, the driver binds it to a Mach notification port.

The driver first creates an ordinary receive port:

```c
mach_port_t notification;

mach_port_allocate(mach_task_self(),
                   MACH_PORT_RIGHT_RECEIVE,
                   &notification);

mach_port_insert_right(mach_task_self(),
                       notification,
                       notification,
                       MACH_MSG_TYPE_MAKE_SEND);
```

It then binds the interrupt:

```c
interrupt_vector_bind(
        cq_irq,
        notification,
        CQ7);
```

A possible interface is:

```c
kern_return_t
interrupt_vector_bind(
        interrupt_vector_t vector,
        mach_port_t notification,
        uint32_t cookie);
```

GNU Mach stores:

```c
iv->notification_port = notification;
iv->cookie = cookie;
iv->bound = true;
```

The cookie is entirely opaque to GNU Mach.

It can identify:

* a hardware queue;
* a driver object;
* a completion ring;
* a userspace dispatcher entry;
* any other driver-defined resource.

---

# 16. Interrupt Delivery

When hardware generates the MSI-X interrupt:

```text
PCI device
    │
    │ MSI-X message
    ▼
Local APIC
    │
    │ vector V
    ▼
IDT[V]
```

the generic interrupt entry obtains the current processor and vector.

Conceptually:

```c
void
user_vector_interrupt(unsigned int vector)
{
        processor_t processor = current_processor();

        struct interrupt_vector *iv =
                vector_table[processor->slot_num][vector];

        lapic_eoi();

        if (iv != NULL && iv->bound && !iv->revoked)
                queue_vector_notification(iv);
}
```

GNU Mach then sends a Mach notification containing:

```c
notification.id = iv->cookie;
```

The existing `DEVICE_INTR_NOTIFY` format may potentially be reused.

The driver's interrupt loop can therefore remain simple:

```c
for (;;) {
        device_intr_notification_t msg;

        receive_interrupt(notification, &msg);

        nvme_drain_completion_queue(msg.id);
}
```

The raw APIC vector number need not be returned in the notification.

---

# 17. MSI-X Programming by the Driver

Because the driver chooses the `(processor, vector)` tuple, it must
either program the MSI-X table itself or communicate the chosen
routing information to whatever process owns MSI-X programming.

For a userspace PCI-driver architecture, direct programming by the
device driver is natural when the PCI arbiter has already delegated
access to the relevant device resources.

Conceptually:

```c
interrupt_vector_allocate(
        irq_space,
        cpu,
        vector_number,
        &irq);

msix_mask(entry);

msix_set_destination(entry, cpu);
msix_set_vector(entry, vector_number);

msix_unmask(entry);

interrupt_vector_bind(
        irq,
        notification_port,
        queue_id);
```

The driver therefore controls both:

```text
hardware MSI-X configuration
```

and:

```text
software interrupt binding
```

while GNU Mach enforces that the selected processor/vector combination
belongs to the authority delegated to that driver.

---

# 18. MSI Message Construction and MSI-X Programming

An MSI or MSI-X interrupt is generated by a PCI device as a specially
formatted memory write. For MSI-X, each table entry contains a
**Message Address** and **Message Data** field describing where and
how the interrupt is to be delivered.

Conceptually:

```text
MSI-X table entry

Message Address ───► interrupt destination/routing
Message Data    ───► interrupt vector and delivery attributes
```

On x86, these values encode architecture-specific information
concerning Local APIC interrupt delivery. The precise encoding depends
on the interrupt architecture in use and may involve details such as
APIC identifiers, xAPIC/x2APIC operation, and potentially
interrupt-remapping facilities.

Userspace drivers should not be required to derive these encodings
from a `processor_name_t`.

The interrupt-vector interface therefore separates the driver's
routing decision:

```c
processor_name_t processor;
unsigned int vector;
```

from the architecture-specific MSI message required to implement that decision.

After allocating a concrete interrupt vector:

```c
interrupt_vector_allocate(
        irq_space,
        processor,
        vector,
        &irq);
```

GNU Mach has sufficient information to associate:

```text
interrupt_vector_t irq
        │
        ├── processor
        └── APIC vector
```

with the platform-specific MSI routing information.

An interface such as:

```c
kern_return_t
interrupt_vector_get_msi(
        interrupt_vector_t vector,
        uint64_t *address,
        uint32_t *data);
```

may therefore convert an allocated interrupt-vector capability into
the corresponding MSI message.

The returned values should be regarded as **opaque hardware routing
values**. Their interpretation is an implementation detail of GNU
Mach's machine-dependent interrupt subsystem.

For example:

```c
uint64_t msg_addr;
uint32_t msg_data;

interrupt_vector_get_msi(
        irq,
        &msg_addr,
        &msg_data);
```

might internally translate:

```text
processor_name_t P3
vector 0x8b
```

into an architecture-specific representation such as:

```text
Message Address = LAPIC destination encoding for P3
Message Data    = vector 0x8b + delivery attributes
```

without requiring the caller to determine the Local APIC ID or
understand the current APIC mode.

## 18.1 Separation Between MSI Construction and MSI-X Programming

Construction of an MSI message and programming of an MSI-X table are
separate operations.

GNU Mach is responsible for the former because it owns the processor
interrupt architecture:

```text
GNU Mach knows:

processor_name_t
        │
        ▼
processor object
        │
        ├── APIC destination
        ├── interrupt architecture
        ├── xAPIC/x2APIC state
        └── platform routing state

                    +

allocated APIC vector

                    │
                    ▼

          valid MSI address/data
```

The PCI subsystem is responsible for the latter because the MSI-X
table is part of the PCI device.

An MSI-X table normally resides within one of the PCI function's BARs.
Its location is described by the MSI-X capability in PCI configuration
space.

Consequently, GNU Mach should not need to know:

* which PCI function uses the interrupt;
* which BAR contains the MSI-X table;
* the offset of the MSI-X table;
* which MSI-X table entry is being configured;
* which hardware queue corresponds to that entry.

Conversely, PCI drivers should not need to know how GNU Mach
represents LAPIC destinations.

The boundary between the two systems is the `interrupt_vector_t` capability.

## 18.2 Role of `pci-arbiter`

On the Hurd, access to PCI resources is mediated by `pci-arbiter`.
This mediation should also apply to MSI-X configuration.

In particular, unrestricted writable mapping of an MSI-X table into an
untrusted driver's address space would allow that driver to construct
arbitrary MSI messages independently of its delegated
`interrupt_vector_space_t`.

For example, a driver delegated only:

```text
processors:
    P2 P3

vectors:
    0x80 ... 0x8f
```

must not be able to bypass that restriction by directly programming an
MSI-X entry targeting:

```text
P0, vector 0xe1
```

The capability system would otherwise constrain which interrupts GNU
Mach agrees to bind without constraining which interrupts the physical
PCI device can generate.

The MSI-X table should therefore normally remain under the authority
of `pci-arbiter`.

Programming an MSI-X entry requires authority over two independent resources:

```text
PCI device capability
        +
interrupt_vector_t
```

The first proves authority over the device whose MSI-X table is to be modified.

The second proves authority over the interrupt destination that is to
be programmed into that table.

Conceptually, MSI-X binding is therefore an operation of the form:

```c
pci_msix_bind(
        pci_device,
        msix_entry,
        interrupt_vector);
```

The operation creates a hardware association between two independently
protected resources:

```text
PCI function                    interrupt_vector_t
     │                                  │
     │                                  │
     └─────────────┬────────────────────┘
                   │
                   ▼
             MSI-X table entry
                   │
                   ▼
       valid MSI address + data
```

## 18.3 Driver Interface Through `libpciaccess`

Drivers should not normally communicate with `pci-arbiter` through
Hurd-specific RPCs directly.

PCI access on the Hurd is already abstracted through the Hurd backend
of `libpciaccess`. MSI-X configuration should follow the same
layering.

The driver-facing interface may therefore be provided as an extension
of `libpciaccess`, conceptually:

```c
int
pci_device_msix_bind(
        struct pci_device *device,
        unsigned int entry,
        interrupt_vector_t vector);
```

A driver can then configure an interrupt without knowing how the PCI
function is represented by `pci-arbiter`:

```c
interrupt_vector_t irq;

interrupt_vector_allocate(
        irq_space,
        cpu,
        0x8b,
        &irq);

pci_device_msix_bind(
        device,
        queue,
        irq);

interrupt_vector_bind(
        irq,
        notification_port,
        queue);
```

The driver remains responsible for choosing:

```text
processor
vector
MSI-X entry / device queue
```

because these are device-policy decisions.

It does not need to know:

```text
APIC ID
MSI message address
MSI message data
MSI-X table BAR
physical MSI-X table mapping
```

unless direct hardware access has explicitly been delegated.

## 18.4 Hurd `libpciaccess` Backend

On the Hurd, the implementation of:

```c
pci_device_msix_bind(device, entry, irq);
```

would communicate with `pci-arbiter`.

Conceptually:

```text
device driver
     │
     │ pci_device_msix_bind()
     ▼
libpciaccess
     │
     ▼
Hurd libpciaccess backend
     │
     │ pci-arbiter RPC
     ▼
pci-arbiter
     │
     ├── validate PCI device authority
     ├── validate MSI-X entry
     │
     │
     ├── present interrupt_vector_t
     │       │
     │       ▼
     │    GNU Mach
     │       │
     │       └── MSI address/data
     │
     └── program physical MSI-X table
```

Internally, `pci-arbiter` may perform an operation equivalent to:

```c
kern_return_t
pci_msix_bind(
        pci_device_t device,
        unsigned int entry,
        interrupt_vector_t irq)
{
        uint64_t address;
        uint32_t data;

        err = interrupt_vector_get_msi(
                irq,
                &address,
                &data);

        if (err)
                return err;

        msix_mask(device, entry);

        msix_write_address(device, entry, address);
        msix_write_data(device, entry, data);

        msix_unmask(device, entry);

        return 0;
}
```

The precise interface between `libpciaccess` and `pci-arbiter` is
independent of the GNU Mach interrupt-vector ABI.

## 18.5 `interrupt_vector_get_msi()` as a Privileged Interface

With mediated MSI-X programming, ordinary device drivers do not need to call:

```c
interrupt_vector_get_msi();
```

directly.

The interface primarily exists for trusted hardware-resource managers
such as `pci-arbiter`.

This provides a stronger capability boundary.

Instead of allowing arbitrary drivers to obtain MSI messages and write
them into device tables:

```text
driver
   │
   ├── interrupt_vector_get_msi()
   └── write MSI-X table
```

the preferred path is:

```text
driver
   │
   │ interrupt_vector_t
   ▼
libpciaccess
   │
   ▼
pci-arbiter
   │
   │ interrupt_vector_get_msi()
   ▼
GNU Mach
   │
   │ opaque address/data
   ▼
pci-arbiter
   │
   ▼
MSI-X table
```

GNU Mach can therefore restrict retrieval of raw MSI routing
information if necessary without preventing the driver from selecting
its processor and vector.

## 18.6 MSI-X Table Mapping

The MSI-X table resides in PCI BAR space, but this does not imply that
the complete BAR must always be mapped writable into the driver.

Where hardware layout permits it, `pci-arbiter` may exclude pages
containing the MSI-X table from the driver's ordinary MMIO mappings.

For example:

```text"
PCI BAR

0x0000 ──────────────────────┐
                             │
       ordinary registers    │──► mapped RW to driver
                             │
0x7fff ──────────────────────┘

0x8000 ──────────────────────┐
                             │
          MSI-X table        │──► retained by pci-arbiter
                             │
0x8fff ──────────────────────┘

0x9000 ──────────────────────┐
                             │
       ordinary registers    │──► mapped RW to driver
                             │
0xffff ──────────────────────┘
```

This isolation is limited by VM page granularity. A device may place
an MSI-X table on pages that also contain registers required by the
driver.

The implementation must therefore distinguish between:

1. hardware for which MSI-X table pages can be withheld cleanly;
2. hardware for which MSI-X state shares pages with other required MMIO state;
3. explicitly trusted drivers for which direct MSI-X mapping is acceptable.

Direct writable MSI-X mapping may remain available as a trusted-driver
optimization, but it should not define the security model.

## 18.7 Nested PCI Arbiters

Mediated MSI-X programming also composes naturally with nested PCI arbiters.

Consider:

```text
root PCI arbiter
       │
       ▼
sub-Hurd PCI arbiter
       │
       ▼
device driver
```

The interrupt-vector namespace may independently be delegated:

```text
root interrupt_vector_space_t
       │
       ▼
sub-Hurd interrupt_vector_space_t
       │
       ▼
driver interrupt_vector_space_t
```

The driver eventually allocates:

```c
interrupt_vector_allocate(
        driver_irq_space,
        cpu,
        vector,
        &irq);
```

and requests:

```c
pci_device_msix_bind(
        device,
        entry,
        irq);
```

The nested `pci-arbiter` may program the MSI-X table itself if it
possesses the required hardware authority, or forward the
configuration request to its parent arbiter.

Thus the configuration path may be hierarchical:

```text
driver
   │
   │ MSI-X configuration
   ▼
sub-Hurd pci-arbiter
   │
   ▼
root pci-arbiter
   │
   ▼
physical MSI-X table
```

while interrupt delivery remains direct:

```text
PCI device
    │
    │ MSI-X
    ▼
Local APIC
    │
    ▼
GNU Mach
    │
    ▼
driver
```

No PCI arbiter participates in the interrupt fast path.

## 18.8 Resulting Division of Responsibility

The resulting responsibilities are:

```text
Device driver
    chooses queue
    chooses processor
    chooses vector
    allocates interrupt_vector_t
    requests MSI-X binding
    handles interrupt

libpciaccess
    provides driver-facing PCI abstraction
    forwards MSI-X operations through OS backend

pci-arbiter
    controls PCI function authority
    controls/protects MSI-X table
    validates MSI-X entry
    requests valid MSI message from GNU Mach
    programs hardware

GNU Mach
    protects interrupt-vector namespace
    validates processor/vector allocation
    knows APIC routing architecture
    constructs valid MSI address/data
    owns IDT
    performs LAPIC EOI
    delivers interrupt notification
```

The resulting setup path is:

```text
driver chooses
(processor, vector)
        │
        ▼
interrupt_vector_allocate()
        │
        ▼
interrupt_vector_t
        │
        ▼
pci_device_msix_bind()
        │
        ▼
libpciaccess
        │
        ▼
pci-arbiter
        │
        ├── interrupt_vector_get_msi()
        │             │
        │             ▼
        │         GNU Mach
        │             │
        │       address + data
        │             │
        ◄─────────────┘
        │
        ▼
physical MSI-X table
```

This arrangement preserves userspace control over interrupt policy
while preventing a PCI driver from bypassing its delegated
interrupt-vector authority by constructing arbitrary MSI messages.

It also preserves the existing Hurd layering: drivers interact with
PCI hardware through `libpciaccess`, the Hurd backend communicates
with `pci-arbiter`, and GNU Mach remains responsible only for
architectural interrupt resources and delivery.

---

# 19. Interrupt Acknowledgement

The existing GNU Mach IRQ-device interface masks an IRQ before
notifying userspace and requires:

```c
device_intr_ack();
```

to re-enable it.

This model should not be applied to MSI-X interrupt-vector objects.

GNU Mach does not own the MSI-X table and therefore should not
manipulate the MSI-X per-vector mask bit.

For an interrupt-vector object, GNU Mach performs only the CPU-side
acknowledgement:

```c
lapic_eoi();
```

and delivers the notification.

Device-side interrupt suppression, masking, coalescing, and rearming
remain the responsibility of the userspace driver.

The resulting path is:

```text
MSI-X interrupt
      │
      ▼
LAPIC
      │
      ▼
GNU Mach
      │
      ├── LAPIC EOI
      │
      └── Mach notification
                │
                ▼
              driver
                │
                ├── drain queue
                ├── acknowledge device
                └── rearm notification
```

No `device_intr_ack()` operation is required for this interrupt class.

The existing acknowledgement mechanism remains unchanged for legacy
IRQ/IO-APIC users.

---

# 20. Nested PCI Arbiters

Recursive vector spaces naturally support nested PCI arbiters and sub-Hurds.

Consider:

```text
GNU Mach
    │
    │ root interrupt_vector_space_t
    ▼
root PCI arbiter
    │
    │ delegated space
    ▼
sub-Hurd PCI arbiter
    │
    │ delegated space
    ▼
NVMe driver
```

The root arbiter might initially possess:

```text
processors:
    P0 P1 P2 P3

vectors:
    0x50 ... 0xdf
```

It assigns a PCI function to a sub-Hurd and delegates:

```text
processors:
    P2 P3

vectors:
    0x80 ... 0x9f
```

The sub-Hurd PCI arbiter may then delegate to its NVMe driver:

```text
processors:
    P2 P3

vectors:
    0x88 ... 0x8f
```

The driver itself chooses:

```text
(P3, 0x8b)
```

and allocates it.

No special API exists for a nested arbiter.

The same `interrupt_vector_space_delegate()` operation is used at every level.

---

# 21. Turtles All the Way Down

The delegation model is deliberately recursive.

Every holder of:

```c
interrupt_vector_space_t
```

may perform:

```c
interrupt_vector_space_delegate();
```

on the subset it still owns.

Thus:

```text
GNU Mach
   │
   ▼
root vector space
   │
   ├── allocate
   │
   └── delegate
          │
          ▼
      child vector space
          │
          ├── allocate
          │
          └── delegate
                 │
                 ▼
             child vector space
                 │
                 ├── allocate
                 │
                 └── delegate
                        │
                        ▼
                     ...
```

There is no semantic distinction between:

* root PCI arbiter;
* nested PCI arbiter;
* sub-Hurd PCI arbiter;
* device driver with suballocation requirements.

The capability itself defines what the holder may do.

---

# 22. Example: Root PCI Driver

Suppose an NVMe controller exposes four useful MSI-X entries.

The root PCI arbiter delegates a suitable vector space to the driver:

```c
interrupt_vector_space_t nvme_space;

interrupt_vector_space_delegate(
        root_space,
        nvme_processors,
        nvme_vectors,
        &nvme_space);
```

The driver receives `nvme_space`.

It decides:

```c
struct nvme_irq_assignment assignments[4] = {
        { cpu0, 0x90 },
        { cpu1, 0x90 },
        { cpu2, 0x90 },
        { cpu3, 0x90 }
};
```

and allocates:

```c
interrupt_vector_t irq[4];

for (unsigned q = 0; q < 4; q++) {
        interrupt_vector_allocate(
                nvme_space,
                assignments[q].cpu,
                assignments[q].vector,
                &irq[q]);
}
```

It obtains MSI routing information:

```c
for (unsigned q = 0; q < 4; q++) {
        uint64_t address;
        uint32_t data;

        interrupt_vector_get_msi(
                irq[q],
                &address,
                &data);

        nvme_program_msix_entry(
                q,
                address,
                data);
}
```

Finally it binds the interrupts:

```c
for (unsigned q = 0; q < 4; q++) {
        mach_port_allocate(
                mach_task_self(),
                MACH_PORT_RIGHT_RECEIVE,
                &notification[q]);

        interrupt_vector_bind(
                irq[q],
                notification[q],
                q);
}
```

The arbiter never needs to understand NVMe queue topology.

---

# 23. Example: Sub-Hurd Delegation

Suppose the root PCI arbiter assigns a PCI function to a sub-Hurd.

It delegates a vector space:

```c
interrupt_vector_space_t subhurd_space;

interrupt_vector_space_delegate(
        root_space,
        subhurd_processors,
        subhurd_vectors,
        &subhurd_space);
```

That capability is passed into the sub-Hurd together with the PCI
device resources.

The sub-Hurd PCI arbiter may then delegate:

```c
interrupt_vector_space_t driver_space;

interrupt_vector_space_delegate(
        subhurd_space,
        driver_processors,
        driver_vectors,
        &driver_space);
```

The device driver receives `driver_space` and chooses its concrete interrupt:

```c
interrupt_vector_t irq;

interrupt_vector_allocate(
        driver_space,
        cpu3,
        0x8b,
        &irq);
```

It programs the device and binds:

```c
interrupt_vector_bind(
        irq,
        notification_port,
        CQ0);
```

The authority path is:

```text
GNU Mach
   │
   ▼
root PCI arbiter
   │
   ▼
sub-Hurd PCI arbiter
   │
   ▼
device driver
```

but the interrupt path is direct:

```text
PCI hardware
     │
     ▼
Local APIC
     │
     ▼
GNU Mach
     │
     ▼
driver notification port
```

The arbitration hierarchy is entirely absent from the fast path.

---

# 24. Revocation

Recursive delegation requires hierarchical revocation.

A parent vector space retains the ability to revoke a child delegation:

```c
kern_return_t
interrupt_vector_space_revoke(
        interrupt_vector_space_t child);
```

Revocation recursively invalidates:

* the child space;
* descendant vector spaces;
* concrete interrupt vectors allocated beneath it;
* associated notification bindings.

Conceptually:

```text
root
  │
  └── sub-Hurd
        │
        └── driver
              │
              ├── IRQ0
              ├── IRQ1
              └── IRQ2
```

Revoking the `sub-Hurd` space invalidates the entire subtree.

This permits a parent PCI arbiter to reclaim all interrupt resources
associated with a delegated PCI function.

The userspace PCI arbiter remains responsible for disabling hardware
MSI-X generation before or while revoking the corresponding interrupt
resources.

---

# 25. Returning Delegated Resources

When a child vector space is destroyed or revoked, its resources may
become available again to its parent once all descendant allocations
have been torn down.

Conceptually:

```text
parent owns:
    0x80 ... 0x9f

delegate child:
    0x88 ... 0x8f

parent temporarily owns:
    0x80 ... 0x87
    0x90 ... 0x9f

destroy child

parent again owns:
    0x80 ... 0x9f
```

This permits dynamic assignment and reclamation of PCI devices without
permanently fragmenting the interrupt namespace.

---

# 26. Compatibility with the Existing IRQ Device

The existing interface remains available:

```c
device_intr_register(
        irq_device,
        irq_number,
        flags,
        notification_port);
```

It continues to represent legacy interrupt resources for which GNU
Mach manages the interrupt controller.

The two models intentionally have different semantics.

| Existing IRQ device                 | Interrupt vector spaces
              |
| ----------------------------------- |
-------------------------------------------- |
| Identified by IRQ/GSI integer       | Authority represented by Mach
capabilities   |
| Primarily INTx/IO-APIC              | Primarily MSI/MSI-X
              |
| GNU Mach owns routing               | Userspace owns routing policy
              |
| GNU Mach maps IRQ to vector         | Leaf allocator chooses
CPU/vector            |
| GNU Mach masks source               | Driver manages device-side
masking           |
| `device_intr_ack()` required        | No Mach interrupt ACK
              |
| GSI-based namespace                 | `(processor, vector)`
namespace              |
| Difficult to subdivide              | Recursively delegatable
              |
| CPU affinity largely kernel-defined | Affinity selected within
delegated authority |

Legacy IRQ support may eventually use the same low-level
vector-dispatch infrastructure internally:

```text
                 generic vector dispatch
                       ▲
                       │
          ┌────────────┴────────────┐
          │                         │
      I/O APIC                    MSI-X
          │                         │
     legacy irq API      interrupt_vector_t
```

without changing the existing userspace ABI.

---

# 27. Security Model

The security model follows capability attenuation.

GNU Mach initially controls the complete architectural vector namespace.

It delegates only a safe externally allocatable subset:

```text
GNU Mach
    │
    ▼
root vector space
```

Each subsequent delegation may only reduce authority:

```text
root
    ↓ subset
child
    ↓ subset
driver
```

A vector-space holder cannot:

* access processors outside its delegated processor set;
* allocate vectors outside its delegated vector set;
* allocate a tuple delegated exclusively to a child;
* allocate kernel-reserved vectors;
* modify the IDT;
* alter another vector-space hierarchy;
* bind an interrupt vector allocated elsewhere.

A concrete `interrupt_vector_t` grants authority only over one already
allocated:

```text
(processor, vector)
```

tuple.

---

# 28. Lifetime and Failure Handling

Vector spaces and allocated vectors participate in ordinary Mach port
lifetime management.

If a driver dies:

* its notification receive ports disappear;
* GNU Mach marks corresponding interrupt vectors unbound;
* the concrete vector objects remain associated with their owning
vector-space hierarchy until reclaimed or revoked.

If a nested PCI arbiter dies, its delegated vector-space objects can
be revoked by the parent resource manager.

The PCI-resource lifecycle and interrupt-vector lifecycle should be
coordinated so that reclaiming a PCI function also reclaims:

* MSI-X entries;
* MMIO mappings;
* DMA authority;
* interrupt-vector spaces;
* concrete interrupt vectors.

---

# 29. Proposed Interfaces

The proposed interface is divided into three layers:

1. **GNU Mach interrupt-vector operations**, responsible for
allocation, delegation, binding, and architecture-specific interrupt
routing.
2. **`libpciaccess` operations**, providing the normal driver-facing
interface for associating PCI MSI-X entries with allocated interrupt
vectors.
3. **`pci-arbiter` operations**, used by the Hurd `libpciaccess`
backend to mediate access to the physical MSI-X table.

This separation allows device drivers to choose their interrupt
topology without requiring them to understand APIC encodings, MSI
message construction, or the representation of PCI resources inside
`pci-arbiter`.

## 29.1 GNU Mach Interrupt-Vector Interface

### Recursive delegation

Any holder of an interrupt-vector space may exclusively delegate a
subset of its remaining processor/vector namespace:

```c
kern_return_t
interrupt_vector_space_delegate(
        interrupt_vector_space_t parent,
        processor_name_array_t processors,
        vector_set_t vectors,
        interrupt_vector_space_t *child);
```

The resulting `child` has the same semantics as its parent and may
itself allocate vectors or recursively delegate further subsets.

GNU Mach guarantees that delegated resources cannot simultaneously be
allocated through the parent or another sibling space.

### Vector allocation

A holder chooses a concrete processor and APIC vector from its
delegated namespace:

```c
kern_return_t
interrupt_vector_allocate(
        interrupt_vector_space_t space,
        processor_name_t processor,
        unsigned int vector_number,
        interrupt_vector_t *vector);
```

The resulting `interrupt_vector_t` represents one allocated:

```text
(processor, APIC vector)
```

tuple.

The caller determines the allocation policy. GNU Mach only verifies
that the requested tuple belongs to the caller's delegated authority
and is available.

### Bind interrupt delivery

An allocated vector is associated with a Mach notification endpoint:

```c
kern_return_t
interrupt_vector_bind(
        interrupt_vector_t vector,
        mach_port_t notification,
        uint32_t cookie);
```

Once bound, arrival of the corresponding hardware interrupt causes GNU
Mach to perform the required CPU-side interrupt acknowledgement and
deliver a notification to `notification`.

The `cookie` is opaque to GNU Mach and may identify a device queue or
another driver-defined object.

### Unbind interrupt delivery

Interrupt delivery may be detached from its current notification endpoint:

```c
kern_return_t
interrupt_vector_unbind(
        interrupt_vector_t vector);
```

Unbinding does not release the `(processor, vector)` allocation. It
only removes the Mach notification binding.

### Release a concrete vector

A concrete interrupt allocation may be returned to its parent vector space:

```c
kern_return_t
interrupt_vector_deallocate(
        interrupt_vector_t vector);
```

GNU Mach removes the corresponding per-processor vector dispatch entry
and makes the tuple available for future allocation once it is safe to
do so.

### Revoke a delegated space

A parent resource manager may reclaim a previously delegated vector space:

```c
kern_return_t
interrupt_vector_space_revoke(
        interrupt_vector_space_t child);
```

Revocation recursively invalidates descendant vector spaces and
concrete vectors allocated from them.

After descendant resources have been torn down, their processor/vector
resources become available to the parent again.

## 29.2 Architecture-Specific MSI Message Construction

An allocated `interrupt_vector_t` contains the policy-level routing decision:

```text
processor_name_t
       +
APIC vector
```

but a PCI MSI/MSI-X device requires an architecture-specific message
consisting of:

```text
MSI Message Address
       +
MSI Message Data
```

GNU Mach therefore provides an operation capable of translating an
allocated interrupt vector into the hardware representation
appropriate for the current interrupt architecture:

```c
kern_return_t
interrupt_vector_get_msi(
        interrupt_vector_t vector,
        uint64_t *address,
        uint32_t *data);
```

The returned address and data are opaque architecture-specific routing
values. Their construction may depend on information private to GNU
Mach, including processor-to-APIC mappings and the active APIC/routing
mode.

This interface is **not intended to be part of the normal
device-driver programming model**.

Its principal consumer on the Hurd is `pci-arbiter`, which uses it
when associating an MSI-X table entry with an `interrupt_vector_t`.

Ordinary drivers should not need to obtain or construct raw MSI messages.

## 29.3 Driver-Facing `libpciaccess` Interface

The normal driver-facing operation for MSI-X configuration should be
exposed through `libpciaccess`.

Conceptually:

```c
int
pci_device_msix_bind(
        struct pci_device *device,
        unsigned int entry,
        interrupt_vector_t vector);
```

The driver first chooses and allocates its interrupt:

```c
interrupt_vector_allocate(
        irq_space,
        cpu,
        vector_number,
        &irq);
```

It then associates a device MSI-X entry with that interrupt:

```c
pci_device_msix_bind(
        device,
        msix_entry,
        irq);
```

Finally, it binds the interrupt to its notification endpoint:

```c
interrupt_vector_bind(
        irq,
        notification_port,
        queue_id);
```

A complete setup therefore appears to the driver as:

```c
interrupt_vector_t irq;

interrupt_vector_allocate(
        irq_space,
        cpu,
        0x8b,
        &irq);

pci_device_msix_bind(
        device,
        queue,
        irq);

interrupt_vector_bind(
        irq,
        notification_port,
        queue);
```

The driver does not need to know:

* the Local APIC identifier corresponding to `cpu`;
* the MSI message address;
* the MSI message data;
* xAPIC/x2APIC encoding details;
* the BAR containing the MSI-X table;
* the physical mapping of the MSI-X table.

These details are handled below the driver-facing interface.

Additional `libpciaccess` operations may be provided for MSI-X
lifecycle management, for example:

```c
int
pci_device_msix_mask(
        struct pci_device *device,
        unsigned int entry);

int
pci_device_msix_unmask(
        struct pci_device *device,
        unsigned int entry);

int
pci_device_msix_unbind(
        struct pci_device *device,
        unsigned int entry);
```

The exact portable API exposed by `libpciaccess` may differ,
particularly because `interrupt_vector_t` is a Mach-specific type. A
Hurd-specific extension or an opaque libpciaccess interrupt object may
therefore be preferable for the actual implementation.

## 29.4 Hurd `libpciaccess` Backend and `pci-arbiter`

On the Hurd, `pci_device_msix_bind()` is implemented by the Hurd
backend of `libpciaccess` through an RPC to `pci-arbiter`.

Conceptually, the arbiter-side operation is:

```c
kern_return_t
pci_msix_bind(
        pci_device_t device,
        unsigned int entry,
        interrupt_vector_t vector);
```

The operation requires authority over both resources being connected:

```text
pci_device_t
      +
interrupt_vector_t
```

`pci-arbiter` verifies that the caller has authority over the PCI
function and that `entry` identifies a valid MSI-X table entry for
that function.

It then obtains the architecture-specific MSI message from GNU Mach:

```c
uint64_t address;
uint32_t data;

interrupt_vector_get_msi(
        vector,
        &address,
        &data);
```

and programs the MSI-X table entry:

```c
msix_mask(device, entry);

msix_write_address(device, entry, address);
msix_write_data(device, entry, data);

msix_unmask(device, entry);
```

The resulting relationship is:

```text
                    interrupt_vector_t
                           │
                           │
                           ▼
driver ──libpciaccess──► pci-arbiter
                           │
                           ├──► GNU Mach
                           │      │
                           │      └── MSI address/data
                           │
                           ▼
                     MSI-X table
```

This ensures that the physical device can only be configured with an
MSI message corresponding to an interrupt capability accepted by GNU
Mach.

## 29.5 Interface Boundaries

The complete interface boundary is therefore:

```text
DEVICE DRIVER

    interrupt_vector_allocate()
             │
             ▼
      interrupt_vector_t
             │
             ├──────────────────────┐
             │                      │
             ▼                      ▼
interrupt_vector_bind()    pci_device_msix_bind()
             │                      │
             ▼                      ▼
         GNU Mach              libpciaccess
                                    │
                                    ▼
                              Hurd backend
                                    │
                                    ▼
                               pci-arbiter
                                    │
                     interrupt_vector_get_msi()
                                    │
                                    ▼
                                GNU Mach
                                    │
                           MSI address/data
                                    │
                                    ▼
                               pci-arbiter
                                    │
                                    ▼
                             physical MSI-X
                                table entry
```

The resulting responsibilities are intentionally distinct:

* **The driver chooses policy:** processor, APIC vector, queue
association, and MSI-X entry.
* **GNU Mach protects interrupt authority:** vector-space delegation,
`(processor, vector)` allocation, IDT dispatch, LAPIC acknowledgement,
and notification delivery.
* **GNU Mach constructs architecture-specific MSI routing
information:** ordinary drivers need not understand APIC destination
encoding.
* **`libpciaccess` provides the driver-facing PCI abstraction:**
drivers need not communicate directly with `pci-arbiter`.
* **`pci-arbiter` protects PCI configuration:** it mediates MSI-X
table programming and connects a PCI-function resource to a valid
`interrupt_vector_t`.

Additional introspection operations may be introduced where required,
but interfaces should avoid exposing global interrupt namespace state,
physical APIC identifiers, or raw MSI routing information to ordinary
device drivers unless there is a concrete need.

---

# 30. Possible MIG Types

Conceptually, the MIG declarations could introduce:

```c
type interrupt_vector_space_t = mach_port_t;
type interrupt_vector_t       = mach_port_t;
```

alongside existing:

```c
processor_name_t
```

A vector-set representation could initially be a range:

```c
struct interrupt_vector_range {
        unsigned first;
        unsigned last;
};
```

although a bitmap may ultimately be more useful for subdivision and
fragmented namespaces.

Similarly, processor delegation could use an array of:

```c
processor_name_t
```

or eventually a separate processor-set capability if that proves more natural.

---

# 31. Required GNU Mach Changes

A minimal implementation requires:

1. A new `interrupt_vector_space` Mach kernel object.
2. A new `interrupt_vector` Mach kernel object.
3. A root externally allocatable vector-space capability.
4. Recursive exclusive delegation of vector spaces.
5. Processor authority represented using `processor_name_t`.
6. Allocation keyed by `(processor, vector)`.
7. Per-processor vector dispatch tables.
8. Generic IDT interrupt stubs for the externally allocatable vector range.
9. LAPIC EOI followed by Mach notification delivery.
10. Hierarchical revocation.
11. Port-death handling for notification endpoints.
12. An operation for obtaining architecture-correct MSI address/data
from an allocated vector.
13. Preservation of the existing `irq` device ABI for legacy interrupts.

It does **not** require GNU Mach to implement:

* PCI enumeration;
* PCI capability parsing;
* MSI-X capability management;
* MSI-X table mapping;
* MSI-X table programming;
* PCI queue management;
* device-specific interrupt acknowledgement;
* interrupt moderation policy.

Those remain userspace responsibilities.

---

# 32. Resulting Architecture

The resulting resource hierarchy is:

```text
                         GNU Mach
                            │
                 architectural namespace
                            │
                            ▼
                root vector-space capability
                            │
                            ▼
                    root PCI arbiter
                            │
                     delegate subset
                            │
                            ▼
                  nested PCI arbiter
                            │
                     delegate subset
                            │
                            ▼
                       PCI driver
                            │
                 choose CPU + vector
                            │
                         allocate
                            │
                            ▼
                  interrupt_vector_t
```

The PCI driver then programs MSI-X:

```text
interrupt_vector_t
        │
        ├── processor
        ├── vector
        │
        ▼
MSI-X table entry
```

while runtime interrupt delivery remains:

```text
PCI hardware
     │
     │ MSI-X
     ▼
Local APIC
     │
     ▼
GNU Mach IDT
     │
     ├── LAPIC EOI
     │
     ▼
Mach notification
     │
     ▼
device driver
```

No PCI arbiter participates in the interrupt fast path.

---

# 33. Conclusion

Proper MSI-X support in GNU Mach does not require moving PCI interrupt
policy into the kernel.

GNU Mach only needs to protect and expose the architectural resource
that cannot safely be implemented entirely in userspace: the mapping
from a processor and APIC vector to kernel interrupt entry and Mach
notification delivery.

The proposed `interrupt_vector_space_t` abstraction represents
authority over a subset of this namespace.

Unlike a centralized interrupt allocator, this capability is
recursively delegatable. Every holder may subdivide its remaining
authority and pass a restricted child space to another task.

Consequently:

```text
GNU Mach
    → root PCI arbiter
        → nested PCI arbiter
            → sub-Hurd PCI arbiter
                → device driver
```

uses the same mechanism at every layer.

The eventual device driver may choose the concrete processor and APIC
vector because it may possess the most detailed knowledge of hardware
queue topology, affinity requirements, and device-specific interrupt
behavior.

Allocation from a vector space produces an `interrupt_vector_t`
representing one concrete `(processor, vector)` endpoint. GNU Mach
validates that the allocation falls within delegated authority, while
userspace remains responsible for programming the corresponding MSI-X
table entry.

This creates a strict separation of responsibility:

**GNU Mach protects the interrupt namespace and provides IDT/LAPIC
delivery mechanisms.**

**Userspace decides how interrupt resources are subdivided, assigned,
routed, and programmed.**

The result is a capability-based MSI-X design compatible with both
ordinary Hurd drivers and recursively nested PCI arbiters, without
placing PCI policy or hierarchy in the kernel interrupt fast path.

Reply via email to