Controlling the HP NetServer 5/133 LS2 LCD
Some time ago I got an old HP NetServer 5/133 LS2 (that I revived with FreeBSD).
After giving some progress during the boot process, the front LCD display gets stuck with a useless description when the system is up and running, as shown here:
I thought it would be nice to be able to write some meaningful text there, so I started investigating how to do that.
On HP's forum I found out that there is an utility called LSECU that could help me. In fact LSECU allows you to setup some custom static string in the BIOS, but I was looking for a way to control the LCD from Unix in order to show realtime status informations. Since I couldn't find anything like that on the internet, I decided to reverse it.
When you boot the system, the LCD will show various text messages before the OS loads, so the NetServer’s BIOS must contain the code to change the LCD text. So I started from the BIOS: googling a bit I found out that on the i386 architecture the BIOS starts at memory address 0xf0000 (and its size is 64k): so I wrote the following program to extract it to stdout.
int main() {
FILE *f = fopen("/dev/mem", "r");
char bios[0xffff];
assert(f);
fseek(f, 0xf0000, SEEK_CUR);
fread(bios, 1, sizeof(bios), f);
fwrite(bios, 1, sizeof(bios), stdout);
}
Obviously only root can run that code (at -1 securelevel). Once I got the BIOS on a file, I went to Windows and downloaded some disassembler programs, as some friends of mine suggested (thanks to sand, Smilzo and tripz), and disassembled it. At first I looked for the string that was first showed by the BIOS, and I was able to find it at offset 0x6f26. Then I started looking for anything using that offset and got:
0000265D: 8CC8 mov ax,cs
0000265F: 8ED8 mov ds,ax
00002661: BE6F26 mov si,0266F
00002664: E888FE call 0000024EF -------- (5)
I don’t know much about x86 assembler, but this seems a call to function. Going to 0x24ef I found:
000024EF: 56 push si
000024F0: 50 push ax
000024F1: 9C pushf
000024F2: FC cld
000024F3: AC lodsb
000024F4: 0AC0 or al,al
000024F6: 7405 je 0000024FD -------- (3)
000024F8: E88EFF call 000002489 -------- (4)
000024FB: EBF6 jmps 0000024F3 -------- (5)
000024FD: 9D popf
000024FE: 58 pop ax
00002500: C3 retn
This function seems to iterate through the string until its gets null. At each iteration the current character is loaded in al, and the 0x2489 function is called. The 0x2489 function looks like:
00002489: 52 push dx
0000248A: E8DCFF call 000002469 -------- (1)
...
Just pushes dx and calls 0x2469 that contains:
00002469: 50 push ax
0000246A: 33C0 xor ax,ax
0000246C: B402 mov ah,002
0000246E: E862FF call 0000023D3 -------- (1)
00002471: 720D jb 000002480 -------- (2)
00002473: 8AF0 mov dh,al
00002475: 80E60F and dh,00F
00002478: 8AD0 mov dl,al
0000247A: C0FA06 sar dl,006
0000247D: 80E201 and dl,001
00002480: 58 pop ax
00002481: C3 retn
This function does some stuff with ax and then calls 0x23d3:
000023D3: 51 push cx
000023D4: 52 push dx
000023D5: 50 push ax
000023D6: B90600 mov cx,00006
000023D9: E8BCF1 call 000001598 -------- (1)
000023DC: 58 pop ax
000023DD: BA000E mov dx,00E00
000023E0: 80FC00 cmp ah,000
000023E3: 7418 je 0000023FD -------- (2)
000023E5: 80FC02 cmp ah,002
000023E8: 7410 je 0000023FA -------- (3)
000023EA: BA040E mov dx,00E04
000023ED: 80FC01 cmp ah,001
000023F0: 740B je 0000023FD -------- (4)
000023F2: 80FC03 cmp ah,003
000023F5: 7403 je 0000023FA -------- (5)
000023F7: F9 stc
000023F8: EB04 jmps 0000023FE -------- (6)
000023FA: EC in al,dx
000023FB: EB01 jmps 0000023FE -------- (7)
000023FD: EE out dx,al
000023FE: 5A pop dx
000023FF: 59 pop cx
00002400: C3 retn
Finally I got what I was looking for: some data bus I/O operations (in and out). Looking at the code, it seems that the LCD uses two I/O ports, 0xe00 and 0xe04 (as you can see these values are loaded into the dx register and then used by the in/out ops).
Given the I/O addresses, I could start writing something. Throwing random bytes at the ports above I was able to put some nonsense characters to the LCD:
That confirms that the addresses are right. But how to write data correctly? It looked easier to me to open the NetServer instead than reversing the whole protocol from the BIOS code, so I took out the control panel…
…to get the LCD driver IC, after removing that metal block:
The IC is HD44780A00
, a processor of the well documented and widely used HD44780
family. Using the HD44780
datasheet it was easy to discover that 0xe00
is used to send commands and 0xe04
is used to send data to the device.
So I’ve written lcdw, a little program to write directly on your Netserver display. Using lcdw, it’s easy to put on the LCD some nice infos about system’s current status, like uptime or load average. Follows an example of monitoring command (showing logged users and system load - the regex is a bit messy to match both FreeBSD and Linux uptime formats):
./lcdw "`uptime | sed 's@.*, *\(.*\),.*,.*,.*$@\1@'`" "`uptime | sed 's@.*,.*,.*: *\(.*,.*,.*\)$@\1@'`"
You can download here the sources of lcdw.