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


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

Instructions:
see the file INSTRUCTIONS. Especially important for sun4d and sun4m
  class machines.


UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING, YOU SHOULD AVOID CHANGING
THE FIRST BYTE OF YOUR HOSTID WHICH IDENTIFIES YOUR SYSTEM TYPE. 
CHANGING THIS BYTE COULD RENDER YOUR SYSTEM UNBOOTABLE.

Mark Henderson <mch@squirrel.com>
Placed in the public domain by the author - Jan 1995

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.

*/


/* 
 * getobphostid - gets hostid according to the openprom driver
 * returns 0 on failure 
 */
unsigned int getobphostid()
{
    struct opio {
       unsigned int size;
       unsigned char arr[OPROMMAXPARAM];
    } foo;
    int fd,done = 0;
    if ((fd = open("/dev/openprom", O_RDONLY)) < 0) {
        return (0);
    }
    foo.size =  OPROMMAXPARAM;
    memset(&foo.arr[0],0,OPROMMAXPARAM);
    if (ioctl(fd, OPROMNEXT, &foo) < 0) {
        return(0);
    }
    foo.size =  OPROMMAXPARAM;
    memset(&foo.arr[0],0,OPROMMAXPARAM);
    for (;!done;) {
        foo.size =  OPROMMAXPARAM;
        if (ioctl(fd, OPROMNXTPROP, &foo) < 0) {
            return(0);
        }
        if (foo.size == 0)   /* not on list ! */
            return 0;
        if (strncmp(&foo.arr[0], "idprom", 6)) 
            continue;
        strcpy(&foo.arr[0],&foo.arr[0]);
        foo.size=OPROMMAXPARAM;
        if (ioctl(fd, OPROMGETPROP, &foo) < 0) {
            return(0);
        }
        done=1;
    }   
    close(fd);
    return ((foo.arr[1] << 24)|(foo.arr[12] << 16)|(foo.arr[13] << 8)
            |(foo.arr[14]));
}

#define OFFSET1 017730       /* for Sun 4m */
#define OFFSET2 03730     /* for Sun 4c */

/* #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];

unsigned int bsd_gethostid()
{
    unsigned char buf[1024];
    buf[0] = 0;
    sysinfo(SI_HW_SERIAL, buf, 1024);
    return (strtoul(buf,NULL,10));
}
void check_eeprom_driver()
{
    int kmem;
    off_t where;
    unsigned int kbuf[12];
    fprintf(stderr,"nvram-sol2 - Mark Henderson <mch@squirrel.com>\n");
    fprintf(stderr,
        "Placed in the public domain by the author - Jan 1995\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");
    if ((kmem = open("/dev/kmem", O_RDONLY)) < 0) {
        fprintf(stderr, "cannot open /dev/kmem\n");
        exit(1);
    }
    nl[0].n_name = "nvram";
    nl[1].n_name = NULL;
    if (nlist("/dev/ksyms", 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 nvram\n");
        fprintf(stderr, "execute the following and try again\n");
        fprintf(stderr, "/usr/sbin/modload /kernel/drv/eeprom\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[11] == 0x9422a028) {
        fprintf(stderr, "please execute the following\n");
        fprintf(stderr,
            "adb -k -w /dev/ksyms /dev/mem <<END\nnvram+2c/W 9422a000\nEND\n");
        exit(0);
    }
    if (kbuf[11] == 0x9422a000) {
        fprintf(stderr, "your kernel looks good\n");
    }
}

/* #define gethostid() getobphostid() */
#define gethostid() bsd_gethostid()

main(argc,argv)
int argc;
char *argv[];
{
    unsigned int hid;
    int fd;
    int i;
    int j;
    unsigned int acc;
    char obuf[20];
    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 || argc == 3) {
        read_hostid = 0;
        sscanf(argv[1],"%08x",&hid);
        if (argc == 3)  /* undocumented -- allows you to override the
                           value returned by sysinfo in the sanity checks */
            sscanf(argv[2],"%08x",&hid_reported_by_gethostid);
    }
    else {
        fprintf(stderr, "usage: %s [hostid]\n",argv[0]);
        fprintf(stderr, "  e.g. %s\n", argv[0]);
        fprintf(stderr, "       %s b0b1fb0b\n", argv[0]);
        exit(1);
    }

    check_eeprom_driver();
    
    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]);
            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

/*
 The check below will fail if sysinfo does not return the same hostid
 as in NVRAM. This can happen because you've already modified the NVRAM
 and not rebooted since then, or because you've modified the kernel
 so that sysinfo does not return the same hostid as in NVRAM
*/

        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,"new hostid is %08x\n", hid);
		fprintf(stderr, "if you are NOT executing this on an SC1000/2000, skip to (*)\n");
		fprintf(stderr, "\tpress STOP-A\n");
		fprintf(stderr, "\ttype \"update-system-idprom\" at the ok prompt\n");
		fprintf(stderr, "\ttype \"go\" at the ok prompt\n");
		fprintf(stderr, "\treboot with \"init 6\"\n");
        fprintf(stderr,"(*) now please execute the following or reboot:\n\n");
        sprintf(obuf,"%u", hid);  
        printf("adb -w -k /dev/ksyms /dev/mem <<END\n");
        printf("hw_serial/W 0x");
        for (i=0; i<4; i++)
            printf("%02x", obuf[i]);
        printf("\n");
        printf("hw_serial+4/W 0x");
        for (; i<8; i++)
            printf("%02x", obuf[i]);
        printf("\n");
        printf("hw_serial+8/W 0x");
        for (; i<12; i++)
            printf("%02x", obuf[i]);
        printf("\n");
        printf("END\n");
#else
    for (i=0; i<16; i++)
        printf("%02x ", buf[i]);
    printf("\n");
#endif
    }
    close(fd);
	return(0);
}

