github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/sentry/devices/nvproxy/uvm.go (about) 1 // Copyright 2023 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package nvproxy 16 17 import ( 18 "fmt" 19 20 "golang.org/x/sys/unix" 21 "github.com/metacubex/gvisor/pkg/abi/nvgpu" 22 "github.com/metacubex/gvisor/pkg/context" 23 "github.com/metacubex/gvisor/pkg/devutil" 24 "github.com/metacubex/gvisor/pkg/errors/linuxerr" 25 "github.com/metacubex/gvisor/pkg/fdnotifier" 26 "github.com/metacubex/gvisor/pkg/hostarch" 27 "github.com/metacubex/gvisor/pkg/log" 28 "github.com/metacubex/gvisor/pkg/marshal" 29 "github.com/metacubex/gvisor/pkg/sentry/arch" 30 "github.com/metacubex/gvisor/pkg/sentry/kernel" 31 "github.com/metacubex/gvisor/pkg/sentry/vfs" 32 "github.com/metacubex/gvisor/pkg/usermem" 33 "github.com/metacubex/gvisor/pkg/waiter" 34 ) 35 36 // uvmDevice implements vfs.Device for /dev/nvidia-uvm. 37 // 38 // +stateify savable 39 type uvmDevice struct { 40 nvp *nvproxy 41 } 42 43 // Open implements vfs.Device.Open. 44 func (dev *uvmDevice) Open(ctx context.Context, mnt *vfs.Mount, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { 45 devClient := devutil.GoferClientFromContext(ctx) 46 if devClient == nil { 47 log.Warningf("devutil.CtxDevGoferClient is not set") 48 return nil, linuxerr.ENOENT 49 } 50 hostFD, err := devClient.OpenAt(ctx, "nvidia-uvm", opts.Flags) 51 if err != nil { 52 ctx.Warningf("nvproxy: failed to open host /dev/nvidia-uvm: %v", err) 53 return nil, err 54 } 55 fd := &uvmFD{ 56 nvp: dev.nvp, 57 hostFD: int32(hostFD), 58 } 59 if err := fd.vfsfd.Init(fd, opts.Flags, mnt, vfsd, &vfs.FileDescriptionOptions{ 60 UseDentryMetadata: true, 61 }); err != nil { 62 unix.Close(hostFD) 63 return nil, err 64 } 65 if err := fdnotifier.AddFD(int32(hostFD), &fd.queue); err != nil { 66 unix.Close(hostFD) 67 return nil, err 68 } 69 fd.memmapFile.fd = fd 70 return &fd.vfsfd, nil 71 } 72 73 // uvmFD implements vfs.FileDescriptionImpl for /dev/nvidia-uvm. 74 // 75 // uvmFD is not savable; we do not implement save/restore of host GPU state. 76 type uvmFD struct { 77 vfsfd vfs.FileDescription 78 vfs.FileDescriptionDefaultImpl 79 vfs.DentryMetadataFileDescriptionImpl 80 vfs.NoLockFD 81 82 nvp *nvproxy 83 hostFD int32 84 memmapFile uvmFDMemmapFile 85 86 queue waiter.Queue 87 } 88 89 // Release implements vfs.FileDescriptionImpl.Release. 90 func (fd *uvmFD) Release(context.Context) { 91 fdnotifier.RemoveFD(fd.hostFD) 92 fd.queue.Notify(waiter.EventHUp) 93 unix.Close(int(fd.hostFD)) 94 } 95 96 // EventRegister implements waiter.Waitable.EventRegister. 97 func (fd *uvmFD) EventRegister(e *waiter.Entry) error { 98 fd.queue.EventRegister(e) 99 if err := fdnotifier.UpdateFD(fd.hostFD); err != nil { 100 fd.queue.EventUnregister(e) 101 return err 102 } 103 return nil 104 } 105 106 // EventUnregister implements waiter.Waitable.EventUnregister. 107 func (fd *uvmFD) EventUnregister(e *waiter.Entry) { 108 fd.queue.EventUnregister(e) 109 if err := fdnotifier.UpdateFD(fd.hostFD); err != nil { 110 panic(fmt.Sprint("UpdateFD:", err)) 111 } 112 } 113 114 // Readiness implements waiter.Waitable.Readiness. 115 func (fd *uvmFD) Readiness(mask waiter.EventMask) waiter.EventMask { 116 return fdnotifier.NonBlockingPoll(fd.hostFD, mask) 117 } 118 119 // Epollable implements vfs.FileDescriptionImpl.Epollable. 120 func (fd *uvmFD) Epollable() bool { 121 return true 122 } 123 124 // Ioctl implements vfs.FileDescriptionImpl.Ioctl. 125 func (fd *uvmFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) { 126 cmd := args[1].Uint() 127 argPtr := args[2].Pointer() 128 129 t := kernel.TaskFromContext(ctx) 130 if t == nil { 131 panic("Ioctl should be called from a task context") 132 } 133 134 if log.IsLogging(log.Debug) { 135 ctx.Debugf("nvproxy: uvm ioctl %#08x", cmd) 136 } 137 138 ui := uvmIoctlState{ 139 fd: fd, 140 ctx: ctx, 141 t: t, 142 cmd: cmd, 143 ioctlParamsAddr: argPtr, 144 } 145 handler := fd.nvp.abi.uvmIoctl[cmd] 146 if handler == nil { 147 ctx.Warningf("nvproxy: unknown uvm ioctl %d", cmd) 148 return 0, linuxerr.EINVAL 149 } 150 return handler(&ui) 151 } 152 153 // uvmIoctlState holds the state of a call to uvmFD.Ioctl(). 154 type uvmIoctlState struct { 155 fd *uvmFD 156 ctx context.Context 157 t *kernel.Task 158 cmd uint32 159 ioctlParamsAddr hostarch.Addr 160 } 161 162 func uvmIoctlNoParams(ui *uvmIoctlState) (uintptr, error) { 163 return uvmIoctlInvoke[byte](ui, nil) 164 } 165 166 func uvmIoctlSimple[Params any, PParams marshalPtr[Params]](ui *uvmIoctlState) (uintptr, error) { 167 var ioctlParams Params 168 if _, err := (PParams)(&ioctlParams).CopyIn(ui.t, ui.ioctlParamsAddr); err != nil { 169 return 0, err 170 } 171 n, err := uvmIoctlInvoke(ui, &ioctlParams) 172 if err != nil { 173 return n, err 174 } 175 if _, err := (PParams)(&ioctlParams).CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 176 return n, err 177 } 178 return n, nil 179 } 180 181 func uvmInitialize(ui *uvmIoctlState) (uintptr, error) { 182 var ioctlParams nvgpu.UVM_INITIALIZE_PARAMS 183 if _, err := ioctlParams.CopyIn(ui.t, ui.ioctlParamsAddr); err != nil { 184 return 0, err 185 } 186 sentryIoctlParams := ioctlParams 187 // This is necessary to share the host UVM FD between sentry and 188 // application processes. 189 sentryIoctlParams.Flags = ioctlParams.Flags | nvgpu.UVM_INIT_FLAGS_MULTI_PROCESS_SHARING_MODE 190 n, err := uvmIoctlInvoke(ui, &sentryIoctlParams) 191 if err != nil { 192 return n, err 193 } 194 outIoctlParams := sentryIoctlParams 195 // Only expose the MULTI_PROCESS_SHARING_MODE flag if it was present in 196 // ioctlParams. 197 outIoctlParams.Flags &^= ^ioctlParams.Flags & nvgpu.UVM_INIT_FLAGS_MULTI_PROCESS_SHARING_MODE 198 if _, err := outIoctlParams.CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 199 return n, err 200 } 201 return n, nil 202 } 203 204 func uvmMMInitialize(ui *uvmIoctlState) (uintptr, error) { 205 var ioctlParams nvgpu.UVM_MM_INITIALIZE_PARAMS 206 if _, err := ioctlParams.CopyIn(ui.t, ui.ioctlParamsAddr); err != nil { 207 return 0, err 208 } 209 210 failWithStatus := func(status uint32) error { 211 outIoctlParams := ioctlParams 212 outIoctlParams.Status = status 213 _, err := outIoctlParams.CopyOut(ui.t, ui.ioctlParamsAddr) 214 return err 215 } 216 217 uvmFileGeneric, _ := ui.t.FDTable().Get(ioctlParams.UvmFD) 218 if uvmFileGeneric == nil { 219 return 0, failWithStatus(nvgpu.NV_ERR_INVALID_ARGUMENT) 220 } 221 defer uvmFileGeneric.DecRef(ui.ctx) 222 uvmFile, ok := uvmFileGeneric.Impl().(*uvmFD) 223 if !ok { 224 return 0, failWithStatus(nvgpu.NV_ERR_INVALID_ARGUMENT) 225 } 226 227 sentryIoctlParams := ioctlParams 228 sentryIoctlParams.UvmFD = uvmFile.hostFD 229 n, err := uvmIoctlInvoke(ui, &sentryIoctlParams) 230 if err != nil { 231 return n, err 232 } 233 234 outIoctlParams := sentryIoctlParams 235 outIoctlParams.UvmFD = ioctlParams.UvmFD 236 if _, err := outIoctlParams.CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 237 return n, err 238 } 239 return n, nil 240 } 241 242 type hasRMCtrlFDPtr[T any] interface { 243 *T 244 marshal.Marshallable 245 nvgpu.HasRMCtrlFD 246 } 247 248 func uvmIoctlHasRMCtrlFD[Params any, PParams hasRMCtrlFDPtr[Params]](ui *uvmIoctlState) (uintptr, error) { 249 var ioctlParams Params 250 if _, err := (PParams)(&ioctlParams).CopyIn(ui.t, ui.ioctlParamsAddr); err != nil { 251 return 0, err 252 } 253 254 rmCtrlFD := (PParams)(&ioctlParams).GetRMCtrlFD() 255 if rmCtrlFD < 0 { 256 n, err := uvmIoctlInvoke(ui, &ioctlParams) 257 if err != nil { 258 return n, err 259 } 260 if _, err := (PParams)(&ioctlParams).CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 261 return n, err 262 } 263 return n, nil 264 } 265 266 ctlFileGeneric, _ := ui.t.FDTable().Get(rmCtrlFD) 267 if ctlFileGeneric == nil { 268 return 0, linuxerr.EINVAL 269 } 270 defer ctlFileGeneric.DecRef(ui.ctx) 271 ctlFile, ok := ctlFileGeneric.Impl().(*frontendFD) 272 if !ok { 273 return 0, linuxerr.EINVAL 274 } 275 276 sentryIoctlParams := ioctlParams 277 (PParams)(&sentryIoctlParams).SetRMCtrlFD(ctlFile.hostFD) 278 n, err := uvmIoctlInvoke(ui, &sentryIoctlParams) 279 if err != nil { 280 return n, err 281 } 282 283 outIoctlParams := sentryIoctlParams 284 (PParams)(&outIoctlParams).SetRMCtrlFD(rmCtrlFD) 285 if _, err := (PParams)(&outIoctlParams).CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 286 return n, err 287 } 288 289 return n, nil 290 }