Skip to content

Commit 820dd7d

Browse files
committed
feat: add bcache support (cache + backing device types)
Add three new disko types for Linux bcache block-layer caching: - bcache_cache: partition content type for cache devices (SSD/eMMC) - bcache_backing: partition content type for backing devices (HDD/SD) - bcache: top-level aggregator that creates the bcache set, attaches cache to backing, and manages nested content This enables declarative bcache configurations where a fast device (SSD, eMMC) caches a slower device (HDD, SD card), significantly improving I/O performance for the backing device.
1 parent 5ad85c8 commit 820dd7d

File tree

6 files changed

+437
-0
lines changed

6 files changed

+437
-0
lines changed

example/bcache.nix

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
disko.devices = {
3+
disk = {
4+
cache-disk = {
5+
type = "disk";
6+
device = "/dev/my-cache-disk";
7+
content = {
8+
type = "gpt";
9+
partitions = {
10+
boot = {
11+
size = "1M";
12+
type = "EF02"; # for grub MBR
13+
};
14+
cache = {
15+
size = "100%";
16+
content = {
17+
type = "bcache_cache";
18+
set = "main";
19+
};
20+
};
21+
};
22+
};
23+
};
24+
backing-disk = {
25+
type = "disk";
26+
device = "/dev/my-backing-disk";
27+
content = {
28+
type = "gpt";
29+
partitions = {
30+
backing = {
31+
size = "100%";
32+
content = {
33+
type = "bcache_backing";
34+
set = "main";
35+
};
36+
};
37+
};
38+
};
39+
};
40+
};
41+
bcache = {
42+
main = {
43+
type = "bcache";
44+
device = "/dev/bcache0";
45+
cacheMode = "writeback";
46+
content = {
47+
type = "filesystem";
48+
format = "ext4";
49+
mountpoint = "/";
50+
};
51+
};
52+
};
53+
};
54+
}

lib/default.nix

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ let
7979
_partitionTypes = {
8080
inherit (diskoLib.types)
8181
bcachefs
82+
bcache_cache
83+
bcache_backing
8284
btrfs
8385
filesystem
8486
zfs
@@ -105,6 +107,8 @@ let
105107
_deviceTypes = {
106108
inherit (diskoLib.types)
107109
bcachefs
110+
bcache_cache
111+
bcache_backing
108112
table
109113
gpt
110114
btrfs
@@ -684,6 +688,7 @@ let
684688
let
685689
devices = {
686690
inherit (cfg.config)
691+
bcache
687692
bcachefs_filesystems
688693
disk
689694
mdadm
@@ -695,6 +700,11 @@ let
695700
in
696701
{
697702
options = {
703+
bcache = lib.mkOption {
704+
type = lib.types.attrsOf diskoLib.types.bcache;
705+
default = { };
706+
description = "bcache device (cache + backing)";
707+
};
698708
bcachefs_filesystems = lib.mkOption {
699709
type = lib.types.attrsOf diskoLib.types.bcachefs_filesystem;
700710
default = { };
@@ -769,6 +779,7 @@ let
769779
# @todo Do we need to add bcachefs-tools or not?
770780
destroyDependencies = with pkgs; [
771781
util-linux
782+
bcache-tools
772783
e2fsprogs
773784
mdadm
774785
zfs

lib/types/bcache.nix

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
{
2+
config,
3+
options,
4+
lib,
5+
diskoLib,
6+
rootMountPoint,
7+
...
8+
}:
9+
{
10+
options = {
11+
name = lib.mkOption {
12+
type = lib.types.str;
13+
default = config._module.args.name;
14+
description = "Name of the bcache set.";
15+
example = "main";
16+
};
17+
type = lib.mkOption {
18+
type = lib.types.enum [ "bcache" ];
19+
internal = true;
20+
description = "Type";
21+
};
22+
device = lib.mkOption {
23+
type = lib.types.str;
24+
default = "/dev/bcache0";
25+
description = ''
26+
The bcache block device path.
27+
For single-backing setups this is always `/dev/bcache0`.
28+
For multi-backing setups, set this explicitly.
29+
'';
30+
};
31+
cacheMode = lib.mkOption {
32+
type = lib.types.enum [
33+
"writethrough"
34+
"writeback"
35+
"writearound"
36+
"none"
37+
];
38+
default = "writethrough";
39+
description = "Cache mode for the bcache device.";
40+
};
41+
extraCacheArgs = lib.mkOption {
42+
type = lib.types.listOf lib.types.str;
43+
default = [ ];
44+
description = "Extra arguments passed to `make-bcache -C` for the cache device.";
45+
};
46+
extraBackingArgs = lib.mkOption {
47+
type = lib.types.listOf lib.types.str;
48+
default = [ ];
49+
description = "Extra arguments passed to `make-bcache -B` for the backing device.";
50+
};
51+
content = diskoLib.deviceType {
52+
parent = config;
53+
device = config.device;
54+
};
55+
_meta = lib.mkOption {
56+
internal = true;
57+
readOnly = true;
58+
type = diskoLib.jsonType;
59+
default = lib.optionalAttrs (config.content != null) (
60+
config.content._meta [
61+
"bcache"
62+
config.name
63+
]
64+
);
65+
description = "Metadata";
66+
};
67+
_create = diskoLib.mkCreateOption {
68+
inherit config options;
69+
default =
70+
let
71+
devBasename = builtins.baseNameOf config.device;
72+
in
73+
''
74+
# Read cache and backing device paths from temp directory
75+
if ! test -s "$disko_devices_dir/bcache_cache_${lib.escapeShellArg config.name}"; then
76+
printf "\033[31mERROR:\033[0m No cache device found for bcache set \"${config.name}\"!\n" >&2
77+
exit 1
78+
fi
79+
if ! test -s "$disko_devices_dir/bcache_backing_${lib.escapeShellArg config.name}"; then
80+
printf "\033[31mERROR:\033[0m No backing device found for bcache set \"${config.name}\"!\n" >&2
81+
exit 1
82+
fi
83+
84+
cache_dev=$(head -n1 "$disko_devices_dir/bcache_cache_${lib.escapeShellArg config.name}")
85+
backing_dev=$(head -n1 "$disko_devices_dir/bcache_backing_${lib.escapeShellArg config.name}")
86+
87+
modprobe bcache
88+
89+
# Resolve symlink to kernel device name for sysfs matching
90+
backing_basename="$(basename "$(readlink -f "$backing_dev")")"
91+
92+
# Idempotency: if the bcache device already exists with our backing device, skip creation
93+
if [ -b "${config.device}" ] && \
94+
[ -e "/sys/block/${devBasename}/bcache/backing_dev_name" ] && \
95+
[ "$(cat "/sys/block/${devBasename}/bcache/backing_dev_name")" = "$backing_basename" ]; then
96+
: # bcache device already active with correct backing — nothing to do
97+
else
98+
# Teardown stale bcache device if it exists (destroy-format-mount scenario)
99+
if [ -e "/sys/block/${devBasename}/bcache/stop" ]; then
100+
echo 1 > "/sys/block/${devBasename}/bcache/stop" 2>/dev/null || true
101+
fi
102+
# Unregister any active cache sets
103+
find /sys/fs/bcache -maxdepth 1 -mindepth 1 -type d -exec sh -c '
104+
for cset; do
105+
if [ -e "$cset/unregister" ]; then
106+
echo 1 > "$cset/unregister" 2>/dev/null || true
107+
fi
108+
done
109+
' _ {} + 2>/dev/null || true
110+
udevadm settle --timeout=10
111+
# Wait for the bcache device to fully disappear
112+
for i in $(seq 1 30); do
113+
[ -b "${config.device}" ] || break
114+
sleep 1
115+
done
116+
117+
# Create bcache set — --force handles pre-existing superblocks
118+
make-bcache \
119+
-C "$cache_dev" \
120+
-B "$backing_dev" \
121+
--force \
122+
${lib.optionalString (config.cacheMode == "writeback") "--writeback"} \
123+
${lib.concatStringsSep " " (map lib.escapeShellArg config.extraCacheArgs)} \
124+
${lib.concatStringsSep " " (map lib.escapeShellArg config.extraBackingArgs)}
125+
126+
# Wait for the bcache block device to appear
127+
for i in $(seq 1 60); do
128+
[ -b "${config.device}" ] && break
129+
sleep 1
130+
done
131+
fi
132+
133+
if [ ! -b "${config.device}" ]; then
134+
printf "\033[31mERROR:\033[0m bcache device ${config.device} did not appear\n" >&2
135+
exit 1
136+
fi
137+
138+
# Set cache mode
139+
echo "${config.cacheMode}" > "/sys/block/${devBasename}/bcache/cache_mode"
140+
141+
${lib.optionalString (config.content != null) config.content._create}
142+
'';
143+
};
144+
_mount = diskoLib.mkMountOption {
145+
inherit config options;
146+
default = lib.optionalAttrs (config.content != null) config.content._mount;
147+
};
148+
_unmount = diskoLib.mkUnmountOption {
149+
inherit config options;
150+
default =
151+
let
152+
devBasename = builtins.baseNameOf config.device;
153+
content = lib.optionalAttrs (config.content != null) config.content._unmount;
154+
in
155+
{
156+
fs = content.fs or { };
157+
dev = ''
158+
${content.dev or ""}
159+
# Stop the bcache device to release cache and backing devices
160+
if [ -e "/sys/block/${devBasename}/bcache/detach" ]; then
161+
echo 1 > "/sys/block/${devBasename}/bcache/detach" 2>/dev/null || true
162+
fi
163+
if [ -e "/sys/block/${devBasename}/bcache/stop" ]; then
164+
echo 1 > "/sys/block/${devBasename}/bcache/stop" 2>/dev/null || true
165+
fi
166+
# Unregister cache sets
167+
find /sys/fs/bcache -maxdepth 1 -mindepth 1 -type d -exec sh -c '
168+
for cset; do
169+
if [ -e "$cset/unregister" ]; then
170+
echo 1 > "$cset/unregister" 2>/dev/null || true
171+
fi
172+
done
173+
' _ {} + 2>/dev/null || true
174+
udevadm settle --timeout=10
175+
'';
176+
};
177+
};
178+
_config = lib.mkOption {
179+
internal = true;
180+
readOnly = true;
181+
default = [
182+
{
183+
boot.bcache.enable = true;
184+
}
185+
]
186+
++ lib.optional (config.content != null) config.content._config;
187+
description = "NixOS configuration";
188+
};
189+
_pkgs = lib.mkOption {
190+
internal = true;
191+
readOnly = true;
192+
type = lib.types.functionTo (lib.types.listOf lib.types.package);
193+
default =
194+
pkgs:
195+
[
196+
pkgs.bcache-tools
197+
pkgs.kmod
198+
pkgs.util-linux
199+
]
200+
++ lib.optionals (config.content != null) (config.content._pkgs pkgs);
201+
description = "Packages";
202+
};
203+
};
204+
}

lib/types/bcache_backing.nix

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
config,
3+
options,
4+
lib,
5+
diskoLib,
6+
parent,
7+
device,
8+
...
9+
}:
10+
{
11+
options = {
12+
type = lib.mkOption {
13+
type = lib.types.enum [ "bcache_backing" ];
14+
internal = true;
15+
description = "Type";
16+
};
17+
device = lib.mkOption {
18+
type = lib.types.str;
19+
default = device;
20+
description = "Device to use as bcache backing device.";
21+
};
22+
set = lib.mkOption {
23+
type = lib.types.str;
24+
description = "Name of the bcache set this backing device belongs to.";
25+
example = "main";
26+
};
27+
_parent = lib.mkOption {
28+
internal = true;
29+
default = parent;
30+
};
31+
_meta = lib.mkOption {
32+
internal = true;
33+
readOnly = true;
34+
type = lib.types.functionTo diskoLib.jsonType;
35+
default = dev: {
36+
deviceDependencies.bcache.${config.set} = [ dev ];
37+
};
38+
description = "Metadata";
39+
};
40+
_create = diskoLib.mkCreateOption {
41+
inherit config options;
42+
default = ''
43+
echo "${config.device}" >> "$disko_devices_dir/bcache_backing_${lib.escapeShellArg config.set}"
44+
'';
45+
};
46+
_mount = diskoLib.mkMountOption {
47+
inherit config options;
48+
default = { };
49+
};
50+
_unmount = diskoLib.mkUnmountOption {
51+
inherit config options;
52+
default = { };
53+
};
54+
_config = lib.mkOption {
55+
internal = true;
56+
readOnly = true;
57+
default = [ ];
58+
description = "NixOS configuration";
59+
};
60+
_pkgs = lib.mkOption {
61+
internal = true;
62+
readOnly = true;
63+
type = lib.types.functionTo (lib.types.listOf lib.types.package);
64+
default = pkgs: [ pkgs.bcache-tools ];
65+
description = "Packages";
66+
};
67+
};
68+
}

0 commit comments

Comments
 (0)