summaryrefslogtreecommitdiffstats
path: root/tools/cuda-bridge/INTEGRATION.md
blob: fcdce8057f85009d445843c2de8077e18a08cff8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# vgsp.ko integration (v2, live path)

The bridge is a userspace daemon with a fixed-size file protocol so the vGPU
kernel module drives it without sharing kernel/userspace memory. The v2 records
are frozen in `PROTOCOL.md` and mirrored by static asserts in
`kernel/gsp_cuda_bridge.c` and `tools/cuda-bridge/cuda_bridge.c`.

## Expected flow

1. **Detect launch.** `vgsp.ko`'s service thread decodes the guest's inline QMD
   on the emulated GR compute channel (`gsp_sema_service.c`), classifies the
   launch shape (mail vs vadd) and reads the parameter bank's pointers/count.
2. **Marshal + publish.** The module resolves the guest source VAs, copies the
   inputs into its own buffer (vadd: `a` then `b`; mail: none) and publishes a
   256-byte `CUBB2` job with a fresh `seq` in `cuda_bridge_job` while serving
   the input blob from `cuda_bridge_in`. One job is published at a time
   (`CUBB2_PEND_MAX` slots, oldest first).
3. **Daemon executes.** `cuda_bridge --bridge --job
   /sys/kernel/debug/vgsp/cuda_bridge_job --in
   /sys/kernel/debug/vgsp/cuda_bridge_in --out
   /sys/kernel/debug/vgsp/cuda_bridge_out --done
   /sys/kernel/debug/vgsp/cuda_bridge_done` polls the job file (default 50 ms),
   reads the inputs, executes the requested kernel on the physical GPU
   (embedded PTX; `cuMemcpyHtoD` / `cuLaunchKernel` / `cuMemcpyDtoH`), writes
   the output blob to `cuda_bridge_out`, then always writes a `CBD2` done
   record to `cuda_bridge_done`.
4. **Module completes the guest.** `vgsp.ko` pops done records on the service
   thread, matches `seq`, copies the output blob through the VA translation
   back into the guest destination, applies the QMD release semaphore, and the
   guest's `cudaDeviceSynchronize` completes.

Everything is inert until a launch is seen; the path can be switched off with
the `cuda_bridge` module parameter (default on).

## Daemon invocation (frozen CLI)

```
cuda_bridge --bridge \
    --job  /sys/kernel/debug/vgsp/cuda_bridge_job \
    --in   /sys/kernel/debug/vgsp/cuda_bridge_in \
    --out  /sys/kernel/debug/vgsp/cuda_bridge_out \
    --done /sys/kernel/debug/vgsp/cuda_bridge_done
```

`--poll-ms` overrides the 50 ms default; `--once` exits after one job
(bring-up only). The legacy v1 `--job J --done D --ram R` GPA mode is separate
and unchanged.

## Debugfs permissions

`cuda_bridge_job`/`_in` are `0444` and `_out`/`_done` are `0200`, owned by
root. When the daemon runs unprivileged, the runner opens them first:

```
for f in job in out done status; do
    sudo chmod 666 /sys/kernel/debug/vgsp/cuda_bridge_$f 2>/dev/null
done
```

## seq semantics

* The module's counter starts at 1 and only increases; a job record is
  immutable while pending.
* The daemon records `last_seq` and ignores any valid record with
  `seq <= last_seq` (duplicate/replay protection). The first valid record
  after daemon startup always executes, so a daemon restart can re-run at most
  the single stale job still parked in the interface.
* A done record whose `seq` matches no pending module job is counted `stale`
  and dropped; malformed magic/version is counted `malformed`. Both are
  visible in `cuda_bridge_status`.

## Logging

The daemon logs one line per executed job (stdout only; correctness never
depends on it):

```
CUBB2 seq=42 op=2 n=1048576 gpu="NVIDIA GeForce RTX 2080 Super with Max-Q Design" \
      kernel_ns=41919 status=ok checksum=392344090.500000 bad=0 out_bytes=4194304
```

`status=err` lines are preceded by a `bridge: job seq=... rejected/failed: ...`
stderr line naming the cause (bounds, bad opcode, input transport, output
transport). Every job — success or failure — gets a done record; failures
never leave the module waiting.

## Limits and error mapping

* `n <= 1<<26`; `in_bytes`, `out_bytes <= 32 MiB`; mail `n <= 4096` (module
  param-length limit). Violations: `status = 1` (`bad job`), `err_code = 22`
  (`EINVAL`).
* Bad opcode: `status = 1`, `err_code = opcode`.
* Input read failure: `status = 3`, `err_code = 5` (`EIO`); allocation
  failure: `err_code = 12` (`ENOMEM`).
* CUDA failure: `status = 2`, `err_code = CUresult`.
* Output write failure: `status = 3`, `err_code = errno`.
* `bad` is data quality, not execution failure: `status` stays 0 when the
  kernel ran and the output was transported.

## Risks / open questions

* **Single writer per job interface.** One job is visible at a time and the
  module serialises publication; the daemon's `last_seq` only protects
  duplicates. Multiple daemons against one mdev are unsupported.
* **Daemon lifetime / restarts.** On restart `last_seq` resets and the one
  parked job may run again. The module can clear or bump the job when (re)starting
  the daemon; the re-run is idempotent for mail/vadd.
* **Timeouts.** A stuck job has no protocol-level timeout; the module bounds
  `n`/bytes and the runner has bounded waits. A watchdog belongs on the
  module side.
* **Device selection.** The daemon always uses `cuDeviceGet(0)`; multi-GPU
  hosts need a `--device` selector and the module must agree on the physical
  GPU behind the mdev.
* **PTX target.** The embedded PTX is `sm_75` (JIT-ed by the driver for the
  physical GPU); a newer-only GPU needs the build-time `PTX_ARCH` bumped.