kernel: device_transfer — you may give away what you hold
The mechanism behind delegation, which device-manager.md named as the step after hello: the device manager claims what discovery seeded and hands each device to the driver it matched, so assignment stops being first-come-first-served. It is a MOVE, not a copy. A claim is exclusive (driver-model.md, invariant 1), so the giver stops holding the device the instant the receiver starts. That is why this is a new syscall rather than the M13 capability path, where a passed handle is shared refcounted — exclusivity cannot be expressed that way. The kernel's whole rule is that you may give away what you hold. It has no notion of which task is the device manager and deliberately gains none: a binary name inside the kernel is not something that cannot safely live in user space. A recipient that does not exist is refused, because a device moved to nobody would be unreachable for the rest of the boot — nothing un-holds a device but task death. Three errnos, each naming its own rule: ENODEV no such device, EPERM you do not hold it, ESRCH no such recipient. Nothing uses it yet. The five claimants move across one at a time in D4-D5, so the suite stays green throughout and a regression names the driver that caused it. Ten assertions, verified to discriminate: removing the ownership check flips four of them, including the giveaway that an illegal transfer then blocks the legitimate claim behind it. Suite 116 -> 117.
This commit is contained in:
@@ -323,6 +323,42 @@ pub const ClaimError = error{
|
||||
AlreadyClaimed, // a live task already owns it
|
||||
};
|
||||
|
||||
/// Why a `transfer` was refused.
|
||||
pub const TransferError = error{
|
||||
NoSuchDevice, // no device with that id
|
||||
NotHeld, // the caller does not hold it — you may only give away what you have
|
||||
};
|
||||
|
||||
/// The errno a refused `transfer` returns to ring 3. (`ESRCH` — no such recipient — is
|
||||
/// raised by the caller in system/kernel/process.zig, which is what can see the task
|
||||
/// table.)
|
||||
pub fn transferErrnoOf(e: TransferError) i64 {
|
||||
return switch (e) {
|
||||
error.NoSuchDevice => abi.ENODEV,
|
||||
error.NotHeld => abi.EPERM,
|
||||
};
|
||||
}
|
||||
|
||||
/// Move device `id` from `from` to `to`. **A move, not a copy**: a claim is exclusive
|
||||
/// (driver-model.md, invariant 1), so the giver stops holding it the moment the
|
||||
/// receiver starts.
|
||||
///
|
||||
/// This is the mechanism behind delegation — the device manager claims what firmware
|
||||
/// discovery seeded and passes each device to the driver it matched, which replaces
|
||||
/// first-come-first-served `device_claim` with policy
|
||||
/// (docs/device-driver-development/device-manager.md). The kernel checks only that the
|
||||
/// caller holds the device: *you may give away what you have*. It knows nothing about
|
||||
/// which task is the manager, and needs to know nothing.
|
||||
///
|
||||
/// Note this is deliberately NOT the M13 capability-passing path, which shares a handle
|
||||
/// refcounted — a copy. Exclusivity cannot be expressed that way.
|
||||
pub fn transfer(id: u64, from: u32, to: u32) TransferError!void {
|
||||
if (id >= count) return error.NoSuchDevice;
|
||||
const holder = claimed[@intCast(id)] orelse return error.NotHeld;
|
||||
if (holder != from) return error.NotHeld;
|
||||
claimed[@intCast(id)] = to;
|
||||
}
|
||||
|
||||
/// The errno a refused `claim` returns to ring 3. (`ECONFINE` — the claim stood but
|
||||
/// the IOMMU would not confine the device — is raised by the caller in
|
||||
/// system/kernel/process.zig, which is what rolls the claim back.)
|
||||
|
||||
Reference in New Issue
Block a user