1 // Copyright 2020 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 //! `dsm` crate implements the required initialization workflows for smart amps. 5 6 mod datastore; 7 mod error; 8 pub mod utils; 9 mod vpd; 10 mod zero_player; 11 12 use std::{ 13 thread, 14 time::{Duration, SystemTime, UNIX_EPOCH}, 15 }; 16 17 use libcras::{CrasClient, CrasNodeType}; 18 use sys_util::{error, info}; 19 20 use crate::datastore::Datastore; 21 pub use crate::error::{Error, Result}; 22 use crate::utils::{run_time, shutdown_time}; 23 use crate::vpd::VPD; 24 pub use crate::zero_player::ZeroPlayer; 25 26 #[derive(Debug, Clone, Copy)] 27 /// `CalibData` represents the calibration data. 28 pub struct CalibData { 29 /// The DC resistance of the speaker is DSM unit. 30 pub rdc: i32, 31 /// The ambient temperature in celsius unit at which the rdc is measured. 32 pub temp: f32, 33 } 34 35 /// `TempConverter` converts the temperature value between celsius and unit in VPD::dsm_calib_temp. 36 pub struct TempConverter { 37 vpd_to_celsius: fn(i32) -> f32, 38 celsius_to_vpd: fn(f32) -> i32, 39 } 40 41 impl Default for TempConverter { default() -> Self42 fn default() -> Self { 43 let vpd_to_celsius = |x: i32| x as f32; 44 let celsius_to_vpd = |x: f32| x.round() as i32; 45 Self { 46 vpd_to_celsius, 47 celsius_to_vpd, 48 } 49 } 50 } 51 52 impl TempConverter { 53 /// Creates a `TempConverter` 54 /// 55 /// # Arguments 56 /// 57 /// * `vpd_to_celsius` - function to convert VPD::dsm_calib_temp to celsius unit` 58 /// * `celsius_to_vpd` - function to convert celsius unit to VPD::dsm_calib_temp` 59 /// # Results 60 /// 61 /// * `TempConverter` - it converts the temperature value between celsius and unit in VPD::dsm_calib_temp. new(vpd_to_celsius: fn(i32) -> f32, celsius_to_vpd: fn(f32) -> i32) -> Self62 pub fn new(vpd_to_celsius: fn(i32) -> f32, celsius_to_vpd: fn(f32) -> i32) -> Self { 63 Self { 64 vpd_to_celsius, 65 celsius_to_vpd, 66 } 67 } 68 } 69 70 /// `SpeakerStatus` are the possible return results of 71 /// DSM::check_speaker_over_heated_workflow. 72 pub enum SpeakerStatus { 73 ///`SpeakerStatus::Cold` means the speakers are not overheated and the Amp can 74 /// trigger the boot time calibration. 75 Cold, 76 /// `SpeakerStatus::Hot(Vec<CalibData>)` means the speakers may be too hot for calibration. 77 /// The boot time calibration should be skipped and the Amp should use the previous 78 /// calibration values returned by the enum. 79 Hot(Vec<CalibData>), 80 } 81 82 /// `DSM`, which implements the required initialization workflows for smart amps. 83 pub struct DSM { 84 snd_card: String, 85 num_channels: usize, 86 temp_converter: TempConverter, 87 rdc_to_ohm: fn(i32) -> f32, 88 temp_upper_limit: f32, 89 temp_lower_limit: f32, 90 } 91 92 impl DSM { 93 const SPEAKER_COOL_DOWN_TIME: Duration = Duration::from_secs(180); 94 const CALI_ERROR_UPPER_LIMIT: f32 = 0.3; 95 const CALI_ERROR_LOWER_LIMIT: f32 = 0.03; 96 97 /// Creates a `DSM` 98 /// 99 /// # Arguments 100 /// 101 /// * `snd_card` - `sound card name`. 102 /// * `num_channels` - `number of channels`. 103 /// * `rdc_to_ohm` - `fn(rdc: i32) -> f32 to convert the CalibData::rdc to ohm unit`. 104 /// * `temp_upper_limit` - the high limit of the valid ambient temperature in dsm unit. 105 /// * `temp_lower_limit` - the low limit of the valid ambient temperature in dsm unit. 106 /// 107 /// # Results 108 /// 109 /// * `DSM` - It implements the required initialization workflows for smart amps. new( snd_card: &str, num_channels: usize, rdc_to_ohm: fn(i32) -> f32, temp_upper_limit: f32, temp_lower_limit: f32, ) -> Self110 pub fn new( 111 snd_card: &str, 112 num_channels: usize, 113 rdc_to_ohm: fn(i32) -> f32, 114 temp_upper_limit: f32, 115 temp_lower_limit: f32, 116 ) -> Self { 117 Self { 118 snd_card: snd_card.to_owned(), 119 num_channels, 120 rdc_to_ohm, 121 temp_converter: TempConverter::default(), 122 temp_upper_limit, 123 temp_lower_limit, 124 } 125 } 126 127 /// Sets self.temp_converter to the given temp_converter. 128 /// 129 /// # Arguments 130 /// 131 /// * `temp_converter` - the convert function to use. set_temp_converter(&mut self, temp_converter: TempConverter)132 pub fn set_temp_converter(&mut self, temp_converter: TempConverter) { 133 self.temp_converter = temp_converter; 134 } 135 136 /// Checks whether the speakers are overheated or not according to the previous shutdown time. 137 /// The boot time calibration should be skipped when the speakers may be too hot 138 /// and the Amp should use the previous calibration value returned by the 139 /// SpeakerStatus::Hot(Vec<CalibData>). 140 /// 141 /// # Results 142 /// 143 /// * `SpeakerStatus::Cold` - which means the speakers are not overheated and the Amp can 144 /// trigger the boot time calibration. 145 /// * `SpeakerStatus::Hot(Vec<CalibData>)` - when the speakers may be too hot. The boot 146 /// time calibration should be skipped and the Amp should use the previous calibration values 147 /// returned by the enum. 148 /// 149 /// # Errors 150 /// 151 /// * The speakers are overheated and there are no previous calibration values stored. 152 /// * Cannot determine whether the speakers are overheated as previous shutdown time record is 153 /// invalid. check_speaker_over_heated_workflow(&self) -> Result<SpeakerStatus>154 pub fn check_speaker_over_heated_workflow(&self) -> Result<SpeakerStatus> { 155 if self.is_first_boot() { 156 return Ok(SpeakerStatus::Cold); 157 } 158 match self.is_speaker_over_heated() { 159 Ok(overheated) => { 160 if overheated { 161 let calib: Vec<CalibData> = (0..self.num_channels) 162 .map(|ch| -> Result<CalibData> { self.get_previous_calibration_value(ch) }) 163 .collect::<Result<Vec<CalibData>>>()?; 164 info!("the speakers are hot, the boot time calibration should be skipped"); 165 return Ok(SpeakerStatus::Hot(calib)); 166 } 167 Ok(SpeakerStatus::Cold) 168 } 169 Err(err) => { 170 // We cannot assume the speakers are not replaced or not overheated 171 // when the shutdown time file is invalid; therefore we can not use the datastore 172 // value anymore and we can not trigger boot time calibration. 173 for ch in 0..self.num_channels { 174 if let Err(e) = Datastore::delete(&self.snd_card, ch) { 175 error!("error delete datastore: {}", e); 176 } 177 } 178 Err(err) 179 } 180 } 181 } 182 183 /// Decides a good calibration value and updates the stored value according to the following 184 /// logic: 185 /// * Returns the previous value if the ambient temperature is not within a valid range. 186 /// * Returns Error::LargeCalibrationDiff if rdc difference is larger than 187 /// `CALI_ERROR_UPPER_LIMIT`. 188 /// * Returns the previous value if the rdc difference is smaller than `CALI_ERROR_LOWER_LIMIT`. 189 /// * Returns the boot time calibration value and updates the datastore value if the rdc. 190 /// difference is between `CALI_ERROR_UPPER_LIMIT` and `CALI_ERROR_LOWER_LIMIT`. 191 /// 192 /// # Arguments 193 /// 194 /// * `card` - `&Card`. 195 /// * `channel` - `channel number`. 196 /// * `calib_data` - `boot time calibrated data`. 197 /// 198 /// # Results 199 /// 200 /// * `CalibData` - the calibration data to be applied according to the deciding logic. 201 /// 202 /// # Errors 203 /// 204 /// * VPD does not exist. 205 /// * rdc difference is larger than `CALI_ERROR_UPPER_LIMIT`. 206 /// * Failed to update Datastore. decide_calibration_value_workflow( &self, channel: usize, calib_data: CalibData, ) -> Result<CalibData>207 pub fn decide_calibration_value_workflow( 208 &self, 209 channel: usize, 210 calib_data: CalibData, 211 ) -> Result<CalibData> { 212 if calib_data.temp < self.temp_lower_limit || calib_data.temp > self.temp_upper_limit { 213 info!("invalid temperature: {}.", calib_data.temp); 214 return self 215 .get_previous_calibration_value(channel) 216 .map_err(|_| Error::InvalidTemperature(calib_data.temp)); 217 } 218 let (datastore_exist, previous_calib) = match self.get_previous_calibration_value(channel) { 219 Ok(previous_calib) => (true, previous_calib), 220 Err(e) => { 221 info!("{}, use vpd as previous calibration value", e); 222 (false, self.get_vpd_calibration_value(channel)?) 223 } 224 }; 225 226 let diff = { 227 let calib_rdc_ohm = (self.rdc_to_ohm)(calib_data.rdc); 228 let previous_rdc_ohm = (self.rdc_to_ohm)(previous_calib.rdc); 229 (calib_rdc_ohm - previous_rdc_ohm) / previous_rdc_ohm 230 }; 231 if diff > Self::CALI_ERROR_UPPER_LIMIT { 232 Err(Error::LargeCalibrationDiff(calib_data)) 233 } else if diff < Self::CALI_ERROR_LOWER_LIMIT { 234 if !datastore_exist { 235 Datastore::UseVPD.save(&self.snd_card, channel)?; 236 } 237 Ok(previous_calib) 238 } else { 239 Datastore::DSM { 240 rdc: calib_data.rdc, 241 temp: (self.temp_converter.celsius_to_vpd)(calib_data.temp), 242 } 243 .save(&self.snd_card, channel)?; 244 Ok(calib_data) 245 } 246 } 247 248 /// Gets the calibration values from vpd. 249 /// 250 /// # Results 251 /// 252 /// * `Vec<CalibData>` - the calibration values in vpd. 253 /// 254 /// # Errors 255 /// 256 /// * Failed to read vpd. get_all_vpd_calibration_value(&self) -> Result<Vec<CalibData>>257 pub fn get_all_vpd_calibration_value(&self) -> Result<Vec<CalibData>> { 258 (0..self.num_channels) 259 .map(|ch| self.get_vpd_calibration_value(ch)) 260 .collect::<Result<Vec<_>>>() 261 } 262 263 /// Blocks until the internal speakers are ready. 264 /// 265 /// # Errors 266 /// 267 /// * Failed to wait the internal speakers to be ready. wait_for_speakers_ready(&self) -> Result<()>268 pub fn wait_for_speakers_ready(&self) -> Result<()> { 269 let find_speaker = || -> Result<()> { 270 let cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?; 271 let _node = cras_client 272 .output_nodes() 273 .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER) 274 .ok_or(Error::InternalSpeakerNotFound)?; 275 Ok(()) 276 }; 277 // TODO(b/155007305): Implement cras_client.wait_node_change and use it here. 278 const RETRY: usize = 3; 279 const RETRY_INTERVAL: Duration = Duration::from_millis(500); 280 for _ in 0..RETRY { 281 match find_speaker() { 282 Ok(_) => return Ok(()), 283 Err(e) => error!("retry on finding speaker: {}", e), 284 }; 285 thread::sleep(RETRY_INTERVAL); 286 } 287 Err(Error::InternalSpeakerNotFound) 288 } 289 is_first_boot(&self) -> bool290 fn is_first_boot(&self) -> bool { 291 !run_time::exists(&self.snd_card) 292 } 293 294 // If (Current time - the latest CRAS shutdown time) < cool_down_time, we assume that 295 // the speakers may be overheated. is_speaker_over_heated(&self) -> Result<bool>296 fn is_speaker_over_heated(&self) -> Result<bool> { 297 let last_run = run_time::from_file(&self.snd_card)?; 298 let last_shutdown = shutdown_time::from_file()?; 299 if last_shutdown < last_run { 300 return Err(Error::InvalidShutDownTime); 301 } 302 303 let now = SystemTime::now() 304 .duration_since(UNIX_EPOCH) 305 .map_err(Error::SystemTimeError)?; 306 307 let elapsed = now 308 .checked_sub(last_shutdown) 309 .ok_or(Error::InvalidShutDownTime)?; 310 311 if elapsed < Self::SPEAKER_COOL_DOWN_TIME { 312 return Ok(true); 313 } 314 Ok(false) 315 } 316 get_previous_calibration_value(&self, ch: usize) -> Result<CalibData>317 fn get_previous_calibration_value(&self, ch: usize) -> Result<CalibData> { 318 let sci_calib = Datastore::from_file(&self.snd_card, ch)?; 319 match sci_calib { 320 Datastore::UseVPD => self.get_vpd_calibration_value(ch), 321 Datastore::DSM { rdc, temp } => Ok(CalibData { 322 rdc, 323 temp: (self.temp_converter.vpd_to_celsius)(temp), 324 }), 325 } 326 } 327 get_vpd_calibration_value(&self, channel: usize) -> Result<CalibData>328 fn get_vpd_calibration_value(&self, channel: usize) -> Result<CalibData> { 329 let vpd = VPD::new(channel)?; 330 Ok(CalibData { 331 rdc: vpd.dsm_calib_r0, 332 temp: (self.temp_converter.vpd_to_celsius)(vpd.dsm_calib_temp), 333 }) 334 } 335 } 336