From 628f1bad343beaa386bd794a5e46096a4271686a Mon Sep 17 00:00:00 2001 From: Jing Chen Date: Thu, 21 Mar 2024 20:40:37 -0700 Subject: [PATCH] Implement the ioctl command VFIO_DEVICE_SET_IRQS. PiperOrigin-RevId: 618047305 --- pkg/abi/linux/BUILD | 1 + pkg/abi/linux/vfio.go | 42 +++++++++ pkg/abi/linux/vfio_unsafe.go | 22 +++++ pkg/sentry/devices/tpuproxy/BUILD | 1 + pkg/sentry/devices/tpuproxy/seccomp_filter.go | 4 + pkg/sentry/devices/tpuproxy/tpu.go | 90 +++++++++++++++++++ 6 files changed, 160 insertions(+) create mode 100644 pkg/abi/linux/vfio_unsafe.go diff --git a/pkg/abi/linux/BUILD b/pkg/abi/linux/BUILD index 2b1c75de9..496db6236 100644 --- a/pkg/abi/linux/BUILD +++ b/pkg/abi/linux/BUILD @@ -79,6 +79,7 @@ go_library( "uio.go", "utsname.go", "vfio.go", + "vfio_unsafe.go", "wait.go", "xattr.go", ], diff --git a/pkg/abi/linux/vfio.go b/pkg/abi/linux/vfio.go index a778f42c7..b8c2b7a66 100644 --- a/pkg/abi/linux/vfio.go +++ b/pkg/abi/linux/vfio.go @@ -69,6 +69,33 @@ const ( VFIO_IRQ_INFO_NORESIZE ) +// VFIOIrqSet flags. +const ( + VFIO_IRQ_SET_DATA_NONE = 1 << iota + VFIO_IRQ_SET_DATA_BOOL + VFIO_IRQ_SET_DATA_EVENTFD + VFIO_IRQ_SET_ACTION_MASK + VFIO_IRQ_SET_ACTION_UNMASK + VFIO_IRQ_SET_ACTION_TRIGGER + + VFIO_IRQ_SET_DATA_TYPE_MASK = VFIO_IRQ_SET_DATA_NONE | + VFIO_IRQ_SET_DATA_BOOL | + VFIO_IRQ_SET_DATA_EVENTFD + VFIO_IRQ_SET_ACTION_TYPE_MASK = VFIO_IRQ_SET_ACTION_MASK | + VFIO_IRQ_SET_ACTION_UNMASK | + VFIO_IRQ_SET_ACTION_TRIGGER +) + +// VFIOIrqSet index. +const ( + VFIO_PCI_INTX_IRQ_INDEX = iota + VFIO_PCI_MSI_IRQ_INDEX + VFIO_PCI_MSIX_IRQ_INDEX + VFIO_PCI_ERR_IRQ_INDEX + VFIO_PCI_REQ_IRQ_INDEX + VFIO_PCI_NUM_IRQS +) + // IOCTLs for VFIO file descriptor from include/uapi/linux/vfio.h. var ( VFIO_CHECK_EXTENSION = IO(VFIO_TYPE, VFIO_BASE+1) @@ -78,6 +105,7 @@ var ( VFIO_DEVICE_GET_INFO = IO(VFIO_TYPE, VFIO_BASE+7) VFIO_DEVICE_GET_REGION_INFO = IO(VFIO_TYPE, VFIO_BASE+8) VFIO_DEVICE_GET_IRQ_INFO = IO(VFIO_TYPE, VFIO_BASE+9) + VFIO_DEVICE_SET_IRQS = IO(VFIO_TYPE, VFIO_BASE+10) ) // VFIODeviceInfo is analogous to vfio_device_info @@ -122,3 +150,17 @@ type VFIOIrqInfo struct { Index uint32 Count uint32 } + +// VFIOIrqSet is analogous to vfio_irq_set +// from include/uapi/linux/vfio.h. +// The last field `data` from vfio_irq_set is omitted which is an +// flexible array member. It will be handled separately. +// +// +marshal +type VFIOIrqSet struct { + Argsz uint32 + Flags uint32 + Index uint32 + Start uint32 + Count uint32 +} diff --git a/pkg/abi/linux/vfio_unsafe.go b/pkg/abi/linux/vfio_unsafe.go new file mode 100644 index 000000000..844899115 --- /dev/null +++ b/pkg/abi/linux/vfio_unsafe.go @@ -0,0 +1,22 @@ +// Copyright 2024 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 linux + +import "unsafe" + +// Size returns the number of bytes for a VFIOIrqSet object. +func (vfioIrqSet VFIOIrqSet) Size() uint64 { + return uint64(unsafe.Sizeof(vfioIrqSet)) +} diff --git a/pkg/sentry/devices/tpuproxy/BUILD b/pkg/sentry/devices/tpuproxy/BUILD index 2b3605325..7cad336e2 100644 --- a/pkg/sentry/devices/tpuproxy/BUILD +++ b/pkg/sentry/devices/tpuproxy/BUILD @@ -30,6 +30,7 @@ go_library( "//pkg/safemem", "//pkg/seccomp", "//pkg/sentry/arch", + "//pkg/sentry/fsimpl/eventfd", "//pkg/sentry/kernel", "//pkg/sentry/memmap", "//pkg/sentry/vfs", diff --git a/pkg/sentry/devices/tpuproxy/seccomp_filter.go b/pkg/sentry/devices/tpuproxy/seccomp_filter.go index 851235c0f..4bedfc830 100644 --- a/pkg/sentry/devices/tpuproxy/seccomp_filter.go +++ b/pkg/sentry/devices/tpuproxy/seccomp_filter.go @@ -81,6 +81,10 @@ func Filters() seccomp.SyscallRules { seccomp.NonNegativeFD{}, seccomp.EqualTo(linux.VFIO_DEVICE_GET_IRQ_INFO), }, + seccomp.PerArg{ + seccomp.NonNegativeFD{}, + seccomp.EqualTo(linux.VFIO_DEVICE_SET_IRQS), + }, }, }) } diff --git a/pkg/sentry/devices/tpuproxy/tpu.go b/pkg/sentry/devices/tpuproxy/tpu.go index 64d9fefd8..477906ccd 100644 --- a/pkg/sentry/devices/tpuproxy/tpu.go +++ b/pkg/sentry/devices/tpuproxy/tpu.go @@ -26,12 +26,19 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/eventfd" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/usermem" "gvisor.dev/gvisor/pkg/waiter" ) +const ( + // A value of -1 can be used to either de-assign interrupts if already + // assigned or skip un-assigned interrupts. + disableInterrupt = -1 +) + var ( // vfioDeviceInfoFlags contains all available flags for // IOCTL command VFIO_DEVICE_GET_INFO. @@ -39,6 +46,8 @@ var ( linux.VFIO_DEVICE_FLAGS_PLATFORM | linux.VFIO_DEVICE_FLAGS_AMBA | linux.VFIO_DEVICE_FLAGS_CCW | linux.VFIO_DEVICE_FLAGS_AP | linux.VFIO_DEVICE_FLAGS_FSL_MC | linux.VFIO_DEVICE_FLAGS_CAPS | linux.VFIO_DEVICE_FLAGS_CDX + // vfioIrqSetFlags includes all available flags for IOCTL comamnd VFIO_DEVICE_SET_IRQS + vfioIrqSetFlags uint32 = linux.VFIO_IRQ_SET_DATA_TYPE_MASK | linux.VFIO_IRQ_SET_ACTION_TYPE_MASK ) // tpuFD implements vfs.FileDescriptionImpl for /dev/vfio/[0-9]+ @@ -231,6 +240,8 @@ func (fd *pciDeviceFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, return fd.vfioRegionInfo(ctx, t, args[2].Pointer()) case linux.VFIO_DEVICE_GET_IRQ_INFO: return fd.vfioIrqInfo(ctx, t, args[2].Pointer()) + case linux.VFIO_DEVICE_SET_IRQS: + return fd.vfioSetIrqs(ctx, t, args[2].Pointer()) } return 0, linuxerr.ENOSYS } @@ -300,3 +311,82 @@ func (fd *pciDeviceFD) vfioIrqInfo(ctx context.Context, t *kernel.Task, arg host } return ret, nil } + +func (fd *pciDeviceFD) vfioSetIrqs(ctx context.Context, t *kernel.Task, arg hostarch.Addr) (uintptr, error) { + var irqSet linux.VFIOIrqSet + if _, err := irqSet.CopyIn(t, arg); err != nil { + return 0, err + } + // Callers must set the payload's size. + if irqSet.Argsz == 0 { + return 0, linuxerr.EINVAL + } + // Invalidate unknown flags. + if irqSet.Flags&^vfioIrqSetFlags != 0 { + return 0, linuxerr.EINVAL + } + // See drivers/vfio/vfio_main.c:vfio_set_irqs_validate_and_prepare, + // VFIO uses the data type at the request's flags to determine + // the memory layout of data field. + // + // The struct vfio_irq_set includes a flexible array member, it + // allocates an array for a continuous trunk of memory to back + // a vfio_irq_set object. In order to mirror that behavior, gVisor + // would allocate a slice to store the underlying bytes + // and pass that through to its host. + switch irqSet.Flags & linux.VFIO_IRQ_SET_DATA_TYPE_MASK { + // VFIO_IRQ_SET_DATA_NONE indicates there is no data field for + // the IOCTL command. + // It works with VFIO_IRQ_SET_ACTION_MASK, VFIO_IRQ_SET_ACTION_UNMASK, + // or VFIO_IRQ_SET_ACTION_TRIGGER to mask an interrupt, unmask an + // interrupt, and trigger an interrupt unconditionally. + case linux.VFIO_IRQ_SET_DATA_NONE: + // When there is no data, passing through the given payload + // works just fine. + return IOCTLInvokePtrArg[uint32](fd.hostFD, linux.VFIO_DEVICE_SET_IRQS, &irqSet) + // VFIO_IRQ_SET_DATA_BOOL indicates that the data field is an array of uint8. + // The action will be performed if the corresponding boolean is true. + case linux.VFIO_IRQ_SET_DATA_BOOL: + payloadSize := uint32(irqSet.Size()) + irqSet.Count + payload := make([]uint8, payloadSize) + if _, err := primitive.CopyUint8SliceIn(t, arg, payload); err != nil { + return 0, err + } + return IOCTLInvokePtrArg[uint32](fd.hostFD, linux.VFIO_DEVICE_SET_IRQS, &payload[0]) + // VFIO_IRQ_SET_DATA_EVENTFD indicates that the data field is an array + // of int32 (or event file descriptors). These descriptors will be + // signalled when an action in the flags happens. + case linux.VFIO_IRQ_SET_DATA_EVENTFD: + payloadSize := uint32(irqSet.Size())/4 + irqSet.Count + payload := make([]int32, payloadSize) + if _, err := primitive.CopyInt32SliceIn(t, arg, payload); err != nil { + return 0, err + } + // Transform the input FDs to host FDs. + for i := 0; i < int(irqSet.Count); i++ { + index := len(payload) - 1 - i + fd := payload[index] + // Skip non-event FD. + if fd == disableInterrupt { + continue + } + eventFileGeneric, _ := t.FDTable().Get(fd) + if eventFileGeneric == nil { + return 0, linuxerr.EBADF + } + defer eventFileGeneric.DecRef(ctx) + eventFile, ok := eventFileGeneric.Impl().(*eventfd.EventFileDescription) + if !ok { + return 0, linuxerr.EINVAL + } + eventfd, err := eventFile.HostFD() + if err != nil { + return 0, err + } + payload[index] = int32(eventfd) + } + return IOCTLInvokePtrArg[uint32](fd.hostFD, linux.VFIO_DEVICE_SET_IRQS, &payload[0]) + } + // No data type is specified or multiple data types are specified. + return 0, linuxerr.EINVAL +}