ddcd08709f
The pcid_interface crate now reads the two env vars emitted by driver-manager's SpawnDecision committee: - REDBEAR_DRIVER_PCI_IRQ_MODE: lets the spawned child know whether to fall back to MSI / INTx instead of MSI-X - REDBEAR_DRIVER_DISABLE_ACCEL: lets the spawned child suppress hardware acceleration (matches PciQuirkFlags::DISABLE_ACCEL) Both values are stored on PciFunctionHandle and exposed via accessors (irq_mode_hint / disable_accel_hint) so the driver daemons can read them at startup. 6 new unit tests cover the env-var parsing. Refs: local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md § 5.2 D2.7.
643 lines
20 KiB
Rust
643 lines
20 KiB
Rust
use std::fs::File;
|
|
use std::io::prelude::*;
|
|
use std::os::fd::{FromRawFd, IntoRawFd, RawFd};
|
|
use std::path::Path;
|
|
use std::ptr::NonNull;
|
|
use std::{env, io};
|
|
use std::{fmt, process};
|
|
|
|
use daemon::Daemon;
|
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
|
|
pub use bar::PciBar;
|
|
pub use cap::VendorSpecificCapability;
|
|
pub use id::FullDeviceId;
|
|
pub use pci_types::PciAddress;
|
|
|
|
mod bar;
|
|
pub mod cap;
|
|
pub mod config;
|
|
mod id;
|
|
pub mod irq_helpers;
|
|
pub mod msi;
|
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
|
pub struct LegacyInterruptLine {
|
|
#[doc(hidden)]
|
|
pub irq: u8,
|
|
pub phandled: Option<(u32, [u32; 3], usize)>,
|
|
}
|
|
|
|
impl LegacyInterruptLine {
|
|
/// Get an IRQ handle for this interrupt line.
|
|
pub fn irq_handle(self, driver: &str) -> File {
|
|
if let Some((phandle, addr, cells)) = self.phandled {
|
|
let path = match cells {
|
|
1 => format!("/scheme/irq/phandle-{}/{}", phandle, addr[0]),
|
|
2 => format!("/scheme/irq/phandle-{}/{},{}", phandle, addr[0], addr[1]),
|
|
3 => format!(
|
|
"/scheme/irq/phandle-{}/{},{},{}",
|
|
phandle, addr[0], addr[1], addr[2]
|
|
),
|
|
_ => panic!(
|
|
"unexpected number of IRQ description cells for phandle {phandle}: {cells}"
|
|
),
|
|
};
|
|
File::create(path)
|
|
.unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}"))
|
|
} else {
|
|
File::open(format!("/scheme/irq/{}", self.irq))
|
|
.unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}"))
|
|
}
|
|
}
|
|
|
|
/// Non-panicking version of `irq_handle`.
|
|
pub fn try_irq_handle(self, _driver: &str) -> io::Result<File> {
|
|
if let Some((phandle, addr, cells)) = self.phandled {
|
|
let path = match cells {
|
|
1 => format!("/scheme/irq/phandle-{}/{}", phandle, addr[0]),
|
|
2 => format!("/scheme/irq/phandle-{}/{},{}", phandle, addr[0], addr[1]),
|
|
3 => format!(
|
|
"/scheme/irq/phandle-{}/{},{},{}",
|
|
phandle, addr[0], addr[1], addr[2]
|
|
),
|
|
_ => {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
format!("unexpected IRQ cells count: {cells}"),
|
|
))
|
|
}
|
|
};
|
|
File::create(path)
|
|
} else {
|
|
File::open(format!("/scheme/irq/{}", self.irq))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for LegacyInterruptLine {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
if let Some((phandle, addr, cells)) = self.phandled {
|
|
match cells {
|
|
1 => write!(f, "(phandle {}, {:?})", phandle, addr[0]),
|
|
2 => write!(f, "(phandle {}, {:?},{:?})", phandle, addr[0], addr[1]),
|
|
3 => write!(f, "(phandle {}, {:?})", phandle, addr),
|
|
_ => panic!(
|
|
"unexpected number of IRQ description cells for phandle {phandle}: {cells}"
|
|
),
|
|
}
|
|
} else {
|
|
write!(f, "{}", self.irq)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(remote = "PciAddress")]
|
|
struct PciAddressDef {
|
|
#[serde(getter = "PciAddress::segment")]
|
|
segment: u16,
|
|
#[serde(getter = "PciAddress::bus")]
|
|
bus: u8,
|
|
#[serde(getter = "PciAddress::device")]
|
|
device: u8,
|
|
#[serde(getter = "PciAddress::function")]
|
|
function: u8,
|
|
}
|
|
|
|
impl From<PciAddressDef> for PciAddress {
|
|
fn from(value: PciAddressDef) -> Self {
|
|
PciAddress::new(value.segment, value.bus, value.device, value.function)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
|
pub struct PciRom {
|
|
pub addr: u32,
|
|
pub size: u32,
|
|
pub enabled: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
|
pub struct PciFunction {
|
|
/// Address of the PCI function.
|
|
#[serde(with = "PciAddressDef")]
|
|
pub addr: PciAddress,
|
|
|
|
/// PCI Base Address Registers
|
|
pub bars: [PciBar; 6],
|
|
|
|
/// PCI Option ROM
|
|
pub rom: Option<PciRom>,
|
|
|
|
/// Legacy IRQ line: It's the responsibility of pcid to make sure that it be mapped in either
|
|
/// the I/O APIC or the 8259 PIC, so that the subdriver can map the interrupt vector directly.
|
|
/// The vector to map is always this field, plus 32.
|
|
/// If INTx# interrupts aren't supported at all this is `None`.
|
|
pub legacy_interrupt_line: Option<LegacyInterruptLine>,
|
|
|
|
/// All identifying information of the PCI function.
|
|
pub full_device_id: FullDeviceId,
|
|
}
|
|
impl PciFunction {
|
|
pub fn name(&self) -> String {
|
|
// FIXME stop replacing : with - once it is a valid character in scheme names
|
|
format!("pci-{}", self.addr).replace(':', "-")
|
|
}
|
|
|
|
pub fn display(&self) -> String {
|
|
let mut string = self.name();
|
|
let mut first = true;
|
|
for (i, bar) in self.bars.iter().enumerate() {
|
|
if !bar.is_none() {
|
|
if first {
|
|
first = false;
|
|
string.push_str(" on:");
|
|
}
|
|
string.push_str(&format!(" {i}={}", bar.display()));
|
|
}
|
|
}
|
|
if let Some(irq) = self.legacy_interrupt_line {
|
|
string.push_str(&format!(" IRQ: {irq}"));
|
|
}
|
|
string
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct SubdriverArguments {
|
|
pub func: PciFunction,
|
|
/// Full device ID (vendor, device, class, subclass, etc.) — pcid
|
|
/// reads this from the kernel's PCIe config space and passes it at
|
|
/// spawn time. Used by subdrivers to apply per-vendor quirks.
|
|
pub device_id: Option<crate::driver_interface::FullDeviceId>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
|
pub enum FeatureStatus {
|
|
Enabled,
|
|
Disabled,
|
|
}
|
|
|
|
impl FeatureStatus {
|
|
pub fn enabled(enabled: bool) -> Self {
|
|
if enabled {
|
|
Self::Enabled
|
|
} else {
|
|
Self::Disabled
|
|
}
|
|
}
|
|
pub fn is_enabled(&self) -> bool {
|
|
if let &Self::Enabled = self {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
|
pub enum PciFeature {
|
|
Msi,
|
|
MsiX,
|
|
}
|
|
impl PciFeature {
|
|
pub fn is_msi(self) -> bool {
|
|
if let Self::Msi = self {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
pub fn is_msix(self) -> bool {
|
|
if let Self::MsiX = self {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum PciFeatureInfo {
|
|
Msi(msi::MsiInfo),
|
|
MsiX(msi::MsixInfo),
|
|
}
|
|
|
|
// TODO: Remove these "features" and just go strait to the actual thing.
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
pub struct MsiSetFeatureInfo {
|
|
/// The Multi Message Enable field of the Message Control in the MSI Capability Structure,
|
|
/// is the log2 of the interrupt vectors, minus one. Can only be 0b000..=0b101.
|
|
pub multi_message_enable: Option<u8>,
|
|
|
|
/// The system-specific message address and data.
|
|
///
|
|
/// The message address contains things like the CPU that will be targeted, at least on
|
|
/// x86_64. The message data contains the actual interrupt vector (lower 8 bits) and
|
|
/// the kind of interrupt, at least on x86_64.
|
|
pub message_address_and_data: Option<msi::MsiAddrAndData>,
|
|
|
|
/// A bitmap of the vectors that are masked. This field is not guaranteed (and not likely,
|
|
/// at least according to the feature flags I got from QEMU), to exist.
|
|
pub mask_bits: Option<u32>,
|
|
}
|
|
|
|
/// Some flags that might be set simultaneously, but separately.
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
#[non_exhaustive]
|
|
pub enum SetFeatureInfo {
|
|
Msi(MsiSetFeatureInfo),
|
|
|
|
MsiX {
|
|
/// Masks the entire function, and all of its vectors.
|
|
function_mask: Option<bool>,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
#[non_exhaustive]
|
|
pub enum PcidClientRequest {
|
|
EnableDevice,
|
|
RequestConfig,
|
|
RequestFeatures,
|
|
RequestVendorCapabilities,
|
|
EnableFeature(PciFeature),
|
|
FeatureInfo(PciFeature),
|
|
SetFeatureInfo(SetFeatureInfo),
|
|
ReadConfig(u16),
|
|
WriteConfig(u16, u32),
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
#[non_exhaustive]
|
|
pub enum PcidServerResponseError {
|
|
NonexistentFeature(PciFeature),
|
|
InvalidBitPattern,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
#[non_exhaustive]
|
|
pub enum PcidClientResponse {
|
|
EnabledDevice,
|
|
Config(SubdriverArguments),
|
|
AllFeatures(Vec<PciFeature>),
|
|
VendorCapabilities(Vec<VendorSpecificCapability>),
|
|
FeatureEnabled(PciFeature),
|
|
FeatureStatus(PciFeature, FeatureStatus),
|
|
Error(PcidServerResponseError),
|
|
FeatureInfo(PciFeature, PciFeatureInfo),
|
|
SetFeatureInfo(PciFeature),
|
|
ReadConfig(u32),
|
|
WriteConfig,
|
|
}
|
|
|
|
pub struct MappedBar {
|
|
pub ptr: NonNull<u8>,
|
|
pub bar_size: usize,
|
|
}
|
|
|
|
/// A handle from a `pcid` client (e.g. `ahcid`) to `pcid`.
|
|
pub struct PciFunctionHandle {
|
|
channel: File,
|
|
config: SubdriverArguments,
|
|
mapped_bars: [Option<MappedBar>; 6],
|
|
irq_mode_hint: Option<String>,
|
|
disable_accel_hint: bool,
|
|
}
|
|
|
|
fn read_irq_mode_hint() -> Option<String> {
|
|
match env::var("REDBEAR_DRIVER_PCI_IRQ_MODE") {
|
|
Ok(v) if !v.is_empty() => Some(v),
|
|
Ok(_) => None,
|
|
Err(_) => None,
|
|
}
|
|
}
|
|
|
|
fn read_disable_accel_hint() -> bool {
|
|
match env::var("REDBEAR_DRIVER_DISABLE_ACCEL") {
|
|
Ok(v) if v == "1" || v.eq_ignore_ascii_case("true") || v == "yes" => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn send<T: Serialize>(w: &mut File, message: &T) {
|
|
let mut data = Vec::new();
|
|
bincode::serialize_into(&mut data, message).expect("couldn't serialize pcid message");
|
|
match w.write(&data) {
|
|
Ok(len) => assert_eq!(len, data.len()),
|
|
Err(err) => {
|
|
log::error!("writing pcid request failed: {err}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
fn recv<T: DeserializeOwned>(r: &mut File) -> T {
|
|
let mut length_bytes = [0u8; 8];
|
|
if let Err(err) = r.read_exact(&mut length_bytes) {
|
|
log::error!("reading pcid response length failed: {err}");
|
|
process::exit(1);
|
|
}
|
|
let length = u64::from_le_bytes(length_bytes);
|
|
if length > 0x100_000 {
|
|
panic!("pcid_interface: buffer too large");
|
|
}
|
|
let mut data = vec![0u8; length as usize];
|
|
if let Err(err) = r.read_exact(&mut data) {
|
|
log::error!("reading pcid response failed: {err}");
|
|
process::exit(1);
|
|
}
|
|
|
|
bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message")
|
|
}
|
|
|
|
impl PciFunctionHandle {
|
|
pub fn connect_default() -> Self {
|
|
let channel_fd = match env::var("PCID_CLIENT_CHANNEL") {
|
|
Ok(channel_fd) => channel_fd,
|
|
Err(err) => {
|
|
log::error!("PCID_CLIENT_CHANNEL invalid: {err}");
|
|
process::exit(1);
|
|
}
|
|
};
|
|
let channel_fd = match channel_fd.parse::<RawFd>() {
|
|
Ok(channel_fd) => channel_fd,
|
|
Err(err) => {
|
|
log::error!("PCID_CLIENT_CHANNEL invalid: {err}");
|
|
process::exit(1);
|
|
}
|
|
};
|
|
Self::connect_common(channel_fd)
|
|
}
|
|
|
|
pub fn connect_by_path(device_path: &Path) -> io::Result<Self> {
|
|
let channel_fd = libredox::call::open(
|
|
device_path.join("channel").to_str().unwrap(),
|
|
libredox::flag::O_RDWR,
|
|
0,
|
|
)?;
|
|
Ok(Self::connect_common(channel_fd as RawFd))
|
|
}
|
|
|
|
fn connect_common(channel_fd: i32) -> PciFunctionHandle {
|
|
let mut channel = unsafe { File::from_raw_fd(channel_fd) };
|
|
|
|
send(&mut channel, &PcidClientRequest::RequestConfig);
|
|
let config = match recv(&mut channel) {
|
|
PcidClientResponse::Config(a) => a,
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
Self {
|
|
channel,
|
|
config,
|
|
mapped_bars: [const { None }; 6],
|
|
irq_mode_hint: read_irq_mode_hint(),
|
|
disable_accel_hint: read_disable_accel_hint(),
|
|
}
|
|
}
|
|
|
|
/// Returns the IRQ-mode hint read from `REDBEAR_DRIVER_PCI_IRQ_MODE` at
|
|
/// connect time, or `None` if the env var was unset. Drivers should
|
|
/// honour this hint when allocating interrupt vectors.
|
|
pub fn irq_mode_hint(&self) -> Option<&str> {
|
|
self.irq_mode_hint.as_deref()
|
|
}
|
|
|
|
/// Returns the disable-acceleration hint read from
|
|
/// `REDBEAR_DRIVER_DISABLE_ACCEL` at connect time. Drivers (notably
|
|
/// GPU drivers) should suppress hardware acceleration paths when this
|
|
/// returns `true`.
|
|
pub fn disable_accel_hint(&self) -> bool {
|
|
self.disable_accel_hint
|
|
}
|
|
|
|
pub fn into_inner_fd(self) -> RawFd {
|
|
self.channel.into_raw_fd()
|
|
}
|
|
|
|
fn send(&mut self, req: &PcidClientRequest) {
|
|
send(&mut self.channel, req)
|
|
}
|
|
fn recv(&mut self) -> PcidClientResponse {
|
|
recv(&mut self.channel)
|
|
}
|
|
|
|
pub fn config(&self) -> SubdriverArguments {
|
|
self.config.clone()
|
|
}
|
|
|
|
pub fn enable_device(&mut self) {
|
|
self.send(&PcidClientRequest::EnableDevice);
|
|
match self.recv() {
|
|
PcidClientResponse::EnabledDevice => {}
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_vendor_capabilities(&mut self) -> Vec<VendorSpecificCapability> {
|
|
self.send(&PcidClientRequest::RequestVendorCapabilities);
|
|
match self.recv() {
|
|
PcidClientResponse::VendorCapabilities(a) => a,
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME turn into struct with bool fields
|
|
pub fn fetch_all_features(&mut self) -> Vec<PciFeature> {
|
|
self.send(&PcidClientRequest::RequestFeatures);
|
|
match self.recv() {
|
|
PcidClientResponse::AllFeatures(a) => a,
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
pub fn enable_feature(&mut self, feature: PciFeature) {
|
|
self.send(&PcidClientRequest::EnableFeature(feature));
|
|
match self.recv() {
|
|
PcidClientResponse::FeatureEnabled(feat) if feat == feature => {}
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
pub fn feature_info(&mut self, feature: PciFeature) -> PciFeatureInfo {
|
|
self.send(&PcidClientRequest::FeatureInfo(feature));
|
|
match self.recv() {
|
|
PcidClientResponse::FeatureInfo(feat, info) if feat == feature => info,
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
pub fn set_feature_info(&mut self, info: SetFeatureInfo) {
|
|
self.send(&PcidClientRequest::SetFeatureInfo(info));
|
|
match self.recv() {
|
|
PcidClientResponse::SetFeatureInfo(_) => {}
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
pub unsafe fn read_config(&mut self, offset: u16) -> u32 {
|
|
self.send(&PcidClientRequest::ReadConfig(offset));
|
|
match self.recv() {
|
|
PcidClientResponse::ReadConfig(value) => value,
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
pub unsafe fn write_config(&mut self, offset: u16, value: u32) {
|
|
self.send(&PcidClientRequest::WriteConfig(offset, value));
|
|
match self.recv() {
|
|
PcidClientResponse::WriteConfig => {}
|
|
other => {
|
|
log::error!("received wrong pcid response: {other:?}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar {
|
|
let mapped_bar = &mut self.mapped_bars[bir as usize];
|
|
if let Some(mapped_bar) = mapped_bar {
|
|
mapped_bar
|
|
} else {
|
|
let (bar, bar_size) = self.config.func.bars[bir as usize].expect_mem();
|
|
|
|
let ptr = match unsafe {
|
|
common::physmap(
|
|
bar,
|
|
bar_size,
|
|
common::Prot::RW,
|
|
// FIXME once the kernel supports this use write-through for prefetchable BAR
|
|
common::MemoryType::Uncacheable,
|
|
)
|
|
} {
|
|
Ok(ptr) => ptr,
|
|
Err(err) => {
|
|
log::error!("failed to map BAR at {bar:016X}: {err}");
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
mapped_bar.insert(MappedBar {
|
|
ptr: NonNull::new(ptr.cast::<u8>()).expect("Mapping a BAR resulted in a nullptr"),
|
|
bar_size,
|
|
})
|
|
}
|
|
}
|
|
pub unsafe fn try_map_bar(&mut self, bir: u8) -> Result<&MappedBar, String> {
|
|
let mapped_bar = &mut self.mapped_bars[bir as usize];
|
|
if mapped_bar.is_none() {
|
|
let (bar, bar_size) = self.config.func.bars[bir as usize]
|
|
.try_mem()
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
let ptr = unsafe {
|
|
common::physmap(
|
|
bar,
|
|
bar_size,
|
|
common::Prot::RW,
|
|
common::MemoryType::Uncacheable,
|
|
)
|
|
}
|
|
.map_err(|e| format!("physmap failed: {e}"))?;
|
|
|
|
let ptr = NonNull::new(ptr.cast::<u8>())
|
|
.ok_or_else(|| "physmap returned null".to_string())?;
|
|
|
|
mapped_bar.insert(MappedBar {
|
|
ptr,
|
|
bar_size,
|
|
});
|
|
}
|
|
Ok(mapped_bar.as_ref().expect("BAR was just mapped"))
|
|
}
|
|
}
|
|
|
|
pub fn pci_daemon<F: FnOnce(Daemon, PciFunctionHandle) -> !>(f: F) -> ! {
|
|
Daemon::new(|daemon| {
|
|
common::init();
|
|
let pcid_handle = PciFunctionHandle::connect_default();
|
|
f(daemon, pcid_handle)
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod hint_tests {
|
|
use super::{read_disable_accel_hint, read_irq_mode_hint};
|
|
use std::sync::Mutex;
|
|
|
|
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
|
|
|
#[test]
|
|
fn irq_mode_hint_returns_none_when_unset() {
|
|
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
unsafe { std::env::remove_var("REDBEAR_DRIVER_PCI_IRQ_MODE") };
|
|
assert!(read_irq_mode_hint().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn irq_mode_hint_returns_some_when_set() {
|
|
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
unsafe { std::env::set_var("REDBEAR_DRIVER_PCI_IRQ_MODE", "intx_or_msi") };
|
|
let v = read_irq_mode_hint();
|
|
unsafe { std::env::remove_var("REDBEAR_DRIVER_PCI_IRQ_MODE") };
|
|
assert_eq!(v.as_deref(), Some("intx_or_msi"));
|
|
}
|
|
|
|
#[test]
|
|
fn irq_mode_hint_returns_none_for_empty_value() {
|
|
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
unsafe { std::env::set_var("REDBEAR_DRIVER_PCI_IRQ_MODE", "") };
|
|
let v = read_irq_mode_hint();
|
|
unsafe { std::env::remove_var("REDBEAR_DRIVER_PCI_IRQ_MODE") };
|
|
assert!(v.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn disable_accel_hint_defaults_false() {
|
|
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
unsafe { std::env::remove_var("REDBEAR_DRIVER_DISABLE_ACCEL") };
|
|
assert!(!read_disable_accel_hint());
|
|
}
|
|
|
|
#[test]
|
|
fn disable_accel_hint_accepts_truthy_values() {
|
|
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
for truthy in &["1", "true", "TRUE", "yes"] {
|
|
unsafe { std::env::set_var("REDBEAR_DRIVER_DISABLE_ACCEL", truthy) };
|
|
let v = read_disable_accel_hint();
|
|
unsafe { std::env::remove_var("REDBEAR_DRIVER_DISABLE_ACCEL") };
|
|
assert!(v, "value {:?} should be truthy", truthy);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn disable_accel_hint_rejects_falsy_values() {
|
|
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
for falsy in &["0", "false", "no", ""] {
|
|
unsafe { std::env::set_var("REDBEAR_DRIVER_DISABLE_ACCEL", falsy) };
|
|
let v = read_disable_accel_hint();
|
|
unsafe { std::env::remove_var("REDBEAR_DRIVER_DISABLE_ACCEL") };
|
|
assert!(!v, "value {:?} should be falsy", falsy);
|
|
}
|
|
}
|
|
}
|