Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b019448

Browse files
nixprimegvisor-bot
authored andcommittedMar 14, 2025·
Add memmap.File.MemoryType()
This has no effect (outside of debug logging) until cl/723723715. Updates #11436 PiperOrigin-RevId: 736686635
1 parent 11aeff6 commit b019448

File tree

18 files changed

+254
-42
lines changed

18 files changed

+254
-42
lines changed
 

‎pkg/abi/nvgpu/frontend.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ type NVOS02_PARAMETERS struct {
198198
Pad1 [4]byte
199199
}
200200

201-
// Bitfields in NVOS02Parameters.Flags:
201+
// Bitfields in NVOS02_PARAMETERS.Flags:
202202
const (
203203
NVOS02_FLAGS_ALLOC_SHIFT = 16
204204
NVOS02_FLAGS_ALLOC_MASK = 0x3
@@ -470,6 +470,18 @@ type NVOS33_PARAMETERS struct {
470470
Flags uint32
471471
}
472472

473+
// Bitfields in NVOS33_PARAMETERS.Flags:
474+
const (
475+
NVOS33_FLAGS_CACHING_TYPE_SHIFT = 23
476+
NVOS33_FLAGS_CACHING_TYPE_MASK = 0x7
477+
NVOS33_FLAGS_CACHING_TYPE_CACHED = 0
478+
NVOS33_FLAGS_CACHING_TYPE_UNCACHED = 1
479+
NVOS33_FLAGS_CACHING_TYPE_WRITECOMBINED = 2
480+
NVOS33_FLAGS_CACHING_TYPE_WRITEBACK = 5
481+
NVOS33_FLAGS_CACHING_TYPE_DEFAULT = 6
482+
NVOS33_FLAGS_CACHING_TYPE_UNCACHED_WEAK = 7
483+
)
484+
473485
// NVOS34_PARAMETERS is the parameter type for NV_ESC_RM_UNMAP_MEMORY.
474486
//
475487
// +marshal

‎pkg/hostarch/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ go_library(
3838
"hostarch.go",
3939
"hostarch_arm64.go",
4040
"hostarch_x86.go",
41+
"memory_type.go",
4142
"sizes_util.go",
4243
],
4344
visibility = ["//:sandbox"],

‎pkg/hostarch/memory_type.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2025 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 hostarch
16+
17+
import "fmt"
18+
19+
// MemoryType specifies CPU memory access behavior.
20+
type MemoryType uint8
21+
22+
const (
23+
// MemoryTypeWriteBack is equivalent to Linux's default pgprot, or the
24+
// following architectural memory types:
25+
//
26+
// - x86: Write-back (WB)
27+
//
28+
// - ARM64: Normal write-back cacheable
29+
//
30+
// This memory type is appropriate for typical application memory and must
31+
// be the zero value for MemoryType.
32+
MemoryTypeWriteBack MemoryType = iota
33+
34+
// MemoryTypeWriteCombine is equivalent to Linux's pgprot_writecombine(),
35+
// or the following architectural memory types:
36+
//
37+
// - x86: Write-combining (WC)
38+
//
39+
// - ARM64: Normal non-cacheable
40+
MemoryTypeWriteCombine
41+
42+
// MemoryTypeUncached is equivalent to Linux's pgprot_noncached(), or the
43+
// following architectural memory types:
44+
//
45+
// - x86: Strong Uncacheable (UC) or Uncacheable (UC-); these differ in
46+
// that UC- may be "downgraded" to WC by a setting of WC or (Intel only) WP
47+
// in MTRR or EPT/NPT, but gVisor does not use MTRRs and KVM never sets WC
48+
// or WP in EPT/NPT.
49+
//
50+
// - ARM64: Device-nGnRnE
51+
MemoryTypeUncached
52+
53+
// NumMemoryTypes is the number of memory types.
54+
NumMemoryTypes
55+
)
56+
57+
// String implements fmt.Stringer.String.
58+
func (mt MemoryType) String() string {
59+
switch mt {
60+
case MemoryTypeWriteBack:
61+
return "WriteBack"
62+
case MemoryTypeWriteCombine:
63+
return "WriteCombine"
64+
case MemoryTypeUncached:
65+
return "Uncached"
66+
default:
67+
return fmt.Sprintf("%d", mt)
68+
}
69+
}
70+
71+
// ShortString returns a two-character string compactly representing the
72+
// MemoryType.
73+
func (mt MemoryType) ShortString() string {
74+
switch mt {
75+
case MemoryTypeWriteBack:
76+
return "WB"
77+
case MemoryTypeWriteCombine:
78+
return "WC"
79+
case MemoryTypeUncached:
80+
return "UC"
81+
default:
82+
return fmt.Sprintf("%02d", mt)
83+
}
84+
}

‎pkg/sentry/devices/nvproxy/frontend.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,12 @@ type frontendDevice struct {
4747
minor uint32
4848
}
4949

50+
func (dev *frontendDevice) isCtlDevice() bool {
51+
return dev.minor == nvgpu.NV_CONTROL_DEVICE_MINOR
52+
}
53+
5054
func (dev *frontendDevice) basename() string {
51-
if dev.minor == nvgpu.NV_CONTROL_DEVICE_MINOR {
55+
if dev.isCtlDevice() {
5256
return "nvidiactl"
5357
}
5458
return fmt.Sprintf("nvidia%d", dev.minor)
@@ -146,8 +150,9 @@ type frontendFD struct {
146150
// These fields are marked nosave since we do not automatically reinvoke
147151
// NV_ESC_RM_MAP_MEMORY after restore, so restored FDs have no
148152
// mmap_context.
149-
mmapLength uint64 `state:"nosave"`
150-
mmapInternal uintptr `state:"nosave"`
153+
mmapLength uint64 `state:"nosave"`
154+
mmapInternal uintptr `state:"nosave"`
155+
mmapMemType hostarch.MemoryType `state:"nosave"`
151156

152157
// clients are handles of clients owned by this frontendFD. clients is
153158
// protected by dev.nvp.objsMu.
@@ -505,6 +510,7 @@ func rmAllocMemorySystem(fi *frontendIoctlState, ioctlParams *nvgpu.IoctlNVOS02P
505510
fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.Params.HRoot, ioctlParams.Params.HObjectNew, ioctlParams.Params.HClass, &miscObject{}, ioctlParams.Params.HObjectParent)
506511
if createMmapCtx {
507512
mapFile.mmapLength = ioctlParams.Params.Limit + 1
513+
mapFile.mmapMemType = getMemoryType(fi.ctx, mapFile.dev, nvgpu.NVOS33_FLAGS_CACHING_TYPE_DEFAULT)
508514
}
509515
}
510516
fi.fd.dev.nvp.objsUnlock()
@@ -1383,6 +1389,15 @@ func rmMapMemory(fi *frontendIoctlState) (uintptr, error) {
13831389
}
13841390
if ioctlParams.Params.Status == nvgpu.NV_OK {
13851391
mapFile.mmapLength = ioctlParams.Params.Length
1392+
// src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl() forces
1393+
// NVOS33_FLAGS_CACHING_TYPE_DEFAULT, but resMap implementations may
1394+
// override the "caching type", so in general the memory type depends
1395+
// on the mapped object. Conveniently, when this occurs, the caching
1396+
// type in pParms->flags must be updated for the call to
1397+
// rm_create_mmap_context(), and pParms is subsequently copied back out
1398+
// by kernel-open/nvidia/nv.c:nvidia_ioctl(), so we can get the final
1399+
// caching type from the updated ioctl params.
1400+
mapFile.mmapMemType = getMemoryType(fi.ctx, mapFile.dev, (ioctlParams.Params.Flags>>nvgpu.NVOS33_FLAGS_CACHING_TYPE_SHIFT)&nvgpu.NVOS33_FLAGS_CACHING_TYPE_MASK)
13861401
}
13871402

13881403
ioctlParams.FD = origFD

‎pkg/sentry/devices/nvproxy/frontend_mmap.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
package nvproxy
1616

1717
import (
18+
"gvisor.dev/gvisor/pkg/abi/nvgpu"
1819
"gvisor.dev/gvisor/pkg/context"
1920
"gvisor.dev/gvisor/pkg/hostarch"
21+
"gvisor.dev/gvisor/pkg/log"
2022
"gvisor.dev/gvisor/pkg/sentry/memmap"
2123
"gvisor.dev/gvisor/pkg/sentry/vfs"
2224
)
@@ -75,6 +77,13 @@ func (mf *frontendFDMemmapFile) IncRef(fr memmap.FileRange, memCgID uint32) {
7577
func (mf *frontendFDMemmapFile) DecRef(fr memmap.FileRange) {
7678
}
7779

80+
// MemoryType implements memmap.File.MemoryType.
81+
func (mf *frontendFDMemmapFile) MemoryType() hostarch.MemoryType {
82+
mf.fd.mmapMu.Lock()
83+
defer mf.fd.mmapMu.Unlock()
84+
return mf.fd.mmapMemType
85+
}
86+
7887
// DataFD implements memmap.File.DataFD.
7988
func (mf *frontendFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
8089
return mf.FD(), nil
@@ -84,3 +93,62 @@ func (mf *frontendFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
8493
func (mf *frontendFDMemmapFile) FD() int {
8594
return int(mf.fd.hostFD)
8695
}
96+
97+
func getMemoryType(ctx context.Context, mapDev *frontendDevice, cachingType uint32) hostarch.MemoryType {
98+
// Compare kernel-open/nvidia/nv-mmap.c:nvidia_mmap_helper() =>
99+
// nv_encode_caching(). Each NVOS33_FLAGS_CACHING_TYPE_* corresponds
100+
// directly to a NV_MEMORY_*; this is checked by asserts in
101+
// src/nvidia/src/kernel/rmapi/mapping_cpu.c.
102+
if !mapDev.isCtlDevice() {
103+
// NOTE(gvisor.dev/issue/11436): In the !NV_IS_CTL_DEVICE() branch of
104+
// nvidia_mmap_helper(), mmap_context->caching is only honored if
105+
// IS_FB_OFFSET() and !IS_UD_OFFSET(). We can get the information we
106+
// need for IS_FB_OFFSET() from NV_ESC_CARD_INFO, but there doesn't
107+
// seem to be any way for us to replicate IS_UD_OFFSET(). So we must
108+
// conservatively specify uncacheable, which applies in all other
109+
// cases. This is unfortunate since it prevents us from using
110+
// write-combining on framebuffer memory. Empirically, mappings of
111+
// framebuffer memory seem to be fairly common, but none of our tests
112+
// result in any IS_UD_OFFSET (USERD?) mappings.
113+
if log.IsLogging(log.Debug) {
114+
ctx.Debugf("nvproxy: inferred memory type %v for mapping of %s", hostarch.MemoryTypeUncached, mapDev.basename())
115+
}
116+
return hostarch.MemoryTypeUncached
117+
}
118+
var memType hostarch.MemoryType
119+
switch cachingType {
120+
case nvgpu.NVOS33_FLAGS_CACHING_TYPE_CACHED, nvgpu.NVOS33_FLAGS_CACHING_TYPE_WRITEBACK:
121+
// Note that nv_encode_caching() doesn't actually handle
122+
// NV_MEMORY_WRITEBACK, so this case should fail during host mmap.
123+
memType = hostarch.MemoryTypeWriteBack
124+
case nvgpu.NVOS33_FLAGS_CACHING_TYPE_WRITECOMBINED, nvgpu.NVOS33_FLAGS_CACHING_TYPE_DEFAULT:
125+
// NOTE(gvisor.dev/issue/11436): In the NV_IS_CTL_DEVICE() branch of
126+
// nvidia_mmap_helper(), memory_type is never
127+
// NV_MEMORY_TYPE_FRAMEBUFFER, so this corresponds to
128+
// kernel-open/common/inc/nv-pgprot.h:NV_PGPROT_WRITE_COMBINED(). On
129+
// ARM64, NV_PGPROT_WRITE_COMBINED() => NV_PGPROT_UNCACHED() implicitly
130+
// uses MT_NORMAL (equivalent to our MemoryTypeWriteBack) rather than
131+
// MT_NORMAL_NC when nvos_is_chipset_io_coherent() =>
132+
// PDB_PROP_CL_IS_CHIPSET_IO_COHERENT is true, which seems to be the
133+
// case on most systems. We should clarify whether this is an
134+
// optimization or required for correctness (cf. Armv8-M Architecture
135+
// Reference Manual Sec. B7.16 "Mismatched memory attributes"), and
136+
// subsequently whether to replicate it.
137+
memType = hostarch.MemoryTypeWriteCombine
138+
case nvgpu.NVOS33_FLAGS_CACHING_TYPE_UNCACHED, nvgpu.NVOS33_FLAGS_CACHING_TYPE_UNCACHED_WEAK:
139+
// NOTE(gvisor.dev/issue/11436): On ARM64, nv_encode_caching()
140+
// distinguishes between NV_PGPROT_UNCACHED() => MT_NORMAL/MT_NORMAL_NC
141+
// and NV_PGPROT_UNCACHED_DEVICE() => MT_DEVICE_nGnRnE; in context, the
142+
// former is used in the !peer_io (NV_MEMORY_TYPE_SYSTEM) case and the
143+
// latter is used in the peer_io (NV_MEMORY_TYPE_DEVICE_MMIO) case. As
144+
// above, we should clarify whether we need to replicate this behavior.
145+
memType = hostarch.MemoryTypeUncached
146+
default:
147+
ctx.Warningf("nvproxy: unknown caching type %d", cachingType)
148+
memType = hostarch.MemoryTypeUncached
149+
}
150+
if log.IsLogging(log.Debug) {
151+
ctx.Debugf("nvproxy: inferred memory type %v for caching type %d", memType, cachingType)
152+
}
153+
return memType
154+
}

‎pkg/sentry/devices/nvproxy/uvm_mmap.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ func (fd *uvmFD) InvalidateUnsavable(ctx context.Context) error {
6363

6464
// +stateify savable
6565
type uvmFDMemmapFile struct {
66+
memmap.DefaultMemoryType
67+
6668
fd *uvmFD
6769
}
6870

‎pkg/sentry/devices/tpuproxy/accel/accel_fd_mmap.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ package accel
1616

1717
import (
1818
"gvisor.dev/gvisor/pkg/context"
19-
"gvisor.dev/gvisor/pkg/errors/linuxerr"
2019
"gvisor.dev/gvisor/pkg/hostarch"
21-
"gvisor.dev/gvisor/pkg/log"
22-
"gvisor.dev/gvisor/pkg/safemem"
2320
"gvisor.dev/gvisor/pkg/sentry/memmap"
2421
"gvisor.dev/gvisor/pkg/sentry/vfs"
2522
)
@@ -61,7 +58,7 @@ func (fd *accelFD) InvalidateUnsavable(ctx context.Context) error {
6158
}
6259

6360
type accelFDMemmapFile struct {
64-
memmap.NoBufferedIOFallback
61+
memmap.NoMapInternal
6562

6663
fd *accelFD
6764
}
@@ -74,12 +71,6 @@ func (mf *accelFDMemmapFile) IncRef(memmap.FileRange, uint32) {
7471
func (mf *accelFDMemmapFile) DecRef(fr memmap.FileRange) {
7572
}
7673

77-
// MapInternal implements memmap.File.MapInternal.
78-
func (mf *accelFDMemmapFile) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
79-
log.Traceback("accel: rejecting accelFDMemmapFile.MapInternal")
80-
return safemem.BlockSeq{}, linuxerr.EINVAL
81-
}
82-
8374
// DataFD implements memmap.File.DataFD.
8475
func (mf *accelFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
8576
return mf.FD(), nil

‎pkg/sentry/devices/tpuproxy/vfio/pci_device_fd_mmap.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ func (fd *pciDeviceFD) InvalidateUnsavable(ctx context.Context) error {
7272
}
7373

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

7782
fd *pciDeviceFD

‎pkg/sentry/devices/tpuproxy/vfio/tpu_fd_mmap.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ package vfio
1616

1717
import (
1818
"gvisor.dev/gvisor/pkg/context"
19-
"gvisor.dev/gvisor/pkg/errors/linuxerr"
2019
"gvisor.dev/gvisor/pkg/hostarch"
21-
"gvisor.dev/gvisor/pkg/log"
22-
"gvisor.dev/gvisor/pkg/safemem"
2320
"gvisor.dev/gvisor/pkg/sentry/memmap"
2421
"gvisor.dev/gvisor/pkg/sentry/vfs"
2522
)
@@ -61,7 +58,9 @@ func (fd *tpuFD) InvalidateUnsavable(ctx context.Context) error {
6158
}
6259

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

6665
fd *tpuFD
6766
}
@@ -74,12 +73,6 @@ func (mf *tpuFDMemmapFile) IncRef(memmap.FileRange, uint32) {
7473
func (mf *tpuFDMemmapFile) DecRef(fr memmap.FileRange) {
7574
}
7675

77-
// MapInternal implements memmap.File.MapInternal.
78-
func (mf *tpuFDMemmapFile) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
79-
log.Traceback("tpuproxy: rejecting tpuFdMemmapFile.MapInternal")
80-
return safemem.BlockSeq{}, linuxerr.EINVAL
81-
}
82-
8376
// DataFD implements memmap.File.DataFD.
8477
func (mf *tpuFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
8578
return mf.FD(), nil

‎pkg/sentry/devices/tpuproxy/vfio/vfio_fd_mmap.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ package vfio
1616

1717
import (
1818
"gvisor.dev/gvisor/pkg/context"
19-
"gvisor.dev/gvisor/pkg/errors/linuxerr"
2019
"gvisor.dev/gvisor/pkg/hostarch"
21-
"gvisor.dev/gvisor/pkg/log"
22-
"gvisor.dev/gvisor/pkg/safemem"
2320
"gvisor.dev/gvisor/pkg/sentry/memmap"
2421
"gvisor.dev/gvisor/pkg/sentry/vfs"
2522
)
@@ -61,7 +58,7 @@ func (fd *vfioFD) InvalidateUnsavable(ctx context.Context) error {
6158
}
6259

6360
type vfioFDMemmapFile struct {
64-
memmap.NoBufferedIOFallback
61+
memmap.NoMapInternal
6562

6663
fd *vfioFD
6764
}
@@ -74,12 +71,6 @@ func (mf *vfioFDMemmapFile) IncRef(memmap.FileRange, uint32) {
7471
func (mf *vfioFDMemmapFile) DecRef(fr memmap.FileRange) {
7572
}
7673

77-
// MapInternal implements memmap.File.MapInternal.
78-
func (mf *vfioFDMemmapFile) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
79-
log.Traceback("tpuproxy: rejecting vfioFdMemmapFile.MapInternal")
80-
return safemem.BlockSeq{}, linuxerr.EINVAL
81-
}
82-
8374
// DataFD implements memmap.File.DataFD.
8475
func (mf *vfioFDMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
8576
return mf.FD(), nil

‎pkg/sentry/fsimpl/erofs/regular_file.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ func (i *inode) InvalidateUnsavable(ctx context.Context) error {
200200

201201
// +stateify savable
202202
type imageMemmapFile struct {
203+
memmap.DefaultMemoryType
203204
memmap.NoBufferedIOFallback
204205

205206
image *erofs.Image

‎pkg/sentry/fsimpl/gofer/regular_file.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,7 @@ func (d *dentry) Evict(ctx context.Context, er pgalloc.EvictableRange) {
904904
//
905905
// +stateify savable
906906
type dentryPlatformFile struct {
907+
memmap.DefaultMemoryType
907908
memmap.NoBufferedIOFallback
908909

909910
*dentry

0 commit comments

Comments
 (0)
Please sign in to comment.