From:     Digestifier <Linux-Development-Request@senator-bedfellow.mit.edu>
To:       Linux-Development@senator-bedfellow.mit.edu
Reply-To: Linux-Development@senator-bedfellow.mit.edu
Date:     Sat, 25 Sep 93 20:13:07 EDT
Subject:  Linux-Development Digest #128

Linux-Development Digest #128, Volume #1         Sat, 25 Sep 93 20:13:07 EDT

Contents:
  SLIP server code exists? (Laszlo Herczeg)
  Re: What do people think about /config? (Andreas Klemm)
  Re: No smart serial boards??? (Cliff Skolnick)
  Re: Linux Slowly Dying Off? (Guru Aleph_Null)
  ELF binary support in pl13 - what is it? (Eugene Kim)
  ** The original IDE patch - updated (was Re: CORRECTED NEW multiple sector transfer for IDE drives (Mark Lord)

----------------------------------------------------------------------------

From: las@r-node.io.org (Laszlo Herczeg)
Subject: SLIP server code exists?
Date: 25 Sep 1993 14:24:14 -0400


 All the net-2 docs discuss SLIP from the client's point of view.
 
Now, what if I wanted to create a SLIP server on _my_ machine to accept
connections?
 
 Does the device driver for this exist or once again we have to go to the
SUN sources?
 
 

-- 
================================================================================
Laszlo Herczeg              E-mail: las@io.org
Nothing fails like success.
================================================================================

------------------------------

From: andreas@knobel.knirsch.de (Andreas Klemm)
Subject: Re: What do people think about /config?
Date: 25 Sep 1993 19:15:48 -0000

imcclogh@cs.ucsd.edu (Ian McCloghrie) writes:

>warb@faatcrl.faa.gov (Dan Warburton) writes:

>>BTW /etc/ probably started out this way.

>       /conf is a mistake.  If all the binaries are moved out of /etc
>into /bin and /sbin, depending upon what they are (as is a standard unix
>practice these days) then that leaves /etc with... guess what.  Config
>files.  Since /conf and /etc now have identical contents, it makes little
>to no sense to have both.  If one should be eliminated, then, IMHO, for
>lack of any better reason, why not keep the one that is used on every unix
>system out there, instead of the one that's new and unknown?

Very good idea !
-- 
Andreas Klemm                 /\/\____ Wiechers & Partner Datentechnik GmbH 
andreas@knobel.knirsch.de ___/\/\/     andreas@sunny.wup.de (Unix Support)

------------------------------

From: cliffs@hip-hop.suvl.ca.us (Cliff Skolnick)
Subject: Re: No smart serial boards???
Date: Sat, 25 Sep 1993 20:21:20 GMT

Jon Brawn (jonb@specialix.com) wrote:
: Cost:
:       Financially, we start getting into an area whereby people get
:       a little reluctant to pay $1500 for a 16 port I/O solution...

[lot's of stuff deleted]

:     Dumb:
:       I/O4    AST four port look-alike                        $   300

The kuowell AST clone is $104 in the SF bay area.   I'm sure
you could get many of these places to ship one to you.  The price is for
16550's, it's only $69 with 16450's.  Finally got all the serial ports
I need :-).

[Price list :-( deleted]

------------------------------

Crossposted-To: comp.os.linux.help,comp.os.linux.misc
From: spj@ukelele.gcr.com (Guru Aleph_Null)
Subject: Re: Linux Slowly Dying Off?
Date: Sat, 25 Sep 1993 20:54:37 GMT

jcburt@gats486.larc.nasa.gov writes:

>In article <sxjcb.8.0011F26F@alaska.edu> sxjcb@alaska.edu (Jay Beavers) writes:

>   In article <27t2au$3v1@samba.oit.unc.edu> mdw@sunSITE.unc.edu (Matt Welsh) writes:

>   >It's not dying off... not at all. It's simply stabilizing. Things aren't
>   >changing as rapidly, which is a sign of maturity. 

>   So, like, CP/M is really really mature now?

>Yep! kinda like a corpse...'bout as mature as its gonna get...:-)

Perhaps its time to recruit this years frosh who are starting college. Find
a frosh and install Linux for them on their PeeCees.

>--
>John Burton                      G & A Technical Software, Inc.
>jcburt@gatsibm.larc.nasa.gov     28 Research Dr. Hampton, Va. 23666
>jcburt@gats486.larc.nasa.gov     (804) 865-7491
-- 
=========================================================================
     Simon "Guru Aleph-Null" Janes     |... don't crespt the weasal ...
         <spj@ukelele.gcr.com>         |... just think of the master ...
                                       |... feel the grass, softly ...

------------------------------

Subject: ELF binary support in pl13 - what is it?
From: eekim@husc11.harvard.edu (Eugene Kim)
Date: 25 Sep 93 21:30:38 GMT

I just read Linus's announcement of pl13, and noticed that ELF binary
support has been integrated into the kernel.  Not having followed previous
ELF posts carefully, my knowledge of this is limited.

I'm curious to know what exactly is ELF?  My understanding is that it is
a binary used by other UNIX.  Does this mean Linux can now run certain other
UNIX binaries?  (If so, that's really cool :)

-- 
== Eugene Eric Kim =========================================================
== eekim@husc.harvard.edu ==================================================
==       "Dangerous stuff, science.  Lots of us not fit for it."          ==
========================================= -H.C. Bailey, "The Long Dinner" ==

------------------------------

From: mlord@bnr.ca (Mark Lord)
Subject: ** The original IDE patch - updated (was Re: CORRECTED NEW multiple sector transfer for IDE drives
Date: Sat, 25 Sep 93 15:56:06 GMT

In article <1993Sep24.224925.2490@sol.cs.wmich.edu> stempien@cs.wmich.edu writes:
>
>Has anyone done tests between the patch by Mark Lord and this one?? I like
>the first one's test output, does this one have the IDE drive output?? What
>about the speedups, reliability, and dropped interupts of both.

They should be more or less identical.  My patches are slightly more streamlined
for 16-bit IDE interfaces, and contain cleaner interrupt enable/disable logic.

However, both his & mine suffer from non-recoverability from disk write errors
when multiple_mode > 2 sectors.  This is due to no attempt to redesign the
error recovery logic that was already in place.  This is also not likely to
be a problem for most of us, but it does explain how people with older drives
that are not 100% up to spec mamanged to trash their filesystems.

For those who care, here are my current IDE patches.  I am working on a more
complete overhaul/replacement of hd.c et al. to permit full error recovery
and to unmask interrupts much more.  That version will eventually be submitted
to Linus, but certainly not my current version (below).


-ml

This patch is for users with IDE drives that support the
"multiple sector mode" for data transfers.  It should also be
compatible (harmless) with anything else that uses hd.c for its driver.

An earlier version of this patch was posted to comp.os.linux.development,
and this revision is based largely on the responses/results observed.

All problems reported thus far have been addressed (I think), largely by
a change which disables "multiple mode" on any drive that fails the ID test
or which supports fewer than 32 sectors in multiple mode (most[ly] older 
drives).  Regardless of that criteria, 8-sector multiple mode is the maximum
ever enabled, as it gives virtually all of the benefits (10-20% faster
data transfers, 10-20% less kernel overhead), without increasing scheduler
latencies by too much.  Interupts are briefly enabled between transfers of
individual sectors, just as without multiple mode, to avoid interfering
with high-speed communications and whatnot.

Be paranoid and use this with your root partition mounted read-only
(and fsck commented out of /etc/rc* ) the first time you try it.

-ml     mlord@bnr.ca

=================cut here===================

--- hd.c.pl12   Tue Aug 31 19:14:40 1993
+++ hd.c        Sat Sep 11 00:23:09 1993
@@ -92,6 +92,83 @@
 #define port_write(port,buf,nr) \
 __asm__("cld;rep;outsw": :"d" (port),"S" (buf),"c" (nr):"cx","si")
 
+#define VERBOSE_DRIVEID 0      /* set to 1 for more drive info at boot time */
+#define MAX_MULTIPLE   8       /* change to 1 to disable multiple mode support */
+#define WIN_MULTREAD   0xC4    /* read multiple sectors - move to hdreg.h someday */
+#define WIN_MULTWRITE  0xC5    /* write multiple sectors - move to hdreg.h someday */
+#define WIN_SETMULT    0xC6    /* enable read/write multiple - move to hdreg.h someday */
+#define WIN_IDENTIFY   0xEC    /* ask drive to identify itself - move to hdreg.h someday */
+
+static int mult_count[MAX_HD] = {0,}; 
+
+#if VERBOSE_DRIVEID
+
+char *cfg_str[] =
+{      "", " HardSect", " SoftSect", " NotMFM", " HdSw>15uSec", " SpinMotCtl",
+       " Fixed", " Removeable", " DTR<=5Mbs", " DTR>5Mbs", " DTR>10Mbs",
+       " RotSpdTol>.5%", " dStbOff", " TrkOff", " FmtGapReq", "",
+};
+
+char *SlowMedFast[] = {"slow", "medium", "fast"};
+char *BuffType[] = {"?", "1Sect", "DualPort", "DualPortCache"};
+
+#define YN(b)  (((b)==0)?"no":"yes")
+
+static void rawstring (char *prefix, char *s, int n)
+{
+       printk(prefix);
+       if (*s) {
+               int i;
+               for (i=0; i < n && s[i^1] == ' '; ++i); /* strip blanks */
+               for (; i < n && s[i^1]; ++i)
+                       if (s[i^1] != ' ' || ((i+1) < n && s[(i+1)^1] != ' '))
+                               printk("%c",s[i^1]);
+       }
+}
+
+static void dmpstr (char *prefix, unsigned int i, char *s[], unsigned int maxi)
+{
+       printk(prefix);
+       if (i > maxi)
+               printk("?");
+       else
+               printk(s[i]);
+}
+
+static void dump_identity (unsigned int dev, unsigned short ib[])
+{
+       int i;
+       printk ("\n+-------------------------------------------------------------------+\n");
+       printk ("hd%c:  Drive Identification Info:\n", dev+'a');
+       rawstring (" Model=",(char *)&ib[27],40);
+       rawstring (", FwRev=",(char *)&ib[23],8);
+       rawstring (", SerialNo=",(char *)&ib[10],20);
+       printk ("\n Config={");
+       for (i=0; i<=15; i++) if (ib[0] & (1<<i)) printk (cfg_str[i]);
+       printk (" }\n");
+       printk (" Default c/h/s=%d/%d/%d, TrkSize=%d, SectSize=%d, ECCbytes=%d\n",
+               ib[1],ib[3],ib[6],ib[4],ib[5], ib[22]);
+       dmpstr (" BuffType=",ib[20],BuffType,3);
+       ib[47] &= 0xFF;
+       printk (", BuffSize=%dKB, MaxMultSect=%d\n", ib[21]/2, ib[47]);
+       printk (" Features: DblWordIO=%s, LBA=%s, DMA=%s",
+               YN(ib[48]&1),YN(ib[49]&0x20),YN(ib[49]&0x10));
+       dmpstr (", tPIO=",ib[51]>>8,SlowMedFast,2);
+       if (ib[49]&0x10 && (ib[53]&1)==0)
+               dmpstr (", tDMA=",ib[52]>>8,SlowMedFast,2);
+       printk ("\n (%s): Current c/h/s=%d/%d/%d, TotSect=%d",
+               (((ib[53]&1)==0)?"maybe":"valid"),
+               ib[54],ib[55],ib[56],*(int *)&ib[57]);
+       if (ib[49]&0x20)
+               printk (", LBAsect=%d", *(int *)&ib[60]);
+       printk ("\n (%s): CurMultSect=%d", ((ib[53]&1)==0)?"maybe":"valid",
+               (ib[59]&0x10)?ib[59]&0xFF:0);
+       if (ib[49]&0x10)
+               printk (", DMA-1w=%04X, DMA-mw=%04X", ib[62], ib[63]);
+       printk ("\n+-------------------------------------------------------------------+\n\n");
+}
+#endif /* VERBOSE_DRIVEID */
+
 #if (HD_DELAY > 0)
 unsigned long read_timer(void)
 {
@@ -141,54 +218,12 @@
        return 1;
 }
 
-static int controller_busy(void);
-static int status_ok(void);
-
-static int controller_ready(unsigned int drive, unsigned int head)
-{
-       int retry = 100;
-
-       do {
-               if (controller_busy() & BUSY_STAT)
-                       return 0;
-               outb_p(0xA0 | (drive<<4) | head, HD_CURRENT);
-               if (status_ok())
-                       return 1;
-       } while (--retry);
-       return 0;
-}
-
-static int status_ok(void)
-{
-       unsigned char status = inb_p(HD_STATUS);
-
-       if (status & BUSY_STAT)
-               return 1;
-       if (status & WRERR_STAT)
-               return 0;
-       if (!(status & READY_STAT))
-               return 0;
-       if (!(status & SEEK_STAT))
-               return 0;
-       return 1;
-}
-
-static int controller_busy(void)
-{
-       int retries = 100000;
-       unsigned char status;
-
-       do {
-               status = inb_p(HD_STATUS);
-       } while ((status & BUSY_STAT) && --retries);
-       return status;
-}
-
 static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
                unsigned int head,unsigned int cyl,unsigned int cmd,
                void (*intr_addr)(void))
 {
        unsigned short port;
+       int retries = 100000;
 
        if (drive>1 || head>15)
                panic("Trying to write bad sector");
@@ -198,7 +233,8 @@
 #endif
        if (reset)
                return;
-       if (!controller_ready(drive, head)) {
+       while (inb_p(HD_STATUS) & BUSY_STAT && --retries);
+       if (!retries) {
                reset = 1;
                return;
        }
@@ -214,6 +250,61 @@
        outb_p(cmd,++port);
 }
 
+static void set_multiple_intr(void)
+{
+       unsigned int dev = MINOR(CURRENT->dev) >> 6;
+
+       if (inb_p(HD_STATUS) & (BUSY_STAT | ERR_STAT)) {
+               mult_count[dev] = 1;
+               sti();
+               printk ("  hd%c: disabled multiple mode\n", dev+'a');
+       } else {
+               sti();
+               printk ("  hd%c: Enabled %d-sector multiple mode\n",
+                       dev+'a', mult_count[dev]);
+       }
+       do_hd_request();
+       return;
+}
+
+static void identify_intr(void)
+{
+       unsigned short ib[64];
+       unsigned dev = MINOR(CURRENT->dev) >> 6;
+
+       if ((inb_p(HD_STATUS)&(BUSY_STAT|ERR_STAT)) == 0) {
+               port_read(HD_DATA,(char *) ib,64);
+               sti();
+#if VERBOSE_DRIVEID
+               dump_identity(dev, ib);
+#endif /* VERBOSE_DRIVEID */
+               if (ib[27]) {
+                       int i;
+                       for (i=27; i<= 46; i++)
+                               ib[i] = (ib[i]>>8) | (ib[i]<<8);
+                       printk ("  hd%c: %-.40s (%dMB IDE w/%dKB Cache)\n",
+                               dev+'a', &ib[27], ib[1]*ib[3]*ib[6] / 2048, ib[21]>>1);
+                       /* skip troublesome older drives with (MaxMult < 32) */
+                       if ((i = ib[47] & 0xff) >= 32)
+                               mult_count[dev] = MAX_MULTIPLE;
+               }
+               port_read(HD_DATA,(char *) &ib,64);
+               port_read(HD_DATA,(char *) &ib,64);
+               port_read(HD_DATA,(char *) &ib,64);
+               if (mult_count[dev] > 1) {
+                       cli();
+                       hd_out(dev,mult_count[dev],0,0,0,WIN_SETMULT,&set_multiple_intr);
+                       if (!reset)
+                               return;
+               }
+       }
+       mult_count[dev] = 1;
+       sti();
+       printk ("  hd%c: disabled multiple mode\n", dev+'a');
+       do_hd_request();
+       return;
+}
+
 static int drive_busy(void)
 {
        unsigned int i;
@@ -249,6 +340,8 @@
 
 repeat:
        if (reset) {
+               for (i=0; i < NR_HD; i++)
+                       mult_count[i] = 1;      /* disable multiple mode */
                reset = 0;
                i = -1;
                reset_controller();
@@ -301,16 +394,6 @@
        /* Otherwise just retry */
 }
 
-static inline int wait_DRQ(void)
-{
-       int retries = 100000;
-
-       while (--retries > 0)
-               if (inb_p(HD_STATUS) & DRQ_STAT)
-                       return 0;
-       return -1;
-}
-
 #define STAT_MASK (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT)
 #define STAT_OK (READY_STAT | SEEK_STAT)
 
@@ -318,6 +401,8 @@
 {
        int i;
        int retries = 100000;
+       unsigned int dev = MINOR(CURRENT->dev) >> 6;
+       int nsect = mult_count[dev];
 
        do {
                i = (unsigned) inb_p(HD_STATUS);
@@ -335,10 +420,10 @@
                printk("HD: read_intr: error = 0x%02x\n",hd_error);
        }
        bad_rw_intr();
-       cli();
        do_hd_request();
        return;
 ok_to_read:
+       cli();
        port_read(HD_DATA,CURRENT->buffer,256);
        CURRENT->errors = 0;
        CURRENT->buffer += 512;
@@ -346,18 +431,30 @@
        i = --CURRENT->nr_sectors;
        --CURRENT->current_nr_sectors;
 #ifdef DEBUG
-       printk("hd%d : sector = %d, %d remaining to buffer = %08x\n",
-               MINOR(CURRENT->dev), CURRENT->sector, i, CURRENT-> 
+       printk("hd%c: RD: sector = %d, %d remaining to buffer = %08x\n",
+               dev+'a', CURRENT->sector, i, CURRENT-> 
                buffer);
 #endif
        if (!i || (CURRENT->bh && !SUBSECTOR(i)))
                end_request(1);
        if (i > 0) {
+               if (--nsect) {
+                       sti();  /* give other interrupts a chance */
+                       goto ok_to_read;
+               }
                SET_INTR(&read_intr);
-               sti();
                return;
        }
-       (void) inb_p(HD_STATUS);
+#if (HD_DELAY > 0)
+       last_req = read_timer();
+#endif
+       do_hd_request();
+       return;
+}
+
+static void final_write_intr(void)
+{
+       end_request(1);
 #if (HD_DELAY > 0)
        last_req = read_timer();
 #endif
@@ -369,6 +466,8 @@
 {
        int i;
        int retries = 100000;
+       unsigned int dev = MINOR(CURRENT->dev) >> 6;
+       int nsect = mult_count[dev];
 
        do {
                i = (unsigned) inb_p(HD_STATUS);
@@ -376,7 +475,7 @@
                        continue;
                if ((i & STAT_MASK) != STAT_OK)
                        break;
-               if ((CURRENT->nr_sectors <= 1) || (i & DRQ_STAT))
+               if (i & DRQ_STAT)
                        goto ok_to_write;
        } while (--retries > 0);
        sti();
@@ -386,26 +485,29 @@
                printk("HD: write_intr: error = 0x%02x\n",hd_error);
        }
        bad_rw_intr();
-       cli();
-       do_hd_request();
        return;
 ok_to_write:
+#ifdef DEBUG
+       printk("hd%c: WR: sector = %d, %d remaining to buffer = %08x\n",
+               dev+'a', CURRENT->sector, i, CURRENT-> 
+               buffer);
+#endif
+       cli();
+       port_write(HD_DATA,CURRENT->buffer,256);
        CURRENT->sector++;
        i = --CURRENT->nr_sectors;
        --CURRENT->current_nr_sectors;
        CURRENT->buffer += 512;
-       if (!i || (CURRENT->bh && !SUBSECTOR(i)))
-               end_request(1);
        if (i > 0) {
+               if (CURRENT->bh && !SUBSECTOR(i))
+                       end_request(1);
+               if (--nsect) {
+                       sti();  /* give other interrupts a chance */
+                       goto ok_to_write;
+               }
                SET_INTR(&write_intr);
-               port_write(HD_DATA,CURRENT->buffer,256);
-               sti();
-       } else {
-#if (HD_DELAY > 0)
-               last_req = read_timer();
-#endif
-               do_hd_request();
-       }
+       } else
+               SET_INTR(&final_write_intr);
        return;
 }
 
@@ -452,22 +554,24 @@
        unsigned int sec,head,cyl,track;
        unsigned int nsect;
 
+       cli();
        if (CURRENT && CURRENT->dev < 0) return;
 
        if (DEVICE_INTR)
                return;
 repeat:
-       timer_active &= ~(1<<HD_TIMER);
-       sti();
+       CLEAR_TIMER;
        INIT_REQUEST;
+       sti();
        dev = MINOR(CURRENT->dev);
        block = CURRENT->sector;
        nsect = CURRENT->nr_sectors;
        if (dev >= (NR_HD<<6) || block >= hd[dev].nr_sects) {
 #ifdef DEBUG
-               printk("hd%d : attempted read for sector %d past end of device at sector %d.\n",
-                       block, hd[dev].nr_sects);
+               printk("hd%c : attempted access on sector %d past end of device at sector %d.\n",
+                       (dev>>6)+'a', block, hd[dev].nr_sects);
 #endif
+               cli();
                end_request(0);
                goto repeat;
        }
@@ -477,10 +581,6 @@
        track = block / hd_info[dev].sect;
        head = track % hd_info[dev].head;
        cyl = track / hd_info[dev].head;
-#ifdef DEBUG
-       printk("hd%d : cyl = %d, head = %d, sector = %d, buffer = %08x\n",
-               dev, cyl, head, sec, CURRENT->buffer);
-#endif
        cli();
        if (reset) {
                int i;
@@ -488,7 +588,6 @@
                for (i=0; i < NR_HD; i++)
                        recalibrate[i] = 1;
                reset_hd();
-               sti();
                return;
        }
        if (recalibrate[dev]) {
@@ -496,27 +595,36 @@
                hd_out(dev,hd_info[dev].sect,0,0,0,WIN_RESTORE,&recal_intr);
                if (reset)
                        goto repeat;
-               sti();
                return;
        }       
-       if (CURRENT->cmd == WRITE) {
-               hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
-               if (reset)
+       if (!mult_count[dev]) {
+               hd_out(dev,0,0,0,0,WIN_IDENTIFY,identify_intr);
+               if (reset) {
+                       mult_count[dev] = 1;
                        goto repeat;
-               if (wait_DRQ()) {
-                       printk("HD: do_hd_request: no DRQ\n");
-                       bad_rw_intr();
-                       goto repeat;
                }
-               port_write(HD_DATA,CURRENT->buffer,256);
-               sti();
                return;
        }
        if (CURRENT->cmd == READ) {
-               hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);
+               unsigned int cmd = mult_count[dev] > 1 ? WIN_MULTREAD : WIN_READ;
+               hd_out(dev,nsect,sec,head,cyl,cmd,&read_intr);
                if (reset)
                        goto repeat;
-               sti();
+#ifdef DEBUG
+               printk("hd%c: Read  sect=%8d n=%3d (cyl%4d)\n", dev+'a', block, nsect, cyl);
+#endif
+               return;
+       }
+       if (CURRENT->cmd == WRITE) {
+               unsigned int cmd = mult_count[dev] > 1 ? WIN_MULTWRITE : WIN_WRITE;
+               hd_out(dev,nsect,sec,head,cyl,cmd,NULL);
+               if (reset)
+                       goto repeat;
+#ifdef DEBUG
+               printk("hd%c: Write sect=%8d n=%3d (cyl%4d)\n", dev+'a', block, nsect, cyl);
+#endif
+               cli();
+               write_intr();
                return;
        }
        panic("unknown hd-command");
@@ -559,7 +667,7 @@
                case BLKFLSBUF:
                        if(!suser())  return -EACCES;
                        if(!inode->i_rdev) return -EINVAL;
-                       sync_dev(inode->i_rdev);
+                       fsync_dev(inode->i_rdev);
                        invalidate_buffers(inode->i_rdev);
                        return 0;
 
@@ -617,7 +725,7 @@
        void (*handler)(void) = DEVICE_INTR;
 
        DEVICE_INTR = NULL;
-       timer_active &= ~(1<<HD_TIMER);
+       CLEAR_TIMER;
        if (!handler)
                handler = unexpected_hd_interrupt;
        handler();
-- 
mlord@bnr.ca    Mark Lord       BNR Ottawa,Canada       613-763-7482

------------------------------


** FOR YOUR REFERENCE **

The service address, to which questions about the list itself and requests
to be added to or deleted from it should be directed, is:

    Internet: Linux-Development-Request@NEWS-DIGESTS.MIT.EDU

You can send mail to the entire list (and comp.os.linux.development) via:

    Internet: Linux-Development@NEWS-DIGESTS.MIT.EDU

Linux may be obtained via one of these FTP sites:
    nic.funet.fi				pub/OS/Linux
    tsx-11.mit.edu				pub/linux
    sunsite.unc.edu				pub/Linux

End of Linux-Development Digest
******************************
