Return to BSD News archive
Path: sserve!manuel.anu.edu.au!munnari.oz.au!news.Hawaii.Edu!ames!haven.umd.edu!uunet!math.fu-berlin.de!mailgzrz.TU-Berlin.DE!news.netmbx.de!Germany.EU.net!hcshh!hm From: hm@hcshh.hcs.de (Hellmuth Michaelis) Newsgroups: comp.os.386bsd.bugs Subject: "new" old bootblock to boot from 1742 with Julian's SCSI driver Summary: Another method to boot 386bsd using an Adaptec 1742 EISA SCSI ccntrlr Keywords: bootblock Adaptec 1742 boot Message-ID: <1650@hcshh.hcs.de> Date: 3 Mar 93 08:42:37 GMT Organization: HCS GmbH, Hamburg, Europe Lines: 747 After buying an Adaptec 1742, and converting to Julian's SCSI driver, i was not able to boot anymore, because Julian's new bootblocks don't run on my machine (or better, they ran, but the kernel stopped after printing the first four lines of the copyright messages). The BIOS announces itself as "Phoenix 80486 ROM BIOS PLUS Version 1.01.13", the firmware on the 1742 is the latest from the Adaptec BBS, Version 1.40. After long fights with the code and the BIOS, i gave up and instead hacked the "old" as-bootblocks to boot using the 1742 in standard mode. (btw: i would really like to make Julian's BIOS booting method work, is someone else out there who had similar problems ?) This is really a hack, because it forces the device being "sd" although an "sd"-device can also be something else than an Adaptec driver (Bustek ?). Second the method how to make the 1742 boot is not very nice nor elegant - in case i find a nicer method, i'll post the results here! It just does the job of booting and solved my problem - if you don't need it, forget it, otherwise it might solve someone else's problem! The files all go into the /sys/i386/stand directory, the Makefile.sd replaces the Makefile in that directory - have a look through it prior to executing it. hellmuth ---- Cut Here and unpack ---- #!/bin/sh # This is a shell archive (shar 3.24) # made 03/02/1993 20:30 UTC by root@sync.hcs.de # Source directory /tmp # # existing files will NOT be overwritten # # This shar contains: # length mode name # ------ ---------- ------------------------------------------ # 3588 -rw-rw---- Makefile.sd # 5681 -rw-rw---- sd.c # 5175 -rw-rw---- sdbootblk.c # if touch 2>&1 | fgrep '[-amc]' > /dev/null then TOUCH=touch else TOUCH=true fi # ============= Makefile.sd ============== if test X"$1" != X"-c" -a -f 'Makefile.sd'; then echo "File already exists: skipping 'Makefile.sd'" else echo "x - extracting Makefile.sd (Text)" sed 's/^X//' << 'SHAR_EOF' > Makefile.sd && X# from: @(#)Makefile 7.9 (Berkeley) 5/8/91 X XDESTDIR=/ XLD=/usr/bin/ld XSTAND= /sys/stand XINCPATH=-I/sys/sys -I/sys -I/sys/ufs -I${STAND} XVPATH= ${STAND} XSTANDDIR= ${DESTDIR}/stand X XCC= cc -traditional XCPP= cpp -traditional ${INCPATH} -DSTANDALONE -DAT386 X XRELOC= 98000 XRELOC2= 98200 X XCFLAGS= -DSTANDALONE -DAT386 -O ${INCPATH} X XDRIVERS=cga.c fd.c kbd.c wd.c as.c sd.c XSRCS= boot.c fdbootblk.c prf.c \ X srt0.c wdbootblk.c ${DRIVERS} ${SASRC} X XALL= wdboot bootwd fdboot bootfd asboot bootas sdboot bootsd X Xall: ${ALL} X X# startups X Xsrt0.o: srt0.c X ${CPP} -E -DLOCORE -DRELOC=0x${RELOC} srt0.c | ${AS} -o srt0.o X Xwsrt0.o: srt0.c X ${CPP} -E -DLOCORE -DSMALL -DRELOC=0x${RELOC} -DREL srt0.c | \ X ${AS} -o wsrt0.o X Xrelsrt0.o: srt0.c X ${CPP} -E -DLOCORE -DRELOC=0x${RELOC} -DREL srt0.c | ${AS} -o relsrt0.o X X# block 0 boots X Xwdbootblk.o: wdbootblk.c X ${CPP} -E -DLOCORE -DRELOC=0x${RELOC} wdbootblk.c | ${AS} -o $@ X Xfdbootblk.o: fdbootblk.c X ${CPP} -E -DLOCORE -DRELOC=0x${RELOC} fdbootblk.c | ${AS} -o $@ X Xasbootblk.o: asbootblk.c X cc -c -O -DRELOC=0x${RELOC} ${INCPATH} asbootblk.c X Xsdbootblk.o: sdbootblk.c X cc -c -O -DRELOC=0x${RELOC} ${INCPATH} sdbootblk.c X X# getting booted from disc X Xwdboot: wdbootblk.o X ${LD} -N -T ${RELOC} wdbootblk.o X rm -f $@; strip a.out; trimhd 32 <a.out >$@; rm -f a.out; ls -l $@ X Xbootwd: wsrt0.o boot.o bmap.o cga.o fs.o kbd.o prf.o wd.o printf.o breadwd.o X ${LD} -N -T ${RELOC2} wsrt0.o boot.o bmap.o cga.o kbd.o prf.o printf.o \ X breadwd.o fs.o wd.o -lc X size a.out X rm -f $@; strip a.out; trimhd 32 <a.out >$@; rm -f a.out; ls -l $@ X Xfdboot: fdbootblk.o X ${LD} -N -T ${RELOC} fdbootblk.o X rm -f $@; strip a.out; trimhd 32 <a.out >$@; rm -f a.out; ls -l $@ X Xbootfd: wsrt0.o boot.o bmap.o cga.o fs.o kbd.o prf.o fd.o printf.o breadfd.o X ${LD} -N -T ${RELOC2} wsrt0.o boot.o bmap.o cga.o kbd.o prf.o printf.o \ X breadfd.o fs.o fd.o -lc X size a.out X rm -f $@; strip a.out; trimhd 32 <a.out >$@; rm -f a.out; ls -l $@ X Xasboot: asbootblk.o X ${LD} -N -T 7c00 asbootblk.o X rm -f $@; strip a.out; trimhd 32 <a.out >$@; rm -f a.out; ls -l $@ X Xbootas: wsrt0.o boot.o bmap.o cga.o fs.o kbd.o prf.o as.o printf.o breadas.o X ${LD} -N -T ${RELOC2} wsrt0.o boot.o bmap.o cga.o kbd.o prf.o printf.o \ X breadas.o fs.o as.o -lc X size a.out X rm -f $@; strip a.out; trimhd 32 <a.out >$@; rm -f a.out; ls -l $@ X Xsdboot: sdbootblk.o X ${LD} -N -T 7c00 sdbootblk.o X rm -f $@; strip a.out; trimhd 32 <a.out >$@; rm -f a.out; ls -l $@ X Xbootsd: wsrt0.o boot.o bmap.o cga.o fs.o kbd.o prf.o sd.o printf.o breadsd.o X ${LD} -N -T ${RELOC2} wsrt0.o boot.o bmap.o cga.o kbd.o prf.o printf.o \ X breadsd.o fs.o sd.o -lc X size a.out X rm -f $@; strip a.out; trimhd 32 <a.out >$@; rm -f a.out; ls -l $@ X Xbreadwd.o: breadwd.c breadxx.o Xbreadfd.o: breadfd.c breadxx.o Xbreadas.o: breadas.c breadxx.o Xbreadsd.o: breadsd.c breadxx.o X Xbreadxx.o: X touch breadxx.o X Xbreadwd.c: breadxx.c X rm -f breadwd.c X sed -e 's/XX/wd/' -e 's/xx/wd/g' < breadxx.c >> breadwd.c X Xbreadfd.c: breadxx.c X rm -f breadfd.c X sed -e 's/XX/fd/' -e 's/xx/fd/g' < breadxx.c >> breadfd.c X Xbreadas.c: breadxx.c X rm -f breadas.c X sed -e 's/XX/as/' -e 's/xx/as/g' < breadxx.c >> breadas.c X Xbreadsd.c: breadxx.c X rm -f breadsd.c X sed -e 's/XX/sd/' -e 's/xx/sd/g' < breadxx.c >> breadsd.c X Xclean: X rm -f *.o *.exe *.i sm_*.c X rm -f a.out bfd bwd fdb wdb ${ALL} X rm -f boot[a-wyz]? boot[a-wyz]?? boot[a-wyz]?.c boot[a-wyz]??.c \ X conf[a-wyz]?.c conf[a-wyz]??.c bread[a-wyz]?.c X Xcleandir: clean X rm -f ${MAN} tags .depend X Xdepend: ${SRCS} X mkdep ${INCPATH} -DSTANDALONE ${SRCS} ${DUMMIES} X Xinstall: ${ALL} X cp ${ALL} /usr/mdec SHAR_EOF $TOUCH -am 0302212193 Makefile.sd && chmod 0660 Makefile.sd || echo "restore of Makefile.sd failed" set `wc -c Makefile.sd`;Wc_c=$1 if test "$Wc_c" != "3588"; then echo original size 3588, current size $Wc_c fi fi # ============= sd.c ============== if test X"$1" != X"-c" -a -f 'sd.c'; then echo "File already exists: skipping 'sd.c'" else echo "x - extracting sd.c (Text)" sed 's/^X//' << 'SHAR_EOF' > sd.c && X/* X * sys/i386/stand/sd.c X * X * Standalone driver for Adaptech 1742 SCSI X * X * Pace Willisson pace@blitz.com April 8, 1992 X * X * modified to boot Julian Elischer's SCSI-driver X * X * Hellmuth Michaelis hm@hcshh.hcs.de February 6, 1993 X */ X X#include "param.h" X#include "disklabel.h" X#include "i386/isa/asreg.h" X#include "saio.h" X X#ifdef SDDEBUG X#ifdef SDDEBUG_EVEN_MORE X#define SDPRINT(x) { printf(x); kbd(); } X#else X#define SDPRINT(x) { printf x; DELAY (10000); } X#endif X#else X#define SDPRINT(x) X#endif X X#define NRETRIES 3 X Xint sd_port = 0x330; X Xstruct mailbox_entry mailbox[2]; X Xint Xsdopen(io) Xstruct iob *io; X{ X char cdb[6]; X char data[12]; X struct iob aio; X extern struct disklabel disklabel; X int retry; X X if (io->i_unit < 0 || io->i_unit > 8 X || io->i_part < 0 || io->i_part > 8 X || io->i_ctlr < 0 || io->i_ctlr > 0) X return (-1); X X /* dma setup: see page 5-31 in the Adaptech manual */ X X outb (0xd6, 0xc1); X outb (0xd4, 0x01); X X SDPRINT (("resetting adaptech card... ")); X X outb (sd_port + AS_CONTROL, AS_CONTROL_HRST); X X /* delay a little */ X inb (0x84); X X while (inb (sd_port + AS_STATUS) != (AS_STATUS_INIT | AS_STATUS_IDLE)) X ; X X SDPRINT (("reset ok ")); X X sd_put_byte (AS_CMD_MAILBOX_INIT); X sd_put_byte (1); /* one mailbox out, one in */ X sd_put_byte ((int)mailbox >> 16); X sd_put_byte ((int)mailbox >> 8); X sd_put_byte ((int)mailbox); X X while (inb (sd_port + AS_STATUS) & AS_STATUS_INIT) X ; X X SDPRINT (("mailbox init ok ")); X X /* we get a unit-attention error because of the hard reset. */ X for (retry = 0; retry < NRETRIES; retry++) X { X /* do mode select to set the logical block size */ X bzero (cdb, 6); X cdb[0] = 0x15; /* MODE SELECT */ X cdb[4] = 12; /* parameter list length */ X X bzero (data, 12); X data[3] = 8; /* block descriptor length */ X data[9] = DEV_BSIZE >> 16; X data[10] = DEV_BSIZE >> 8; X data[11] = DEV_BSIZE; X X if(sdcmd(io->i_unit, 0, cdb, 6, data, 12,retry==NRETRIES-1)==0) X break; X } X X if(retry >= NRETRIES) X { X printf ("sd%d: error setting logical block size\n",io->i_unit); X#ifdef SDDEBUG X kbd(); X#endif X return (-1); X } X X aio = *io; X aio.i_bn = LABELSECTOR; X aio.i_cc = DEV_BSIZE; X aio.i_boff = 0; X X io->i_boff = disklabel.d_partitions[io->i_part].p_offset; X X SDPRINT (("partition offset %d ", io->i_boff)); X X SDPRINT (("sdopen ok ")); X X#ifdef SDDEBUG X#ifndef SDDEBUG_EVEN_MORE X kbd(); X#endif X#endif X X return(0); X} X X/* func is F_WRITE or F_READ X * io->i_unit, io->i_part, io->i_bn is starting block X * io->i_cc is byte count X * io->i_ma is memory address X * io->i_boff is block offset for this partition (set up in sdopen) X */ Xint Xsdstrategy(io, func) Xstruct iob *io; X{ X char cdb[6]; X int blkno; X int retry; X X SDPRINT (("sdstrategy(target=%d, block=%d+%d, count=%d) ", X io->i_unit, io->i_bn, io->i_boff, io->i_cc)); X X if (func == F_WRITE) { X printf ("sd%d: write not supported\n", io->i_unit); X return (0); X } X X if (io->i_cc == 0) X return (0); X X if (io->i_cc % DEV_BSIZE != 0) { X printf ("sd%d: transfer size not multiple of %d\n", X io->i_unit, DEV_BSIZE); X return (0); X } X X /* retry in case we get a unit-attention error, which just X * means the drive has been reset since the last command X */ X for (retry = 0; retry < NRETRIES; retry++) { X blkno = io->i_bn + io->i_boff; X X cdb[0] = 8; /* scsi read opcode */ X cdb[1] = (blkno >> 16) & 0x1f; X cdb[2] = blkno >> 8; X cdb[3] = blkno; X cdb[4] = io->i_cc / DEV_BSIZE; X cdb[5] = 0; /* control byte (used in linking) */ X X if (sdcmd (io->i_unit, 1, cdb, 6, io->i_ma, io->i_cc, X retry == NRETRIES - 1) >= 0) { X SDPRINT (("sdstrategy ok ")); X return (io->i_cc); X } X } X X SDPRINT (("sdstrategy failed ")); X return (0); X} X Xint Xsdcmd (target, readflag, cdb, cdblen, data, datalen, printerr) Xint target; Xint readflag; Xchar *cdb; Xint cdblen; Xchar *data; Xint datalen; Xint printerr; X{ X struct ccb ccb; X int physaddr; X unsigned char *sp; X int i; X X if (mailbox[0].cmd != 0) X /* this can't happen, unless the card flakes */ X _stop ("sdstart: mailbox not available\n"); X X bzero (&ccb, sizeof ccb); X X ccb.ccb_opcode = 3; X ccb.ccb_addr_and_control = target << 5; X if (datalen != 0) X ccb.ccb_addr_and_control |= readflag ? 8 : 0x10; X else X ccb.ccb_addr_and_control |= 0x18; X X ccb.ccb_data_len_msb = datalen >> 16; X ccb.ccb_data_len_mid = datalen >> 8; X ccb.ccb_data_len_lsb = datalen; X X ccb.ccb_requst_sense_allocation_len = MAXSENSE; X X physaddr = (int)data; X ccb.ccb_data_ptr_msb = physaddr >> 16; X ccb.ccb_data_ptr_mid = physaddr >> 8; X ccb.ccb_data_ptr_lsb = physaddr; X X ccb.ccb_scsi_command_len = cdblen; X bcopy (cdb, ccb.ccb_cdb, cdblen); X X#ifdef SDDEBUG X printf ("ccb: "); X for (i = 0; i < 48; i++) X printf ("%x ", ((unsigned char *)&ccb)[i]); X printf ("\n"); X kbd(); X#endif X X physaddr = (int)&ccb; X mailbox[0].msb = physaddr >> 16; X mailbox[0].mid = physaddr >> 8; X mailbox[0].lsb = physaddr; X mailbox[0].cmd = 1; X X /* tell controller to look in its mailbox */ X outb (sd_port + AS_CONTROL, AS_CONTROL_IRST); X sd_put_byte (AS_CMD_START_SCSI_COMMAND); X X /* wait for status */ X SDPRINT (("waiting for status...")); X while (mailbox[1].cmd == 0) X ; X mailbox[1].cmd = 0; X X X if (ccb.ccb_host_status != 0 || ccb.ccb_target_status != 0) { X#ifdef SDDEBUG X printerr = 1; X#endif X if (printerr) { X printf ("sd%d error: hst=%x tst=%x sense=", X target, X ccb.ccb_host_status, X ccb.ccb_target_status); X sp = ccb_sense (&ccb); X for (i = 0; i < 8; i++) X printf ("%x ", sp[i]); X printf ("\n"); X#ifdef SDDEBUG X kbd(); X#endif X } X return (-1); X } X X SDPRINT (("sdcmd ok ")); X X return (0); X} X Xint Xsd_put_byte (val) Xint val; X{ X while (inb (sd_port + AS_STATUS) & AS_STATUS_CDF) X ; X outb (sd_port + AS_DATA_OUT, val); X} X SHAR_EOF $TOUCH -am 0302212093 sd.c && chmod 0660 sd.c || echo "restore of sd.c failed" set `wc -c sd.c`;Wc_c=$1 if test "$Wc_c" != "5681"; then echo original size 5681, current size $Wc_c fi fi # ============= sdbootblk.c ============== if test X"$1" != X"-c" -a -f 'sdbootblk.c'; then echo "File already exists: skipping 'sdbootblk.c'" else echo "x - extracting sdbootblk.c (Text)" sed 's/^X//' << 'SHAR_EOF' > sdbootblk.c && X/* X * sys/i386/stand/sdbootblk.c X * X * Boot block for Adaptec 1742 SCSI X * X * Pace Willisson pace@blitz.com April 10, 1992 X * X * modified to boot Julian Elischer's SCSI-driver X * X * Hellmuth Michaelis hm@hcshh.hcs.de February 6, 1993 X * X * Placed in the public domain with NO WARRANTIES, not even the X * implied warranties for MERCHANTABILITY or FITNESS FOR A X * PARTICULAR PURPOSE. X * X * To compile: X * X * cc -O -c -DRELOC=0x70000 sdbootblk.c X * ld -N -T 7c00 sdbootblk.o X * X * This should result in a file with 512 bytes of text and no initialized X * data. Strip the 32 bit header and place in block 0. X * X * When run, this program copies at least the first 8 blocks of SCSI X * target 0 to the address specified by RELOC, then jumps to the X * address RELOC+1024 (skipping the boot block and disk label). Usually, X * disks have 512 bytes per block, but I don't think they ever have X * less, and it wont hurt if they are bigger, as long as RELOC + 8*SIZE X * is less than 0xa0000. X * X * This bootblock does not support fdisk partitions, and can only be used X * as the master boot block. X */ X X#include "param.h" X#include "disklabel.h" X#include "i386/isa/asreg.h" X X/* RELOC should be defined with a -D flag to cc */ X X#define SECOND_LEVEL_BOOT_START (RELOC + 0x400) X#define READ_SIZE 8192 X X#define sd_port 0x330 X#define target 0 X X X#define NBLOCKS (READ_SIZE / 512) /* how many logical blocks to read */ X X X/* These are the parameters to pass to the second level boot */ X#define dev 4 /* major device number of sd driver in X i386/stand/conf.c and i386/i386/conf.c */ X#define unit 0 /* partition number of root file system */ X#define off 0 /* block offset of root file system */ X X/* inline i/o borrowed from Roell X server */ Xstatic __inline__ void Xoutb(port, val) Xshort port; Xchar val; X{ X __asm__ volatile("outb %%al, %1" : :"a" (val), "d" (port)); X} X Xstatic __inline__ unsigned int Xinb(port) Xshort port; X{ X unsigned int ret; X __asm__ volatile("xorl %%eax, %%eax; inb %1, %%al" X : "=a" (ret) : "d" (port)); X return ret; X} X X/* this code is linked at 0x7c00 and is loaded there by the BIOS */ X Xasm (" X /* we're running in 16 real mode, so normal assembly doesn't work */ Xbootbase: X /* interrupts off */ X cli X X /* load gdt */ X .byte 0x2e,0x0f,0x01,0x16 /* lgdt %cs:$imm */ X .word _gdtarg + 2 X X /* turn on protected mode */ X smsw %ax X orb $1,%al X lmsw %ax X X /* flush prefetch queue and reload %cs */ X .byte 0xea /* ljmp $8, flush */ X .word flush X .word 8 X Xflush: X /* now running in 32 bit mode */ X movl $0x10,%eax X movl %ax,%ds X movl %ax,%es X movl %ax,%ss X movl $0x7c00,%esp X call _main X"); /* end of asm */ X Xconst char gdt[] = { X 0, 0, 0, 0, 0, 0, 0, 0, X 0xff, 0xff, 0, 0, 0, 0x9f, 0xcf, 0, /* code segment */ X 0xff, 0xff, 0, 0, 0, 0x93, 0xcf, 0, /* data segment */ X}; X Xconst struct { X short filler; X short size; X const char *gdt; X} gdtarg = { 0, sizeof gdt - 1, gdt }; X X#define CRTBASE ((char *)0xb8000) X#define CHECKPOINT(x) (CRTBASE[0] = x) X Xvolatile struct mailbox_entry mailbox[2]; Xconst char ccb[] = { X 3, /* opcode: normal read/write */ X (target << 5) | 8, /* target num and read flag */ X 10, /* scsi cmd len */ X 1, /* no automatic request for sense */ X READ_SIZE >> 16, /* data length */ X READ_SIZE >> 8, X READ_SIZE, X RELOC >> 16, /* data pointer */ X RELOC >> 8, X RELOC, X 0, 0, 0, /* link pointer */ X 0, /* link id */ X 0, /* host status */ X 0, /* target status */ X 0, 0, /* reserved */ X X /* scsi cdb */ X 0x28, /* read opcode */ X 0, /* logical unit number */ X 0, 0, 0, 0, /* logical block address */ X 0, /* reserved */ X 0, NBLOCKS, /* transfer length */ X 0, /* link control */ X}; X Xint (*f)(); X Xmain () X{ X int i; X extern char edata[], end[]; X char volatile * volatile p, *q; X int physaddr; X X CHECKPOINT ('a'); X X /* clear bss */ X for (p = edata; p < end; p++) X *p = 0; X X f = (int (*)())SECOND_LEVEL_BOOT_START; X X /* dma setup: see page 5-31 in the Adaptech manual */ X /* this knows we are using drq 5 */ X outb (0xd6, 0xc1); X outb (0xd4, 0x01); X X outb (sd_port + AS_CONTROL, AS_CONTROL_SRST); X X /* delay a little */ X inb (0x84); X X while (inb (sd_port + AS_STATUS) != (AS_STATUS_INIT | AS_STATUS_IDLE)) X ; X X CHECKPOINT ('b'); X X sd_put_byte (AS_CMD_MAILBOX_INIT); X sd_put_byte (1); /* one mailbox out, one in */ X sd_put_byte ((int)mailbox >> 16); X sd_put_byte ((int)mailbox >> 8); X sd_put_byte ((int)mailbox); X X while (inb (sd_port + AS_STATUS) & AS_STATUS_INIT) X ; X X CHECKPOINT ('c'); X X mailbox[0].msb = (int)ccb >> 16; X mailbox[0].mid = (int)ccb >> 8; X mailbox[0].lsb = (int)ccb; X mailbox[0].cmd = 1; X X sd_put_byte (AS_CMD_START_SCSI_COMMAND); X X /* wait for done */ X while (mailbox[1].cmd == 0) X ; X X CHECKPOINT ('d'); X X if (mailbox[1].cmd != 1) { X /* some error */ X CHECKPOINT ('X'); X while (1); X } X X CHECKPOINT ('e'); X X /* the optimazation that gcc uses when it knows we are jumpping X * to a constant address is broken, so we have to use a variable X * here X */ X (*f)(dev, unit, off); X} X Xint Xsd_put_byte (val) Xint val; X{ X while (inb (sd_port + AS_STATUS) & AS_STATUS_CDF) X ; X outb (sd_port + AS_DATA_OUT, val); X} X Xasm (" Xebootblkcode: X . = 510 X .byte 0x55 X .byte 0xaa Xebootblk: /* MUST BE EXACTLY 0x200 BIG FOR SURE */ X"); SHAR_EOF $TOUCH -am 0302212193 sdbootblk.c && chmod 0660 sdbootblk.c || echo "restore of sdbootblk.c failed" set `wc -c sdbootblk.c`;Wc_c=$1 if test "$Wc_c" != "5175"; then echo original size 5175, current size $Wc_c fi fi exit 0 -------------------------------------------------------------------------- -- hellmuth michaelis HCS Hanseatischer Computerservice GmbH hamburg, europe hm@hcshh.hcs.de tel: +49/40/55903-170 fax: +49/40/5591486 [ the opinions expressed above are my own and not the opinion of anybody else ]