Skip to content

Commit

Permalink
First, semi-working ARM Mac support version
Browse files Browse the repository at this point in the history
  • Loading branch information
haimgel committed Nov 3, 2024
1 parent 9677289 commit bf6ddf2
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 199 deletions.
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
99 changes: 99 additions & 0 deletions src/arm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use crate::error::Error;
use crate::error::Error::{DisplayLocationNotFound, ServiceNotFound};
use crate::iokit::CoreDisplay_DisplayCreateInfoDictionary;
use crate::iokit::IoIterator;
use crate::kern_try;
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 io_kit_sys::keys::kIOServicePlane;
use io_kit_sys::types::{io_object_t, io_registry_entry_t};
use io_kit_sys::{
kIORegistryIterateRecursively, IORegistryEntryCreateCFProperty, IORegistryEntryGetName, IORegistryEntryGetPath,
};
use std::ffi::CStr;
use std::os::raw::{c_uint, c_void};

pub type IOAVService = CFTypeRef;

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

// Reads data over I2C from the specified IOAVService
pub 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
pub fn IOAVServiceWriteI2C(
service: IOAVService,
chip_address: c_uint,
data_address: c_uint,
input_buffer: *const c_void,
input_buffer_size: c_uint,
) -> OSStatus;
}

pub fn get_display_av_service(display: CGDisplay) -> Result<IOAVService, 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.as_raw()) {
if registry_location == location {
while let Some(service) = iter.next() {
if get_service_registry_entry_name(service.as_raw())? == "DCPAVServiceProxy" {
let av_service = unsafe { IOAVServiceCreateWithService(kCFAllocatorDefault, service.as_raw()) };
let loc_ref = unsafe {
CFType::wrap_under_create_rule(IORegistryEntryCreateCFProperty(
service.as_raw(),
CFString::from_static_string("Location").as_concrete_TypeRef(),
kCFAllocatorDefault,
kIORegistryIterateRecursively,
))
};
if !av_service.is_null() && loc_ref == external_location {
return Ok(av_service);
}
}
}
}
}
}
Err(ServiceNotFound)
}

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())
}
}
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 mach::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("Service 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)
}
}
130 changes: 130 additions & 0 deletions src/intel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use crate::error::{verify_io, Error};
use crate::iokit::{kIODisplayOnlyPreferredName, IODisplayCreateInfoDictionary};
use crate::iokit::{
kIOI2CDDCciReplyTransactionType, kIOI2CNoTransactionType, kIOI2CSimpleTransactionType, IOFBCopyI2CInterfaceForBus,
IOFBGetI2CInterfaceCount, IOI2CRequest, IoI2CInterfaceConnection,
};
use crate::iokit::{IoIterator, IoObject};
use core_foundation::base::{CFType, TCFType};
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use core_foundation_sys::base::kCFAllocatorDefault;
use core_graphics::display::CGDisplay;
use ddc::{Command, CommandResult};
use io_kit_sys::ret::kIOReturnSuccess;
use io_kit_sys::types::{io_service_t, IOItemCount};
use io_kit_sys::IORegistryEntryCreateCFProperties;
use mach::kern_return::KERN_FAILURE;

pub fn display_info_dict(frame_buffer: &IoObject) -> Option<CFDictionary<CFString, CFType>> {
unsafe {
let info = IODisplayCreateInfoDictionary(frame_buffer.into(), kIODisplayOnlyPreferredName).as_ref()?;
Some(CFDictionary::<CFString, CFType>::wrap_under_create_rule(info))
}
}

/// Get supported I2C / DDC transaction types
/// DDCciReply is what we want, but Simple will also work
pub unsafe fn get_supported_transaction_type() -> Option<u32> {
let transaction_types_key = CFString::from_static_string("IOI2CTransactionTypes");

for io_service in IoIterator::for_service_names("IOFramebufferI2CInterface")? {
let mut service_properties = std::ptr::null_mut();
if IORegistryEntryCreateCFProperties(
(&io_service).into(),
&mut service_properties,
kCFAllocatorDefault as _,
0,
) == kIOReturnSuccess
{
let info = CFDictionary::<CFString, CFType>::wrap_under_create_rule(service_properties as _);
let transaction_types = info.find(&transaction_types_key)?.downcast::<CFNumber>()?.to_i64()?;
if ((1 << kIOI2CDDCciReplyTransactionType) & transaction_types) != 0 {
return Some(kIOI2CDDCciReplyTransactionType);
} else if ((1 << kIOI2CSimpleTransactionType) & transaction_types) != 0 {
return Some(kIOI2CSimpleTransactionType);
}
}
}
None
}

/// Finds if a framebuffer that matches display
fn framebuffer_port_matches_display(port: &IoObject, display: CGDisplay) -> Option<()> {
let mut bus_count: IOItemCount = 0;
unsafe {
IOFBGetI2CInterfaceCount(port.into(), &mut bus_count);
}
if bus_count == 0 {
return None;
};

let info = display_info_dict(port)?;

let display_vendor_key = CFString::from_static_string("DisplayVendorID");
let display_product_key = CFString::from_static_string("DisplayProductID");
let display_serial_key = CFString::from_static_string("DisplaySerialNumber");

let display_vendor = info.find(&display_vendor_key)?.downcast::<CFNumber>()?.to_i64()? as u32;
let display_product = info.find(&display_product_key)?.downcast::<CFNumber>()?.to_i64()? as u32;
// Display serial number is not always present. If it's not there, default to zero
// (to match what CGDisplay.serial_number() returns
let display_serial = info
.find(&display_serial_key)
.and_then(|x| x.downcast::<CFNumber>())
.and_then(|x| x.to_i32())
.map(|x| x as u32)
.unwrap_or(0);

if display_vendor == display.vendor_number()
&& display_product == display.model_number()
&& display_serial == display.serial_number()
{
Some(())
} else {
None
}
}

/// Gets the framebuffer port for a display
pub fn get_io_framebuffer_port(display: CGDisplay) -> Option<IoObject> {
if display.is_builtin() {
return None;
}
IoIterator::for_services("IOFramebuffer")?
.find(|framebuffer| framebuffer_port_matches_display(framebuffer, display).is_some())
}

/// send an I2C request to a display
pub(crate) unsafe fn send_request(
service: &IoObject,
request: &mut IOI2CRequest,
post_request_delay: u32,
) -> Result<(), Error> {
let mut bus_count = 0;
let mut result: Result<(), Error> = Err(Error::Io(KERN_FAILURE));
verify_io(IOFBGetI2CInterfaceCount(service.into(), &mut bus_count))?;
for bus in 0..bus_count {
let mut interface: io_service_t = 0;
if IOFBCopyI2CInterfaceForBus(service.into(), bus, &mut interface) == kIOReturnSuccess {
let interface = IoObject::from(interface);
result = IoI2CInterfaceConnection::new(&interface)
.and_then(|connection| connection.send_request(request))
.map_err(From::from);
if result.is_ok() {
break;
}
}
}
std::thread::sleep(std::time::Duration::from_millis(post_request_delay as u64));
result
}

pub(crate) fn get_response_transaction_type<C: Command>() -> u32 {
if C::Ok::MAX_LEN == 0 {
kIOI2CNoTransactionType
} else {
unsafe { get_supported_transaction_type().unwrap_or(kIOI2CNoTransactionType) }
}
}
8 changes: 8 additions & 0 deletions src/iokit/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

/// Selective translation of IOKit/graphics/IOGraphicsLib.h
use core_foundation::dictionary::CFDictionaryRef;
use core_graphics::display::CGDirectDisplayID;
use io_kit_sys::types::{io_service_t, IOOptionBits};
use mach::port::mach_port_t;

pub const kIODisplayMatchingInfo: IOOptionBits = 0x00000100;
pub const kIODisplayOnlyPreferredName: IOOptionBits = 0x00000200;
pub const kIODisplayNoProductName: IOOptionBits = 0x00000400;

extern "C" {
// For some reason, this is missing from io_kit_sys
pub static kIOMainPortDefault: mach_port_t;
#[link(name = "IOKit", kind = "framework")]
pub fn IODisplayCreateInfoDictionary(framebuffer: io_service_t, options: IOOptionBits) -> CFDictionaryRef;

#[link(name = "CoreDisplay", kind = "framework")]
// Creates a display info dictionary for a specified display ID
pub fn CoreDisplay_DisplayCreateInfoDictionary(display_id: CGDirectDisplayID) -> CFDictionaryRef;
}
1 change: 1 addition & 0 deletions src/iokit/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copied from https://github.com/svartalf/rust-battery/blob/master/battery/src/platform/darwin/iokit/errors.rs

#[macro_export]
macro_rules! r#kern_try {
($expr:expr) => {
match $expr {
Expand Down
10 changes: 7 additions & 3 deletions src/iokit/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#[macro_use]
pub(crate) mod errors;
pub(crate) mod display;
pub(crate) mod io2c_interface;
pub(crate) mod wrappers;
mod display;
mod io2c_interface;
mod wrappers;

pub(crate) use display::*;
pub(crate) use io2c_interface::*;
pub(crate) use wrappers::*;
24 changes: 23 additions & 1 deletion src/iokit/wrappers.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::iokit::display::kIOMainPortDefault;
use core_foundation::base::{kCFAllocatorDefault, CFType, TCFType};
use core_foundation::dictionary::{CFDictionary, CFMutableDictionary, CFMutableDictionaryRef};
use core_foundation::string::CFString;
use io_kit_sys::keys::kIOServicePlane;
use io_kit_sys::types::{io_iterator_t, io_object_t};
use io_kit_sys::{
kIOMasterPortDefault, IOIteratorNext, IOObjectRelease, IORegistryEntryCreateCFProperties,
kIOMasterPortDefault, kIORegistryIterateRecursively, IOIteratorNext, IOObjectRelease,
IORegistryEntryCreateCFProperties, IORegistryEntryCreateIterator, IORegistryGetRootEntry,
IOServiceGetMatchingServices, IOServiceMatching, IOServiceNameMatching,
};
use std::ops::{Deref, DerefMut};
Expand All @@ -25,6 +28,11 @@ impl IoObject {
Ok(CFMutableDictionary::wrap_under_create_rule(props as _).to_immutable())
}
}

/// Accessor method to get the inner `io_object_t`
pub fn as_raw(&self) -> io_object_t {
self.0
}
}

impl From<io_object_t> for IoObject {
Expand Down Expand Up @@ -68,6 +76,20 @@ impl IoIterator {
}
Ok(Self(iter))
}

pub fn root() -> Result<IoIterator, std::io::Error> {
let mut iter: io_iterator_t = 0;
unsafe {
let root = IORegistryGetRootEntry(kIOMainPortDefault);
kern_try!(IORegistryEntryCreateIterator(
root,
kIOServicePlane,
kIORegistryIterateRecursively,
&mut iter
));
};
Ok(Self(iter))
}
}

impl Deref for IoIterator {
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
//! # }
//! ```

mod arm;
mod error;
mod intel;
mod iokit;
mod monitor;

pub use error::*;
pub use monitor::*;
Loading

0 comments on commit bf6ddf2

Please sign in to comment.