···
+
isConvertibleWithToString
+
inherit (lib.path.subpath) join;
+
inherit (utils) escapeSystemdPath;
+
cfg = config.boot.kernel.sysfs;
+
sysfsAttrs = with lib.types; nullOr (either sysfsValue (attrsOf sysfsAttrs));
+
sysfsValue = lib.mkOptionType {
+
description = "sysfs attribute value";
+
descriptionClass = "noun";
+
check = v: isConvertibleWithToString v;
+
if length defs == 1 then
+
# merge definitions if they produce the same value string
+
throwIf (mkValueString first.value != mkValueString def.value)
+
"The option \"${showOption loc}\" has conflicting definition values:${
+
) (head defs) (tail defs)).value;
+
mapAttrsToListRecursive =
+
if isAttrs v && !isDerivation v then mapAttrsToList (n: v: recurse (p ++ [ n ]) v) v else fn p v;
+
flatten (recurse [ ] set);
+
mkPath = p: "/sys" + removePrefix "." (join p);
+
hasGlob = p: any (n: match ''(.*[^\\])?[*?[].*'' n != null) p;
+
# true will be converted to "1" by toString, saving one branch
+
floatToString v # warn about loss of precision
+
concatStringsSep "," (map mkValueString v)
+
# escape whitespace and linebreaks, as well as the escape character itself,
+
# to ensure that field boundaries are always preserved
+
escapeTmpfiles = escapeC [
+
tmpfiles = pkgs.runCommand "nixos-sysfs-tmpfiles.d" { } (
+
mapAttrsToListRecursive (
+
printf 'w %s - - - - %s\n' \
+
${escapeShellArg (escapeTmpfiles path)} \
+
${escapeShellArg (escapeTmpfiles (mkValueString v))} \
+
>"$out"/${escapeShellArg (escapeSystemdPath path)}.conf
+
boot.kernel.sysfs = lib.mkOption {
+
type = lib.types.submodule {
+
freeformType = lib.types.attrsOf sysfsAttrs // {
+
description = "nested attribute set of null or sysfs attribute values";
+
sysfs attributes to be set as soon as they become available.
+
Attribute names represent path components in the sysfs filesystem and
+
cannot be `.` or `..` nor contain any slash character (`/`).
+
Names may contain shell‐style glob patterns (`*`, `?` and `[…]`)
+
matching a single path component, these should however be used with
+
caution, as they may produce unexpected results if attribute paths
+
Values will be converted to strings, with list elements concatenated
+
with commata and booleans converted to numeric values (`0` or `1`).
+
`null` values are ignored, allowing removal of values defined in other
+
modules, as are empty attribute sets.
+
List values defined in different modules will _not_ be concatenated.
+
This option may only be used for attributes which can be set
+
idempotently, as the configured values might be written more than once.
+
example = lib.literalExpression ''
+
# enable transparent hugepages with deferred defragmentaion
+
kernel.mm.transparent_hugepage = {
+
shmem_enabled = "within_size";
+
# configure powesave frequency governor for all CPUs
+
# the [0-9]* glob pattern ensures that other paths
+
# like cpufreq or cpuidle are not matched
+
scaling_governor = "powersave";
+
energy_performance_preference = 8;
+
# disable frequency boost
+
intel_pstate.no_turbo = true;
+
config = lib.mkIf (cfg != { }) {
+
description = "/%I attribute watcher";
+
pathConfig.PathExistsGlob = "/%I";
+
unitConfig.DefaultDependencies = false;
+
mapAttrsToListRecursive (
+
nameValuePair "nixos-sysfs@${escapeSystemdPath (mkPath p)}" {
+
overrideStrategy = "asDropin";
+
wantedBy = [ "sysinit.target" ];
+
before = [ "sysinit.target" ];
+
services."nixos-sysfs@" = {
+
description = "/%I attribute setter";
+
DefaultDependencies = false;
+
AssertPathIsMountPoint = "/sys";
+
AssertPathExistsGlob = "/%I";
+
RemainAfterExit = true;
+
# while we could be tempted to use simple shell script to set the
+
# sysfs attributes specified by the path or glob pattern, it is
+
# almost impossible to properly escape a glob pattern so that it
+
# can be used safely in a shell script
+
ExecStart = "${lib.getExe' config.systemd.package "systemd-tmpfiles"} --prefix=/sys --create ${tmpfiles}/%i.conf";
+
# hardening may be overkill for such a simple and short‐lived
+
# service, the following settings would however be suitable to deny
+
# access to anything but /sys
+
#ProtectProc = "noaccess";
+
#ProtectSystem = "strict";
+
#PrivateDevices = true;
+
#SystemCallErrorNumber = "EPERM";
+
warnings = mapAttrsToListRecursive (
+
"Attribute path \"${concatStringsSep "." p}\" contains glob patterns. Please ensure that it does not overlap with other paths."
+
assertions = mapAttrsToListRecursive (p: v: {
+
assertion = all (n: match ''(\.\.?|.*/.*)'' n == null) p;
+
message = "Attribute path \"${concatStringsSep "." p}\" has invalid components.";
+
meta.maintainers = with lib.maintainers; [ mvs ];