use std::cmp::min;
use std::fmt;
use std::fmt::Display;
use std::mem;
use std::sync::Arc;
use std::sync::Weak;
use base::debug;
use base::error;
use base::info;
use base::warn;
use base::Error as SysError;
use base::Event;
use bit_field::Error as BitFieldError;
use remain::sorted;
use sync::Mutex;
use thiserror::Error;
use usb_util::TransferStatus;
use usb_util::UsbRequestSetup;
use vm_memory::GuestMemory;
use vm_memory::GuestMemoryError;
use super::device_slot::DeviceSlot;
use super::interrupter::Error as InterrupterError;
use super::interrupter::Interrupter;
use super::scatter_gather_buffer::Error as BufferError;
use super::scatter_gather_buffer::ScatterGatherBuffer;
use super::usb_hub::Error as HubError;
use super::usb_hub::UsbPort;
use super::xhci_abi::AddressedTrb;
use super::xhci_abi::Error as TrbError;
use super::xhci_abi::EventDataTrb;
use super::xhci_abi::SetupStageTrb;
use super::xhci_abi::TransferDescriptor;
use super::xhci_abi::TrbCast;
use super::xhci_abi::TrbCompletionCode;
use super::xhci_abi::TrbType;
use super::xhci_regs::MAX_INTERRUPTER;
#[sorted]
#[derive(Error, Debug)]
pub enum Error {
#[error("unexpected trb type: {0:?}")]
BadTrbType(TrbType),
#[error("cannot cast trb: {0}")]
CastTrb(TrbError),
#[error("cannot create transfer buffer: {0}")]
CreateBuffer(BufferError),
#[error("cannot detach from port: {0}")]
DetachPort(HubError),
#[error("failed to halt the endpoint: {0}")]
HaltEndpoint(u8),
#[error("failed to read guest memory: {0}")]
ReadGuestMemory(GuestMemoryError),
#[error("cannot send interrupt: {0}")]
SendInterrupt(InterrupterError),
#[error("failed to submit transfer to backend")]
SubmitTransfer,
#[error("cannot get transfer length: {0}")]
TransferLength(TrbError),
#[error("cannot get trb type: {0}")]
TrbType(BitFieldError),
#[error("cannot write completion event: {0}")]
WriteCompletionEvent(SysError),
#[error("failed to write guest memory: {0}")]
WriteGuestMemory(GuestMemoryError),
}
type Result<T> = std::result::Result<T, Error>;
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum TransferDirection {
In,
Out,
Control,
}
pub enum XhciTransferState {
Created,
Submitted {
cancel_callback: Box<dyn FnOnce() + Send>,
},
Cancelling,
Cancelled,
Completed,
}
impl XhciTransferState {
pub fn try_cancel(&mut self) {
match mem::replace(self, XhciTransferState::Created) {
XhciTransferState::Submitted { cancel_callback } => {
*self = XhciTransferState::Cancelling;
cancel_callback();
}
XhciTransferState::Cancelling => {
error!("Another cancellation is already issued.");
}
_ => {
*self = XhciTransferState::Cancelled;
}
}
}
}
pub enum XhciTransferType {
Normal,
SetupStage,
DataStage,
StatusStage,
Isochronous,
Noop,
}
impl Display for XhciTransferType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::XhciTransferType::*;
match self {
Normal => write!(f, "Normal"),
SetupStage => write!(f, "SetupStage"),
DataStage => write!(f, "DataStage"),
StatusStage => write!(f, "StatusStage"),
Isochronous => write!(f, "Isochronous"),
Noop => write!(f, "Noop"),
}
}
}
#[derive(Clone)]
pub struct XhciTransferManager {
transfers: Arc<Mutex<Vec<Weak<Mutex<XhciTransferState>>>>>,
device_slot: Weak<DeviceSlot>,
}
impl XhciTransferManager {
pub fn new(device_slot: Weak<DeviceSlot>) -> XhciTransferManager {
XhciTransferManager {
transfers: Arc::new(Mutex::new(Vec::new())),
device_slot,
}
}
pub fn create_transfer(
&self,
mem: GuestMemory,
port: Arc<UsbPort>,
interrupter: Arc<Mutex<Interrupter>>,
slot_id: u8,
endpoint_id: u8,
transfer_descriptor: TransferDescriptor,
completion_event: Event,
stream_id: Option<u16>,
) -> XhciTransfer {
let transfer_dir = {
if endpoint_id == 0 {
TransferDirection::Control
} else if (endpoint_id % 2) == 0 {
TransferDirection::Out
} else {
TransferDirection::In
}
};
let t = XhciTransfer {
manager: self.clone(),
state: Arc::new(Mutex::new(XhciTransferState::Created)),
mem,
port,
interrupter,
transfer_completion_event: completion_event,
slot_id,
endpoint_id,
transfer_dir,
transfer_descriptor,
device_slot: self.device_slot.clone(),
stream_id,
};
self.transfers.lock().push(Arc::downgrade(&t.state));
t
}
pub fn cancel_all(&self) {
self.transfers.lock().iter().for_each(|t| {
let state = match t.upgrade() {
Some(state) => state,
None => {
error!("transfer is already cancelled or finished");
return;
}
};
state.lock().try_cancel();
});
}
fn remove_transfer(&self, t: &Arc<Mutex<XhciTransferState>>) {
let mut transfers = self.transfers.lock();
match transfers.iter().position(|wt| match wt.upgrade() {
Some(wt) => Arc::ptr_eq(&wt, t),
None => false,
}) {
None => error!("attempted to remove unknown transfer"),
Some(i) => {
transfers.swap_remove(i);
}
}
}
}
impl Default for XhciTransferManager {
fn default() -> Self {
Self::new(Weak::new())
}
}
pub struct XhciTransfer {
manager: XhciTransferManager,
state: Arc<Mutex<XhciTransferState>>,
mem: GuestMemory,
port: Arc<UsbPort>,
interrupter: Arc<Mutex<Interrupter>>,
slot_id: u8,
endpoint_id: u8,
transfer_dir: TransferDirection,
transfer_descriptor: TransferDescriptor,
transfer_completion_event: Event,
device_slot: Weak<DeviceSlot>,
stream_id: Option<u16>,
}
impl Drop for XhciTransfer {
fn drop(&mut self) {
self.manager.remove_transfer(&self.state);
}
}
impl fmt::Debug for XhciTransfer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"xhci_transfer slot id: {}, endpoint id {}, transfer_dir {:?}, transfer_descriptor {:?}",
self.slot_id, self.endpoint_id, self.transfer_dir, self.transfer_descriptor
)
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
enum TransferAction {
HaltEndpoint,
SendEvent {
code: TrbCompletionCode,
gpa: u64,
residual_or_edtla: u32,
event_data: bool,
},
}
impl XhciTransfer {
pub fn state(&self) -> &Arc<Mutex<XhciTransferState>> {
&self.state
}
pub fn get_transfer_type(&self) -> Result<XhciTransferType> {
match self
.transfer_descriptor
.first_atrb()
.trb
.get_trb_type()
.map_err(Error::TrbType)?
{
TrbType::Normal => Ok(XhciTransferType::Normal),
TrbType::SetupStage => Ok(XhciTransferType::SetupStage),
TrbType::DataStage => Ok(XhciTransferType::DataStage),
TrbType::StatusStage => Ok(XhciTransferType::StatusStage),
TrbType::Isoch => Ok(XhciTransferType::Isochronous),
TrbType::Noop => Ok(XhciTransferType::Noop),
t => Err(Error::BadTrbType(t)),
}
}
pub fn create_buffer(&self) -> Result<ScatterGatherBuffer> {
ScatterGatherBuffer::new(self.mem.clone(), self.transfer_descriptor.clone())
.map_err(Error::CreateBuffer)
}
pub fn create_usb_request_setup(&self) -> Result<UsbRequestSetup> {
let first_atrb = self.transfer_descriptor.first_atrb();
let trb = first_atrb
.trb
.checked_cast::<SetupStageTrb>()
.map_err(Error::CastTrb)?;
Ok(UsbRequestSetup::new(
trb.get_request_type(),
trb.get_request(),
trb.get_value(),
trb.get_index(),
trb.get_length(),
))
}
pub fn get_endpoint_number(&self) -> u8 {
self.endpoint_id / 2
}
pub fn get_transfer_dir(&self) -> TransferDirection {
self.transfer_dir
}
pub fn get_stream_id(&self) -> Option<u16> {
self.stream_id
}
fn process_td_results(
&self,
status: &TransferStatus,
bytes_transferred: u32,
) -> Result<Vec<TransferAction>> {
let mut actions = Vec::new();
if *status == TransferStatus::Stalled {
warn!("xhci: endpoint is stalled. set state to Halted");
actions.push(TransferAction::HaltEndpoint);
}
let mut edtla: u32 = 0;
let mut remaining_transferred = bytes_transferred;
let mut retiring_on_short: bool = false;
let mut residual_on_short: u32 = 0;
let last_atrb_gpa = self.transfer_descriptor.last_atrb().gpa;
for atrb in &self.transfer_descriptor {
if atrb.trb.get_trb_type().map_err(Error::TrbType)? == TrbType::EventData {
let code = if retiring_on_short {
TrbCompletionCode::ShortPacket
} else {
TrbCompletionCode::Success
};
actions.push(TransferAction::SendEvent {
code,
gpa: atrb
.trb
.cast::<EventDataTrb>()
.map_err(Error::CastTrb)?
.get_event_data(),
residual_or_edtla: edtla,
event_data: true,
});
edtla = 0;
continue;
}
let length = atrb.trb.transfer_length().map_err(Error::TransferLength)?;
let transferred = min(length, remaining_transferred);
remaining_transferred -= transferred;
let residual = length - transferred;
edtla += transferred;
if *status == TransferStatus::Stalled && (residual > 0 || atrb.gpa == last_atrb_gpa) {
debug!("xhci: on transfer complete stalled");
actions.push(TransferAction::SendEvent {
code: TrbCompletionCode::StallError,
gpa: atrb.gpa,
residual_or_edtla: residual,
event_data: false,
});
break;
}
if retiring_on_short {
if atrb.trb.interrupt_on_completion() {
actions.push(TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: atrb.gpa,
residual_or_edtla: residual_on_short,
event_data: false,
});
}
} else if residual > 0 {
retiring_on_short = true;
residual_on_short = residual;
if atrb.trb.interrupt_on_completion() || atrb.trb.interrupt_on_short_packet() {
debug!("xhci: on transfer complete short packet");
actions.push(TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: atrb.gpa,
residual_or_edtla: residual,
event_data: false,
});
}
} else if atrb.trb.interrupt_on_completion() {
debug!("xhci: on transfer complete success");
actions.push(TransferAction::SendEvent {
code: TrbCompletionCode::Success,
gpa: atrb.gpa,
residual_or_edtla: 0,
event_data: false,
});
}
}
Ok(actions)
}
pub fn on_transfer_complete(
&self,
status: &TransferStatus,
bytes_transferred: u32,
) -> Result<()> {
match status {
TransferStatus::NoDevice => {
info!("xhci: device disconnected, detaching from port");
return match self.port.detach() {
Ok(()) => Ok(()),
Err(HubError::AlreadyDetached(_e)) => Ok(()),
Err(e) => Err(Error::DetachPort(e)),
};
}
TransferStatus::Cancelled => {
return self
.transfer_completion_event
.signal()
.map_err(Error::WriteCompletionEvent);
}
TransferStatus::Completed | TransferStatus::Stalled => {
self.transfer_completion_event
.signal()
.map_err(Error::WriteCompletionEvent)?;
}
_ => {
self.transfer_completion_event
.signal()
.map_err(Error::WriteCompletionEvent)?;
}
}
let actions = self.process_td_results(status, bytes_transferred)?;
for action in actions {
match action {
TransferAction::SendEvent {
code,
gpa,
residual_or_edtla,
event_data,
} => {
self.interrupter
.lock()
.send_transfer_event_trb(
code,
gpa,
residual_or_edtla,
event_data,
self.slot_id,
self.endpoint_id,
)
.map_err(Error::SendInterrupt)?;
}
TransferAction::HaltEndpoint => {
if let Some(device_slot) = self.device_slot.upgrade() {
device_slot
.halt_endpoint(self.endpoint_id)
.map_err(|_| Error::HaltEndpoint(self.endpoint_id))?;
}
}
}
}
Ok(())
}
pub fn send_to_backend_if_valid(self) -> Result<()> {
if self.validate_transfer()? {
let port = self.port.clone();
let mut backend = port.backend_device();
match &mut *backend {
Some(backend) => backend
.lock()
.submit_xhci_transfer(self)
.map_err(|_| Error::SubmitTransfer)?,
None => {
error!("backend is already disconnected");
self.transfer_completion_event
.signal()
.map_err(Error::WriteCompletionEvent)?;
}
}
} else {
error!("invalid td on transfer ring");
self.transfer_completion_event
.signal()
.map_err(Error::WriteCompletionEvent)?;
}
Ok(())
}
fn validate_transfer(&self) -> Result<bool> {
let mut valid = true;
for atrb in &self.transfer_descriptor {
if !trb_is_valid(atrb) {
self.interrupter
.lock()
.send_transfer_event_trb(
TrbCompletionCode::TrbError,
atrb.gpa,
0,
false,
self.slot_id,
self.endpoint_id,
)
.map_err(Error::SendInterrupt)?;
valid = false;
}
}
Ok(valid)
}
}
fn trb_is_valid(atrb: &AddressedTrb) -> bool {
let can_be_in_transfer_ring = match atrb.trb.can_be_in_transfer_ring() {
Ok(v) => v,
Err(e) => {
error!("unknown error {:?}", e);
return false;
}
};
can_be_in_transfer_ring && (atrb.trb.interrupter_target() < MAX_INTERRUPTER)
}
#[cfg(test)]
mod tests {
use base::pagesize;
use vm_memory::GuestAddress;
use super::*;
use crate::usb::xhci::xhci_abi::NormalTrb;
use crate::usb::xhci::xhci_abi::StatusStageTrb;
use crate::usb::xhci::xhci_abi::Trb;
use crate::usb::xhci::xhci_backend_device::BackendType;
use crate::usb::xhci::XhciRegs;
fn create_test_transfer(trbs: Vec<Trb>) -> XhciTransfer {
let mem = GuestMemory::new(&[(GuestAddress(0), pagesize() as u64)]).unwrap();
let mut gpa = 0x100;
let mut atrbs = Vec::new();
for trb in trbs {
mem.write_obj_at_addr(trb, GuestAddress(gpa)).unwrap();
atrbs.push(AddressedTrb { trb, gpa });
gpa += 16;
}
let td = TransferDescriptor::new(atrbs).unwrap();
let manager = XhciTransferManager::new(Weak::new());
let test_reg32 = register!(
name: "test",
ty: u32,
offset: 0x0,
reset_value: 0,
guest_writeable_mask: 0x0,
guest_write_1_to_clear_mask: 0,
);
let test_reg64 = register!(
name: "test",
ty: u64,
offset: 0x0,
reset_value: 0,
guest_writeable_mask: 0x0,
guest_write_1_to_clear_mask: 0,
);
let xhci_regs = XhciRegs {
usbcmd: test_reg32.clone(),
usbsts: test_reg32.clone(),
dnctrl: test_reg32.clone(),
crcr: test_reg64.clone(),
dcbaap: test_reg64.clone(),
config: test_reg64.clone(),
portsc: vec![test_reg32.clone(); 16],
doorbells: Vec::new(),
iman: test_reg32.clone(),
imod: test_reg32.clone(),
erstsz: test_reg32.clone(),
erstba: test_reg64.clone(),
erdp: test_reg64.clone(),
};
XhciTransfer {
manager,
state: Arc::new(Mutex::new(XhciTransferState::Created)),
mem,
port: Arc::new(UsbPort::new(
BackendType::Usb2,
1,
test_reg32.clone(),
test_reg32.clone(),
Arc::new(Mutex::new(Interrupter::new(
GuestMemory::new(&[]).unwrap(),
Event::new().unwrap(),
&xhci_regs,
))),
)),
interrupter: Arc::new(Mutex::new(Interrupter::new(
GuestMemory::new(&[]).unwrap(),
Event::new().unwrap(),
&xhci_regs,
))),
transfer_completion_event: Event::new().unwrap(),
slot_id: 1,
endpoint_id: 2,
transfer_dir: TransferDirection::Out,
transfer_descriptor: td,
device_slot: Weak::new(),
stream_id: None,
}
}
#[test]
fn test_bulk_success() {
let mut trb = Trb::new();
let normal_trb = trb.cast_mut::<NormalTrb>().unwrap();
normal_trb.set_trb_type(TrbType::Normal);
normal_trb.set_trb_transfer_length(100);
normal_trb.set_interrupt_on_completion(1);
let transfer = create_test_transfer(vec![trb]);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 100)
.unwrap();
assert_eq!(
actions,
vec![TransferAction::SendEvent {
code: TrbCompletionCode::Success,
gpa: 0x100,
residual_or_edtla: 0,
event_data: false,
}]
);
}
#[test]
fn test_bulk_short_with_isp() {
let mut trb = Trb::new();
let normal_trb = trb.cast_mut::<NormalTrb>().unwrap();
normal_trb.set_trb_type(TrbType::Normal);
normal_trb.set_trb_transfer_length(100);
normal_trb.set_interrupt_on_short_packet(1);
let transfer = create_test_transfer(vec![trb]);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 40)
.unwrap();
assert_eq!(
actions,
vec![TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x100,
residual_or_edtla: 60,
event_data: false,
}]
);
}
#[test]
fn test_bulk_short_with_ioc() {
let mut trb = Trb::new();
let normal_trb = trb.cast_mut::<NormalTrb>().unwrap();
normal_trb.set_trb_type(TrbType::Normal);
normal_trb.set_trb_transfer_length(100);
normal_trb.set_interrupt_on_completion(1);
let transfer = create_test_transfer(vec![trb]);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 40)
.unwrap();
assert_eq!(
actions,
vec![TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x100,
residual_or_edtla: 60,
event_data: false,
}]
);
}
#[test]
fn test_bulk_without_evendata_retiring_after_short() {
let mut trb1 = Trb::new();
let normal_trb1 = trb1.cast_mut::<NormalTrb>().unwrap();
normal_trb1.set_trb_type(TrbType::Normal);
normal_trb1.set_trb_transfer_length(100);
normal_trb1.set_interrupt_on_short_packet(1);
let mut trb2 = Trb::new();
let normal_trb2 = trb2.cast_mut::<NormalTrb>().unwrap();
normal_trb2.set_trb_type(TrbType::Normal);
normal_trb2.set_trb_transfer_length(100);
normal_trb2.set_interrupt_on_completion(1);
let mut trb3 = Trb::new();
let normal_trb3 = trb3.cast_mut::<NormalTrb>().unwrap();
normal_trb3.set_trb_type(TrbType::Normal);
normal_trb3.set_trb_transfer_length(100);
normal_trb3.set_interrupt_on_completion(1);
let transfer = create_test_transfer(vec![trb1, trb2, trb3]);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 40)
.unwrap();
assert_eq!(
actions,
vec![
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x100,
residual_or_edtla: 60,
event_data: false,
},
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x110,
residual_or_edtla: 60,
event_data: false,
},
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x120,
residual_or_edtla: 60,
event_data: false,
},
]
);
}
#[test]
fn test_bulk_with_evendata_retiring_after_short() {
let mut trb1 = Trb::new();
let normal_trb1 = trb1.cast_mut::<NormalTrb>().unwrap();
normal_trb1.set_trb_type(TrbType::Normal);
normal_trb1.set_trb_transfer_length(100);
normal_trb1.set_interrupt_on_short_packet(1);
let mut trb2 = Trb::new();
let event_trb = trb2.cast_mut::<EventDataTrb>().unwrap();
event_trb.set_trb_type(TrbType::EventData);
event_trb.set_event_data(0x12345678abcdef0);
event_trb.set_interrupt_on_completion(1);
let mut trb3 = Trb::new();
let event_trb = trb3.cast_mut::<EventDataTrb>().unwrap();
event_trb.set_trb_type(TrbType::EventData);
event_trb.set_event_data(0x12345678abcdef1);
event_trb.set_interrupt_on_completion(1);
let transfer = create_test_transfer(vec![trb1, trb2, trb3]);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 40)
.unwrap();
assert_eq!(
actions,
vec![
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x100,
residual_or_edtla: 60,
event_data: false,
},
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x12345678abcdef0,
residual_or_edtla: 40,
event_data: true,
},
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x12345678abcdef1,
residual_or_edtla: 0,
event_data: true,
},
]
);
}
#[test]
fn test_bulk_stall_partial() {
let mut trb = Trb::new();
let normal_trb = trb.cast_mut::<NormalTrb>().unwrap();
normal_trb.set_trb_type(TrbType::Normal);
normal_trb.set_trb_transfer_length(100);
let transfer = create_test_transfer(vec![trb]);
let actions = transfer
.process_td_results(&TransferStatus::Stalled, 40)
.unwrap();
assert_eq!(
actions,
vec![
TransferAction::HaltEndpoint,
TransferAction::SendEvent {
code: TrbCompletionCode::StallError,
gpa: 0x100,
residual_or_edtla: 60,
event_data: false,
}
]
);
}
#[test]
fn test_control_stall_no_data_stage() {
let mut trb = Trb::new();
let status_trb = trb.cast_mut::<StatusStageTrb>().unwrap();
status_trb.set_trb_type(TrbType::StatusStage);
status_trb.set_interrupt_on_completion(1);
let transfer = create_test_transfer(vec![trb]);
let actions = transfer
.process_td_results(&TransferStatus::Stalled, 0)
.unwrap();
assert_eq!(
actions,
vec![
TransferAction::HaltEndpoint,
TransferAction::SendEvent {
code: TrbCompletionCode::StallError,
gpa: 0x100,
residual_or_edtla: 0,
event_data: false,
}
]
);
}
#[test]
fn test_bulk_stall_at_trb_start() {
let mut trb1 = Trb::new();
let normal_trb1 = trb1.cast_mut::<NormalTrb>().unwrap();
normal_trb1.set_trb_type(TrbType::Normal);
normal_trb1.set_trb_transfer_length(100);
let mut trb2 = Trb::new();
let normal_trb2 = trb2.cast_mut::<NormalTrb>().unwrap();
normal_trb2.set_trb_type(TrbType::Normal);
normal_trb2.set_trb_transfer_length(100);
let transfer = create_test_transfer(vec![trb1, trb2]);
let actions = transfer
.process_td_results(&TransferStatus::Stalled, 100)
.unwrap();
assert_eq!(
actions,
vec![
TransferAction::HaltEndpoint,
TransferAction::SendEvent {
code: TrbCompletionCode::StallError,
gpa: 0x110,
residual_or_edtla: 100,
event_data: false,
}
]
);
}
#[test]
fn test_event_data_single() {
let mut trb1 = Trb::new();
let normal_trb = trb1.cast_mut::<NormalTrb>().unwrap();
normal_trb.set_trb_type(TrbType::Normal);
normal_trb.set_trb_transfer_length(100);
let mut trb2 = Trb::new();
let event_trb = trb2.cast_mut::<EventDataTrb>().unwrap();
event_trb.set_trb_type(TrbType::EventData);
event_trb.set_event_data(0x12345678abcdef0);
event_trb.set_interrupt_on_completion(1);
let transfer = create_test_transfer(vec![trb1, trb2]);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 100)
.unwrap();
assert_eq!(
actions,
vec![TransferAction::SendEvent {
code: TrbCompletionCode::Success,
gpa: 0x12345678abcdef0,
residual_or_edtla: 100,
event_data: true,
}]
);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 40)
.unwrap();
assert_eq!(
actions,
vec![TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x12345678abcdef0,
residual_or_edtla: 40,
event_data: true,
}]
);
}
#[test]
fn test_event_data_multiple() {
let mut trb1 = Trb::new();
let normal_trb = trb1.cast_mut::<NormalTrb>().unwrap();
normal_trb.set_trb_type(TrbType::Normal);
normal_trb.set_trb_transfer_length(100);
normal_trb.set_interrupt_on_short_packet(1);
let mut trb2 = Trb::new();
let event_trb = trb2.cast_mut::<EventDataTrb>().unwrap();
event_trb.set_trb_type(TrbType::EventData);
event_trb.set_event_data(0x12345678abcdef0);
event_trb.set_interrupt_on_completion(1);
let transfer = create_test_transfer(vec![trb1, trb2, trb1, trb2]);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 200)
.unwrap();
assert_eq!(
actions,
vec![
TransferAction::SendEvent {
code: TrbCompletionCode::Success,
gpa: 0x12345678abcdef0,
residual_or_edtla: 100,
event_data: true,
},
TransferAction::SendEvent {
code: TrbCompletionCode::Success,
gpa: 0x12345678abcdef0,
residual_or_edtla: 100,
event_data: true,
},
]
);
let actions = transfer
.process_td_results(&TransferStatus::Completed, 40)
.unwrap();
assert_eq!(
actions,
vec![
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x100,
residual_or_edtla: 60,
event_data: false,
},
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x12345678abcdef0,
residual_or_edtla: 40,
event_data: true,
},
TransferAction::SendEvent {
code: TrbCompletionCode::ShortPacket,
gpa: 0x12345678abcdef0,
residual_or_edtla: 0,
event_data: true,
},
]
);
}
}