summaryrefslogtreecommitdiff
path: root/sound_card_init/dsm/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'sound_card_init/dsm/src/lib.rs')
-rw-r--r--sound_card_init/dsm/src/lib.rs335
1 files changed, 335 insertions, 0 deletions
diff --git a/sound_card_init/dsm/src/lib.rs b/sound_card_init/dsm/src/lib.rs
new file mode 100644
index 00000000..0b3ec64c
--- /dev/null
+++ b/sound_card_init/dsm/src/lib.rs
@@ -0,0 +1,335 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! `dsm` crate implements the required initialization workflows for smart amps.
+
+mod datastore;
+mod error;
+pub mod utils;
+mod vpd;
+mod zero_player;
+
+use std::{
+ thread,
+ time::{Duration, SystemTime, UNIX_EPOCH},
+};
+
+use libcras::{CrasClient, CrasNodeType};
+use sys_util::{error, info};
+
+use crate::datastore::Datastore;
+pub use crate::error::{Error, Result};
+use crate::utils::{run_time, shutdown_time};
+use crate::vpd::VPD;
+pub use crate::zero_player::ZeroPlayer;
+
+#[derive(Debug, Clone, Copy)]
+/// `CalibData` represents the calibration data.
+pub struct CalibData {
+ /// The DC resistance of the speaker is DSM unit.
+ pub rdc: i32,
+ /// The ambient temperature in celsius unit at which the rdc is measured.
+ pub temp: f32,
+}
+
+/// `TempConverter` converts the temperature value between celsius and unit in VPD::dsm_calib_temp.
+pub struct TempConverter {
+ vpd_to_celsius: fn(i32) -> f32,
+ celsius_to_vpd: fn(f32) -> i32,
+}
+
+impl Default for TempConverter {
+ fn default() -> Self {
+ let vpd_to_celsius = |x: i32| x as f32;
+ let celsius_to_vpd = |x: f32| x.round() as i32;
+ Self {
+ vpd_to_celsius,
+ celsius_to_vpd,
+ }
+ }
+}
+
+impl TempConverter {
+ /// Creates a `TempConverter`
+ ///
+ /// # Arguments
+ ///
+ /// * `vpd_to_celsius` - function to convert VPD::dsm_calib_temp to celsius unit`
+ /// * `celsius_to_vpd` - function to convert celsius unit to VPD::dsm_calib_temp`
+ /// # Results
+ ///
+ /// * `TempConverter` - it converts the temperature value between celsius and unit in VPD::dsm_calib_temp.
+ pub fn new(vpd_to_celsius: fn(i32) -> f32, celsius_to_vpd: fn(f32) -> i32) -> Self {
+ Self {
+ vpd_to_celsius,
+ celsius_to_vpd,
+ }
+ }
+}
+
+/// `SpeakerStatus` are the possible return results of
+/// DSM::check_speaker_over_heated_workflow.
+pub enum SpeakerStatus {
+ ///`SpeakerStatus::Cold` means the speakers are not overheated and the Amp can
+ /// trigger the boot time calibration.
+ Cold,
+ /// `SpeakerStatus::Hot(Vec<CalibData>)` means the speakers may be too hot for calibration.
+ /// The boot time calibration should be skipped and the Amp should use the previous
+ /// calibration values returned by the enum.
+ Hot(Vec<CalibData>),
+}
+
+/// `DSM`, which implements the required initialization workflows for smart amps.
+pub struct DSM {
+ snd_card: String,
+ num_channels: usize,
+ temp_converter: TempConverter,
+ rdc_to_ohm: fn(i32) -> f32,
+ temp_upper_limit: f32,
+ temp_lower_limit: f32,
+}
+
+impl DSM {
+ const SPEAKER_COOL_DOWN_TIME: Duration = Duration::from_secs(180);
+ const CALI_ERROR_UPPER_LIMIT: f32 = 0.3;
+ const CALI_ERROR_LOWER_LIMIT: f32 = 0.03;
+
+ /// Creates a `DSM`
+ ///
+ /// # Arguments
+ ///
+ /// * `snd_card` - `sound card name`.
+ /// * `num_channels` - `number of channels`.
+ /// * `rdc_to_ohm` - `fn(rdc: i32) -> f32 to convert the CalibData::rdc to ohm unit`.
+ /// * `temp_upper_limit` - the high limit of the valid ambient temperature in dsm unit.
+ /// * `temp_lower_limit` - the low limit of the valid ambient temperature in dsm unit.
+ ///
+ /// # Results
+ ///
+ /// * `DSM` - It implements the required initialization workflows for smart amps.
+ pub fn new(
+ snd_card: &str,
+ num_channels: usize,
+ rdc_to_ohm: fn(i32) -> f32,
+ temp_upper_limit: f32,
+ temp_lower_limit: f32,
+ ) -> Self {
+ Self {
+ snd_card: snd_card.to_owned(),
+ num_channels,
+ rdc_to_ohm,
+ temp_converter: TempConverter::default(),
+ temp_upper_limit,
+ temp_lower_limit,
+ }
+ }
+
+ /// Sets self.temp_converter to the given temp_converter.
+ ///
+ /// # Arguments
+ ///
+ /// * `temp_converter` - the convert function to use.
+ pub fn set_temp_converter(&mut self, temp_converter: TempConverter) {
+ self.temp_converter = temp_converter;
+ }
+
+ /// Checks whether the speakers are overheated or not according to the previous shutdown time.
+ /// The boot time calibration should be skipped when the speakers may be too hot
+ /// and the Amp should use the previous calibration value returned by the
+ /// SpeakerStatus::Hot(Vec<CalibData>).
+ ///
+ /// # Results
+ ///
+ /// * `SpeakerStatus::Cold` - which means the speakers are not overheated and the Amp can
+ /// trigger the boot time calibration.
+ /// * `SpeakerStatus::Hot(Vec<CalibData>)` - when the speakers may be too hot. The boot
+ /// time calibration should be skipped and the Amp should use the previous calibration values
+ /// returned by the enum.
+ ///
+ /// # Errors
+ ///
+ /// * The speakers are overheated and there are no previous calibration values stored.
+ /// * Cannot determine whether the speakers are overheated as previous shutdown time record is
+ /// invalid.
+ pub fn check_speaker_over_heated_workflow(&self) -> Result<SpeakerStatus> {
+ if self.is_first_boot() {
+ return Ok(SpeakerStatus::Cold);
+ }
+ match self.is_speaker_over_heated() {
+ Ok(overheated) => {
+ if overheated {
+ let calib: Vec<CalibData> = (0..self.num_channels)
+ .map(|ch| -> Result<CalibData> { self.get_previous_calibration_value(ch) })
+ .collect::<Result<Vec<CalibData>>>()?;
+ info!("the speakers are hot, the boot time calibration should be skipped");
+ return Ok(SpeakerStatus::Hot(calib));
+ }
+ Ok(SpeakerStatus::Cold)
+ }
+ Err(err) => {
+ // We cannot assume the speakers are not replaced or not overheated
+ // when the shutdown time file is invalid; therefore we can not use the datastore
+ // value anymore and we can not trigger boot time calibration.
+ for ch in 0..self.num_channels {
+ if let Err(e) = Datastore::delete(&self.snd_card, ch) {
+ error!("error delete datastore: {}", e);
+ }
+ }
+ Err(err)
+ }
+ }
+ }
+
+ /// Decides a good calibration value and updates the stored value according to the following
+ /// logic:
+ /// * Returns the previous value if the ambient temperature is not within a valid range.
+ /// * Returns Error::LargeCalibrationDiff if rdc difference is larger than
+ /// `CALI_ERROR_UPPER_LIMIT`.
+ /// * Returns the previous value if the rdc difference is smaller than `CALI_ERROR_LOWER_LIMIT`.
+ /// * Returns the boot time calibration value and updates the datastore value if the rdc.
+ /// difference is between `CALI_ERROR_UPPER_LIMIT` and `CALI_ERROR_LOWER_LIMIT`.
+ ///
+ /// # Arguments
+ ///
+ /// * `card` - `&Card`.
+ /// * `channel` - `channel number`.
+ /// * `calib_data` - `boot time calibrated data`.
+ ///
+ /// # Results
+ ///
+ /// * `CalibData` - the calibration data to be applied according to the deciding logic.
+ ///
+ /// # Errors
+ ///
+ /// * VPD does not exist.
+ /// * rdc difference is larger than `CALI_ERROR_UPPER_LIMIT`.
+ /// * Failed to update Datastore.
+ pub fn decide_calibration_value_workflow(
+ &self,
+ channel: usize,
+ calib_data: CalibData,
+ ) -> Result<CalibData> {
+ if calib_data.temp < self.temp_lower_limit || calib_data.temp > self.temp_upper_limit {
+ info!("invalid temperature: {}.", calib_data.temp);
+ return self
+ .get_previous_calibration_value(channel)
+ .map_err(|_| Error::InvalidTemperature(calib_data.temp));
+ }
+ let (datastore_exist, previous_calib) = match self.get_previous_calibration_value(channel) {
+ Ok(previous_calib) => (true, previous_calib),
+ Err(e) => {
+ info!("{}, use vpd as previous calibration value", e);
+ (false, self.get_vpd_calibration_value(channel)?)
+ }
+ };
+
+ let diff = {
+ let calib_rdc_ohm = (self.rdc_to_ohm)(calib_data.rdc);
+ let previous_rdc_ohm = (self.rdc_to_ohm)(previous_calib.rdc);
+ (calib_rdc_ohm - previous_rdc_ohm) / previous_rdc_ohm
+ };
+ if diff > Self::CALI_ERROR_UPPER_LIMIT {
+ Err(Error::LargeCalibrationDiff(calib_data))
+ } else if diff < Self::CALI_ERROR_LOWER_LIMIT {
+ if !datastore_exist {
+ Datastore::UseVPD.save(&self.snd_card, channel)?;
+ }
+ Ok(previous_calib)
+ } else {
+ Datastore::DSM {
+ rdc: calib_data.rdc,
+ temp: (self.temp_converter.celsius_to_vpd)(calib_data.temp),
+ }
+ .save(&self.snd_card, channel)?;
+ Ok(calib_data)
+ }
+ }
+
+ /// Gets the calibration values from vpd.
+ ///
+ /// # Results
+ ///
+ /// * `Vec<CalibData>` - the calibration values in vpd.
+ ///
+ /// # Errors
+ ///
+ /// * Failed to read vpd.
+ pub fn get_all_vpd_calibration_value(&self) -> Result<Vec<CalibData>> {
+ (0..self.num_channels)
+ .map(|ch| self.get_vpd_calibration_value(ch))
+ .collect::<Result<Vec<_>>>()
+ }
+
+ /// Blocks until the internal speakers are ready.
+ ///
+ /// # Errors
+ ///
+ /// * Failed to wait the internal speakers to be ready.
+ pub fn wait_for_speakers_ready(&self) -> Result<()> {
+ let find_speaker = || -> Result<()> {
+ let cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?;
+ let _node = cras_client
+ .output_nodes()
+ .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER)
+ .ok_or(Error::InternalSpeakerNotFound)?;
+ Ok(())
+ };
+ // TODO(b/155007305): Implement cras_client.wait_node_change and use it here.
+ const RETRY: usize = 3;
+ const RETRY_INTERVAL: Duration = Duration::from_millis(500);
+ for _ in 0..RETRY {
+ match find_speaker() {
+ Ok(_) => return Ok(()),
+ Err(e) => error!("retry on finding speaker: {}", e),
+ };
+ thread::sleep(RETRY_INTERVAL);
+ }
+ Err(Error::InternalSpeakerNotFound)
+ }
+
+ fn is_first_boot(&self) -> bool {
+ !run_time::exists(&self.snd_card)
+ }
+
+ // If (Current time - the latest CRAS shutdown time) < cool_down_time, we assume that
+ // the speakers may be overheated.
+ fn is_speaker_over_heated(&self) -> Result<bool> {
+ let last_run = run_time::from_file(&self.snd_card)?;
+ let last_shutdown = shutdown_time::from_file()?;
+ if last_shutdown < last_run {
+ return Err(Error::InvalidShutDownTime);
+ }
+
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .map_err(Error::SystemTimeError)?;
+
+ let elapsed = now
+ .checked_sub(last_shutdown)
+ .ok_or(Error::InvalidShutDownTime)?;
+
+ if elapsed < Self::SPEAKER_COOL_DOWN_TIME {
+ return Ok(true);
+ }
+ Ok(false)
+ }
+
+ fn get_previous_calibration_value(&self, ch: usize) -> Result<CalibData> {
+ let sci_calib = Datastore::from_file(&self.snd_card, ch)?;
+ match sci_calib {
+ Datastore::UseVPD => self.get_vpd_calibration_value(ch),
+ Datastore::DSM { rdc, temp } => Ok(CalibData {
+ rdc,
+ temp: (self.temp_converter.vpd_to_celsius)(temp),
+ }),
+ }
+ }
+
+ fn get_vpd_calibration_value(&self, channel: usize) -> Result<CalibData> {
+ let vpd = VPD::new(channel)?;
+ Ok(CalibData {
+ rdc: vpd.dsm_calib_r0,
+ temp: (self.temp_converter.vpd_to_celsius)(vpd.dsm_calib_temp),
+ })
+ }
+}