----- Forwarded message from stan <st...@panix.com> -----

From: stan <st...@panix.com>
To: Theo de Raadt <dera...@cvs.openbsd.org>
Subject: Re: watchdog suport for new hardware
Date: Tue, 26 Apr 2016 09:19:20 -0400
User-Agent: Mutt/1.5.4i
X-Operating-System: Debian GNU/Linux
X-Kernel-Version: 2.4.23
X-Uptime: 09:17:17 up 91 days,  8:18,  1 user,  load average: 0.00, 0.02, 0.04
X-Editor: gVim

Hee is the core of the functionality:


/*      $OpenBSD: selwd.c,v 1.0 2016/04/01 05:00:00 jsg Exp $   */
/*
 * Copyright (c) 2016 PREMIER System Integrators
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Schweitzer Engineering Laboratories: SEL-3355 Embedded controller
*/

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/malloc.h>

#include <machine/bus.h>

#include <dev/isa/isavar.h>
#include <dev/isa/selwdreg.h>

struct selwd_softc {
        /* sc_dev must be the first item in the struct */
        struct device           sc_dev;
        /* device access through bus space */
        bus_space_tag_t         sc_iot;
        bus_space_handle_t      sc_ioh;
};

int selwd_wait(bus_space_tag_t, bus_space_handle_t, bool);

/* Autoconfiguration glue */
int selwd_probe(struct device *, void *, void *);
void selwd_attach(struct device *, struct device *, void *);
int selwd_print(void *, const char *);
int selwd_wd_cb(void *, int);

/* functions to interact with the controller */
void selwd_write_wdog(bus_space_tag_t, bus_space_handle_t, int);
u_int8_t selwd_read_wdog(bus_space_tag_t, bus_space_handle_t);
u_int8_t selwd_read_status(bus_space_tag_t, bus_space_handle_t);
void selwd_abort(bus_space_tag_t, bus_space_handle_t);
u_int32_t selwd_read_boardid(bus_space_tag_t, bus_space_handle_t);
u_int32_t selwd_read_mcversion(bus_space_tag_t, bus_space_handle_t);
u_int16_t selwd_read_pciboardid(bus_space_tag_t, bus_space_handle_t);
u_int8_t selwd_read_ledctl0(bus_space_tag_t, bus_space_handle_t);
void selwd_write_ledctl0(bus_space_tag_t, bus_space_handle_t, u_int8_t);
u_int8_t selwd_read_ledctl1(bus_space_tag_t, bus_space_handle_t);
void selwd_write_ledctl1(bus_space_tag_t, bus_space_handle_t, u_int8_t);
u_int8_t selwd_read_miscctl0(bus_space_tag_t, bus_space_handle_t);
u_int8_t selwd_read_miscctl1(bus_space_tag_t, bus_space_handle_t);
void selwd_read_modelno(bus_space_tag_t, bus_space_handle_t, char*);
void selwd_read_serialno(bus_space_tag_t, bus_space_handle_t, char*);
void selwd_read_configid(bus_space_tag_t, bus_space_handle_t, char*);

/* macros to extract bits from the status register */
#define EC_STATUS_IBF(status)   (((status) >> 0x1) & 0x1)
#define EC_STATUS_OBF(status)   (((status) & 0x1))
#define EC_STATUS_BUSY(status)  (((status) >> 0x4) & 0x1)
#define EC_STATUS_STATE(status) (((status) >> 0x6) & 0x3)

struct cfattach selwd_ca = {
        sizeof(struct selwd_softc), selwd_probe, selwd_attach
};

struct cfdriver selwd_cd = {
        NULL, "selwd", DV_DULL
};

const char* selwd_models[] = { SELWD_DEV_3355, SELWD_DEV_3360 };

int selwd_wait(bus_space_tag_t iot, bus_space_handle_t ioh, bool useobf)
{
        uint32_t timeout = 0;
        uint8_t status = 0;

        while (timeout < 50) {
                status = bus_space_read_1(iot, ioh, SELWD_CMD);
                if ((EC_STATUS_IBF(status) == 0x0) &&
                        (EC_STATUS_BUSY(status) == 0x0) &&
                        (EC_STATUS_STATE(status) != 0x3) &&
                        ((EC_STATUS_OBF(status) == 0x0) || !useobf)) {
                        return 1;
                }
                delay(10);
                ++timeout;
        }
        return 0;
}

static __inline void
selwd_conf_enable(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t cmd)
{
        /* write the desired command to the command register */
        bus_space_write_1(iot, ioh, SELWD_CMD, cmd);
        /* wait for controller to be ready */
        selwd_wait(iot,ioh,0);
}

static __inline u_int8_t
selwd_conf_read(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t target)
{
        /* write the target address to the data register */
        bus_space_write_1(iot, ioh, SELWD_DATA, target);
        /* wait for controller to be ready */
        selwd_wait(iot, ioh,1);
        /* return the value from the data register */
        return (bus_space_read_1(iot, ioh, SELWD_DATA));
}

static __inline void
selwd_conf_write(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t target,
        u_int8_t data)
{
        /* write the target address to the data register */
        bus_space_write_1(iot, ioh, SELWD_DATA, target);
        /* wait for controller to be ready */
        selwd_wait(iot, ioh,0);
        /* write the desired data to the data register */
        bus_space_write_1(iot, ioh, SELWD_DATA, data);
        /* wait for controller to be ready */
        selwd_wait(iot, ioh,0);
}

int
selwd_probe(struct device *parent, void *match, void *aux)
{
        struct isa_attach_args *ia = aux;
        bus_space_tag_t iot;
        bus_space_handle_t ioh;

        /* Match by device ID */
        iot = ia->ia_iot;
        if (bus_space_map(iot, ia->ipa_io[0].base, SELWD_IOSIZE, 0, &ioh))
                return 0;
        
        /* print some diagnostic stuff */
        struct device *g1parent = parent->dv_parent;
        struct device *g2parent = g1parent->dv_parent;
        struct device *g3parent = g2parent->dv_parent;
        struct device *g4parent = g3parent->dv_parent;

        if (g4parent == NULL)
                printf("SEL device: parent=%s, g1parent=%s, g2parent=%s, 
g3parent=%s, 
g4parent=NULL\n",parent->dv_xname,g1parent->dv_xname,g2parent->dv_xname,g3parent->dv_xname);
      
        else    
                printf("SEL device: parent=%s, g1parent=%s, g2parent=%s, 
g3parent=%s, 
g4parent=%s\n",parent->dv_xname,g1parent->dv_xname,g2parent->dv_xname,g3parent->dv_xname,g4parent->dv_xname);
     
        
        /* read model number */
        char *model = malloc(sizeof(char)*16, M_DEVBUF, M_WAITOK | M_ZERO);
        selwd_read_modelno(iot, ioh, model);
        
        bus_space_unmap(iot, ioh, SELWD_IOSIZE);

        /* model number must match pre-defined values */        
        int i;
        for (i=0; selwd_models[i]; i++) {
                if (!strcmp(model,selwd_models[i])) {   
                        ia->ipa_nio = 1;
                        ia->ipa_io[0].length = SELWD_IOSIZE;
                        ia->ipa_nmem = 0;
                        ia->ipa_nirq = 0;
                        ia->ipa_ndrq = 0;
                        return 1;
                }
                i++;
        }
        free(model, M_DEVBUF, 0);
        return 0;
}

void
selwd_attach(struct device *parent, struct device *self, void *aux)
{
        struct selwd_softc      *sc = (struct selwd_softc *)self;
        struct isa_attach_args  *ia = aux;
        struct isa_attach_args  nia;
        u_int32_t devid;
        
        /* Map ISA I/O space */
        sc->sc_iot = ia->ia_iot;
        if (bus_space_map(sc->sc_iot, ia->ipa_io[0].base, 
                SELWD_IOSIZE, 0, &sc->sc_ioh)) {
                printf("SEL Watchdog cannot map i/o space\n");
                return;
        }       
        
        /* read board ID */
        devid = selwd_read_boardid(sc->sc_iot, sc->sc_ioh);
        printf(": SEL Board ID (%x)",devid);
                
        /* read model number */
        char *model = malloc(sizeof(char)*16, M_DEVBUF, M_WAITOK | M_ZERO);
        selwd_read_modelno(sc->sc_iot, sc->sc_ioh, model);
        printf(", Model No (%s)",model);
        free(model, M_DEVBUF, 0);
                
        /* read serial number */
        char *serial = malloc(sizeof(char)*16, M_DEVBUF, M_WAITOK | M_ZERO);
        selwd_read_serialno(sc->sc_iot, sc->sc_ioh, serial);
        printf(", Serial No (%s)\n",serial);
        free(serial, M_DEVBUF, 0);

        /* write to Enabled and Alarm LEDs */
        /* turns Enabled green and Alarm off */
        u_int8_t led0 = 0x02;
        selwd_write_ledctl0(sc->sc_iot, sc->sc_ioh, led0);
        
        /* write to Aux LEDs */
        /* turns off the LEDs */
        u_int8_t led1 = 0x00;
        selwd_write_ledctl1(sc->sc_iot, sc->sc_ioh, led1);
        
        nia = *ia;
        nia.ia_iobase = 0x192;
        nia.ia_aux = (void *)(u_long)devid; /* pass devid down to wb_match */
        
        config_found(self, &nia, selwd_print);
        wdog_register(selwd_wd_cb,sc);  
}

int
selwd_wd_cb(void *arg, int period)
{
        /* This function is called by the watchdog daemon */
        /* Get a reference to the software context */
        struct selwd_softc *sc = arg;
        if (period < 0)
                period = 0;
        /* The period value is in seconds, the watchdog value is in 
twos-of-secs */
        int wd = period / 2;
        /* write watchdog value */
        selwd_write_wdog(sc->sc_iot, sc->sc_ioh, wd);   
        /* return the period back to the daemon */      
        return period;
}

int
selwd_print(void *aux, const char *pnp)
{
        struct isa_attach_args *ia = aux;

        if (pnp)
                printf("%s", pnp);
        if (ia->ia_iosize)
                printf(" port 0x%x", ia->ia_iobase);
        if (ia->ia_iosize > 1)
                printf("/%d", ia->ia_iosize);
        printf("\n");
        return 0;
}

void
selwd_write_wdog(bus_space_tag_t iot, bus_space_handle_t ioh, int value)
{
        /* Check to make sure that the state is idle */
        selwd_abort(iot, ioh);  
        /* Write watchdog timer value */
        /* write the WRITECFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_WRITECFG);
        /* write the watchdog value to the watchdog address */
        selwd_conf_write(iot, ioh, SELWD_CFG_WATCHDOG, value);
}

u_int8_t
selwd_read_wdog(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the watchdog value from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t wdog;
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the WATCHDOG data value from the data register */
        wdog = selwd_conf_read(iot, ioh, SELWD_CFG_WATCHDOG);
        return wdog;
}

u_int8_t
selwd_read_status(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the command/status register */
        u_int8_t status = 0;
        status = bus_space_read_1(iot, ioh, SELWD_CMD);
        return status;
}

void
selwd_abort(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* write the abort value to the command/status register */
        u_int8_t status = 0;
        /* first read the status register */
        status = bus_space_read_1(iot, ioh, SELWD_CMD);
        /* if the state is IDLE return */
        if (EC_STATUS_STATE(status) == 0)
                return;
        uint32_t timeout = 0;
        /* make sure status is 0 before proceeding */
        while (EC_STATUS_STATE(status) != 0 && timeout < 50) {
                /* write the abort command */
                bus_space_write_1(iot, ioh, SELWD_CMD, SELWD_CMD_ABORT);
                /* wait until controller isn't busy */
                selwd_wait(iot, ioh, 0);
                /* read the status register */
                status = bus_space_read_1(iot, ioh, SELWD_CMD);
                delay(10);
                /* increment timeout */
                ++timeout;
        }
        
}

u_int32_t
selwd_read_boardid(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the board ID from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg0, reg1, reg2, reg3;
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the BOARID0 data value from the data register */
        reg0 = selwd_conf_read(iot, ioh, SELWD_CFG_BOARDID0);
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the BOARID1 data value from the data register */
        reg1 = selwd_conf_read(iot, ioh, SELWD_CFG_BOARDID1);
                
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the BOARID2 data value from the data register */
        reg2 = selwd_conf_read(iot, ioh, SELWD_CFG_BOARDID2);
                
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the BOARID3 data value from the data register */
        reg3 = selwd_conf_read(iot, ioh, SELWD_CFG_BOARDID3);

        /* convert the 4 bytes into a 32 bit number */
        u_int32_t boardid = (reg3 << 24) | (reg2 << 16) | (reg1 << 8) | reg0;
        return boardid;
}

u_int32_t
selwd_read_mcversion(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the MC version from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg0, reg1, reg2, reg3;
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the VERSION0 data value from the data register */
        reg0 = selwd_conf_read(iot, ioh, SELWD_CFG_VERSION0);

        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the VERSION1 data value from the data register */
        reg1 = selwd_conf_read(iot, ioh, SELWD_CFG_VERSION1);

        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the VERSION2 data value from the data register */
        reg2 = selwd_conf_read(iot, ioh, SELWD_CFG_VERSION2);

        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the VERSION3 data value from the data register */
        reg3 = selwd_conf_read(iot, ioh, SELWD_CFG_VERSION3);

        /* convert the 4 bytes into a 32 bit number */
        u_int32_t version = (reg3 << 24) | (reg2 << 16) | (reg1 << 8) | reg0;
        return version;
}


u_int16_t
selwd_read_pciboardid(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the PCI board ID from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg0, reg1;
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the PCIBRDID0 data value from the data register */
        reg0 = selwd_conf_read(iot, ioh, SELWD_CFG_PCIBRDID0);
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the PCIBRDID1 data value from the data register */
        reg1 = selwd_conf_read(iot, ioh, SELWD_CFG_PCIBRDID1);
                
        /* convert the 2 bytes into a 16 bit number */
        u_int16_t boardid = (reg1 << 8) | reg0;
        return boardid;
}

u_int8_t
selwd_read_miscctl0(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the misc control 0 value from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg;
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the MISCCTL0 data value from the data register */
        reg = selwd_conf_read(iot, ioh, SELWD_CFG_MISCCTL0);
        return reg;
}

u_int8_t
selwd_read_miscctl1(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the misc control 1 value from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg;
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the MISCCTL1 data value from the data register */
        reg = selwd_conf_read(iot, ioh, SELWD_CFG_MISCCTL1);
        return reg;
}

u_int8_t
selwd_read_ledctl0(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the LED control 0 value from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg;
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the LEDCTRL0 data value from the data register */
        reg = selwd_conf_read(iot, ioh, SELWD_CFG_LEDCTRL0);
        return reg;
}

void
selwd_write_ledctl0(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t data)
{
        /* write the LED control 0 value to the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        /* write the WRITECFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_WRITECFG);
        /* write the LEDCTRL0 data value to the data register */
        selwd_conf_write(iot, ioh, SELWD_CFG_LEDCTRL0, data);
}

u_int8_t
selwd_read_ledctl1(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        /* read the LED control 1 value from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg;
        
        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the LEDCTRL1 data value from the data register */
        reg = selwd_conf_read(iot, ioh, SELWD_CFG_LEDCTRL1);
        return reg;
}


void
selwd_write_ledctl1(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t data)
{
        /* write the LED control 1 value to the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        /* write the WRITECFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_WRITECFG);
        /* write the LEDCTRL1 data value to the data register */
        selwd_conf_write(iot, ioh, SELWD_CFG_LEDCTRL1, data);
}

void
selwd_read_modelno(bus_space_tag_t iot, bus_space_handle_t ioh, char* model)
{
        /* read the MODEL NUMBER value from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg;
        int i = 0;

        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the MODELNO data value from the data register */
        reg = selwd_conf_read(iot, ioh, SELWD_CFG_MODELNO);
        while (reg != 0 && i < 15) {
                model[i] = reg;
                /* make sure status is 0 before proceeding */
                selwd_abort(iot, ioh);
                /* write the READCFG command to the command register */
                selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
                /* read the MODELNO data value from the data register */
                reg = selwd_conf_read(iot, ioh, SELWD_CFG_MODELNO);
                i++;
        }

}

void
selwd_read_serialno(bus_space_tag_t iot, bus_space_handle_t ioh, char* serial)
{
        /* read the SERIAL NUMBER value from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg;
        int i = 0;

        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the SERIALNO data value from the data register */
        reg = selwd_conf_read(iot, ioh, SELWD_CFG_SERIALNO);
        while (reg != 0 && i < 15) {
                serial[i] = reg;
                /* make sure status is 0 before proceeding */
                selwd_abort(iot, ioh);
                /* write the READCFG command to the command register */
                selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
                /* read the SERIALNO data value from the data register */
                reg = selwd_conf_read(iot, ioh, SELWD_CFG_SERIALNO);
                i++;
        }

}

void
selwd_read_configid(bus_space_tag_t iot, bus_space_handle_t ioh, char* configid)
{
        /* read the CONFIGURATION ID value from the controller */
        /* make sure status is 0 before proceeding */
        selwd_abort(iot, ioh);
        u_int8_t reg;
        int i = 0;

        /* write the READCFG command to the command register */
        selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
        /* read the CONFIGID data value from the data register */
        reg = selwd_conf_read(iot, ioh, SELWD_CFG_CONFIGID);
        while (reg != 0 && i < 15) {
                configid[i] = reg;
                /* make sure status is 0 before proceeding */
                selwd_abort(iot, ioh);
                /* write the READCFG command to the command register */
                selwd_conf_enable(iot, ioh, SELWD_CMD_READCFG);
                /* read the CONFIGID data value from the data register */
                reg = selwd_conf_read(iot, ioh, SELWD_CFG_CONFIGID);
                i++;
        }

}



On Tue, Apr 26, 2016 at 06:44:34AM -0600, Theo de Raadt wrote:
> obviously you show the code, and then when the complexity/simplicity of it
> is seen, some people can jump in and help.
> 
> that is the traditional way: show it
> 
> > We are embarking on a project where we will be using a number of
> > industrially hardened computers manufactured by  Schweitzer Engineering
> > Laboratories, Inc. (SEL). SEL provides a very well whiten document
> > describing certain special features of these computers. One of these is a
> > hardware watchdog. 
> > 
> > We have contracted a systems integrator to write a device driver for this
> > watchdog, with the stipulation that the code will be relapsed under the BSD
> > licence. This work is essiantly complete, and I was wondering if someone 
> > could
> > suggest to me the best way to submit this for (potential) inclusion in the
> > OpenBSD base system?
> > 
> > Thanks for all the good work on a really solid OS!
> > 
> > 
> > -- 
> > A: Because it messes up the order in which people normally read text.
> > Q: Why is top-posting such a bad thing?
> > A: Top-posting.
> > Q: What is the most annoying thing in e-mail?
> > 
> 

-- 
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing in e-mail?

----- End forwarded message -----

-- 
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing in e-mail?

Reply via email to