#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <nlist.h>


/* 
get/change enet addr in NVRAM --
see the comments and instructions in nvram.c
USE AT YOUR OWN RISK

1. run chenet without any parameters, follow the instruction 
2. run chenet without any parameters again. It should spit out
   your enet addr. Do not proceed if it doesn't spit out anything
   sensible.
3. now run chenet to actually modify the NVRAM
   e.g. chenet 08 00 20 fd fe fd

Placed in the public domain by the author - 1 July 1994

only tested on some sun4m architecture and sun4c architecture machines.
The kernel patches will be different for Sun4 and I don't have one to
play with.

If your particular machine is not supported, you may be able
to hack support for the machine into this program.

This program is still experimental. Dangerous and tricky stuff.
If you are unwilling to experiment and clean up after disasters
don't use it.  USE AT YOUR OWN RISK.  If this program malfunctions
or it is misused, you can get your Sun into a state where it will
not boot, even from CDROM.

*/


#define OFFSET1 017730       /* for Sun 4m, Sun OS 4.1.3 */
#define OFFSET2 03730     /* for Sun 4c, SUN OS 4.1.1 */

#define SANITY_CHECK    /* don't turn this off unless you need to. If this
                           fails it generally means that the program won't
                           work and very well may be destructive */

/* #define TEST        /* doesn't actually do mods if TEST is defined */
unsigned char buf[17];  /* larger than it needs to be */ 

/* offset table -- in ascending order of magnitude */
off_t offtab[2] = { OFFSET2, OFFSET1 };
#define nofftab  (sizeof(offtab) / sizeof(off_t))
off_t offset;

struct nlist nl[] = { "_mmeeprom" };
void check_eeprom_driver()
{
    int kmem;
    off_t where;
    unsigned int kbuf[12];
    if ((kmem = open("/dev/kmem", O_RDONLY)) < 0) {
        fprintf(stderr, "cannot open /dev/kmem\n");
        exit(1);
    }
    if (nlist("/vmunix", nl) < 0) {
        fprintf(stderr, "cannot read namelist out of /vmunix\n");
        exit(1);
    }
    if ((where = nl[0].n_value) == 0) {
        fprintf(stderr, "unknown kernel variable _mmeeprom\n");
        fprintf(stderr, 
            "your kernel is strange - nvram modifier won't operate\n");
        exit(1);
    }
    if (lseek(kmem, where, SEEK_SET) == (-1)) {
        fprintf(stderr, "lseek on /dev/kmem failed\n");
        fprintf(stderr, 
            "your kernel is strange - nvram modifier won't operate\n");
        exit(1);
    }
    if (read(kmem, (char *)&kbuf[0], 48) < 48) {
        fprintf(stderr, "read from /dev/kmem failed\n");
        fprintf(stderr, 
            "your kernel is strange - nvram modifier won't operate\n");
        exit(1);
    }
    close(kmem);
    if (kbuf[1] == 0x80a6a7d8 && kbuf[10] == 0x80a6a7d8) {
        fprintf(stderr, "please execute the following:\n");
        fprintf(stderr, 
            "adb -w -k /vmunix /dev/mem <<END\n_mmeeprom+4/W 80a6a7ff\n");
        fprintf(stderr, "_mmeeprom+0x28/W 80a6a7ff\nEND\n");
        exit(0);
    }
    if (kbuf[2] == 0x901223d8) {
        fprintf(stderr, "please execute the following:\n");
        fprintf(stderr, 
            "adb -k -w /vmunix /dev/mem <<END\n_mmeeprom+8/W 901223ff\nEND\n");
        exit(0);
    }
    if (kbuf[1] == 0x80a6a7ff && kbuf[10] == 0x80a6a7ff) {
        fprintf(stderr, "your kernel looks good (sun4c)\n");
    }
    if (kbuf[2] == 0x901223ff) {
        fprintf(stderr, "your kernel looks good (sun4m)\n");
    }
}
    

main(argc,argv)
int argc;
char *argv[];
{
    int fd;
    int i;
    int j;
    unsigned int new[6];
    unsigned int acc;
    int read_enet;
    unsigned int hid_reported_by_gethostid = gethostid();

    if (argc == 1) {
        /* no args - just get enetaddr from prom */
        read_enet = 1;
    }
    else if (argc == 7) {
        read_enet = 0;
        for (i=0;i<6;i++) {
            sscanf(argv[i+1],"%x",&new[i]);
            if (new[i] > 0xff) {
                fprintf(stderr, "usage: %s [xx xx xx xx xx xx]\n",argv[0]);
                fprintf(stderr, "  e.g. %s [8 0 20 7 19 4d]\n", argv[0]);
                exit(1);
            }
        }
    }
    else {
        fprintf(stderr, "usage: %s [xx xx xx xx xx xx]\n",argv[0]);
        fprintf(stderr, "  e.g. %s [8 0 20 7 19 4d]\n", argv[0]);
        exit(1);
    }
    check_eeprom_driver(); /* note: if you know what you are doing you might 
                                want to comment this line out. */
    if (read_enet) {
        if ((fd = open("/dev/eeprom", O_RDONLY)) < 0) {
            fprintf(stderr, "cannot open /dev/eeprom for read\n");
            exit(1);
        }
    } 
    else {
#ifdef TEST
        if ((fd = open("/dev/eeprom", O_RDONLY)) < 0) {
            fprintf(stderr, "cannot open /dev/eeprom for read\n");
            exit(1);
        }
#else
        if ((fd = open("/dev/eeprom", O_RDWR)) < 0) {
            fprintf(stderr, "cannot open /dev/eeprom for write\n");
            exit(1);
        }
#endif
    }

    offset = 0xffffffff;     /* dummy value */

    for (j = 0; j < nofftab; j++) {
        if (lseek(fd,offtab[j],SEEK_SET) < 0) { 
            fprintf(stderr, "lseek failed for %o\n", offtab[j]);
            continue;
        }
        if (read(fd,buf,16) < 16) {
            fprintf(stderr, "read of /dev/eeprom failed for %o\n", offtab[j]);
            continue;
        }
#ifdef TEST
        for (i=0; i<16; i++)
            printf("%02x ", buf[i]);
        printf("\n");
#endif
/* this will fail if you have modified the value returned by
   gethostid by using one of the other programs in this package.
   In that case I wouldn't disable this check, but restore your old 
   gethostid behaviour or replace the gethostid call in this code 
   with a reference to your "real" hostid. If your architecture 
   is weird this should detect the problem and not try and
   write into places we shouldn't. (remember the AT YOUR OWN RISK 
   comment). 
  */

        for (acc = 0 , i=0; i<15; i++) acc^=buf[i];
        if ((acc != buf[15]) 
            || (buf[1] != (hid_reported_by_gethostid >> 24) )
            || (buf[12] != ((hid_reported_by_gethostid >> 16) &0xff))
            || (buf[13] != ((hid_reported_by_gethostid >> 8) &0xff))
            || (buf[14] != (hid_reported_by_gethostid &0xff))) {
            continue;
        }
        offset = offtab[j];
        break;
    }
    if (offset == 0xffffffff) {
        fprintf(stderr, "perhaps you have a weird kernel -- failed\n");
        exit(1);
    }
    printf("current hostid = %02x%02x%02x%02x\n", 
    buf[1], buf[12], buf[13], buf[14]);

    printf("enetaddr       = %02x:%02x:%02x:%02x:%02x:%02x\n", 
    buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
    if (!read_enet) {
        if (lseek(fd,offset,SEEK_SET) < 0) { 
            fprintf(stderr, "lseek failed\n");
            exit(1);
        }
        for (i=0; i<6; i++) 
            buf[i+2] = (unsigned char) new[i];

        for (acc = 0 , i=0; i<15; i++) acc^=buf[i];
        buf[15] = acc & 0xff;
#ifndef TEST
        if (write(fd,buf,16) < 16) {
            fprintf(stderr, "write failed\n");
        }
#else
    for (i=0; i<16; i++)
        printf("%02x ", buf[i]);
    printf("\n");
#endif
    fprintf(stderr, "now please execute the following command\n");
    fprintf(stderr, "/usr/etc/eeprom 'diag-switch?=false'\n");
    }
    close(fd);
	return(0);
}

