1 // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 // 4 // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. 5 // Use of this source code is governed by a BSD-style license that can be 6 // found in the LICENSE-BSD-3-Clause file. 7 8 use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START}; 9 use crate::x86_64::mpspec; 10 use crate::GuestMemoryMmap; 11 use libc::c_char; 12 use std::io; 13 use std::mem; 14 use std::result; 15 use std::slice; 16 use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError}; 17 18 // This is a workaround to the Rust enforcement specifying that any implementation of a foreign 19 // trait (in this case `ByteValued`) where: 20 // * the type that is implementing the trait is foreign or 21 // * all of the parameters being passed to the trait (if there are any) are also foreign 22 // is prohibited. 23 #[derive(Copy, Clone, Default)] 24 struct MpcBusWrapper(mpspec::mpc_bus); 25 #[derive(Copy, Clone, Default)] 26 struct MpcCpuWrapper(mpspec::mpc_cpu); 27 #[derive(Copy, Clone, Default)] 28 struct MpcIntsrcWrapper(mpspec::mpc_intsrc); 29 #[derive(Copy, Clone, Default)] 30 struct MpcIoapicWrapper(mpspec::mpc_ioapic); 31 #[derive(Copy, Clone, Default)] 32 struct MpcTableWrapper(mpspec::mpc_table); 33 #[derive(Copy, Clone, Default)] 34 struct MpcLintsrcWrapper(mpspec::mpc_lintsrc); 35 #[derive(Copy, Clone, Default)] 36 struct MpfIntelWrapper(mpspec::mpf_intel); 37 38 // SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization. 39 unsafe impl ByteValued for MpcBusWrapper {} 40 unsafe impl ByteValued for MpcCpuWrapper {} 41 unsafe impl ByteValued for MpcIntsrcWrapper {} 42 unsafe impl ByteValued for MpcIoapicWrapper {} 43 unsafe impl ByteValued for MpcTableWrapper {} 44 unsafe impl ByteValued for MpcLintsrcWrapper {} 45 unsafe impl ByteValued for MpfIntelWrapper {} 46 47 #[derive(Debug)] 48 pub enum Error { 49 /// There was too little guest memory to store the entire MP table. 50 NotEnoughMemory, 51 /// The MP table has too little address space to be stored. 52 AddressOverflow, 53 /// Failure while zeroing out the memory for the MP table. 54 Clear(GuestMemoryError), 55 /// Number of CPUs exceeds the maximum supported CPUs 56 TooManyCpus, 57 /// Failure to write the MP floating pointer. 58 WriteMpfIntel(GuestMemoryError), 59 /// Failure to write MP CPU entry. 60 WriteMpcCpu(GuestMemoryError), 61 /// Failure to write MP ioapic entry. 62 WriteMpcIoapic(GuestMemoryError), 63 /// Failure to write MP bus entry. 64 WriteMpcBus(GuestMemoryError), 65 /// Failure to write MP interrupt source entry. 66 WriteMpcIntsrc(GuestMemoryError), 67 /// Failure to write MP local interrupt source entry. 68 WriteMpcLintsrc(GuestMemoryError), 69 /// Failure to write MP table header. 70 WriteMpcTable(GuestMemoryError), 71 } 72 73 pub type Result<T> = result::Result<T, Error>; 74 75 // With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies 76 // one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's 77 // a large number for FC usecases. 78 pub const MAX_SUPPORTED_CPUS: u32 = 254; 79 80 // Convenience macro for making arrays of diverse character types. 81 macro_rules! char_array { 82 ($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] ) 83 } 84 85 // Most of these variables are sourced from the Intel MP Spec 1.4. 86 const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_'); 87 const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P'); 88 const MPC_SPEC: i8 = 4; 89 const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' '); 90 const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12]; 91 const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' '); 92 const APIC_VERSION: u8 = 0x14; 93 const CPU_STEPPING: u32 = 0x600; 94 const CPU_FEATURE_APIC: u32 = 0x200; 95 const CPU_FEATURE_FPU: u32 = 0x001; 96 97 fn compute_checksum<T: Copy>(v: &T) -> u8 { 98 // Safe because we are only reading the bytes within the size of the `T` reference `v`. 99 let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) }; 100 let mut checksum: u8 = 0; 101 for i in v_slice.iter() { 102 checksum = checksum.wrapping_add(*i); 103 } 104 checksum 105 } 106 107 fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 { 108 let checksum = compute_checksum(v).wrapping_sub(v.checksum); 109 (!checksum).wrapping_add(1) 110 } 111 112 fn compute_mp_size(num_cpus: u8) -> usize { 113 mem::size_of::<MpfIntelWrapper>() 114 + mem::size_of::<MpcTableWrapper>() 115 + mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize) 116 + mem::size_of::<MpcIoapicWrapper>() 117 + mem::size_of::<MpcBusWrapper>() 118 + mem::size_of::<MpcIntsrcWrapper>() * 16 119 + mem::size_of::<MpcLintsrcWrapper>() * 2 120 } 121 122 /// Performs setup of the MP table for the given `num_cpus`. 123 pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> { 124 if num_cpus as u32 > MAX_SUPPORTED_CPUS { 125 return Err(Error::TooManyCpus); 126 } 127 128 // Used to keep track of the next base pointer into the MP table. 129 let mut base_mp = offset; 130 131 let mp_size = compute_mp_size(num_cpus); 132 133 if offset.unchecked_add(mp_size as u64) >= HIGH_RAM_START { 134 warn!("Skipping mptable creation due to insufficient space"); 135 return Ok(()); 136 } 137 138 let mut checksum: u8 = 0; 139 let ioapicid: u8 = num_cpus + 1; 140 141 // The checked_add here ensures the all of the following base_mp.unchecked_add's will be without 142 // overflow. 143 if let Some(end_mp) = base_mp.checked_add((mp_size - 1) as u64) { 144 if !mem.address_in_range(end_mp) { 145 return Err(Error::NotEnoughMemory); 146 } 147 } else { 148 return Err(Error::AddressOverflow); 149 } 150 151 mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size) 152 .map_err(Error::Clear)?; 153 154 { 155 let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default()); 156 let size = mem::size_of::<MpfIntelWrapper>() as u64; 157 mpf_intel.0.signature = SMP_MAGIC_IDENT; 158 mpf_intel.0.length = 1; 159 mpf_intel.0.specification = 4; 160 mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32; 161 mpf_intel.0.checksum = mpf_intel_compute_checksum(&mpf_intel.0); 162 mem.write_obj(mpf_intel, base_mp) 163 .map_err(Error::WriteMpfIntel)?; 164 base_mp = base_mp.unchecked_add(size); 165 } 166 167 // We set the location of the mpc_table here but we can't fill it out until we have the length 168 // of the entire table later. 169 let table_base = base_mp; 170 base_mp = base_mp.unchecked_add(mem::size_of::<MpcTableWrapper>() as u64); 171 172 { 173 let size = mem::size_of::<MpcCpuWrapper>(); 174 for cpu_id in 0..num_cpus { 175 let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default()); 176 mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8; 177 mpc_cpu.0.apicid = cpu_id; 178 mpc_cpu.0.apicver = APIC_VERSION; 179 mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8 180 | if cpu_id == 0 { 181 mpspec::CPU_BOOTPROCESSOR as u8 182 } else { 183 0 184 }; 185 mpc_cpu.0.cpufeature = CPU_STEPPING; 186 mpc_cpu.0.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU; 187 mem.write_obj(mpc_cpu, base_mp) 188 .map_err(Error::WriteMpcCpu)?; 189 base_mp = base_mp.unchecked_add(size as u64); 190 checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu.0)); 191 } 192 } 193 { 194 let size = mem::size_of::<MpcBusWrapper>(); 195 let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default()); 196 mpc_bus.0.type_ = mpspec::MP_BUS as u8; 197 mpc_bus.0.busid = 0; 198 mpc_bus.0.bustype = BUS_TYPE_ISA; 199 mem.write_obj(mpc_bus, base_mp) 200 .map_err(Error::WriteMpcBus)?; 201 base_mp = base_mp.unchecked_add(size as u64); 202 checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0)); 203 } 204 { 205 let size = mem::size_of::<MpcIoapicWrapper>(); 206 let mut mpc_ioapic = MpcIoapicWrapper(mpspec::mpc_ioapic::default()); 207 mpc_ioapic.0.type_ = mpspec::MP_IOAPIC as u8; 208 mpc_ioapic.0.apicid = ioapicid; 209 mpc_ioapic.0.apicver = APIC_VERSION; 210 mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8; 211 mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32; 212 mem.write_obj(mpc_ioapic, base_mp) 213 .map_err(Error::WriteMpcIoapic)?; 214 base_mp = base_mp.unchecked_add(size as u64); 215 checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic.0)); 216 } 217 // Per kvm_setup_default_irq_routing() in kernel 218 for i in 0..16 { 219 let size = mem::size_of::<MpcIntsrcWrapper>(); 220 let mut mpc_intsrc = MpcIntsrcWrapper(mpspec::mpc_intsrc::default()); 221 mpc_intsrc.0.type_ = mpspec::MP_INTSRC as u8; 222 mpc_intsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_INT as u8; 223 mpc_intsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16; 224 mpc_intsrc.0.srcbus = 0; 225 mpc_intsrc.0.srcbusirq = i; 226 mpc_intsrc.0.dstapic = ioapicid; 227 mpc_intsrc.0.dstirq = i; 228 mem.write_obj(mpc_intsrc, base_mp) 229 .map_err(Error::WriteMpcIntsrc)?; 230 base_mp = base_mp.unchecked_add(size as u64); 231 checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0)); 232 } 233 { 234 let size = mem::size_of::<MpcLintsrcWrapper>(); 235 let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default()); 236 mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8; 237 mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_EXT_INT as u8; 238 mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16; 239 mpc_lintsrc.0.srcbusid = 0; 240 mpc_lintsrc.0.srcbusirq = 0; 241 mpc_lintsrc.0.destapic = 0; 242 mpc_lintsrc.0.destapiclint = 0; 243 mem.write_obj(mpc_lintsrc, base_mp) 244 .map_err(Error::WriteMpcLintsrc)?; 245 base_mp = base_mp.unchecked_add(size as u64); 246 checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0)); 247 } 248 { 249 let size = mem::size_of::<MpcLintsrcWrapper>(); 250 let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default()); 251 mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8; 252 mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_NMI as u8; 253 mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16; 254 mpc_lintsrc.0.srcbusid = 0; 255 mpc_lintsrc.0.srcbusirq = 0; 256 mpc_lintsrc.0.destapic = 0xFF; /* to all local APICs */ 257 mpc_lintsrc.0.destapiclint = 1; 258 mem.write_obj(mpc_lintsrc, base_mp) 259 .map_err(Error::WriteMpcLintsrc)?; 260 base_mp = base_mp.unchecked_add(size as u64); 261 checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0)); 262 } 263 264 // At this point we know the size of the mp_table. 265 let table_end = base_mp; 266 267 { 268 let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default()); 269 mpc_table.0.signature = MPC_SIGNATURE; 270 mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16; 271 mpc_table.0.spec = MPC_SPEC; 272 mpc_table.0.oem = MPC_OEM; 273 mpc_table.0.productid = MPC_PRODUCT_ID; 274 mpc_table.0.lapic = APIC_START.0 as u32; 275 checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0)); 276 mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8; 277 mem.write_obj(mpc_table, table_base) 278 .map_err(Error::WriteMpcTable)?; 279 } 280 281 Ok(()) 282 } 283 284 #[cfg(test)] 285 mod tests { 286 use super::*; 287 use crate::layout::MPTABLE_START; 288 use vm_memory::{GuestAddress, GuestUsize}; 289 290 fn table_entry_size(type_: u8) -> usize { 291 match type_ as u32 { 292 mpspec::MP_PROCESSOR => mem::size_of::<MpcCpuWrapper>(), 293 mpspec::MP_BUS => mem::size_of::<MpcBusWrapper>(), 294 mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(), 295 mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(), 296 mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(), 297 _ => panic!("unrecognized mpc table entry type: {}", type_), 298 } 299 } 300 301 #[test] 302 fn bounds_check() { 303 let num_cpus = 4; 304 let mem = 305 GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap(); 306 307 setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap(); 308 } 309 310 #[test] 311 fn bounds_check_fails() { 312 let num_cpus = 4; 313 let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)]) 314 .unwrap(); 315 316 assert!(setup_mptable(MPTABLE_START, &mem, num_cpus).is_err()); 317 } 318 319 #[test] 320 fn mpf_intel_checksum() { 321 let num_cpus = 1; 322 let mem = 323 GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap(); 324 325 setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap(); 326 327 let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap(); 328 329 assert_eq!( 330 mpf_intel_compute_checksum(&mpf_intel.0), 331 mpf_intel.0.checksum 332 ); 333 } 334 335 #[test] 336 fn mpc_table_checksum() { 337 let num_cpus = 4; 338 let mem = 339 GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap(); 340 341 setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap(); 342 343 let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap(); 344 let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize); 345 let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap(); 346 347 struct Sum(u8); 348 impl io::Write for Sum { 349 fn write(&mut self, buf: &[u8]) -> io::Result<usize> { 350 for v in buf.iter() { 351 self.0 = self.0.wrapping_add(*v); 352 } 353 Ok(buf.len()) 354 } 355 fn flush(&mut self) -> io::Result<()> { 356 Ok(()) 357 } 358 } 359 360 let mut sum = Sum(0); 361 mem.write_to(mpc_offset, &mut sum, mpc_table.0.length as usize) 362 .unwrap(); 363 assert_eq!(sum.0, 0); 364 } 365 366 #[test] 367 fn cpu_entry_count() { 368 let mem = GuestMemoryMmap::from_ranges(&[( 369 MPTABLE_START, 370 compute_mp_size(MAX_SUPPORTED_CPUS as u8), 371 )]) 372 .unwrap(); 373 374 for i in 0..MAX_SUPPORTED_CPUS as u8 { 375 setup_mptable(MPTABLE_START, &mem, i).unwrap(); 376 377 let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap(); 378 let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize); 379 let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap(); 380 let mpc_end = mpc_offset 381 .checked_add(mpc_table.0.length as GuestUsize) 382 .unwrap(); 383 384 let mut entry_offset = mpc_offset 385 .checked_add(mem::size_of::<MpcTableWrapper>() as GuestUsize) 386 .unwrap(); 387 let mut cpu_count = 0; 388 while entry_offset < mpc_end { 389 let entry_type: u8 = mem.read_obj(entry_offset).unwrap(); 390 entry_offset = entry_offset 391 .checked_add(table_entry_size(entry_type) as GuestUsize) 392 .unwrap(); 393 assert!(entry_offset <= mpc_end); 394 if entry_type as u32 == mpspec::MP_PROCESSOR { 395 cpu_count += 1; 396 } 397 } 398 assert_eq!(cpu_count, i); 399 } 400 } 401 402 #[test] 403 fn cpu_entry_count_max() { 404 let cpus = MAX_SUPPORTED_CPUS + 1; 405 let mem = 406 GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap(); 407 408 let result = setup_mptable(MPTABLE_START, &mem, cpus as u8); 409 assert!(result.is_err()); 410 } 411 } 412