patch-2.1.36 linux/arch/m68k/boot/amiga/linuxboot.c
Next file: linux/arch/m68k/boot/amiga/linuxboot.h
Previous file: linux/arch/m68k/boot/amiga/bootstrap.h
Back to the patch index
Back to the overall index
- Lines: 671
- Date:
Thu Apr 17 13:20:42 1997
- Orig file:
v2.1.35/linux/arch/m68k/boot/amiga/linuxboot.c
- Orig date:
Fri Dec 20 01:19:58 1996
diff -u --recursive --new-file v2.1.35/linux/arch/m68k/boot/amiga/linuxboot.c linux/arch/m68k/boot/amiga/linuxboot.c
@@ -20,6 +20,24 @@
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
+ *
+ * History:
+ * 03 Feb 1997 Implemented kernel decompression (Geert, based on Roman's
+ * code for ataboot)
+ * 30 Dec 1996 Reverted the CPU detection to the old scheme
+ * New boot parameter override scheme (Geert)
+ * 27 Nov 1996 Compatibility with bootinfo interface version 1.0 (Geert)
+ * 9 Sep 1996 Rewritten option parsing
+ * New parameter passing to linuxboot() (linuxboot_args)
+ * (Geert)
+ * 18 Aug 1996 Updated for the new boot information structure (Geert)
+ * 10 Jan 1996 The real Linux/m68k boot code moved to linuxboot.[ch]
+ * (Geert)
+ * 11 Jul 1995 Support for ELF kernel (untested!) (Andreas)
+ * 7 Mar 1995 Memory block sizes are rounded to a multiple of 256K
+ * instead of 1M (Geert)
+ * 31 May 1994 Memory thrash problem solved (Geert)
+ * 11 May 1994 A3640 MapROM check (Geert)
*/
@@ -29,6 +47,8 @@
#define BOOTINFO_COMPAT_1_0 /* bootinfo interface version 1.0 compatible */
+/* support compressed kernels? */
+#define ZKERNEL
#include <stddef.h>
#include <string.h>
@@ -60,7 +80,7 @@
static const struct linuxboot_args *linuxboot_args;
/* Bootinfo */
-static struct amiga_bootinfo bi;
+struct amiga_bootinfo bi;
#ifdef BOOTINFO_COMPAT_1_0
static struct compat_bootinfo compat_bootinfo;
@@ -75,7 +95,6 @@
#define kernelname linuxboot_args->kernelname
#define ramdiskname linuxboot_args->ramdiskname
-#define commandline linuxboot_args->commandline
#define debugflag linuxboot_args->debugflag
#define keep_video linuxboot_args->keep_video
#define reset_boards linuxboot_args->reset_boards
@@ -91,8 +110,6 @@
#define Close linuxboot_args->close
#define FileSize linuxboot_args->filesize
#define Sleep linuxboot_args->sleep
-#define ModifyBootinfo linuxboot_args->modify_bootinfo
-
/*
* Function Prototypes
@@ -115,6 +132,16 @@
u_long kernel_size) __attribute__ ((noreturn));
asmlinkage u_long maprommed(void);
asmlinkage u_long check346(void);
+#ifdef ZKERNEL
+static int load_zkernel(int fd);
+static int KRead(int fd, void *buf, int cnt);
+static int KSeek(int fd, int offset);
+static int KClose(int fd);
+#else
+#define KRead Read
+#define KSeek Seek
+#define KClose Close
+#endif
/*
@@ -174,7 +201,7 @@
u_long linuxboot(const struct linuxboot_args *args)
{
- int kfd = -1, rfd = -1, elf_kernel = 0;
+ int kfd = -1, rfd = -1, elf_kernel = 0, do_fast, do_chip;
int i, j;
const struct MemHeader *mnp;
struct ConfigDev *cdp = NULL;
@@ -196,38 +223,42 @@
Puts("\nLinux/m68k Amiga Bootstrap version " AMIBOOT_VERSION "\n");
Puts("Copyright 1993,1994 by Hamish Macdonald and Greg Harp\n\n");
- memset(&bi, 0, sizeof(bi));
+ /* Note: Initial values in bi override detected values */
+ bi = args->bi;
/* machine is Amiga */
bi.machtype = MACH_AMIGA;
/* determine chipset */
- bi.chipset = get_chipset();
+ if (!bi.chipset)
+ bi.chipset = get_chipset();
/* determine CPU, FPU and MMU type */
- get_processor(&bi.cputype, &bi.fputype, &bi.mmutype);
+ if (!bi.cputype)
+ get_processor(&bi.cputype, &bi.fputype, &bi.mmutype);
/* determine Amiga model */
- bi.model = get_model(bi.chipset);
+ if (!bi.model)
+ bi.model = get_model(bi.chipset);
model_mask = (bi.model != AMI_UNKNOWN) ? 1<<bi.model : 0;
/* Memory & AutoConfig based on 'unix_boot.c' by C= */
/* find all of the autoconfig boards in the system */
- bi.num_autocon = 0;
- for (i = 0; (cdp = (struct ConfigDev *)FindConfigDev(cdp, -1, -1)); i++) {
- if (bi.num_autocon < ZORRO_NUM_AUTO) {
- /* copy the contents of each structure into our boot info */
- memcpy(&bi.autocon[bi.num_autocon], cdp, sizeof(struct ConfigDev));
- /* count this device */
- bi.num_autocon++;
- } else
- Printf("Warning: too many AutoConfig devices. Ignoring device at "
- "0x%08lx\n", cdp->cd_BoardAddr);
- }
+ if (!bi.num_autocon)
+ for (i = 0; (cdp = (struct ConfigDev *)FindConfigDev(cdp, -1, -1)); i++)
+ if (bi.num_autocon < ZORRO_NUM_AUTO)
+ /* copy the contents of each structure into our boot info and
+ count this device */
+ memcpy(&bi.autocon[bi.num_autocon++], cdp,
+ sizeof(struct ConfigDev));
+ else
+ Printf("Warning: too many AutoConfig devices. Ignoring device at "
+ "0x%08lx\n", cdp->cd_BoardAddr);
+ do_fast = bi.num_memory ? 0 : 1;
+ do_chip = bi.chip_size ? 0 : 1;
/* find out the memory in the system */
- bi.num_memory = 0;
for (mnp = (struct MemHeader *)SysBase->MemList.lh_Head;
mnp->mh_Node.ln_Succ;
mnp = (struct MemHeader *)mnp->mh_Node.ln_Succ) {
@@ -266,7 +297,7 @@
mh.mh_Lower = (void *)((u_long)mh.mh_Lower & 0xfffff000);
/* if fast memory */
- if (mh.mh_Attributes & MEMF_FAST) {
+ if (do_fast && mh.mh_Attributes & MEMF_FAST) {
/* set the size value to the size of this block and mask off to a
256K increment */
u_long size = ((u_long)mh.mh_Upper-(u_long)mh.mh_Lower)&0xfffc0000;
@@ -279,30 +310,26 @@
bi.num_memory++;
} else
Printf("Warning: too many memory blocks. Ignoring block "
- "of %ldK at 0x%08x\n", size>>10,
+ "of %ldK at 0x%08x\n", size>>10,
(u_long)mh.mh_Lower);
- } else if (mh.mh_Attributes & MEMF_CHIP)
+ } else if (do_chip && mh.mh_Attributes & MEMF_CHIP)
/* if CHIP memory, record the size */
bi.chip_size = (u_long)mh.mh_Upper;
}
/* get info from ExecBase */
- bi.vblank = SysBase->VBlankFrequency;
- bi.psfreq = SysBase->PowerSupplyFrequency;
- bi.eclock = SysBase->ex_EClockFrequency;
+ if (!bi.vblank)
+ bi.vblank = SysBase->VBlankFrequency;
+ if (!bi.psfreq)
+ bi.psfreq = SysBase->PowerSupplyFrequency;
+ if (!bi.eclock)
+ bi.eclock = SysBase->ex_EClockFrequency;
/* serial port */
- realbaud = baud ? baud : DEFAULT_BAUD;
- bi.serper = (5*bi.eclock+realbaud/2)/realbaud-1;
-
- /* copy command line options into the kernel command line */
- strncpy(bi.command_line, commandline, CL_SIZE);
- bi.command_line[CL_SIZE-1] = '\0';
-
-
- /* modify the bootinfo, e.g. to change the memory configuration */
- if (ModifyBootinfo && !ModifyBootinfo(&bi))
- goto Fail;
+ if (!bi.serper) {
+ realbaud = baud ? baud : DEFAULT_BAUD;
+ bi.serper = (5*bi.eclock+realbaud/2)/realbaud-1;
+ }
/* display Amiga model */
if (bi.model >= first_amiga_model && bi.model <= last_amiga_model)
@@ -347,7 +374,7 @@
}
/* display the chipset */
- switch(bi.chipset) {
+ switch (bi.chipset) {
case CS_STONEAGE:
Puts(", old or unknown chipset");
break;
@@ -457,11 +484,24 @@
Printf("Unable to open kernel file `%s'\n", kernelname);
goto Fail;
}
- if (Read(kfd, (void *)&kexec, sizeof(kexec)) != sizeof(kexec)) {
+ if (KRead(kfd, (void *)&kexec, sizeof(kexec)) != sizeof(kexec)) {
Puts("Unable to read exec header from kernel file\n");
goto Fail;
}
+#ifdef ZKERNEL
+ if (((unsigned char *)&kexec)[0] == 037 &&
+ (((unsigned char *)&kexec)[1] == 0213 ||
+ ((unsigned char *)&kexec)[1] == 0236)) {
+ /* That's a compressed kernel */
+ Puts("Kernel is compressed\n");
+ if (load_zkernel(kfd)) {
+ Puts("Decompression error -- aborting\n");
+ goto Fail;
+ }
+ }
+#endif
+
switch (N_MAGIC(kexec)) {
case ZMAGIC:
if (debugflag)
@@ -479,8 +519,8 @@
default:
/* Try to parse it as an ELF header */
- Seek(kfd, 0);
- if ((Read(kfd, (void *)&kexec_elf, sizeof(kexec_elf)) ==
+ KSeek(kfd, 0);
+ if ((KRead(kfd, (void *)&kexec_elf, sizeof(kexec_elf)) ==
sizeof(kexec_elf)) &&
(memcmp(&kexec_elf.e_ident[EI_MAG0], ELFMAG, SELFMAG) == 0)) {
elf_kernel = 1;
@@ -501,8 +541,8 @@
Puts("Unable to allocate memory for program headers\n");
goto Fail;
}
- Seek(kfd, kexec_elf.e_phoff);
- if (Read(kfd, (void *)kernel_phdrs,
+ KSeek(kfd, kexec_elf.e_phoff);
+ if (KRead(kfd, (void *)kernel_phdrs,
kexec_elf.e_phnum*sizeof(*kernel_phdrs)) !=
kexec_elf.e_phnum*sizeof(*kernel_phdrs)) {
Puts("Unable to read program headers from kernel file\n");
@@ -557,32 +597,32 @@
/* read the text and data segments from the kernel image */
if (elf_kernel)
for (i = 0; i < kexec_elf.e_phnum; i++) {
- if (Seek(kfd, kernel_phdrs[i].p_offset) == -1) {
+ if (KSeek(kfd, kernel_phdrs[i].p_offset) == -1) {
Printf("Failed to seek to segment %ld\n", i);
goto Fail;
}
- if (Read(kfd, memptr+kernel_phdrs[i].p_vaddr-PAGE_SIZE,
- kernel_phdrs[i].p_filesz) != kernel_phdrs[i].p_filesz) {
+ if (KRead(kfd, memptr+kernel_phdrs[i].p_vaddr-PAGE_SIZE,
+ kernel_phdrs[i].p_filesz) != kernel_phdrs[i].p_filesz) {
Printf("Failed to read segment %ld\n", i);
goto Fail;
}
}
else {
- if (Seek(kfd, text_offset) == -1) {
+ if (KSeek(kfd, text_offset) == -1) {
Puts("Failed to seek to text\n");
goto Fail;
}
- if (Read(kfd, memptr, kexec.a_text) != kexec.a_text) {
+ if (KRead(kfd, memptr, kexec.a_text) != kexec.a_text) {
Puts("Failed to read text\n");
goto Fail;
}
/* data follows immediately after text */
- if (Read(kfd, memptr+kexec.a_text, kexec.a_data) != kexec.a_data) {
+ if (KRead(kfd, memptr+kexec.a_text, kexec.a_data) != kexec.a_data) {
Puts("Failed to read data\n");
goto Fail;
}
}
- Close(kfd);
+ KClose(kfd);
kfd = -1;
/* Check kernel's bootinfo version */
@@ -706,7 +746,7 @@
/* Clean up and exit in case of a failure */
Fail:
if (kfd != -1)
- Close(kfd);
+ KClose(kfd);
if (rfd != -1)
Close(rfd);
if (memptr)
@@ -748,37 +788,17 @@
* Determine the CPU Type
*/
-/* Dectection of 68030 and up is unreliable, so we check ourself */
-/* Keep consistent with asm/setup.h! */
-/* 24.11.1996 Joerg Dorchain */
-asm( ".text\n"
-ALIGN_STR "\n"
-SYMBOL_NAME_STR(check346) ":
- orw #0x700,%sr | disable ints
- movec %vbr,%a0 | get vbr
- movel %a0@(11*4),%a1 | save old trap vector (Line F)
- movel #L1,%a0@(11*4) | set L1 as new vector
- movel %sp,%d1 | save stack pointer
- moveq #2,%d0 | value with exception (030)
- .long 0xf6208000 | move16 %a0@+,%a0@+, the 030 test instruction
- nop | clear instruction pipeline
- movel %d1,%sp | restore stack pointer
- movec %vbr,%a0 | get vbr again
- moveq #4,%d0 | value with exception (040)
- .word 0xf5c8 | plpar %a0@, the 040 test instruction
- nop | clear instruction pipeline
- moveq #8,%d0 | value if we come here
-L1: movel %d1,%sp | restore stack pointer
- movel %a1,%a0@(11*4) | restore vector
- rte"
-);
-
static void get_processor(u_long *cpu, u_long *fpu, u_long *mmu)
{
- if (SysBase->AttnFlags & (AFF_68030|AFF_68040|AFF_68060))
- *cpu = Supervisor(check346);
+ *cpu = *fpu = 0;
+ if (SysBase->AttnFlags & AFF_68060)
+ *cpu = CPU_68060;
+ else if (SysBase->AttnFlags & AFF_68040)
+ *cpu = CPU_68040;
+ else if (SysBase->AttnFlags & AFF_68030)
+ *cpu = CPU_68030;
else if (SysBase->AttnFlags & AFF_68020)
- *cpu = CPU_68020;
+ *cpu = CPU_68020;
if (*cpu == CPU_68040 || *cpu == CPU_68060) {
if (SysBase->AttnFlags & AFF_FPU40)
*fpu = *cpu;
@@ -786,7 +806,7 @@
if (SysBase->AttnFlags & AFF_68882)
*fpu = FPU_68882;
else if (SysBase->AttnFlags & AFF_68881)
- *cpu = FPU_68881;
+ *fpu = FPU_68881;
}
*mmu = *cpu;
}
@@ -806,7 +826,7 @@
else {
if (debugflag)
Puts(" Chipset: ");
- switch(chipset) {
+ switch (chipset) {
case CS_STONEAGE:
if (debugflag)
Puts("Old or unknown\n");
@@ -1361,7 +1381,7 @@
Disable();
*nic_cr = 0x21; /* nic command register: software reset etc. */
- while(((*nic_isr & 0x80) == 0) && --n) /* wait for reset to complete */
+ while (((*nic_isr & 0x80) == 0) && --n) /* wait for reset to complete */
;
Enable();
@@ -1373,3 +1393,294 @@
#error reset_a2060: not yet implemented
}
#endif
+
+
+#ifdef ZKERNEL
+
+#define ZFILE_CHUNK_BITS 16 /* chunk is 64 KB */
+#define ZFILE_CHUNK_SIZE (1 << ZFILE_CHUNK_BITS)
+#define ZFILE_CHUNK_MASK (ZFILE_CHUNK_SIZE-1)
+#define ZFILE_N_CHUNKS (2*1024*1024/ZFILE_CHUNK_SIZE)
+
+/* variables for storing the uncompressed data */
+static char *ZFile[ZFILE_N_CHUNKS];
+static int ZFileSize = 0;
+static int ZFpos = 0;
+static int Zwpos = 0;
+
+static int Zinfd = 0; /* fd of compressed file */
+
+/*
+ * gzip declarations
+ */
+
+#define OF(args) args
+
+#define memzero(s, n) memset ((s), 0, (n))
+
+typedef unsigned char uch;
+typedef unsigned short ush;
+typedef unsigned long ulg;
+
+#define INBUFSIZ 4096
+#define WSIZE 0x8000 /* window size--must be a power of two, and */
+ /* at least 32K for zip's deflate method */
+
+static uch *inbuf;
+static uch *window;
+
+static unsigned insize = 0; /* valid bytes in inbuf */
+static unsigned inptr = 0; /* index of next byte to be processed in inbuf */
+static unsigned outcnt = 0; /* bytes in output buffer */
+static int exit_code = 0;
+static long bytes_out = 0;
+
+#define get_byte() (inptr < insize ? inbuf[inptr++] : fill_inbuf())
+
+/* Diagnostic functions (stubbed out) */
+#define Assert(cond,msg)
+#define Trace(x)
+#define Tracev(x)
+#define Tracevv(x)
+#define Tracec(c,x)
+#define Tracecv(c,x)
+
+#define STATIC static
+
+static int fill_inbuf(void);
+static void flush_window(void);
+static void error(char *m);
+static void gzip_mark(void **);
+static void gzip_release(void **);
+
+#define malloc(x) AllocVec(x, MEMF_FAST | MEMF_PUBLIC)
+#define free(x) FreeVec(x)
+
+#ifdef LILO
+#include "inflate.c"
+#else
+#include "../../../../lib/inflate.c"
+#endif
+
+static void gzip_mark(void **ptr)
+{
+}
+
+static void gzip_release(void **ptr)
+{
+}
+
+
+/*
+ * Fill the input buffer. This is called only when the buffer is empty
+ * and at least one byte is really needed.
+ */
+static int fill_inbuf(void)
+{
+ if (exit_code)
+ return -1;
+
+ insize = Read(Zinfd, inbuf, INBUFSIZ);
+ if (insize <= 0)
+ return -1;
+
+ inptr = 1;
+ return(inbuf[0]);
+}
+
+/*
+ * Write the output window window[0..outcnt-1] and update crc and bytes_out.
+ * (Used for the decompressed data only.)
+ */
+static void flush_window(void)
+{
+ ulg c = crc; /* temporary variable */
+ unsigned n;
+ uch *in, ch;
+ int chunk = Zwpos >> ZFILE_CHUNK_BITS;
+
+ if (exit_code)
+ return;
+
+ if (chunk >= ZFILE_N_CHUNKS) {
+ error("Compressed image too large! Aborting.\n");
+ return;
+ }
+ if (!ZFile[chunk]) {
+ if (!(ZFile[chunk] = (char *)AllocMem(ZFILE_CHUNK_SIZE,
+ MEMF_FAST | MEMF_PUBLIC))) {
+ error("Out of memory for decompresing kernel image\n");
+ return;
+ }
+ }
+ memcpy(ZFile[chunk] + (Zwpos & ZFILE_CHUNK_MASK), window, outcnt);
+ Zwpos += outcnt;
+
+#define DISPLAY_BITS 10
+ if ((Zwpos & ((1 << DISPLAY_BITS)-1)) == 0)
+ PutChar('.');
+
+ in = window;
+ for (n = 0; n < outcnt; n++) {
+ ch = *in++;
+ c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8);
+ }
+ crc = c;
+ bytes_out += (ulg)outcnt;
+ outcnt = 0;
+}
+
+static void error(char *x)
+{
+ Printf("\n%s", x);
+ exit_code = 1;
+}
+
+static inline int call_sub(int (*func)(void), void *stackp)
+{
+ register int _res __asm("d0");
+ register int (*a0)(void) __asm("a0") = func;
+ register int (*a1)(void) __asm("a1") = stackp;
+
+ __asm __volatile ("movel sp,a2;"
+ "movel a1,sp;"
+ "jsr a0@;"
+ "movel a2,sp"
+ : "=r" (_res)
+ : "r" (a0), "r" (a1)
+ : "a0", "a1", "a2", "d0", "d1", "memory");
+ return(_res);
+}
+
+static int load_zkernel(int fd)
+{
+ int i, err = -1;
+#define ZSTACKSIZE (16384)
+ u_long *zstack;
+
+ for (i = 0; i < ZFILE_N_CHUNKS; ++i)
+ ZFile[i] = NULL;
+ Zinfd = fd;
+ Seek(fd, 0);
+
+ if (!(inbuf = (uch *)AllocMem(INBUFSIZ, MEMF_FAST | MEMF_PUBLIC)))
+ Puts("Couldn't allocate gunzip buffer\n");
+ else {
+ if (!(window = (uch *)AllocMem(WSIZE, MEMF_FAST | MEMF_PUBLIC)))
+ Puts("Couldn't allocate gunzip window\n");
+ else {
+ if (!(zstack = (u_long *)AllocMem(ZSTACKSIZE,
+ MEMF_FAST | MEMF_PUBLIC)))
+ Puts("Couldn't allocate gunzip stack\n");
+ else {
+ Puts("Uncompressing kernel image ");
+ makecrc();
+ if (!(err = call_sub(gunzip, (char *)zstack+ZSTACKSIZE)))
+ Puts("done\n");
+ ZFileSize = Zwpos;
+ FreeMem(zstack, ZSTACKSIZE);
+ }
+ FreeMem(window, WSIZE);
+ window = NULL;
+ }
+ FreeMem(inbuf, INBUFSIZ);
+ inbuf = NULL;
+ }
+ Close(Zinfd); /* input file not needed anymore */
+ return(err);
+}
+
+
+/* Note about the read/lseek wrapper and its memory management: It assumes
+ * that all seeks are only forward, and thus data already read or skipped can
+ * be freed. This is true for current organization of bootstrap and kernels.
+ * Little exception: The struct kexec at the start of the file. After reading
+ * it, there may be a seek back to the end of the file. But this currently
+ * doesn't hurt. (Roman)
+ */
+
+static int KRead(int fd, void *buf, int cnt)
+{
+ unsigned done = 0;
+
+ if (!ZFileSize)
+ return(Read(fd, buf, cnt));
+
+ if (ZFpos + cnt > ZFileSize)
+ cnt = ZFileSize - ZFpos;
+
+ while (cnt > 0) {
+ unsigned chunk = ZFpos >> ZFILE_CHUNK_BITS;
+ unsigned endchunk = (chunk+1) << ZFILE_CHUNK_BITS;
+ unsigned n = cnt;
+
+ if (ZFpos + n > endchunk)
+ n = endchunk - ZFpos;
+ memcpy(buf, ZFile[chunk] + (ZFpos & ZFILE_CHUNK_MASK), n);
+ cnt -= n;
+ buf += n;
+ done += n;
+ ZFpos += n;
+
+ if (ZFpos == endchunk) {
+ FreeMem(ZFile[chunk], ZFILE_CHUNK_SIZE);
+ ZFile[chunk] = NULL;
+ }
+ }
+
+ return(done);
+}
+
+
+static int KSeek(int fd, int offset)
+{
+ unsigned oldpos, oldchunk, newchunk;
+
+ if (!ZFileSize)
+ return(Seek(fd, offset));
+
+ oldpos = ZFpos;
+ ZFpos = offset;
+ if (ZFpos < 0) {
+ ZFpos = 0;
+ return(-1);
+ } else if (ZFpos > ZFileSize) {
+ ZFpos = ZFileSize;
+ return(-1);
+ }
+
+ /* free memory of skipped-over data */
+ oldchunk = oldpos >> ZFILE_CHUNK_BITS;
+ newchunk = ZFpos >> ZFILE_CHUNK_BITS;
+ while(oldchunk < newchunk) {
+ if (ZFile[oldchunk]) {
+ FreeMem(ZFile[oldchunk], ZFILE_CHUNK_SIZE);
+ ZFile[oldchunk] = NULL;
+ }
+ ++oldchunk;
+ }
+ return(ZFpos);
+}
+
+
+static void free_zfile(void)
+{
+ int i;
+
+ for (i = 0; i < ZFILE_N_CHUNKS; ++i)
+ if (ZFile[i]) {
+ FreeMem(ZFile[i], ZFILE_CHUNK_SIZE);
+ ZFile[i] = NULL;
+ }
+}
+
+static int KClose(int fd)
+{
+ if (ZFileSize) {
+ free_zfile();
+ ZFileSize = 0;
+ } else
+ Close(fd);
+ return(0);
+}
+#endif /* ZKERNEL */
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov