From 932d9dc64b83f3fc1fc2e3b048636e7e921c6178 Mon Sep 17 00:00:00 2001 From: Lucas Manning Date: Tue, 3 Sep 2024 16:48:00 -0700 Subject: [PATCH] Add nested PCI device support and option to read directly from host dev files. PiperOrigin-RevId: 670751194 --- pkg/sentry/devices/tpuproxy/tpuproxy.go | 12 +-- pkg/sentry/devices/tpuproxy/tpuproxy_test.go | 4 +- pkg/sentry/devices/tpuproxy/vfio/vfio.go | 96 +++++++++++++------ pkg/sentry/fsimpl/sys/pci.go | 91 +++++++++--------- pkg/sentry/fsimpl/sys/sys.go | 19 ++-- pkg/sentry/fsimpl/sys/sys_integration_test.go | 58 +++++++---- runsc/boot/vfs.go | 2 +- 7 files changed, 173 insertions(+), 109 deletions(-) diff --git a/pkg/sentry/devices/tpuproxy/tpuproxy.go b/pkg/sentry/devices/tpuproxy/tpuproxy.go index 4f9e1c194..7c68c16b2 100644 --- a/pkg/sentry/devices/tpuproxy/tpuproxy.go +++ b/pkg/sentry/devices/tpuproxy/tpuproxy.go @@ -32,8 +32,8 @@ import ( ) const ( - pciPathGlobTPUv4 = "/sys/devices/pci0000:*/*/accel/accel*" - pciPathGlobTPUv5 = "/sys/devices/pci0000:*/*/vfio-dev/vfio*" + pciPathGlobTPUv4 = "/sys/devices/pci0000:*/**/accel/accel*" + pciPathGlobTPUv5 = "/sys/devices/pci0000:*/**/vfio-dev/vfio*" iommuGroupPathGlob = "/sys/kernel/iommu_groups/*/devices/*" ) @@ -42,8 +42,8 @@ var ( // TPU v4 devices are accessible via /sys/devices/pci0000:00//accel/accel# on the host. // TPU v5 devices are accessible via at /sys/devices/pci0000:00//vfio-dev/vfio# on the host. pathGlobToPathRegex = map[string]string{ - pciPathGlobTPUv4: `^/sys/devices/pci0000:[[:xdigit:]]{2}/\d+:\d+:\d+\.\d+/accel/accel(\d+)$`, - pciPathGlobTPUv5: `^/sys/devices/pci0000:[[:xdigit:]]{2}/\d+:\d+:\d+\.\d+/vfio-dev/vfio(\d+)$`, + pciPathGlobTPUv4: `^/sys/devices/pci0000:[[:xdigit:]]{2}/(0000:([[:xdigit:]]{2}|[[:xdigit:]]{4}):[[:xdigit:]]{2}\.[[:xdigit:]]{1,2}/)+accel/accel(\d+)$`, + pciPathGlobTPUv5: `^/sys/devices/pci0000:[[:xdigit:]]{2}/(0000:([[:xdigit:]]{2}|[[:xdigit:]]{4}):[[:xdigit:]]{2}\.[[:xdigit:]]{1,2}/)+vfio-dev/vfio(\d+)$`, } ) @@ -61,7 +61,7 @@ func RegisterHostTPUDevices(vfsObj *vfs.VirtualFilesystem, allowedDeviceIDs map[ if ms == nil { continue } - minorNum, err := strconv.ParseUint(ms[1], 10, 32) + minorNum, err := strconv.ParseUint(ms[len(ms)-1], 10, 32) if err != nil { return fmt.Errorf("parsing PCI device number: %w", err) } @@ -117,7 +117,7 @@ func registerTPUDevice(vfsObj *vfs.VirtualFilesystem, minor, deviceNum uint32, d case tpu.TPUV4DeviceID, tpu.TPUV4liteDeviceID: return accel.RegisterTPUDevice(vfsObj, minor, deviceID == tpu.TPUV4liteDeviceID) case tpu.TPUV5eDeviceID, tpu.TPUV5pDeviceID: - return vfio.RegisterTPUDevice(vfsObj, minor, deviceNum) + return vfio.RegisterTPUDevice(vfsObj, minor, deviceNum, false /* useDevGofer */) default: return fmt.Errorf("unsupported TPU device with ID: 0x%x", deviceID) } diff --git a/pkg/sentry/devices/tpuproxy/tpuproxy_test.go b/pkg/sentry/devices/tpuproxy/tpuproxy_test.go index 257e6efbc..d9ac53bff 100644 --- a/pkg/sentry/devices/tpuproxy/tpuproxy_test.go +++ b/pkg/sentry/devices/tpuproxy/tpuproxy_test.go @@ -32,7 +32,7 @@ func TestTPUPath(t *testing.T) { name: "TPUv4PCIPathMatch", pathGlob: pciPathGlobTPUv4, path: "/sys/devices/pci0000:00/0000:00:01.0/accel/accel16", - submatch: []string{"/sys/devices/pci0000:00/0000:00:01.0/accel/accel16", "16"}, + submatch: []string{"/sys/devices/pci0000:00/0000:00:01.0/accel/accel16", "0000:00:01.0/", "00", "16"}, }, { name: "TPUv4PCIPathNoMatch", @@ -44,7 +44,7 @@ func TestTPUPath(t *testing.T) { name: "TPUv5PCIPathMatch", pathGlob: pciPathGlobTPUv5, path: "/sys/devices/pci0000:00/0000:00:05.0/vfio-dev/vfio20", - submatch: []string{"/sys/devices/pci0000:00/0000:00:05.0/vfio-dev/vfio20", "20"}, + submatch: []string{"/sys/devices/pci0000:00/0000:00:05.0/vfio-dev/vfio20", "0000:00:05.0/", "00", "20"}, }, { name: "TPUv5PCIPathNoMatch", diff --git a/pkg/sentry/devices/tpuproxy/vfio/vfio.go b/pkg/sentry/devices/tpuproxy/vfio/vfio.go index b894d33dd..49243544c 100644 --- a/pkg/sentry/devices/tpuproxy/vfio/vfio.go +++ b/pkg/sentry/devices/tpuproxy/vfio/vfio.go @@ -15,9 +15,9 @@ package vfio import ( + "path" "path/filepath" "strconv" - "strings" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" @@ -51,23 +51,40 @@ type tpuDevice struct { minor uint32 // num is the number of the device in the dev filesystem (e.g /dev/vfio/0). num uint32 + // useDevGofer indicates whether to use device gofer to open the TPU device. + useDevGofer bool } // Open implements vfs.Device.Open. func (dev *tpuDevice) Open(ctx context.Context, mnt *vfs.Mount, d *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { - devClient := devutil.GoferClientFromContext(ctx) - if devClient == nil { - log.Warningf("devutil.CtxDevGoferClient is not set") - return nil, linuxerr.ENOENT - } dev.mu.Lock() defer dev.mu.Unlock() - devName := filepath.Join("vfio", strconv.Itoa(int(dev.num))) - hostFD, err := devClient.OpenAt(ctx, devName, opts.Flags) - if err != nil { - ctx.Warningf("tpuDevice: failed to open host %s: %v", devName, err) - return nil, err + + var hostFD int + if dev.useDevGofer { + devClient := devutil.GoferClientFromContext(ctx) + if devClient == nil { + log.Warningf("devutil.CtxDevGoferClient is not set") + return nil, linuxerr.ENOENT + } + devName := filepath.Join("vfio", strconv.Itoa(int(dev.num))) + var err error + hostFD, err = devClient.OpenAt(ctx, devName, opts.Flags) + if err != nil { + ctx.Warningf("tpuDevice: failed to open host %s: %v", devName, err) + return nil, err + } + } else { + devPath := filepath.Join("/", "dev", "vfio", strconv.Itoa(int(dev.num))) + var err error + flags := int(opts.Flags&unix.O_ACCMODE | unix.O_NOFOLLOW) + hostFD, err = unix.Openat(-1, devPath, flags, 0) + if err != nil { + ctx.Warningf("tpuDevice: failed to open host %s: %v", devPath, err) + return nil, err + } } + fd := &tpuFD{ hostFD: int32(hostFD), device: dev, @@ -87,22 +104,40 @@ func (dev *tpuDevice) Open(ctx context.Context, mnt *vfs.Mount, d *vfs.Dentry, o } // device implements vfs.Device for /dev/vfio/vfio. -type vfioDevice struct{} +type vfioDevice struct { + // useDevGofer indicates whether to use device gofer to open the VFIO device. + useDevGofer bool +} // Open implements vfs.Device.Open. func (dev *vfioDevice) Open(ctx context.Context, mnt *vfs.Mount, d *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { - client := devutil.GoferClientFromContext(ctx) - if client == nil { - log.Warningf("devutil.CtxDevGoferClient is not set") - return nil, linuxerr.ENOENT + var hostFD int + if dev.useDevGofer { + client := devutil.GoferClientFromContext(ctx) + if client == nil { + log.Warningf("devutil.CtxDevGoferClient is not set") + return nil, linuxerr.ENOENT + } + + name := filepath.Join("vfio", "vfio") + var err error + hostFD, err = client.OpenAt(ctx, name, opts.Flags) + if err != nil { + ctx.Warningf("failed to open host file %s: %v", name, err) + return nil, err + } + } else { + devPath := filepath.Join("/", "dev", "vfio", "vfio") + flags := int(opts.Flags&unix.O_ACCMODE | unix.O_NOFOLLOW) + var err error + hostFD, err = unix.Openat(-1, devPath, flags, 0) + if err != nil { + ctx.Warningf("vfioDevice: failed to open host %s: %v", devPath, err) + log.Infof("here failed to open %v flags", devPath, opts.Flags) + return nil, err + } } - name := strings.ReplaceAll(VFIOPath, "/dev/", "") - hostFD, err := client.OpenAt(ctx, name, opts.Flags) - if err != nil { - ctx.Warningf("failed to open host file %s: %v", name, err) - return nil, err - } fd := &vfioFD{ hostFD: int32(hostFD), device: dev, @@ -122,18 +157,25 @@ func (dev *vfioDevice) Open(ctx context.Context, mnt *vfs.Mount, d *vfs.Dentry, } // RegisterTPUDevice registers devices implemented by this package in vfsObj. -func RegisterTPUDevice(vfsObj *vfs.VirtualFilesystem, minor, deviceNum uint32) error { +func RegisterTPUDevice(vfsObj *vfs.VirtualFilesystem, minor, deviceNum uint32, useDevGofer bool) error { return vfsObj.RegisterDevice(vfs.CharDevice, linux.VFIO_MAJOR, minor, &tpuDevice{ - minor: minor, - num: deviceNum, + minor: minor, + num: deviceNum, + useDevGofer: useDevGofer, }, &vfs.RegisterDeviceOptions{ GroupName: tpuDeviceGroupName, + Pathname: path.Join("vfio", strconv.Itoa(int(deviceNum))), + FilePerms: 0666, }) } // RegisterVFIODevice registers VFIO devices that are implemented by this package in vfsObj. -func RegisterVFIODevice(vfsObj *vfs.VirtualFilesystem) error { - return vfsObj.RegisterDevice(vfs.CharDevice, linux.MISC_MAJOR, VFIO_MINOR, &vfioDevice{}, &vfs.RegisterDeviceOptions{ +func RegisterVFIODevice(vfsObj *vfs.VirtualFilesystem, useDevGofer bool) error { + return vfsObj.RegisterDevice(vfs.CharDevice, linux.MISC_MAJOR, VFIO_MINOR, &vfioDevice{ + useDevGofer: useDevGofer, + }, &vfs.RegisterDeviceOptions{ GroupName: vfioDeviceGroupName, + Pathname: path.Join("vfio", "vfio"), + FilePerms: 0666, }) } diff --git a/pkg/sentry/fsimpl/sys/pci.go b/pkg/sentry/fsimpl/sys/pci.go index f80a0acf0..7242b8544 100644 --- a/pkg/sentry/fsimpl/sys/pci.go +++ b/pkg/sentry/fsimpl/sys/pci.go @@ -52,39 +52,48 @@ var ( "is_device_owned": nil, "device_owner": nil, "framework_version": nil, "user_mem_ranges": nil, "interrupt_counts": nil, "chip_model": nil, "bar_offsets": nil, "bar_sizes": nil, "resource0": nil, "resource1": nil, - "resource2": nil, "resource3": nil, "resource4": nil, "resource5": nil, "enable": nil, + "resource2": nil, "resource3": nil, "resource4": nil, "resource5": nil, + "enable": nil, } + + pciAddressLength = 13 ) -// sysDevicesPCIPaths returns the paths of all PCI devices on the host in a +// pciDevicePaths returns the paths of all PCI devices on the host in a // /sys/devices directory. -func sysDevicesPCIPaths(sysDevicesPath string) ([]string, error) { +func pciDevicePaths(sysDevicesPath string) (map[string]string, error) { sysDevicesDents, err := hostDirEntries(sysDevicesPath) if err != nil { return nil, err } - var pciPaths []string - for _, dent := range sysDevicesDents { - if pciBusRegex.MatchString(dent) { - pciDents, err := hostDirEntries(path.Join(sysDevicesPath, dent)) - if err != nil { + pciPaths := map[string]string{} + for _, busDent := range sysDevicesDents { + if pciBusRegex.MatchString(busDent) { + if err := walkPCIDeviceTopology(busDent, sysDevicesPath, pciPaths); err != nil { return nil, err } - for _, pciDent := range pciDents { - if pciDeviceRegex.MatchString(pciDent) { - pciPaths = append(pciPaths, path.Join(sysDevicesPath, dent, pciDent)) - } - } } } return pciPaths, nil } -// pciBusFromAddress returns the PCI bus address from a PCI address. -// -// Preconditions: pciAddr is a valid PCI address. -func pciBusFromAddress(pciAddr string) string { - return strings.Join(strings.Split(pciAddr, ":")[:2], ":") +// walkPCIDeviceTopology recursively walks the PCI device topology and returns +// a map from PCI device name to its path starting from the PCI bus directory. +func walkPCIDeviceTopology(pciPath, sysDevicesPath string, devices map[string]string) error { + dents, err := hostDirEntries(path.Join(sysDevicesPath, pciPath)) + if err != nil { + return err + } + for _, dent := range dents { + if pciDeviceRegex.MatchString(dent) && len(dent) <= pciAddressLength { + dentPath := path.Join(pciPath, dent) + devices[dent] = dentPath + if err := walkPCIDeviceTopology(dentPath, sysDevicesPath, devices); err != nil { + return err + } + } + } + return nil } // Creates TPU devices' symlinks under /sys/class/. TPU device types that are @@ -92,18 +101,14 @@ func pciBusFromAddress(pciAddr string) string { // // TPU v4 symlinks are created at /sys/class/accel/accel#. // TPU v5 symlinks go to /sys/class/vfio-dev/vfio#. -func (fs *filesystem) newDeviceClassDir(ctx context.Context, creds *auth.Credentials, tpuDeviceTypes []string, sysDevicesPath string) (map[string]map[string]kernfs.Inode, error) { +func (fs *filesystem) newDeviceClassDir(ctx context.Context, creds *auth.Credentials, tpuDeviceTypes []string, sysDevicesPath string, pciPaths map[string]string) (map[string]map[string]kernfs.Inode, error) { dirs := map[string]map[string]kernfs.Inode{} for _, tpuDeviceType := range tpuDeviceTypes { dirs[tpuDeviceType] = map[string]kernfs.Inode{} } - pciPaths, err := sysDevicesPCIPaths(sysDevicesPath) - if err != nil { - return nil, err - } for _, pciPath := range pciPaths { for _, tpuDeviceType := range tpuDeviceTypes { - subPath := path.Join(pciPath, tpuDeviceType) + subPath := path.Join(sysDevicesPath, pciPath, tpuDeviceType) deviceDents, err := hostDirEntries(subPath) if err != nil { // Skips the path that doesn't exist. @@ -115,9 +120,8 @@ func (fs *filesystem) newDeviceClassDir(ctx context.Context, creds *auth.Credent if numOfDeviceDents := len(deviceDents); numOfDeviceDents != 1 { return nil, fmt.Errorf("exactly one entry is expected at %v while there are %d", subPath, numOfDeviceDents) } - pciAddr := path.Base(pciPath) - pciBus := pciBusFromAddress(pciAddr) - dirs[tpuDeviceType][deviceDents[0]] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../devices/pci%s/%s/%s/%s", pciBus, pciAddr, tpuDeviceType, deviceDents[0])) + + dirs[tpuDeviceType][deviceDents[0]] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../devices/%s/%s/%s", pciPath, tpuDeviceType, deviceDents[0])) } } if len(dirs) == 0 { @@ -127,24 +131,17 @@ func (fs *filesystem) newDeviceClassDir(ctx context.Context, creds *auth.Credent } // Create /sys/bus/pci/devices symlinks. -func (fs *filesystem) newBusPCIDevicesDir(ctx context.Context, creds *auth.Credentials, sysDevicesPath string) (map[string]kernfs.Inode, error) { +func (fs *filesystem) newBusPCIDevicesDir(ctx context.Context, creds *auth.Credentials, pciPaths map[string]string) (map[string]kernfs.Inode, error) { pciDevicesDir := map[string]kernfs.Inode{} - pciPaths, err := sysDevicesPCIPaths(sysDevicesPath) - if err != nil { - return nil, err + for pciDevice, pciPath := range pciPaths { + pciDevicesDir[pciDevice] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../../devices/%s", pciPath)) } - for _, pciPath := range pciPaths { - pciAddr := path.Base(pciPath) - pciBus := pciBusFromAddress(pciAddr) - pciDevicesDir[pciAddr] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../../devices/pci%s/%s", pciBus, pciAddr)) - } - return pciDevicesDir, nil } // Recursively build out sysfs directories according to the allowlisted files, // directories, and symlinks defined in this package. -func (fs *filesystem) mirrorSysDevicesDir(ctx context.Context, creds *auth.Credentials, dir string, iommuGroups map[string]string) (map[string]kernfs.Inode, error) { +func (fs *filesystem) mirrorSysDevicesDir(ctx context.Context, creds *auth.Credentials, dir string, iommuGroups, pciPaths map[string]string) (map[string]kernfs.Inode, error) { subs := map[string]kernfs.Inode{} dents, err := hostDirEntries(dir) if err != nil { @@ -161,7 +158,7 @@ func (fs *filesystem) mirrorSysDevicesDir(ctx context.Context, creds *auth.Crede if match := sysDevicesDirRegex.MatchString(dent); !match { continue } - contents, err := fs.mirrorSysDevicesDir(ctx, creds, dentPath, iommuGroups) + contents, err := fs.mirrorSysDevicesDir(ctx, creds, dentPath, iommuGroups, pciPaths) if err != nil { return nil, err } @@ -180,7 +177,7 @@ func (fs *filesystem) mirrorSysDevicesDir(ctx context.Context, creds *auth.Crede } // Both the device and PCI address entries are links to the original PCI // device directory that's at the same place earlier in the dir tree. - linkContent = fmt.Sprintf("../../../%s", pciDeviceName) + linkContent = fmt.Sprintf("../../../%s", pciPaths[pciDeviceName]) case dent == "iommu_group": pciDeviceName, err := pciDeviceName(dir) if err != nil { @@ -190,7 +187,12 @@ func (fs *filesystem) mirrorSysDevicesDir(ctx context.Context, creds *auth.Crede if !exist { return nil, fmt.Errorf("no IOMMU group is found for device %v", pciDeviceName) } - linkContent = fmt.Sprintf("../../../kernel/iommu_groups/%s", iommuGroupNum) + // A PCI device path looks something like pci0000:00/0000:00:04.0. To + // get to the /sys directory, we need to go up as many directories as + // are in the pciPath plus one more for the "devices" directory. + pciPathComponents := strings.Split(pciPaths[pciDeviceName], "/") + upDirs := strings.Repeat("../", len(pciPathComponents)+1) + linkContent = fmt.Sprintf("%skernel/iommu_groups/%s", upDirs, iommuGroupNum) default: continue } @@ -202,11 +204,11 @@ func (fs *filesystem) mirrorSysDevicesDir(ctx context.Context, creds *auth.Crede // Infer a PCI device's name from its path. func pciDeviceName(pciDevicePath string) (string, error) { - pciDeviceName := pciDeviceRegex.FindString(pciDevicePath) - if pciDeviceName == "" { + pciDeviceNames := pciDeviceRegex.FindAllString(pciDevicePath, -1) + if len(pciDeviceNames) == 0 { return "", fmt.Errorf("no valid device name for the device path at %v", pciDevicePath) } - return pciDeviceName, nil + return pciDeviceNames[len(pciDeviceNames)-1], nil } func hostFileMode(path string) (uint32, error) { @@ -214,6 +216,7 @@ func hostFileMode(path string) (uint32, error) { if err != nil { return 0, err } + defer unix.Close(fd) stat := unix.Stat_t{} if err := unix.Fstat(fd, &stat); err != nil { return 0, err diff --git a/pkg/sentry/fsimpl/sys/sys.go b/pkg/sentry/fsimpl/sys/sys.go index b493c1825..2c3efd0af 100644 --- a/pkg/sentry/fsimpl/sys/sys.go +++ b/pkg/sentry/fsimpl/sys/sys.go @@ -138,7 +138,11 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt return nil, nil, err } sysDevicesPath := path.Join(idata.TestSysfsPathPrefix, sysDevicesMainPath) - sysDevicesSub, err := fs.mirrorSysDevicesDir(ctx, creds, sysDevicesPath, deviceToIOMMUGroup) + pciPaths, err := pciDevicePaths(sysDevicesPath) + if err != nil { + return nil, nil, err + } + sysDevicesSub, err := fs.mirrorSysDevicesDir(ctx, creds, sysDevicesPath, deviceToIOMMUGroup, pciPaths) if err != nil { return nil, nil, err } @@ -146,7 +150,7 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt devicesSub[dir] = sub } - deviceDirs, err := fs.newDeviceClassDir(ctx, creds, []string{accelDevice, vfioDevice}, sysDevicesPath) + deviceDirs, err := fs.newDeviceClassDir(ctx, creds, []string{accelDevice, vfioDevice}, sysDevicesPath, pciPaths) if err != nil { return nil, nil, err } @@ -154,7 +158,7 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt for tpuDeviceType, symlinkDir := range deviceDirs { classSub[tpuDeviceType] = fs.newDir(ctx, creds, defaultSysDirMode, symlinkDir) } - pciDevicesSub, err := fs.newBusPCIDevicesDir(ctx, creds, sysDevicesPath) + pciDevicesSub, err := fs.newBusPCIDevicesDir(ctx, creds, pciPaths) if err != nil { return nil, nil, err } @@ -162,7 +166,7 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt "devices": fs.newDir(ctx, creds, defaultSysDirMode, pciDevicesSub), }) iommuPath := path.Join(idata.TestSysfsPathPrefix, iommuGroupSysPath) - iommuGroups, err := fs.mirrorIOMMUGroups(ctx, creds, iommuPath) + iommuGroups, err := fs.mirrorIOMMUGroups(ctx, creds, iommuPath, pciPaths) if err != nil { return nil, nil, err } @@ -321,7 +325,7 @@ func kernelDir(ctx context.Context, fs *filesystem, creds *auth.Credentials) map } // Recursively build out IOMMU directories from the host. -func (fs *filesystem) mirrorIOMMUGroups(ctx context.Context, creds *auth.Credentials, dir string) (map[string]kernfs.Inode, error) { +func (fs *filesystem) mirrorIOMMUGroups(ctx context.Context, creds *auth.Credentials, dir string, pciPaths map[string]string) (map[string]kernfs.Inode, error) { subs := map[string]kernfs.Inode{} dents, err := hostDirEntries(dir) if err != nil { @@ -340,7 +344,7 @@ func (fs *filesystem) mirrorIOMMUGroups(ctx context.Context, creds *auth.Credent } switch mode { case unix.S_IFDIR: - contents, err := fs.mirrorIOMMUGroups(ctx, creds, absPath) + contents, err := fs.mirrorIOMMUGroups(ctx, creds, absPath, pciPaths) if err != nil { return nil, err } @@ -349,8 +353,7 @@ func (fs *filesystem) mirrorIOMMUGroups(ctx context.Context, creds *auth.Credent subs[dent] = fs.newHostFile(ctx, creds, defaultSysMode, absPath) case unix.S_IFLNK: if pciDeviceRegex.MatchString(dent) { - pciBus := pciBusFromAddress(dent) - subs[dent] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../../../devices/pci%s/%s", pciBus, dent)) + subs[dent] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../../../devices/%s", pciPaths[dent])) } } } diff --git a/pkg/sentry/fsimpl/sys/sys_integration_test.go b/pkg/sentry/fsimpl/sys/sys_integration_test.go index 4bc8da1cc..73ddef55d 100644 --- a/pkg/sentry/fsimpl/sys/sys_integration_test.go +++ b/pkg/sentry/fsimpl/sys/sys_integration_test.go @@ -186,10 +186,11 @@ func TestEnableTPUProxyPathsV4(t *testing.T) { type PCIDeviceInfo struct { // IOMMU group. - group string - pciPath string - pciAddress string - name string + group string + pciPath string + pciAddress string + name string + nestedDeviceIndex int } func (dev PCIDeviceInfo) path() string { @@ -218,22 +219,32 @@ func TestEnableTPUProxyPathsV5(t *testing.T) { devices := []PCIDeviceInfo{ PCIDeviceInfo{ - group: "0", - pciPath: pciPath0, - pciAddress: "0000:00:04.0", - name: "vfio0", + group: "0", + pciPath: pciPath0, + pciAddress: "0000:00:04.0", + name: "vfio0", + nestedDeviceIndex: -1, }, PCIDeviceInfo{ - group: "1", - pciPath: pciPath0, - pciAddress: "0000:00:05.0", - name: "vfio1", + group: "1", + pciPath: pciPath0, + pciAddress: "0000:00:05.0", + name: "vfio1", + nestedDeviceIndex: -1, }, PCIDeviceInfo{ - group: "2", - pciPath: pciPath1, - pciAddress: "0000:10:05.0", - name: "vfio2", + group: "2", + pciPath: pciPath1, + pciAddress: "0000:10:05.0", + name: "vfio2", + nestedDeviceIndex: 3, + }, + PCIDeviceInfo{ + group: "3", + pciPath: pciPath1, + pciAddress: "0000:10:05.0/0000:03:00.1", + name: "vfio3", + nestedDeviceIndex: -1, }, } for _, device := range devices { @@ -244,7 +255,7 @@ func TestEnableTPUProxyPathsV5(t *testing.T) { if err := os.Symlink(path.Join("..", "..", "..", device.pciAddress), path.Join(devicePath, "device")); err != nil { t.Fatalf("Failed to symlink device directory: %v", err) } - if err := os.Symlink(path.Join("..", "..", "..", "devices", path.Base(device.pciPath), device.pciAddress), path.Join(busPath, device.pciAddress)); err != nil { + if err := os.Symlink(path.Join("..", "..", "..", "devices", path.Base(device.pciPath), device.pciAddress), path.Join(busPath, path.Base(device.pciAddress))); err != nil { t.Fatalf("Failed to symlink bus directory: %v", err) } if err := os.Symlink(path.Join("..", "..", "devices", path.Base(device.pciPath), device.pciAddress, vfioDev, device.name), path.Join(sysClassPath, device.name)); err != nil { @@ -254,7 +265,7 @@ func TestEnableTPUProxyPathsV5(t *testing.T) { if err := os.MkdirAll(iommuPath, 0755); err != nil { t.Fatalf("Failed to create iommu_groups directory: %v", err) } - if err := os.Symlink(path.Join("..", "..", "..", "..", "devices", path.Base(device.pciPath), device.pciAddress), path.Join(iommuPath, device.pciAddress)); err != nil { + if err := os.Symlink(path.Join("..", "..", "..", "..", "devices", path.Base(device.pciPath), device.pciAddress), path.Join(iommuPath, path.Base(device.pciAddress))); err != nil { t.Fatalf("Failed to symlink iommu_group devices directory: %v", err) } if err := os.Symlink(path.Join("..", "..", "..", "kernel", "iommu_groups", device.group), path.Join(device.pciPath, device.pciAddress, "iommu_group")); err != nil { @@ -267,10 +278,15 @@ func TestEnableTPUProxyPathsV5(t *testing.T) { for _, device := range devices { // Validate PCI device symlinks. pop := s.PathOpAtRoot(path.Join("devices", path.Base(device.pciPath), device.pciAddress)) - s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{ + contents := map[string]testutil.DirentType{ "iommu_group": linux.DT_LNK, vfioDev: linux.DT_DIR, - }) + } + if device.nestedDeviceIndex != -1 { + deviceName := path.Base(devices[device.nestedDeviceIndex].pciAddress) + contents[deviceName] = linux.DT_DIR + } + s.AssertAllDirentTypes(s.ListDirents(pop), contents) // Validate VFIO device symlinks. pop = s.PathOpAtRoot(path.Join("devices", path.Base(device.pciPath), device.pciAddress, vfioDev, device.name)) s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{ @@ -279,7 +295,7 @@ func TestEnableTPUProxyPathsV5(t *testing.T) { // Validate $IOMMU_GROUP/devices. pop = s.PathOpAtRoot(path.Join("kernel", "iommu_groups", string(device.group), "devices")) s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{ - device.pciAddress: linux.DT_LNK, + path.Base(device.pciAddress): linux.DT_LNK, }) } } diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index 0cdff8b3d..b8ac7c313 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -1388,7 +1388,7 @@ func tpuProxyRegisterDevices(info *containerInfo, vfsObj *vfs.VirtualFilesystem) if err := tpuproxy.RegisterHostTPUDevices(vfsObj, allowedTPUDeviceIDs); err != nil { return fmt.Errorf("registering host TPU devices: %w", err) } - if err := vfio.RegisterVFIODevice(vfsObj); err != nil { + if err := vfio.RegisterVFIODevice(vfsObj, true /* useDevGofer */); err != nil { return fmt.Errorf("registering vfio driver: %w", err) } return nil