summaryrefslogtreecommitdiff
path: root/sound_card_init/dsm
diff options
context:
space:
mode:
Diffstat (limited to 'sound_card_init/dsm')
-rw-r--r--sound_card_init/dsm/Cargo.lock229
-rw-r--r--sound_card_init/dsm/Cargo.toml15
-rw-r--r--sound_card_init/dsm/src/datastore.rs72
-rw-r--r--sound_card_init/dsm/src/error.rs128
-rw-r--r--sound_card_init/dsm/src/lib.rs335
-rw-r--r--sound_card_init/dsm/src/utils.rs82
-rw-r--r--sound_card_init/dsm/src/vpd.rs41
-rw-r--r--sound_card_init/dsm/src/zero_player.rs209
8 files changed, 1111 insertions, 0 deletions
diff --git a/sound_card_init/dsm/Cargo.lock b/sound_card_init/dsm/Cargo.lock
new file mode 100644
index 00000000..411c7527
--- /dev/null
+++ b/sound_card_init/dsm/Cargo.lock
@@ -0,0 +1,229 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "alsa-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644d308f5822c2b39fba5a6d850f74c208bf73c61d1d2dfad62505d6960e4977"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "assertions"
+version = "0.1.0"
+
+[[package]]
+name = "audio_streams"
+version = "0.1.0"
+dependencies = [
+ "sync",
+ "sys_util",
+]
+
+[[package]]
+name = "cras-sys"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "data_model",
+]
+
+[[package]]
+name = "cros_alsa"
+version = "0.1.0"
+dependencies = [
+ "alsa-sys",
+ "libc",
+ "remain",
+ "sys_util",
+]
+
+[[package]]
+name = "data_model"
+version = "0.1.0"
+dependencies = [
+ "assertions",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
+
+[[package]]
+name = "libc"
+version = "0.2.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
+
+[[package]]
+name = "libcras"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cras-sys",
+ "data_model",
+ "libc",
+ "sys_util",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
+
+[[package]]
+name = "max98390d"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cros_alsa",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sound_card_util",
+ "sys_util",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+
+[[package]]
+name = "poll_token_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "remain"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99c861227fc40c8da6fdaa3d58144ac84c0537080a43eb1d7d45c28f88dcb888"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35"
+dependencies = [
+ "dtoa",
+ "linked-hash-map",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sound_card_util"
+version = "0.1.0"
+dependencies = [
+ "cros_alsa",
+ "remain",
+ "sys_util",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "sync"
+version = "0.1.0"
+
+[[package]]
+name = "sys_util"
+version = "0.1.0"
+dependencies = [
+ "data_model",
+ "libc",
+ "poll_token_derive",
+ "sync",
+ "syscall_defines",
+ "tempfile",
+]
+
+[[package]]
+name = "syscall_defines"
+version = "0.1.0"
+
+[[package]]
+name = "tempfile"
+version = "3.0.7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/sound_card_init/dsm/Cargo.toml b/sound_card_init/dsm/Cargo.toml
new file mode 100644
index 00000000..280896d2
--- /dev/null
+++ b/sound_card_init/dsm/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "dsm"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+description = "The boot time calibration logic for smart amp"
+
+[dependencies]
+cros_alsa = "*"
+audio_streams = "*"
+libcras = "*"
+remain = "0.2.1"
+serde = { version = "1.0", features = ["derive"]}
+serde_yaml = "0.8.11"
+sys_util = "*" \ No newline at end of file
diff --git a/sound_card_init/dsm/src/datastore.rs b/sound_card_init/dsm/src/datastore.rs
new file mode 100644
index 00000000..f0180cc2
--- /dev/null
+++ b/sound_card_init/dsm/src/datastore.rs
@@ -0,0 +1,72 @@
+// 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.
+use std::fs::{remove_file, File};
+use std::io::{prelude::*, BufReader, BufWriter};
+use std::path::PathBuf;
+
+use serde::{Deserialize, Serialize};
+use sys_util::info;
+
+use crate::error::{Error, Result};
+
+/// `Datastore`, which stores and reads calibration values in yaml format.
+#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
+pub enum Datastore {
+ /// Indicates using values in VPD.
+ UseVPD,
+ DSM {
+ rdc: i32,
+ temp: i32,
+ },
+}
+
+impl Datastore {
+ /// The dir of datastore.
+ pub const DATASTORE_DIR: &'static str = "/var/lib/sound_card_init";
+
+ /// Creates a `Datastore` and initializes its fields from the datastore file.
+ pub fn from_file(snd_card: &str, channel: usize) -> Result<Datastore> {
+ let path = Self::path(snd_card, channel);
+ let reader =
+ BufReader::new(File::open(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?);
+ let datastore: Datastore =
+ serde_yaml::from_reader(reader).map_err(|e| Error::SerdeError(path.to_owned(), e))?;
+ Ok(datastore)
+ }
+
+ /// Saves a `Datastore` to file.
+ pub fn save(&self, snd_card: &str, channel: usize) -> Result<()> {
+ let path = Self::path(snd_card, channel);
+
+ let mut writer = BufWriter::new(
+ File::create(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?,
+ );
+ writer
+ .write(
+ serde_yaml::to_string(self)
+ .map_err(|e| Error::SerdeError(path.to_owned(), e))?
+ .as_bytes(),
+ )
+ .map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+ writer
+ .flush()
+ .map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+ info!("update Datastore {}: {:?}", path.to_string_lossy(), self);
+ Ok(())
+ }
+
+ /// Deletes the datastore file.
+ pub fn delete(snd_card: &str, channel: usize) -> Result<()> {
+ let path = Self::path(snd_card, channel);
+ remove_file(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+ info!("datastore: {:#?} is deleted.", path);
+ Ok(())
+ }
+
+ fn path(snd_card: &str, channel: usize) -> PathBuf {
+ PathBuf::from(Self::DATASTORE_DIR)
+ .join(snd_card)
+ .join(format!("calib_{}", channel))
+ }
+}
diff --git a/sound_card_init/dsm/src/error.rs b/sound_card_init/dsm/src/error.rs
new file mode 100644
index 00000000..4b6e8dc2
--- /dev/null
+++ b/sound_card_init/dsm/src/error.rs
@@ -0,0 +1,128 @@
+// 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.
+use std::any::Any;
+use std::error;
+use std::fmt;
+use std::io;
+use std::num::ParseIntError;
+use std::path::PathBuf;
+use std::sync::PoisonError;
+use std::time;
+
+use remain::sorted;
+
+use crate::CalibData;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[sorted]
+#[derive(Debug)]
+pub enum Error {
+ AlsaCardError(cros_alsa::CardError),
+ AlsaControlError(cros_alsa::ControlError),
+ AlsaControlTLVError(cros_alsa::ControlTLVError),
+ CalibrationTimeout,
+ CrasClientFailed(libcras::Error),
+ DeserializationFailed(String, serde_yaml::Error),
+ DSMParamUpdateFailed(cros_alsa::ControlTLVError),
+ FileIOFailed(PathBuf, io::Error),
+ InternalSpeakerNotFound,
+ InvalidDatastore,
+ InvalidDSMParam,
+ InvalidShutDownTime,
+ InvalidTemperature(f32),
+ LargeCalibrationDiff(CalibData),
+ MissingDSMParam,
+ MutexPoisonError,
+ NewPlayStreamFailed(libcras::BoxError),
+ NextPlaybackBufferFailed(libcras::BoxError),
+ PlaybackFailed(io::Error),
+ SerdeError(PathBuf, serde_yaml::Error),
+ StartPlaybackTimeout,
+ SystemTimeError(time::SystemTimeError),
+ UnsupportedSoundCard(String),
+ VPDParseFailed(String, ParseIntError),
+ WorkerPanics(Box<dyn Any + Send + 'static>),
+ ZeroPlayerIsNotRunning,
+ ZeroPlayerIsRunning,
+}
+
+impl PartialEq for Error {
+ // Implement eq for more Error when needed.
+ fn eq(&self, other: &Error) -> bool {
+ match (self, other) {
+ (Error::InvalidDSMParam, Error::InvalidDSMParam) => true,
+ _ => false,
+ }
+ }
+}
+
+impl From<cros_alsa::CardError> for Error {
+ fn from(err: cros_alsa::CardError) -> Error {
+ Error::AlsaCardError(err)
+ }
+}
+
+impl From<cros_alsa::ControlError> for Error {
+ fn from(err: cros_alsa::ControlError) -> Error {
+ Error::AlsaControlError(err)
+ }
+}
+
+impl From<cros_alsa::ControlTLVError> for Error {
+ fn from(err: cros_alsa::ControlTLVError) -> Error {
+ Error::AlsaControlTLVError(err)
+ }
+}
+
+impl<T> From<PoisonError<T>> for Error {
+ fn from(_: PoisonError<T>) -> Error {
+ Error::MutexPoisonError
+ }
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use Error::*;
+ match self {
+ AlsaCardError(e) => write!(f, "AlsaCardError: {}", e),
+ AlsaControlError(e) => write!(f, "AlsaControlError: {}", e),
+ AlsaControlTLVError(e) => write!(f, "AlsaControlTLVError: {}", e),
+ CalibrationTimeout => write!(f, "calibration is not finished in time"),
+ DSMParamUpdateFailed(e) => write!(f, "failed to update DsmParam, err: {}", e),
+ CrasClientFailed(e) => write!(f, "failed to create cras client: {}", e),
+ DeserializationFailed(file_path, e) => {
+ write!(f, "failed to parse {}: {}", file_path, e)
+ }
+ FileIOFailed(file_path, e) => write!(f, "{:#?}: {}", file_path, e),
+ InvalidShutDownTime => write!(f, "invalid shutdown time"),
+ InternalSpeakerNotFound => write!(f, "internal speaker is not found in cras"),
+ InvalidTemperature(temp) => write!(
+ f,
+ "invalid calibration temperature: {}, and there is no datastore",
+ temp
+ ),
+ InvalidDatastore => write!(f, "invalid datastore format"),
+ InvalidDSMParam => write!(f, "invalid dsm param from kcontrol"),
+ LargeCalibrationDiff(calib) => {
+ write!(f, "calibration difference is too large, calib: {:?}", calib)
+ }
+ MissingDSMParam => write!(f, "missing dsm_param.bin"),
+ MutexPoisonError => write!(f, "mutex is poisoned"),
+ NewPlayStreamFailed(e) => write!(f, "{}", e),
+ NextPlaybackBufferFailed(e) => write!(f, "{}", e),
+ PlaybackFailed(e) => write!(f, "{}", e),
+ SerdeError(file_path, e) => write!(f, "{:?}: {}", file_path, e),
+ StartPlaybackTimeout => write!(f, "playback is not started in time"),
+ SystemTimeError(e) => write!(f, "{}", e),
+ UnsupportedSoundCard(name) => write!(f, "unsupported sound card: {}", name),
+ VPDParseFailed(file_path, e) => write!(f, "failed to parse vpd {}: {}", file_path, e),
+ WorkerPanics(e) => write!(f, "run_play_zero_worker panics: {:#?}", e),
+ ZeroPlayerIsNotRunning => write!(f, "zero player is not running"),
+ ZeroPlayerIsRunning => write!(f, "zero player is running"),
+ }
+ }
+}
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),
+ })
+ }
+}
diff --git a/sound_card_init/dsm/src/utils.rs b/sound_card_init/dsm/src/utils.rs
new file mode 100644
index 00000000..64f6c972
--- /dev/null
+++ b/sound_card_init/dsm/src/utils.rs
@@ -0,0 +1,82 @@
+// 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.
+//! It contains common utils shared within sound_card_init.
+#![deny(missing_docs)]
+
+use std::fs::File;
+use std::io::{prelude::*, BufReader, BufWriter};
+use std::path::PathBuf;
+use std::time::Duration;
+
+use crate::datastore::Datastore;
+use crate::error::{Error, Result};
+
+fn duration_from_file(path: &PathBuf) -> Result<Duration> {
+ let reader =
+ BufReader::new(File::open(&path).map_err(|e| Error::FileIOFailed(path.clone(), e))?);
+ serde_yaml::from_reader(reader).map_err(|e| Error::SerdeError(path.clone(), e))
+}
+
+/// The utils to parse CRAS shutdown time file.
+pub mod shutdown_time {
+ use super::*;
+ // The path of CRAS shutdown time file.
+ const SHUTDOWN_TIME_FILE: &str = "/var/lib/cras/stop";
+
+ /// Reads the unix time from CRAS shutdown time file.
+ pub fn from_file() -> Result<Duration> {
+ duration_from_file(&PathBuf::from(SHUTDOWN_TIME_FILE))
+ }
+}
+
+/// The utils to create and parse sound_card_init run time file.
+pub mod run_time {
+ use std::time::SystemTime;
+
+ use super::*;
+ // The filename of sound_card_init run time file.
+ const RUN_TIME_FILE: &str = "run";
+
+ /// Returns the sound_card_init run time file existence.
+ pub fn exists(snd_card: &str) -> bool {
+ run_time_file(snd_card).exists()
+ }
+
+ /// Reads the unix time from sound_card_init run time file.
+ pub fn from_file(snd_card: &str) -> Result<Duration> {
+ duration_from_file(&run_time_file(snd_card))
+ }
+
+ /// Saves the current unix time to sound_card_init run time file.
+ pub fn now_to_file(snd_card: &str) -> Result<()> {
+ match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
+ Ok(t) => to_file(snd_card, t),
+ Err(e) => Err(Error::SystemTimeError(e)),
+ }
+ }
+
+ /// Saves the unix time to sound_card_init run time file.
+ pub fn to_file(snd_card: &str, duration: Duration) -> Result<()> {
+ let path = run_time_file(snd_card);
+ let mut writer =
+ BufWriter::new(File::create(&path).map_err(|e| Error::FileIOFailed(path.clone(), e))?);
+ writer
+ .write_all(
+ serde_yaml::to_string(&duration)
+ .map_err(|e| Error::SerdeError(path.clone(), e))?
+ .as_bytes(),
+ )
+ .map_err(|e| Error::FileIOFailed(path.clone(), e))?;
+ writer
+ .flush()
+ .map_err(|e| Error::FileIOFailed(path.clone(), e))?;
+ Ok(())
+ }
+
+ fn run_time_file(snd_card: &str) -> PathBuf {
+ PathBuf::from(Datastore::DATASTORE_DIR)
+ .join(snd_card)
+ .join(RUN_TIME_FILE)
+ }
+}
diff --git a/sound_card_init/dsm/src/vpd.rs b/sound_card_init/dsm/src/vpd.rs
new file mode 100644
index 00000000..b00864cc
--- /dev/null
+++ b/sound_card_init/dsm/src/vpd.rs
@@ -0,0 +1,41 @@
+// 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.
+use std::fs::File;
+use std::io::prelude::*;
+use std::io::BufReader;
+use std::path::PathBuf;
+
+use crate::error::{Error, Result};
+
+const VPD_DIR: &str = "/sys/firmware/vpd/ro/vpdfile";
+
+/// `VPD`, which represents the amplifier factory calibration values.
+#[derive(Default, Debug)]
+pub struct VPD {
+ pub dsm_calib_r0: i32,
+ pub dsm_calib_temp: i32,
+}
+
+impl VPD {
+ /// Creates a `VPD` and initializes its fields from VPD_DIR/dsm_calib_r0_{channel}.
+ /// # Arguments
+ ///
+ /// * `channel` - channel number.
+ pub fn new(channel: usize) -> Result<VPD> {
+ let mut vpd: VPD = Default::default();
+ vpd.dsm_calib_r0 = read_vpd_files(&format!("dsm_calib_r0_{}", channel))?;
+ vpd.dsm_calib_temp = read_vpd_files(&format!("dsm_calib_temp_{}", channel))?;
+ Ok(vpd)
+ }
+}
+
+fn read_vpd_files(file: &str) -> Result<i32> {
+ let path = PathBuf::from(VPD_DIR).with_file_name(file);
+ let io_err = |e| Error::FileIOFailed(path.to_owned(), e);
+ let mut reader = BufReader::new(File::open(&path).map_err(io_err)?);
+ let mut line = String::new();
+ reader.read_line(&mut line).map_err(io_err)?;
+ line.parse::<i32>()
+ .map_err(|e| Error::VPDParseFailed(path.to_string_lossy().to_string(), e))
+}
diff --git a/sound_card_init/dsm/src/zero_player.rs b/sound_card_init/dsm/src/zero_player.rs
new file mode 100644
index 00000000..441f7ffa
--- /dev/null
+++ b/sound_card_init/dsm/src/zero_player.rs
@@ -0,0 +1,209 @@
+// 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.
+use std::io::Write;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Condvar, Mutex};
+use std::thread;
+use std::thread::JoinHandle;
+use std::time::Duration;
+
+use audio_streams::SampleFormat;
+use libcras::{CrasClient, CrasNodeType};
+use sys_util::error;
+
+use crate::error::{Error, Result};
+
+/// `ZeroPlayer` provides the functionality to play zeros sample in the background thread.
+#[derive(Default)]
+pub struct ZeroPlayer {
+ thread_info: Option<PlayZeroWorkerInfo>,
+}
+
+impl Drop for ZeroPlayer {
+ fn drop(&mut self) {
+ if self.thread_info.is_some() {
+ if let Err(e) = self.stop() {
+ error!("{}", e);
+ }
+ }
+ }
+}
+
+impl ZeroPlayer {
+ /// It takes about 400 ms to get CRAS_NODE_TYPE_INTERNAL_SPEAKER during the boot time.
+ const TIMEOUT: Duration = Duration::from_millis(1000);
+
+ /// Returns whether the ZeroPlayer is running.
+ pub fn running(&self) -> bool {
+ self.thread_info.is_some()
+ }
+
+ /// Starts to play zeros for at most `max_playback_time`.
+ /// This function blocks and returns until playback has started for `min_playback_time`.
+ /// This function must be called when self.running() returns false.
+ ///
+ /// # Arguments
+ ///
+ /// * `min_playback_time` - It blocks and returns until playback has started for
+ /// `min_playback_time`.
+ ///
+ /// # Errors
+ ///
+ /// * If it's called when the `ZeroPlayer` is already running.
+ /// * Failed to find internal speakers.
+ /// * Failed to start the background thread.
+ pub fn start(&mut self, min_playback_time: Duration) -> Result<()> {
+ if self.running() {
+ return Err(Error::ZeroPlayerIsRunning);
+ }
+ self.thread_info = Some(PlayZeroWorkerInfo::new(min_playback_time));
+ if let Some(thread_info) = &mut self.thread_info {
+ // Block until playback of zeros has started for min_playback_time or timeout.
+ let (lock, cvar) = &*(thread_info.ready);
+ let result = cvar.wait_timeout_while(
+ lock.lock()?,
+ min_playback_time + ZeroPlayer::TIMEOUT,
+ |&mut is_ready| !is_ready,
+ )?;
+ if result.1.timed_out() {
+ return Err(Error::StartPlaybackTimeout);
+ }
+ }
+ Ok(())
+ }
+
+ /// Stops playing zeros in the background thread.
+ /// This function must be called when self.running() returns true.
+ ///
+ /// # Errors
+ ///
+ /// * If it's called again when the `ZeroPlayer` is not running.
+ /// * Failed to play zeros to internal speakers via CRAS client.
+ /// * Failed to join the background thread.
+ pub fn stop(&mut self) -> Result<()> {
+ match self.thread_info.take() {
+ Some(mut thread_info) => Ok(thread_info.destroy()?),
+ None => Err(Error::ZeroPlayerIsNotRunning),
+ }
+ }
+}
+
+// Audio thread book-keeping data
+struct PlayZeroWorkerInfo {
+ thread: Option<JoinHandle<Result<()>>>,
+ // Uses `thread_run` to notify the background thread to stop.
+ thread_run: Arc<AtomicBool>,
+ // The background thread uses `ready` to notify the main thread that playback
+ // of zeros has started for min_playback_time.
+ ready: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl Drop for PlayZeroWorkerInfo {
+ fn drop(&mut self) {
+ if let Err(e) = self.destroy() {
+ error!("{}", e);
+ }
+ }
+}
+
+impl PlayZeroWorkerInfo {
+ // Spawns the PlayZeroWorker.
+ fn new(min_playback_time: Duration) -> Self {
+ let thread_run = Arc::new(AtomicBool::new(false));
+ let ready = Arc::new((Mutex::new(false), Condvar::new()));
+ let mut worker = PlayZeroWorker::new(min_playback_time, thread_run.clone(), ready.clone());
+ Self {
+ thread: Some(thread::spawn(move || -> Result<()> {
+ worker.run()?;
+ Ok(())
+ })),
+ thread_run,
+ ready,
+ }
+ }
+
+ // Joins the PlayZeroWorker.
+ fn destroy(&mut self) -> Result<()> {
+ self.thread_run.store(false, Ordering::Relaxed);
+ if let Some(handle) = self.thread.take() {
+ let res = handle.join().map_err(Error::WorkerPanics)?;
+ return match res {
+ Err(e) => Err(e),
+ Ok(_) => Ok(()),
+ };
+ }
+ Ok(())
+ }
+}
+
+struct PlayZeroWorker {
+ min_playback_time: Duration,
+ // Uses `thread_run` to notify the background thread to stop.
+ thread_run: Arc<AtomicBool>,
+ // The background thread uses `ready` to notify the main thread that playback
+ // of zeros has started for min_playback_time.
+ ready: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl PlayZeroWorker {
+ const FRAMES_PER_BUFFER: usize = 256;
+ const FRAME_RATE: u32 = 48000;
+ const NUM_CHANNELS: usize = 2;
+ const FORMAT: SampleFormat = SampleFormat::S16LE;
+
+ fn new(
+ min_playback_time: Duration,
+ thread_run: Arc<AtomicBool>,
+ ready: Arc<(Mutex<bool>, Condvar)>,
+ ) -> Self {
+ Self {
+ min_playback_time,
+ thread_run,
+ ready,
+ }
+ }
+
+ fn run(&mut self) -> Result<()> {
+ let mut cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?;
+ // TODO(b/155007305): Implement cras_client.wait_node_change and use it here.
+ let node = cras_client
+ .output_nodes()
+ .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER)
+ .ok_or(Error::InternalSpeakerNotFound)?;
+ let local_buffer =
+ vec![0u8; Self::FRAMES_PER_BUFFER * Self::NUM_CHANNELS * Self::FORMAT.sample_bytes()];
+ let min_playback_iterations = (Self::FRAME_RATE
+ * self.min_playback_time.as_millis() as u32)
+ / Self::FRAMES_PER_BUFFER as u32
+ / 1000;
+ let (_control, mut stream) = cras_client
+ .new_pinned_playback_stream(
+ node.iodev_index,
+ Self::NUM_CHANNELS,
+ Self::FORMAT,
+ Self::FRAME_RATE,
+ Self::FRAMES_PER_BUFFER,
+ )
+ .map_err(|e| Error::NewPlayStreamFailed(e))?;
+
+ let mut iter = 0;
+ self.thread_run.store(true, Ordering::Relaxed);
+ while self.thread_run.load(Ordering::Relaxed) {
+ let mut buffer = stream
+ .next_playback_buffer()
+ .map_err(|e| Error::NextPlaybackBufferFailed(e))?;
+ let _write_frames = buffer.write(&local_buffer).map_err(Error::PlaybackFailed)?;
+
+ // Notifies the main thread that playback of zeros has started for min_playback_time.
+ if iter == min_playback_iterations {
+ let (lock, cvar) = &*self.ready;
+ let mut is_ready = lock.lock()?;
+ *is_ready = true;
+ cvar.notify_one();
+ }
+ iter += 1;
+ }
+ Ok(())
+ }
+}