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:

HP NetServer 5/133 LS2 LCD Display

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];
    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.