brightness: map a dp connector's ddc bus as well as its aux bus

ref #3724
port 1.6
This commit is contained in:
bbedward
2026-10-08 09:34:01 -04:00
parent 2bf7f7c556
commit 8b47d3cf70
3 changed files with 52 additions and 30 deletions
+2 -7
View File
@@ -24,7 +24,6 @@ const (
EDID_ADDR = 0x50
)
// ddcutil's DEFAULT_FLOCK_POLL_MILLISEC and DEFAULT_FLOCK_MAX_WAIT_MILLISEC (src/base/parms.h).
const (
ddcBusLockPoll = 100 * time.Millisecond
ddcBusLockWait = 3 * time.Second
@@ -178,8 +177,7 @@ func (b *DDCBackend) probeDDCDevice(bus int, readEDID bool) (*ddcDevice, error)
return dev, nil
}
// Based on ddcutil's i2c_open_bus() cross-instance lock (src/base/flock.c): ddcutil, other dms
// processes and this one take turns on a bus.
// flock on the device node is how ddc tools and other dms processes take turns on a bus.
func openDDCBus(bus int) (int, error) {
// Without O_CLOEXEC a process spawned mid-probe inherits the fd and the flock with it.
fd, err := syscall.Open(fmt.Sprintf("/dev/i2c-%d", bus), syscall.O_RDWR|syscall.O_CLOEXEC, 0)
@@ -207,7 +205,6 @@ func setI2CAddr(fd, addr int) error {
return nil
}
// Based on ddcutil's i2c_detect_x37() (src/i2c/i2c_x37.c).
func detectX37(fd int) bool {
if n, err := syscall.Read(fd, make([]byte, 1)); err == nil && n == 1 {
return true
@@ -216,7 +213,6 @@ func detectX37(fd int) bool {
return err == nil
}
// Based on ddcutil's i2c_get_raw_edid_by_fd() with the fileio reader (src/i2c/i2c_edid.c).
func readBusEDID(fd int) ([]byte, error) {
if err := setI2CAddr(fd, EDID_ADDR); err != nil {
return nil, err
@@ -507,8 +503,7 @@ func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) er
return nil
}
// Based on ddcutil's i2c_check_open_bus_alive() (src/i2c/i2c_bus_core.c), sysfs side only. Known
// devices are keyed by bus number, which the kernel hands to the next adapter once one goes away.
// Known devices are keyed by bus number, which the kernel hands to the next adapter once one goes away.
func (b *DDCBackend) checkBusAlive(dev *ddcDevice) error {
if isIgnorableI2CBus(dev.bus) || getI2CDeviceSysfsName(dev.bus) != dev.adapter {
return errBusReused
+25 -23
View File
@@ -13,8 +13,6 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
)
// isIgnorableI2CBus checks if an I2C bus should be skipped during DDC probing.
// Based on ddcutil's sysfs_is_ignorable_i2c_device() (src/sysfs/sysfs_simple.c)
func isIgnorableI2CBus(busno int) bool {
name := getI2CDeviceSysfsName(busno)
if name == "DPMST" {
@@ -42,7 +40,6 @@ func isIgnorableI2CBus(busno int) bool {
return ignorable
}
// Based on ddcutil's ignorable_i2c_device_sysfs_name() (src/sysfs/sysfs_simple.c)
func isIgnorableI2CDeviceName(name, driver string) bool {
ignorablePrefixes := []string{
"SMBus",
@@ -69,7 +66,6 @@ func isIgnorableI2CDeviceName(name, driver string) bool {
return false
}
// Based on ddcutil's get_i2c_device_sysfs_name() (sysfs_base.c:1175)
func getI2CDeviceSysfsName(busno int) string {
path := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/name", busno)
data, err := os.ReadFile(path)
@@ -79,7 +75,6 @@ func getI2CDeviceSysfsName(busno int) string {
return strings.TrimSpace(string(data))
}
// Based on ddcutil's get_i2c_device_sysfs_class() (src/sysfs/sysfs_simple.c)
func getI2CDeviceSysfsClass(busno int) uint32 {
paths := []string{
fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d", busno),
@@ -103,7 +98,6 @@ func getI2CDeviceSysfsClass(busno int) uint32 {
return 0
}
// Based on ddcutil's get_driver_for_busno() (src/sysfs/sysfs_simple.c)
func getI2CSysfsDriver(busno int) string {
adapter := findI2CAdapter(fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d", busno))
if adapter == "" {
@@ -116,8 +110,7 @@ func getI2CSysfsDriver(busno int) string {
return filepath.Base(module)
}
// Based on ddcutil's sysfs_find_adapter() (src/sysfs/sysfs_simple.c): the nearest
// ancestor under /sys/devices carrying a class attribute.
// The nearest ancestor under /sys/devices carrying a class attribute.
func findI2CAdapter(path string) string {
resolved, err := filepath.EvalSymlinks(path)
if err != nil {
@@ -132,8 +125,7 @@ func findI2CAdapter(path string) string {
return ""
}
// ddcutil's known_reliable_drivers (src/sysfs/sysfs_base.c): drivers that keep the DRM connector
// edid, status and dpms attributes current.
// Drivers that keep the DRM connector edid, status and dpms attributes current; nvidia does not.
var sysfsReliableDrivers = []string{"i915", "xe", "amdgpu", "radeon", "nouveau"}
var (
@@ -149,7 +141,6 @@ const (
ddcBusNeedsEDIDRead
)
// Based on ddcutil's i2c_edid_exists() and its laptop panel exclusion (src/i2c/i2c_bus_core.c).
func ddcBusVerdictFor(busno int, connectors map[int]string) ddcBusVerdict {
name := getI2CDeviceSysfsName(busno)
displayLink := name == "DisplayLink I2C Adapter"
@@ -177,7 +168,6 @@ func ddcBusVerdictFor(busno int, connectors map[int]string) ddcBusVerdict {
return ddcBusNeedsEDIDRead
}
// Based on ddcutil's dpms_check_drm_asleep_by_businfo() (src/sysfs/sysfs_dpms.c).
func ddcDisplayAsleep(busno int, connectors map[int]string) bool {
connector, mapped := connectors[busno]
if !mapped || !slices.Contains(sysfsReliableDrivers, getI2CSysfsDriver(busno)) {
@@ -186,11 +176,13 @@ func ddcDisplayAsleep(busno int, connectors map[int]string) bool {
return strings.TrimSpace(drmConnectorAttr(connector, "dpms")) != "On"
}
// Based on ddcutil's get_connector_bus_numbers() (src/sysfs/sysfs_simple.c): a DP connector's own
// i2c-N directory, any other connector's ddc/i2c-dev/i2c-N.
func drmConnectorsByBus() map[int]string {
return drmConnectorsByBusIn("/sys/class/drm")
}
func drmConnectorsByBusIn(root string) map[int]string {
connectors := map[int]string{}
entries, err := os.ReadDir("/sys/class/drm")
entries, err := os.ReadDir(root)
if err != nil {
return connectors
}
@@ -199,17 +191,29 @@ func drmConnectorsByBus() map[int]string {
if !drmConnectorPattern.MatchString(name) {
continue
}
dir := filepath.Join("/sys/class/drm", name)
if !strings.Contains(name, "-DP-") {
dir = filepath.Join(dir, "ddc", "i2c-dev")
}
if busno, ok := i2cSubdirBus(dir); ok {
for _, busno := range drmConnectorBuses(name, filepath.Join(root, name)) {
connectors[busno] = name
}
}
return connectors
}
// amdgpu gives a DP connector two buses, the aux channel under i2c-N and the hardware i2c bus
// behind ddc, and a DP++ to HDMI adapter answers only on the ddc one (#3724).
func drmConnectorBuses(name, dir string) []int {
buses := []int{}
if busno, ok := i2cSubdirBus(filepath.Join(dir, "ddc", "i2c-dev")); ok {
buses = append(buses, busno)
}
if !strings.Contains(name, "-DP-") {
return buses
}
if busno, ok := i2cSubdirBus(dir); ok {
buses = append(buses, busno)
}
return buses
}
func i2cSubdirBus(dir string) (int, bool) {
entries, err := os.ReadDir(dir)
if err != nil {
@@ -237,7 +241,6 @@ func drmConnectorAttr(connector, attr string) string {
var edidHeader = []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}
// Based on ddcutil's is_valid_raw_edid() (src/util/edid.c).
func isValidEDID(edid []byte) bool {
if len(edid) < 128 || !bytes.Equal(edid[:8], edidHeader) {
return false
@@ -249,12 +252,11 @@ func isValidEDID(edid []byte) bool {
return sum == 0
}
// Based on ddcutil's is_laptop_parsed_edid() (src/util/edid.c).
// A panel with neither a monitor name nor a serial descriptor is a laptop panel.
func isLaptopEDID(edid []byte) bool {
return edidDescriptorText(edid, 0xfc) == "" && edidDescriptorText(edid, 0xff) == ""
}
// Based on ddcutil's get_edid_descriptor_strings() (src/util/edid.c).
func edidDescriptorText(edid []byte, tag byte) string {
text := ""
for i := range 4 {
@@ -1,6 +1,9 @@
package brightness
import (
"maps"
"os"
"path/filepath"
"testing"
)
@@ -71,3 +74,25 @@ func TestIsIgnorableI2CDeviceName(t *testing.T) {
})
}
}
func TestDrmConnectorsByBusMapsBothDPBuses(t *testing.T) {
root := t.TempDir()
for _, dir := range []string{
"card1-DP-2/i2c-13",
"card1-DP-2/ddc/i2c-dev/i2c-4",
"card1-HDMI-A-1/ddc/i2c-dev/i2c-2",
"card1-DP-9",
"renderD128",
} {
if err := os.MkdirAll(filepath.Join(root, dir), 0o755); err != nil {
t.Fatal(err)
}
}
got := drmConnectorsByBusIn(root)
want := map[int]string{13: "card1-DP-2", 4: "card1-DP-2", 2: "card1-HDMI-A-1"}
if !maps.Equal(got, want) {
t.Fatalf("drmConnectorsByBusIn() = %v, want %v", got, want)
}
}