Perhaps take a look here to see how it is done for the Plasma option services.xserver.desktopManager.plasma5.supportDDC
.
I have this in my config:
{ lib, config, pkgs, ... }: {
boot.extraModulePackages = [config.boot.kernelPackages.ddcci-driver];
boot.kernelModules = ["i2c-dev" "ddcci_backlight"];
services.udev.extraRules = ''
SUBSYSTEM=="i2c-dev", ACTION=="add",\
ATTR{name}=="NVIDIA i2c adapter*",\
TAG+="ddcci",\
TAG+="systemd",\
ENV{SYSTEMD_WANTS}+="ddcci@$kernel.service"
'';
systemd.services."ddcci@" = {
scriptArgs = "%i";
script = ''
echo Trying to attach ddcci to $1
i=0
id=$(echo $1 | cut -d "-" -f 2)
if ${pkgs.ddcutil}/bin/ddcutil getvcp 10 -b $id; then
echo ddcci 0x37 > /sys/bus/i2c/devices/$1/new_device
fi
'';
serviceConfig.Type = "oneshot";
};
}
This makes the display brightness work the same as if it were a laptop backlight, so the brightness controls on my keyboard work. No extension required. The important part is the ddcci_backlight
kernel module, which is supposed to be all you need, but due to a bug in Nvidia’s drivers, I had to add the udev rule and systemd service.
Oh wow thank you. This looks promising. I’ll check this out! It’s the kernel extension and permissions that seem to be missing. This looks right.
# DDC support
boot.kernelModules = lib.optional cfg.supportDDC "i2c_dev";
services.udev.extraRules = lib.optionalString cfg.supportDDC ''
KERNEL=="i2c-[0-9]*", TAG+="uaccess"
'';
This is really interesting! I wonder how it works on a docked laptop that I sometimes need ddc when docked, but sometimes internal when used as a laptop
Ok, thanks to some digging AND feedback here, i’ve got it working on rebuild and boot. Basically just add these lines to the config. (one loads the kernel module, the other sets the udev rules)
boot.kernelModules = ["i2c-dev"];
services.udev.extraRules = ''
KERNEL=="i2c-[0-9]*", GROUP="i2c", MODE="0660"
'';
Then just make sure your user is added to the “i2c” group, like…
users.users.mkelly = {
isNormalUser = true;
description = "Mike Kelly";
extraGroups = [ "networkmanager" "wheel" "docker" "i2c"];
};
When you boot up, you shoudl be able to run ddcutil commands without issue!
I hit a similar issue a while back trying to get
running under NixOS; I knew the answer would be something with udev rules but (at the time) didn’t know how to look further, and then totally forgot about it.
So, I should revisit that. Thanks!
Current most simplest NixOS way is to use hardware.i2c.enable
. Posting here since this was showing up first in my Google results.
hardware.i2c.enable = true;
FWIW I have an AMD GPU now and this is all I need to have brightness controls via normal the backlight interface:
boot.extraModulePackages = [config.boot.kernelPackages.ddcci-driver];
boot.kernelModules = ["i2c-dev" "ddcci_backlight"];
I still have had to manually do echo ddcci 0x37 > /sys/bus/i2c/devices/$dev/new_device
on rare occasion, but I no longer need to have it automatically run. I guess AMD’s driver just handles this a little bit better.
In theory the latter line is unnecessary if you’re logged in locally, according to the doc linked.
Ah you are right, didn’t understand the “any user with a seat”. Thanks for pointing it out
Note that this feature it’s actually broken, see `hardware.i2c.enable` incorrectly uses systemd's "uaccess" udev tag · Issue #210856 · NixOS/nixpkgs · GitHub.
The fix is pretty trivial but no one has done it, yet.
That’s a good find, thanks for sharing. Though I just tested it with nixos-unstable
and rebooted and it works as expected for me with just hardware.i2c.enable = true;
$ whoami
username
$ groups
users wheel video networkmanager scanner docker input qemu-libvirtd
$ ls -l /dev/i2c-7
crw-rw----+ 1 root i2c 89, 7 Mar 10 11:56 /dev/i2c-7
$ getfacl /dev/i2c-7
getfacl: Removing leading '/' from absolute path names
# file: dev/i2c-7
# owner: root
# group: i2c
user::rw-
user:username:rw-
group::rw-
mask::rw-
other::---
$ ddcutil detect
Display 1
I2C bus: /dev/i2c-7
DRM connector: card0-DP-1
EDID synopsis:
Mfg id: DEL - Dell Inc.
Model: DELL xyz
Product code: xyz (xyz)
Serial number: xyz
Binary serial number: xyz (xyz)
Manufacture year: 2020, Week: 40
VCP version: 2.1
In addition to this with an AMD GPU I also needed
services.udev.extraRules = ''
KERNEL=="i2c-[0-9]*", GROUP="i2c", MODE="0660"
'';
IIUC modifying services.udev.extraRules
shouldn’t be necessary:
-
ddcutil
includes alib/udev/rules.d/60-ddcutil-i2c.rules
file containingSUBSYSTEM=="i2c-dev", KERNEL=="i2c-[0-9]*", ATTRS{class}=="0x030000", TAG+="uaccess"
- This file seems to be linked as
/nix/var/nix/profiles/system/etc/profiles/per-user/USERNAME/lib/udev/rules.d/60-ddcutil-i2c.rules
(I installed inusers.users.<name>.packages
), which I believe means it’s included in the udev rules automatically.
On the other hand, trying a command from this post, sudo udevadm test $(sudo udevadm info -q path -n i2c-7)
, listed it reading a lot of files, but not the ddcutil one…
It actually doesn’t. You have to add the package to services.udev.package
for udev to use its rules
OK, that seems like a bug. Not exactly easy to discover.
I don’t think I’d call it a bug… Like, we don’t include systemd units from a package unless it’s in systemd.packages
. These are just things that should not be implicit.
If installing pkgs.ddcutil
is not enough for that package to do its most common job, then I’d say it’s a bug.
This is why we very often have programs.foo.enable
. To get it to do its thing correctly. environment.systemPackages
literally just means “link things in /run/current-system/sw
” and nothing else.