17 min read

Расширяем поддержку оборудования в PR2100

Небольшая предыстория к ниже описанному.

В этот раз попробую решить недостаток с работой usb, как известно то usb устройства на PR2100 могут работать только как накопитель, все остальное просто не воспринимается. Аналогичные проблемы описаны на форуме wd тут и тут. Есть несколько вариантов решения:

  • собрать модуль для текущего ядра и загружать его:
    • каждый раз вручную
    • при старте системы
  • собрать новое ядро со встроенным модулем

Идея эксперемента в том чтоб не трогать родную прошивку, а запускаясь с флешки подхватывать настройки и основной образ из памяти NAS. В случае если что то пойдет не так, достаточно вытащить флешку и загрузить родную прошивку, весь софт и все контейнеры продолжат работать.

Для начала стоит посмотреть как вообще запускается система на NAS. Поэтому монтирую первый раздел mmc и смотрю что там:

grub.cfg

root@hub ~ # mount /dev/mmcblk0p1 /tmp/mmc
root@hub ~ # cat /tmp/mmc/EFI/BOOT/grub.cfg
set default="0"
set timeout="1"
set fallback=1

# Serial console id:
#set gfxpayload=text
set scid="serial"
serial --speed=115200 --word=8 --parity=no --stop=1# ${scid}
terminal_output --append ${scid}
terminal_input --append ${scid}

set kver="4.14.22"
set kname="bzImage-${kver}"

set kcmdline="root=/dev/ram0 rootfstype=ramfs rootwait console=ttyS0,115200n8 net.ifnames=0 acpi_enforce_resources=lax"

menuentry "Linux ${kver}" {
Alpha_CRC32_Reset
search --no-floppy --set=root --label wdnas_kernel
Alpha_CRC32_Check /uImage
search --no-floppy --set=root --label wdnas_initramfs
Alpha_CRC32_Check /uRamdisk
search --no-floppy --set=root --label wdnas_kernel
linux /uImage ${kcmdline}
search --no-floppy --set=root --label wdnas_initramfs
initrd /uRamdisk
}

menuentry "Rescure Mode - Linux ${kver}" {
Alpha_CRC32_Reset
search --no-floppy --set=root --label wdnas_rescue_fw
Alpha_CRC32_Check /uImage /uRamdisk
linux /uImage ${kcmdline}
initrd /uRamdisk
}

Стоит обычный grub который:

  • ищет по метке нужные разделы
  • проверяет CRC ядра и initramfs
  • загружает ядро и initramfs

Примечательно то что несмотря на присутствие нормального bios, ядро и initramfs упакованы в формате u-boot. Модуль ядра может загружаться несколькими способами:

  • вместе с ядром
  • в момент загрузки initramfs
  • при запуске основных сервисов

Значит, для того чтоб добавить минимальную поддержку zigbee достаточно будет собрать модуль ядра и загрузить его на этапе загрузки initramfs. Пользуясь моментом попробую также обновить busybox.


Ядро

WD предоставляет исходники для open source компонентов, ими я и воспользуюсь. Для этого идем на сайт поддержки и качаем GPL исходники для WD My Cloud PR2100. :

wget https://download.wdc.com/gpl/WDMyCloud_PR2100_GPL_v5.31.102_20250605.tar.gz -O PR2100_gpl.tar.gz

На данный момент последняя 5.31.102_20250605

$ ls -l
итого 48
-rwxr-xr-x   1 user user  741 июн  5 09:48 build_kernel.sh
-rwxr-xr-x   1 user user 1710 июн  5 09:48 build_open-source-packages.sh
drwxr-xr-x 131 user user 4096 июн  5 09:47 deb-prebuilt
drwxr-xr-x   4 user user 4096 июн  5 09:47 dockerfile
drwxr-xr-x   3 user user 4096 июн  5 09:47 firmware
drwxr-xr-x   2 user user 4096 июн  5 09:48 gpl-deb-packages
drwxr-xr-x   3 user user 4096 авг 12 18:13 kernel
drwxr-xr-x   2 user user 4096 июн  5 09:42 LICENSES
drwxr-xr-x   4 user user 4096 июн  5 09:48 nas
drwxr-xr-x  59 user user 4096 июн  5 09:48 open-source-packages
drwxr-xr-x   2 user user 4096 июн  5 09:47 RestSDK
-rwxr-xr-x   1 user user 1202 июн  5 09:48 WDMyCloud_PR2100_GPL_Release_Notes_v5.31.102.txt
$ ls -l kernel/
-rwxr-xr-x  1 user user       929 июн  5 09:47 build_ext_kernel_module.sh
-rw-r--r--  1 user user 161423529 июн  5 09:47 linux-4.14.22.tar.gz
-rw-r--r--  1 user user     22292 июн  5 09:47 netatop-2.0.tar.gz
# Распаковываем архив с ядром
$ tar -xf kernel/linux-4.14.22.tar.gz -C kernel/
$ cd linux-4.14.22

После распаковки смотрим что внутри

По хорошему для того чтобы все было совместимо лучше собирать с той же версией gcc что и оригинальное ядро. Поэтому имеет смысл использовать докер контейнер для сборки ядра и оcтального.

dockerfile

FROM debian:buster

ENV DEBIAN_FRONTEND=noninteractive

RUN sed -i 's/deb.debian/archive.debian/g' /etc/apt/sources.list
RUN apt update && apt install -y build-essential bc kmod cpio flex bison libssl-dev wget vim libncurses-dev libelf-dev u-boot-tools nano && rm -rf /var/lib/apt/lists/*

WORKDIR /kernel_build

CMD ["/bin/bash"]

# сборка контейнера
docker build -t kernel-builder-buster:latest .
# запуск из каталога с исходниками
docker run --rm -it -v ./kernel/linux-4.14.22:/kernel_build --workdir /kernel_build kernel-builder-buster:latest

Модули ядра

В целом, в каталоге с исходниками уже есть готовый конфиг. Так как мне нужны драйвера для zigbee координатора, а еще я хочу подключить BT адаптер, надо включить соответствующие модули для сборки. Пробуем:

# ./xbuild.sh clean
# scripts/config --module CONFIG_USB_ACM
# scripts/config --module CONFIG_BT
# scripts/config --module CONFIG_BT_HCIBTUSB
# scripts/config --module CONFIG_BT_RTL
# scripts/config --disable BT_LEDS
# scripts/config --disable LEDS_TRIGGERS
# sed -i '/Realtek.*Bluetooth devices/ a\        { USB_DEVICE(0x0bda, 0xa729), .driver_info = BTUSB_REALTEK },' kernel/linux-4.14.22/drivers/bluetooth/btusb.c

# make olddefconfig
# ./xbuild.sh build
...
# find . |grep "bluetooth.ko\|btusb.ko\|btrtl.ko\|cdc-acm.ko\|ecdh_generic.ko\|btintel.ko\|btbcm.ko" |grep modules
./_install/lib/modules/4.14.22/kernel/crypto/ecdh_generic.ko
./_install/lib/modules/4.14.22/kernel/net/bluetooth/bluetooth.ko
./_install/lib/modules/4.14.22/kernel/drivers/usb/class/cdc-acm.ko
./_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btbcm.ko
./_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btusb.ko
./_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btrtl.ko
./_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btintel.ko

# mkdir -p mods
# find _install/   \( -name "bluetooth.ko" -o -name "btusb.ko" -o -name "btrtl.ko" -o -name "cdc-acm.ko" -o -name "ecdh_generic.ko" -o -name "btintel.ko"  -o -name "btbcm.ko" \)   -exec cp -v {} mods/ \;
'_install/lib/modules/4.14.22/kernel/crypto/ecdh_generic.ko' -> 'mods/ecdh_generic.ko'
'_install/lib/modules/4.14.22/kernel/net/bluetooth/bluetooth.ko' -> 'mods/bluetooth.ko'
'_install/lib/modules/4.14.22/kernel/drivers/usb/class/cdc-acm.ko' -> 'mods/cdc-acm.ko'
'_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btbcm.ko' -> 'mods/btbcm.ko'
'_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btusb.ko' -> 'mods/btusb.ko'
'_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btrtl.ko' -> 'mods/btrtl.ko'
'_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btintel.ko' -> 'mods/btintel.ko'

После окончания работы ./xbuild build получил готовое ядро:

# ls -l arch/x86_64/boot/
total 4752
lrwxrwxrwx 1 root root      22 Aug 12 16:01 bzImage -> ../../x86/boot/bzImage
-rw-r--r-- 1 root root 4862800 Aug 12 16:02 uImage
# file arch/x86_64/boot/uImage 
arch/x86_64/boot/uImage: u-boot legacy uImage, kernel, Linux/Intel x86, OS Kernel Image (gzip), 4862736 bytes, Tue Aug 12 16:02:32 2025, Load Address: 0x00000000, Entry Point: 0x00000000, Header CRC: 0xAAAA538A, Data CRC: 0xFFFFFFFF

Готовое ядро

Собранные модули лежат в каталоге mods, ради теста подкинул cdc-acm.ko на NAS и загрузил:

rsync -avz mods/lib/modules/4.14.22/kernel/drivers/usb/class/cdc-acm.ko hub.cave-labs.ru:/home/root/
root@hub ~ # ls -l
-rw-r--r--    1 root     root         58120 Aug 13 12:48 cdc-acm.ko
root@hub ~ # insmod ./cdc-acm.ko 
root@hub ~ # dmesg |tail -n 10
...
[404322.428280] usbcore: registered new interface driver cdc_acm
[404322.434723] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
[404392.169098] usb 1-3: new full-speed USB device number 2 using xhci_hcd
[404392.305852] cdc_acm 1-3:1.0: ttyACM0: USB ACM device
[404392.312471] sdhci-pci 0000:00:12.0: SDHCI controller found [8086:2296] (rev 35)
root@hub ~ # ls -l /dev/ttyACM0 
crw-rw----    1 root     root      166,   0 Aug 13 12:57 /dev/ttyACM0

О_о внезапная победа, после подключения модуля zigbee свисток обнаружился и появился интерфейс ttyACM0, его можно прокинуть в докер и полноценно пользоваться homeassistant. Теперь ядро как таковое уже не нужно и можно обойтись только модулем.


Bluetooth

Важно: далее описаны действия и предположения применительны только к моему bt адаптеру: Realtek Bluetooth 5.1 / 5.3 Radio (RTL8761BU / RTL8761BUV). Как оказалось, просто так собрать и закинуть модуль недостаточно, нужно еще и firmware:

[  123.812922] Bluetooth: Core ver 2.22
[  123.817019] NET: Registered protocol family 31
[  123.821989] Bluetooth: HCI device and connection manager initialized
[  123.829105] Bluetooth: HCI socket layer initialized
[  123.834592] Bluetooth: L2CAP socket layer initialized
[  123.840255] Bluetooth: SCO socket layer initialized
[  124.368593] usbcore: registered new interface driver btusb
[  124.654999] usbcore: registered new interface driver cdc_acm
[  124.661362] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
....
[  209.500873] Bluetooth: hci0: rtl: examining hci_ver=0a hci_rev=000b lmp_ver=0a lmp_subver=8761
[  209.500878] Bluetooth: hci0: rtl: loading rtl_bt/rtl8761a_config.bin
[  209.500904] bluetooth hci0: Direct firmware load for rtl_bt/rtl8761a_config.bin failed with error -2
[  209.500907] Bluetooth: hci0: rtl: loading rtl_bt/rtl8761a_fw.bin
[  209.500925] bluetooth hci0: Direct firmware load for rtl_bt/rtl8761a_fw.bin failed with error -2
[  209.500927] Bluetooth: hci0: Failed to load rtl_bt/rtl8761a_fw.bin

Нашел firmware, подкинул:

~ # dmesg | grep -iE 'bluetooth|hci0'
[   10.704413] bluetooth: Unknown symbol crypto_ecdh_key_len (err 0)
[   10.711398] bluetooth: Unknown symbol crypto_ecdh_encode_key (err 0)
[   11.052748] Bluetooth: Core ver 2.22
[   11.061735] Bluetooth: HCI device and connection manager initialized
[   11.068835] Bluetooth: HCI socket layer initialized
[   11.074290] Bluetooth: L2CAP socket layer initialized
[   11.079939] Bluetooth: SCO socket layer initialized
[   11.110730] Bluetooth: hci0: rtl: examining hci_ver=0a hci_rev=000b lmp_ver=0a lmp_subver=8761
[   11.121873] Bluetooth: hci0: rtl: loading rtl_bt/rtl8761a_config.bin
[   11.137194] Bluetooth: hci0: rtl: loading rtl_bt/rtl8761a_fw.bin
[   11.145748] Bluetooth: hci0: rom_version status=0 version=1
[   11.153317] Bluetooth: hci0: unknown project id 14

обнадеживает

unknown project id 14 говорит нам о том что модуль ядра не знает что это. Попробуем пропатчить, добавляем в drivers/bluetooth/btrtl.c :

{ RTL_ROM_LMP_8761A, 14 },

Собираем и пытаемся проверить работоспособность:

root@hub:/# bluetoothctl show
Controller XX:XX:XX:XX:XX:XX (public)
        Name: BlueZ 5.66
        Alias: BlueZ 5.66
        Class: 0x00000000
        Powered: yes
        Discoverable: no
        DiscoverableTimeout: 0x000000b4
        Pairable: no
        UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
        UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
        UUID: PnP Information           (00001200-0000-1000-8000-00805f9b34fb)
        UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
        UUID: A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)
        UUID: Device Information        (0000180a-0000-1000-8000-00805f9b34fb)
        Modalias: usb:v1D6Bp0246d0542
        Discovering: no
        Roles: central
        Roles: peripheral
Advertising Features:
        ActiveInstances: 0x00 (0)
        SupportedInstances: 0x05 (5)
        SupportedIncludes: tx-power
        SupportedIncludes: appearance
        SupportedIncludes: local-name

RAM диск

Ядро трогать уже нет смысла, а лезть в файлы прошивки мне пока что не очень хочется, поэтому самый логичный на мой взгляд выход это модифицировать initramfs так чтоб на этапе загрузки в ядро загружались модули с флешки. В качестве initramfs загружается uRamdisk с раздела с меткой wdnas_initramfs. Посмотрим что там:

root@hub ~ # mount /dev/mmcblk0p3 /tmp/mmc
root@hub ~ # ls -l /tmp/mmc/
-rw-r--r--    1 root     root       4004704 Jul  6 16:10 uRamdisk
root@hub ~ #
$ rsync -az hub.cave-labs.ru:/tmp/mmc/uRamdisk ./
$ file uRamdisk
uRamdisk: u-boot legacy uImage, Initramfs, Linux/Intel x86, RAMDisk Image (gzip), 4004640 bytes, Mon May 19 11:06:01 2025, Load Address: 00000000, Entry Point: 00000000, Header CRC: 0XB477CB30, Data CRC: 0XE9E4BF5A

монтируем раздел с initramfs и достаем uRamdisk

$ tail -c +65 uRamdisk > uRamdisk.raw
$ mkdir -p uRam
$ cd uRam/
$ gzip -dc ../uRamdisk.raw | cpio -idmv

достаем полезные данные из initramfs

В самом рамдиске ничего экзотичного нет, стартует busybox который выполняет /etc/rc.sh:

содержимое etc/rc.sh

$ cat etc/rc.sh
#!/bin/sh

#
# SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates.
#
# SPDX-License-Identifier: GPL-2.0-or-later
#

MOUNT_CMD="busybox mount"
UMOUNT_CMD="busybox umount"

#echo "initramfs: remount %root%"
#${MOUNT_CMD} -o remount -w %root% /

echo "initramfs: mount all in /etc/fstab"
${MOUNT_CMD} -a
sleep 1

${MOUNT_CMD} -t proc proc /proc
${MOUNT_CMD} -a

# Cgroup support
CGROUP_ROOT=/sys/fs/cgroup
${MOUNT_CMD} -t cgroup -o rw,memory,cpu cgroup ${CGROUP_ROOT}

echo 8192 > /proc/sys/vm/min_free_kbytes

echo 4096 > /proc/sys/net/core/somaxconn
echo 16777216 > /proc/sys/net/core/wmem_max
echo 16777216 > /proc/sys/net/core/rmem_max
echo 163840 > /proc/sys/net/core/wmem_default
echo 163840 > /proc/sys/net/core/rmem_default
echo 3000 > /proc/sys/net/core/netdev_max_backlog

echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
#echo 1 > /proc/sys/net/ipv4/tcp_syncookies
echo 0 > /proc/sys/net/ipv4/tcp_timestamps

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
touch /dev/mdev.seq

#mount -t devtmpfs none /dev
mkdir /dev/pts
${MOUNT_CMD} -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true

# Hack for shell to get job control
#if [ ! -L /dev/console ]; then
# rm -f /dev/console
# ln -s /dev/ttyS0 /dev/console
#fi

WAIT_DEV=4
# Wait for EMMC & FileSystem being recognized,
# $WAIT_DEV secs should be enough, sad for this.
echo -n "initramfs: wait for EMMC being setup"
for i in $(seq 1 ${WAIT_DEV}); do
echo -n " ."
sleep 1
done
echo

# This one need the mount from util-linux, busybox's one can't handle LABEL
#echo "initramfs: mounting label=image.cfs partition"
#mount -t ext4 LABEL=image.cfs /usr/local/tmp

chk_image

#echo "initramfs: mounting squashfs'd image.cfs"
#${MOUNT_CMD} -t squashfs -o loop /usr/local/tmp/image.cfs /usr/local/modules

echo -n "initramfs: checking integrity of system partitions "
ucpd="$(blkid | grep 'wdnas_efi' | awk -F: '{ print $1}')"
ucpd="${ucpd:0:13}"
echo "($ucpd)"
# 1st: vfat EFI
if stat "${ucpd}1" >/dev/null 2>&1; then
dosfsck -p ${ucpd}1 2>/dev/null
else
echo "initramfs: cannot find devnode: ${ucpd}1"
fi
# all others ext4
for i in $(seq 2 9); do
if stat "${ucpd}$i" >/dev/null 2>&1; then
if [ "$i" = "6" ] || [ "$i" = "9" ]; then
e2fsck -p "${ucpd}$i" 2>/dev/null || e2fsck -f -y "${ucpd}$i"
else
e2fsck -p "${ucpd}$i" 2>/dev/null
fi
else
echo "initramfs: cannot find devnode: ${ucpd}$i"
fi
done

echo "-------- Sleep 1 Second for recovering journal -------"
sleep 1 # sleep 1 second for recovering journal.
echo "-------------------------------"
echo "initramfs: mount tmpfs config partition"

mkdir /usr/local/tmp_wdnas_config
mount -t ext4 -o rw,noatime,nodiratime,sync LABEL=wdnas_config /usr/local/tmp_wdnas_config
rm -rf /usr/local/config
if [ ! -e /usr/local/tmp_wdnas_config/config ]; then
mkdir /usr/local/tmp_wdnas_config/config
fi
ln -s /usr/local/tmp_wdnas_config/config /usr/local/config

echo "initramfs: running realworld system_init"
/usr/local/modules/script/system_init

Если кратко, то процесс запуска такой:

  • монтируются основные разделы и выполняются основные настройки
  • запускается chk_image, бинарник который творит магию с проверкой образа image.cfs и монтированием его в /usr/local/modules.
  • проверяются системные разделы на mmc
  • запускается system_init который загружает модули ядра и стартует все сервисы, включая web панель управления.

В теории можно забить на chk_image и просто смонтировать образ раскоментировав маунты в скрипте:

# This one need the mount from util-linux, busybox's one can't handle LABEL
echo "initramfs: mounting label=image.cfs partition"
mount -t ext4 LABEL=image.cfs /usr/local/tmp

#chk_image

echo "initramfs: mounting squashfs'd image.cfs"
${MOUNT_CMD} -t squashfs -o loop /usr/local/tmp/image.cfs /usr/local/modules

Но я решил оставить chk_image как есть, а после него свою магию творить буду я. А именно, монтирование оверлея:

# This one need the mount from util-linux, busybox's one can't handle LABEL
#echo "initramfs: mounting label=image.cfs partition"
#mount -t ext4 LABEL=image.cfs /usr/local/tmp

chk_image

#echo "initramfs: mounting squashfs'd image.cfs"
#${MOUNT_CMD} -t squashfs -o loop /usr/local/tmp/image.cfs /usr/local/modules

# ---------- overlay + custom kernel modules, from usb ----------
USB_OVERLAY=/mnt/usb_overlay
LOWER=/usr/local/modules
LOWER_RO=/usr/local/modules_ro
CUSTOM=$USB_OVERLAY/upper/custom_modules

mkdir -p "$USB_OVERLAY" "$LOWER_RO"
if busybox mount -t ext4 -o rw LABEL=usb_overlay "$USB_OVERLAY"; then
    mkdir -p "$USB_OVERLAY/upper" "$USB_OVERLAY/work"
    [ -f "$CUSTOM/exportfs.ko" ] && busybox insmod "$CUSTOM/exportfs.ko" 2>&1
    [ -f "$CUSTOM/overlay.ko" ] && busybox insmod "$CUSTOM/overlay.ko" 2>&1
    if grep -q overlay /proc/filesystems; then
        busybox mount --move "$LOWER" "$LOWER_RO" &&
        busybox mount -t overlay overlay -o lowerdir=$LOWER_RO,upperdir=$USB_OVERLAY/upper,workdir=$USB_OVERLAY/work "$LOWER" &&
        echo "initramfs: overlay active" ||
        { echo "initramfs: overlay mount failed, restoring squashfs"; busybox mount --move "$LOWER_RO" "$LOWER"; }
    else
        echo "initramfs: kernel has no overlay support, staying stock"
    fi
else
    echo "initramfs: usb_overlay not found, staying stock"
fi

Включение хаков и кастомных модулей:

# bluetooth firmware
mkdir -p /lib/firmware/rtl_bt
cp "$USB_OVERLAY/firmware/rtl_bt/"* /lib/firmware/rtl_bt/ 2>&1 && echo "initramfs: bt firmware installed"

# docker storage-driver override
if [ -f "$USB_OVERLAY/upper/files/docker_daemon.json" ]; then
    mkdir -p /etc/docker
    cp "$USB_OVERLAY/upper/files/docker_daemon.json" /etc/docker/daemon.json
    echo "initramfs: docker daemon.json installed from usb overlay"
fi
# custom kernel modules, order-independent retry
echo "initramfs: inserting custom kernel modules"
MOD_DIR=/usr/local/modules/custom_modules
PENDING=$(ls "$MOD_DIR"/*.ko 2>/dev/null)
PASS=0
while [ -n "$PENDING" ] && [ "$PASS" -lt 10 ]; do
    PASS=$((PASS + 1)); NEXT=""
    for m in $PENDING; do
        OUT=$(busybox insmod "$m" 2>&1)
        if [ -z "$OUT" ] || echo "$OUT" | grep -qi "File exists"; then
            echo "initramfs: loaded $(basename "$m")"
        else
            NEXT="$NEXT $m"
        fi
    done
    [ "$NEXT" = "$PENDING" ] && break
    PENDING=$NEXT
done
[ -n "$PENDING" ] && echo "initramfs: WARNING, not loaded:$PENDING"

Так же не лишним будет выключить проверку разделов:

echo -n "initramfs: checking integrity of system partitions "
ucpd="$(blkid | grep 'wdnas_efi' | awk -F: '{ print $1}')"
ucpd="${ucpd:0:13}"
echo "($ucpd)"
# 1st: vfat EFI
if stat "${ucpd}1" >/dev/null 2>&1; then
    dosfsck -p ${ucpd}1 2>/dev/null
else
    echo "initramfs: cannot find devnode: ${ucpd}1"
fi
# all others ext4
for i in $(seq 2 9); do
    if stat "${ucpd}$i" >/dev/null 2>&1; then
        if [ "$i" = "6" ] || [ "$i" = "9" ]; then
            e2fsck -p "${ucpd}$i" 2>/dev/null || e2fsck -f -y "${ucpd}$i"
        else
            e2fsck -p "${ucpd}$i" 2>/dev/null
        fi
    else
        echo "initramfs: cannot find devnode: ${ucpd}$i"
    fi
done

убираем либо коментируем этот кусок

В конце монтируем домашний каталог для рута:

# ---------- Persistent partition for /home/root ----------
echo "initramfs: mounting usb_home to /home/root"
mkdir -p /home/root
mount -t ext4 LABEL=usb_home /home/root 2>/dev/null || echo "initramfs: usb_home not found, /home/root still ramfs"

Раз уж я полез в initramfs, то неплохо было бы обновить busybox:

# качаем последний busybox
wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2
# Распаковываем
tar xf busybox-1.37.0.tar.bz2
cd busybox-1.37.0
# заходим в контейнер для сборки
docker run --rm -it -v ./:/kernel_build --workdir /kernel_build kernel-builder-buster:latest
# сборка
make defconfig
make -j$(nproc) LDFLAGS="--static"
# проверка результата
./busybox
BusyBox v1.37.0 (2025-08-12 17:11:32 UTC) multi-call binary.
# Копирование в initramfs
cd ..
cp -f busybox-1.37.0/busybox uRam/bin/busybox

сборка и замена busybox

В конце запаковываем uRamdisk обратно:

find . | cpio -o -H newc | gzip > ../uRamdisk.raw.new
cd ..
mkimage -A x86 -O linux -T ramdisk -C gzip -a 0x0 -e 0x0 -n "New uRamdisk" -d uRamdisk.raw.new uRamdisk.new


$ file uRamdisk.new 
uRamdisk.new: u-boot legacy uImage, New uRamdisk, Linux/Intel x86, RAMDisk Image (gzip), 5229658 bytes, Tue Aug 12 18:11:13 2025, Load Address: 00000000, Entry Point: 00000000, Header CRC: 0X29AAA346, Data CRC: 0X594DA0BF

Rootfs

После монтирования image.cfs, запускается system_init. Из интересного:
- функция Insert_Kernel_Driver которая подгружает все модули из /usr/local/modules/driver/
- создание симлинков из modules в bin/sbin/usr и копирование конфигов в etc происходит на этом этапе.
- если знать где хранятся конфиги то можно изменить или переопределить стандартные настройки, например повесить стандартный web ui на нестандартный порт и проксировать через NPM или изменить версию ПО там самым избавиться от автообновления.

# Монтируем на NAS
# mount /dev/mmcblk0p4 /tmp/mmc
# ls -l /tmp/mmc/
-rw-r--r--    1 root     root            10 Aug 12 21:20 _un
-rw-r--r--    1 root     root     209004544 Jul  6 16:10 image.cfs
# Копируем к себе
$ rsync -az hub.cave-labs.ru:/tmp/mmc/image.cfs ./

Шпаргалка для squashfs:

# Посмотреть параметры оригинала
unsquashfs -s -o 2048 image.cfs
# Распаковывка
unsquashfs -o 2048 image.cfs
# Упаковка обратно
mksquashfs squashfs-root new_image.cfs -comp xz -b 131072

# Вытаскиваем хедер из оригинала
dd if=image.cfs of=header.bin bs=2048 count=1
# Склеиваем вместе
cat header.bin new_image.cfs > image_patched.cfs

проверить готовый образ можно через binwalk

На этом из интересного все, сильно копаться в прошивке пока что не имеет особого смысла.


Готовим флешку

Для начала посмотрим какие есть разделы есть на mmc:

/dev/mmcblk0p1: LABEL_FATBOOT="wdnas_efi" LABEL="wdnas_efi" UUID="640F-1CC4" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="EFI System" PARTUUID="c7d73ff4-bb7a-419c-b76c-f67325cd6015"
/dev/mmcblk0p2: LABEL="wdnas_kernel" UUID="001208be-c41c-4f72-9a0c-fd745ce42aca" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="kernel" PARTUUID="d58b27ab-f44e-4eb9-86e8-68d04932f5d5"
/dev/mmcblk0p3: LABEL="wdnas_initramfs" UUID="8ce8b16c-0eae-46b6-806c-cbaa0c744785" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="ramdisk" PARTUUID="7201983b-a751-4262-aa12-464e627c76ff"
/dev/mmcblk0p4: LABEL="wdnas_image.cfs" UUID="cac6bb5a-5fdd-472f-a9ab-eb5e534bdcbc" BLOCK_SIZE="4096" TYPE="ext4" PARTLABEL="image.cfs" PARTUUID="6bec99c3-813f-4555-894b-2830b578edd2"
/dev/mmcblk0p5: LABEL="wdnas_rescue_fw" UUID="366ca9d2-601d-4681-8e10-27067ff6bb9a" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="rescue_fw" PARTUUID="ae0f3c54-724b-4100-87a6-8876642ce1f3"
/dev/mmcblk0p6: LABEL="wdnas_config" UUID="eccec0dd-f604-44e0-8ec5-8a0b5a57f1bf" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="config" PARTUUID="a20cf9b0-f7a4-49c5-a734-417631d791fb"
/dev/mmcblk0p9: LABEL="wdnas_backup" UUID="c586d83b-d618-4257-a03d-a24d1b78e004" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="backup" PARTUUID="8cbc089c-930f-4e9b-9489-d31d0d81b662"

описание разделов

mmcblk0p1 - efi раздел
mmcblk0p2 - раздел с ядром
mmcblk0p3 - initramfs
mmcblk0p4 - образ image.cfs
mmcblk0p5 - rescue ядро и initramfs
mmcblk0p6 - судя по всему раздел с временными конфигами(сбрасываются через reset)
mmcblk0p9 - постоянные конфиги

По сути для того чтобы стартануть, нам нужен загрузочный раздел c EFI, ядро и initramfs, а все остальное может загружаться из памяти NAS. Первая попытка имела частичный успех, NAS запустился и работал как полный сток, однако после некоторых тестов выяснилось что у меня отвалился докер, причем он не запускается что с флешки что с родной прошивки. Помогло переустановить сам докер и подкинуть все его старые файлы, ну и заново задеплоить все приложения.

Новая схема разделов на флешке выглядит так:

NAME   FSTYPE LABEL
sdc           
├─sdc1 vfat   
├─sdc2 ext2   usb_wdnas_kernel
├─sdc3 ext2   usb_wdnas_initrd
├─sdc4 ext2   usb_overlay
└─sdc6 ext2   usb_home

Загрузчик: тут без фантазии, берем содержимое efi с NAS, правим grub и запускаемся с usb. Примерный конфиг:

set default="0"
set timeout="1"
set fallback=1

# Serial console id:
#set gfxpayload=text
set scid="serial"
serial --speed=115200 --word=8 --parity=no --stop=1# ${scid}
terminal_output --append ${scid}
terminal_input --append ${scid}

set kver="4.14.22"
set kname="bzImage-${kver}"

set kcmdline="root=/dev/ram0 rootfstype=ramfs rootwait console=ttyS0,115200n8 net.ifnames=0 acpi_enforce_resources=lax"


menuentry "WD PR2100 - USB boot" {
	search --no-floppy --set=root --label usb_wdnas_kernel
	linux /uImage ${kcmdline}
	search --no-floppy --set=root --label usb_wdnas_initrd
	initrd /uRamdisk
}

Копируем загрузчик, ядро, initramfs и оверлей на флешку, вставляем в NAS, подключаемся к UART и молимся пробуем загрузиться

и ... ничего не происходит :)

Ладно, хотя бы видно проблему. По какой то причине не видно разделов о чем говорит "no such device". Эксперементально пришел к тому что выше ext2 grub не видит.

система запустилась и загрузила нужные модули

Иии.. у меня случился нервный срыв снова отвалился докер. в случае без usb:

root@hub ~ # docker ps
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
root@hub ~ # /mnt/HD/HD_a2/Nas_Prog/docker/docker/start.sh 
DOCKER START: setup daemon
DOCKER START: start daemon
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

а если загрузиться с флешки то:

~ # ps aux |grep docker
 5396 root      0:00 /sbin/dockerd --ip-masq=true
 5406 root      0:00 containerd --config /var/run/docker/containerd/containerd.toml --log-level info
 6221 root      0:00 grep docker
~ # docker ps --all
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
~ #

т.е. в сток прошивке докер даже не запустился, а на usb запустился но не нашел контейнеров. Но(!) в этот раз я решил разобраться в проблеме и предварительная причина: storage драйвер докера почему то поменялся на overlay2. Видимо потому что я ранее подгружал модуль ядра для монтирования оверлея. Проверяем:

mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'EOF'
{
  "storage-driver": "vfs"
}
EOF
rm -rf /mnt/HD/HD_a2/Nas_Prog/_docker/overlay2
/mnt/HD/HD_a2/Nas_Prog/docker/start.sh /mnt/HD/HD_a2/Nas_Prog/docker/docker
docker ps --all
docker info | grep -i "storage driver"

временный конфиг и перезапуск для применения изменений

Решение: насколько я догадываюсь, в сток прошивке используется последний выбранный драйвер, в таком случае для usb пропишем явно:

{
  "storage-driver": "vfs"
}

upper/files/docker_daemon.json

При каждом запуске применяем изменения:

# docker storage-driver override 
if [ -f "$USB_OVERLAY/upper/files/docker_daemon.json" ]; then
    mkdir -p /etc/docker
    cp "$USB_OVERLAY/upper/files/docker_daemon.json" /etc/docker/daemon.json
    echo "initramfs: docker daemon.json installed from usb overlay"
fi

подкидываем нужный нам конфиг

Перезагружаемся и проверяем:

~ # docker ps
CONTAINER ID   IMAGE                                 COMMAND                  CREATED        STATUS                           PORTS                                                                                    NAMES
aa09752548cc   gitea/gitea:latest                    "/usr/bin/entrypoint…"   4 days ago     Up 3 seconds                     0.0.0.0:3000->3000/tcp, 0.0.0.0:222->22/tcp                                              gitea
1862422d2aa4   pihole/pihole:latest                  "start.sh"               3 weeks ago    Up 1 second (health: starting)   67/udp, 0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp, 123/udp, 443/tcp, 0.0.0.0:9001->80/tcp   pihole
9e6504313333   homeassistant/home-assistant:stable   "/init"                  5 months ago   Up 3 seconds                     0.0.0.0:8123->8123/tcp                                                                   homeassistant
ffb703de9066   koenkk/zigbee2mqtt:latest             "docker-entrypoint.s…"   9 months ago   Up 4 seconds                     8080/tcp                                                                                 zigbee2mqtt
0b74633f83b1   ghost:latest                          "docker-entrypoint.s…"   9 months ago   Up 3 seconds                     0.0.0.0:2368->2368/tcp                                                                   ghost
b24ea3ec4626   mariadb:latest                        "docker-entrypoint.s…"   9 months ago   Up 2 seconds                     3306/tcp                                                                                 mariadb
c0e071824faa   jc21/nginx-proxy-manager:latest       "/init"                  9 months ago   Up 2 seconds                     0.0.0.0:81->81/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8080->80/tcp                           nginx_proxy
194a8242e044   portainer/portainer-ce                "/portainer"             9 months ago   Up 5 seconds                     8000/tcp, 9443/tcp, 0.0.0.0:9000->9000/tcp                          

Ура победа, все работает


Итог

Итак это победа, мы имеем флешку которая не меняет стоковую прошивку, но расширяет её возможности. В то же время можно вытащить флешку и загрузиться в полностью оригинальную прошивку и все продолжит работать. Бонусом имеем:

  • обновленный busybox
  • поддержка zigbee и bluetooth
  • возможность дальнейших кастомизаций с помощью overlay.

Плюшки оверлея:
- overlay/upper/busybox-bin - path который прописывается на постоянном /home/root и добавляет новые алиасы для busybox
- overlay/upper/custom_modules - собранные модули ядра для поддержки оборудования
- overlay/upper/files - конфиг для докера с переопределением storage-driver для обратной совместимости

полезные файлы

оригинальный uRamdisk
кастомный uRamdisk
оригинальное ядро
конфиг ядра
исходники ядра
собранные модули
firmware для realtek bluetooth