1 // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 // 5 // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 6 7 use std::cmp::min; 8 use std::mem; 9 use std::sync::atomic::{AtomicBool, Ordering}; 10 use std::sync::{Arc, Barrier}; 11 use std::thread; 12 13 // https://github.com/rust-lang/libc/issues/1848 14 #[cfg_attr(target_env = "musl", allow(deprecated))] 15 use libc::time_t; 16 use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME}; 17 use vm_device::BusDevice; 18 use vmm_sys_util::eventfd::EventFd; 19 20 const INDEX_MASK: u8 = 0x7f; 21 const INDEX_OFFSET: u64 = 0x0; 22 const DATA_OFFSET: u64 = 0x1; 23 const DATA_LEN: usize = 128; 24 25 /// A CMOS/RTC device commonly seen on x86 I/O port 0x70/0x71. 26 pub struct Cmos { 27 index: u8, 28 data: [u8; DATA_LEN], 29 reset_evt: EventFd, 30 vcpus_kill_signalled: Option<Arc<AtomicBool>>, 31 } 32 33 impl Cmos { 34 /// Constructs a CMOS/RTC device with initial data. 35 /// `mem_below_4g` is the size of memory in bytes below the 32-bit gap. 36 /// `mem_above_4g` is the size of memory in bytes above the 32-bit gap. 37 pub fn new( 38 mem_below_4g: u64, 39 mem_above_4g: u64, 40 reset_evt: EventFd, 41 vcpus_kill_signalled: Option<Arc<AtomicBool>>, 42 ) -> Cmos { 43 let mut data = [0u8; DATA_LEN]; 44 45 // Extended memory from 16 MB to 4 GB in units of 64 KB 46 let ext_mem = min( 47 0xFFFF, 48 mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024), 49 ); 50 data[0x34] = ext_mem as u8; 51 data[0x35] = (ext_mem >> 8) as u8; 52 53 // High memory (> 4GB) in units of 64 KB 54 let high_mem = min(0x00FF_FFFF, mem_above_4g / (64 * 1024)); 55 data[0x5b] = high_mem as u8; 56 data[0x5c] = (high_mem >> 8) as u8; 57 data[0x5d] = (high_mem >> 16) as u8; 58 59 Cmos { 60 index: 0, 61 data, 62 reset_evt, 63 vcpus_kill_signalled, 64 } 65 } 66 } 67 68 impl BusDevice for Cmos { 69 fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> { 70 if data.len() != 1 { 71 warn!("Invalid write size on CMOS device: {}", data.len()); 72 return None; 73 } 74 75 match offset { 76 INDEX_OFFSET => self.index = data[0], 77 DATA_OFFSET => { 78 if self.index == 0x8f && data[0] == 0 { 79 info!("CMOS reset"); 80 self.reset_evt.write(1).unwrap(); 81 if let Some(vcpus_kill_signalled) = self.vcpus_kill_signalled.take() { 82 // Spin until we are sure the reset_evt has been handled and that when 83 // we return from the KVM_RUN we will exit rather than re-enter the guest. 84 while !vcpus_kill_signalled.load(Ordering::SeqCst) { 85 // This is more effective than thread::yield_now() at 86 // avoiding a priority inversion with the VMM thread 87 thread::sleep(std::time::Duration::from_millis(1)); 88 } 89 } 90 } else { 91 self.data[(self.index & INDEX_MASK) as usize] = data[0] 92 } 93 } 94 o => warn!("bad write offset on CMOS device: {}", o), 95 }; 96 None 97 } 98 99 fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { 100 fn to_bcd(v: u8) -> u8 { 101 assert!(v < 100); 102 ((v / 10) << 4) | (v % 10) 103 } 104 105 if data.len() != 1 { 106 warn!("Invalid read size on CMOS device: {}", data.len()); 107 return; 108 } 109 110 data[0] = match offset { 111 INDEX_OFFSET => self.index, 112 DATA_OFFSET => { 113 let seconds; 114 let minutes; 115 let hours; 116 let week_day; 117 let day; 118 let month; 119 let year; 120 // SAFETY: The clock_gettime and gmtime_r calls are safe as long as the structs they are 121 // given are large enough, and neither of them fail. It is safe to zero initialize 122 // the tm and timespec struct because it contains only plain data. 123 let update_in_progress = unsafe { 124 let mut timespec: timespec = mem::zeroed(); 125 clock_gettime(CLOCK_REALTIME, &mut timespec as *mut _); 126 127 // https://github.com/rust-lang/libc/issues/1848 128 #[cfg_attr(target_env = "musl", allow(deprecated))] 129 let now: time_t = timespec.tv_sec; 130 let mut tm: tm = mem::zeroed(); 131 gmtime_r(&now, &mut tm as *mut _); 132 133 // The following lines of code are safe but depend on tm being in scope. 134 seconds = tm.tm_sec; 135 minutes = tm.tm_min; 136 hours = tm.tm_hour; 137 week_day = tm.tm_wday + 1; 138 day = tm.tm_mday; 139 month = tm.tm_mon + 1; 140 year = tm.tm_year; 141 142 // Update in Progress bit held for last 224us of each second 143 const NANOSECONDS_PER_SECOND: i64 = 1_000_000_000; 144 const UIP_HOLD_LENGTH: i64 = 8 * NANOSECONDS_PER_SECOND / 32768; 145 timespec.tv_nsec >= (NANOSECONDS_PER_SECOND - UIP_HOLD_LENGTH) 146 }; 147 match self.index { 148 0x00 => to_bcd(seconds as u8), 149 0x02 => to_bcd(minutes as u8), 150 0x04 => to_bcd(hours as u8), 151 0x06 => to_bcd(week_day as u8), 152 0x07 => to_bcd(day as u8), 153 0x08 => to_bcd(month as u8), 154 0x09 => to_bcd((year % 100) as u8), 155 // Bit 5 for 32kHz clock. Bit 7 for Update in Progress 156 0x0a => 1 << 5 | (update_in_progress as u8) << 7, 157 // Bit 0-6 are reserved and must be 0. 158 // Bit 7 must be 1 (CMOS has power) 159 0x0d => 1 << 7, 160 0x32 => to_bcd(((year + 1900) / 100) as u8), 161 _ => { 162 // self.index is always guaranteed to be in range via INDEX_MASK. 163 self.data[(self.index & INDEX_MASK) as usize] 164 } 165 } 166 } 167 o => { 168 warn!("bad read offset on CMOS device: {}", o); 169 0 170 } 171 } 172 } 173 } 174