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


/* 
get/change hostid in NVRAM -- Solaris 1.1 only

Instructions:

1. Read this entire comment. Especially the warnings and disclaimers.

2. compile nvram
   cc -o nvram nvram.c

3. Run nvram without any parameters.
If it fails with some bizarre error message, then your architecture
is not supported. If you don't mind hacking the kernel you can probably
hack in support for your architecture.  If you send me mods I'll fold
them in to the distribution.  It should suggest a few adb commands
to execute to make a kernel modification.

4. Run nvram again without any parameters.  It just prints out the
hostid and hw ethernet address.  Check this against the arp tables on
another machine or against to information displayed on boot. If it is
not correct, do not proceed with the next step.

5. now you can try and modify the hostid by running
nvram with the desired hostid as the second parameter (without 0x)
e.g.
nvram b0b1fb0b

---

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

you can actually do this on a sun3x machine (sun 3/80). Just remove the
stuff that tries to suggest and check for kernel patches
(check_eeprom_driver). The appropriate bytes are accessible directly
via /dev/eeprom. (Sun OS 4.1.1)

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.

first you need to turn off the protection in the OS to keep
you from modifying /dev/eeprom locations after 017730 (or the other offset)

nvram will try and figure out what your kernel looks like and suggest
a change to execute using adb. 

If you don't care about technical details, follow the instructions below.
The commands will be one of the following:
sun4m machines 
adb -k -w /vmunix /dev/mem
_mmeeprom+8/W 901223ff

which changes
or      %o1, 0x3d8, %o1
to
or      %o1, 0x3ff, %o1


OR
(Sun4c)

adb -w /vmunix /dev/mem
_mmeeprom+4/W 80a6a7ff
_mmeeprom+0x28/W 80a6a7ff
which changes a couple of compare operations. 

Also be very careful choosing a new hostid.  The first byte of the
hostid identifies the type of system you are running. On modern Suns, OS
boot CDs depend on this information to get the correct architecture. If
you change it you may be reduced to poking around in NVRAM from the
monitor next time you have to boot from CD.

Here's a mapping of what gets read into buf (at OFFSET)
buf[0] = always 01?
buf[1] = system id/first byte of hostid.
buf[2] \
buf[3]  \
buf[4]   \  HW ethernet address (6 bytes)
buf[5]    /  
buf[6]  /
buf[7]/
buf[8],...,buf[11] = always 00?
buf[12] = second byte of hostid
buf[13] = third byte of hostid
buf[14] = fourth byte of hostid
buf[15] = checksum

the checksum is buf[0] ^ buf[1] ^ ... ^ buf[14]
i.e. it must be the case that the  xor of buf[0] through buf[15] is 0


Mark Henderson <mch@squirrel.com>
Placed in the public domain by the author - 23 June 1994

This program is distributed in the hope that it will be useful,
but without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose. You use
this program at your own risk. The author disclaims responsibility for
any damages that might result from the use of this program, even
if they result from negligence on the part of the author.

Also, please don't use this program to steal software. The intended
use is for emergency situations where an application has to be moved from
one computer to another (e.g. in the event of a hardware malfunction)
and licence keys cannot be obtained quickly from the vendor. Many
vendors will not supply licence keys outside of business hours.

Sorry about all the legal BS, but given that use of this program is
dangerous and inexperienced people will use it, I don't have much choice
from both an ethical and legal point of view.

*/


#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[2];
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);
    }
    fprintf(stderr,"nvram - Mark Henderson <mch@squirrel.com>\n");
    fprintf(stderr,
        "Placed in the public domain by the author - 23 June 1994\n\n");
    fprintf(stderr,
        "This program is distributed in the hope that it will be useful,\n");
    fprintf(stderr,
        "but without any warranty; without even the implied warranty of\n");
    fprintf(stderr,
        "merchantability or fitness for a particular purpose. You use\n");
    fprintf(stderr,
    "this program at your own risk. The author disclaims responsibility for\n");
    fprintf(stderr,
        "any damages that might result from the use of this program, even\n");
    fprintf(stderr,
        "if they result from negligence on the part of the author.\n");
    fprintf(stderr, 
        "Malfunction or misuse of this program can damage your computer.\n\n");
    nl[0].n_name="_mmeeprom";
    nl[1].n_name = NULL;
    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[];
{
    unsigned int hid;
    int fd;
    int i;
    int j;
    unsigned int acc;
    int read_hostid;
    unsigned int hid_reported_by_gethostid = gethostid();

    if (argc == 1) {
        /* no args - just get hostid from prom */
        read_hostid = 1;
    }
    else if (argc == 2) {
        read_hostid = 0;
        sscanf(argv[1],"%08x",&hid);
    }
    else {
        fprintf(stderr, "usage: %s [hostid]\n",argv[0]);
        fprintf(stderr, "  e.g. %s b0b1fb0b\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_hostid) {
        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]);

    /* may as well print this out */
    printf("enetaddr       = %02x:%02x:%02x:%02x:%02x:%02x\n", 
    buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
    if (!read_hostid) {
        if (lseek(fd,offset,SEEK_SET) < 0) { 
            fprintf(stderr, "lseek failed\n");
            exit(1);
        }

        buf[1] = hid >> 24;
        buf[12] = (hid >> 16) &0xff;
        buf[13] = (hid >> 8) &0xff;
        buf[14] = hid &0xff;
        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");
        }
        fprintf(stderr, "gethostid now returns %08x\n", gethostid());
#else
    for (i=0; i<16; i++)
        printf("%02x ", buf[i]);
    printf("\n");
#endif
    }
    close(fd);
	return(0);
}

