Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/hypervisor/src/gunyah/aarch64.rs
5394 views
1
// Copyright 2023 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
use std::collections::BTreeMap;
6
7
use aarch64_sys_reg::AArch64SysRegId;
8
use anyhow::bail;
9
use anyhow::Context;
10
use base::error;
11
use base::Error;
12
use base::MappedRegion;
13
use base::Result;
14
use cros_fdt::Fdt;
15
use cros_fdt::FdtNode;
16
use libc::ENOTSUP;
17
use libc::ENOTTY;
18
use snapshot::AnySnapshot;
19
use vm_memory::GuestAddress;
20
use vm_memory::MemoryRegionPurpose;
21
22
use super::GunyahVcpu;
23
use super::GunyahVm;
24
use crate::Hypervisor;
25
use crate::PsciVersion;
26
use crate::VcpuAArch64;
27
use crate::VcpuRegAArch64;
28
use crate::VmAArch64;
29
use crate::PSCI_0_2;
30
31
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
32
33
const IRQ_TYPE_EDGE_RISING: u32 = 0x00000001;
34
const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004;
35
const MAX_VM_SIZE: u64 = 0x780000000;
36
37
fn fdt_create_shm_device(
38
parent: &mut FdtNode,
39
index: u32,
40
guest_addr: GuestAddress,
41
) -> cros_fdt::Result<()> {
42
let shm_name = format!("shm-{index:x}");
43
let shm_node = parent.subnode_mut(&shm_name)?;
44
shm_node.set_prop("vdevice-type", "shm")?;
45
shm_node.set_prop("peer-default", ())?;
46
shm_node.set_prop("dma_base", 0u64)?;
47
let mem_node = shm_node.subnode_mut("memory")?;
48
// We have to add the shm device for RM to accept the swiotlb memparcel.
49
// Memparcel is only used on android14-6.1. Once android14-6.1 is EOL
50
// we should be able to remove all the times we call fdt_create_shm_device()
51
mem_node.set_prop("optional", ())?;
52
mem_node.set_prop("label", index)?;
53
mem_node.set_prop("#address-cells", 2u32)?;
54
mem_node.set_prop("base", guest_addr.offset())
55
}
56
57
impl VmAArch64 for GunyahVm {
58
fn get_hypervisor(&self) -> &dyn Hypervisor {
59
&self.gh
60
}
61
62
fn load_protected_vm_firmware(
63
&mut self,
64
fw_addr: GuestAddress,
65
fw_max_size: u64,
66
) -> Result<()> {
67
self.set_protected_vm_firmware_ipa(fw_addr, fw_max_size)
68
}
69
70
fn create_vcpu(&self, id: usize) -> Result<Box<dyn VcpuAArch64>> {
71
Ok(Box::new(GunyahVm::create_vcpu(self, id)?))
72
}
73
74
fn create_fdt(&self, fdt: &mut Fdt, phandles: &BTreeMap<&str, u32>) -> cros_fdt::Result<()> {
75
let top_node = fdt.root_mut().subnode_mut("gunyah-vm-config")?;
76
77
top_node.set_prop("image-name", "crosvm-vm")?;
78
top_node.set_prop("os-type", "linux")?;
79
80
let memory_node = top_node.subnode_mut("memory")?;
81
memory_node.set_prop("#address-cells", 2u32)?;
82
memory_node.set_prop("#size-cells", 2u32)?;
83
84
let mut base_set = false;
85
let mut firmware_set = false;
86
let mut size = 0;
87
for region in self.guest_mem.regions() {
88
match region.options.purpose {
89
MemoryRegionPurpose::GuestMemoryRegion => {
90
// Assume first GuestMemoryRegion contains the payload
91
if !base_set {
92
base_set = true;
93
memory_node.set_prop("base-address", region.guest_addr.offset())?;
94
memory_node.set_prop("size-max", MAX_VM_SIZE)?;
95
}
96
size += region.size as u64;
97
}
98
MemoryRegionPurpose::ProtectedFirmwareRegion => {
99
if firmware_set {
100
// Should only have one protected firmware memory region.
101
error!("Multiple ProtectedFirmwareRegions unexpected.");
102
unreachable!()
103
}
104
firmware_set = true;
105
memory_node.set_prop("firmware-address", region.guest_addr.offset())?;
106
}
107
MemoryRegionPurpose::StaticSwiotlbRegion => {
108
size += region.size as u64;
109
}
110
_ => {}
111
}
112
}
113
if size > MAX_VM_SIZE {
114
panic!("Total memory size {size} exceeds maximum allowed size {MAX_VM_SIZE}");
115
}
116
117
let interrupts_node = top_node.subnode_mut("interrupts")?;
118
interrupts_node.set_prop("config", *phandles.get("intc").unwrap())?;
119
120
let vcpus_node = top_node.subnode_mut("vcpus")?;
121
vcpus_node.set_prop("affinity", "proxy")?;
122
123
let vdev_node = top_node.subnode_mut("vdevices")?;
124
vdev_node.set_prop("generate", "/hypervisor")?;
125
126
for irq in self.routes.lock().iter() {
127
let bell_name = format!("bell-{:x}", irq.irq);
128
let bell_node = vdev_node.subnode_mut(&bell_name)?;
129
bell_node.set_prop("vdevice-type", "doorbell")?;
130
let path_name = format!("/hypervisor/bell-{:x}", irq.irq);
131
bell_node.set_prop("generate", path_name)?;
132
bell_node.set_prop("label", irq.irq)?;
133
bell_node.set_prop("peer-default", ())?;
134
bell_node.set_prop("source-can-clear", ())?;
135
136
let interrupt_type = if irq.level {
137
IRQ_TYPE_LEVEL_HIGH
138
} else {
139
IRQ_TYPE_EDGE_RISING
140
};
141
let interrupts = [GIC_FDT_IRQ_TYPE_SPI, irq.irq, interrupt_type];
142
bell_node.set_prop("interrupts", &interrupts)?;
143
}
144
145
for region in self.guest_mem.regions() {
146
let create_shm_node = match region.options.purpose {
147
MemoryRegionPurpose::Bios => false,
148
MemoryRegionPurpose::GuestMemoryRegion => false,
149
// Described by the "firmware-address" property
150
MemoryRegionPurpose::ProtectedFirmwareRegion => false,
151
MemoryRegionPurpose::ReservedMemory => false,
152
MemoryRegionPurpose::StaticSwiotlbRegion => true,
153
};
154
155
if create_shm_node {
156
fdt_create_shm_device(
157
vdev_node,
158
region.index.try_into().unwrap(),
159
region.guest_addr,
160
)?;
161
}
162
}
163
164
Ok(())
165
}
166
167
fn init_arch(
168
&self,
169
payload_entry_address: GuestAddress,
170
fdt_address: GuestAddress,
171
fdt_size: usize,
172
) -> anyhow::Result<()> {
173
// The payload entry is the memory address where the kernel starts.
174
// This memory region contains both the DTB and the kernel image,
175
// so ensure they are located together.
176
177
let (dtb_mapping, _, dtb_obj_offset) = self
178
.guest_mem
179
.find_region(fdt_address)
180
.context("Failed to find FDT region")?;
181
let (payload_mapping, payload_offset, payload_obj_offset) = self
182
.guest_mem
183
.find_region(payload_entry_address)
184
.context("Failed to find payload region")?;
185
186
if !std::ptr::eq(dtb_mapping, payload_mapping) || dtb_obj_offset != payload_obj_offset {
187
bail!("DTB and payload are not part of same memory region.");
188
}
189
190
if self.vm_id.is_some() && self.pas_id.is_some() {
191
// Gunyah will find the metadata about the Qualcomm Trusted VM in the
192
// first few pages (decided at build time) of the primary payload region.
193
// This metadata consists of the elf header which tells Gunyah where
194
// the different elf segments (kernel/DTB/ramdisk) are. As we send the entire
195
// primary payload as a single memory parcel to Gunyah, with the offsets from
196
// the elf header, Gunyah can find the VM DTBOs.
197
// Pass on the primary payload region start address and its size for Qualcomm
198
// Trusted VMs.
199
if payload_offset != 0 {
200
bail!("QCOM Trusted VM: payload offset {payload_offset} != 0");
201
}
202
self.set_vm_auth_type_to_qcom_trusted_vm(
203
payload_entry_address,
204
payload_mapping.size().try_into().unwrap(),
205
)
206
.context("Failed to set VM authentication type")?;
207
}
208
209
self.set_dtb_config(fdt_address, fdt_size)?;
210
211
// Gunyah sets the PC to the payload entry point for protected VMs without firmware.
212
// It needs to be 0 as Gunyah assumes it to be kernel start.
213
if self.hv_cfg.protection_type.isolates_memory()
214
&& !self.hv_cfg.protection_type.runs_firmware()
215
&& payload_offset != 0
216
{
217
bail!("Payload offset must be zero");
218
}
219
220
if let Err(e) = self.set_boot_pc(payload_entry_address.offset()) {
221
if e.errno() == ENOTTY {
222
// GH_VM_SET_BOOT_CONTEXT ioctl is not supported, but returning success
223
// for backward compatibility when the offset is zero.
224
if payload_offset != 0 {
225
bail!("Payload offset must be zero");
226
}
227
} else {
228
return Err(e).context("set_boot_pc failed");
229
}
230
}
231
232
self.start()?;
233
234
Ok(())
235
}
236
}
237
238
impl VcpuAArch64 for GunyahVcpu {
239
fn init(&self, _features: &[crate::VcpuFeature]) -> Result<()> {
240
Ok(())
241
}
242
243
fn init_pmu(&self, _irq: u64) -> Result<()> {
244
Err(Error::new(ENOTSUP))
245
}
246
247
fn has_pvtime_support(&self) -> bool {
248
false
249
}
250
251
fn init_pvtime(&self, _pvtime_ipa: u64) -> Result<()> {
252
Err(Error::new(ENOTSUP))
253
}
254
255
fn set_one_reg(&self, _reg_id: VcpuRegAArch64, _data: u64) -> Result<()> {
256
unimplemented!()
257
}
258
259
fn get_one_reg(&self, _reg_id: VcpuRegAArch64) -> Result<u64> {
260
Err(Error::new(ENOTSUP))
261
}
262
263
fn set_vector_reg(&self, _reg_num: u8, _data: u128) -> Result<()> {
264
unimplemented!()
265
}
266
267
fn get_vector_reg(&self, _reg_num: u8) -> Result<u128> {
268
unimplemented!()
269
}
270
271
fn get_psci_version(&self) -> Result<PsciVersion> {
272
Ok(PSCI_0_2)
273
}
274
275
fn set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()> {
276
Err(Error::new(ENOTSUP))
277
}
278
279
fn get_max_hw_bps(&self) -> Result<usize> {
280
Err(Error::new(ENOTSUP))
281
}
282
283
fn get_system_regs(&self) -> Result<BTreeMap<AArch64SysRegId, u64>> {
284
Err(Error::new(ENOTSUP))
285
}
286
287
fn get_cache_info(&self) -> Result<BTreeMap<u8, u64>> {
288
Err(Error::new(ENOTSUP))
289
}
290
291
fn set_cache_info(&self, _cache_info: BTreeMap<u8, u64>) -> Result<()> {
292
Err(Error::new(ENOTSUP))
293
}
294
295
fn hypervisor_specific_snapshot(&self) -> anyhow::Result<AnySnapshot> {
296
unimplemented!()
297
}
298
299
fn hypervisor_specific_restore(&self, _data: AnySnapshot) -> anyhow::Result<()> {
300
unimplemented!()
301
}
302
}
303
304