refactor feedback Trait fn, fix max_packet_size calculation in descriptors

This commit is contained in:
2026-05-07 16:03:54 -07:00
parent 537e22a7ee
commit f38f36578d
+45 -34
View File
@@ -106,8 +106,8 @@ impl<T: RangeType + PartialOrd> PartialOrd for RangeEntry<T> {
/// Fixed point 10.14, packed to the least significant 3-bytes of a 4-byte USB feedback endpoint response
#[derive(Copy, Clone)]
pub struct UsbIsochronousFeedback {
int: u16,
frac: u16,
pub int: u16,
pub frac: u16,
}
impl UsbIsochronousFeedback {
@@ -164,13 +164,12 @@ pub trait UsbAudioClass<'a, B: UsbBus> {
fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>) {}
/// Called when it's time to send an isochronous feedback update. Should
/// return the correct feedback payload. Should not be considered a great
/// timing reference. Better to track sample timing using other means (even
/// `audio_data_rx`).
/// return the correct feedback payload. Feedback always runs at 1ms (in
/// this implementation), and will be passed the nominal frame size.
///
/// Required for isochronous asynchronous mode to work properly. If None is
/// returned, no IN packet will be emitted at feedback time.
fn feedback(&mut self) -> Option<UsbIsochronousFeedback> {
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
None
}
@@ -191,8 +190,13 @@ pub trait UsbAudioClass<'a, B: UsbBus> {
pub trait UsbAudioClockImpl {
const CLOCK_TYPE: ClockType;
const SOF_SYNC: bool;
/// Called when the host requests the current sample rate. Returns the sample rate in Hz.
fn get_sample_rate(&self) -> core::result::Result<u32, UsbAudioClassError>;
/// Called when the host or class needs the current sample rate. Returns the
/// sample rate in Hz. It should be cheap and infallible as it gets called in every cycle
/// of the feedback loop. Use clock validity to signal to the host if the clock is not usable.
///
/// Should never return 0 as it may be used in divides in the feedback loop
/// and that would cause a hard fault.
fn get_sample_rate(&self) -> u32;
/// Called when the host requests to set the sample rate. Not necessarily called at all startups,
/// so alt_setting should start/stop the clock. Not required for 'fixed' clocks.
fn set_sample_rate(
@@ -468,9 +472,9 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
/// Allocate the various USB IDs, and build the class implementation
pub fn build(self, alloc: &'a UsbBusAllocator<B>) -> Result<AudioClass<'a, B, CS, AU>> {
let speed = self.speed;
let interval = match speed {
UsbSpeed::Full => 1,
UsbSpeed::High | UsbSpeed::Super => 4, // rate = 2^(4-1) * 125us = 1ms, same as full speed
let (interval, fb_interval, audio_rate) = match speed {
UsbSpeed::Full => (1, 1, 1000),
UsbSpeed::High | UsbSpeed::Super => (1, 4, 8000), //
UsbSpeed::Low => return Err(Error::InvalidSpeed),
};
let max_rate = self
@@ -498,6 +502,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
out_ep: 0,
fb_ep: 0,
speed,
audio_rate,
};
if let Some(config) = self.output_config {
@@ -505,14 +510,14 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
let endpoint = alloc.isochronous(
config.sync_type,
IsochronousUsageType::Data,
((config.bytes_per_frame() * max_rate) / 1000) as u16,
(max_rate.div_ceil(audio_rate) * config.bytes_per_frame()) as u16,
interval,
);
let feedback_ep = alloc.isochronous(
IsochronousSynchronizationType::NoSynchronization,
IsochronousUsageType::Feedback,
4,
interval,
fb_interval,
);
let alt_setting = DEFAULT_ALTERNATE_SETTING;
ac.out_iface = interface.into();
@@ -532,7 +537,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
let endpoint = alloc.isochronous(
config.sync_type,
IsochronousUsageType::Data,
((config.bytes_per_frame() * max_rate) / 1000) as u16,
(max_rate.div_ceil(audio_rate) * config.bytes_per_frame()) as u16,
interval,
);
let alt_setting = DEFAULT_ALTERNATE_SETTING;
@@ -664,6 +669,7 @@ pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a
out_ep: usize,
fb_ep: usize,
speed: UsbSpeed,
audio_rate: u32, // audio packet rate in hz
}
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
@@ -811,19 +817,25 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
.audio_data_rx(&self.output.as_ref().unwrap().endpoint);
let new_count = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if new_count.is_multiple_of(1 as usize) {
if let Some(fb) = self.audio_impl.feedback() {
debug!(" emitting feedback IN {:08x}", fb.to_u32_12_13());
let r = match self.speed {
UsbSpeed::Low | UsbSpeed::Full => {
self.feedback.as_ref().unwrap().write(&fb.to_bytes_10_14())
}
UsbSpeed::High | UsbSpeed::Super => {
self.feedback.as_ref().unwrap().write(&fb.to_bytes_12_13())
}
if let Some(fb_ep) = self.feedback.as_ref() {
if new_count.is_multiple_of(1 << (fb_ep.interval() - 1) as usize) {
let cur_rate = self.clock_impl.get_sample_rate();
let numerator = (cur_rate as u64) << 16;
let raw = (numerator + self.audio_rate as u64 / 2) / (self.audio_rate as u64);
let nominal_rate = UsbIsochronousFeedback {
int: (raw >> 16) as u16,
frac: (raw & 0xffff) as u16,
};
if let Err(e) = r {
warn!(" feedback IN failed {:?}", e);
if let Some(fb) = self.audio_impl.feedback(nominal_rate) {
debug!(" emitting feedback IN {:08x}", fb.to_u32_12_13());
let r = match self.speed {
UsbSpeed::Low | UsbSpeed::Full => fb_ep.write(&fb.to_bytes_10_14()),
UsbSpeed::High | UsbSpeed::Super => fb_ep.write(&fb.to_bytes_12_13()),
};
if let Err(e) = r {
warn!(" feedback IN failed {:?}", e);
}
}
}
}
@@ -1118,14 +1130,13 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
channel
);
}
xfer.accept(|mut buf| match self.clock_impl.get_sample_rate() {
Ok(rate) => {
debug!(" {}", rate);
buf.write_u32::<LittleEndian>(rate)
.map_err(|_e| UsbError::BufferOverflow)?;
Ok(4)
}
Err(_e) => Err(UsbError::InvalidState),
xfer.accept(|mut buf| {
let rate = self.clock_impl.get_sample_rate();
debug!(" {}", rate);
buf.write_u32::<LittleEndian>(rate)
.map_err(|_e| UsbError::BufferOverflow)?;
Ok(4)
})
.ok();
}