Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Apple Silicon Macs (M1/M2/M3) #7

Merged
merged 9 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
rust-toolchain
11 changes: 6 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ddc-macos"
version = "0.3.0"
version = "0.2.2"
authors = ["Haim Gelfenbeyn <haim@g8n.me>"]
description = "DDC/CI monitor control on MacOS"
documentation = "https://haimgel.github.io/ddc-macos-rs/ddc_macos"
Expand All @@ -12,13 +12,14 @@ categories = ["hardware-support", "os::macos-apis"]
edition = "2021"

[dependencies]
core-foundation = "0.10.0"
core-foundation = "0.10"
core-foundation-sys = "0.8"
core-graphics = "0.24"
ddc = "0.3"
# ddc must stay on "0.2" till ddc-hi is also updated
ddc = "0.2"
io-kit-sys = "0.4"
mach = "0.3"
thiserror = "1"
mach2 = "0.4"
thiserror = "1.0"

[dev-dependencies]
edid-rs = "0.1"
Expand Down
2 changes: 1 addition & 1 deletion rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
max_width = 120
edition = "2021"
style_edition = "2021"
use_try_shorthand = true
175 changes: 175 additions & 0 deletions src/arm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use crate::error::Error;
use crate::error::Error::{DisplayLocationNotFound, ServiceNotFound};
use crate::iokit::IoIterator;
use crate::iokit::{CoreDisplay_DisplayCreateInfoDictionary, IoObject};
use crate::{kern_try, verify_io};
use core_foundation::base::{CFType, TCFType};
use core_foundation::dictionary::CFDictionary;
use core_foundation::string::CFString;
use core_foundation_sys::base::{kCFAllocatorDefault, CFAllocatorRef, CFTypeRef, OSStatus};
use core_graphics::display::CGDisplay;
use ddc::{I2C_ADDRESS_DDC_CI, SUB_ADDRESS_DDC_CI};
use io_kit_sys::keys::kIOServicePlane;
use io_kit_sys::types::{io_object_t, io_registry_entry_t};
use io_kit_sys::{
kIORegistryIterateRecursively, IORegistryEntryCreateCFProperty, IORegistryEntryGetName,
IORegistryEntryGetParentEntry, IORegistryEntryGetPath,
};
use mach2::kern_return::KERN_SUCCESS;
use std::ffi::CStr;
use std::os::raw::{c_uint, c_void};
use std::time::Duration;

pub type IOAVService = CFTypeRef;

pub(crate) fn execute<'a>(
service: &IOAVService,
i2c_address: u16,
request_data: &[u8],
out: &'a mut [u8],
response_delay: Duration,
) -> Result<&'a mut [u8], crate::error::Error> {
unsafe {
verify_io(IOAVServiceWriteI2C(
*service,
i2c_address as _, // I2C_ADDRESS_DDC_CI as u32,
SUB_ADDRESS_DDC_CI as _,
// Skip the first byte, which is the I2C address, which this API does not need
request_data[1..].as_ptr() as _,
(request_data.len() - 1) as _, // command_length as u32 + 3,
))?;
};
if !out.is_empty() {
std::thread::sleep(response_delay);
unsafe {
verify_io(IOAVServiceReadI2C(
*service,
i2c_address as _, // I2C_ADDRESS_DDC_CI as u32,
0,
out.as_ptr() as _,
out.len() as u32,
))?;
};
Ok(out)
} else {
Ok(&mut [0u8; 0])
}
}

/// Returns an AVService and its DDC I2C address for a given display
pub(crate) fn get_display_av_service(display: CGDisplay) -> Result<(IOAVService, u16), Error> {
if display.is_builtin() {
return Err(ServiceNotFound);
}
let display_infos: CFDictionary<CFString, CFType> =
unsafe { CFDictionary::wrap_under_create_rule(CoreDisplay_DisplayCreateInfoDictionary(display.id)) };
let location = display_infos
.find(CFString::from_static_string("IODisplayLocation"))
.ok_or(DisplayLocationNotFound)?
.downcast::<CFString>()
.ok_or(DisplayLocationNotFound)?
.to_string();
let external_location = CFString::from_static_string("External").into_CFType();

let mut iter = IoIterator::root()?;
while let Some(service) = iter.next() {
if let Ok(registry_location) = get_service_registry_entry_path((&service).into()) {
if registry_location == location {
while let Some(service) = iter.next() {
if get_service_registry_entry_name((&service).into())? == "DCPAVServiceProxy" {
let av_service = unsafe { IOAVServiceCreateWithService(kCFAllocatorDefault, (&service).into()) };
let loc_ref = unsafe {
IORegistryEntryCreateCFProperty(
(&service).into(),
CFString::from_static_string("Location").as_concrete_TypeRef(),
kCFAllocatorDefault,
kIORegistryIterateRecursively,
)
};
if !loc_ref.is_null() {
let loc_ref = unsafe { CFType::wrap_under_create_rule(loc_ref) };
if !av_service.is_null() && (loc_ref == external_location) {
return Ok((av_service, i2c_address(service)));
}
}
}
}
}
}
}
Err(ServiceNotFound)
}

const I2C_ADDRESS_DDC_CI_MDCP29XX: u16 = 0xB7;

/// Returns the I2C chip address for a given service
fn i2c_address(service: IoObject) -> u16 {
// M1 Macs use a non-standard chip address on their builtin HDMI ports: they are behind a
// MDCP29xx DisplayPort to HDMI bridge chip, and it needs a different I2C slave address:
// not a standard 0x37 but 0xB7.
let mut parent: io_registry_entry_t = 0;
unsafe {
if IORegistryEntryGetParentEntry((&service).into(), kIOServicePlane, &mut parent) != KERN_SUCCESS {
return I2C_ADDRESS_DDC_CI;
}
}
let class_ref = unsafe {
IORegistryEntryCreateCFProperty(
parent,
CFString::from_static_string("EPICProviderClass").as_concrete_TypeRef(),
kCFAllocatorDefault,
kIORegistryIterateRecursively,
)
};
if class_ref.is_null() {
return I2C_ADDRESS_DDC_CI;
}
let mcdp29xx = CFString::from_static_string("AppleDCPMCDP29XX").into_CFType();
let class_ref = unsafe { CFType::wrap_under_create_rule(class_ref) };
if class_ref == mcdp29xx {
I2C_ADDRESS_DDC_CI_MDCP29XX
} else {
I2C_ADDRESS_DDC_CI
}
}

fn get_service_registry_entry_path(entry: io_registry_entry_t) -> Result<String, Error> {
let mut path_buffer = [0_i8; 1024];
unsafe {
kern_try!(IORegistryEntryGetPath(entry, kIOServicePlane, path_buffer.as_mut_ptr()));
Ok(CStr::from_ptr(path_buffer.as_ptr()).to_string_lossy().into_owned())
}
}

fn get_service_registry_entry_name(entry: io_registry_entry_t) -> Result<String, Error> {
let mut name = [0; 128];
unsafe {
kern_try!(IORegistryEntryGetName(entry, name.as_mut_ptr()));
Ok(CStr::from_ptr(name.as_ptr()).to_string_lossy().into_owned())
}
}

#[link(name = "CoreDisplay", kind = "framework")]
extern "C" {
// Creates an IOAVService from an existing I/O Kit service
fn IOAVServiceCreateWithService(allocator: CFAllocatorRef, service: io_object_t) -> IOAVService;

// Reads data over I2C from the specified IOAVService
fn IOAVServiceReadI2C(
service: IOAVService,
chip_address: c_uint,
offset: c_uint,
output_buffer: *mut c_void,
output_buffer_size: c_uint,
) -> OSStatus;

// Writes data over I2C to the specified IOAVService
fn IOAVServiceWriteI2C(
service: IOAVService,
chip_address: c_uint,
data_address: c_uint,
input_buffer: *const c_void,
input_buffer_size: c_uint,
) -> OSStatus;
}

51 changes: 51 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use core_graphics::base::CGError;
use ddc::ErrorCode;
use io_kit_sys::ret::kIOReturnSuccess;
use mach2::kern_return::{kern_return_t, KERN_FAILURE};
use thiserror::Error;

/// An error that can occur during DDC/CI communication with a monitor
#[derive(Error, Debug)]
pub enum Error {
/// Core Graphics errors
#[error("Core Graphics error: {0}")]
CoreGraphics(CGError),
/// Kernel I/O errors
#[error("MacOS kernel I/O error: {0}")]
Io(kern_return_t),
/// DDC/CI errors
#[error("DDC/CI error: {0}")]
Ddc(ErrorCode),
/// Service not found
#[error("Service not found")]
ServiceNotFound,
/// Display location not found
#[error("Display location not found")]
DisplayLocationNotFound,
}

pub fn verify_io(result: kern_return_t) -> Result<(), Error> {
if result == kIOReturnSuccess {
Ok(())
} else {
Err(Error::Io(result))
}
}

impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::Io(error.raw_os_error().unwrap_or(KERN_FAILURE))
}
}

impl From<ErrorCode> for Error {
fn from(error: ErrorCode) -> Self {
Error::Ddc(error)
}
}

impl From<CGError> for Error {
fn from(error: CGError) -> Self {
Error::CoreGraphics(error)
}
}
Loading
Loading