1 // Copyright 2024 The Android Open Source Project
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 //! Tests running a VM with LLNDK
16
17 use anyhow::{bail, ensure, Context, Result};
18 use log::info;
19 use std::fs::File;
20 use std::io::{self, BufWriter, Write};
21 use std::os::fd::IntoRawFd;
22 use std::time::{Duration, Instant};
23 use vsock::{VsockListener, VsockStream, VMADDR_CID_HOST};
24
25 use avf_bindgen::*;
26 use service_vm_comm::{Request, Response, ServiceVmRequest, VmType};
27
28 const VM_MEMORY_MB: i32 = 16;
29 const WRITE_BUFFER_CAPACITY: usize = 512;
30
31 const LISTEN_TIMEOUT: Duration = Duration::from_secs(10);
32 const READ_TIMEOUT: Duration = Duration::from_secs(10);
33 const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
34 const STOP_TIMEOUT: timespec = timespec { tv_sec: 10, tv_nsec: 0 };
35
36 /// Processes the request in the service VM.
process_request(vsock_stream: &mut VsockStream, request: Request) -> Result<Response>37 fn process_request(vsock_stream: &mut VsockStream, request: Request) -> Result<Response> {
38 write_request(vsock_stream, &ServiceVmRequest::Process(request))?;
39 read_response(vsock_stream)
40 }
41
42 /// Sends the request to the service VM.
write_request(vsock_stream: &mut VsockStream, request: &ServiceVmRequest) -> Result<()>43 fn write_request(vsock_stream: &mut VsockStream, request: &ServiceVmRequest) -> Result<()> {
44 let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, vsock_stream);
45 ciborium::into_writer(request, &mut buffer)?;
46 buffer.flush().context("Failed to flush the buffer")?;
47 Ok(())
48 }
49
50 /// Reads the response from the service VM.
read_response(vsock_stream: &mut VsockStream) -> Result<Response>51 fn read_response(vsock_stream: &mut VsockStream) -> Result<Response> {
52 let response: Response = ciborium::from_reader(vsock_stream)
53 .context("Failed to read the response from the service VM")?;
54 Ok(response)
55 }
56
listen_from_guest(port: u32) -> Result<VsockStream>57 fn listen_from_guest(port: u32) -> Result<VsockStream> {
58 let vsock_listener =
59 VsockListener::bind_with_cid_port(VMADDR_CID_HOST, port).context("Failed to bind vsock")?;
60 vsock_listener.set_nonblocking(true).context("Failed to set nonblocking")?;
61 let start_time = Instant::now();
62 loop {
63 if start_time.elapsed() >= LISTEN_TIMEOUT {
64 bail!("Timeout while listening");
65 }
66 match vsock_listener.accept() {
67 Ok((vsock_stream, _peer_addr)) => return Ok(vsock_stream),
68 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
69 std::thread::sleep(Duration::from_millis(100));
70 }
71 Err(e) => bail!("Failed to listen: {e:?}"),
72 }
73 }
74 }
75
run_rialto(protected_vm: bool) -> Result<()>76 fn run_rialto(protected_vm: bool) -> Result<()> {
77 let kernel_file =
78 File::open("/data/nativetest64/vendor/rialto.bin").context("Failed to open kernel file")?;
79 let kernel_fd = kernel_file.into_raw_fd();
80
81 // SAFETY: AVirtualMachineRawConfig_create() isn't unsafe but rust_bindgen forces it to be seen
82 // as unsafe
83 let config = unsafe { AVirtualMachineRawConfig_create() };
84
85 info!("raw config created");
86
87 // The first 4 bytes of an instance ID for a vendor VM must be 0xFFFFFFFF
88 let mut instance_id: [u8; 64] = [0; 64];
89 instance_id[0..4].copy_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
90
91 // SAFETY: config is the only reference to a valid object
92 unsafe {
93 AVirtualMachineRawConfig_setName(config, c"vts_libavf_test_rialto".as_ptr());
94 AVirtualMachineRawConfig_setKernel(config, kernel_fd);
95 AVirtualMachineRawConfig_setProtectedVm(config, protected_vm);
96 AVirtualMachineRawConfig_setMemoryMiB(config, VM_MEMORY_MB);
97 AVirtualMachineRawConfig_setInstanceId(config, instance_id.as_ptr(), instance_id.len());
98 }
99
100 let mut vm = std::ptr::null_mut();
101 let mut service = std::ptr::null_mut();
102
103 ensure!(
104 // SAFETY: &mut service is a valid pointer to *AVirtualizationService
105 unsafe { AVirtualizationService_create(&mut service, false) } == 0,
106 "AVirtualizationService_create failed"
107 );
108
109 scopeguard::defer! {
110 // SAFETY: service is a valid pointer to AVirtualizationService
111 unsafe { AVirtualizationService_destroy(service); }
112 }
113
114 ensure!(
115 // SAFETY: &mut vm is a valid pointer to *AVirtualMachine
116 unsafe {
117 AVirtualMachine_createRaw(
118 service, config, -1, // console_in
119 -1, // console_out
120 -1, // log
121 &mut vm,
122 )
123 } == 0,
124 "AVirtualMachine_createRaw failed"
125 );
126
127 scopeguard::defer! {
128 // SAFETY: vm is a valid pointer to AVirtualMachine
129 unsafe { AVirtualMachine_destroy(vm); }
130 }
131
132 info!("vm created");
133
134 let vm_type = if protected_vm { VmType::ProtectedVm } else { VmType::NonProtectedVm };
135
136 let listener_thread = std::thread::spawn(move || listen_from_guest(vm_type.port()));
137
138 // SAFETY: vm is the only reference to a valid object
139 unsafe {
140 AVirtualMachine_start(vm);
141 }
142
143 info!("VM started");
144
145 let mut vsock_stream = listener_thread.join().unwrap()?;
146 vsock_stream.set_read_timeout(Some(READ_TIMEOUT))?;
147 vsock_stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
148
149 info!("client connected");
150
151 let request_data = vec![1, 2, 3, 4, 5];
152 let expected_data = vec![5, 4, 3, 2, 1];
153 let response = process_request(&mut vsock_stream, Request::Reverse(request_data))
154 .context("Failed to process request")?;
155 let Response::Reverse(reversed_data) = response else {
156 bail!("Expected Response::Reverse but was {response:?}");
157 };
158 ensure!(reversed_data == expected_data, "Expected {expected_data:?} but was {reversed_data:?}");
159
160 info!("request processed");
161
162 write_request(&mut vsock_stream, &ServiceVmRequest::Shutdown)
163 .context("Failed to send shutdown")?;
164
165 info!("shutdown sent");
166
167 let mut stop_reason = AVirtualMachineStopReason::AVIRTUAL_MACHINE_UNRECOGNISED;
168 ensure!(
169 // SAFETY: vm is the only reference to a valid object
170 unsafe { AVirtualMachine_waitForStop(vm, &STOP_TIMEOUT, &mut stop_reason) },
171 "AVirtualMachine_waitForStop failed"
172 );
173
174 info!("stopped");
175
176 Ok(())
177 }
178
179 #[test]
test_run_rialto_protected() -> Result<()>180 fn test_run_rialto_protected() -> Result<()> {
181 if hypervisor_props::is_protected_vm_supported()? {
182 run_rialto(true /* protected_vm */)
183 } else {
184 info!("pVMs are not supported on device. skipping test");
185 Ok(())
186 }
187 }
188
189 #[test]
test_run_rialto_non_protected() -> Result<()>190 fn test_run_rialto_non_protected() -> Result<()> {
191 if hypervisor_props::is_vm_supported()? {
192 run_rialto(false /* protected_vm */)
193 } else {
194 info!("non-pVMs are not supported on device. skipping test");
195 Ok(())
196 }
197 }
198