sound_card_init: initial commit

1. /lib/udev/rules.d/99-sound_card_init.rules starts sound_card_init upstart job
   and provides the sound_card_id.

2.sound_card_init reads /etc/sound_card_init/<sound_card_id>.yaml to
  perform per sound card initialization.

BUG=b:149437381
TEST=deploy to DUT and then reboot to see whether it reads config correctly

Change-Id: I740b98cad785d60e19dafba9a5bc8e5293fb07b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/2086422
Reviewed-by: Chih-Yang Hsia <paulhsia@chromium.org>
Tested-by: Judy Hsiao <judyhsiao@chromium.org>
Commit-Queue: Judy Hsiao <judyhsiao@chromium.org>
diff --git a/sound_card_init/.gitignore b/sound_card_init/.gitignore
new file mode 100644
index 0000000..f2e972d
--- /dev/null
+++ b/sound_card_init/.gitignore
@@ -0,0 +1,6 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
diff --git a/sound_card_init/99-sound_card_init.rules b/sound_card_init/99-sound_card_init.rules
new file mode 100644
index 0000000..82a3aec
--- /dev/null
+++ b/sound_card_init/99-sound_card_init.rules
@@ -0,0 +1,13 @@
+# 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.
+
+SUBSYSTEM!="sound", GOTO="sci_end"
+ACTION!="change", GOTO="sci_end"
+KERNEL!="card*", GOTO="sci_end"
+
+GOTO="sci_action"
+
+LABEL="sci_action"
+RUN+="/sbin/initctl start sound_card_init SOUND_CARD_ID=$attr{id}"
+LABEL="sci_end"
diff --git a/sound_card_init/Cargo.lock b/sound_card_init/Cargo.lock
new file mode 100644
index 0000000..6a412f3
--- /dev/null
+++ b/sound_card_init/Cargo.lock
@@ -0,0 +1,127 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "assertions"
+version = "0.1.0"
+
+[[package]]
+name = "data_model"
+version = "0.1.0"
+dependencies = [
+ "assertions 0.1.0",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "poll_token_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "remain"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sound_card_init"
+version = "0.1.0"
+dependencies = [
+ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sys_util 0.1.0",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sync"
+version = "0.1.0"
+
+[[package]]
+name = "sys_util"
+version = "0.1.0"
+dependencies = [
+ "data_model 0.1.0",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "poll_token_derive 0.1.0",
+ "sync 0.1.0",
+ "syscall_defines 0.1.0",
+ "tempfile 3.0.7",
+]
+
+[[package]]
+name = "syscall_defines"
+version = "0.1.0"
+
+[[package]]
+name = "tempfile"
+version = "3.0.7"
+dependencies = [
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
+"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
+"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
+"checksum remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99c861227fc40c8da6fdaa3d58144ac84c0537080a43eb1d7d45c28f88dcb888"
+"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
+"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
+"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
diff --git a/sound_card_init/Cargo.toml b/sound_card_init/Cargo.toml
new file mode 100644
index 0000000..3987b25
--- /dev/null
+++ b/sound_card_init/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "sound_card_init"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+description = "Sound Card Initializer"
+
+[dependencies]
+getopts = "0.2"
+remain = "0.2.1"
+sys_util = "*"
+
+[patch.crates-io]
+sys_util = { path = "../../../platform/crosvm/sys_util" } # ignored by ebuild
diff --git a/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy b/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy
new file mode 100644
index 0000000..91e6cf1
--- /dev/null
+++ b/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy
@@ -0,0 +1,76 @@
+# 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.
+
+close: 1
+write: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_wait: 1
+recvfrom: 1
+lseek: 1
+read: 1
+openat: 1
+mmap: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
+fstat: 1
+ioctl: arg1 == 0x540f || arg1 == 0x81785501 || arg1 == 0x5413 || arg1 == 0x80045500 || arg1 == 0xc1105511 || arg1 == 0xc4c85512 || arg1 == 0x5401 || arg1 == 0xc4c85513
+rt_sigaction: 1
+mprotect: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
+stat: 1
+fcntl: 1
+prlimit64: 1
+munmap: 1
+setresuid: 1
+getpid: 1
+setresgid: 1
+rt_sigprocmask: 1
+recvmsg: 1
+access: 1
+getuid: 1
+futex: 1
+brk: 1
+getdents: 1
+sigaltstack: 1
+umask: 1
+socket: arg0 == 0x1 || arg0 == 0x10
+sendto: 1
+setgroups: 1
+set_robust_list: 1
+connect: 1
+geteuid: 1
+prctl: arg0 == 0x3 || arg0 == 0x4
+execve: 1
+arch_prctl: 1
+set_tid_address: 1
+getgroups: 1
+getresuid: 1
+getresgid: 1
+pipe2: 1
+clone: 1
+dup: 1
+ppoll: 1
+sched_getaffinity: 1
+exit_group: 1
+getppid: 1
+getpgid: 1
+getsid: 1
+getgid: 1
+getegid: 1
+getcwd: 1
+uname: 1
+bind: 1
+getsockname: 1
+setuid: 1
+setgid: 1
+getpriority: 1
+setpriority: 1
+getpgrp: 1
+dup2: 1
+getrandom: 1
+socketpair: 1
+sendmsg: 1
+madvise: 1
+exit: 1
+rt_sigreturn: 1
+wait4: 1
+restart_syscall: 1
diff --git a/sound_card_init/sound_card_init.conf b/sound_card_init/sound_card_init.conf
new file mode 100644
index 0000000..0f504fa
--- /dev/null
+++ b/sound_card_init/sound_card_init.conf
@@ -0,0 +1,73 @@
+# 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.
+
+# Installed by sound_card_init package.
+# sound_card_init upstart job.
+# sound_card_init is started by /lib/udev/rules.d/99-sound_card_init.rules
+
+description     "Chrome OS sound card initializer"
+author          "chromium-os-dev@chromium.org"
+
+# sound_card_init is a short-running process, but we don't start it as
+# a task job, because sound_card_init needs the sound card to be ready in
+# CRAS therefore we do not want to block the udev rule processing.
+
+# Make the task killable, because if it has a leak it's better to
+# restart it than to OOM-panic.
+oom score 0
+
+# SOUND_CARD_ID is provided by /lib/udev/rules.d/99-sound_card_init.rules.
+import SOUND_CARD_ID
+
+pre-start script
+  if ! echo "${SOUND_CARD_ID}" | grep -Eq "^[a-zA-Z0-9]+$"; then
+    logger -t "${UPSTART_JOB}" \
+      "Invalid SOUND_CARD_ID supplied"
+    exit 1
+  else
+    mkdir -p /var/lib/sound_card_init/"${SOUND_CARD_ID}"
+    chown sound_card_init:sound_card_init /var/lib/sound_card_init/"${SOUND_CARD_ID}"
+  fi
+end script
+
+# Here (in order) are a list of the args added:
+# --uts: Create and enter new UTS namespace (hostname/NIS domain name).
+# -e: doesn't need network access.
+# -l: process doesn't use SysV shared memory or IPC.
+# -N: doesn't need to modify control groups settings.
+# -v: run inside a new VFS namespace.
+# -p -r: process doesn't need to access other processes in the system.
+# -n: process doesn't need new privileges.
+# -P: set /mnt/empty as the root fs.
+# -b: bind /
+# -k: Get a writeable and empty /run tmpfs path.
+# -b: need /run/cras to connect cras.
+# -b: /run/systemd/journal: needed for syslog.
+# -b: need /dev to send ioctls to the system's block devices.
+# -k: empty /sys tmpfs path.
+# -b: need /sys/firmware/vpd/ro/ access to read the default calibration value in vpd.
+# -k: get a writeable and empty /var tmpfs path.
+# -b: need /var/lib/sound_card_init/$SOUND_CARD_ID writable
+#     access for datastore update.
+exec minijail0 \
+    --uts \
+    -e \
+    -l \
+    -N \
+    -v \
+    -p -r \
+    -n \
+    -P /mnt/empty \
+    -b / \
+    -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+    -b /run/cras \
+    -b /run/systemd/journal \
+    -b /dev \
+    -k 'tmpfs,/sys,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+    -b /sys/firmware/vpd/ro/ \
+    -k 'tmpfs,/var,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+    -b /var/lib/sound_card_init/"${SOUND_CARD_ID}"/,,1 \
+    -u sound_card_init -g sound_card_init -G \
+    -S /usr/share/policy/sound_card_init-seccomp.policy \
+    /usr/bin/sound_card_init "--id=${SOUND_CARD_ID}"
diff --git a/sound_card_init/src/main.rs b/sound_card_init/src/main.rs
new file mode 100644
index 0000000..5bd307d
--- /dev/null
+++ b/sound_card_init/src/main.rs
@@ -0,0 +1,115 @@
+// 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.
+//!  `sound_card_init` is an user space binary to perform sound card initialization during boot time.
+//!
+//!
+//!  # Arguments
+//!
+//!  * `sound_card_id` - The sound card name, ex: sofcmlmax98390d.
+//!
+//!  Given the `sound_card_id`, this binary parses the CONF_DIR/<sound_card_id>.yaml to perform per sound card initialization.
+//!  The upstart job of `sound_card_init` is started by the udev event specified in /lib/udev/rules.d/99-sound_card_init.rules.
+#![deny(missing_docs)]
+use std::env;
+use std::error;
+use std::fmt;
+use std::fs;
+use std::io;
+use std::path::PathBuf;
+use std::process;
+use std::string::String;
+
+use getopts::Options;
+use remain::sorted;
+use sys_util::{error, info, syslog};
+
+type Result<T> = std::result::Result<T, Error>;
+const CONF_DIR: &str = "/etc/sound_card_init";
+
+#[derive(Default)]
+struct Args {
+    pub sound_card_id: String,
+}
+
+#[sorted]
+#[derive(Debug)]
+enum Error {
+    MissingOption(String),
+    OpenConfigFailed(String, io::Error),
+    ParseArgsFailed(getopts::Fail),
+    UnsupportedSoundCard(String),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            MissingOption(option) => write!(f, "missing required option: {}", option),
+            OpenConfigFailed(file, e) => write!(f, "failed to open file {}: {}", file, e),
+            ParseArgsFailed(e) => write!(f, "parse_args failed: {}", e),
+            UnsupportedSoundCard(name) => write!(f, "unsupported sound card: {}", name),
+        }
+    }
+}
+
+fn print_usage(opts: &Options) {
+    let brief = "Usage: sound_card_init [options]".to_owned();
+    print!("{}", opts.usage(&brief));
+}
+
+fn parse_args() -> Result<Args> {
+    let mut opts = Options::new();
+    opts.optopt("", "id", "sound card id", "ID");
+    opts.optflag("h", "help", "print help menu");
+    let matches = opts
+        .parse(&env::args().collect::<Vec<_>>()[1..])
+        .map_err(|e| {
+            print_usage(&opts);
+            Error::ParseArgsFailed(e)
+        })?;
+
+    if matches.opt_present("h") {
+        print_usage(&opts);
+        process::exit(0);
+    }
+
+    let sound_card_id = matches
+        .opt_str("id")
+        .ok_or_else(|| Error::MissingOption("id".to_owned()))
+        .map_err(|e| {
+            print_usage(&opts);
+            e
+        })?;
+
+    Ok(Args { sound_card_id })
+}
+
+fn get_config(args: &Args) -> Result<String> {
+    let config_path = PathBuf::from(CONF_DIR)
+        .join(&args.sound_card_id)
+        .with_extension("yaml");
+
+    fs::read_to_string(&config_path)
+        .map_err(|e| Error::OpenConfigFailed(config_path.to_string_lossy().to_string(), e))
+}
+
+/// Parses the CONF_DIR/<sound_card_id>.yaml and starts sound card initialization.
+fn sound_card_init() -> std::result::Result<(), Box<dyn error::Error>> {
+    let args = parse_args()?;
+    info!("sound_card_id: {}", args.sound_card_id);
+    let _conf = get_config(&args)?;
+
+    match args.sound_card_id.as_str() {
+        _ => return Err(Error::UnsupportedSoundCard(args.sound_card_id).into()),
+    };
+}
+
+fn main() {
+    syslog::init().expect("failed to initialize syslog");
+    if let Err(e) = sound_card_init() {
+        error!("sound_card_init: {}", e);
+    }
+}