diff --git a/Cargo.lock b/Cargo.lock index e1c8cb2f68..4722e016e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ - "memchr", + "memchr 2.7.6", ] [[package]] @@ -156,7 +156,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -167,7 +167,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -278,6 +278,16 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" +[[package]] +name = "bitbang-hal" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a6e78d48a099db1643b06824ea903b72c39b38ebc0e98ba5a6693d35f360a28" +dependencies = [ + "embedded-hal", + "nb 1.1.0", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -669,6 +679,25 @@ dependencies = [ "redox_syscall 0.6.0", ] +[[package]] +name = "edid" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ce75530893d834dcfe3bb67ce0e7dec489484e7cb4423ca31618af4bab24fe" +dependencies = [ + "nom", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + [[package]] name = "env_logger" version = "0.8.4" @@ -695,7 +724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -855,7 +884,7 @@ dependencies = [ "futures-macro", "futures-sink", "futures-task", - "memchr", + "memchr 2.7.6", "pin-project-lite", "pin-utils", "slab", @@ -1048,6 +1077,29 @@ dependencies = [ "spin 0.9.8", ] +[[package]] +name = "ihdgd" +version = "0.1.0" +dependencies = [ + "bitbang-hal", + "common", + "daemon", + "driver-graphics", + "edid", + "embedded-hal", + "graphics-ipc", + "inputd", + "libredox", + "log", + "nb 1.1.0", + "pcid", + "range-alloc", + "redox-scheme", + "redox_event", + "redox_syscall 0.6.0", + "void", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -1224,12 +1276,45 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nom" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" +dependencies = [ + "memchr 1.0.2", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -1631,6 +1716,11 @@ dependencies = [ "sha2", ] +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "git+https://github.com/jackpot51/range-alloc.git#23493c401729c9617355804d4ef6bd0ffafb0465" + [[package]] name = "ransid" version = "0.4.9" @@ -1803,7 +1893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", - "memchr", + "memchr 2.7.6", "regex-automata", "regex-syntax", ] @@ -1815,7 +1905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", - "memchr", + "memchr 2.7.6", "regex-syntax", ] @@ -1899,7 +1989,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -2035,7 +2125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", - "memchr", + "memchr 2.7.6", "ryu", "serde", "serde_core", @@ -2506,6 +2596,12 @@ dependencies = [ "virtio-core", ] +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "vte" version = "0.3.3" @@ -2597,7 +2693,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -2665,15 +2761,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -2683,77 +2770,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "winnow" version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ - "memchr", + "memchr 2.7.6", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b8893d4243..525c3b4b3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ "drivers/graphics/driver-graphics", "drivers/graphics/fbcond", "drivers/graphics/graphics-ipc", + "drivers/graphics/ihdgd", "drivers/graphics/vesad", "drivers/graphics/virtio-gpud", diff --git a/drivers/common/src/io.rs b/drivers/common/src/io.rs index b79d4cabe8..375949bacf 100644 --- a/drivers/common/src/io.rs +++ b/drivers/common/src/io.rs @@ -4,10 +4,12 @@ use core::{ }; mod mmio; +mod mmio_ptr; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod pio; pub use mmio::*; +pub use mmio_ptr::*; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub use pio::*; @@ -89,9 +91,5 @@ impl WriteOnly { self.inner.write(value) } - #[inline(always)] - /// Calls [Io::writef] - pub fn writef(&mut self, flags: I::Value, value: bool) { - self.inner.writef(flags, value) - } + // writef requires read which is not valid when write-only } diff --git a/drivers/common/src/io/mmio_ptr.rs b/drivers/common/src/io/mmio_ptr.rs new file mode 100644 index 0000000000..5cbe36aa9c --- /dev/null +++ b/drivers/common/src/io/mmio_ptr.rs @@ -0,0 +1,157 @@ +use core::ptr; + +use super::Io; + +/// MMIO using pointer instead of wrapped type +pub struct MmioPtr { + ptr: *mut T, +} + +impl MmioPtr { + //TODO: reads and writes are unsafe, not new. + pub unsafe fn new(ptr: *mut T) -> Self { + Self { ptr } + } + + pub const fn as_ptr(&self) -> *const T { + self.ptr + } + + pub const fn as_mut_ptr(&mut self) -> *mut T { + self.ptr + } +} + +// Generic implementation (WARNING: requires aligned pointers!) +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] +impl Io for MmioPtr +where + T: Copy + + PartialEq + + core::ops::BitAnd + + core::ops::BitOr + + core::ops::Not, +{ + type Value = T; + + fn read(&self) -> T { + unsafe { ptr::read_volatile(self.ptr) } + } + + fn write(&mut self, value: T) { + unsafe { ptr::write_volatile(self.ptr, value) }; + } +} + +// x86 u8 implementation +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl Io for MmioPtr { + type Value = u8; + + fn read(&self) -> Self::Value { + unsafe { + let value: Self::Value; + core::arch::asm!( + "mov {}, [{}]", + out(reg_byte) value, + in(reg) self.ptr + ); + value + } + } + + fn write(&mut self, value: Self::Value) { + unsafe { + core::arch::asm!( + "mov [{}], {}", + in(reg) self.ptr, + in(reg_byte) value, + ); + } + } +} + +// x86 u16 implementation +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl Io for MmioPtr { + type Value = u16; + + fn read(&self) -> Self::Value { + unsafe { + let value: Self::Value; + core::arch::asm!( + "mov {:x}, [{}]", + out(reg) value, + in(reg) self.ptr + ); + value + } + } + + fn write(&mut self, value: Self::Value) { + unsafe { + core::arch::asm!( + "mov [{}], {:x}", + in(reg) self.ptr, + in(reg) value, + ); + } + } +} + +// x86 u32 implementation +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl Io for MmioPtr { + type Value = u32; + + fn read(&self) -> Self::Value { + unsafe { + let value: Self::Value; + core::arch::asm!( + "mov {:e}, [{}]", + out(reg) value, + in(reg) self.ptr + ); + value + } + } + + fn write(&mut self, value: Self::Value) { + unsafe { + core::arch::asm!( + "mov [{}], {:e}", + in(reg) self.ptr, + in(reg) value, + ); + } + } +} + +// x86 u64 implementation (x86_64 only) +#[cfg(target_arch = "x86_64")] +impl Io for MmioPtr { + type Value = u64; + + fn read(&self) -> Self::Value { + unsafe { + let value: Self::Value; + core::arch::asm!( + "mov {:r}, [{}]", + out(reg) value, + in(reg) self.ptr + ); + value + } + } + + fn write(&mut self, value: Self::Value) { + unsafe { + core::arch::asm!( + "mov [{}], {:r}", + in(reg) self.ptr, + in(reg) value, + ); + } + } +} + diff --git a/drivers/graphics/ihdgd/Cargo.toml b/drivers/graphics/ihdgd/Cargo.toml new file mode 100644 index 0000000000..0a04a549d9 --- /dev/null +++ b/drivers/graphics/ihdgd/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ihdgd" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitbang-hal = "0.3" +#TODO: edid is abandoned, fork it an maintain? +edid = "0.3.0" +#TODO: waiting for bitbang-hal to update to embedded-hal 1.0 +embedded-hal = "0.2.7" +log = "0.4" +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" } +graphics-ipc = { path = "../graphics-ipc" } +inputd = { path = "../../inputd" } +pcid = { path = "../../pcid" } + +libredox = "0.1.3" +redox-scheme = "0.8" +redox_event = "0.4.1" +redox_syscall = "0.6" \ No newline at end of file diff --git a/drivers/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml new file mode 100644 index 0000000000..acbb4e7879 --- /dev/null +++ b/drivers/graphics/ihdgd/config.toml @@ -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"] diff --git a/drivers/graphics/ihdgd/src/device/aux.rs b/drivers/graphics/ihdgd/src/device/aux.rs new file mode 100644 index 0000000000..7629fd0da5 --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/aux.rs @@ -0,0 +1,166 @@ +use common::{io::Io, timeout::Timeout}; +use embedded_hal::blocking::i2c::{self, SevenBitAddress, Operation, 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), + ]) + } +} \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/device/ddi.rs b/drivers/graphics/ihdgd/src/device/ddi.rs new file mode 100644 index 0000000000..db21cd5d0f --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/ddi.rs @@ -0,0 +1,681 @@ +use common::io::{Io, MmioPtr, WriteOnly}; +use std::sync::Arc; +use syscall::error::{Error, Result, EIO}; + +use super::{DeviceKind, 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, + pub port_base: Option, + pub aux_ctl: MmioPtr, + pub aux_datas: [MmioPtr; 5], + pub buf_ctl: MmioPtr, + pub dpclka_cfgcr0_clock_shift: Option, + pub dpclka_cfgcr0_clock_off: Option, + pub gmbus_pin_pair: Option, + pub gpio_port: Option, + 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, + pub transcoder_index: Option, +} + +//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> { + //TODO: handle gttmm.mmio error? + unsafe { self.gttmm.mmio(self.port_base? + offset).ok() } + } + + pub fn port_cl(&self, reg: PortClReg) -> Option> { + self.port_reg(reg as usize) + } + + pub fn port_comp(&self, reg: PortCompReg) -> Option> { + self.port_reg(reg as usize) + } + + //TODO: return WriteOnly if PortLane::Grp? + pub fn port_pcs(&self, reg: PortPcsReg, lane: PortLane) -> Option> { + 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> { + self.port_reg((reg as usize) + (lane as usize)) + } + + 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 mut 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 mut 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) -> Result> { + 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) -> Result> { + 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) -> Result> { + 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) + } +} \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/device/dpll.rs b/drivers/graphics/ihdgd/src/device/dpll.rs new file mode 100644 index 0000000000..516f550d8f --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/dpll.rs @@ -0,0 +1,200 @@ + +use common::io::{Io, MmioPtr}; +use syscall::error::{Error, Result, EIO}; + +use super::{DeviceKind, 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, + // IHD-OS-TGL-Vol 2c-12.21 DPLL_CFGCR1 + pub cfgcr1: MmioPtr, + // IHD-OS-TGL-Vol 2c-12.21 DPLL_DIV0 + pub div0: MmioPtr, + // 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, + // IHD-OS-TGL-Vol 2c-12.21 DPLL_SSC + pub ssc: MmioPtr, +} + +//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 = 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> { + 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) + } +} \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/device/gmbus.rs b/drivers/graphics/ihdgd/src/device/gmbus.rs new file mode 100644 index 0000000000..3161aceaac --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/gmbus.rs @@ -0,0 +1,144 @@ +use common::{io::{Io, MmioPtr}, timeout::Timeout}; +use embedded_hal::blocking::i2c::{self, SevenBitAddress, Operation, 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; 6], +} + +impl Gmbus { + pub unsafe fn new(gttmm: &MmioRegion) -> syscall::Result { + 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; 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), + ]) + } +} diff --git a/drivers/graphics/ihdgd/src/device/gpio.rs b/drivers/graphics/ihdgd/src/device/gpio.rs new file mode 100644 index 0000000000..e818b5fae2 --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/gpio.rs @@ -0,0 +1,81 @@ +use common::io::{Io, MmioPtr}; +use embedded_hal::{ + digital, + blocking::i2c::{self, SevenBitAddress, Operation}, +}; + +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 clock(&self, gttmm: &MmioRegion) -> syscall::Result { + Ok(GpioPin { + ctl: gttmm.mmio(*self as usize)?, + shift: GPIO_CLOCK_SHIFT, + }) + } + + pub unsafe fn data(&self, gttmm: &MmioRegion) -> syscall::Result { + Ok(GpioPin { + ctl: gttmm.mmio(*self as usize)?, + shift: GPIO_DATA_SHIFT, + }) + } +} + +pub struct GpioPin { + ctl: MmioPtr, + shift: u32, +} + +impl digital::InputPin for GpioPin { + fn is_high(&self) -> bool { + ((self.ctl.read() >> self.shift) & GPIO_VAL_IN) == GPIO_VAL_IN + } + + fn is_low(&self) -> bool { + ((self.ctl.read() >> self.shift) & GPIO_VAL_IN) == 0 + } +} + +impl digital::OutputPin for GpioPin { + fn set_low(&mut self) { + // Set GPIO to output with value 0 + let value = GPIO_DIR_MASK | GPIO_DIR_OUT | GPIO_VAL_MASK; + self.ctl.write(value << self.shift); + } + + fn set_high(&mut self) { + // Assuming external pull-up, set GPIO to input + let value = GPIO_DIR_MASK; + self.ctl.write(value << self.shift); + } +} \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/device/hal/mod.rs b/drivers/graphics/ihdgd/src/device/hal/mod.rs new file mode 100644 index 0000000000..d67ca64059 --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/hal/mod.rs @@ -0,0 +1,2 @@ +mod timer; +pub use self::timer::*; \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/device/hal/timer.rs b/drivers/graphics/ihdgd/src/device/hal/timer.rs new file mode 100644 index 0000000000..f860ca12a3 --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/hal/timer.rs @@ -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>(&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 {} \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/device/mod.rs b/drivers/graphics/ihdgd/src/device/mod.rs new file mode 100644 index 0000000000..879d0e41cd --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/mod.rs @@ -0,0 +1,1089 @@ +use common::{io::{Io, MmioPtr}, timeout::Timeout}; +use embedded_hal::prelude::*; +use pcid_interface::PciFunction; +use range_alloc::RangeAllocator; +use std::{collections::VecDeque, mem, ptr, sync::Arc, time::Duration}; +use syscall::error::{Error, Result, EIO, ENODEV, ERANGE}; + +mod aux; +use self::aux::*; +mod ddi; +use self::ddi::*; +mod dpll; +use self::dpll::*; +mod gmbus; +pub use self::gmbus::*; +mod gpio; +pub use self::gpio::*; +mod hal; +pub use self::hal::*; +mod pipe; +use self::pipe::*; +mod power; +use self::power::*; +mod scheme; +use self::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, +} + +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 { + 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, + value: u32, +} + +impl ChangeDetect { + fn new(name: &'static str, reg: MmioPtr) -> 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, + // Interrupt mask register, masks isr for iir, 0 is unmasked + pub imr: MmioPtr, + // Interrupt identity register, write 1 to clear + pub iir: MmioPtr, + // Interrupt enable register, 1 allows interrupt to propogate + pub ier: MmioPtr, +} + +pub struct Interrupter { + change_detects: Vec, + display_int_ctl: MmioPtr, + display_int_ctl_enable: u32, + display_int_ctl_sde: u32, + gfx_mstr_intr: Option>, + 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 { + 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> { + // Any errors here will return ERANGE + let err = Error::new(ERANGE); + if offset.checked_add(mem::size_of::()).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, + alloc_surfaces: RangeAllocator, + ddis: Vec, + dpclka_cfgcr0: Option>, + dplls: Vec, + events: VecDeque, + framebuffers: Vec, + int: Interrupter, + gttmm: Arc, + gm: MmioRegion, + gmbus: Gmbus, + pipes: Vec, + power_wells: PowerWells, + ref_freq: u64, + transcoders: Vec, +} + +impl Device { + pub fn new(func: &PciFunction) -> Result { + 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); + */ + + // GMBUS seems to be stable for all generations + let gmbus = unsafe { Gmbus::new(>tmm)? }; + + 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(>tmm)?; + //TODO: kaby lake dplls + dplls = Vec::new(); + pipes = Pipe::kabylake(>tmm)?; + power_wells = PowerWells::kabylake(>tmm)?; + transcoders = Transcoder::kabylake(>tmm)?; + }, + DeviceKind::TigerLake => { + ddis = Ddi::tigerlake(>tmm)?; + dplls = Dpll::tigerlake(>tmm)?; + pipes = Pipe::tigerlake(>tmm)?; + power_wells = PowerWells::tigerlake(>tmm)?; + transcoders = Transcoder::tigerlake(>tmm)?; + }, + DeviceKind::Alchemist => { + // Many registers are identical to tigerlake + dplls = Dpll::tigerlake(>tmm)?; + pipes = Pipe::tigerlake(>tmm)?; + transcoders = Transcoder::tigerlake(>tmm)?; + // Power wells are distinct + ddis = Ddi::alchemist(>tmm)?; + power_wells = PowerWells::alchemist(>tmm)?; + } + } + + + let mut this = Self { + kind, + alloc_buffers: RangeAllocator::new(0..1024), //TODO: get number of available buffers + alloc_surfaces: RangeAllocator::new(0..gm.size as u32), + ddis, + dpclka_cfgcr0, + dplls, + events: VecDeque::new(), + framebuffers: Vec::new(), + int, + gttmm, + gm, + gmbus, + pipes, + power_wells, + ref_freq, + transcoders, + }; + this.init()?; + Ok(this) + } + + pub fn init(&mut self) -> Result<()> { + // Discover current framebuffers + self.alloc_buffers.reset(); + self.alloc_surfaces.reset(); + self.framebuffers.clear(); + for pipe in self.pipes.iter() { + for plane in pipe.planes.iter() { + if plane.ctl.readf(PLANE_CTL_ENABLE) { + let buf_cfg = plane.buf_cfg.read(); + let buffer_start = buf_cfg & 0x7FF; + let buffer_end = (buf_cfg >> 16) & 0x7FF; + self.alloc_buffers.allocate_exact_range(buffer_start .. (buffer_end + 1)).map_err(|err| { + log::warn!("failed to allocate pre-existing buffer blocks {} to {}: {:?}", buffer_start, buffer_end, err); + Error::new(EIO) + })?; + + let size = plane.size.read(); + let width = (size & 0xFFFF) + 1; + let height = ((size >> 16) & 0xFFFF) + 1; + let stride_16 = plane.stride.read() & 0x7FF; + //TODO: this will be wrong for tiled planes + let stride = stride_16 * 16; + let surf = plane.surf.read() & 0xFFFFF000; + //TODO: read bits per pixel + let surf_size = (stride * height * 4).next_multiple_of(4096); + self.alloc_surfaces.allocate_exact_range(surf .. (surf + surf_size)).map_err(|err| { + log::warn!("failed to allocate pre-existing surface at 0x{:x} of size {}: {:?}", surf, surf_size, err); + Error::new(EIO) + })?; + + self.framebuffers.push(unsafe { + DeviceFb::new( + (self.gm.virt + surf as usize) as *mut u32, + width as usize, + height as usize, + stride as usize, + false + ) + }); + } + } + } + + // 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)?; + } + + 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(); + } + + Ok(()) + } + + 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 { + 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)?; + + //TODO: init port if needed + if let Some(port_comp_dw0) = ddi.port_comp(PortCompReg::Dw0) { + log::debug!("PORT_COMP_DW0_{}: {:08X}", ddi.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(self.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]; + self.gmbus.pin_pair(pin_pair).write_read( + 0x50, + &[0x00], + &mut edid_data + ).map_err(|_err| { + Error::new(EIO) + })?; + + Ok(edid_data) + }; + + let mut 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]; + let i2c_freq = 100_000.0; + bitbang_hal::i2c::I2cBB::new( + unsafe { port.clock(&self.gttmm)? }, + unsafe { port.data(&self.gttmm)? }, + HalTimer::new(Duration::from_secs_f64(1.0 / i2c_freq)) + ).write_read( + 0x50, + &[0x00], + &mut edid_data + ).map_err(|_err| { + Error::new(EIO) + })?; + + Ok(edid_data) + }; + + let (source, edid_data) = match aux_read_edid(ddi) { + Ok(edid_data) => ("AUX", edid_data), + Err(err) => { + log::debug!("DDI {} failed to read EDID from AUX: {}", ddi.name, err); + match gmbus_read_edid(ddi) { + Ok(edid_data) => ("GMBUS", edid_data), + Err(err) => { + log::debug!("DDI {} failed to read EDID from GMBUS: {}", ddi.name, err); + match gpio_read_edid(ddi) { + Ok(edid_data) => ("GPIO", edid_data), + Err(err) => { + log::debug!("DDI {} failed to read EDID from GPIO: {}", ddi.name, err); + // Will try again but not fail the driver + 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 mut timing_opt = None; + for desc in edid.descriptors.iter() { + match desc { + edid::Descriptor::DetailedTiming(timing) => { + timing_opt = Some(timing); + break; + } + _ => {} + } + } + 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() { + //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 = self.alloc_buffers.allocate_range(buffer_size).map_err(|err| { + log::warn!("failed to allocate {} buffer blocks: {:?}", buffer_size, err); + Error::new(EIO) + })?; + plane.buf_cfg.write(buffer.start | (buffer.end << 16)); + + let width = timing.horizontal_active_pixels as u32; + let height = timing.vertical_active_lines as u32; + plane.size.write((width - 1) | ((height - 1) << 16)); + + //TODO: documentation on this is not great + let stride_16 = (width + 15) / 16; + plane.stride.write(stride_16); + let stride = stride_16 * 16; + + //TODO: how is memory allocated for PLANE_SURF? + let surf_size = (stride * height * 4).next_multiple_of(4096); + let surf = self.alloc_surfaces.allocate_range(surf_size).map_err(|err| { + log::warn!("failed to allocate surface of size {}: {:?}", surf_size, err); + Error::new(EIO) + })?; + plane.surf.write(surf.start); + + //TODO: correct watermark calculation + plane.wm[0].write( + PLANE_WM_ENABLE | + (2 << PLANE_WM_LINES_SHIFT) | + buffer_size + ); + for i in 1..plane.wm.len() { + plane.wm[i].writef(PLANE_WM_ENABLE, false); + } + plane.wm_trans.writef(PLANE_WM_ENABLE, false); + + self.framebuffers.push(unsafe { + DeviceFb::new( + (self.gm.virt + surf.start as usize) as *mut u32, + width as usize, + height as usize, + stride as usize, + true + ) + }); + + // Disable gamma + if let Some(color_ctl) = &mut plane.color_ctl { + color_ctl.write(plane.color_ctl_gamma_disable); + } + + //TODO: more PLANE_CTL bits + plane.ctl.write( + PLANE_CTL_ENABLE | + plane.ctl_source_rgb_8888 + ); + } + + //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)); + } + } + } + } + } +} diff --git a/drivers/graphics/ihdgd/src/device/pipe.rs b/drivers/graphics/ihdgd/src/device/pipe.rs new file mode 100644 index 0000000000..ea38d9743a --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/pipe.rs @@ -0,0 +1,181 @@ +use common::io::{Io, MmioPtr}; +use syscall::error::Result; + +use super::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; + +pub struct Plane { + pub name: &'static str, + pub index: usize, + pub buf_cfg: MmioPtr, + pub color_ctl: Option>, + pub color_ctl_gamma_disable: u32, + pub ctl: MmioPtr, + pub ctl_source_rgb_8888: u32, + pub ctl_source_mask: u32, + pub offset: MmioPtr, + pub pos: MmioPtr, + pub size: MmioPtr, + pub stride: MmioPtr, + pub surf: MmioPtr, + pub wm: [MmioPtr; 8], + pub wm_trans: MmioPtr, +} + +impl Plane { + 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, + pub bottom_color: MmioPtr, + pub misc: MmioPtr, + pub srcsz: MmioPtr, +} + +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> { + 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> { + 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) + } +} \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/device/power.rs b/drivers/graphics/ihdgd/src/device/power.rs new file mode 100644 index 0000000000..6a0fde68b4 --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/power.rs @@ -0,0 +1,320 @@ +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, + pub ctl_aux: MmioPtr, + pub ctl_ddi: MmioPtr, + pub fuse_status: MmioPtr, + pub fuse_status_pg0: u32, + pub wells: Vec, +} + +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 { + // 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 { + // 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 { + // 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, + }) + } +} \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/device/scheme.rs b/drivers/graphics/ihdgd/src/device/scheme.rs new file mode 100644 index 0000000000..07cd49ccaa --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/scheme.rs @@ -0,0 +1,177 @@ +//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 driver_graphics::{CursorFramebuffer, CursorPlane, Framebuffer, GraphicsAdapter}; +use graphics_ipc::v1::Damage; +use graphics_ipc::v2::ipc::{DRM_CAP_DUMB_BUFFER, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT}; +use syscall::{error::EINVAL, PAGE_SIZE}; + +use super::Device; + +//TODO: use hardware cursor +pub enum Cursor {} + +impl CursorFramebuffer for Cursor {} + +impl GraphicsAdapter for Device { + type Framebuffer = DumbFb; + type Cursor = Cursor; + + fn name(&self) -> &'static [u8] { + b"ihdgd" + } + + fn desc(&self) -> &'static [u8] { + b"Intel HD Graphics" + } + + fn get_cap(&self, cap: u32) -> syscall::Result { + match cap { + DRM_CAP_DUMB_BUFFER => Ok(1), + _ => 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 display_count(&self) -> usize { + self.framebuffers.len() + } + + fn display_size(&self, display_id: usize) -> (u32, u32) { + ( + self.framebuffers[display_id].width as u32, + self.framebuffers[display_id].height as u32, + ) + } + + fn create_dumb_framebuffer(&mut self, width: u32, height: u32) -> Self::Framebuffer { + DumbFb::new(width as usize, height as usize) + } + + fn map_dumb_framebuffer(&mut self, framebuffer: &Self::Framebuffer) -> *mut u8 { + framebuffer.ptr.as_ptr().cast::() + } + + fn update_plane(&mut self, display_id: usize, framebuffer: &Self::Framebuffer, damage: Damage) { + framebuffer.sync(&mut self.framebuffers[display_id], damage) + } + + fn supports_hw_cursor(&self) -> bool { + false + } + + fn create_cursor_framebuffer(&mut self) -> Self::Cursor { + unimplemented!("ihdgd does not support this function"); + } + + fn map_cursor_framebuffer(&mut self, _cursor: &Self::Cursor) -> *mut u8 { + unimplemented!("ihdgd does not support this function"); + } + + fn handle_cursor(&mut self, _cursor: &CursorPlane, _dirty_fb: bool) { + unimplemented!("ihdgd does not support this function"); + } +} + +pub struct DeviceFb { + pub onscreen: *mut [u32], + pub width: usize, + pub height: usize, + pub stride: usize, +} + +impl DeviceFb { + pub unsafe fn new(virt: *mut u32, width: usize, height: usize, stride: usize, clear: bool) -> Self { + let onscreen = ptr::slice_from_raw_parts_mut(virt, stride * height); + if clear { + (&mut *onscreen).fill(0); + } + Self { + onscreen, + width, + height, + stride, + } + } +} + +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::(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 Framebuffer for DumbFb { + fn width(&self) -> u32 { + self.width as u32 + } + + fn height(&self) -> u32 { + self.height as u32 + } +} + +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.onscreen as *mut u32; // FIXME use as_mut_ptr once stable + + 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 + start_x), + w, + ); + } + } + } +} diff --git a/drivers/graphics/ihdgd/src/device/transcoder.rs b/drivers/graphics/ihdgd/src/device/transcoder.rs new file mode 100644 index 0000000000..97cf424750 --- /dev/null +++ b/drivers/graphics/ihdgd/src/device/transcoder.rs @@ -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, + pub clk_sel_shift: u32, + pub conf: MmioPtr, + pub ddi_func_ctl: MmioPtr, + 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>, + pub hblank: MmioPtr, + pub hsync: MmioPtr, + pub htotal: MmioPtr, + pub msa_misc: MmioPtr, + pub mult: MmioPtr, + pub push: Option>, + pub space: MmioPtr, + pub stereo3d_ctl: MmioPtr, + pub vblank: MmioPtr, + pub vrr_ctl: Option>, + pub vrr_flipline: Option>, + pub vrr_status: Option>, + pub vrr_status2: Option>, + pub vrr_vmax: Option>, + pub vrr_vmaxshift: Option>, + pub vrr_vmin: Option>, + pub vrr_vtotal_prev: Option>, + pub vsync: MmioPtr, + pub vsyncshift: MmioPtr, + pub vtotal: MmioPtr, +} + +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> { + 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> { + 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) + } +} \ No newline at end of file diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs new file mode 100644 index 0000000000..11d2f442c5 --- /dev/null +++ b/drivers/graphics/ihdgd/src/main.rs @@ -0,0 +1,108 @@ +use driver_graphics::GraphicsScheme; +use event::{user_data, EventQueue}; +use inputd::DisplayHandle; +use pcid_interface::{PciFunctionHandle, irq_helpers::pci_allocate_interrupt_vector}; +use redox_scheme::{RequestKind, SignalBehavior, Socket}; +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, + log::LevelFilter::Debug, //common::output_level(), + common::file_level(), + ); + + log::info!("IHDG {}", pci_config.func.display()); + + let device = Device::new(&pci_config.func).expect("ihdgd: failed to initialize device"); + + let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd"); + + let mut inputd_display_handle = DisplayHandle::new(format!("ihdg.{}", name)).unwrap(); + + let mut scheme = GraphicsScheme::new(device, format!("display.ihdg.{}", name)); + + user_data! { + enum Source { + Input, + Irq, + Scheme, + } + } + + let event_queue: EventQueue = + EventQueue::new().expect("ihdgd: failed to create event queue"); + event_queue + .subscribe( + inputd_display_handle.inner().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 => { + while let Some(vt_event) = inputd_display_handle + .read_vt_event() + .expect("ihdgd: failed to read display handle") + { + scheme.handle_vt_event(vt_event); + } + } + 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); +} diff --git a/drivers/storage/ided/src/ide.rs b/drivers/storage/ided/src/ide.rs index 0a14bcc99a..c6632e266a 100644 --- a/drivers/storage/ided/src/ide.rs +++ b/drivers/storage/ided/src/ide.rs @@ -229,12 +229,12 @@ impl Disk for AtaDisk { if self.lba_48 { // Set high sector count and LBA - chan.control.writef(0x80, true); + chan.control.write(0x80); chan.sector_count.write((sectors >> 8) as u8); chan.lba_0.write((block >> 24) as u8); chan.lba_1.write((block >> 32) as u8); chan.lba_2.write((block >> 40) as u8); - chan.control.writef(0x80, false); + chan.control.write(0x00); } // Set low sector count and LBA @@ -373,12 +373,12 @@ impl Disk for AtaDisk { if self.lba_48 { // Set high sector count and LBA - chan.control.writef(0x80, true); + chan.control.write(0x80); chan.sector_count.write((sectors >> 8) as u8); chan.lba_0.write((block >> 24) as u8); chan.lba_1.write((block >> 32) as u8); chan.lba_2.write((block >> 40) as u8); - chan.control.writef(0x80, false); + chan.control.write(0x00); } // Set low sector count and LBA