Skip to content

Instantly share code, notes, and snippets.

@zopieux
Last active May 2, 2024 15:12
Show Gist options
  • Save zopieux/0b38fe1c3cd49039c98d5612ca84a045 to your computer and use it in GitHub Desktop.
Save zopieux/0b38fe1c3cd49039c98d5612ca84a045 to your computer and use it in GitHub Desktop.
QNAP TS-453 Pro: LCD, LEDs, fan & buttons

QNAP TS-453 Pro: LCD, LEDs, fan & buttons

QNAP NAS model TS-453 Pro has limited documentation on how to control its various peripherals, namely:

  • the front LCD display, a 2 row, 16 column blue & white LCD display, and its two menu buttons ("ENTER", "SELECT")
  • the front LEDs "STATUS" (red & green) & "USB" (blue)
  • the 4 "disk error" LEDs (red)
  • the front "USB COPY" button
  • the main rear fan

I've spent some time toying with the stock firmware to figure that out.

Some background

Just FYI, to act of these peripherals (and a lot of other stuff such as flashing the firmware), the stock firmware uses a combination of a 1.3 MB shared library libuLinux_hal.so, a 364 KB hal_daemon service and a 176 KB hal_app command-line tool, plus some other libraries for parsing the specific INI-stored "HAL config" for each device model. For example, to produce a beep:

$ hal_app --se_buzzer enc_id=0,mode=1

sends a message to hal_daemon which eventually writes to the relevant low-level I/O port through libuLinux_hal.

Fan control

This can be controlled using standard hwmon tooling on Linux. Install lm-sensors/fancontrol. A sensible /etc/fancontrol is presented below:

INTERVAL=10
DEVPATH=hwmon1=devices/platform/coretemp.0 hwmon2=devices/platform/f71882fg.656
DEVNAME=hwmon1=coretemp hwmon2=f71869a
FCTEMPS=hwmon2/device/pwm1=hwmon1/temp2_input
FCFANS= hwmon2/device/pwm1=hwmon2/device/fan1_input
MINTEMP=hwmon2/device/pwm1=30
MAXTEMP=hwmon2/device/pwm1=65
MINSTART=hwmon2/device/pwm1=150
MINSTOP=hwmon2/device/pwm1=0

LCD display

This is a well-documented A125 board. There are multiple scripts and programs on the web to read the buttons and control the LCD display. See eg. the implementation for qcontrol: a125.c.

The tl;dr is:

  • serial port /dev/ttyS1, baudrate 1200 (yes; that's 2 updates per second at best)
  • write 0x4D, 0x5E, 0x00 to switch the backlight off, write 0x4D, 0x5E, 0x01 to switch the backlight on
  • write 0x4D, 0x0C, 0x00, 0x20, {16 chars} to write to the first line, write 0x4D, 0x0C, 0x01, 0x20, {16 chars} to write to the second line (pad with spaces to clear the previous characters)
  • for each button press and release, the read buffer will receive 4 characters of the form 0x53, 0x05, 0x00, {BM} where {BM} is a bitmask of 0x01 → ENTER (UP) and 0x02 → SELECT (DOWN). Possible values are thus 0x00, 0x01, 0x02, 0x03. Pressing ENTER then pressing SELECT then releasing ENTER then releasing SELECT will send four 4-byte messages ending with 0x01 → 0x03 → 0x02 → 0x00.

Front LEDs and USB "COPY" button

This is the funny, otherwise undocumented part.

The stock firmware contains model-specific config files, eg. model_QW370_QW550_16_10.conf, that describes on what low-level I/O ports should one read and write. Reproduced below are the interesting parts:

[System Enclosure]
VENDOR = QNAP
MODEL = TS-453 Pro
SIO_DEVICE = F71869A  # The motherboard model.
# …
[System IO]
RESET_BUTTON = SIO:I92:B1
STATUS_GREEN_LED = SIO:I91:B2
STATUS_RED_LED = SIO:I91:B3
USB_COPY_BUTTON = SIO:IE2:B2
FRONT_USB_LED = SIO:IE1:B7
# …
[System Disk 1]
DEV_BUS = B00:D28:F0
DEV_PORT = 1
ERR_LED = SIO:I81:B0
# …

What I couldn't find in this file is the base port number. strace-ing the stock binary, I discovered it's 0xA05 (2565).

Understanding the Super I/O spec format

Port Purpose
0xA05 Control "register" XX (:I{XX})
0xA06 Register value, using negative bitmask offset X (:B{X})

Switching a LED

To change a LED state, write its control register to 0xA05 and the relevant bitmask to 0xA06.

For instance, to change STATUS_GREEN_LED = SIO:I91:B2:

  • write to 0xA05: 0x91 ( :I91)
  • write to 0xA06: 0xFF ^ 0b100, as GREEN is 2nd bit (:B2)

Reading a button status

To read a button status, write the control register to 0xA05, read 0xA06 and mask it to get the relevant bit. For instance, to poll for COPY button status USB_COPY_BUTTON = SIO:IE2:B2:

  • write to 0xA05: 0xE2 ( :IE2)
  • read 0xA06 and get its 2nd bit (:B2)

There is a sample C program below to demo this feature.

[System Enclosure]
VENDOR = QNAP
MODEL = TS-453 Pro
CAP=0x06145bdc
MAX_DISK_NUM = 4
MAX_FAN_NUM = 1
MAX_TEMP_NUM = 2
MAX_NET_PORT_NUM = 4
INTERNAL_NET_PORT_NUM = 4
MAX_PCIE_SLOT = 1
CPU_TEMP_UNIT=DTS:4
SYSTEM_TEMP_UNIT=SIO:3
SIO_DEVICE = F71869A
PWR_RECOVERY_UNIT = SIO
PWR_RECOVERY_CMOS_STORE = 0x70,0x61
BOARD_SN_DEVICE = NET:1
ETH_MAC_DEVICE = NET
DISK_DRV_TYPE = ATA
DISK_DEFAULT_MAX_LINK_SPEED = PD_SATA_SAS_6G
SYSTEM_DISK_CACHEABLE_BITMAP = 0x1e
SS_MAX_CHANNELS = 40
SS_FREE_CHANNELS = 2
[System FAN]
FAN_UNIT = SIO
FAN_1=I1
FAN_2=I2
FAN_LEVEL_0 = 0
FAN_LEVEL_1 = 70
FAN_LEVEL_2 = 90
FAN_LEVEL_3 = 110
FAN_LEVEL_4 = 130
FAN_LEVEL_5 = 150
FAN_LEVEL_6 = 200
FAN_LEVEL_7 = 250
[System I2C]
DEV_BUS = B00:D31:F3
DEV_PORT = 0
[System EDID 1]
DEV_BUS = B00:D02:F0
DEV_PORT = 4
[System IO]
RESET_BUTTON = SIO:I92:B1
STATUS_GREEN_LED = SIO:I91:B2
STATUS_RED_LED = SIO:I91:B3
LED_BV_CTRL = GPIO
USB_COPY_BUTTON = SIO:IE2:B2
FRONT_USB_LED = SIO:IE1:B7
VPD_MB = I2C:0x54
VPD_BP = I2C:0x56
[System Disk 1]
DEV_BUS = B00:D28:F0
DEV_PORT = 1
ERR_LED = SIO:I81:B0
[System Disk 2]
DEV_BUS = B00:D28:F0
DEV_PORT = 0
ERR_LED = SIO:I81:B1
[System Disk 3]
DEV_BUS = B00:D28:F1
DEV_PORT = 0
ERR_LED = SIO:I81:B2
[System Disk 4]
DEV_BUS = B00:D28:F1
DEV_PORT = 1
ERR_LED = SIO:I81:B3
[System Network 1]
DEV_BUS = B00:D28:F2
PCI_SWITCH_PORT = 1
DEV_PORT = 0
[System Network 2]
DEV_BUS = B00:D28:F2
PCI_SWITCH_PORT = 2
DEV_PORT = 0
[System Network 3]
DEV_BUS = B00:D28:F3
PCI_SWITCH_PORT = 1
DEV_PORT = 0
[System Network 4]
DEV_BUS = B00:D28:F3
PCI_SWITCH_PORT = 2
DEV_PORT = 0
[System PCIE SLOT 1]
DEV_BUS = B00:D01:F0
MAX_PCIE_LINK_WIDTH = 8
[Usb Enclosure]
VENDOR = QNAP
MODEL = USB
MAX_PORT_NUM = 5
USB3_PORT_BITMAP = 0xE
[Usb Port 1]
DEV_BUS = B00:D20:F0
IN_HUB = 1
HUB_PORT = 1
DEV_PORT = 3
[Usb Port 2]
DEV_BUS = B00:D20:F0
IN_HUB = 1
HUB_PORT = 1
DEV_PORT = 1
[Usb Port 3]
DEV_BUS = B00:D20:F0
IN_HUB = 1
HUB_PORT = 1
DEV_PORT = 4
[Usb Port 4]
DEV_BUS = B00:D20:F0
IN_HUB = 0
DEV_PORT = 3
[Usb Port 5]
DEV_BUS = B00:D20:F0
IN_HUB = 0
DEV_PORT = 2
[Boot Enclosure]
VENDOR = QNAP
MODEL = BOOT
MAX_DISK_NUM = 1
DISK_DRV_TYPE = USB
[Boot Disk 1]
DEV_BUS = B00:D20:F0
IN_HUB = 0
DEV_PORT = 4
[System Memory]
MAX_CHANNEL_NUM = 2
MAX_SLOT_NUM = 2
SLOT1_ADDR = 1, 0x50
SLOT2_ADDR = 2, 0x51
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/io.h>
#define BASEPORT 0xa05
#define NPORTS 2
#define GREEN_LED 0x91
#define GREEN_LED_B (1 << 2)
#define RED_LED 0x91
#define RED_LED_B (1 << 3)
#define USB_LED 0xe1
#define USB_LED_B (1 << 7)
#define COPY_BUTTON 0xe2
#define COPY_BUTTON_B (1 << 2)
#define RESET_BUTTON 0x92
#define RESET_BUTTON_B (1 << 1)
// You can also act of disk error LEDs, not included for brevity.
int main() {
// Get access to the ports
if (ioperm(BASEPORT, NPORTS, 1)) { perror("ioperm"); exit(1); }
// Switch some LEDs on
outb(GREEN_LED, BASEPORT);
outb(0xff ^ GREEN_LED_B, BASEPORT + 1);
outb(USB_LED, BASEPORT);
outb(0xff ^ USB_LED_B, BASEPORT + 1);
usleep(1000000);
// Switch them off
outb(GREEN_LED, BASEPORT);
outb(0xff, BASEPORT + 1);
outb(USB_LED, BASEPORT);
outb(0xff, BASEPORT + 1);
// Poll USB COPY button for a while
outb(COPY_BUTTON, BASEPORT);
for (int t = 0; t < 10; t++) {
int value = inb(BASEPORT + 1) & COPY_BUTTON_B;
printf("COPY button: %s\n", value ? "released" : "pressed");
usleep(100000);
}
// We don't need the ports anymore
if (ioperm(BASEPORT, NPORTS, 0)) { perror("ioperm"); exit(1); }
}
@westernmagic
Copy link

Great work! Any hints as to how to control the buzzer?

@zopieux
Copy link
Author

zopieux commented Aug 29, 2020

Hey @westernmagic, thanks for the kind word. Sorry I didn't have interest in controlling the piezzo buzzer so I didn't look. Feel free to ping me if you find something!

@markb139
Copy link

This is great. My TS-453 Pro died the other day and after a bit of diagnosis I've come to the conclusion that the motherboard has failed. I didn't want to through it all away so was looking to re-use some parts. Was thinking of housing a Raspberry Pi. Being able to use the LCD will be useful

@artvel
Copy link

artvel commented May 28, 2021

Great work! Any idea how to make the power button working when running the NAS with debian?

@zopieux
Copy link
Author

zopieux commented May 29, 2021

@artvel I did not look but to change the behavior of power buttons you'll usually have to toy with ACPI, which is a standard interface. No reason it wouldn't work with QNAP NASes.

@artvel
Copy link

artvel commented May 29, 2021

Hi @zopieux, thanks for your response.
Just for the record it is a QNAP TVS-472XT-PT-4G.

Only the second Power Button event seems to respond but with a delay of approx. 4sec. So I had to hold the power button for 4 seconds until I saw the first event.

/dev/input/event3:	Sleep Button
/dev/input/event4:	Power Button
/dev/input/event5:	Power Button //only this one worked
/dev/input/event6:	ITE8708 CIR transceiver

I receive events on both on my other NAS. Seems a little bit awkward to me but anyways.

Hope it helps someone.

@system1357
Copy link

system1357 commented Aug 4, 2021

Hi,
Thanks for the research, may I ask that if you're interested in finding ways to configure the ITE (it87) variants?
I have a fairly old TS-469L, and after digging around in the stock firmware I found similar configs, with the only difference of the SuperIO chip (ITE IT8721F)
Here's the area of interest:

[System Enclosure]
VENDOR = QNAP
MODEL = TS-469L
# ...
CPU_TEMP_UNIT=SIO:1
SYSTEM_TEMP_UNIT=SIO:2
SIO_DEVICE = IT87

[System IO]
RESET_BUTTON = SIO:I00:B7
USB_COPY_BUTTON = SIO:I02:B4
STATUS_GREEN_LED = SIO:I01:B2
STATUS_RED_LED = SIO:I01:B3
FRONT_USB_LED = SIO:I04:B0
POWER_LED=SIO:I03:B0
# ...
[System Disk 1]
DEV_BUS = B00:D31:F2
DEV_PORT = 0
ERR_LED = SIO:I07:B4

I've tried to change the base address according to it87_wdt and gpio_it87 kernel drivers but failed
0x2e and 0x25 seems incorrect, and I have limited experience on reversing binaries
Any help is appreciated

@stephenhouser
Copy link

Curious how you got strace up and running on the QNAP. I have a TVS-671 and I'm not getting positive results. Thinking the base port might be different.

@zopieux
Copy link
Author

zopieux commented Feb 17, 2022

My QNAP original install had root access unlocked. I don't remember if I had to install some ipkg to get strace, but that was pretty much it.

@stephenhouser
Copy link

Thanks. I managed to get hal_app running under TrueNAS by copying over the shared libraries and some LD_LIBRARY_PATH trickery. Still have not quite found the right i/o port for the LEDs. Seems hal_app just sets bits in a file /var/../ledvalue that hal_daemon reads and outputs. Your work here has given me some good leads, so thank you for that!

@mooglestiltzkin
Copy link

Any tips for fixing the lcds on a QNAP TS-877 using truenas scale?

Tried this but didn't work
https://github.com/elvisimprsntr/QnapFreeLCD

@stephenhouser
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment