Correctly parse EDID info

Should fix output make always being Unknown and some cases of the monitor name not being parsed because of a faulty detailed timings parse
This commit is contained in:
Ottatop 2024-03-22 14:27:18 -05:00
parent 13335e9cdd
commit 698cd1d973
2 changed files with 212 additions and 16 deletions

View file

@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
mod drm_util;
use std::{
collections::{HashMap, HashSet},
ffi::OsString,
@ -66,10 +68,8 @@ use smithay::{
utils::{Clock, DeviceFd, Logical, Monotonic, Point, Transform},
wayland::dmabuf::{DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
};
use smithay_drm_extras::{
drm_scanner::{DrmScanEvent, DrmScanner},
edid::EdidInfo,
};
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
use tracing::warn;
use crate::{
backend::Backend,
@ -80,6 +80,8 @@ use crate::{
window::WindowElement,
};
use self::drm_util::EdidInfo;
use super::BackendData;
const SUPPORTED_FORMATS: &[Fourcc] = &[
@ -183,7 +185,7 @@ impl State {
.surface()
.clear_plane(overlay_plane.handle)
{
tracing::warn!("Failed to clear overlay planes: {err}");
warn!("Failed to clear overlay planes: {err}");
}
}
}
@ -228,7 +230,7 @@ impl BackendData for Udev {
fn early_import(&mut self, surface: &WlSurface) {
if let Err(err) = self.gpu_manager.early_import(self.primary_gpu, surface) {
tracing::warn!("early buffer import failed: {}", err);
warn!("early buffer import failed: {}", err);
}
}
}
@ -376,7 +378,7 @@ pub fn setup_udev(
backend.drm.activate(false).expect("failed to activate drm");
for surface in backend.surfaces.values_mut() {
if let Err(err) = surface.compositor.surface().reset_state() {
tracing::warn!("Failed to reset drm surface state: {}", err);
warn!("Failed to reset drm surface state: {}", err);
}
// reset the buffers after resume to trigger a full redraw
// this is important after a vt switch as the primary plane
@ -445,7 +447,7 @@ pub fn setup_udev(
as Box<dyn Allocator<Buffer = Dmabuf, Error = AnyError>>);
}
Err(err) => {
tracing::warn!("Failed to create vulkan allocator: {}", err);
warn!("Failed to create vulkan allocator: {}", err);
}
}
}
@ -778,7 +780,6 @@ impl State {
}
/// A display was plugged in.
// TODO: better edid info from cosmic-comp
fn connector_connected(
&mut self,
node: DrmNode,
@ -827,7 +828,7 @@ impl State {
{
Ok(surface) => surface,
Err(err) => {
tracing::warn!("Failed to create drm surface: {}", err);
warn!("Failed to create drm surface: {}", err);
return;
}
};
@ -838,9 +839,13 @@ impl State {
connector.interface_id()
);
let (make, model) = EdidInfo::for_connector(&device.drm, connector.handle())
.map(|info| (info.manufacturer, info.model))
.unwrap_or_else(|| ("Unknown".into(), "Unknown".into()));
let (make, model) =
EdidInfo::try_from_device_and_connector(&device.drm, connector.handle())
.map(|info| (info.manufacturer, info.model))
.unwrap_or_else(|err| {
warn!("Failed to parse EDID info: {err}");
("Unknown".into(), "Unknown".into())
});
let (phys_w, phys_h) = connector.size().unwrap_or((0, 0));
@ -897,7 +902,7 @@ impl State {
let driver = match device.drm.get_driver() {
Ok(driver) => driver,
Err(err) => {
tracing::warn!("Failed to query drm driver: {}", err);
warn!("Failed to query drm driver: {}", err);
return;
}
};
@ -932,7 +937,7 @@ impl State {
) {
Ok(compositor) => compositor,
Err(err) => {
tracing::warn!("Failed to create drm compositor: {}", err);
warn!("Failed to create drm compositor: {}", err);
return;
}
}
@ -1154,7 +1159,7 @@ impl State {
}
}
Err(err) => {
tracing::warn!("Error during rendering: {:?}", err);
warn!("Error during rendering: {:?}", err);
if let SwapBuffersError::ContextLost(err) = err {
panic!("Rendering loop lost: {}", err)
}

View file

@ -0,0 +1,191 @@
use smithay::reexports::drm::control::{connector, property, Device, ResourceHandle};
// A bunch of this stuff is from cosmic-comp
#[derive(Debug, Clone)]
pub struct EdidInfo {
pub model: String,
pub manufacturer: String,
}
impl EdidInfo {
pub fn try_from_device_and_connector(
device: &impl Device,
connector: connector::Handle,
) -> anyhow::Result<Self> {
let edid_prop = get_prop(device, connector, "EDID")?;
let edid_info = device.get_property(edid_prop)?;
let mut info = Err(anyhow::anyhow!("No info"));
let props = device.get_properties(connector)?;
let (ids, vals) = props.as_props_and_values();
for (&id, &val) in ids.iter().zip(vals.iter()) {
if id == edid_prop {
if let property::Value::Blob(edid_blob) = edid_info.value_type().convert_value(val)
{
let blob = device.get_property_blob(edid_blob)?;
info = parse_edid(&blob);
}
break;
}
}
dbg!(info)
}
}
/// Minimally parse the model and manufacturer from the given EDID data buffer.
///
/// `edid-rs` does not properly parse manufacturer ids (it has the order of the id bytes reversed
/// and doesn't add 64 to map the byte to a character), and it additionally
/// fails to parse detailed timing descriptors with an hactive that's divisible by 256
/// (see https://github.com/tuomas56/edid-rs/pull/1).
///
/// Because of this, we're just rolling our own minimal parser instead.
fn parse_edid(buffer: &[u8]) -> anyhow::Result<EdidInfo> {
// Manufacterer id is bytes 8-9, big endian
let manu_id = u16::from_be_bytes(buffer[8..=9].try_into()?);
// Characters are bits 14-10, 9-5, and 4-0.
// They also map 0b00001..=0b11010 to A..=Z, so add 64 to get the character.
let char1 = ((manu_id & 0b0111110000000000) >> 10) as u8 + 64;
let char2 = ((manu_id & 0b0000001111100000) >> 5) as u8 + 64;
let char3 = (manu_id & 0b0000000000011111) as u8 + 64;
let manufacturer = get_manufacturer([char1 as char, char2 as char, char3 as char]);
// Monitor names are inside of these display/monitor descriptors at bytes 72..=125.
// Each descriptor is 18 bytes long.
let descriptor1 = &buffer[72..=89];
let descriptor2 = &buffer[90..=107];
let descriptor3 = &buffer[108..=125];
let descriptors = [descriptor1, descriptor2, descriptor3];
let model = descriptors
.into_iter()
.find_map(|desc| {
// The descriptor is a monitor descriptor if its first 2 bytes are 0.
let is_monitor_descriptor = desc[0..=1] == [0, 0];
// The descriptor describes a monitor name if it has the tag 0xfc at byte 3.
let is_monitor_name = desc[3] == 0xfc;
if is_monitor_descriptor && is_monitor_name {
// Name is up to 13 bytes at bytes 5..=17 within the descriptor.
let monitor_name = desc[5..=17]
.iter()
// Names are terminated with a newline if shorter than 13 bytes.
.take_while(|&&byte| byte != b'\n')
.map(|&byte| byte as char)
.collect::<String>();
// NOTE: The EDID spec mandates that bytes after the newline are padded with
// | spaces (0x20), but we're just gonna ignore that haha
Some(monitor_name)
} else {
None
}
})
.or_else(|| {
// Get the product code instead.
// It's at bytes 10..=11, little-endian.
let product_code = u16::from_le_bytes(buffer[10..=11].try_into().ok()?);
Some(format!("{product_code:x}"))
})
.unwrap_or("Unknown".to_string());
Ok(EdidInfo {
model,
manufacturer,
})
}
fn get_prop(
device: &impl Device,
handle: impl ResourceHandle,
name: &str,
) -> anyhow::Result<property::Handle> {
let props = device.get_properties(handle)?;
let (prop_handles, _) = props.as_props_and_values();
for prop in prop_handles {
let info = device.get_property(*prop)?;
if Some(name) == info.name().to_str().ok() {
return Ok(*prop);
}
}
anyhow::bail!("No prop found for {}", name)
}
fn get_manufacturer(vendor: [char; 3]) -> String {
match vendor {
['A', 'A', 'A'] => "Avolites Ltd".to_string(),
['A', 'C', 'I'] => "Ancor Communications Inc".to_string(),
['A', 'C', 'R'] => "Acer Technologies".to_string(),
['A', 'D', 'A'] => "Addi-Data GmbH".to_string(),
['A', 'P', 'P'] => "Apple Computer Inc".to_string(),
['A', 'S', 'K'] => "Ask A/S".to_string(),
['A', 'V', 'T'] => "Avtek (Electronics) Pty Ltd".to_string(),
['B', 'N', 'O'] => "Bang & Olufsen".to_string(),
['B', 'N', 'Q'] => "BenQ Corporation".to_string(),
['C', 'M', 'N'] => "Chimei Innolux Corporation".to_string(),
['C', 'M', 'O'] => "Chi Mei Optoelectronics corp.".to_string(),
['C', 'R', 'O'] => "Extraordinary Technologies PTY Limited".to_string(),
['D', 'E', 'L'] => "Dell Inc.".to_string(),
['D', 'G', 'C'] => "Data General Corporation".to_string(),
['D', 'O', 'N'] => "DENON, Ltd.".to_string(),
['E', 'N', 'C'] => "Eizo Nanao Corporation".to_string(),
['E', 'P', 'H'] => "Epiphan Systems Inc.".to_string(),
['E', 'X', 'P'] => "Data Export Corporation".to_string(),
['F', 'N', 'I'] => "Funai Electric Co., Ltd.".to_string(),
['F', 'U', 'S'] => "Fujitsu Siemens Computers GmbH".to_string(),
['G', 'S', 'M'] => "Goldstar Company Ltd".to_string(),
['H', 'I', 'Q'] => "Kaohsiung Opto Electronics Americas, Inc.".to_string(),
['H', 'S', 'D'] => "HannStar Display Corp".to_string(),
['H', 'T', 'C'] => "Hitachi Ltd".to_string(),
['H', 'W', 'P'] => "Hewlett Packard".to_string(),
['I', 'N', 'T'] => "Interphase Corporation".to_string(),
['I', 'N', 'X'] => "Communications Supply Corporation (A division of WESCO)".to_string(),
['I', 'T', 'E'] => "Integrated Tech Express Inc".to_string(),
['I', 'V', 'M'] => "Iiyama North America".to_string(),
['L', 'E', 'N'] => "Lenovo Group Limited".to_string(),
['M', 'A', 'X'] => "Rogen Tech Distribution Inc".to_string(),
['M', 'E', 'G'] => "Abeam Tech Ltd".to_string(),
['M', 'E', 'I'] => "Panasonic Industry Company".to_string(),
['M', 'T', 'C'] => "Mars-Tech Corporation".to_string(),
['M', 'T', 'X'] => "Matrox".to_string(),
['N', 'E', 'C'] => "NEC Corporation".to_string(),
['N', 'E', 'X'] => "Nexgen Mediatech Inc.".to_string(),
['O', 'N', 'K'] => "ONKYO Corporation".to_string(),
['O', 'R', 'N'] => "ORION ELECTRIC CO., LTD.".to_string(),
['O', 'T', 'M'] => "Optoma Corporation".to_string(),
['O', 'V', 'R'] => "Oculus VR, Inc.".to_string(),
['P', 'H', 'L'] => "Philips Consumer Electronics Company".to_string(),
['P', 'I', 'O'] => "Pioneer Electronic Corporation".to_string(),
['P', 'N', 'R'] => "Planar Systems, Inc.".to_string(),
['Q', 'D', 'S'] => "Quanta Display Inc.".to_string(),
['R', 'A', 'T'] => "Rent-A-Tech".to_string(),
['R', 'E', 'N'] => "Renesas Technology Corp.".to_string(),
['S', 'A', 'M'] => "Samsung Electric Company".to_string(),
['S', 'A', 'N'] => "Sanyo Electric Co., Ltd.".to_string(),
['S', 'E', 'C'] => "Seiko Epson Corporation".to_string(),
['S', 'H', 'P'] => "Sharp Corporation".to_string(),
['S', 'I', 'I'] => "Silicon Image, Inc.".to_string(),
['S', 'N', 'Y'] => "Sony".to_string(),
['S', 'T', 'D'] => "STD Computer Inc".to_string(),
['S', 'V', 'S'] => "SVSI".to_string(),
['S', 'Y', 'N'] => "Synaptics Inc".to_string(),
['T', 'C', 'L'] => "Technical Concepts Ltd".to_string(),
['T', 'O', 'P'] => "Orion Communications Co., Ltd.".to_string(),
['T', 'S', 'B'] => "Toshiba America Info Systems Inc".to_string(),
['T', 'S', 'T'] => "Transtream Inc".to_string(),
['U', 'N', 'K'] => "Unknown".to_string(),
['V', 'E', 'S'] => "Vestel Elektronik Sanayi ve Ticaret A. S.".to_string(),
['V', 'I', 'T'] => "Visitech AS".to_string(),
['V', 'I', 'Z'] => "VIZIO, Inc".to_string(),
['V', 'S', 'C'] => "ViewSonic Corporation".to_string(),
['Y', 'M', 'H'] => "Yamaha Corporation".to_string(),
_ => vendor.iter().collect(),
}
}