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