gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/devices/nvproxy/object.go (about)

     1  // Copyright 2024 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  	"gvisor.dev/gvisor/pkg/abi/nvgpu"
    19  	"gvisor.dev/gvisor/pkg/context"
    20  	"gvisor.dev/gvisor/pkg/log"
    21  	"gvisor.dev/gvisor/pkg/marshal"
    22  	"gvisor.dev/gvisor/pkg/sentry/mm"
    23  )
    24  
    25  // object tracks a driver object.
    26  //
    27  // +stateify savable
    28  type object struct {
    29  	// These fields are initialized by nvproxy.objAdd() and are immutable thereafter.
    30  	nvp    *nvproxy
    31  	client *rootClient // may be == impl
    32  	class  nvgpu.ClassID
    33  	handle nvgpu.Handle // in client.resources, and also nvp.clients if impl is rootClient
    34  	impl   objectImpl
    35  
    36  	// The driver tracks parent/child relationships and "arbitrary dependency"
    37  	// relationships between objects separately; we treat parent/child
    38  	// relationships as equivalent to other dependencies. These fields are
    39  	// protected by nvp.objsMu.
    40  	deps  map[*object]struct{} // objects that this object depends on
    41  	rdeps map[*object]struct{} // objects that depend on this object
    42  	objectFreeEntry
    43  }
    44  
    45  type objectImpl interface {
    46  	// Object returns the object embedded in this objectImpl.
    47  	Object() *object
    48  
    49  	// Release is called when the driver object represented by this objectImpl
    50  	// is freed.
    51  	//
    52  	// Preconditions: nvproxy.objsMu must be locked.
    53  	Release(ctx context.Context)
    54  }
    55  
    56  // Object implements objectImpl.Object.
    57  func (o *object) Object() *object {
    58  	return o
    59  }
    60  
    61  func (nvp *nvproxy) objsLock() {
    62  	nvp.objsMu.Lock()
    63  }
    64  
    65  func (nvp *nvproxy) objsUnlock() {
    66  	cleanup := nvp.objsCleanup
    67  	nvp.objsCleanup = nil
    68  	nvp.objsMu.Unlock()
    69  	for _, f := range cleanup {
    70  		f()
    71  	}
    72  }
    73  
    74  // objAdd records the allocation of a driver object with class c and handle h,
    75  // in the client with handle clientH, represented by oi. Each non-zero handle
    76  // in deps is a dependency of the created object, such that the freeing of any
    77  // of those objects also results in the freeing of the recorded object.
    78  func (nvp *nvproxy) objAdd(ctx context.Context, clientH, h nvgpu.Handle, c nvgpu.ClassID, oi objectImpl, deps ...nvgpu.Handle) {
    79  	if h.Val == 0 {
    80  		log.Traceback("nvproxy: new object (class %v) has invalid handle 0", c)
    81  		return
    82  	}
    83  	var client *rootClient
    84  	// The driver forced NV01_ROOT and NV01_ROOT_NON_PRIV to NV01_ROOT_CLIENT,
    85  	// so we only need to check for the latter.
    86  	if c == nvgpu.NV01_ROOT_CLIENT {
    87  		clientH = h
    88  		client = oi.(*rootClient)
    89  		if _, ok := nvp.clients[h]; ok {
    90  			ctx.Warningf("nvproxy: client handle %v already in use", h)
    91  		}
    92  		nvp.clients[h] = client
    93  	} else {
    94  		var ok bool
    95  		client, ok = nvp.clients[clientH]
    96  		if !ok {
    97  			log.Traceback("nvproxy: new object %v (class %v) has invalid client handle %v", h, c, clientH)
    98  			return
    99  		}
   100  	}
   101  	o := oi.Object()
   102  	o.nvp = nvp
   103  	o.client = client
   104  	o.class = c
   105  	o.handle = h
   106  	o.impl = oi
   107  	if _, ok := client.resources[h]; ok {
   108  		ctx.Warningf("nvproxy: handle %v:%v already in use", clientH, h)
   109  	}
   110  	client.resources[h] = o
   111  	for _, depH := range deps {
   112  		if depH.Val == 0 /* aka NV01_NULL_OBJECT */ {
   113  			continue
   114  		}
   115  		dep, ok := client.resources[depH]
   116  		if !ok {
   117  			log.Traceback("nvproxy: new object %v:%v (class %v) has invalid dependency handle %v", clientH, h, c, depH)
   118  			continue
   119  		}
   120  		nvp.objDep(o, dep)
   121  	}
   122  	if ctx.IsLogging(log.Debug) {
   123  		ctx.Debugf("nvproxy: added object %v:%v (class %v) with dependencies %v", clientH, h, c, deps)
   124  	}
   125  }
   126  
   127  // objAddDep records a dependency between the existing object with handle h1 on
   128  // the existing object with handle h2, such that the freeing of the object with
   129  // handle h2 results in the freeing of object h1. Both h1 and h2 are handles in
   130  // the client with handle clientH.
   131  func (nvp *nvproxy) objAddDep(clientH, h1, h2 nvgpu.Handle) {
   132  	if h1.Val == 0 || h2.Val == 0 {
   133  		return
   134  	}
   135  	client, ok := nvp.clients[clientH]
   136  	if !ok {
   137  		log.Traceback("nvproxy: invalid client handle %v", clientH)
   138  		return
   139  	}
   140  	o1, ok := client.resources[h1]
   141  	if !ok {
   142  		log.Traceback("nvproxy: invalid handle %v:%v", clientH, h1)
   143  		return
   144  	}
   145  	o2, ok := client.resources[h2]
   146  	if !ok {
   147  		log.Traceback("nvproxy: invalid handle %v:%v", clientH, h2)
   148  		return
   149  	}
   150  	nvp.objDep(o1, o2)
   151  }
   152  
   153  func (nvp *nvproxy) objDep(o1, o2 *object) {
   154  	if o1.deps == nil {
   155  		o1.deps = make(map[*object]struct{})
   156  	}
   157  	o1.deps[o2] = struct{}{}
   158  	if o2.rdeps == nil {
   159  		o2.rdeps = make(map[*object]struct{})
   160  	}
   161  	o2.rdeps[o1] = struct{}{}
   162  }
   163  
   164  // objFree marks an object and its transitive dependents as freed.
   165  //
   166  // Compare
   167  // src/nvidia/src/libraries/resserv/src/rs_server.c:serverFreeResourceTree().
   168  func (nvp *nvproxy) objFree(ctx context.Context, clientH, h nvgpu.Handle) {
   169  	// Check for recursive calls to objFree() (via objectImpl.Release()).
   170  	// serverFreeResourceTree() permits this; we currently don't for
   171  	// simplicity.
   172  	if !nvp.objsFreeList.Empty() {
   173  		panic("nvproxy.objFree called with non-empty free list (possible recursion?)")
   174  	}
   175  
   176  	client, ok := nvp.clients[clientH]
   177  	if !ok {
   178  		ctx.Warningf("nvproxy: freeing object handle %v with unknown client handle %v", h, clientH)
   179  		return
   180  	}
   181  	o, ok := client.resources[h]
   182  	if !ok {
   183  		// When RS_COMPATABILITY_MODE is defined as true in the driver (as it
   184  		// is in Linux), the driver permits NV_ESC_RM_FREE on nonexistent
   185  		// handles as a no-op, and applications do this, so log at level INFO
   186  		// rather than WARNING.
   187  		ctx.Infof("nvproxy: freeing object with unknown handle %v:%v", clientH, h)
   188  		return
   189  	}
   190  	nvp.prependFreedLockedRecursive(o)
   191  	for !nvp.objsFreeList.Empty() {
   192  		o2 := nvp.objsFreeList.Front()
   193  		o2.impl.Release(ctx)
   194  		for o3 := range o2.deps {
   195  			delete(o3.rdeps, o2)
   196  		}
   197  		delete(o2.client.resources, o2.handle)
   198  		if o2.class == nvgpu.NV01_ROOT_CLIENT {
   199  			delete(nvp.clients, o2.handle)
   200  		}
   201  		nvp.objsFreeList.Remove(o2)
   202  		delete(nvp.objsFreeSet, o2)
   203  		if ctx.IsLogging(log.Debug) {
   204  			ctx.Debugf("nvproxy: freed object %v:%v (class %v)", o2.client.handle, o2.handle, o2.class)
   205  		}
   206  	}
   207  }
   208  
   209  func (nvp *nvproxy) prependFreedLockedRecursive(o *object) {
   210  	if _, ok := nvp.objsFreeSet[o]; ok {
   211  		// o is already on the free list; move it to the front so that it
   212  		// remains freed before our caller's o.
   213  		nvp.objsFreeList.Remove(o)
   214  	} else {
   215  		nvp.objsFreeSet[o] = struct{}{}
   216  	}
   217  	nvp.objsFreeList.PushFront(o)
   218  
   219  	// In the driver, freeing an object causes its children and dependents to
   220  	// be freed first; see
   221  	// src/nvidia/src/libraries/resserv/src/rs_server.c:serverFreeResourceTree()
   222  	// => clientUpdatePendingFreeList_IMPL(). Replicate this freeing order.
   223  	for o2 := range o.rdeps {
   224  		nvp.prependFreedLockedRecursive(o2)
   225  	}
   226  }
   227  
   228  // enqueueCleanup enqueues a cleanup function that will run after nvp.objsMu is
   229  // unlocked.
   230  func (nvp *nvproxy) enqueueCleanup(f func()) {
   231  	nvp.objsCleanup = append(nvp.objsCleanup, f)
   232  }
   233  
   234  // +stateify savable
   235  type capturedRmAllocParams struct {
   236  	fd              *frontendFD
   237  	ioctlParams     nvgpu.NVOS64Parameters
   238  	rightsRequested nvgpu.RS_ACCESS_MASK
   239  	allocParams     []byte
   240  }
   241  
   242  func captureRmAllocParams[Params any](fd *frontendFD, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *Params) capturedRmAllocParams {
   243  	var allocParamsBuf []byte
   244  	if allocParams != nil {
   245  		if allocParamsMarshal, ok := any(allocParams).(marshal.Marshallable); ok {
   246  			allocParamsBuf = make([]byte, allocParamsMarshal.SizeBytes())
   247  			allocParamsMarshal.MarshalBytes(allocParamsBuf)
   248  		} else {
   249  			log.Traceback("nvproxy: allocParams %T is not marshalable")
   250  		}
   251  	}
   252  	return capturedRmAllocParams{
   253  		fd:              fd,
   254  		ioctlParams:     *ioctlParams,
   255  		rightsRequested: rightsRequested,
   256  		allocParams:     allocParamsBuf,
   257  	}
   258  }
   259  
   260  // rmAllocObject is an objectImpl tracking a driver object allocated by an
   261  // invocation of NV_ESC_RM_ALLOC whose class is not represented by a more
   262  // specific type.
   263  //
   264  // +stateify savable
   265  type rmAllocObject struct {
   266  	object
   267  
   268  	params capturedRmAllocParams
   269  }
   270  
   271  func newRmAllocObject[Params any](fd *frontendFD, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *Params) *rmAllocObject {
   272  	return &rmAllocObject{
   273  		params: captureRmAllocParams(fd, ioctlParams, rightsRequested, allocParams),
   274  	}
   275  }
   276  
   277  // Release implements objectImpl.Release.
   278  func (o *rmAllocObject) Release(ctx context.Context) {
   279  	// no-op
   280  }
   281  
   282  // rootClient is an objectImpl tracking a NV01_ROOT_CLIENT.
   283  //
   284  // +stateify savable
   285  type rootClient struct {
   286  	object
   287  
   288  	// These fields are protected by nvproxy.objsMu.
   289  	resources map[nvgpu.Handle]*object
   290  
   291  	params capturedRmAllocParams
   292  }
   293  
   294  func newRootClient(fd *frontendFD, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.Handle) *rootClient {
   295  	return &rootClient{
   296  		resources: make(map[nvgpu.Handle]*object),
   297  		params:    captureRmAllocParams(fd, ioctlParams, rightsRequested, allocParams),
   298  	}
   299  }
   300  
   301  // Release implements objectImpl.Release.
   302  func (o *rootClient) Release(ctx context.Context) {
   303  	delete(o.params.fd.clients, o.handle)
   304  }
   305  
   306  // osDescMem is an objectImpl tracking a NV01_MEMORY_SYSTEM_OS_DESCRIPTOR.
   307  type osDescMem struct {
   308  	object
   309  	pinnedRanges []mm.PinnedRange
   310  }
   311  
   312  // Release implements objectImpl.Release.
   313  func (o *osDescMem) Release(ctx context.Context) {
   314  	// Unpin pages (which takes MM locks) without holding nvproxy locks.
   315  	o.nvp.enqueueCleanup(func() {
   316  		mm.Unpin(o.pinnedRanges)
   317  		if ctx.IsLogging(log.Debug) {
   318  			total := uint64(0)
   319  			for _, pr := range o.pinnedRanges {
   320  				total += uint64(pr.Source.Length())
   321  			}
   322  			ctx.Debugf("nvproxy: unpinned %d bytes for released OS descriptor", total)
   323  		}
   324  	})
   325  }
   326  
   327  // osEvent is an objectImpl tracking a NV01_EVENT_OS_EVENT.
   328  type osEvent struct {
   329  	object
   330  }
   331  
   332  // Release implements objectImpl.Release.
   333  func (o *osEvent) Release(ctx context.Context) {
   334  	// no-op
   335  }
   336  
   337  // virtMem is an objectImpl tracking a NV50_MEMORY_VIRTUAL.
   338  type virtMem struct {
   339  	object
   340  }
   341  
   342  // Release implements objectImpl.Release.
   343  func (o *virtMem) Release(ctx context.Context) {
   344  	// no-op
   345  }