Some time ago, I got an old HP NetServer 5/133 LS2 (that I revived with FreeBSD).

After giving some progress informations during the boot process, I noticed that the front LCD display gets stuck with useless informations when the system is up and running, as shown here:

HP NetServer 5/133 LS2 LCD Display

Since I wanted to put some meaningful text into the LCD, I started investigating around for a way to accomplish that.

I figured out (in HP’s forum) that there is some utility called LSECU that could help me. In fact the LSECU utility allows the user 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, so after some failed researches, I started thinking how to get it done on my own.

Obviously the NetServer’s BIOS contains the code to change the LCD text because of the multiple updates being issued at boot time. 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 the BIOS 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 I/O on 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. With a little random data test, I was able to put some nonsense characters to the LCD:

nonsense

Now It was time to understand how to write correctly data. 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…

controlpanel

…to get the LCD driver IC, after removing that metal block:

inside1 inside2

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 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@'`"

stats

You can download here the sources of lcdw.