// Copyright 2018-2021 System76 
//
// SPDX-License-Identifier: GPL-3.0-only

pub mod mux;
pub mod sideband;

use sideband::{Sideband, SidebandError, PCR_BASE_ADDRESS};
use std::{
    fs,
    io::{self, Read, Seek},
};

#[derive(Debug, thiserror::Error)]
pub enum HotPlugDetectError {
    #[error("failed to read DMI product version: {}", _0)]
    ProductVersion(io::Error),
    #[error("error constructing sideband: {}", _0)]
    Sideband(SidebandError),
    #[error("{} variant "{}" does not support hotplug detection", model, variant)]
    VariantUnsupported { model: &"static str, variant: String },
    #[error("model "{}" does not support hotplug detection", _0)]
    ModelUnsupported(String),
    #[error("failed to read {}"s subsystem device: {}", model, why)]
    SubsystemDevice { model: &"static str, why: io::Error },
    #[error("failed to open /dev/mem: {}", _0)]
    DevMemAccess(io::Error),
}

impl From for HotPlugDetectError {
    fn from(err: SidebandError) -> Self { HotPlugDetectError::Sideband(err) }
}

pub trait Detect {
    unsafe fn detect(&mut self) -> [bool; 4];
}

const AMD_FCH_GPIO_CONTROL_BASE: u32 = 0xFED8_1500;

struct Amd {
    mem:   fs::File,
    gpios: Vec,
}

impl Amd {
    unsafe fn new(gpios: Vec) -> Result {
        let mem = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open("/dev/mem")
            .map_err(HotPlugDetectError::DevMemAccess)?;

        Ok(Self { mem, gpios })
    }
}

impl Detect for Amd {
    unsafe fn detect(&mut self) -> [bool; 4] {
        let mut hpd = [false; 4];

        for (i, offset) in self.gpios.iter().enumerate() {
            let control_offset = AMD_FCH_GPIO_CONTROL_BASE + offset * 4;
            if self.mem.seek(io::SeekFrom::Start(u64::from(control_offset))).is_err() {
                return hpd;
            }

            let mut control = [0; 4];
            if self.mem.read(&mut control).is_err() {
                return hpd;
            }

            let value = u32::from_ne_bytes(control);
            hpd[i] = value & (1 << 16) == (1 << 16);
        }

        hpd
    }
}

pub struct Intel {
    sideband: Sideband,
    port:     u8,
    pins:     [u8; 4],
}

impl Detect for Intel {
    unsafe fn detect(&mut self) -> [bool; 4] {
        let mut hpd = [false; 4];
        for (i, &pin) in self.pins.iter().enumerate() {
            if pin > 0 {
                let data = self.sideband.gpio(self.port, pin);
                hpd[i] = data & 2 == 2;
            }
        }
        hpd
    }
}

enum Integrated {
    Amd(Amd),
    Intel(Intel),
}

pub struct HotPlugDetect {
    integrated: Integrated,
}

impl HotPlugDetect {
    /// # Errors
    ///
    /// - If `/sys/class/dmi/id/product_version` cannot be read
    /// - If `Sideband::new` fails
    #[allow(clippy::too_many_lines)]
    pub unsafe fn new(nvidia_device: Option) -> Result {
        let model = fs::read_to_string("/sys/class/dmi/id/product_version")
            .map_err(HotPlugDetectError::ProductVersion)?;

        match model.trim() {
            "addw1" | "addw2" => Ok(Self {
                integrated: Integrated::Intel(Intel {
                    sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                    port:     0x6A,
                    pins:     [
                        0x28, // USB-C on rear
                        0x2a, // HDMI
                        0x2c, // Mini DisplayPort
                        0x2e, // USB-C on right
                    ],
                }),
            }),
            "gaze14" => {
                let variant =
                    fs::read_to_string("/sys/bus/pci/devices/0000:00:00.0/subsystem_device")
                        .map_err(|why| HotPlugDetectError::SubsystemDevice {
                            model: "gaze14",
                            why,
                        })?;

                match variant.trim() {
                    // NVIDIA GTX 1660 Ti
                    "0x8550" | "0x8551" => Ok(Self {
                        integrated: Integrated::Intel(Intel {
                            sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                            port:     0x6A,
                            pins:     [
                                0x2a, // HDMI
                                0x00, // Mini DisplayPort (0x2c) is connected to Intel graphics
                                0x2e, // USB-C
                                0x00, // Not Connected
                            ],
                        }),
                    }),
                    // NVIDIA GTX 1650
                    "0x8560" | "0x8561" => Ok(Self {
                        integrated: Integrated::Intel(Intel {
                            sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                            port:     0x6A,
                            pins:     [
                                0x00, // HDMI (0x2a) is connected to Intel graphics
                                0x2e, // Mini DisplayPort
                                0x00, // Not Connected
                                0x00, // Not Connected
                            ],
                        }),
                    }),
                    other => Err(HotPlugDetectError::VariantUnsupported {
                        model:   "gaze14",
                        variant: other.into(),
                    }),
                }
            }
            "gaze15" => {
                let variant = nvidia_device.unwrap_or_else(|| "unknown".to_string());

                match variant.trim() {
                    // NVIDIA GTX 1660 Ti
                    "0x2191" => Ok(Self {
                        integrated: Integrated::Intel(Intel {
                            sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                            port:     0x6A,
                            pins:     [
                                0x2a, // HDMI
                                0x00, // Mini DisplayPort (0x2c) is connected to Intel graphics
                                0x2e, // USB-C
                                0x00, // Not Connected
                            ],
                        }),
                    }),
                    // NVIDIA GTX 1650, 1650 Ti
                    "0x1f99" | "0x1f95" => Ok(Self {
                        integrated: Integrated::Intel(Intel {
                            sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                            port:     0x6A,
                            pins:     [
                                0x00, // HDMI (0x2a) is connected to Intel graphics
                                0x2e, // Mini DisplayPort
                                0x00, // Not Connected
                                0x00, // Not Connected
                            ],
                        }),
                    }),
                    other => Err(HotPlugDetectError::VariantUnsupported {
                        model:   "gaze15",
                        variant: other.into(),
                    }),
                }
            }
            "gaze16-3050" => Ok(Self {
                integrated: Integrated::Intel(Intel {
                    sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                    port:     0x6A,
                    pins:     [
                        0x00, // HDMI (0x52) is connected to Intel graphics
                        0x58, // Mini DisplayPort
                        0x00, // Not Connected
                        0x00, // Not Connected
                    ],
                }),
            }),
            "gaze16-3060" | "gaze16-3060-b" => Ok(Self {
                integrated: Integrated::Intel(Intel {
                    sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                    port:     0x69,
                    pins:     [
                        0x02, // Mini DisplayPort
                        0x04, // USB-C
                        0x00, // Not Connected
                        0x00, // Not Connected
                    ],
                }),
            }),
            "gaze17-3060-b" => Ok(Self {
                integrated: Integrated::Intel(Intel {
                    sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                    port:     0x6E,
                    pins:     [
                        0x72, // Mini DisplayPort
                        0x78, // HDMI
                        0x00, // Not Connected
                        0x00, // Not Connected
                    ],
                }),
            }),
            "kudu6" => {
                let gpios = vec![
                    0x02, // USB-C
                    0x03, // HDMI
                    0x15, // Mini DisplayPort
                ];
                Ok(Self { integrated: Integrated::Amd(Amd::new(gpios)?) })
            }

            "oryp4" | "oryp4-b" | "oryp5" => Ok(Self {
                integrated: Integrated::Intel(Intel {
                    sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                    port:     0x6A,
                    pins:     [
                        0x28, // USB-C
                        0x2a, // HDMI
                        0x2c, // Mini DisplayPort
                        0x00, // Not Connected
                    ],
                }),
            }),
            "oryp6" | "oryp7" => Ok(Self {
                integrated: Integrated::Intel(Intel {
                    sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                    port:     0x6A,
                    pins:     [
                        0x2a, // HDMI
                        0x2c, // Mini DisplayPort
                        0x2e, // USB-C
                        0x00, // Not Connected
                    ],
                }),
            }),
            "oryp8" => Ok(Self {
                integrated: Integrated::Intel(Intel {
                    sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                    port:     0x69,
                    pins:     [
                        0x02, // Mini DisplayPort
                        0x04, // HDMI
                        0x06, // USB-C
                        0x00, // Not Connected
                    ],
                }),
            }),
            "oryp9" | "oryp10" => Ok(Self {
                integrated: Integrated::Intel(Intel {
                    sideband: Sideband::new(PCR_BASE_ADDRESS)?,
                    port:     0x6E,
                    pins:     [
                        0x72, // Mini DisplayPort
                        0x78, // HDMI
                        0x7C, // USB-C
                        0x00, // Not Connected
                    ],
                }),
            }),
            other => Err(HotPlugDetectError::ModelUnsupported(other.into())),
        }
    }
}

impl Detect for HotPlugDetect {
    unsafe fn detect(&mut self) -> [bool; 4] {
        match &mut self.integrated {
            Integrated::Amd(amd) => amd.detect(),
            Integrated::Intel(intel) => intel.detect(),
        }
    }
}

Related articles

system76-power snd

// Copyright 2018-2021 System76 <[email protected]> // // SPDX-License-Identifier: GPL-3.0-only use crate::kernel_parameters::{DeviceList, KernelParameter, PowerSave, PowerSaveController}; use std::path::Path; pub struct SoundDevice { device:

system76-power pci

// Copyright 2018-2021 System76 <[email protected]> // // SPDX-License-Identifier: GPL-3.0-only use std::{fs::write, io, path::PathBuf}; pub struct PciBus { path: PathBuf, } impl PciBus { pub fn new() -> io::Result<PciBus> { let path =

system76-power CI

on: push: branches: [master] pull_request: name: Continuous integration jobs: fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: install toolchain run: rustup toolchain instal

system76-power gpio

use log::LevelFilter; use std::process; use system76_power::{ hotplug::sideband::{Sideband, SidebandError, PCR_BASE_ADDRESS}, logging, }; struct GpioGroup<"a> { name: &"a str, count: u8, } struct GpioCommunity<"a> { id: u8,

system76-power TESTING

# Testing This document provides a guideline for testing and verifying the expected behaviors of the project. When a patch is ready for testing, the checklists may be copied and marked as they are proven to be working. ## Checklists Tasks for a tester

system76-power args

// Copyright 2018-2022 System76 <[email protected]> // // SPDX-License-Identifier: GPL-3.0-only use clap::{builder::PossibleValuesParser, Parser}; #[derive(Parser)] #[clap( about = "Query or set the graphics mode", long_about = "Query or set th

system76-power disks

// Copyright 2018-2021 System76 <[email protected]> // // SPDX-License-Identifier: GPL-3.0-only use crate::errors::DiskPowerError; use std::{ fs::{read_to_string, write}, path::{Path, PathBuf}, process::{Command, Stdio}, }; const AUTOSUSPEN

system76-power hid_backlight

// Copyright 2018-2021 System76 <[email protected]> // // SPDX-License-Identifier: GPL-3.0-only use hidapi::{HidApi, HidDevice, HidResult}; use inotify::{Inotify, WatchMask}; use std::{fs, path::Path}; fn keyboard(device: &HidDevice, brightness: u8, co

system76-power autoswitch

use log::LevelFilter; use std::{process, thread, time}; use system76_power::{ hotplug::sideband::{Sideband, SidebandError, PCR_BASE_ADDRESS}, logging, }; fn inner() -> Result<(), SidebandError> { let sideband = unsafe { Sideband::new(PCR_BAS

system76-power graphics

use log::LevelFilter; use std::{io, process}; use system76_power::{graphics::Graphics, logging}; fn inner() -> io::Result<()> { Graphics::new()?; Ok(()) } fn main() { if let Err(why) = logging::setup(LevelFilter::Debug) { eprintln!