Il CMOS

Le macchine dotate di processore Intel 80286 o superiore dispongono di 64 byte (o 128, a seconda dei modelli) di memoria CMOS, permanentemente alimentata da una batteria, nella quale sono memorizzate, oltre alla data e ora, lo shutdown byte [1] e le informazioni relative alla configurazione hardware[2].

L'accesso alla memoria CMOS è possibile attraverso operazioni di lettura e scrittura sulle porte hardware 70h e 71h.

/********************

    BARNINGA_Z! - 1991

    READCMOS.C - readcmos()

    unsigned char cdecl readcmos(int off);
    int off;     l'offset, nel CMOS, del byte da leggere.
    Restituisce: il byte letto nel CMOS all'offset specificato. 

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -k- -mx readcmos.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#pragma  inline
#pragma  option -k-

unsigned char cdecl readcmos(int off)
{
    asm {
        mov al,byte ptr off;
        out 70h,al;    /* imposta l'offset */
        jmp $+2;       /* genera breve ritardo */
        in al,71h;     /* legge il byte */
    }
    return(_AL);
}

Il listato della readcmos() è estremamente semplice; l'unica particolarità è rappresentata dall'istruzione JMP $+2, ininfluente dal punto di vista del flusso di esecuzione[3]: essa ha solamente lo scopo di introdurre un piccolo ritardo tra le due operazioni sulle porte, in modo tale che prima della seconda trascorrano i cicli di clock necessari al completamento della prima.

La scrittura di un byte nel CMOS avviene in maniera analoga:

/********************

    BARNINGA_Z! - 1991

    WRITCMOS.C - writcmos()

    void cdecl readcmos(int off, unsigned char val);
    int off;              l'offset, nel CMOS, del byte da scrivere.
    unsigned char val;    il byte da scrivere nel CMOS.
    Restituisce: nulla. 

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -k- -mx writcmos.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#pragma  inline
#pragma  option -k-

void cdecl writcmos(int off,unsigned char val)
{
    asm {
        mov al,byte ptr off;
        out 70h,al;
        mov al,val;
        out 71h,al;
    }
}

Nella writcmos() la presenza dell'istruzione MOV AL,VAL tra le due operazioni di scrittura sulle porte elimina la necessità di inserire un'istruzione JMP $+2.

Di seguito presentiamo un programma che utilizza le due funzioni testè commentate per effettuare un salvataggio su file del contenuto del CMOS, nonché il suo eventuale ripristino. Chi abbia intenzione di sperimentare l'effetto di modifiche alla configurazione hardware del proprio personal computer pasticciando con le routine di setup del BIOS intuisce al volo quanto possa essere prezioso un backup dei dati originali. Si ricordi inoltre che la batteria di alimentazione del CMOS, come tutte le batterie, ha il pessimo vizio di scaricarsi, più o meno lentamente: può accadere a chiunque, un brutto giorno, di ritrovarsi nella necessità di ricostrure a memoria tutta la configurazione dopo avere sostituito la batteria ormai esausta.

/******************************************************************************

    Barninga_Z! - 09/08/93

    CMOSBKP.C - Se richiesta opzione -S (SAVE), crea una copia del CMOS nel file
        CMOSBKP.SAV nella stessa directory dalla quale e' stato lanciato (sono
        copiati i byte da offset 0x10 a 0x7F; in pratica NON vengono copiati i
        bytes usati dall'orologio e dalla diagnostica). Se viene richiesta
        opzione -r oppure -R (RESTORE), copia nel CMOS (a partire da offset 0x10)
        il contenuto del file CMOSBKP.SAV precedentemente creato (-r NON
        ripristina i bytes ad offset CMOS da 40h a 4Fh). Se richiesta opzione -o
        oppure -O confronta il contenuto del CMOS con il contenuto del file
        CMOSBKP.SAV (o non confronta i bytes ad offset CMOS da 40h a 4Fh). Se
        richiesta opzione -c effettua un test sul byte del CMOS usato dalla
        diagnostica del bootstrap e visualizza i risultati.
        Il nome di file puo' essere specificato sulla command line se si vuole
        che esso non sia CMOSBKP.SAV.

    Compilato con BORLAND C++ 3.1:

        bcc -O -d -rd -mt -lt cmosbkp.c

******************************************************************************/
#pragma  inline  // per readcmos() e writcmos()

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define  CHECK          0x0E                    /* byte diagnostico nel CMOS */
#define  START          0x10               /* primo byte da gestire nel CMOS */
#define  OFF1           0x40    /* inizio intervallo da saltare se richiesto */
#define  OFF2           0x50    /* byte successivo a fine intervallo saltato */
#define  END            0x7F              /* ultimo byte da gestire nel CMOS */
#define  BYTES          (END-START+1) /* numero di bytes da gestire nel CMOS */
#define  NAME           "CMOSBKP"                      /* nome del programma */
#define  EXT            "SAV"                    /* estensione del file dati */
#define  REL            "1.5"                                    /* versione */
#define  OK             "O.K."         /* messaggio per diagnostica tutto OK */
#define  SW             '-'                         /* switch per le opzioni */

int pascal __IOerror(int dosErr);

int main(int argc,char **argv);
void checkcmos(void);
int compcmos(char *datafile,unsigned cod);
int handlecmos(char *opt,char *datafile);
unsigned char cdecl readcmos(int off);
int readfile(char *datafile,unsigned char *cmos);
int restcmos(char *datafile,unsigned cod);
int savecmos(char *datafile);
void writcmos(int off,unsigned char val);

struct CMOSCHK {
    char     *item;                                /* oggetto della diagnosi */
    unsigned  mask;                        /* mask per il bit corrispondente */
    char     *bad;                                 /* messaggio per problema */
} chk[] = {
    {"Date/Time",  4,"Invalid"},
    {"Hard Disk",  8,"Can't boot"},
    {"RAM Size ", 16,"Different from startup check"},
    {"Equipment", 32,"Different from startup check"},
    {"Checksum ", 64,"Different from equipment record"},
    {"Battery  ",128,"Power lost"},
    {NULL,0,NULL}
};

char errmsg[] = "\
%s: syntax: %s option [filename]\n\n\
    option is one of the following:\n\
        -c    check CMOS status\n\
        -O    compare CMOS with file\n\
        -o    compare CMOS with file but offsets 40h to 4Fh\n\
        -R    restore CMOS from file\n\
        -r    restore CMOS from file but offsets 40h to 4Fh\n\
        -S    save CMOS to file but offsets 00h to 0Fh\n\
";

// main() gestisce una prima analisi della command line per decidere quale
// funzione invocare

int main(int argc,char **argv)
{
    int pflag = 2;      /* 0 o 2 - elemento di argv[] contenente il nomefile */

    printf("%s: CMOS save/restore utility -%s- Barninga_Z!\n",NAME,REL);
    switch(argc) {
        case 2:                          /* passata solo l'opzione su cmdlin */
            strcpy(argv[0]+strlen(argv[0])-3,EXT);
            pflag = 0;
        case 1:                  /* nessuna opzione: gestito da handlecmos() */
        case 3:                   /* passati opz e nome di file sulla cmdlin */
            if(!handlecmos(argv[1],argv[pflag]))
                break;
        default:
            perror(NAME);
            printf(errmsg,NAME,NAME);
    }
    return(errno);
}

// checkcmos() legge dal CMOS il byte di checksum, poi effettua la somma dei bytes 
// di cui quello e' a sua volta la somma e confronta i valori trovati.

void checkcmos(void)
{
    register i;
    unsigned char cbyte;

    cbyte = readcmos(CHECK);
    printf("%s: CMOS check results as follows:\n\n",NAME);
    for(i = 0; chk[i].item; i++)
        printf("    %s: %s\n",chk[i].item,(cbyte && chk[i].mask) ? chk[i].bad : OK);
}

// compcmos() confronta il contenuto del CMOS con il contenuto di un file per vedere
// se questo e' una copia della configurazione attuale

int compcmos(char *datafile,unsigned cod)
{
    register i, diff;
    unsigned char cmos[BYTES];

    if(readfile(datafile,cmos))
        return(1);
    for(diff = 0, i = 0; i < OFF1; i++)
        if(cmos[i] != readcmos(i+START))
            ++diff;
    if(!cod)
        for(; i < OFF2; i++)
            if(cmos[i] != readcmos(i+START))
                ++diff;
    for(i = OFF2; i < BYTES; i++)
        if(cmos[i] != readcmos(i+START))
            ++diff;
    printf("%s: %u differences between CMOS and %s data.\n",NAME,diff,datafile);
    return(0);
}

// handlecmos() analizza le opzioni della command line e invoca la funzione
// corrispondente

int handlecmos(char *opt,char *datafile)
{
    register cod = 0;

    if(*opt != SW)
        cod = __IOerror(EINVDAT);
    else
        switch(*(opt+1)) {
            case 'c':
                checkcmos();
                break;
            case 'o':
                cod = 1;
            case 'O':
                cod = compcmos(datafile,cod);
                break;
            case 'r':
                cod = 1;
            case 'R':
                cod = restcmos(datafile,cod);
                break;
            case 's':
                cod = 1;
            case 'S':
                cod = savecmos(datafile);
                break;
            default:
                cod = __IOerror(EINVDAT);                  /* opzione errata */
        }
    return(cod);
}

// readcmos() legge un byte dal CMOS

unsigned char readcmos(int off)
{
    asm {
        mov al,byte ptr off;
        out 70h,al;    /* imposta l'offset */
        jmp $+2;       /* genera breve ritardo */
        in al,71h;     /* legge il byte */
    }
    return(_AL);
}

// readfile() legge in un buffer il contenuto di un file contenente una
// configurazione di CMOS

int readfile(char *datafile,unsigned char *cmos)
{
    FILE *indata;

    if(!(indata = fopen(datafile,"rb")))
        return(1);
    if(fread(cmos,sizeof(char),BYTES,indata) < BYTES)
        return(1);
    return(0);
}


// restcmos() scrive nel CMOS il contenuto del buffer riempito con i bytes letti
// da file da readfile()

int restcmos(char *datafile,unsigned cod)
{
    register i, cnt;
    unsigned char cmos[BYTES];

    if(readfile(datafile,cmos))
        return(1);
    for(cnt = 0, i = 0; i < OFF1; cnt++, i++)
        writcmos(i+START,cmos[i]);
    if(!cod)
        for(; i < OFF2; cnt++, i++)
            writcmos(i+START,cmos[i]);
    for(i = OFF2; i < BYTES; cnt++, i++)
        writcmos(i+START,cmos[i]);
    printf("%s: %u CMOS bytes restored from %s.\n",NAME,cnt,datafile);
    return(0);
}

// savecmos() scrive il contenuto del CMOS in un buffer e poi scrive questo in un
// file, generando cosi' una copia di backup del CMOS

int savecmos(char *datafile)
{
    FILE *outdata;
    register i;
    unsigned char cmos[BYTES];

    if(!(outdata = fopen(datafile,"wb")))
        return(1);
    for(i = 0; i < BYTES; i++)
        cmos[i] = readcmos(i+START);
    if(fwrite(cmos,sizeof(char),BYTES,outdata) < BYTES)
        return(1);
    printf("%s: %u CMOS bytes saved to %s.\n",NAME,i,datafile);
    return(0);
}

// writcmos() scrive un byte nel CMOS

void writcmos(int off,unsigned char val)
{
    asm {
        mov al,byte ptr off;
        out 70h,al;
        mov al,val;
        out 71h,al;
    }
}

Come si vede, CMOSBKP si compone di poche funzioni, ciascuna dedicata ad una operazione elementare di gestione dei dati del CMOS: non sembra necessario, pertanto, dilungarsi in una loro approfondita descrizione; tuttavia va osservato che il programma potrebbe essere perfezionato eliminando la handlecmos() ed utilizzando, in luogo, gli strumenti di gestione delle opzioni della riga di comando descritti nel paragrafo dedicato. Ciò consentirebbe, inoltre, di razionalizzare la struttura di main().

Concludiamo queste scarne note in tema di CMOS con un... consiglio da amico: è buona norma predisporre un dischetto autopartente[4] e copiare sul medesimo, oltre alle indispensabili componenti del DOS, CMOSBKP.EXE e un file di backup del CMOS da questo generato. Può sempre servire.


OK, andiamo avanti a leggere il libro...

Non ci ho capito niente! Ricominciamo...