milestone: desktop path Phases 1-5

Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests
Phase 2 (Wayland Compositor): bounded scaffold, zero warnings
Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick)
Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker
Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker

Build: zero warnings, all scripts syntax-clean. Oracle-verified.
This commit is contained in:
2026-04-29 09:54:06 +01:00
parent b23714f542
commit 8acc73d774
508 changed files with 76526 additions and 396 deletions
@@ -0,0 +1,30 @@
[package]
name = "ihdgd"
description = "Intel graphics driver"
version = "0.1.0"
edition = "2021"
[dependencies]
bitbang-hal = "0.3"
drm-sys.workspace = true
edid.workspace = true #TODO: edid is abandoned, fork it and maintain?
#TODO: waiting for bitbang-hal to update to embedded-hal 1.0
embedded-hal = { version = "0.2.7", features = ["unproven"] }
log.workspace = true
nb = "1.0"
# Patched to allow for exact range allocation
range-alloc = { git = "https://github.com/jackpot51/range-alloc.git" }
void = "1.0"
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
driver-graphics = { path = "../driver-graphics" }
pcid = { path = "../../pcid" }
libredox.workspace = true
redox-scheme.workspace = true
redox_event.workspace = true
redox_syscall.workspace = true
[lints]
workspace = true
@@ -0,0 +1,55 @@
[[drivers]]
name = "Intel HD Graphics"
class = 0x03
ids = { 0x8086 = [
# Kaby Lake from Volume 4: Configurations in
# https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/kaby-lake.html
0x5912,
0x5916,
0x591B,
0x591E,
0x5926,
# Comet Lake from Volume 1: Configurations in
# https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/comet-lake.html
0x9B21,
0x9B41,
0x9BA4,
0x9BAA,
0x9BAC,
0x9BC4,
0x9BC5,
0x9BC6,
0x9BC8,
0x9BCA,
0x9BCC,
0x9BE6,
0x9BF6,
# Tiger Lake Mobile from Volume 4: Configurations in
# https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/tiger-lake.html
0x9A40,
0x9A49,
0x9A60,
0x9A68,
0x9A70,
0x9A78,
# Alchemist from Volume 4: Configurations in
# https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/alchemist-arctic-sound-m.html
0x5690, # A770M
0x5691, # A730M
0x5692, # A550M
0x5693, # A370M
0x5694, # A350M
0x5696, # A570M
0x5697, # A530M
0x56A0, # A770
0x56A1, # A750
0x56A5, # A380
0x56A6, # A310
0x56B0, # Pro A30M
0x56B1, # Pro A40/A50
0x56B2, # Pro A60M
0x56B3, # Pro A60
0x56C0, # GPU Flex 170
0x56C1, # GPU Flex 140
] }
command = ["ihdgd"]
@@ -0,0 +1,169 @@
use common::{io::Io, timeout::Timeout};
use embedded_hal::blocking::i2c::{self, Operation, SevenBitAddress, Transactional};
use super::ddi::*;
pub struct Aux<'a> {
ddi: &'a mut Ddi,
}
impl<'a> Aux<'a> {
pub fn new(ddi: &'a mut Ddi) -> Self {
Self { ddi }
}
}
impl<'a> Transactional for Aux<'a> {
type Error = ();
fn exec(&mut self, addr7: SevenBitAddress, full_ops: &mut [Operation<'_>]) -> Result<(), ()> {
// Break ops into 16-byte chunks that will fit into aux data
let mut ops = Vec::new();
for op in full_ops.iter_mut() {
match op {
Operation::Read(buf) => {
for chunk in buf.chunks_mut(16) {
ops.push(Operation::Read(chunk));
}
}
Operation::Write(buf) => {
for chunk in buf.chunks(16) {
ops.push(Operation::Write(chunk));
}
}
}
}
let ops_len = ops.len();
for (i, op) in ops.iter_mut().enumerate() {
// Write header and data
let mut header = 0;
match op {
Operation::Read(_) => {
header |= 1 << 4;
}
Operation::Write(_) => (),
}
if (i + 1) < ops_len {
// Middle of transaction
header |= 1 << 6;
}
let mut aux_datas = [0u8; 20];
let mut aux_data_i = 0;
aux_datas[aux_data_i] = header;
aux_data_i += 1;
//TODO: what is this byte?
aux_datas[aux_data_i] = 0;
aux_data_i += 1;
aux_datas[aux_data_i] = addr7;
aux_data_i += 1;
match op {
Operation::Read(buf) => {
if !buf.is_empty() {
aux_datas[aux_data_i] = (buf.len() - 1) as u8;
aux_data_i += 1;
}
}
Operation::Write(buf) => {
if !buf.is_empty() {
aux_datas[aux_data_i] = (buf.len() - 1) as u8;
aux_data_i += 1;
for b in buf.iter() {
aux_datas[aux_data_i] = *b;
aux_data_i += 1;
}
}
}
}
// Write data to registers (big endian, dword access only)
for (i, chunk) in aux_datas.chunks(4).enumerate() {
let mut bytes = [0; 4];
bytes[..chunk.len()].copy_from_slice(&chunk);
self.ddi.aux_datas[i].write(u32::from_be_bytes(bytes));
}
let mut v = self.ddi.aux_ctl.read();
// Set length
v &= !DDI_AUX_CTL_SIZE_MASK;
v |= (aux_data_i as u32) << DDI_AUX_CTL_SIZE_SHIFT;
// Set timeout
v &= !DDI_AUX_CTL_TIMEOUT_MASK;
v |= DDI_AUX_CTL_TIMEOUT_4000US;
// Set I/O select to legacy (cleared)
//TODO: TBT support?
v &= !DDI_AUX_CTL_IO_SELECT;
// Start transaction
v |= DDI_AUX_CTL_BUSY;
self.ddi.aux_ctl.write(v);
// Wait while busy
let timeout = Timeout::from_secs(1);
while self.ddi.aux_ctl.readf(DDI_AUX_CTL_BUSY) {
timeout.run().map_err(|()| {
log::debug!(
"AUX I2C transaction wait timeout 0x{:08X}",
self.ddi.aux_ctl.read()
);
()
})?;
}
// Read result
v = self.ddi.aux_ctl.read();
if (v & DDI_AUX_CTL_TIMEOUT_ERROR) != 0 {
log::debug!("AUX I2C transaction timeout error");
return Err(());
}
if (v & DDI_AUX_CTL_RECEIVE_ERROR) != 0 {
log::debug!("AUX I2C transaction receive error");
return Err(());
}
if (v & DDI_AUX_CTL_DONE) == 0 {
log::debug!("AUX I2C transaction done not set");
return Err(());
}
// Read data from registers (big endian, dword access only)
for (i, chunk) in aux_datas.chunks_mut(4).enumerate() {
let bytes = self.ddi.aux_datas[i].read().to_be_bytes();
chunk.copy_from_slice(&bytes[..chunk.len()]);
}
aux_data_i = 0;
let response = aux_datas[aux_data_i];
if response != 0 {
log::debug!("AUX I2C unexpected response {:02X}", response);
return Err(());
}
aux_data_i += 1;
match op {
Operation::Read(buf) => {
if !buf.is_empty() {
for b in buf.iter_mut() {
*b = aux_datas[aux_data_i];
aux_data_i += 1;
}
}
}
Operation::Write(_) => (),
}
}
Ok(())
}
}
impl<'a> i2c::WriteRead for Aux<'a> {
type Error = ();
fn write_read(
&mut self,
addr7: SevenBitAddress,
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), ()> {
self.exec(
addr7,
&mut [Operation::Write(bytes), Operation::Read(buffer)],
)
}
}
@@ -0,0 +1,233 @@
use common::io::{Io, Mmio, ReadOnly};
use std::mem;
use syscall::error::{Error, Result, EIO};
use super::MmioRegion;
const MBOX_VBT: u32 = 1 << 3;
fn read_bios_string(array: &[ReadOnly<Mmio<u8>>]) -> String {
let mut string = String::new();
for reg in array.iter() {
let b = reg.read();
if b == 0 {
break;
}
string.push(b as char);
}
string
}
#[repr(C, packed)]
pub struct BiosHeader {
signature: [ReadOnly<Mmio<u8>>; 16],
size: ReadOnly<Mmio<u32>>,
struct_version: ReadOnly<Mmio<u32>>,
system_bios_version: [ReadOnly<Mmio<u8>>; 32],
video_bios_version: [ReadOnly<Mmio<u8>>; 16],
//TODO: should we write graphics driver version?
graphics_driver_version: [ReadOnly<Mmio<u8>>; 16],
mailboxes: ReadOnly<Mmio<u32>>,
driver_model: Mmio<u32>,
platform_config: ReadOnly<Mmio<u32>>,
gop_version: [ReadOnly<Mmio<u8>>; 32],
}
impl BiosHeader {
pub fn dump(&self) {
eprint!(" op region header");
eprint!(" signature {:?}", read_bios_string(&self.signature));
eprint!(" size {:08X}", self.size.read());
eprint!(" struct_version {:08X}", self.struct_version.read());
eprint!(
" system_bios_version {:?}",
read_bios_string(&self.system_bios_version)
);
eprint!(
" video_bios_version {:?}",
read_bios_string(&self.video_bios_version)
);
eprint!(
" graphics_driver_version {:?}",
read_bios_string(&self.graphics_driver_version)
);
eprint!(" mailboxes {:08X}", self.mailboxes.read());
eprint!(" driver_model {:08X}", self.driver_model.read());
eprint!(" platform_config {:08X}", self.platform_config.read());
eprint!(" gop_version {:?}", read_bios_string(&self.gop_version));
eprintln!();
}
}
#[repr(C, packed)]
pub struct VbtHeader {
signature: [ReadOnly<Mmio<u8>>; 20],
version: Mmio<u16>,
header_size: Mmio<u16>,
vbt_size: Mmio<u16>,
vbt_checksum: Mmio<u8>,
_rsvd: Mmio<u8>,
bdb_offset: Mmio<u32>,
aim_offsets: [Mmio<u32>; 4],
}
impl VbtHeader {
pub fn dump(&self) {
eprint!(" VBT header");
eprint!(" signature {:?}", read_bios_string(&self.signature));
eprint!(" version {:04X}", self.version.read());
eprint!(" header_size {:04X}", self.header_size.read());
eprint!(" vbt_size {:04X}", self.vbt_size.read());
eprint!(" vbt_checksum {:02X}", self.vbt_checksum.read());
eprint!(" bdb_offset {:08X}", self.bdb_offset.read());
for (i, aim_offset) in self.aim_offsets.iter().enumerate() {
eprint!(" aim_offset{} {:08X}", i, aim_offset.read());
}
eprintln!();
}
}
#[repr(C, packed)]
pub struct BdbHeader {
signature: [ReadOnly<Mmio<u8>>; 16],
version: Mmio<u16>,
header_size: Mmio<u16>,
bdb_size: Mmio<u16>,
}
impl BdbHeader {
pub fn dump(&self) {
eprint!(" BDB header");
eprint!(" signature {:?}", read_bios_string(&self.signature));
eprint!(" version {:04X}", self.version.read());
eprint!(" header_size {:04X}", self.header_size.read());
eprint!(" bdb_size {:04X}", self.bdb_size.read());
eprintln!();
}
}
#[repr(C, packed)]
pub struct BdbBlock {
id: Mmio<u8>,
size: Mmio<u16>,
}
impl BdbBlock {
pub fn dump(&self) {
eprint!(" BDB block");
eprint!(" id {}", self.id.read());
eprint!(" size {}", self.size.read());
eprintln!();
}
}
#[repr(C, packed)]
pub struct BdbGeneralDefinitions {
crt_ddc_gmbus_pin: Mmio<u8>,
dpms: Mmio<u8>,
boot_displays: [Mmio<u8>; 2],
child_dev_size: Mmio<u8>,
}
impl BdbGeneralDefinitions {
pub fn dump(&self) {
eprint!(" BDB general definitions");
eprint!(" crt_ddc_gmbus_pin {:02X}", self.crt_ddc_gmbus_pin.read());
eprint!(" dpms {:02X}", self.dpms.read());
for (i, boot_display) in self.boot_displays.iter().enumerate() {
eprint!(" boot_display{} {:02X}", i, boot_display.read());
}
eprint!(" child_dev_size {:02X}", self.child_dev_size.read());
eprintln!();
}
}
pub struct Bios {
region: MmioRegion,
header: &'static mut BiosHeader,
}
impl Bios {
pub fn new(region: MmioRegion) -> Result<Self> {
let header = unsafe { &mut *(region.virt as *mut BiosHeader) };
header.dump();
{
let sig = read_bios_string(&header.signature);
if sig != "IntelGraphicsMem" {
log::warn!("invalid op region signature {:?}", sig);
return Err(Error::new(EIO));
}
}
let size = (header.size.read() as usize) * 1024;
if size != region.size {
log::warn!("invalid op region size {}", size);
return Err(Error::new(EIO));
}
//TODO: other mailboxes?
if header.mailboxes.read() & MBOX_VBT == 0 {
log::warn!("op region does not support VBT mailbox");
return Err(Error::new(EIO));
}
//TODO: read VBT from mailbox 3 RVDA (0x3BA) and RVDS (0x3C2) if missing in mailbox 4
let vbt_addr = region.virt + 1024;
let vbt_header = unsafe { &*(vbt_addr as *const VbtHeader) };
vbt_header.dump();
//TODO: check vbt checksum
{
let sig = read_bios_string(&vbt_header.signature);
if !sig.starts_with("$VBT") {
log::warn!("invalid VBT signature {:?}", sig);
return Err(Error::new(EIO));
}
}
let bdb_addr = vbt_addr + (vbt_header.bdb_offset.read() as usize);
let bdb_header = unsafe { &*(bdb_addr as *const BdbHeader) };
bdb_header.dump();
{
let sig = read_bios_string(&bdb_header.signature);
if sig != "BIOS_DATA_BLOCK " {
log::warn!("invalid BDB signature {:?}", sig);
bdb_header.dump();
return Err(Error::new(EIO));
}
}
let mut block_addr = bdb_addr + bdb_header.header_size.read() as usize;
let block_end = bdb_addr + bdb_header.bdb_size.read() as usize;
while block_addr + mem::size_of::<BdbBlock>() <= block_end {
let block = unsafe { &*(block_addr as *const BdbBlock) };
//TODO: mipi sequence v3 has different size field
let id = block.id.read();
let size = block.size.read() as usize;
block_addr += mem::size_of::<BdbBlock>();
if block_addr + size <= block_end {
match id {
2 => {
if size >= mem::size_of::<BdbGeneralDefinitions>() {
let gen_defs =
unsafe { &*(block_addr as *const BdbGeneralDefinitions) };
gen_defs.dump();
} else {
log::warn!("BDB general definitions too small");
block.dump();
}
}
_ => block.dump(),
}
block_addr += block.size.read() as usize;
} else {
log::warn!("truncated block id {} size {}", id, size);
break;
}
}
Ok(Self { region, header })
}
}
@@ -0,0 +1,46 @@
use std::{ptr, slice};
use crate::device::ggtt::GlobalGtt;
use crate::device::MmioRegion;
#[derive(Debug)]
pub struct GpuBuffer {
pub virt: *mut u8,
pub gm_offset: u32,
pub size: u32,
}
impl GpuBuffer {
pub unsafe fn new(gm: &MmioRegion, gm_offset: u32, size: u32, clear: bool) -> Self {
let virt = ptr::with_exposed_provenance_mut::<u8>(gm.virt + gm_offset as usize);
if clear {
let onscreen = slice::from_raw_parts_mut(virt, size as usize);
onscreen.fill(0);
}
Self {
virt,
gm_offset,
size,
}
}
pub fn alloc(gm: &MmioRegion, ggtt: &mut GlobalGtt, size: u32) -> syscall::Result<Self> {
let gm_offset = ggtt.alloc_phys_mem(size)?;
Ok(unsafe { GpuBuffer::new(gm, gm_offset, size, true) })
}
pub fn alloc_dumb(
gm: &MmioRegion,
ggtt: &mut GlobalGtt,
width: u32,
height: u32,
) -> syscall::Result<(Self, u32)> {
//TODO: documentation on this is not great
let stride = (width * 4).next_multiple_of(64);
Ok((GpuBuffer::alloc(gm, ggtt, stride * height)?, stride))
}
}
@@ -0,0 +1,758 @@
use common::io::{Io, MmioPtr, WriteOnly};
use common::timeout::Timeout;
use embedded_hal::prelude::*;
use std::sync::Arc;
use syscall::error::{Error, Result, EIO};
use crate::device::aux::Aux;
use crate::device::power::PowerWells;
use crate::device::{CallbackGuard, Gmbus};
use super::{GpioPort, MmioRegion};
// IHD-OS-TGL-Vol 2c-12.21 DDI_AUX_CTL
pub const DDI_AUX_CTL_BUSY: u32 = 1 << 31;
pub const DDI_AUX_CTL_DONE: u32 = 1 << 30;
pub const DDI_AUX_CTL_TIMEOUT_ERROR: u32 = 1 << 28;
pub const DDI_AUX_CTL_TIMEOUT_SHIFT: u32 = 26;
pub const DDI_AUX_CTL_TIMEOUT_MASK: u32 = 0b11 << DDI_AUX_CTL_TIMEOUT_SHIFT;
pub const DDI_AUX_CTL_TIMEOUT_4000US: u32 = 0b11 << DDI_AUX_CTL_TIMEOUT_SHIFT;
pub const DDI_AUX_CTL_RECEIVE_ERROR: u32 = 1 << 25;
pub const DDI_AUX_CTL_SIZE_SHIFT: u32 = 20;
pub const DDI_AUX_CTL_SIZE_MASK: u32 = 0b11111 << 20;
pub const DDI_AUX_CTL_IO_SELECT: u32 = 1 << 11;
// IHD-OS-TGL-Vol 2c-12.21 DDI_BUF_CTL
pub const DDI_BUF_CTL_ENABLE: u32 = 1 << 31;
pub const DDI_BUF_CTL_IDLE: u32 = 1 << 7;
// IHD-OS-TGL-Vol 2c-12.21 PORT_CL_DW5
pub const PORT_CL_DW5_SUS_CLOCK_MASK: u32 = 0b11 << 0;
// IHD-OS-TGL-Vol 2c-12.21 PORT_CL_DW10
pub const PORT_CL_DW10_EDP4K2K_MODE_OVRD_EN: u32 = 1 << 3;
pub const PORT_CL_DW10_EDP4K2K_MODE_OVRD_VAL: u32 = 1 << 2;
// IHD-OS-TGL-Vol 2c-12.21 PORT_PCS_DW9
pub const PORT_PCS_DW1_CMNKEEPER_ENABLE: u32 = 1 << 26;
// IHD-OS-TGL-Vol 2c-12.21 PORT_TX_DW2
pub const PORT_TX_DW2_SWING_SEL_UPPER_SHIFT: u32 = 15;
pub const PORT_TX_DW2_SWING_SEL_UPPER_MASK: u32 = 1 << PORT_TX_DW2_SWING_SEL_UPPER_SHIFT;
pub const PORT_TX_DW2_SWING_SEL_LOWER_SHIFT: u32 = 11;
pub const PORT_TX_DW2_SWING_SEL_LOWER_MASK: u32 = 0b111 << PORT_TX_DW2_SWING_SEL_LOWER_SHIFT;
pub const PORT_TX_DW2_RCOMP_SCALAR_SHIFT: u32 = 0;
pub const PORT_TX_DW2_RCOMP_SCALAR_MASK: u32 = 0xFF << PORT_TX_DW2_RCOMP_SCALAR_SHIFT;
// IHD-OS-TGL-Vol 2c-12.21 PORT_TX_DW4
pub const PORT_TX_DW4_SELECT: u32 = 1 << 31;
pub const PORT_TX_DW4_POST_CURSOR_1_SHIFT: u32 = 12;
pub const PORT_TX_DW4_POST_CURSOR_1_MASK: u32 = 0b111111 << PORT_TX_DW4_POST_CURSOR_1_SHIFT;
pub const PORT_TX_DW4_POST_CURSOR_2_SHIFT: u32 = 6;
pub const PORT_TX_DW4_POST_CURSOR_2_MASK: u32 = 0b111111 << PORT_TX_DW4_POST_CURSOR_2_SHIFT;
pub const PORT_TX_DW4_CURSOR_COEFF_SHIFT: u32 = 0;
pub const PORT_TX_DW4_CURSOR_COEFF_MASK: u32 = 0b111111 << PORT_TX_DW4_CURSOR_COEFF_SHIFT;
// IHD-OS-TGL-Vol 2c-12.21 PORT_TX_DW5
pub const PORT_TX_DW5_TRAINING_ENABLE: u32 = 1 << 31;
pub const PORT_TX_DW5_DISABLE_2_TAP_SHIFT: u32 = 29;
pub const PORT_TX_DW5_DISABLE_2_TAP: u32 = 1 << PORT_TX_DW5_DISABLE_2_TAP_SHIFT;
pub const PORT_TX_DW5_DISABLE_3_TAP: u32 = 1 << 29;
pub const PORT_TX_DW5_CURSOR_PROGRAM: u32 = 1 << 26;
pub const PORT_TX_DW5_COEFF_POLARITY: u32 = 1 << 25;
pub const PORT_TX_DW5_SCALING_MODE_SEL_SHIFT: u32 = 18;
pub const PORT_TX_DW5_SCALING_MODE_SEL_MASK: u32 = 0b111 << PORT_TX_DW5_SCALING_MODE_SEL_SHIFT;
pub const PORT_TX_DW5_RTERM_SELECT_SHIFT: u32 = 3;
pub const PORT_TX_DW5_RTERM_SELECT_MASK: u32 = 0b111 << PORT_TX_DW5_RTERM_SELECT_SHIFT;
// IHD-OS-TGL-Vol 2c-12.21 PORT_TX_DW7
pub const PORT_TX_DW7_N_SCALAR_SHIFT: u32 = 24;
#[derive(Clone, Copy, Debug)]
#[repr(usize)]
pub enum PortClReg {
Dw5 = 0x14,
Dw10 = 0x28,
Dw12 = 0x30,
Dw15 = 0x3C,
Dw16 = 0x40,
}
#[derive(Clone, Copy, Debug)]
#[repr(usize)]
pub enum PortCompReg {
Dw0 = 0x100,
Dw1 = 0x104,
Dw3 = 0x10C,
Dw8 = 0x120,
Dw9 = 0x124,
Dw10 = 0x128,
}
#[derive(Clone, Copy, Debug)]
#[repr(usize)]
pub enum PortPcsReg {
Dw1 = 0x04,
Dw9 = 0x24,
}
#[derive(Clone, Copy, Debug)]
#[repr(usize)]
pub enum PortTxReg {
Dw0 = 0x80,
Dw1 = 0x84,
Dw2 = 0x88,
Dw4 = 0x90,
Dw5 = 0x94,
Dw6 = 0x98,
Dw7 = 0x9C,
Dw8 = 0xA0,
}
#[derive(Clone, Copy, Debug)]
#[repr(usize)]
pub enum PortLane {
Aux = 0x300,
Grp = 0x600,
Ln0 = 0x800,
Ln1 = 0x900,
Ln2 = 0xA00,
Ln3 = 0xB00,
}
pub struct Ddi {
pub name: &'static str,
pub index: usize,
pub gttmm: Arc<MmioRegion>,
pub port_base: Option<usize>,
pub aux_ctl: MmioPtr<u32>,
pub aux_datas: [MmioPtr<u32>; 5],
pub buf_ctl: MmioPtr<u32>,
pub dpclka_cfgcr0_clock_shift: Option<u32>,
pub dpclka_cfgcr0_clock_off: Option<u32>,
pub gmbus_pin_pair: Option<u8>,
pub gpio_port: Option<GpioPort>,
pub pwr_well_ctl_aux_request: u32,
pub pwr_well_ctl_aux_state: u32,
pub pwr_well_ctl_ddi_request: u32,
pub pwr_well_ctl_ddi_state: u32,
pub sde_interrupt_hotplug: Option<u32>,
pub transcoder_index: Option<u32>,
}
//TODO: verify offsets and count using DeviceKind?
impl Ddi {
pub fn dump(&self) {
eprint!("Ddi {} {}", self.name, self.index);
eprint!(" buf_ctl {:08X}", self.buf_ctl.read());
let lanes = [PortLane::Ln0, PortLane::Ln1, PortLane::Ln2, PortLane::Ln3];
for reg in [
PortClReg::Dw5,
PortClReg::Dw10,
PortClReg::Dw12,
PortClReg::Dw15,
PortClReg::Dw16,
] {
if let Some(mmio) = self.port_cl(reg) {
eprint!(" CL_{:?} {:08X}", reg, mmio.read());
}
}
for reg in [PortPcsReg::Dw1, PortPcsReg::Dw9] {
for lane in lanes {
if let Some(mmio) = self.port_pcs(reg, lane) {
eprint!(" PCS_{:?}_{:?} {:08X}", reg, lane, mmio.read());
}
}
}
for reg in [
PortTxReg::Dw0,
PortTxReg::Dw1,
PortTxReg::Dw2,
PortTxReg::Dw4,
PortTxReg::Dw5,
PortTxReg::Dw6,
PortTxReg::Dw7,
PortTxReg::Dw8,
] {
for lane in lanes {
if let Some(mmio) = self.port_tx(reg, lane) {
eprint!(" TX_{:?}_{:?} {:08X}", reg, lane, mmio.read());
}
}
}
eprintln!();
}
fn port_reg(&self, offset: usize) -> Option<MmioPtr<u32>> {
//TODO: handle gttmm.mmio error?
unsafe { self.gttmm.mmio(self.port_base? + offset).ok() }
}
pub fn port_cl(&self, reg: PortClReg) -> Option<MmioPtr<u32>> {
self.port_reg(reg as usize)
}
pub fn port_comp(&self, reg: PortCompReg) -> Option<MmioPtr<u32>> {
self.port_reg(reg as usize)
}
//TODO: return WriteOnly if PortLane::Grp?
pub fn port_pcs(&self, reg: PortPcsReg, lane: PortLane) -> Option<MmioPtr<u32>> {
self.port_reg((reg as usize) + (lane as usize))
}
//TODO: return WriteOnly if PortLane::Grp?
pub fn port_tx(&self, reg: PortTxReg, lane: PortLane) -> Option<MmioPtr<u32>> {
self.port_reg((reg as usize) + (lane as usize))
}
pub fn probe_edid(
&mut self,
power_wells: &mut PowerWells,
gttmm: &MmioRegion,
gmbus: &mut Gmbus,
) -> Result<Option<(&'static str, [u8; 128])>, Error> {
if let Some(port_comp_dw0) = self.port_comp(PortCompReg::Dw0) {
log::debug!("PORT_COMP_DW0_{}: {:08X}", self.name, port_comp_dw0.read());
}
let mut aux_read_edid = |ddi: &mut Ddi| -> Result<[u8; 128]> {
//TODO: BLOCK TCCOLD?
//TODO: the request can be shared by multiple DDIs
let pwr_well_ctl_aux_request = ddi.pwr_well_ctl_aux_request;
let pwr_well_ctl_aux_state = ddi.pwr_well_ctl_aux_state;
let mut pwr_well_ctl_aux = unsafe { MmioPtr::new(power_wells.ctl_aux.as_mut_ptr()) };
let _pwr_guard = CallbackGuard::new(
&mut pwr_well_ctl_aux,
|pwr_well_ctl_aux| {
// Enable aux power
pwr_well_ctl_aux.writef(pwr_well_ctl_aux_request, true);
let timeout = Timeout::from_micros(1500);
while !pwr_well_ctl_aux.readf(pwr_well_ctl_aux_state) {
timeout.run().map_err(|()| {
log::debug!("timeout while requesting DDI {} aux power", ddi.name);
Error::new(EIO)
})?;
}
Ok(())
},
|pwr_well_ctl_aux| {
// Disable aux power
pwr_well_ctl_aux.writef(pwr_well_ctl_aux_request, false);
},
)?;
let mut edid_data = [0; 128];
Aux::new(ddi)
.write_read(0x50, &[0x00], &mut edid_data)
.map_err(|_err| Error::new(EIO))?;
Ok(edid_data)
};
let mut gmbus_read_edid = |ddi: &mut Ddi| -> Result<[u8; 128]> {
let Some(pin_pair) = ddi.gmbus_pin_pair else {
return Err(Error::new(EIO));
};
let mut edid_data = [0; 128];
gmbus
.pin_pair(pin_pair)
.write_read(0x50, &[0x00], &mut edid_data)
.map_err(|_err| Error::new(EIO))?;
Ok(edid_data)
};
let gpio_read_edid = |ddi: &mut Ddi| -> Result<[u8; 128]> {
let Some(port) = &ddi.gpio_port else {
return Err(Error::new(EIO));
};
let mut edid_data = [0; 128];
unsafe { port.i2c(gttmm)? }
.write_read(0x50, &[0x00], &mut edid_data)
.map_err(|_err| Error::new(EIO))?;
Ok(edid_data)
};
match aux_read_edid(self) {
Ok(edid_data) => return Ok(Some(("AUX", edid_data))),
Err(err) => {
log::debug!("DDI {} failed to read EDID from AUX: {}", self.name, err);
}
}
match gmbus_read_edid(self) {
Ok(edid_data) => return Ok(Some(("GMBUS", edid_data))),
Err(err) => {
log::debug!("DDI {} failed to read EDID from GMBUS: {}", self.name, err);
}
}
match gpio_read_edid(self) {
Ok(edid_data) => return Ok(Some(("GPIO", edid_data))),
Err(err) => {
log::debug!("DDI {} failed to read EDID from GPIO: {}", self.name, err);
}
}
// Will try again but not fail the driver
Ok(None)
}
pub fn voltage_swing_hdmi(
&mut self,
gttmm: &MmioRegion,
timing: &edid::DetailedTiming,
) -> Result<()> {
struct Setting {
dw2_swing_sel: u32,
dw7_n_scalar: u32,
dw4_cursor_coeff: u32,
dw4_post_cursor_1: u32,
dw5_2_tap_disable: u32,
}
impl Setting {
pub fn new(
dw2_swing_sel: u32,
dw7_n_scalar: u32,
dw4_cursor_coeff: u32,
dw4_post_cursor_1: u32,
dw5_2_tap_disable: u32,
) -> Self {
Self {
dw2_swing_sel,
dw7_n_scalar,
dw4_cursor_coeff,
dw4_post_cursor_1,
dw5_2_tap_disable,
}
}
}
// IHD-OS-TGL-Vol 12-1.22-Rev2.0 "Voltage Swing Programming"
let settings = vec![
// HDMI 450mV, 450mV, 0.0dB
Setting::new(0b1010, 0x60, 0x3F, 0x00, 0b0),
// HDMI 450mV, 650mV, 3.2dB
Setting::new(0b1011, 0x73, 0x36, 0x09, 0b0),
// HDMI 450mV, 850mV, 5.5dB
Setting::new(0b0110, 0x7F, 0x31, 0x0E, 0b0),
// HDMI 650mV, 650mV, 0.0dB
Setting::new(0b1011, 0x73, 0x3F, 0x00, 0b0),
// HDMI 650mV, 850mV, 2.3dB
Setting::new(0b0110, 0x7F, 0x37, 0x08, 0b0),
// HDMI 850mV, 850mV, 0.0dB
Setting::new(0b0110, 0x7F, 0x3F, 0x00, 0b0),
// HDMI 600mV, 850mV, 3.0dB
Setting::new(0b0110, 0x7F, 0x35, 0x0A, 0b0),
];
// Last setting is the default
//TODO: get correct setting index from BIOS
let setting = settings.last().unwrap();
// This allows unwraps on port functions below without panic
if self.port_base.is_none() {
log::error!("HDMI voltage swing procedure only implemented on combo DDI");
return Err(Error::new(EIO));
};
// Clear cmnkeeper_enable for HDMI
{
// It is not possible to read from GRP register, so use LN0 as template
let pcs_dw1_ln0 = self.port_pcs(PortPcsReg::Dw1, PortLane::Ln0).unwrap();
let mut pcs_dw1_grp =
WriteOnly::new(self.port_pcs(PortPcsReg::Dw1, PortLane::Grp).unwrap());
let mut v = pcs_dw1_ln0.read();
v &= !PORT_PCS_DW1_CMNKEEPER_ENABLE;
pcs_dw1_grp.write(v);
}
// Program loadgen select
//TODO: this assumes bit rate <= 6 GHz and 4 lanes enabled
{
let mut tx_dw4_ln0 = self.port_tx(PortTxReg::Dw4, PortLane::Ln0).unwrap();
tx_dw4_ln0.writef(PORT_TX_DW4_SELECT, false);
let mut tx_dw4_ln1 = self.port_tx(PortTxReg::Dw4, PortLane::Ln1).unwrap();
tx_dw4_ln1.writef(PORT_TX_DW4_SELECT, true);
let mut tx_dw4_ln2 = self.port_tx(PortTxReg::Dw4, PortLane::Ln2).unwrap();
tx_dw4_ln2.writef(PORT_TX_DW4_SELECT, true);
let mut tx_dw4_ln3 = self.port_tx(PortTxReg::Dw4, PortLane::Ln3).unwrap();
tx_dw4_ln3.writef(PORT_TX_DW4_SELECT, true);
}
// Set PORT_CL_DW5 sus clock config to 11b
{
let mut cl_dw5 = self.port_cl(PortClReg::Dw5).unwrap();
cl_dw5.writef(PORT_CL_DW5_SUS_CLOCK_MASK, true);
}
// Clear training enable to change swing values
let tx_dw5_ln0 = self.port_tx(PortTxReg::Dw5, PortLane::Ln0).unwrap();
let mut tx_dw5_grp = WriteOnly::new(self.port_tx(PortTxReg::Dw5, PortLane::Grp).unwrap());
{
let mut v = tx_dw5_ln0.read();
v &= !PORT_TX_DW5_TRAINING_ENABLE;
tx_dw5_grp.write(v);
}
// Program swing and de-emphasis
// Disable eDP bits in PORT_CL_DW10
let mut cl_dw10 = self.port_cl(PortClReg::Dw10).unwrap();
cl_dw10.writef(
PORT_CL_DW10_EDP4K2K_MODE_OVRD_EN | PORT_CL_DW10_EDP4K2K_MODE_OVRD_VAL,
false,
);
// For PORT_TX_DW5:
// - Set 2 tap disable from settings
// - Set scaling mode sel to 010b
// - Set rterm select to 110b
// - Set 3 tap disable to 1
// - Set cursor program to 0
// - Set coeff polarity to 0
{
let mut v = tx_dw5_ln0.read();
v &= !(PORT_TX_DW5_DISABLE_2_TAP
| PORT_TX_DW5_CURSOR_PROGRAM
| PORT_TX_DW5_COEFF_POLARITY
| PORT_TX_DW5_SCALING_MODE_SEL_MASK
| PORT_TX_DW5_RTERM_SELECT_MASK);
v |= (setting.dw5_2_tap_disable << PORT_TX_DW5_DISABLE_2_TAP_SHIFT)
| PORT_TX_DW5_DISABLE_3_TAP
| (0b010 << PORT_TX_DW5_SCALING_MODE_SEL_SHIFT)
| (0b110 << PORT_TX_DW5_RTERM_SELECT_SHIFT);
tx_dw5_grp.write(v);
}
// Individual lane settings are used to avoid overwriting lane-specific settings, and because
// group registers cannot be read
let lanes = [PortLane::Ln0, PortLane::Ln1, PortLane::Ln2, PortLane::Ln3];
// For PORT_TX_DW2:
// - Set swing sel from settings
// - Set rcomp scalar to 0x98
for lane in lanes {
let mut tx_dw2 = self.port_tx(PortTxReg::Dw2, lane).unwrap();
let mut v = tx_dw2.read();
v &= !(PORT_TX_DW2_SWING_SEL_UPPER_MASK
| PORT_TX_DW2_SWING_SEL_LOWER_MASK
| PORT_TX_DW2_RCOMP_SCALAR_MASK);
v |= (((setting.dw2_swing_sel >> 3) & 1) << PORT_TX_DW2_SWING_SEL_UPPER_SHIFT)
| ((setting.dw2_swing_sel & 0b111) << PORT_TX_DW2_SWING_SEL_LOWER_SHIFT)
| (0x98 << PORT_TX_DW2_RCOMP_SCALAR_SHIFT);
tx_dw2.write(v);
}
// For PORT_TX_DW4:
// - Set post cursor 1 from settings
// - Set post cursor 2 to 0x0
// - Set cursor coeff from settings
for lane in lanes {
let mut tx_dw4 = self.port_tx(PortTxReg::Dw4, lane).unwrap();
let mut v = tx_dw4.read();
v &= !(PORT_TX_DW4_POST_CURSOR_1_MASK
| PORT_TX_DW4_POST_CURSOR_2_MASK
| PORT_TX_DW4_CURSOR_COEFF_MASK);
v |= (setting.dw4_post_cursor_1 << PORT_TX_DW4_POST_CURSOR_1_SHIFT)
| (setting.dw4_cursor_coeff << PORT_TX_DW4_CURSOR_COEFF_SHIFT);
tx_dw4.write(v);
}
// For PORT_TX_DW7:
// - Set n scalar from settings
for lane in lanes {
let mut tx_dw7 = self.port_tx(PortTxReg::Dw7, lane).unwrap();
// All other bits are spare
tx_dw7.write(setting.dw7_n_scalar << PORT_TX_DW7_N_SCALAR_SHIFT);
}
// Set training enable to trigger update
{
let mut v = tx_dw5_ln0.read();
v |= PORT_TX_DW5_TRAINING_ENABLE;
tx_dw5_grp.write(v);
}
Ok(())
}
pub fn kabylake(gttmm: &Arc<MmioRegion>) -> Result<Vec<Self>> {
let mut ddis = Vec::new();
for (i, name) in [
"A", "B", "C", "D",
//TODO: missing AUX regs? "E",
]
.iter()
.enumerate()
{
ddis.push(Self {
name,
index: i,
port_base: None, //TODO: port regs
gttmm: gttmm.clone(),
// IHD-OS-KBL-Vol 2c-1.17 DDI_AUX_CTL
aux_ctl: unsafe { gttmm.mmio(0x64010 + i * 0x100)? },
// IHD-OS-KBL-Vol 2c-1.17 DDI_AUX_DATA
aux_datas: [
unsafe { gttmm.mmio(0x64014 + i * 0x100)? },
unsafe { gttmm.mmio(0x64018 + i * 0x100)? },
unsafe { gttmm.mmio(0x6401C + i * 0x100)? },
unsafe { gttmm.mmio(0x64020 + i * 0x100)? },
unsafe { gttmm.mmio(0x64024 + i * 0x100)? },
],
// IHD-OS-KBL-Vol 2c-1.17 DDI_BUF_CTL
buf_ctl: unsafe { gttmm.mmio(0x64000 + i * 0x100)? },
// N/A
dpclka_cfgcr0_clock_shift: None,
dpclka_cfgcr0_clock_off: None,
// IHD-OS-KBL-Vol 2c-1.17 GMBUS0
gmbus_pin_pair: match *name {
"B" => Some(0b101),
"C" => Some(0b100),
"D" => Some(0b110),
_ => None,
},
// IHD-OS-KBL-Vol 12-1.17 GMBUS and GPIO
gpio_port: match *name {
"B" => Some(GpioPort::Port4),
"C" => Some(GpioPort::Port3),
"D" => Some(GpioPort::Port5),
_ => None,
},
// IHD-OS-KBL-Vol 2c-1.17 PWR_WELL_CTL
// All auxes go through the same Misc IO request
pwr_well_ctl_aux_request: 1 << 1,
pwr_well_ctl_aux_state: 1 << 0,
pwr_well_ctl_ddi_request: match *name {
"A" | "E" => 1 << 3,
"B" => 1 << 5,
"C" => 1 << 7,
"D" => 1 << 9,
_ => unreachable!(),
},
pwr_well_ctl_ddi_state: match *name {
"A" | "E" => 1 << 2,
"B" => 1 << 4,
"C" => 1 << 6,
"D" => 1 << 8,
_ => unreachable!(),
},
// IHD-OS-KBL-Vol 2c-1.17 SDE_INTERRUPT
sde_interrupt_hotplug: match *name {
"A" => Some(1 << 24),
"B" => Some(1 << 21),
"C" => Some(1 << 22),
"D" => Some(1 << 23),
"E" => Some(1 << 25),
_ => None,
},
// IHD-OS-KBL-Vol 2c-1.17 TRANS_CLK_SEL
transcoder_index: match *name {
"B" => Some(0b010),
"C" => Some(0b011),
"D" => Some(0b100),
"E" => Some(0b101),
_ => None,
},
});
}
Ok(ddis)
}
pub fn tigerlake(gttmm: &Arc<MmioRegion>) -> Result<Vec<Self>> {
let mut ddis = Vec::new();
for (i, name) in [
"A", "B", "C", "USBC1", "USBC2", "USBC3", "USBC4", "USBC5", "USBC6",
]
.iter()
.enumerate()
{
let port_base = match i {
0 => Some(0x162000),
1 => Some(0x6C000),
2 => Some(0x160000),
_ => None,
};
ddis.push(Self {
name,
index: i,
port_base,
gttmm: gttmm.clone(),
// IHD-OS-TGL-Vol 2c-12.21 DDI_AUX_CTL
aux_ctl: unsafe { gttmm.mmio(0x64010 + i * 0x100)? },
// IHD-OS-TGL-Vol 2c-12.21 DDI_AUX_DATA
aux_datas: [
unsafe { gttmm.mmio(0x64014 + i * 0x100)? },
unsafe { gttmm.mmio(0x64018 + i * 0x100)? },
unsafe { gttmm.mmio(0x6401C + i * 0x100)? },
unsafe { gttmm.mmio(0x64020 + i * 0x100)? },
unsafe { gttmm.mmio(0x64024 + i * 0x100)? },
],
// IHD-OS-TGL-Vol 2c-12.21 DDI_BUF_CTL
buf_ctl: unsafe { gttmm.mmio(0x64000 + i * 0x100)? },
// IHD-OS-TGL-Vol 2c-12.21 DPCLKA_CFGCR0
dpclka_cfgcr0_clock_shift: match i {
0 => Some(0),
1 => Some(2),
2 => Some(4),
_ => None,
},
dpclka_cfgcr0_clock_off: match i {
// DDI
0 => Some(1 << 10),
1 => Some(1 << 11),
2 => Some(1 << 24),
// Type C
3 => Some(1 << 12),
4 => Some(1 << 13),
5 => Some(1 << 14),
6 => Some(1 << 21),
7 => Some(1 << 22),
8 => Some(1 << 23),
_ => None,
},
//TODO: link to docs
gmbus_pin_pair: match i {
// DDI pins
0 => Some(1),
1 => Some(2),
2 => Some(3),
// Type C pins
3 => Some(9),
4 => Some(10),
5 => Some(11),
6 => Some(12),
7 => Some(13),
8 => Some(14),
_ => None,
},
// IHD-OS-TGL-Vol 12-1.22-Rev2.0 GMBUS and GPIO
gpio_port: match *name {
"A" => Some(GpioPort::Port1),
"B" => Some(GpioPort::Port2),
"C" => Some(GpioPort::Port3),
"USBC1" => Some(GpioPort::Port9),
"USBC2" => Some(GpioPort::Port10),
"USBC3" => Some(GpioPort::Port11),
"USBC4" => Some(GpioPort::Port12),
"USBC5" => Some(GpioPort::Port13),
"USBC6" => Some(GpioPort::Port14),
_ => None,
},
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL_AUX
pwr_well_ctl_aux_request: 2 << (i * 2),
pwr_well_ctl_aux_state: 1 << (i * 2),
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL_DDI
pwr_well_ctl_ddi_request: 2 << (i * 2),
pwr_well_ctl_ddi_state: 1 << (i * 2),
// IHD-OS-TGL-Vol 2c-12.21 SDE_INTERRUPT
sde_interrupt_hotplug: match i {
0 => Some(1 << 16),
1 => Some(1 << 17),
2 => Some(1 << 18),
_ => None,
},
// IHD-OS-TGL-Vol 2c-12.21 TRANS_CLK_SEL
transcoder_index: Some((i + 1) as u32),
})
}
Ok(ddis)
}
pub fn alchemist(gttmm: &Arc<MmioRegion>) -> Result<Vec<Self>> {
let mut ddis = Vec::new();
for (i, name) in ["A", "B", "C", "USBC1", "USBC2", "USBC3", "USBC4", "D", "E"]
.iter()
.enumerate()
{
let port_base = match i {
0 => Some(0x162000),
1 => Some(0x6C000),
2 => Some(0x160000),
_ => None,
};
ddis.push(Self {
name,
index: i,
port_base,
gttmm: gttmm.clone(),
// IHD-OS-ACM-Vol 2c-3.23 DDI_AUX_CTL
aux_ctl: unsafe { gttmm.mmio(0x64010 + i * 0x100)? },
// IHD-OS-ACM-Vol 2c-3.23 DDI_AUX_DATA
aux_datas: [
unsafe { gttmm.mmio(0x64014 + i * 0x100)? },
unsafe { gttmm.mmio(0x64018 + i * 0x100)? },
unsafe { gttmm.mmio(0x6401C + i * 0x100)? },
unsafe { gttmm.mmio(0x64020 + i * 0x100)? },
unsafe { gttmm.mmio(0x64024 + i * 0x100)? },
],
// IHD-OS-ACM-Vol 2c-3.23 DDI_BUF_CTL
buf_ctl: unsafe { gttmm.mmio(0x64000 + i * 0x100)? },
// IHD-OS-ACM-Vol 2c-3.23 DPCLKA_CFGCR0
dpclka_cfgcr0_clock_shift: match i {
0 => Some(0),
1 => Some(2),
2 => Some(4),
_ => None,
},
dpclka_cfgcr0_clock_off: match i {
// DDI
0 => Some(1 << 10),
1 => Some(1 << 11),
2 => Some(1 << 24),
// Type C
3 => Some(1 << 12),
4 => Some(1 << 13),
5 => Some(1 << 14),
6 => Some(1 << 21),
7 => Some(1 << 22),
8 => Some(1 << 23),
_ => None,
},
//TODO: link to docs
gmbus_pin_pair: match i {
// DDI pins
0 => Some(1),
1 => Some(2),
2 => Some(3),
// Type C pins
3 => Some(9),
4 => Some(10),
5 => Some(11),
6 => Some(12),
7 => Some(13),
8 => Some(14),
_ => None,
},
// IHD-OS-ACM-Vol 12-3.23 GMBUS and GPIO
gpio_port: match *name {
"A" => Some(GpioPort::Port1),
"B" => Some(GpioPort::Port2),
"C" => Some(GpioPort::Port3),
"D" => Some(GpioPort::Port4),
"USBC1" => Some(GpioPort::Port9),
_ => None,
},
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL_AUX
pwr_well_ctl_aux_request: 2 << (i * 2),
pwr_well_ctl_aux_state: 1 << (i * 2),
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL_DDI
pwr_well_ctl_ddi_request: 2 << (i * 2),
pwr_well_ctl_ddi_state: 1 << (i * 2),
// IHD-OS-ACM-Vol 2c-3.23 SDE_INTERRUPT
sde_interrupt_hotplug: match i {
0 => Some(1 << 16),
1 => Some(1 << 17),
2 => Some(1 << 18),
_ => None,
},
// IHD-OS-ACM-Vol 2c-3.23 TRANS_CLK_SEL
transcoder_index: Some((i + 1) as u32),
})
}
Ok(ddis)
}
}
@@ -0,0 +1,197 @@
use common::io::{Io, MmioPtr};
use syscall::error::{Error, Result, EIO};
use super::MmioRegion;
pub const DPLL_CFGCR1_QDIV_RATIO_SHIFT: u32 = 10;
pub const DPLL_CFGCR1_QDIV_RATIO_MASK: u32 = 0xFF << DPLL_CFGCR1_QDIV_RATIO_SHIFT;
pub const DPLL_CFGCR1_QDIV_MODE: u32 = 1 << 9;
pub const DPLL_CFGCR1_KDIV_1: u32 = 0b001 << 6;
pub const DPLL_CFGCR1_KDIV_2: u32 = 0b010 << 6;
pub const DPLL_CFGCR1_KDIV_3: u32 = 0b100 << 6;
pub const DPLL_CFGCR1_KDIV_MASK: u32 = 0b111 << 6;
pub const DPLL_CFGCR1_PDIV_2: u32 = 0b0001 << 2;
pub const DPLL_CFGCR1_PDIV_3: u32 = 0b0010 << 2;
pub const DPLL_CFGCR1_PDIV_5: u32 = 0b0100 << 2;
pub const DPLL_CFGCR1_PDIV_7: u32 = 0b1000 << 2;
pub const DPLL_CFGCR1_PDIV_MASK: u32 = 0b1111 << 2;
pub const DPLL_ENABLE_ENABLE: u32 = 1 << 31;
pub const DPLL_ENABLE_LOCK: u32 = 1 << 30;
pub const DPLL_ENABLE_POWER_ENABLE: u32 = 1 << 27;
pub const DPLL_ENABLE_POWER_STATE: u32 = 1 << 26;
pub const DPLL_SSC_ENABLE: u32 = 1 << 9;
pub struct Dpll {
pub name: &'static str,
// IHD-OS-TGL-Vol 2c-12.21 DPLL_CFGCR0
pub cfgcr0: MmioPtr<u32>,
// IHD-OS-TGL-Vol 2c-12.21 DPLL_CFGCR1
pub cfgcr1: MmioPtr<u32>,
// IHD-OS-TGL-Vol 2c-12.21 DPLL_DIV0
pub div0: MmioPtr<u32>,
// IHD-OS-TGL-Vol 2c-12.21 DPCLKA_CFGCR0
pub dpclka_cfgcr0_clock_value: u32,
// IHD-OS-TGL-Vol 2c-12.21 DPLL_ENABLE
pub enable: MmioPtr<u32>,
// IHD-OS-TGL-Vol 2c-12.21 DPLL_SSC
pub ssc: MmioPtr<u32>,
}
//TODO: verify offsets and count using DeviceKind?
impl Dpll {
pub fn dump(&self) {
eprint!("Dpll {}", self.name);
eprint!(" cfgcr0 {:08X}", self.cfgcr0.read());
eprint!(" cfgcr1 {:08X}", self.cfgcr1.read());
eprint!(" div0 {:08X}", self.div0.read());
eprint!(" enable {:08X}", self.enable.read());
eprint!(" ssc {:08X}", self.ssc.read());
eprintln!();
}
pub fn set_freq_hdmi(
&mut self,
mut ref_freq: u64,
timing: &edid::DetailedTiming,
) -> Result<()> {
// IHD-OS-TGL-Vol 12-1.22-Rev2.0 "Formula for HDMI Mode DPLL Programming"
const KHz: u64 = 1_000;
const MHz: u64 = KHz * 1_000;
let dco_min: u64 = 7_998 * MHz;
let dco_mid: u64 = 8_999 * MHz;
let dco_max: u64 = 10_000 * MHz;
// If reference frequency is 38.4, use 19.2 because the DPLL automatically divides that by 2.
if ref_freq == 38_400_000 {
ref_freq /= 2;
}
//TODO: this symbol frequency is only valid for RGB 8 bits per color
let symbol_freq = (timing.pixel_clock as u64) * KHz;
let pll_freq = symbol_freq * 5;
#[derive(Debug)]
struct Setting {
pdiv: u64,
kdiv: u64,
qdiv: u64,
cfgcr1: u32,
dco: u64,
dco_dist: u64,
}
let mut best_setting: Option<Setting> = None;
for (pdiv, pdiv_reg) in [
(2, DPLL_CFGCR1_PDIV_2),
(3, DPLL_CFGCR1_PDIV_3),
(5, DPLL_CFGCR1_PDIV_5),
(7, DPLL_CFGCR1_PDIV_7),
] {
for (kdiv, kdiv_reg) in [
(1, DPLL_CFGCR1_KDIV_1),
(2, DPLL_CFGCR1_KDIV_2),
(3, DPLL_CFGCR1_KDIV_3),
] {
let qdiv_range = if kdiv == 2 { 1..=0xFF } else { 1..=1 };
for qdiv in qdiv_range {
let qdiv_reg = if qdiv == 1 {
0
} else {
((qdiv as u32) << DPLL_CFGCR1_QDIV_RATIO_SHIFT) | DPLL_CFGCR1_QDIV_MODE
};
let dco = pll_freq * pdiv * kdiv * qdiv;
if dco <= dco_min || dco >= dco_max {
// DCO outside of valid range
continue;
}
let dco_dist = dco.abs_diff(dco_mid);
let setting = Setting {
pdiv,
kdiv,
qdiv,
cfgcr1: pdiv_reg | kdiv_reg | qdiv_reg,
dco,
dco_dist,
};
best_setting = match best_setting.take() {
Some(other) if other.dco_dist < setting.dco_dist => Some(other),
_ => Some(setting),
};
}
}
}
let Some(setting) = best_setting else {
log::error!("failed to find valid DPLL setting");
return Err(Error::new(EIO));
};
eprintln!("{:?}", setting);
// Configure DPLL_CFGCR0 to set DCO frequency
{
let dco_int = setting.dco / ref_freq;
let dco_fract = ((setting.dco - (dco_int * ref_freq)) << 15) / ref_freq;
self.cfgcr0
.write(((dco_fract as u32) << 10) | (dco_int as u32));
}
// Configure DPLL_CFGCR1 to set the dividers
{
let mut v = self.cfgcr1.read();
let mask = DPLL_CFGCR1_QDIV_RATIO_MASK
| DPLL_CFGCR1_QDIV_MODE
| DPLL_CFGCR1_KDIV_MASK
| DPLL_CFGCR1_PDIV_MASK;
v &= !mask;
v |= setting.cfgcr1 & mask;
self.cfgcr1.write(v);
}
// Read back DPLL_CFGCR0 and DPLL_CFGCR1 to ensure writes are complete
let _ = self.cfgcr0.read();
let _ = self.cfgcr1.read();
Ok(())
}
pub fn tigerlake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
let mut dplls = Vec::new();
dplls.push(Self {
name: "0",
cfgcr0: unsafe { gttmm.mmio(0x164284)? },
cfgcr1: unsafe { gttmm.mmio(0x164288)? },
div0: unsafe { gttmm.mmio(0x164B00)? },
dpclka_cfgcr0_clock_value: 0b00,
enable: unsafe { gttmm.mmio(0x46010)? },
ssc: unsafe { gttmm.mmio(0x164B10)? },
});
dplls.push(Self {
name: "1",
cfgcr0: unsafe { gttmm.mmio(0x16428C)? },
cfgcr1: unsafe { gttmm.mmio(0x164290)? },
div0: unsafe { gttmm.mmio(0x164C00)? },
dpclka_cfgcr0_clock_value: 0b01,
enable: unsafe { gttmm.mmio(0x46014)? },
ssc: unsafe { gttmm.mmio(0x164C10)? },
});
/*TODO: not present on U-class CPUs
dplls.push(Self {
name: "4",
cfgcr0: unsafe { gttmm.mmio(0x164294)? },
cfgcr1: unsafe { gttmm.mmio(0x164298)? },
div0: unsafe { gttmm.mmio(0x164E00)? },
dpclka_cfgcr0_clock_value: 0b10,
enable: unsafe { gttmm.mmio(0x46018)? },
ssc: unsafe { gttmm.mmio(0x164E10)? },
});
*/
Ok(dplls)
}
}
@@ -0,0 +1,134 @@
use std::sync::Arc;
use std::{mem, ptr};
use pcid_interface::PciFunctionHandle;
use range_alloc::RangeAllocator;
use syscall::{Error, EIO};
use crate::device::MmioRegion;
/// Global Graphics Translation Table (global GTT)
///
/// The global GTT is a page table used by all parts of the GPU that don't use
/// the PPGTT (Per-Process GTT). This includes the display engine and the GM
/// aperture that the CPU can access.
///
/// The global GTT is located in the GTTMM BAR at offset 8MiB, is up to 8MiB big
/// and consists of 64bit entries. Each entry has a present bit as LSB and the
/// address of the frame at bits 12 through 38. The rest of the bits are ignored.
///
/// Source: Pages 6 and 75 of intel-gfx-prm-osrc-kbl-vol05-memory_views.pdf
pub struct GlobalGtt {
gttmm: Arc<MmioRegion>,
/// Base the GTT
gtt_base: *mut u64,
/// Size of the GTT
gtt_size: usize,
/// Allocator for GM aperture pages
gm_alloc: RangeAllocator<u32>,
// FIXME reuse DSM memory for something useful
/// Base Data of Stolen Memory (DSM)
base_dsm: *mut (),
/// Size of DSM
size_data_stolen_memory: usize,
}
const GTT_PAGE_SIZE: u32 = 4096;
impl GlobalGtt {
pub unsafe fn new(
pcid_handle: &mut PciFunctionHandle,
gttmm: Arc<MmioRegion>,
gm_size: u32,
) -> Self {
let gtt_offset = 8 * 1024 * 1024;
let gtt_base = ptr::with_exposed_provenance_mut(gttmm.virt + gtt_offset);
let base_dsm = unsafe { pcid_handle.read_config(0x5C) };
let ggc = unsafe { pcid_handle.read_config(0x50) };
let dsm_size = match (ggc >> 8) & 0xFF {
size if size & 0xF0 == 0 => size * 32 * 1024 * 1024,
size => (size & !0xF0) * 4 * 1024 * 1024,
} as usize;
let gtt_size = match (ggc >> 6) & 0x3 {
0 => 0,
1 => 2 * 1024 * 1024,
2 => 4 * 1024 * 1024,
3 => 8 * 1024 * 1024,
_ => unreachable!(),
} as usize;
log::info!("Base DSM: {:X}", base_dsm);
log::info!(
"GGC: {:X} => global GTT size: {}MiB; DSM size: {}MiB",
ggc,
gtt_size / 1024 / 1024,
dsm_size / 1024 / 1024,
);
let gm_alloc = RangeAllocator::new(0..gm_size / 4096);
GlobalGtt {
gttmm,
gtt_base,
gtt_size,
gm_alloc,
base_dsm: core::ptr::with_exposed_provenance_mut(base_dsm as usize),
size_data_stolen_memory: dsm_size,
}
}
/// Reset the global GTT by clearing out all existing mappings.
pub unsafe fn reset(&mut self) {
for i in 0..self.gtt_size / 8 {
unsafe { *self.gtt_base.add(i) = 0 };
}
}
pub fn reserve(&mut self, surf: u32, surf_size: u32) {
assert!(surf.is_multiple_of(GTT_PAGE_SIZE));
assert!(surf_size.is_multiple_of(GTT_PAGE_SIZE));
self.gm_alloc
.allocate_exact_range(
surf / GTT_PAGE_SIZE..surf / GTT_PAGE_SIZE + surf_size / GTT_PAGE_SIZE,
)
.unwrap_or_else(|err| {
panic!(
"failed to allocate pre-existing surface at 0x{:x} of size {}: {:?}",
surf, surf_size, err
);
});
}
pub fn alloc_phys_mem(&mut self, size: u32) -> syscall::Result<u32> {
let size = size.next_multiple_of(GTT_PAGE_SIZE);
let sgl = common::sgl::Sgl::new(size as usize)?;
let range = self
.gm_alloc
.allocate_range(size / GTT_PAGE_SIZE)
.map_err(|err| {
log::warn!("failed to allocate buffer of size {}: {:?}", size, err);
Error::new(EIO)
})?;
for chunk in sgl.chunks() {
for i in 0..chunk.length / GTT_PAGE_SIZE as usize {
unsafe {
*self
.gtt_base
.add(range.start as usize + chunk.offset / GTT_PAGE_SIZE as usize + i) =
chunk.phys as u64 + i as u64 * u64::from(GTT_PAGE_SIZE) + 1;
}
}
}
mem::forget(sgl);
Ok(range.start * 4096)
}
}
@@ -0,0 +1,150 @@
use common::{
io::{Io, MmioPtr},
timeout::Timeout,
};
use embedded_hal::blocking::i2c::{self, Operation, SevenBitAddress, Transactional};
use super::MmioRegion;
const GMBUS1_SW_RDY: u32 = 1 << 30;
const GMBUS1_CYCLE_STOP: u32 = 1 << 27;
const GMBUS1_CYCLE_INDEX: u32 = 1 << 26;
const GMBUS1_CYCLE_WAIT: u32 = 1 << 25;
const GMBUS1_SIZE_SHIFT: u32 = 16;
const GMBUS1_INDEX_SHIFT: u32 = 8;
const GMBUS2_HW_RDY: u32 = 1 << 11;
const GMBUS2_ACTIVE: u32 = 1 << 9;
pub struct Gmbus {
regs: [MmioPtr<u32>; 6],
}
impl Gmbus {
pub unsafe fn new(gttmm: &MmioRegion) -> syscall::Result<Self> {
Ok(Self {
regs: [
gttmm.mmio(0xC5100)?,
gttmm.mmio(0xC5104)?,
gttmm.mmio(0xC5108)?,
gttmm.mmio(0xC510C)?,
gttmm.mmio(0xC5110)?,
gttmm.mmio(0xC5120)?,
],
})
}
pub fn pin_pair<'a>(&'a mut self, pin_pair: u8) -> GmbusPinPair<'a> {
GmbusPinPair {
regs: &mut self.regs,
pin_pair,
}
}
}
pub struct GmbusPinPair<'a> {
regs: &'a mut [MmioPtr<u32>; 6],
pin_pair: u8,
}
impl<'a> Transactional for GmbusPinPair<'a> {
type Error = ();
fn exec(&mut self, addr7: SevenBitAddress, ops: &mut [Operation<'_>]) -> Result<(), ()> {
let mut ops_iter = ops.iter_mut();
//TODO: gmbus is actually smbus, not fully i2c compatible!
// The first operation MUST be a write of the index
let index = match ops_iter.next() {
Some(Operation::Write(buf)) if buf.len() == 1 => buf[0],
unsupported => {
log::error!("GMBUS unsupported first operation {:?}", unsupported);
return Err(());
}
};
// Reset
self.regs[1].write(0);
// Set pin pair, enabling interface
self.regs[0].write(self.pin_pair as u32);
for op in ops_iter {
// Start operation
let (addr8, size) = match op {
Operation::Read(buf) => ((addr7 << 1) | 1, buf.len() as u32),
Operation::Write(buf) => (addr7 << 1, buf.len() as u32),
};
if size >= 512 {
log::error!("GMBUS transaction size {} too large", size);
return Err(());
}
self.regs[1].write(
GMBUS1_SW_RDY
| GMBUS1_CYCLE_INDEX
| GMBUS1_CYCLE_WAIT
| (size << GMBUS1_SIZE_SHIFT)
| (index as u32) << GMBUS1_INDEX_SHIFT
| (addr8 as u32),
);
// Perform transaction
match op {
Operation::Read(buf) => {
for chunk in buf.chunks_mut(4) {
{
//TODO: ideal timeout for gmbus read?
let timeout = Timeout::from_millis(10);
while !self.regs[2].readf(GMBUS2_HW_RDY) {
timeout.run().map_err(|()| {
log::debug!(
"timeout on GMBUS read 0x{:08x}",
self.regs[2].read()
);
()
})?;
}
}
let bytes = self.regs[3].read().to_le_bytes();
chunk.copy_from_slice(&bytes[..chunk.len()]);
}
}
Operation::Write(buf) => {
log::warn!("TODO: GMBUS WRITE");
return Err(());
}
}
}
// Stop transaction
self.regs[1].write(GMBUS1_SW_RDY | GMBUS1_CYCLE_STOP);
// Wait idle
let timeout = Timeout::from_millis(10);
while self.regs[2].readf(GMBUS2_ACTIVE) {
timeout.run().map_err(|()| {
log::debug!("timeout on GMBUS active 0x{:08x}", self.regs[2].read());
()
})?;
}
// Disable GMBUS interface
self.regs[0].write(0);
Ok(())
}
}
impl<'a> i2c::WriteRead for GmbusPinPair<'a> {
type Error = ();
fn write_read(
&mut self,
addr7: SevenBitAddress,
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), ()> {
self.exec(
addr7,
&mut [Operation::Write(bytes), Operation::Read(buffer)],
)
}
}
@@ -0,0 +1,99 @@
use std::convert::Infallible;
use std::time::Duration;
use common::io::{Io, MmioPtr};
use embedded_hal::digital::v2 as digital;
use crate::device::HalTimer;
use super::MmioRegion;
const GPIO_DIR_MASK: u32 = 1 << 0;
const GPIO_DIR_OUT: u32 = 1 << 1;
const GPIO_VAL_MASK: u32 = 1 << 2;
const GPIO_VAL_OUT: u32 = 1 << 3;
const GPIO_VAL_IN: u32 = 1 << 4;
const GPIO_CLOCK_SHIFT: u32 = 0;
const GPIO_DATA_SHIFT: u32 = 8;
#[derive(Copy, Clone, Debug)]
#[repr(usize)]
pub enum GpioPort {
Port0 = 0xC5010,
Port1 = 0xC5014,
Port2 = 0xC5018,
Port3 = 0xC501C,
Port4 = 0xC5020,
Port5 = 0xC5024,
Port6 = 0xC5028,
Port7 = 0xC502C,
Port8 = 0xC5030,
Port9 = 0xC5034,
Port10 = 0xC5038,
Port11 = 0xC503C,
Port12 = 0xC5040,
Port13 = 0xC5044,
Port14 = 0xC5048,
Port15 = 0xC504C,
}
impl GpioPort {
pub unsafe fn i2c(
&self,
gttmm: &MmioRegion,
) -> syscall::Result<bitbang_hal::i2c::I2cBB<GpioPin, GpioPin, HalTimer>> {
let i2c_freq = 100_000.0;
let (scl, sda) = unsafe {
(
GpioPin {
ctl: gttmm.mmio(*self as usize)?,
shift: GPIO_CLOCK_SHIFT,
},
GpioPin {
ctl: gttmm.mmio(*self as usize)?,
shift: GPIO_DATA_SHIFT,
},
)
};
Ok(bitbang_hal::i2c::I2cBB::new(
scl,
sda,
HalTimer::new(Duration::from_secs_f64(1.0 / i2c_freq)),
))
}
}
pub struct GpioPin {
ctl: MmioPtr<u32>,
shift: u32,
}
impl digital::InputPin for GpioPin {
type Error = Infallible;
fn is_high(&self) -> Result<bool, Infallible> {
Ok(((self.ctl.read() >> self.shift) & GPIO_VAL_IN) == GPIO_VAL_IN)
}
fn is_low(&self) -> Result<bool, Infallible> {
Ok(((self.ctl.read() >> self.shift) & GPIO_VAL_IN) == 0)
}
}
impl digital::OutputPin for GpioPin {
type Error = Infallible;
fn set_low(&mut self) -> Result<(), Infallible> {
// Set GPIO to output with value 0
let value = GPIO_DIR_MASK | GPIO_DIR_OUT | GPIO_VAL_MASK;
self.ctl.write(value << self.shift);
Ok(())
}
fn set_high(&mut self) -> Result<(), Infallible> {
// Assuming external pull-up, set GPIO to input
let value = GPIO_DIR_MASK;
self.ctl.write(value << self.shift);
Ok(())
}
}
@@ -0,0 +1,2 @@
mod timer;
pub use self::timer::*;
@@ -0,0 +1,38 @@
use embedded_hal::timer;
use std::time::{Duration, Instant};
use void::Void;
pub struct HalTimer {
instant: Instant,
duration: Duration,
}
impl HalTimer {
pub fn new(duration: Duration) -> Self {
Self {
instant: Instant::now(),
duration,
}
}
}
impl timer::CountDown for HalTimer {
type Time = Duration;
fn start<T: Into<Duration>>(&mut self, duration: T) {
self.instant = Instant::now();
self.duration = duration.into();
}
fn wait(&mut self) -> nb::Result<(), Void> {
if self.instant.elapsed() < self.duration {
std::thread::yield_now();
Err(nb::Error::WouldBlock)
} else {
// Since this is periodic it must trigger at the next duration
self.instant += self.duration;
Ok(())
}
}
}
impl timer::Periodic for HalTimer {}
@@ -0,0 +1,966 @@
use common::{
io::{Io, MmioPtr},
timeout::Timeout,
};
use pcid_interface::{PciFunction, PciFunctionHandle};
use range_alloc::RangeAllocator;
use std::{collections::VecDeque, fmt, mem, sync::Arc};
use syscall::error::{Error, Result, EIO, ENODEV, ERANGE};
mod aux;
mod bios;
use self::bios::*;
mod buffer;
mod ddi;
use self::ddi::*;
mod dpll;
use self::dpll::*;
mod gmbus;
pub use self::gmbus::*;
mod gpio;
pub use self::gpio::*;
mod ggtt;
use ggtt::*;
mod hal;
pub use self::hal::*;
mod pipe;
use self::pipe::*;
mod power;
use self::power::*;
mod scheme;
mod transcoder;
use self::transcoder::*;
//TODO: move to common?
pub struct CallbackGuard<'a, T, F: FnOnce(&mut T)> {
value: &'a mut T,
fini: Option<F>,
}
impl<'a, T, F: FnOnce(&mut T)> CallbackGuard<'a, T, F> {
// Note that fini will also run if init fails
pub fn new(value: &'a mut T, init: impl FnOnce(&mut T) -> Result<()>, fini: F) -> Result<Self> {
let mut this = Self {
value,
fini: Some(fini),
};
init(&mut this.value)?;
Ok(this)
}
}
impl<'a, T, F: FnOnce(&mut T)> Drop for CallbackGuard<'a, T, F> {
fn drop(&mut self) {
let fini = self.fini.take().unwrap();
fini(&mut self.value);
}
}
pub struct ChangeDetect {
name: &'static str,
reg: MmioPtr<u32>,
value: u32,
}
impl ChangeDetect {
fn new(name: &'static str, reg: MmioPtr<u32>) -> Self {
let value = reg.read();
Self { name, reg, value }
}
fn log(&self) {
log::info!("{} {:08X}", self.name, self.value);
}
fn check(&mut self) {
let value = self.reg.read();
if value != self.value {
self.value = value;
self.log();
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum DeviceKind {
KabyLake,
TigerLake,
Alchemist,
}
pub enum Event {
DdiHotplug(&'static str),
}
pub struct InterruptRegs {
// Interrupt status register, has live status of interrupts
pub isr: MmioPtr<u32>,
// Interrupt mask register, masks isr for iir, 0 is unmasked
pub imr: MmioPtr<u32>,
// Interrupt identity register, write 1 to clear
pub iir: MmioPtr<u32>,
// Interrupt enable register, 1 allows interrupt to propogate
pub ier: MmioPtr<u32>,
}
pub struct Interrupter {
change_detects: Vec<ChangeDetect>,
display_int_ctl: MmioPtr<u32>,
display_int_ctl_enable: u32,
display_int_ctl_sde: u32,
gfx_mstr_intr: Option<MmioPtr<u32>>,
gfx_mstr_intr_display: u32,
gfx_mstr_intr_enable: u32,
sde_interrupt: InterruptRegs,
}
#[derive(Debug)]
pub struct MmioRegion {
phys: usize,
virt: usize,
size: usize,
}
impl MmioRegion {
fn new(phys: usize, size: usize, memory_type: common::MemoryType) -> Result<Self> {
let virt = unsafe { common::physmap(phys, size, common::Prot::RW, memory_type)? as usize };
Ok(Self { phys, virt, size })
}
unsafe fn mmio(&self, offset: usize) -> Result<MmioPtr<u32>> {
// Any errors here will return ERANGE
let err = Error::new(ERANGE);
if offset.checked_add(mem::size_of::<u32>()).ok_or(err)? > self.size {
return Err(err);
}
let addr = self.virt.checked_add(offset).ok_or(err)?;
Ok(unsafe { MmioPtr::new(addr as *mut u32) })
}
}
impl Drop for MmioRegion {
fn drop(&mut self) {
unsafe {
let _ = libredox::call::munmap(self.virt as *mut (), self.size);
}
}
}
#[derive(Clone, Copy, Debug)]
enum VideoInput {
Hdmi,
Dp,
}
pub struct Device {
kind: DeviceKind,
alloc_buffers: RangeAllocator<u32>,
bios: Option<Bios>,
ddis: Vec<Ddi>,
dpclka_cfgcr0: Option<MmioPtr<u32>>,
dplls: Vec<Dpll>,
events: VecDeque<Event>,
framebuffers: Vec<DeviceFb>,
int: Interrupter,
gttmm: Arc<MmioRegion>,
ggtt: GlobalGtt,
gm: MmioRegion,
gmbus: Gmbus,
pipes: Vec<Pipe>,
power_wells: PowerWells,
ref_freq: u64,
transcoders: Vec<Transcoder>,
}
impl fmt::Debug for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Device")
.field("kind", &self.kind)
.field("alloc_buffers", &self.alloc_buffers)
.field("gttmm", &self.gttmm)
.field("gm", &self.gm)
.field("ref_freq", &self.ref_freq)
.finish_non_exhaustive()
}
}
impl Device {
pub fn new(pcid_handle: &mut PciFunctionHandle, func: &PciFunction) -> Result<Self> {
let kind = match (func.full_device_id.vendor_id, func.full_device_id.device_id) {
// Kaby Lake
(0x8086, 0x5912) |
(0x8086, 0x5916) |
(0x8086, 0x591B) |
(0x8086, 0x591E) |
(0x8086, 0x5926) |
// Comet Lake, seems to be compatible with Kaby Lake
(0x8086, 0x9B21) |
(0x8086, 0x9B41) |
(0x8086, 0x9BA4) |
(0x8086, 0x9BAA) |
(0x8086, 0x9BAC) |
(0x8086, 0x9BC4) |
(0x8086, 0x9BC5) |
(0x8086, 0x9BC6) |
(0x8086, 0x9BC8) |
(0x8086, 0x9BCA) |
(0x8086, 0x9BCC) |
(0x8086, 0x9BE6) |
(0x8086, 0x9BF6) => {
DeviceKind::KabyLake
}
// Tiger Lake
(0x8086, 0x9A40) |
(0x8086, 0x9A49) |
(0x8086, 0x9A60) |
(0x8086, 0x9A68) |
(0x8086, 0x9A70) |
(0x8086, 0x9A78) => {
DeviceKind::TigerLake
}
// Alchemist
(0x8086, 0x5690) | // A770M
(0x8086, 0x5691) | // A730M
(0x8086, 0x5692) | // A550M
(0x8086, 0x5693) | // A370M
(0x8086, 0x5694) | // A350M
(0x8086, 0x5696) | // A570M
(0x8086, 0x5697) | // A530M
(0x8086, 0x56A0) | // A770
(0x8086, 0x56A1) | // A750
(0x8086, 0x56A5) | // A380
(0x8086, 0x56A6) | // A310
(0x8086, 0x56B0) | // Pro A30M
(0x8086, 0x56B1) | // Pro A40/A50
(0x8086, 0x56B2) | // Pro A60M
(0x8086, 0x56B3) | // Pro A60
(0x8086, 0x56C0) | // GPU Flex 170
(0x8086, 0x56C1) // GPU Flex 140
=> {
DeviceKind::Alchemist
}
(vendor_id, device_id) => {
log::error!("unsupported ID {:04X}:{:04X}", vendor_id, device_id);
return Err(Error::new(ENODEV));
}
};
let gttmm = {
let (phys, size) = func.bars[0].expect_mem();
Arc::new(MmioRegion::new(
phys,
size,
common::MemoryType::Uncacheable,
)?)
};
log::info!("GTTMM {:X?}", gttmm);
let gm = {
let (phys, size) = func.bars[2].expect_mem();
MmioRegion::new(phys, size, common::MemoryType::WriteCombining)?
};
log::info!("GM {:X?}", gm);
/* IOBAR not used, not present on all generations
let iobar = func.bars[4].expect_port();
log::debug!("IOBAR {:X?}", iobar);
*/
// IGD OpRegion/Software SCI/_DSM for Skylake Processors
let bios_base = unsafe { pcid_handle.read_config(0xFC) };
let bios = if bios_base != 0 {
log::info!("BIOS {:X?}", bios_base);
// This is the default BIOS size
let bios_size = 8 * 1024;
match MmioRegion::new(
bios_base as usize,
bios_size,
common::MemoryType::Uncacheable,
) {
Ok(region) => match Bios::new(region) {
Ok(bios) => Some(bios),
Err(err) => {
log::warn!("failed to parse BIOS at {:08X}: {}", bios_base, err);
None
}
},
Err(err) => {
log::warn!("failed to map BIOS at {:08X}: {}", bios_base, err);
None
}
}
} else {
None
};
let ggtt = unsafe {
GlobalGtt::new(
pcid_handle,
gttmm.clone(),
//TODO: how to use 64-bit surface addresses?
gm.size.min(u32::MAX as usize) as u32,
)
};
//unsafe { ggtt.reset() };
// GMBUS seems to be stable for all generations
let gmbus = unsafe { Gmbus::new(&gttmm)? };
let dpclka_cfgcr0;
let int;
let ref_freq;
match kind {
DeviceKind::KabyLake => {
dpclka_cfgcr0 = None;
int = Interrupter {
change_detects: Vec::new(),
// IHD-OS-KBL-Vol 2c-1.17 MASTER_INT_CTL
display_int_ctl: unsafe { gttmm.mmio(0x44200)? },
display_int_ctl_enable: 1 << 31,
display_int_ctl_sde: 1 << 23,
gfx_mstr_intr: None,
gfx_mstr_intr_display: 0,
gfx_mstr_intr_enable: 0,
sde_interrupt: InterruptRegs {
isr: unsafe { gttmm.mmio(0xC4000)? },
imr: unsafe { gttmm.mmio(0xC4004)? },
iir: unsafe { gttmm.mmio(0xC4008)? },
ier: unsafe { gttmm.mmio(0xC400C)? },
},
};
// IHD-OS-KBL-Vol 12-1.17
ref_freq = 24_000_000;
}
DeviceKind::TigerLake | DeviceKind::Alchemist => {
// TigerLake: IHD-OS-TGL-Vol 2c-12.21
// Alchemist: IHD-OS-ACM-Vol 2c-3.23
dpclka_cfgcr0 = Some(unsafe { gttmm.mmio(0x164280)? });
let dssm = unsafe { gttmm.mmio(0x51004)? };
log::debug!("dssm {:08X}", dssm.read());
const DSSM_REF_FREQ_24_MHZ: u32 = 0b000 << 29;
const DSSM_REF_FREQ_19_2_MHZ: u32 = 0b001 << 29;
const DSSM_REF_FREQ_38_4_MHZ: u32 = 0b010 << 29;
const DSSM_REF_FREQ_MASK: u32 = 0b111 << 29;
ref_freq = match dssm.read() & DSSM_REF_FREQ_MASK {
DSSM_REF_FREQ_24_MHZ => 24_000_000,
DSSM_REF_FREQ_19_2_MHZ => 19_200_000,
DSSM_REF_FREQ_38_4_MHZ => 38_400_000,
unknown => {
log::error!("unknown DSSM reference frequency {}", unknown);
return Err(Error::new(EIO));
}
};
int = Interrupter {
change_detects: vec![
ChangeDetect::new("de_hpd_interrupt", unsafe { gttmm.mmio(0x44470)? }),
ChangeDetect::new("de_port_interrupt", unsafe { gttmm.mmio(0x44440)? }),
ChangeDetect::new("shotplug_ctl_ddi", unsafe { gttmm.mmio(0xC4030)? }),
ChangeDetect::new("shotplug_ctl_tc", unsafe { gttmm.mmio(0xC4034)? }),
ChangeDetect::new("tbt_hotplug_ctl", unsafe { gttmm.mmio(0x44030)? }),
ChangeDetect::new("tc_hotplug_ctl", unsafe { gttmm.mmio(0x44038)? }),
],
display_int_ctl: unsafe { gttmm.mmio(0x44200)? },
display_int_ctl_enable: 1 << 31,
display_int_ctl_sde: 1 << 23,
gfx_mstr_intr: Some(unsafe { gttmm.mmio(0x190010)? }),
gfx_mstr_intr_display: 1 << 16,
gfx_mstr_intr_enable: 1 << 31,
sde_interrupt: InterruptRegs {
isr: unsafe { gttmm.mmio(0xC4000)? },
imr: unsafe { gttmm.mmio(0xC4004)? },
iir: unsafe { gttmm.mmio(0xC4008)? },
ier: unsafe { gttmm.mmio(0xC400C)? },
},
};
}
}
let ddis;
let dplls;
let pipes;
let power_wells;
let transcoders;
match kind {
DeviceKind::KabyLake => {
ddis = Ddi::kabylake(&gttmm)?;
//TODO: kaby lake dplls
dplls = Vec::new();
pipes = Pipe::kabylake(&gttmm)?;
power_wells = PowerWells::kabylake(&gttmm)?;
transcoders = Transcoder::kabylake(&gttmm)?;
}
DeviceKind::TigerLake => {
ddis = Ddi::tigerlake(&gttmm)?;
dplls = Dpll::tigerlake(&gttmm)?;
pipes = Pipe::tigerlake(&gttmm)?;
power_wells = PowerWells::tigerlake(&gttmm)?;
transcoders = Transcoder::tigerlake(&gttmm)?;
}
DeviceKind::Alchemist => {
// Many registers are identical to tigerlake
dplls = Dpll::tigerlake(&gttmm)?;
pipes = Pipe::alchemist(&gttmm)?;
// FIXME transcoders are probably different too
transcoders = Transcoder::tigerlake(&gttmm)?;
// Power wells are distinct
ddis = Ddi::alchemist(&gttmm)?;
power_wells = PowerWells::alchemist(&gttmm)?;
}
}
//TODO: get number of available buffers
let buffers = 1024;
Ok(Self {
kind,
alloc_buffers: RangeAllocator::new(0..buffers),
bios,
ddis,
dpclka_cfgcr0,
dplls,
events: VecDeque::new(),
framebuffers: Vec::new(),
int,
gttmm,
ggtt,
gm,
gmbus,
pipes,
power_wells,
ref_freq,
transcoders,
})
}
pub fn init_inner(&mut self) {
// Discover current framebuffers
self.alloc_buffers.reset();
self.framebuffers.clear();
for pipe in self.pipes.iter() {
for plane in pipe.planes.iter() {
if plane.ctl.readf(PLANE_CTL_ENABLE) {
plane.fetch_modeset(&mut self.alloc_buffers);
self.framebuffers
.push(plane.fetch_framebuffer(&self.gm, &mut self.ggtt));
}
}
}
// Probe all DDIs
let ddi_names: Vec<&str> = self.ddis.iter().map(|ddi| ddi.name).collect();
for ddi_name in ddi_names {
self.probe_ddi(ddi_name).expect("failed to probe DDI");
}
self.dump();
log::info!(
"device initialized with {} framebuffers",
self.framebuffers.len()
);
// Enable SDE interrupts
{
let mut mask = 0;
for ddi in self.ddis.iter() {
if let Some(sde_interrupt_hotplug) = ddi.sde_interrupt_hotplug {
mask |= sde_interrupt_hotplug;
}
}
let sde_int = &mut self.int.sde_interrupt;
// Enable DDI hotplug interrupts
sde_int.ier.write(mask);
// Clear identity register
sde_int.iir.write(sde_int.iir.read());
// Unmask all interrupts
sde_int.imr.write(0);
}
// Enable display interrupts
self.int
.display_int_ctl
.write(self.int.display_int_ctl_enable);
if let Some(gfx_mstr_intr) = &mut self.int.gfx_mstr_intr {
// Enable graphics interrupts
gfx_mstr_intr.write(self.int.gfx_mstr_intr_enable);
}
for change_detect in self.int.change_detects.iter_mut() {
change_detect.log();
}
}
pub fn dump(&self) {
for ddi in self.ddis.iter() {
if ddi.buf_ctl.readf(DDI_BUF_CTL_ENABLE) {
ddi.dump();
}
}
if let Some(dpclka_cfgcr0) = &self.dpclka_cfgcr0 {
eprintln!("dpclka_cfgcr0 {:08X}", dpclka_cfgcr0.read());
}
for dpll in self.dplls.iter() {
if dpll.enable.readf(DPLL_ENABLE_ENABLE) {
dpll.dump();
}
}
for (transcoder, pipe) in self.transcoders.iter().zip(self.pipes.iter()) {
if transcoder.conf.readf(TRANS_CONF_ENABLE) {
transcoder.dump();
pipe.dump();
for plane in pipe.planes.iter() {
if plane.index == 0 || plane.ctl.readf(PLANE_CTL_ENABLE) {
eprint!(" ");
plane.dump();
}
}
}
}
}
pub fn probe_ddi(&mut self, name: &str) -> Result<bool> {
let Some(ddi) = self.ddis.iter_mut().find(|ddi| ddi.name == name) else {
log::warn!("DDI {} not found", name);
return Err(Error::new(EIO));
};
// Enable DDI power well
self.power_wells.enable_well_by_ddi(ddi.name)?;
let Some((source, edid_data)) =
ddi.probe_edid(&mut self.power_wells, &self.gttmm, &mut self.gmbus)?
else {
return Ok(false);
};
let edid = match edid::parse(&edid_data).to_full_result() {
Ok(edid) => {
log::info!("DDI {} EDID from {}: {:?}", ddi.name, source, edid);
edid
}
Err(err) => {
log::warn!(
"DDI {} failed to parse EDID from {}: {:?}",
ddi.name,
source,
err
);
// Will try again but not fail the driver
return Ok(false);
}
};
let timing_opt = edid.descriptors.iter().find_map(|desc| match desc {
edid::Descriptor::DetailedTiming(timing) => Some(timing),
_ => None,
});
let Some(timing) = timing_opt else {
log::warn!(
"DDI {} EDID from {} missing detailed timing",
ddi.name,
source
);
// Will try again but not fail the driver
return Ok(false);
};
let mut modeset = |ddi: &mut Ddi, input: VideoInput| -> Result<()> {
// IHD-OS-TGL-Vol 12-1.22-Rev2.0 "Sequences for HDMI and DVI"
// Power wells should already be enabled
//TODO: Type-C needs aux power enabled and max lanes set
// Enable port PLL without SSC. Not required on Type-C ports
if let Some(clock_shift) = ddi.dpclka_cfgcr0_clock_shift {
// Find free DPLL
let dpll = self
.dplls
.iter_mut()
.find(|dpll| !dpll.enable.readf(DPLL_ENABLE_ENABLE))
.ok_or_else(|| {
log::error!("failed to find free DPLL");
Error::new(EIO)
})?;
// DPLL power guard
let mut dpll_enable = unsafe { MmioPtr::new(dpll.enable.as_mut_ptr()) };
let dpll_power_guard = CallbackGuard::new(
&mut dpll_enable,
|dpll_enable| {
// Enable DPLL power
dpll_enable.writef(DPLL_ENABLE_POWER_ENABLE, true);
//TODO: timeout not specified in docs, should be very fast
let timeout = Timeout::from_micros(1);
while !dpll_enable.readf(DPLL_ENABLE_POWER_STATE) {
timeout.run().map_err(|()| {
log::debug!("timeout while enabling DPLL {} power", dpll.name);
Error::new(EIO)
})?;
}
Ok(())
},
|dpll_enable| {
// Disable DPLL power
dpll_enable.writef(DPLL_ENABLE_POWER_ENABLE, false);
},
)?;
match input {
VideoInput::Hdmi => {
// Set SSC enable/disable. For HDMI, always disable
dpll.ssc.writef(DPLL_SSC_ENABLE, false);
// Configure DPLL frequency
dpll.set_freq_hdmi(self.ref_freq, &timing)?;
}
VideoInput::Dp => {
log::warn!("DPLL for DisplayPort not implemented");
return Err(Error::new(EIO));
}
}
//TODO: "Sequence Before Frequency Change"
// Enable DPLL
//TODO: use guard?
{
dpll.enable.writef(DPLL_ENABLE_ENABLE, true);
let timeout = Timeout::from_micros(50);
while !dpll.enable.readf(DPLL_ENABLE_LOCK) {
timeout.run().map_err(|()| {
log::debug!("timeout while enabling DPLL {}", dpll.name);
Error::new(EIO)
})?;
}
}
//TODO: "Sequence After Frequency Change"
// Update DPLL mapping
if let Some(dpclka_cfgcr0) = &mut self.dpclka_cfgcr0 {
const DPCLKA_CFGCR0_CLOCK_MASK: u32 = 0b11;
let mut v = dpclka_cfgcr0.read();
v &= !(DPCLKA_CFGCR0_CLOCK_MASK << clock_shift);
v |= dpll.dpclka_cfgcr0_clock_value << clock_shift;
dpclka_cfgcr0.write(v);
}
// Continue to allow DPLL power
mem::forget(dpll_power_guard);
}
// Enable DPLL clock (must be done separately from PLL mapping)
if let Some(dpclka_cfgcr0) = &mut self.dpclka_cfgcr0 {
if let Some(clock_off) = ddi.dpclka_cfgcr0_clock_off {
dpclka_cfgcr0.writef(clock_off, false);
}
}
// Enable IO power
//TODO: the request can be shared by multiple DDIs
//TODO: skip if TBT
let pwr_well_ctl_ddi_request = ddi.pwr_well_ctl_ddi_request;
let pwr_well_ctl_ddi_state = ddi.pwr_well_ctl_ddi_state;
let mut pwr_well_ctl_ddi =
unsafe { MmioPtr::new(self.power_wells.ctl_ddi.as_mut_ptr()) };
let pwr_guard = CallbackGuard::new(
&mut pwr_well_ctl_ddi,
|pwr_well_ctl_ddi| {
// Enable IO power
pwr_well_ctl_ddi.writef(pwr_well_ctl_ddi_request, true);
let timeout = Timeout::from_micros(30);
while !pwr_well_ctl_ddi.readf(pwr_well_ctl_ddi_state) {
timeout.run().map_err(|()| {
log::debug!("timeout while requesting DDI {} IO power", ddi.name);
Error::new(EIO)
})?;
}
Ok(())
},
|pwr_well_ctl_ddi| {
// Disable IO power
pwr_well_ctl_ddi.writef(pwr_well_ctl_ddi_request, false);
},
)?;
//TODO: Type-C DP_MODE
// Enable planes, pipe, and transcoder
{
// Find free transcoder with free pipe
let mut transcoder_pipe = None;
for (transcoder, pipe) in self.transcoders.iter_mut().zip(self.pipes.iter_mut()) {
if transcoder.conf.readf(TRANS_CONF_ENABLE) {
continue;
}
//TODO: how would we know if pipe is in use?
transcoder_pipe = Some((transcoder, pipe));
break;
}
let Some((transcoder, pipe)) = transcoder_pipe else {
log::error!("free transcoder and pipe not found");
return Err(Error::new(EIO));
};
// Enable pipe and transcoder power wells
self.power_wells.enable_well_by_pipe(pipe.name)?;
self.power_wells
.enable_well_by_transcoder(transcoder.name)?;
// Configure transcoder clock select
if let Some(transcoder_index) = ddi.transcoder_index {
transcoder
.clk_sel
.write(transcoder_index << transcoder.clk_sel_shift);
}
// Set pipe bottom color to blue for debugging
pipe.bottom_color.write(0x3FF);
// Configure and enable planes
//TODO: THIS IS HACKY
if let Some(plane) = pipe.planes.first_mut() {
let width = timing.horizontal_active_pixels as u32;
let height = timing.vertical_active_lines as u32;
let fb = DeviceFb::alloc(&self.gm, &mut self.ggtt, width, height)?;
plane.modeset(&mut self.alloc_buffers)?;
plane.set_framebuffer(&fb);
self.framebuffers.push(fb);
}
//TODO: VGA and panel fitter steps?
// Configure transcoder timings and other pipe and transcoder settings
transcoder.modeset(pipe, &timing);
// Configure and enable TRANS_DDI_FUNC_CTL
{
let mut ddi_func_ctl = TRANS_DDI_FUNC_CTL_ENABLE |
//TODO: allow different bits per color
TRANS_DDI_FUNC_CTL_BPC_8 |
//TODO: correct port width selection
TRANS_DDI_FUNC_CTL_PORT_WIDTH_4;
if let Some(transcoder_index) = ddi.transcoder_index {
ddi_func_ctl |= transcoder_index << transcoder.ddi_func_ctl_ddi_shift;
}
match input {
VideoInput::Hdmi => {
ddi_func_ctl |= TRANS_DDI_FUNC_CTL_MODE_HDMI;
// Set HDMI scrambling and high TMDS char rate based on symbol rate > 340 MHz
if timing.pixel_clock > 340_000 {
ddi_func_ctl |= transcoder.ddi_func_ctl_hdmi_scrambling
| transcoder.ddi_func_ctl_high_tmds_char_rate;
}
}
VideoInput::Dp => {
//TODO: MST
ddi_func_ctl |= TRANS_DDI_FUNC_CTL_MODE_DP_SST;
}
}
match (timing.features >> 3) & 0b11 {
// Digital sync, separate
0b11 => {
if (timing.features & (1 << 2)) != 0 {
ddi_func_ctl |= TRANS_DDI_FUNC_CTL_SYNC_POLARITY_VSHIGH;
}
if (timing.features & (1 << 1)) != 0 {
ddi_func_ctl |= TRANS_DDI_FUNC_CTL_SYNC_POLARITY_HSHIGH;
}
}
unsupported => {
log::warn!("unsupported sync {:#x}", unsupported);
}
}
transcoder.ddi_func_ctl.write(ddi_func_ctl);
}
// Configure and enable TRANS_CONF
let mut conf = transcoder.conf.read();
// Set mode to progressive
conf &= !TRANS_CONF_MODE_MASK;
// Enable transcoder
conf |= TRANS_CONF_ENABLE;
transcoder.conf.write(conf);
//TODO: what is the correct timeout?
let timeout = Timeout::from_millis(100);
while !transcoder.conf.readf(TRANS_CONF_STATE) {
timeout.run().map_err(|()| {
log::error!(
"timeout on DDI {} transcoder {} enable",
ddi.name,
transcoder.name
);
Error::new(EIO)
})?;
}
}
// Enable port
{
// Configure voltage swing and related IO settings
match input {
VideoInput::Hdmi => {
ddi.voltage_swing_hdmi(&self.gttmm, &timing)?;
}
VideoInput::Dp => {
//TODO ddi.voltage_swing_dp(&self.gttmm)?;
log::error!("voltage swing for DP not implemented");
return Err(Error::new(EIO));
}
}
// Configure PORT_CL_DW10 static power down to power up all lanes
//TODO: only power up required lanes
if let Some(mut port_cl_dw10) = ddi.port_cl(PortClReg::Dw10) {
port_cl_dw10.writef(0b1111 << 4, false);
}
// Configure and enable DDI_BUF_CTL
//TODO: more DDI_BUF_CTL bits?
ddi.buf_ctl.writef(DDI_BUF_CTL_ENABLE, true);
// Wait for DDI_BUF_CTL IDLE = 0, timeout after 500 us
let timeout = Timeout::from_micros(500);
while ddi.buf_ctl.readf(DDI_BUF_CTL_IDLE) {
timeout.run().map_err(|()| {
log::warn!("timeout while waiting for DDI {} active", ddi.name);
Error::new(EIO)
})?;
}
}
// Keep IO power on if finished
mem::forget(pwr_guard);
Ok(())
};
if ddi.buf_ctl.readf(DDI_BUF_CTL_IDLE) {
log::info!("DDI {} idle, will attempt mode setting", ddi.name);
const EDID_VIDEO_INPUT_UNDEFINED: u8 = (1 << 7) | 0b0000;
const EDID_VIDEO_INPUT_DVI: u8 = (1 << 7) | 0b0001;
const EDID_VIDEO_INPUT_HDMI_A: u8 = (1 << 7) | 0b0010;
const EDID_VIDEO_INPUT_HDMI_B: u8 = (1 << 7) | 0b0011;
const EDID_VIDEO_INPUT_DP: u8 = (1 << 7) | 0b0101;
const EDID_VIDEO_INPUT_MASK: u8 = (1 << 7) | 0b1111;
let input = match edid_data[20] & EDID_VIDEO_INPUT_MASK {
//TODO: how to accurately discover input type?
//TODO: HDMI often shows up as undefined, do others?
EDID_VIDEO_INPUT_UNDEFINED
| EDID_VIDEO_INPUT_DVI
| EDID_VIDEO_INPUT_HDMI_A
| EDID_VIDEO_INPUT_HDMI_B => VideoInput::Hdmi,
EDID_VIDEO_INPUT_DP => VideoInput::Dp,
unknown => {
log::warn!("EDID video input 0x{:02X} not supported", unknown);
return Err(Error::new(EIO));
}
};
//TODO: DisplayPort modeset not complete
match modeset(ddi, input) {
Ok(()) => {
log::info!("DDI {} modeset {:?} finished", ddi.name, input);
}
Err(err) => {
log::warn!("DDI {} modeset {:?} failed: {}", ddi.name, input, err);
// Will try again but not fail the driver
return Ok(false);
}
}
} else {
log::info!("DDI {} already active", ddi.name);
}
Ok(true)
}
pub fn handle_display_irq(&mut self) -> bool {
let display_ints = self.int.display_int_ctl.read() & !self.int.display_int_ctl_enable;
if display_ints != 0 {
log::info!(" display ints {:08X}", display_ints);
if display_ints & self.int.display_int_ctl_sde != 0 {
let sde_ints = self.int.sde_interrupt.iir.read();
self.int.sde_interrupt.iir.write(sde_ints);
log::info!(" south display engine ints {:08X}", sde_ints);
for ddi in self.ddis.iter() {
if let Some(sde_interrupt_hotplug) = ddi.sde_interrupt_hotplug {
if sde_ints & sde_interrupt_hotplug == sde_interrupt_hotplug {
self.events.push_back(Event::DdiHotplug(ddi.name));
}
}
}
}
true
} else {
false
}
}
pub fn handle_irq(&mut self) -> bool {
let had_irq = if let Some(gfx_mstr_intr) = &mut self.int.gfx_mstr_intr {
let gfx_ints = gfx_mstr_intr.read() & !self.int.gfx_mstr_intr_enable;
if gfx_ints != 0 {
log::info!("gfx ints {:08X}", gfx_ints);
gfx_mstr_intr.write(gfx_ints | self.int.gfx_mstr_intr_enable);
if gfx_ints & self.int.gfx_mstr_intr_display != 0 {
self.handle_display_irq();
}
true
} else {
false
}
} else {
self.handle_display_irq()
};
if had_irq {
for change_detect in self.int.change_detects.iter_mut() {
change_detect.check();
}
}
had_irq
}
pub fn handle_events(&mut self) {
while let Some(event) = self.events.pop_front() {
match event {
Event::DdiHotplug(ddi_name) => {
log::info!("DDI {} plugged", ddi_name);
for _attempt in 0..4 {
//TODO: gmbus times out!
match self.probe_ddi(ddi_name) {
Ok(true) => {
break;
}
Ok(false) => {
log::warn!("timeout probing {}", ddi_name);
}
Err(err) => {
log::warn!("failed to probe {}: {}", ddi_name, err);
}
}
//TODO: do this asynchronously so scheme events can be handled
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
}
}
}
}
@@ -0,0 +1,356 @@
use common::io::{Io, MmioPtr};
use range_alloc::RangeAllocator;
use syscall::error::Result;
use syscall::{Error, EIO};
use super::buffer::GpuBuffer;
use super::{GlobalGtt, MmioRegion};
pub const PLANE_CTL_ENABLE: u32 = 1 << 31;
pub const PLANE_WM_ENABLE: u32 = 1 << 31;
pub const PLANE_WM_LINES_SHIFT: u32 = 14;
#[derive(Debug)]
pub struct DeviceFb {
pub buffer: GpuBuffer,
pub width: u32,
pub height: u32,
pub stride: u32,
}
impl DeviceFb {
pub unsafe fn new(
gm: &MmioRegion,
surf: u32,
width: u32,
height: u32,
stride: u32,
clear: bool,
) -> Self {
Self {
buffer: unsafe { GpuBuffer::new(gm, surf, stride * height, clear) },
width,
height,
stride,
}
}
pub fn alloc(
gm: &MmioRegion,
ggtt: &mut GlobalGtt,
width: u32,
height: u32,
) -> syscall::Result<Self> {
let (buffer, stride) = GpuBuffer::alloc_dumb(gm, ggtt, width, height)?;
Ok(DeviceFb {
buffer,
width,
height,
stride,
})
}
}
pub struct Plane {
pub name: &'static str,
pub index: usize,
pub buf_cfg: MmioPtr<u32>,
pub color_ctl: Option<MmioPtr<u32>>,
pub color_ctl_gamma_disable: u32,
pub ctl: MmioPtr<u32>,
pub ctl_source_rgb_8888: u32,
pub ctl_source_mask: u32,
pub offset: MmioPtr<u32>,
pub pos: MmioPtr<u32>,
pub size: MmioPtr<u32>,
pub stride: MmioPtr<u32>,
pub surf: MmioPtr<u32>,
pub wm: [MmioPtr<u32>; 8],
pub wm_trans: MmioPtr<u32>,
}
impl Plane {
pub fn fetch_modeset(&self, alloc_buffers: &mut RangeAllocator<u32>) {
let buf_cfg = self.buf_cfg.read();
let buffer_start = buf_cfg & 0x7FF;
let buffer_end = (buf_cfg >> 16) & 0x7FF;
alloc_buffers
.allocate_exact_range(buffer_start..(buffer_end + 1))
.unwrap_or_else(|err| {
panic!(
"failed to allocate pre-existing buffer blocks {} to {}: {:?}",
buffer_start, buffer_end, err
);
});
}
pub fn modeset(&mut self, alloc_buffers: &mut RangeAllocator<u32>) -> syscall::Result<()> {
// FIXME handle runtime buffer reconfiguration
//TODO: enable DBUF if more buffers needed
//TODO: more blocks would mean better power usage
// Minimum is 8 blocks for linear planes, 160 blocks is recommended for pre-OS init
let buffer_size = 160;
let buffer = alloc_buffers.allocate_range(buffer_size).map_err(|err| {
log::warn!(
"failed to allocate {} buffer blocks: {:?}",
buffer_size,
err
);
Error::new(EIO)
})?;
self.buf_cfg.write(buffer.start | (buffer.end << 16));
//TODO: correct watermark calculation
self.wm[0].write(PLANE_WM_ENABLE | (2 << PLANE_WM_LINES_SHIFT) | buffer.len() as u32);
for i in 1..self.wm.len() {
self.wm[i].writef(PLANE_WM_ENABLE, false);
}
self.wm_trans.writef(PLANE_WM_ENABLE, false);
Ok(())
}
pub fn fetch_framebuffer(&self, gm: &MmioRegion, ggtt: &mut GlobalGtt) -> DeviceFb {
let size = self.size.read();
let width = (size & 0xFFFF) + 1;
let height = ((size >> 16) & 0xFFFF) + 1;
let stride_64 = self.stride.read() & 0x7FF;
//TODO: this will be wrong for tiled planes
let stride = stride_64 * 64;
let surf = self.surf.read() & 0xFFFFF000;
//TODO: read bits per pixel
let surf_size = (stride * height).next_multiple_of(4096);
ggtt.reserve(surf, surf_size);
unsafe { DeviceFb::new(gm, surf, width, height, stride, true) }
}
pub fn set_framebuffer(&mut self, fb: &DeviceFb) {
//TODO: documentation on this is not great
let stride_64 = fb.stride / 64;
self.size.write((fb.width - 1) | ((fb.height - 1) << 16));
self.stride.write(stride_64);
self.surf.write(fb.buffer.gm_offset);
// Disable gamma
if let Some(color_ctl) = &mut self.color_ctl {
color_ctl.write(self.color_ctl_gamma_disable);
}
//TODO: more PLANE_CTL bits
self.ctl.write(PLANE_CTL_ENABLE | self.ctl_source_rgb_8888);
}
pub fn dump(&self) {
eprint!("Plane {}", self.name);
eprint!(" buf_cfg {:08X}", self.buf_cfg.read());
if let Some(reg) = &self.color_ctl {
eprint!(" color_ctl {:08X}", reg.read());
}
eprint!(" ctl {:08X}", self.ctl.read());
eprint!(" offset {:08X}", self.offset.read());
eprint!(" pos {:08X}", self.offset.read());
eprint!(" size {:08X}", self.size.read());
eprint!(" stride {:08X}", self.stride.read());
eprint!(" surf {:08X}", self.surf.read());
for i in 0..self.wm.len() {
eprint!(" wm_{} {:08X}", i, self.wm[i].read());
}
eprint!(" wm_trans {:08X}", self.wm_trans.read());
eprintln!();
}
}
pub struct Pipe {
pub name: &'static str,
pub index: usize,
pub planes: Vec<Plane>,
pub bottom_color: MmioPtr<u32>,
pub misc: MmioPtr<u32>,
pub srcsz: MmioPtr<u32>,
}
impl Pipe {
pub fn dump(&self) {
eprint!("Pipe {}", self.name);
eprint!(" bottom_color {:08X}", self.bottom_color.read());
eprint!(" misc {:08X}", self.misc.read());
eprint!(" srcsz {:08X}", self.srcsz.read());
eprintln!();
}
pub fn kabylake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
let mut pipes = Vec::with_capacity(3);
for (i, name) in ["A", "B", "C"].iter().enumerate() {
let mut planes = Vec::new();
//TODO: cursor plane
for (j, name) in ["1", "2", "3"].iter().enumerate() {
planes.push(Plane {
name,
index: j,
// IHD-OS-KBL-Vol 2c-1.17 PLANE_BUF_CFG
buf_cfg: unsafe { gttmm.mmio(0x7027C + i * 0x1000 + j * 0x100)? },
// N/A
color_ctl: None,
color_ctl_gamma_disable: 0,
// IHD-OS-KBL-Vol 2c-1.17 PLANE_CTL
ctl: unsafe { gttmm.mmio(0x70180 + i * 0x1000 + j * 0x100)? },
ctl_source_rgb_8888: 0b0100 << 24,
ctl_source_mask: 0b1111 << 24,
// IHD-OS-KBL-Vol 2c-1.17 PLANE_OFFSET
offset: unsafe { gttmm.mmio(0x701A4 + i * 0x1000 + j * 0x100)? },
// IHD-OS-KBL-Vol 2c-1.17 PLANE_POS
pos: unsafe { gttmm.mmio(0x7018C + i * 0x1000 + j * 0x100)? },
// IHD-OS-KBL-Vol 2c-1.17 PLANE_SIZE
size: unsafe { gttmm.mmio(0x70190 + i * 0x1000 + j * 0x100)? },
// IHD-OS-KBL-Vol 2c-1.17 PLANE_STRIDE
stride: unsafe { gttmm.mmio(0x70188 + i * 0x1000 + j * 0x100)? },
// IHD-OS-KBL-Vol 2c-1.17 PLANE_SURF
surf: unsafe { gttmm.mmio(0x7019C + i * 0x1000 + j * 0x100)? },
// IHD-OS-KBL-Vol 2c-1.17 PLANE_WM
wm: [
unsafe { gttmm.mmio(0x70240 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70244 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70248 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x7024C + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70250 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70254 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70258 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x7025C + i * 0x1000 + j * 0x100)? },
],
wm_trans: unsafe { gttmm.mmio(0x70268 + i * 0x1000 + j * 0x100)? },
});
}
pipes.push(Pipe {
name,
index: i,
planes,
// IHD-OS-KBL-Vol 2c-1.17 PIPE_BOTTOM_COLOR
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 PIPE_MISC
misc: unsafe { gttmm.mmio(0x70030 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 PIPE_SRCSZ
srcsz: unsafe { gttmm.mmio(0x6001C + i * 0x1000)? },
})
}
Ok(pipes)
}
pub fn tigerlake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
let mut pipes = Vec::with_capacity(4);
for (i, name) in ["A", "B", "C", "D"].iter().enumerate() {
let mut planes = Vec::new();
//TODO: cursor plane
for (j, name) in ["1", "2", "3", "4", "5", "6", "7"].iter().enumerate() {
planes.push(Plane {
name,
index: j,
// IHD-OS-TGL-Vol 2c-12.21 PLANE_BUF_CFG
buf_cfg: unsafe { gttmm.mmio(0x7027C + i * 0x1000 + j * 0x100)? },
// IHD-OS-TGL-Vol 2c-12.21 PLANE_COLOR_CTL
color_ctl: Some(unsafe { gttmm.mmio(0x701CC + i * 0x1000 + j * 0x100)? }),
color_ctl_gamma_disable: 1 << 13,
// IHD-OS-TGL-Vol 2c-12.21 PLANE_CTL
ctl: unsafe { gttmm.mmio(0x70180 + i * 0x1000 + j * 0x100)? },
ctl_source_rgb_8888: 0b01000 << 23,
ctl_source_mask: 0b11111 << 23,
// IHD-OS-TGL-Vol 2c-12.21 PLANE_OFFSET
offset: unsafe { gttmm.mmio(0x701A4 + i * 0x1000 + j * 0x100)? },
// IHD-OS-TGL-Vol 2c-12.21 PLANE_POS
pos: unsafe { gttmm.mmio(0x7018C + i * 0x1000 + j * 0x100)? },
// IHD-OS-TGL-Vol 2c-12.21 PLANE_SIZE
size: unsafe { gttmm.mmio(0x70190 + i * 0x1000 + j * 0x100)? },
// IHD-OS-TGL-Vol 2c-12.21 PLANE_STRIDE
stride: unsafe { gttmm.mmio(0x70188 + i * 0x1000 + j * 0x100)? },
// IHD-OS-TGL-Vol 2c-12.21 PLANE_SURF
surf: unsafe { gttmm.mmio(0x7019C + i * 0x1000 + j * 0x100)? },
// IHD-OS-TGL-Vol 2c-12.21 PLANE_WM
wm: [
unsafe { gttmm.mmio(0x70240 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70244 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70248 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x7024C + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70250 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70254 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70258 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x7025C + i * 0x1000 + j * 0x100)? },
],
wm_trans: unsafe { gttmm.mmio(0x70268 + i * 0x1000 + j * 0x100)? },
});
}
pipes.push(Pipe {
name,
index: i,
planes,
// IHD-OS-TGL-Vol 2c-12.21 PIPE_BOTTOM_COLOR
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 PIPE_MISC
misc: unsafe { gttmm.mmio(0x70030 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 PIPE_SRCSZ
srcsz: unsafe { gttmm.mmio(0x6001C + i * 0x1000)? },
})
}
Ok(pipes)
}
pub fn alchemist(gttmm: &MmioRegion) -> Result<Vec<Self>> {
let mut pipes = Vec::with_capacity(4);
for (i, name) in ["A", "B", "C", "D"].iter().enumerate() {
let mut planes = Vec::new();
//TODO: cursor plane
for (j, name) in ["1", "2", "3", "4", "5"].iter().enumerate() {
planes.push(Plane {
name,
index: j,
// IHD-OS-ACM-Vol 2c-3.23 PLANE_BUF_CFG
buf_cfg: unsafe { gttmm.mmio(0x7057C + i * 0x1000 + j * 0x100)? },
// IHD-OS-ACM-Vol 2c-3.23 PLANE_COLOR_CTL
color_ctl: Some(unsafe { gttmm.mmio(0x704CC + i * 0x1000 + j * 0x100)? }),
color_ctl_gamma_disable: 1 << 13,
// IHD-OS-ACM-Vol 2c-3.23 PLANE_CTL
ctl: unsafe { gttmm.mmio(0x70480 + i * 0x1000 + j * 0x100)? },
ctl_source_rgb_8888: 0b01000 << 23,
ctl_source_mask: 0b11111 << 23,
// IHD-OS-ACM-Vol 2c-3.23 PLANE_OFFSET
offset: unsafe { gttmm.mmio(0x704A4 + i * 0x1000 + j * 0x100)? },
// IHD-OS-ACM-Vol 2c-3.23 PLANE_POS
pos: unsafe { gttmm.mmio(0x7048C + i * 0x1000 + j * 0x100)? },
// IHD-OS-ACM-Vol 2c-3.23 PLANE_SIZE
size: unsafe { gttmm.mmio(0x70490 + i * 0x1000 + j * 0x100)? },
// IHD-OS-ACM-Vol 2c-3.23 PLANE_STRIDE
stride: unsafe { gttmm.mmio(0x70488 + i * 0x1000 + j * 0x100)? },
// IHD-OS-ACM-Vol 2c-3.23 PLANE_SURF
surf: unsafe { gttmm.mmio(0x7049C + i * 0x1000 + j * 0x100)? },
// IHD-OS-ACM-Vol 2c-3.23 PLANE_WM
wm: [
unsafe { gttmm.mmio(0x70540 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70544 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70548 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x7054C + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70550 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70554 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x70558 + i * 0x1000 + j * 0x100)? },
unsafe { gttmm.mmio(0x7055C + i * 0x1000 + j * 0x100)? },
],
wm_trans: unsafe { gttmm.mmio(0x70568 + i * 0x1000 + j * 0x100)? },
});
}
pipes.push(Pipe {
name,
index: i,
planes,
// IHD-OS-ACM-Vol 2c-3.23 PIPE_BOTTOM_COLOR
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
// IHD-OS-ACM-Vol 2c-3.23 PIPE_MISC
misc: unsafe { gttmm.mmio(0x70030 + i * 0x1000)? },
// IHD-OS-ACM-Vol 2c-3.23 PIPE_SRCSZ
srcsz: unsafe { gttmm.mmio(0x6001C + i * 0x1000)? },
})
}
Ok(pipes)
}
}
@@ -0,0 +1,323 @@
use common::{
io::{Io, MmioPtr},
timeout::Timeout,
};
use syscall::error::{Error, Result, EIO};
use super::MmioRegion;
#[derive(Clone, Copy)]
pub struct PowerWell {
pub name: &'static str,
pub depends: &'static [&'static str],
pub ddis: &'static [&'static str],
pub pipes: &'static [&'static str],
pub transcoders: &'static [&'static str],
pub request: u32,
pub state: u32,
pub fuse_status: u32,
}
pub struct PowerWells {
pub ctl: MmioPtr<u32>,
pub ctl_aux: MmioPtr<u32>,
pub ctl_ddi: MmioPtr<u32>,
pub fuse_status: MmioPtr<u32>,
pub fuse_status_pg0: u32,
pub wells: Vec<PowerWell>,
}
impl PowerWells {
//TODO: return guard?
pub fn enable_well(&mut self, name: &'static str) -> Result<()> {
// Wait 20us for distribution of PG0
{
let timeout = Timeout::from_micros(20);
while !self.fuse_status.readf(self.fuse_status_pg0) {
timeout.run().map_err(|()| {
log::warn!("timeout on distribution of power well 0");
Error::new(EIO)
})?;
}
}
// self.wells iter copied to allow mutable self.enable_well later
for well in self.wells.iter().copied() {
if well.name == name {
// Enable dependent wells
for depend in well.depends.iter() {
self.enable_well(depend)?;
}
if !self.ctl.readf(well.request) {
log::info!("enabling power well {}", well.name);
}
// Set request bit
self.ctl.writef(well.request, true);
// Wait 100us for enabled state
{
let timeout = Timeout::from_micros(100);
while !self.ctl.readf(well.state) {
timeout.run().map_err(|()| {
log::warn!("timeout enabling power well {}", well.name);
Error::new(EIO)
})?;
}
}
// Wait 20us for distribution
{
let timeout = Timeout::from_micros(20);
while !self.fuse_status.readf(well.fuse_status) {
timeout.run().map_err(|()| {
log::warn!("timeout on distribution of power well {}", well.name);
Error::new(EIO)
})?;
}
}
return Ok(());
}
}
log::warn!("power well {} not found", name);
Err(Error::new(EIO))
}
pub fn enable_well_by_ddi(&mut self, name: &'static str) -> Result<()> {
for well in self.wells.iter() {
if well.ddis.contains(&name) {
return self.enable_well(well.name);
}
}
log::warn!("power well for DDI {} not found", name);
Err(Error::new(EIO))
}
pub fn enable_well_by_pipe(&mut self, name: &'static str) -> Result<()> {
for well in self.wells.iter() {
if well.pipes.contains(&name) {
return self.enable_well(well.name);
}
}
log::warn!("power well for pipe {} not found", name);
Err(Error::new(EIO))
}
pub fn enable_well_by_transcoder(&mut self, name: &'static str) -> Result<()> {
for well in self.wells.iter() {
if well.transcoders.contains(&name) {
return self.enable_well(well.name);
}
}
log::warn!("power well for transcoder {} not found", name);
Err(Error::new(EIO))
}
pub fn kabylake(gttmm: &MmioRegion) -> Result<Self> {
// IHD-OS-KBL-Vol 2c-1.17 PWR_WELL_CTL
let ctl = unsafe { gttmm.mmio(0x45404)? };
// Hack since these power ctl registers are combined
let ctl_aux = unsafe { gttmm.mmio(0x45404)? };
let ctl_ddi = unsafe { gttmm.mmio(0x45404)? };
// IHD-OS-KBL-Vol 2c-1.17 FUSE_STATUS
let fuse_status = unsafe { gttmm.mmio(0x42000)? };
let fuse_status_pg0 = 1 << 27;
let wells = vec![
PowerWell {
name: "1",
depends: &[],
ddis: &["A"],
pipes: &["A"],
transcoders: &["EDP"],
request: 1 << 29,
state: 1 << 28,
fuse_status: 1 << 26,
},
PowerWell {
name: "2",
depends: &["1"],
ddis: &["B", "C", "D", "E"],
pipes: &["B", "C"],
transcoders: &["A", "B", "C"],
request: 1 << 31,
state: 1 << 30,
fuse_status: 1 << 25,
},
];
Ok(Self {
ctl,
ctl_aux,
ctl_ddi,
fuse_status,
fuse_status_pg0,
wells,
})
}
pub fn tigerlake(gttmm: &MmioRegion) -> Result<Self> {
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL
let ctl = unsafe { gttmm.mmio(0x45404)? };
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL_AUX
let ctl_aux = unsafe { gttmm.mmio(0x45444)? };
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL_DDI
let ctl_ddi = unsafe { gttmm.mmio(0x45454)? };
// IHD-OS-TGL-Vol 2c-12.21 FUSE_STATUS
let fuse_status = unsafe { gttmm.mmio(0x42000)? };
let fuse_status_pg0 = 1 << 27;
let wells = vec![
// DBUF functionality, Pipe A, Transcoder A and DSI, DDI A-C, FBC, DSS
PowerWell {
name: "1",
depends: &[],
ddis: &["A", "B", "C"],
pipes: &["A"],
transcoders: &["A"],
request: 1 << 1,
state: 1 << 0,
fuse_status: 1 << 26,
},
// VDSC for pipe A
PowerWell {
name: "2",
depends: &["1"],
ddis: &[],
pipes: &[],
transcoders: &[],
request: 1 << 3,
state: 1 << 2,
fuse_status: 1 << 25,
},
// Pipe B, Audio, Transcoder WD, VGA, Transcoder B, DDI USBC1-6, KVMR
PowerWell {
name: "3",
depends: &["2"],
ddis: &["USBC1", "USBC2", "USBC3", "USBC4", "USBC5", "USBC6"],
pipes: &["B"],
transcoders: &["B"],
request: 1 << 5,
state: 1 << 4,
fuse_status: 1 << 24,
},
// Pipe C, Transcoder C
PowerWell {
name: "4",
depends: &["3"],
ddis: &[],
pipes: &["C"],
transcoders: &["C"],
request: 1 << 7,
state: 1 << 6,
fuse_status: 1 << 23,
},
// Pipe D, Transcoder D
PowerWell {
name: "5",
depends: &["4"],
ddis: &[],
pipes: &["D"],
transcoders: &["D"],
request: 1 << 9,
state: 1 << 8,
fuse_status: 1 << 22,
},
];
Ok(Self {
ctl,
ctl_aux,
ctl_ddi,
fuse_status,
fuse_status_pg0,
wells,
})
}
pub fn alchemist(gttmm: &MmioRegion) -> Result<Self> {
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL
let ctl = unsafe { gttmm.mmio(0x45404)? };
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL_AUX
let ctl_aux = unsafe { gttmm.mmio(0x45444)? };
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL_DDI
let ctl_ddi = unsafe { gttmm.mmio(0x45454)? };
// IHD-OS-ACM-Vol 2c-3.23 FUSE_STATUS
let fuse_status = unsafe { gttmm.mmio(0x42000)? };
let fuse_status_pg0 = 1 << 27;
let wells = vec![
// DBUF functionality, Transcoder A, DDI A-B
PowerWell {
name: "1",
depends: &[],
ddis: &["A", "B"],
pipes: &[],
transcoders: &["A"],
request: 1 << 1,
state: 1 << 0,
fuse_status: 1 >> 26,
},
// Audio playback, Transcoder WD, VGA, DDI C-E, Type-C, KVMR
PowerWell {
name: "2",
depends: &["1"],
ddis: &["C", "D", "E", "USBC1", "USBC2", "USBC3", "USBC4"],
pipes: &[],
transcoders: &[],
request: 1 << 3,
state: 1 << 2,
fuse_status: 1 << 25,
},
// Pipe A, FBC
PowerWell {
name: "A",
depends: &["1"],
ddis: &[],
pipes: &["A"],
transcoders: &[],
request: 1 << 11,
state: 1 << 10,
fuse_status: 1 << 21,
},
// Pipe B, Transcoder B
PowerWell {
name: "B",
depends: &["2"],
ddis: &[],
pipes: &["B"],
transcoders: &["B"],
request: 1 << 13,
state: 1 << 12,
fuse_status: 1 << 20,
},
// Pipe C, Transcoder C
PowerWell {
name: "C",
depends: &["2"],
ddis: &[],
pipes: &["C"],
transcoders: &["C"],
request: 1 << 15,
state: 1 << 14,
fuse_status: 1 << 19,
},
// Pipe D, Transcoder D
PowerWell {
name: "D",
depends: &["2"],
ddis: &[],
pipes: &["D"],
transcoders: &["D"],
request: 1 << 17,
state: 1 << 16,
fuse_status: 1 << 18,
},
];
Ok(Self {
ctl,
ctl_aux,
ctl_ddi,
fuse_status,
fuse_status_pg0,
wells,
})
}
}
@@ -0,0 +1,208 @@
//TODO: this is copied from vesad and should be adapted
use std::alloc::{self, Layout};
use std::convert::TryInto;
use std::ptr::{self, NonNull};
use std::sync::Mutex;
use driver_graphics::kms::connector::{KmsConnectorDriver, KmsConnectorStatus};
use driver_graphics::kms::objects::{KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects};
use driver_graphics::{Buffer, CursorPlane, Damage, GraphicsAdapter};
use drm_sys::{
DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFER_SHADOW, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
};
use syscall::{error::EINVAL, PAGE_SIZE};
use super::pipe::DeviceFb;
use super::Device;
#[derive(Debug)]
pub struct Connector {
framebuffer_id: usize,
}
impl KmsConnectorDriver for Connector {
type State = ();
}
impl GraphicsAdapter for Device {
type Connector = Connector;
type Crtc = ();
type Buffer = DumbFb;
type Framebuffer = ();
fn name(&self) -> &'static [u8] {
b"ihdgd"
}
fn desc(&self) -> &'static [u8] {
b"Intel HD Graphics"
}
fn init(&mut self, objects: &mut KmsObjects<Self>) {
self.init_inner();
// FIXME enumerate actual connectors
for (framebuffer_id, _) in self.framebuffers.iter().enumerate() {
let crtc = objects.add_crtc((), ());
objects.add_connector(Connector { framebuffer_id }, (), &[crtc]);
}
}
fn get_cap(&self, cap: u32) -> syscall::Result<u64> {
match cap {
DRM_CAP_DUMB_BUFFER => Ok(1),
DRM_CAP_DUMB_PREFER_SHADOW => Ok(0),
_ => Err(syscall::Error::new(EINVAL)),
}
}
fn set_client_cap(&self, cap: u32, _value: u64) -> syscall::Result<()> {
match cap {
// FIXME hide cursor plane unless this client cap is set
DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT => Ok(()),
_ => Err(syscall::Error::new(EINVAL)),
}
}
fn probe_connector(&mut self, objects: &mut KmsObjects<Self>, id: KmsObjectId) {
let mut connector = objects.get_connector(id).unwrap().lock().unwrap();
let framebuffer = &self.framebuffers[connector.driver_data.framebuffer_id];
connector.connection = KmsConnectorStatus::Connected;
connector.update_from_size(framebuffer.width as u32, framebuffer.height as u32);
// FIXME fetch EDID
}
fn create_dumb_buffer(&mut self, width: u32, height: u32) -> (Self::Buffer, u32) {
(DumbFb::new(width as usize, height as usize), width * 4)
}
fn map_dumb_buffer(&mut self, framebuffer: &Self::Buffer) -> *mut u8 {
framebuffer.ptr.as_ptr().cast::<u8>()
}
fn create_framebuffer(&mut self, _buffer: &Self::Buffer) -> Self::Framebuffer {
()
}
fn set_crtc(
&mut self,
objects: &KmsObjects<Self>,
crtc: &Mutex<KmsCrtc<Self>>,
state: KmsCrtcState<Self>,
damage: Damage,
) -> syscall::Result<()> {
let mut crtc = crtc.lock().unwrap();
let buffer = state
.fb_id
.map(|fb_id| objects.get_framebuffer(fb_id))
.transpose()?;
crtc.state = state;
for connector in objects.connectors() {
let connector = connector.lock().unwrap();
if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] {
continue;
}
let framebuffer_id = connector.driver_data.framebuffer_id;
let framebuffer = &mut self.framebuffers[framebuffer_id];
if let Some(buffer) = buffer {
buffer.buffer.sync(framebuffer, damage)
} else {
let onscreen_ptr = framebuffer.buffer.virt.cast::<u32>();
for row in 0..framebuffer.height {
unsafe {
ptr::write_bytes(
onscreen_ptr.add((row * framebuffer.stride) as usize),
0,
framebuffer.width as usize,
);
}
}
}
}
Ok(())
}
fn hw_cursor_size(&self) -> Option<(u32, u32)> {
None
}
fn handle_cursor(&mut self, _cursor: &CursorPlane<Self::Buffer>, _dirty_fb: bool) {
unimplemented!("ihdgd does not support this function");
}
}
#[derive(Debug)]
pub struct DumbFb {
width: usize,
height: usize,
ptr: NonNull<[u32]>,
}
impl DumbFb {
fn new(width: usize, height: usize) -> DumbFb {
let len = width * height;
let layout = Self::layout(len);
let ptr = unsafe { alloc::alloc_zeroed(layout) };
let ptr = ptr::slice_from_raw_parts_mut(ptr.cast(), len);
let ptr = NonNull::new(ptr).unwrap_or_else(|| alloc::handle_alloc_error(layout));
DumbFb { width, height, ptr }
}
#[inline]
fn layout(len: usize) -> Layout {
// optimizes to an integer mul
Layout::array::<u32>(len)
.unwrap()
.align_to(PAGE_SIZE)
.unwrap()
}
}
impl Drop for DumbFb {
fn drop(&mut self) {
let layout = Self::layout(self.ptr.len());
unsafe { alloc::dealloc(self.ptr.as_ptr().cast(), layout) };
}
}
impl Buffer for DumbFb {
fn size(&self) -> usize {
self.width * self.height * 4
}
}
impl DumbFb {
fn sync(&self, framebuffer: &mut DeviceFb, sync_rect: Damage) {
let sync_rect = sync_rect.clip(
self.width.try_into().unwrap(),
self.height.try_into().unwrap(),
);
let start_x: usize = sync_rect.x.try_into().unwrap();
let start_y: usize = sync_rect.y.try_into().unwrap();
let w: usize = sync_rect.width.try_into().unwrap();
let h: usize = sync_rect.height.try_into().unwrap();
let offscreen_ptr = self.ptr.as_ptr() as *mut u32;
let onscreen_ptr = framebuffer.buffer.virt.cast::<u32>();
for row in start_y..start_y + h {
unsafe {
ptr::copy(
offscreen_ptr.add(row * self.width + start_x),
onscreen_ptr.add(row * framebuffer.stride as usize / 4 + start_x),
w,
);
}
}
}
}
@@ -0,0 +1,258 @@
use common::io::{Io, MmioPtr};
use syscall::error::Result;
use super::{MmioRegion, Pipe};
// IHD-OS-KBL-Vol 2c-1.17 TRANS_CONF
// IHD-OS-TGL-Vol 2c-12.21 TRANS_CONF
pub const TRANS_CONF_ENABLE: u32 = 1 << 31;
pub const TRANS_CONF_STATE: u32 = 1 << 30;
pub const TRANS_CONF_MODE_MASK: u32 = 0b11 << 21;
// IHD-OS-KBL-Vol 2c-1.17 TRANS_DDI_FUNC_CTL
// IHD-OS-TGL-Vol 2c-12.21 TRANS_DDI_FUNC_CTL
pub const TRANS_DDI_FUNC_CTL_ENABLE: u32 = 1 << 31;
pub const TRANS_DDI_FUNC_CTL_MODE_HDMI: u32 = 0b000 << 24;
pub const TRANS_DDI_FUNC_CTL_MODE_DVI: u32 = 0b001 << 24;
pub const TRANS_DDI_FUNC_CTL_MODE_DP_SST: u32 = 0b010 << 24;
pub const TRANS_DDI_FUNC_CTL_MODE_DP_MST: u32 = 0b011 << 24;
pub const TRANS_DDI_FUNC_CTL_BPC_8: u32 = 0b000 << 20;
pub const TRANS_DDI_FUNC_CTL_BPC_10: u32 = 0b001 << 20;
pub const TRANS_DDI_FUNC_CTL_BPC_6: u32 = 0b010 << 20;
pub const TRANS_DDI_FUNC_CTL_BPC_12: u32 = 0b011 << 20;
pub const TRANS_DDI_FUNC_CTL_SYNC_POLARITY_HSHIGH: u32 = 0b01 << 16;
pub const TRANS_DDI_FUNC_CTL_SYNC_POLARITY_VSHIGH: u32 = 0b10 << 16;
pub const TRANS_DDI_FUNC_CTL_DSI_INPUT_PIPE_SHIFT: u32 = 12;
pub const TRANS_DDI_FUNC_CTL_PORT_WIDTH_1: u32 = 0b000 << 1;
pub const TRANS_DDI_FUNC_CTL_PORT_WIDTH_2: u32 = 0b001 << 1;
pub const TRANS_DDI_FUNC_CTL_PORT_WIDTH_3: u32 = 0b010 << 1;
pub const TRANS_DDI_FUNC_CTL_PORT_WIDTH_4: u32 = 0b011 << 1;
pub struct Transcoder {
pub name: &'static str,
pub index: usize,
pub clk_sel: MmioPtr<u32>,
pub clk_sel_shift: u32,
pub conf: MmioPtr<u32>,
pub ddi_func_ctl: MmioPtr<u32>,
pub ddi_func_ctl_ddi_shift: u32,
pub ddi_func_ctl_hdmi_scrambling: u32,
pub ddi_func_ctl_high_tmds_char_rate: u32,
pub ddi_func_ctl2: Option<MmioPtr<u32>>,
pub hblank: MmioPtr<u32>,
pub hsync: MmioPtr<u32>,
pub htotal: MmioPtr<u32>,
pub msa_misc: MmioPtr<u32>,
pub mult: MmioPtr<u32>,
pub push: Option<MmioPtr<u32>>,
pub space: MmioPtr<u32>,
pub stereo3d_ctl: MmioPtr<u32>,
pub vblank: MmioPtr<u32>,
pub vrr_ctl: Option<MmioPtr<u32>>,
pub vrr_flipline: Option<MmioPtr<u32>>,
pub vrr_status: Option<MmioPtr<u32>>,
pub vrr_status2: Option<MmioPtr<u32>>,
pub vrr_vmax: Option<MmioPtr<u32>>,
pub vrr_vmaxshift: Option<MmioPtr<u32>>,
pub vrr_vmin: Option<MmioPtr<u32>>,
pub vrr_vtotal_prev: Option<MmioPtr<u32>>,
pub vsync: MmioPtr<u32>,
pub vsyncshift: MmioPtr<u32>,
pub vtotal: MmioPtr<u32>,
}
impl Transcoder {
pub fn dump(&self) {
eprint!("Transcoder {} {}", self.name, self.index);
eprint!(" clk_sel {:08X}", self.clk_sel.read());
eprint!(" conf {:08X}", self.conf.read());
eprint!(" ddi_func_ctl {:08X}", self.ddi_func_ctl.read());
if let Some(reg) = &self.ddi_func_ctl2 {
eprint!(" ddi_func_ctl2 {:08X}", reg.read());
}
eprint!(" hblank {:08X}", self.hblank.read());
eprint!(" hsync {:08X}", self.hsync.read());
eprint!(" htotal {:08X}", self.htotal.read());
eprint!(" msa_misc {:08X}", self.msa_misc.read());
eprint!(" mult {:08X}", self.mult.read());
if let Some(reg) = &self.push {
eprint!(" push {:08X}", reg.read());
}
eprint!(" space {:08X}", self.space.read());
eprint!(" stereo3d_ctl {:08X}", self.stereo3d_ctl.read());
eprint!(" vblank {:08X}", self.vblank.read());
if let Some(reg) = &self.vrr_ctl {
eprint!(" vrr_ctl {:08X}", reg.read());
}
if let Some(reg) = &self.vrr_flipline {
eprint!(" vrr_flipline {:08X}", reg.read());
}
if let Some(reg) = &self.vrr_status {
eprint!(" vrr_status {:08X}", reg.read());
}
if let Some(reg) = &self.vrr_status2 {
eprint!(" vrr_status2 {:08X}", reg.read());
}
if let Some(reg) = &self.vrr_vmax {
eprint!(" vrr_vmax {:08X}", reg.read());
}
if let Some(reg) = &self.vrr_vmaxshift {
eprint!(" vrr_vmaxshift {:08X}", reg.read());
}
if let Some(reg) = &self.vrr_vmin {
eprint!(" vrr_vmin {:08X}", reg.read());
}
if let Some(reg) = &self.vrr_vtotal_prev {
eprint!(" vrr_vtotal_prev {:08X}", reg.read());
}
eprint!(" vsync {:08X}", self.vsync.read());
eprint!(" vsyncshift {:08X}", self.vsyncshift.read());
eprint!(" vtotal {:08X}", self.vtotal.read());
eprintln!();
}
pub fn modeset(&mut self, pipe: &mut Pipe, timing: &edid::DetailedTiming) {
let hactive = (timing.horizontal_active_pixels as u32) - 1;
let htotal = hactive + (timing.horizontal_blanking_pixels as u32);
let hsync_start = hactive + (timing.horizontal_front_porch as u32);
let hsync_end = hsync_start + (timing.horizontal_sync_width as u32);
let vactive = (timing.vertical_active_lines as u32) - 1;
let vtotal = vactive + (timing.vertical_blanking_lines as u32);
let vsync_start = vactive + (timing.vertical_front_porch as u32);
let vsync_end = vsync_start + (timing.vertical_sync_width as u32);
// Configure horizontal sync
self.htotal.write(hactive | (htotal << 16));
self.hblank.write(hactive | (htotal << 16));
self.hsync.write(hsync_start | (hsync_end << 16));
// Configure vertical sync
self.vtotal.write(vactive | (vtotal << 16));
self.vblank.write(vactive | (vtotal << 16));
self.vsync.write(vsync_start | (vsync_end << 16));
// Configure pipe
pipe.srcsz.write(vactive | (hactive << 16));
}
pub fn kabylake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
let mut transcoders = Vec::with_capacity(4);
//TODO: Transcoder EDP
for (i, name) in ["A", "B", "C"].iter().enumerate() {
transcoders.push(Transcoder {
name,
index: i,
// IHD-OS-KBL-Vol 2c-1.17 TRANS_CLK_SEL
clk_sel: unsafe { gttmm.mmio(0x46140 + i * 0x4)? },
clk_sel_shift: 29,
// IHD-OS-KBL-Vol 2c-1.17 TRANS_CONF
conf: unsafe { gttmm.mmio(0x70008 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_DDI_FUNC_CTL
ddi_func_ctl: unsafe { gttmm.mmio(0x60400 + i * 0x1000)? },
ddi_func_ctl_ddi_shift: 28,
// HDMI scrambling not supported on Kaby Lake
ddi_func_ctl_hdmi_scrambling: 0,
ddi_func_ctl_high_tmds_char_rate: 0,
// N/A
ddi_func_ctl2: None,
// IHD-OS-KBL-Vol 2c-1.17 TRANS_HBLANK
hblank: unsafe { gttmm.mmio(0x60004 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_HSYNC
hsync: unsafe { gttmm.mmio(0x60008 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_HTOTAL
htotal: unsafe { gttmm.mmio(0x60000 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_MSA_MISC
msa_misc: unsafe { gttmm.mmio(0x60410 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_MULT
mult: unsafe { gttmm.mmio(0x6002C + i * 0x1000)? },
// N/A
push: None,
// IHD-OS-KBL-Vol 2c-1.17 TRANS_SPACE
space: unsafe { gttmm.mmio(0x60020 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_STEREO3D_CTL
stereo3d_ctl: unsafe { gttmm.mmio(0x70020 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_VBLANK
vblank: unsafe { gttmm.mmio(0x60010 + i * 0x1000)? },
// N/A
vrr_ctl: None,
vrr_flipline: None,
vrr_status: None,
vrr_status2: None,
vrr_vmax: None,
vrr_vmaxshift: None,
vrr_vmin: None,
vrr_vtotal_prev: None,
// IHD-OS-KBL-Vol 2c-1.17 TRANS_VSYNC
vsync: unsafe { gttmm.mmio(0x60014 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_VSYNCSHIFT
vsyncshift: unsafe { gttmm.mmio(0x60028 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 TRANS_VTOTAL
vtotal: unsafe { gttmm.mmio(0x6000C + i * 0x1000)? },
});
}
Ok(transcoders)
}
pub fn tigerlake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
let mut transcoders = Vec::with_capacity(4);
for (i, name) in ["A", "B", "C", "D"].iter().enumerate() {
transcoders.push(Transcoder {
name,
index: i,
// IHD-OS-TGL-Vol 2c-12.21 TRANS_CLK_SEL
clk_sel: unsafe { gttmm.mmio(0x46140 + i * 0x4)? },
clk_sel_shift: 28,
// IHD-OS-TGL-Vol 2c-12.21 TRANS_CONF
conf: unsafe { gttmm.mmio(0x70008 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_DDI_FUNC_CTL
ddi_func_ctl: unsafe { gttmm.mmio(0x60400 + i * 0x1000)? },
ddi_func_ctl_ddi_shift: 27,
ddi_func_ctl_hdmi_scrambling: 1 << 0,
ddi_func_ctl_high_tmds_char_rate: 1 << 4,
// IHD-OS-TGL-Vol 2c-12.21 TRANS_DDI_FUNC_CTL2
ddi_func_ctl2: Some(unsafe { gttmm.mmio(0x60404 + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_HBLANK
hblank: unsafe { gttmm.mmio(0x60004 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_HSYNC
hsync: unsafe { gttmm.mmio(0x60008 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_HTOTAL
htotal: unsafe { gttmm.mmio(0x60000 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_MSA_MISC
msa_misc: unsafe { gttmm.mmio(0x60410 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_MULT
mult: unsafe { gttmm.mmio(0x6002C + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_PUSH
push: Some(unsafe { gttmm.mmio(0x60A70 + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_SPACE
space: unsafe { gttmm.mmio(0x60020 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_STEREO3D_CTL
stereo3d_ctl: unsafe { gttmm.mmio(0x70020 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VBLANK
vblank: unsafe { gttmm.mmio(0x60010 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_CTL
vrr_ctl: Some(unsafe { gttmm.mmio(0x60420 + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_FLIPLINE
vrr_flipline: Some(unsafe { gttmm.mmio(0x60438 + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_STATUS
vrr_status: Some(unsafe { gttmm.mmio(0x6042C + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_STATUS2
vrr_status2: Some(unsafe { gttmm.mmio(0x6043C + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_VMAX
vrr_vmax: Some(unsafe { gttmm.mmio(0x60424 + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_VMAXSHIFT
vrr_vmaxshift: Some(unsafe { gttmm.mmio(0x60428 + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_VMIN
vrr_vmin: Some(unsafe { gttmm.mmio(0x60434 + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_VTOTAL_PREV
vrr_vtotal_prev: Some(unsafe { gttmm.mmio(0x60480 + i * 0x1000)? }),
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VSYNC
vsync: unsafe { gttmm.mmio(0x60014 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VSYNCSHIFT
vsyncshift: unsafe { gttmm.mmio(0x60028 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VTOTAL
vtotal: unsafe { gttmm.mmio(0x6000C + i * 0x1000)? },
})
}
Ok(transcoders)
}
}
@@ -0,0 +1,105 @@
use driver_graphics::GraphicsScheme;
use event::{user_data, EventQueue};
use pcid_interface::{irq_helpers::pci_allocate_interrupt_vector, PciFunctionHandle};
use std::{
io::{Read, Write},
os::fd::AsRawFd,
};
mod device;
use self::device::Device;
fn main() {
pcid_interface::pci_daemon(daemon);
}
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let pci_config = pcid_handle.config();
let mut name = pci_config.func.name();
name.push_str("_ihdg");
common::setup_logging(
"graphics",
"pci",
&name,
common::output_level(),
common::file_level(),
);
log::info!("IHDG {}", pci_config.func.display());
let device = Device::new(&mut pcid_handle, &pci_config.func)
.expect("ihdgd: failed to initialize device");
let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd");
// Needs to be before GraphicsScheme::new to avoid a deadlock due to initnsmgr blocking on
// /scheme/event as it is already blocked on opening /scheme/display.ihdg.*.
// FIXME change the initnsmgr to not block on openat for the target scheme.
let event_queue: EventQueue<Source> =
EventQueue::new().expect("ihdgd: failed to create event queue");
let mut scheme = GraphicsScheme::new(device, format!("display.ihdg.{}", name), false);
user_data! {
enum Source {
Input,
Irq,
Scheme,
}
}
event_queue
.subscribe(
scheme.inputd_event_handle().as_raw_fd() as usize,
Source::Input,
event::EventFlags::READ,
)
.unwrap();
event_queue
.subscribe(
irq_file.irq_handle().as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
.unwrap();
event_queue
.subscribe(
scheme.event_handle().raw(),
Source::Scheme,
event::EventFlags::READ,
)
.unwrap();
libredox::call::setrens(0, 0).expect("ihdgd: failed to enter null namespace");
daemon.ready();
let all = [Source::Input, Source::Irq, Source::Scheme];
for event in all
.into_iter()
.chain(event_queue.map(|e| e.expect("ihdgd: failed to get next event").user_data))
{
match event {
Source::Input => scheme.handle_vt_events(),
Source::Irq => {
let mut irq = [0; 8];
irq_file.irq_handle().read(&mut irq).unwrap();
if scheme.adapter_mut().handle_irq() {
irq_file.irq_handle().write(&mut irq).unwrap();
scheme.adapter_mut().handle_events();
scheme.tick().unwrap();
}
}
Source::Scheme => {
scheme
.tick()
.expect("ihdgd: failed to handle scheme events");
}
}
}
std::process::exit(0);
}