mirror of
https://github.com/miskcoo/ugreen_dx4600_leds_controller.git
synced 2025-07-22 20:10:36 +02:00
add a kernel module.
Squashed commit of the following:
commit 48ad47c2cb990c7550325e798064cfab612c3e7e
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Fri Jun 7 01:51:01 2024 +0800
update README.md
commit 576cc1e21d629e83ac589cd7f806698814da4858
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Fri Jun 7 01:23:31 2024 +0800
update makefiles
commit 577e2fc326df4d8edc8c303a89bde89ceca90949
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Fri Jun 7 00:29:26 2024 +0800
change the i2c device name
commit 303a05e1f4695dcc0bc117c412b0e668dd7bb2a5
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Thu Jun 6 22:46:18 2024 +0800
add the status attribute
commit ca1a51522b9ad28b04dc12d6012994bc6dce022c
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Thu Jun 6 22:34:33 2024 +0800
fix including filename
commit 4684bd1aaaa0de089537686d65caee4352df3bcf
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Thu Jun 6 22:18:11 2024 +0800
add dkms.conf
commit 4925eec10259c5c626c9459a65a7abeb115c4050
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Thu Jun 6 19:35:37 2024 +0800
Update the manual of the kernel module
commit e07f1570efab507221092d9f2d09d6b98e32f2eb
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Thu Jun 6 16:25:30 2024 +0800
add a netdev monitoring script
commit 813884f0bd928424b3e5be9053a7cc9289ab2263
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Thu Jun 6 15:33:28 2024 +0800
support blink and breath
commit 991db7c11521a2b7e2f841bf5916ff58003b951a
Merge: a4e540c c3e6324
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Thu Jun 6 13:35:16 2024 +0800
Merge branch 'master' into kmod
commit a4e540cbbde41d6a8570881a85bbfebab9def1ad
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Thu Jun 6 13:20:28 2024 +0800
add a script of monitoring diskio
commit 5b65173ec68d2a9ebc952d120935e0f533f6760f
Author: Yuhao Zhou <miskcoo@gmail.com>
Date: Wed Jun 5 23:26:09 2024 +0800
add a kernel driver of leds
This commit is contained in:
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,6 +1,16 @@
|
||||
kmod
|
||||
*.o
|
||||
dx4600_leds_cli
|
||||
ugreen_leds_cli
|
||||
run.sh
|
||||
zpool_leds.sh
|
||||
|
||||
*.ko
|
||||
*.mod.c
|
||||
*.o
|
||||
*.cmd
|
||||
*.mod
|
||||
modules.order
|
||||
compile_commands.json
|
||||
|
||||
.cache
|
||||
Module.symvers
|
||||
|
78
README.md
78
README.md
@@ -3,7 +3,7 @@ LED Controller of UGREEN's DX4600 Pro NAS
|
||||
|
||||
UGREEN's DX4600 Pro is a four-bay NAS with a built-in system based on OpenWRT called `UGOS`. It can install Debian or other open-source NAS systems, but the issue is that the installed non-UGOS system does not have drivers for the six LED lights on the front panel (indicating power, network card, and four hard drives). By default, only the power indicator light blinks, and other indicator lights are off.
|
||||
|
||||
This repository describes the control logic of UGOS for these LED lights and provides a command-line tool to control them. For the process of understanding this control logic, please refer to [my blog (in Chinese)](https://blog.miskcoo.com/2024/05/ugreen-dx4600-pro-led-controller).
|
||||
This repository describes the control logic of UGOS for these LED lights and provides a command-line tool and a kernel module to control them. For the process of understanding this control logic, please refer to [my blog (in Chinese)](https://blog.miskcoo.com/2024/05/ugreen-dx4600-pro-led-controller).
|
||||
|
||||
**WARNING:** Only tested on the following devices. I guess that it works for all DX4600 series. For other devices, please follow the [Preparation](#Preparation) section to check if the protocol is compatible, and run `./ugreen_leds_cli all` to see which LEDs are supported by this tool.
|
||||
|
||||
@@ -12,10 +12,7 @@ This repository describes the control logic of UGOS for these LED lights and pro
|
||||
- [x] UGREEN DX4600 Pro
|
||||
- [x] UGREEN DXP8800 Plus (see [this repo](https://github.com/meyergru/ugreen_dxp8800_leds_controller) and [#1](https://github.com/miskcoo/ugreen_dx4600_leds_controller/issues/1))
|
||||
|
||||
**I am not sure whether this is compatible with other devices. If you have tested it in other devices, please feel free to update the about list.**
|
||||
|
||||
**TODO:**
|
||||
- [ ] a driver to register the LEDs in `/sys/class/leds`. (WIP: see the `kmod` branch)
|
||||
**I am not sure whether this is compatible with other devices. If you have tested it in other devices, please feel free to update the list above.**
|
||||
|
||||
Below is an example:
|
||||
|
||||
@@ -23,18 +20,18 @@ Below is an example:
|
||||
|
||||
It can be achieved by the following commands:
|
||||
```bash
|
||||
sudo ./ugreen_leds_cli all -off -status
|
||||
sudo ./ugreen_leds_cli power -color 255 0 255 -blink 400 600 -status
|
||||
ugreen_leds_cli all -off -status
|
||||
ugreen_leds_cli power -color 255 0 255 -blink 400 600
|
||||
sleep 0.1
|
||||
sudo ./ugreen_leds_cli netdev -color 255 0 0 -blink 400 600 -status
|
||||
ugreen_leds_cli netdev -color 255 0 0 -blink 400 600
|
||||
sleep 0.1
|
||||
sudo ./ugreen_leds_cli disk1 -color 255 255 0 -blink 400 600 -status
|
||||
ugreen_leds_cli disk1 -color 255 255 0 -blink 400 600
|
||||
sleep 0.1
|
||||
sudo ./ugreen_leds_cli disk2 -color 0 255 0 -blink 400 600 -status
|
||||
ugreen_leds_cli disk2 -color 0 255 0 -blink 400 600
|
||||
sleep 0.1
|
||||
sudo ./ugreen_leds_cli disk3 -color 0 255 255 -blink 400 600 -status
|
||||
ugreen_leds_cli disk3 -color 0 255 255 -blink 400 600
|
||||
sleep 0.1
|
||||
sudo ./ugreen_leds_cli disk4 -color 0 0 255 -blink 400 600 -status
|
||||
ugreen_leds_cli disk4 -color 0 0 255 -blink 400 600
|
||||
```
|
||||
|
||||
## Preparation
|
||||
@@ -66,11 +63,13 @@ $ i2cdetect -y 1
|
||||
70: -- -- -- -- -- -- -- --
|
||||
```
|
||||
|
||||
## Build
|
||||
## Build & Usage
|
||||
|
||||
After cloning the current repository, use `make` to build this project. Once the build is complete, we can use `ugreen_leds_cli` to modify the LED states (requires root permissions).
|
||||
**Note**: The kernel module and the command-line tool cannot be simultaneously used. To use the command-line tool, you must unload the `led_ugreen` module.
|
||||
|
||||
## Usage
|
||||
### The Command-line Tool
|
||||
|
||||
Use `cd cli && make` to build the command-line tool, and `ugreen_leds_cli` to modify the LED states (requires root permissions).
|
||||
|
||||
```
|
||||
Usage: ugreen_leds_cli [LED-NAME...] [-on] [-off] [-(blink|breath) T_ON T_OFF]
|
||||
@@ -90,22 +89,63 @@ Usage: ugreen_leds_cli [LED-NAME...] [-on] [-off] [-(blink|breath) T_ON T_OFF]
|
||||
-status: display the status of corresponding LEDs.
|
||||
```
|
||||
|
||||
### Examples
|
||||
Below is an example:
|
||||
|
||||
```bash
|
||||
# turn on all LEDs
|
||||
sudo ./ugreen_leds_cli all -on
|
||||
ugreen_leds_cli all -on
|
||||
|
||||
# query LEDs' status
|
||||
sudo ./ugreen_leds_cli all -status
|
||||
ugreen_leds_cli all -status
|
||||
|
||||
# turn on the power indicator,
|
||||
# and then set its color to blue,
|
||||
# and then set its brightness to 128 / 256,
|
||||
# and finally display its status
|
||||
sudo ./ugreen_leds_cli power -on -color 0 0 255 -brightness 128 -status
|
||||
ugreen_leds_cli power -on -color 0 0 255 -brightness 128 -status
|
||||
```
|
||||
|
||||
### The Kernel Module
|
||||
|
||||
There are three methods to install the module:
|
||||
|
||||
- Run `cd kmod && make` to build the kernel module, and then load it with `sudo insmod led-ugreen.ko`.
|
||||
|
||||
- Alternatively, you can install it with dkms:
|
||||
|
||||
```bash
|
||||
cp -r kmod /usr/src/led-ugreen-0.1
|
||||
dkms add -m led-ugreen -v 0.1
|
||||
dkms build -m led-ugreen -v 0.1 && dkms install -m led-ugreen -v 0.1
|
||||
```
|
||||
|
||||
- You can also install the package [here](https://github.com/miskcoo/ugreen_dx4600_leds_controller/releases).
|
||||
|
||||
After loading the `led-ugreen` module, you need to run `kmod/ugreen-probe-leds`, and you can see LEDs in `/sys/class/leds`.
|
||||
|
||||
Below is an example of setting color, brightness, and blink of the `power` LED:
|
||||
|
||||
```bash
|
||||
echo 255 > /sys/class/leds/power/brightness # non-zero brightness turns it on
|
||||
echo "255 0 0" > /sys/class/leds/power/color # set the color to RGB(255, 0, 0)
|
||||
echo "blink 100 100" > /sys/class/leds/power/blink_type # blink at 10Hz
|
||||
```
|
||||
|
||||
To blink the `netdev` LED when an NIC is active, you can use the `ledtrig-netdev` module (see `kmod/netdevmon.sh`):
|
||||
|
||||
```bash
|
||||
led="netdev"
|
||||
modprobe ledtrig-netdev
|
||||
echo netdev > /sys/class/leds/$led/trigger
|
||||
echo enp2s0 > /sys/class/leds/$led/device_name
|
||||
echo 1 > /sys/class/leds/$led/link
|
||||
echo 1 > /sys/class/leds/$led/tx
|
||||
echo 1 > /sys/class/leds/$led/rx
|
||||
echo 100 > /sys/class/leds/$led/interval
|
||||
```
|
||||
|
||||
To blink the `disk` LED when a block device is active, you can use the `ledtrig-oneshot` module and monitor the changes of`/sys/block/sda/stat` (see `kmod/diskmon.sh` for an example).
|
||||
|
||||
## Communication Protocols
|
||||
|
||||
The IDs for the six LED lights on the front panel of the chassis are as follows:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
|
||||
CC = g++
|
||||
CFLAGS = -I. -O2 -Wall
|
||||
CFLAGS = -I. -O2 -Wall -static
|
||||
DEPS = i2c.h ugreen_leds.h
|
||||
OBJ = i2c.o ugreen_leds.o
|
||||
|
7
kmod/Makefile
Normal file
7
kmod/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
TARGET = led-ugreen
|
||||
obj-m += led-ugreen.o
|
||||
|
||||
all:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||||
clean:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
44
kmod/diskmon.sh
Executable file
44
kmod/diskmon.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
devices=(sda sdb sdc sdd)
|
||||
led_map=(disk1 disk2 disk3 disk4)
|
||||
|
||||
diskio_data_r=()
|
||||
diskio_data_w=()
|
||||
|
||||
if ! lsmod | grep ledtrig_ondshot; then
|
||||
modprobe -v ledtrig_oneshot
|
||||
fi
|
||||
|
||||
for led in ${led_map[@]}; do
|
||||
echo oneshot > /sys/class/leds/$led/trigger
|
||||
echo 1 > /sys/class/leds/$led/invert
|
||||
echo 100 > /sys/class/leds/$led/delay_on
|
||||
echo 100 > /sys/class/leds/$led/delay_off
|
||||
done
|
||||
|
||||
while true; do
|
||||
|
||||
for i in "${!devices[@]}"; do
|
||||
dev=${devices[$i]}
|
||||
diskio_old_r=${diskio_data_r[$i]}
|
||||
diskio_old_w=${diskio_data_w[$i]}
|
||||
|
||||
diskio_new_r=$(cat /sys/block/${dev}/stat 2>/dev/null | awk '{ print $1 }')
|
||||
diskio_new_w=$(cat /sys/block/${dev}/stat 2>/dev/null | awk '{ print $4 }')
|
||||
|
||||
if [ $? != 0 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "${diskio_old_r}" != "${diskio_new_r}" ] || [ "${diskio_old_w}" != "${diskio_new_w}" ]; then
|
||||
echo 1 > /sys/class/leds/${led_map[$i]}/shot
|
||||
fi
|
||||
|
||||
diskio_data_r[$i]=$diskio_new_r
|
||||
diskio_data_w[$i]=$diskio_new_w
|
||||
done
|
||||
|
||||
sleep 0.1
|
||||
|
||||
done
|
7
kmod/dkms.conf
Normal file
7
kmod/dkms.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
PACKAGE_NAME="ugreen-led"
|
||||
PACKAGE_VERSION="0.1"
|
||||
MAKE[0]="make"
|
||||
CLEAN="make clean"
|
||||
BUILT_MODULE_NAME[0]="led-ugreen"
|
||||
DEST_MODULE_LOCATION[0]="/kernel/drivers/leds"
|
||||
AUTOINSTALL="yes"
|
586
kmod/led-ugreen.c
Normal file
586
kmod/led-ugreen.c
Normal file
@@ -0,0 +1,586 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* UGREEN NAS LED driver.
|
||||
*
|
||||
* Copyright (C) 2024.
|
||||
* Author: Yuhao Zhou <miskcoo@gmail.com>
|
||||
*/
|
||||
|
||||
#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 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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
|
||||
// probe and initialize leds
|
||||
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;
|
||||
|
||||
// register the brightness control
|
||||
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;
|
||||
|
||||
led_classdev_register(&client->dev, &state->cdev);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void 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");
|
||||
}
|
||||
|
||||
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("Yuhao Zhou <miskcoo@gmail.com>");
|
||||
MODULE_DESCRIPTION("UGREEN NAS LED driver");
|
||||
MODULE_LICENSE("GPL v2");
|
45
kmod/led-ugreen.h
Normal file
45
kmod/led-ugreen.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#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 // __UGREEN_LED_H
|
13
kmod/netdevmon.sh
Executable file
13
kmod/netdevmon.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
if ! lsmod | grep ledtrig_netdev; then
|
||||
modprobe -v ledtrig_netdev
|
||||
fi
|
||||
|
||||
led="netdev"
|
||||
echo netdev > /sys/class/leds/$led/trigger
|
||||
echo enp2s0 > /sys/class/leds/$led/device_name
|
||||
echo 1 > /sys/class/leds/$led/link
|
||||
echo 1 > /sys/class/leds/$led/tx
|
||||
echo 1 > /sys/class/leds/$led/rx
|
||||
echo 100 > /sys/class/leds/$led/interval
|
16
kmod/ugreen-probe-leds
Executable file
16
kmod/ugreen-probe-leds
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if ! lsmod | grep i2c-dev; then
|
||||
modprobe -v i2c-dev
|
||||
fi
|
||||
|
||||
i2c_dev=$(i2cdetect -l | grep "SMBus I801 adapter" | grep -Po "i2c-\d+")
|
||||
|
||||
if [ $? = 0 ]; then
|
||||
echo "Found I2C device /dev/${i2c_dev}"
|
||||
echo "led-ugreen 0x3a" > /sys/bus/i2c/devices/${i2c_dev}/new_device || true
|
||||
else
|
||||
echo "I2C device not found!"
|
||||
fi
|
Reference in New Issue
Block a user