• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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