From 2bcbf76b04776c232aeeda3deb7f47764e93393f Mon Sep 17 00:00:00 2001 From: T-Crypt <132922589+T-Crypt@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:43:40 -0600 Subject: [PATCH] vfio fix(cli): status with an address still asks berthd, and names the VM With an address, berth vfio status skipped berthd as well as nvidia-smi and then reported it unreachable; while the card was in a VM it printed the last memory reading as if current. Both seen on aphotic with galaxy holding the card. --- CHANGELOG.md | 2 ++ internal/cli/vfio.go | 41 ++++++++++++++++++++++++++++++--------- internal/cli/vfio_test.go | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb82bb..258fa0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,8 @@ has a matching GitHub release whose body comes from its section below. ### Fixed +- `berth vfio status ADDR` asks berthd for its view instead of calling it unreachable, and while the + card is in a VM it says so instead of printing the last memory reading as current. - The wake page and start errors show an engine's last log line without terminal color codes (`[32m[INFO][0m`), and a progress bar's line as its last redraw. - Windows: a process the GPU counters report but nvidia-smi does not list, such as dwm.exe diff --git a/internal/cli/vfio.go b/internal/cli/vfio.go index 8b09772..709d70a 100644 --- a/internal/cli/vfio.go +++ b/internal/cli/vfio.go @@ -28,19 +28,20 @@ type vfioCard struct { } // vfioCardAt resolves the governed card. With an explicit address (status -// takes one as its argument) no daemon or nvidia-smi is asked at all, so the -// state stays readable while the card is bound to vfio-pci and nvidia-smi -// cannot see it. Otherwise the status berthd reports names the scheduled +// takes one as its argument) nvidia-smi is not asked, so the state stays +// readable while the card is bound to vfio-pci and nvidia-smi cannot see it; +// berthd is asked either way. Otherwise the status berthd reports names the scheduled // index, and nvidia-smi names that card's bus id; when berthd is down or // has no reading yet, index 0. func vfioCardAt(sock, explicit, smiBin string) (vfioCard, *Error) { - if explicit != "" { - addr := vfio.SysAddr(explicit) - return vfioCard{Addr: addr, BusID: explicitNvidiaForm(addr)}, nil - } var card vfioCard st, err := vfioStatusOf(sock) card.Daemon, card.DialErr = st, err + if explicit != "" { + card.Addr = vfio.SysAddr(explicit) + card.BusID = explicitNvidiaForm(card.Addr) + return card, nil + } if err == nil { card.Index = vfioIndex(st) } @@ -150,6 +151,10 @@ type vfioBerthdDoc struct { ProbeErr string `json:"probe_error,omitempty"` GPU *vfioGPUDoc `json:"gpu,omitempty"` Hold string `json:"hold,omitempty"` // the granted vfio hold, lease and engine + // Passthrough is berthd's card state while the card is in a VM or + // switching ("passthrough", "detaching"); its memory figures are then the + // last reading before the hold, not the card now. + Passthrough string `json:"passthrough,omitempty"` } type vfioGPUDoc struct { @@ -240,8 +245,13 @@ func (v vfioStatusView) Human(c *Context, ok bool) error { return nil } if g := d.Berthd.GPU; g != nil { - c.Printf("berthd gpu%d %s: %s used of %s (read %.0f s ago)\n", - g.Index, g.Name, server.MiB(g.Used), server.MiB(g.Total), g.Age) + if d.Berthd.Passthrough != "" { + c.Printf("berthd gpu%d %s: %s (last memory reading %.0f s ago)\n", + g.Index, g.Name, cardLine(&proto.Passthrough{State: d.Berthd.Passthrough, Driver: vfioDriverOf(d)}), g.Age) + } else { + c.Printf("berthd gpu%d %s: %s used of %s (read %.0f s ago)\n", + g.Index, g.Name, server.MiB(g.Used), server.MiB(g.Total), g.Age) + } } if d.Berthd.ProbeErr != "" { c.Printf("probe %s\n", c.Style.Err(d.Berthd.ProbeErr)) @@ -339,6 +349,9 @@ func vfioBerthdView(card vfioCard) *vfioBerthdDoc { return &vfioBerthdDoc{} } b := &vfioBerthdDoc{Reachable: true, ProbeErr: card.Daemon.ProbeErr} + if p := card.Daemon.Passthrough; p != nil { + b.Passthrough = p.State + } for _, g := range card.Daemon.GPUs { if g.Index == card.Index { b.GPU = &vfioGPUDoc{Index: g.Index, Name: g.Name, Used: g.Used, Total: g.Total, Age: g.Age} @@ -351,3 +364,13 @@ func vfioBerthdView(card vfioCard) *vfioBerthdDoc { } return b } + +// vfioDriverOf is the driver bound to the card's own function in the reading. +func vfioDriverOf(d vfioStatusDoc) string { + for _, f := range d.Group { + if f.Addr == d.Addr { + return f.Driver + } + } + return "" +} diff --git a/internal/cli/vfio_test.go b/internal/cli/vfio_test.go index ff1297c..9ff0dba 100644 --- a/internal/cli/vfio_test.go +++ b/internal/cli/vfio_test.go @@ -310,6 +310,24 @@ func TestVfioStatusReportsPassthroughWithAnAddress(t *testing.T) { } } +// TestVfioStatusWithAnAddressStillAsksBerthd: an address skips nvidia-smi, +// which cannot see a card on vfio-pci, but berthd still answers and its view +// is shown; it is not reported unreachable. +func TestVfioStatusWithAnAddressStillAsksBerthd(t *testing.T) { + isolate(t) + sock, _ := startVfioDaemon(t) + tr := newVfioTree(t) + tr.function(vfioAddr, "vfio-pci") + tr.group() + code, out, errOut := runVfioStatusIn(t, tr, tr.smi(), "--socket", sock, vfioAddr) + if code != ExitOK { + t.Fatalf("exit = %d, want 0 (stderr %q)", code, errOut) + } + if strings.Contains(out, "unreachable") { + t.Errorf("berthd answers but the report says unreachable: %q", out) + } +} + // TestVfioRunRefusesIOMMUOff: exit 1 with the hint, and nothing is acquired. func TestVfioRunRefusesIOMMUOff(t *testing.T) { isolate(t) @@ -632,3 +650,22 @@ func TestVfioRunScriptExit127StillWaitsForTheCard(t *testing.T) { } waitUntil(t, none(b)) } + +// TestVfioStatusNamesTheVMNotAStaleReading: while berthd reports the card in a +// VM, its memory figures are the last reading before the hold; the report +// says where the card is instead of printing them as current. +func TestVfioStatusNamesTheVMNotAStaleReading(t *testing.T) { + var out bytes.Buffer + c := &Context{Out: &out, Err: io.Discard, Format: FormatHuman, Style: newStyle(&out), errStyle: newStyle(io.Discard)} + doc := vfioStatusDoc{Addr: vfioAddr, IOMMU: true, Passed: true, + Group: []vfio.Function{{Addr: vfioAddr, Driver: "vfio-pci"}}, + Berthd: &vfioBerthdDoc{Reachable: true, Passthrough: "passthrough", + GPU: &vfioGPUDoc{Index: 0, Name: "RTX 4090", Used: 4 << 20, Total: 24 << 30, Age: 1400}}} + if err := (vfioStatusView{doc}).Human(c, true); err != nil { + t.Fatal(err) + } + got := out.String() + if !strings.Contains(got, "passed through to a VM (vfio-pci) (last memory reading 1400 s ago)") || strings.Contains(got, "used of") { + t.Errorf("berthd's line = %q, want the VM state and no current-looking memory figure", got) + } +}