Skip to content

Commit

Permalink
Add memmap.File.MemoryType()
Browse files Browse the repository at this point in the history
This has no effect (outside of debug logging) until cl/723723715.

Updates #11436

PiperOrigin-RevId: 723723714
  • Loading branch information
nixprime authored and gvisor-bot committed Feb 13, 2025
1 parent dd8ea25 commit 51a58b5
Show file tree
Hide file tree
Showing 18 changed files with 254 additions and 42 deletions.
14 changes: 13 additions & 1 deletion pkg/abi/nvgpu/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ type NVOS02_PARAMETERS struct {
Pad1 [4]byte
}

// Bitfields in NVOS02Parameters.Flags:
// Bitfields in NVOS02_PARAMETERS.Flags:
const (
NVOS02_FLAGS_ALLOC_SHIFT = 16
NVOS02_FLAGS_ALLOC_MASK = 0x3
Expand Down Expand Up @@ -470,6 +470,18 @@ type NVOS33_PARAMETERS struct {
Flags uint32
}

// Bitfields in NVOS33_PARAMETERS.Flags:
const (
NVOS33_FLAGS_CACHING_TYPE_SHIFT = 23
NVOS33_FLAGS_CACHING_TYPE_MASK = 0x7
NVOS33_FLAGS_CACHING_TYPE_CACHED = 0
NVOS33_FLAGS_CACHING_TYPE_UNCACHED = 1
NVOS33_FLAGS_CACHING_TYPE_WRITECOMBINED = 2
NVOS33_FLAGS_CACHING_TYPE_WRITEBACK = 5
NVOS33_FLAGS_CACHING_TYPE_DEFAULT = 6
NVOS33_FLAGS_CACHING_TYPE_UNCACHED_WEAK = 7
)

// NVOS34_PARAMETERS is the parameter type for NV_ESC_RM_UNMAP_MEMORY.
//
// +marshal
Expand Down
1 change: 1 addition & 0 deletions pkg/hostarch/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ go_library(
"hostarch.go",
"hostarch_arm64.go",
"hostarch_x86.go",
"memory_type.go",
"sizes_util.go",
],
visibility = ["//:sandbox"],
Expand Down
84 changes: 84 additions & 0 deletions pkg/hostarch/memory_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2025 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hostarch

import "fmt"

// MemoryType specifies CPU memory access behavior.
type MemoryType uint8

const (
// MemoryTypeWriteBack is equivalent to Linux's default pgprot, or the
// following architectural memory types:
//
// - x86: Write-back (WB)
//
// - ARM64: Normal write-back cacheable
//
// This memory type is appropriate for typical application memory and must
// be the zero value for MemoryType.
MemoryTypeWriteBack MemoryType = iota

// MemoryTypeWriteCombine is equivalent to Linux's pgprot_writecombine(),
// or the following architectural memory types:
//
// - x86: Write-combining (WC)
//
// - ARM64: Normal non-cacheable
MemoryTypeWriteCombine

// MemoryTypeUncached is equivalent to Linux's pgprot_noncached(), or the
// following architectural memory types:
//
// - x86: Strong Uncacheable (UC) or Uncacheable (UC-); these differ in
// that UC- may be "downgraded" to WC by a setting of WC or (Intel only) WP
// in MTRR or EPT/NPT, but gVisor does not use MTRRs and KVM never sets WC
// or WP in EPT/NPT.
//
// - ARM64: Device-nGnRnE
MemoryTypeUncached

// NumMemoryTypes is the number of memory types.
NumMemoryTypes
)

// String implements fmt.Stringer.String.
func (mt MemoryType) String() string {
switch mt {
case MemoryTypeWriteBack:
return "WriteBack"
case MemoryTypeWriteCombine:
return "WriteCombine"
case MemoryTypeUncached:
return "Uncached"
default:
return fmt.Sprintf("%d", mt)
}
}

// ShortString returns a two-character string compactly representing the
// MemoryType.
func (mt MemoryType) ShortString() string {
switch mt {
case MemoryTypeWriteBack:
return "WB"
case MemoryTypeWriteCombine:
return "WC"
case MemoryTypeUncached:
return "UC"
default:
return fmt.Sprintf("%02d", mt)
}
}
21 changes: 18 additions & 3 deletions pkg/sentry/devices/nvproxy/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ type frontendDevice struct {
minor uint32
}

func (dev *frontendDevice) isCtlDevice() bool {
return dev.minor == nvgpu.NV_CONTROL_DEVICE_MINOR
}

func (dev *frontendDevice) basename() string {
if dev.minor == nvgpu.NV_CONTROL_DEVICE_MINOR {
if dev.isCtlDevice() {
return "nvidiactl"
}
return fmt.Sprintf("nvidia%d", dev.minor)
Expand Down Expand Up @@ -134,8 +138,9 @@ type frontendFD struct {
// These fields are marked nosave since we do not automatically reinvoke
// NV_ESC_RM_MAP_MEMORY after restore, so restored FDs have no
// mmap_context.
mmapLength uint64 `state:"nosave"`
mmapInternal uintptr `state:"nosave"`
mmapLength uint64 `state:"nosave"`
mmapInternal uintptr `state:"nosave"`
mmapMemType hostarch.MemoryType `state:"nosave"`

// clients are handles of clients owned by this frontendFD. clients is
// protected by dev.nvp.objsMu.
Expand Down Expand Up @@ -493,6 +498,7 @@ func rmAllocMemorySystem(fi *frontendIoctlState, ioctlParams *nvgpu.IoctlNVOS02P
fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.Params.HRoot, ioctlParams.Params.HObjectNew, ioctlParams.Params.HClass, &miscObject{}, ioctlParams.Params.HObjectParent)
if createMmapCtx {
mapFile.mmapLength = ioctlParams.Params.Limit + 1
mapFile.mmapMemType = getMemoryType(fi.ctx, mapFile.dev, nvgpu.NVOS33_FLAGS_CACHING_TYPE_DEFAULT)
}
}
fi.fd.dev.nvp.objsUnlock()
Expand Down Expand Up @@ -1343,6 +1349,15 @@ func rmMapMemory(fi *frontendIoctlState) (uintptr, error) {
}
if ioctlParams.Params.Status == nvgpu.NV_OK {
mapFile.mmapLength = ioctlParams.Params.Length
// src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl() forces
// NVOS33_FLAGS_CACHING_TYPE_DEFAULT, but resMap implementations may
// override the "caching type", so in general the memory type depends
// on the mapped object. Conveniently, when this occurs, the caching
// type in pParms->flags must be updated for the call to
// rm_create_mmap_context(), and pParms is subsequently copied back out
// by kernel-open/nvidia/nv.c:nvidia_ioctl(), so we can get the final
// caching type from the updated ioctl params.
mapFile.mmapMemType = getMemoryType(fi.ctx, mapFile.dev, (ioctlParams.Params.Flags>>nvgpu.NVOS33_FLAGS_CACHING_TYPE_SHIFT)&nvgpu.NVOS33_FLAGS_CACHING_TYPE_MASK)
}

ioctlParams.FD = origFD
Expand Down
68 changes: 68 additions & 0 deletions pkg/sentry/devices/nvproxy/frontend_mmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
package nvproxy

import (
"gvisor.dev/gvisor/pkg/abi/nvgpu"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
Expand Down Expand Up @@ -75,6 +77,13 @@ func (mf *frontendFDMemmapFile) IncRef(fr memmap.FileRange, memCgID uint32) {
func (mf *frontendFDMemmapFile) DecRef(fr memmap.FileRange) {
}

// MemoryType implements memmap.File.MemoryType.
func (mf *frontendFDMemmapFile) MemoryType() hostarch.MemoryType {
mf.fd.mmapMu.Lock()
defer mf.fd.mmapMu.Unlock()
return mf.fd.mmapMemType
}

// DataFD implements memmap.File.DataFD.
func (mf *frontendFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
return mf.FD(), nil
Expand All @@ -84,3 +93,62 @@ func (mf *frontendFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
func (mf *frontendFDMemmapFile) FD() int {
return int(mf.fd.hostFD)
}

func getMemoryType(ctx context.Context, mapDev *frontendDevice, cachingType uint32) hostarch.MemoryType {
// Compare kernel-open/nvidia/nv-mmap.c:nvidia_mmap_helper() =>
// nv_encode_caching(). Each NVOS33_FLAGS_CACHING_TYPE_* corresponds
// directly to a NV_MEMORY_*; this is checked by asserts in
// src/nvidia/src/kernel/rmapi/mapping_cpu.c.
if !mapDev.isCtlDevice() {
// NOTE(gvisor.dev/issue/11436): In the !NV_IS_CTL_DEVICE() branch of
// nvidia_mmap_helper(), mmap_context->caching is only honored if
// IS_FB_OFFSET() and !IS_UD_OFFSET(). We can get the information we
// need for IS_FB_OFFSET() from NV_ESC_CARD_INFO, but there doesn't
// seem to be any way for us to replicate IS_UD_OFFSET(). So we must
// conservatively specify uncacheable, which applies in all other
// cases. This is unfortunate since it prevents us from using
// write-combining on framebuffer memory. Empirically, mappings of
// framebuffer memory seem to be fairly common, but none of our tests
// result in any IS_UD_OFFSET (USERD?) mappings.
if log.IsLogging(log.Debug) {
ctx.Debugf("nvproxy: inferred memory type %v for mapping of %s", hostarch.MemoryTypeUncached, mapDev.basename())
}
return hostarch.MemoryTypeUncached
}
var memType hostarch.MemoryType
switch cachingType {
case nvgpu.NVOS33_FLAGS_CACHING_TYPE_CACHED, nvgpu.NVOS33_FLAGS_CACHING_TYPE_WRITEBACK:
// Note that nv_encode_caching() doesn't actually handle
// NV_MEMORY_WRITEBACK, so this case should fail during host mmap.
memType = hostarch.MemoryTypeWriteBack
case nvgpu.NVOS33_FLAGS_CACHING_TYPE_WRITECOMBINED, nvgpu.NVOS33_FLAGS_CACHING_TYPE_DEFAULT:
// NOTE(gvisor.dev/issue/11436): In the NV_IS_CTL_DEVICE() branch of
// nvidia_mmap_helper(), memory_type is never
// NV_MEMORY_TYPE_FRAMEBUFFER, so this corresponds to
// kernel-open/common/inc/nv-pgprot.h:NV_PGPROT_WRITE_COMBINED(). On
// ARM64, NV_PGPROT_WRITE_COMBINED() => NV_PGPROT_UNCACHED() implicitly
// uses MT_NORMAL (equivalent to our MemoryTypeWriteBack) rather than
// MT_NORMAL_NC when nvos_is_chipset_io_coherent() =>
// PDB_PROP_CL_IS_CHIPSET_IO_COHERENT is true, which seems to be the
// case on most systems. We should clarify whether this is an
// optimization or required for correctness (cf. Armv8-M Architecture
// Reference Manual Sec. B7.16 "Mismatched memory attributes"), and
// subsequently whether to replicate it.
memType = hostarch.MemoryTypeWriteCombine
case nvgpu.NVOS33_FLAGS_CACHING_TYPE_UNCACHED, nvgpu.NVOS33_FLAGS_CACHING_TYPE_UNCACHED_WEAK:
// NOTE(gvisor.dev/issue/11436): On ARM64, nv_encode_caching()
// distinguishes between NV_PGPROT_UNCACHED() => MT_NORMAL/MT_NORMAL_NC
// and NV_PGPROT_UNCACHED_DEVICE() => MT_DEVICE_nGnRnE; in context, the
// former is used in the !peer_io (NV_MEMORY_TYPE_SYSTEM) case and the
// latter is used in the peer_io (NV_MEMORY_TYPE_DEVICE_MMIO) case. As
// above, we should clarify whether we need to replicate this behavior.
memType = hostarch.MemoryTypeUncached
default:
ctx.Warningf("nvproxy: unknown caching type %d", cachingType)
memType = hostarch.MemoryTypeUncached
}
if log.IsLogging(log.Debug) {
ctx.Debugf("nvproxy: inferred memory type %v for caching type %d", memType, cachingType)
}
return memType
}
2 changes: 2 additions & 0 deletions pkg/sentry/devices/nvproxy/uvm_mmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ func (fd *uvmFD) InvalidateUnsavable(ctx context.Context) error {

// +stateify savable
type uvmFDMemmapFile struct {
memmap.DefaultMemoryType

fd *uvmFD
}

Expand Down
11 changes: 1 addition & 10 deletions pkg/sentry/devices/tpuproxy/accel/accel_fd_mmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ package accel

import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
Expand Down Expand Up @@ -61,7 +58,7 @@ func (fd *accelFD) InvalidateUnsavable(ctx context.Context) error {
}

type accelFDMemmapFile struct {
memmap.NoBufferedIOFallback
memmap.NoMapInternal

fd *accelFD
}
Expand All @@ -74,12 +71,6 @@ func (mf *accelFDMemmapFile) IncRef(memmap.FileRange, uint32) {
func (mf *accelFDMemmapFile) DecRef(fr memmap.FileRange) {
}

// MapInternal implements memmap.File.MapInternal.
func (mf *accelFDMemmapFile) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
log.Traceback("accel: rejecting accelFDMemmapFile.MapInternal")
return safemem.BlockSeq{}, linuxerr.EINVAL
}

// DataFD implements memmap.File.DataFD.
func (mf *accelFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
return mf.FD(), nil
Expand Down
5 changes: 5 additions & 0 deletions pkg/sentry/devices/tpuproxy/vfio/pci_device_fd_mmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ func (fd *pciDeviceFD) InvalidateUnsavable(ctx context.Context) error {
}

type pciDeviceFdMemmapFile struct {
// FIXME(jamieliu): This is consistent with legacy behavior, but not
// clearly correct; drivers/vfio/pci/vfio_pci_core.c:vfio_pci_core_mmap()
// uses pgprot_noncached(), which would correspond to our
// MemoryTypeUncached.
memmap.DefaultMemoryType
memmap.NoBufferedIOFallback

fd *pciDeviceFD
Expand Down
13 changes: 3 additions & 10 deletions pkg/sentry/devices/tpuproxy/vfio/tpu_fd_mmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ package vfio

import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
Expand Down Expand Up @@ -61,7 +58,9 @@ func (fd *tpuFD) InvalidateUnsavable(ctx context.Context) error {
}

type tpuFDMemmapFile struct {
memmap.NoBufferedIOFallback
// FIXME(jamieliu): IIUC, tpuFD corresponds to Linux's
// drivers/vfio/vfio.c:vfio_group_fops, which does not support mmap at all.
memmap.NoMapInternal

fd *tpuFD
}
Expand All @@ -74,12 +73,6 @@ func (mf *tpuFDMemmapFile) IncRef(memmap.FileRange, uint32) {
func (mf *tpuFDMemmapFile) DecRef(fr memmap.FileRange) {
}

// MapInternal implements memmap.File.MapInternal.
func (mf *tpuFDMemmapFile) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
log.Traceback("tpuproxy: rejecting tpuFdMemmapFile.MapInternal")
return safemem.BlockSeq{}, linuxerr.EINVAL
}

// DataFD implements memmap.File.DataFD.
func (mf *tpuFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
return mf.FD(), nil
Expand Down
11 changes: 1 addition & 10 deletions pkg/sentry/devices/tpuproxy/vfio/vfio_fd_mmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ package vfio

import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
Expand Down Expand Up @@ -61,7 +58,7 @@ func (fd *vfioFD) InvalidateUnsavable(ctx context.Context) error {
}

type vfioFDMemmapFile struct {
memmap.NoBufferedIOFallback
memmap.NoMapInternal

fd *vfioFD
}
Expand All @@ -74,12 +71,6 @@ func (mf *vfioFDMemmapFile) IncRef(memmap.FileRange, uint32) {
func (mf *vfioFDMemmapFile) DecRef(fr memmap.FileRange) {
}

// MapInternal implements memmap.File.MapInternal.
func (mf *vfioFDMemmapFile) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
log.Traceback("tpuproxy: rejecting vfioFdMemmapFile.MapInternal")
return safemem.BlockSeq{}, linuxerr.EINVAL
}

// DataFD implements memmap.File.DataFD.
func (mf *vfioFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
return mf.FD(), nil
Expand Down
1 change: 1 addition & 0 deletions pkg/sentry/fsimpl/erofs/regular_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ func (i *inode) InvalidateUnsavable(ctx context.Context) error {

// +stateify savable
type imageMemmapFile struct {
memmap.DefaultMemoryType
memmap.NoBufferedIOFallback

image *erofs.Image
Expand Down
1 change: 1 addition & 0 deletions pkg/sentry/fsimpl/gofer/regular_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@ func (d *dentry) Evict(ctx context.Context, er pgalloc.EvictableRange) {
//
// +stateify savable
type dentryPlatformFile struct {
memmap.DefaultMemoryType
memmap.NoBufferedIOFallback

*dentry
Expand Down
Loading

0 comments on commit 51a58b5

Please sign in to comment.