1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 concatLines
11 concatStringsSep
12 mapAttrsToList
13 optional
14 optionals
15
16 mkIf
17 mkOption
18 types
19 ;
20
21 cfg = config.hardware.block;
22 escape = lib.strings.escape [ ''"'' ];
23
24 udevValue = types.addCheck types.nonEmptyStr (x: builtins.match "[^\n\r]*" x != null) // {
25 name = "udevValue";
26 description = "udev rule value";
27 descriptionClass = "noun";
28 };
29
30 udevRule =
31 {
32 rotational ? null,
33 include ? null,
34 exclude ? null,
35 scheduler,
36 }:
37 concatStringsSep ", " (
38 [
39 ''SUBSYSTEM=="block"''
40 ''ACTION=="add|change"''
41 ''TEST=="queue/scheduler"''
42 ]
43 ++ optionals (rotational != null) [
44 ''ATTR{queue/rotational}=="${if rotational then "1" else "0"}"''
45 ]
46 ++ optionals (include != null) [
47 ''KERNEL=="${escape include}"''
48 ]
49 ++ optionals (exclude != null) [
50 ''KERNEL!="${escape exclude}"''
51 ]
52 ++ [
53 ''ATTR{queue/scheduler}="${escape scheduler}"''
54 ]
55 );
56in
57{
58 options.hardware.block = {
59 defaultScheduler = mkOption {
60 type = types.nullOr udevValue;
61 default = null;
62 description = ''
63 Default block I/O scheduler.
64
65 Unless `null`, the value is assigned through a udev rule matching all
66 block devices.
67 '';
68 example = "kyber";
69 };
70
71 defaultSchedulerRotational = mkOption {
72 type = types.nullOr udevValue;
73 default = null;
74 description = ''
75 Default block I/O scheduler for rotational drives (e.g. hard disks).
76
77 Unless `null`, the value is assigned through a udev rule matching all
78 rotational block devices.
79
80 This option takes precedence over
81 {option}`config.hardware.block.defaultScheduler`.
82 '';
83 example = "bfq";
84 };
85
86 defaultSchedulerExclude = mkOption {
87 type = types.nullOr udevValue;
88 default = "loop[0-9]*";
89 description = ''
90 Device name pattern to exclude from default scheduler assignment
91 through {option}`config.hardware.block.defaultScheduler` and
92 {option}`config.hardware.block.defaultSchedulerRotational`.
93
94 By default this excludes loop devices which generally do not benefit
95 from extra I/O scheduling in addition to the scheduling already
96 performed for their backing devices.
97
98 This setting does not affect {option}`config.hardware.block.scheduler`.
99 '';
100 };
101
102 scheduler = mkOption {
103 type = types.attrsOf udevValue;
104 default = { };
105 description = ''
106 Assign block I/O scheduler by device name pattern.
107
108 Names are matched using the {manpage}`udev(7)` pattern syntax:
109
110 `*`
111 : Matches zero or more characters.
112
113 `?`
114 : Matches any single character.
115
116 `[]`
117 : Matches any single character specified in the brackets. Ranges are
118 supported via the `-` character.
119
120 `|`
121 : Separates alternative patterns.
122
123
124 Please note that overlapping patterns may produce unexpected results.
125 More complex configurations requiring these should instead be specified
126 directly through custom udev rules, for example via
127 [{option}`config.services.udev.extraRules`](#opt-services.udev.extraRules),
128 to ensure correct ordering.
129
130 Available schedulers depend on the kernel configuration but modern
131 Linux systems typically support:
132
133 `none`
134 : No‐operation scheduler with no re‐ordering of requests. Suitable
135 for devices with fast random I/O such as NVMe SSDs.
136
137 [`mq-deadline`](https://www.kernel.org/doc/html/latest/block/deadline-iosched.html)
138 : Simple latency‐oriented general‐purpose scheduler.
139
140 [`kyber`](https://www.kernel.org/doc/html/latest/block/kyber-iosched.html)
141 : Simple latency‐oriented scheduler for fast multi‐queue devices
142 like NVMe SSDs.
143
144 [`bfq`](https://www.kernel.org/doc/html/latest/block/bfq-iosched.html)
145 : Complex fairness‐oriented scheduler. Higher processing overhead,
146 but good interactive response, especially with slower devices.
147
148
149 Schedulers assigned through this option take precedence over
150 {option}`config.hardware.block.defaultScheduler` and
151 {option}`config.hardware.block.defaultSchedulerRotational` but may be
152 overridden by other udev rules.
153 '';
154 example = {
155 "mmcblk[0-9]*" = "bfq";
156 "nvme[0-9]*" = "kyber";
157 };
158 };
159 };
160
161 config =
162 mkIf
163 (cfg.defaultScheduler != null || cfg.defaultSchedulerRotational != null || cfg.scheduler != { })
164 {
165 services.udev.packages = [
166 (pkgs.writeTextDir "etc/udev/rules.d/98-block-io-scheduler.rules" (
167 concatLines (
168 optional (cfg.defaultScheduler != null) (udevRule {
169 exclude = cfg.defaultSchedulerExclude;
170 scheduler = cfg.defaultScheduler;
171 })
172 ++ optional (cfg.defaultSchedulerRotational != null) (udevRule {
173 rotational = true;
174 exclude = cfg.defaultSchedulerExclude;
175 scheduler = cfg.defaultSchedulerRotational;
176 })
177 ++ mapAttrsToList (
178 include: scheduler:
179 udevRule {
180 inherit include scheduler;
181 }
182 ) cfg.scheduler
183 )
184 ))
185 ];
186 };
187
188 meta.maintainers = with lib.maintainers; [ mvs ];
189}