Parallel-port programmer for MX25L8005 1MB serial flash RAM

AKA: The BIOS Unfucker

This is a gadget I knocked up in the middle of the night after I had been experimenting with a Foxconn RS780 motherboard to see which more-fully-featured BIOS versions intended for related boards would work on it. After a few successful experiments I ended up loading a BIOS that didn't work and so found it necessary to build a standalone device to reprogram the BIOS chip with a working one. This is the result.

It is a programmer for the 8-pin MX25L8005 1MB serial flash RAM chip and compatible chips; it would not be difficult to modify the software and hardware to work with other similar chips. It is both driven and powered from the parallel port so does not need a separate power supply.

Circuit diagram of MX25L8005 serial flash RAM programmer

Sketching the circuit on paper and then scanning it is a whole fuck load easier than using any schematic capture, CAD, or general-purpose drawing application to draw it.

Power for the circuit is taken via diodes from all the unused output lines of the parallel port. The parallel port uses 5V levels but the chip wants a 3.3V supply, so the circuit includes a regulator (which doubles as a power on/off control). The two green LEDs provide the voltage reference for the regulator, with switchable bias via the 1k2 resistor. These LEDs should be the older type made from Ga[As]P as these have a sharper knee and near-horizontal post-knee region. The more recently developed InGaN-based materials produce LEDs with a significant slope after the knee, so are much worse voltage references.

40106

40106

The 40106 is there to square up the edges after they have come down the cable, and also provides level-shifting. The latter is done using the 47k resistors and the chip's internal protection diodes, which is kind of yuck, but does do the job. This is of course the semiconductor 40106 - I find the Philips HEF-prefix series perform well - not the 136-ton diesel version, which as well as being a bit unwieldy is not electrically suitable for this application.

The totem-pole structure to the left of the 40106 is the level shifter and line driver for the one output line from the serial RAM. The 33pF capacitor provides positive feedback to sharpen up the edges.

The software is written for Linux. Porting it to other operating systems, or modifying it to program other similar chips, is up to you. The code is heavily commented so I don't see the point in any further waffling about how it works; the comments, the circuit and the datasheet for the RAM chip ought to provide plenty enough clue to work it out.

Links to resources:

C source code for programmer software
MX25L8005 datasheet
Parallel port pinout and registers

/*
 * Program to drive a reader/writer for MX25L8005 8-pin
 * serial flash memory chips as used by some motherboards
 * to hold the BIOS. 
 * 
 * Compile this with: gcc -lssl -O -o serialram serialram.c
 *                    sudo chown root serialram
 *                    sudo chmod u+s serialram
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define _XOPEN_SOURCE /* for getopt() */
#include <unistd.h>
#include <sys/io.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <openssl/md5.h>
#include <time.h>
#include <sys/time.h>
#include <signal.h>

/* Assume we're only interested in 1 meg chips */
#define MEMSIZE 1048576
#define BLOCKMAX 15
/* There are 3 protection bits... */
#define PROTMAX 7
/* 1 page = 256 bytes */
#define PAGESIZE 0x000100
#define PAGEMASK 0x0000ff
#define PAGESHIFT 8
#define PAGECOUNT 4096
/* 1 sector = 16 pages = minimum erasable chunk size */
#define SECSIZE 0x001000
#define SECMASK 0x000fff
#define SECSHIFT 12
#define SECCOUNT 256
/* 1 block = 16 sectors = minimum resolution of protection bits */
#define BLOCKSIZE 0x010000
#define BLOCKMASK 0x00ffff
#define BLOCKSHIFT 16
#define BLOCKCOUNT 16

/* Bitmasks and stuff */
#define SI 0x01
#define SCLK 0x02
#define CS 0x04
#define SO 0x80
#define STROBE 0x01
#define PWR 0xf8
#define PWRDATA 0xff
#define ALLDATA 0xf8
#define ALLCTL 0x0f
#define DATAINVMASK 0x07
#define STATINVMASK 0x00
#define CTLINVMASK 0x0b
#define REGMASK 0x03
#define DATAREG 0
#define STATREG 1
#define CTLREG 2
#define WIP 0x01

/* Macros to assert/deassert ~CS */
#define CSLOW outbit(DATAREG,CS|SCLK,0)
#define CSHIGH outbit(DATAREG,CS,1)

/* Serial RAM command definitions */
#define WREN 0x06
#define WRDI 0x04
#define RDID 0x9f
#define RDSR 0x05
#define WRSR 0x01
#define READ 0x03
#define FREAD 0x0b
#define SE 0x20
#define BE 0x52 /* alt: 0xd8 - Datasheet quotes 2 possible values */
#define CE 0x60 /* alt: 0xc7 - and for this too */
#define PP 0x02
#define DP 0xb9
#define RDP 0xab
#define RES 0xab /* Same as RDP? That's what the datasheet says */
#define REMS 0x90

/* Default time to usleep() before reading port, to allow for slow rise time */
#define IODEL 1
/* iodel values >= this are considered excessively silly and rejected */
#define IODEL_SILLY 10000
/* Bitfield to define which port ops IODEL applies to
 * Bit 0 = falling edge, 1 = rising edge, 2 = input read */
#define IOOPS 1

/* Times for write commands to complete, in us */
#define WRSRDEL 5000
#define SEDEL 90000
#define BEDEL 1000000
#define CEDEL 10000000
#define PPDEL 1400

/* Ports and registers */
#define NUMPORTS 3
#define NUMREGS 3

/* Minimum data length to display progress indicator */
#define PROGMIN 0x800

/* Standard LPT port addresses */
const unsigned long ports[NUMPORTS]={0x3bc, 0x378, 0x278};

/* Register bitmasks for inverted bits */
const int regmasks[NUMREGS]={DATAINVMASK, STATINVMASK, CTLINVMASK};

/* Lowest block protected at given protection bit settings */
const int blockprot[PROTMAX+1]={BLOCKMAX+1, 15, 14, 12, 8, 0, 0, 0};

/* Port status data type */
struct portrec {
    unsigned long portnum;
    unsigned char regs[NUMREGS];
    int iodel[3];
};

/* Just to save pissing about */
void nsleep(int del) {
    struct timespec req;
    
    req.tv_sec=0;
    req.tv_nsec=(long)del;
    nanosleep(&req, NULL);
}

/* Read/update portrec structure: func: -1 = delete, 0 = read, >0 = fill in port data */
struct portrec *portdat(int func) {
    static struct portrec pdat={0,{0,0,0}};
    struct portrec *ret;
    
    switch(func) {
        case -1: pdat.portnum=0;
                 ret=NULL;
                 break;
        case 0 : ret=&pdat;
                 break;
        default: pdat.portnum=(unsigned long)(func & 0xfff);
                 nsleep(IODEL);
                 pdat.regs[DATAREG]=inb_p(pdat.portnum + DATAREG) ^ DATAINVMASK;
                 nsleep(IODEL);
                 pdat.regs[STATREG]=inb_p(pdat.portnum + STATREG) ^ STATINVMASK;
                 nsleep(IODEL);
                 pdat.regs[CTLREG]=inb_p(pdat.portnum + CTLREG) ^ CTLINVMASK;
                 pdat.iodel[0]=((IOOPS & 0x01) ? IODEL : 0);
                 pdat.iodel[1]=((IOOPS & 0x02) ? IODEL : 0);
                 pdat.iodel[2]=((IOOPS & 0x04) ? IODEL : 0);
                 ret=&pdat;
                 break;
    }
    return(ret);
}

/* The following two functions, as well as sending/receiving data,
 * isolate us from all the bit inversions in the parallel port and
 * in the reader. So in the rest of the program, everything is in
 * "positive logic". */

/* Set "bitmask" bits in "reg" to "val" (1 or 0) */
void outbit(int reg, int bitmask, int val) {
    unsigned char c;
    struct portrec *port;
    struct timespec req;
    
    port=portdat(0);
    val=(val ? -1 : 0);
    val&=bitmask;
    c=port->regs[reg];
    c=(c & (bitmask ^ -1)) | val;
    port->regs[reg]=c;
    c^=regmasks[reg];
    if (req.tv_nsec=(long)port->iodel[val ? 1 : 0]) {
           req.tv_sec=0;
        outb(c, port->portnum + reg);
        nanosleep(&req, NULL);
    } else {
        outb_p(c, port->portnum + reg);
    }
}

/* Read "bitmask" bits from "reg" */
int inbit(int reg, int bitmask) {
    unsigned char c;
    struct portrec *port;
    struct timespec req;
    
    port=portdat(0);
    if (req.tv_nsec=(long)port->iodel[2]) {
           req.tv_sec=0;
        nanosleep(&req, NULL);
        c=inb(port->portnum + reg);
    } else {
        c=inb_p(port->portnum + reg);
    }
    c^=regmasks[reg];
    port->regs[reg]=c;
    return ((c & bitmask) ? 1 : 0);
}

/* Power down */
void port_off(void) {
    outbit(DATAREG, PWRDATA, 0);
    outbit(CTLREG, ALLCTL, 0);
}

/* Power up */
void port_on(void) {
    /* Turn power on */
    outbit(DATAREG, PWRDATA, 1);
    outbit(CTLREG, ALLCTL ^ STROBE, 1);
    /* Wait for it to stabilise */
    sleep(1);
    /* Activate reader */
    outbit(CTLREG, ALLCTL, 1);
}

/* "Open" the port; if "port" is 0, scan for a port */
int get_port(int port, int iodel, int ioops) {
    int i, n, f;
    struct portrec *newport; /* Gwent */
    
    f=-1;
    /* Need permissions on port 0x80 for _p variants of inb & outb to work */
    if (ioperm(0x80, 1, 1)) {
        fprintf(stderr, "Cannot get permissions for port 0x80: %s\n",strerror(errno));
        exit(1);
    }
    for (i=0; i<NUMPORTS; i++) {
        if ((port!=0) && (port!=ports[i])) continue;
        if (ioperm(ports[i], NUMREGS, 1)) {
            fprintf(stderr, "Cannot get permissions for port 0x%lx: %s\n", ports[i], strerror(errno));
            exit(1);
        }
        n=0;
        outb_p(0xaa, ports[i]);
        if (iodel) nsleep(iodel);
        if (inb_p(ports[i])!=0xaa) n++;
        outb_p(0x55, ports[i]);
        if (iodel) nsleep(iodel);
        if (inb_p(ports[i])!=0x55) n++;
        outb_p(0, ports[i]);
        if (n) {
            ioperm(ports[i], NUMREGS, 0);
        } else {
            newport=portdat((int)ports[i]);
            newport->iodel[0]=((ioops & 0x01) ? iodel : 0);
            newport->iodel[1]=((ioops & 0x02) ? iodel : 0);
            newport->iodel[2]=((ioops & 0x04) ? iodel : 0);
            port_off();
            f=i;
            break;
        }
    }
    return(f<0 ? f : ports[f]);
}

/* Close the port */
void release_port(void) {
    struct portrec *port;
    
    port=portdat(0);
    if (port->portnum) {
        ioperm(port->portnum, NUMREGS, 0);
        ioperm(0x80, 1, 0);
        portdat(-1);
    }
}

/* Signal handler to ensure reader is powered off if program is interrupted */
void exit_closedown(int dummy) {
    if (portdat(0)->portnum) port_off();
    exit(1);
}

/* Read a single byte from the port */
unsigned char byte_read(void) {
    int i;
    unsigned char byte;
    
    byte=0;
/*    outbit(DATAREG, SCLK, 0);*/
    for (i=8; i; i--) {
        byte<<=1;
        byte|=(inbit(STATREG, SO) ? 1 : 0);
        outbit(DATAREG, SCLK, 1);
        outbit(DATAREG, SCLK, 0);
    }
    return(byte);
}

/* Write a single byte to the port */
void byte_write(unsigned char byte) {
    int i;
    
    for (i=8; i; i--) {
        outbit(DATAREG, SCLK, 0);
        outbit(DATAREG, SI, byte & 0x80);
        outbit(DATAREG, SCLK, 1);
        byte<<=1;
    }
    outbit(DATAREG, SCLK, 0);
}

/* Write a command followed by 1 parameter byte */
void cmd_byte_write(unsigned char cmd, unsigned char byte) {
    int i;
    unsigned long word;
    
    word=((((unsigned long)cmd)<<8) & 0xff00) | byte;
    for (i=16; i; i--) {
        outbit(DATAREG, SCLK, 0);
        outbit(DATAREG, SI, word & 0x8000);
        outbit(DATAREG, SCLK, 1);
        word<<=1;
    }
    outbit(DATAREG, SCLK, 0);
}

/* Write a command followed by a 3-byte address */
void cmd_addr_write(unsigned char cmd, unsigned addr) {
    int i;
    unsigned long word;
    
    word=((((unsigned long)cmd)<<24) & 0xff000000) | addr;
    for (i=32; i; i--) {
        outbit(DATAREG, SCLK, 0);
        outbit(DATAREG, SI, word & 0x80000000);
        outbit(DATAREG, SCLK, 1);
        word<<=1;
    }
    /* FREAD needs an extra dummy address byte */
    if (cmd==FREAD) for (i=8; i; i--) {
        outbit(DATAREG, SCLK, 0);
        outbit(DATAREG, SI, 0);
        outbit(DATAREG, SCLK, 1);
    }
    outbit(DATAREG, SCLK, 0);
}

/* Write a command followed by a 3-byte address and some data */
void cmd_addr_data_write(unsigned char cmd, unsigned addr, unsigned char *buf, int len) {
    int i, j;
    unsigned long word;
    
    word=((((unsigned long)cmd)<<24) & 0xff000000) | addr;
    for (i=32; i; i--) {
        outbit(DATAREG, SCLK, 0);
        outbit(DATAREG, SI, word & 0x80000000);
        outbit(DATAREG, SCLK, 1);
        word<<=1;
    }
    for (j=len; j; j--) {
        word=(unsigned long)(*buf++);
        for (i=8; i; i--) {
            outbit(DATAREG, SCLK, 0);
            outbit(DATAREG, SI, word & 0x80);
            outbit(DATAREG, SCLK, 1);
            word<<=1;
        }
    }
    outbit(DATAREG, SCLK, 0);
}

/* Each chip command is implemented using its own function */

void wren(void) {
    CSLOW;
    byte_write(WREN);
    CSHIGH;
}

void wrdi(void) {
    CSLOW;
    byte_write(WRDI);
    CSHIGH;
}

unsigned rdid(void) {
    int i;
    unsigned r;
    
    r=0;
    CSLOW;
    byte_write(RDID);
    for (i=3; i; i--) {
        r<<=8;
        r|=byte_read();
    }
    CSHIGH;
    return(r);
}

unsigned rdsr(void) {
    unsigned r;
    
    CSLOW;
    byte_write(RDSR);
    r=byte_read();
    CSHIGH;
    return(r);
}

/* This is used by slow write functions that take a while to complete */
void wait_complete(unsigned del, int progress) {
    unsigned del2;
    unsigned sr;
    
    del2=(del > 1000000 ? 1000000 : del);
    if (del2<1000000) progress=0;
    do {
        if (progress) {
            printf(".");
            fflush(stdout);
        }
        usleep(del2);
        sr=rdsr();
    } while (sr & WIP);
    if (progress) printf("\n");
}

void wrsr(unsigned char val) {
    wren();
    CSLOW;
    cmd_byte_write(WRSR, val);
    CSHIGH;
    wait_complete(WRSRDEL, 0);
}

/* Function names: both read() and fread() already exist... 
 * Difference between "normal" and "fast" read is that "normal"
 * operates at max clock rate of 33MHz whereas "fast" can go up
 * to 70MHz. Not that it makes much difference when we're going
 * through the parallel port... */

void n_read(unsigned addr, unsigned char *buf, int len, int progress) {
    int i;
    
    CSLOW;
    cmd_addr_write(READ, addr);
    for (i=0; i<len; i++) { 
        *buf++=byte_read();
        if (((i & 0xff)==0) && (progress)) {
            printf("Read 0x%06x of 0x%06x bytes\r", i, len);
            fflush(stdout);
        }
    }
    CSHIGH;
    if (progress) printf("Read 0x%06x of 0x%06x bytes\n", i, len);
}

void f_read(unsigned addr, unsigned char *buf, int len) {
    int i;
    
    CSLOW;
    cmd_addr_write(FREAD, addr);
    for (i=len; i; i--) *buf++=byte_read();
    CSHIGH;
}

void se(unsigned addr) {
    wren();
    CSLOW;
    cmd_addr_write(SE, addr);
    CSHIGH;
    wait_complete(SEDEL, 0);
}

void be(unsigned addr, int progress) {
    wren();
    CSLOW;
    cmd_addr_write(BE, addr);
    CSHIGH;
    wait_complete(BEDEL, progress);
}

void ce(int progress) {
    wren();
    CSLOW;
    /* Datasheet is ambiguous over whether address needs to be sent or not.
     * However, it says "don't send it" in more places than it
     * says "do", so we're set up to not send it for the moment.
     * Untested - I haven't actually erased a chip to find out... */
    byte_write(CE);
/*    cmd_addr_write(CE, 0);*/
    CSHIGH;
    wait_complete(CEDEL, progress);
}

void pp(unsigned addr, unsigned char *buf, int len) {
    
    if (len<1) return;
    if (len>256) len=256;
    wren();
    CSLOW;
    cmd_addr_data_write(PP, addr, buf, len);
    CSHIGH;
    wait_complete(PPDEL, 0);
}

void dp(void) {
    CSLOW;
    byte_write(DP);
    CSHIGH;
}

void rdp(void) {
    CSLOW;
    byte_write(RDP);
    CSHIGH;
}

/* This does not work - it always returns 0xff.
 * However, the datasheet says it's "deprecated", and it's a
 * pretty old datasheet, so maybe it just isn't supposed to
 * be working any more. */
unsigned res(void) {
    unsigned r;
    
    CSLOW;
    cmd_addr_write(DP, 0);
    r=byte_read();
    CSHIGH;
    return(r);
}

unsigned rems(unsigned addr) {
    int i;
    unsigned r;
    
    r=0;
    CSLOW;
    cmd_addr_write(REMS, addr);
    for (i=2; i; i--) {
        r<<=8;
        r|=byte_read();
    }
    CSHIGH;
    return(r);
}

/* The program prints md5sums of all chip and file data, for
 * checking against image files etc. It also uses md5sums to
 * verify data written to the chip. */
char *do_md5sum(unsigned char *buf, int len) {
    unsigned char sumbuf[MD5_DIGEST_LENGTH];
    static char hexbuf[MD5_DIGEST_LENGTH+MD5_DIGEST_LENGTH+1];
    int i;
    
    MD5(buf, len, sumbuf);
    for (i=0; i<MD5_DIGEST_LENGTH; i++) sprintf(&hexbuf[i<<1], "%02x", sumbuf[i]);
    return(hexbuf);
}

/* Do a nicely-formatted hex dump of a buffer, labelled as
 * beginning at "start" */
void hexdump(unsigned char *buf, int len, unsigned start) {
    char line[80];
    char *b;
    unsigned char *c;
    unsigned char x;
    unsigned end, sa, ea, n, i;
    
    end=start + len - 1;
    sa=start & (-1 << 4);
    ea=(end & (-1 << 4)) + 16;
    printf("          +0 +1 +2 +3 +4 +5 +6 +7 : +8 +9 +a +b +c +d +e +f\n");
    printf("          ------------------------:------------------------\n");
    for (n=sa; n<ea; n+=16) {
        b=line;
        b+=sprintf(b, "%08x  ", n);
        c=buf;
        for (i=n; i<n+16; i++) b+=((i<start) || (i>end) ? sprintf(b, "%s", (i==n+7 ? "   : " :  "   ")) : sprintf(b, "%02x%s", *c++, ((i & 0x0f)==7 ? " : " : " ")));
        b+=sprintf(b, "  ");
        for (i=n; i<n+16; i++) b+=sprintf(b, "%c", ((i<start) || (i>end) ? ' ' : (isprint(x=*buf++) ? x : '.')));
        printf("%s\n", line);
    }
}

/* Run hexdump() with output redirected to external program "viewer", if the
 * "viewer" executable is valid. If not, just blurge it to the screen as usual.
 * "viewer" is searched for using execlp() so as to make use of PATH to find it. */
void hexdump_viewer(unsigned char *buf, int len, unsigned start, char *viewer) {
    int pipefd[2];
    int oldstdout;
    int cpid;
    int pstat=0;
    
    if ((!viewer) || (!*viewer) || (pipe(pipefd)==-1)) {
        hexdump(buf, len, start);
        return;
    }
    switch(cpid=fork()) {
        /* fork() failed, just dump away anyway */
        case -1 : close(pipefd[0]);
                  close(pipefd[1]);
                  hexdump(buf, len, start);
                  break;
        /* child process */
        case 0  : close(pipefd[1]);
                  dup2(pipefd[0], STDIN_FILENO);
                  ptrace(PTRACE_TRACEME, 0, NULL, NULL);
                  /* Result of this execlp() will be:
                   * If it fails, child will exit with status 1
                   * If it succeeds, child will receive SIGTRAP and will
                   * stop until parent kicks it off again */
                  execlp(viewer, viewer, (char *)NULL);
                  _exit(1);
                  break;
        /* main process */
        default : close(pipefd[0]);
                  /* Save current stdout for later... */
                  oldstdout=dup(STDOUT_FILENO);
                  /* If execlp() fails, child will exit and waitpid() will return WIFEXITED
                   * If execlp() succeeds, child will have received SIGTRAP and will be
                   * waiting to be restarted; waitpid() will return WIFSTOPPED */
                  waitpid(cpid, &pstat, 0);
                  if (WIFSTOPPED(pstat)) { 
                      /* execlp() has succeeded - we have an instance
                       * of "viewer" waiting to receive the hexdump,
                       * so redirect stdout into the pipe */
                      dup2(pipefd[1], STDOUT_FILENO);
                      /* Detach the trace and let child continue */
                      ptrace(PTRACE_DETACH, cpid, NULL, NULL);
                  }
                  /* If we had a valid "viewer" it will now receive the output from
                   * the hexdump via stdout/pipe. If we didn't, stdout will be
                   * unchanged and hexdump will act as if "viewer" was not given */
                  hexdump(buf, len, start);
                  close(pipefd[1]);
                  if (WIFSTOPPED(pstat)) {
                      /* Must close stdout to make sure child receives EOF.
                       * Just closing the pipe doesn't work; child sits there for
                       * ever waiting for EOF, while waitpid() sits there for ever
                       * waiting for child to receive EOF and exit... Hence the
                       * need to have saved stdout earlier, so we can put it back */
                      close(STDOUT_FILENO);
                      waitpid(cpid, NULL, 0);
                      dup2(oldstdout, STDOUT_FILENO);
                  }
                  /* We don't need this any more now */
                  close(oldstdout);
                  break;
    }
}

/* Dump a buffer to a file */
int filedump(char *fn, unsigned char *buf, int len) {
    int fd, res;

    if ((fn==NULL) || (*fn==0)) {
        fprintf(stderr, "No output filename specified\n");
        return(-1);
    }
    if ((fd=open(fn, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))<0) {
        fprintf(stderr, "%s: cannot open: %s\n", fn, strerror(errno));
        return(-1);
    }
    if ((res=write(fd, buf, len))<0) {
        fprintf(stderr, "%s: error writing to file: %s\n", fn, strerror(errno));
    }
    close(fd);
    return(res);
}

/* Read a file into a buffer */
int fileread(char *fn, unsigned char *buf, int len) {
    int fd, res;
    
    if ((fn==NULL) || (*fn==0)) {
        fprintf(stderr, "No input filename specified\n");
        return(-1);
    }
    if ((fd=open(fn, O_RDONLY))<0) {
        fprintf(stderr, "%s: cannot open: %s\n", fn, strerror(errno));
        return(-1);
    }
    if ((res=read(fd, buf, len))<0) {
        fprintf(stderr, "%s: error reading file: %s\n", fn, strerror(errno));
    }
    close(fd);
    return(res);
}

/* Initialise a buffer with 0xdeadbeef, dword-aligned, big-endian */
void deadbeef(unsigned char *buf, int len) {
    const char beef[4]={0xde,0xad,0xbe,0xef};
    int i;
    
    for (i=len; i; i--) *buf++=beef[(int)buf & 0x03];
}

/* Print which blocks are protected for a given protection-bits value */
void printprot(int level, int cr) {
    switch (blockprot[level]) {
        case BLOCKMAX+1 : printf("None");
                          break;
        case 0          : printf("All");
                          break;
        case BLOCKMAX   : printf("%d", blockprot[level]);
                          break;
        default         : printf("%d to %d", blockprot[level], BLOCKMAX);
                          break;
    }
    if (cr) printf("\n");
}

/* Moan-and-exit function used by option parser */
void erropt(char *arg, int opt) {
    fprintf(stderr, "Invalid argument %s to option %c\n", arg, opt);
    exit(1);
}

void usage(void) {
/* 80 chars 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
    printf("Usage: serialram [-l addr] [-d us] [-s] [-r addr] [-w addr] [-n num] [-N num]\n");
    printf("                   [-i file] [-o file] [-H] [-V file] [-c] [-C] [-p] [-P num]\n");
    printf("                   [-b num] [-Z] [-{h?}]\n\n");
    printf("  -l <addr> : Use LPT port at <addr>\n");
    printf("  -d <ns>   : Time to nanosleep() during port I/O (1 to %d; 0 to disable)\n", IODEL_SILLY-1);
    printf("  -e <n>    : Which port I/O operations -d applies to (see below)\n");
    printf("  -s        : Read status information - rdid(), rems(), rdsr()\n");
    printf("  -r <addr> : Read memory from <addr> (hex)\n");
    printf("  -w <addr> : Write memory at <addr> (hex)\n");
    printf("  -n <n>    : Number of bytes to read (hex)\n");
    printf("  -N <n>    : Number of bytes to write (hex)\n");
    printf("  -a        : Do not bother doing overlap adjustments, just write bare image\n");
    printf("  -S        : Skip the verification of the data after writing it\n");
    printf("  -m        : Verify written data by md5sum as well as on the fly\n");
    printf("  -i <fn>   : Input filename (Data to be written to memory)\n");
    printf("  -o <fn>   : Output filename (Data read from memory)\n");
    printf("  -O <fn>   : Image filename (Dump of write image as written to memory)\n");
    printf("  -H        : Hex dump data read from memory\n");
    printf("  -V <fn>   : Viewer (eg. less) to handle hex dump output\n");
    printf("  -c        : Clear protection bits as required before writing\n");
    printf("  -C        : Clear all protection bits whether required or not (overrides -c)\n");
    printf("  -p        : Set original protection bits again after writing\n");
    printf("  -P <n>    : Set protection bits to <n> (overrides -p)\n");
    printf("  -b <n>    : Block-erase block <n> (may be repeated to erase multiple blocks)\n");
    printf("  -Z        : Erase entire chip (overrides all protection options)\n");
    printf("  -R        : Really do write and erase operations (default is dummy ops)\n");
    printf("  -q        : Do not display progress count on long reads/erases/writes\n");
    printf("  -h, -?    : Print usage and exit\n\n");
    printf("With no parameters: scan LPT ports for attached serial RAMS and exit\n\n");
    printf("Environment variables: (Will be overridden by command line parameters)\n");
    printf("  SERIALRAM_IODEL   : Time to nanosleep() during port I/O (as -d)\n");
    printf("  SERIALRAM_IOOPS   : Which port I/O operations -d applies to (as -e)\n");
    printf("  SERIALRAM_VIEWER  : Viewer (eg. less) to handle hexdump output (as -V)\n");
    printf("  VIEWER            : Fallback for SERIALRAM_VIEWER\n");
    printf("  PAGER             : Fallback for VIEWER\n\n");
    printf("Read, erase and write operations can be specified together; read will be\n");
    printf("performed first, then erase, then write. Writes are verified in any case,\n");
    printf("unless the -S option is specified.\n\n");
    printf("The requirement to erase sectors before writing means that non-aligned writes\n");
    printf("must be padded with the original \"head\" and \"tail\" data from the remainder\n");
    printf("of the start and end sectors. Hence the actual write image may differ from the\n");
    printf("supplied data by this padding data. The -a option skips this adjustment; it is\n");
    printf("for testing purposes only, and will more than likely fuck the whole thing up\n");
    printf("if used in a \"real\" situation. The -O option allows the adjusted write image\n");
    printf("to be dumped to a file, permitting inspection of what was actually written.\n\n");
    printf("The delay as specified by the -d option is to compensate for timing-related\n");
    printf("errors caused by slow rise/fall times of the parallel port signals. It is a\n");
    printf("bit shit really because simply calling nanosleep() seems to take several\n");
    printf("microseconds regardless of the value passed, so the actual delay obtained is\n");
    printf("greater than that specified by a time which is indeterminate but still much\n");
    printf("larger than the actual delay requested. A value of 0 disables the nanosleep()\n");
    printf("call and just uses the built-in delay of inb_p() and outb_p() (nominally 8 ISA\n");
    printf("clock cycles). The parameter to the -e option is a bitfield specifying which\n");
    printf("port operations the delay applies to:\n");
    printf("  Bit 0 : Delay after sending a falling edge\n");
    printf("  Bit 1 : Delay after sending a rising edge\n");
    printf("  Bit 2 : Delay before reading the input line\n");
    printf("Default values are: -d = %d, -e = %d\n\n", IODEL, IOOPS);
    printf("The viewer defined by -V or [SERIALRAM_]VIEWER|PAGER is executed using\n");
    printf("execlp(), so the standard environment variable PATH is used to locate it.\n\n");
    printf("This binary must be installed setuid root in order to have the privileges to\n");
    printf("access the LPT port. It drops root privileges once the port has been acquired.\n\n");
}

int main(int argc, char *argv[]) {
    int i, n, b, bsp, bsps, cumu;
    unsigned char *s;
    struct portrec *port;
    unsigned char *buf;
    unsigned char vbuf[SECSIZE];
    char md5buf1[MD5_DIGEST_LENGTH+MD5_DIGEST_LENGTH+1];
    char md5buf2[MD5_DIGEST_LENGTH+MD5_DIGEST_LENGTH+1];
    int tbuflen, maskedwa;
    struct sigaction sa;
    sigset_t dset;
    struct timeval t1, t2, tb, te, tr;
    
    const char *optstring=":l:d:e:sr:w:n:N:aSmi:o:O:HV:cCpP:b:qZRh?";
    int opt;
    
    int portaddr=0;
    int readaddr=0;
    int writeaddr=0;
    int numread=0;
    int numwrite=0;
    int overadj=1;
    int verify=1;
    int doubleverify=0;
    int buggered[SECCOUNT];
    int numbuggered=0;
    int pbits=0;
    int block=-1;
    int numblockstoerase=0;
    int blockstoerase[BLOCKMAX+1];
    int maxblockzap=-1;
    int iodel=IODEL;
    int ioops=IOOPS;
    char *ifn=NULL;
    char *ofn=NULL;
    char *dfn=NULL;
    char *viewer=NULL;
    int statread=0;
    int dohexdump=0;
    int protclear=0;
    int protset=PROTMAX+1;
    int real=0;
    int showprogress=1;
    int fuckthewholething=0;
    int oldprotbits=0;
    
    int adjmaskL=SECMASK, adjmaskH=SECMASK;
    int adjsizeL=SECSIZE, adjsizeH=SECSIZE;
    int blk, sec, page, sece, wrn;

    gettimeofday(&tb, NULL);
    
    /* Make sure port will get turned off if we ^C out of it or whatever */
    sigemptyset(&dset);
    sa.sa_handler=exit_closedown;
    sa.sa_mask=dset;
    sa.sa_flags=0;
    sigaction(SIGHUP, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGILL, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);
    sigaction(SIGPIPE, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    
    for (i=0; i<=BLOCKMAX; i++) blockstoerase[i]=-1;

    /* Parse environment variables and parameters */
    if (s=getenv("SERIALRAM_IODEL")) {
        if (sscanf(s, "%d", &i)==1) {
            if ((i>=0) && (i<IODEL_SILLY)) iodel=i;
        }
    }
    if (s=getenv("SERIALRAM_IOOPS")) {
        if (sscanf(s, "%d", &i)==1) {
            if ((i>=0) && (i<=7)) ioops=i;
        }
    }
    if ((s=getenv("SERIALRAM_VIEWER")) || (s=getenv("VIEWER")) || (s=getenv("PAGER"))) asprintf(&viewer, "%s", s);
    opterr=0;
    while ((opt=getopt(argc, argv, optstring))!=-1) {
        switch(opt) {
            case 'l' : if (sscanf(optarg, "%x", &portaddr)!=1) erropt(optarg, opt);
                       n=0;
                       for (i=0; i<NUMPORTS; i++) if (portaddr==ports[i]) n=1;
                       if (!n) erropt(optarg, opt);
                       break;
            case 'd' : if (sscanf(optarg, "%d", &iodel)!=1) erropt(optarg, opt);
                       if ((iodel<0) || (iodel>=IODEL_SILLY)) erropt(optarg, opt);
                       break;
            case 'e' : if (sscanf(optarg, "%d", &ioops)!=1) erropt(optarg, opt);
                       if ((ioops<0) || (ioops>7)) erropt(optarg, opt);
                       break;
            case 's' : statread=1;
                       break;
            case 'r' : if (sscanf(optarg, "%x", &readaddr)!=1) erropt(optarg, opt);
                       if ((readaddr<0) || (readaddr>(MEMSIZE-1))) erropt(optarg, opt);
                       break;
            case 'w' : if (sscanf(optarg, "%x", &writeaddr)!=1) erropt(optarg, opt);
                       if ((writeaddr<0) || (writeaddr>(MEMSIZE-1))) erropt(optarg, opt);
                       break;
            case 'n' : if (sscanf(optarg, "%x", &numread)!=1) erropt(optarg, opt);
                       if ((numread<1) || (numread>MEMSIZE)) erropt(optarg, opt);
                       break;
            case 'N' : if (sscanf(optarg, "%x", &numwrite)!=1) erropt(optarg, opt);
                       if ((numwrite<1) || (numwrite>MEMSIZE)) erropt(optarg, opt);
                       break;
            case 'a' : overadj=0;
                       break;
            case 'S' : verify=0;
                       break;
            case 'm' : doubleverify=1;
                       break;
            case 'i' : asprintf(&ifn, "%s", optarg);
                       break;
            case 'o' : asprintf(&ofn, "%s", optarg);
                       break;
            case 'O' : asprintf(&dfn, "%s", optarg);
                       break;
            case 'H' : dohexdump=1;
                       break;
            case 'V' : if (viewer) free(viewer);
                       asprintf(&viewer, "%s", optarg);
                       break;
            case 'c' : if (protclear==0) protclear=-1;
                       break;
            case 'C' : protclear=1;
                       break;
            case 'p' : if (protset>PROTMAX) protset=-1;
                       break;
            case 'P' : if (sscanf(optarg, "%d", &protset)!=1) erropt(optarg, opt);
                       if ((protset<0) || (protset>PROTMAX)) erropt(optarg, opt);
                       break;
            case 'b' : if (sscanf(optarg, "%d", &block)!=1) erropt(optarg, opt);
                       if ((block<0) || (block>=BLOCKMAX)) erropt(optarg, opt);
                       if (blockstoerase[block]==-1) {
                           blockstoerase[block]=block;
                           if (maxblockzap<block) maxblockzap=block;
                           numblockstoerase++;
                       }
                       break;
            case 'R' : real=1;
                       break;
            case 'q' : showprogress=0;
                       break;
            case 'Z' : fuckthewholething=1;
                       break;
            case ':' : fprintf(stderr, "Missing argument to option %c\n", optopt);
                       exit(1);
                       break;
            case '?' : if ((optopt=='?') || (optopt==0)) {
            case 'h' :     usage();
                           exit(0);
                       } else {
                           fprintf(stderr, "Unrecognised option %c\n", optopt);
                           exit(1);
                       }
                       break;
            default  : fprintf(stderr, "Alien duck sex: %c\n", opt);
                       exit(1);
                       break;
        }
    }
    if (optind<argc) {
        fprintf(stderr, "What's all this shit...?");
        while (optind<argc) fprintf(stderr, " %s", argv[optind++]);
        fprintf(stderr, "\n");
        exit(1);
    }
    
    /* DO THE SHIT */
    
    if (argc==1) { /* No parameters = Scan ports and exit */
        for (i=0; i<NUMPORTS; i++) {
            printf("Testing port 0x%03lx... ", ports[i]);
            if (get_port(ports[i], iodel, ioops)<0) {
                printf("not found\n");
            } else {
                printf("Exists; looking for serial RAM... ");
                port_on();
                rdp();
                n=rdid();
                if ((n==0) || (n==0xffffff)) {
                    printf("not found\n");
                } else {
                    printf("rdid() returns 0x%06x\n", n);
                }
                port_off();
            }
            release_port();
        }
        exit(0);
    }

    /* We've got parameters, so are potentially doing something more interesting */
    /* First thing to do: open the port... */
    if (get_port(portaddr, iodel, ioops)<0) {
        if (portaddr) {
            fprintf(stderr, "Cannot open port 0x%03x\nRun with no parameters to scan for valid ports\n", portaddr);
        } else {
            fprintf(stderr, "No ports found. Check BIOS settings and parport[-pc] module status\n");
        }
        exit(1);
    }
    /* Drop root privileges now we've finished with ioperm() */
    setuid(getuid());
    printf("Using port %03lx\n", portdat(0)->portnum);
    port_on();

    /* Now, do the read-y stuff... */
    
    /* Dump status/ID information */
    if (statread) {
        n=rdid();
        printf("rdid(): Manufacturer ID = %02x, Memory type = %02x, Memory density = %02x\n", (n>>16)&0xff, (n>>8)&0xff, n&0xff);
        n=rems(0);
        printf("rems(): Manufacturer ID = %02x, Device ID = %02x\n", (n>>8)&0xff, n&0xff);
        n=rdsr();
        printf("rdsr(): Status register = %02x, protected blocks = ", n&0xff);
        n=(n>>2) & PROTMAX;
        printprot(n, 1);
    }
    
    /* Read memory, md5sum it, dump to file and/or screen as appropriate */
    if (numread>0) {
        printf("Reading 0x%x bytes from address 0x%06x\n", numread, readaddr);
        buf=malloc(numread);
        deadbeef(buf, numread);
        n_read(readaddr, buf, numread, (numread<PROGMIN ? 0 : showprogress));
        printf("md5sum of data: %s\n", do_md5sum(buf, numread));
        if ((ofn) && (*ofn)) {
            printf("Writing 0x%x bytes to %s...\n", numread, ofn);
            n=filedump(ofn, buf, numread);
            printf("%s, 0x%x bytes written\n", (n<0 ? "Failure" : "Success"), (n<0 ? 0 : n));
            if (n<0) {
                printf("File write failed, will hexdump...\n");
                dohexdump=1;
            }
        }
        if (dohexdump) hexdump_viewer(buf, numread, readaddr, viewer);
        free(buf);
        buf=NULL;
    }
        
    /* Read-y stuff done, now do the write-y stuff... */

    /* Check protection level and abort if not low enough */
    n=rdsr();
    oldprotbits=(n>>2) & PROTMAX;
    if ((maxblockzap>=blockprot[oldprotbits]) && (!protclear) && (!fuckthewholething)) {
        printf("Clearing blocks as requested not allowed by protection bits\nBlocks to erase:");
        for (i=0; i<BLOCKMAX; i++) if (blockstoerase[i]>=0) printf(" %d", i);
        printf("\nCurrent block protection level is ");
        printprot(oldprotbits, 1);
        printf("Block erase aborted. Run again with -c or -C options\n");
        for (i=0; i<BLOCKMAX; i++) blockstoerase[i]=-1;
        numblockstoerase=0;
        maxblockzap=-1;
    }
    
    /* Interaction of block protection and writing data */
    /* Erasing blocks also affects how we deal with non-aligned writes */
    if (numwrite>0) {
           n=(writeaddr&(BLOCKMASK^-1))>>(BLOCKSHIFT);
        if (blockstoerase[n]>=0) {
            adjmaskL=BLOCKMASK;
            adjsizeL=BLOCKSIZE;
           }
        n=((writeaddr+numwrite-1)&(BLOCKMASK^-1))>>(BLOCKSHIFT);
        if (blockstoerase[n]>=0) {
            adjmaskH=BLOCKMASK;
               adjsizeH=BLOCKSIZE;
        }
        if (maxblockzap<n) maxblockzap=n;
        if ((n>=blockprot[oldprotbits]) && (!protclear) && (!fuckthewholething)) {
            printf("Writing 0x%x bytes from 0x%06x not allowed by protection bits\n", numwrite, writeaddr);
               printf("Write ends in block %d\n", n);
            printf("Current block protection level is ");
            printprot(oldprotbits, 1);
            printf("Write aborted. Run again with -c or -C options\n");
               numwrite=0;
        }
        if ((fuckthewholething) || (!overadj)) {
            adjmaskL=adjmaskH=0;
            adjsizeL=adjsizeH=0;
        }
    }
    
    /* Prepare the write image in the buffer.
     * Datasheet states that areas to be written must be erased first; this means
     * that non-aligned writes require the partly-used areas at start and/or end
     * to be read first and the contents merged with the data to write - this is
     * what all the pissing about is for. */
    if (numwrite>0) {
        tbuflen=numwrite;
        maskedwa=writeaddr&adjmaskL;
        if (maskedwa!=0) tbuflen+=adjsizeL;
        if (((writeaddr+numwrite) & adjmaskH)!=0) tbuflen+=adjsizeH;
        buf=malloc(tbuflen);
        deadbeef(buf, tbuflen);
        if ((ifn) && (*ifn)) {
            printf("Reading 0x%x bytes from %s...\n", numwrite, ifn);
            n=fileread(ifn, &buf[maskedwa], numwrite);
            printf("%s, 0x%x bytes read\n", (n<0 ? "Failure" : "Success"), (n<0 ? 0 : n));
            if (n<0) {
                printf("File read failed, will not write memory\n");
                numwrite=0;
                free(buf);
                buf=NULL;
            } else {
                printf("md5sum of file: %s\n", do_md5sum(buf, numwrite));
            }
        }
        if (buf) { /* will be true if a file read failure hasn't aborted the operation */
            if (overadj) {
                if (maskedwa>0) {
                    printf("Reading lower overlap, 0x%06x bytes from 0x%06x...\n", maskedwa, writeaddr-maskedwa);
                    n_read(writeaddr-maskedwa, buf, maskedwa, showprogress);
                }
                n=(adjsizeH-((writeaddr+numwrite)&adjmaskH))&adjmaskH;
                if (n>0) {
                    printf("Reading upper overlap, 0x%06x bytes from 0x%06x...\n", n, writeaddr+numwrite);
                    n_read(writeaddr+numwrite, &buf[maskedwa+numwrite], n, showprogress);
                }
                numwrite=maskedwa+numwrite+n;
                writeaddr-=maskedwa;
                printf("After adjustment for overlaps, need to write 0x%06x bytes from 0x%06x\n", numwrite, writeaddr);
            }
            printf("md5sum of write image: %s\n", strcpy(md5buf1, do_md5sum(buf, numwrite)));
            /* If -O was set, dump write image */
            if ((dfn) && (*dfn)) {
                printf("Writing 0x%x bytes to %s...\n", numwrite, dfn);
                n=filedump(dfn, buf, numwrite);
                printf("%s, 0x%x bytes written\n", (n<0 ? "Failure" : "Success"), (n<0 ? 0 : n));
                if (n<0) {
                    printf("File write failed, will hexdump...\n");
                    hexdump_viewer(buf, numwrite, writeaddr, viewer);
                }
            }
        }
    }

    /* Clear chip or blocks.
     * protclear ==  1 : clear all protection bits regardless
     * protclear == -1 : only clear those needed for the current action
     * Erasing the whole chip implies clearing all bits */
    if ((fuckthewholething) || (protclear==1)) {
        n=0;
        printf("Clearing protection bits to %x... (protected: ", n);
        printprot(n, 0);
        if (real) {
            wrsr((rdsr() & ((PROTMAX<<2)^-1)) | (n<<2));
            printf(")\n");
        } else {
            printf(") -R not given; not done\n");
        }
    }
    if (fuckthewholething) {
        printf("Erasing entire chip, this may take some time...\n");
        if (real) {
            ce(showprogress);
        } else {
            printf("-R not given, chip not erased\n");
        }
    } else {
        /* This protection bits clearing will do for memory write as well as block erase */
        if (protclear==-1) {
            n=-1;
            for (i=0; i<=PROTMAX; i++) if (blockprot[i]>maxblockzap) n=i;
            printf("Clearing protection bits to %x... (protected: ", n);
            printprot(n, 0);
            if (real) {
                wrsr((rdsr() & ((PROTMAX<<2)^-1)) | (n<<2));
                printf(")\n");
            } else {
                printf(") -R not given; not done\n");
            }
        }
        for (i=0; i<=BLOCKMAX; i++) if (blockstoerase[i]>=0) {
            printf("Erasing block %d...\n", i);
            if (real) {
                be(i<<(BLOCKMAX+1), showprogress);
            } else {
                printf("-R not given, block %d not erased\n", i);
            }
        }
    }
    
    /* Write memory */
    if (numwrite>0) {
        printf("Starting write of 0x%06x bytes at 0x%06x...\n", numwrite, writeaddr);
        blk=writeaddr & (BLOCKMASK^-1);
        sec=writeaddr & BLOCKMASK & (SECMASK^-1);
        page=writeaddr & SECMASK;
        sece=-1;
        wrn=PAGESIZE-(page & PAGEMASK);
        n=0;
        bsps=0;
        s=NULL;
        while (n<numwrite) {
            /* Are we moving into a non-blank area which needs to be
             * erased before we can write to it? */
            if ((blockstoerase[blk>>BLOCKSHIFT]<0) && (sece<(sec|blk))) {
                sece=(sec|blk);
                if (showprogress) printf("Erasing sector 0x%x...", sece);
                if (real) {
                    se(sece);
                    if (showprogress) printf("\n");
                } else {
                    if (showprogress) printf(" -R not given; not done\n");
                }
                s=NULL;
            }
            bsp=blk+sec+page;
            if (showprogress) printf("Writing 0x%03x bytes to address 0x%06x (%05x - %04x - %03x)... ", wrn, bsp, blk, sec, page);
            if (real) {
                if (!s) {
                    s=&buf[n];
                    cumu=0;
                    bsps=bsp;
                    gettimeofday(&t1, NULL);
                }
                pp(bsp, &buf[n], wrn);
                printf("\n");
            } else {
                if (showprogress) printf(" -R not given; not done\n");
            }
            cumu+=wrn;
            n+=wrn;
            page+=wrn;
            if (page>=SECSIZE) {
                page-=SECSIZE;
                sec+=SECSIZE;
                if (sec>=BLOCKSIZE) {
                    sec-=BLOCKSIZE;
                    blk+=BLOCKSIZE;
                }
                if (verify) {
                       if (showprogress) printf("Verifying the sector...\n");
                    n_read(bsps, vbuf, cumu, (cumu<PROGMIN ? 0 : showprogress));
                    b=0;
                    for (i=0; i<cumu; i++) if (vbuf[i]!=*s++) b++;
                       if (b) buggered[numbuggered++]=(bsp)>>PAGESHIFT;
                    if (showprogress) printf((b ? "Sector is fucked (%d error(s))\n" : "Sector is OK\n"), b);
                       s=NULL;
                   }
                if (showprogress) {
                    gettimeofday(&t2, NULL);
                    timersub(&t2, &t1, &tr);
                    printf("%d bytes in %ld.%03ld seconds (%.3f bytes/sec)\n", cumu, tr.tv_sec, tr.tv_usec/1000, (double)cumu/((double)tr.tv_sec+(((double)tr.tv_usec)/1.0e6)));
                }
            }
            wrn=numwrite-n;
            if (wrn>PAGESIZE) wrn=PAGESIZE;
        }
        if (verify) {
            md5buf2[0]=0;
            if (doubleverify) {
                printf("Verifying: reading 0x%x bytes from address 0x%06x\n", numwrite, writeaddr);
                n_read(writeaddr, buf, numwrite, (numwrite<PROGMIN ? 0 : showprogress));
                printf("md5sum of data read back : %s\n", strcpy(md5buf2, do_md5sum(buf, numwrite)));
                printf("md5sum of write image    : %s\n", md5buf1);
            }
            if (((!strcmp(md5buf1, md5buf2)) || (!doubleverify)) && (!numbuggered)) {
                printf("Write successful\n");
            } else {
                printf("Fucking SHITARSE BOLLOCKWANK. It didn't fucking work\nList of fucked pages:\n");
                for (i=0; i<numbuggered; i++) {
                    printf("0x%04x ", buggered[i]);
                    if ((i & 15)==0) printf("\n");
                }
                if (((i - 1) & 15)==0) printf("\n");
            }
        }
    }
    
    /* Set protection bits as required
     * protset < 0             : restore original protection bits
     * 0 <= protset <= PROTMAX : set bits to protset
     * protset > PROTMAX       : don't set anything */
    if (protset<=PROTMAX) {
        n=(protset < 0 ? oldprotbits : protset);
        printf("Setting protection bits to %x (protected: ", n);
        printprot(n, 0);          
        n=(n<<2) | (rdsr() & (PROTMAX<<2));
        if (real) {
            wrsr(n);
            printf(")\n");
        } else {
            printf(") -R not given; protection bits not set\n");
        }
    }
    
    /* Fuck me, is that the lot? I think it is! */
    
    port_off();
    release_port();

    gettimeofday(&te, NULL);
    timersub(&te, &tb, &tr);
    printf("Total execution time: %ld.%03ld seconds (%ldh %ldm %ld.%03lds)\n", tr.tv_sec, tr.tv_usec/1000, tr.tv_sec/3600, (tr.tv_sec%3600)/60, tr.tv_sec%60, tr.tv_usec/1000);
    
}




Back to Pigeon's Nest


Be kind to pigeons




Valid HTML 4.01!