Port of ac97 from crosvm with pulseaudio backend.
This commit is contained in:
parent
13efc4fa11
commit
cded52b7c9
@ -16,4 +16,5 @@ thiserror = "1.0"
|
||||
vmm-sys-util = "0.11.1"
|
||||
kvm-ioctls = "0.12.0"
|
||||
kvm-bindings = "0.6.0"
|
||||
pulse = { version = "2.27.1", package = "libpulse-binding" }
|
||||
libcitadel = { git = "https://github.com/brl/citadel-tools", rev="44d5ce660f1f5cf8a3ad1060b143926a99be5148" }
|
||||
|
113
src/audio/mod.rs
Normal file
113
src/audio/mod.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use std::{error, fmt};
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod shm_streams;
|
||||
pub mod pulse;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum SampleFormat {
|
||||
U8,
|
||||
S16LE,
|
||||
S24LE,
|
||||
S32LE,
|
||||
}
|
||||
|
||||
impl SampleFormat {
|
||||
pub fn sample_bytes(self) -> usize {
|
||||
use SampleFormat::*;
|
||||
match self {
|
||||
U8 => 1,
|
||||
S16LE => 2,
|
||||
S24LE => 4, // Not a typo, S24_LE samples are stored in 4 byte chunks.
|
||||
S32LE => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SampleFormat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use SampleFormat::*;
|
||||
match self {
|
||||
U8 => write!(f, "Unsigned 8 bit"),
|
||||
S16LE => write!(f, "Signed 16 bit Little Endian"),
|
||||
S24LE => write!(f, "Signed 24 bit Little Endian"),
|
||||
S32LE => write!(f, "Signed 32 bit Little Endian"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SampleFormat {
|
||||
type Err = SampleFormatError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"U8" => Ok(SampleFormat::U8),
|
||||
"S16_LE" => Ok(SampleFormat::S16LE),
|
||||
"S24_LE" => Ok(SampleFormat::S24LE),
|
||||
"S32_LE" => Ok(SampleFormat::S32LE),
|
||||
_ => Err(SampleFormatError::InvalidSampleFormat),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that are possible from a `SampleFormat`.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SampleFormatError {
|
||||
#[error("Must be in [U8, S16_LE, S24_LE, S32_LE]")]
|
||||
InvalidSampleFormat,
|
||||
}
|
||||
/// Valid directions of an audio stream.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum StreamDirection {
|
||||
Playback,
|
||||
Capture,
|
||||
}
|
||||
|
||||
/// Valid effects for an audio stream.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum StreamEffect {
|
||||
NoEffect,
|
||||
EchoCancellation,
|
||||
}
|
||||
|
||||
impl Default for StreamEffect {
|
||||
fn default() -> Self {
|
||||
StreamEffect::NoEffect
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can pass across threads.
|
||||
pub type BoxError = Box<dyn error::Error + Send + Sync>;
|
||||
|
||||
/// Errors that are possible from a `StreamEffect`.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StreamEffectError {
|
||||
#[error("Must be in [EchoCancellation, aec]")]
|
||||
InvalidEffect,
|
||||
}
|
||||
|
||||
impl FromStr for StreamEffect {
|
||||
type Err = StreamEffectError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"EchoCancellation" | "aec" => Ok(StreamEffect::EchoCancellation),
|
||||
_ => Err(StreamEffectError::InvalidEffect),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `StreamControl` provides a way to set the volume and mute states of a stream. `StreamControl`
|
||||
/// is separate from the stream so it can be owned by a different thread if needed.
|
||||
pub trait StreamControl: Send + Sync {
|
||||
fn set_volume(&mut self, _scaler: f64) {}
|
||||
fn set_mute(&mut self, _mute: bool) {}
|
||||
}
|
||||
|
||||
/// `BufferCommit` is a cleanup funcion that must be called before dropping the buffer,
|
||||
/// allowing arbitrary code to be run after the buffer is filled or read by the user.
|
||||
pub trait BufferCommit {
|
||||
/// `write_playback_buffer` or `read_capture_buffer` would trigger this automatically. `nframes`
|
||||
/// indicates the number of audio frames that were read or written to the device.
|
||||
fn commit(&mut self, nframes: usize);
|
||||
}
|
66
src/audio/pulse/client.rs
Normal file
66
src/audio/pulse/client.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use pulse::sample::{Format, Spec};
|
||||
use crate::audio::pulse::context::PulseContext;
|
||||
use crate::audio::pulse::message::PulseMessageChannel;
|
||||
use crate::audio::pulse::Result;
|
||||
use crate::audio::{SampleFormat, StreamDirection};
|
||||
use crate::audio::shm_streams::{GenericResult, NullShmStream, ShmStream, ShmStreamSource};
|
||||
use crate::memory::GuestRam;
|
||||
|
||||
pub struct PulseClient {
|
||||
channel: PulseMessageChannel,
|
||||
}
|
||||
|
||||
impl PulseClient {
|
||||
pub fn connect(guest_ram: GuestRam) -> Result<Self> {
|
||||
let (tx,rx) = mpsc::channel();
|
||||
|
||||
let _ = thread::spawn(move || {
|
||||
let mut ctx = PulseContext::new(guest_ram);
|
||||
if let Err(err) = ctx.connect() {
|
||||
warn!("PulseAudio Error: {}", err);
|
||||
} else {
|
||||
ctx.run(rx);
|
||||
}
|
||||
});
|
||||
Ok(PulseClient {
|
||||
channel: PulseMessageChannel::new(tx),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn create_spec(num_channels: usize, format: SampleFormat, frame_rate: u32) -> Spec {
|
||||
let format = match format {
|
||||
SampleFormat::U8 => Format::U8,
|
||||
SampleFormat::S16LE => Format::S16le,
|
||||
SampleFormat::S24LE => Format::S24le,
|
||||
SampleFormat::S32LE => Format::S32le,
|
||||
};
|
||||
|
||||
Spec {
|
||||
format,
|
||||
rate: frame_rate,
|
||||
channels: num_channels as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmStreamSource for PulseClient {
|
||||
fn new_stream(&mut self,
|
||||
direction: StreamDirection,
|
||||
num_channels: usize,
|
||||
format: SampleFormat,
|
||||
frame_rate: u32,
|
||||
buffer_size: usize)-> GenericResult<Box<dyn ShmStream>> {
|
||||
|
||||
if direction != StreamDirection::Playback {
|
||||
let stream = NullShmStream::new(buffer_size, num_channels, format, frame_rate);
|
||||
return Ok(Box::new(stream))
|
||||
}
|
||||
let spec = PulseClient::create_spec(num_channels, format, frame_rate);
|
||||
let stream = self.channel.send_new_playback_stream(spec, buffer_size, self.channel.clone())?;
|
||||
Ok(Box::new(stream))
|
||||
}
|
||||
}
|
148
src/audio/pulse/context.rs
Normal file
148
src/audio/pulse/context.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use std::cell::RefCell;
|
||||
use std::ops::DerefMut;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use pulse::context::{Context, FlagSet, State};
|
||||
use pulse::mainloop::threaded::Mainloop;
|
||||
use pulse::proplist::{properties, Proplist};
|
||||
use pulse::sample::Spec;
|
||||
use pulse::stream::Stream;
|
||||
use crate::memory::GuestRam;
|
||||
use crate::audio::pulse::{Result, PulseError, PulseStream};
|
||||
use crate::audio::pulse::message::{PulseContextMessage, PulseContextRequest, PulseMessageChannel};
|
||||
|
||||
pub struct PulseContext {
|
||||
guest_ram: GuestRam,
|
||||
mainloop: Rc<RefCell<Mainloop>>,
|
||||
context: Rc<RefCell<Context>>,
|
||||
}
|
||||
|
||||
impl PulseContext {
|
||||
pub fn mainloop_lock(&self) {
|
||||
self.mainloop.borrow_mut().lock();
|
||||
}
|
||||
|
||||
pub fn mainloop_unlock(&self) {
|
||||
self.mainloop.borrow_mut().unlock();
|
||||
}
|
||||
|
||||
pub fn mainloop_wait(&self) {
|
||||
self.mainloop.borrow_mut().wait();
|
||||
}
|
||||
|
||||
pub fn mainloop(&self) -> Rc<RefCell<Mainloop>> {
|
||||
self.mainloop.clone()
|
||||
}
|
||||
|
||||
pub fn new(guest_ram: GuestRam) -> Self {
|
||||
let mainloop = Mainloop::new()
|
||||
.expect("Failed to create a pulseaudio mainloop");
|
||||
|
||||
let mut proplist = Proplist::new()
|
||||
.expect("Failed to create pulseaudio proplist");
|
||||
|
||||
proplist.set_str(properties::APPLICATION_NAME, "pH")
|
||||
.expect("Failed to set pulseaudio property");
|
||||
|
||||
let context = Context::new_with_proplist(
|
||||
&mainloop,
|
||||
"pHContext",
|
||||
&proplist
|
||||
).expect("Failed to create a pulseaudio context");
|
||||
|
||||
PulseContext {
|
||||
guest_ram,
|
||||
mainloop: Rc::new(RefCell::new(mainloop)),
|
||||
context: Rc::new(RefCell::new(context)),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_and_connect(&self) -> Result<()> {
|
||||
self.mainloop_lock();
|
||||
|
||||
self.context.borrow_mut().set_state_callback(Some(Box::new({
|
||||
let ml_ref = self.mainloop.clone();
|
||||
move || unsafe {
|
||||
(*ml_ref.as_ptr()).signal(false);
|
||||
}
|
||||
})));
|
||||
|
||||
self.context.borrow_mut().connect(None, FlagSet::NOFLAGS, None)
|
||||
.map_err(PulseError::ConnectFailed)?;
|
||||
|
||||
self.mainloop.borrow_mut().start()
|
||||
.map_err(PulseError::StartFailed)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_context_connected(&self) -> Result<()> {
|
||||
loop {
|
||||
let st = self.context.borrow().get_state();
|
||||
if st == State::Ready {
|
||||
break;
|
||||
} else if !st.is_good() {
|
||||
return Err(PulseError::ConnectFailedErr)
|
||||
}
|
||||
self.mainloop.borrow_mut().wait();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn context_connect_finish(&self) {
|
||||
self.context.borrow_mut().set_state_callback(None);
|
||||
self.mainloop_unlock();
|
||||
}
|
||||
|
||||
pub fn connect(&self) -> Result<()> {
|
||||
let result = self.start_and_connect().and_then(|()| {
|
||||
self.wait_context_connected()
|
||||
});
|
||||
self.context_connect_finish();
|
||||
result
|
||||
}
|
||||
|
||||
fn new_playback_stream(&self, spec: Spec, buffer_size: usize, channel: PulseMessageChannel) -> PulseStream {
|
||||
self.mainloop_lock();
|
||||
|
||||
let stream = Stream::new(self.context.borrow_mut().deref_mut(),
|
||||
"ph-pa-playback",
|
||||
&spec,
|
||||
None)
|
||||
.expect("Failed to create pulseaudio stream");
|
||||
|
||||
let ps = PulseStream::new_playback(stream, self.guest_ram.clone(), spec, buffer_size, channel);
|
||||
self.mainloop_unlock();
|
||||
ps
|
||||
}
|
||||
|
||||
pub fn run(&mut self, receiver: Receiver<PulseContextMessage>) {
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(msg) => self.dispatch_message(msg),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_message(&mut self, msg: PulseContextMessage) {
|
||||
match msg.request() {
|
||||
PulseContextRequest::MainloopLock => {
|
||||
self.mainloop_lock();
|
||||
msg.respond_ok();
|
||||
}
|
||||
PulseContextRequest::MainloopUnlock => {
|
||||
self.mainloop_unlock();
|
||||
msg.respond_ok();
|
||||
}
|
||||
PulseContextRequest::NewPlaybackStream {spec, buffer_size, channel} => {
|
||||
let mut ps = self.new_playback_stream(*spec, *buffer_size, channel.clone());
|
||||
match ps.connect(self) {
|
||||
Ok(()) => msg.respond_stream(ps),
|
||||
Err(err) => msg.respond_err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
104
src/audio/pulse/message.rs
Normal file
104
src/audio/pulse/message.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use pulse::sample::Spec;
|
||||
use crate::audio::pulse::{PulseError, PulseStream, Result};
|
||||
use crate::audio::pulse::PulseError::UnexpectedResponse;
|
||||
|
||||
pub enum PulseContextRequest {
|
||||
MainloopLock,
|
||||
MainloopUnlock,
|
||||
NewPlaybackStream {
|
||||
spec: Spec,
|
||||
buffer_size: usize,
|
||||
channel: PulseMessageChannel,
|
||||
},
|
||||
}
|
||||
|
||||
pub enum PulseContextResponse {
|
||||
ResponseOk,
|
||||
ResponseError(PulseError),
|
||||
ResponseStream(PulseStream),
|
||||
}
|
||||
|
||||
pub struct PulseContextMessage {
|
||||
request: PulseContextRequest,
|
||||
response_channel: Sender<PulseContextResponse>,
|
||||
}
|
||||
|
||||
impl PulseContextMessage {
|
||||
|
||||
pub fn new(request: PulseContextRequest) -> (Self, Receiver<PulseContextResponse>) {
|
||||
let (tx,rx) = mpsc::channel();
|
||||
let msg = PulseContextMessage {
|
||||
request,
|
||||
response_channel: tx,
|
||||
};
|
||||
(msg, rx)
|
||||
}
|
||||
|
||||
pub fn request(&self) -> &PulseContextRequest {
|
||||
&self.request
|
||||
}
|
||||
|
||||
fn send_response(&self, response: PulseContextResponse) {
|
||||
if let Err(err) = self.response_channel.send(response) {
|
||||
warn!("PulseAudio: Error sending message response: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn respond_ok(&self) {
|
||||
self.send_response(PulseContextResponse::ResponseOk)
|
||||
}
|
||||
|
||||
pub fn respond_err(&self, err: PulseError) {
|
||||
self.send_response(PulseContextResponse::ResponseError(err))
|
||||
}
|
||||
|
||||
pub fn respond_stream(&self, stream: PulseStream) {
|
||||
self.send_response(PulseContextResponse::ResponseStream(stream));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PulseMessageChannel {
|
||||
sender: Sender<PulseContextMessage>
|
||||
}
|
||||
|
||||
impl PulseMessageChannel {
|
||||
pub fn new(sender: Sender<PulseContextMessage>) -> Self {
|
||||
PulseMessageChannel { sender }
|
||||
}
|
||||
|
||||
fn exchange_message(&self, req: PulseContextRequest) -> Result<PulseContextResponse> {
|
||||
let (msg, rx) = PulseContextMessage::new(req);
|
||||
self.sender.send(msg).map_err(|_| PulseError::SendMessageFailed)?;
|
||||
let resp = rx.recv().map_err(|_| PulseError::RecvMessageFailed)?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
fn send_expect_ok(&self, req: PulseContextRequest) -> Result<()> {
|
||||
let (msg, rx) = PulseContextMessage::new(req);
|
||||
self.sender.send(msg).map_err(|_| PulseError::SendMessageFailed)?;
|
||||
let response = rx.recv().map_err(|_| PulseError::RecvMessageFailed)?;
|
||||
if let PulseContextResponse::ResponseError(err) = response {
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_mainloop_lock(&self) -> Result<()> {
|
||||
self.send_expect_ok(PulseContextRequest::MainloopLock)
|
||||
}
|
||||
|
||||
pub fn send_mainloop_unlock(&self) -> Result<()> {
|
||||
self.send_expect_ok(PulseContextRequest::MainloopUnlock)
|
||||
}
|
||||
|
||||
pub fn send_new_playback_stream(&self, spec: Spec, buffer_size: usize, channel: PulseMessageChannel) -> Result<PulseStream> {
|
||||
match self.exchange_message(PulseContextRequest::NewPlaybackStream { spec, buffer_size, channel})? {
|
||||
PulseContextResponse::ResponseOk => Err(UnexpectedResponse),
|
||||
PulseContextResponse::ResponseError(err) => Err(err),
|
||||
PulseContextResponse::ResponseStream(stream) => Ok(stream),
|
||||
}
|
||||
}
|
||||
}
|
32
src/audio/pulse/mod.rs
Normal file
32
src/audio/pulse/mod.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use std::result;
|
||||
use pulse::error::PAErr;
|
||||
|
||||
mod client;
|
||||
mod context;
|
||||
mod message;
|
||||
mod stream;
|
||||
|
||||
pub type Result<T> = result::Result<T, PulseError>;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum PulseError {
|
||||
#[error("connection to pulseaudio failed: {0}")]
|
||||
ConnectFailed(PAErr),
|
||||
#[error("failed to connect to pulseaudio server")]
|
||||
ConnectFailedErr,
|
||||
#[error("failed to start pulseaudio mainloop: {0}")]
|
||||
StartFailed(PAErr),
|
||||
#[error("failed to connect pulseaudio stream: {0}")]
|
||||
StreamConnect(PAErr),
|
||||
#[error("stream connect failed")]
|
||||
StreamConnectFailed,
|
||||
#[error("failed to send channel message")]
|
||||
SendMessageFailed,
|
||||
#[error("failed to receive channel response message")]
|
||||
RecvMessageFailed,
|
||||
#[error("unexpected response to channel message")]
|
||||
UnexpectedResponse,
|
||||
}
|
||||
|
||||
pub use stream::PulseStream;
|
||||
pub use client::PulseClient;
|
182
src/audio/pulse/stream.rs
Normal file
182
src/audio/pulse/stream.rs
Normal file
@ -0,0 +1,182 @@
|
||||
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
|
||||
use std::time::Duration;
|
||||
use pulse::sample::Spec;
|
||||
use pulse::stream::{FlagSet, SeekMode, State, Stream};
|
||||
use crate::audio::pulse::{PulseError,Result};
|
||||
use crate::audio::pulse::context::PulseContext;
|
||||
use crate::audio::pulse::message::PulseMessageChannel;
|
||||
use crate::audio::shm_streams::{BufferSet, GenericResult, ServerRequest, ShmStream};
|
||||
use crate::memory::GuestRam;
|
||||
|
||||
struct Available {
|
||||
byte_count: Mutex<usize>,
|
||||
cond: Condvar,
|
||||
}
|
||||
|
||||
impl Available {
|
||||
fn new() -> Self {
|
||||
Available {
|
||||
byte_count: Mutex::new(0),
|
||||
cond: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_count_lock(&self) -> MutexGuard<usize> {
|
||||
self.byte_count.lock().unwrap()
|
||||
}
|
||||
|
||||
fn update(&self, value: usize) {
|
||||
let mut byte_count = self.byte_count_lock();
|
||||
*byte_count = value;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
|
||||
fn decrement(&self, amount: usize) {
|
||||
let mut byte_count = self.byte_count_lock();
|
||||
*byte_count -= amount;
|
||||
}
|
||||
|
||||
fn wait_space(&self, timeout: Duration) -> Option<usize> {
|
||||
let mut byte_count = self.byte_count_lock();
|
||||
while *byte_count == 0 {
|
||||
let (new_lock, wt_result) = self.cond.wait_timeout(byte_count, timeout).unwrap();
|
||||
if wt_result.timed_out() {
|
||||
return None;
|
||||
}
|
||||
byte_count = new_lock;
|
||||
}
|
||||
Some(*byte_count)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PulseStream {
|
||||
spec: Spec,
|
||||
buffer_size: usize,
|
||||
guest_ram: GuestRam,
|
||||
stream: Arc<Mutex<Stream>>,
|
||||
avail: Arc<Available>,
|
||||
channel: PulseMessageChannel,
|
||||
}
|
||||
|
||||
impl PulseStream {
|
||||
|
||||
fn stream_connected_finish(&mut self, ctx: &PulseContext) {
|
||||
self.stream().set_state_callback(None);
|
||||
ctx.mainloop_unlock();
|
||||
}
|
||||
|
||||
fn wait_stream_connected(&mut self, ctx: &PulseContext) -> Result<()> {
|
||||
loop {
|
||||
let state = self.stream().get_state();
|
||||
if state == State::Ready {
|
||||
break;
|
||||
} else if !state.is_good() {
|
||||
return Err(PulseError::StreamConnectFailed);
|
||||
}
|
||||
ctx.mainloop_wait();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn connect(&mut self, ctx: &PulseContext) -> Result<()> {
|
||||
ctx.mainloop_lock();
|
||||
|
||||
self.stream().set_state_callback(Some(Box::new({
|
||||
let ml_ref = ctx.mainloop();
|
||||
move || unsafe {
|
||||
(*ml_ref.as_ptr()).signal(false);
|
||||
}
|
||||
})));
|
||||
|
||||
|
||||
if let Err(err) = self.stream().connect_playback(
|
||||
None,
|
||||
None,
|
||||
FlagSet::NOFLAGS,
|
||||
None,
|
||||
None) {
|
||||
self.stream().set_state_callback(None);
|
||||
ctx.mainloop_unlock();
|
||||
return Err(PulseError::StreamConnect(err))
|
||||
}
|
||||
|
||||
let result = self.wait_stream_connected(ctx);
|
||||
self.stream_connected_finish(ctx);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn new_playback(mut stream: Stream, guest_ram: GuestRam, spec: Spec, buffer_size: usize, channel: PulseMessageChannel) -> Self {
|
||||
let avail = Arc::new(Available::new());
|
||||
|
||||
stream.set_write_callback(Some(Box::new({
|
||||
let avail = avail.clone();
|
||||
move |writeable_bytes| {
|
||||
avail.update(writeable_bytes);
|
||||
}
|
||||
})));
|
||||
|
||||
let stream = Arc::new(Mutex::new(stream));
|
||||
PulseStream {
|
||||
spec,
|
||||
buffer_size,
|
||||
guest_ram,
|
||||
avail,
|
||||
stream,
|
||||
channel,
|
||||
}
|
||||
}
|
||||
|
||||
fn stream(&self) -> MutexGuard<Stream> {
|
||||
self.stream.lock().unwrap()
|
||||
}
|
||||
|
||||
fn uncork(&self) -> GenericResult<()> {
|
||||
self.channel.send_mainloop_lock()?;
|
||||
if self.stream().is_corked().unwrap() {
|
||||
self.stream().uncork(None);
|
||||
}
|
||||
self.channel.send_mainloop_unlock()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmStream for PulseStream {
|
||||
fn frame_size(&self) -> usize {
|
||||
self.spec.frame_size()
|
||||
}
|
||||
|
||||
fn num_channels(&self) -> usize {
|
||||
self.spec.channels as usize
|
||||
}
|
||||
|
||||
fn frame_rate(&self) -> u32 {
|
||||
self.spec.rate
|
||||
}
|
||||
|
||||
fn wait_for_next_action_with_timeout(&self, timeout: Duration) -> GenericResult<Option<ServerRequest>> {
|
||||
if let Some(bytes) = self.avail.wait_space(timeout) {
|
||||
let frames = bytes / self.frame_size();
|
||||
let req = frames.min(self.buffer_size);
|
||||
return Ok(Some(ServerRequest::new(req, self)))
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferSet for PulseStream {
|
||||
fn callback(&self, address: u64, frames: usize) -> GenericResult<()> {
|
||||
self.uncork()?;
|
||||
let bytes = self.guest_ram.slice(address, frames * self.frame_size())?;
|
||||
|
||||
self.channel.send_mainloop_lock()?;
|
||||
self.stream().write_copy(bytes, 0, SeekMode::Relative)?;
|
||||
self.channel.send_mainloop_unlock()?;
|
||||
self.avail.decrement(bytes.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ignore(&self) -> GenericResult<()> {
|
||||
info!("Request ignored...");
|
||||
Ok(())
|
||||
}
|
||||
}
|
278
src/audio/shm_streams.rs
Normal file
278
src/audio/shm_streams.rs
Normal file
@ -0,0 +1,278 @@
|
||||
// Copyright 2019 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use thiserror::Error;
|
||||
use crate::audio::{BoxError, SampleFormat, StreamDirection};
|
||||
|
||||
pub(crate) type GenericResult<T> = Result<T, BoxError>;
|
||||
|
||||
/// `BufferSet` is used as a callback mechanism for `ServerRequest` objects.
|
||||
/// It is meant to be implemented by the audio stream, allowing arbitrary code
|
||||
/// to be run after a buffer address and length is set.
|
||||
pub trait BufferSet {
|
||||
/// Called when the client sets a buffer address and length.
|
||||
///
|
||||
/// `address` is the guest address of the buffer and `frames`
|
||||
/// indicates the number of audio frames that can be read from or written to
|
||||
/// the buffer.
|
||||
fn callback(&self, address: u64, frames: usize) -> GenericResult<()>;
|
||||
|
||||
/// Called when the client ignores a request from the server.
|
||||
fn ignore(&self) -> GenericResult<()>;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Provided number of frames {0} exceeds requested number of frames {1}")]
|
||||
TooManyFrames(usize, usize),
|
||||
}
|
||||
|
||||
/// `ServerRequest` represents an active request from the server for the client
|
||||
/// to provide a buffer in shared memory to playback from or capture to.
|
||||
pub struct ServerRequest<'a> {
|
||||
requested_frames: usize,
|
||||
buffer_set: &'a dyn BufferSet,
|
||||
}
|
||||
|
||||
impl<'a> ServerRequest<'a> {
|
||||
/// Create a new ServerRequest object
|
||||
///
|
||||
/// Create a ServerRequest object representing a request from the server
|
||||
/// for a buffer `requested_frames` in size.
|
||||
///
|
||||
/// When the client responds to this request by calling
|
||||
/// [`set_buffer_address_and_frames`](ServerRequest::set_buffer_address_and_frames),
|
||||
/// BufferSet::callback will be called on `buffer_set`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `requested_frames` - The requested buffer size in frames.
|
||||
/// * `buffer_set` - The object implementing the callback for when a buffer is provided.
|
||||
//pub fn new<D: BufferSet>(requested_frames: usize, buffer_set: &'a mut D) -> Self {
|
||||
pub fn new<D: BufferSet>(requested_frames: usize, buffer_set: &'a D) -> Self {
|
||||
Self {
|
||||
requested_frames,
|
||||
buffer_set,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the number of frames of audio data requested by the server.
|
||||
///
|
||||
/// The returned value should never be greater than the `buffer_size`
|
||||
/// given in [`new_stream`](ShmStreamSource::new_stream).
|
||||
pub fn requested_frames(&self) -> usize {
|
||||
self.requested_frames
|
||||
}
|
||||
|
||||
/// Sets the buffer address and length for the requested buffer.
|
||||
///
|
||||
/// Sets the buffer address and length of the buffer that fulfills this
|
||||
/// server request to `address` and `length`, respectively. This means that
|
||||
/// `length` bytes of audio samples may be read from/written to that
|
||||
/// location in `client_shm` for a playback/capture stream, respectively.
|
||||
/// This function may only be called once for a `ServerRequest`, at which
|
||||
/// point the ServerRequest is dropped and no further calls are possible.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `address` - The value to use as the address for the next buffer.
|
||||
/// * `frames` - The length of the next buffer in frames.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * If `frames` is greater than `requested_frames`.
|
||||
pub fn set_buffer_address_and_frames(self, address: u64, frames: usize) -> GenericResult<()> {
|
||||
if frames > self.requested_frames {
|
||||
return Err(Box::new(Error::TooManyFrames(
|
||||
frames,
|
||||
self.requested_frames,
|
||||
)));
|
||||
}
|
||||
|
||||
self.buffer_set.callback(address, frames)
|
||||
}
|
||||
|
||||
/// Ignore this request
|
||||
///
|
||||
/// If the client does not intend to respond to this ServerRequest with a
|
||||
/// buffer, they should call this function. The stream will be notified that
|
||||
/// the request has been ignored and will handle it properly.
|
||||
pub fn ignore_request(self) -> GenericResult<()> {
|
||||
self.buffer_set.ignore()
|
||||
}
|
||||
}
|
||||
|
||||
/// `ShmStream` allows a client to interact with an active CRAS stream.
|
||||
pub trait ShmStream: Send {
|
||||
/// Get the size of a frame of audio data for this stream.
|
||||
fn frame_size(&self) -> usize;
|
||||
|
||||
/// Get the number of channels of audio data for this stream.
|
||||
fn num_channels(&self) -> usize;
|
||||
|
||||
/// Get the frame rate of audio data for this stream.
|
||||
fn frame_rate(&self) -> u32;
|
||||
|
||||
/// Waits until the next server message indicating action is required.
|
||||
///
|
||||
/// For playback streams, this will be `AUDIO_MESSAGE_REQUEST_DATA`, meaning
|
||||
/// that we must set the buffer address to the next location where playback
|
||||
/// data can be found.
|
||||
/// For capture streams, this will be `AUDIO_MESSAGE_DATA_READY`, meaning
|
||||
/// that we must set the buffer address to the next location where captured
|
||||
/// data can be written to.
|
||||
/// Will return early if `timeout` elapses before a message is received.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `timeout` - The amount of time to wait until a message is received.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// Returns `Some(request)` where `request` is an object that implements the
|
||||
/// [`ServerRequest`](ServerRequest) trait and which can be used to get the
|
||||
/// number of bytes requested for playback streams or that have already been
|
||||
/// written to shm for capture streams.
|
||||
///
|
||||
/// If the timeout occurs before a message is received, returns `None`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * If an invalid message type is received for the stream.
|
||||
fn wait_for_next_action_with_timeout(
|
||||
&self,
|
||||
timeout: Duration,
|
||||
) -> GenericResult<Option<ServerRequest>>;
|
||||
}
|
||||
|
||||
/// `SharedMemory` specifies features of shared memory areas passed on to `ShmStreamSource`.
|
||||
pub trait SharedMemory {
|
||||
type Error: std::error::Error;
|
||||
|
||||
/// Creates a new shared memory file descriptor without specifying a name.
|
||||
fn anon(size: u64) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Gets the size in bytes of the shared memory.
|
||||
///
|
||||
/// The size returned here does not reflect changes by other interfaces or users of the shared
|
||||
/// memory file descriptor..
|
||||
fn size(&self) -> u64;
|
||||
|
||||
/// Returns the underlying raw fd.
|
||||
#[cfg(unix)]
|
||||
fn as_raw_fd(&self) -> RawFd;
|
||||
}
|
||||
|
||||
/// `ShmStreamSource` creates streams for playback or capture of audio.
|
||||
pub trait ShmStreamSource: Send {
|
||||
/// Creates a new [`ShmStream`](ShmStream)
|
||||
///
|
||||
/// Creates a new `ShmStream` object, which allows:
|
||||
/// * Waiting until the server has communicated that data is ready or
|
||||
/// requested that we make more data available.
|
||||
/// * Setting the location and length of buffers for reading/writing audio data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `direction` - The direction of the stream, either `Playback` or `Capture`.
|
||||
/// * `num_channels` - The number of audio channels for the stream.
|
||||
/// * `format` - The audio format to use for audio samples.
|
||||
/// * `frame_rate` - The stream's frame rate in Hz.
|
||||
/// * `buffer_size` - The maximum size of an audio buffer. This will be the
|
||||
/// size used for transfers of audio data between client
|
||||
/// and server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * If sending the connect stream message to the server fails.
|
||||
fn new_stream(
|
||||
&mut self,
|
||||
direction: StreamDirection,
|
||||
num_channels: usize,
|
||||
format: SampleFormat,
|
||||
frame_rate: u32,
|
||||
buffer_size: usize,
|
||||
) -> GenericResult<Box<dyn ShmStream>>;
|
||||
}
|
||||
|
||||
/// Class that implements ShmStream trait but does nothing with the samples
|
||||
pub struct NullShmStream {
|
||||
num_channels: usize,
|
||||
frame_rate: u32,
|
||||
buffer_size: usize,
|
||||
frame_size: usize,
|
||||
interval: Duration,
|
||||
next_frame: Mutex<Duration>,
|
||||
start_time: Instant,
|
||||
}
|
||||
|
||||
impl NullShmStream {
|
||||
/// Attempt to create a new NullShmStream with the given number of channels,
|
||||
/// format, frame_rate, and buffer_size.
|
||||
pub fn new(
|
||||
buffer_size: usize,
|
||||
num_channels: usize,
|
||||
format: SampleFormat,
|
||||
frame_rate: u32,
|
||||
) -> Self {
|
||||
let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
|
||||
Self {
|
||||
num_channels,
|
||||
frame_rate,
|
||||
buffer_size,
|
||||
frame_size: format.sample_bytes() * num_channels,
|
||||
interval,
|
||||
next_frame: Mutex::new(interval),
|
||||
start_time: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferSet for NullShmStream {
|
||||
fn callback(&self, _address: u64, _frames: usize) -> GenericResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ignore(&self) -> GenericResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmStream for NullShmStream {
|
||||
fn frame_size(&self) -> usize {
|
||||
self.frame_size
|
||||
}
|
||||
|
||||
fn num_channels(&self) -> usize {
|
||||
self.num_channels
|
||||
}
|
||||
|
||||
fn frame_rate(&self) -> u32 {
|
||||
self.frame_rate
|
||||
}
|
||||
|
||||
fn wait_for_next_action_with_timeout(
|
||||
&self,
|
||||
timeout: Duration,
|
||||
) -> GenericResult<Option<ServerRequest>> {
|
||||
let elapsed = self.start_time.elapsed();
|
||||
let mut next_frame = self.next_frame.lock().unwrap();
|
||||
if elapsed < *next_frame {
|
||||
if timeout < *next_frame - elapsed {
|
||||
std::thread::sleep(timeout);
|
||||
return Ok(None);
|
||||
} else {
|
||||
std::thread::sleep(*next_frame - elapsed);
|
||||
}
|
||||
}
|
||||
*next_frame += self.interval;
|
||||
Ok(Some(ServerRequest::new(self.buffer_size, self)))
|
||||
}
|
||||
}
|
188
src/devices/ac97/ac97.rs
Normal file
188
src/devices/ac97/ac97.rs
Normal file
@ -0,0 +1,188 @@
|
||||
// Copyright 2018 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::io;
|
||||
|
||||
use thiserror::Error;
|
||||
use crate::audio::pulse::{PulseClient, PulseError};
|
||||
use crate::devices::ac97::ac97_bus_master::{Ac97BusMaster, AudioStreamSource};
|
||||
use crate::devices::ac97::ac97_mixer::Ac97Mixer;
|
||||
use crate::devices::ac97::ac97_regs::{MASTER_REGS_SIZE, MIXER_REGS_SIZE};
|
||||
use crate::devices::irq_event::IrqLevelEvent;
|
||||
use crate::io::pci::{PciBar, PciBarAllocation, PciConfiguration, PciDevice};
|
||||
use crate::memory::GuestRam;
|
||||
use crate::vm::KvmVm;
|
||||
|
||||
|
||||
// Use 82801AA because it's what qemu does.
|
||||
const PCI_DEVICE_ID_INTEL_82801AA_5: u16 = 0x2415;
|
||||
|
||||
/// AC97 audio device emulation.
|
||||
/// Provides the PCI interface for the internal Ac97 emulation.
|
||||
/// Internally the `Ac97BusMaster` and `Ac97Mixer` structs are used to emulated the bus master and
|
||||
/// mixer registers respectively. `Ac97BusMaster` handles moving samples between guest memory and
|
||||
/// the audio backend.
|
||||
/// Errors that are possible from a `Ac97`.
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Ac97Error {
|
||||
#[error("Error creating IRQ level event: {0}")]
|
||||
IrqLevelEventError(io::Error),
|
||||
#[error("PulseAudio: {0}")]
|
||||
PulseError(PulseError),
|
||||
}
|
||||
|
||||
pub struct Ac97Dev {
|
||||
irq: u8,
|
||||
pci_config: PciConfiguration,
|
||||
bus_master: Ac97BusMaster,
|
||||
mixer: Ac97Mixer,
|
||||
}
|
||||
|
||||
const PCI_CLASS_MULTIMEDIA_AUDIO:u16 = 0x0401;
|
||||
const PCI_VENDOR_ID_INTEL: u16 = 0x8086;
|
||||
|
||||
impl Ac97Dev {
|
||||
/// Creates an 'Ac97Dev' that uses the given `GuestRam` and starts with all registers at
|
||||
/// default values.
|
||||
pub fn new(
|
||||
irq: u8,
|
||||
mem: GuestRam,
|
||||
audio_server: AudioStreamSource,
|
||||
) -> Self {
|
||||
let pci_config = PciConfiguration::new(irq, PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_5, PCI_CLASS_MULTIMEDIA_AUDIO);
|
||||
|
||||
Self {
|
||||
irq,
|
||||
pci_config,
|
||||
bus_master: Ac97BusMaster::new(
|
||||
mem,
|
||||
audio_server,
|
||||
),
|
||||
mixer: Ac97Mixer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an `Ac97Dev` with suitable audio server inside based on Ac97Parameters. If it fails
|
||||
/// to create `Ac97Dev` with the given back-end, it'll fallback to the null audio device.
|
||||
pub fn try_new(
|
||||
kvm_vm: &KvmVm,
|
||||
irq: u8,
|
||||
mem: GuestRam,
|
||||
) -> Result<Self, Ac97Error> {
|
||||
let mut ac97 = Self::initialize_pulseaudio(irq, mem)?;
|
||||
let irq_event = IrqLevelEvent::register(kvm_vm, irq)
|
||||
.map_err(Ac97Error::IrqLevelEventError)?;
|
||||
ac97.bus_master.set_irq_event(irq_event);
|
||||
Ok(ac97)
|
||||
}
|
||||
|
||||
fn initialize_pulseaudio(irq: u8, mem: GuestRam) -> Result<Self, Ac97Error> {
|
||||
let server = PulseClient::connect(mem.clone())
|
||||
.map_err(Ac97Error::PulseError)?;
|
||||
Ok(Self::new(
|
||||
irq,
|
||||
mem,
|
||||
Box::new(server),
|
||||
))
|
||||
}
|
||||
|
||||
fn read_mixer(&mut self, offset: u64, data: &mut [u8]) {
|
||||
match data.len() {
|
||||
// The mixer is only accessed with 16-bit words.
|
||||
2 => {
|
||||
let val: u16 = self.mixer.readw(offset);
|
||||
data[0] = val as u8;
|
||||
data[1] = (val >> 8) as u8;
|
||||
}
|
||||
l => warn!("mixer read length of {}", l),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_mixer(&mut self, offset: u64, data: &[u8]) {
|
||||
match data.len() {
|
||||
// The mixer is only accessed with 16-bit words.
|
||||
2 => self
|
||||
.mixer
|
||||
.writew(offset, u16::from(data[0]) | u16::from(data[1]) << 8),
|
||||
l => warn!("mixer write length of {}", l),
|
||||
}
|
||||
// Apply the new mixer settings to the bus master.
|
||||
self.bus_master.update_mixer_settings(&self.mixer);
|
||||
}
|
||||
|
||||
fn read_bus_master(&mut self, offset: u64, data: &mut [u8]) {
|
||||
match data.len() {
|
||||
1 => data[0] = self.bus_master.readb(offset),
|
||||
2 => {
|
||||
let val: u16 = self.bus_master.readw(offset, &self.mixer);
|
||||
data[0] = val as u8;
|
||||
data[1] = (val >> 8) as u8;
|
||||
}
|
||||
4 => {
|
||||
let val: u32 = self.bus_master.readl(offset);
|
||||
data[0] = val as u8;
|
||||
data[1] = (val >> 8) as u8;
|
||||
data[2] = (val >> 16) as u8;
|
||||
data[3] = (val >> 24) as u8;
|
||||
}
|
||||
l => warn!("read length of {}", l),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_bus_master(&mut self, offset: u64, data: &[u8]) {
|
||||
match data.len() {
|
||||
1 => self.bus_master.writeb(offset, data[0], &self.mixer),
|
||||
2 => self
|
||||
.bus_master
|
||||
.writew(offset, u16::from(data[0]) | u16::from(data[1]) << 8),
|
||||
4 => self.bus_master.writel(
|
||||
offset,
|
||||
(u32::from(data[0]))
|
||||
| (u32::from(data[1]) << 8)
|
||||
| (u32::from(data[2]) << 16)
|
||||
| (u32::from(data[3]) << 24),
|
||||
&mut self.mixer,
|
||||
),
|
||||
l => warn!("write length of {}", l),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PciDevice for Ac97Dev {
|
||||
fn config(&self) -> &PciConfiguration {
|
||||
&self.pci_config
|
||||
}
|
||||
|
||||
fn config_mut(&mut self) -> &mut PciConfiguration {
|
||||
&mut self.pci_config
|
||||
}
|
||||
|
||||
fn read_bar(&mut self, bar: PciBar, offset: u64, data: &mut [u8]) {
|
||||
match bar {
|
||||
PciBar::Bar0 => self.read_mixer(offset, data),
|
||||
PciBar::Bar1 => self.read_bus_master(offset, data),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn write_bar(&mut self, bar: PciBar, offset: u64, data: &[u8]) {
|
||||
match bar {
|
||||
PciBar::Bar0 => self.write_mixer(offset, data),
|
||||
PciBar::Bar1 => self.write_bus_master(offset, data),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn irq(&self) -> Option<u8> {
|
||||
Some(self.irq)
|
||||
}
|
||||
|
||||
fn bar_allocations(&self) -> Vec<PciBarAllocation> {
|
||||
vec![
|
||||
PciBarAllocation::Mmio(PciBar::Bar0, MIXER_REGS_SIZE as usize),
|
||||
PciBarAllocation::Mmio(PciBar::Bar1, MASTER_REGS_SIZE as usize)
|
||||
]
|
||||
}
|
||||
}
|
947
src/devices/ac97/ac97_bus_master.rs
Normal file
947
src/devices/ac97/ac97_bus_master.rs
Normal file
@ -0,0 +1,947 @@
|
||||
// Copyright 2018 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
|
||||
use std::{cmp, thread};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use thiserror::Error;
|
||||
use crate::audio::shm_streams::{ShmStream, ShmStreamSource};
|
||||
use crate::audio::{BoxError, SampleFormat, StreamControl, StreamDirection};
|
||||
use crate::devices::ac97::ac97_mixer::Ac97Mixer;
|
||||
use crate::devices::ac97::ac97_regs::*;
|
||||
use crate::devices::irq_event::IrqLevelEvent;
|
||||
use crate::memory::GuestRam;
|
||||
use crate::system;
|
||||
|
||||
const INPUT_SAMPLE_RATE: u32 = 48000;
|
||||
const DEVICE_INPUT_CHANNEL_COUNT: usize = 2;
|
||||
|
||||
pub(crate) type AudioStreamSource = Box<dyn ShmStreamSource>;
|
||||
|
||||
// Bus Master registers. Keeps the state of the bus master register values. Used to share the state
|
||||
// between the main and audio threads.
|
||||
struct Ac97BusMasterRegs {
|
||||
pi_regs: Ac97FunctionRegs, // Input
|
||||
po_regs: Ac97FunctionRegs, // Output
|
||||
po_pointer_update_time: Instant, // Time the picb and civ regs were last updated.
|
||||
mc_regs: Ac97FunctionRegs, // Microphone
|
||||
glob_cnt: u32,
|
||||
glob_sta: u32,
|
||||
|
||||
// IRQ event - driven by the glob_sta register.
|
||||
irq_evt: Option<IrqLevelEvent>,
|
||||
}
|
||||
|
||||
impl Ac97BusMasterRegs {
|
||||
fn new() -> Ac97BusMasterRegs {
|
||||
Ac97BusMasterRegs {
|
||||
pi_regs: Ac97FunctionRegs::new("Input"),
|
||||
po_regs: Ac97FunctionRegs::new("Output"),
|
||||
po_pointer_update_time: Instant::now(),
|
||||
mc_regs: Ac97FunctionRegs::new("Microphone"),
|
||||
glob_cnt: 0,
|
||||
glob_sta: GLOB_STA_RESET_VAL,
|
||||
irq_evt: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn func_regs(&self, func: Ac97Function) -> &Ac97FunctionRegs {
|
||||
match func {
|
||||
Ac97Function::Input => &self.pi_regs,
|
||||
Ac97Function::Output => &self.po_regs,
|
||||
Ac97Function::Microphone => &self.mc_regs,
|
||||
}
|
||||
}
|
||||
|
||||
fn func_regs_mut(&mut self, func: Ac97Function) -> &mut Ac97FunctionRegs {
|
||||
match func {
|
||||
Ac97Function::Input => &mut self.pi_regs,
|
||||
Ac97Function::Output => &mut self.po_regs,
|
||||
Ac97Function::Microphone => &mut self.mc_regs,
|
||||
}
|
||||
}
|
||||
|
||||
fn tube_count(&self, func: Ac97Function) -> usize {
|
||||
fn output_tube_count(glob_cnt: u32) -> usize {
|
||||
let val = (glob_cnt & GLOB_CNT_PCM_246_MASK) >> 20;
|
||||
match val {
|
||||
0 => 2,
|
||||
1 => 4,
|
||||
2 => 6,
|
||||
_ => {
|
||||
warn!("unknown tube_count: 0x{:x}", val);
|
||||
2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match func {
|
||||
Ac97Function::Output => output_tube_count(self.glob_cnt),
|
||||
_ => DEVICE_INPUT_CHANNEL_COUNT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the irq is set for any one of the bus master function registers.
|
||||
pub fn has_irq(&self) -> bool {
|
||||
self.pi_regs.has_irq() || self.po_regs.has_irq() || self.mc_regs.has_irq()
|
||||
}
|
||||
}
|
||||
|
||||
// Internal error type used for reporting errors from guest memory reading.
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum GuestMemoryError {
|
||||
// Failure getting the address of the audio buffer.
|
||||
#[error("Failed to get the address of the audio buffer: {0}.")]
|
||||
ReadingGuestBufferAddress(system::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum AudioError {
|
||||
#[error("Failed to create audio stream: {0}.")]
|
||||
CreateStream(BoxError),
|
||||
#[error("Offset > max usize")]
|
||||
InvalidBufferOffset,
|
||||
#[error("Failed to read guest memory: {0}.")]
|
||||
ReadingGuestError(GuestMemoryError),
|
||||
// Failure to respond to the ServerRequest.
|
||||
#[error("Failed to respond to the ServerRequest: {0}")]
|
||||
RespondRequest(BoxError),
|
||||
// Failure to wait for a request from the stream.
|
||||
#[error("Failed to wait for a message from the stream: {0}")]
|
||||
WaitForAction(BoxError),
|
||||
}
|
||||
|
||||
impl From<GuestMemoryError> for AudioError {
|
||||
fn from(err: GuestMemoryError) -> Self {
|
||||
AudioError::ReadingGuestError(err)
|
||||
}
|
||||
}
|
||||
|
||||
type GuestMemoryResult<T> = Result<T, GuestMemoryError>;
|
||||
|
||||
type AudioResult<T> = Result<T, AudioError>;
|
||||
|
||||
// Audio thread book-keeping data
|
||||
struct AudioThreadInfo {
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
thread_run: Arc<AtomicBool>,
|
||||
thread_semaphore: Arc<Condvar>,
|
||||
stream_control: Option<Box<dyn StreamControl>>,
|
||||
}
|
||||
|
||||
impl AudioThreadInfo {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
thread: None,
|
||||
thread_run: Arc::new(AtomicBool::new(false)),
|
||||
thread_semaphore: Arc::new(Condvar::new()),
|
||||
stream_control: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
self.thread_run.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn start(&mut self, mut worker: AudioWorker) {
|
||||
self.thread_run.store(true, Ordering::Relaxed);
|
||||
self.thread = Some(thread::spawn(move || {
|
||||
|
||||
if let Err(e) = worker.run() {
|
||||
warn!("{:?} error: {}", worker.func, e);
|
||||
}
|
||||
|
||||
worker.thread_run.store(false, Ordering::Relaxed);
|
||||
}));
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
self.thread_run.store(false, Ordering::Relaxed);
|
||||
self.thread_semaphore.notify_one();
|
||||
if let Some(thread) = self.thread.take() {
|
||||
if let Err(e) = thread.join() {
|
||||
warn!("Failed to join thread: {:?}.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Ac97BusMaster` emulates the bus master portion of AC97. It exposes a register read/write
|
||||
/// interface compliant with the ICH bus master.
|
||||
pub struct Ac97BusMaster {
|
||||
// Keep guest memory as each function will use it for buffer descriptors.
|
||||
mem: GuestRam,
|
||||
regs: Arc<Mutex<Ac97BusMasterRegs>>,
|
||||
acc_sema: u8,
|
||||
|
||||
// Bookkeeping info for playback and capture stream.
|
||||
po_info: AudioThreadInfo,
|
||||
pi_info: AudioThreadInfo,
|
||||
pmic_info: AudioThreadInfo,
|
||||
|
||||
// Audio server used to create playback or capture streams.
|
||||
audio_server: AudioStreamSource,
|
||||
|
||||
// Thread for hadlind IRQ resample events from the guest.
|
||||
irq_resample_thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Ac97BusMaster {
|
||||
|
||||
/// Creates an Ac97BusMaster` object that plays audio from `mem` to streams provided by
|
||||
/// `audio_server`.
|
||||
pub fn new(mem: GuestRam, audio_server: AudioStreamSource) -> Self {
|
||||
Ac97BusMaster {
|
||||
mem,
|
||||
regs: Arc::new(Mutex::new(Ac97BusMasterRegs::new())),
|
||||
acc_sema: 0,
|
||||
|
||||
po_info: AudioThreadInfo::new(),
|
||||
pi_info: AudioThreadInfo::new(),
|
||||
pmic_info: AudioThreadInfo::new(),
|
||||
audio_server,
|
||||
|
||||
irq_resample_thread: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn regs(&self) -> MutexGuard<Ac97BusMasterRegs> {
|
||||
self.regs.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Provides the events needed to raise interrupts in the guest.
|
||||
pub fn set_irq_event(&mut self, irq_evt: IrqLevelEvent) {
|
||||
let thread_regs = self.regs.clone();
|
||||
self.regs().irq_evt = Some(irq_evt.try_clone().expect("cloning irq_evt failed"));
|
||||
|
||||
self.irq_resample_thread = Some(thread::spawn(move || {
|
||||
loop {
|
||||
if let Err(e) = irq_evt.wait_resample() {
|
||||
warn!(
|
||||
"Failed to read the irq event from the resample thread: {}.",
|
||||
e,
|
||||
);
|
||||
break;
|
||||
}
|
||||
{
|
||||
// Scope for the lock on thread_regs.
|
||||
let regs = thread_regs.lock().unwrap();
|
||||
if regs.has_irq() {
|
||||
if let Err(e) = irq_evt.trigger() {
|
||||
warn!("Failed to set the irq from the resample thread: {}.", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// Called when `mixer` has been changed and the new values should be applied to currently
|
||||
/// active streams.
|
||||
pub fn update_mixer_settings(&mut self, mixer: &Ac97Mixer) {
|
||||
if let Some(control) = self.po_info.stream_control.as_mut() {
|
||||
// The audio server only supports one volume, not separate left and right.
|
||||
let (muted, left_volume, _right_volume) = mixer.get_master_volume();
|
||||
control.set_volume(left_volume);
|
||||
control.set_mute(muted);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the bus master is in the cold reset state.
|
||||
pub fn is_cold_reset(&self) -> bool {
|
||||
self.regs().glob_cnt & GLOB_CNT_COLD_RESET == 0
|
||||
}
|
||||
|
||||
/// Reads a byte from the given `offset`.
|
||||
pub fn readb(&mut self, offset: u64) -> u8 {
|
||||
fn readb_func_regs(func_regs: &Ac97FunctionRegs, offset: u64) -> u8 {
|
||||
let result = match offset {
|
||||
CIV_OFFSET => func_regs.civ,
|
||||
LVI_OFFSET => func_regs.lvi,
|
||||
SR_OFFSET => func_regs.sr as u8,
|
||||
PIV_OFFSET => func_regs.piv,
|
||||
CR_OFFSET => func_regs.cr,
|
||||
_ => 0,
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
let regs = self.regs();
|
||||
match offset {
|
||||
PI_BASE_00..=PI_CR_0B => readb_func_regs(®s.pi_regs, offset - PI_BASE_00),
|
||||
PO_BASE_10..=PO_CR_1B => readb_func_regs(®s.po_regs, offset - PO_BASE_10),
|
||||
MC_BASE_20..=MC_CR_2B => readb_func_regs(®s.mc_regs, offset - MC_BASE_20),
|
||||
ACC_SEMA_34 => self.acc_sema,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a word from the given `offset`.
|
||||
pub fn readw(&mut self, offset: u64, mixer: &Ac97Mixer) -> u16 {
|
||||
let regs = self.regs();
|
||||
match offset {
|
||||
PI_SR_06 => regs.pi_regs.sr,
|
||||
PI_PICB_08 => regs.pi_regs.picb,
|
||||
PO_SR_16 => regs.po_regs.sr,
|
||||
PO_PICB_18 => {
|
||||
// PO PICB
|
||||
if !self.thread_info(Ac97Function::Output).is_running() {
|
||||
// Not running, no need to estimate what has been consumed.
|
||||
regs.po_regs.picb
|
||||
} else {
|
||||
// Estimate how many samples have been played since the last audio callback.
|
||||
let num_channels = regs.tube_count(Ac97Function::Output) as u64;
|
||||
let micros = regs.po_pointer_update_time.elapsed().subsec_micros();
|
||||
// Round down to the next 10 millisecond boundary. The linux driver often
|
||||
// assumes that two rapid reads from picb will return the same value.
|
||||
let millis = micros / 1000 / 10 * 10;
|
||||
let sample_rate = self.current_sample_rate(Ac97Function::Output, mixer);
|
||||
let frames_consumed = sample_rate as u64 * u64::from(millis) / 1000;
|
||||
|
||||
regs.po_regs
|
||||
.picb
|
||||
.saturating_sub((num_channels * frames_consumed) as u16)
|
||||
}
|
||||
}
|
||||
MC_SR_26 => regs.mc_regs.sr,
|
||||
MC_PICB_28 => regs.mc_regs.picb,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a 32-bit word from the given `offset`.
|
||||
pub fn readl(&mut self, offset: u64) -> u32 {
|
||||
let regs = self.regs();
|
||||
let result = match offset {
|
||||
PI_BDBAR_00 => regs.pi_regs.bdbar,
|
||||
PI_CIV_04 => regs.pi_regs.atomic_status_regs(),
|
||||
PO_BDBAR_10 => regs.po_regs.bdbar,
|
||||
PO_CIV_14 => regs.po_regs.atomic_status_regs(),
|
||||
MC_BDBAR_20 => regs.mc_regs.bdbar,
|
||||
MC_CIV_24 => regs.mc_regs.atomic_status_regs(),
|
||||
GLOB_CNT_2C => regs.glob_cnt,
|
||||
GLOB_STA_30 => regs.glob_sta,
|
||||
_ => 0,
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
/// Writes the byte `val` to the register specified by `offset`.
|
||||
pub fn writeb(&mut self, offset: u64, val: u8, mixer: &Ac97Mixer) {
|
||||
// Only process writes to the control register when cold reset is set.
|
||||
if self.is_cold_reset() {
|
||||
info!("Ignoring control register write at offset {:02x} due to cold reset status");
|
||||
return;
|
||||
}
|
||||
|
||||
match offset {
|
||||
PI_CIV_04 => (), // RO
|
||||
PI_LVI_05 => self.set_lvi(Ac97Function::Input, val),
|
||||
PI_SR_06 => self.set_sr(Ac97Function::Input, u16::from(val)),
|
||||
PI_PIV_0A => (), // RO
|
||||
PI_CR_0B => self.set_cr(Ac97Function::Input, val, mixer),
|
||||
PO_CIV_14 => (), // RO
|
||||
PO_LVI_15 => self.set_lvi(Ac97Function::Output, val),
|
||||
PO_SR_16 => self.set_sr(Ac97Function::Output, u16::from(val)),
|
||||
PO_PIV_1A => (), // RO
|
||||
PO_CR_1B => self.set_cr(Ac97Function::Output, val, mixer),
|
||||
MC_CIV_24 => (), // RO
|
||||
MC_LVI_25 => self.set_lvi(Ac97Function::Microphone, val),
|
||||
MC_SR_26 => self.set_sr(Ac97Function::Microphone, u16::from(val)),
|
||||
MC_PIV_2A => (), // RO
|
||||
MC_CR_2B => self.set_cr(Ac97Function::Microphone, val, mixer),
|
||||
ACC_SEMA_34 => self.acc_sema = val,
|
||||
o => warn!("AC97: write byte to 0x{:x}", o),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the word `val` to the register specified by `offset`.
|
||||
pub fn writew(&mut self, offset: u64, val: u16) {
|
||||
// Only process writes to the control register when cold reset is set.
|
||||
if self.is_cold_reset() {
|
||||
return;
|
||||
}
|
||||
match offset {
|
||||
PI_SR_06 => self.set_sr(Ac97Function::Input, val),
|
||||
PI_PICB_08 => (), // RO
|
||||
PO_SR_16 => self.set_sr(Ac97Function::Output, val),
|
||||
PO_PICB_18 => (), // RO
|
||||
MC_SR_26 => self.set_sr(Ac97Function::Microphone, val),
|
||||
MC_PICB_28 => (), // RO
|
||||
o => warn!("AC97: write word to 0x{:x}", o),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the 32-bit `val` to the register specified by `offset`.
|
||||
pub fn writel(&mut self, offset: u64, val: u32, mixer: &mut Ac97Mixer) {
|
||||
// Only process writes to the control register when cold reset is set.
|
||||
if self.is_cold_reset() && offset != 0x2c {
|
||||
return;
|
||||
}
|
||||
match offset {
|
||||
PI_BDBAR_00 => self.set_bdbar(Ac97Function::Input, val),
|
||||
PO_BDBAR_10 => self.set_bdbar(Ac97Function::Output, val),
|
||||
MC_BDBAR_20 => self.set_bdbar(Ac97Function::Microphone, val),
|
||||
GLOB_CNT_2C => self.set_glob_cnt(val, mixer),
|
||||
GLOB_STA_30 => (), // RO
|
||||
o => warn!("AC97: write long to 0x{:x}", o),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_bdbar(&mut self, func: Ac97Function, val: u32) {
|
||||
self.regs().func_regs_mut(func).bdbar = val & !0x07;
|
||||
}
|
||||
|
||||
fn set_lvi(&mut self, func: Ac97Function, val: u8) {
|
||||
let mut regs = self.regs();
|
||||
let func_regs = regs.func_regs_mut(func);
|
||||
func_regs.lvi = val % 32; // LVI wraps at 32.
|
||||
|
||||
// If running and stalled waiting for more valid buffers, restart by clearing the "DMA
|
||||
// stopped" bit.
|
||||
if func_regs.cr & CR_RPBM == CR_RPBM
|
||||
&& func_regs.sr & SR_DCH == SR_DCH
|
||||
&& func_regs.civ != func_regs.lvi
|
||||
{
|
||||
Ac97BusMaster::check_and_move_to_next_buffer(func_regs);
|
||||
|
||||
func_regs.sr &= !(SR_DCH | SR_CELV);
|
||||
|
||||
self.thread_semaphore_notify(func);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_sr(&mut self, func: Ac97Function, val: u16) {
|
||||
let mut sr = self.regs().func_regs(func).sr;
|
||||
if val & SR_FIFOE != 0 {
|
||||
sr &= !SR_FIFOE;
|
||||
}
|
||||
if val & SR_LVBCI != 0 {
|
||||
sr &= !SR_LVBCI;
|
||||
}
|
||||
if val & SR_BCIS != 0 {
|
||||
sr &= !SR_BCIS;
|
||||
}
|
||||
let mut regs = self.regs();
|
||||
update_sr(&mut regs, func, sr);
|
||||
}
|
||||
|
||||
fn set_cr(&mut self, func: Ac97Function, val: u8, mixer: &Ac97Mixer) {
|
||||
if val & CR_RR != 0 {
|
||||
let mut regs = self.regs();
|
||||
Self::reset_func_regs(&mut regs, func);
|
||||
} else {
|
||||
let cr = self.regs().func_regs(func).cr;
|
||||
if val & CR_RPBM == 0 {
|
||||
// Run/Pause set to pause.
|
||||
self.thread_info_mut(func).stop();
|
||||
let mut regs = self.regs();
|
||||
regs.func_regs_mut(func).sr |= SR_DCH;
|
||||
} else if cr & CR_RPBM == 0 {
|
||||
// Not already running.
|
||||
// Run/Pause set to run.
|
||||
{
|
||||
let mut regs = self.regs();
|
||||
let func_regs = regs.func_regs_mut(func);
|
||||
func_regs.piv = 1;
|
||||
func_regs.civ = 0;
|
||||
func_regs.sr &= !SR_DCH;
|
||||
}
|
||||
if let Err(e) = self.start_audio(func, mixer) {
|
||||
warn!("Failed to start audio: {}", e);
|
||||
}
|
||||
}
|
||||
let mut regs = self.regs();
|
||||
regs.func_regs_mut(func).cr = val & CR_VALID_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_glob_cnt(&mut self, new_glob_cnt: u32, mixer: &mut Ac97Mixer) {
|
||||
// Only the reset bits are emulated, the GPI and PCM formatting are not supported.
|
||||
if new_glob_cnt & GLOB_CNT_COLD_RESET == 0 {
|
||||
self.reset_audio_regs();
|
||||
mixer.reset();
|
||||
self.regs().glob_cnt = new_glob_cnt & GLOB_CNT_STABLE_BITS;
|
||||
self.acc_sema = 0;
|
||||
return;
|
||||
}
|
||||
if new_glob_cnt & GLOB_CNT_WARM_RESET != 0 {
|
||||
// Check if running and if so, ignore. Warm reset is specified to no-op when the device
|
||||
// is playing or recording audio.
|
||||
if !self.is_audio_running() {
|
||||
self.stop_all_audio();
|
||||
let mut regs = self.regs();
|
||||
regs.glob_cnt = new_glob_cnt & !GLOB_CNT_WARM_RESET; // Auto-cleared reset bit.
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.regs().glob_cnt = new_glob_cnt;
|
||||
}
|
||||
|
||||
fn current_sample_rate(&self, func: Ac97Function, mixer: &Ac97Mixer) -> u32 {
|
||||
match func {
|
||||
Ac97Function::Output => mixer.get_sample_rate().into(),
|
||||
_ => INPUT_SAMPLE_RATE,
|
||||
}
|
||||
}
|
||||
|
||||
fn thread_info(&self, func: Ac97Function) -> &AudioThreadInfo {
|
||||
match func {
|
||||
Ac97Function::Microphone => &self.pmic_info,
|
||||
Ac97Function::Input => &self.pi_info,
|
||||
Ac97Function::Output => &self.po_info,
|
||||
}
|
||||
}
|
||||
|
||||
fn thread_info_mut(&mut self, func: Ac97Function) -> &mut AudioThreadInfo {
|
||||
match func {
|
||||
Ac97Function::Microphone => &mut self.pmic_info,
|
||||
Ac97Function::Input => &mut self.pi_info,
|
||||
Ac97Function::Output => &mut self.po_info,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_audio_running(&self) -> bool {
|
||||
self.thread_info(Ac97Function::Output).is_running()
|
||||
|| self.thread_info(Ac97Function::Input).is_running()
|
||||
|| self.thread_info(Ac97Function::Microphone).is_running()
|
||||
}
|
||||
|
||||
fn stop_all_audio(&mut self) {
|
||||
self.thread_info_mut(Ac97Function::Input).stop();
|
||||
self.thread_info_mut(Ac97Function::Output).stop();
|
||||
self.thread_info_mut(Ac97Function::Microphone).stop();
|
||||
}
|
||||
|
||||
// Helper function for resetting function registers.
|
||||
fn reset_func_regs(regs: &mut Ac97BusMasterRegs, func: Ac97Function) {
|
||||
regs.func_regs_mut(func).do_reset();
|
||||
update_sr(regs, func, SR_DCH);
|
||||
}
|
||||
|
||||
fn reset_audio_regs(&mut self) {
|
||||
self.stop_all_audio();
|
||||
let mut regs = self.regs();
|
||||
Self::reset_func_regs(&mut regs, Ac97Function::Input);
|
||||
Self::reset_func_regs(&mut regs, Ac97Function::Output);
|
||||
Self::reset_func_regs(&mut regs, Ac97Function::Microphone);
|
||||
}
|
||||
|
||||
fn check_and_move_to_next_buffer(func_regs: &mut Ac97FunctionRegs) {
|
||||
if func_regs.sr & SR_CELV != 0 {
|
||||
// CELV means we'd already processed the buffer at CIV.
|
||||
// Move CIV to the next buffer now that LVI has moved.
|
||||
func_regs.move_to_next_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
fn thread_semaphore_notify(&self, func: Ac97Function) {
|
||||
match func {
|
||||
Ac97Function::Input => self.pi_info.thread_semaphore.notify_one(),
|
||||
Ac97Function::Output => self.po_info.thread_semaphore.notify_one(),
|
||||
Ac97Function::Microphone => self.pmic_info.thread_semaphore.notify_one(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_audio_worker(&mut self, mixer: &Ac97Mixer, func: Ac97Function) -> AudioResult<AudioWorker> {
|
||||
info!("AC97: create_audio_worker({:?})", func);
|
||||
let direction = match func {
|
||||
Ac97Function::Microphone => StreamDirection::Capture,
|
||||
Ac97Function::Input => StreamDirection::Capture,
|
||||
Ac97Function::Output => StreamDirection::Playback,
|
||||
};
|
||||
|
||||
let locked_regs = self.regs.lock().unwrap();
|
||||
let sample_rate = self.current_sample_rate(func, mixer);
|
||||
let buffer_samples = current_buffer_size(locked_regs.func_regs(func), &self.mem)?;
|
||||
let num_channels = locked_regs.tube_count(func);
|
||||
let buffer_frames = buffer_samples / num_channels;
|
||||
|
||||
let pending_buffers = VecDeque::with_capacity(2);
|
||||
|
||||
let stream = self
|
||||
.audio_server
|
||||
.new_stream(
|
||||
direction,
|
||||
num_channels,
|
||||
SampleFormat::S16LE,
|
||||
sample_rate,
|
||||
buffer_frames)
|
||||
.map_err(AudioError::CreateStream)?;
|
||||
|
||||
let params = AudioWorkerParams {
|
||||
func,
|
||||
stream,
|
||||
pending_buffers,
|
||||
message_interval: Duration::from_secs_f64(buffer_frames as f64 / sample_rate as f64),
|
||||
};
|
||||
Ok(AudioWorker::new(self, params))
|
||||
}
|
||||
|
||||
fn start_audio(&mut self, func: Ac97Function, mixer: &Ac97Mixer) -> AudioResult<()> {
|
||||
let audio_worker = self.create_audio_worker(mixer, func)?;
|
||||
self.thread_info_mut(func).start(audio_worker);
|
||||
self.update_mixer_settings(mixer);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_buffer_samples(
|
||||
func_regs: &Ac97FunctionRegs,
|
||||
mem: &GuestRam,
|
||||
index: u8,
|
||||
) -> GuestMemoryResult<usize> {
|
||||
let descriptor_addr = func_regs.bdbar + u32::from(index) * DESCRIPTOR_LENGTH as u32;
|
||||
let control_reg: u32 = mem
|
||||
.read_int(u64::from(descriptor_addr) + 4)
|
||||
.map_err(GuestMemoryError::ReadingGuestBufferAddress)?;
|
||||
let buffer_samples = control_reg as usize & 0x0000_ffff;
|
||||
Ok(buffer_samples)
|
||||
}
|
||||
|
||||
// Marks the current buffer completed and moves to the next buffer for the given
|
||||
// function and registers.
|
||||
fn buffer_completed(
|
||||
regs: &mut Ac97BusMasterRegs,
|
||||
mem: &GuestRam,
|
||||
func: Ac97Function,
|
||||
) -> AudioResult<()> {
|
||||
// check if the completed descriptor wanted an interrupt on completion.
|
||||
let civ = regs.func_regs(func).civ;
|
||||
let descriptor_addr = regs.func_regs(func).bdbar + u32::from(civ) * DESCRIPTOR_LENGTH as u32;
|
||||
let control_reg: u32 = mem
|
||||
.read_int(u64::from(descriptor_addr) + 4)
|
||||
.map_err(GuestMemoryError::ReadingGuestBufferAddress)?;
|
||||
|
||||
let mut new_sr = regs.func_regs(func).sr & !SR_CELV;
|
||||
if control_reg & BD_IOC != 0 {
|
||||
new_sr |= SR_BCIS;
|
||||
}
|
||||
|
||||
let lvi = regs.func_regs(func).lvi;
|
||||
// if the current buffer was the last valid buffer, then update the status register to
|
||||
// indicate that the end of audio was hit and possibly raise an interrupt.
|
||||
if civ == lvi {
|
||||
new_sr |= SR_DCH | SR_CELV | SR_LVBCI;
|
||||
} else {
|
||||
regs.func_regs_mut(func).move_to_next_buffer();
|
||||
}
|
||||
|
||||
update_sr(regs, func, new_sr);
|
||||
|
||||
regs.func_regs_mut(func).picb = current_buffer_size(regs.func_regs(func), mem)? as u16;
|
||||
if func == Ac97Function::Output {
|
||||
regs.po_pointer_update_time = Instant::now();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Update the status register and if any interrupts need to fire, raise them.
|
||||
fn update_sr(regs: &mut Ac97BusMasterRegs, func: Ac97Function, val: u16) {
|
||||
let int_mask = match func {
|
||||
Ac97Function::Input => GS_PIINT,
|
||||
Ac97Function::Output => GS_POINT,
|
||||
Ac97Function::Microphone => GS_MINT,
|
||||
};
|
||||
|
||||
let mut interrupt_high = false;
|
||||
|
||||
{
|
||||
let func_regs = regs.func_regs_mut(func);
|
||||
let old_sr = func_regs.sr;
|
||||
func_regs.sr = val;
|
||||
if (old_sr ^ val) & SR_INT_MASK != 0 {
|
||||
if (val & SR_LVBCI) != 0 && (func_regs.cr & CR_LVBIE) != 0 {
|
||||
interrupt_high = true;
|
||||
}
|
||||
if (val & SR_BCIS) != 0 && (func_regs.cr & CR_IOCE) != 0 {
|
||||
interrupt_high = true;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if interrupt_high {
|
||||
regs.glob_sta |= int_mask;
|
||||
if let Some(ref irq_evt) = regs.irq_evt {
|
||||
// Ignore write failure, nothing can be done about it from here.
|
||||
let _ = irq_evt.trigger();
|
||||
} else {
|
||||
info!("AC97: No interrupt! uh oh");
|
||||
}
|
||||
} else {
|
||||
regs.glob_sta &= !int_mask;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the size in samples of the buffer pointed to by the CIV register.
|
||||
fn current_buffer_size(
|
||||
func_regs: &Ac97FunctionRegs,
|
||||
mem: &GuestRam,
|
||||
) -> GuestMemoryResult<usize> {
|
||||
let civ = func_regs.civ;
|
||||
get_buffer_samples(func_regs, mem, civ)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct GuestBuffer {
|
||||
index: u8,
|
||||
address: u64,
|
||||
samples: usize,
|
||||
channels: usize,
|
||||
consumed_frames: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl GuestBuffer {
|
||||
fn new(index: u8, address: u64, samples: usize, channels: usize) -> Self {
|
||||
GuestBuffer {
|
||||
index,
|
||||
address,
|
||||
samples,
|
||||
channels,
|
||||
consumed_frames: Arc::new(AtomicUsize::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_address(&self, frame_size: usize) -> u64 {
|
||||
self.address + (self.consumed_frames() * frame_size) as u64
|
||||
}
|
||||
|
||||
fn frames(&self) -> usize {
|
||||
self.samples / self.channels
|
||||
}
|
||||
|
||||
fn add_consumed(&self, frames: usize) {
|
||||
self.consumed_frames.fetch_add(frames, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn consumed_frames(&self) -> usize {
|
||||
self.consumed_frames.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn remaining_frames(&self) -> usize {
|
||||
self.frames() - self.consumed_frames()
|
||||
}
|
||||
|
||||
fn is_consumed(&self) -> bool {
|
||||
self.consumed_frames() >= self.frames()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for GuestBuffer {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "GuestBuffer([{}]@0x{:08x}, [{} of {} frames remaining])", self.index, self.address, self.remaining_frames(), self.frames())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_buffer_address(
|
||||
func_regs: &Ac97FunctionRegs,
|
||||
mem: &GuestRam,
|
||||
index: u8,
|
||||
) -> GuestMemoryResult<u64> {
|
||||
let descriptor_addr = func_regs.bdbar + u32::from(index) * DESCRIPTOR_LENGTH as u32;
|
||||
let buffer_addr_reg: u32 = mem
|
||||
.read_int(u64::from(descriptor_addr))
|
||||
.map_err(GuestMemoryError::ReadingGuestBufferAddress)?;
|
||||
let buffer_addr = (buffer_addr_reg & !0x03u32) as u64; // The address must be aligned to four bytes.
|
||||
Ok(buffer_addr)
|
||||
}
|
||||
|
||||
// Gets the start address and length of the buffer at `civ + offset` from the
|
||||
// guest.
|
||||
// This will return `None` if `civ + offset` is past LVI; if the DMA controlled
|
||||
// stopped bit is set, such as after an underrun where CIV hits LVI; or if
|
||||
// `civ + offset == LVI and the CELV flag is set.
|
||||
fn next_guest_buffer(
|
||||
regs: &Ac97BusMasterRegs,
|
||||
mem: &GuestRam,
|
||||
func: Ac97Function,
|
||||
offset: usize,
|
||||
) -> AudioResult<Option<GuestBuffer>> {
|
||||
let func_regs = regs.func_regs(func);
|
||||
let offset = (offset % 32) as u8;
|
||||
let index = (func_regs.civ + offset) % 32;
|
||||
|
||||
// Check that value is between `low` and `high` modulo some `n`.
|
||||
fn check_between(low: u8, high: u8, value: u8) -> bool {
|
||||
// If low <= high, value must be in the interval between them:
|
||||
// 0 l h n
|
||||
// ......+++++++......
|
||||
(low <= high && (low <= value && value <= high)) ||
|
||||
// If low > high, value must not be in the interval between them:
|
||||
// 0 h l n
|
||||
// +++++++++......++++
|
||||
(low > high && (low <= value || value <= high))
|
||||
}
|
||||
|
||||
// Check if
|
||||
// * we're halted
|
||||
// * `index` is not between CIV and LVI (mod 32)
|
||||
// * `index is LVI and we've already processed LVI (SR_CELV is set)
|
||||
// if any of these are true `index` isn't valid.
|
||||
if func_regs.sr & SR_DCH != 0
|
||||
|| !check_between(func_regs.civ, func_regs.lvi, index)
|
||||
|| func_regs.sr & SR_CELV != 0
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let address = get_buffer_address(func_regs, mem, index)?
|
||||
.try_into()
|
||||
.map_err(|_| AudioError::InvalidBufferOffset)?;
|
||||
|
||||
let samples = get_buffer_samples(func_regs, mem, index)?;
|
||||
let channels = regs.tube_count(func);
|
||||
|
||||
Ok(Some(GuestBuffer::new(index, address, samples, channels)))
|
||||
}
|
||||
|
||||
// Runs and updates the offset within the stream shm where samples can be
|
||||
// found/placed for shm playback/capture streams, respectively
|
||||
struct AudioWorker {
|
||||
func: Ac97Function,
|
||||
regs: Arc<Mutex<Ac97BusMasterRegs>>,
|
||||
mem: GuestRam,
|
||||
thread_run: Arc<AtomicBool>,
|
||||
lvi_semaphore: Arc<Condvar>,
|
||||
message_interval: Duration,
|
||||
stream: Box<dyn ShmStream>,
|
||||
pending_buffers: Arc<Mutex<VecDeque<Option<GuestBuffer>>>>,
|
||||
}
|
||||
|
||||
struct AudioWorkerParams {
|
||||
func: Ac97Function,
|
||||
stream: Box<dyn ShmStream>,
|
||||
pending_buffers: VecDeque<Option<GuestBuffer>>,
|
||||
message_interval: Duration,
|
||||
}
|
||||
|
||||
impl AudioWorker {
|
||||
fn new(bus_master: &Ac97BusMaster, args: AudioWorkerParams) -> Self {
|
||||
Self {
|
||||
func: args.func,
|
||||
regs: bus_master.regs.clone(),
|
||||
mem: bus_master.mem.clone(),
|
||||
thread_run: bus_master.thread_info(args.func).thread_run.clone(),
|
||||
lvi_semaphore: bus_master.thread_info(args.func).thread_semaphore.clone(),
|
||||
message_interval: args.message_interval,
|
||||
stream: args.stream,
|
||||
pending_buffers: Arc::new( Mutex::new(args.pending_buffers)),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_guest_buffer(&self) -> AudioResult<Option<GuestBuffer>> {
|
||||
let mut pending = self.pending_buffers.lock().unwrap();
|
||||
if let Some(Some(front_buffer)) = pending.front() {
|
||||
if !front_buffer.is_consumed() {
|
||||
return Ok(Some(front_buffer.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let mut locked_regs = self.regs.lock().unwrap();
|
||||
if pending.len() == 2 {
|
||||
// When we have two pending buffers and receive a request for
|
||||
// another, we know that oldest buffer has been completed.
|
||||
// However, if that old buffer was an empty buffer we sent
|
||||
// because the guest driver had no available buffers, we don't
|
||||
// want to mark a buffer complete.
|
||||
if let Some(Some(_)) = pending.pop_front() {
|
||||
buffer_completed(&mut locked_regs, &self.mem, self.func)?;
|
||||
if let Some(Some(front_buffer)) = pending.front() {
|
||||
if !front_buffer.is_consumed() {
|
||||
return Ok(Some(front_buffer.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We count the number of pending, real buffers at the server, and
|
||||
// then use that as our offset from CIV.
|
||||
let offset = pending.iter().filter(|e| e.is_some()).count();
|
||||
|
||||
// Get a buffer to respond to our request. If there's no buffer
|
||||
// available, we'll wait one buffer interval and check again.
|
||||
let buffer = loop {
|
||||
if let Some(buffer) = next_guest_buffer(&locked_regs, &self.mem, self.func, offset)?
|
||||
{
|
||||
break Some(buffer);
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
if elapsed > self.message_interval {
|
||||
break None;
|
||||
}
|
||||
locked_regs = self
|
||||
.lvi_semaphore
|
||||
.wait_timeout(locked_regs, self.message_interval - elapsed)
|
||||
.unwrap()
|
||||
.0;
|
||||
};
|
||||
pending.push_back(buffer.clone());
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
// Runs and updates the offset within the stream shm where samples can be
|
||||
// found/placed for shm playback/capture streams, respectively
|
||||
fn run(&mut self) -> AudioResult<()> {
|
||||
let func = self.func;
|
||||
// Set up picb.
|
||||
{
|
||||
let mut locked_regs = self.regs.lock().unwrap();
|
||||
locked_regs.func_regs_mut(func).picb =
|
||||
current_buffer_size(locked_regs.func_regs(func), &self.mem)? as u16;
|
||||
}
|
||||
|
||||
'audio_loop: while self.thread_run.load(Ordering::Relaxed) {
|
||||
{
|
||||
let mut locked_regs = self.regs.lock().unwrap();
|
||||
while locked_regs.func_regs(func).sr & SR_DCH != 0 {
|
||||
locked_regs = self.lvi_semaphore.wait(locked_regs).unwrap();
|
||||
if !self.thread_run.load(Ordering::Relaxed) {
|
||||
break 'audio_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let timeout = Duration::from_secs(1);
|
||||
let action = self
|
||||
.stream
|
||||
.wait_for_next_action_with_timeout(timeout)
|
||||
.map_err(AudioError::WaitForAction)?;
|
||||
|
||||
let request = match action {
|
||||
None => {
|
||||
warn!("No audio message received within timeout of {:?}", timeout);
|
||||
continue;
|
||||
}
|
||||
Some(request) => request,
|
||||
};
|
||||
|
||||
match self.next_guest_buffer()? {
|
||||
None => request.ignore_request().map_err(AudioError::RespondRequest)?,
|
||||
Some(buffer) => {
|
||||
|
||||
let addr = buffer.start_address(self.stream.frame_size());
|
||||
|
||||
let nframes = cmp::min(request.requested_frames(), buffer.remaining_frames());
|
||||
|
||||
buffer.add_consumed(nframes);
|
||||
request.set_buffer_address_and_frames(addr, nframes)
|
||||
.map_err(AudioError::RespondRequest)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
200
src/devices/ac97/ac97_mixer.rs
Normal file
200
src/devices/ac97/ac97_mixer.rs
Normal file
@ -0,0 +1,200 @@
|
||||
// Copyright 2018 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
|
||||
use crate::devices::ac97::ac97_regs::*;
|
||||
|
||||
// Extented Audio ID
|
||||
const AC97_EXTENDED_ID: u16 = MIXER_EI_VRA | MIXER_EI_CDAC | MIXER_EI_SDAC | MIXER_EI_LDAC;
|
||||
const PCI_VENDOR_ID_INTEL: u16 = 0x8086;
|
||||
|
||||
// Master volume register is specified in 1.5dB steps.
|
||||
const MASTER_VOLUME_STEP_DB: f64 = 1.5;
|
||||
|
||||
/// `Ac97Mixer` holds the mixer state for the AC97 bus.
|
||||
/// The mixer is used by calling the `readb`/`readw`/`readl` functions to read register values and
|
||||
/// the `writeb`/`writew`/`writel` functions to set register values.
|
||||
pub struct Ac97Mixer {
|
||||
// Mixer Registers
|
||||
master_volume_l: u8,
|
||||
master_volume_r: u8,
|
||||
master_mute: bool,
|
||||
mic_muted: bool,
|
||||
mic_20db: bool,
|
||||
mic_volume: u8,
|
||||
record_gain_l: u8,
|
||||
record_gain_r: u8,
|
||||
record_gain_mute: bool,
|
||||
pcm_out_vol_l: u16,
|
||||
pcm_out_vol_r: u16,
|
||||
pcm_out_mute: bool,
|
||||
power_down_control: u16,
|
||||
ext_audio_status_ctl: u16,
|
||||
pcm_front_dac_rate: u16,
|
||||
pcm_surr_dac_rate: u16,
|
||||
pcm_lfe_dac_rate: u16,
|
||||
}
|
||||
|
||||
impl Ac97Mixer {
|
||||
/// Creates an 'Ac97Mixer' with the standard default register values.
|
||||
pub fn new() -> Self {
|
||||
Ac97Mixer {
|
||||
master_volume_l: 0,
|
||||
master_volume_r: 0,
|
||||
master_mute: true,
|
||||
mic_muted: true,
|
||||
mic_20db: false,
|
||||
mic_volume: 0x8,
|
||||
record_gain_l: 0,
|
||||
record_gain_r: 0,
|
||||
record_gain_mute: true,
|
||||
pcm_out_vol_l: 0x8,
|
||||
pcm_out_vol_r: 0x8,
|
||||
pcm_out_mute: true,
|
||||
power_down_control: PD_REG_STATUS_MASK, // Report everything is ready.
|
||||
ext_audio_status_ctl: 0,
|
||||
// Default to 48 kHz.
|
||||
pcm_front_dac_rate: 0xBB80,
|
||||
pcm_surr_dac_rate: 0xBB80,
|
||||
pcm_lfe_dac_rate: 0xBB80,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
// Upon reset, the audio sample rate registers default to 48 kHz, and VRA=0.
|
||||
self.ext_audio_status_ctl &= !MIXER_EI_VRA;
|
||||
self.pcm_front_dac_rate = 0xBB80;
|
||||
self.pcm_surr_dac_rate = 0xBB80;
|
||||
self.pcm_lfe_dac_rate = 0xBB80;
|
||||
}
|
||||
|
||||
/// Reads a word from the register at `offset`.
|
||||
pub fn readw(&self, offset: u64) -> u16 {
|
||||
match offset {
|
||||
MIXER_RESET_00 => BC_DEDICATED_MIC,
|
||||
MIXER_MASTER_VOL_MUTE_02 => self.get_master_reg(),
|
||||
MIXER_MIC_VOL_MUTE_0E => self.get_mic_volume(),
|
||||
MIXER_PCM_OUT_VOL_MUTE_18 => self.get_pcm_out_volume(),
|
||||
MIXER_REC_VOL_MUTE_1C => self.get_record_gain_reg(),
|
||||
MIXER_POWER_DOWN_CONTROL_26 => self.power_down_control,
|
||||
MIXER_EXTENDED_AUDIO_ID_28 => AC97_EXTENDED_ID,
|
||||
MIXER_VENDOR_ID1_7C => PCI_VENDOR_ID_INTEL,
|
||||
MIXER_VENDOR_ID2_7E => PCI_VENDOR_ID_INTEL,
|
||||
MIXER_EXTENDED_AUDIO_STATUS_CONTROL_28 => self.ext_audio_status_ctl,
|
||||
MIXER_PCM_FRONT_DAC_RATE_2C => self.pcm_front_dac_rate,
|
||||
MIXER_PCM_SURR_DAC_RATE_2E => self.pcm_surr_dac_rate,
|
||||
MIXER_PCM_LFE_DAC_RATE_30 => self.pcm_lfe_dac_rate,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a word `val` to the register `offset`.
|
||||
pub fn writew(&mut self, offset: u64, val: u16) {
|
||||
match offset {
|
||||
MIXER_RESET_00 => self.reset(),
|
||||
MIXER_MASTER_VOL_MUTE_02 => self.set_master_reg(val),
|
||||
MIXER_MIC_VOL_MUTE_0E => self.set_mic_volume(val),
|
||||
MIXER_PCM_OUT_VOL_MUTE_18 => self.set_pcm_out_volume(val),
|
||||
MIXER_REC_VOL_MUTE_1C => self.set_record_gain_reg(val),
|
||||
MIXER_POWER_DOWN_CONTROL_26 => self.set_power_down_reg(val),
|
||||
MIXER_EXTENDED_AUDIO_STATUS_CONTROL_28 => self.ext_audio_status_ctl = val,
|
||||
MIXER_PCM_FRONT_DAC_RATE_2C => self.pcm_front_dac_rate = val,
|
||||
MIXER_PCM_SURR_DAC_RATE_2E => self.pcm_surr_dac_rate = val,
|
||||
MIXER_PCM_LFE_DAC_RATE_30 => self.pcm_lfe_dac_rate = val,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the mute status and left and right attenuation from the master volume register.
|
||||
pub fn get_master_volume(&self) -> (bool, f64, f64) {
|
||||
(
|
||||
self.master_mute,
|
||||
f64::from(self.master_volume_l) * MASTER_VOLUME_STEP_DB,
|
||||
f64::from(self.master_volume_r) * MASTER_VOLUME_STEP_DB,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the front sample rate (reg 0x2c).
|
||||
pub fn get_sample_rate(&self) -> u16 {
|
||||
// MIXER_PCM_FRONT_DAC_RATE_2C, MIXER_PCM_SURR_DAC_RATE_2E, and MIXER_PCM_LFE_DAC_RATE_30
|
||||
// are updated to the same rate when playback with 2,4 and 6 tubes.
|
||||
self.pcm_front_dac_rate
|
||||
}
|
||||
|
||||
// Returns the master mute and l/r volumes (reg 0x02).
|
||||
fn get_master_reg(&self) -> u16 {
|
||||
let reg = (u16::from(self.master_volume_l)) << 8 | u16::from(self.master_volume_r);
|
||||
if self.master_mute {
|
||||
reg | MUTE_REG_BIT
|
||||
} else {
|
||||
reg
|
||||
}
|
||||
}
|
||||
|
||||
// Handles writes to the master register (0x02).
|
||||
fn set_master_reg(&mut self, val: u16) {
|
||||
self.master_mute = val & MUTE_REG_BIT != 0;
|
||||
self.master_volume_r = (val & VOL_REG_MASK) as u8;
|
||||
self.master_volume_l = (val >> 8 & VOL_REG_MASK) as u8;
|
||||
}
|
||||
|
||||
// Returns the value read in the Mic volume register (0x0e).
|
||||
fn get_mic_volume(&self) -> u16 {
|
||||
let mut reg = u16::from(self.mic_volume);
|
||||
if self.mic_muted {
|
||||
reg |= MUTE_REG_BIT;
|
||||
}
|
||||
if self.mic_20db {
|
||||
reg |= MIXER_MIC_20DB;
|
||||
}
|
||||
reg
|
||||
}
|
||||
|
||||
// Sets the mic input mute, boost, and volume settings (0x0e).
|
||||
fn set_mic_volume(&mut self, val: u16) {
|
||||
self.mic_volume = (val & MIXER_VOL_MASK) as u8;
|
||||
self.mic_muted = val & MUTE_REG_BIT != 0;
|
||||
self.mic_20db = val & MIXER_MIC_20DB != 0;
|
||||
}
|
||||
|
||||
// Returns the value read in the Mic volume register (0x18).
|
||||
fn get_pcm_out_volume(&self) -> u16 {
|
||||
let reg = (self.pcm_out_vol_l as u16) << 8 | self.pcm_out_vol_r as u16;
|
||||
if self.pcm_out_mute {
|
||||
reg | MUTE_REG_BIT
|
||||
} else {
|
||||
reg
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the pcm output mute and volume states (0x18).
|
||||
fn set_pcm_out_volume(&mut self, val: u16) {
|
||||
self.pcm_out_vol_r = val & MIXER_VOL_MASK;
|
||||
self.pcm_out_vol_l = (val >> MIXER_VOL_LEFT_SHIFT) & MIXER_VOL_MASK;
|
||||
self.pcm_out_mute = val & MUTE_REG_BIT != 0;
|
||||
}
|
||||
|
||||
// Returns the record gain register (0x01c).
|
||||
fn get_record_gain_reg(&self) -> u16 {
|
||||
let reg = u16::from(self.record_gain_l) << 8 | u16::from(self.record_gain_r);
|
||||
if self.record_gain_mute {
|
||||
reg | MUTE_REG_BIT
|
||||
} else {
|
||||
reg
|
||||
}
|
||||
}
|
||||
|
||||
// Handles writes to the record_gain register (0x1c).
|
||||
fn set_record_gain_reg(&mut self, val: u16) {
|
||||
self.record_gain_mute = val & MUTE_REG_BIT != 0;
|
||||
self.record_gain_r = (val & VOL_REG_MASK) as u8;
|
||||
self.record_gain_l = (val >> 8 & VOL_REG_MASK) as u8;
|
||||
}
|
||||
|
||||
// Handles writes to the powerdown ctrl/status register (0x26).
|
||||
fn set_power_down_reg(&mut self, val: u16) {
|
||||
self.power_down_control =
|
||||
(val & !PD_REG_STATUS_MASK) | (self.power_down_control & PD_REG_STATUS_MASK);
|
||||
}
|
||||
}
|
292
src/devices/ac97/ac97_regs.rs
Normal file
292
src/devices/ac97/ac97_regs.rs
Normal file
@ -0,0 +1,292 @@
|
||||
// Copyright 2018 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
// Audio Mixer Registers
|
||||
// 00h Reset
|
||||
// 02h Master Volume Mute
|
||||
// 04h Headphone Volume Mute
|
||||
// 06h Master Volume Mono Mute
|
||||
// 08h Master Tone (R & L)
|
||||
// 0Ah PC_BEEP Volume Mute
|
||||
// 0Ch Phone Volume Mute
|
||||
// 0Eh Mic Volume Mute
|
||||
// 10h Line In Volume Mute
|
||||
// 12h CD Volume Mute
|
||||
// 14h Video Volume Mute
|
||||
// 16h Aux Volume Mute
|
||||
// 18h PCM Out Volume Mute
|
||||
// 1Ah Record Select
|
||||
// 1Ch Record Gain Mute
|
||||
// 1Eh Record Gain Mic Mute
|
||||
// 20h General Purpose
|
||||
// 22h 3D Control
|
||||
// 24h AC’97 RESERVED
|
||||
// 26h Powerdown Ctrl/Stat
|
||||
// 28h Extended Audio ID
|
||||
// 2Ah Extended Audio Status and Control
|
||||
// 2CH PCM Front DAC Rate
|
||||
// 2Eh PCM Surr DAC Rate
|
||||
// 30h PCM LFE DAC Rate
|
||||
// 32h PCM L/R ADC Rate
|
||||
// 34h PCM MIC ADC Rate
|
||||
|
||||
// Size of IO register regions
|
||||
pub const MIXER_REGS_SIZE: u64 = 0x400;
|
||||
pub const MASTER_REGS_SIZE: u64 = 0x100;
|
||||
|
||||
pub const MIXER_RESET_00: u64 = 0x00;
|
||||
pub const MIXER_MASTER_VOL_MUTE_02: u64 = 0x02;
|
||||
pub const MIXER_MIC_VOL_MUTE_0E: u64 = 0x0e;
|
||||
pub const MIXER_PCM_OUT_VOL_MUTE_18: u64 = 0x18;
|
||||
pub const MIXER_REC_VOL_MUTE_1C: u64 = 0x1c;
|
||||
pub const MIXER_POWER_DOWN_CONTROL_26: u64 = 0x26;
|
||||
pub const MIXER_EXTENDED_AUDIO_ID_28: u64 = 0x28;
|
||||
pub const MIXER_EXTENDED_AUDIO_STATUS_CONTROL_28: u64 = 0x2a;
|
||||
pub const MIXER_PCM_FRONT_DAC_RATE_2C: u64 = 0x2c;
|
||||
pub const MIXER_PCM_SURR_DAC_RATE_2E: u64 = 0x2e;
|
||||
pub const MIXER_PCM_LFE_DAC_RATE_30: u64 = 0x30;
|
||||
pub const MIXER_VENDOR_ID1_7C: u64 = 0x7c;
|
||||
pub const MIXER_VENDOR_ID2_7E: u64 = 0x7e;
|
||||
|
||||
// Extended Audio ID Bits.
|
||||
pub const MIXER_EI_VRA: u16 = 0x0001; // Variable Rate Audio mode is available.
|
||||
pub const MIXER_EI_CDAC: u16 = 0x0040; // PCM Center DAC is available.
|
||||
pub const MIXER_EI_SDAC: u16 = 0x0080; // PCM Surround DAC is available.
|
||||
pub const MIXER_EI_LDAC: u16 = 0x0100; // PCM LFE DAC is available.
|
||||
|
||||
// Basic capabilities for MIXER_RESET_00
|
||||
pub const BC_DEDICATED_MIC: u16 = 0x0001; /* Dedicated Mic PCM In Tube */
|
||||
|
||||
// Bus Master regs from ICH spec:
|
||||
// 00h PI_BDBAR PCM In Buffer Descriptor list Base Address Register
|
||||
// 04h PI_CIV PCM In Current Index Value
|
||||
// 05h PI_LVI PCM In Last Valid Index
|
||||
// 06h PI_SR PCM In Status Register
|
||||
// 08h PI_PICB PCM In Position In Current Buffer
|
||||
// 0Ah PI_PIV PCM In Prefetched Index Value
|
||||
// 0Bh PI_CR PCM In Control Register
|
||||
// 10h PO_BDBAR PCM Out Buffer Descriptor list Base Address Register
|
||||
// 14h PO_CIV PCM Out Current Index Value
|
||||
// 15h PO_LVI PCM Out Last Valid Index
|
||||
// 16h PO_SR PCM Out Status Register
|
||||
// 18h PO_PICB PCM Out Position In Current Buffer
|
||||
// 1Ah PO_PIV PCM Out Prefetched Index Value
|
||||
// 1Bh PO_CR PCM Out Control Register
|
||||
// 20h MC_BDBAR Mic. In Buffer Descriptor list Base Address Register
|
||||
// 24h PM_CIV Mic. In Current Index Value
|
||||
// 25h MC_LVI Mic. In Last Valid Index
|
||||
// 26h MC_SR Mic. In Status Register
|
||||
// 28h MC_PICB Mic In Position In Current Buffer
|
||||
// 2Ah MC_PIV Mic. In Prefetched Index Value
|
||||
// 2Bh MC_CR Mic. In Control Register
|
||||
// 2Ch GLOB_CNT Global Control
|
||||
// 30h GLOB_STA Global Status
|
||||
// 34h ACC_SEMA Codec Write Semaphore Register
|
||||
|
||||
// Global Control
|
||||
pub const GLOB_CNT_2C: u64 = 0x2C;
|
||||
pub const GLOB_CNT_COLD_RESET: u32 = 0x0000_0002;
|
||||
pub const GLOB_CNT_WARM_RESET: u32 = 0x0000_0004;
|
||||
pub const GLOB_CNT_STABLE_BITS: u32 = 0x0000_007f; // Bits not affected by reset.
|
||||
|
||||
// PCM 4/6 Enable bits
|
||||
pub const GLOB_CNT_PCM_2: u32 = 0x0000_0000; // 2 tubes
|
||||
pub const GLOB_CNT_PCM_4: u32 = 0x0010_0000; // 4 tubes
|
||||
pub const GLOB_CNT_PCM_6: u32 = 0x0020_0000; // 6 tubes
|
||||
pub const GLOB_CNT_PCM_246_MASK: u32 = GLOB_CNT_PCM_4 | GLOB_CNT_PCM_6; // tube mask
|
||||
|
||||
// Global status
|
||||
pub const GLOB_STA_30: u64 = 0x30;
|
||||
// Primary codec ready set and turn on D20:21 to support 4 and 6 tubes on PCM out.
|
||||
pub const GLOB_STA_RESET_VAL: u32 = 0x0030_0100;
|
||||
|
||||
// glob_sta bits
|
||||
pub const GS_MD3: u32 = 1 << 17;
|
||||
pub const GS_AD3: u32 = 1 << 16;
|
||||
pub const GS_RCS: u32 = 1 << 15;
|
||||
pub const GS_B3S12: u32 = 1 << 14;
|
||||
pub const GS_B2S12: u32 = 1 << 13;
|
||||
pub const GS_B1S12: u32 = 1 << 12;
|
||||
pub const GS_S1R1: u32 = 1 << 11;
|
||||
pub const GS_S0R1: u32 = 1 << 10;
|
||||
pub const GS_S1CR: u32 = 1 << 9;
|
||||
pub const GS_S0CR: u32 = 1 << 8;
|
||||
pub const GS_MINT: u32 = 1 << 7;
|
||||
pub const GS_POINT: u32 = 1 << 6;
|
||||
pub const GS_PIINT: u32 = 1 << 5;
|
||||
pub const GS_RSRVD: u32 = 1 << 4 | 1 << 3;
|
||||
pub const GS_MOINT: u32 = 1 << 2;
|
||||
pub const GS_MIINT: u32 = 1 << 1;
|
||||
pub const GS_GSCI: u32 = 1;
|
||||
pub const GS_RO_MASK: u32 = GS_B3S12
|
||||
| GS_B2S12
|
||||
| GS_B1S12
|
||||
| GS_S1CR
|
||||
| GS_S0CR
|
||||
| GS_MINT
|
||||
| GS_POINT
|
||||
| GS_PIINT
|
||||
| GS_RSRVD
|
||||
| GS_MOINT
|
||||
| GS_MIINT;
|
||||
pub const GS_VALID_MASK: u32 = 0x0003_ffff;
|
||||
pub const GS_WCLEAR_MASK: u32 = GS_RCS | GS_S1R1 | GS_S0R1 | GS_GSCI;
|
||||
|
||||
pub const ACC_SEMA_34: u64 = 0x34;
|
||||
|
||||
// Audio funciton registers.
|
||||
pub const CIV_OFFSET: u64 = 0x04;
|
||||
pub const LVI_OFFSET: u64 = 0x05;
|
||||
pub const SR_OFFSET: u64 = 0x06;
|
||||
pub const PICB_OFFSET: u64 = 0x08;
|
||||
pub const PIV_OFFSET: u64 = 0x0a;
|
||||
pub const CR_OFFSET: u64 = 0x0b;
|
||||
|
||||
// Capture
|
||||
pub const PI_BASE_00: u64 = 0x00;
|
||||
pub const PI_BDBAR_00: u64 = PI_BASE_00;
|
||||
pub const PI_CIV_04: u64 = PI_BASE_00 + CIV_OFFSET;
|
||||
pub const PI_LVI_05: u64 = PI_BASE_00 + LVI_OFFSET;
|
||||
pub const PI_SR_06: u64 = PI_BASE_00 + SR_OFFSET;
|
||||
pub const PI_PICB_08: u64 = PI_BASE_00 + PICB_OFFSET;
|
||||
pub const PI_PIV_0A: u64 = PI_BASE_00 + PIV_OFFSET;
|
||||
pub const PI_CR_0B: u64 = PI_BASE_00 + CR_OFFSET;
|
||||
|
||||
// Play Out
|
||||
pub const PO_BASE_10: u64 = 0x10;
|
||||
pub const PO_BDBAR_10: u64 = PO_BASE_10;
|
||||
pub const PO_CIV_14: u64 = PO_BASE_10 + CIV_OFFSET;
|
||||
pub const PO_LVI_15: u64 = PO_BASE_10 + LVI_OFFSET;
|
||||
pub const PO_SR_16: u64 = PO_BASE_10 + SR_OFFSET;
|
||||
pub const PO_PICB_18: u64 = PO_BASE_10 + PICB_OFFSET;
|
||||
pub const PO_PIV_1A: u64 = PO_BASE_10 + PIV_OFFSET;
|
||||
pub const PO_CR_1B: u64 = PO_BASE_10 + CR_OFFSET;
|
||||
|
||||
// Microphone
|
||||
pub const MC_BASE_20: u64 = 0x20;
|
||||
pub const MC_BDBAR_20: u64 = MC_BASE_20;
|
||||
pub const MC_CIV_24: u64 = MC_BASE_20 + CIV_OFFSET;
|
||||
pub const MC_LVI_25: u64 = MC_BASE_20 + LVI_OFFSET;
|
||||
pub const MC_SR_26: u64 = MC_BASE_20 + SR_OFFSET;
|
||||
pub const MC_PICB_28: u64 = MC_BASE_20 + PICB_OFFSET;
|
||||
pub const MC_PIV_2A: u64 = MC_BASE_20 + PIV_OFFSET;
|
||||
pub const MC_CR_2B: u64 = MC_BASE_20 + CR_OFFSET;
|
||||
|
||||
// Status Register Bits.
|
||||
pub const SR_DCH: u16 = 0x01;
|
||||
pub const SR_CELV: u16 = 0x02;
|
||||
pub const SR_LVBCI: u16 = 0x04;
|
||||
pub const SR_BCIS: u16 = 0x08;
|
||||
pub const SR_FIFOE: u16 = 0x10;
|
||||
pub const SR_VALID_MASK: u16 = 0x1f;
|
||||
pub const SR_WCLEAR_MASK: u16 = SR_FIFOE | SR_BCIS | SR_LVBCI;
|
||||
pub const SR_RO_MASK: u16 = SR_DCH | SR_CELV;
|
||||
pub const SR_INT_MASK: u16 = SR_BCIS | SR_LVBCI;
|
||||
|
||||
// Control Register Bits.
|
||||
pub const CR_RPBM: u8 = 0x01;
|
||||
pub const CR_RR: u8 = 0x02;
|
||||
pub const CR_LVBIE: u8 = 0x04;
|
||||
pub const CR_FEIE: u8 = 0x08;
|
||||
pub const CR_IOCE: u8 = 0x10;
|
||||
pub const CR_VALID_MASK: u8 = 0x1f;
|
||||
pub const CR_DONT_CLEAR_MASK: u8 = CR_IOCE | CR_FEIE | CR_LVBIE;
|
||||
|
||||
// Mixer register bits
|
||||
pub const MUTE_REG_BIT: u16 = 0x8000;
|
||||
pub const VOL_REG_MASK: u16 = 0x003f;
|
||||
pub const MIXER_VOL_MASK: u16 = 0x001f;
|
||||
pub const MIXER_VOL_LEFT_SHIFT: usize = 8;
|
||||
pub const MIXER_MIC_20DB: u16 = 0x0040;
|
||||
// Powerdown reg
|
||||
pub const PD_REG_STATUS_MASK: u16 = 0x000f;
|
||||
pub const PD_REG_OUTPUT_MUTE_MASK: u16 = 0xb200;
|
||||
pub const PD_REG_INPUT_MUTE_MASK: u16 = 0x0d00;
|
||||
|
||||
// Buffer descriptors are four bytes of pointer and 4 bytes of control/length.
|
||||
pub const DESCRIPTOR_LENGTH: usize = 8;
|
||||
pub const BD_IOC: u32 = 1 << 31;
|
||||
|
||||
/// The functions that are supported by the Ac97 subsystem.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Ac97Function {
|
||||
Input,
|
||||
Output,
|
||||
Microphone,
|
||||
}
|
||||
|
||||
/// Registers for individual audio functions.
|
||||
/// Each audio function in Ac97 gets a set of these registers.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Ac97FunctionRegs {
|
||||
label: String,
|
||||
pub bdbar: u32,
|
||||
pub civ: u8,
|
||||
pub lvi: u8,
|
||||
pub sr: u16,
|
||||
pub picb: u16,
|
||||
pub piv: u8,
|
||||
pub cr: u8,
|
||||
}
|
||||
|
||||
impl Ac97FunctionRegs {
|
||||
/// Creates a new set of function registers, these can be used for the capture, playback, or
|
||||
/// microphone functions.
|
||||
pub fn new(label: &str) -> Self {
|
||||
let mut regs = Ac97FunctionRegs {
|
||||
label: label.to_string(),
|
||||
sr: SR_DCH,
|
||||
..Default::default()
|
||||
};
|
||||
regs.do_reset();
|
||||
regs
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &str {
|
||||
&self.label
|
||||
}
|
||||
|
||||
/// Reset all the registers to the PoR defaults. `sr` should be updated by `update_sr`.
|
||||
pub fn do_reset(&mut self) {
|
||||
self.bdbar = 0;
|
||||
self.civ = 0;
|
||||
self.lvi = 0;
|
||||
self.picb = 0;
|
||||
self.piv = 0;
|
||||
self.cr &= CR_DONT_CLEAR_MASK;
|
||||
}
|
||||
|
||||
/// Read register 4, 5, and 6 as one 32 bit word.
|
||||
/// According to the ICH spec, reading these three with one 32 bit access is allowed.
|
||||
pub fn atomic_status_regs(&self) -> u32 {
|
||||
u32::from(self.civ) | u32::from(self.lvi) << 8 | u32::from(self.sr) << 16
|
||||
}
|
||||
|
||||
/// Returns the mask for enabled interrupts. The returned mask represents the bits in the status
|
||||
/// register that should trigger and interrupt.
|
||||
pub fn int_mask(&self) -> u16 {
|
||||
let mut int_mask = 0;
|
||||
if self.cr & CR_LVBIE != 0 {
|
||||
int_mask |= SR_LVBCI;
|
||||
}
|
||||
if self.cr & CR_IOCE != 0 {
|
||||
int_mask |= SR_BCIS;
|
||||
}
|
||||
int_mask
|
||||
}
|
||||
|
||||
/// Sets the current buffer to the next buffer by updating CIV to PIV, and
|
||||
/// updates related fields.
|
||||
pub fn move_to_next_buffer(&mut self) {
|
||||
self.civ = self.piv;
|
||||
self.piv = (self.piv + 1) % 32; // move piv to the next buffer.
|
||||
}
|
||||
|
||||
/// Returns irq status.
|
||||
pub fn has_irq(&self) -> bool {
|
||||
self.sr & self.int_mask() != 0
|
||||
}
|
||||
}
|
7
src/devices/ac97/mod.rs
Normal file
7
src/devices/ac97/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
mod ac97;
|
||||
mod ac97_mixer;
|
||||
mod ac97_bus_master;
|
||||
mod ac97_regs;
|
||||
|
||||
pub use ac97::Ac97Dev;
|
@ -1,3 +1,4 @@
|
||||
pub mod ac97;
|
||||
pub mod serial;
|
||||
pub mod rtc;
|
||||
mod virtio_9p;
|
||||
@ -6,6 +7,7 @@ mod virtio_rng;
|
||||
mod virtio_wl;
|
||||
mod virtio_block;
|
||||
mod virtio_net;
|
||||
mod irq_event;
|
||||
|
||||
pub use self::virtio_serial::VirtioSerial;
|
||||
pub use self::virtio_9p::VirtioP9;
|
||||
|
@ -7,8 +7,9 @@ pub mod util;
|
||||
mod vm;
|
||||
mod memory;
|
||||
mod devices;
|
||||
mod virtio;
|
||||
mod disk;
|
||||
mod io;
|
||||
mod audio;
|
||||
|
||||
pub use util::{Logger,LogLevel};
|
||||
pub use vm::VmConfig;
|
||||
|
@ -15,6 +15,7 @@ pub struct VmConfig {
|
||||
wayland: bool,
|
||||
dmabuf: bool,
|
||||
network: bool,
|
||||
audio: bool,
|
||||
home: String,
|
||||
colorscheme: String,
|
||||
bridge_name: String,
|
||||
@ -39,6 +40,7 @@ impl VmConfig {
|
||||
wayland: true,
|
||||
dmabuf: false,
|
||||
network: true,
|
||||
audio: true,
|
||||
bridge_name: "vz-clear".to_string(),
|
||||
home: Self::default_homedir(),
|
||||
colorscheme: "dracula".to_string(),
|
||||
@ -213,6 +215,10 @@ impl VmConfig {
|
||||
self.dmabuf
|
||||
}
|
||||
|
||||
pub fn is_audio_enable(&self) -> bool {
|
||||
self.audio
|
||||
}
|
||||
|
||||
pub fn bridge(&self) -> &str {
|
||||
&self.bridge_name
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
use std::{result, io};
|
||||
use kvm_ioctls::Cap;
|
||||
use crate::{system, virtio};
|
||||
use crate::system;
|
||||
use crate::system::netlink;
|
||||
use crate::vm::arch;
|
||||
|
||||
use thiserror::Error;
|
||||
use crate::io::virtio;
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[derive(Error,Debug)]
|
||||
@ -37,4 +39,6 @@ pub enum Error {
|
||||
SetupVirtio(virtio::Error),
|
||||
#[error("failed to create Vcpu: {0}")]
|
||||
CreateVcpu(kvm_ioctls::Error),
|
||||
#[error("{0}")]
|
||||
VirtioError(#[from]crate::io::VirtioError),
|
||||
}
|
@ -13,6 +13,7 @@ fn add_defaults(cmdline: &mut KernelCmdLine) {
|
||||
|
||||
.push("init_on_alloc=0")
|
||||
.push("init_on_free=0")
|
||||
|
||||
.push_set_val("console", "hvc0")
|
||||
|
||||
.push("i8042.direct")
|
||||
@ -20,10 +21,12 @@ fn add_defaults(cmdline: &mut KernelCmdLine) {
|
||||
.push("i8042.nopnp")
|
||||
.push("i8042.noaux")
|
||||
.push("i8042.nomux")
|
||||
// .push("initcall_debug")
|
||||
|
||||
.push_set_val("iommu", "off")
|
||||
.push("cryptomgr.notests")
|
||||
|
||||
.push("snd_intel8x0.inside_vm=1")
|
||||
.push("snd_intel8x0.ac97_clock=48000")
|
||||
.push_set_val("8250.nr_uarts", "0");
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ use crate::memory::MemoryManager;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use kvm_ioctls::VmFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use crate::devices::ac97::{Ac97Dev, Ac97Parameters};
|
||||
use crate::devices::ac97::Ac97Dev;
|
||||
use crate::devices::serial::SerialPort;
|
||||
use crate::io::manager::IoManager;
|
||||
use crate::{Logger, LogLevel};
|
||||
@ -132,7 +132,7 @@ impl <T: ArchSetup> VmSetup <T> {
|
||||
let irq = vm.io_manager.allocator().allocate_irq();
|
||||
let mem = vm.memory.guest_ram().clone();
|
||||
// XXX expect()
|
||||
let ac97 = Ac97Dev::try_new(&vm.kvm_vm, irq, mem, Ac97Parameters::new_pulseaudio()).expect("audio initialize error");
|
||||
let ac97 = Ac97Dev::try_new(&vm.kvm_vm, irq, mem).expect("audio initialize error");
|
||||
vm.io_manager.add_pci_device(Arc::new(Mutex::new(ac97)));
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user