Mittwoch, 7. Juli 2021

Debian on WD My Cloud Home single-bay (MCH) - part 3 (controlling the LED)

 In part 1 Debian Buster was installed via debootstrap. Part 2 discussed how a suitable kernel can be compiled.

Getting the LED to work

Having looked at the wrong places, I finally got the LED control to work. The DTB configures all internal devices and exposes them at /proc/devices/platform.

Aparrently there ar four PWM devices, of which only #3 is enabled. By adjusting the duty cycle one can actually control the LED:

echo 0 > /sys/devices/platform/980070d0.pwm/dutyRate3   # LED off
echo 100 > /sys/devices/platform/980070d0.pwm/dutyRate3 # LED 100% brightness

Digging deeper I found a kernel document describing how to add PWM controlled LEDs to the generic LED interface of the kernel. From there it was straight forward adding the required lines to the DTB (part of my kernel patches). 

// linux-kernel/arch/arm64/boot/dts/realtek/wd-monarch-1GB.SATA.dts
    pwmleds {
        compatible = "pwm-leds";
        pwm3{
          label = "led";
          pwms = <&pwm 3 37878>;
          max-brightness = <255>;
       };
    };

Now the LED can be controlled via

echo 0 > /sys/class/leds/led/brightness   # LED off
echo 255 > /sys/class/leds/led/brightness # LED 100% brightness

The updated pre-compiled DTB matching my kernel can be found here. Have a look at part 2 on how to apply that in case you are wondering...

Below is a modified version of the monitorio script I use from my old MyBook Live. It's been modified to match the disks of the MCH. Additional required packages are hdparm and smartmontools.

The LED will now be turned off when the disk enters standby, and return to 100% brightness when the disk is active.

#!/bin/bash
#
# (c) 2011 Western Digital Technologies, Inc. All rights reserved.
#
# monitorio - Monitor disk activity, and put system into standby.
##
PATH=/sbin:/bin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
. /lib/lsb/init-functions

MIN_SINCE_DISK_ACCESS=/tmp/minutes_since_disk_access

dataVolumeDevice=/dev/sataa24
currentRootDevice=/dev/sataa20

declare -i sleepcount
declare -i rootdisk_thresh
declare -i enterStandbyTime=0
rm -f /tmp/standby
source /usr/local/etc/standby.conf

resetSleepCount() {
        sleepcount=0

        source /usr/local/etc/standby.conf
        rootdisk_thresh=$((standby_time-1))
}

rootDisk=`basename ${currentRootDevice}`
dataVolumeDisk=`basename ${dataVolumeDevice}`
drivelist=(${dataVolumeDevice})

echo "0" > ${MIN_SINCE_DISK_ACCESS}

echo 255 > /sys/class/leds/led/brightness

if [ "$1" == "debug" ]; then
        echo "1" > /proc/sys/vm/block_dump
        dmesg -c > /dev/null
fi

while :; do

    for i in ${drivelist[@]}; do
            smartctl -i -n standby /dev/sataa | grep -q "STANDBY"
            standby_test=$?
            [ "$standby_test" -eq "1" ] && break
    done

    if [ "$standby_test" -eq "0" ]; then
        sleep 5
        continue
    else
        if [ -f /tmp/standby ]; then
            standby_since=`stat --format %z /tmp/standby`
            rm -f /tmp/standby
            # Turn on LED
            echo 255 > /sys/class/leds/led/brightness
            currentTime=`date +%s`
            timeInStandby=`expr $currentTime - $enterStandbyTime`
            echo "exit standby after $timeInStandby (since $standby_since)"
            logger "exit standby after $timeInStandby (since $standby_since)"
            if [ "$1" == "debug" ]; then
                    dmesg -c
            fi
        fi

                resetSleepCount

        echo $sleepcount > ${MIN_SINCE_DISK_ACCESS}
        ior_root=`awk -v disk="${rootDisk}" '{if ($3==disk) print $6}' /proc/diskstats`
        iow_root=`awk -v disk="${rootDisk}" '{if ($3==disk) print $10}' /proc/diskstats`
        ior_datavol=`awk -v disk="${dataVolumeDisk}" '{if ($3==disk) print $6}' /proc/diskstats`
        iow_datavol=`awk -v disk="${dataVolumeDisk}" '{if ($3==disk) print $10}' /proc/diskstats`
        if [ "$1" == "debug" ]; then
                echo "Init          ior_datavol=$ior_datavol ior_datavol2=$ior_datavol2"
                echo "              iow_datavol=$iow_datavol iow_datavol2=$iow_datavol2"
                echo "              ior_root=$ior_root       ior_root2=$ior_root2"
                echo "              iow_root=$iow_root       iow_root2=$iow_root2"
                dmesg -c
        fi

        while :; do
            # Wait for 60 seconds
            sleep 60
            ior_root2=`awk -v disk="${rootDisk}" '{if ($3==disk) print $6}' /proc/diskstats`
            iow_root2=`awk -v disk="${rootDisk}" '{if ($3==disk) print $10}' /proc/diskstats`
            ior_datavol2=`awk -v disk="${dataVolumeDisk}" '{if ($3==disk) print $6}' /proc/diskstats`
            iow_datavol2=`awk -v disk="${dataVolumeDisk}" '{if ($3==disk) print $10}' /proc/diskstats`

            # use data volume writes until near sleep threshold, then check all disk writes
            old_sleepcount=sleepcount
            if [ $((sleepcount)) -eq $((rootdisk_thresh)) ] && [ "$ior_root" -eq "$ior_root2" ] && [ "$iow_root" -eq "$iow_root2" ]; then
                sleepcount=$((sleepcount+1))
            elif  [ $((sleepcount)) -lt $((rootdisk_thresh)) ] && [ "$ior_datavol" -eq "$ior_datavol2" ] && [ "$iow_datavol" -eq "$iow_datavol2" ]; then
                sleepcount=$((sleepcount+1))
            else
                resetSleepCount
            fi
            echo $sleepcount > ${MIN_SINCE_DISK_ACCESS}
            if [ "$1" == "debug" ]; then

                [ "$sleepcount" != "0" ] &&  echo "sleepcount: $sleepcount"
                [ "$sleepcount" == "0" ] && echo "Disk activity:"
                echo "... ior_datavol=$ior_datavol      ior_datavol2=$ior_datavol2"
                echo "... iow_datavol=$iow_datavol      iow_datavol2=$iow_datavol2"
                echo "... ior_root=$ior_root    ior_root2=$ior_root2"
                echo "... iow_root=$iow_root    iow_root2=$iow_root2"
                # dmesg -c
            fi
            ior_datavol=$ior_datavol2
            iow_datavol=$iow_datavol2
            ior_root=$ior_root2
            iow_root=$iow_root2

            if [ "$standby_enable" == "enabled" ] && [ "$sleepcount" -eq "$standby_time" ] ; then
                touch /tmp/standby
                enterStandbyTime=`date +%s`
                echo "Enter standby"
                if [ "$1" == "debug" ]; then
                        echo "`date`: Enter standby "
                        dmesg -c > /dev/null
                fi
                for i in ${drivelist[@]}; do
                        hdparm -y $i >/dev/null
                done

                # Turn off LED
                echo 0 > /sys/class/leds/led/brightness
                sleep 5
                break
            fi
        done
    fi
done

The script requires an additional config file:

# /usr/local/etc/standby.conf
standby_enable=enabled
standby_time=17

Of course putting the disk to sleep only makes sense if regular write operations don't go to disk but to RAM. After having used a custom RamLog script before, folder2ram is the preferred solution I now use. My folder2ram.conf looks like this:

#<type>         <mount point>                   <options>
tmpfs           /var/log
tmpfs           /var/tmp
tmpfs           /var/spool
tmpfs           /var/cache/samba
tmpfs           /var/lib/smartmontools
tmpfs           /var/lib/systemd
tmpfs           /var/lib/samba
tmpfs           /var/lib/dhcp
For Debian Bullseye make sure to remove the /var/log/journal folder!

7 Kommentare:

  1. Cool. What do you use it for?
    As I understand, you can set the brightness to XX%
    Does that mean it will permanently emit light at XX%?
    Or can it be made to show what is going on in the system. Like when the HD is busy? Or flash when errors occur?
    Could it be made flashing when someone tries to log in via SSH?

    AntwortenLöschen
    Antworten
    1. There is some LED trigger drivers:
      * heartbeat
      * transient
      * oneshot
      * timer

      Neither the network driver nor the SATA driver support trigger actions. That means you'll have to write your own userspace program to manage the LED.

      All I wanted to do is turn it off in order to save some power :D

      Löschen
    2. I updated the post to demonstrate how the LED can be turned on/off by the monitorio script.

      Löschen
  2. Hello :)
    It seems that i dont have the "/sys/class/leds/led/brightness" mine stops at "/sys/class/leds/" where did i go wrong ?

    ty in advance

    AntwortenLöschen
    Antworten
    1. This is actually what the DTB change is supposed to do. Did you install the modified one from above?
      See "Installing the Kernel" here https://blog.loetzimmer.de/2021/01/debian-on-wd-my-cloud-home-single-bay.html

      Löschen
  3. how to make the script run on boot ?

    AntwortenLöschen
    Antworten
    1. If you don't want to create a SystemD unit file for it

      #/etc/systemd/system/monitorio.service
      [Unit]
      Description=Disk Sleep Daemon
      After=local-fs.target

      [Service]
      ExecStart=/usr/local/sbin/monitorio.sh
      ExecStop=/usr/bin/killall monitorio.sh

      [Install]
      WantedBy=multi-user.target

      you could put it in /etc/rc.local

      Löschen