// Copyright (C) 2019-2021 Alibaba Cloud. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-Google file.

//! Common traits and structs for vhost-kern and vhost-user backend drivers.

use std::cell::RefCell;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;
use std::sync::RwLock;

use vm_memory::{bitmap::Bitmap, Address, GuestMemoryRegion, GuestRegionMmap};
use vmm_sys_util::eventfd::EventFd;

#[cfg(feature = "vhost-user")]
use super::vhost_user::message::{VhostUserMemoryRegion, VhostUserSingleMemoryRegion};
use super::{Error, Result};

/// Maximum number of memory regions supported.
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;

/// Vring configuration data.
#[derive(Default, Clone, Copy)]
pub struct VringConfigData {
    /// Maximum queue size supported by the driver.
    pub queue_max_size: u16,
    /// Actual queue size negotiated by the driver.
    pub queue_size: u16,
    /// Bitmask of vring flags.
    pub flags: u32,
    /// Descriptor table address.
    pub desc_table_addr: u64,
    /// Used ring buffer address.
    pub used_ring_addr: u64,
    /// Available ring buffer address.
    pub avail_ring_addr: u64,
    /// Optional address for logging.
    pub log_addr: Option<u64>,
}

impl VringConfigData {
    /// Check whether the log (flag, address) pair is valid.
    pub fn is_log_addr_valid(&self) -> bool {
        if self.flags & 0x1 != 0 && self.log_addr.is_none() {
            return false;
        }

        true
    }

    /// Get the log address, default to zero if not available.
    pub fn get_log_addr(&self) -> u64 {
        if self.flags & 0x1 != 0 && self.log_addr.is_some() {
            self.log_addr.unwrap()
        } else {
            0
        }
    }
}

/// Memory region configuration data.
#[derive(Default, Clone, Copy)]
pub struct VhostUserMemoryRegionInfo {
    /// Guest physical address of the memory region.
    pub guest_phys_addr: u64,
    /// Size of the memory region.
    pub memory_size: u64,
    /// Virtual address in the current process.
    pub userspace_addr: u64,
    /// Optional offset where region starts in the mapped memory.
    pub mmap_offset: u64,
    /// Optional file descriptor for mmap.
    pub mmap_handle: RawFd,

    #[cfg(feature = "xen")]
    /// Xen specific flags.
    pub xen_mmap_flags: u32,

    #[cfg(feature = "xen")]
    /// Xen specific data.
    pub xen_mmap_data: u32,
}

impl VhostUserMemoryRegionInfo {
    /// Creates Self from GuestRegionMmap.
    pub fn from_guest_region<B: Bitmap>(region: &GuestRegionMmap<B>) -> Result<Self> {
        let file_offset = region
            .file_offset()
            .ok_or(Error::InvalidGuestMemoryRegion)?;

        Ok(Self {
            guest_phys_addr: region.start_addr().raw_value(),
            memory_size: region.len(),
            userspace_addr: region.as_ptr() as u64,
            mmap_offset: file_offset.start(),
            mmap_handle: file_offset.file().as_raw_fd(),
            #[cfg(feature = "xen")]
            xen_mmap_flags: region.xen_mmap_flags(),
            #[cfg(feature = "xen")]
            xen_mmap_data: region.xen_mmap_data(),
        })
    }

    /// Creates VhostUserMemoryRegion from Self.
    #[cfg(feature = "vhost-user")]
    pub fn to_region(&self) -> VhostUserMemoryRegion {
        #[cfg(not(feature = "xen"))]
        return VhostUserMemoryRegion::new(
            self.guest_phys_addr,
            self.memory_size,
            self.userspace_addr,
            self.mmap_offset,
        );

        #[cfg(feature = "xen")]
        VhostUserMemoryRegion::with_xen(
            self.guest_phys_addr,
            self.memory_size,
            self.userspace_addr,
            self.mmap_offset,
            self.xen_mmap_flags,
            self.xen_mmap_data,
        )
    }

    /// Creates VhostUserSingleMemoryRegion from Self.
    #[cfg(feature = "vhost-user")]
    pub fn to_single_region(&self) -> VhostUserSingleMemoryRegion {
        VhostUserSingleMemoryRegion::new(
            self.guest_phys_addr,
            self.memory_size,
            self.userspace_addr,
            self.mmap_offset,
            #[cfg(feature = "xen")]
            self.xen_mmap_flags,
            #[cfg(feature = "xen")]
            self.xen_mmap_data,
        )
    }
}

/// Shared memory region data for logging dirty pages
#[derive(Default, Clone, Copy)]
pub struct VhostUserDirtyLogRegion {
    /// Size of the shared memory region for logging dirty pages
    pub mmap_size: u64,
    /// Offset where region starts
    pub mmap_offset: u64,
    /// File descriptor for mmap
    pub mmap_handle: RawFd,
}

/// Vhost memory access permission (VHOST_ACCESS_* mapping)
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum VhostAccess {
    /// No access.
    #[default]
    No = 0,
    /// Read-Only access.
    ReadOnly = 1,
    /// Write-Only access.
    WriteOnly = 2,
    /// Read and Write access.
    ReadWrite = 3,
}

/// Vhost IOTLB message type (VHOST_IOTLB_* mapping)
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum VhostIotlbType {
    /// Empty message (not valid).
    #[default]
    Empty = 0,
    /// I/O virtual address mapping is missing or invalidated.
    Miss = 1,
    /// Update the I/O virtual address mapping.
    Update = 2,
    /// Invalidate the I/O virtual address mapping.
    Invalidate = 3,
    /// Access failed to an I/O virtual address.
    AccessFail = 4,
    /// Batch of multiple `Update` messages begins.
    BatchBegin = 5,
    /// Batch of multiple `Update` messages ends.
    BatchEnd = 6,
}

/// Vhost IOTLB message structure.
#[derive(Default, Clone, Copy)]
pub struct VhostIotlbMsg {
    /// I/O virtual address.
    pub iova: u64,
    /// Size of the I/O mapping.
    pub size: u64,
    /// Virtual address in the current process.
    pub userspace_addr: u64,
    /// Access permissions.
    pub perm: VhostAccess,
    /// Type of the message.
    pub msg_type: VhostIotlbType,
}

/// Vhost IOTLB message parser.
pub trait VhostIotlbMsgParser {
    /// Parse the IOTLB message and fill a VhostIotlbMsg.
    ///
    /// # Arguments
    /// * `msg` - IOTLB message parsed.
    fn parse(&self, msg: &mut VhostIotlbMsg) -> Result<()>;
}

/// An interface for IOTLB messages support for vhost-based backend
pub trait VhostIotlbBackend: std::marker::Sized {
    /// Send an IOTLB message to the vhost-based backend.
    ///
    /// # Arguments
    /// * `msg` - IOTLB message to send.
    fn send_iotlb_msg(&self, msg: &VhostIotlbMsg) -> Result<()>;
}

/// An interface for setting up vhost-based backend drivers with interior mutability.
///
/// Vhost devices are subset of virtio devices, which improve virtio device's performance by
/// delegating data plane operations to dedicated IO service processes. Vhost devices use the
/// same virtqueue layout as virtio devices to allow vhost devices to be mapped directly to
/// virtio devices.
///
/// The purpose of vhost is to implement a subset of a virtio device's functionality outside the
/// VMM process. Typically fast paths for IO operations are delegated to the dedicated IO service
/// processes, and slow path for device configuration are still handled by the VMM process. It may
/// also be used to control access permissions of virtio backend devices.
pub trait VhostBackend: std::marker::Sized {
    /// Get a bitmask of supported virtio/vhost features.
    fn get_features(&self) -> Result<u64>;

    /// Inform the vhost subsystem which features to enable.
    /// This should be a subset of supported features from get_features().
    ///
    /// # Arguments
    /// * `features` - Bitmask of features to set.
    fn set_features(&self, features: u64) -> Result<()>;

    /// Set the current process as the owner of the vhost backend.
    /// This must be run before any other vhost commands.
    fn set_owner(&self) -> Result<()>;

    /// Used to be sent to request disabling all rings
    /// This is no longer used.
    fn reset_owner(&self) -> Result<()>;

    /// Set the guest memory mappings for vhost to use.
    fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>;

    /// Set base address for page modification logging.
    fn set_log_base(&self, base: u64, region: Option<VhostUserDirtyLogRegion>) -> Result<()>;

    /// Specify an eventfd file descriptor to signal on log write.
    fn set_log_fd(&self, fd: RawFd) -> Result<()>;

    /// Set the number of descriptors in the vring.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to set descriptor count for.
    /// * `num` - Number of descriptors in the queue.
    fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()>;

    /// Set the addresses for a given vring.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to set addresses for.
    /// * `config_data` - Configuration data for a vring.
    fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()>;

    /// Set the first index to look for available descriptors.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to modify.
    /// * `num` - Index where available descriptors start.
    fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()>;

    /// Get the available vring base offset.
    fn get_vring_base(&self, queue_index: usize) -> Result<u32>;

    /// Set the eventfd to trigger when buffers have been used by the host.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to modify.
    /// * `fd` - EventFd to trigger.
    fn set_vring_call(&self, queue_index: usize, fd: &EventFd) -> Result<()>;

    /// Set the eventfd that will be signaled by the guest when buffers are
    /// available for the host to process.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to modify.
    /// * `fd` - EventFd that will be signaled from guest.
    fn set_vring_kick(&self, queue_index: usize, fd: &EventFd) -> Result<()>;

    /// Set the eventfd that will be signaled by the guest when error happens.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to modify.
    /// * `fd` - EventFd that will be signaled from guest.
    fn set_vring_err(&self, queue_index: usize, fd: &EventFd) -> Result<()>;
}

/// An interface for setting up vhost-based backend drivers.
///
/// Vhost devices are subset of virtio devices, which improve virtio device's performance by
/// delegating data plane operations to dedicated IO service processes. Vhost devices use the
/// same virtqueue layout as virtio devices to allow vhost devices to be mapped directly to
/// virtio devices.
///
/// The purpose of vhost is to implement a subset of a virtio device's functionality outside the
/// VMM process. Typically fast paths for IO operations are delegated to the dedicated IO service
/// processes, and slow path for device configuration are still handled by the VMM process. It may
/// also be used to control access permissions of virtio backend devices.
pub trait VhostBackendMut: std::marker::Sized {
    /// Get a bitmask of supported virtio/vhost features.
    fn get_features(&mut self) -> Result<u64>;

    /// Inform the vhost subsystem which features to enable.
    /// This should be a subset of supported features from get_features().
    ///
    /// # Arguments
    /// * `features` - Bitmask of features to set.
    fn set_features(&mut self, features: u64) -> Result<()>;

    /// Set the current process as the owner of the vhost backend.
    /// This must be run before any other vhost commands.
    fn set_owner(&mut self) -> Result<()>;

    /// Used to be sent to request disabling all rings
    /// This is no longer used.
    fn reset_owner(&mut self) -> Result<()>;

    /// Set the guest memory mappings for vhost to use.
    fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>;

    /// Set base address for page modification logging.
    fn set_log_base(&mut self, base: u64, region: Option<VhostUserDirtyLogRegion>) -> Result<()>;

    /// Specify an eventfd file descriptor to signal on log write.
    fn set_log_fd(&mut self, fd: RawFd) -> Result<()>;

    /// Set the number of descriptors in the vring.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to set descriptor count for.
    /// * `num` - Number of descriptors in the queue.
    fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>;

    /// Set the addresses for a given vring.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to set addresses for.
    /// * `config_data` - Configuration data for a vring.
    fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()>;

    /// Set the first index to look for available descriptors.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to modify.
    /// * `num` - Index where available descriptors start.
    fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()>;

    /// Get the available vring base offset.
    fn get_vring_base(&mut self, queue_index: usize) -> Result<u32>;

    /// Set the eventfd to trigger when buffers have been used by the host.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to modify.
    /// * `fd` - EventFd to trigger.
    fn set_vring_call(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;

    /// Set the eventfd that will be signaled by the guest when buffers are
    /// available for the host to process.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to modify.
    /// * `fd` - EventFd that will be signaled from guest.
    fn set_vring_kick(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;

    /// Set the eventfd that will be signaled by the guest when error happens.
    ///
    /// # Arguments
    /// * `queue_index` - Index of the queue to modify.
    /// * `fd` - EventFd that will be signaled from guest.
    fn set_vring_err(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
}

impl<T: VhostBackendMut> VhostBackend for RwLock<T> {
    fn get_features(&self) -> Result<u64> {
        self.write().unwrap().get_features()
    }

    fn set_features(&self, features: u64) -> Result<()> {
        self.write().unwrap().set_features(features)
    }

    fn set_owner(&self) -> Result<()> {
        self.write().unwrap().set_owner()
    }

    fn reset_owner(&self) -> Result<()> {
        self.write().unwrap().reset_owner()
    }

    fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
        self.write().unwrap().set_mem_table(regions)
    }

    fn set_log_base(&self, base: u64, region: Option<VhostUserDirtyLogRegion>) -> Result<()> {
        self.write().unwrap().set_log_base(base, region)
    }

    fn set_log_fd(&self, fd: RawFd) -> Result<()> {
        self.write().unwrap().set_log_fd(fd)
    }

    fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> {
        self.write().unwrap().set_vring_num(queue_index, num)
    }

    fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
        self.write()
            .unwrap()
            .set_vring_addr(queue_index, config_data)
    }

    fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()> {
        self.write().unwrap().set_vring_base(queue_index, base)
    }

    fn get_vring_base(&self, queue_index: usize) -> Result<u32> {
        self.write().unwrap().get_vring_base(queue_index)
    }

    fn set_vring_call(&self, queue_index: usize, fd: &EventFd) -> Result<()> {
        self.write().unwrap().set_vring_call(queue_index, fd)
    }

    fn set_vring_kick(&self, queue_index: usize, fd: &EventFd) -> Result<()> {
        self.write().unwrap().set_vring_kick(queue_index, fd)
    }

    fn set_vring_err(&self, queue_index: usize, fd: &EventFd) -> Result<()> {
        self.write().unwrap().set_vring_err(queue_index, fd)
    }
}

impl<T: VhostBackendMut> VhostBackend for RefCell<T> {
    fn get_features(&self) -> Result<u64> {
        self.borrow_mut().get_features()
    }

    fn set_features(&self, features: u64) -> Result<()> {
        self.borrow_mut().set_features(features)
    }

    fn set_owner(&self) -> Result<()> {
        self.borrow_mut().set_owner()
    }

    fn reset_owner(&self) -> Result<()> {
        self.borrow_mut().reset_owner()
    }

    fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
        self.borrow_mut().set_mem_table(regions)
    }

    fn set_log_base(&self, base: u64, region: Option<VhostUserDirtyLogRegion>) -> Result<()> {
        self.borrow_mut().set_log_base(base, region)
    }

    fn set_log_fd(&self, fd: RawFd) -> Result<()> {
        self.borrow_mut().set_log_fd(fd)
    }

    fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> {
        self.borrow_mut().set_vring_num(queue_index, num)
    }

    fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
        self.borrow_mut().set_vring_addr(queue_index, config_data)
    }

    fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()> {
        self.borrow_mut().set_vring_base(queue_index, base)
    }

    fn get_vring_base(&self, queue_index: usize) -> Result<u32> {
        self.borrow_mut().get_vring_base(queue_index)
    }

    fn set_vring_call(&self, queue_index: usize, fd: &EventFd) -> Result<()> {
        self.borrow_mut().set_vring_call(queue_index, fd)
    }

    fn set_vring_kick(&self, queue_index: usize, fd: &EventFd) -> Result<()> {
        self.borrow_mut().set_vring_kick(queue_index, fd)
    }

    fn set_vring_err(&self, queue_index: usize, fd: &EventFd) -> Result<()> {
        self.borrow_mut().set_vring_err(queue_index, fd)
    }
}

#[cfg(any(test, feature = "test-utils"))]
impl VhostUserMemoryRegionInfo {
    /// creates instance of `VhostUserMemoryRegionInfo`.
    pub fn new(
        guest_phys_addr: u64,
        memory_size: u64,
        userspace_addr: u64,
        mmap_offset: u64,
        mmap_handle: RawFd,
    ) -> Self {
        Self {
            guest_phys_addr,
            memory_size,
            userspace_addr,
            mmap_offset,
            mmap_handle,

            #[cfg(feature = "xen")]
            xen_mmap_flags: vm_memory::MmapXenFlags::UNIX.bits(),
            #[cfg(feature = "xen")]
            xen_mmap_data: 0,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct MockBackend {}

    impl VhostBackendMut for MockBackend {
        fn get_features(&mut self) -> Result<u64> {
            Ok(0x1)
        }

        fn set_features(&mut self, features: u64) -> Result<()> {
            assert_eq!(features, 0x1);
            Ok(())
        }

        fn set_owner(&mut self) -> Result<()> {
            Ok(())
        }

        fn reset_owner(&mut self) -> Result<()> {
            Ok(())
        }

        fn set_mem_table(&mut self, _regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
            Ok(())
        }

        fn set_log_base(
            &mut self,
            base: u64,
            region: Option<VhostUserDirtyLogRegion>,
        ) -> Result<()> {
            assert_eq!(base, 0x100);
            let region = region.unwrap();
            assert_eq!(region.mmap_size, 0x1000);
            assert_eq!(region.mmap_offset, 0x10);
            assert_eq!(region.mmap_handle, 100);
            Ok(())
        }

        fn set_log_fd(&mut self, fd: RawFd) -> Result<()> {
            assert_eq!(fd, 100);
            Ok(())
        }

        fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> {
            assert_eq!(queue_index, 1);
            assert_eq!(num, 256);
            Ok(())
        }

        fn set_vring_addr(
            &mut self,
            queue_index: usize,
            _config_data: &VringConfigData,
        ) -> Result<()> {
            assert_eq!(queue_index, 1);
            Ok(())
        }

        fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()> {
            assert_eq!(queue_index, 1);
            assert_eq!(base, 2);
            Ok(())
        }

        fn get_vring_base(&mut self, queue_index: usize) -> Result<u32> {
            assert_eq!(queue_index, 1);
            Ok(2)
        }

        fn set_vring_call(&mut self, queue_index: usize, _fd: &EventFd) -> Result<()> {
            assert_eq!(queue_index, 1);
            Ok(())
        }

        fn set_vring_kick(&mut self, queue_index: usize, _fd: &EventFd) -> Result<()> {
            assert_eq!(queue_index, 1);
            Ok(())
        }

        fn set_vring_err(&mut self, queue_index: usize, _fd: &EventFd) -> Result<()> {
            assert_eq!(queue_index, 1);
            Ok(())
        }
    }

    #[test]
    fn test_vring_backend_mut() {
        let b = RwLock::new(MockBackend {});

        assert_eq!(b.get_features().unwrap(), 0x1);
        b.set_features(0x1).unwrap();
        b.set_owner().unwrap();
        b.reset_owner().unwrap();
        b.set_mem_table(&[]).unwrap();
        b.set_log_base(
            0x100,
            Some(VhostUserDirtyLogRegion {
                mmap_size: 0x1000,
                mmap_offset: 0x10,
                mmap_handle: 100,
            }),
        )
        .unwrap();
        b.set_log_fd(100).unwrap();
        b.set_vring_num(1, 256).unwrap();

        let config = VringConfigData {
            queue_max_size: 0x1000,
            queue_size: 0x2000,
            flags: 0x0,
            desc_table_addr: 0x4000,
            used_ring_addr: 0x5000,
            avail_ring_addr: 0x6000,
            log_addr: None,
        };
        b.set_vring_addr(1, &config).unwrap();

        b.set_vring_base(1, 2).unwrap();
        assert_eq!(b.get_vring_base(1).unwrap(), 2);

        let eventfd = EventFd::new(0).unwrap();
        b.set_vring_call(1, &eventfd).unwrap();
        b.set_vring_kick(1, &eventfd).unwrap();
        b.set_vring_err(1, &eventfd).unwrap();
    }

    #[test]
    fn test_vring_config_data() {
        let mut config = VringConfigData {
            queue_max_size: 0x1000,
            queue_size: 0x2000,
            flags: 0x0,
            desc_table_addr: 0x4000,
            used_ring_addr: 0x5000,
            avail_ring_addr: 0x6000,
            log_addr: None,
        };

        assert!(config.is_log_addr_valid());
        assert_eq!(config.get_log_addr(), 0);

        config.flags = 0x1;
        assert!(!config.is_log_addr_valid());
        assert_eq!(config.get_log_addr(), 0);

        config.log_addr = Some(0x7000);
        assert!(config.is_log_addr_valid());
        assert_eq!(config.get_log_addr(), 0x7000);

        config.flags = 0x0;
        assert!(config.is_log_addr_valid());
        assert_eq!(config.get_log_addr(), 0);
    }
}
