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 base::error;
8 use base::Error;
9 use base::Result;
10 use cros_fdt::Fdt;
11 use cros_fdt::FdtNode;
12 use libc::ENOENT;
13 use libc::ENOTSUP;
14 use vm_memory::GuestAddress;
15 use vm_memory::MemoryRegionPurpose;
16
17 use super::GunyahVcpu;
18 use super::GunyahVm;
19 use crate::Hypervisor;
20 use crate::PsciVersion;
21 use crate::VcpuAArch64;
22 use crate::VcpuRegAArch64;
23 use crate::VmAArch64;
24 use crate::PSCI_0_2;
25
26 const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
27
28 const IRQ_TYPE_EDGE_RISING: u32 = 0x00000001;
29 const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004;
30
fdt_create_shm_device( parent: &mut FdtNode, index: u32, guest_addr: GuestAddress, ) -> cros_fdt::Result<()>31 fn fdt_create_shm_device(
32 parent: &mut FdtNode,
33 index: u32,
34 guest_addr: GuestAddress,
35 ) -> cros_fdt::Result<()> {
36 let shm_name = format!("shm-{:x}", index);
37 let shm_node = parent.subnode_mut(&shm_name)?;
38 shm_node.set_prop("vdevice-type", "shm")?;
39 shm_node.set_prop("peer-default", ())?;
40 shm_node.set_prop("dma_base", 0u64)?;
41 let mem_node = shm_node.subnode_mut("memory")?;
42 // We have to add the shm device for RM to accept the swiotlb memparcel.
43 // Memparcel is only used on android14-6.1. Once android14-6.1 is EOL
44 // we should be able to remove all the times we call fdt_create_shm_device()
45 mem_node.set_prop("optional", ())?;
46 mem_node.set_prop("label", index)?;
47 mem_node.set_prop("#address-cells", 2u32)?;
48 mem_node.set_prop("base", guest_addr.offset())
49 }
50
51 impl VmAArch64 for GunyahVm {
get_hypervisor(&self) -> &dyn Hypervisor52 fn get_hypervisor(&self) -> &dyn Hypervisor {
53 &self.gh
54 }
55
load_protected_vm_firmware( &mut self, fw_addr: GuestAddress, fw_max_size: u64, ) -> Result<()>56 fn load_protected_vm_firmware(
57 &mut self,
58 fw_addr: GuestAddress,
59 fw_max_size: u64,
60 ) -> Result<()> {
61 self.set_protected_vm_firmware_ipa(fw_addr, fw_max_size)
62 }
63
create_vcpu(&self, id: usize) -> Result<Box<dyn VcpuAArch64>>64 fn create_vcpu(&self, id: usize) -> Result<Box<dyn VcpuAArch64>> {
65 Ok(Box::new(GunyahVm::create_vcpu(self, id)?))
66 }
67
create_fdt(&self, fdt: &mut Fdt, phandles: &BTreeMap<&str, u32>) -> cros_fdt::Result<()>68 fn create_fdt(&self, fdt: &mut Fdt, phandles: &BTreeMap<&str, u32>) -> cros_fdt::Result<()> {
69 let top_node = fdt.root_mut().subnode_mut("gunyah-vm-config")?;
70
71 top_node.set_prop("image-name", "crosvm-vm")?;
72 top_node.set_prop("os-type", "linux")?;
73
74 let memory_node = top_node.subnode_mut("memory")?;
75 memory_node.set_prop("#address-cells", 2u32)?;
76 memory_node.set_prop("#size-cells", 2u32)?;
77
78 let mut base_set = false;
79 let mut firmware_set = false;
80 for region in self.guest_mem.regions() {
81 match region.options.purpose {
82 MemoryRegionPurpose::GuestMemoryRegion => {
83 // Assume first GuestMemoryRegion contains the payload
84 if !base_set {
85 base_set = true;
86 memory_node.set_prop("base-address", region.guest_addr.offset())?;
87 }
88 }
89 MemoryRegionPurpose::ProtectedFirmwareRegion => {
90 if firmware_set {
91 // Should only have one protected firmware memory region.
92 error!("Multiple ProtectedFirmwareRegions unexpected.");
93 unreachable!()
94 }
95 firmware_set = true;
96 memory_node.set_prop("firmware-address", region.guest_addr.offset())?;
97 }
98 _ => {}
99 }
100 }
101
102 let interrupts_node = top_node.subnode_mut("interrupts")?;
103 interrupts_node.set_prop("config", *phandles.get("intc").unwrap())?;
104
105 let vcpus_node = top_node.subnode_mut("vcpus")?;
106 vcpus_node.set_prop("affinity", "proxy")?;
107
108 let vdev_node = top_node.subnode_mut("vdevices")?;
109 vdev_node.set_prop("generate", "/hypervisor")?;
110
111 for irq in self.routes.lock().iter() {
112 let bell_name = format!("bell-{:x}", irq.irq);
113 let bell_node = vdev_node.subnode_mut(&bell_name)?;
114 bell_node.set_prop("vdevice-type", "doorbell")?;
115 let path_name = format!("/hypervisor/bell-{:x}", irq.irq);
116 bell_node.set_prop("generate", path_name)?;
117 bell_node.set_prop("label", irq.irq)?;
118 bell_node.set_prop("peer-default", ())?;
119 bell_node.set_prop("source-can-clear", ())?;
120
121 let interrupt_type = if irq.level {
122 IRQ_TYPE_LEVEL_HIGH
123 } else {
124 IRQ_TYPE_EDGE_RISING
125 };
126 let interrupts = [GIC_FDT_IRQ_TYPE_SPI, irq.irq, interrupt_type];
127 bell_node.set_prop("interrupts", &interrupts)?;
128 }
129
130 let mut base_set = false;
131 for region in self.guest_mem.regions() {
132 let create_shm_node = match region.options.purpose {
133 MemoryRegionPurpose::GuestMemoryRegion => {
134 // Assume first GuestMemoryRegion contains the payload
135 // This memory region is described by the "base-address" property
136 // and doesn't get re-described as a separate shm node.
137 let ret = base_set;
138 base_set = true;
139 ret
140 }
141 // Described by the "firmware-address" property
142 MemoryRegionPurpose::ProtectedFirmwareRegion => false,
143 MemoryRegionPurpose::StaticSwiotlbRegion => true,
144 };
145
146 if create_shm_node {
147 fdt_create_shm_device(
148 vdev_node,
149 region.index.try_into().unwrap(),
150 region.guest_addr,
151 )?;
152 }
153 }
154
155 Ok(())
156 }
157
init_arch( &self, payload_entry_address: GuestAddress, fdt_address: GuestAddress, fdt_size: usize, ) -> Result<()>158 fn init_arch(
159 &self,
160 payload_entry_address: GuestAddress,
161 fdt_address: GuestAddress,
162 fdt_size: usize,
163 ) -> Result<()> {
164 // Gunyah initializes the PC to be the payload entry (except for protected VMs)
165 // and assumes that the image is loaded at the beginning of the "primary"
166 // memory parcel (region). This parcel contains both DTB and kernel Image, so
167 // make sure that DTB and payload are in the same memory region and that
168 // payload is at the start of that region.
169
170 let (dtb_mapping, _, dtb_obj_offset) = self
171 .guest_mem
172 .find_region(fdt_address)
173 .map_err(|_| Error::new(ENOENT))?;
174 let (payload_mapping, payload_offset, payload_obj_offset) = self
175 .guest_mem
176 .find_region(payload_entry_address)
177 .map_err(|_| Error::new(ENOENT))?;
178
179 if !std::ptr::eq(dtb_mapping, payload_mapping) || dtb_obj_offset != payload_obj_offset {
180 panic!("DTB and payload are not part of same memory region.");
181 }
182
183 if payload_offset != 0 {
184 panic!("Payload offset must be zero");
185 }
186
187 self.set_dtb_config(fdt_address, fdt_size)?;
188
189 self.start()?;
190
191 Ok(())
192 }
193 }
194
195 impl VcpuAArch64 for GunyahVcpu {
init(&self, _features: &[crate::VcpuFeature]) -> Result<()>196 fn init(&self, _features: &[crate::VcpuFeature]) -> Result<()> {
197 Ok(())
198 }
199
init_pmu(&self, _irq: u64) -> Result<()>200 fn init_pmu(&self, _irq: u64) -> Result<()> {
201 Err(Error::new(ENOTSUP))
202 }
203
has_pvtime_support(&self) -> bool204 fn has_pvtime_support(&self) -> bool {
205 false
206 }
207
init_pvtime(&self, _pvtime_ipa: u64) -> Result<()>208 fn init_pvtime(&self, _pvtime_ipa: u64) -> Result<()> {
209 Err(Error::new(ENOTSUP))
210 }
211
set_one_reg(&self, _reg_id: VcpuRegAArch64, _data: u64) -> Result<()>212 fn set_one_reg(&self, _reg_id: VcpuRegAArch64, _data: u64) -> Result<()> {
213 unimplemented!()
214 }
215
get_one_reg(&self, _reg_id: VcpuRegAArch64) -> Result<u64>216 fn get_one_reg(&self, _reg_id: VcpuRegAArch64) -> Result<u64> {
217 Err(Error::new(ENOTSUP))
218 }
219
set_vector_reg(&self, _reg_num: u8, _data: u128) -> Result<()>220 fn set_vector_reg(&self, _reg_num: u8, _data: u128) -> Result<()> {
221 unimplemented!()
222 }
223
get_vector_reg(&self, _reg_num: u8) -> Result<u128>224 fn get_vector_reg(&self, _reg_num: u8) -> Result<u128> {
225 unimplemented!()
226 }
227
get_psci_version(&self) -> Result<PsciVersion>228 fn get_psci_version(&self) -> Result<PsciVersion> {
229 Ok(PSCI_0_2)
230 }
231
232 #[cfg(feature = "gdb")]
set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()>233 fn set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()> {
234 Err(Error::new(ENOTSUP))
235 }
236
237 #[cfg(feature = "gdb")]
set_gdb_registers( &self, _regs: &<gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::Registers, ) -> Result<()>238 fn set_gdb_registers(
239 &self,
240 _regs: &<gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::Registers,
241 ) -> Result<()> {
242 Err(Error::new(ENOTSUP))
243 }
244
245 #[cfg(feature = "gdb")]
get_gdb_registers( &self, _regs: &mut <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::Registers, ) -> Result<()>246 fn get_gdb_registers(
247 &self,
248 _regs: &mut <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::Registers,
249 ) -> Result<()> {
250 Err(Error::new(ENOTSUP))
251 }
252
253 #[cfg(feature = "gdb")]
get_max_hw_bps(&self) -> Result<usize>254 fn get_max_hw_bps(&self) -> Result<usize> {
255 Err(Error::new(ENOTSUP))
256 }
257
258 #[cfg(feature = "gdb")]
set_gdb_register( &self, _reg: <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::RegId, _data: &[u8], ) -> Result<()>259 fn set_gdb_register(
260 &self,
261 _reg: <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::RegId,
262 _data: &[u8],
263 ) -> Result<()> {
264 Err(Error::new(ENOTSUP))
265 }
266
267 #[cfg(feature = "gdb")]
get_gdb_register( &self, _reg: <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::RegId, _data: &mut [u8], ) -> Result<usize>268 fn get_gdb_register(
269 &self,
270 _reg: <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::RegId,
271 _data: &mut [u8],
272 ) -> Result<usize> {
273 Err(Error::new(ENOTSUP))
274 }
275 }
276