Skip to content

Commit

Permalink
add inter-vm conmmunication on hvisor
Browse files Browse the repository at this point in the history
  • Loading branch information
HeartLinked committed Dec 5, 2024
1 parent 4ab23d3 commit e8a2ef2
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 1 deletion.
2 changes: 1 addition & 1 deletion modules/ruxhal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repository = "https://github.com/syswonder/ruxos/tree/main/modules/ruxhal"
[features]
virtio_hal = ["driver_virtio","virtio-drivers","axalloc"]
smp = []
alloc = []
alloc = ["axalloc"]
fp_simd = []
paging = ["axalloc", "page_table"]
irq = []
Expand Down
5 changes: 5 additions & 0 deletions modules/ruxhal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ pub mod misc {
pub use super::platform::misc::*;
}

/// Inter-VM communication.
pub mod ivc {
pub use super::platform::ivc::*;
}

/// Multi-core operations.
#[cfg(feature = "smp")]
pub mod mp {
Expand Down
218 changes: 218 additions & 0 deletions modules/ruxhal/src/platform/aarch64_common/ivc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/* Copyright (c) [2024] [Syswonder Community]
* [Ruxos] is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/

use core::alloc::Layout;
use core::ptr;
use core::ptr::NonNull;
use axalloc:: {global_allocator, GlobalAllocator};

pub const CONFIG_MAX_IVC_CONFIGS: usize = 0x2;
pub const HVISOR_HC_IVC_INFO: usize = 0x5;
pub const HVISOR_HC_IVC_INFO_ALIGN: usize = 0x8;
pub const HVISOR_HC_IVC_INFO_SIZE: usize = 56;
pub const __PA: usize = 0xffff_0000_0000_0000;

#[repr(C)]
#[derive(Debug)]
struct IvCInfo {
len: u64, // The number of IVC shared memory
ivc_ct_ipas: [u64; CONFIG_MAX_IVC_CONFIGS], // Control Table IPA
ivc_shmem_ipas: [u64; CONFIG_MAX_IVC_CONFIGS], // Share memory IPA
ivc_ids: [u32; CONFIG_MAX_IVC_CONFIGS], // IVC id; the ivc id of zones that communicate with each other have to be the same
ivc_irqs: [u32; CONFIG_MAX_IVC_CONFIGS], // irq number
}

#[repr(C)]
#[derive(Debug)]
struct ControlTable {
ivc_id: u32,
max_peers: u32,
rw_sec_size: u32,
out_sec_size: u32,
peer_id: u32,
ipi_invoke: u32,
}

/// This module provides a way to establish a communication channel with a hypervisor (hvisor)
/// for virtual machine (VM) communication using shared memory. Each VM can have two communication
/// regions, and the region to be used for communication is specified during the connection setup.
///
/// The communication is handled through the following steps:
///
/// 1. **Connection Setup**: The `connect()` function allocates memory for communication structures
/// and invokes a hypervisor call (`hvc`) to retrieve necessary information about the IVC
/// (Inter-VM Communication) and control tables. The specific communication region to be used
/// is determined by the parameter passed to `connect()`. This process sets up the shared memory
/// and prepares the communication channel.
/// 2. **Message Sending**: The `send_message()` function writes a message to the shared memory area
/// specified by the hypervisor. The message is written to a predefined memory location, and
/// the control table is updated to notify the target VM of the message.
/// 3. **Connection Teardown**: The `close()` function frees the allocated memory and closes the
/// communication channel, cleaning up resources to prevent memory leaks.
///
/// The module relies on a `GlobalAllocator` for memory management and uses raw pointers and unsafe
/// Rust operations to interact with memory addresses provided by the hypervisor. It is critical
/// that the communication sequence follows the correct order: connect -> send_message -> close.
///
/// # Example
/// ```
/// let mut conn = Connection::new();
/// if let Err(e) = conn.connect(0) { // Choose the first communication region (0)
/// info!("Error connecting: {}", e);
/// return;
/// }
/// if let Err(e) = conn.send_message("Hello from VM!") {
/// error!("Error sending message: {}", e);
/// }
/// if let Err(e) = conn.close() {
/// error!("Error closing connection: {}", e);
/// }
/// ```
pub fn ivc_example() {
let mut conn = Connection::new();

// Establish the connection
if let Err(e) = conn.connect(0) {
info!("Error connecting: {}", e);
return;
}

// Send the message
if let Err(e) = conn.send_message("Hello from VM!") {
info!("Error sending message: {}", e);
}

// Close the connection
if let Err(e) = conn.close() {
info!("Error closing connection: {}", e);
}
}

pub struct Connection<'a> {
// Reference to the allocator
allocator: &'a GlobalAllocator,
ivc_info_ptr: *mut IvCInfo,
control_table_ptr: *mut ControlTable,
}

impl<'a> Connection<'a> {

pub fn new() -> Self {
// Get the reference to the global allocator
let allocator = global_allocator();
debug!("Connection created.");
Connection {
allocator,
ivc_info_ptr: ptr::null_mut(),
control_table_ptr: ptr::null_mut(),
}
}

pub fn connect(&mut self, communication_zone: usize) -> Result<(), &'static str> {
let alloc_size = HVISOR_HC_IVC_INFO_SIZE;
let align = HVISOR_HC_IVC_INFO_ALIGN;
let layout = Layout::from_size_align(alloc_size, align).unwrap();

let ptr = self.allocator.alloc(layout).expect("Memory allocation failed!");
self.ivc_info_ptr = ptr.as_ptr() as *mut IvCInfo;

let vpa_ivcinfo = self.ivc_info_ptr as usize;
let pa_ivcinfo: usize = vpa_ivcinfo - __PA;

debug!("The memory address of the IVC Info: VA: 0x{:x}, IPA: 0x{:x}", vpa_ivcinfo, pa_ivcinfo);

ivc_hvc_call(HVISOR_HC_IVC_INFO as u32, pa_ivcinfo, HVISOR_HC_IVC_INFO_SIZE);
debug!("ivc_hvc_call finished.");

// Safety: At this point we know ivc_info_ptr is valid and allocated
let ivc_info: &IvCInfo = unsafe { &*self.ivc_info_ptr };

let control_table_ptr = (ivc_info.ivc_ct_ipas[communication_zone] + __PA as u64) as *mut ControlTable;
self.control_table_ptr = control_table_ptr;

info!("IVC Connection established.");
Ok(())
}

pub fn send_message(&self, message: &str) -> Result<(), &'static str> {
if self.ivc_info_ptr.is_null() {
return Err("Not connected");
}

let ivc_info: &IvCInfo = unsafe { &*self.ivc_info_ptr };

let control_table: &ControlTable = unsafe { &*self.control_table_ptr };

// Suppose we are zone1, we are to send message to zone0.
// Therefore use the out_sec_size field of the ControlTable struct (typically 0x1000).
let offset = control_table.out_sec_size as u64;
let address: u64 = ivc_info.ivc_shmem_ipas[0] + offset + __PA as u64;

write_to_address(address, message)?;
info!("Message written to shared memory: {}", message);

// Modify the control table to inform Zone0 Linux
let control_table: &mut ControlTable = unsafe { &mut *self.control_table_ptr };
debug!("Ipi_invoke reset to inform Zone0 linux.");
control_table.ipi_invoke = 0x0;

Ok(())
}


pub fn close(&mut self) -> Result<(), &'static str> {
if self.ivc_info_ptr.is_null() {
return Err("Not connected");
}
// free the memory
let layout = Layout::from_size_align(HVISOR_HC_IVC_INFO_SIZE, HVISOR_HC_IVC_INFO_ALIGN).unwrap();
self.allocator.dealloc(unsafe { NonNull::new_unchecked(self.ivc_info_ptr as *mut u8) }, layout);
info!("IVC Connection closed.");
Ok(())
}
}

/// ivc "hvc" method call
fn ivc_hvc_call(func: u32, arg0: usize, arg1: usize) -> usize {
let ret;
unsafe {
core::arch::asm!(
"hvc #4856",
inlateout("x0") func as usize => ret,
in("x1") arg0,
in("x2") arg1,
options(nostack)
);
info!("Ivc call: func: {:x}, arg0: 0x{:x}, arg1: 0x{:x}, ret: 0x{:x}", func, arg0, arg1, ret);
}
ret
}

fn write_to_address(addr: u64, data: &str) -> Result<(), &'static str> {
let len = data.len();

if addr == 0 {
return Err("Invalid address: 0x0");
}

unsafe {
let ptr = addr as *mut u8;

for (i, &byte) in data.as_bytes().iter().enumerate() {
let target_ptr = ptr.add(i);
*target_ptr = byte;
}

let null_ptr = ptr.add(len);
*null_ptr = 0;
}

Ok(())
}
2 changes: 2 additions & 0 deletions modules/ruxhal/src/platform/aarch64_common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ pub mod pl011;

#[cfg(feature = "rtc")]
pub mod pl031;

pub mod ivc;
4 changes: 4 additions & 0 deletions modules/ruxhal/src/platform/aarch64_qemu_virt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub mod misc {
pub use crate::platform::aarch64_common::psci::system_off as terminate;
}

pub mod ivc {
pub use crate::platform::aarch64_common::ivc::*;
}

extern "C" {
fn exception_vector_base();
fn rust_main(cpu_id: usize, dtb: usize);
Expand Down
2 changes: 2 additions & 0 deletions platforms/aarch64-qemu-virt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ family = "aarch64-qemu-virt"
phys-memory-base = "0x4000_0000"
# Size of the whole physical memory.
# phys-memory-size = "0x800_0000" # 128M
# phys-memory-size = "0x3000_0000" # 0.75G, same as zone1 linux on Hvisor
# phys-memory-size = "0x4000_0000" # 1G
phys-memory-size = "0x8000_0000" # 2G
# phys-memory-size = "0xc000_0000" # 3G
Expand All @@ -34,6 +35,7 @@ mmio-regions = [
["0x1000_0000", "0x2eff_0000"], # PCI memory ranges (ranges 1: 32-bit MMIO space)
["0x40_1000_0000", "0x1000_0000"], # PCI config space
["0x0901_0000", "0x1000"], # RTC space
["0x7000_0000", "0x4_0000"] # Hvisor IVC
]
# VirtIO MMIO regions with format (`base_paddr`, `size`).
virtio-mmio-regions = [
Expand Down

0 comments on commit e8a2ef2

Please sign in to comment.