You've already forked UGREEN-NAS
This commit is contained in:
1
root/DEBIAN/conffiles
Normal file
1
root/DEBIAN/conffiles
Normal file
@@ -0,0 +1 @@
|
||||
/etc/ugreen-nas.conf
|
||||
11
root/DEBIAN/control
Normal file
11
root/DEBIAN/control
Normal file
@@ -0,0 +1,11 @@
|
||||
Package: ugreen-nas
|
||||
Version: 1.0.0
|
||||
Architecture:
|
||||
Installed-Size:
|
||||
Depends: bc, dkms, dmidecode, i2c-tools, iproute2, iputils-ping
|
||||
Recommends: linux-headers-amd64, smartmontools, zfsutils-linux
|
||||
Priority: optional
|
||||
Section: misc
|
||||
Homepage: http://www.privlab.it
|
||||
Maintainer: PrivLab <hostmaster@privlab.it>
|
||||
Description: UGREEN NAS LED Controller
|
||||
31
root/DEBIAN/postinst
Normal file
31
root/DEBIAN/postinst
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/sh
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
if [ -d '/run/systemd/system' ]; then
|
||||
/usr/bin/systemctl --system daemon-reload > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
fi
|
||||
|
||||
case "${1}" in
|
||||
configure)
|
||||
if [ -x '/usr/bin/deb-systemd-helper' ]; then
|
||||
/usr/bin/deb-systemd-helper unmask 'ugreen-led-init.service' > '/dev/null' || true
|
||||
/usr/bin/deb-systemd-helper unmask 'ugreen-led-power.service' > '/dev/null' || true
|
||||
/usr/bin/deb-systemd-helper unmask 'ugreen-led-disk.service' > '/dev/null' || true
|
||||
fi
|
||||
if /usr/bin/deb-systemd-helper --quiet was-enabled '.service'; then
|
||||
/usr/bin/deb-systemd-helper enable 'ugreen-led-init.service' > '/dev/null' || true
|
||||
/usr/bin/deb-systemd-helper enable 'ugreen-led-power.service' > '/dev/null' || true
|
||||
/usr/bin/deb-systemd-helper enable 'ugreen-led-disk.service' > '/dev/null' || true
|
||||
else
|
||||
/usr/bin/deb-systemd-helper update-state 'ugreen-led-init.service' > '/dev/null' || true
|
||||
/usr/bin/deb-systemd-helper update-state 'ugreen-led-power.service' > '/dev/null' || true
|
||||
/usr/bin/deb-systemd-helper update-state 'ugreen-led-disk.service' > '/dev/null' || true
|
||||
fi
|
||||
/usr/sbin/dkms add -m 'led-ugreen' -v 'VERSION'
|
||||
/usr/sbin/dkms build --force -m 'led-ugreen' -v 'VERSION'
|
||||
/usr/sbin/dkms install --force -m 'led-ugreen' -v 'VERSION'
|
||||
;;
|
||||
esac
|
||||
28
root/DEBIAN/postrm
Normal file
28
root/DEBIAN/postrm
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/sh
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
if [ -d '/run/systemd/system' ]; then
|
||||
/usr/bin/systemctl --system daemon-reload > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
fi
|
||||
|
||||
case "${1}" in
|
||||
remove|purge)
|
||||
if [ -x '/usr/bin/deb-systemd-helper' ]; then
|
||||
/usr/bin/deb-systemd-helper purge 'ugreen-led-init.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper unmask 'ugreen-led-init.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper purge 'ugreen-led-power.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper unmask 'ugreen-led-power.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper purge 'ugreen-led-disk.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper unmask 'ugreen-led-disk.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
25
root/DEBIAN/prerm
Normal file
25
root/DEBIAN/prerm
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/sh
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
case "${1}" in
|
||||
remove)
|
||||
if [ -x '/usr/bin/deb-systemd-invoke' ]; then
|
||||
/usr/bin/deb-systemd-helper stop 'ugreen-led-init.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper disable 'ugreen-led-init.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper stop 'ugreen-led-power.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper disable 'ugreen-led-power.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper stop 'ugreen-led-disk.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
/usr/bin/deb-systemd-helper disable 'ugreen-led-disk.service' > '/dev/null' 2>&1 || \
|
||||
/usr/bin/true
|
||||
fi
|
||||
/usr/sbin/dkms remove -m 'led-ugreen' -v 'VERSION' --all
|
||||
/usr/bin/rm --force --recursive '/usr/src/led-ugreen-VERSION'
|
||||
;;
|
||||
esac
|
||||
99
root/etc/ugreen-nas.conf
Normal file
99
root/etc/ugreen-nas.conf
Normal file
@@ -0,0 +1,99 @@
|
||||
#
|
||||
# UGREEN NAS Configuration
|
||||
#
|
||||
|
||||
# Option: [disk_serial]
|
||||
# Notes: The serial numbers of disks.
|
||||
# DISK_SERIAL='S/N S/N S/N S/N S/N S/N S/N S/N'
|
||||
# S/N -- disk1
|
||||
# S/N -- disk2
|
||||
# S/N -- disk3
|
||||
# S/N -- disk4
|
||||
# S/N -- disk5
|
||||
# S/N -- disk6
|
||||
# S/N -- disk7
|
||||
# S/N -- disk8
|
||||
# Values: [ SERIAL NUMBER ] Default: ''
|
||||
DISK_SERIAL=''
|
||||
|
||||
# Option: [disk_zfs]
|
||||
# Notes: Check the zpool health.
|
||||
# Values: [ TRUE | FALSE ] Default: 'false'
|
||||
DISK_ZFS='false'
|
||||
|
||||
# Option: [disk_smart]
|
||||
# Notes: Check the disk health by smartctl.
|
||||
# Values: [ TRUE | FALSE ] Default: 'false'
|
||||
DISK_SMART='false'
|
||||
|
||||
# Option: [disk_led_color_standby]
|
||||
# Notes: Set the brightness of the disks LED.
|
||||
# Values: [ NUMBER (0-255) ] Default: '255' (100%)
|
||||
DISK_LED_BRIGHTNESS='255'
|
||||
|
||||
# Option: [disk_led_color_standby]
|
||||
# Notes: Sets the color value of disk standby LED.
|
||||
# Values: [ RGB (255 255 255) ] Default: '0 0 255'
|
||||
DISK_LED_COLOR_STANDBY='0 0 255'
|
||||
|
||||
# Option: [disk_led_color_health]
|
||||
# Notes: Sets the color value of disk health LED.
|
||||
# Values: [ RGB (255 255 255) ] Default: '0 255 0'
|
||||
DISK_LED_COLOR_HEALTH='0 255 0'
|
||||
|
||||
# Option: [disk_led_color_failed]
|
||||
# Notes: Sets the color value of disk failed LED.
|
||||
# Values: [ RGB (255 255 255) ] Default: '255 0 0'
|
||||
DISK_LED_COLOR_FAILED='255 0 0'
|
||||
|
||||
# Option: [net_led_tx_rx]
|
||||
# Notes: Activates a visual blinking of the network LED
|
||||
# when sending or receiving data.
|
||||
# Values: [ TRUE | FALSE ] Default: 'true'
|
||||
NET_LED_TX_RX='true'
|
||||
|
||||
# Option: [net_led_brightness]
|
||||
# Notes: Set the brightness of the network LED.
|
||||
# Values: [ NUMBER (0-255) ] Default: '255'
|
||||
NET_LED_BRIGHTNESS='255'
|
||||
|
||||
# Option: [net_speed_min]
|
||||
# Notes: Set the speed at which the option
|
||||
# [net_led_color_speed_min] is used
|
||||
# for the color.
|
||||
# Values: [ NUMBER ] Default: '0'
|
||||
NET_SPEED_MIN='0'
|
||||
|
||||
# Option: [net_led_color_speed_min]
|
||||
# Notes: Sets the color value of network LED,
|
||||
# used at minimum speed.
|
||||
# Values: [ RGB (255 255 255) ] Default: '255 0 0'
|
||||
NET_LED_COLOR_SPEED_MIN='255 0 0'
|
||||
|
||||
# Option: [net_speed_max]
|
||||
# Notes: Set the speed at which the option
|
||||
# [net_led_color_speed_max] is used
|
||||
# for the color.
|
||||
# Values: [ NUMBER ] Default: '10000'
|
||||
NET_SPEED_MAX='10000'
|
||||
|
||||
# Option: [net_led_color_speed_max]
|
||||
# Notes: Sets the color value of network LED,
|
||||
# used at maximum speed.
|
||||
# Values: [ RGB (255 255 255) ] Default: '0 255 0'
|
||||
NET_LED_COLOR_SPEED_MAX='0 255 0'
|
||||
|
||||
# Option: [power_led_blink_pattern]
|
||||
# Notes: Set the flashing pattern of the power button.
|
||||
# Values: [ NONE | BREATH | BLINK ] Default: 'none'
|
||||
POWER_LED_BLINK_PATTERN='none'
|
||||
|
||||
# Option: [power_led_brightness]
|
||||
# Notes: Set the brightness of the power button.
|
||||
# Values: [ NUMBER (0-255) ] Default: '255' (100%)
|
||||
POWER_LED_BRIGHTNESS='255'
|
||||
|
||||
# Option: [power_led_color]
|
||||
# Notes: Set the color of the power button.
|
||||
# Values: [ RGB (255 255 255) ] Default: '255 255 255' (White)
|
||||
POWER_LED_COLOR='255 255 255'
|
||||
11
root/lib/systemd/system/ugreen-led-disk.service
Normal file
11
root/lib/systemd/system/ugreen-led-disk.service
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=UGREEN NAS Disk IO Monitor
|
||||
ConditionPathExists=/etc/ugreen-nas.conf
|
||||
After=ugreen-led-init.service
|
||||
Requires=ugreen-led-init.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/ugreen-led-disk
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
10
root/lib/systemd/system/ugreen-led-init.service
Normal file
10
root/lib/systemd/system/ugreen-led-init.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=UGREEN NAS LED Controller
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/sbin/ugreen-led-init
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
11
root/lib/systemd/system/ugreen-led-netdev@.service
Normal file
11
root/lib/systemd/system/ugreen-led-netdev@.service
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=UGREEN NAS Network Device (%i) Monitor
|
||||
ConditionPathExists=/etc/ugreen-nas.conf
|
||||
After=ugreen-led-init.service
|
||||
Requires=ugreen-led-init.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/ugreen-led-netdev %i
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
13
root/lib/systemd/system/ugreen-led-power.service
Normal file
13
root/lib/systemd/system/ugreen-led-power.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=UGREEN NAS Power LED Controller
|
||||
ConditionPathExists=/etc/ugreen-nas.conf
|
||||
After=ugreen-led-init.service
|
||||
Requires=ugreen-led-init.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/sbin/ugreen-led-power
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
260
root/usr/sbin/ugreen-led-disk
Executable file
260
root/usr/sbin/ugreen-led-disk
Executable file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Options Section
|
||||
#
|
||||
###
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Variables Section
|
||||
#
|
||||
###
|
||||
|
||||
if [[ -f '/etc/ugreen-nas.conf' ]]; then
|
||||
source '/etc/ugreen-nas.conf'
|
||||
fi
|
||||
|
||||
DISK_ZFS="${DISK_ZFS:='false'}"
|
||||
DISK_SMART="${DISK_SMART:='false'}"
|
||||
DISK_LED_BRIGHTNESS="${DISK_LED_BRIGHTNESS:='255'}"
|
||||
DISK_LED_COLOR_HEALTH="${DISK_LED_COLOR_HEALTH:='0 255 0'}"
|
||||
DISK_LED_COLOR_FAILED="${DISK_LED_COLOR_FAILED:='255 0 0'}"
|
||||
DISK_LED_COLOR_STANDBY="${DISK_LED_COLOR_STANDBY:='0 0 255'}"
|
||||
|
||||
MODEL=$(/usr/sbin/dmidecode --string 'system-product-name')
|
||||
SERIAL_MAP=(${DISK_SERIAL})
|
||||
|
||||
declare -A DEVICES
|
||||
declare -A DEV_MAP
|
||||
declare -A DEV_LED_MAP
|
||||
declare -A ZFS_LED_MAP
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Function Section
|
||||
#
|
||||
###
|
||||
|
||||
function trap_exit () {
|
||||
if [[ -n "${PID_ZFS}" ]]; then
|
||||
/usr/bin/kill "${PID_ZFS}" &> '/dev/null'
|
||||
fi
|
||||
if [[ -n "${PID_SMART}" ]]; then
|
||||
/usr/bin/kill "${PID_SMART}" &> '/dev/null'
|
||||
fi
|
||||
if [[ -n "${PID_DISK}" ]]; then
|
||||
/usr/bin/kill "${PID_DISK}" &> '/dev/null'
|
||||
fi
|
||||
if [[ -n "${PID_DISK_STANDBY}" ]]; then
|
||||
/usr/bin/kill "${PID_DISK_STANDBY}" &> '/dev/null'
|
||||
fi
|
||||
}
|
||||
|
||||
function load_module () {
|
||||
MODULE="${1}"
|
||||
if ! /usr/sbin/lsmod | /usr/bin/grep --quiet "${MODULE}"; then
|
||||
/usr/sbin/modprobe "${MODULE}"
|
||||
fi
|
||||
}
|
||||
|
||||
function check_disk_health () {
|
||||
if [[ "${1}" == "${DISK_LED_COLOR_HEALTH}" || \
|
||||
"${1}" == "${DISK_LED_COLOR_STANDBY}" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function check_disk_zfs () {
|
||||
while true; do
|
||||
while read LINE; do
|
||||
LINE=(${LINE})
|
||||
DEV_NAME="${LINE[0]}"
|
||||
DEV_STATE="${LINE[1]}"
|
||||
if [[ -v "ZFS_LED_MAP[${DEV_NAME}]" ]]; then
|
||||
LED="${ZFS_LED_MAP[${DEV_NAME}]}"
|
||||
LED_COLOR=$(/usr/bin/cat "/sys/class/leds/${LED}/color")
|
||||
if ! check_disk_health "${LED_COLOR}"; then
|
||||
continue;
|
||||
fi
|
||||
if [[ "${DEV_STATE}" != "ONLINE" ]]; then
|
||||
/usr/bin/echo "${COLOR_ZPOOL_FAIL}" > "/sys/class/leds/${LED}/color"
|
||||
fi
|
||||
fi
|
||||
done <<< $(/usr/bin/zpool status -L | /usr/bin/grep --extended-regexp ^\\s*\(nvme\|dm\))
|
||||
/usr/bin/sleep '15s'
|
||||
done
|
||||
}
|
||||
|
||||
function disk_parameters () {
|
||||
for LED in "${!DEVICES[@]}"; do
|
||||
/usr/bin/echo "${DEVICES[${LED}]} ${LED}"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Runtime Environment
|
||||
#
|
||||
###
|
||||
|
||||
if [[ -z "${DISK_SERIAL}" ]]; then
|
||||
/usr/bin/echo "The serial number of the hard drives could not be found in the configuration."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
trap 'trap_exit' EXIT
|
||||
|
||||
case "${MODEL}" in
|
||||
DXP2800*)
|
||||
LED_MAP=(disk1 disk2)
|
||||
;;
|
||||
DH4300*)
|
||||
LED_MAP=(disk1 disk2 disk3 disk4)
|
||||
;;
|
||||
DXP4800*)
|
||||
LED_MAP=(disk1 disk2 disk3 disk4)
|
||||
;;
|
||||
DXP6800*)
|
||||
LED_MAP=(disk1 disk2 disk3 disk4 disk5 disk6)
|
||||
;;
|
||||
DXP8800*)
|
||||
LED_MAP=(disk1 disk2 disk3 disk4 disk5 disk6 disk7 disk8)
|
||||
;;
|
||||
*)
|
||||
/usr/bin/echo "The UGREEN NAS ${MODEL} is not supported."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
load_module 'ledtrig_oneshot'
|
||||
|
||||
while read LINE; do
|
||||
LINE=(${LINE})
|
||||
SERIAL="${LINE[1]}"
|
||||
DISK="${LINE[0]}"
|
||||
DEV_MAP[${SERIAL}]="${DISK}"
|
||||
done <<< $(/usr/bin/lsblk --all --output 'name,serial,tran' | /usr/bin/grep --extended-regexp '^nvme[0-9]+n[0-9]+ ')
|
||||
|
||||
for MAP in "${!LED_MAP[@]}"; do
|
||||
LED="${LED_MAP[MAP]}"
|
||||
if [[ -d "/sys/class/leds/${LED}" ]]; then
|
||||
/usr/bin/echo 'oneshot' > "/sys/class/leds/${LED}/trigger"
|
||||
/usr/bin/echo '1' > "/sys/class/leds/${LED}/invert"
|
||||
/usr/bin/echo '100' > "/sys/class/leds/${LED}/delay_on"
|
||||
/usr/bin/echo '100' > "/sys/class/leds/${LED}/delay_off"
|
||||
/usr/bin/echo "${DISK_LED_COLOR_HEALTH}" > "/sys/class/leds/${LED}/color"
|
||||
/usr/bin/echo "${DISK_LED_BRIGHTNESS}" > "/sys/class/leds/${LED}/brightness"
|
||||
TMP_STR="SERIAL_MAP[@]"
|
||||
TMP_ARR=("${!TMP_STR}")
|
||||
if [[ -v "DEV_MAP[${TMP_ARR[MAP]}]" ]]; then
|
||||
DEV="${DEV_MAP[${TMP_ARR[MAP]}]}"
|
||||
if [[ -f "/sys/class/block/${DEV}/stat" ]]; then
|
||||
DEVICES[${LED}]="${DEV}"
|
||||
DEV_LED_MAP[${DEV}]="${LED}"
|
||||
else
|
||||
/usr/bin/echo '0' > "/sys/class/leds/${LED}/brightness"
|
||||
/usr/bin/echo 'none' > "/sys/class/leds/${LED}/trigger"
|
||||
fi
|
||||
else
|
||||
/usr/bin/echo '0' > "/sys/class/leds/${LED}/brightness"
|
||||
/usr/bin/echo 'none' > "/sys/class/leds/${LED}/trigger"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${DISK_ZFS}" == 'true' ]]; then
|
||||
while read LINE; do
|
||||
LINE=(${LINE})
|
||||
DEV_NAME="${LINE[0]}"
|
||||
SCSI_DEV_NAME="unknown"
|
||||
case "${DEV_NAME}" in
|
||||
nvme*)
|
||||
SCSI_DEV_NAME="${DEV_NAME}"
|
||||
;;
|
||||
dm*)
|
||||
DM_SLAVES=($(/usr/bin/ls "/sys/block/${DEV_NAME}/slaves"))
|
||||
SCSI_DEV_NAME="${DM_SLAVES[0]}"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
if [[ -v "DEV_LED_MAP[${SCSI_DEV_NAME}]" ]]; then
|
||||
ZFS_LED_MAP[${DEV_NAME}]="${DEV_LED_MAP[${SCSI_DEV_NAME}]}"
|
||||
fi
|
||||
done <<< $(/usr/bin/zpool status -L | /usr/bin/grep --extended-regexp ^\\s*\(nvme\|dm\))
|
||||
check_disk_zfs &
|
||||
PID_ZFS="${!}"
|
||||
fi
|
||||
|
||||
if [[ "${DISK_SMART}" == 'true' ]]; then
|
||||
(
|
||||
while true; do
|
||||
for LED in "${!DEVICES[@]}"; do
|
||||
LED_COLOR=$(/usr/bin/cat "/sys/class/leds/${LED}/color")
|
||||
if ! check_disk_health "${LED_COLOR}"; then
|
||||
continue;
|
||||
fi
|
||||
DEV="${DEVICES[${LED}]}"
|
||||
/usr/sbin/smartctl --health "/dev/${DEV}" --nocheck='standby,0' &> '/dev/null'
|
||||
RETURN="${?}"
|
||||
if (( "${RETURN}" & ~32 )); then
|
||||
/usr/bin/echo "${DISK_LED_COLOR_FAILED}" > "/sys/class/leds/${LED}/color"
|
||||
continue
|
||||
fi
|
||||
done
|
||||
/usr/bin/sleep '900s'
|
||||
done
|
||||
) &
|
||||
PID_SMART="${!}"
|
||||
fi
|
||||
|
||||
(
|
||||
while true; do
|
||||
for LED in "${!DEVICES[@]}"; do
|
||||
DEV="${DEVICES[${LED}]}"
|
||||
LED_COLOR=$(/usr/bin/cat "/sys/class/leds/${LED}/color")
|
||||
if ! check_disk_health "${LED_COLOR}"; then
|
||||
continue;
|
||||
fi
|
||||
if [[ ! -f "/sys/class/block/${DEV}/stat" ]]; then
|
||||
/usr/bin/echo "${DISK_LED_COLOR_FAILED}" > "/sys/class/leds/${LED}/color"
|
||||
continue
|
||||
fi
|
||||
done
|
||||
/usr/bin/sleep '15s'
|
||||
done
|
||||
) &
|
||||
PID_DISK="${!}"
|
||||
|
||||
if [[ -x '/usr/sbin/ugreen-led-disk-standby' ]]; then
|
||||
/usr/sbin/ugreen-led-disk-standby '15' "${DISK_LED_COLOR_STANDBY}" "${DISK_LED_COLOR_HEALTH}" $(disk_parameters) &
|
||||
PID_DISK_STANDBY="${!}"
|
||||
fi
|
||||
|
||||
if [[ -x '/usr/sbin/ugreen-led-disk-blink' ]]; then
|
||||
/usr/sbin/ugreen-led-disk-blink '0.1' $(disk_parameters)
|
||||
else
|
||||
declare -A DISKIO_RW
|
||||
while true; do
|
||||
for LED in "${!DEVICES[@]}"; do
|
||||
DISKIO_RW_CMP=$(/usr/bin/cat "/sys/block/${DEVICES[${LED}]}/stat" 2> '/dev/null')
|
||||
if [[ "${DISKIO_RW[${LED}]}" != "${DISKIO_RW_COMP}" ]]; then
|
||||
/usr/bin/echo '1' > "/sys/class/leds/${LED}/shot"
|
||||
fi
|
||||
DISKIO_RW[${LED}]="${DISKIO_RW_CMP}"
|
||||
done
|
||||
/usr/bin/sleep '0.1s'
|
||||
done
|
||||
fi
|
||||
51
root/usr/sbin/ugreen-led-init
Executable file
51
root/usr/sbin/ugreen-led-init
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Options Section
|
||||
#
|
||||
###
|
||||
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Function Section
|
||||
#
|
||||
###
|
||||
|
||||
function load_module () {
|
||||
MODULE="${1}"
|
||||
if ! /usr/sbin/lsmod | /usr/bin/grep --quiet "${MODULE}"; then
|
||||
/usr/sbin/modprobe "${MODULE}"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Runtime Environment
|
||||
#
|
||||
###
|
||||
|
||||
load_module 'i2c_dev'
|
||||
load_module 'led_ugreen'
|
||||
|
||||
I2C_DEV=$(/usr/sbin/i2cdetect -l | /usr/bin/grep 'SMBus I801 adapter' | grep --perl-regexp --only-matching 'i2c-\d+')
|
||||
|
||||
if [[ -n "${I2C_DEV}" ]]; then
|
||||
DEV_PATH="/sys/bus/i2c/devices/$I2C_DEV/${I2C_DEV/i2c-/}-003a"
|
||||
if [[ -d "${DEV_PATH}" ]]; then
|
||||
DEV_NAME=$(/usr/bin/cat "${DEV_PATH}/name")
|
||||
fi
|
||||
if [[ ! -d "${DEV_PATH}" ]]; then
|
||||
/usr/bin/echo 'led-ugreen 0x3a' > "/sys/bus/i2c/devices/${I2C_DEV}/new_device"
|
||||
elif [[ "${DEV_NAME}" != 'led-ugreen' ]]; then
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
87
root/usr/sbin/ugreen-led-netdev
Executable file
87
root/usr/sbin/ugreen-led-netdev
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Options Section
|
||||
#
|
||||
###
|
||||
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Variables Section
|
||||
#
|
||||
###
|
||||
|
||||
if [[ -f '/etc/ugreen-nas.conf' ]]; then
|
||||
source '/etc/ugreen-nas.conf'
|
||||
fi
|
||||
|
||||
NET_DEVICE="${1}"
|
||||
NET_SPEED_MIN=${NET_SPEED_MIN:='0'}
|
||||
NET_SPEED_MAX=${NET_SPEED_MAX:='10000'}
|
||||
NET_LED_COLOR_SPEED_MIN=${NET_LED_COLOR_SPEED_MIN:='255 0 0'}
|
||||
NET_LED_COLOR_SPEED_MAX=${NET_LED_COLOR_SPEED_MAX:='0 255 0'}
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Function Section
|
||||
#
|
||||
###
|
||||
|
||||
function load_module () {
|
||||
local MODULE
|
||||
MODULE="${1}"
|
||||
if ! /usr/sbin/lsmod | /usr/bin/grep --quiet "${MODULE}"; then
|
||||
/usr/sbin/modprobe "${MODULE}"
|
||||
fi
|
||||
}
|
||||
|
||||
function set_net_led_color () {
|
||||
local IFS
|
||||
NET_SPEED=$(/usr/bin/cat "/sys/class/net/${NET_DEVICE}/speed")
|
||||
NET_SPEED_PERCENTAGE=$(/usr/bin/bc --mathlib <<< "(${NET_SPEED} - ${NET_SPEED_MIN}) / (${NET_SPEED_MAX} - ${NET_SPEED_MIN})")
|
||||
IFS=' '
|
||||
read -r RGB_RED_MIN RGB_GREEN_MIN RGB_BLUE_MIN <<< "${NET_LED_COLOR_SPEED_MIN}"
|
||||
read -r RGB_RED_MAX RGB_GREEN_MAX RGB_BLUE_MAX <<< "${NET_LED_COLOR_SPEED_MAX}"
|
||||
RGB_RED=$(/usr/bin/bc --mathlib <<< "scale=0; ${RGB_RED_MIN} + ${NET_SPEED_PERCENTAGE} * (${RGB_RED_MAX} - ${RGB_RED_MIN})/1")
|
||||
RGB_GREEN=$(/usr/bin/bc --mathlib <<< "scale=0; ${RGB_GREEN_MIN} + ${NET_SPEED_PERCENTAGE} * (${RGB_GREEN_MAX} - ${RGB_GREEN_MIN})/1")
|
||||
RGB_BLUE=$(/usr/bin/bc --mathlib <<< "scale=0; ${RGB_BLUE_MIN} + ${NET_SPEED_PERCENTAGE} * (${RGB_BLUE_MAX} - ${RGB_BLUE_MIN})/1")
|
||||
/usr/bin/echo "${RGB_RED} ${RGB_GREEN} ${RGB_BLUE}" > '/sys/class/leds/netdev/color'
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Runtime Environment
|
||||
#
|
||||
###
|
||||
|
||||
load_module 'ledtrig_netdev'
|
||||
|
||||
/usr/bin/echo 'netdev' > '/sys/class/leds/netdev/trigger'
|
||||
/usr/bin/echo "${NET_DEVICE}" > '/sys/class/leds/netdev/device_name'
|
||||
/usr/bin/echo '1' > '/sys/class/leds/netdev/link'
|
||||
if [[ "${NET_LED_TX_RX}" == 'true' ]]; then
|
||||
/usr/bin/echo '1' > '/sys/class/leds/netdev/tx'
|
||||
/usr/bin/echo '1' > '/sys/class/leds/netdev/rx'
|
||||
fi
|
||||
/usr/bin/echo '250' > '/sys/class/leds/netdev/interval'
|
||||
/usr/bin/echo '255 255 255' > '/sys/class/leds/netdev/color'
|
||||
/usr/bin/echo "${NET_LED_BRIGHTNESS}" > '/sys/class/leds/netdev/brightness'
|
||||
|
||||
while true; do
|
||||
GATEWAY=$(/usr/bin/ip route | /usr/bin/mawk '/default/ { print $3 }')
|
||||
if /usr/bin/ping -q -c '1' -W '1' "${GATEWAY}" &> '/dev/null'; then
|
||||
set_net_led_color
|
||||
else
|
||||
/usr/bin/echo '255 0 0' > '/sys/class/leds/netdev/color'
|
||||
fi
|
||||
/usr/bin/sleep '1m'
|
||||
done
|
||||
40
root/usr/sbin/ugreen-led-power
Normal file
40
root/usr/sbin/ugreen-led-power
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Options Section
|
||||
#
|
||||
###
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Variables Section
|
||||
#
|
||||
###
|
||||
|
||||
if [[ -f '/etc/ugreen-nas.conf' ]]; then
|
||||
source '/etc/ugreen-nas.conf'
|
||||
fi
|
||||
|
||||
POWER_LED_BLINK_PATTERN=${POWER_LED_BLINK_PATTERN:='none'}
|
||||
POWER_LED_BRIGHTNESS=${POWER_LED_BRIGHTNESS:='255'}
|
||||
POWER_LED_COLOR=${POWER_LED_COLOR:='255 255 255'}
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Runtime Environment
|
||||
#
|
||||
###
|
||||
|
||||
|
||||
if [[ -d '/sys/class/leds/power' ]]; then
|
||||
/usr/bin/echo "${POWER_LED_BLINK_PATTERN}" > '/sys/class/leds/power/blink_type'
|
||||
/usr/bin/echo "${POWER_LED_BRIGHTNESS}" > '/sys/class/leds/power/brightness'
|
||||
/usr/bin/echo "${POWER_LED_COLOR}" > '/sys/class/leds/power/color'
|
||||
fi
|
||||
16
root/usr/src/led-ugreen/Makefile
Normal file
16
root/usr/src/led-ugreen/Makefile
Normal file
@@ -0,0 +1,16 @@
|
||||
TARGET = led-ugreen
|
||||
obj-m += led-ugreen.o
|
||||
ccflags-y := -std=gnu11
|
||||
|
||||
KERNELRELEASE ?= $(shell uname -r)
|
||||
KDIR ?= /lib/modules/$(KERNELRELEASE)/build
|
||||
INSTALL_MOD_PATH ?= /
|
||||
|
||||
all:
|
||||
make -C $(KDIR) M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
make -C $(KDIR) M=$(PWD) clean
|
||||
|
||||
install:
|
||||
make -C $(KDIR) M=$(PWD) INSTALL_MOD_PATH=$(INSTALL_MOD_PATH) modules_install
|
||||
7
root/usr/src/led-ugreen/dkms.conf
Normal file
7
root/usr/src/led-ugreen/dkms.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
PACKAGE_NAME="led-ugreen"
|
||||
PACKAGE_VERSION="-VERSION-"
|
||||
MAKE[0]="make"
|
||||
CLEAN="make clean"
|
||||
BUILT_MODULE_NAME[0]="led-ugreen"
|
||||
DEST_MODULE_LOCATION[0]="/kernel/drivers/leds"
|
||||
AUTOINSTALL="yes"
|
||||
603
root/usr/src/led-ugreen/led-ugreen.c
Normal file
603
root/usr/src/led-ugreen/led-ugreen.c
Normal file
@@ -0,0 +1,603 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* UGREEN NAS LED Driver
|
||||
*
|
||||
* Copyright (C) 2025
|
||||
* Author: PrivLab <hostmaster@privlab.it>
|
||||
*/
|
||||
|
||||
#include "linux/stddef.h"
|
||||
#include <linux/init.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include "led-ugreen.h"
|
||||
|
||||
#ifdef pr_fmt
|
||||
#undef pr_fmt
|
||||
#endif
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
static bool verbose = false;
|
||||
module_param(verbose, bool, 0644);
|
||||
MODULE_PARM_DESC(verbose, "Enable verbose output");
|
||||
|
||||
static struct ugreen_led_state *lcdev_to_ugreen_led_state(struct led_classdev *led_cdev) {
|
||||
return container_of(led_cdev, struct ugreen_led_state, cdev);
|
||||
}
|
||||
|
||||
static int ugreen_led_change_state(
|
||||
struct i2c_client *client,
|
||||
u8 led_id,
|
||||
u8 command,
|
||||
u8 param1,
|
||||
u8 param2,
|
||||
u8 param3,
|
||||
u8 param4
|
||||
) {
|
||||
// compute the checksum
|
||||
u16 cksum = 0xa1 + (u16)command + param1 + param2 + param3 + param4;
|
||||
|
||||
// construct the write buffer
|
||||
u8 buf[12] = {
|
||||
led_id,
|
||||
0xa0, 0x01, 0x00, 0x00,
|
||||
command,
|
||||
param1, param2, param3, param4,
|
||||
(u8)((cksum >> 8) & 0xff),
|
||||
(u8)(cksum & 0xff)
|
||||
};
|
||||
|
||||
// write the buffer to the I2C device by sending block data
|
||||
s32 rc = i2c_smbus_write_i2c_block_data(client, led_id, 12, buf);
|
||||
|
||||
// check the return code
|
||||
if (rc < 0) {
|
||||
pr_err("%s: i2c_smbus_write_i2c_block_data failed with id %d,"
|
||||
"cmd 0x%x, params (0x%x, 0x%x, 0x%x, 0x%x), err %d",
|
||||
__func__, led_id, command, param1, param2, param3, param4, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get the state of the DX4600 LEDs
|
||||
static int ugreen_led_get_state(
|
||||
struct i2c_client *client,
|
||||
u8 led_id,
|
||||
struct ugreen_led_state *state
|
||||
) {
|
||||
if (!state) {
|
||||
pr_err("%s: invalid state buffer", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// read the state of the LED from the I2C device
|
||||
u8 buf[11];
|
||||
s32 rc = i2c_smbus_read_i2c_block_data(client, 0x81 + led_id, 11, (u8 *)buf);
|
||||
|
||||
// check the return code
|
||||
if (rc < 0) {
|
||||
pr_err("%s: i2c_smbus_read_i2c_block_data failed with id %d, err %d",
|
||||
__func__, led_id, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// compute the checksum of received data
|
||||
u16 sum = 0;
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
sum += buf[i];
|
||||
}
|
||||
|
||||
// check the checksum
|
||||
if (sum == 0 || (sum != (((u16)buf[9] << 8) | buf[10]))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parse the state of the LED
|
||||
state->status = buf[0];
|
||||
state->brightness = buf[1];
|
||||
state->r = buf[2];
|
||||
state->g = buf[3];
|
||||
state->b = buf[4];
|
||||
u16 t_hight = (((int)buf[5]) << 8) | buf[6];
|
||||
u16 t_low = (((int)buf[7]) << 8) | buf[8];
|
||||
state->t_on = t_low;
|
||||
state->t_cycle = t_hight;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ugreen_led_get_last_command_status(struct i2c_client *client) {
|
||||
|
||||
// read the status byte from the I2C device
|
||||
s32 rc = i2c_smbus_read_byte_data(client, 0x80);
|
||||
|
||||
// check the return code
|
||||
if (rc < 0) {
|
||||
pr_err("%s: i2c_smbus_read_byte_data failed with err %d", __func__, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
return rc == 1;
|
||||
}
|
||||
|
||||
static int ugreen_led_change_state_robust(
|
||||
struct i2c_client *client,
|
||||
u8 led_id,
|
||||
u8 command,
|
||||
u8 param1,
|
||||
u8 param2,
|
||||
u8 param3,
|
||||
u8 param4
|
||||
) {
|
||||
int rc = 0;
|
||||
for (int i = 0; i < UGREEN_LED_CHANGE_STATE_RETRY_COUNT; ++i) {
|
||||
|
||||
if (i == 0) usleep_range(500, 1500);
|
||||
else msleep(30);
|
||||
|
||||
if (i > 0) pr_debug("retrying %d", i);
|
||||
|
||||
rc = ugreen_led_change_state(client, led_id, command, param1, param2, param3, param4);
|
||||
if (rc == 0) {
|
||||
usleep_range(1500, 2500);
|
||||
if (ugreen_led_get_last_command_status(client)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ugreen_led_get_state_robust(
|
||||
struct i2c_client *client,
|
||||
u8 led_id,
|
||||
struct ugreen_led_state *state
|
||||
) {
|
||||
int rc = 0;
|
||||
for (int i = 0; i < UGREEN_LED_CHANGE_STATE_RETRY_COUNT; ++i) {
|
||||
|
||||
if (i == 0) usleep_range(500, 1500);
|
||||
else msleep(30);
|
||||
|
||||
rc = ugreen_led_get_state(client, led_id, state);
|
||||
if (rc == 0) return 0;
|
||||
}
|
||||
|
||||
state->status = UGREEN_LED_STATE_INVALID;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ugreen_led_turn_on_or_off_unlock(struct ugreen_led_array *priv, u8 led_id, bool on) {
|
||||
|
||||
if (priv->state[led_id].status == (on ? UGREEN_LED_STATE_ON : UGREEN_LED_STATE_OFF)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int rc = ugreen_led_change_state_robust(priv->client, led_id, 0x03, on ? 1 : 0, 0, 0, 0);
|
||||
if (rc == 0) {
|
||||
priv->state[led_id].status = on ? UGREEN_LED_STATE_ON : UGREEN_LED_STATE_OFF;
|
||||
} else if (verbose) {
|
||||
pr_err("failed to turn %d %s", led_id, on ? "on" : "off");
|
||||
}
|
||||
}
|
||||
|
||||
static void ugreen_led_set_brightness_unlock(struct ugreen_led_array *priv, u8 led_id, enum led_brightness brightness) {
|
||||
|
||||
struct ugreen_led_state *state = priv->state + led_id;
|
||||
|
||||
if (brightness == 0) {
|
||||
ugreen_led_turn_on_or_off_unlock(priv, led_id, false);
|
||||
} else {
|
||||
if (state->brightness != brightness) {
|
||||
int rc = ugreen_led_change_state_robust(priv->client, led_id, 0x01, brightness, 0, 0, 0);
|
||||
if (rc == 0) {
|
||||
state->brightness = brightness;
|
||||
} else if (verbose) {
|
||||
pr_err("failed to set brightness of %d to %d", led_id, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
if (state->status == UGREEN_LED_STATE_OFF)
|
||||
ugreen_led_turn_on_or_off_unlock(priv, led_id, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void ugreen_led_set_color_unlock(struct ugreen_led_array *priv, u8 led_id, u8 r, u8 g, u8 b) {
|
||||
|
||||
struct ugreen_led_state *state = priv->state + led_id;
|
||||
|
||||
if (!r && !g && !b) {
|
||||
return ugreen_led_turn_on_or_off_unlock(priv, led_id, false);
|
||||
}
|
||||
|
||||
if (state->r != r || state->g != g || state->b != b) {
|
||||
int rc = ugreen_led_change_state_robust(priv->client, led_id, 0x02, r, g, b, 0);
|
||||
if (rc == 0) {
|
||||
state->r = r;
|
||||
state->g = g;
|
||||
state->b = b;
|
||||
} else if (verbose) {
|
||||
pr_err("failed to set color of %d to 0x%02x%02x%02x", led_id, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ugreen_led_set_blink_or_breath_unlock(struct ugreen_led_array *priv, u8 led_id, u16 t_on, u16 t_cycle, bool is_blink) {
|
||||
|
||||
int rc;
|
||||
struct ugreen_led_state *state = priv->state + led_id;
|
||||
u8 led_status = is_blink ? UGREEN_LED_STATE_BLINK : UGREEN_LED_STATE_BREATH;
|
||||
|
||||
if (state->t_on == t_on && state->t_cycle == t_cycle && state->status == led_status) {
|
||||
rc = 0;
|
||||
} else {
|
||||
rc = ugreen_led_change_state_robust(priv->client, led_id, is_blink ? 0x04 : 0x05,
|
||||
(u8)(t_cycle >> 8), (u8)(t_cycle & 0xff),
|
||||
(u8)(t_on >> 8), (u8)(t_on & 0xff)
|
||||
);
|
||||
|
||||
if (rc == 0) {
|
||||
state->t_on = t_on;
|
||||
state->t_cycle = t_cycle;
|
||||
state->status = led_status;
|
||||
} else if (verbose) {
|
||||
pr_err("failed to set %s of %d to %d %d", is_blink ? "blink" : "breath", led_id, t_on, t_cycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int ugreen_led_set_brightness_blocking(struct led_classdev *cdev, enum led_brightness brightness) {
|
||||
|
||||
struct ugreen_led_state *state = lcdev_to_ugreen_led_state(cdev);
|
||||
struct ugreen_led_array *priv = state->priv;
|
||||
int led_id = state->led_id;
|
||||
|
||||
pr_debug("set brightness of %d to %d\n", led_id, brightness);
|
||||
|
||||
mutex_lock(&priv->mutex);
|
||||
ugreen_led_set_brightness_unlock(priv, led_id, brightness);
|
||||
mutex_unlock(&priv->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum led_brightness ugreen_led_get_brightness(struct led_classdev *cdev) {
|
||||
|
||||
struct ugreen_led_state *state = lcdev_to_ugreen_led_state(cdev);
|
||||
|
||||
pr_debug("get brightness of %d\n", state->led_id);
|
||||
|
||||
if (!state->r && !state->g && !state->b)
|
||||
return LED_OFF;
|
||||
|
||||
return state->status == UGREEN_LED_STATE_OFF ? LED_OFF : state->brightness;
|
||||
}
|
||||
|
||||
static void truncate_blink_delay_time(unsigned long *delay_on, unsigned long *delay_off) {
|
||||
|
||||
if (*delay_on < 100) *delay_on = 100;
|
||||
else if (*delay_on > 0x7fff) *delay_on = 0x7fff;
|
||||
|
||||
if (*delay_off < 100) *delay_off = 100;
|
||||
else if (*delay_off > 0x7fff) *delay_off = 0x7fff;
|
||||
}
|
||||
|
||||
static int ugreen_led_set_blink(struct led_classdev *cdev, unsigned long *delay_on, unsigned long *delay_off) {
|
||||
|
||||
struct ugreen_led_state *state = lcdev_to_ugreen_led_state(cdev);
|
||||
struct ugreen_led_array *priv = state->priv;
|
||||
int led_id = state->led_id;
|
||||
|
||||
truncate_blink_delay_time(delay_on, delay_off);
|
||||
|
||||
pr_debug("set blink of %d to %lu %lu\n", led_id, *delay_on, *delay_off);
|
||||
|
||||
mutex_lock(&priv->mutex);
|
||||
|
||||
ugreen_led_set_blink_or_breath_unlock(priv, led_id, *delay_on, *delay_on + *delay_off, true);
|
||||
*delay_on = state->t_on;
|
||||
*delay_off = state->t_cycle - state->t_on;
|
||||
|
||||
mutex_unlock(&priv->mutex);
|
||||
|
||||
return state->status == UGREEN_LED_STATE_BLINK ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static ssize_t color_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct ugreen_led_state *state = lcdev_to_ugreen_led_state(cdev);
|
||||
struct ugreen_led_array *priv = state->priv;
|
||||
int led_id = state->led_id;
|
||||
u8 r, g, b;
|
||||
|
||||
int nrchars;
|
||||
|
||||
if (sscanf(buf, "%hhu %hhu %hhu%n", &r, &g, &b, &nrchars) != 3) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (++nrchars < size) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pr_debug("set color of %d to 0x%02x%02x%02x\n", led_id, r, g, b);
|
||||
|
||||
mutex_lock(&priv->mutex);
|
||||
ugreen_led_set_color_unlock(priv, led_id, r, g, b);
|
||||
mutex_unlock(&priv->mutex);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t color_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||||
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct ugreen_led_state *state = lcdev_to_ugreen_led_state(cdev);
|
||||
return sprintf(buf, "%d %d %d\n", state->r, state->g, state->b);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(color);
|
||||
|
||||
static ssize_t blink_type_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct ugreen_led_state *state = lcdev_to_ugreen_led_state(cdev);
|
||||
|
||||
u8 blink_type;
|
||||
unsigned long delay_on, delay_off;
|
||||
int nrchars;
|
||||
|
||||
if (sscanf(buf, "blink %lu %lu%n", &delay_on, &delay_off, &nrchars) == 2) {
|
||||
blink_type = UGREEN_LED_STATE_BLINK;
|
||||
} else if(sscanf(buf, "breath %lu %lu%n", &delay_on, &delay_off, &nrchars) == 2) {
|
||||
blink_type = UGREEN_LED_STATE_BREATH;
|
||||
} else if(strcmp(buf, "none\n") == 0) {
|
||||
blink_type = UGREEN_LED_STATE_ON;
|
||||
nrchars = size;
|
||||
} else return -EINVAL;
|
||||
|
||||
if (++nrchars < size) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&state->priv->mutex);
|
||||
|
||||
if (blink_type == UGREEN_LED_STATE_ON) {
|
||||
ugreen_led_turn_on_or_off_unlock(state->priv, state->led_id, true);
|
||||
} else {
|
||||
truncate_blink_delay_time(&delay_on, &delay_off);
|
||||
ugreen_led_set_blink_or_breath_unlock(state->priv, state->led_id,
|
||||
(u16)delay_on, (u16)(delay_on + delay_off),
|
||||
blink_type == UGREEN_LED_STATE_BLINK ? true : false);
|
||||
}
|
||||
|
||||
mutex_unlock(&state->priv->mutex);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t blink_type_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||||
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct ugreen_led_state *state = lcdev_to_ugreen_led_state(cdev);
|
||||
|
||||
ssize_t size = 0;
|
||||
|
||||
mutex_lock(&state->priv->mutex);
|
||||
u8 status = state->status;
|
||||
int delay_on = state->t_on;
|
||||
int delay_off = state->t_cycle - state->t_on;
|
||||
mutex_unlock(&state->priv->mutex);
|
||||
|
||||
if (status == UGREEN_LED_STATE_BLINK) {
|
||||
size += sprintf(buf, "none [blink] breath\n");
|
||||
} else if (status == UGREEN_LED_STATE_BREATH) {
|
||||
size += sprintf(buf, "none blink [breath]\n");
|
||||
} else {
|
||||
size += sprintf(buf, "[none] blink breath\n");
|
||||
}
|
||||
|
||||
if (status == UGREEN_LED_STATE_BLINK || status == UGREEN_LED_STATE_BREATH) {
|
||||
size += sprintf(buf + size, "delay_on: %d, delay_off: %d\n", delay_on, delay_off);
|
||||
}
|
||||
|
||||
size += sprintf(buf + size, "\nUsage: write \"blink <delay_on> <delay_off>\" to change the state.\n");
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(blink_type);
|
||||
|
||||
static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||||
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct ugreen_led_state state = *lcdev_to_ugreen_led_state(cdev);
|
||||
|
||||
mutex_lock(&state.priv->mutex);
|
||||
int status = state.status;
|
||||
if (status >= ARRAY_SIZE(ugreen_led_state_name)) {
|
||||
status = UGREEN_LED_STATE_INVALID;
|
||||
}
|
||||
ssize_t size = sprintf(buf, "%s %d %d %d %d %d %d\n",
|
||||
ugreen_led_state_name[state.status], (int)state.brightness,
|
||||
(int)state.r, (int)state.g, (int)state.b,
|
||||
(int)state.t_on, (int)(state.t_cycle - state.t_on));
|
||||
mutex_unlock(&state.priv->mutex);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(status);
|
||||
|
||||
static struct attribute *ugreen_led_attrs[] = {
|
||||
&dev_attr_color.attr,
|
||||
&dev_attr_status.attr,
|
||||
&dev_attr_blink_type.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(ugreen_led);
|
||||
|
||||
static int ugreen_led_probe(struct i2c_client *client) {
|
||||
|
||||
pr_info ("i2c probed");
|
||||
|
||||
struct ugreen_led_array *priv;
|
||||
|
||||
priv = devm_kzalloc(&client->dev, sizeof(struct ugreen_led_array), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
priv->client = client;
|
||||
|
||||
mutex_init(&priv->mutex);
|
||||
|
||||
for (int i = 0; i < UGREEN_MAX_LED_NUMBER; ++i) {
|
||||
|
||||
priv->state[i].priv = priv;
|
||||
priv->state[i].led_id = i;
|
||||
|
||||
ugreen_led_get_state_robust(client, i, priv->state + i);
|
||||
|
||||
struct ugreen_led_state *state = priv->state + i;
|
||||
if (state->status != UGREEN_LED_STATE_INVALID) {
|
||||
|
||||
pr_info("probed led id %d, status %d, rgb 0x%02x%02x%02x, "
|
||||
"brightness %d, t_on %d, t_cycle %d\n", i,
|
||||
state->status, state->r, state->g, state->b,
|
||||
state->brightness, state->t_on, state->t_cycle);
|
||||
|
||||
ugreen_led_set_brightness_unlock(priv, i, 128);
|
||||
ugreen_led_set_color_unlock(priv, i, 0xff, 0xff, 0xff);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, priv);
|
||||
|
||||
mutex_lock(&priv->mutex);
|
||||
|
||||
// register leds class devices
|
||||
const char *led_name[] = {
|
||||
"power", "netdev", "disk1", "disk2", "disk3", "disk4", "disk5", "disk6", "disk7", "disk8"
|
||||
};
|
||||
|
||||
for (int i = 0; i < UGREEN_MAX_LED_NUMBER; ++i) {
|
||||
|
||||
struct ugreen_led_state *state = priv->state + i;
|
||||
if (state->status == UGREEN_LED_STATE_INVALID)
|
||||
continue;
|
||||
|
||||
if (i < ARRAY_SIZE(led_name))
|
||||
state->cdev.name = led_name[i];
|
||||
else state->cdev.name = "unknown";
|
||||
|
||||
state->cdev.brightness = state->cdev.brightness;
|
||||
state->cdev.max_brightness = 0xff;
|
||||
state->cdev.brightness_set_blocking = ugreen_led_set_brightness_blocking;
|
||||
state->cdev.brightness_get = ugreen_led_get_brightness;
|
||||
state->cdev.groups = ugreen_led_groups;
|
||||
state->cdev.blink_set = ugreen_led_set_blink;
|
||||
|
||||
if (i == 1) {
|
||||
state->cdev.default_trigger = "netdev";
|
||||
} else if (i >= 2) {
|
||||
state->cdev.default_trigger = "oneshot";
|
||||
}
|
||||
|
||||
led_classdev_register(&client->dev, &state->cdev);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0)
|
||||
static void
|
||||
#else
|
||||
static int
|
||||
#endif
|
||||
ugreen_led_remove(struct i2c_client *client) {
|
||||
|
||||
struct ugreen_led_array *priv = i2c_get_clientdata(client);
|
||||
|
||||
for (int i = 0; i < UGREEN_MAX_LED_NUMBER; ++i) {
|
||||
|
||||
struct ugreen_led_state *state = priv->state + i;
|
||||
if (state->status == UGREEN_LED_STATE_INVALID)
|
||||
continue;
|
||||
|
||||
led_classdev_unregister(&state->cdev);
|
||||
}
|
||||
|
||||
mutex_destroy(&priv->mutex);
|
||||
|
||||
pr_info ("i2c removed");
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0)
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ugreen_led_id[] = {
|
||||
{ UGREEN_LED_SLAVE_NAME, 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, ugreen_led_id);
|
||||
|
||||
static struct i2c_driver ugreen_led_driver = {
|
||||
.driver = {
|
||||
.name = UGREEN_LED_SLAVE_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,3,0)
|
||||
.probe = ugreen_led_probe,
|
||||
#else
|
||||
.probe_new = ugreen_led_probe,
|
||||
#endif
|
||||
.remove = ugreen_led_remove,
|
||||
.id_table = ugreen_led_id,
|
||||
};
|
||||
|
||||
|
||||
static int __init ugreen_led_init(void) {
|
||||
pr_info ("initializing");
|
||||
i2c_add_driver(&ugreen_led_driver);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit ugreen_led_exit(void) {
|
||||
i2c_del_driver(&ugreen_led_driver);
|
||||
pr_info ("exited");
|
||||
}
|
||||
|
||||
module_init(ugreen_led_init);
|
||||
module_exit(ugreen_led_exit);
|
||||
|
||||
|
||||
// Module metadata
|
||||
MODULE_AUTHOR("PrivLab <hostmaster@privlab.it>");
|
||||
MODULE_DESCRIPTION("UGREEN NAS LED Controller");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
40
root/usr/src/led-ugreen/led-ugreen.h
Normal file
40
root/usr/src/led-ugreen/led-ugreen.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef __UGREEN_LED_H
|
||||
#define __UGREEN_LED_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#define MODULE_NAME ("led-ugreen")
|
||||
#define UGREEN_LED_SLAVE_ADDR (0x3a)
|
||||
#define UGREEN_LED_SLAVE_NAME ("led-ugreen")
|
||||
#define UGREEN_MAX_LED_NUMBER (10)
|
||||
#define UGREEN_LED_CHANGE_STATE_RETRY_COUNT (5)
|
||||
#define UGREEN_LED_STATE_OFF (0)
|
||||
#define UGREEN_LED_STATE_ON (1)
|
||||
#define UGREEN_LED_STATE_BLINK (2)
|
||||
#define UGREEN_LED_STATE_BREATH (3)
|
||||
#define UGREEN_LED_STATE_INVALID (4)
|
||||
|
||||
static const char *ugreen_led_state_name[] = { "off", "on", "blink", "breath", "unknown" };
|
||||
|
||||
struct ugreen_led_array;
|
||||
|
||||
struct ugreen_led_state {
|
||||
u8 status;
|
||||
u8 r, g, b;
|
||||
u8 brightness;
|
||||
u16 t_on, t_cycle;
|
||||
|
||||
u8 led_id;
|
||||
struct led_classdev cdev;
|
||||
struct ugreen_led_array * priv;
|
||||
};
|
||||
|
||||
struct ugreen_led_array {
|
||||
struct i2c_client * client;
|
||||
struct mutex mutex;
|
||||
struct ugreen_led_state state[UGREEN_MAX_LED_NUMBER];
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user