milestone: desktop path Phases 1-5
Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests Phase 2 (Wayland Compositor): bounded scaffold, zero warnings Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick) Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker Build: zero warnings, all scripts syntax-clean. Oracle-verified.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "console-draw"
|
||||
description = "Shared terminal drawing code library"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
drm.workspace = true
|
||||
orbclient.workspace = true
|
||||
ransid.workspace = true
|
||||
|
||||
graphics-ipc = { path = "../graphics-ipc" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,460 @@
|
||||
extern crate ransid;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::{cmp, io, mem, ptr};
|
||||
|
||||
use drm::buffer::{Buffer, DrmFourcc};
|
||||
use drm::control::{connector, crtc, framebuffer, ClipRect, Device, Mode};
|
||||
use graphics_ipc::{CpuBackedBuffer, V2GraphicsHandle};
|
||||
use orbclient::FONT;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Damage {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl Damage {
|
||||
pub const NONE: Self = Damage {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
|
||||
pub fn merge(self, other: Self) -> Self {
|
||||
if self.width == 0 || self.height == 0 {
|
||||
return other;
|
||||
}
|
||||
|
||||
if other.width == 0 || other.height == 0 {
|
||||
return self;
|
||||
}
|
||||
|
||||
let x = cmp::min(self.x, other.x);
|
||||
let y = cmp::min(self.y, other.y);
|
||||
let x2 = cmp::max(self.x + self.width, other.x + other.width);
|
||||
let y2 = cmp::max(self.y + self.height, other.y + other.height);
|
||||
|
||||
Damage {
|
||||
x,
|
||||
y,
|
||||
width: x2 - x,
|
||||
height: y2 - y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct V2DisplayMap {
|
||||
pub display_handle: V2GraphicsHandle,
|
||||
connector: connector::Handle,
|
||||
crtc: crtc::Handle,
|
||||
fb: framebuffer::Handle,
|
||||
pub buffer: CpuBackedBuffer,
|
||||
}
|
||||
|
||||
impl V2DisplayMap {
|
||||
pub fn new(display_handle: V2GraphicsHandle) -> io::Result<Self> {
|
||||
let connector = display_handle.first_display().unwrap();
|
||||
let connector_info = display_handle.get_connector(connector, true).unwrap();
|
||||
|
||||
let mode = connector_info.modes()[0];
|
||||
let (width, height) = mode.size();
|
||||
|
||||
// FIXME do something smarter that avoids conflicts
|
||||
let crtc = display_handle.resource_handles().unwrap().filter_crtcs(
|
||||
display_handle
|
||||
.get_encoder(connector_info.encoders()[0])
|
||||
.unwrap()
|
||||
.possible_crtcs(),
|
||||
)[0];
|
||||
|
||||
let buffer = CpuBackedBuffer::new(
|
||||
&display_handle,
|
||||
(width.into(), height.into()),
|
||||
DrmFourcc::Argb8888,
|
||||
32,
|
||||
)?;
|
||||
let fb = display_handle.add_framebuffer(buffer.buffer(), 32, 32)?;
|
||||
|
||||
display_handle.set_crtc(crtc, Some(fb), (0, 0), &[connector], Some(mode))?;
|
||||
|
||||
Ok(Self {
|
||||
display_handle,
|
||||
connector,
|
||||
crtc,
|
||||
fb,
|
||||
buffer,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn console_map(&mut self) -> DisplayMap {
|
||||
let size = self.buffer.buffer().size();
|
||||
let shadow_buf = self.buffer.shadow_buf();
|
||||
|
||||
DisplayMap {
|
||||
offscreen: ptr::slice_from_raw_parts_mut(
|
||||
shadow_buf.as_mut_ptr() as *mut u32,
|
||||
shadow_buf.len() / 4,
|
||||
),
|
||||
width: size.0 as usize,
|
||||
height: size.1 as usize,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dirty_fb(&mut self, damage: Damage) -> io::Result<()> {
|
||||
self.buffer
|
||||
.sync_rect(damage.x, damage.y, damage.width, damage.height);
|
||||
|
||||
self.display_handle.dirty_framebuffer(
|
||||
self.fb,
|
||||
&[ClipRect::new(
|
||||
damage.x as u16,
|
||||
damage.y as u16,
|
||||
(damage.x + damage.width) as u16,
|
||||
(damage.y + damage.height) as u16,
|
||||
)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayMap {
|
||||
offscreen: *mut [u32],
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
pub struct TextScreen {
|
||||
console: ransid::Console,
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn new() -> TextScreen {
|
||||
TextScreen {
|
||||
// Width and height will be filled in on the next write to the console
|
||||
console: ransid::Console::new(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a rectangle
|
||||
fn rect(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize, color: u32) {
|
||||
let start_y = cmp::min(map.height, y);
|
||||
let end_y = cmp::min(map.height, y + h);
|
||||
|
||||
let start_x = cmp::min(map.width, x);
|
||||
let len = cmp::min(map.width, x + w) - start_x;
|
||||
|
||||
let mut offscreen_ptr = map.offscreen as *mut u8 as usize;
|
||||
|
||||
let stride = map.width * 4;
|
||||
|
||||
let offset = y * stride + start_x * 4;
|
||||
offscreen_ptr += offset;
|
||||
|
||||
let mut rows = end_y - start_y;
|
||||
while rows > 0 {
|
||||
for i in 0..len {
|
||||
unsafe {
|
||||
*(offscreen_ptr as *mut u32).add(i) = color;
|
||||
}
|
||||
}
|
||||
offscreen_ptr += stride;
|
||||
rows -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Invert a rectangle
|
||||
fn invert(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize) {
|
||||
let start_y = cmp::min(map.height, y);
|
||||
let end_y = cmp::min(map.height, y + h);
|
||||
|
||||
let start_x = cmp::min(map.width, x);
|
||||
let len = cmp::min(map.width, x + w) - start_x;
|
||||
|
||||
let mut offscreen_ptr = map.offscreen as *mut u8 as usize;
|
||||
|
||||
let stride = map.width * 4;
|
||||
|
||||
let offset = y * stride + start_x * 4;
|
||||
offscreen_ptr += offset;
|
||||
|
||||
let mut rows = end_y - start_y;
|
||||
while rows > 0 {
|
||||
let mut row_ptr = offscreen_ptr;
|
||||
let mut cols = len;
|
||||
while cols > 0 {
|
||||
unsafe {
|
||||
let color = *(row_ptr as *mut u32);
|
||||
*(row_ptr as *mut u32) = !color;
|
||||
}
|
||||
row_ptr += 4;
|
||||
cols -= 1;
|
||||
}
|
||||
offscreen_ptr += stride;
|
||||
rows -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a character
|
||||
fn char(
|
||||
map: &mut DisplayMap,
|
||||
x: usize,
|
||||
y: usize,
|
||||
character: char,
|
||||
color: u32,
|
||||
_bold: bool,
|
||||
_italic: bool,
|
||||
) {
|
||||
if x + 8 <= map.width && y + 16 <= map.height {
|
||||
let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4;
|
||||
|
||||
let font_i = 16 * (character as usize);
|
||||
if font_i + 16 <= FONT.len() {
|
||||
for row in 0..16 {
|
||||
let row_data = FONT[font_i + row];
|
||||
for col in 0..8 {
|
||||
if (row_data >> (7 - col)) & 1 == 1 {
|
||||
unsafe {
|
||||
*((dst + col * 4) as *mut u32) = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
dst += map.width * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn write(
|
||||
&mut self,
|
||||
map: &mut V2DisplayMap,
|
||||
buf: &[u8],
|
||||
input: &mut VecDeque<u8>,
|
||||
) -> Damage {
|
||||
let map = unsafe { &mut map.console_map() };
|
||||
|
||||
let mut min_changed = map.height;
|
||||
let mut max_changed = 0;
|
||||
let mut line_changed = |line| {
|
||||
if line < min_changed {
|
||||
min_changed = line;
|
||||
}
|
||||
if line > max_changed {
|
||||
max_changed = line;
|
||||
}
|
||||
};
|
||||
|
||||
self.console.resize(map.width / 8, map.height / 16);
|
||||
if self.console.state.x >= self.console.state.w {
|
||||
self.console.state.x = self.console.state.w - 1;
|
||||
}
|
||||
if self.console.state.y >= self.console.state.h {
|
||||
self.console.state.y = self.console.state.h - 1;
|
||||
}
|
||||
|
||||
if self.console.state.cursor
|
||||
&& self.console.state.x < self.console.state.w
|
||||
&& self.console.state.y < self.console.state.h
|
||||
{
|
||||
let x = self.console.state.x;
|
||||
let y = self.console.state.y;
|
||||
Self::invert(map, x * 8, y * 16, 8, 16);
|
||||
line_changed(y);
|
||||
}
|
||||
|
||||
self.console.write(buf, |event| match event {
|
||||
ransid::Event::Char {
|
||||
x,
|
||||
y,
|
||||
c,
|
||||
color,
|
||||
bold,
|
||||
..
|
||||
} => {
|
||||
Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false);
|
||||
line_changed(y);
|
||||
}
|
||||
ransid::Event::Input { data } => input.extend(data),
|
||||
ransid::Event::Rect { x, y, w, h, color } => {
|
||||
Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb());
|
||||
for y2 in y..y + h {
|
||||
line_changed(y2);
|
||||
}
|
||||
}
|
||||
ransid::Event::ScreenBuffer { .. } => (),
|
||||
ransid::Event::Move {
|
||||
from_x,
|
||||
from_y,
|
||||
to_x,
|
||||
to_y,
|
||||
w,
|
||||
h,
|
||||
} => {
|
||||
let width = map.width;
|
||||
let pixels = unsafe { &mut *map.offscreen };
|
||||
|
||||
for raw_y in 0..h {
|
||||
let y = if from_y > to_y { raw_y } else { h - raw_y - 1 };
|
||||
|
||||
for pixel_y in 0..16 {
|
||||
{
|
||||
let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8;
|
||||
let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8;
|
||||
let len = w * 8;
|
||||
|
||||
if off_from + len <= pixels.len() && off_to + len <= pixels.len() {
|
||||
unsafe {
|
||||
let data_ptr = pixels.as_mut_ptr() as *mut u32;
|
||||
ptr::copy(
|
||||
data_ptr.offset(off_from as isize),
|
||||
data_ptr.offset(off_to as isize),
|
||||
len,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line_changed(to_y + y);
|
||||
}
|
||||
}
|
||||
ransid::Event::Resize { .. } => (),
|
||||
ransid::Event::Title { .. } => (),
|
||||
});
|
||||
|
||||
if self.console.state.cursor
|
||||
&& self.console.state.x < self.console.state.w
|
||||
&& self.console.state.y < self.console.state.h
|
||||
{
|
||||
let x = self.console.state.x;
|
||||
let y = self.console.state.y;
|
||||
Self::invert(map, x * 8, y * 16, 8, 16);
|
||||
line_changed(y);
|
||||
}
|
||||
|
||||
let width = map.width.try_into().unwrap();
|
||||
let damage = Damage {
|
||||
x: 0,
|
||||
y: u32::try_from(min_changed).unwrap() * 16,
|
||||
width,
|
||||
height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap() * 16,
|
||||
};
|
||||
|
||||
damage
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, map: &mut V2DisplayMap, mode: Mode) -> io::Result<()> {
|
||||
// FIXME fold row when target is narrower and maybe unfold when it is wider
|
||||
fn copy_row(
|
||||
old_map: &mut DisplayMap,
|
||||
new_map: &mut DisplayMap,
|
||||
from_row: usize,
|
||||
to_row: usize,
|
||||
) {
|
||||
for x in 0..cmp::min(old_map.width, new_map.width) {
|
||||
let old_idx = from_row * old_map.width + x;
|
||||
let new_idx = to_row * new_map.width + x;
|
||||
unsafe {
|
||||
(*new_map.offscreen)[new_idx] = (*old_map.offscreen)[old_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_buffer = CpuBackedBuffer::new(
|
||||
&map.display_handle,
|
||||
(u32::from(mode.size().0), u32::from(mode.size().1)),
|
||||
DrmFourcc::Argb8888,
|
||||
32,
|
||||
)?;
|
||||
let new_fb = map
|
||||
.display_handle
|
||||
.add_framebuffer(new_buffer.buffer(), 24, 32)?;
|
||||
|
||||
new_buffer.shadow_buf().fill(0);
|
||||
|
||||
{
|
||||
let old_map = unsafe { &mut map.console_map() };
|
||||
|
||||
let new_size = new_buffer.buffer().size();
|
||||
let new_shadow_buf = new_buffer.shadow_buf();
|
||||
let new_map = &mut DisplayMap {
|
||||
offscreen: ptr::slice_from_raw_parts_mut(
|
||||
new_shadow_buf.as_mut_ptr() as *mut u32,
|
||||
new_shadow_buf.len() / 4,
|
||||
),
|
||||
width: new_size.0 as usize,
|
||||
height: new_size.1 as usize,
|
||||
};
|
||||
|
||||
if new_map.height >= old_map.height {
|
||||
for row in 0..old_map.height {
|
||||
copy_row(old_map, new_map, row, row);
|
||||
}
|
||||
} else {
|
||||
let deleted_rows = (old_map.height - new_map.height).div_ceil(16);
|
||||
for row in 0..new_map.height {
|
||||
if row + (deleted_rows + 1) * 16 >= old_map.height {
|
||||
break;
|
||||
}
|
||||
copy_row(old_map, new_map, row + deleted_rows * 16, row);
|
||||
}
|
||||
self.console.state.y = self.console.state.y.saturating_sub(deleted_rows);
|
||||
}
|
||||
}
|
||||
|
||||
let old_buffer = mem::replace(&mut map.buffer, new_buffer);
|
||||
old_buffer.destroy(&map.display_handle)?;
|
||||
|
||||
let old_fb = mem::replace(&mut map.fb, new_fb);
|
||||
map.display_handle.set_crtc(
|
||||
map.crtc,
|
||||
Some(map.fb),
|
||||
(0, 0),
|
||||
&[map.connector],
|
||||
Some(mode),
|
||||
)?;
|
||||
let _ = map.display_handle.destroy_framebuffer(old_fb);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextBuffer {
|
||||
pub lines: VecDeque<Vec<u8>>,
|
||||
pub lines_max: usize,
|
||||
}
|
||||
|
||||
impl TextBuffer {
|
||||
pub fn new(max: usize) -> Self {
|
||||
let mut lines = VecDeque::new();
|
||||
lines.push_back(Vec::new());
|
||||
Self {
|
||||
lines,
|
||||
lines_max: max,
|
||||
}
|
||||
}
|
||||
pub fn write(&mut self, buf: &[u8]) {
|
||||
if buf.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
for &byte in buf {
|
||||
self.lines.back_mut().unwrap().push(byte);
|
||||
|
||||
if byte == b'\n' {
|
||||
self.lines.push_back(Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
let max_len = self.lines_max;
|
||||
while self.lines.len() > max_len {
|
||||
self.lines.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "driver-graphics"
|
||||
description = "Shared video and graphics code library"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
drm-fourcc = "2.2.0"
|
||||
drm-sys.workspace = true
|
||||
edid.workspace = true #TODO: edid is abandoned, fork it and maintain?
|
||||
log.workspace = true
|
||||
redox-ioctl.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
redox_syscall.workspace = true
|
||||
libredox.workspace = true
|
||||
|
||||
common = { path = "../../common" }
|
||||
inputd = { path = "../../inputd" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,249 @@
|
||||
use std::ffi::c_char;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use drm_sys::{
|
||||
drm_mode_modeinfo, DRM_MODE_CONNECTOR_Unknown, DRM_MODE_DPMS_OFF, DRM_MODE_DPMS_ON,
|
||||
DRM_MODE_DPMS_STANDBY, DRM_MODE_DPMS_SUSPEND, DRM_MODE_TYPE_PREFERRED,
|
||||
};
|
||||
use syscall::Result;
|
||||
|
||||
use crate::kms::objects::{KmsObjectId, KmsObjects};
|
||||
use crate::kms::properties::{define_object_props, KmsPropertyData, CRTC_ID, DPMS, EDID};
|
||||
use crate::GraphicsAdapter;
|
||||
|
||||
impl<T: GraphicsAdapter> KmsObjects<T> {
|
||||
pub fn add_connector(
|
||||
&mut self,
|
||||
driver_data: T::Connector,
|
||||
driver_data_state: <T::Connector as KmsConnectorDriver>::State,
|
||||
crtcs: &[KmsObjectId],
|
||||
) -> KmsObjectId {
|
||||
let mut possible_crtcs = 0;
|
||||
for &crtc in crtcs {
|
||||
possible_crtcs = 1 << self.get_crtc(crtc).unwrap().lock().unwrap().crtc_index;
|
||||
}
|
||||
|
||||
let encoder_id = self.add(KmsEncoder {
|
||||
crtc_id: KmsObjectId::INVALID,
|
||||
possible_crtcs: possible_crtcs,
|
||||
possible_clones: 1 << self.encoders.len(),
|
||||
});
|
||||
self.encoders.push(encoder_id);
|
||||
|
||||
let connector_id = self.add(Mutex::new(KmsConnector {
|
||||
encoder_id,
|
||||
modes: vec![],
|
||||
connector_type: DRM_MODE_CONNECTOR_Unknown,
|
||||
connector_type_id: self.connectors.len() as u32, // FIXME maybe pick unique id within connector type?
|
||||
connection: KmsConnectorStatus::Unknown,
|
||||
mm_width: 0,
|
||||
mm_height: 0,
|
||||
subpixel: DrmSubpixelOrder::Unknown,
|
||||
properties: KmsConnector::base_properties(),
|
||||
edid: KmsObjectId::INVALID,
|
||||
state: KmsConnectorState {
|
||||
dpms: KmsDpms::On,
|
||||
crtc_id: KmsObjectId::INVALID,
|
||||
driver_data: driver_data_state,
|
||||
},
|
||||
driver_data,
|
||||
}));
|
||||
self.connectors.push(connector_id);
|
||||
|
||||
connector_id
|
||||
}
|
||||
|
||||
pub fn connector_ids(&self) -> &[KmsObjectId] {
|
||||
&self.connectors
|
||||
}
|
||||
|
||||
pub fn connectors(&self) -> impl Iterator<Item = &Mutex<KmsConnector<T>>> + use<'_, T> {
|
||||
self.connectors
|
||||
.iter()
|
||||
.map(|&id| self.get_connector(id).unwrap())
|
||||
}
|
||||
|
||||
pub fn get_connector(&self, id: KmsObjectId) -> Result<&Mutex<KmsConnector<T>>> {
|
||||
self.get(id)
|
||||
}
|
||||
|
||||
pub fn encoder_ids(&self) -> &[KmsObjectId] {
|
||||
&self.encoders
|
||||
}
|
||||
|
||||
pub fn get_encoder(&self, id: KmsObjectId) -> Result<&KmsEncoder> {
|
||||
self.get(id)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait KmsConnectorDriver: Debug {
|
||||
type State: Clone + Debug;
|
||||
}
|
||||
|
||||
impl KmsConnectorDriver for () {
|
||||
type State = ();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsConnector<T: GraphicsAdapter> {
|
||||
pub encoder_id: KmsObjectId,
|
||||
pub modes: Vec<drm_mode_modeinfo>,
|
||||
pub connector_type: u32,
|
||||
pub connector_type_id: u32,
|
||||
pub connection: KmsConnectorStatus,
|
||||
pub mm_width: u32,
|
||||
pub mm_height: u32,
|
||||
pub subpixel: DrmSubpixelOrder,
|
||||
pub properties: Vec<KmsPropertyData<Self>>,
|
||||
pub edid: KmsObjectId,
|
||||
pub state: KmsConnectorState<T>,
|
||||
pub driver_data: T::Connector,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsConnectorState<T: GraphicsAdapter> {
|
||||
pub dpms: KmsDpms,
|
||||
pub crtc_id: KmsObjectId,
|
||||
pub driver_data: <T::Connector as KmsConnectorDriver>::State,
|
||||
}
|
||||
|
||||
impl<T: GraphicsAdapter> Clone for KmsConnectorState<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
dpms: self.dpms.clone(),
|
||||
crtc_id: self.crtc_id.clone(),
|
||||
driver_data: self.driver_data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_object_props!(object, KmsConnector<T: GraphicsAdapter> {
|
||||
EDID {
|
||||
get => u64::from(object.edid.0),
|
||||
}
|
||||
DPMS {
|
||||
get => object.state.dpms as u64,
|
||||
}
|
||||
CRTC_ID {
|
||||
get => u64::from(object.state.crtc_id.0),
|
||||
}
|
||||
});
|
||||
|
||||
impl<T: GraphicsAdapter> KmsConnector<T> {
|
||||
pub fn update_from_size(&mut self, width: u32, height: u32) {
|
||||
self.modes = vec![modeinfo_for_size(width, height)];
|
||||
}
|
||||
|
||||
pub fn update_from_edid(&mut self, edid: &[u8]) {
|
||||
let edid = edid::parse(edid).unwrap().1;
|
||||
|
||||
if let Some(first_detailed_timing) =
|
||||
edid.descriptors
|
||||
.iter()
|
||||
.find_map(|descriptor| match descriptor {
|
||||
edid::Descriptor::DetailedTiming(detailed_timing) => Some(detailed_timing),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.mm_width = first_detailed_timing.horizontal_size.into();
|
||||
self.mm_height = first_detailed_timing.vertical_size.into();
|
||||
} else {
|
||||
log::error!("No edid timing descriptor detected");
|
||||
}
|
||||
|
||||
self.modes = edid
|
||||
.descriptors
|
||||
.iter()
|
||||
.filter_map(|descriptor| {
|
||||
match descriptor {
|
||||
edid::Descriptor::DetailedTiming(detailed_timing) => {
|
||||
// FIXME extract full information
|
||||
Some(modeinfo_for_size(
|
||||
u32::from(detailed_timing.horizontal_active_pixels),
|
||||
u32::from(detailed_timing.vertical_active_lines),
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// First detailed timing descriptor indicates preferred mode.
|
||||
for mode in self.modes.iter_mut().skip(1) {
|
||||
mode.flags &= !DRM_MODE_TYPE_PREFERRED;
|
||||
}
|
||||
|
||||
// FIXME update the EDID property
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn modeinfo_for_size(width: u32, height: u32) -> drm_mode_modeinfo {
|
||||
let mut modeinfo = drm_mode_modeinfo {
|
||||
// The actual visible display size
|
||||
hdisplay: width as u16,
|
||||
vdisplay: height as u16,
|
||||
|
||||
// These are used to calculate the refresh rate
|
||||
clock: 60 * width * height / 1000,
|
||||
htotal: width as u16,
|
||||
vtotal: height as u16,
|
||||
vscan: 0,
|
||||
vrefresh: 60,
|
||||
|
||||
type_: drm_sys::DRM_MODE_TYPE_PREFERRED | drm_sys::DRM_MODE_TYPE_DRIVER,
|
||||
name: [0; 32],
|
||||
|
||||
// These only matter when modesetting physical display adapters. For
|
||||
// those we should be able to parse the EDID blob.
|
||||
hsync_start: width as u16,
|
||||
hsync_end: width as u16,
|
||||
hskew: 0,
|
||||
vsync_start: height as u16,
|
||||
vsync_end: height as u16,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let name = format!("{width}x{height}").into_bytes();
|
||||
for (to, from) in modeinfo.name.iter_mut().zip(name) {
|
||||
*to = from as c_char;
|
||||
}
|
||||
|
||||
modeinfo
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(u32)]
|
||||
pub enum KmsConnectorStatus {
|
||||
Disconnected = 0,
|
||||
Connected = 1,
|
||||
Unknown = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(u32)]
|
||||
pub enum DrmSubpixelOrder {
|
||||
Unknown = 0,
|
||||
HorizontalRGB,
|
||||
HorizontalBGR,
|
||||
VerticalRGB,
|
||||
VerticalBGR,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(u64)]
|
||||
pub enum KmsDpms {
|
||||
On = DRM_MODE_DPMS_ON as u64,
|
||||
Standby = DRM_MODE_DPMS_STANDBY as u64,
|
||||
Suspend = DRM_MODE_DPMS_SUSPEND as u64,
|
||||
Off = DRM_MODE_DPMS_OFF as u64,
|
||||
}
|
||||
|
||||
// FIXME can we represent connector and encoder using a single struct?
|
||||
#[derive(Debug)]
|
||||
pub struct KmsEncoder {
|
||||
pub crtc_id: KmsObjectId,
|
||||
pub possible_crtcs: u32,
|
||||
pub possible_clones: u32,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod connector;
|
||||
pub mod objects;
|
||||
pub mod properties;
|
||||
@@ -0,0 +1,237 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use drm_sys::{
|
||||
drm_mode_modeinfo, DRM_MODE_OBJECT_BLOB, DRM_MODE_OBJECT_CONNECTOR, DRM_MODE_OBJECT_CRTC,
|
||||
DRM_MODE_OBJECT_ENCODER, DRM_MODE_OBJECT_FB, DRM_MODE_OBJECT_PROPERTY,
|
||||
};
|
||||
use syscall::{Error, Result, EINVAL};
|
||||
|
||||
use crate::kms::connector::{KmsConnector, KmsEncoder};
|
||||
use crate::kms::properties::{
|
||||
define_object_props, init_standard_props, KmsBlob, KmsProperty, KmsPropertyData,
|
||||
};
|
||||
use crate::GraphicsAdapter;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsObjects<T: GraphicsAdapter> {
|
||||
next_id: KmsObjectId,
|
||||
pub(crate) connectors: Vec<KmsObjectId>,
|
||||
pub(crate) encoders: Vec<KmsObjectId>,
|
||||
crtcs: Vec<KmsObjectId>,
|
||||
framebuffers: Vec<KmsObjectId>,
|
||||
pub(crate) objects: HashMap<KmsObjectId, KmsObject<T>>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: GraphicsAdapter> KmsObjects<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
let mut objects = KmsObjects {
|
||||
next_id: KmsObjectId(1),
|
||||
connectors: vec![],
|
||||
encoders: vec![],
|
||||
crtcs: vec![],
|
||||
framebuffers: vec![],
|
||||
objects: HashMap::new(),
|
||||
_marker: PhantomData,
|
||||
};
|
||||
init_standard_props(&mut objects);
|
||||
objects
|
||||
}
|
||||
|
||||
pub(crate) fn add<U: Into<KmsObject<T>>>(&mut self, data: U) -> KmsObjectId {
|
||||
let id = self.next_id;
|
||||
self.objects.insert(id, data.into());
|
||||
self.next_id.0 += 1;
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn get<'a, U: 'a>(&'a self, id: KmsObjectId) -> Result<&'a U>
|
||||
where
|
||||
&'a U: TryFrom<&'a KmsObject<T>>,
|
||||
{
|
||||
let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?;
|
||||
if let Ok(object) = object.try_into() {
|
||||
Ok(object)
|
||||
} else {
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn object_type(&self, id: KmsObjectId) -> Result<u32> {
|
||||
let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?;
|
||||
Ok(object.object_type())
|
||||
}
|
||||
|
||||
pub fn add_crtc(
|
||||
&mut self,
|
||||
driver_data: T::Crtc,
|
||||
driver_data_state: <T::Crtc as KmsCrtcDriver>::State,
|
||||
) -> KmsObjectId {
|
||||
let crtc_index = self.crtcs.len() as u32;
|
||||
let id = self.add(Mutex::new(KmsCrtc {
|
||||
crtc_index,
|
||||
gamma_size: 0,
|
||||
properties: KmsCrtc::base_properties(),
|
||||
state: KmsCrtcState {
|
||||
fb_id: None,
|
||||
mode: None,
|
||||
driver_data: driver_data_state,
|
||||
},
|
||||
driver_data,
|
||||
}));
|
||||
self.crtcs.push(id);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn crtc_ids(&self) -> &[KmsObjectId] {
|
||||
&self.crtcs
|
||||
}
|
||||
|
||||
pub fn crtcs(&self) -> impl Iterator<Item = &Mutex<KmsCrtc<T>>> + use<'_, T> {
|
||||
self.crtcs
|
||||
.iter()
|
||||
.map(|&id| self.get::<Mutex<KmsCrtc<T>>>(id).unwrap())
|
||||
}
|
||||
|
||||
pub fn get_crtc(&self, id: KmsObjectId) -> Result<&Mutex<KmsCrtc<T>>> {
|
||||
self.get(id)
|
||||
}
|
||||
|
||||
pub fn add_framebuffer(&mut self, fb: KmsFramebuffer<T>) -> KmsObjectId {
|
||||
let id = self.add(fb);
|
||||
self.framebuffers.push(id);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn remove_framebuffer(&mut self, id: KmsObjectId) -> Result<()> {
|
||||
let Some(object) = self.objects.get(&id) else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
let KmsObject::Framebuffer(_) = object else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
self.objects.remove(&id).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fb_ids(&self) -> &[KmsObjectId] {
|
||||
&self.framebuffers
|
||||
}
|
||||
|
||||
pub fn get_framebuffer(&self, id: KmsObjectId) -> Result<&KmsFramebuffer<T>> {
|
||||
self.get(id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KmsObjectId(pub(crate) u32);
|
||||
|
||||
impl KmsObjectId {
|
||||
pub const INVALID: KmsObjectId = KmsObjectId(0);
|
||||
}
|
||||
|
||||
impl From<KmsObjectId> for u64 {
|
||||
fn from(value: KmsObjectId) -> Self {
|
||||
value.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_object_kinds {
|
||||
(<$T:ident> $(
|
||||
$variant:ident($data:ty) = $type:ident,
|
||||
)*) => {
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum KmsObject<$T: GraphicsAdapter> {
|
||||
$($variant($data),)*
|
||||
}
|
||||
|
||||
impl<$T: GraphicsAdapter> KmsObject<$T> {
|
||||
fn object_type(&self) -> u32 {
|
||||
match self {
|
||||
$(Self::$variant(_) => $type,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl<$T: GraphicsAdapter> From<$data> for KmsObject<$T> {
|
||||
fn from(value: $data) -> Self {
|
||||
Self::$variant(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, $T: GraphicsAdapter> TryFrom<&'a KmsObject<$T>> for &'a $data {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &'a KmsObject<T>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
KmsObject::$variant(data) => Ok(data),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
define_object_kinds! { <T>
|
||||
Crtc(Mutex<KmsCrtc<T>>) = DRM_MODE_OBJECT_CRTC,
|
||||
Connector(Mutex<KmsConnector<T>>) = DRM_MODE_OBJECT_CONNECTOR,
|
||||
Encoder(KmsEncoder) = DRM_MODE_OBJECT_ENCODER,
|
||||
Property(KmsProperty) = DRM_MODE_OBJECT_PROPERTY,
|
||||
Framebuffer(KmsFramebuffer<T>) = DRM_MODE_OBJECT_FB,
|
||||
Blob(KmsBlob) = DRM_MODE_OBJECT_BLOB,
|
||||
}
|
||||
|
||||
pub trait KmsCrtcDriver: Debug {
|
||||
type State: Clone + Debug;
|
||||
}
|
||||
|
||||
impl KmsCrtcDriver for () {
|
||||
type State = ();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsCrtc<T: GraphicsAdapter> {
|
||||
pub crtc_index: u32,
|
||||
pub gamma_size: u32,
|
||||
pub properties: Vec<KmsPropertyData<Self>>,
|
||||
pub state: KmsCrtcState<T>,
|
||||
pub driver_data: T::Crtc,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsCrtcState<T: GraphicsAdapter> {
|
||||
pub fb_id: Option<KmsObjectId>,
|
||||
pub mode: Option<drm_mode_modeinfo>,
|
||||
pub driver_data: <T::Crtc as KmsCrtcDriver>::State,
|
||||
}
|
||||
|
||||
impl<T: GraphicsAdapter> Clone for KmsCrtcState<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
fb_id: self.fb_id.clone(),
|
||||
mode: self.mode.clone(),
|
||||
driver_data: self.driver_data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_object_props!(object, KmsCrtc<T: GraphicsAdapter> {});
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsFramebuffer<T: GraphicsAdapter> {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub pitch: u32,
|
||||
pub bpp: u32,
|
||||
pub depth: u32,
|
||||
pub buffer: Arc<T::Buffer>,
|
||||
pub driver_data: T::Framebuffer,
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
use std::ffi::c_char;
|
||||
use std::fmt::Debug;
|
||||
use std::mem;
|
||||
|
||||
use drm_sys::{
|
||||
DRM_MODE_DPMS_OFF, DRM_MODE_DPMS_ON, DRM_MODE_DPMS_STANDBY, DRM_MODE_DPMS_SUSPEND,
|
||||
DRM_MODE_OBJECT_CRTC, DRM_MODE_OBJECT_FB, DRM_PLANE_TYPE_CURSOR, DRM_PLANE_TYPE_OVERLAY,
|
||||
DRM_PLANE_TYPE_PRIMARY, DRM_PROP_NAME_LEN,
|
||||
};
|
||||
use syscall::{Error, Result, EINVAL};
|
||||
|
||||
use crate::kms::objects::{KmsObject, KmsObjectId, KmsObjects};
|
||||
use crate::GraphicsAdapter;
|
||||
|
||||
impl<T: GraphicsAdapter> KmsObjects<T> {
|
||||
pub fn add_property(
|
||||
&mut self,
|
||||
name: &str,
|
||||
immutable: bool,
|
||||
atomic: bool,
|
||||
kind: KmsPropertyKind,
|
||||
) -> KmsObjectId {
|
||||
match &kind {
|
||||
KmsPropertyKind::Range(start, end) => assert!(start < end),
|
||||
KmsPropertyKind::Enum(_variants) => {
|
||||
// FIXME check duplicate variant numbers
|
||||
}
|
||||
KmsPropertyKind::Blob => {}
|
||||
KmsPropertyKind::Bitmask(_bitmask_flags) => {
|
||||
// FIXME check overlapping flag numbers
|
||||
}
|
||||
KmsPropertyKind::Object { type_: _ } => {}
|
||||
KmsPropertyKind::SignedRange(start, end) => assert!(start < end),
|
||||
}
|
||||
|
||||
let mut name_bytes = [0; DRM_PROP_NAME_LEN as usize];
|
||||
for (to, &from) in name_bytes.iter_mut().zip(name.as_bytes()) {
|
||||
*to = from as c_char;
|
||||
}
|
||||
|
||||
self.add(KmsProperty {
|
||||
name: KmsPropertyName::new("Property name", name),
|
||||
immutable,
|
||||
atomic,
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_property(&self, id: KmsObjectId) -> Result<&KmsProperty> {
|
||||
self.get(id)
|
||||
}
|
||||
|
||||
pub fn get_object_properties_data(&self, id: KmsObjectId) -> Result<(Vec<u32>, Vec<u64>)> {
|
||||
let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?;
|
||||
match object {
|
||||
KmsObject::Crtc(crtc) => {
|
||||
let crtc = crtc.lock().unwrap();
|
||||
let props = &crtc.properties;
|
||||
Ok((
|
||||
props.iter().map(|prop| prop.id.0).collect::<Vec<_>>(),
|
||||
props
|
||||
.iter()
|
||||
.map(|prop| (prop.getter)(&crtc))
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
KmsObject::Connector(connector) => {
|
||||
let connector = connector.lock().unwrap();
|
||||
let props = &connector.properties;
|
||||
Ok((
|
||||
props.iter().map(|prop| prop.id.0).collect::<Vec<_>>(),
|
||||
props
|
||||
.iter()
|
||||
.map(|prop| (prop.getter)(&connector))
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
KmsObject::Encoder(_)
|
||||
| KmsObject::Property(_)
|
||||
| KmsObject::Framebuffer(_)
|
||||
| KmsObject::Blob(_) => Ok((vec![], vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_blob(&mut self, data: Vec<u8>) -> KmsObjectId {
|
||||
self.add(KmsBlob { data })
|
||||
}
|
||||
|
||||
pub fn get_blob(&self, id: KmsObjectId) -> Result<&[u8]> {
|
||||
Ok(&self.get::<KmsBlob>(id)?.data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct KmsPropertyName(pub [c_char; DRM_PROP_NAME_LEN as usize]);
|
||||
|
||||
impl KmsPropertyName {
|
||||
fn new(context: &str, name: &str) -> KmsPropertyName {
|
||||
if name.len() > DRM_PROP_NAME_LEN as usize {
|
||||
panic!("{context} {name} is too long");
|
||||
}
|
||||
|
||||
let mut name_bytes = [0; DRM_PROP_NAME_LEN as usize];
|
||||
for (to, &from) in name_bytes.iter_mut().zip(name.as_bytes()) {
|
||||
*to = from as c_char;
|
||||
}
|
||||
|
||||
KmsPropertyName(name_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for KmsPropertyName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let u8_bytes = unsafe { mem::transmute::<&[c_char], &[u8]>(&self.0) };
|
||||
f.write_str(&String::from_utf8_lossy(u8_bytes).trim_end_matches('\0'))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsProperty {
|
||||
pub name: KmsPropertyName,
|
||||
pub immutable: bool,
|
||||
pub atomic: bool,
|
||||
pub kind: KmsPropertyKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum KmsPropertyKind {
|
||||
Range(u64, u64),
|
||||
Enum(Vec<(KmsPropertyName, u64)>),
|
||||
Blob,
|
||||
Bitmask(Vec<(KmsPropertyName, u64)>),
|
||||
Object { type_: u32 },
|
||||
SignedRange(i64, i64),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsPropertyData<T> {
|
||||
pub id: KmsObjectId,
|
||||
pub getter: fn(&T) -> u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KmsBlob {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
macro_rules! define_properties {
|
||||
($($prop:ident $($prop_name:literal)?: $prop_type:ident $({$($prop_content:tt)*})? [$($prop_flag:ident)?],)*) => {
|
||||
$(#[allow(non_upper_case_globals)] pub const $prop: KmsObjectId = KmsObjectId(1 + ${index()});)*
|
||||
|
||||
pub(super) fn init_standard_props<T: GraphicsAdapter>(objects: &mut KmsObjects<T>) {
|
||||
$(
|
||||
assert_eq!(objects.add_property(
|
||||
define_properties!(@prop_name $prop $($prop_name)?),
|
||||
define_properties!(@is_immutable $($prop_flag)?),
|
||||
define_properties!(@is_atomic $($prop_flag)?),
|
||||
define_properties!(@prop_kind $prop_type $({$($prop_content)*})?),
|
||||
), $prop);
|
||||
)*
|
||||
}
|
||||
};
|
||||
(@prop_name $prop:ident $prop_name:literal) => { $prop_name };
|
||||
(@prop_name $prop:ident) => { stringify!($prop) };
|
||||
(@is_immutable) => { false };
|
||||
(@is_immutable immutable) => { true };
|
||||
(@is_immutable atomic) => { false };
|
||||
(@is_atomic) => { false };
|
||||
(@is_atomic immutable) => { false };
|
||||
(@is_atomic atomic) => { true };
|
||||
(@prop_kind range { $start:expr, $end:expr }) => {
|
||||
KmsPropertyKind::Range($start, $end)
|
||||
};
|
||||
(@prop_kind enum { $($variant:ident = $value:expr,)* }) => {
|
||||
KmsPropertyKind::Enum(vec![
|
||||
$((KmsPropertyName::new("Property variant name", stringify!($variant)), $value)),*]
|
||||
)
|
||||
};
|
||||
(@prop_kind blob) => {
|
||||
KmsPropertyKind::Blob
|
||||
};
|
||||
(@prop_kind object { $type:ident }) => {
|
||||
KmsPropertyKind::Object { type_: $type }
|
||||
};
|
||||
(@prop_kind srange { $start:expr, $end:expr }) => {
|
||||
KmsPropertyKind::SignedRange($start, $end)
|
||||
};
|
||||
}
|
||||
|
||||
define_properties! {
|
||||
// Connector + Plane
|
||||
CRTC_ID: object { DRM_MODE_OBJECT_CRTC } [atomic],
|
||||
|
||||
// Connector
|
||||
EDID: blob [immutable],
|
||||
DPMS: enum {
|
||||
On = u64::from(DRM_MODE_DPMS_ON),
|
||||
Standby = u64::from(DRM_MODE_DPMS_STANDBY),
|
||||
Suspend = u64::from(DRM_MODE_DPMS_SUSPEND),
|
||||
Off = u64::from(DRM_MODE_DPMS_OFF),
|
||||
} [],
|
||||
|
||||
// CRTC
|
||||
ACTIVE: range { 0,1 } [atomic],
|
||||
MODE_ID: blob [atomic],
|
||||
|
||||
// Plane
|
||||
type_ "type": enum {
|
||||
Overlay = u64::from(DRM_PLANE_TYPE_OVERLAY),
|
||||
Primary = u64::from(DRM_PLANE_TYPE_PRIMARY),
|
||||
Cursor = u64::from(DRM_PLANE_TYPE_CURSOR),
|
||||
} [immutable],
|
||||
FB_ID: object { DRM_MODE_OBJECT_FB } [atomic],
|
||||
CRTC_X: srange { i64::from(i32::MIN), i64::from(i32::MAX) } [atomic],
|
||||
CRTC_Y: srange { i64::from(i32::MIN), i64::from(i32::MAX) } [atomic],
|
||||
CRTC_W: range { 0, u64::from(u32::MAX) } [atomic],
|
||||
CRTC_H: range { 0, u64::from(u32::MAX) } [atomic],
|
||||
SRC_X: range { 0, u64::from(u32::MAX) } [atomic],
|
||||
SRC_Y: range { 0, u64::from(u32::MAX) } [atomic],
|
||||
SRC_W: range { 0, u64::from(u32::MAX) } [atomic],
|
||||
SRC_H: range { 0, u64::from(u32::MAX) } [atomic],
|
||||
FB_DAMAGE_CLIPS: blob [atomic],
|
||||
}
|
||||
|
||||
macro_rules! define_object_props {
|
||||
($object:ident, $obj:ident$(<$($T:ident$(: $bound:ident)?),*>)? { $(
|
||||
$prop:ident {
|
||||
get => $get:expr,
|
||||
}
|
||||
)* }) => {
|
||||
impl$(<$($T$(: $bound)?),*>)? $obj$(<$($T),*>)? {
|
||||
pub(super) fn base_properties() -> Vec<KmsPropertyData<Self>> {
|
||||
vec![$(KmsPropertyData {
|
||||
id: $prop,
|
||||
getter: |$object| $get
|
||||
}),*]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(super) use define_object_props;
|
||||
@@ -0,0 +1,986 @@
|
||||
#![feature(macro_metavar_expr)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{cmp, mem};
|
||||
|
||||
use drm_fourcc::DrmFourcc;
|
||||
use drm_sys::{
|
||||
drm_mode_property_enum, DRM_MODE_CURSOR_BO, DRM_MODE_CURSOR_MOVE, DRM_MODE_PROP_ATOMIC,
|
||||
DRM_MODE_PROP_BITMASK, DRM_MODE_PROP_BLOB, DRM_MODE_PROP_ENUM, DRM_MODE_PROP_IMMUTABLE,
|
||||
DRM_MODE_PROP_OBJECT, DRM_MODE_PROP_RANGE, DRM_MODE_PROP_SIGNED_RANGE,
|
||||
};
|
||||
use inputd::{DisplayHandle, VtEventKind};
|
||||
use libredox::Fd;
|
||||
use redox_scheme::scheme::{register_scheme_inner, SchemeState, SchemeSync};
|
||||
use redox_scheme::{CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket};
|
||||
use scheme_utils::{FpathWriter, HandleMap};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error, MapFlags, Result, EACCES, EAGAIN, EINVAL, ENOENT, EOPNOTSUPP};
|
||||
|
||||
use crate::kms::connector::{KmsConnectorDriver, KmsConnectorState};
|
||||
use crate::kms::objects::{self, KmsCrtc, KmsCrtcDriver, KmsCrtcState, KmsObjectId, KmsObjects};
|
||||
use crate::kms::properties::KmsPropertyKind;
|
||||
|
||||
pub mod kms;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Damage {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl Damage {
|
||||
fn merge(self, other: Self) -> Self {
|
||||
if self.width == 0 || self.height == 0 {
|
||||
return other;
|
||||
}
|
||||
|
||||
if other.width == 0 || other.height == 0 {
|
||||
return self;
|
||||
}
|
||||
|
||||
let x = cmp::min(self.x, other.x);
|
||||
let y = cmp::min(self.y, other.y);
|
||||
let x2 = cmp::max(self.x + self.width, other.x + other.width);
|
||||
let y2 = cmp::max(self.y + self.height, other.y + other.height);
|
||||
|
||||
Damage {
|
||||
x,
|
||||
y,
|
||||
width: x2 - x,
|
||||
height: y2 - y,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn clip(mut self, width: u32, height: u32) -> Self {
|
||||
// Clip damage
|
||||
let x2 = self.x + self.width;
|
||||
self.x = cmp::min(self.x, width);
|
||||
if x2 > width {
|
||||
self.width = width - self.x;
|
||||
}
|
||||
|
||||
let y2 = self.y + self.height;
|
||||
self.y = cmp::min(self.y, height);
|
||||
if y2 > height {
|
||||
self.height = height - self.y;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GraphicsAdapter: Sized + Debug {
|
||||
type Connector: KmsConnectorDriver;
|
||||
type Crtc: KmsCrtcDriver;
|
||||
|
||||
type Buffer: Buffer;
|
||||
type Framebuffer: Framebuffer;
|
||||
|
||||
fn name(&self) -> &'static [u8];
|
||||
fn desc(&self) -> &'static [u8];
|
||||
|
||||
fn init(&mut self, objects: &mut KmsObjects<Self>);
|
||||
|
||||
fn get_cap(&self, cap: u32) -> Result<u64>;
|
||||
fn set_client_cap(&self, cap: u32, value: u64) -> Result<()>;
|
||||
|
||||
fn probe_connector(&mut self, objects: &mut KmsObjects<Self>, id: KmsObjectId);
|
||||
|
||||
fn create_dumb_buffer(&mut self, width: u32, height: u32) -> (Self::Buffer, u32);
|
||||
fn map_dumb_buffer(&mut self, buffer: &Self::Buffer) -> *mut u8;
|
||||
|
||||
fn create_framebuffer(&mut self, buffer: &Self::Buffer) -> Self::Framebuffer;
|
||||
|
||||
fn set_crtc(
|
||||
&mut self,
|
||||
objects: &KmsObjects<Self>,
|
||||
crtc: &Mutex<KmsCrtc<Self>>,
|
||||
new_state: KmsCrtcState<Self>,
|
||||
damage: Damage,
|
||||
) -> syscall::Result<()>;
|
||||
|
||||
fn hw_cursor_size(&self) -> Option<(u32, u32)>;
|
||||
fn handle_cursor(&mut self, cursor: &CursorPlane<Self::Buffer>, dirty_fb: bool);
|
||||
}
|
||||
|
||||
pub trait Buffer: Debug {
|
||||
fn size(&self) -> usize;
|
||||
}
|
||||
|
||||
pub trait Framebuffer: Debug {}
|
||||
|
||||
impl Framebuffer for () {}
|
||||
|
||||
pub struct CursorPlane<C: Buffer> {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub hot_x: i32,
|
||||
pub hot_y: i32,
|
||||
pub buffer: Option<Arc<C>>,
|
||||
}
|
||||
|
||||
pub struct GraphicsScheme<T: GraphicsAdapter> {
|
||||
inner: GraphicsSchemeInner<T>,
|
||||
inputd_handle: DisplayHandle,
|
||||
state: SchemeState,
|
||||
}
|
||||
|
||||
impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||
pub fn new(mut adapter: T, scheme_name: String, early: bool) -> Self {
|
||||
assert!(scheme_name.starts_with("display"));
|
||||
let socket = Socket::nonblock().expect("failed to create graphics scheme");
|
||||
|
||||
let disable_graphical_debug = Some(
|
||||
File::open("/scheme/debug/disable-graphical-debug")
|
||||
.expect("vesad: Failed to open /scheme/debug/disable-graphical-debug"),
|
||||
);
|
||||
|
||||
let mut objects = KmsObjects::new();
|
||||
adapter.init(&mut objects);
|
||||
for connector_id in objects.connector_ids().to_vec() {
|
||||
adapter.probe_connector(&mut objects, connector_id)
|
||||
}
|
||||
|
||||
let mut inner = GraphicsSchemeInner {
|
||||
adapter,
|
||||
scheme_name,
|
||||
disable_graphical_debug,
|
||||
socket,
|
||||
objects,
|
||||
handles: HandleMap::new(),
|
||||
active_vt: 0,
|
||||
vts: HashMap::new(),
|
||||
};
|
||||
|
||||
let cap_id = inner.scheme_root().expect("failed to get this scheme root");
|
||||
register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id)
|
||||
.expect("failed to register graphics scheme root");
|
||||
|
||||
let display_handle = if early {
|
||||
DisplayHandle::new_early(&inner.scheme_name).unwrap()
|
||||
} else {
|
||||
DisplayHandle::new(&inner.scheme_name).unwrap()
|
||||
};
|
||||
|
||||
Self {
|
||||
inner,
|
||||
inputd_handle: display_handle,
|
||||
state: SchemeState::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_handle(&self) -> &Fd {
|
||||
self.inner.socket.inner()
|
||||
}
|
||||
|
||||
pub fn inputd_event_handle(&self) -> BorrowedFd<'_> {
|
||||
self.inputd_handle.inner()
|
||||
}
|
||||
|
||||
pub fn adapter(&self) -> &T {
|
||||
&self.inner.adapter
|
||||
}
|
||||
|
||||
pub fn adapter_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.adapter
|
||||
}
|
||||
|
||||
pub fn kms_objects(&self) -> &KmsObjects<T> {
|
||||
&self.inner.objects
|
||||
}
|
||||
|
||||
pub fn kms_objects_mut(&mut self) -> &mut KmsObjects<T> {
|
||||
&mut self.inner.objects
|
||||
}
|
||||
|
||||
pub fn adapter_and_kms_objects_mut(&mut self) -> (&mut T, &mut KmsObjects<T>) {
|
||||
(&mut self.inner.adapter, &mut self.inner.objects)
|
||||
}
|
||||
|
||||
pub fn handle_vt_events(&mut self) {
|
||||
while let Some(vt_event) = self
|
||||
.inputd_handle
|
||||
.read_vt_event()
|
||||
.expect("driver-graphics: failed to read display handle")
|
||||
{
|
||||
match vt_event.kind {
|
||||
VtEventKind::Activate => self.inner.activate_vt(vt_event.vt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_displays_changed(&mut self) {
|
||||
// FIXME notify clients
|
||||
}
|
||||
|
||||
/// Process new scheme requests.
|
||||
///
|
||||
/// This needs to be called each time there is a new event on the scheme
|
||||
/// file.
|
||||
pub fn tick(&mut self) -> io::Result<()> {
|
||||
loop {
|
||||
let request = match self.inner.socket.next_request(SignalBehavior::Restart) {
|
||||
Ok(Some(request)) => request,
|
||||
Ok(None) => {
|
||||
// Scheme likely got unmounted
|
||||
std::process::exit(0);
|
||||
}
|
||||
Err(err) if err.errno == EAGAIN => break,
|
||||
Err(err) => panic!("driver-graphics: failed to read display scheme: {err}"),
|
||||
};
|
||||
|
||||
match request.kind() {
|
||||
RequestKind::Call(call) => {
|
||||
let response = call.handle_sync(&mut self.inner, &mut self.state);
|
||||
self.inner
|
||||
.socket
|
||||
.write_response(response, SignalBehavior::Restart)
|
||||
.expect("driver-graphics: failed to write response");
|
||||
}
|
||||
RequestKind::OnClose { id } => {
|
||||
self.inner.on_close(id);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct GraphicsSchemeInner<T: GraphicsAdapter> {
|
||||
adapter: T,
|
||||
|
||||
scheme_name: String,
|
||||
disable_graphical_debug: Option<File>,
|
||||
socket: Socket,
|
||||
objects: KmsObjects<T>,
|
||||
handles: HandleMap<Handle<T>>,
|
||||
|
||||
active_vt: usize,
|
||||
vts: HashMap<usize, VtState<T>>,
|
||||
}
|
||||
|
||||
struct VtState<T: GraphicsAdapter> {
|
||||
connector_state: Vec<KmsConnectorState<T>>,
|
||||
crtc_state: Vec<KmsCrtcState<T>>,
|
||||
cursor_plane: CursorPlane<T::Buffer>,
|
||||
}
|
||||
|
||||
enum Handle<T: GraphicsAdapter> {
|
||||
V2 {
|
||||
vt: usize,
|
||||
next_id: u32,
|
||||
buffers: HashMap<u32, Arc<T::Buffer>>,
|
||||
},
|
||||
SchemeRoot,
|
||||
}
|
||||
|
||||
impl<T: GraphicsAdapter> GraphicsSchemeInner<T> {
|
||||
fn get_or_create_vt<'a>(
|
||||
objects: &KmsObjects<T>,
|
||||
vts: &'a mut HashMap<usize, VtState<T>>,
|
||||
vt: usize,
|
||||
) -> &'a mut VtState<T> {
|
||||
vts.entry(vt).or_insert_with(|| VtState {
|
||||
connector_state: objects
|
||||
.connectors()
|
||||
.map(|connector| connector.lock().unwrap().state.clone())
|
||||
.collect(),
|
||||
crtc_state: objects
|
||||
.crtcs()
|
||||
.map(|crtc| crtc.lock().unwrap().state.clone())
|
||||
.collect(),
|
||||
cursor_plane: CursorPlane {
|
||||
x: 0,
|
||||
y: 0,
|
||||
hot_x: 0,
|
||||
hot_y: 0,
|
||||
buffer: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn activate_vt(&mut self, vt: usize) {
|
||||
log::info!("activate {}", vt);
|
||||
|
||||
// Disable the kernel graphical debug writing once switching vt's for the
|
||||
// first time. This way the kernel graphical debug remains enabled if the
|
||||
// userspace logging infrastructure doesn't start up because for example a
|
||||
// kernel panic happened prior to it starting up or logd crashed.
|
||||
if let Some(mut disable_graphical_debug) = self.disable_graphical_debug.take() {
|
||||
let _ = disable_graphical_debug.write(&[1]);
|
||||
}
|
||||
|
||||
self.active_vt = vt;
|
||||
|
||||
let vt_state = GraphicsSchemeInner::get_or_create_vt(&self.objects, &mut self.vts, vt);
|
||||
|
||||
for (connector_idx, connector_state) in vt_state.connector_state.iter().enumerate() {
|
||||
let connector_id = self.objects.connector_ids()[connector_idx];
|
||||
let mut connector = self
|
||||
.objects
|
||||
.get_connector(connector_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
connector.state = connector_state.clone();
|
||||
}
|
||||
|
||||
for (crtc_idx, crtc_state) in vt_state.crtc_state.iter().enumerate() {
|
||||
let crtc_id = self.objects.crtc_ids()[crtc_idx];
|
||||
let crtc = self.objects.get_crtc(crtc_id).unwrap();
|
||||
let connector_id = self.objects.connector_ids()[crtc_idx];
|
||||
|
||||
let fb = crtc_state.fb_id.map(|fb_id| {
|
||||
self.objects
|
||||
.get_framebuffer(fb_id)
|
||||
.expect("removed framebuffers should be unset")
|
||||
});
|
||||
|
||||
self.adapter
|
||||
.set_crtc(
|
||||
&self.objects,
|
||||
crtc,
|
||||
crtc_state.clone(),
|
||||
Damage {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: fb.map_or(0, |fb| fb.width),
|
||||
height: fb.map_or(0, |fb| fb.height),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.objects
|
||||
.get_connector(connector_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.state
|
||||
.crtc_id = crtc_id;
|
||||
}
|
||||
|
||||
if self.adapter.hw_cursor_size().is_some() {
|
||||
self.adapter.handle_cursor(&vt_state.cursor_plane, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MAP_FAKE_OFFSET_MULTIPLIER: usize = 0x10_000_000;
|
||||
|
||||
impl<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(self.handles.insert(Handle::SchemeRoot))
|
||||
}
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
if path.is_empty() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let handle = if path.starts_with("v") {
|
||||
if !path.starts_with("v2/") {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
let vt = path["v2/".len()..]
|
||||
.parse::<usize>()
|
||||
.map_err(|_| Error::new(EINVAL))?;
|
||||
|
||||
// Ensure the VT exists such that the rest of the methods can freely access it.
|
||||
Self::get_or_create_vt(&self.objects, &mut self.vts, vt);
|
||||
|
||||
Handle::V2 {
|
||||
vt,
|
||||
next_id: 0,
|
||||
buffers: HashMap::new(),
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
let id = self.handles.insert(handle);
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: id,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> syscall::Result<usize> {
|
||||
FpathWriter::with(buf, &self.scheme_name, |w| {
|
||||
match self.handles.get(id)? {
|
||||
Handle::V2 {
|
||||
vt,
|
||||
next_id: _,
|
||||
buffers: _,
|
||||
} => write!(w, "v2/{vt}").unwrap(),
|
||||
Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)),
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
id: usize,
|
||||
payload: &mut [u8],
|
||||
metadata: &[u64],
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
use redox_ioctl::drm as ipc;
|
||||
|
||||
fn id_index(id: u32) -> u32 {
|
||||
id & 0xFF
|
||||
}
|
||||
|
||||
fn plane_id(i: u32) -> u32 {
|
||||
id_index(i) | (1 << 13)
|
||||
}
|
||||
|
||||
match self.handles.get_mut(id)? {
|
||||
Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)),
|
||||
Handle::V2 {
|
||||
vt,
|
||||
next_id,
|
||||
buffers,
|
||||
} => match metadata[0] {
|
||||
ipc::VERSION => ipc::DrmVersion::with(payload, |mut data| {
|
||||
data.set_version_major(1);
|
||||
data.set_version_minor(4);
|
||||
data.set_version_patchlevel(0);
|
||||
|
||||
data.set_name(unsafe { mem::transmute(self.adapter.name()) });
|
||||
data.set_date(unsafe { mem::transmute(&b"0"[..]) });
|
||||
data.set_desc(unsafe { mem::transmute(self.adapter.desc()) });
|
||||
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::GET_CAP => ipc::DrmGetCap::with(payload, |mut data| {
|
||||
data.set_value(
|
||||
self.adapter.get_cap(
|
||||
data.capability()
|
||||
.try_into()
|
||||
.map_err(|_| syscall::Error::new(EINVAL))?,
|
||||
)?,
|
||||
);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::SET_CLIENT_CAP => ipc::DrmSetClientCap::with(payload, |data| {
|
||||
self.adapter.set_client_cap(
|
||||
data.capability()
|
||||
.try_into()
|
||||
.map_err(|_| syscall::Error::new(EINVAL))?,
|
||||
data.value(),
|
||||
)?;
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_CARD_RES => ipc::DrmModeCardRes::with(payload, |mut data| {
|
||||
let conn_ids = self
|
||||
.objects
|
||||
.connector_ids()
|
||||
.iter()
|
||||
.map(|id| id.0)
|
||||
.collect::<Vec<_>>();
|
||||
let crtc_ids = self
|
||||
.objects
|
||||
.crtc_ids()
|
||||
.iter()
|
||||
.map(|id| id.0)
|
||||
.collect::<Vec<_>>();
|
||||
let enc_ids = self
|
||||
.objects
|
||||
.encoder_ids()
|
||||
.iter()
|
||||
.map(|id| id.0)
|
||||
.collect::<Vec<_>>();
|
||||
let fb_ids = self
|
||||
.objects
|
||||
.fb_ids()
|
||||
.iter()
|
||||
.map(|id| id.0)
|
||||
.collect::<Vec<_>>();
|
||||
data.set_fb_id_ptr(&fb_ids);
|
||||
data.set_crtc_id_ptr(&crtc_ids);
|
||||
data.set_connector_id_ptr(&conn_ids);
|
||||
data.set_encoder_id_ptr(&enc_ids);
|
||||
data.set_min_width(0);
|
||||
data.set_max_width(16384);
|
||||
data.set_min_height(0);
|
||||
data.set_max_height(16384);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_CRTC => ipc::DrmModeCrtc::with(payload, |mut data| {
|
||||
let crtc = self
|
||||
.objects
|
||||
.get_crtc(KmsObjectId(data.crtc_id()))?
|
||||
.lock()
|
||||
.unwrap();
|
||||
// Don't touch set_connectors, that is only used by MODE_SET_CRTC
|
||||
data.set_fb_id(crtc.state.fb_id.unwrap_or(KmsObjectId::INVALID).0);
|
||||
// FIXME fill x and y with the data from the primary plane
|
||||
data.set_x(0);
|
||||
data.set_y(0);
|
||||
data.set_gamma_size(crtc.gamma_size);
|
||||
if let Some(mode) = crtc.state.mode {
|
||||
data.set_mode_valid(1);
|
||||
data.set_mode(mode);
|
||||
} else {
|
||||
data.set_mode_valid(0);
|
||||
data.set_mode(Default::default());
|
||||
}
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_SET_CRTC => ipc::DrmModeCrtc::with(payload, |data| {
|
||||
let crtc = self.objects.get_crtc(KmsObjectId(data.crtc_id()))?;
|
||||
let connector_ids: Vec<KmsObjectId> = data
|
||||
.set_connectors_ptr()
|
||||
.iter()
|
||||
.take(data.count_connectors() as usize)
|
||||
.map(|&id| KmsObjectId(id))
|
||||
.collect();
|
||||
let fb_id = if data.fb_id() != 0 {
|
||||
Some(KmsObjectId(data.fb_id()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mode = if data.mode_valid() != 0 {
|
||||
Some(data.mode())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut new_state = crtc.lock().unwrap().state.clone();
|
||||
new_state.fb_id = fb_id;
|
||||
new_state.mode = mode;
|
||||
if *vt == self.active_vt {
|
||||
self.adapter.set_crtc(
|
||||
&self.objects,
|
||||
crtc,
|
||||
new_state.clone(),
|
||||
Damage {
|
||||
x: data.x(),
|
||||
y: data.y(),
|
||||
width: mode.map_or(0, |m| m.hdisplay as u32),
|
||||
height: mode.map_or(0, |m| m.vdisplay as u32),
|
||||
},
|
||||
)?;
|
||||
|
||||
for connector in connector_ids {
|
||||
self.objects
|
||||
.get_connector(connector)?
|
||||
.lock()
|
||||
.unwrap()
|
||||
.state
|
||||
.crtc_id = KmsObjectId(data.crtc_id());
|
||||
}
|
||||
}
|
||||
self.vts.get_mut(vt).unwrap().crtc_state
|
||||
[crtc.lock().unwrap().crtc_index as usize] = new_state;
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_CURSOR => ipc::DrmModeCursor::with(payload, |data| {
|
||||
let vt_state = self.vts.get_mut(vt).unwrap();
|
||||
|
||||
let cursor_plane = &mut vt_state.cursor_plane;
|
||||
|
||||
let update_buffer = data.flags() & DRM_MODE_CURSOR_BO != 0;
|
||||
if update_buffer {
|
||||
cursor_plane.buffer = if data.handle() == 0 {
|
||||
None
|
||||
} else if let Some(buffer) = buffers.get(&data.handle()) {
|
||||
Some(buffer.clone())
|
||||
} else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
}
|
||||
|
||||
if data.flags() & DRM_MODE_CURSOR_MOVE != 0 {
|
||||
cursor_plane.x = data.x();
|
||||
cursor_plane.y = data.y();
|
||||
}
|
||||
|
||||
self.adapter.handle_cursor(cursor_plane, update_buffer);
|
||||
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_ENCODER => ipc::DrmModeGetEncoder::with(payload, |mut data| {
|
||||
let encoder = self.objects.get_encoder(KmsObjectId(data.encoder_id()))?;
|
||||
data.set_crtc_id(encoder.crtc_id.0);
|
||||
data.set_possible_crtcs(encoder.possible_crtcs);
|
||||
data.set_possible_clones(encoder.possible_clones);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_CONNECTOR => ipc::DrmModeGetConnector::with(payload, |mut data| {
|
||||
if data.count_modes() == 0 {
|
||||
self.adapter
|
||||
.probe_connector(&mut self.objects, KmsObjectId(data.connector_id()));
|
||||
}
|
||||
let connector = self
|
||||
.objects
|
||||
.get_connector(KmsObjectId(data.connector_id()))?
|
||||
.lock()
|
||||
.unwrap();
|
||||
data.set_encoders_ptr(&[connector.encoder_id.0]);
|
||||
data.set_modes_ptr(&connector.modes);
|
||||
data.set_connector_type(data.connector_type());
|
||||
data.set_connector_type_id(data.connector_type_id());
|
||||
data.set_connection(connector.connection as u32);
|
||||
data.set_mm_width(connector.mm_width);
|
||||
data.set_mm_height(connector.mm_width);
|
||||
data.set_subpixel(connector.subpixel as u32);
|
||||
drop(connector);
|
||||
let (props, prop_vals) = self
|
||||
.objects
|
||||
.get_object_properties_data(KmsObjectId(data.connector_id()))?;
|
||||
data.set_props_ptr(&props);
|
||||
data.set_prop_values_ptr(&prop_vals);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_PROPERTY => ipc::DrmModeGetProperty::with(payload, |mut data| {
|
||||
let property = self.objects.get_property(KmsObjectId(data.prop_id()))?;
|
||||
data.set_name(property.name.0);
|
||||
let mut flags = 0;
|
||||
if property.immutable {
|
||||
flags |= DRM_MODE_PROP_IMMUTABLE;
|
||||
}
|
||||
if property.atomic {
|
||||
flags |= DRM_MODE_PROP_ATOMIC;
|
||||
}
|
||||
match &property.kind {
|
||||
&KmsPropertyKind::Range(start, end) => {
|
||||
data.set_flags(flags | DRM_MODE_PROP_RANGE);
|
||||
data.set_values_ptr(&[start, end]);
|
||||
data.set_enum_blob_ptr(&[]);
|
||||
}
|
||||
KmsPropertyKind::Enum(variants) => {
|
||||
data.set_flags(flags | DRM_MODE_PROP_ENUM);
|
||||
data.set_values_ptr(
|
||||
&variants.iter().map(|&(_, value)| value).collect::<Vec<_>>(),
|
||||
);
|
||||
data.set_enum_blob_ptr(
|
||||
&variants
|
||||
.iter()
|
||||
.map(|&(name, value)| drm_mode_property_enum {
|
||||
name: name.0,
|
||||
value,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
KmsPropertyKind::Blob => {
|
||||
data.set_flags(flags | DRM_MODE_PROP_BLOB);
|
||||
data.set_values_ptr(&[]);
|
||||
data.set_enum_blob_ptr(&[]);
|
||||
}
|
||||
KmsPropertyKind::Bitmask(bitmask_flags) => {
|
||||
data.set_flags(flags | DRM_MODE_PROP_BITMASK);
|
||||
data.set_values_ptr(
|
||||
&bitmask_flags
|
||||
.iter()
|
||||
.map(|&(_, value)| value)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
data.set_enum_blob_ptr(
|
||||
&bitmask_flags
|
||||
.iter()
|
||||
.map(|&(name, value)| drm_mode_property_enum {
|
||||
name: name.0,
|
||||
value,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
KmsPropertyKind::Object { type_ } => {
|
||||
data.set_flags(flags | DRM_MODE_PROP_OBJECT);
|
||||
data.set_values_ptr(&[u64::from(*type_)]);
|
||||
data.set_enum_blob_ptr(&[]);
|
||||
}
|
||||
&KmsPropertyKind::SignedRange(start, end) => {
|
||||
data.set_flags(flags | DRM_MODE_PROP_SIGNED_RANGE);
|
||||
data.set_values_ptr(&[start as u64, end as u64]);
|
||||
data.set_enum_blob_ptr(&[]);
|
||||
}
|
||||
}
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_PROP_BLOB => ipc::DrmModeGetBlob::with(payload, |mut data| {
|
||||
let blob = self.objects.get_blob(KmsObjectId(data.blob_id()))?;
|
||||
data.set_data(&blob);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_FB => ipc::DrmModeFbCmd::with(payload, |mut data| {
|
||||
let fb = self.objects.get_framebuffer(KmsObjectId(data.fb_id()))?;
|
||||
|
||||
*next_id += 1;
|
||||
buffers.insert(*next_id, fb.buffer.clone());
|
||||
|
||||
data.set_width(fb.width);
|
||||
data.set_height(fb.height);
|
||||
data.set_pitch(fb.pitch);
|
||||
data.set_bpp(fb.bpp);
|
||||
data.set_depth(fb.depth);
|
||||
data.set_handle(*next_id);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_ADD_FB => ipc::DrmModeFbCmd::with(payload, |mut data| {
|
||||
let buffer = buffers.get(&data.handle()).ok_or(Error::new(EINVAL))?;
|
||||
|
||||
let fb = self.adapter.create_framebuffer(buffer);
|
||||
|
||||
let id = self.objects.add_framebuffer(objects::KmsFramebuffer {
|
||||
width: data.width(),
|
||||
height: data.height(),
|
||||
pitch: data.pitch(),
|
||||
bpp: data.bpp(),
|
||||
depth: data.depth(),
|
||||
buffer: buffer.clone(),
|
||||
driver_data: fb,
|
||||
});
|
||||
|
||||
data.set_fb_id(id.0);
|
||||
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_RM_FB => ipc::StandinForUint::with(payload, |data| {
|
||||
let fb_id = KmsObjectId(data.inner());
|
||||
self.objects.remove_framebuffer(fb_id)?;
|
||||
|
||||
// Disable planes that use this framebuffer.
|
||||
for (vt, vt_data) in &mut self.vts {
|
||||
for (crtc_idx, crtc_state) in vt_data.crtc_state.iter_mut().enumerate() {
|
||||
if crtc_state.fb_id != Some(fb_id) {
|
||||
continue;
|
||||
}
|
||||
crtc_state.fb_id = None;
|
||||
|
||||
if *vt != self.active_vt {
|
||||
continue;
|
||||
}
|
||||
let crtc = self.objects.crtcs().nth(crtc_idx).unwrap();
|
||||
self.adapter
|
||||
.set_crtc(
|
||||
&self.objects,
|
||||
crtc,
|
||||
crtc_state.clone(),
|
||||
Damage {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_DIRTYFB => ipc::DrmModeFbDirtyCmd::with(payload, |data| {
|
||||
let fb = self.objects.get_framebuffer(KmsObjectId(data.fb_id()))?;
|
||||
|
||||
let damage = data
|
||||
.clips_ptr()
|
||||
.iter()
|
||||
.map(|rect| Damage {
|
||||
x: u32::from(rect.x1),
|
||||
y: u32::from(rect.y1),
|
||||
width: u32::from(rect.x2 - rect.x1),
|
||||
height: u32::from(rect.y2 - rect.y1),
|
||||
})
|
||||
.reduce(Damage::merge)
|
||||
.unwrap_or(Damage {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: fb.width,
|
||||
height: fb.height,
|
||||
});
|
||||
|
||||
if *vt == self.active_vt {
|
||||
for crtc in self.objects.crtcs() {
|
||||
let state = crtc.lock().unwrap().state.clone();
|
||||
if state.fb_id == Some(KmsObjectId(data.fb_id())) {
|
||||
self.adapter.set_crtc(&self.objects, crtc, state, damage)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_CREATE_DUMB => ipc::DrmModeCreateDumb::with(payload, |mut data| {
|
||||
if data.bpp() != 32 || data.flags() != 0 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let (buffer, pitch) =
|
||||
self.adapter.create_dumb_buffer(data.width(), data.height());
|
||||
|
||||
data.set_pitch(pitch);
|
||||
data.set_size(buffer.size() as u64);
|
||||
|
||||
*next_id += 1;
|
||||
buffers.insert(*next_id, Arc::new(buffer));
|
||||
data.set_handle(*next_id as u32);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_MAP_DUMB => ipc::DrmModeMapDumb::with(payload, |mut data| {
|
||||
if data.offset() != 0 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let buffer_id = data.handle();
|
||||
|
||||
if !buffers.contains_key(&buffer_id) {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
// FIXME use a better scheme for creating map offsets
|
||||
assert!(buffers[&buffer_id].size() < MAP_FAKE_OFFSET_MULTIPLIER);
|
||||
|
||||
data.set_offset((buffer_id as usize * MAP_FAKE_OFFSET_MULTIPLIER) as u64);
|
||||
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_DESTROY_DUMB => ipc::DrmModeDestroyDumb::with(payload, |data| {
|
||||
if buffers.remove(&data.handle()).is_none() {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_PLANE_RES => ipc::DrmModeGetPlaneRes::with(payload, |mut data| {
|
||||
let count = self.objects.crtc_ids().len();
|
||||
let mut ids = Vec::with_capacity(count);
|
||||
for i in 0..(count as u32) {
|
||||
ids.push(plane_id(i));
|
||||
}
|
||||
data.set_plane_id_ptr(&ids);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_PLANE => ipc::DrmModeGetPlane::with(payload, |mut data| {
|
||||
let i = id_index(data.plane_id());
|
||||
let crtc_id = self.objects.crtc_ids()[i as usize];
|
||||
let crtc = self.objects.get_crtc(crtc_id).unwrap();
|
||||
data.set_crtc_id(crtc_id.0);
|
||||
data.set_fb_id(
|
||||
crtc.lock()
|
||||
.unwrap()
|
||||
.state
|
||||
.fb_id
|
||||
.unwrap_or(KmsObjectId::INVALID)
|
||||
.0,
|
||||
);
|
||||
data.set_possible_crtcs(1 << i);
|
||||
data.set_format_type_ptr(&[DrmFourcc::Argb8888 as u32]);
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_OBJ_GET_PROPERTIES => {
|
||||
ipc::DrmModeObjGetProperties::with(payload, |mut data| {
|
||||
// FIXME remove once all drm objects are materialized in self.objects
|
||||
if data.obj_id() >= 1 << 11 {
|
||||
data.set_props_ptr(&[]);
|
||||
data.set_prop_values_ptr(&[]);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let (props, prop_vals) = self
|
||||
.objects
|
||||
.get_object_properties_data(KmsObjectId(data.obj_id()))?;
|
||||
data.set_props_ptr(&props);
|
||||
data.set_prop_values_ptr(&prop_vals);
|
||||
data.set_obj_type(self.objects.object_type(KmsObjectId(data.obj_id()))?);
|
||||
Ok(0)
|
||||
})
|
||||
}
|
||||
ipc::MODE_CURSOR2 => ipc::DrmModeCursor2::with(payload, |data| {
|
||||
let vt_state = self.vts.get_mut(vt).unwrap();
|
||||
|
||||
let cursor_plane = &mut vt_state.cursor_plane;
|
||||
|
||||
let update_buffer = data.flags() & DRM_MODE_CURSOR_BO != 0;
|
||||
if update_buffer {
|
||||
cursor_plane.buffer = if data.handle() == 0 {
|
||||
None
|
||||
} else if let Some(buffer) = buffers.get(&data.handle()) {
|
||||
Some(buffer.clone())
|
||||
} else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
cursor_plane.hot_x = data.hot_x();
|
||||
cursor_plane.hot_y = data.hot_y();
|
||||
}
|
||||
|
||||
if data.flags() & DRM_MODE_CURSOR_MOVE != 0 {
|
||||
cursor_plane.x = data.x();
|
||||
cursor_plane.y = data.y();
|
||||
}
|
||||
|
||||
self.adapter.handle_cursor(cursor_plane, update_buffer);
|
||||
|
||||
Ok(0)
|
||||
}),
|
||||
ipc::MODE_GET_FB2 => ipc::DrmModeFbCmd2::with(payload, |mut data| {
|
||||
let fb = self.objects.get_framebuffer(KmsObjectId(data.fb_id()))?;
|
||||
|
||||
*next_id += 1;
|
||||
buffers.insert(*next_id, fb.buffer.clone());
|
||||
|
||||
data.set_width(fb.width);
|
||||
data.set_height(fb.height);
|
||||
data.set_pixel_format(DrmFourcc::Argb8888 as u32);
|
||||
data.set_handles([*next_id, 0, 0, 0]);
|
||||
data.set_pitches([fb.width * 4, 0, 0, 0]);
|
||||
data.set_offsets([0; 4]);
|
||||
data.set_modifier([0; 4]);
|
||||
Ok(0)
|
||||
}),
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn mmap_prep(
|
||||
&mut self,
|
||||
id: usize,
|
||||
offset: u64,
|
||||
_size: usize,
|
||||
_flags: MapFlags,
|
||||
_ctx: &CallerCtx,
|
||||
) -> syscall::Result<usize> {
|
||||
// log::trace!("KSMSG MMAP {} {:?} {} {}", id, _flags, _offset, _size);
|
||||
let (framebuffer, offset) = match self.handles.get(id)? {
|
||||
Handle::V2 {
|
||||
vt: _,
|
||||
next_id: _,
|
||||
buffers,
|
||||
} => (
|
||||
buffers
|
||||
.get(&((offset as usize / MAP_FAKE_OFFSET_MULTIPLIER) as u32))
|
||||
.ok_or(Error::new(EINVAL))
|
||||
.unwrap(),
|
||||
offset & (MAP_FAKE_OFFSET_MULTIPLIER as u64 - 1),
|
||||
),
|
||||
Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)),
|
||||
};
|
||||
let ptr = T::map_dumb_buffer(&mut self.adapter, framebuffer);
|
||||
Ok(unsafe { ptr.add(offset as usize) } as usize)
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
self.handles.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "fbbootlogd"
|
||||
description = "Boot log drawing daemon"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
drm.workspace = true
|
||||
orbclient.workspace = true
|
||||
ransid.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
console-draw = { path = "../console-draw" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
graphics-ipc = { path = "../graphics-ipc" }
|
||||
inputd = { path = "../../inputd" }
|
||||
libredox.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,115 @@
|
||||
//! Fbbootlogd renders the boot log and presents it on VT1.
|
||||
//!
|
||||
//! While fbbootlogd is superficially similar to fbcond, the major difference is:
|
||||
//!
|
||||
//! * Fbbootlogd doesn't accept input coming from the keyboard. It only allows getting written to.
|
||||
//!
|
||||
//! In the future fbbootlogd may also pull from logd as opposed to have logd push logs to it. And it
|
||||
//! it could display a boot splash like plymouth instead of a boot log when booting in quiet mode.
|
||||
|
||||
use std::ops::ControlFlow;
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use event::EventQueue;
|
||||
use inputd::ConsumerHandleEvent;
|
||||
use orbclient::Event;
|
||||
use redox_scheme::Socket;
|
||||
use scheme_utils::Blocking;
|
||||
|
||||
use crate::scheme::FbbootlogScheme;
|
||||
|
||||
mod scheme;
|
||||
|
||||
fn main() {
|
||||
daemon::SchemeDaemon::new(daemon);
|
||||
}
|
||||
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||
let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue");
|
||||
|
||||
event::user_data! {
|
||||
enum Source {
|
||||
Scheme,
|
||||
Input,
|
||||
}
|
||||
}
|
||||
|
||||
let socket = Socket::nonblock().expect("fbbootlogd: failed to create fbbootlog scheme");
|
||||
|
||||
let mut scheme = FbbootlogScheme::new();
|
||||
let mut handler = Blocking::new(&socket, 16);
|
||||
|
||||
event_queue
|
||||
.subscribe(
|
||||
socket.inner().raw(),
|
||||
Source::Scheme,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.expect("fbbootlogd: failed to subscribe to scheme events");
|
||||
|
||||
event_queue
|
||||
.subscribe(
|
||||
scheme.input_handle.event_handle().as_raw_fd() as usize,
|
||||
Source::Input,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.expect("fbbootlogd: failed to subscribe to scheme events");
|
||||
|
||||
{
|
||||
let log_fd = socket
|
||||
.create_this_scheme_fd(0, 0, 0, 0)
|
||||
.expect("fbbootlogd: failed to create log fd");
|
||||
// Add ourself as log sink
|
||||
let log_file = libredox::Fd::open(
|
||||
"/scheme/log/add_sink",
|
||||
libredox::flag::O_WRONLY | libredox::flag::O_CLOEXEC,
|
||||
0,
|
||||
)
|
||||
.expect("fbbootlogd: failed to open log/add_sink");
|
||||
log_file
|
||||
.call_wo(&log_fd.to_ne_bytes(), syscall::CallFlags::FD, &[])
|
||||
.expect("fbbootlogd: failed to send log fd to log scheme.");
|
||||
}
|
||||
|
||||
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
|
||||
|
||||
// This is not possible for now as fbbootlogd needs to open new displays at runtime for graphics
|
||||
// driver handoff. In the future inputd may directly pass a handle to the display instead.
|
||||
//libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace");
|
||||
|
||||
for event in event_queue {
|
||||
match event.expect("fbbootlogd: failed to get event").user_data {
|
||||
Source::Scheme => loop {
|
||||
match handler
|
||||
.process_requests_nonblocking(&mut scheme)
|
||||
.expect("fbbootlogd: failed to process requests")
|
||||
{
|
||||
ControlFlow::Continue(()) => {}
|
||||
ControlFlow::Break(()) => break,
|
||||
}
|
||||
},
|
||||
Source::Input => {
|
||||
let mut events = [Event::new(); 16];
|
||||
loop {
|
||||
match scheme
|
||||
.input_handle
|
||||
.read_events(&mut events)
|
||||
.expect("fbbootlogd: error while reading events")
|
||||
{
|
||||
ConsumerHandleEvent::Events(&[]) => break,
|
||||
ConsumerHandleEvent::Events(events) => {
|
||||
for event in events {
|
||||
scheme.handle_input(&event);
|
||||
}
|
||||
}
|
||||
ConsumerHandleEvent::Handoff => {
|
||||
eprintln!("fbbootlogd: handoff requested");
|
||||
scheme.handle_handoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use console_draw::{Damage, TextScreen, V2DisplayMap};
|
||||
use drm::buffer::Buffer;
|
||||
use drm::control::Device;
|
||||
use graphics_ipc::V2GraphicsHandle;
|
||||
use inputd::ConsumerHandle;
|
||||
use orbclient::{Event, EventOption};
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::{CallerCtx, OpenResult};
|
||||
use scheme_utils::FpathWriter;
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error, Result, EACCES, EBADF, EINVAL, ENOENT};
|
||||
|
||||
pub struct FbbootlogScheme {
|
||||
pub input_handle: ConsumerHandle,
|
||||
display_map: Option<V2DisplayMap>,
|
||||
text_screen: console_draw::TextScreen,
|
||||
text_buffer: console_draw::TextBuffer,
|
||||
is_scrollback: bool,
|
||||
scrollback_offset: usize,
|
||||
shift: bool,
|
||||
}
|
||||
|
||||
impl FbbootlogScheme {
|
||||
pub fn new() -> FbbootlogScheme {
|
||||
let mut scheme = FbbootlogScheme {
|
||||
input_handle: ConsumerHandle::bootlog_vt().expect("fbbootlogd: Failed to open vt"),
|
||||
display_map: None,
|
||||
text_screen: console_draw::TextScreen::new(),
|
||||
text_buffer: console_draw::TextBuffer::new(1000),
|
||||
is_scrollback: false,
|
||||
scrollback_offset: 1000,
|
||||
shift: false,
|
||||
};
|
||||
|
||||
scheme.handle_handoff();
|
||||
|
||||
scheme
|
||||
}
|
||||
|
||||
pub fn handle_handoff(&mut self) {
|
||||
let new_display_handle = match self.input_handle.open_display_v2() {
|
||||
Ok(display) => V2GraphicsHandle::from_file(display).unwrap(),
|
||||
Err(err) => {
|
||||
eprintln!("fbbootlogd: No display present yet: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match V2DisplayMap::new(new_display_handle) {
|
||||
Ok(display_map) => self.display_map = Some(display_map),
|
||||
Err(err) => {
|
||||
eprintln!("fbbootlogd: failed to open display: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!("fbbootlogd: mapped display");
|
||||
}
|
||||
|
||||
pub fn handle_input(&mut self, ev: &Event) {
|
||||
match ev.to_option() {
|
||||
EventOption::Key(key_event) => {
|
||||
if key_event.scancode == 0x2A || key_event.scancode == 0x36 {
|
||||
self.shift = key_event.pressed;
|
||||
} else if !key_event.pressed || !self.shift {
|
||||
return;
|
||||
}
|
||||
match key_event.scancode {
|
||||
0x48 => {
|
||||
// Up
|
||||
if self.scrollback_offset >= 1 {
|
||||
self.scrollback_offset -= 1;
|
||||
}
|
||||
}
|
||||
0x49 => {
|
||||
// Page up
|
||||
if self.scrollback_offset >= 10 {
|
||||
self.scrollback_offset -= 10;
|
||||
} else {
|
||||
self.scrollback_offset = 0;
|
||||
}
|
||||
}
|
||||
0x50 => {
|
||||
// Down
|
||||
self.scrollback_offset += 1;
|
||||
}
|
||||
0x51 => {
|
||||
// Page down
|
||||
self.scrollback_offset += 10;
|
||||
}
|
||||
0x47 => {
|
||||
// Home
|
||||
self.scrollback_offset = 0;
|
||||
}
|
||||
0x4F => {
|
||||
// End
|
||||
self.scrollback_offset = self.text_buffer.lines_max;
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
self.handle_scrollback_render();
|
||||
}
|
||||
|
||||
fn handle_scrollback_render(&mut self) {
|
||||
let Some(map) = &mut self.display_map else {
|
||||
return;
|
||||
};
|
||||
let buffer_len = self.text_buffer.lines.len();
|
||||
// for both extra space on wrapping text and a scrollback indicator
|
||||
let spare_lines = 3;
|
||||
self.is_scrollback = true;
|
||||
self.scrollback_offset = cmp::min(
|
||||
self.scrollback_offset,
|
||||
buffer_len - map.buffer.buffer().size().1 as usize / 16 + spare_lines,
|
||||
);
|
||||
let mut i = self.scrollback_offset;
|
||||
self.text_screen
|
||||
.write(map, b"\x1B[1;1H\x1B[2J", &mut VecDeque::new());
|
||||
|
||||
let mut total_damage = Damage::NONE;
|
||||
while i < buffer_len {
|
||||
let mut damage =
|
||||
self.text_screen
|
||||
.write(map, &self.text_buffer.lines[i][..], &mut VecDeque::new());
|
||||
i += 1;
|
||||
let yd = (damage.y + damage.height) as usize;
|
||||
if i == buffer_len || yd + spare_lines * 16 > map.buffer.buffer().size().1 as usize {
|
||||
// render until end of screen
|
||||
damage.height = map.buffer.buffer().size().1 - damage.y;
|
||||
total_damage = total_damage.merge(damage);
|
||||
self.is_scrollback = i < buffer_len;
|
||||
break;
|
||||
} else {
|
||||
total_damage = total_damage.merge(damage);
|
||||
}
|
||||
}
|
||||
map.dirty_fb(total_damage).unwrap();
|
||||
}
|
||||
|
||||
fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) {
|
||||
let mode = match map
|
||||
.display_handle
|
||||
.first_display()
|
||||
.and_then(|handle| Ok(map.display_handle.get_connector(handle, true)?.modes()[0]))
|
||||
{
|
||||
Ok(mode) => mode,
|
||||
Err(err) => {
|
||||
eprintln!("fbbootlogd: failed to get display size: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if (u32::from(mode.size().0), u32::from(mode.size().1)) != map.buffer.buffer().size() {
|
||||
match text_screen.resize(map, mode) {
|
||||
Ok(()) => eprintln!("fbbootlogd: mapped display"),
|
||||
Err(err) => {
|
||||
eprintln!("fbbootlogd: failed to create or map framebuffer: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SCHEME_ROOT_ID: usize = 1;
|
||||
|
||||
impl SchemeSync for FbbootlogScheme {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(SCHEME_ROOT_ID)
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path_str: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if dirfd != SCHEME_ROOT_ID {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
if !path_str.is_empty() {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: 0,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
||||
FpathWriter::with_legacy(buf, "fbbootlog", |_| Ok(()))
|
||||
}
|
||||
|
||||
fn fsync(&mut self, _id: usize, _ctx: &CallerCtx) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
_id: usize,
|
||||
_buf: &mut [u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &[u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
if id == SCHEME_ROOT_ID {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
if let Some(map) = &mut self.display_map {
|
||||
Self::handle_resize(map, &mut self.text_screen);
|
||||
self.text_buffer.write(buf);
|
||||
|
||||
if !self.is_scrollback {
|
||||
let damage = self.text_screen.write(map, buf, &mut VecDeque::new());
|
||||
|
||||
if let Some(map) = &mut self.display_map {
|
||||
map.dirty_fb(damage).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "fbcond"
|
||||
description = "Terminal daemon"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
drm.workspace = true
|
||||
log.workspace = true
|
||||
orbclient.workspace = true
|
||||
ransid.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
common = { path = "../../common" }
|
||||
console-draw = { path = "../console-draw" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
graphics-ipc = { path = "../graphics-ipc" }
|
||||
inputd = { path = "../../inputd" }
|
||||
libredox.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,83 @@
|
||||
use console_draw::{Damage, TextScreen, V2DisplayMap};
|
||||
use drm::buffer::Buffer;
|
||||
use drm::control::Device;
|
||||
use graphics_ipc::V2GraphicsHandle;
|
||||
use inputd::ConsumerHandle;
|
||||
use std::io;
|
||||
|
||||
pub struct Display {
|
||||
pub input_handle: ConsumerHandle,
|
||||
pub map: Option<V2DisplayMap>,
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn open_new_vt() -> io::Result<Self> {
|
||||
let mut display = Self {
|
||||
input_handle: ConsumerHandle::new_vt()?,
|
||||
map: None,
|
||||
};
|
||||
|
||||
display.reopen_for_handoff();
|
||||
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
/// Re-open the display after a handoff.
|
||||
pub fn reopen_for_handoff(&mut self) {
|
||||
let display_file = match self.input_handle.open_display_v2() {
|
||||
Ok(display_file) => display_file,
|
||||
Err(err) => {
|
||||
log::error!("fbcond: No display present yet: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap();
|
||||
|
||||
log::debug!("fbcond: Opened new display");
|
||||
|
||||
match V2DisplayMap::new(new_display_handle) {
|
||||
Ok(map) => {
|
||||
log::debug!(
|
||||
"fbcond: Mapped new display with size {}x{}",
|
||||
map.buffer.buffer().size().0,
|
||||
map.buffer.buffer().size().1,
|
||||
);
|
||||
self.map = Some(map)
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("fbcond: failed to map new display: {err}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) {
|
||||
let mode = match map
|
||||
.display_handle
|
||||
.first_display()
|
||||
.and_then(|handle| Ok(map.display_handle.get_connector(handle, true)?.modes()[0]))
|
||||
{
|
||||
Ok(mode) => mode,
|
||||
Err(err) => {
|
||||
eprintln!("fbcond: failed to get display size: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if (u32::from(mode.size().0), u32::from(mode.size().1)) != map.buffer.buffer().size() {
|
||||
match text_screen.resize(map, mode) {
|
||||
Ok(()) => eprintln!("fbcond: mapped display"),
|
||||
Err(err) => {
|
||||
eprintln!("fbcond: failed to create or map framebuffer: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_rect(&mut self, damage: Damage) {
|
||||
if let Some(map) = &mut self.map {
|
||||
map.dirty_fb(damage).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
use event::EventQueue;
|
||||
use inputd::ConsumerHandleEvent;
|
||||
use libredox::errno::{EAGAIN, EINTR};
|
||||
use orbclient::Event;
|
||||
use redox_scheme::{
|
||||
scheme::{Op, SchemeResponse, SchemeState, SchemeSync},
|
||||
CallerCtx, RequestKind, Response, SignalBehavior, Socket,
|
||||
};
|
||||
use std::env;
|
||||
use syscall::{EOPNOTSUPP, EVENT_READ};
|
||||
|
||||
use crate::scheme::{FbconScheme, Handle, VtIndex};
|
||||
|
||||
mod display;
|
||||
mod scheme;
|
||||
mod text;
|
||||
|
||||
fn main() {
|
||||
daemon::SchemeDaemon::new(daemon);
|
||||
}
|
||||
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||
let vt_ids = env::args()
|
||||
.skip(1)
|
||||
.map(|arg| arg.parse().expect("invalid vt number"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
common::setup_logging(
|
||||
"graphics",
|
||||
"fbcond",
|
||||
"fbcond",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue");
|
||||
|
||||
// FIXME listen for resize events from inputd and handle them
|
||||
|
||||
let mut socket = Socket::nonblock().expect("fbcond: failed to create fbcon scheme");
|
||||
event_queue
|
||||
.subscribe(
|
||||
socket.inner().raw(),
|
||||
VtIndex::SCHEMA_SENTINEL,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.expect("fbcond: failed to subscribe to scheme events");
|
||||
|
||||
let mut state = SchemeState::new();
|
||||
let mut scheme = FbconScheme::new(&vt_ids, &mut event_queue);
|
||||
|
||||
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
|
||||
|
||||
// This is not possible for now as fbcond needs to open new displays at runtime for graphics
|
||||
// driver handoff. In the future inputd may directly pass a handle to the display instead.
|
||||
// libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace");
|
||||
|
||||
let mut blocked = Vec::new();
|
||||
|
||||
// Handle all events that could have happened before registering with the event queue.
|
||||
handle_event(
|
||||
&mut socket,
|
||||
&mut scheme,
|
||||
&mut state,
|
||||
&mut blocked,
|
||||
VtIndex::SCHEMA_SENTINEL,
|
||||
);
|
||||
for vt_i in scheme.vts.keys().copied().collect::<Vec<_>>() {
|
||||
handle_event(&mut socket, &mut scheme, &mut state, &mut blocked, vt_i);
|
||||
}
|
||||
|
||||
for event in event_queue {
|
||||
let event = event.expect("fbcond: failed to read event from event queue");
|
||||
handle_event(
|
||||
&mut socket,
|
||||
&mut scheme,
|
||||
&mut state,
|
||||
&mut blocked,
|
||||
event.user_data,
|
||||
);
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
socket: &mut Socket,
|
||||
scheme: &mut FbconScheme,
|
||||
state: &mut SchemeState,
|
||||
blocked: &mut Vec<(Op, CallerCtx)>,
|
||||
event: VtIndex,
|
||||
) {
|
||||
match event {
|
||||
VtIndex::SCHEMA_SENTINEL => loop {
|
||||
let request = match socket.next_request(SignalBehavior::Restart) {
|
||||
Ok(Some(request)) => request,
|
||||
Ok(None) => {
|
||||
// Scheme likely got unmounted
|
||||
std::process::exit(0);
|
||||
}
|
||||
Err(err) if err.errno == EAGAIN => {
|
||||
break;
|
||||
}
|
||||
Err(err) => panic!("fbcond: failed to read display scheme: {err}"),
|
||||
};
|
||||
|
||||
match request.kind() {
|
||||
RequestKind::Call(req) => {
|
||||
let caller = req.caller();
|
||||
let mut op = match req.op() {
|
||||
Ok(op) => op,
|
||||
Err(req) => {
|
||||
let _ = socket
|
||||
.write_response(
|
||||
Response::err(EOPNOTSUPP, req),
|
||||
SignalBehavior::Restart,
|
||||
)
|
||||
.expect("fbcond: failed to write responses to fbcon scheme");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match op.handle_sync_dont_consume(&caller, scheme, state) {
|
||||
SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e))
|
||||
if libredox::error::Error::from(e).is_wouldblock()
|
||||
&& !op.is_explicitly_nonblock() =>
|
||||
{
|
||||
blocked.push((op, caller));
|
||||
}
|
||||
SchemeResponse::Regular(r) => {
|
||||
let _ = socket
|
||||
.write_response(Response::new(r, op), SignalBehavior::Restart)
|
||||
.expect("fbcond: failed to write responses to fbcon scheme");
|
||||
}
|
||||
SchemeResponse::Opened(o) => {
|
||||
let _ = socket
|
||||
.write_response(
|
||||
Response::open_dup_like(o, op),
|
||||
SignalBehavior::Restart,
|
||||
)
|
||||
.expect("fbcond: failed to write responses to fbcon scheme");
|
||||
}
|
||||
SchemeResponse::RegularAndNotifyOnDetach(status) => {
|
||||
let _ = socket
|
||||
.write_response(
|
||||
Response::new_notify_on_detach(status, op),
|
||||
SignalBehavior::Restart,
|
||||
)
|
||||
.expect("fbcond: failed to write scheme");
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestKind::OnClose { id } => {
|
||||
scheme.on_close(id);
|
||||
}
|
||||
RequestKind::Cancellation(cancellation_request) => {
|
||||
if let Some(i) = blocked
|
||||
.iter()
|
||||
.position(|(_op, caller)| caller.id == cancellation_request.id)
|
||||
{
|
||||
let (blocked_req, _) = blocked.remove(i);
|
||||
let resp = Response::err(EINTR, blocked_req);
|
||||
socket
|
||||
.write_response(resp, SignalBehavior::Restart)
|
||||
.expect("vesad: failed to write display scheme");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
vt_i => {
|
||||
let vt = scheme.vts.get_mut(&vt_i).unwrap();
|
||||
|
||||
let mut events = [Event::new(); 16];
|
||||
loop {
|
||||
match vt
|
||||
.display
|
||||
.input_handle
|
||||
.read_events(&mut events)
|
||||
.expect("fbcond: Error while reading events")
|
||||
{
|
||||
ConsumerHandleEvent::Events(&[]) => break,
|
||||
|
||||
ConsumerHandleEvent::Events(events) => {
|
||||
for event in events {
|
||||
vt.input(event)
|
||||
}
|
||||
}
|
||||
ConsumerHandleEvent::Handoff => vt.handle_handoff(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are blocked readers, try to handle them.
|
||||
{
|
||||
let mut i = 0;
|
||||
while i < blocked.len() {
|
||||
let (op, caller) = blocked
|
||||
.get_mut(i)
|
||||
.expect("vesad: Failed to get blocked request");
|
||||
let resp = match op.handle_sync_dont_consume(&caller, scheme, state) {
|
||||
SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e))
|
||||
if libredox::error::Error::from(e).is_wouldblock()
|
||||
&& !op.is_explicitly_nonblock() =>
|
||||
{
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
SchemeResponse::Regular(r) => {
|
||||
let (op, _) = blocked.remove(i);
|
||||
Response::new(r, op)
|
||||
}
|
||||
SchemeResponse::Opened(o) => {
|
||||
let (op, _) = blocked.remove(i);
|
||||
Response::open_dup_like(o, op)
|
||||
}
|
||||
SchemeResponse::RegularAndNotifyOnDetach(status) => {
|
||||
let (op, _) = blocked.remove(i);
|
||||
Response::new_notify_on_detach(status, op)
|
||||
}
|
||||
};
|
||||
let _ = socket
|
||||
.write_response(resp, SignalBehavior::Restart)
|
||||
.expect("vesad: failed to write display scheme");
|
||||
}
|
||||
}
|
||||
|
||||
for (handle_id, handle) in scheme.handles.iter_mut() {
|
||||
let handle = match handle {
|
||||
Handle::SchemeRoot => continue,
|
||||
Handle::Vt(handle) => handle,
|
||||
};
|
||||
|
||||
if !handle.events.contains(EVENT_READ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let can_read = scheme
|
||||
.vts
|
||||
.get(&handle.vt_i)
|
||||
.map_or(false, |console| console.can_read());
|
||||
|
||||
if can_read {
|
||||
if !handle.notified_read {
|
||||
handle.notified_read = true;
|
||||
let response = Response::post_fevent(*handle_id, EVENT_READ.bits());
|
||||
socket
|
||||
.write_response(response, SignalBehavior::Restart)
|
||||
.expect("fbcond: failed to write display event");
|
||||
}
|
||||
} else {
|
||||
handle.notified_read = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use event::{EventQueue, UserData};
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::{CallerCtx, OpenResult};
|
||||
use scheme_utils::{FpathWriter, HandleMap};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, ENOENT, O_NONBLOCK};
|
||||
|
||||
use crate::display::Display;
|
||||
use crate::text::TextScreen;
|
||||
|
||||
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Debug)]
|
||||
pub struct VtIndex(usize);
|
||||
|
||||
impl VtIndex {
|
||||
pub const SCHEMA_SENTINEL: VtIndex = VtIndex(usize::MAX);
|
||||
}
|
||||
|
||||
impl UserData for VtIndex {
|
||||
fn into_user_data(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn from_user_data(user_data: usize) -> Self {
|
||||
VtIndex(user_data)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FdHandle {
|
||||
pub vt_i: VtIndex,
|
||||
pub flags: usize,
|
||||
pub events: EventFlags,
|
||||
pub notified_read: bool,
|
||||
}
|
||||
|
||||
pub enum Handle {
|
||||
Vt(FdHandle),
|
||||
SchemeRoot,
|
||||
}
|
||||
|
||||
pub struct FbconScheme {
|
||||
pub vts: BTreeMap<VtIndex, TextScreen>,
|
||||
pub handles: HandleMap<Handle>,
|
||||
}
|
||||
|
||||
impl FbconScheme {
|
||||
pub fn new(vt_ids: &[usize], event_queue: &mut EventQueue<VtIndex>) -> FbconScheme {
|
||||
let mut vts = BTreeMap::new();
|
||||
|
||||
for &vt_i in vt_ids {
|
||||
let display = Display::open_new_vt().expect("Failed to open display for vt");
|
||||
event_queue
|
||||
.subscribe(
|
||||
display.input_handle.event_handle().as_raw_fd() as usize,
|
||||
VtIndex(vt_i),
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.expect("Failed to subscribe to input events for vt");
|
||||
vts.insert(VtIndex(vt_i), TextScreen::new(display));
|
||||
}
|
||||
|
||||
FbconScheme {
|
||||
vts,
|
||||
handles: HandleMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vt_handle_mut(&mut self, id: usize) -> Result<&mut FdHandle> {
|
||||
match self.handles.get_mut(id)? {
|
||||
Handle::Vt(handle) => Ok(handle),
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for FbconScheme {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(self.handles.insert(Handle::SchemeRoot))
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path_str: &str,
|
||||
flags: usize,
|
||||
fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
|
||||
let vt_i = VtIndex(path_str.parse::<usize>().map_err(|_| Error::new(ENOENT))?);
|
||||
if self.vts.contains_key(&vt_i) {
|
||||
let id = self.handles.insert(Handle::Vt(FdHandle {
|
||||
vt_i,
|
||||
flags: flags | fcntl_flags as usize,
|
||||
events: EventFlags::empty(),
|
||||
notified_read: false,
|
||||
}));
|
||||
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: id,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(ENOENT))
|
||||
}
|
||||
}
|
||||
|
||||
fn fevent(
|
||||
&mut self,
|
||||
id: usize,
|
||||
flags: syscall::EventFlags,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<syscall::EventFlags> {
|
||||
let handle = self.get_vt_handle_mut(id)?;
|
||||
|
||||
handle.notified_read = false;
|
||||
handle.events = flags;
|
||||
|
||||
Ok(syscall::EventFlags::empty())
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
||||
FpathWriter::with_legacy(buf, "fbcon", |w| {
|
||||
let handle = self.get_vt_handle_mut(id)?;
|
||||
write!(w, "{}", handle.vt_i.0).unwrap();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
|
||||
let _handle = self.get_vt_handle_mut(id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
|
||||
self.handles.get(id)?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let handle = match self.handles.get(id)? {
|
||||
Handle::Vt(handle) => Ok(handle),
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
}?;
|
||||
|
||||
if let Some(screen) = self.vts.get_mut(&handle.vt_i) {
|
||||
if !screen.can_read() {
|
||||
if handle.flags & O_NONBLOCK != 0 {
|
||||
Err(Error::new(EAGAIN))
|
||||
} else {
|
||||
Err(Error::new(EAGAIN))
|
||||
}
|
||||
} else {
|
||||
screen.read(buf)
|
||||
}
|
||||
} else {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &[u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let vt_i = self.get_vt_handle_mut(id)?.vt_i;
|
||||
|
||||
if let Some(console) = self.vts.get_mut(&vt_i) {
|
||||
console.write(buf)
|
||||
} else {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
self.handles.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use orbclient::{Event, EventOption};
|
||||
use syscall::error::*;
|
||||
|
||||
use crate::display::Display;
|
||||
|
||||
pub struct TextScreen {
|
||||
pub display: Display,
|
||||
inner: console_draw::TextScreen,
|
||||
ctrl: bool,
|
||||
input: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn new(display: Display) -> TextScreen {
|
||||
TextScreen {
|
||||
display,
|
||||
inner: console_draw::TextScreen::new(),
|
||||
ctrl: false,
|
||||
input: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_handoff(&mut self) {
|
||||
log::info!("fbcond: Performing handoff");
|
||||
self.display.reopen_for_handoff();
|
||||
}
|
||||
|
||||
pub fn input(&mut self, event: &Event) {
|
||||
let mut buf = vec![];
|
||||
|
||||
match event.to_option() {
|
||||
EventOption::Key(key_event) => {
|
||||
if key_event.scancode == 0x1D {
|
||||
self.ctrl = key_event.pressed;
|
||||
} else if key_event.pressed {
|
||||
match key_event.scancode {
|
||||
0x0E => {
|
||||
// Backspace
|
||||
buf.extend_from_slice(b"\x7F");
|
||||
}
|
||||
0x47 => {
|
||||
// Home
|
||||
buf.extend_from_slice(b"\x1B[H");
|
||||
}
|
||||
0x48 => {
|
||||
// Up
|
||||
buf.extend_from_slice(b"\x1B[A");
|
||||
}
|
||||
0x49 => {
|
||||
// Page up
|
||||
buf.extend_from_slice(b"\x1B[5~");
|
||||
}
|
||||
0x4B => {
|
||||
// Left
|
||||
buf.extend_from_slice(b"\x1B[D");
|
||||
}
|
||||
0x4D => {
|
||||
// Right
|
||||
buf.extend_from_slice(b"\x1B[C");
|
||||
}
|
||||
0x4F => {
|
||||
// End
|
||||
buf.extend_from_slice(b"\x1B[F");
|
||||
}
|
||||
0x50 => {
|
||||
// Down
|
||||
buf.extend_from_slice(b"\x1B[B");
|
||||
}
|
||||
0x51 => {
|
||||
// Page down
|
||||
buf.extend_from_slice(b"\x1B[6~");
|
||||
}
|
||||
0x52 => {
|
||||
// Insert
|
||||
buf.extend_from_slice(b"\x1B[2~");
|
||||
}
|
||||
0x53 => {
|
||||
// Delete
|
||||
buf.extend_from_slice(b"\x1B[3~");
|
||||
}
|
||||
_ => {
|
||||
let c = match key_event.character {
|
||||
c @ 'A'..='Z' if self.ctrl => ((c as u8 - b'A') + b'\x01') as char,
|
||||
c @ 'a'..='z' if self.ctrl => ((c as u8 - b'a') + b'\x01') as char,
|
||||
c => c,
|
||||
};
|
||||
|
||||
if c != '\0' {
|
||||
let mut b = [0; 4];
|
||||
buf.extend_from_slice(c.encode_utf8(&mut b).as_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (), //TODO: Mouse in terminal
|
||||
}
|
||||
|
||||
for &b in buf.iter() {
|
||||
self.input.push_back(b);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_read(&self) -> bool {
|
||||
!self.input.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
let mut i = 0;
|
||||
|
||||
while i < buf.len() && !self.input.is_empty() {
|
||||
buf[i] = self.input.pop_front().unwrap();
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(i)
|
||||
}
|
||||
|
||||
pub fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||
if let Some(map) = &mut self.display.map {
|
||||
Display::handle_resize(map, &mut self.inner);
|
||||
|
||||
let damage = self.inner.write(map, buf, &mut self.input);
|
||||
|
||||
self.display.sync_rect(damage);
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "graphics-ipc"
|
||||
description = "Shared graphics IPC code library"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
drm.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,127 @@
|
||||
use std::fs::File;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::{io, mem, ptr};
|
||||
|
||||
use drm::buffer::Buffer;
|
||||
use drm::control::connector::{self, State};
|
||||
use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};
|
||||
use drm::control::Device as _;
|
||||
use drm::{Device as _, DriverCapability};
|
||||
|
||||
/// A graphics handle using the v2 graphics API.
|
||||
///
|
||||
/// The v2 graphics API allows creating framebuffers on the fly, using them for page flipping and
|
||||
/// handles all displays using a single fd. This is basically a subset of the Linux DRM interface
|
||||
/// with a couple of custom ioctls in the place of the KMS ioctls that are missing.
|
||||
pub struct V2GraphicsHandle {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl AsFd for V2GraphicsHandle {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.file.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl drm::Device for V2GraphicsHandle {}
|
||||
impl drm::control::Device for V2GraphicsHandle {}
|
||||
|
||||
impl V2GraphicsHandle {
|
||||
pub fn from_file(file: File) -> io::Result<Self> {
|
||||
let handle = V2GraphicsHandle { file };
|
||||
assert!(handle.get_driver_capability(DriverCapability::DumbBuffer)? == 1);
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
pub fn first_display(&self) -> io::Result<connector::Handle> {
|
||||
for &connector in self.resource_handles().unwrap().connectors() {
|
||||
if self.get_connector(connector, true)?.state() == State::Connected {
|
||||
return Ok(connector);
|
||||
}
|
||||
}
|
||||
Err(io::Error::other("no connected display"))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CpuBackedBuffer {
|
||||
buffer: DumbBuffer,
|
||||
map: DumbMapping<'static>,
|
||||
shadow: Option<Box<[u8]>>,
|
||||
}
|
||||
|
||||
impl CpuBackedBuffer {
|
||||
pub fn new(
|
||||
display_handle: &V2GraphicsHandle,
|
||||
size: (u32, u32),
|
||||
format: drm::buffer::DrmFourcc,
|
||||
bpp: u32,
|
||||
) -> io::Result<CpuBackedBuffer> {
|
||||
let mut buffer = display_handle.create_dumb_buffer(size, format, bpp)?;
|
||||
|
||||
let map = display_handle.map_dumb_buffer(&mut buffer)?;
|
||||
let map = unsafe { mem::transmute::<DumbMapping<'_>, DumbMapping<'static>>(map) };
|
||||
|
||||
let shadow = if display_handle
|
||||
.get_driver_capability(DriverCapability::DumbPreferShadow)
|
||||
.unwrap_or(1)
|
||||
== 0
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(vec![0; map.len()].into_boxed_slice())
|
||||
};
|
||||
|
||||
Ok(CpuBackedBuffer {
|
||||
buffer,
|
||||
map,
|
||||
shadow,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &DumbBuffer {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
pub fn has_shadow_buf(&self) -> bool {
|
||||
self.shadow.is_some()
|
||||
}
|
||||
|
||||
pub fn shadow_buf(&mut self) -> &mut [u8] {
|
||||
self.shadow.as_deref_mut().unwrap_or(&mut *self.map)
|
||||
}
|
||||
|
||||
pub fn sync_rect(&mut self, x: u32, y: u32, width: u32, height: u32) {
|
||||
let Some(shadow) = &self.shadow else {
|
||||
return; // No shadow buffer; all writes are already propagated to the GPU.
|
||||
};
|
||||
|
||||
assert!(x.checked_add(width).unwrap() <= self.buffer.size().0);
|
||||
assert!(y.checked_add(height).unwrap() <= self.buffer.size().1);
|
||||
|
||||
let start_x: usize = x.try_into().unwrap();
|
||||
let start_y: usize = y.try_into().unwrap();
|
||||
let w: usize = width.try_into().unwrap();
|
||||
let h: usize = height.try_into().unwrap();
|
||||
|
||||
let offscreen_ptr = shadow.as_ptr().cast::<u32>();
|
||||
let onscreen_ptr = self.map.as_mut_ptr().cast::<u32>();
|
||||
|
||||
for row in start_y..start_y + h {
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
offscreen_ptr.add(row * self.buffer.pitch() as usize / 4 + start_x),
|
||||
onscreen_ptr.add(row * self.buffer.pitch() as usize / 4 + start_x),
|
||||
w,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// No need for a wbinvd to flush the write combining writes as they are
|
||||
// already flushed on the next syscall anyway. And the user will need
|
||||
// to do a DRM ioctl to actually present the changes on the display.
|
||||
}
|
||||
|
||||
pub fn destroy(self, display_handle: &V2GraphicsHandle) -> io::Result<()> {
|
||||
display_handle.destroy_dumb_buffer(self.buffer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "ihdgd"
|
||||
description = "Intel graphics driver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bitbang-hal = "0.3"
|
||||
drm-sys.workspace = true
|
||||
edid.workspace = true #TODO: edid is abandoned, fork it and maintain?
|
||||
#TODO: waiting for bitbang-hal to update to embedded-hal 1.0
|
||||
embedded-hal = { version = "0.2.7", features = ["unproven"] }
|
||||
log.workspace = true
|
||||
nb = "1.0"
|
||||
# Patched to allow for exact range allocation
|
||||
range-alloc = { git = "https://github.com/jackpot51/range-alloc.git" }
|
||||
void = "1.0"
|
||||
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
driver-graphics = { path = "../driver-graphics" }
|
||||
pcid = { path = "../../pcid" }
|
||||
|
||||
libredox.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,55 @@
|
||||
[[drivers]]
|
||||
name = "Intel HD Graphics"
|
||||
class = 0x03
|
||||
ids = { 0x8086 = [
|
||||
# Kaby Lake from Volume 4: Configurations in
|
||||
# https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/kaby-lake.html
|
||||
0x5912,
|
||||
0x5916,
|
||||
0x591B,
|
||||
0x591E,
|
||||
0x5926,
|
||||
# Comet Lake from Volume 1: Configurations in
|
||||
# https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/comet-lake.html
|
||||
0x9B21,
|
||||
0x9B41,
|
||||
0x9BA4,
|
||||
0x9BAA,
|
||||
0x9BAC,
|
||||
0x9BC4,
|
||||
0x9BC5,
|
||||
0x9BC6,
|
||||
0x9BC8,
|
||||
0x9BCA,
|
||||
0x9BCC,
|
||||
0x9BE6,
|
||||
0x9BF6,
|
||||
# Tiger Lake Mobile from Volume 4: Configurations in
|
||||
# https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/tiger-lake.html
|
||||
0x9A40,
|
||||
0x9A49,
|
||||
0x9A60,
|
||||
0x9A68,
|
||||
0x9A70,
|
||||
0x9A78,
|
||||
# Alchemist from Volume 4: Configurations in
|
||||
# https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/alchemist-arctic-sound-m.html
|
||||
0x5690, # A770M
|
||||
0x5691, # A730M
|
||||
0x5692, # A550M
|
||||
0x5693, # A370M
|
||||
0x5694, # A350M
|
||||
0x5696, # A570M
|
||||
0x5697, # A530M
|
||||
0x56A0, # A770
|
||||
0x56A1, # A750
|
||||
0x56A5, # A380
|
||||
0x56A6, # A310
|
||||
0x56B0, # Pro A30M
|
||||
0x56B1, # Pro A40/A50
|
||||
0x56B2, # Pro A60M
|
||||
0x56B3, # Pro A60
|
||||
0x56C0, # GPU Flex 170
|
||||
0x56C1, # GPU Flex 140
|
||||
] }
|
||||
command = ["ihdgd"]
|
||||
@@ -0,0 +1,169 @@
|
||||
use common::{io::Io, timeout::Timeout};
|
||||
use embedded_hal::blocking::i2c::{self, Operation, SevenBitAddress, Transactional};
|
||||
|
||||
use super::ddi::*;
|
||||
|
||||
pub struct Aux<'a> {
|
||||
ddi: &'a mut Ddi,
|
||||
}
|
||||
|
||||
impl<'a> Aux<'a> {
|
||||
pub fn new(ddi: &'a mut Ddi) -> Self {
|
||||
Self { ddi }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Transactional for Aux<'a> {
|
||||
type Error = ();
|
||||
fn exec(&mut self, addr7: SevenBitAddress, full_ops: &mut [Operation<'_>]) -> Result<(), ()> {
|
||||
// Break ops into 16-byte chunks that will fit into aux data
|
||||
let mut ops = Vec::new();
|
||||
for op in full_ops.iter_mut() {
|
||||
match op {
|
||||
Operation::Read(buf) => {
|
||||
for chunk in buf.chunks_mut(16) {
|
||||
ops.push(Operation::Read(chunk));
|
||||
}
|
||||
}
|
||||
Operation::Write(buf) => {
|
||||
for chunk in buf.chunks(16) {
|
||||
ops.push(Operation::Write(chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ops_len = ops.len();
|
||||
for (i, op) in ops.iter_mut().enumerate() {
|
||||
// Write header and data
|
||||
let mut header = 0;
|
||||
match op {
|
||||
Operation::Read(_) => {
|
||||
header |= 1 << 4;
|
||||
}
|
||||
Operation::Write(_) => (),
|
||||
}
|
||||
if (i + 1) < ops_len {
|
||||
// Middle of transaction
|
||||
header |= 1 << 6;
|
||||
}
|
||||
let mut aux_datas = [0u8; 20];
|
||||
let mut aux_data_i = 0;
|
||||
aux_datas[aux_data_i] = header;
|
||||
aux_data_i += 1;
|
||||
//TODO: what is this byte?
|
||||
aux_datas[aux_data_i] = 0;
|
||||
aux_data_i += 1;
|
||||
aux_datas[aux_data_i] = addr7;
|
||||
aux_data_i += 1;
|
||||
match op {
|
||||
Operation::Read(buf) => {
|
||||
if !buf.is_empty() {
|
||||
aux_datas[aux_data_i] = (buf.len() - 1) as u8;
|
||||
aux_data_i += 1;
|
||||
}
|
||||
}
|
||||
Operation::Write(buf) => {
|
||||
if !buf.is_empty() {
|
||||
aux_datas[aux_data_i] = (buf.len() - 1) as u8;
|
||||
aux_data_i += 1;
|
||||
for b in buf.iter() {
|
||||
aux_datas[aux_data_i] = *b;
|
||||
aux_data_i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write data to registers (big endian, dword access only)
|
||||
for (i, chunk) in aux_datas.chunks(4).enumerate() {
|
||||
let mut bytes = [0; 4];
|
||||
bytes[..chunk.len()].copy_from_slice(&chunk);
|
||||
self.ddi.aux_datas[i].write(u32::from_be_bytes(bytes));
|
||||
}
|
||||
|
||||
let mut v = self.ddi.aux_ctl.read();
|
||||
// Set length
|
||||
v &= !DDI_AUX_CTL_SIZE_MASK;
|
||||
v |= (aux_data_i as u32) << DDI_AUX_CTL_SIZE_SHIFT;
|
||||
// Set timeout
|
||||
v &= !DDI_AUX_CTL_TIMEOUT_MASK;
|
||||
v |= DDI_AUX_CTL_TIMEOUT_4000US;
|
||||
// Set I/O select to legacy (cleared)
|
||||
//TODO: TBT support?
|
||||
v &= !DDI_AUX_CTL_IO_SELECT;
|
||||
// Start transaction
|
||||
v |= DDI_AUX_CTL_BUSY;
|
||||
self.ddi.aux_ctl.write(v);
|
||||
|
||||
// Wait while busy
|
||||
let timeout = Timeout::from_secs(1);
|
||||
while self.ddi.aux_ctl.readf(DDI_AUX_CTL_BUSY) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::debug!(
|
||||
"AUX I2C transaction wait timeout 0x{:08X}",
|
||||
self.ddi.aux_ctl.read()
|
||||
);
|
||||
()
|
||||
})?;
|
||||
}
|
||||
|
||||
// Read result
|
||||
v = self.ddi.aux_ctl.read();
|
||||
if (v & DDI_AUX_CTL_TIMEOUT_ERROR) != 0 {
|
||||
log::debug!("AUX I2C transaction timeout error");
|
||||
return Err(());
|
||||
}
|
||||
if (v & DDI_AUX_CTL_RECEIVE_ERROR) != 0 {
|
||||
log::debug!("AUX I2C transaction receive error");
|
||||
return Err(());
|
||||
}
|
||||
if (v & DDI_AUX_CTL_DONE) == 0 {
|
||||
log::debug!("AUX I2C transaction done not set");
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// Read data from registers (big endian, dword access only)
|
||||
for (i, chunk) in aux_datas.chunks_mut(4).enumerate() {
|
||||
let bytes = self.ddi.aux_datas[i].read().to_be_bytes();
|
||||
chunk.copy_from_slice(&bytes[..chunk.len()]);
|
||||
}
|
||||
|
||||
aux_data_i = 0;
|
||||
let response = aux_datas[aux_data_i];
|
||||
if response != 0 {
|
||||
log::debug!("AUX I2C unexpected response {:02X}", response);
|
||||
return Err(());
|
||||
}
|
||||
aux_data_i += 1;
|
||||
match op {
|
||||
Operation::Read(buf) => {
|
||||
if !buf.is_empty() {
|
||||
for b in buf.iter_mut() {
|
||||
*b = aux_datas[aux_data_i];
|
||||
aux_data_i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Operation::Write(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> i2c::WriteRead for Aux<'a> {
|
||||
type Error = ();
|
||||
fn write_read(
|
||||
&mut self,
|
||||
addr7: SevenBitAddress,
|
||||
bytes: &[u8],
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), ()> {
|
||||
self.exec(
|
||||
addr7,
|
||||
&mut [Operation::Write(bytes), Operation::Read(buffer)],
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
use common::io::{Io, Mmio, ReadOnly};
|
||||
use std::mem;
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use super::MmioRegion;
|
||||
|
||||
const MBOX_VBT: u32 = 1 << 3;
|
||||
|
||||
fn read_bios_string(array: &[ReadOnly<Mmio<u8>>]) -> String {
|
||||
let mut string = String::new();
|
||||
for reg in array.iter() {
|
||||
let b = reg.read();
|
||||
if b == 0 {
|
||||
break;
|
||||
}
|
||||
string.push(b as char);
|
||||
}
|
||||
string
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct BiosHeader {
|
||||
signature: [ReadOnly<Mmio<u8>>; 16],
|
||||
size: ReadOnly<Mmio<u32>>,
|
||||
struct_version: ReadOnly<Mmio<u32>>,
|
||||
system_bios_version: [ReadOnly<Mmio<u8>>; 32],
|
||||
video_bios_version: [ReadOnly<Mmio<u8>>; 16],
|
||||
//TODO: should we write graphics driver version?
|
||||
graphics_driver_version: [ReadOnly<Mmio<u8>>; 16],
|
||||
mailboxes: ReadOnly<Mmio<u32>>,
|
||||
driver_model: Mmio<u32>,
|
||||
platform_config: ReadOnly<Mmio<u32>>,
|
||||
gop_version: [ReadOnly<Mmio<u8>>; 32],
|
||||
}
|
||||
|
||||
impl BiosHeader {
|
||||
pub fn dump(&self) {
|
||||
eprint!(" op region header");
|
||||
eprint!(" signature {:?}", read_bios_string(&self.signature));
|
||||
eprint!(" size {:08X}", self.size.read());
|
||||
eprint!(" struct_version {:08X}", self.struct_version.read());
|
||||
eprint!(
|
||||
" system_bios_version {:?}",
|
||||
read_bios_string(&self.system_bios_version)
|
||||
);
|
||||
eprint!(
|
||||
" video_bios_version {:?}",
|
||||
read_bios_string(&self.video_bios_version)
|
||||
);
|
||||
eprint!(
|
||||
" graphics_driver_version {:?}",
|
||||
read_bios_string(&self.graphics_driver_version)
|
||||
);
|
||||
eprint!(" mailboxes {:08X}", self.mailboxes.read());
|
||||
eprint!(" driver_model {:08X}", self.driver_model.read());
|
||||
eprint!(" platform_config {:08X}", self.platform_config.read());
|
||||
eprint!(" gop_version {:?}", read_bios_string(&self.gop_version));
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct VbtHeader {
|
||||
signature: [ReadOnly<Mmio<u8>>; 20],
|
||||
version: Mmio<u16>,
|
||||
header_size: Mmio<u16>,
|
||||
vbt_size: Mmio<u16>,
|
||||
vbt_checksum: Mmio<u8>,
|
||||
_rsvd: Mmio<u8>,
|
||||
bdb_offset: Mmio<u32>,
|
||||
aim_offsets: [Mmio<u32>; 4],
|
||||
}
|
||||
|
||||
impl VbtHeader {
|
||||
pub fn dump(&self) {
|
||||
eprint!(" VBT header");
|
||||
eprint!(" signature {:?}", read_bios_string(&self.signature));
|
||||
eprint!(" version {:04X}", self.version.read());
|
||||
eprint!(" header_size {:04X}", self.header_size.read());
|
||||
eprint!(" vbt_size {:04X}", self.vbt_size.read());
|
||||
eprint!(" vbt_checksum {:02X}", self.vbt_checksum.read());
|
||||
eprint!(" bdb_offset {:08X}", self.bdb_offset.read());
|
||||
for (i, aim_offset) in self.aim_offsets.iter().enumerate() {
|
||||
eprint!(" aim_offset{} {:08X}", i, aim_offset.read());
|
||||
}
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct BdbHeader {
|
||||
signature: [ReadOnly<Mmio<u8>>; 16],
|
||||
version: Mmio<u16>,
|
||||
header_size: Mmio<u16>,
|
||||
bdb_size: Mmio<u16>,
|
||||
}
|
||||
|
||||
impl BdbHeader {
|
||||
pub fn dump(&self) {
|
||||
eprint!(" BDB header");
|
||||
eprint!(" signature {:?}", read_bios_string(&self.signature));
|
||||
eprint!(" version {:04X}", self.version.read());
|
||||
eprint!(" header_size {:04X}", self.header_size.read());
|
||||
eprint!(" bdb_size {:04X}", self.bdb_size.read());
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct BdbBlock {
|
||||
id: Mmio<u8>,
|
||||
size: Mmio<u16>,
|
||||
}
|
||||
|
||||
impl BdbBlock {
|
||||
pub fn dump(&self) {
|
||||
eprint!(" BDB block");
|
||||
eprint!(" id {}", self.id.read());
|
||||
eprint!(" size {}", self.size.read());
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct BdbGeneralDefinitions {
|
||||
crt_ddc_gmbus_pin: Mmio<u8>,
|
||||
dpms: Mmio<u8>,
|
||||
boot_displays: [Mmio<u8>; 2],
|
||||
child_dev_size: Mmio<u8>,
|
||||
}
|
||||
|
||||
impl BdbGeneralDefinitions {
|
||||
pub fn dump(&self) {
|
||||
eprint!(" BDB general definitions");
|
||||
eprint!(" crt_ddc_gmbus_pin {:02X}", self.crt_ddc_gmbus_pin.read());
|
||||
eprint!(" dpms {:02X}", self.dpms.read());
|
||||
for (i, boot_display) in self.boot_displays.iter().enumerate() {
|
||||
eprint!(" boot_display{} {:02X}", i, boot_display.read());
|
||||
}
|
||||
eprint!(" child_dev_size {:02X}", self.child_dev_size.read());
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bios {
|
||||
region: MmioRegion,
|
||||
header: &'static mut BiosHeader,
|
||||
}
|
||||
|
||||
impl Bios {
|
||||
pub fn new(region: MmioRegion) -> Result<Self> {
|
||||
let header = unsafe { &mut *(region.virt as *mut BiosHeader) };
|
||||
header.dump();
|
||||
|
||||
{
|
||||
let sig = read_bios_string(&header.signature);
|
||||
if sig != "IntelGraphicsMem" {
|
||||
log::warn!("invalid op region signature {:?}", sig);
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
|
||||
let size = (header.size.read() as usize) * 1024;
|
||||
if size != region.size {
|
||||
log::warn!("invalid op region size {}", size);
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
|
||||
//TODO: other mailboxes?
|
||||
|
||||
if header.mailboxes.read() & MBOX_VBT == 0 {
|
||||
log::warn!("op region does not support VBT mailbox");
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
|
||||
//TODO: read VBT from mailbox 3 RVDA (0x3BA) and RVDS (0x3C2) if missing in mailbox 4
|
||||
let vbt_addr = region.virt + 1024;
|
||||
let vbt_header = unsafe { &*(vbt_addr as *const VbtHeader) };
|
||||
vbt_header.dump();
|
||||
|
||||
//TODO: check vbt checksum
|
||||
{
|
||||
let sig = read_bios_string(&vbt_header.signature);
|
||||
if !sig.starts_with("$VBT") {
|
||||
log::warn!("invalid VBT signature {:?}", sig);
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
|
||||
let bdb_addr = vbt_addr + (vbt_header.bdb_offset.read() as usize);
|
||||
let bdb_header = unsafe { &*(bdb_addr as *const BdbHeader) };
|
||||
bdb_header.dump();
|
||||
{
|
||||
let sig = read_bios_string(&bdb_header.signature);
|
||||
if sig != "BIOS_DATA_BLOCK " {
|
||||
log::warn!("invalid BDB signature {:?}", sig);
|
||||
bdb_header.dump();
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
|
||||
let mut block_addr = bdb_addr + bdb_header.header_size.read() as usize;
|
||||
let block_end = bdb_addr + bdb_header.bdb_size.read() as usize;
|
||||
while block_addr + mem::size_of::<BdbBlock>() <= block_end {
|
||||
let block = unsafe { &*(block_addr as *const BdbBlock) };
|
||||
//TODO: mipi sequence v3 has different size field
|
||||
let id = block.id.read();
|
||||
let size = block.size.read() as usize;
|
||||
block_addr += mem::size_of::<BdbBlock>();
|
||||
if block_addr + size <= block_end {
|
||||
match id {
|
||||
2 => {
|
||||
if size >= mem::size_of::<BdbGeneralDefinitions>() {
|
||||
let gen_defs =
|
||||
unsafe { &*(block_addr as *const BdbGeneralDefinitions) };
|
||||
gen_defs.dump();
|
||||
} else {
|
||||
log::warn!("BDB general definitions too small");
|
||||
block.dump();
|
||||
}
|
||||
}
|
||||
_ => block.dump(),
|
||||
}
|
||||
block_addr += block.size.read() as usize;
|
||||
} else {
|
||||
log::warn!("truncated block id {} size {}", id, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { region, header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use std::{ptr, slice};
|
||||
|
||||
use crate::device::ggtt::GlobalGtt;
|
||||
use crate::device::MmioRegion;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GpuBuffer {
|
||||
pub virt: *mut u8,
|
||||
pub gm_offset: u32,
|
||||
pub size: u32,
|
||||
}
|
||||
|
||||
impl GpuBuffer {
|
||||
pub unsafe fn new(gm: &MmioRegion, gm_offset: u32, size: u32, clear: bool) -> Self {
|
||||
let virt = ptr::with_exposed_provenance_mut::<u8>(gm.virt + gm_offset as usize);
|
||||
|
||||
if clear {
|
||||
let onscreen = slice::from_raw_parts_mut(virt, size as usize);
|
||||
onscreen.fill(0);
|
||||
}
|
||||
|
||||
Self {
|
||||
virt,
|
||||
gm_offset,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc(gm: &MmioRegion, ggtt: &mut GlobalGtt, size: u32) -> syscall::Result<Self> {
|
||||
let gm_offset = ggtt.alloc_phys_mem(size)?;
|
||||
|
||||
Ok(unsafe { GpuBuffer::new(gm, gm_offset, size, true) })
|
||||
}
|
||||
|
||||
pub fn alloc_dumb(
|
||||
gm: &MmioRegion,
|
||||
ggtt: &mut GlobalGtt,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> syscall::Result<(Self, u32)> {
|
||||
//TODO: documentation on this is not great
|
||||
let stride = (width * 4).next_multiple_of(64);
|
||||
|
||||
Ok((GpuBuffer::alloc(gm, ggtt, stride * height)?, stride))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
use common::io::{Io, MmioPtr, WriteOnly};
|
||||
use common::timeout::Timeout;
|
||||
use embedded_hal::prelude::*;
|
||||
use std::sync::Arc;
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use crate::device::aux::Aux;
|
||||
use crate::device::power::PowerWells;
|
||||
use crate::device::{CallbackGuard, Gmbus};
|
||||
|
||||
use super::{GpioPort, MmioRegion};
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DDI_AUX_CTL
|
||||
pub const DDI_AUX_CTL_BUSY: u32 = 1 << 31;
|
||||
pub const DDI_AUX_CTL_DONE: u32 = 1 << 30;
|
||||
pub const DDI_AUX_CTL_TIMEOUT_ERROR: u32 = 1 << 28;
|
||||
pub const DDI_AUX_CTL_TIMEOUT_SHIFT: u32 = 26;
|
||||
pub const DDI_AUX_CTL_TIMEOUT_MASK: u32 = 0b11 << DDI_AUX_CTL_TIMEOUT_SHIFT;
|
||||
pub const DDI_AUX_CTL_TIMEOUT_4000US: u32 = 0b11 << DDI_AUX_CTL_TIMEOUT_SHIFT;
|
||||
pub const DDI_AUX_CTL_RECEIVE_ERROR: u32 = 1 << 25;
|
||||
pub const DDI_AUX_CTL_SIZE_SHIFT: u32 = 20;
|
||||
pub const DDI_AUX_CTL_SIZE_MASK: u32 = 0b11111 << 20;
|
||||
pub const DDI_AUX_CTL_IO_SELECT: u32 = 1 << 11;
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DDI_BUF_CTL
|
||||
pub const DDI_BUF_CTL_ENABLE: u32 = 1 << 31;
|
||||
pub const DDI_BUF_CTL_IDLE: u32 = 1 << 7;
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PORT_CL_DW5
|
||||
pub const PORT_CL_DW5_SUS_CLOCK_MASK: u32 = 0b11 << 0;
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PORT_CL_DW10
|
||||
pub const PORT_CL_DW10_EDP4K2K_MODE_OVRD_EN: u32 = 1 << 3;
|
||||
pub const PORT_CL_DW10_EDP4K2K_MODE_OVRD_VAL: u32 = 1 << 2;
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PORT_PCS_DW9
|
||||
pub const PORT_PCS_DW1_CMNKEEPER_ENABLE: u32 = 1 << 26;
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PORT_TX_DW2
|
||||
pub const PORT_TX_DW2_SWING_SEL_UPPER_SHIFT: u32 = 15;
|
||||
pub const PORT_TX_DW2_SWING_SEL_UPPER_MASK: u32 = 1 << PORT_TX_DW2_SWING_SEL_UPPER_SHIFT;
|
||||
pub const PORT_TX_DW2_SWING_SEL_LOWER_SHIFT: u32 = 11;
|
||||
pub const PORT_TX_DW2_SWING_SEL_LOWER_MASK: u32 = 0b111 << PORT_TX_DW2_SWING_SEL_LOWER_SHIFT;
|
||||
pub const PORT_TX_DW2_RCOMP_SCALAR_SHIFT: u32 = 0;
|
||||
pub const PORT_TX_DW2_RCOMP_SCALAR_MASK: u32 = 0xFF << PORT_TX_DW2_RCOMP_SCALAR_SHIFT;
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PORT_TX_DW4
|
||||
pub const PORT_TX_DW4_SELECT: u32 = 1 << 31;
|
||||
pub const PORT_TX_DW4_POST_CURSOR_1_SHIFT: u32 = 12;
|
||||
pub const PORT_TX_DW4_POST_CURSOR_1_MASK: u32 = 0b111111 << PORT_TX_DW4_POST_CURSOR_1_SHIFT;
|
||||
pub const PORT_TX_DW4_POST_CURSOR_2_SHIFT: u32 = 6;
|
||||
pub const PORT_TX_DW4_POST_CURSOR_2_MASK: u32 = 0b111111 << PORT_TX_DW4_POST_CURSOR_2_SHIFT;
|
||||
pub const PORT_TX_DW4_CURSOR_COEFF_SHIFT: u32 = 0;
|
||||
pub const PORT_TX_DW4_CURSOR_COEFF_MASK: u32 = 0b111111 << PORT_TX_DW4_CURSOR_COEFF_SHIFT;
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PORT_TX_DW5
|
||||
pub const PORT_TX_DW5_TRAINING_ENABLE: u32 = 1 << 31;
|
||||
pub const PORT_TX_DW5_DISABLE_2_TAP_SHIFT: u32 = 29;
|
||||
pub const PORT_TX_DW5_DISABLE_2_TAP: u32 = 1 << PORT_TX_DW5_DISABLE_2_TAP_SHIFT;
|
||||
pub const PORT_TX_DW5_DISABLE_3_TAP: u32 = 1 << 29;
|
||||
pub const PORT_TX_DW5_CURSOR_PROGRAM: u32 = 1 << 26;
|
||||
pub const PORT_TX_DW5_COEFF_POLARITY: u32 = 1 << 25;
|
||||
pub const PORT_TX_DW5_SCALING_MODE_SEL_SHIFT: u32 = 18;
|
||||
pub const PORT_TX_DW5_SCALING_MODE_SEL_MASK: u32 = 0b111 << PORT_TX_DW5_SCALING_MODE_SEL_SHIFT;
|
||||
pub const PORT_TX_DW5_RTERM_SELECT_SHIFT: u32 = 3;
|
||||
pub const PORT_TX_DW5_RTERM_SELECT_MASK: u32 = 0b111 << PORT_TX_DW5_RTERM_SELECT_SHIFT;
|
||||
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PORT_TX_DW7
|
||||
pub const PORT_TX_DW7_N_SCALAR_SHIFT: u32 = 24;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(usize)]
|
||||
pub enum PortClReg {
|
||||
Dw5 = 0x14,
|
||||
Dw10 = 0x28,
|
||||
Dw12 = 0x30,
|
||||
Dw15 = 0x3C,
|
||||
Dw16 = 0x40,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(usize)]
|
||||
pub enum PortCompReg {
|
||||
Dw0 = 0x100,
|
||||
Dw1 = 0x104,
|
||||
Dw3 = 0x10C,
|
||||
Dw8 = 0x120,
|
||||
Dw9 = 0x124,
|
||||
Dw10 = 0x128,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(usize)]
|
||||
pub enum PortPcsReg {
|
||||
Dw1 = 0x04,
|
||||
Dw9 = 0x24,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(usize)]
|
||||
pub enum PortTxReg {
|
||||
Dw0 = 0x80,
|
||||
Dw1 = 0x84,
|
||||
Dw2 = 0x88,
|
||||
Dw4 = 0x90,
|
||||
Dw5 = 0x94,
|
||||
Dw6 = 0x98,
|
||||
Dw7 = 0x9C,
|
||||
Dw8 = 0xA0,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(usize)]
|
||||
pub enum PortLane {
|
||||
Aux = 0x300,
|
||||
Grp = 0x600,
|
||||
Ln0 = 0x800,
|
||||
Ln1 = 0x900,
|
||||
Ln2 = 0xA00,
|
||||
Ln3 = 0xB00,
|
||||
}
|
||||
|
||||
pub struct Ddi {
|
||||
pub name: &'static str,
|
||||
pub index: usize,
|
||||
pub gttmm: Arc<MmioRegion>,
|
||||
pub port_base: Option<usize>,
|
||||
pub aux_ctl: MmioPtr<u32>,
|
||||
pub aux_datas: [MmioPtr<u32>; 5],
|
||||
pub buf_ctl: MmioPtr<u32>,
|
||||
pub dpclka_cfgcr0_clock_shift: Option<u32>,
|
||||
pub dpclka_cfgcr0_clock_off: Option<u32>,
|
||||
pub gmbus_pin_pair: Option<u8>,
|
||||
pub gpio_port: Option<GpioPort>,
|
||||
pub pwr_well_ctl_aux_request: u32,
|
||||
pub pwr_well_ctl_aux_state: u32,
|
||||
pub pwr_well_ctl_ddi_request: u32,
|
||||
pub pwr_well_ctl_ddi_state: u32,
|
||||
pub sde_interrupt_hotplug: Option<u32>,
|
||||
pub transcoder_index: Option<u32>,
|
||||
}
|
||||
|
||||
//TODO: verify offsets and count using DeviceKind?
|
||||
impl Ddi {
|
||||
pub fn dump(&self) {
|
||||
eprint!("Ddi {} {}", self.name, self.index);
|
||||
eprint!(" buf_ctl {:08X}", self.buf_ctl.read());
|
||||
let lanes = [PortLane::Ln0, PortLane::Ln1, PortLane::Ln2, PortLane::Ln3];
|
||||
for reg in [
|
||||
PortClReg::Dw5,
|
||||
PortClReg::Dw10,
|
||||
PortClReg::Dw12,
|
||||
PortClReg::Dw15,
|
||||
PortClReg::Dw16,
|
||||
] {
|
||||
if let Some(mmio) = self.port_cl(reg) {
|
||||
eprint!(" CL_{:?} {:08X}", reg, mmio.read());
|
||||
}
|
||||
}
|
||||
for reg in [PortPcsReg::Dw1, PortPcsReg::Dw9] {
|
||||
for lane in lanes {
|
||||
if let Some(mmio) = self.port_pcs(reg, lane) {
|
||||
eprint!(" PCS_{:?}_{:?} {:08X}", reg, lane, mmio.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
for reg in [
|
||||
PortTxReg::Dw0,
|
||||
PortTxReg::Dw1,
|
||||
PortTxReg::Dw2,
|
||||
PortTxReg::Dw4,
|
||||
PortTxReg::Dw5,
|
||||
PortTxReg::Dw6,
|
||||
PortTxReg::Dw7,
|
||||
PortTxReg::Dw8,
|
||||
] {
|
||||
for lane in lanes {
|
||||
if let Some(mmio) = self.port_tx(reg, lane) {
|
||||
eprint!(" TX_{:?}_{:?} {:08X}", reg, lane, mmio.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!();
|
||||
}
|
||||
|
||||
fn port_reg(&self, offset: usize) -> Option<MmioPtr<u32>> {
|
||||
//TODO: handle gttmm.mmio error?
|
||||
unsafe { self.gttmm.mmio(self.port_base? + offset).ok() }
|
||||
}
|
||||
|
||||
pub fn port_cl(&self, reg: PortClReg) -> Option<MmioPtr<u32>> {
|
||||
self.port_reg(reg as usize)
|
||||
}
|
||||
|
||||
pub fn port_comp(&self, reg: PortCompReg) -> Option<MmioPtr<u32>> {
|
||||
self.port_reg(reg as usize)
|
||||
}
|
||||
|
||||
//TODO: return WriteOnly if PortLane::Grp?
|
||||
pub fn port_pcs(&self, reg: PortPcsReg, lane: PortLane) -> Option<MmioPtr<u32>> {
|
||||
self.port_reg((reg as usize) + (lane as usize))
|
||||
}
|
||||
|
||||
//TODO: return WriteOnly if PortLane::Grp?
|
||||
pub fn port_tx(&self, reg: PortTxReg, lane: PortLane) -> Option<MmioPtr<u32>> {
|
||||
self.port_reg((reg as usize) + (lane as usize))
|
||||
}
|
||||
|
||||
pub fn probe_edid(
|
||||
&mut self,
|
||||
power_wells: &mut PowerWells,
|
||||
gttmm: &MmioRegion,
|
||||
gmbus: &mut Gmbus,
|
||||
) -> Result<Option<(&'static str, [u8; 128])>, Error> {
|
||||
if let Some(port_comp_dw0) = self.port_comp(PortCompReg::Dw0) {
|
||||
log::debug!("PORT_COMP_DW0_{}: {:08X}", self.name, port_comp_dw0.read());
|
||||
}
|
||||
let mut aux_read_edid = |ddi: &mut Ddi| -> Result<[u8; 128]> {
|
||||
//TODO: BLOCK TCCOLD?
|
||||
|
||||
//TODO: the request can be shared by multiple DDIs
|
||||
let pwr_well_ctl_aux_request = ddi.pwr_well_ctl_aux_request;
|
||||
let pwr_well_ctl_aux_state = ddi.pwr_well_ctl_aux_state;
|
||||
let mut pwr_well_ctl_aux = unsafe { MmioPtr::new(power_wells.ctl_aux.as_mut_ptr()) };
|
||||
let _pwr_guard = CallbackGuard::new(
|
||||
&mut pwr_well_ctl_aux,
|
||||
|pwr_well_ctl_aux| {
|
||||
// Enable aux power
|
||||
pwr_well_ctl_aux.writef(pwr_well_ctl_aux_request, true);
|
||||
let timeout = Timeout::from_micros(1500);
|
||||
while !pwr_well_ctl_aux.readf(pwr_well_ctl_aux_state) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::debug!("timeout while requesting DDI {} aux power", ddi.name);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|pwr_well_ctl_aux| {
|
||||
// Disable aux power
|
||||
pwr_well_ctl_aux.writef(pwr_well_ctl_aux_request, false);
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut edid_data = [0; 128];
|
||||
Aux::new(ddi)
|
||||
.write_read(0x50, &[0x00], &mut edid_data)
|
||||
.map_err(|_err| Error::new(EIO))?;
|
||||
|
||||
Ok(edid_data)
|
||||
};
|
||||
let mut gmbus_read_edid = |ddi: &mut Ddi| -> Result<[u8; 128]> {
|
||||
let Some(pin_pair) = ddi.gmbus_pin_pair else {
|
||||
return Err(Error::new(EIO));
|
||||
};
|
||||
|
||||
let mut edid_data = [0; 128];
|
||||
gmbus
|
||||
.pin_pair(pin_pair)
|
||||
.write_read(0x50, &[0x00], &mut edid_data)
|
||||
.map_err(|_err| Error::new(EIO))?;
|
||||
|
||||
Ok(edid_data)
|
||||
};
|
||||
let gpio_read_edid = |ddi: &mut Ddi| -> Result<[u8; 128]> {
|
||||
let Some(port) = &ddi.gpio_port else {
|
||||
return Err(Error::new(EIO));
|
||||
};
|
||||
|
||||
let mut edid_data = [0; 128];
|
||||
unsafe { port.i2c(gttmm)? }
|
||||
.write_read(0x50, &[0x00], &mut edid_data)
|
||||
.map_err(|_err| Error::new(EIO))?;
|
||||
|
||||
Ok(edid_data)
|
||||
};
|
||||
match aux_read_edid(self) {
|
||||
Ok(edid_data) => return Ok(Some(("AUX", edid_data))),
|
||||
Err(err) => {
|
||||
log::debug!("DDI {} failed to read EDID from AUX: {}", self.name, err);
|
||||
}
|
||||
}
|
||||
match gmbus_read_edid(self) {
|
||||
Ok(edid_data) => return Ok(Some(("GMBUS", edid_data))),
|
||||
Err(err) => {
|
||||
log::debug!("DDI {} failed to read EDID from GMBUS: {}", self.name, err);
|
||||
}
|
||||
}
|
||||
match gpio_read_edid(self) {
|
||||
Ok(edid_data) => return Ok(Some(("GPIO", edid_data))),
|
||||
Err(err) => {
|
||||
log::debug!("DDI {} failed to read EDID from GPIO: {}", self.name, err);
|
||||
}
|
||||
}
|
||||
// Will try again but not fail the driver
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn voltage_swing_hdmi(
|
||||
&mut self,
|
||||
gttmm: &MmioRegion,
|
||||
timing: &edid::DetailedTiming,
|
||||
) -> Result<()> {
|
||||
struct Setting {
|
||||
dw2_swing_sel: u32,
|
||||
dw7_n_scalar: u32,
|
||||
dw4_cursor_coeff: u32,
|
||||
dw4_post_cursor_1: u32,
|
||||
dw5_2_tap_disable: u32,
|
||||
}
|
||||
|
||||
impl Setting {
|
||||
pub fn new(
|
||||
dw2_swing_sel: u32,
|
||||
dw7_n_scalar: u32,
|
||||
dw4_cursor_coeff: u32,
|
||||
dw4_post_cursor_1: u32,
|
||||
dw5_2_tap_disable: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
dw2_swing_sel,
|
||||
dw7_n_scalar,
|
||||
dw4_cursor_coeff,
|
||||
dw4_post_cursor_1,
|
||||
dw5_2_tap_disable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IHD-OS-TGL-Vol 12-1.22-Rev2.0 "Voltage Swing Programming"
|
||||
let settings = vec![
|
||||
// HDMI 450mV, 450mV, 0.0dB
|
||||
Setting::new(0b1010, 0x60, 0x3F, 0x00, 0b0),
|
||||
// HDMI 450mV, 650mV, 3.2dB
|
||||
Setting::new(0b1011, 0x73, 0x36, 0x09, 0b0),
|
||||
// HDMI 450mV, 850mV, 5.5dB
|
||||
Setting::new(0b0110, 0x7F, 0x31, 0x0E, 0b0),
|
||||
// HDMI 650mV, 650mV, 0.0dB
|
||||
Setting::new(0b1011, 0x73, 0x3F, 0x00, 0b0),
|
||||
// HDMI 650mV, 850mV, 2.3dB
|
||||
Setting::new(0b0110, 0x7F, 0x37, 0x08, 0b0),
|
||||
// HDMI 850mV, 850mV, 0.0dB
|
||||
Setting::new(0b0110, 0x7F, 0x3F, 0x00, 0b0),
|
||||
// HDMI 600mV, 850mV, 3.0dB
|
||||
Setting::new(0b0110, 0x7F, 0x35, 0x0A, 0b0),
|
||||
];
|
||||
|
||||
// Last setting is the default
|
||||
//TODO: get correct setting index from BIOS
|
||||
let setting = settings.last().unwrap();
|
||||
|
||||
// This allows unwraps on port functions below without panic
|
||||
if self.port_base.is_none() {
|
||||
log::error!("HDMI voltage swing procedure only implemented on combo DDI");
|
||||
return Err(Error::new(EIO));
|
||||
};
|
||||
|
||||
// Clear cmnkeeper_enable for HDMI
|
||||
{
|
||||
// It is not possible to read from GRP register, so use LN0 as template
|
||||
let pcs_dw1_ln0 = self.port_pcs(PortPcsReg::Dw1, PortLane::Ln0).unwrap();
|
||||
let mut pcs_dw1_grp =
|
||||
WriteOnly::new(self.port_pcs(PortPcsReg::Dw1, PortLane::Grp).unwrap());
|
||||
let mut v = pcs_dw1_ln0.read();
|
||||
v &= !PORT_PCS_DW1_CMNKEEPER_ENABLE;
|
||||
pcs_dw1_grp.write(v);
|
||||
}
|
||||
|
||||
// Program loadgen select
|
||||
//TODO: this assumes bit rate <= 6 GHz and 4 lanes enabled
|
||||
{
|
||||
let mut tx_dw4_ln0 = self.port_tx(PortTxReg::Dw4, PortLane::Ln0).unwrap();
|
||||
tx_dw4_ln0.writef(PORT_TX_DW4_SELECT, false);
|
||||
|
||||
let mut tx_dw4_ln1 = self.port_tx(PortTxReg::Dw4, PortLane::Ln1).unwrap();
|
||||
tx_dw4_ln1.writef(PORT_TX_DW4_SELECT, true);
|
||||
|
||||
let mut tx_dw4_ln2 = self.port_tx(PortTxReg::Dw4, PortLane::Ln2).unwrap();
|
||||
tx_dw4_ln2.writef(PORT_TX_DW4_SELECT, true);
|
||||
|
||||
let mut tx_dw4_ln3 = self.port_tx(PortTxReg::Dw4, PortLane::Ln3).unwrap();
|
||||
tx_dw4_ln3.writef(PORT_TX_DW4_SELECT, true);
|
||||
}
|
||||
|
||||
// Set PORT_CL_DW5 sus clock config to 11b
|
||||
{
|
||||
let mut cl_dw5 = self.port_cl(PortClReg::Dw5).unwrap();
|
||||
cl_dw5.writef(PORT_CL_DW5_SUS_CLOCK_MASK, true);
|
||||
}
|
||||
|
||||
// Clear training enable to change swing values
|
||||
let tx_dw5_ln0 = self.port_tx(PortTxReg::Dw5, PortLane::Ln0).unwrap();
|
||||
let mut tx_dw5_grp = WriteOnly::new(self.port_tx(PortTxReg::Dw5, PortLane::Grp).unwrap());
|
||||
{
|
||||
let mut v = tx_dw5_ln0.read();
|
||||
v &= !PORT_TX_DW5_TRAINING_ENABLE;
|
||||
tx_dw5_grp.write(v);
|
||||
}
|
||||
|
||||
// Program swing and de-emphasis
|
||||
|
||||
// Disable eDP bits in PORT_CL_DW10
|
||||
let mut cl_dw10 = self.port_cl(PortClReg::Dw10).unwrap();
|
||||
cl_dw10.writef(
|
||||
PORT_CL_DW10_EDP4K2K_MODE_OVRD_EN | PORT_CL_DW10_EDP4K2K_MODE_OVRD_VAL,
|
||||
false,
|
||||
);
|
||||
|
||||
// For PORT_TX_DW5:
|
||||
// - Set 2 tap disable from settings
|
||||
// - Set scaling mode sel to 010b
|
||||
// - Set rterm select to 110b
|
||||
// - Set 3 tap disable to 1
|
||||
// - Set cursor program to 0
|
||||
// - Set coeff polarity to 0
|
||||
{
|
||||
let mut v = tx_dw5_ln0.read();
|
||||
v &= !(PORT_TX_DW5_DISABLE_2_TAP
|
||||
| PORT_TX_DW5_CURSOR_PROGRAM
|
||||
| PORT_TX_DW5_COEFF_POLARITY
|
||||
| PORT_TX_DW5_SCALING_MODE_SEL_MASK
|
||||
| PORT_TX_DW5_RTERM_SELECT_MASK);
|
||||
v |= (setting.dw5_2_tap_disable << PORT_TX_DW5_DISABLE_2_TAP_SHIFT)
|
||||
| PORT_TX_DW5_DISABLE_3_TAP
|
||||
| (0b010 << PORT_TX_DW5_SCALING_MODE_SEL_SHIFT)
|
||||
| (0b110 << PORT_TX_DW5_RTERM_SELECT_SHIFT);
|
||||
tx_dw5_grp.write(v);
|
||||
}
|
||||
|
||||
// Individual lane settings are used to avoid overwriting lane-specific settings, and because
|
||||
// group registers cannot be read
|
||||
let lanes = [PortLane::Ln0, PortLane::Ln1, PortLane::Ln2, PortLane::Ln3];
|
||||
|
||||
// For PORT_TX_DW2:
|
||||
// - Set swing sel from settings
|
||||
// - Set rcomp scalar to 0x98
|
||||
for lane in lanes {
|
||||
let mut tx_dw2 = self.port_tx(PortTxReg::Dw2, lane).unwrap();
|
||||
let mut v = tx_dw2.read();
|
||||
v &= !(PORT_TX_DW2_SWING_SEL_UPPER_MASK
|
||||
| PORT_TX_DW2_SWING_SEL_LOWER_MASK
|
||||
| PORT_TX_DW2_RCOMP_SCALAR_MASK);
|
||||
v |= (((setting.dw2_swing_sel >> 3) & 1) << PORT_TX_DW2_SWING_SEL_UPPER_SHIFT)
|
||||
| ((setting.dw2_swing_sel & 0b111) << PORT_TX_DW2_SWING_SEL_LOWER_SHIFT)
|
||||
| (0x98 << PORT_TX_DW2_RCOMP_SCALAR_SHIFT);
|
||||
tx_dw2.write(v);
|
||||
}
|
||||
|
||||
// For PORT_TX_DW4:
|
||||
// - Set post cursor 1 from settings
|
||||
// - Set post cursor 2 to 0x0
|
||||
// - Set cursor coeff from settings
|
||||
for lane in lanes {
|
||||
let mut tx_dw4 = self.port_tx(PortTxReg::Dw4, lane).unwrap();
|
||||
let mut v = tx_dw4.read();
|
||||
v &= !(PORT_TX_DW4_POST_CURSOR_1_MASK
|
||||
| PORT_TX_DW4_POST_CURSOR_2_MASK
|
||||
| PORT_TX_DW4_CURSOR_COEFF_MASK);
|
||||
v |= (setting.dw4_post_cursor_1 << PORT_TX_DW4_POST_CURSOR_1_SHIFT)
|
||||
| (setting.dw4_cursor_coeff << PORT_TX_DW4_CURSOR_COEFF_SHIFT);
|
||||
tx_dw4.write(v);
|
||||
}
|
||||
|
||||
// For PORT_TX_DW7:
|
||||
// - Set n scalar from settings
|
||||
for lane in lanes {
|
||||
let mut tx_dw7 = self.port_tx(PortTxReg::Dw7, lane).unwrap();
|
||||
// All other bits are spare
|
||||
tx_dw7.write(setting.dw7_n_scalar << PORT_TX_DW7_N_SCALAR_SHIFT);
|
||||
}
|
||||
|
||||
// Set training enable to trigger update
|
||||
{
|
||||
let mut v = tx_dw5_ln0.read();
|
||||
v |= PORT_TX_DW5_TRAINING_ENABLE;
|
||||
tx_dw5_grp.write(v);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn kabylake(gttmm: &Arc<MmioRegion>) -> Result<Vec<Self>> {
|
||||
let mut ddis = Vec::new();
|
||||
for (i, name) in [
|
||||
"A", "B", "C", "D",
|
||||
//TODO: missing AUX regs? "E",
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
ddis.push(Self {
|
||||
name,
|
||||
index: i,
|
||||
port_base: None, //TODO: port regs
|
||||
gttmm: gttmm.clone(),
|
||||
// IHD-OS-KBL-Vol 2c-1.17 DDI_AUX_CTL
|
||||
aux_ctl: unsafe { gttmm.mmio(0x64010 + i * 0x100)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 DDI_AUX_DATA
|
||||
aux_datas: [
|
||||
unsafe { gttmm.mmio(0x64014 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64018 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x6401C + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64020 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64024 + i * 0x100)? },
|
||||
],
|
||||
// IHD-OS-KBL-Vol 2c-1.17 DDI_BUF_CTL
|
||||
buf_ctl: unsafe { gttmm.mmio(0x64000 + i * 0x100)? },
|
||||
// N/A
|
||||
dpclka_cfgcr0_clock_shift: None,
|
||||
dpclka_cfgcr0_clock_off: None,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 GMBUS0
|
||||
gmbus_pin_pair: match *name {
|
||||
"B" => Some(0b101),
|
||||
"C" => Some(0b100),
|
||||
"D" => Some(0b110),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-KBL-Vol 12-1.17 GMBUS and GPIO
|
||||
gpio_port: match *name {
|
||||
"B" => Some(GpioPort::Port4),
|
||||
"C" => Some(GpioPort::Port3),
|
||||
"D" => Some(GpioPort::Port5),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PWR_WELL_CTL
|
||||
// All auxes go through the same Misc IO request
|
||||
pwr_well_ctl_aux_request: 1 << 1,
|
||||
pwr_well_ctl_aux_state: 1 << 0,
|
||||
pwr_well_ctl_ddi_request: match *name {
|
||||
"A" | "E" => 1 << 3,
|
||||
"B" => 1 << 5,
|
||||
"C" => 1 << 7,
|
||||
"D" => 1 << 9,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pwr_well_ctl_ddi_state: match *name {
|
||||
"A" | "E" => 1 << 2,
|
||||
"B" => 1 << 4,
|
||||
"C" => 1 << 6,
|
||||
"D" => 1 << 8,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
// IHD-OS-KBL-Vol 2c-1.17 SDE_INTERRUPT
|
||||
sde_interrupt_hotplug: match *name {
|
||||
"A" => Some(1 << 24),
|
||||
"B" => Some(1 << 21),
|
||||
"C" => Some(1 << 22),
|
||||
"D" => Some(1 << 23),
|
||||
"E" => Some(1 << 25),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_CLK_SEL
|
||||
transcoder_index: match *name {
|
||||
"B" => Some(0b010),
|
||||
"C" => Some(0b011),
|
||||
"D" => Some(0b100),
|
||||
"E" => Some(0b101),
|
||||
_ => None,
|
||||
},
|
||||
});
|
||||
}
|
||||
Ok(ddis)
|
||||
}
|
||||
|
||||
pub fn tigerlake(gttmm: &Arc<MmioRegion>) -> Result<Vec<Self>> {
|
||||
let mut ddis = Vec::new();
|
||||
for (i, name) in [
|
||||
"A", "B", "C", "USBC1", "USBC2", "USBC3", "USBC4", "USBC5", "USBC6",
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let port_base = match i {
|
||||
0 => Some(0x162000),
|
||||
1 => Some(0x6C000),
|
||||
2 => Some(0x160000),
|
||||
_ => None,
|
||||
};
|
||||
ddis.push(Self {
|
||||
name,
|
||||
index: i,
|
||||
port_base,
|
||||
gttmm: gttmm.clone(),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DDI_AUX_CTL
|
||||
aux_ctl: unsafe { gttmm.mmio(0x64010 + i * 0x100)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DDI_AUX_DATA
|
||||
aux_datas: [
|
||||
unsafe { gttmm.mmio(0x64014 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64018 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x6401C + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64020 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64024 + i * 0x100)? },
|
||||
],
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DDI_BUF_CTL
|
||||
buf_ctl: unsafe { gttmm.mmio(0x64000 + i * 0x100)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DPCLKA_CFGCR0
|
||||
dpclka_cfgcr0_clock_shift: match i {
|
||||
0 => Some(0),
|
||||
1 => Some(2),
|
||||
2 => Some(4),
|
||||
_ => None,
|
||||
},
|
||||
dpclka_cfgcr0_clock_off: match i {
|
||||
// DDI
|
||||
0 => Some(1 << 10),
|
||||
1 => Some(1 << 11),
|
||||
2 => Some(1 << 24),
|
||||
// Type C
|
||||
3 => Some(1 << 12),
|
||||
4 => Some(1 << 13),
|
||||
5 => Some(1 << 14),
|
||||
6 => Some(1 << 21),
|
||||
7 => Some(1 << 22),
|
||||
8 => Some(1 << 23),
|
||||
_ => None,
|
||||
},
|
||||
//TODO: link to docs
|
||||
gmbus_pin_pair: match i {
|
||||
// DDI pins
|
||||
0 => Some(1),
|
||||
1 => Some(2),
|
||||
2 => Some(3),
|
||||
// Type C pins
|
||||
3 => Some(9),
|
||||
4 => Some(10),
|
||||
5 => Some(11),
|
||||
6 => Some(12),
|
||||
7 => Some(13),
|
||||
8 => Some(14),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-TGL-Vol 12-1.22-Rev2.0 GMBUS and GPIO
|
||||
gpio_port: match *name {
|
||||
"A" => Some(GpioPort::Port1),
|
||||
"B" => Some(GpioPort::Port2),
|
||||
"C" => Some(GpioPort::Port3),
|
||||
"USBC1" => Some(GpioPort::Port9),
|
||||
"USBC2" => Some(GpioPort::Port10),
|
||||
"USBC3" => Some(GpioPort::Port11),
|
||||
"USBC4" => Some(GpioPort::Port12),
|
||||
"USBC5" => Some(GpioPort::Port13),
|
||||
"USBC6" => Some(GpioPort::Port14),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL_AUX
|
||||
pwr_well_ctl_aux_request: 2 << (i * 2),
|
||||
pwr_well_ctl_aux_state: 1 << (i * 2),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL_DDI
|
||||
pwr_well_ctl_ddi_request: 2 << (i * 2),
|
||||
pwr_well_ctl_ddi_state: 1 << (i * 2),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 SDE_INTERRUPT
|
||||
sde_interrupt_hotplug: match i {
|
||||
0 => Some(1 << 16),
|
||||
1 => Some(1 << 17),
|
||||
2 => Some(1 << 18),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_CLK_SEL
|
||||
transcoder_index: Some((i + 1) as u32),
|
||||
})
|
||||
}
|
||||
Ok(ddis)
|
||||
}
|
||||
|
||||
pub fn alchemist(gttmm: &Arc<MmioRegion>) -> Result<Vec<Self>> {
|
||||
let mut ddis = Vec::new();
|
||||
for (i, name) in ["A", "B", "C", "USBC1", "USBC2", "USBC3", "USBC4", "D", "E"]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let port_base = match i {
|
||||
0 => Some(0x162000),
|
||||
1 => Some(0x6C000),
|
||||
2 => Some(0x160000),
|
||||
_ => None,
|
||||
};
|
||||
ddis.push(Self {
|
||||
name,
|
||||
index: i,
|
||||
port_base,
|
||||
gttmm: gttmm.clone(),
|
||||
// IHD-OS-ACM-Vol 2c-3.23 DDI_AUX_CTL
|
||||
aux_ctl: unsafe { gttmm.mmio(0x64010 + i * 0x100)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 DDI_AUX_DATA
|
||||
aux_datas: [
|
||||
unsafe { gttmm.mmio(0x64014 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64018 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x6401C + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64020 + i * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x64024 + i * 0x100)? },
|
||||
],
|
||||
// IHD-OS-ACM-Vol 2c-3.23 DDI_BUF_CTL
|
||||
buf_ctl: unsafe { gttmm.mmio(0x64000 + i * 0x100)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 DPCLKA_CFGCR0
|
||||
dpclka_cfgcr0_clock_shift: match i {
|
||||
0 => Some(0),
|
||||
1 => Some(2),
|
||||
2 => Some(4),
|
||||
_ => None,
|
||||
},
|
||||
dpclka_cfgcr0_clock_off: match i {
|
||||
// DDI
|
||||
0 => Some(1 << 10),
|
||||
1 => Some(1 << 11),
|
||||
2 => Some(1 << 24),
|
||||
// Type C
|
||||
3 => Some(1 << 12),
|
||||
4 => Some(1 << 13),
|
||||
5 => Some(1 << 14),
|
||||
6 => Some(1 << 21),
|
||||
7 => Some(1 << 22),
|
||||
8 => Some(1 << 23),
|
||||
_ => None,
|
||||
},
|
||||
//TODO: link to docs
|
||||
gmbus_pin_pair: match i {
|
||||
// DDI pins
|
||||
0 => Some(1),
|
||||
1 => Some(2),
|
||||
2 => Some(3),
|
||||
// Type C pins
|
||||
3 => Some(9),
|
||||
4 => Some(10),
|
||||
5 => Some(11),
|
||||
6 => Some(12),
|
||||
7 => Some(13),
|
||||
8 => Some(14),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-ACM-Vol 12-3.23 GMBUS and GPIO
|
||||
gpio_port: match *name {
|
||||
"A" => Some(GpioPort::Port1),
|
||||
"B" => Some(GpioPort::Port2),
|
||||
"C" => Some(GpioPort::Port3),
|
||||
"D" => Some(GpioPort::Port4),
|
||||
"USBC1" => Some(GpioPort::Port9),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL_AUX
|
||||
pwr_well_ctl_aux_request: 2 << (i * 2),
|
||||
pwr_well_ctl_aux_state: 1 << (i * 2),
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL_DDI
|
||||
pwr_well_ctl_ddi_request: 2 << (i * 2),
|
||||
pwr_well_ctl_ddi_state: 1 << (i * 2),
|
||||
// IHD-OS-ACM-Vol 2c-3.23 SDE_INTERRUPT
|
||||
sde_interrupt_hotplug: match i {
|
||||
0 => Some(1 << 16),
|
||||
1 => Some(1 << 17),
|
||||
2 => Some(1 << 18),
|
||||
_ => None,
|
||||
},
|
||||
// IHD-OS-ACM-Vol 2c-3.23 TRANS_CLK_SEL
|
||||
transcoder_index: Some((i + 1) as u32),
|
||||
})
|
||||
}
|
||||
Ok(ddis)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
use common::io::{Io, MmioPtr};
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use super::MmioRegion;
|
||||
|
||||
pub const DPLL_CFGCR1_QDIV_RATIO_SHIFT: u32 = 10;
|
||||
pub const DPLL_CFGCR1_QDIV_RATIO_MASK: u32 = 0xFF << DPLL_CFGCR1_QDIV_RATIO_SHIFT;
|
||||
pub const DPLL_CFGCR1_QDIV_MODE: u32 = 1 << 9;
|
||||
pub const DPLL_CFGCR1_KDIV_1: u32 = 0b001 << 6;
|
||||
pub const DPLL_CFGCR1_KDIV_2: u32 = 0b010 << 6;
|
||||
pub const DPLL_CFGCR1_KDIV_3: u32 = 0b100 << 6;
|
||||
pub const DPLL_CFGCR1_KDIV_MASK: u32 = 0b111 << 6;
|
||||
pub const DPLL_CFGCR1_PDIV_2: u32 = 0b0001 << 2;
|
||||
pub const DPLL_CFGCR1_PDIV_3: u32 = 0b0010 << 2;
|
||||
pub const DPLL_CFGCR1_PDIV_5: u32 = 0b0100 << 2;
|
||||
pub const DPLL_CFGCR1_PDIV_7: u32 = 0b1000 << 2;
|
||||
pub const DPLL_CFGCR1_PDIV_MASK: u32 = 0b1111 << 2;
|
||||
|
||||
pub const DPLL_ENABLE_ENABLE: u32 = 1 << 31;
|
||||
pub const DPLL_ENABLE_LOCK: u32 = 1 << 30;
|
||||
pub const DPLL_ENABLE_POWER_ENABLE: u32 = 1 << 27;
|
||||
pub const DPLL_ENABLE_POWER_STATE: u32 = 1 << 26;
|
||||
|
||||
pub const DPLL_SSC_ENABLE: u32 = 1 << 9;
|
||||
|
||||
pub struct Dpll {
|
||||
pub name: &'static str,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DPLL_CFGCR0
|
||||
pub cfgcr0: MmioPtr<u32>,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DPLL_CFGCR1
|
||||
pub cfgcr1: MmioPtr<u32>,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DPLL_DIV0
|
||||
pub div0: MmioPtr<u32>,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DPCLKA_CFGCR0
|
||||
pub dpclka_cfgcr0_clock_value: u32,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DPLL_ENABLE
|
||||
pub enable: MmioPtr<u32>,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 DPLL_SSC
|
||||
pub ssc: MmioPtr<u32>,
|
||||
}
|
||||
|
||||
//TODO: verify offsets and count using DeviceKind?
|
||||
impl Dpll {
|
||||
pub fn dump(&self) {
|
||||
eprint!("Dpll {}", self.name);
|
||||
eprint!(" cfgcr0 {:08X}", self.cfgcr0.read());
|
||||
eprint!(" cfgcr1 {:08X}", self.cfgcr1.read());
|
||||
eprint!(" div0 {:08X}", self.div0.read());
|
||||
eprint!(" enable {:08X}", self.enable.read());
|
||||
eprint!(" ssc {:08X}", self.ssc.read());
|
||||
eprintln!();
|
||||
}
|
||||
|
||||
pub fn set_freq_hdmi(
|
||||
&mut self,
|
||||
mut ref_freq: u64,
|
||||
timing: &edid::DetailedTiming,
|
||||
) -> Result<()> {
|
||||
// IHD-OS-TGL-Vol 12-1.22-Rev2.0 "Formula for HDMI Mode DPLL Programming"
|
||||
const KHz: u64 = 1_000;
|
||||
const MHz: u64 = KHz * 1_000;
|
||||
let dco_min: u64 = 7_998 * MHz;
|
||||
let dco_mid: u64 = 8_999 * MHz;
|
||||
let dco_max: u64 = 10_000 * MHz;
|
||||
|
||||
// If reference frequency is 38.4, use 19.2 because the DPLL automatically divides that by 2.
|
||||
if ref_freq == 38_400_000 {
|
||||
ref_freq /= 2;
|
||||
}
|
||||
|
||||
//TODO: this symbol frequency is only valid for RGB 8 bits per color
|
||||
let symbol_freq = (timing.pixel_clock as u64) * KHz;
|
||||
let pll_freq = symbol_freq * 5;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Setting {
|
||||
pdiv: u64,
|
||||
kdiv: u64,
|
||||
qdiv: u64,
|
||||
cfgcr1: u32,
|
||||
dco: u64,
|
||||
dco_dist: u64,
|
||||
}
|
||||
|
||||
let mut best_setting: Option<Setting> = None;
|
||||
for (pdiv, pdiv_reg) in [
|
||||
(2, DPLL_CFGCR1_PDIV_2),
|
||||
(3, DPLL_CFGCR1_PDIV_3),
|
||||
(5, DPLL_CFGCR1_PDIV_5),
|
||||
(7, DPLL_CFGCR1_PDIV_7),
|
||||
] {
|
||||
for (kdiv, kdiv_reg) in [
|
||||
(1, DPLL_CFGCR1_KDIV_1),
|
||||
(2, DPLL_CFGCR1_KDIV_2),
|
||||
(3, DPLL_CFGCR1_KDIV_3),
|
||||
] {
|
||||
let qdiv_range = if kdiv == 2 { 1..=0xFF } else { 1..=1 };
|
||||
for qdiv in qdiv_range {
|
||||
let qdiv_reg = if qdiv == 1 {
|
||||
0
|
||||
} else {
|
||||
((qdiv as u32) << DPLL_CFGCR1_QDIV_RATIO_SHIFT) | DPLL_CFGCR1_QDIV_MODE
|
||||
};
|
||||
|
||||
let dco = pll_freq * pdiv * kdiv * qdiv;
|
||||
if dco <= dco_min || dco >= dco_max {
|
||||
// DCO outside of valid range
|
||||
continue;
|
||||
}
|
||||
|
||||
let dco_dist = dco.abs_diff(dco_mid);
|
||||
|
||||
let setting = Setting {
|
||||
pdiv,
|
||||
kdiv,
|
||||
qdiv,
|
||||
cfgcr1: pdiv_reg | kdiv_reg | qdiv_reg,
|
||||
dco,
|
||||
dco_dist,
|
||||
};
|
||||
|
||||
best_setting = match best_setting.take() {
|
||||
Some(other) if other.dco_dist < setting.dco_dist => Some(other),
|
||||
_ => Some(setting),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(setting) = best_setting else {
|
||||
log::error!("failed to find valid DPLL setting");
|
||||
return Err(Error::new(EIO));
|
||||
};
|
||||
|
||||
eprintln!("{:?}", setting);
|
||||
|
||||
// Configure DPLL_CFGCR0 to set DCO frequency
|
||||
{
|
||||
let dco_int = setting.dco / ref_freq;
|
||||
let dco_fract = ((setting.dco - (dco_int * ref_freq)) << 15) / ref_freq;
|
||||
self.cfgcr0
|
||||
.write(((dco_fract as u32) << 10) | (dco_int as u32));
|
||||
}
|
||||
|
||||
// Configure DPLL_CFGCR1 to set the dividers
|
||||
{
|
||||
let mut v = self.cfgcr1.read();
|
||||
let mask = DPLL_CFGCR1_QDIV_RATIO_MASK
|
||||
| DPLL_CFGCR1_QDIV_MODE
|
||||
| DPLL_CFGCR1_KDIV_MASK
|
||||
| DPLL_CFGCR1_PDIV_MASK;
|
||||
v &= !mask;
|
||||
v |= setting.cfgcr1 & mask;
|
||||
self.cfgcr1.write(v);
|
||||
}
|
||||
|
||||
// Read back DPLL_CFGCR0 and DPLL_CFGCR1 to ensure writes are complete
|
||||
let _ = self.cfgcr0.read();
|
||||
let _ = self.cfgcr1.read();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tigerlake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
|
||||
let mut dplls = Vec::new();
|
||||
dplls.push(Self {
|
||||
name: "0",
|
||||
cfgcr0: unsafe { gttmm.mmio(0x164284)? },
|
||||
cfgcr1: unsafe { gttmm.mmio(0x164288)? },
|
||||
div0: unsafe { gttmm.mmio(0x164B00)? },
|
||||
dpclka_cfgcr0_clock_value: 0b00,
|
||||
enable: unsafe { gttmm.mmio(0x46010)? },
|
||||
ssc: unsafe { gttmm.mmio(0x164B10)? },
|
||||
});
|
||||
dplls.push(Self {
|
||||
name: "1",
|
||||
cfgcr0: unsafe { gttmm.mmio(0x16428C)? },
|
||||
cfgcr1: unsafe { gttmm.mmio(0x164290)? },
|
||||
div0: unsafe { gttmm.mmio(0x164C00)? },
|
||||
dpclka_cfgcr0_clock_value: 0b01,
|
||||
enable: unsafe { gttmm.mmio(0x46014)? },
|
||||
ssc: unsafe { gttmm.mmio(0x164C10)? },
|
||||
});
|
||||
/*TODO: not present on U-class CPUs
|
||||
dplls.push(Self {
|
||||
name: "4",
|
||||
cfgcr0: unsafe { gttmm.mmio(0x164294)? },
|
||||
cfgcr1: unsafe { gttmm.mmio(0x164298)? },
|
||||
div0: unsafe { gttmm.mmio(0x164E00)? },
|
||||
dpclka_cfgcr0_clock_value: 0b10,
|
||||
enable: unsafe { gttmm.mmio(0x46018)? },
|
||||
ssc: unsafe { gttmm.mmio(0x164E10)? },
|
||||
});
|
||||
*/
|
||||
Ok(dplls)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
use std::sync::Arc;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
use range_alloc::RangeAllocator;
|
||||
use syscall::{Error, EIO};
|
||||
|
||||
use crate::device::MmioRegion;
|
||||
|
||||
/// Global Graphics Translation Table (global GTT)
|
||||
///
|
||||
/// The global GTT is a page table used by all parts of the GPU that don't use
|
||||
/// the PPGTT (Per-Process GTT). This includes the display engine and the GM
|
||||
/// aperture that the CPU can access.
|
||||
///
|
||||
/// The global GTT is located in the GTTMM BAR at offset 8MiB, is up to 8MiB big
|
||||
/// and consists of 64bit entries. Each entry has a present bit as LSB and the
|
||||
/// address of the frame at bits 12 through 38. The rest of the bits are ignored.
|
||||
///
|
||||
/// Source: Pages 6 and 75 of intel-gfx-prm-osrc-kbl-vol05-memory_views.pdf
|
||||
pub struct GlobalGtt {
|
||||
gttmm: Arc<MmioRegion>,
|
||||
/// Base the GTT
|
||||
gtt_base: *mut u64,
|
||||
/// Size of the GTT
|
||||
gtt_size: usize,
|
||||
|
||||
/// Allocator for GM aperture pages
|
||||
gm_alloc: RangeAllocator<u32>,
|
||||
|
||||
// FIXME reuse DSM memory for something useful
|
||||
/// Base Data of Stolen Memory (DSM)
|
||||
base_dsm: *mut (),
|
||||
/// Size of DSM
|
||||
size_data_stolen_memory: usize,
|
||||
}
|
||||
|
||||
const GTT_PAGE_SIZE: u32 = 4096;
|
||||
|
||||
impl GlobalGtt {
|
||||
pub unsafe fn new(
|
||||
pcid_handle: &mut PciFunctionHandle,
|
||||
gttmm: Arc<MmioRegion>,
|
||||
gm_size: u32,
|
||||
) -> Self {
|
||||
let gtt_offset = 8 * 1024 * 1024;
|
||||
let gtt_base = ptr::with_exposed_provenance_mut(gttmm.virt + gtt_offset);
|
||||
|
||||
let base_dsm = unsafe { pcid_handle.read_config(0x5C) };
|
||||
let ggc = unsafe { pcid_handle.read_config(0x50) };
|
||||
|
||||
let dsm_size = match (ggc >> 8) & 0xFF {
|
||||
size if size & 0xF0 == 0 => size * 32 * 1024 * 1024,
|
||||
size => (size & !0xF0) * 4 * 1024 * 1024,
|
||||
} as usize;
|
||||
let gtt_size = match (ggc >> 6) & 0x3 {
|
||||
0 => 0,
|
||||
1 => 2 * 1024 * 1024,
|
||||
2 => 4 * 1024 * 1024,
|
||||
3 => 8 * 1024 * 1024,
|
||||
_ => unreachable!(),
|
||||
} as usize;
|
||||
|
||||
log::info!("Base DSM: {:X}", base_dsm);
|
||||
log::info!(
|
||||
"GGC: {:X} => global GTT size: {}MiB; DSM size: {}MiB",
|
||||
ggc,
|
||||
gtt_size / 1024 / 1024,
|
||||
dsm_size / 1024 / 1024,
|
||||
);
|
||||
|
||||
let gm_alloc = RangeAllocator::new(0..gm_size / 4096);
|
||||
|
||||
GlobalGtt {
|
||||
gttmm,
|
||||
gtt_base,
|
||||
gtt_size,
|
||||
gm_alloc,
|
||||
base_dsm: core::ptr::with_exposed_provenance_mut(base_dsm as usize),
|
||||
size_data_stolen_memory: dsm_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the global GTT by clearing out all existing mappings.
|
||||
pub unsafe fn reset(&mut self) {
|
||||
for i in 0..self.gtt_size / 8 {
|
||||
unsafe { *self.gtt_base.add(i) = 0 };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, surf: u32, surf_size: u32) {
|
||||
assert!(surf.is_multiple_of(GTT_PAGE_SIZE));
|
||||
assert!(surf_size.is_multiple_of(GTT_PAGE_SIZE));
|
||||
|
||||
self.gm_alloc
|
||||
.allocate_exact_range(
|
||||
surf / GTT_PAGE_SIZE..surf / GTT_PAGE_SIZE + surf_size / GTT_PAGE_SIZE,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"failed to allocate pre-existing surface at 0x{:x} of size {}: {:?}",
|
||||
surf, surf_size, err
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn alloc_phys_mem(&mut self, size: u32) -> syscall::Result<u32> {
|
||||
let size = size.next_multiple_of(GTT_PAGE_SIZE);
|
||||
|
||||
let sgl = common::sgl::Sgl::new(size as usize)?;
|
||||
|
||||
let range = self
|
||||
.gm_alloc
|
||||
.allocate_range(size / GTT_PAGE_SIZE)
|
||||
.map_err(|err| {
|
||||
log::warn!("failed to allocate buffer of size {}: {:?}", size, err);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
|
||||
for chunk in sgl.chunks() {
|
||||
for i in 0..chunk.length / GTT_PAGE_SIZE as usize {
|
||||
unsafe {
|
||||
*self
|
||||
.gtt_base
|
||||
.add(range.start as usize + chunk.offset / GTT_PAGE_SIZE as usize + i) =
|
||||
chunk.phys as u64 + i as u64 * u64::from(GTT_PAGE_SIZE) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
mem::forget(sgl);
|
||||
|
||||
Ok(range.start * 4096)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
use common::{
|
||||
io::{Io, MmioPtr},
|
||||
timeout::Timeout,
|
||||
};
|
||||
use embedded_hal::blocking::i2c::{self, Operation, SevenBitAddress, Transactional};
|
||||
|
||||
use super::MmioRegion;
|
||||
|
||||
const GMBUS1_SW_RDY: u32 = 1 << 30;
|
||||
const GMBUS1_CYCLE_STOP: u32 = 1 << 27;
|
||||
const GMBUS1_CYCLE_INDEX: u32 = 1 << 26;
|
||||
const GMBUS1_CYCLE_WAIT: u32 = 1 << 25;
|
||||
const GMBUS1_SIZE_SHIFT: u32 = 16;
|
||||
const GMBUS1_INDEX_SHIFT: u32 = 8;
|
||||
|
||||
const GMBUS2_HW_RDY: u32 = 1 << 11;
|
||||
const GMBUS2_ACTIVE: u32 = 1 << 9;
|
||||
|
||||
pub struct Gmbus {
|
||||
regs: [MmioPtr<u32>; 6],
|
||||
}
|
||||
|
||||
impl Gmbus {
|
||||
pub unsafe fn new(gttmm: &MmioRegion) -> syscall::Result<Self> {
|
||||
Ok(Self {
|
||||
regs: [
|
||||
gttmm.mmio(0xC5100)?,
|
||||
gttmm.mmio(0xC5104)?,
|
||||
gttmm.mmio(0xC5108)?,
|
||||
gttmm.mmio(0xC510C)?,
|
||||
gttmm.mmio(0xC5110)?,
|
||||
gttmm.mmio(0xC5120)?,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pin_pair<'a>(&'a mut self, pin_pair: u8) -> GmbusPinPair<'a> {
|
||||
GmbusPinPair {
|
||||
regs: &mut self.regs,
|
||||
pin_pair,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GmbusPinPair<'a> {
|
||||
regs: &'a mut [MmioPtr<u32>; 6],
|
||||
pin_pair: u8,
|
||||
}
|
||||
|
||||
impl<'a> Transactional for GmbusPinPair<'a> {
|
||||
type Error = ();
|
||||
fn exec(&mut self, addr7: SevenBitAddress, ops: &mut [Operation<'_>]) -> Result<(), ()> {
|
||||
let mut ops_iter = ops.iter_mut();
|
||||
//TODO: gmbus is actually smbus, not fully i2c compatible!
|
||||
// The first operation MUST be a write of the index
|
||||
let index = match ops_iter.next() {
|
||||
Some(Operation::Write(buf)) if buf.len() == 1 => buf[0],
|
||||
unsupported => {
|
||||
log::error!("GMBUS unsupported first operation {:?}", unsupported);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
// Reset
|
||||
self.regs[1].write(0);
|
||||
|
||||
// Set pin pair, enabling interface
|
||||
self.regs[0].write(self.pin_pair as u32);
|
||||
|
||||
for op in ops_iter {
|
||||
// Start operation
|
||||
let (addr8, size) = match op {
|
||||
Operation::Read(buf) => ((addr7 << 1) | 1, buf.len() as u32),
|
||||
Operation::Write(buf) => (addr7 << 1, buf.len() as u32),
|
||||
};
|
||||
if size >= 512 {
|
||||
log::error!("GMBUS transaction size {} too large", size);
|
||||
return Err(());
|
||||
}
|
||||
self.regs[1].write(
|
||||
GMBUS1_SW_RDY
|
||||
| GMBUS1_CYCLE_INDEX
|
||||
| GMBUS1_CYCLE_WAIT
|
||||
| (size << GMBUS1_SIZE_SHIFT)
|
||||
| (index as u32) << GMBUS1_INDEX_SHIFT
|
||||
| (addr8 as u32),
|
||||
);
|
||||
|
||||
// Perform transaction
|
||||
match op {
|
||||
Operation::Read(buf) => {
|
||||
for chunk in buf.chunks_mut(4) {
|
||||
{
|
||||
//TODO: ideal timeout for gmbus read?
|
||||
let timeout = Timeout::from_millis(10);
|
||||
while !self.regs[2].readf(GMBUS2_HW_RDY) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::debug!(
|
||||
"timeout on GMBUS read 0x{:08x}",
|
||||
self.regs[2].read()
|
||||
);
|
||||
()
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
let bytes = self.regs[3].read().to_le_bytes();
|
||||
chunk.copy_from_slice(&bytes[..chunk.len()]);
|
||||
}
|
||||
}
|
||||
Operation::Write(buf) => {
|
||||
log::warn!("TODO: GMBUS WRITE");
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop transaction
|
||||
self.regs[1].write(GMBUS1_SW_RDY | GMBUS1_CYCLE_STOP);
|
||||
|
||||
// Wait idle
|
||||
let timeout = Timeout::from_millis(10);
|
||||
while self.regs[2].readf(GMBUS2_ACTIVE) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::debug!("timeout on GMBUS active 0x{:08x}", self.regs[2].read());
|
||||
()
|
||||
})?;
|
||||
}
|
||||
|
||||
// Disable GMBUS interface
|
||||
self.regs[0].write(0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> i2c::WriteRead for GmbusPinPair<'a> {
|
||||
type Error = ();
|
||||
fn write_read(
|
||||
&mut self,
|
||||
addr7: SevenBitAddress,
|
||||
bytes: &[u8],
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), ()> {
|
||||
self.exec(
|
||||
addr7,
|
||||
&mut [Operation::Write(bytes), Operation::Read(buffer)],
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
use std::convert::Infallible;
|
||||
use std::time::Duration;
|
||||
|
||||
use common::io::{Io, MmioPtr};
|
||||
use embedded_hal::digital::v2 as digital;
|
||||
|
||||
use crate::device::HalTimer;
|
||||
|
||||
use super::MmioRegion;
|
||||
|
||||
const GPIO_DIR_MASK: u32 = 1 << 0;
|
||||
const GPIO_DIR_OUT: u32 = 1 << 1;
|
||||
const GPIO_VAL_MASK: u32 = 1 << 2;
|
||||
const GPIO_VAL_OUT: u32 = 1 << 3;
|
||||
const GPIO_VAL_IN: u32 = 1 << 4;
|
||||
const GPIO_CLOCK_SHIFT: u32 = 0;
|
||||
const GPIO_DATA_SHIFT: u32 = 8;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(usize)]
|
||||
pub enum GpioPort {
|
||||
Port0 = 0xC5010,
|
||||
Port1 = 0xC5014,
|
||||
Port2 = 0xC5018,
|
||||
Port3 = 0xC501C,
|
||||
Port4 = 0xC5020,
|
||||
Port5 = 0xC5024,
|
||||
Port6 = 0xC5028,
|
||||
Port7 = 0xC502C,
|
||||
Port8 = 0xC5030,
|
||||
Port9 = 0xC5034,
|
||||
Port10 = 0xC5038,
|
||||
Port11 = 0xC503C,
|
||||
Port12 = 0xC5040,
|
||||
Port13 = 0xC5044,
|
||||
Port14 = 0xC5048,
|
||||
Port15 = 0xC504C,
|
||||
}
|
||||
|
||||
impl GpioPort {
|
||||
pub unsafe fn i2c(
|
||||
&self,
|
||||
gttmm: &MmioRegion,
|
||||
) -> syscall::Result<bitbang_hal::i2c::I2cBB<GpioPin, GpioPin, HalTimer>> {
|
||||
let i2c_freq = 100_000.0;
|
||||
let (scl, sda) = unsafe {
|
||||
(
|
||||
GpioPin {
|
||||
ctl: gttmm.mmio(*self as usize)?,
|
||||
shift: GPIO_CLOCK_SHIFT,
|
||||
},
|
||||
GpioPin {
|
||||
ctl: gttmm.mmio(*self as usize)?,
|
||||
shift: GPIO_DATA_SHIFT,
|
||||
},
|
||||
)
|
||||
};
|
||||
Ok(bitbang_hal::i2c::I2cBB::new(
|
||||
scl,
|
||||
sda,
|
||||
HalTimer::new(Duration::from_secs_f64(1.0 / i2c_freq)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpioPin {
|
||||
ctl: MmioPtr<u32>,
|
||||
shift: u32,
|
||||
}
|
||||
|
||||
impl digital::InputPin for GpioPin {
|
||||
type Error = Infallible;
|
||||
|
||||
fn is_high(&self) -> Result<bool, Infallible> {
|
||||
Ok(((self.ctl.read() >> self.shift) & GPIO_VAL_IN) == GPIO_VAL_IN)
|
||||
}
|
||||
|
||||
fn is_low(&self) -> Result<bool, Infallible> {
|
||||
Ok(((self.ctl.read() >> self.shift) & GPIO_VAL_IN) == 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl digital::OutputPin for GpioPin {
|
||||
type Error = Infallible;
|
||||
|
||||
fn set_low(&mut self) -> Result<(), Infallible> {
|
||||
// Set GPIO to output with value 0
|
||||
let value = GPIO_DIR_MASK | GPIO_DIR_OUT | GPIO_VAL_MASK;
|
||||
self.ctl.write(value << self.shift);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_high(&mut self) -> Result<(), Infallible> {
|
||||
// Assuming external pull-up, set GPIO to input
|
||||
let value = GPIO_DIR_MASK;
|
||||
self.ctl.write(value << self.shift);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod timer;
|
||||
pub use self::timer::*;
|
||||
@@ -0,0 +1,38 @@
|
||||
use embedded_hal::timer;
|
||||
use std::time::{Duration, Instant};
|
||||
use void::Void;
|
||||
|
||||
pub struct HalTimer {
|
||||
instant: Instant,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
impl HalTimer {
|
||||
pub fn new(duration: Duration) -> Self {
|
||||
Self {
|
||||
instant: Instant::now(),
|
||||
duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl timer::CountDown for HalTimer {
|
||||
type Time = Duration;
|
||||
fn start<T: Into<Duration>>(&mut self, duration: T) {
|
||||
self.instant = Instant::now();
|
||||
self.duration = duration.into();
|
||||
}
|
||||
|
||||
fn wait(&mut self) -> nb::Result<(), Void> {
|
||||
if self.instant.elapsed() < self.duration {
|
||||
std::thread::yield_now();
|
||||
Err(nb::Error::WouldBlock)
|
||||
} else {
|
||||
// Since this is periodic it must trigger at the next duration
|
||||
self.instant += self.duration;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl timer::Periodic for HalTimer {}
|
||||
@@ -0,0 +1,966 @@
|
||||
use common::{
|
||||
io::{Io, MmioPtr},
|
||||
timeout::Timeout,
|
||||
};
|
||||
use pcid_interface::{PciFunction, PciFunctionHandle};
|
||||
use range_alloc::RangeAllocator;
|
||||
use std::{collections::VecDeque, fmt, mem, sync::Arc};
|
||||
use syscall::error::{Error, Result, EIO, ENODEV, ERANGE};
|
||||
|
||||
mod aux;
|
||||
mod bios;
|
||||
use self::bios::*;
|
||||
mod buffer;
|
||||
mod ddi;
|
||||
use self::ddi::*;
|
||||
mod dpll;
|
||||
use self::dpll::*;
|
||||
mod gmbus;
|
||||
pub use self::gmbus::*;
|
||||
mod gpio;
|
||||
pub use self::gpio::*;
|
||||
mod ggtt;
|
||||
use ggtt::*;
|
||||
mod hal;
|
||||
pub use self::hal::*;
|
||||
mod pipe;
|
||||
use self::pipe::*;
|
||||
mod power;
|
||||
use self::power::*;
|
||||
mod scheme;
|
||||
mod transcoder;
|
||||
use self::transcoder::*;
|
||||
|
||||
//TODO: move to common?
|
||||
pub struct CallbackGuard<'a, T, F: FnOnce(&mut T)> {
|
||||
value: &'a mut T,
|
||||
fini: Option<F>,
|
||||
}
|
||||
|
||||
impl<'a, T, F: FnOnce(&mut T)> CallbackGuard<'a, T, F> {
|
||||
// Note that fini will also run if init fails
|
||||
pub fn new(value: &'a mut T, init: impl FnOnce(&mut T) -> Result<()>, fini: F) -> Result<Self> {
|
||||
let mut this = Self {
|
||||
value,
|
||||
fini: Some(fini),
|
||||
};
|
||||
init(&mut this.value)?;
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, F: FnOnce(&mut T)> Drop for CallbackGuard<'a, T, F> {
|
||||
fn drop(&mut self) {
|
||||
let fini = self.fini.take().unwrap();
|
||||
fini(&mut self.value);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChangeDetect {
|
||||
name: &'static str,
|
||||
reg: MmioPtr<u32>,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl ChangeDetect {
|
||||
fn new(name: &'static str, reg: MmioPtr<u32>) -> Self {
|
||||
let value = reg.read();
|
||||
Self { name, reg, value }
|
||||
}
|
||||
|
||||
fn log(&self) {
|
||||
log::info!("{} {:08X}", self.name, self.value);
|
||||
}
|
||||
|
||||
fn check(&mut self) {
|
||||
let value = self.reg.read();
|
||||
if value != self.value {
|
||||
self.value = value;
|
||||
self.log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DeviceKind {
|
||||
KabyLake,
|
||||
TigerLake,
|
||||
Alchemist,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
DdiHotplug(&'static str),
|
||||
}
|
||||
|
||||
pub struct InterruptRegs {
|
||||
// Interrupt status register, has live status of interrupts
|
||||
pub isr: MmioPtr<u32>,
|
||||
// Interrupt mask register, masks isr for iir, 0 is unmasked
|
||||
pub imr: MmioPtr<u32>,
|
||||
// Interrupt identity register, write 1 to clear
|
||||
pub iir: MmioPtr<u32>,
|
||||
// Interrupt enable register, 1 allows interrupt to propogate
|
||||
pub ier: MmioPtr<u32>,
|
||||
}
|
||||
|
||||
pub struct Interrupter {
|
||||
change_detects: Vec<ChangeDetect>,
|
||||
display_int_ctl: MmioPtr<u32>,
|
||||
display_int_ctl_enable: u32,
|
||||
display_int_ctl_sde: u32,
|
||||
gfx_mstr_intr: Option<MmioPtr<u32>>,
|
||||
gfx_mstr_intr_display: u32,
|
||||
gfx_mstr_intr_enable: u32,
|
||||
sde_interrupt: InterruptRegs,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MmioRegion {
|
||||
phys: usize,
|
||||
virt: usize,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl MmioRegion {
|
||||
fn new(phys: usize, size: usize, memory_type: common::MemoryType) -> Result<Self> {
|
||||
let virt = unsafe { common::physmap(phys, size, common::Prot::RW, memory_type)? as usize };
|
||||
Ok(Self { phys, virt, size })
|
||||
}
|
||||
|
||||
unsafe fn mmio(&self, offset: usize) -> Result<MmioPtr<u32>> {
|
||||
// Any errors here will return ERANGE
|
||||
let err = Error::new(ERANGE);
|
||||
if offset.checked_add(mem::size_of::<u32>()).ok_or(err)? > self.size {
|
||||
return Err(err);
|
||||
}
|
||||
let addr = self.virt.checked_add(offset).ok_or(err)?;
|
||||
Ok(unsafe { MmioPtr::new(addr as *mut u32) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MmioRegion {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = libredox::call::munmap(self.virt as *mut (), self.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum VideoInput {
|
||||
Hdmi,
|
||||
Dp,
|
||||
}
|
||||
|
||||
pub struct Device {
|
||||
kind: DeviceKind,
|
||||
alloc_buffers: RangeAllocator<u32>,
|
||||
bios: Option<Bios>,
|
||||
ddis: Vec<Ddi>,
|
||||
dpclka_cfgcr0: Option<MmioPtr<u32>>,
|
||||
dplls: Vec<Dpll>,
|
||||
events: VecDeque<Event>,
|
||||
framebuffers: Vec<DeviceFb>,
|
||||
int: Interrupter,
|
||||
gttmm: Arc<MmioRegion>,
|
||||
ggtt: GlobalGtt,
|
||||
gm: MmioRegion,
|
||||
gmbus: Gmbus,
|
||||
pipes: Vec<Pipe>,
|
||||
power_wells: PowerWells,
|
||||
ref_freq: u64,
|
||||
transcoders: Vec<Transcoder>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Device {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Device")
|
||||
.field("kind", &self.kind)
|
||||
.field("alloc_buffers", &self.alloc_buffers)
|
||||
.field("gttmm", &self.gttmm)
|
||||
.field("gm", &self.gm)
|
||||
.field("ref_freq", &self.ref_freq)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(pcid_handle: &mut PciFunctionHandle, func: &PciFunction) -> Result<Self> {
|
||||
let kind = match (func.full_device_id.vendor_id, func.full_device_id.device_id) {
|
||||
// Kaby Lake
|
||||
(0x8086, 0x5912) |
|
||||
(0x8086, 0x5916) |
|
||||
(0x8086, 0x591B) |
|
||||
(0x8086, 0x591E) |
|
||||
(0x8086, 0x5926) |
|
||||
// Comet Lake, seems to be compatible with Kaby Lake
|
||||
(0x8086, 0x9B21) |
|
||||
(0x8086, 0x9B41) |
|
||||
(0x8086, 0x9BA4) |
|
||||
(0x8086, 0x9BAA) |
|
||||
(0x8086, 0x9BAC) |
|
||||
(0x8086, 0x9BC4) |
|
||||
(0x8086, 0x9BC5) |
|
||||
(0x8086, 0x9BC6) |
|
||||
(0x8086, 0x9BC8) |
|
||||
(0x8086, 0x9BCA) |
|
||||
(0x8086, 0x9BCC) |
|
||||
(0x8086, 0x9BE6) |
|
||||
(0x8086, 0x9BF6) => {
|
||||
DeviceKind::KabyLake
|
||||
}
|
||||
// Tiger Lake
|
||||
(0x8086, 0x9A40) |
|
||||
(0x8086, 0x9A49) |
|
||||
(0x8086, 0x9A60) |
|
||||
(0x8086, 0x9A68) |
|
||||
(0x8086, 0x9A70) |
|
||||
(0x8086, 0x9A78) => {
|
||||
DeviceKind::TigerLake
|
||||
}
|
||||
// Alchemist
|
||||
(0x8086, 0x5690) | // A770M
|
||||
(0x8086, 0x5691) | // A730M
|
||||
(0x8086, 0x5692) | // A550M
|
||||
(0x8086, 0x5693) | // A370M
|
||||
(0x8086, 0x5694) | // A350M
|
||||
(0x8086, 0x5696) | // A570M
|
||||
(0x8086, 0x5697) | // A530M
|
||||
(0x8086, 0x56A0) | // A770
|
||||
(0x8086, 0x56A1) | // A750
|
||||
(0x8086, 0x56A5) | // A380
|
||||
(0x8086, 0x56A6) | // A310
|
||||
(0x8086, 0x56B0) | // Pro A30M
|
||||
(0x8086, 0x56B1) | // Pro A40/A50
|
||||
(0x8086, 0x56B2) | // Pro A60M
|
||||
(0x8086, 0x56B3) | // Pro A60
|
||||
(0x8086, 0x56C0) | // GPU Flex 170
|
||||
(0x8086, 0x56C1) // GPU Flex 140
|
||||
=> {
|
||||
DeviceKind::Alchemist
|
||||
}
|
||||
(vendor_id, device_id) => {
|
||||
log::error!("unsupported ID {:04X}:{:04X}", vendor_id, device_id);
|
||||
return Err(Error::new(ENODEV));
|
||||
}
|
||||
};
|
||||
|
||||
let gttmm = {
|
||||
let (phys, size) = func.bars[0].expect_mem();
|
||||
Arc::new(MmioRegion::new(
|
||||
phys,
|
||||
size,
|
||||
common::MemoryType::Uncacheable,
|
||||
)?)
|
||||
};
|
||||
log::info!("GTTMM {:X?}", gttmm);
|
||||
let gm = {
|
||||
let (phys, size) = func.bars[2].expect_mem();
|
||||
MmioRegion::new(phys, size, common::MemoryType::WriteCombining)?
|
||||
};
|
||||
log::info!("GM {:X?}", gm);
|
||||
/* IOBAR not used, not present on all generations
|
||||
let iobar = func.bars[4].expect_port();
|
||||
log::debug!("IOBAR {:X?}", iobar);
|
||||
*/
|
||||
|
||||
// IGD OpRegion/Software SCI/_DSM for Skylake Processors
|
||||
let bios_base = unsafe { pcid_handle.read_config(0xFC) };
|
||||
let bios = if bios_base != 0 {
|
||||
log::info!("BIOS {:X?}", bios_base);
|
||||
// This is the default BIOS size
|
||||
let bios_size = 8 * 1024;
|
||||
match MmioRegion::new(
|
||||
bios_base as usize,
|
||||
bios_size,
|
||||
common::MemoryType::Uncacheable,
|
||||
) {
|
||||
Ok(region) => match Bios::new(region) {
|
||||
Ok(bios) => Some(bios),
|
||||
Err(err) => {
|
||||
log::warn!("failed to parse BIOS at {:08X}: {}", bios_base, err);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::warn!("failed to map BIOS at {:08X}: {}", bios_base, err);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ggtt = unsafe {
|
||||
GlobalGtt::new(
|
||||
pcid_handle,
|
||||
gttmm.clone(),
|
||||
//TODO: how to use 64-bit surface addresses?
|
||||
gm.size.min(u32::MAX as usize) as u32,
|
||||
)
|
||||
};
|
||||
//unsafe { ggtt.reset() };
|
||||
|
||||
// GMBUS seems to be stable for all generations
|
||||
let gmbus = unsafe { Gmbus::new(>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::alchemist(>tmm)?;
|
||||
// FIXME transcoders are probably different too
|
||||
transcoders = Transcoder::tigerlake(>tmm)?;
|
||||
// Power wells are distinct
|
||||
ddis = Ddi::alchemist(>tmm)?;
|
||||
power_wells = PowerWells::alchemist(>tmm)?;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: get number of available buffers
|
||||
let buffers = 1024;
|
||||
Ok(Self {
|
||||
kind,
|
||||
alloc_buffers: RangeAllocator::new(0..buffers),
|
||||
bios,
|
||||
ddis,
|
||||
dpclka_cfgcr0,
|
||||
dplls,
|
||||
events: VecDeque::new(),
|
||||
framebuffers: Vec::new(),
|
||||
int,
|
||||
gttmm,
|
||||
ggtt,
|
||||
gm,
|
||||
gmbus,
|
||||
pipes,
|
||||
power_wells,
|
||||
ref_freq,
|
||||
transcoders,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init_inner(&mut self) {
|
||||
// Discover current framebuffers
|
||||
self.alloc_buffers.reset();
|
||||
self.framebuffers.clear();
|
||||
for pipe in self.pipes.iter() {
|
||||
for plane in pipe.planes.iter() {
|
||||
if plane.ctl.readf(PLANE_CTL_ENABLE) {
|
||||
plane.fetch_modeset(&mut self.alloc_buffers);
|
||||
|
||||
self.framebuffers
|
||||
.push(plane.fetch_framebuffer(&self.gm, &mut self.ggtt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Probe all DDIs
|
||||
let ddi_names: Vec<&str> = self.ddis.iter().map(|ddi| ddi.name).collect();
|
||||
for ddi_name in ddi_names {
|
||||
self.probe_ddi(ddi_name).expect("failed to probe DDI");
|
||||
}
|
||||
|
||||
self.dump();
|
||||
|
||||
log::info!(
|
||||
"device initialized with {} framebuffers",
|
||||
self.framebuffers.len()
|
||||
);
|
||||
|
||||
// Enable SDE interrupts
|
||||
{
|
||||
let mut mask = 0;
|
||||
for ddi in self.ddis.iter() {
|
||||
if let Some(sde_interrupt_hotplug) = ddi.sde_interrupt_hotplug {
|
||||
mask |= sde_interrupt_hotplug;
|
||||
}
|
||||
}
|
||||
let sde_int = &mut self.int.sde_interrupt;
|
||||
// Enable DDI hotplug interrupts
|
||||
sde_int.ier.write(mask);
|
||||
// Clear identity register
|
||||
sde_int.iir.write(sde_int.iir.read());
|
||||
// Unmask all interrupts
|
||||
sde_int.imr.write(0);
|
||||
}
|
||||
// Enable display interrupts
|
||||
self.int
|
||||
.display_int_ctl
|
||||
.write(self.int.display_int_ctl_enable);
|
||||
if let Some(gfx_mstr_intr) = &mut self.int.gfx_mstr_intr {
|
||||
// Enable graphics interrupts
|
||||
gfx_mstr_intr.write(self.int.gfx_mstr_intr_enable);
|
||||
}
|
||||
for change_detect in self.int.change_detects.iter_mut() {
|
||||
change_detect.log();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump(&self) {
|
||||
for ddi in self.ddis.iter() {
|
||||
if ddi.buf_ctl.readf(DDI_BUF_CTL_ENABLE) {
|
||||
ddi.dump();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dpclka_cfgcr0) = &self.dpclka_cfgcr0 {
|
||||
eprintln!("dpclka_cfgcr0 {:08X}", dpclka_cfgcr0.read());
|
||||
}
|
||||
for dpll in self.dplls.iter() {
|
||||
if dpll.enable.readf(DPLL_ENABLE_ENABLE) {
|
||||
dpll.dump();
|
||||
}
|
||||
}
|
||||
|
||||
for (transcoder, pipe) in self.transcoders.iter().zip(self.pipes.iter()) {
|
||||
if transcoder.conf.readf(TRANS_CONF_ENABLE) {
|
||||
transcoder.dump();
|
||||
pipe.dump();
|
||||
for plane in pipe.planes.iter() {
|
||||
if plane.index == 0 || plane.ctl.readf(PLANE_CTL_ENABLE) {
|
||||
eprint!(" ");
|
||||
plane.dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn probe_ddi(&mut self, name: &str) -> Result<bool> {
|
||||
let Some(ddi) = self.ddis.iter_mut().find(|ddi| ddi.name == name) else {
|
||||
log::warn!("DDI {} not found", name);
|
||||
return Err(Error::new(EIO));
|
||||
};
|
||||
|
||||
// Enable DDI power well
|
||||
self.power_wells.enable_well_by_ddi(ddi.name)?;
|
||||
|
||||
let Some((source, edid_data)) =
|
||||
ddi.probe_edid(&mut self.power_wells, &self.gttmm, &mut self.gmbus)?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let edid = match edid::parse(&edid_data).to_full_result() {
|
||||
Ok(edid) => {
|
||||
log::info!("DDI {} EDID from {}: {:?}", ddi.name, source, edid);
|
||||
edid
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"DDI {} failed to parse EDID from {}: {:?}",
|
||||
ddi.name,
|
||||
source,
|
||||
err
|
||||
);
|
||||
// Will try again but not fail the driver
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
let timing_opt = edid.descriptors.iter().find_map(|desc| match desc {
|
||||
edid::Descriptor::DetailedTiming(timing) => Some(timing),
|
||||
_ => None,
|
||||
});
|
||||
let Some(timing) = timing_opt else {
|
||||
log::warn!(
|
||||
"DDI {} EDID from {} missing detailed timing",
|
||||
ddi.name,
|
||||
source
|
||||
);
|
||||
// Will try again but not fail the driver
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let mut modeset = |ddi: &mut Ddi, input: VideoInput| -> Result<()> {
|
||||
// IHD-OS-TGL-Vol 12-1.22-Rev2.0 "Sequences for HDMI and DVI"
|
||||
|
||||
// Power wells should already be enabled
|
||||
|
||||
//TODO: Type-C needs aux power enabled and max lanes set
|
||||
|
||||
// Enable port PLL without SSC. Not required on Type-C ports
|
||||
if let Some(clock_shift) = ddi.dpclka_cfgcr0_clock_shift {
|
||||
// Find free DPLL
|
||||
let dpll = self
|
||||
.dplls
|
||||
.iter_mut()
|
||||
.find(|dpll| !dpll.enable.readf(DPLL_ENABLE_ENABLE))
|
||||
.ok_or_else(|| {
|
||||
log::error!("failed to find free DPLL");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
|
||||
// DPLL power guard
|
||||
let mut dpll_enable = unsafe { MmioPtr::new(dpll.enable.as_mut_ptr()) };
|
||||
let dpll_power_guard = CallbackGuard::new(
|
||||
&mut dpll_enable,
|
||||
|dpll_enable| {
|
||||
// Enable DPLL power
|
||||
dpll_enable.writef(DPLL_ENABLE_POWER_ENABLE, true);
|
||||
//TODO: timeout not specified in docs, should be very fast
|
||||
let timeout = Timeout::from_micros(1);
|
||||
while !dpll_enable.readf(DPLL_ENABLE_POWER_STATE) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::debug!("timeout while enabling DPLL {} power", dpll.name);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|dpll_enable| {
|
||||
// Disable DPLL power
|
||||
dpll_enable.writef(DPLL_ENABLE_POWER_ENABLE, false);
|
||||
},
|
||||
)?;
|
||||
|
||||
match input {
|
||||
VideoInput::Hdmi => {
|
||||
// Set SSC enable/disable. For HDMI, always disable
|
||||
dpll.ssc.writef(DPLL_SSC_ENABLE, false);
|
||||
|
||||
// Configure DPLL frequency
|
||||
dpll.set_freq_hdmi(self.ref_freq, &timing)?;
|
||||
}
|
||||
VideoInput::Dp => {
|
||||
log::warn!("DPLL for DisplayPort not implemented");
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: "Sequence Before Frequency Change"
|
||||
|
||||
// Enable DPLL
|
||||
//TODO: use guard?
|
||||
{
|
||||
dpll.enable.writef(DPLL_ENABLE_ENABLE, true);
|
||||
let timeout = Timeout::from_micros(50);
|
||||
while !dpll.enable.readf(DPLL_ENABLE_LOCK) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::debug!("timeout while enabling DPLL {}", dpll.name);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: "Sequence After Frequency Change"
|
||||
|
||||
// Update DPLL mapping
|
||||
if let Some(dpclka_cfgcr0) = &mut self.dpclka_cfgcr0 {
|
||||
const DPCLKA_CFGCR0_CLOCK_MASK: u32 = 0b11;
|
||||
|
||||
let mut v = dpclka_cfgcr0.read();
|
||||
v &= !(DPCLKA_CFGCR0_CLOCK_MASK << clock_shift);
|
||||
v |= dpll.dpclka_cfgcr0_clock_value << clock_shift;
|
||||
dpclka_cfgcr0.write(v);
|
||||
}
|
||||
|
||||
// Continue to allow DPLL power
|
||||
mem::forget(dpll_power_guard);
|
||||
}
|
||||
|
||||
// Enable DPLL clock (must be done separately from PLL mapping)
|
||||
if let Some(dpclka_cfgcr0) = &mut self.dpclka_cfgcr0 {
|
||||
if let Some(clock_off) = ddi.dpclka_cfgcr0_clock_off {
|
||||
dpclka_cfgcr0.writef(clock_off, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable IO power
|
||||
//TODO: the request can be shared by multiple DDIs
|
||||
//TODO: skip if TBT
|
||||
let pwr_well_ctl_ddi_request = ddi.pwr_well_ctl_ddi_request;
|
||||
let pwr_well_ctl_ddi_state = ddi.pwr_well_ctl_ddi_state;
|
||||
let mut pwr_well_ctl_ddi =
|
||||
unsafe { MmioPtr::new(self.power_wells.ctl_ddi.as_mut_ptr()) };
|
||||
let pwr_guard = CallbackGuard::new(
|
||||
&mut pwr_well_ctl_ddi,
|
||||
|pwr_well_ctl_ddi| {
|
||||
// Enable IO power
|
||||
pwr_well_ctl_ddi.writef(pwr_well_ctl_ddi_request, true);
|
||||
let timeout = Timeout::from_micros(30);
|
||||
while !pwr_well_ctl_ddi.readf(pwr_well_ctl_ddi_state) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::debug!("timeout while requesting DDI {} IO power", ddi.name);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|pwr_well_ctl_ddi| {
|
||||
// Disable IO power
|
||||
pwr_well_ctl_ddi.writef(pwr_well_ctl_ddi_request, false);
|
||||
},
|
||||
)?;
|
||||
|
||||
//TODO: Type-C DP_MODE
|
||||
|
||||
// Enable planes, pipe, and transcoder
|
||||
{
|
||||
// Find free transcoder with free pipe
|
||||
let mut transcoder_pipe = None;
|
||||
for (transcoder, pipe) in self.transcoders.iter_mut().zip(self.pipes.iter_mut()) {
|
||||
if transcoder.conf.readf(TRANS_CONF_ENABLE) {
|
||||
continue;
|
||||
}
|
||||
//TODO: how would we know if pipe is in use?
|
||||
transcoder_pipe = Some((transcoder, pipe));
|
||||
break;
|
||||
}
|
||||
let Some((transcoder, pipe)) = transcoder_pipe else {
|
||||
log::error!("free transcoder and pipe not found");
|
||||
return Err(Error::new(EIO));
|
||||
};
|
||||
|
||||
// Enable pipe and transcoder power wells
|
||||
self.power_wells.enable_well_by_pipe(pipe.name)?;
|
||||
self.power_wells
|
||||
.enable_well_by_transcoder(transcoder.name)?;
|
||||
|
||||
// Configure transcoder clock select
|
||||
if let Some(transcoder_index) = ddi.transcoder_index {
|
||||
transcoder
|
||||
.clk_sel
|
||||
.write(transcoder_index << transcoder.clk_sel_shift);
|
||||
}
|
||||
|
||||
// Set pipe bottom color to blue for debugging
|
||||
pipe.bottom_color.write(0x3FF);
|
||||
|
||||
// Configure and enable planes
|
||||
//TODO: THIS IS HACKY
|
||||
if let Some(plane) = pipe.planes.first_mut() {
|
||||
let width = timing.horizontal_active_pixels as u32;
|
||||
let height = timing.vertical_active_lines as u32;
|
||||
|
||||
let fb = DeviceFb::alloc(&self.gm, &mut self.ggtt, width, height)?;
|
||||
|
||||
plane.modeset(&mut self.alloc_buffers)?;
|
||||
plane.set_framebuffer(&fb);
|
||||
|
||||
self.framebuffers.push(fb);
|
||||
}
|
||||
|
||||
//TODO: VGA and panel fitter steps?
|
||||
|
||||
// Configure transcoder timings and other pipe and transcoder settings
|
||||
transcoder.modeset(pipe, &timing);
|
||||
|
||||
// Configure and enable TRANS_DDI_FUNC_CTL
|
||||
{
|
||||
let mut ddi_func_ctl = TRANS_DDI_FUNC_CTL_ENABLE |
|
||||
//TODO: allow different bits per color
|
||||
TRANS_DDI_FUNC_CTL_BPC_8 |
|
||||
//TODO: correct port width selection
|
||||
TRANS_DDI_FUNC_CTL_PORT_WIDTH_4;
|
||||
|
||||
if let Some(transcoder_index) = ddi.transcoder_index {
|
||||
ddi_func_ctl |= transcoder_index << transcoder.ddi_func_ctl_ddi_shift;
|
||||
}
|
||||
|
||||
match input {
|
||||
VideoInput::Hdmi => {
|
||||
ddi_func_ctl |= TRANS_DDI_FUNC_CTL_MODE_HDMI;
|
||||
|
||||
// Set HDMI scrambling and high TMDS char rate based on symbol rate > 340 MHz
|
||||
if timing.pixel_clock > 340_000 {
|
||||
ddi_func_ctl |= transcoder.ddi_func_ctl_hdmi_scrambling
|
||||
| transcoder.ddi_func_ctl_high_tmds_char_rate;
|
||||
}
|
||||
}
|
||||
VideoInput::Dp => {
|
||||
//TODO: MST
|
||||
ddi_func_ctl |= TRANS_DDI_FUNC_CTL_MODE_DP_SST;
|
||||
}
|
||||
}
|
||||
|
||||
match (timing.features >> 3) & 0b11 {
|
||||
// Digital sync, separate
|
||||
0b11 => {
|
||||
if (timing.features & (1 << 2)) != 0 {
|
||||
ddi_func_ctl |= TRANS_DDI_FUNC_CTL_SYNC_POLARITY_VSHIGH;
|
||||
}
|
||||
if (timing.features & (1 << 1)) != 0 {
|
||||
ddi_func_ctl |= TRANS_DDI_FUNC_CTL_SYNC_POLARITY_HSHIGH;
|
||||
}
|
||||
}
|
||||
unsupported => {
|
||||
log::warn!("unsupported sync {:#x}", unsupported);
|
||||
}
|
||||
}
|
||||
|
||||
transcoder.ddi_func_ctl.write(ddi_func_ctl);
|
||||
}
|
||||
|
||||
// Configure and enable TRANS_CONF
|
||||
let mut conf = transcoder.conf.read();
|
||||
// Set mode to progressive
|
||||
conf &= !TRANS_CONF_MODE_MASK;
|
||||
// Enable transcoder
|
||||
conf |= TRANS_CONF_ENABLE;
|
||||
transcoder.conf.write(conf);
|
||||
//TODO: what is the correct timeout?
|
||||
let timeout = Timeout::from_millis(100);
|
||||
while !transcoder.conf.readf(TRANS_CONF_STATE) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!(
|
||||
"timeout on DDI {} transcoder {} enable",
|
||||
ddi.name,
|
||||
transcoder.name
|
||||
);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// Enable port
|
||||
{
|
||||
// Configure voltage swing and related IO settings
|
||||
match input {
|
||||
VideoInput::Hdmi => {
|
||||
ddi.voltage_swing_hdmi(&self.gttmm, &timing)?;
|
||||
}
|
||||
VideoInput::Dp => {
|
||||
//TODO ddi.voltage_swing_dp(&self.gttmm)?;
|
||||
log::error!("voltage swing for DP not implemented");
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
|
||||
// Configure PORT_CL_DW10 static power down to power up all lanes
|
||||
//TODO: only power up required lanes
|
||||
if let Some(mut port_cl_dw10) = ddi.port_cl(PortClReg::Dw10) {
|
||||
port_cl_dw10.writef(0b1111 << 4, false);
|
||||
}
|
||||
|
||||
// Configure and enable DDI_BUF_CTL
|
||||
//TODO: more DDI_BUF_CTL bits?
|
||||
ddi.buf_ctl.writef(DDI_BUF_CTL_ENABLE, true);
|
||||
|
||||
// Wait for DDI_BUF_CTL IDLE = 0, timeout after 500 us
|
||||
let timeout = Timeout::from_micros(500);
|
||||
while ddi.buf_ctl.readf(DDI_BUF_CTL_IDLE) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::warn!("timeout while waiting for DDI {} active", ddi.name);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep IO power on if finished
|
||||
mem::forget(pwr_guard);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if ddi.buf_ctl.readf(DDI_BUF_CTL_IDLE) {
|
||||
log::info!("DDI {} idle, will attempt mode setting", ddi.name);
|
||||
const EDID_VIDEO_INPUT_UNDEFINED: u8 = (1 << 7) | 0b0000;
|
||||
const EDID_VIDEO_INPUT_DVI: u8 = (1 << 7) | 0b0001;
|
||||
const EDID_VIDEO_INPUT_HDMI_A: u8 = (1 << 7) | 0b0010;
|
||||
const EDID_VIDEO_INPUT_HDMI_B: u8 = (1 << 7) | 0b0011;
|
||||
const EDID_VIDEO_INPUT_DP: u8 = (1 << 7) | 0b0101;
|
||||
const EDID_VIDEO_INPUT_MASK: u8 = (1 << 7) | 0b1111;
|
||||
let input = match edid_data[20] & EDID_VIDEO_INPUT_MASK {
|
||||
//TODO: how to accurately discover input type?
|
||||
//TODO: HDMI often shows up as undefined, do others?
|
||||
EDID_VIDEO_INPUT_UNDEFINED
|
||||
| EDID_VIDEO_INPUT_DVI
|
||||
| EDID_VIDEO_INPUT_HDMI_A
|
||||
| EDID_VIDEO_INPUT_HDMI_B => VideoInput::Hdmi,
|
||||
EDID_VIDEO_INPUT_DP => VideoInput::Dp,
|
||||
unknown => {
|
||||
log::warn!("EDID video input 0x{:02X} not supported", unknown);
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
};
|
||||
//TODO: DisplayPort modeset not complete
|
||||
match modeset(ddi, input) {
|
||||
Ok(()) => {
|
||||
log::info!("DDI {} modeset {:?} finished", ddi.name, input);
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("DDI {} modeset {:?} failed: {}", ddi.name, input, err);
|
||||
// Will try again but not fail the driver
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::info!("DDI {} already active", ddi.name);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn handle_display_irq(&mut self) -> bool {
|
||||
let display_ints = self.int.display_int_ctl.read() & !self.int.display_int_ctl_enable;
|
||||
if display_ints != 0 {
|
||||
log::info!(" display ints {:08X}", display_ints);
|
||||
if display_ints & self.int.display_int_ctl_sde != 0 {
|
||||
let sde_ints = self.int.sde_interrupt.iir.read();
|
||||
self.int.sde_interrupt.iir.write(sde_ints);
|
||||
log::info!(" south display engine ints {:08X}", sde_ints);
|
||||
for ddi in self.ddis.iter() {
|
||||
if let Some(sde_interrupt_hotplug) = ddi.sde_interrupt_hotplug {
|
||||
if sde_ints & sde_interrupt_hotplug == sde_interrupt_hotplug {
|
||||
self.events.push_back(Event::DdiHotplug(ddi.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_irq(&mut self) -> bool {
|
||||
let had_irq = if let Some(gfx_mstr_intr) = &mut self.int.gfx_mstr_intr {
|
||||
let gfx_ints = gfx_mstr_intr.read() & !self.int.gfx_mstr_intr_enable;
|
||||
if gfx_ints != 0 {
|
||||
log::info!("gfx ints {:08X}", gfx_ints);
|
||||
gfx_mstr_intr.write(gfx_ints | self.int.gfx_mstr_intr_enable);
|
||||
|
||||
if gfx_ints & self.int.gfx_mstr_intr_display != 0 {
|
||||
self.handle_display_irq();
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.handle_display_irq()
|
||||
};
|
||||
|
||||
if had_irq {
|
||||
for change_detect in self.int.change_detects.iter_mut() {
|
||||
change_detect.check();
|
||||
}
|
||||
}
|
||||
|
||||
had_irq
|
||||
}
|
||||
|
||||
pub fn handle_events(&mut self) {
|
||||
while let Some(event) = self.events.pop_front() {
|
||||
match event {
|
||||
Event::DdiHotplug(ddi_name) => {
|
||||
log::info!("DDI {} plugged", ddi_name);
|
||||
for _attempt in 0..4 {
|
||||
//TODO: gmbus times out!
|
||||
match self.probe_ddi(ddi_name) {
|
||||
Ok(true) => {
|
||||
break;
|
||||
}
|
||||
Ok(false) => {
|
||||
log::warn!("timeout probing {}", ddi_name);
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to probe {}: {}", ddi_name, err);
|
||||
}
|
||||
}
|
||||
//TODO: do this asynchronously so scheme events can be handled
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
use common::io::{Io, MmioPtr};
|
||||
use range_alloc::RangeAllocator;
|
||||
use syscall::error::Result;
|
||||
use syscall::{Error, EIO};
|
||||
|
||||
use super::buffer::GpuBuffer;
|
||||
use super::{GlobalGtt, MmioRegion};
|
||||
|
||||
pub const PLANE_CTL_ENABLE: u32 = 1 << 31;
|
||||
|
||||
pub const PLANE_WM_ENABLE: u32 = 1 << 31;
|
||||
pub const PLANE_WM_LINES_SHIFT: u32 = 14;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceFb {
|
||||
pub buffer: GpuBuffer,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub stride: u32,
|
||||
}
|
||||
|
||||
impl DeviceFb {
|
||||
pub unsafe fn new(
|
||||
gm: &MmioRegion,
|
||||
surf: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
stride: u32,
|
||||
clear: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
buffer: unsafe { GpuBuffer::new(gm, surf, stride * height, clear) },
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc(
|
||||
gm: &MmioRegion,
|
||||
ggtt: &mut GlobalGtt,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> syscall::Result<Self> {
|
||||
let (buffer, stride) = GpuBuffer::alloc_dumb(gm, ggtt, width, height)?;
|
||||
|
||||
Ok(DeviceFb {
|
||||
buffer,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Plane {
|
||||
pub name: &'static str,
|
||||
pub index: usize,
|
||||
pub buf_cfg: MmioPtr<u32>,
|
||||
pub color_ctl: Option<MmioPtr<u32>>,
|
||||
pub color_ctl_gamma_disable: u32,
|
||||
pub ctl: MmioPtr<u32>,
|
||||
pub ctl_source_rgb_8888: u32,
|
||||
pub ctl_source_mask: u32,
|
||||
pub offset: MmioPtr<u32>,
|
||||
pub pos: MmioPtr<u32>,
|
||||
pub size: MmioPtr<u32>,
|
||||
pub stride: MmioPtr<u32>,
|
||||
pub surf: MmioPtr<u32>,
|
||||
pub wm: [MmioPtr<u32>; 8],
|
||||
pub wm_trans: MmioPtr<u32>,
|
||||
}
|
||||
|
||||
impl Plane {
|
||||
pub fn fetch_modeset(&self, alloc_buffers: &mut RangeAllocator<u32>) {
|
||||
let buf_cfg = self.buf_cfg.read();
|
||||
let buffer_start = buf_cfg & 0x7FF;
|
||||
let buffer_end = (buf_cfg >> 16) & 0x7FF;
|
||||
alloc_buffers
|
||||
.allocate_exact_range(buffer_start..(buffer_end + 1))
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"failed to allocate pre-existing buffer blocks {} to {}: {:?}",
|
||||
buffer_start, buffer_end, err
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn modeset(&mut self, alloc_buffers: &mut RangeAllocator<u32>) -> syscall::Result<()> {
|
||||
// FIXME handle runtime buffer reconfiguration
|
||||
//TODO: enable DBUF if more buffers needed
|
||||
//TODO: more blocks would mean better power usage
|
||||
// Minimum is 8 blocks for linear planes, 160 blocks is recommended for pre-OS init
|
||||
let buffer_size = 160;
|
||||
let buffer = alloc_buffers.allocate_range(buffer_size).map_err(|err| {
|
||||
log::warn!(
|
||||
"failed to allocate {} buffer blocks: {:?}",
|
||||
buffer_size,
|
||||
err
|
||||
);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
self.buf_cfg.write(buffer.start | (buffer.end << 16));
|
||||
|
||||
//TODO: correct watermark calculation
|
||||
self.wm[0].write(PLANE_WM_ENABLE | (2 << PLANE_WM_LINES_SHIFT) | buffer.len() as u32);
|
||||
for i in 1..self.wm.len() {
|
||||
self.wm[i].writef(PLANE_WM_ENABLE, false);
|
||||
}
|
||||
self.wm_trans.writef(PLANE_WM_ENABLE, false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fetch_framebuffer(&self, gm: &MmioRegion, ggtt: &mut GlobalGtt) -> DeviceFb {
|
||||
let size = self.size.read();
|
||||
let width = (size & 0xFFFF) + 1;
|
||||
let height = ((size >> 16) & 0xFFFF) + 1;
|
||||
let stride_64 = self.stride.read() & 0x7FF;
|
||||
//TODO: this will be wrong for tiled planes
|
||||
let stride = stride_64 * 64;
|
||||
let surf = self.surf.read() & 0xFFFFF000;
|
||||
//TODO: read bits per pixel
|
||||
let surf_size = (stride * height).next_multiple_of(4096);
|
||||
ggtt.reserve(surf, surf_size);
|
||||
|
||||
unsafe { DeviceFb::new(gm, surf, width, height, stride, true) }
|
||||
}
|
||||
|
||||
pub fn set_framebuffer(&mut self, fb: &DeviceFb) {
|
||||
//TODO: documentation on this is not great
|
||||
let stride_64 = fb.stride / 64;
|
||||
|
||||
self.size.write((fb.width - 1) | ((fb.height - 1) << 16));
|
||||
self.stride.write(stride_64);
|
||||
|
||||
self.surf.write(fb.buffer.gm_offset);
|
||||
|
||||
// Disable gamma
|
||||
if let Some(color_ctl) = &mut self.color_ctl {
|
||||
color_ctl.write(self.color_ctl_gamma_disable);
|
||||
}
|
||||
|
||||
//TODO: more PLANE_CTL bits
|
||||
self.ctl.write(PLANE_CTL_ENABLE | self.ctl_source_rgb_8888);
|
||||
}
|
||||
|
||||
pub fn dump(&self) {
|
||||
eprint!("Plane {}", self.name);
|
||||
eprint!(" buf_cfg {:08X}", self.buf_cfg.read());
|
||||
if let Some(reg) = &self.color_ctl {
|
||||
eprint!(" color_ctl {:08X}", reg.read());
|
||||
}
|
||||
eprint!(" ctl {:08X}", self.ctl.read());
|
||||
eprint!(" offset {:08X}", self.offset.read());
|
||||
eprint!(" pos {:08X}", self.offset.read());
|
||||
eprint!(" size {:08X}", self.size.read());
|
||||
eprint!(" stride {:08X}", self.stride.read());
|
||||
eprint!(" surf {:08X}", self.surf.read());
|
||||
for i in 0..self.wm.len() {
|
||||
eprint!(" wm_{} {:08X}", i, self.wm[i].read());
|
||||
}
|
||||
eprint!(" wm_trans {:08X}", self.wm_trans.read());
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pipe {
|
||||
pub name: &'static str,
|
||||
pub index: usize,
|
||||
pub planes: Vec<Plane>,
|
||||
pub bottom_color: MmioPtr<u32>,
|
||||
pub misc: MmioPtr<u32>,
|
||||
pub srcsz: MmioPtr<u32>,
|
||||
}
|
||||
|
||||
impl Pipe {
|
||||
pub fn dump(&self) {
|
||||
eprint!("Pipe {}", self.name);
|
||||
eprint!(" bottom_color {:08X}", self.bottom_color.read());
|
||||
eprint!(" misc {:08X}", self.misc.read());
|
||||
eprint!(" srcsz {:08X}", self.srcsz.read());
|
||||
eprintln!();
|
||||
}
|
||||
|
||||
pub fn kabylake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
|
||||
let mut pipes = Vec::with_capacity(3);
|
||||
for (i, name) in ["A", "B", "C"].iter().enumerate() {
|
||||
let mut planes = Vec::new();
|
||||
//TODO: cursor plane
|
||||
for (j, name) in ["1", "2", "3"].iter().enumerate() {
|
||||
planes.push(Plane {
|
||||
name,
|
||||
index: j,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PLANE_BUF_CFG
|
||||
buf_cfg: unsafe { gttmm.mmio(0x7027C + i * 0x1000 + j * 0x100)? },
|
||||
// N/A
|
||||
color_ctl: None,
|
||||
color_ctl_gamma_disable: 0,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PLANE_CTL
|
||||
ctl: unsafe { gttmm.mmio(0x70180 + i * 0x1000 + j * 0x100)? },
|
||||
ctl_source_rgb_8888: 0b0100 << 24,
|
||||
ctl_source_mask: 0b1111 << 24,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PLANE_OFFSET
|
||||
offset: unsafe { gttmm.mmio(0x701A4 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PLANE_POS
|
||||
pos: unsafe { gttmm.mmio(0x7018C + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PLANE_SIZE
|
||||
size: unsafe { gttmm.mmio(0x70190 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PLANE_STRIDE
|
||||
stride: unsafe { gttmm.mmio(0x70188 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PLANE_SURF
|
||||
surf: unsafe { gttmm.mmio(0x7019C + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PLANE_WM
|
||||
wm: [
|
||||
unsafe { gttmm.mmio(0x70240 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70244 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70248 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x7024C + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70250 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70254 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70258 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x7025C + i * 0x1000 + j * 0x100)? },
|
||||
],
|
||||
wm_trans: unsafe { gttmm.mmio(0x70268 + i * 0x1000 + j * 0x100)? },
|
||||
});
|
||||
}
|
||||
pipes.push(Pipe {
|
||||
name,
|
||||
index: i,
|
||||
planes,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PIPE_BOTTOM_COLOR
|
||||
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PIPE_MISC
|
||||
misc: unsafe { gttmm.mmio(0x70030 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PIPE_SRCSZ
|
||||
srcsz: unsafe { gttmm.mmio(0x6001C + i * 0x1000)? },
|
||||
})
|
||||
}
|
||||
Ok(pipes)
|
||||
}
|
||||
|
||||
pub fn tigerlake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
|
||||
let mut pipes = Vec::with_capacity(4);
|
||||
for (i, name) in ["A", "B", "C", "D"].iter().enumerate() {
|
||||
let mut planes = Vec::new();
|
||||
//TODO: cursor plane
|
||||
for (j, name) in ["1", "2", "3", "4", "5", "6", "7"].iter().enumerate() {
|
||||
planes.push(Plane {
|
||||
name,
|
||||
index: j,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_BUF_CFG
|
||||
buf_cfg: unsafe { gttmm.mmio(0x7027C + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_COLOR_CTL
|
||||
color_ctl: Some(unsafe { gttmm.mmio(0x701CC + i * 0x1000 + j * 0x100)? }),
|
||||
color_ctl_gamma_disable: 1 << 13,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_CTL
|
||||
ctl: unsafe { gttmm.mmio(0x70180 + i * 0x1000 + j * 0x100)? },
|
||||
ctl_source_rgb_8888: 0b01000 << 23,
|
||||
ctl_source_mask: 0b11111 << 23,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_OFFSET
|
||||
offset: unsafe { gttmm.mmio(0x701A4 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_POS
|
||||
pos: unsafe { gttmm.mmio(0x7018C + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_SIZE
|
||||
size: unsafe { gttmm.mmio(0x70190 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_STRIDE
|
||||
stride: unsafe { gttmm.mmio(0x70188 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_SURF
|
||||
surf: unsafe { gttmm.mmio(0x7019C + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PLANE_WM
|
||||
wm: [
|
||||
unsafe { gttmm.mmio(0x70240 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70244 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70248 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x7024C + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70250 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70254 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70258 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x7025C + i * 0x1000 + j * 0x100)? },
|
||||
],
|
||||
wm_trans: unsafe { gttmm.mmio(0x70268 + i * 0x1000 + j * 0x100)? },
|
||||
});
|
||||
}
|
||||
pipes.push(Pipe {
|
||||
name,
|
||||
index: i,
|
||||
planes,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PIPE_BOTTOM_COLOR
|
||||
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PIPE_MISC
|
||||
misc: unsafe { gttmm.mmio(0x70030 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PIPE_SRCSZ
|
||||
srcsz: unsafe { gttmm.mmio(0x6001C + i * 0x1000)? },
|
||||
})
|
||||
}
|
||||
Ok(pipes)
|
||||
}
|
||||
|
||||
pub fn alchemist(gttmm: &MmioRegion) -> Result<Vec<Self>> {
|
||||
let mut pipes = Vec::with_capacity(4);
|
||||
for (i, name) in ["A", "B", "C", "D"].iter().enumerate() {
|
||||
let mut planes = Vec::new();
|
||||
//TODO: cursor plane
|
||||
for (j, name) in ["1", "2", "3", "4", "5"].iter().enumerate() {
|
||||
planes.push(Plane {
|
||||
name,
|
||||
index: j,
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_BUF_CFG
|
||||
buf_cfg: unsafe { gttmm.mmio(0x7057C + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_COLOR_CTL
|
||||
color_ctl: Some(unsafe { gttmm.mmio(0x704CC + i * 0x1000 + j * 0x100)? }),
|
||||
color_ctl_gamma_disable: 1 << 13,
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_CTL
|
||||
ctl: unsafe { gttmm.mmio(0x70480 + i * 0x1000 + j * 0x100)? },
|
||||
ctl_source_rgb_8888: 0b01000 << 23,
|
||||
ctl_source_mask: 0b11111 << 23,
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_OFFSET
|
||||
offset: unsafe { gttmm.mmio(0x704A4 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_POS
|
||||
pos: unsafe { gttmm.mmio(0x7048C + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_SIZE
|
||||
size: unsafe { gttmm.mmio(0x70490 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_STRIDE
|
||||
stride: unsafe { gttmm.mmio(0x70488 + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_SURF
|
||||
surf: unsafe { gttmm.mmio(0x7049C + i * 0x1000 + j * 0x100)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PLANE_WM
|
||||
wm: [
|
||||
unsafe { gttmm.mmio(0x70540 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70544 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70548 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x7054C + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70550 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70554 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x70558 + i * 0x1000 + j * 0x100)? },
|
||||
unsafe { gttmm.mmio(0x7055C + i * 0x1000 + j * 0x100)? },
|
||||
],
|
||||
wm_trans: unsafe { gttmm.mmio(0x70568 + i * 0x1000 + j * 0x100)? },
|
||||
});
|
||||
}
|
||||
pipes.push(Pipe {
|
||||
name,
|
||||
index: i,
|
||||
planes,
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PIPE_BOTTOM_COLOR
|
||||
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PIPE_MISC
|
||||
misc: unsafe { gttmm.mmio(0x70030 + i * 0x1000)? },
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PIPE_SRCSZ
|
||||
srcsz: unsafe { gttmm.mmio(0x6001C + i * 0x1000)? },
|
||||
})
|
||||
}
|
||||
Ok(pipes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
use common::{
|
||||
io::{Io, MmioPtr},
|
||||
timeout::Timeout,
|
||||
};
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use super::MmioRegion;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PowerWell {
|
||||
pub name: &'static str,
|
||||
pub depends: &'static [&'static str],
|
||||
pub ddis: &'static [&'static str],
|
||||
pub pipes: &'static [&'static str],
|
||||
pub transcoders: &'static [&'static str],
|
||||
pub request: u32,
|
||||
pub state: u32,
|
||||
pub fuse_status: u32,
|
||||
}
|
||||
|
||||
pub struct PowerWells {
|
||||
pub ctl: MmioPtr<u32>,
|
||||
pub ctl_aux: MmioPtr<u32>,
|
||||
pub ctl_ddi: MmioPtr<u32>,
|
||||
pub fuse_status: MmioPtr<u32>,
|
||||
pub fuse_status_pg0: u32,
|
||||
pub wells: Vec<PowerWell>,
|
||||
}
|
||||
|
||||
impl PowerWells {
|
||||
//TODO: return guard?
|
||||
pub fn enable_well(&mut self, name: &'static str) -> Result<()> {
|
||||
// Wait 20us for distribution of PG0
|
||||
{
|
||||
let timeout = Timeout::from_micros(20);
|
||||
while !self.fuse_status.readf(self.fuse_status_pg0) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::warn!("timeout on distribution of power well 0");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// self.wells iter copied to allow mutable self.enable_well later
|
||||
for well in self.wells.iter().copied() {
|
||||
if well.name == name {
|
||||
// Enable dependent wells
|
||||
for depend in well.depends.iter() {
|
||||
self.enable_well(depend)?;
|
||||
}
|
||||
|
||||
if !self.ctl.readf(well.request) {
|
||||
log::info!("enabling power well {}", well.name);
|
||||
}
|
||||
|
||||
// Set request bit
|
||||
self.ctl.writef(well.request, true);
|
||||
|
||||
// Wait 100us for enabled state
|
||||
{
|
||||
let timeout = Timeout::from_micros(100);
|
||||
while !self.ctl.readf(well.state) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::warn!("timeout enabling power well {}", well.name);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait 20us for distribution
|
||||
{
|
||||
let timeout = Timeout::from_micros(20);
|
||||
while !self.fuse_status.readf(well.fuse_status) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::warn!("timeout on distribution of power well {}", well.name);
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
log::warn!("power well {} not found", name);
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
|
||||
pub fn enable_well_by_ddi(&mut self, name: &'static str) -> Result<()> {
|
||||
for well in self.wells.iter() {
|
||||
if well.ddis.contains(&name) {
|
||||
return self.enable_well(well.name);
|
||||
}
|
||||
}
|
||||
log::warn!("power well for DDI {} not found", name);
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
|
||||
pub fn enable_well_by_pipe(&mut self, name: &'static str) -> Result<()> {
|
||||
for well in self.wells.iter() {
|
||||
if well.pipes.contains(&name) {
|
||||
return self.enable_well(well.name);
|
||||
}
|
||||
}
|
||||
log::warn!("power well for pipe {} not found", name);
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
|
||||
pub fn enable_well_by_transcoder(&mut self, name: &'static str) -> Result<()> {
|
||||
for well in self.wells.iter() {
|
||||
if well.transcoders.contains(&name) {
|
||||
return self.enable_well(well.name);
|
||||
}
|
||||
}
|
||||
log::warn!("power well for transcoder {} not found", name);
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
|
||||
pub fn kabylake(gttmm: &MmioRegion) -> Result<Self> {
|
||||
// IHD-OS-KBL-Vol 2c-1.17 PWR_WELL_CTL
|
||||
let ctl = unsafe { gttmm.mmio(0x45404)? };
|
||||
// Hack since these power ctl registers are combined
|
||||
let ctl_aux = unsafe { gttmm.mmio(0x45404)? };
|
||||
let ctl_ddi = unsafe { gttmm.mmio(0x45404)? };
|
||||
// IHD-OS-KBL-Vol 2c-1.17 FUSE_STATUS
|
||||
let fuse_status = unsafe { gttmm.mmio(0x42000)? };
|
||||
let fuse_status_pg0 = 1 << 27;
|
||||
let wells = vec![
|
||||
PowerWell {
|
||||
name: "1",
|
||||
depends: &[],
|
||||
ddis: &["A"],
|
||||
pipes: &["A"],
|
||||
transcoders: &["EDP"],
|
||||
request: 1 << 29,
|
||||
state: 1 << 28,
|
||||
fuse_status: 1 << 26,
|
||||
},
|
||||
PowerWell {
|
||||
name: "2",
|
||||
depends: &["1"],
|
||||
ddis: &["B", "C", "D", "E"],
|
||||
pipes: &["B", "C"],
|
||||
transcoders: &["A", "B", "C"],
|
||||
request: 1 << 31,
|
||||
state: 1 << 30,
|
||||
fuse_status: 1 << 25,
|
||||
},
|
||||
];
|
||||
Ok(Self {
|
||||
ctl,
|
||||
ctl_aux,
|
||||
ctl_ddi,
|
||||
fuse_status,
|
||||
fuse_status_pg0,
|
||||
wells,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tigerlake(gttmm: &MmioRegion) -> Result<Self> {
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL
|
||||
let ctl = unsafe { gttmm.mmio(0x45404)? };
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL_AUX
|
||||
let ctl_aux = unsafe { gttmm.mmio(0x45444)? };
|
||||
// IHD-OS-TGL-Vol 2c-12.21 PWR_WELL_CTL_DDI
|
||||
let ctl_ddi = unsafe { gttmm.mmio(0x45454)? };
|
||||
// IHD-OS-TGL-Vol 2c-12.21 FUSE_STATUS
|
||||
let fuse_status = unsafe { gttmm.mmio(0x42000)? };
|
||||
let fuse_status_pg0 = 1 << 27;
|
||||
let wells = vec![
|
||||
// DBUF functionality, Pipe A, Transcoder A and DSI, DDI A-C, FBC, DSS
|
||||
PowerWell {
|
||||
name: "1",
|
||||
depends: &[],
|
||||
ddis: &["A", "B", "C"],
|
||||
pipes: &["A"],
|
||||
transcoders: &["A"],
|
||||
request: 1 << 1,
|
||||
state: 1 << 0,
|
||||
fuse_status: 1 << 26,
|
||||
},
|
||||
// VDSC for pipe A
|
||||
PowerWell {
|
||||
name: "2",
|
||||
depends: &["1"],
|
||||
ddis: &[],
|
||||
pipes: &[],
|
||||
transcoders: &[],
|
||||
request: 1 << 3,
|
||||
state: 1 << 2,
|
||||
fuse_status: 1 << 25,
|
||||
},
|
||||
// Pipe B, Audio, Transcoder WD, VGA, Transcoder B, DDI USBC1-6, KVMR
|
||||
PowerWell {
|
||||
name: "3",
|
||||
depends: &["2"],
|
||||
ddis: &["USBC1", "USBC2", "USBC3", "USBC4", "USBC5", "USBC6"],
|
||||
pipes: &["B"],
|
||||
transcoders: &["B"],
|
||||
request: 1 << 5,
|
||||
state: 1 << 4,
|
||||
fuse_status: 1 << 24,
|
||||
},
|
||||
// Pipe C, Transcoder C
|
||||
PowerWell {
|
||||
name: "4",
|
||||
depends: &["3"],
|
||||
ddis: &[],
|
||||
pipes: &["C"],
|
||||
transcoders: &["C"],
|
||||
request: 1 << 7,
|
||||
state: 1 << 6,
|
||||
fuse_status: 1 << 23,
|
||||
},
|
||||
// Pipe D, Transcoder D
|
||||
PowerWell {
|
||||
name: "5",
|
||||
depends: &["4"],
|
||||
ddis: &[],
|
||||
pipes: &["D"],
|
||||
transcoders: &["D"],
|
||||
request: 1 << 9,
|
||||
state: 1 << 8,
|
||||
fuse_status: 1 << 22,
|
||||
},
|
||||
];
|
||||
Ok(Self {
|
||||
ctl,
|
||||
ctl_aux,
|
||||
ctl_ddi,
|
||||
fuse_status,
|
||||
fuse_status_pg0,
|
||||
wells,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn alchemist(gttmm: &MmioRegion) -> Result<Self> {
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL
|
||||
let ctl = unsafe { gttmm.mmio(0x45404)? };
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL_AUX
|
||||
let ctl_aux = unsafe { gttmm.mmio(0x45444)? };
|
||||
// IHD-OS-ACM-Vol 2c-3.23 PWR_WELL_CTL_DDI
|
||||
let ctl_ddi = unsafe { gttmm.mmio(0x45454)? };
|
||||
// IHD-OS-ACM-Vol 2c-3.23 FUSE_STATUS
|
||||
let fuse_status = unsafe { gttmm.mmio(0x42000)? };
|
||||
let fuse_status_pg0 = 1 << 27;
|
||||
let wells = vec![
|
||||
// DBUF functionality, Transcoder A, DDI A-B
|
||||
PowerWell {
|
||||
name: "1",
|
||||
depends: &[],
|
||||
ddis: &["A", "B"],
|
||||
pipes: &[],
|
||||
transcoders: &["A"],
|
||||
request: 1 << 1,
|
||||
state: 1 << 0,
|
||||
fuse_status: 1 >> 26,
|
||||
},
|
||||
// Audio playback, Transcoder WD, VGA, DDI C-E, Type-C, KVMR
|
||||
PowerWell {
|
||||
name: "2",
|
||||
depends: &["1"],
|
||||
ddis: &["C", "D", "E", "USBC1", "USBC2", "USBC3", "USBC4"],
|
||||
pipes: &[],
|
||||
transcoders: &[],
|
||||
request: 1 << 3,
|
||||
state: 1 << 2,
|
||||
fuse_status: 1 << 25,
|
||||
},
|
||||
// Pipe A, FBC
|
||||
PowerWell {
|
||||
name: "A",
|
||||
depends: &["1"],
|
||||
ddis: &[],
|
||||
pipes: &["A"],
|
||||
transcoders: &[],
|
||||
request: 1 << 11,
|
||||
state: 1 << 10,
|
||||
fuse_status: 1 << 21,
|
||||
},
|
||||
// Pipe B, Transcoder B
|
||||
PowerWell {
|
||||
name: "B",
|
||||
depends: &["2"],
|
||||
ddis: &[],
|
||||
pipes: &["B"],
|
||||
transcoders: &["B"],
|
||||
request: 1 << 13,
|
||||
state: 1 << 12,
|
||||
fuse_status: 1 << 20,
|
||||
},
|
||||
// Pipe C, Transcoder C
|
||||
PowerWell {
|
||||
name: "C",
|
||||
depends: &["2"],
|
||||
ddis: &[],
|
||||
pipes: &["C"],
|
||||
transcoders: &["C"],
|
||||
request: 1 << 15,
|
||||
state: 1 << 14,
|
||||
fuse_status: 1 << 19,
|
||||
},
|
||||
// Pipe D, Transcoder D
|
||||
PowerWell {
|
||||
name: "D",
|
||||
depends: &["2"],
|
||||
ddis: &[],
|
||||
pipes: &["D"],
|
||||
transcoders: &["D"],
|
||||
request: 1 << 17,
|
||||
state: 1 << 16,
|
||||
fuse_status: 1 << 18,
|
||||
},
|
||||
];
|
||||
Ok(Self {
|
||||
ctl,
|
||||
ctl_aux,
|
||||
ctl_ddi,
|
||||
fuse_status,
|
||||
fuse_status_pg0,
|
||||
wells,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
//TODO: this is copied from vesad and should be adapted
|
||||
|
||||
use std::alloc::{self, Layout};
|
||||
use std::convert::TryInto;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use driver_graphics::kms::connector::{KmsConnectorDriver, KmsConnectorStatus};
|
||||
use driver_graphics::kms::objects::{KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects};
|
||||
use driver_graphics::{Buffer, CursorPlane, Damage, GraphicsAdapter};
|
||||
use drm_sys::{
|
||||
DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFER_SHADOW, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
|
||||
};
|
||||
use syscall::{error::EINVAL, PAGE_SIZE};
|
||||
|
||||
use super::pipe::DeviceFb;
|
||||
use super::Device;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Connector {
|
||||
framebuffer_id: usize,
|
||||
}
|
||||
|
||||
impl KmsConnectorDriver for Connector {
|
||||
type State = ();
|
||||
}
|
||||
|
||||
impl GraphicsAdapter for Device {
|
||||
type Connector = Connector;
|
||||
type Crtc = ();
|
||||
|
||||
type Buffer = DumbFb;
|
||||
type Framebuffer = ();
|
||||
|
||||
fn name(&self) -> &'static [u8] {
|
||||
b"ihdgd"
|
||||
}
|
||||
|
||||
fn desc(&self) -> &'static [u8] {
|
||||
b"Intel HD Graphics"
|
||||
}
|
||||
|
||||
fn init(&mut self, objects: &mut KmsObjects<Self>) {
|
||||
self.init_inner();
|
||||
|
||||
// FIXME enumerate actual connectors
|
||||
for (framebuffer_id, _) in self.framebuffers.iter().enumerate() {
|
||||
let crtc = objects.add_crtc((), ());
|
||||
|
||||
objects.add_connector(Connector { framebuffer_id }, (), &[crtc]);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cap(&self, cap: u32) -> syscall::Result<u64> {
|
||||
match cap {
|
||||
DRM_CAP_DUMB_BUFFER => Ok(1),
|
||||
DRM_CAP_DUMB_PREFER_SHADOW => Ok(0),
|
||||
_ => Err(syscall::Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_client_cap(&self, cap: u32, _value: u64) -> syscall::Result<()> {
|
||||
match cap {
|
||||
// FIXME hide cursor plane unless this client cap is set
|
||||
DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT => Ok(()),
|
||||
_ => Err(syscall::Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn probe_connector(&mut self, objects: &mut KmsObjects<Self>, id: KmsObjectId) {
|
||||
let mut connector = objects.get_connector(id).unwrap().lock().unwrap();
|
||||
let framebuffer = &self.framebuffers[connector.driver_data.framebuffer_id];
|
||||
connector.connection = KmsConnectorStatus::Connected;
|
||||
connector.update_from_size(framebuffer.width as u32, framebuffer.height as u32);
|
||||
// FIXME fetch EDID
|
||||
}
|
||||
|
||||
fn create_dumb_buffer(&mut self, width: u32, height: u32) -> (Self::Buffer, u32) {
|
||||
(DumbFb::new(width as usize, height as usize), width * 4)
|
||||
}
|
||||
|
||||
fn map_dumb_buffer(&mut self, framebuffer: &Self::Buffer) -> *mut u8 {
|
||||
framebuffer.ptr.as_ptr().cast::<u8>()
|
||||
}
|
||||
|
||||
fn create_framebuffer(&mut self, _buffer: &Self::Buffer) -> Self::Framebuffer {
|
||||
()
|
||||
}
|
||||
|
||||
fn set_crtc(
|
||||
&mut self,
|
||||
objects: &KmsObjects<Self>,
|
||||
crtc: &Mutex<KmsCrtc<Self>>,
|
||||
state: KmsCrtcState<Self>,
|
||||
damage: Damage,
|
||||
) -> syscall::Result<()> {
|
||||
let mut crtc = crtc.lock().unwrap();
|
||||
let buffer = state
|
||||
.fb_id
|
||||
.map(|fb_id| objects.get_framebuffer(fb_id))
|
||||
.transpose()?;
|
||||
crtc.state = state;
|
||||
|
||||
for connector in objects.connectors() {
|
||||
let connector = connector.lock().unwrap();
|
||||
|
||||
if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] {
|
||||
continue;
|
||||
}
|
||||
|
||||
let framebuffer_id = connector.driver_data.framebuffer_id;
|
||||
|
||||
let framebuffer = &mut self.framebuffers[framebuffer_id];
|
||||
if let Some(buffer) = buffer {
|
||||
buffer.buffer.sync(framebuffer, damage)
|
||||
} else {
|
||||
let onscreen_ptr = framebuffer.buffer.virt.cast::<u32>();
|
||||
for row in 0..framebuffer.height {
|
||||
unsafe {
|
||||
ptr::write_bytes(
|
||||
onscreen_ptr.add((row * framebuffer.stride) as usize),
|
||||
0,
|
||||
framebuffer.width as usize,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hw_cursor_size(&self) -> Option<(u32, u32)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_cursor(&mut self, _cursor: &CursorPlane<Self::Buffer>, _dirty_fb: bool) {
|
||||
unimplemented!("ihdgd does not support this function");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DumbFb {
|
||||
width: usize,
|
||||
height: usize,
|
||||
ptr: NonNull<[u32]>,
|
||||
}
|
||||
|
||||
impl DumbFb {
|
||||
fn new(width: usize, height: usize) -> DumbFb {
|
||||
let len = width * height;
|
||||
let layout = Self::layout(len);
|
||||
let ptr = unsafe { alloc::alloc_zeroed(layout) };
|
||||
let ptr = ptr::slice_from_raw_parts_mut(ptr.cast(), len);
|
||||
let ptr = NonNull::new(ptr).unwrap_or_else(|| alloc::handle_alloc_error(layout));
|
||||
|
||||
DumbFb { width, height, ptr }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn layout(len: usize) -> Layout {
|
||||
// optimizes to an integer mul
|
||||
Layout::array::<u32>(len)
|
||||
.unwrap()
|
||||
.align_to(PAGE_SIZE)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DumbFb {
|
||||
fn drop(&mut self) {
|
||||
let layout = Self::layout(self.ptr.len());
|
||||
unsafe { alloc::dealloc(self.ptr.as_ptr().cast(), layout) };
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer for DumbFb {
|
||||
fn size(&self) -> usize {
|
||||
self.width * self.height * 4
|
||||
}
|
||||
}
|
||||
|
||||
impl DumbFb {
|
||||
fn sync(&self, framebuffer: &mut DeviceFb, sync_rect: Damage) {
|
||||
let sync_rect = sync_rect.clip(
|
||||
self.width.try_into().unwrap(),
|
||||
self.height.try_into().unwrap(),
|
||||
);
|
||||
|
||||
let start_x: usize = sync_rect.x.try_into().unwrap();
|
||||
let start_y: usize = sync_rect.y.try_into().unwrap();
|
||||
let w: usize = sync_rect.width.try_into().unwrap();
|
||||
let h: usize = sync_rect.height.try_into().unwrap();
|
||||
|
||||
let offscreen_ptr = self.ptr.as_ptr() as *mut u32;
|
||||
let onscreen_ptr = framebuffer.buffer.virt.cast::<u32>();
|
||||
|
||||
for row in start_y..start_y + h {
|
||||
unsafe {
|
||||
ptr::copy(
|
||||
offscreen_ptr.add(row * self.width + start_x),
|
||||
onscreen_ptr.add(row * framebuffer.stride as usize / 4 + start_x),
|
||||
w,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
use common::io::{Io, MmioPtr};
|
||||
use syscall::error::Result;
|
||||
|
||||
use super::{MmioRegion, Pipe};
|
||||
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_CONF
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_CONF
|
||||
pub const TRANS_CONF_ENABLE: u32 = 1 << 31;
|
||||
pub const TRANS_CONF_STATE: u32 = 1 << 30;
|
||||
pub const TRANS_CONF_MODE_MASK: u32 = 0b11 << 21;
|
||||
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_DDI_FUNC_CTL
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_DDI_FUNC_CTL
|
||||
pub const TRANS_DDI_FUNC_CTL_ENABLE: u32 = 1 << 31;
|
||||
pub const TRANS_DDI_FUNC_CTL_MODE_HDMI: u32 = 0b000 << 24;
|
||||
pub const TRANS_DDI_FUNC_CTL_MODE_DVI: u32 = 0b001 << 24;
|
||||
pub const TRANS_DDI_FUNC_CTL_MODE_DP_SST: u32 = 0b010 << 24;
|
||||
pub const TRANS_DDI_FUNC_CTL_MODE_DP_MST: u32 = 0b011 << 24;
|
||||
pub const TRANS_DDI_FUNC_CTL_BPC_8: u32 = 0b000 << 20;
|
||||
pub const TRANS_DDI_FUNC_CTL_BPC_10: u32 = 0b001 << 20;
|
||||
pub const TRANS_DDI_FUNC_CTL_BPC_6: u32 = 0b010 << 20;
|
||||
pub const TRANS_DDI_FUNC_CTL_BPC_12: u32 = 0b011 << 20;
|
||||
pub const TRANS_DDI_FUNC_CTL_SYNC_POLARITY_HSHIGH: u32 = 0b01 << 16;
|
||||
pub const TRANS_DDI_FUNC_CTL_SYNC_POLARITY_VSHIGH: u32 = 0b10 << 16;
|
||||
pub const TRANS_DDI_FUNC_CTL_DSI_INPUT_PIPE_SHIFT: u32 = 12;
|
||||
pub const TRANS_DDI_FUNC_CTL_PORT_WIDTH_1: u32 = 0b000 << 1;
|
||||
pub const TRANS_DDI_FUNC_CTL_PORT_WIDTH_2: u32 = 0b001 << 1;
|
||||
pub const TRANS_DDI_FUNC_CTL_PORT_WIDTH_3: u32 = 0b010 << 1;
|
||||
pub const TRANS_DDI_FUNC_CTL_PORT_WIDTH_4: u32 = 0b011 << 1;
|
||||
|
||||
pub struct Transcoder {
|
||||
pub name: &'static str,
|
||||
pub index: usize,
|
||||
pub clk_sel: MmioPtr<u32>,
|
||||
pub clk_sel_shift: u32,
|
||||
pub conf: MmioPtr<u32>,
|
||||
pub ddi_func_ctl: MmioPtr<u32>,
|
||||
pub ddi_func_ctl_ddi_shift: u32,
|
||||
pub ddi_func_ctl_hdmi_scrambling: u32,
|
||||
pub ddi_func_ctl_high_tmds_char_rate: u32,
|
||||
pub ddi_func_ctl2: Option<MmioPtr<u32>>,
|
||||
pub hblank: MmioPtr<u32>,
|
||||
pub hsync: MmioPtr<u32>,
|
||||
pub htotal: MmioPtr<u32>,
|
||||
pub msa_misc: MmioPtr<u32>,
|
||||
pub mult: MmioPtr<u32>,
|
||||
pub push: Option<MmioPtr<u32>>,
|
||||
pub space: MmioPtr<u32>,
|
||||
pub stereo3d_ctl: MmioPtr<u32>,
|
||||
pub vblank: MmioPtr<u32>,
|
||||
pub vrr_ctl: Option<MmioPtr<u32>>,
|
||||
pub vrr_flipline: Option<MmioPtr<u32>>,
|
||||
pub vrr_status: Option<MmioPtr<u32>>,
|
||||
pub vrr_status2: Option<MmioPtr<u32>>,
|
||||
pub vrr_vmax: Option<MmioPtr<u32>>,
|
||||
pub vrr_vmaxshift: Option<MmioPtr<u32>>,
|
||||
pub vrr_vmin: Option<MmioPtr<u32>>,
|
||||
pub vrr_vtotal_prev: Option<MmioPtr<u32>>,
|
||||
pub vsync: MmioPtr<u32>,
|
||||
pub vsyncshift: MmioPtr<u32>,
|
||||
pub vtotal: MmioPtr<u32>,
|
||||
}
|
||||
|
||||
impl Transcoder {
|
||||
pub fn dump(&self) {
|
||||
eprint!("Transcoder {} {}", self.name, self.index);
|
||||
eprint!(" clk_sel {:08X}", self.clk_sel.read());
|
||||
eprint!(" conf {:08X}", self.conf.read());
|
||||
eprint!(" ddi_func_ctl {:08X}", self.ddi_func_ctl.read());
|
||||
if let Some(reg) = &self.ddi_func_ctl2 {
|
||||
eprint!(" ddi_func_ctl2 {:08X}", reg.read());
|
||||
}
|
||||
eprint!(" hblank {:08X}", self.hblank.read());
|
||||
eprint!(" hsync {:08X}", self.hsync.read());
|
||||
eprint!(" htotal {:08X}", self.htotal.read());
|
||||
eprint!(" msa_misc {:08X}", self.msa_misc.read());
|
||||
eprint!(" mult {:08X}", self.mult.read());
|
||||
if let Some(reg) = &self.push {
|
||||
eprint!(" push {:08X}", reg.read());
|
||||
}
|
||||
eprint!(" space {:08X}", self.space.read());
|
||||
eprint!(" stereo3d_ctl {:08X}", self.stereo3d_ctl.read());
|
||||
eprint!(" vblank {:08X}", self.vblank.read());
|
||||
if let Some(reg) = &self.vrr_ctl {
|
||||
eprint!(" vrr_ctl {:08X}", reg.read());
|
||||
}
|
||||
if let Some(reg) = &self.vrr_flipline {
|
||||
eprint!(" vrr_flipline {:08X}", reg.read());
|
||||
}
|
||||
if let Some(reg) = &self.vrr_status {
|
||||
eprint!(" vrr_status {:08X}", reg.read());
|
||||
}
|
||||
if let Some(reg) = &self.vrr_status2 {
|
||||
eprint!(" vrr_status2 {:08X}", reg.read());
|
||||
}
|
||||
if let Some(reg) = &self.vrr_vmax {
|
||||
eprint!(" vrr_vmax {:08X}", reg.read());
|
||||
}
|
||||
if let Some(reg) = &self.vrr_vmaxshift {
|
||||
eprint!(" vrr_vmaxshift {:08X}", reg.read());
|
||||
}
|
||||
if let Some(reg) = &self.vrr_vmin {
|
||||
eprint!(" vrr_vmin {:08X}", reg.read());
|
||||
}
|
||||
if let Some(reg) = &self.vrr_vtotal_prev {
|
||||
eprint!(" vrr_vtotal_prev {:08X}", reg.read());
|
||||
}
|
||||
eprint!(" vsync {:08X}", self.vsync.read());
|
||||
eprint!(" vsyncshift {:08X}", self.vsyncshift.read());
|
||||
eprint!(" vtotal {:08X}", self.vtotal.read());
|
||||
eprintln!();
|
||||
}
|
||||
|
||||
pub fn modeset(&mut self, pipe: &mut Pipe, timing: &edid::DetailedTiming) {
|
||||
let hactive = (timing.horizontal_active_pixels as u32) - 1;
|
||||
let htotal = hactive + (timing.horizontal_blanking_pixels as u32);
|
||||
let hsync_start = hactive + (timing.horizontal_front_porch as u32);
|
||||
let hsync_end = hsync_start + (timing.horizontal_sync_width as u32);
|
||||
let vactive = (timing.vertical_active_lines as u32) - 1;
|
||||
let vtotal = vactive + (timing.vertical_blanking_lines as u32);
|
||||
let vsync_start = vactive + (timing.vertical_front_porch as u32);
|
||||
let vsync_end = vsync_start + (timing.vertical_sync_width as u32);
|
||||
|
||||
// Configure horizontal sync
|
||||
self.htotal.write(hactive | (htotal << 16));
|
||||
self.hblank.write(hactive | (htotal << 16));
|
||||
self.hsync.write(hsync_start | (hsync_end << 16));
|
||||
|
||||
// Configure vertical sync
|
||||
self.vtotal.write(vactive | (vtotal << 16));
|
||||
self.vblank.write(vactive | (vtotal << 16));
|
||||
self.vsync.write(vsync_start | (vsync_end << 16));
|
||||
|
||||
// Configure pipe
|
||||
pipe.srcsz.write(vactive | (hactive << 16));
|
||||
}
|
||||
|
||||
pub fn kabylake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
|
||||
let mut transcoders = Vec::with_capacity(4);
|
||||
//TODO: Transcoder EDP
|
||||
for (i, name) in ["A", "B", "C"].iter().enumerate() {
|
||||
transcoders.push(Transcoder {
|
||||
name,
|
||||
index: i,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_CLK_SEL
|
||||
clk_sel: unsafe { gttmm.mmio(0x46140 + i * 0x4)? },
|
||||
clk_sel_shift: 29,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_CONF
|
||||
conf: unsafe { gttmm.mmio(0x70008 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_DDI_FUNC_CTL
|
||||
ddi_func_ctl: unsafe { gttmm.mmio(0x60400 + i * 0x1000)? },
|
||||
ddi_func_ctl_ddi_shift: 28,
|
||||
// HDMI scrambling not supported on Kaby Lake
|
||||
ddi_func_ctl_hdmi_scrambling: 0,
|
||||
ddi_func_ctl_high_tmds_char_rate: 0,
|
||||
// N/A
|
||||
ddi_func_ctl2: None,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_HBLANK
|
||||
hblank: unsafe { gttmm.mmio(0x60004 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_HSYNC
|
||||
hsync: unsafe { gttmm.mmio(0x60008 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_HTOTAL
|
||||
htotal: unsafe { gttmm.mmio(0x60000 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_MSA_MISC
|
||||
msa_misc: unsafe { gttmm.mmio(0x60410 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_MULT
|
||||
mult: unsafe { gttmm.mmio(0x6002C + i * 0x1000)? },
|
||||
// N/A
|
||||
push: None,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_SPACE
|
||||
space: unsafe { gttmm.mmio(0x60020 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_STEREO3D_CTL
|
||||
stereo3d_ctl: unsafe { gttmm.mmio(0x70020 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_VBLANK
|
||||
vblank: unsafe { gttmm.mmio(0x60010 + i * 0x1000)? },
|
||||
// N/A
|
||||
vrr_ctl: None,
|
||||
vrr_flipline: None,
|
||||
vrr_status: None,
|
||||
vrr_status2: None,
|
||||
vrr_vmax: None,
|
||||
vrr_vmaxshift: None,
|
||||
vrr_vmin: None,
|
||||
vrr_vtotal_prev: None,
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_VSYNC
|
||||
vsync: unsafe { gttmm.mmio(0x60014 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_VSYNCSHIFT
|
||||
vsyncshift: unsafe { gttmm.mmio(0x60028 + i * 0x1000)? },
|
||||
// IHD-OS-KBL-Vol 2c-1.17 TRANS_VTOTAL
|
||||
vtotal: unsafe { gttmm.mmio(0x6000C + i * 0x1000)? },
|
||||
});
|
||||
}
|
||||
Ok(transcoders)
|
||||
}
|
||||
|
||||
pub fn tigerlake(gttmm: &MmioRegion) -> Result<Vec<Self>> {
|
||||
let mut transcoders = Vec::with_capacity(4);
|
||||
for (i, name) in ["A", "B", "C", "D"].iter().enumerate() {
|
||||
transcoders.push(Transcoder {
|
||||
name,
|
||||
index: i,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_CLK_SEL
|
||||
clk_sel: unsafe { gttmm.mmio(0x46140 + i * 0x4)? },
|
||||
clk_sel_shift: 28,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_CONF
|
||||
conf: unsafe { gttmm.mmio(0x70008 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_DDI_FUNC_CTL
|
||||
ddi_func_ctl: unsafe { gttmm.mmio(0x60400 + i * 0x1000)? },
|
||||
ddi_func_ctl_ddi_shift: 27,
|
||||
ddi_func_ctl_hdmi_scrambling: 1 << 0,
|
||||
ddi_func_ctl_high_tmds_char_rate: 1 << 4,
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_DDI_FUNC_CTL2
|
||||
ddi_func_ctl2: Some(unsafe { gttmm.mmio(0x60404 + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_HBLANK
|
||||
hblank: unsafe { gttmm.mmio(0x60004 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_HSYNC
|
||||
hsync: unsafe { gttmm.mmio(0x60008 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_HTOTAL
|
||||
htotal: unsafe { gttmm.mmio(0x60000 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_MSA_MISC
|
||||
msa_misc: unsafe { gttmm.mmio(0x60410 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_MULT
|
||||
mult: unsafe { gttmm.mmio(0x6002C + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_PUSH
|
||||
push: Some(unsafe { gttmm.mmio(0x60A70 + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_SPACE
|
||||
space: unsafe { gttmm.mmio(0x60020 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_STEREO3D_CTL
|
||||
stereo3d_ctl: unsafe { gttmm.mmio(0x70020 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VBLANK
|
||||
vblank: unsafe { gttmm.mmio(0x60010 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_CTL
|
||||
vrr_ctl: Some(unsafe { gttmm.mmio(0x60420 + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_FLIPLINE
|
||||
vrr_flipline: Some(unsafe { gttmm.mmio(0x60438 + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_STATUS
|
||||
vrr_status: Some(unsafe { gttmm.mmio(0x6042C + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_STATUS2
|
||||
vrr_status2: Some(unsafe { gttmm.mmio(0x6043C + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_VMAX
|
||||
vrr_vmax: Some(unsafe { gttmm.mmio(0x60424 + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_VMAXSHIFT
|
||||
vrr_vmaxshift: Some(unsafe { gttmm.mmio(0x60428 + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_VMIN
|
||||
vrr_vmin: Some(unsafe { gttmm.mmio(0x60434 + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VRR_VTOTAL_PREV
|
||||
vrr_vtotal_prev: Some(unsafe { gttmm.mmio(0x60480 + i * 0x1000)? }),
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VSYNC
|
||||
vsync: unsafe { gttmm.mmio(0x60014 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VSYNCSHIFT
|
||||
vsyncshift: unsafe { gttmm.mmio(0x60028 + i * 0x1000)? },
|
||||
// IHD-OS-TGL-Vol 2c-12.21 TRANS_VTOTAL
|
||||
vtotal: unsafe { gttmm.mmio(0x6000C + i * 0x1000)? },
|
||||
})
|
||||
}
|
||||
Ok(transcoders)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use driver_graphics::GraphicsScheme;
|
||||
use event::{user_data, EventQueue};
|
||||
use pcid_interface::{irq_helpers::pci_allocate_interrupt_vector, PciFunctionHandle};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
os::fd::AsRawFd,
|
||||
};
|
||||
|
||||
mod device;
|
||||
use self::device::Device;
|
||||
|
||||
fn main() {
|
||||
pcid_interface::pci_daemon(daemon);
|
||||
}
|
||||
|
||||
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
let pci_config = pcid_handle.config();
|
||||
|
||||
let mut name = pci_config.func.name();
|
||||
name.push_str("_ihdg");
|
||||
|
||||
common::setup_logging(
|
||||
"graphics",
|
||||
"pci",
|
||||
&name,
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
log::info!("IHDG {}", pci_config.func.display());
|
||||
|
||||
let device = Device::new(&mut pcid_handle, &pci_config.func)
|
||||
.expect("ihdgd: failed to initialize device");
|
||||
|
||||
let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd");
|
||||
|
||||
// Needs to be before GraphicsScheme::new to avoid a deadlock due to initnsmgr blocking on
|
||||
// /scheme/event as it is already blocked on opening /scheme/display.ihdg.*.
|
||||
// FIXME change the initnsmgr to not block on openat for the target scheme.
|
||||
let event_queue: EventQueue<Source> =
|
||||
EventQueue::new().expect("ihdgd: failed to create event queue");
|
||||
|
||||
let mut scheme = GraphicsScheme::new(device, format!("display.ihdg.{}", name), false);
|
||||
|
||||
user_data! {
|
||||
enum Source {
|
||||
Input,
|
||||
Irq,
|
||||
Scheme,
|
||||
}
|
||||
}
|
||||
|
||||
event_queue
|
||||
.subscribe(
|
||||
scheme.inputd_event_handle().as_raw_fd() as usize,
|
||||
Source::Input,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
irq_file.irq_handle().as_raw_fd() as usize,
|
||||
Source::Irq,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
scheme.event_handle().raw(),
|
||||
Source::Scheme,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
libredox::call::setrens(0, 0).expect("ihdgd: failed to enter null namespace");
|
||||
|
||||
daemon.ready();
|
||||
|
||||
let all = [Source::Input, Source::Irq, Source::Scheme];
|
||||
for event in all
|
||||
.into_iter()
|
||||
.chain(event_queue.map(|e| e.expect("ihdgd: failed to get next event").user_data))
|
||||
{
|
||||
match event {
|
||||
Source::Input => scheme.handle_vt_events(),
|
||||
Source::Irq => {
|
||||
let mut irq = [0; 8];
|
||||
irq_file.irq_handle().read(&mut irq).unwrap();
|
||||
if scheme.adapter_mut().handle_irq() {
|
||||
irq_file.irq_handle().write(&mut irq).unwrap();
|
||||
|
||||
scheme.adapter_mut().handle_events();
|
||||
scheme.tick().unwrap();
|
||||
}
|
||||
}
|
||||
Source::Scheme => {
|
||||
scheme
|
||||
.tick()
|
||||
.expect("ihdgd: failed to handle scheme events");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "vesad"
|
||||
description = "VESA driver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
drm-sys.workspace = true
|
||||
orbclient.workspace = true
|
||||
ransid.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
redox_event.workspace = true
|
||||
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
driver-graphics = { path = "../driver-graphics" }
|
||||
libredox.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,133 @@
|
||||
extern crate orbclient;
|
||||
extern crate syscall;
|
||||
|
||||
use driver_graphics::GraphicsScheme;
|
||||
use event::{user_data, EventQueue};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use crate::scheme::{FbAdapter, FrameBuffer};
|
||||
|
||||
mod scheme;
|
||||
|
||||
fn main() {
|
||||
common::init();
|
||||
daemon::Daemon::new(daemon);
|
||||
}
|
||||
fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
if env::var("FRAMEBUFFER_WIDTH").is_err() {
|
||||
println!("vesad: No boot framebuffer");
|
||||
daemon.ready();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let width = usize::from_str_radix(
|
||||
&env::var("FRAMEBUFFER_WIDTH").expect("FRAMEBUFFER_WIDTH not set"),
|
||||
16,
|
||||
)
|
||||
.expect("failed to parse FRAMEBUFFER_WIDTH");
|
||||
let height = usize::from_str_radix(
|
||||
&env::var("FRAMEBUFFER_HEIGHT").expect("FRAMEBUFFER_HEIGHT not set"),
|
||||
16,
|
||||
)
|
||||
.expect("failed to parse FRAMEBUFFER_HEIGHT");
|
||||
let phys = usize::from_str_radix(
|
||||
&env::var("FRAMEBUFFER_ADDR").expect("FRAMEBUFFER_ADDR not set"),
|
||||
16,
|
||||
)
|
||||
.expect("failed to parse FRAMEBUFFER_ADDR");
|
||||
let stride = usize::from_str_radix(
|
||||
&env::var("FRAMEBUFFER_STRIDE").expect("FRAMEBUFFER_STRIDE not set"),
|
||||
16,
|
||||
)
|
||||
.expect("failed to parse FRAMEBUFFER_STRIDE");
|
||||
|
||||
println!(
|
||||
"vesad: {}x{} stride {} at 0x{:X}",
|
||||
width, height, stride, phys
|
||||
);
|
||||
|
||||
if phys == 0 {
|
||||
println!("vesad: Boot framebuffer at address 0");
|
||||
daemon.ready();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let mut framebuffers = vec![unsafe { FrameBuffer::new(phys, width, height, stride) }];
|
||||
|
||||
//TODO: ideal maximum number of outputs?
|
||||
let bootloader_env = std::fs::read_to_string("/scheme/sys/env")
|
||||
.expect("failed to read env")
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let (env, value) = line.split_once('=').unwrap();
|
||||
(env.to_owned(), value.to_owned())
|
||||
})
|
||||
.collect::<HashMap<String, String>>();
|
||||
for i in 1..1024 {
|
||||
match bootloader_env.get(&format!("FRAMEBUFFER{}", i)) {
|
||||
Some(var) => match unsafe { FrameBuffer::parse(&var) } {
|
||||
Some(fb) => {
|
||||
println!(
|
||||
"vesad: framebuffer {}: {}x{} stride {} at 0x{:X}",
|
||||
i, fb.width, fb.height, fb.stride, fb.phys
|
||||
);
|
||||
framebuffers.push(fb);
|
||||
}
|
||||
None => {
|
||||
eprintln!("vesad: framebuffer {}: failed to parse '{}'", i, var);
|
||||
}
|
||||
},
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
|
||||
let mut scheme =
|
||||
GraphicsScheme::new(FbAdapter { framebuffers }, "display.vesa".to_owned(), true);
|
||||
|
||||
user_data! {
|
||||
enum Source {
|
||||
Input,
|
||||
Scheme,
|
||||
}
|
||||
}
|
||||
|
||||
let event_queue: EventQueue<Source> =
|
||||
EventQueue::new().expect("vesad: failed to create event queue");
|
||||
event_queue
|
||||
.subscribe(
|
||||
scheme.inputd_event_handle().as_raw_fd() as usize,
|
||||
Source::Input,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
scheme.event_handle().raw(),
|
||||
Source::Scheme,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
libredox::call::setrens(0, 0).expect("vesad: failed to enter null namespace");
|
||||
|
||||
daemon.ready();
|
||||
|
||||
let all = [Source::Input, Source::Scheme];
|
||||
for event in all
|
||||
.into_iter()
|
||||
.chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data))
|
||||
{
|
||||
match event {
|
||||
Source::Input => scheme.handle_vt_events(),
|
||||
Source::Scheme => {
|
||||
scheme
|
||||
.tick()
|
||||
.expect("vesad: failed to handle scheme events");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!();
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
use std::alloc::{self, Layout};
|
||||
use std::convert::TryInto;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use driver_graphics::kms::connector::{KmsConnectorDriver, KmsConnectorStatus};
|
||||
use driver_graphics::kms::objects::{KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects};
|
||||
use driver_graphics::{Buffer, CursorPlane, Damage, GraphicsAdapter};
|
||||
use drm_sys::{
|
||||
DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFER_SHADOW, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
|
||||
};
|
||||
use syscall::{EINVAL, PAGE_SIZE};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FbAdapter {
|
||||
pub framebuffers: Vec<FrameBuffer>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Connector {
|
||||
width: u32,
|
||||
height: u32,
|
||||
framebuffer_id: usize,
|
||||
}
|
||||
|
||||
impl KmsConnectorDriver for Connector {
|
||||
type State = ();
|
||||
}
|
||||
|
||||
impl GraphicsAdapter for FbAdapter {
|
||||
type Connector = Connector;
|
||||
type Crtc = ();
|
||||
|
||||
type Buffer = GraphicScreen;
|
||||
type Framebuffer = ();
|
||||
|
||||
fn name(&self) -> &'static [u8] {
|
||||
b"vesad"
|
||||
}
|
||||
|
||||
fn desc(&self) -> &'static [u8] {
|
||||
b"VESA"
|
||||
}
|
||||
|
||||
fn init(&mut self, objects: &mut KmsObjects<Self>) {
|
||||
for (framebuffer_id, framebuffer) in self.framebuffers.iter().enumerate() {
|
||||
let crtc = objects.add_crtc((), ());
|
||||
|
||||
objects.add_connector(
|
||||
Connector {
|
||||
width: framebuffer.width as u32,
|
||||
height: framebuffer.height as u32,
|
||||
framebuffer_id,
|
||||
},
|
||||
(),
|
||||
&[crtc],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cap(&self, cap: u32) -> syscall::Result<u64> {
|
||||
match cap {
|
||||
DRM_CAP_DUMB_BUFFER => Ok(1),
|
||||
DRM_CAP_DUMB_PREFER_SHADOW => Ok(0),
|
||||
_ => Err(syscall::Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_client_cap(&self, cap: u32, _value: u64) -> syscall::Result<()> {
|
||||
match cap {
|
||||
DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT => Ok(()),
|
||||
_ => Err(syscall::Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn probe_connector(&mut self, objects: &mut KmsObjects<Self>, id: KmsObjectId) {
|
||||
let mut connector = objects.get_connector(id).unwrap().lock().unwrap();
|
||||
let connector = &mut *connector;
|
||||
connector.connection = KmsConnectorStatus::Connected;
|
||||
connector.update_from_size(connector.driver_data.width, connector.driver_data.height);
|
||||
}
|
||||
|
||||
fn create_dumb_buffer(&mut self, width: u32, height: u32) -> (Self::Buffer, u32) {
|
||||
(
|
||||
GraphicScreen::new(width as usize, height as usize),
|
||||
width * 4,
|
||||
)
|
||||
}
|
||||
|
||||
fn map_dumb_buffer(&mut self, framebuffer: &Self::Buffer) -> *mut u8 {
|
||||
framebuffer.ptr.as_ptr().cast::<u8>()
|
||||
}
|
||||
|
||||
fn create_framebuffer(&mut self, _buffer: &Self::Buffer) -> Self::Framebuffer {
|
||||
()
|
||||
}
|
||||
|
||||
fn set_crtc(
|
||||
&mut self,
|
||||
objects: &KmsObjects<Self>,
|
||||
crtc: &Mutex<KmsCrtc<Self>>,
|
||||
state: KmsCrtcState<Self>,
|
||||
damage: Damage,
|
||||
) -> syscall::Result<()> {
|
||||
let mut crtc = crtc.lock().unwrap();
|
||||
let buffer = state
|
||||
.fb_id
|
||||
.map(|fb_id| objects.get_framebuffer(fb_id))
|
||||
.transpose()?;
|
||||
crtc.state = state;
|
||||
|
||||
for connector in objects.connectors() {
|
||||
let connector = connector.lock().unwrap();
|
||||
|
||||
if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] {
|
||||
continue;
|
||||
}
|
||||
|
||||
let framebuffer_id = connector.driver_data.framebuffer_id;
|
||||
let framebuffer = &mut self.framebuffers[framebuffer_id];
|
||||
|
||||
if let Some(buffer) = buffer {
|
||||
buffer.buffer.sync(framebuffer, damage)
|
||||
} else {
|
||||
let onscreen_ptr = framebuffer.onscreen as *mut u32; // FIXME use as_mut_ptr once stable
|
||||
for row in 0..framebuffer.height {
|
||||
unsafe {
|
||||
ptr::write_bytes(
|
||||
onscreen_ptr.add(row * framebuffer.stride),
|
||||
0,
|
||||
framebuffer.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hw_cursor_size(&self) -> Option<(u32, u32)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_cursor(&mut self, _cursor: &CursorPlane<Self::Buffer>, _dirty_fb: bool) {
|
||||
unimplemented!("Vesad does not support this function");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FrameBuffer {
|
||||
pub onscreen: *mut [u32],
|
||||
pub phys: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub stride: usize,
|
||||
}
|
||||
|
||||
impl FrameBuffer {
|
||||
pub unsafe fn new(phys: usize, width: usize, height: usize, stride: usize) -> Self {
|
||||
let size = stride * height;
|
||||
let virt = common::physmap(
|
||||
phys,
|
||||
size * 4,
|
||||
common::Prot {
|
||||
read: true,
|
||||
write: true,
|
||||
},
|
||||
common::MemoryType::WriteCombining,
|
||||
)
|
||||
.expect("vesad: failed to map framebuffer") as *mut u32;
|
||||
|
||||
let onscreen = ptr::slice_from_raw_parts_mut(virt, size);
|
||||
|
||||
Self {
|
||||
onscreen,
|
||||
phys,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn parse(var: &str) -> Option<Self> {
|
||||
fn parse_number(part: &str) -> Option<usize> {
|
||||
let (start, radix) = if part.starts_with("0x") {
|
||||
(2, 16)
|
||||
} else {
|
||||
(0, 10)
|
||||
};
|
||||
match usize::from_str_radix(&part[start..], radix) {
|
||||
Ok(ok) => Some(ok),
|
||||
Err(err) => {
|
||||
eprintln!("vesad: failed to parse '{}': {}", part, err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut parts = var.split(',');
|
||||
let phys = parse_number(parts.next()?)?;
|
||||
let width = parse_number(parts.next()?)?;
|
||||
let height = parse_number(parts.next()?)?;
|
||||
let stride = parse_number(parts.next()?)?;
|
||||
Some(Self::new(phys, width, height, stride))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GraphicScreen {
|
||||
width: usize,
|
||||
height: usize,
|
||||
ptr: NonNull<[u32]>,
|
||||
}
|
||||
|
||||
impl GraphicScreen {
|
||||
fn new(width: usize, height: usize) -> GraphicScreen {
|
||||
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));
|
||||
|
||||
GraphicScreen { width, height, ptr }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn layout(len: usize) -> Layout {
|
||||
// optimizes to an integer mul
|
||||
Layout::array::<u32>(len)
|
||||
.unwrap()
|
||||
.align_to(PAGE_SIZE)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GraphicScreen {
|
||||
fn drop(&mut self) {
|
||||
let layout = Self::layout(self.ptr.len());
|
||||
unsafe { alloc::dealloc(self.ptr.as_ptr().cast(), layout) };
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer for GraphicScreen {
|
||||
fn size(&self) -> usize {
|
||||
self.width * self.height * 4
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicScreen {
|
||||
fn sync(&self, framebuffer: &mut FrameBuffer, 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "virtio-gpud"
|
||||
description = "VirtIO-GPU driver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Anhad Singh <andypython@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
drm-sys.workspace = true
|
||||
log.workspace = true
|
||||
static_assertions.workspace = true
|
||||
futures = { version = "0.3.28", features = ["executor"] }
|
||||
anyhow.workspace = true
|
||||
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
driver-graphics = { path = "../driver-graphics" }
|
||||
virtio-core = { path = "../../virtio-core" }
|
||||
pcid = { path = "../../pcid" }
|
||||
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
orbclient.workspace = true
|
||||
spin.workspace = true
|
||||
libredox.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,615 @@
|
||||
//! `virtio-gpu` is a virtio based graphics adapter. It can operate in 2D mode and in 3D mode.
|
||||
//!
|
||||
//! XXX: 3D mode will offload rendering ops to the host gpu and therefore requires a GPU with 3D support
|
||||
//! on the host machine.
|
||||
|
||||
// Notes for the future:
|
||||
//
|
||||
// `virtio-gpu` 2D acceleration is just blitting. 3D acceleration has 2 kinds:
|
||||
// - virgl - OpenGL
|
||||
// - venus - Vulkan
|
||||
//
|
||||
// The Venus driver requires support for the following from the `virtio-gpu` kernel driver:
|
||||
// - VIRTGPU_PARAM_3D_FEATURES
|
||||
// - VIRTGPU_PARAM_CAPSET_QUERY_FIX
|
||||
// - VIRTGPU_PARAM_RESOURCE_BLOB
|
||||
// - VIRTGPU_PARAM_HOST_VISIBLE
|
||||
// - VIRTGPU_PARAM_CROSS_DEVICE
|
||||
// - VIRTGPU_PARAM_CONTEXT_INIT
|
||||
//
|
||||
// cc https://docs.mesa3d.org/drivers/venus.html
|
||||
// cc https://docs.mesa3d.org/drivers/virgl.html
|
||||
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use driver_graphics::GraphicsAdapter;
|
||||
use event::{user_data, EventQueue};
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
|
||||
use virtio_core::utils::VolatileCell;
|
||||
use virtio_core::MSIX_PRIMARY_VECTOR;
|
||||
|
||||
mod scheme;
|
||||
|
||||
//const VIRTIO_GPU_F_VIRGL: u32 = 0;
|
||||
const VIRTIO_GPU_F_EDID: u32 = 1;
|
||||
//const VIRTIO_GPU_F_RESOURCE_UUID: u32 = 2;
|
||||
//const VIRTIO_GPU_F_RESOURCE_BLOB: u32 = 3;
|
||||
//const VIRTIO_GPU_F_CONTEXT_INIT: u32 = 4;
|
||||
|
||||
const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0;
|
||||
const VIRTIO_GPU_MAX_SCANOUTS: usize = 16;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct GpuConfig {
|
||||
/// Signals pending events to the driver.
|
||||
pub events_read: VolatileCell<u32>, // read-only
|
||||
/// Clears pending events in the device (write-to-clear).
|
||||
pub events_clear: VolatileCell<u32>, // write-only
|
||||
|
||||
pub num_scanouts: VolatileCell<u32>,
|
||||
pub num_capsets: VolatileCell<u32>,
|
||||
}
|
||||
|
||||
impl GpuConfig {
|
||||
#[inline]
|
||||
pub fn num_scanouts(&self) -> u32 {
|
||||
self.num_scanouts.get()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[repr(u32)]
|
||||
pub enum CommandTy {
|
||||
Undefined = 0,
|
||||
|
||||
// 2D commands
|
||||
GetDisplayInfo = 0x0100,
|
||||
ResourceCreate2d,
|
||||
ResourceUnref,
|
||||
SetScanout,
|
||||
ResourceFlush,
|
||||
TransferToHost2d,
|
||||
ResourceAttachBacking,
|
||||
ResourceDetachBacking,
|
||||
GetCapsetInfo,
|
||||
GetCapset,
|
||||
GetEdid,
|
||||
ResourceAssignUuid,
|
||||
ResourceCreateBlob,
|
||||
SetScanoutBlob,
|
||||
|
||||
// 3D commands
|
||||
CtxCreate = 0x0200,
|
||||
CtxDestroy,
|
||||
CtxAttachResource,
|
||||
CtxDetachResource,
|
||||
ResourceCreate3d,
|
||||
TransferToHost3d,
|
||||
TransferFromHost3d,
|
||||
Submit3d,
|
||||
ResourceMapBlob,
|
||||
ResourceUnmapBlob,
|
||||
|
||||
// cursor commands
|
||||
UpdateCursor = 0x0300,
|
||||
MoveCursor,
|
||||
|
||||
// success responses
|
||||
RespOkNodata = 0x1100,
|
||||
RespOkDisplayInfo,
|
||||
RespOkCapsetInfo,
|
||||
RespOkCapset,
|
||||
RespOkEdid,
|
||||
RespOkResourceUuid,
|
||||
RespOkMapInfo,
|
||||
|
||||
// error responses
|
||||
RespErrUnspec = 0x1200,
|
||||
RespErrOutOfMemory,
|
||||
RespErrInvalidScanoutId,
|
||||
RespErrInvalidResourceId,
|
||||
RespErrInvalidContextId,
|
||||
RespErrInvalidParameter,
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<CommandTy>(), 4);
|
||||
|
||||
const VIRTIO_GPU_FLAG_FENCE: u32 = 1 << 0;
|
||||
//const VIRTIO_GPU_FLAG_INFO_RING_IDX: u32 = 1 << 1;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct ControlHeader {
|
||||
pub ty: CommandTy,
|
||||
pub flags: u32,
|
||||
pub fence_id: u64,
|
||||
pub ctx_id: u32,
|
||||
pub ring_index: u8,
|
||||
padding: [u8; 3],
|
||||
}
|
||||
|
||||
impl ControlHeader {
|
||||
pub fn with_ty(ty: CommandTy) -> Self {
|
||||
Self {
|
||||
ty,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ControlHeader {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ty: CommandTy::Undefined,
|
||||
flags: 0,
|
||||
fence_id: 0,
|
||||
ctx_id: 0,
|
||||
ring_index: 0,
|
||||
padding: [0; 3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct GpuRect {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl GpuRect {
|
||||
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct DisplayInfo {
|
||||
rect: GpuRect,
|
||||
pub enabled: u32,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct GetDisplayInfo {
|
||||
pub header: ControlHeader,
|
||||
pub display_info: [DisplayInfo; VIRTIO_GPU_MAX_SCANOUTS],
|
||||
}
|
||||
|
||||
impl Default for GetDisplayInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
header: ControlHeader {
|
||||
ty: CommandTy::GetDisplayInfo,
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
display_info: unsafe { core::mem::zeroed() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); // XXX: 0 is reserved for whatever that takes `resource_id`.
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct ResourceId(u32);
|
||||
|
||||
impl ResourceId {
|
||||
const NONE: ResourceId = ResourceId(0);
|
||||
|
||||
fn alloc() -> Self {
|
||||
ResourceId(RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(u32)]
|
||||
pub enum ResourceFormat {
|
||||
Unknown = 0,
|
||||
|
||||
Bgrx = 2,
|
||||
Xrgb = 4,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct ResourceCreate2d {
|
||||
pub header: ControlHeader,
|
||||
resource_id: ResourceId,
|
||||
format: ResourceFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl ResourceCreate2d {
|
||||
fn new(resource_id: ResourceId, format: ResourceFormat, width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::ResourceCreate2d),
|
||||
resource_id,
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct MemEntry {
|
||||
pub address: u64,
|
||||
pub length: u32,
|
||||
pub padding: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct AttachBacking {
|
||||
pub header: ControlHeader,
|
||||
pub resource_id: ResourceId,
|
||||
pub num_entries: u32,
|
||||
}
|
||||
|
||||
impl AttachBacking {
|
||||
pub fn new(resource_id: ResourceId, num_entries: u32) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::ResourceAttachBacking),
|
||||
resource_id,
|
||||
num_entries,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct DetachBacking {
|
||||
pub header: ControlHeader,
|
||||
pub resource_id: ResourceId,
|
||||
pub padding: u32,
|
||||
}
|
||||
|
||||
impl DetachBacking {
|
||||
pub fn new(resource_id: ResourceId) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::ResourceDetachBacking),
|
||||
resource_id,
|
||||
padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct ResourceFlush {
|
||||
pub header: ControlHeader,
|
||||
pub rect: GpuRect,
|
||||
pub resource_id: ResourceId,
|
||||
pub padding: u32,
|
||||
}
|
||||
|
||||
impl ResourceFlush {
|
||||
pub fn new(resource_id: ResourceId, rect: GpuRect) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::ResourceFlush),
|
||||
rect,
|
||||
resource_id,
|
||||
padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct ResourceUnref {
|
||||
pub header: ControlHeader,
|
||||
pub resource_id: ResourceId,
|
||||
pub padding: u32,
|
||||
}
|
||||
|
||||
impl ResourceUnref {
|
||||
pub fn new(resource_id: ResourceId) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::ResourceUnref),
|
||||
resource_id,
|
||||
padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct SetScanout {
|
||||
pub header: ControlHeader,
|
||||
pub rect: GpuRect,
|
||||
pub scanout_id: u32,
|
||||
pub resource_id: ResourceId,
|
||||
}
|
||||
|
||||
impl SetScanout {
|
||||
pub fn new(scanout_id: u32, resource_id: ResourceId, rect: GpuRect) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::SetScanout),
|
||||
|
||||
rect,
|
||||
scanout_id,
|
||||
resource_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct XferToHost2d {
|
||||
pub header: ControlHeader,
|
||||
pub rect: GpuRect,
|
||||
pub offset: u64,
|
||||
pub resource_id: ResourceId,
|
||||
pub padding: u32,
|
||||
}
|
||||
|
||||
impl XferToHost2d {
|
||||
pub fn new(resource_id: ResourceId, rect: GpuRect, offset: u64) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::TransferToHost2d),
|
||||
rect,
|
||||
offset,
|
||||
resource_id,
|
||||
padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct GetEdid {
|
||||
pub header: ControlHeader,
|
||||
pub scanout: u32,
|
||||
pub padding: u32,
|
||||
}
|
||||
|
||||
impl GetEdid {
|
||||
pub fn new(scanout_id: u32) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::GetEdid),
|
||||
scanout: scanout_id,
|
||||
padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct GetEdidResp {
|
||||
pub header: ControlHeader,
|
||||
pub size: u32,
|
||||
pub padding: u32,
|
||||
pub edid: [u8; 1024],
|
||||
}
|
||||
|
||||
impl GetEdidResp {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::GetEdid),
|
||||
size: 0,
|
||||
padding: 0,
|
||||
edid: [0; 1024],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct CursorPos {
|
||||
pub scanout_id: u32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
impl CursorPos {
|
||||
pub fn new(scanout_id: u32, x: i32, y: i32) -> Self {
|
||||
Self {
|
||||
scanout_id,
|
||||
x,
|
||||
y,
|
||||
_padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* VIRTIO_GPU_CMD_UPDATE_CURSOR, VIRTIO_GPU_CMD_MOVE_CURSOR */
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct UpdateCursor {
|
||||
pub header: ControlHeader,
|
||||
pub pos: CursorPos,
|
||||
pub resource_id: ResourceId,
|
||||
pub hot_x: i32,
|
||||
pub hot_y: i32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
impl UpdateCursor {
|
||||
pub fn update_cursor(x: i32, y: i32, hot_x: i32, hot_y: i32, resource_id: ResourceId) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::UpdateCursor),
|
||||
pos: CursorPos::new(0, x, y),
|
||||
resource_id,
|
||||
hot_x,
|
||||
hot_y,
|
||||
_padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MoveCursor {
|
||||
pub header: ControlHeader,
|
||||
pub pos: CursorPos,
|
||||
pub resource_id: ResourceId,
|
||||
pub hot_x: i32,
|
||||
pub hot_y: i32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
impl MoveCursor {
|
||||
pub fn move_cursor(x: i32, y: i32) -> Self {
|
||||
Self {
|
||||
header: ControlHeader::with_ty(CommandTy::MoveCursor),
|
||||
pos: CursorPos::new(0, x, y),
|
||||
resource_id: ResourceId(0),
|
||||
hot_x: 0,
|
||||
hot_y: 0,
|
||||
_padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static DEVICE: spin::Once<virtio_core::Device> = spin::Once::new();
|
||||
|
||||
fn main() {
|
||||
pcid_interface::pci_daemon(daemon_runner);
|
||||
}
|
||||
|
||||
fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
daemon(redox_daemon, pcid_handle).unwrap();
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> {
|
||||
common::setup_logging(
|
||||
"graphics",
|
||||
"pci",
|
||||
"virtio-gpud",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
// Double check that we have the right device.
|
||||
//
|
||||
// 0x1050 - virtio-gpu
|
||||
let pci_config = pcid_handle.config();
|
||||
|
||||
assert_eq!(pci_config.func.full_device_id.device_id, 0x1050);
|
||||
log::info!("virtio-gpu: initiating startup sequence :^)");
|
||||
|
||||
let device = DEVICE.try_call_once(|| virtio_core::probe_device(&mut pcid_handle))?;
|
||||
let config = unsafe { &mut *(device.device_space as *mut GpuConfig) };
|
||||
|
||||
// Negotiate features.
|
||||
let has_edid = device.transport.check_device_feature(VIRTIO_GPU_F_EDID);
|
||||
if has_edid {
|
||||
device.transport.ack_driver_feature(VIRTIO_GPU_F_EDID);
|
||||
}
|
||||
device.transport.finalize_features();
|
||||
|
||||
// Queue for sending control commands.
|
||||
let control_queue = device
|
||||
.transport
|
||||
.setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
|
||||
|
||||
// Queue for sending cursor updates.
|
||||
let cursor_queue = device
|
||||
.transport
|
||||
.setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
|
||||
|
||||
device.transport.setup_config_notify(MSIX_PRIMARY_VECTOR);
|
||||
|
||||
device.transport.run_device();
|
||||
|
||||
// Needs to be before GpuScheme::new to avoid a deadlock due to initnsmgr blocking on
|
||||
// /scheme/event as it is already blocked on opening /scheme/display.virtio-gpu.
|
||||
// FIXME change the initnsmgr to not block on openat for the target scheme.
|
||||
let event_queue: EventQueue<Source> =
|
||||
EventQueue::new().expect("virtio-gpud: failed to create event queue");
|
||||
|
||||
let mut scheme = scheme::GpuScheme::new(
|
||||
config,
|
||||
control_queue.clone(),
|
||||
cursor_queue.clone(),
|
||||
device.transport.clone(),
|
||||
has_edid,
|
||||
)?;
|
||||
daemon.ready();
|
||||
|
||||
user_data! {
|
||||
enum Source {
|
||||
Input,
|
||||
Scheme,
|
||||
Interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
event_queue
|
||||
.subscribe(
|
||||
scheme.inputd_event_handle().as_raw_fd() as usize,
|
||||
Source::Input,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
scheme.event_handle().raw(),
|
||||
Source::Scheme,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
device.irq_handle.as_raw_fd() as usize,
|
||||
Source::Interrupt,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let all = [Source::Input, Source::Scheme, Source::Interrupt];
|
||||
for event in all
|
||||
.into_iter()
|
||||
.chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data))
|
||||
{
|
||||
match event {
|
||||
Source::Input => scheme.handle_vt_events(),
|
||||
Source::Scheme => {
|
||||
scheme
|
||||
.tick()
|
||||
.expect("virtio-gpud: failed to process scheme events");
|
||||
}
|
||||
Source::Interrupt => loop {
|
||||
let before_gen = device.transport.config_generation();
|
||||
|
||||
let events = scheme.adapter().config.events_read.get();
|
||||
|
||||
if events & VIRTIO_GPU_EVENT_DISPLAY != 0 {
|
||||
let (adapter, objects) = scheme.adapter_and_kms_objects_mut();
|
||||
futures::executor::block_on(async { adapter.update_displays().await.unwrap() });
|
||||
for connector_id in objects.connector_ids().to_vec() {
|
||||
adapter.probe_connector(objects, connector_id);
|
||||
}
|
||||
scheme.notify_displays_changed();
|
||||
scheme
|
||||
.adapter_mut()
|
||||
.config
|
||||
.events_clear
|
||||
.set(VIRTIO_GPU_EVENT_DISPLAY);
|
||||
}
|
||||
|
||||
let after_gen = device.transport.config_generation();
|
||||
if before_gen == after_gen {
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use common::{dma::Dma, sgl};
|
||||
use driver_graphics::kms::connector::{KmsConnectorDriver, KmsConnectorStatus};
|
||||
use driver_graphics::kms::objects::{KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects};
|
||||
use driver_graphics::{Buffer as DrmBuffer, CursorPlane, Damage, GraphicsAdapter, GraphicsScheme};
|
||||
use drm_sys::{
|
||||
DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFER_SHADOW,
|
||||
DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
|
||||
};
|
||||
|
||||
use syscall::{EINVAL, PAGE_SIZE};
|
||||
|
||||
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
|
||||
use virtio_core::transport::{Error, Queue, Transport};
|
||||
|
||||
use crate::*;
|
||||
|
||||
impl Into<GpuRect> for Damage {
|
||||
fn into(self) -> GpuRect {
|
||||
GpuRect {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VirtGpuConnector {
|
||||
display_id: u32,
|
||||
}
|
||||
|
||||
impl KmsConnectorDriver for VirtGpuConnector {
|
||||
type State = ();
|
||||
}
|
||||
|
||||
pub struct VirtGpuFramebuffer<'a> {
|
||||
queue: Arc<Queue<'a>>,
|
||||
id: ResourceId,
|
||||
sgl: sgl::Sgl,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for VirtGpuFramebuffer<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("VirtGpuFramebuffer")
|
||||
.field("id", &self.id)
|
||||
.field("sgl", &self.sgl)
|
||||
.field("width", &self.width)
|
||||
.field("height", &self.height)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl DrmBuffer for VirtGpuFramebuffer<'_> {
|
||||
fn size(&self) -> usize {
|
||||
(self.width * self.height * 4) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VirtGpuFramebuffer<'_> {
|
||||
fn drop(&mut self) {
|
||||
futures::executor::block_on(async {
|
||||
let request = Dma::new(ResourceUnref::new(self.id)).unwrap();
|
||||
|
||||
let header = Dma::new(ControlHeader::default()).unwrap();
|
||||
let command = ChainBuilder::new()
|
||||
.chain(Buffer::new(&request))
|
||||
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
|
||||
.build();
|
||||
|
||||
self.queue.send(command).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Display {
|
||||
enabled: bool,
|
||||
width: u32,
|
||||
height: u32,
|
||||
edid: Vec<u8>,
|
||||
active_resource: Option<ResourceId>,
|
||||
}
|
||||
|
||||
pub struct VirtGpuAdapter<'a> {
|
||||
pub config: &'a mut GpuConfig,
|
||||
control_queue: Arc<Queue<'a>>,
|
||||
cursor_queue: Arc<Queue<'a>>,
|
||||
transport: Arc<dyn Transport>,
|
||||
has_edid: bool,
|
||||
displays: Vec<Display>,
|
||||
hidden_cursor: Option<Arc<VirtGpuFramebuffer<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for VirtGpuAdapter<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("VirtGpuAdapter")
|
||||
.field("displays", &self.displays)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtGpuAdapter<'_> {
|
||||
pub async fn update_displays(&mut self) -> Result<(), Error> {
|
||||
let display_info = self.get_display_info().await?;
|
||||
let raw_displays = &display_info.display_info[..self.config.num_scanouts() as usize];
|
||||
|
||||
self.displays.resize(
|
||||
raw_displays.len(),
|
||||
Display {
|
||||
enabled: false,
|
||||
width: 0,
|
||||
height: 0,
|
||||
edid: vec![],
|
||||
active_resource: None,
|
||||
},
|
||||
);
|
||||
for (i, info) in raw_displays.iter().enumerate() {
|
||||
log::info!(
|
||||
"virtio-gpu: display {i} ({}x{}px)",
|
||||
info.rect.width,
|
||||
info.rect.height
|
||||
);
|
||||
|
||||
self.displays[i].enabled = info.enabled != 0;
|
||||
|
||||
if info.rect.width == 0 || info.rect.height == 0 {
|
||||
// QEMU gives all displays other than the first a zero width and height, but trying
|
||||
// to attach a zero sized framebuffer to the display will result an error, so
|
||||
// default to 640x480px.
|
||||
self.displays[i].width = 640;
|
||||
self.displays[i].height = 480;
|
||||
} else {
|
||||
self.displays[i].width = info.rect.width;
|
||||
self.displays[i].height = info.rect.height;
|
||||
}
|
||||
|
||||
if self.has_edid {
|
||||
let edid = self.get_edid(i as u32).await?;
|
||||
self.displays[i].edid = edid.edid[..edid.size as usize].to_vec();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_request<T>(&self, request: Dma<T>) -> Result<Dma<ControlHeader>, Error> {
|
||||
let header = Dma::new(ControlHeader::default())?;
|
||||
let command = ChainBuilder::new()
|
||||
.chain(Buffer::new(&request))
|
||||
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
|
||||
.build();
|
||||
|
||||
self.control_queue.send(command).await;
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
async fn send_request_fenced<T>(&self, request: Dma<T>) -> Result<Dma<ControlHeader>, Error> {
|
||||
let mut header = Dma::new(ControlHeader::default())?;
|
||||
header.flags |= VIRTIO_GPU_FLAG_FENCE;
|
||||
let command = ChainBuilder::new()
|
||||
.chain(Buffer::new(&request))
|
||||
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
|
||||
.build();
|
||||
|
||||
self.control_queue.send(command).await;
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
async fn get_display_info(&self) -> Result<Dma<GetDisplayInfo>, Error> {
|
||||
let header = Dma::new(ControlHeader::with_ty(CommandTy::GetDisplayInfo))?;
|
||||
|
||||
let response = Dma::new(GetDisplayInfo::default())?;
|
||||
let command = ChainBuilder::new()
|
||||
.chain(Buffer::new(&header))
|
||||
.chain(Buffer::new(&response).flags(DescriptorFlags::WRITE_ONLY))
|
||||
.build();
|
||||
|
||||
self.control_queue.send(command).await;
|
||||
assert!(response.header.ty == CommandTy::RespOkDisplayInfo);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn get_edid(&self, scanout_id: u32) -> Result<Dma<GetEdidResp>, Error> {
|
||||
let header = Dma::new(GetEdid::new(scanout_id))?;
|
||||
|
||||
let response = Dma::new(GetEdidResp::new())?;
|
||||
let command = ChainBuilder::new()
|
||||
.chain(Buffer::new(&header))
|
||||
.chain(Buffer::new(&response).flags(DescriptorFlags::WRITE_ONLY))
|
||||
.build();
|
||||
|
||||
self.control_queue.send(command).await;
|
||||
assert!(response.header.ty == CommandTy::RespOkEdid);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn update_cursor(
|
||||
&mut self,
|
||||
cursor: &VirtGpuFramebuffer,
|
||||
x: i32,
|
||||
y: i32,
|
||||
hot_x: i32,
|
||||
hot_y: i32,
|
||||
) {
|
||||
//Transfering cursor resource to host
|
||||
futures::executor::block_on(async {
|
||||
let transfer_request = Dma::new(XferToHost2d::new(
|
||||
cursor.id,
|
||||
GpuRect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 64,
|
||||
height: 64,
|
||||
},
|
||||
0,
|
||||
))
|
||||
.unwrap();
|
||||
let header = self.send_request_fenced(transfer_request).await.unwrap();
|
||||
assert_eq!(header.ty, CommandTy::RespOkNodata);
|
||||
});
|
||||
|
||||
//Update the cursor position
|
||||
let request = Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)).unwrap();
|
||||
futures::executor::block_on(async {
|
||||
let command = ChainBuilder::new().chain(Buffer::new(&request)).build();
|
||||
self.cursor_queue.send(command).await;
|
||||
});
|
||||
}
|
||||
|
||||
fn move_cursor(&mut self, x: i32, y: i32) {
|
||||
let request = Dma::new(MoveCursor::move_cursor(x, y)).unwrap();
|
||||
|
||||
futures::executor::block_on(async {
|
||||
let command = ChainBuilder::new().chain(Buffer::new(&request)).build();
|
||||
self.cursor_queue.send(command).await;
|
||||
});
|
||||
}
|
||||
|
||||
fn disable_cursor(&mut self) {
|
||||
if self.hidden_cursor.is_none() {
|
||||
let (width, height) = self.hw_cursor_size().unwrap();
|
||||
let (cursor, stride) = self.create_dumb_buffer(width, height);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(
|
||||
cursor.sgl.as_ptr() as *mut u8,
|
||||
0,
|
||||
(stride * height) as usize,
|
||||
);
|
||||
}
|
||||
self.hidden_cursor = Some(Arc::new(cursor));
|
||||
}
|
||||
let hidden_cursor = self.hidden_cursor.as_ref().unwrap().clone();
|
||||
|
||||
self.update_cursor(&hidden_cursor, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> {
|
||||
type Connector = VirtGpuConnector;
|
||||
type Crtc = ();
|
||||
|
||||
type Buffer = VirtGpuFramebuffer<'a>;
|
||||
type Framebuffer = ();
|
||||
|
||||
fn name(&self) -> &'static [u8] {
|
||||
b"virtio-gpud"
|
||||
}
|
||||
|
||||
fn desc(&self) -> &'static [u8] {
|
||||
b"VirtIO GPU"
|
||||
}
|
||||
|
||||
fn init(&mut self, objects: &mut KmsObjects<Self>) {
|
||||
futures::executor::block_on(async {
|
||||
self.update_displays().await.unwrap();
|
||||
});
|
||||
|
||||
for display_id in 0..self.config.num_scanouts.get() {
|
||||
let crtc = objects.add_crtc((), ());
|
||||
|
||||
objects.add_connector(VirtGpuConnector { display_id }, (), &[crtc]);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cap(&self, cap: u32) -> syscall::Result<u64> {
|
||||
match cap {
|
||||
DRM_CAP_DUMB_BUFFER => Ok(1),
|
||||
DRM_CAP_DUMB_PREFER_SHADOW => Ok(0),
|
||||
DRM_CAP_CURSOR_WIDTH => Ok(64),
|
||||
DRM_CAP_CURSOR_HEIGHT => Ok(64),
|
||||
_ => Err(syscall::Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_client_cap(&self, cap: u32, _value: u64) -> syscall::Result<()> {
|
||||
match cap {
|
||||
// FIXME hide cursor plane unless this client cap is set
|
||||
DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT => Ok(()),
|
||||
_ => Err(syscall::Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn probe_connector(&mut self, objects: &mut KmsObjects<Self>, id: KmsObjectId) {
|
||||
futures::executor::block_on(async {
|
||||
let mut connector = objects.get_connector(id).unwrap().lock().unwrap();
|
||||
let display = &self.displays[connector.driver_data.display_id as usize];
|
||||
|
||||
connector.connection = if display.enabled {
|
||||
KmsConnectorStatus::Connected
|
||||
} else {
|
||||
KmsConnectorStatus::Disconnected
|
||||
};
|
||||
|
||||
if self.has_edid {
|
||||
connector.update_from_edid(&display.edid);
|
||||
|
||||
drop(connector);
|
||||
|
||||
let blob = objects.add_blob(display.edid.clone());
|
||||
objects.get_connector(id).unwrap().lock().unwrap().edid = blob;
|
||||
} else {
|
||||
connector.update_from_size(display.width, display.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_dumb_buffer(&mut self, width: u32, height: u32) -> (Self::Buffer, u32) {
|
||||
futures::executor::block_on(async {
|
||||
let bpp = 32;
|
||||
let fb_size = width as usize * height as usize * bpp / 8;
|
||||
let sgl = sgl::Sgl::new(fb_size).unwrap();
|
||||
|
||||
unsafe {
|
||||
core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 255, fb_size);
|
||||
}
|
||||
|
||||
let res_id = ResourceId::alloc();
|
||||
|
||||
// Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`.
|
||||
let request = Dma::new(ResourceCreate2d::new(
|
||||
res_id,
|
||||
ResourceFormat::Bgrx,
|
||||
width,
|
||||
height,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let header = self.send_request(request).await.unwrap();
|
||||
assert_eq!(header.ty, CommandTy::RespOkNodata);
|
||||
|
||||
// Use the allocated framebuffer from the guest ram, and attach it as backing
|
||||
// storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`.
|
||||
|
||||
let mut mem_entries =
|
||||
unsafe { Dma::zeroed_slice(sgl.chunks().len()).unwrap().assume_init() };
|
||||
for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) {
|
||||
*entry = MemEntry {
|
||||
address: chunk.phys as u64,
|
||||
length: chunk.length.next_multiple_of(PAGE_SIZE) as u32,
|
||||
padding: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let attach_request =
|
||||
Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)).unwrap();
|
||||
let header = Dma::new(ControlHeader::default()).unwrap();
|
||||
let command = ChainBuilder::new()
|
||||
.chain(Buffer::new(&attach_request))
|
||||
.chain(Buffer::new_unsized(&mem_entries))
|
||||
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
|
||||
.build();
|
||||
|
||||
self.control_queue.send(command).await;
|
||||
assert_eq!(header.ty, CommandTy::RespOkNodata);
|
||||
|
||||
(
|
||||
VirtGpuFramebuffer {
|
||||
queue: self.control_queue.clone(),
|
||||
id: res_id,
|
||||
sgl,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
width * 4,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn map_dumb_buffer(&mut self, buffer: &Self::Buffer) -> *mut u8 {
|
||||
buffer.sgl.as_ptr()
|
||||
}
|
||||
|
||||
fn create_framebuffer(&mut self, _buffer: &Self::Buffer) -> Self::Framebuffer {
|
||||
()
|
||||
}
|
||||
|
||||
fn set_crtc(
|
||||
&mut self,
|
||||
objects: &KmsObjects<Self>,
|
||||
crtc: &Mutex<KmsCrtc<Self>>,
|
||||
state: KmsCrtcState<Self>,
|
||||
damage: Damage,
|
||||
) -> syscall::Result<()> {
|
||||
futures::executor::block_on(async {
|
||||
let mut crtc = crtc.lock().unwrap();
|
||||
let framebuffer = state
|
||||
.fb_id
|
||||
.map(|fb_id| objects.get_framebuffer(fb_id))
|
||||
.transpose()?;
|
||||
crtc.state = state;
|
||||
|
||||
for connector in objects.connectors() {
|
||||
let connector = connector.lock().unwrap();
|
||||
|
||||
if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] {
|
||||
continue;
|
||||
}
|
||||
|
||||
let display_id = connector.driver_data.display_id;
|
||||
|
||||
let Some(framebuffer) = framebuffer else {
|
||||
let scanout_request = Dma::new(SetScanout::new(
|
||||
display_id,
|
||||
ResourceId::NONE,
|
||||
GpuRect::new(0, 0, 0, 0),
|
||||
))
|
||||
.unwrap();
|
||||
let header = self.send_request(scanout_request).await.unwrap();
|
||||
assert_eq!(header.ty, CommandTy::RespOkNodata);
|
||||
self.displays[display_id as usize].active_resource = None;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let req = Dma::new(XferToHost2d::new(
|
||||
framebuffer.buffer.id,
|
||||
GpuRect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: framebuffer.width,
|
||||
height: framebuffer.height,
|
||||
},
|
||||
0,
|
||||
))
|
||||
.unwrap();
|
||||
let header = self.send_request(req).await.unwrap();
|
||||
assert_eq!(header.ty, CommandTy::RespOkNodata);
|
||||
|
||||
// FIXME once we support resizing we also need to check that the current and target size match
|
||||
if self.displays[display_id as usize].active_resource != Some(framebuffer.buffer.id)
|
||||
{
|
||||
let scanout_request = Dma::new(SetScanout::new(
|
||||
display_id,
|
||||
framebuffer.buffer.id,
|
||||
GpuRect::new(0, 0, framebuffer.width, framebuffer.height),
|
||||
))
|
||||
.unwrap();
|
||||
let header = self.send_request(scanout_request).await.unwrap();
|
||||
assert_eq!(header.ty, CommandTy::RespOkNodata);
|
||||
self.displays[display_id as usize].active_resource =
|
||||
Some(framebuffer.buffer.id);
|
||||
}
|
||||
|
||||
let flush = ResourceFlush::new(
|
||||
framebuffer.buffer.id,
|
||||
damage.clip(framebuffer.width, framebuffer.height).into(),
|
||||
);
|
||||
let header = self.send_request(Dma::new(flush).unwrap()).await.unwrap();
|
||||
assert_eq!(header.ty, CommandTy::RespOkNodata);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn hw_cursor_size(&self) -> Option<(u32, u32)> {
|
||||
Some((64, 64))
|
||||
}
|
||||
|
||||
fn handle_cursor(&mut self, cursor: &CursorPlane<Self::Buffer>, dirty_fb: bool) {
|
||||
if let Some(buffer) = &cursor.buffer {
|
||||
if dirty_fb {
|
||||
self.update_cursor(buffer, cursor.x, cursor.y, cursor.hot_x, cursor.hot_y);
|
||||
} else {
|
||||
self.move_cursor(cursor.x, cursor.y);
|
||||
}
|
||||
} else {
|
||||
if dirty_fb {
|
||||
self.disable_cursor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpuScheme {}
|
||||
|
||||
impl<'a> GpuScheme {
|
||||
pub fn new(
|
||||
config: &'a mut GpuConfig,
|
||||
control_queue: Arc<Queue<'a>>,
|
||||
cursor_queue: Arc<Queue<'a>>,
|
||||
transport: Arc<dyn Transport>,
|
||||
has_edid: bool,
|
||||
) -> Result<GraphicsScheme<VirtGpuAdapter<'a>>, Error> {
|
||||
let adapter = VirtGpuAdapter {
|
||||
config,
|
||||
control_queue,
|
||||
cursor_queue,
|
||||
transport,
|
||||
has_edid,
|
||||
displays: vec![],
|
||||
hidden_cursor: None,
|
||||
};
|
||||
|
||||
Ok(GraphicsScheme::new(
|
||||
adapter,
|
||||
"display.virtio-gpu".to_owned(),
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user