uni
Libreria di Calcolatori Elettronici.
Questa libreria contiene alcune funzioni per l’accesso a basso livello ad una macchina PC-compatibile (in particolare, una macchina QEMU con processore Intel/AMD a 64 bit, bus PCI a 32 bit e periferiche ISA).
La libreria è usata per i seguenti scopi durante il corso di Calcolatori Elettronici:
per supportare un boot loader (boot64, incluso nella libreria stessa) che porta il processore nella modalità a 64 bit partendo dal modo protetto a 32 bit; in questo caso la libreria è compilata a 32 bit;
per supportare i programmi caricati da boot64. Questi programmi girano con la CPU a livello sistema e hanno il completo controllo della macchina QEMU. Questi programmi sono detti “bare” (nudi) nella documentazione, e durante il corso si distinguono in:
esercizi di traduzione da C++ ad Assembler (soprattutto nel caso in cui la macchina ospite non sia Intel/AMD, ma sia ad esempio un Mac M1/M2/…);
esempiIO, piccoli programmi che sono utilizzati per spiegare il funzionamento delle periferiche ISA, del bus PCI e delle interruzioni;
il modulo sistema del nucleo introdotto nella seconda parte del corso.
per supportare i moduli aggiuntivi del nucleo (moduli io e utente);
I file nella directory bare sono specifici dei programmi bare e non sono usati da boot64 o dai moduli io e utente.
Per compilare la libreria è sufficiente eseguire make. Si otterranno i seguenti file:
boot.bin (il bootloader boot64)
libce32.a (libreria compilata a 32 bit, usata per costruire boot.bin)
libce64.a (libreria compilata a 64 bit, usata per tutto il resto)
I file oggetto della libreria si trovano in build, suddivisi in varie sottodirectory.
La libreria contiene anche gli script compile, boot e debug e un file libce-debug.py che contiene alcune estensioni per gdb, usate da debug. Contiene inoltre gli script colorlog e decodelog usati per il post-processing del messaggi invati sul log.
libce.h
/*! @file libce.h
* @brief File che va incluso sempre per primo
*/
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup const Costanti
///
/// Alcune costanti di uso comune. Usiamo delle macro in modo da poterle
/// usare anche in assembler.
/// @{
//////////////////////////////////////////////////////////////////////////////
/// kibibyte
#define KiB 1024ULL
/// mibibyte
#define MiB (1024*KiB)
/// gibibyte
#define GiB (1024*MiB)
/// dimensione in byte di una pagina o di un frame
#define DIM_PAGINA (4*KiB)
/// dimensione in byte di un blocco dell'hard disk
#define DIM_BLOCK 512ULL
/// DPL del livello sistema
#define LIV_SISTEMA 0
/// DPL del livello utente
#define LIV_UTENTE 3
/// @}
#ifndef __ASSEMBLER__
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup types Tipi base
///
/// Alcuni tipi di uso comune, più altri la cui definizione è richiesta dallo
/// standard.
/// @{
/////////////////////////////////////////////////////////////////////////////
/// indirizzo nello spazio di I/O
using ioaddr = unsigned short;
/// naturale su un byte
using natb = unsigned char;
/// naturale su due byte
using natw = unsigned short;
/// naturale su 4 byte
using natl = unsigned int;
/// @addtogroup archtypes Tipi dipendenti dall'architettura
///
/// Questi tipi sono definiti in modo diverso se la compilazione è a 32 bit
/// (usata dal boot loader) o a 64 bit. Per `natq`, `vaddr` e `paddr` vogliamo che
/// la dimensione in byte sia sempre 8, indipendentemente dalla modalità a 32 o
/// 64 bit. Gli altri tipi (`size_t`, `ssize_t`, ...) hanno definizione diverse
/// decise dallo standard.
/// @{
#if defined(__x86_64__) || defined(CE_UTILS)
/// naturale su 8 byte (64bit)
using natq = unsigned long;
/// indirizzo virtuale (64bit)
using vaddr = unsigned long;
/// indirizzo fisico (64bit)
using paddr = unsigned long;
/// @name Tipi richiesti dallo standard (64bit)
/// @{
/// tipo restituito da sizeof
typedef unsigned long size_t;
/// tipo che può contenere una dimensione o un errore
typedef long ssize_t;
/// tipo che può contenere il risultato della sottrazione tra due puntatori
typedef long ptrdiff_t;
/// tipo intero più capiente supportato dal sistema
typedef long intmax_t;
/// tipo intero senza segno più capiente supportato dal sistema
typedef unsigned long uintmax_t;
/// tipo senza segno che può contenere il valore di un puntatore
typedef unsigned long uintptr_t;
/// @}
#else
/// naturale su 8 byte (32bit)
using natq = unsigned long long;
/// indirizzo virtuale (32bit)
using vaddr = unsigned long long;
/// indirizzo fisico (32bit)
using paddr = unsigned long long;
/// @name Tipi richiesti dallo standard (32bit)
/// @{
/// tipo restituito da sizeof
typedef unsigned int size_t;
/// tipo che può contenere una dimensione o un errore
typedef int ssize_t;
/// tipo che può contenere il risultato della sottrazione tra due puntatori
typedef int ptrdiff_t;
/// tipo intero più capiente supportato dal sistema
typedef int intmax_t;
/// tipo intero senza segno più capiente supportato dal sistema
typedef unsigned int uintmax_t;
/// tipo senza segno che può contenere il valore di un puntatore
typedef unsigned int uintptr_t;
/// @}
#endif
/// @}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup util Funzioni di utilità generale
///
/// Le funzioni non inline sono definite nella directory `util`, ciascuna
/// nel proprio file.
/// @{
//////////////////////////////////////////////////////////////////////////////
/*! @brief Restituisce il massimo tra due valori confrontabili.
* @tparam T tipo dei valori (deve definire `<`)
* @param a primo valore da confrontare
* @param b secondo valore da confrontare
* @return massimo tra _a_ e _b_
*/
template<typename T> T max(T a, T b) { return a < b ? b : a; }
/*! @brief Restituisce il minimo tra due valori confrontabili.
* @tparam T tipo dei valori (deve definire `<`)
* @param a primo valore da confrontare
* @param b secondo valore da confrontare
* @return minimo tra _a_ e _b_
*/
template<typename T> T min(T a, T b) { return a < b ? a : b; }
/*! @brief Scrive lo stesso valore in tutti i byte di un intervallo.
* @param dest indirizzo base dell'intervallo
* @param c valore da scrivere
* @param n dimensione in byte dell'intervallo
* @return _dest_
*/
void *memset(void *dest, int c, size_t n);
/*! @brief Copia un intervallo di memoria su un altro (i due intervalli
* non possono sovrapporsi).
* @param dest base dell'intervallo di destinazione
* @param src base dell'intervallo sorgente
* @param n dimensione in byte dei due intervalli
* @return _dest_
*/
void *memcpy(void *dest, const void *src, size_t n);
/*! @brief Calcola la lunghezza di una stringa.
* @param str stringa terminata con `\0`
* @return lunghezza della stringa (escluso il terminatore)
*/
size_t strlen(const char str[]);
#include <stdarg.h> // file 'magico' che ci permette di usare le funzioni variadiche
/*! @brief Scrittura formattata su schermo.
*
* Segue la stessa sintassi della `printf(3)` della libreria standard del C,
* con l'esclusione dei parametri di tipo `float` e `double`.
*
* @param fmt la stringa di formato
* @param ... argomenti richiesti da _fmt_
* @return il numero di caratteri stampati
*
* @note La strana stringa `__attribute__((format(printf, 1, 2)))` introduce
* una estensione di gcc. In questo caso dice al compilatore che _fmt_
* (argomento 1) segue la sintassi della `printf(3)` standard e che la funzione
* si aspetta un numero variabile di argomenti a partire da quello in seconda
* posizione . Ad ogni invocazione di questa funzione con una stringa _fmt_
* nota a tempo di compilazione, il compilatore controllerà che gli argomenti
* attuali corrispondano in tipo e numero con quelli richiesti da _fmt_,
* emettendo dei warning in caso contrario.
*/
int printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
/*! @brief Scrittura formattata su un buffer in memoria.
*
* Segue la stessa sintassi della `snprintf(3)` della libreria standard del C,
* con l'esclusione dei parametri di tipo `float` e `double`.
*
* @param buf buffer di destinazione
* @param n dimensione in byte del buffer di destinazione
* @param fmt stringa di formato
* @param ... argomenti richiesti da _fmt_
* @return il numero di caratteri dell'output completo (se >= _n_ l'output
* è stato troncato)
*
* @note si veda @ref printf per il significato di `__attribute__`
*/
int snprintf(char *buf, natl n, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
/*! @brief Livello di severità del messaggio inviato al log
* (usato per colorare i messaggi ove previsto)
*/
enum log_sev {
LOG_DEBUG, //!< debugging
LOG_INFO, //!< informazione
LOG_WARN, //!< avviso
LOG_ERR, //!< errore
LOG_USR //!< messaggio proveniente da livello utente
};
/*! @brief Invio di un messaggio formattato sul log.
*
* Si possono usare gli stessi operatori di @ref printf.
*
* Nella configurazione di default il messaggio viene visualizzato sul terminale
* dal quale è stato lanciato QEMU. Il messaggio sarà preceduto dall'id del processo
* che lo ha inviato (se disponibile) e da una stringa di tre lettere che identifica
* la severità del messaggio.
*
* @param sev severità del messaggio
* @param fmt stringa di formato
* @param ... argomenti richiesti da _fmt_
*
* @note si veda @ref printf per il significato di `__attribute__`
*/
extern "C" void flog(log_sev sev, const char* fmt, ...) __attribute__((format(printf, 2, 3)));
/*! @brief Stampa un messaggio e attende che venga premuto il tasto ESC.
*/
void pause();
/*! @brief Invia sul log lo stato di tutti i registri e lo stack backtrace al momento della
* chiamata
*
* @param sev severità dei messaggi da inviare al log
*/
extern "C" void dump_status(log_sev sev);
/*! @brief Invia un messaggio sul log (severità ERR) ed esegue lo shutdown.
*
* Si possono usare gli stessi operatori di @ref printf.
*
* @param fmt stringa di formato
* @param ... argomenti richiesti da _fmt_
*
* @note si veda @ref printf per il significato di `__attribute__`
*/
[[noreturn]] void fpanic(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
/*! @brief Converte da intero a puntatore non void.
*
* L'intero deve essere allineato correttamente e deve poter essere contenuto
* in un puntatore (4 byte a 32 bit, 8 byte a 64 bit) In caso contrario la
* funzione chiama @ref fpanic().
*
* @tparam To tipo degli oggetti puntati
* @tparam From un tipo intero
* @param v valore (di tipo _From_) da convertire
* @return puntatore a _To_
*/
template<typename To, typename From>
static inline __attribute__((always_inline)) To* ptr_cast(From v)
{
uintptr_t vp = static_cast<uintptr_t>(v);
unsigned long long v_ = static_cast<unsigned long long>(v);
if constexpr (From(-1) < From(0)) {
if (v < 0)
fpanic("Conversione di %lld a puntatore: intero negativo",
static_cast<long long>(v));
}
if (vp & (alignof(To) - 1)) {
fpanic("Conversione di %llx a puntatore: non allineato a %zu", v_, alignof(To));
}
if (vp != v_) {
fpanic("Conversione di %llx a puntatore: perdita di precisione", v_);
}
return reinterpret_cast<To*>(vp);
}
/*! @brief Converte da intero a puntatore a void.
*
* L'intero deve poter essere contenuto in un puntatore (4 byte a 32 bit, 8
* byte a 64 bit) In caso contrario la funzione chiama @ref fpanic().
*
* @tparam From un tipo intero
* @param v valore (di tipo _From_) da convertire
* @return puntatore a void
*/
template<typename From>
static inline __attribute__((always_inline)) void* voidptr_cast(From v)
{
uintptr_t vp = static_cast<uintptr_t>(v);
if (vp != v) {
fpanic("Conversione di %llu a puntatore: perdita di precisione",
static_cast<unsigned long long>(v));
}
return reinterpret_cast<void*>(vp);
}
/*! @brief Converte da puntatore a intero.
*
* Il tipo _To_ deve essere sufficientemente grande per contenere l'indirizzo.
* In caso contrario la funzione chiama @ref fpanic().
*
* @tparam To un tipo intero
* @tparam From tipo degli oggetti puntati
* @param p puntatore da convertire
* @return valore di _p_ convertito a intero
*/
template<typename To, typename From>
static inline __attribute__((always_inline)) To int_cast(From* p)
{
uintptr_t vp = reinterpret_cast<uintptr_t>(p);
To v = static_cast<To>(vp);
if (vp != v) {
fpanic("Conversione di %p a intero: perdita di precisione", p);
}
return v;
}
/*! @brief Restituisce il più piccolo multiplo di _a_ maggiore o uguale a _v_.
*/
static inline natq allinea(natq v, natq a) {
v = (v % a == 0 ? v : ((v + a - 1) / a) * a);
return v;
}
/*! @brief Restituisce il più piccolo puntatore a _T_ allineato ad _a_ e maggiore
* o uguale a _p_.
*/
template<typename T>
static inline T* allinea_ptr(T* p, natq a) {
natq v = int_cast<natq>(p);
v = allinea(v, a);
return ptr_cast<T>(v);
}
/*! @brief Restituisce un intero pseudo-causale.
*/
long int random();
/*! @brief Imposta il seme iniziale del generatore di numeri pseudo-casuali.
*
* @param seed valore del seme
*/
void setseed(natl seed);
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup heap Memoria dinamica
///
/// Queste funzioni definiscono un semplice allocatore di memoria che può
/// essere usato per implementare gli operatori `new` e `delete`. La zona di
/// memoria da usare come heap va prima inizializzata con @ref heap_init().
///
/// Le funzioni sono definite nella directory `heap`.
///
/// @{
//////////////////////////////////////////////////////////////////////////////
/*! @brief Inizializza un intervallo di memoria in modo che possa essere
* usata con @ref alloc(), @ref alloc_aligned() e @ref dealloc().
*
* L'intervallo deve essere accessibile in lettura e scrittura. L'allocatore
* mantiene una lista di chunk liberi e i descrittori dei chunk sono allocati
* nell'intervallo stesso.
*
* @param start base dell'intervallo
* @param size dimensione in byte dell'intervallo
*/
void heap_init(void *start, size_t size);
/*! @brief Alloca una zona di memoria nello heap.
*
* @param dim dimensione in byte della zona da allocare
* @return puntatore alla zona allocata se disponibile,
* @return `nullptr` altrimenti
*/
void* alloc(size_t dim);
#ifndef CE_UTILS
namespace std {
/*! @brief Tipo standard per la definzione della new con allineamento.
*/
enum class align_val_t : size_t {};
}
#endif
/*! @brief Alloca una zona di memoria nello heap, con vincoli di allineamento.
*
* L'indirizzo restituito sarà multiplo dell'allineamento richiesto.
*
* @param dim dimensione in byte della zona da allocare
* @param align allineamento richiesto
* @return puntatore alla zona allocata se disponibile,
* `nullptr` altrimenti
*/
void* alloc_aligned(size_t dim, std::align_val_t align);
/*! @brief Dealloca una zona di memoria, restituendola allo heap.
*
* @param p puntatore alla zona da deallocare.
*
* Il puntatore deve essere stato precedentemente ottenuto tramite
* @ref alloc() o @ref alloc_aligned().
*/
void dealloc(void *p);
/*! @brief Memoria libera nello heap.
*
* @return quantità di memoria attualmente libera nello heap
*/
size_t disponibile();
/*!
* @brief Accedi alla testa della lista dei chunk liberi
*
* @return indirizzo della testa della lista dei chunk liberi
*/
natq heap_getinitmem();
/// @name Overload standard.
/// Overloading degli operatori di default normalmente forniti dalla dalla
/// libreria standard del C++. Si limitano a richiamare in modo appropriato
/// `operator new` e `operator delete`, che devono esssere definiti a parte.
/// @{
/// versione di new per l'allocazione di array
void *operator new[](size_t s);
/// versione di new per l'allocazione di array con allineamento
void *operator new[](size_t s, std::align_val_t a);
/// versione di delete con dimensione esplicita
void operator delete(void *p, size_t s);
/// versione di delete con dimensione esplicita e allineamento
void operator delete(void *p, size_t s, std::align_val_t);
/// versione di delete per la deallocazione degli array
void operator delete[](void *p);
/// versione di delete per la deallocazione degli array con dimensione esplicita
void operator delete[](void *p, size_t s);
/// @}
/// @name Allocazione e deallocazione per i programmi bare
/// I programmi bare possono usare le definizioni di `operator new` e `operator
/// delete` fornite dalla libce. Queste si limitano ad usare alloc(),
/// alloc_aligned() e dealloc().
/// @{
void *operator new(size_t s);
void *operator new(size_t, std::align_val_t a);
void operator delete(void *p);
/// @}
/// @}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup IO Interazione con le interfaccie di I/O
///
/// Queste sono le funzioni definite e usate in esempiIO e nei moduli sistema e
/// IO del nucleo. Le funzioni di ogni interfaccia sono raggruppate nel proprio
/// namespace e definite in una directory che ha lo stesso nome del namespace.
/// @{
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup rw Lettura e scrittura nello spazio di I/O
///
/// Funzioni generiche per l'accesso allo spazio di I/O. Dal momento che usano
/// le istruzioni IN e OUT sono definite in assembler. Le definizioni si
/// trovano nella directory `as64` (versione a 64 bit) e `as32` (versione a 32bit)
/// @{
//////////////////////////////////////////////////////////////////////////////
/*! @brief Legge un byte da una porta di I/O.
*
* @param reg indirizzo della porta nello spazio di I/O
* @return byte letto
*/
extern "C" natb inputb(ioaddr reg);
/*! @brief Legge una word (2 byte) da una porta di I/O.
*
* @param reg indirizzo della porta nello spazio di I/O
* @return word letta
*/
extern "C" natw inputw(ioaddr reg);
/*! @brief Legge un long (4 byte) da una porta di I/O.
*
* @param reg indirizzo della porta nello spazio di I/O
* @return long letto
*/
extern "C" natl inputl(ioaddr reg);
/*! @brief Legge una successione di word (2 byte) da una porta di I/O.
*
* @param reg indirizzo della porta nello spazio di I/O
* @param vetti buffer in cui ricevere le word lette
* @param n numero di word da leggere
*/
extern "C" void inputbw(ioaddr reg, natw vetti[], int n);
/*! @brief Invia un byte ad una porta di I/O.
*
* @param a byte da inviare
* @param reg indirizzo della porta nello spazio di I/O
*/
extern "C" void outputb(natb a, ioaddr reg);
/*! @brief Invia una word (2 byte) ad una porta di I/O.
*
* @param a word da inviare
* @param reg indirizzo della porta nello spazio di I/O
*/
extern "C" void outputw(natw a, ioaddr reg);
/*! @brief Invia un long (4 byte) ad una porta di I/O.
*
* @param a long da inviare
* @param reg indirizzo della porta nello spazio di I/O
*/
extern "C" void outputl(natl a, ioaddr reg);
/*! @brief Invia una successione di word (2 byte) ad una porta di I/O.
*
* @param vetto buffer contenente le word da inviare
* @param n numero di word da inviare
* @param reg indirizzo della porta nello spazio di I/O
*/
extern "C" void outputbw(natw vetto[], int n, ioaddr reg);
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup kbd Tastiera
///
/// Dispensa: <https://calcolatori.iet.unipi.it/resources/periferiche.pdf>
///
/// EsempiIO: tastiera-1, tastiera-2
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse della tastiera
namespace kbd {
/*! @brief Restituisce l'ultimo codice di scansione ricevuto dalla tastiera
* (esegue una attesa attiva fino a quando il codice non è disponibile).
*
* @return codice di scansione
*/
natb get_code();
/*! @brief Converte un codice di scansione nel corrispondente codice ASCII.
*
* @param c codice di scansione da convertire
* @return codice ASCII corrispondente; 0 se il codice è sconosciuto
*/
char conv(natb c);
/*! @brief Restituisce il codice ASCII dell'ultimo carattere letto dalla tastiera
* (esegue una attesa attiva fino a quando il carattere non è disponibile).
*
* @return carattere ricevuto; 0 se è stato ricevuto un codice sconosciuto
*/
char char_read();
/*! @brief Restituisce il codice ASCII corrispondente al codice di scansione contenuto in RBR
* (non esegue una attesa attiva).
*
* La funzione assume che sia già noto che RBR contiene un nuovo valore,
* per esempio perché è stata ricevuta una richiesta di interruzione dalla tastiera.
*
* @return carattere letto; 0 se RBR conteneva un codice sconosciuto
*/
char char_read_intr();
/*! @brief Abilita l'interfaccia della tastiera a generare richieste di interruzione.
*/
void enable_intr();
/*! @brief Disabilita l'interfaccia della tastiera a generare richieste di interruzione.
*/
void disable_intr();
/*! @brief Svuota il buffer della tastiera.
*/
void drain();
}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup vid Video in modalità testo
///
/// Dispensa: <https://calcolatori.iet.unipi.it/resources/periferiche.pdf>
///
/// EsempiIO: video-testo-1, video-testo-2
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse del video in modalità testo
namespace vid {
/*! @brief Ripulisce il video.
*/
void clear();
/*! @brief Ripulisce il video e setta l'attributo colore.
*
* @param col attributo colore
*/
void clear(natb col);
/*! @brief Scrive un carattere nella posizione corrente del cursore.
*
* @param c codice ASCII del carattere da scrivere
*/
void char_write(char c);
/*! @brief Scrive una stringa (null-terminated) a partire dalla posizione corrente del cursore.
*
* @param str stringa da scrivere
*/
void str_write(const char str[]);
/*! @brief Scrive un carattere in una posizione qualsiasi dello schermo.
*
* @param c codice ASCII del carattere da scrivere
* @param x colonna in cui scrivere
* @param y riga in cui scrivere
*/
void char_put(char c, natw x, natw y);
/*! @brief Restituisce un riferimento ad una posizione dello schermo, date
* le coordinate.
*
* @param x colonna della posizione richiesta
* @param y riga della posizione richiesta
* @return riferimento alla corrispondente word
*/
volatile natw& pos(natw x, natw y);
/*! @brief Restituisce il numero di colonne del video (modalità testo).
*
* @return numero di colonne
*/
natl cols();
/*! @brief Restituisce il numero di righe del video (modalità testo).
*
* @return numero di righe
*/
natl rows();
}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup svga Video in modalità grafica
///
/// Dispensa: <https://calcolatori.iet.unipi.it/resources/periferiche.pdf>
///
/// EsempiIO: svga-1, svga-2, svga-2
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse legate al video in modalità grafica
namespace svga {
/*! @brief Configura la modalità grafica a 256 colori.
*
* @param max_screenx pixel orizzontali
* @param max_screeny pixel verticali
* @return indirizzo del framebuffer
*/
volatile natb* config(natw max_screenx, natw max_screeny);
}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup libtimer Timer
///
/// Dispensa: <https://calcolatori.iet.unipi.it/resources/periferiche.pdf>
///
/// EsempiIO: timer
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse legate al timer
namespace timer {
/*! @brief Programma il timer 0 in modo che invii una richiesta di interruzione
* periodica.
*
* @param N periodo delle richieste di interruzione
*/
void start0(natw N);
/*! @brief Programma il timer 2 in modo che produca un'onda quadra.
*
* @param N periodo dell'onda quadra
*/
void start2(natw N);
/*! @brief Abilita lo speaker a ricevere il segnale proveniente dal timer 2.
*/
void enable_spk();
/*! @brief Disabilita lo speaker a ricevere il segnale proveniente dal timer 2.
*/
void disable_spk();
}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup hd Hard disk
///
/// Dispensa: <https://calcolatori.iet.unipi.it/resources/periferiche.pdf>
///
/// EsempiIO: hard-disk-1, hard-disk-2
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse legate all'hard disk
namespace hd {
/*! @brief Possibili valori da scrivere nel registro di comando dell'hard disk.
*/
enum cmd {
WRITE_SECT = 0x30, //!< scrittura di settori
READ_SECT = 0x20, //!< lettura di settori
WRITE_DMA = 0xCA, //!< scrittura di settori in DMA
READ_DMA = 0xC8 //!< lettura di settori in DMA
};
/*! @brief Avvia una operazione sull'hard disk.
*
* @param lba logical block address del primo settore
* @param quanti numero di settori
* @param cmd codice del comando
*/
void start_cmd(natl lba, natb quanti, cmd cmd);
/*! @brief Scrive un settore nel buffer interno dell'interfaccia dell'hard disk
*
* @param buf buffer contenente il settore da scrivere
*/
void output_sect(natb *buf);
/*! @brief Legge un settore dal buffer interno dell'interfaccia dell'hard disk
*
* @param buf buffer che dovrà ricevere il settore
*/
void input_sect(natb *buf);
/*! @brief Abilita l'interfaccia dell'hard disk a generare richieste di interruzione.
*/
void enable_intr();
/*! @brief Disabilita l'interfaccia dell'hard disk a generare richieste di interruzione.
*/
void disable_intr();
/*! @brief Azione di risposta alla richiesta di interruzione dell'interfaccia dell'hard disk.
*/
void ack();
}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup pci Bus PCI
///
/// Dispensa: <https://calcolatori.iet.unipi.it/resources/pci.pdf>
///
/// EsempiIO: pci
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse legate al PCI
namespace pci {
/*! @brief Legge un byte dallo spazio di configurazione PCI.
*
* @param bus numero di bus
* @param dev numero di dispositivo
* @param fun numero di funzione
* @param regn offset del byte
* @return byte letto
*/
natb read_confb(natb bus, natb dev, natb fun, natb regn);
/*! @brief Legge una word (2 byte) dallo spazio di configurazione PCI.
*
* @param bus numero di bus
* @param dev numero di dispositivo
* @param fun numero di funzione
* @param regn offset della word
* @return word letta
*/
natw read_confw(natb bus, natb dev, natb fun, natb regn);
/*! @brief Legge un long (4 byte) dallo spazio di configurazione PCI.
*
* @param bus numero di bus
* @param dev numero di dispositivo
* @param fun numero di funzione
* @param regn offset del long
* @return long letto
*/
natl read_confl(natb bus, natb dev, natb fun, natb regn);
/*! @brief Scrive un byte nello spazio di configurazione PCI.
*
* @param bus numero di bus
* @param dev numero di dispositivo
* @param fun numero di funzione
* @param regn offset del byte su cui scrivere
* @param data valore da scrivere
*/
void write_confb(natb bus, natb dev, natb fun, natb regn, natb data);
/*! @brief Scrive una word (2 byte) nello spazio di configurazione PCI.
*
* @param bus numero di bus
* @param dev numero di dispositivo
* @param fun numero di funzione
* @param regn offset della word su cui scrivere
* @param data valore da scrivere
*/
void write_confw(natb bus, natb dev, natb fun, natb regn, natw data);
/*! @brief Scrive un long (4 byte) nello spazio di configurazione PCI.
*
* @param bus numero di bus
* @param dev numero di dispositivo
* @param fun numero di funzione
* @param regn offset del long su cui scrivere
* @param data valore da scrivere
*/
void write_confl(natb bus, natb dev, natb fun, natb regn, natl data);
/*! @brief Trova una funzione PCI dato il vendor ID e il device ID.
*
* In ingresso, _bus_ / _dev_ / _fun_ devono contenere le coordinate da cui iniziare la
* ricerca. Al ritorno, se la ricerca ha avuto successo, contengono le
* coordinate della funzione.
*
* @param[in,out] bus numero di bus
* @param[in,out] dev numero di dispositivo
* @param[in,out] fun numero di funzione
* @param vendorID vendor ID della funzione cercata
* @param deviceID device ID della funzione cercata
* @return true se trovato, false altrimenti
*/
bool find_dev(natb& bus, natb& dev, natb& fun, natw vendorID, natw deviceID);
/*! @brief Trova una funzione PCI dato il Class Code.
*
* In ingresso, _bus_ / _dev_ / _fun_ devono contenere le coordinate da cui iniziare la
* ricerca. Al ritorno, se la ricerca ha avuto successo, contengono le
* coordinate della funzione.
*
* _code_ deve essere un array di 3 byte. In ingresso, uno o più di questi
* byte possono contenere il valore 0xFF che vale come "jolly". In uscita,
* se la ricerca ha avuto successo, i valori jolly saranno sostituiti dai
* byte effettivamente contenuti nella funzione trovata.
*
* @param[in,out] bus numero di bus
* @param[in,out] dev numero di dispositivo
* @param[in,out] fun numero di funzione
* @param[in,out] code il class code
* @return true se trovato, false altrimenti
*/
bool find_class(natb& bus, natb& dev, natb& fun, natb code[]);
/*! @brief Calcola le prossime coordinate nel bus PCI.
*
* Assumimiamo che le coordinate bus/dev/fun siano ordinate nel
* modo seguente:
*
* 0/0/0, 0/0/1, 0/0/2, ..., 0/0/7,
* 0/1/0, 0/1/1, 0/0/2, ..., 0/1/7,
* 0/2/0, ..., 0/2/7,
* ...,
* 0/31/0, ..., 0/31,7,
* 1/0/0,
* ...,
* 255/0/0, ..., 255/31/7.
*
* In ingresso _bus_ / _dev_ / _fun_ devono contenere delle coordinate valide.
* Al ritorno, se le coordinate in ingresso non erano le ultime, _bus_ / _dev_ / _fun_
* conterranno le coordinate successive.
*
* @param[in,out] bus numero di bus
* @param[in,out] dev numero di dispositivo
* @param[in,out] fun numero di funzione
* @return false se la coordinata in ingresso era l'ultima,
* true altrimenti
*/
bool next(natb& bus, natb& dev, natb& fun);
/*! @brief Decodifica il primo byte di un Class Code
*
* @param class_code byte meno significativo di un Class Code
* @return stringa che descrive il Class Code
*/
const char* decode_class(natb class_code);
}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup bm PCI BUS Mastering ATA
///
/// Dispensa: <https://calcolatori.iet.unipi.it/resources/dma.pdf>
///
/// Specifiche: <https://calcolatori.iet.unipi.it/deep/idems100.pdf>
///
/// EsempiIO: bm-hard-disk
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse legate al PCI BUS Mastering
namespace bm {
/*! @brief cerca il prossimo bus master ATA.
*
* In ingresso _bus_ / _dev_ / _fun_ devono contenere le coordinate da cui iniziare la
* ricerca. Al ritorno, se la ricerca ha avuto successo, contengono le
* coordinate del bus master.
*
* @param[in,out] bus numero del bus
* @param[in,out] dev numero del dispositivo
* @param[in,out] fun numero di funzione
* @return true se trovato, false altrimenti
*/
bool find(natb& bus, natb& dev, natb& fun);
/*! @brief inizializza un bus master.
*
* @param bus numero di bus del bus master
* @param dev numero di dispositivo del bus master
* @param fun numero di funzione del bus master
*/
void init(natb bus, natb dev, natb fun);
/*! @brief prepara una operazione di bus mastering.
*
* @param prd indirizzo fisico dell'array di descrittori
* @param write operazione di scrittura (true) o lettura (false)
*/
void prepare(paddr prd, bool write);
/*! @brief avvia l'operazione di bus mastering precedentemente preparata.
*/
void start();
/*! @brief azione di risposta alle richieste di interruzione del bus master
*/
void ack();
}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup serial Porte seriali
///
/// @note Le porte seriali non sono più trattate a lezione, ma sono usate per
/// implementare il log. Ci sono comunque due esempi in esempiIO: seriale-1
/// e seriale-2.
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse legate alle porte seriali
namespace serial {
/*! @brief inizializza la porta seriale 1.
*/
void init1();
/*! @brief invia un byte sulla porta seriale 1.
*
* @param c byte da inviare
*/
void out1(char c);
/*! @brief riceve un byte dalla porta seriale 1.
*
* @return byte ricevuo
*/
natb in1();
/*! @brief inizializza la porta seriale 2.
*/
void init2();
/*! @brief invia un byte sulla porta seriale 2.
*
* @param c byte da inviare
*/
void out2(char c);
/*! @brief ricevi un byte dalla porta seriale 2.
*
* @return byte ricevuto
*/
natb in2();
}
/// @}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup intr Interruzioni
///
/// Dispensa: https://calcolatori.iet.unipi.it/resources/interruzioni.pdf
/// @{
//////////////////////////////////////////////////////////////////////////////
/// @brief Interrupt Flag
///
/// Flag del registro RFLAGS. Se attivo abilita il processore ad accettare le
/// richieste di interruzione esterne mascherabili.
const natq BIT_IF = 1UL << 9;
/// @brief Trap Flag
///
/// Flag del registro RFLAGS. Se attivo, il processore solleva una eccezione
/// di tipo 1 (debug) dopo l'esecuzione di ogni istruzione.
const natq BIT_TF = 1UL << 8;
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup idt Tabella IDT
///
/// Queste funzioni sono definite nella directory `util`.
/// @{
//////////////////////////////////////////////////////////////////////////////
/*! @brief Inizializza un gate della IDT.
*
* Il gate viene inizializzato in ogni caso per portare il processore a
* livello sistema.
*
* @param num numero del gate (0-255)
* @param routine funzione da associare al gate
* @param trap se true, crea un gate di tipo trap
* @param liv DPL del gate (LIV_UTENTE o LIV_SISTEMA)
*/
extern "C" void gate_init(natb num, void routine(), bool trap = false, int liv = LIV_UTENTE);
/*! @brief Controlla che un gate non sia già occupato.
*
* @param num numero del gate
* @return true se il gate è occupato (P==1), false altrimenti
*/
extern "C" bool gate_present(natb num);
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup apic APIC
///
/// Dispensa: https://calcolatori.iet.unipi.it/resources/interruzioni.pdf
///
/// esempiIO: interrupt-1, interrupt-2, interrupt-3
/// @{
//////////////////////////////////////////////////////////////////////////////
/// namespace per le risorse legate all'APIC
namespace apic {
/*! @brief Massimo numero di IRQ supportati dall'APIC
*/
static const int MAX_IRQ = 24;
/*! @brief Inizializza l'APIC.
*
* Abilita l'APIC, quindi maschera tutti gli IRQ e azzera gli altri campi.
*
* @return false in caso di errore, true altrimenti
*/
bool init();
/*! @brief Invia l'End Of Interrupt.
*/
void send_EOI();
/*! @brief Maschera/smaschera una sorgente di richieste di interruzione.
*
* @param irq irq da mascherare/smascherare
* @param enable maschera se true, smaschera se false
*/
void set_MIRQ(natl irq, bool enable);
/*! @brief Setta la modalità di riconoscimento di una sorgente di richieste.
* di interruzione.
*
* @param irq irq su cui agire
* @param enable true -> riconoscimento sul livello
* false -> riconoscimento sul fronte
*/
void set_TRGM(natl irq, bool enable);
/*! @brief Imposta il vettore di interruzione associato ad una sorgente di
* richieste di interruzione.
*
* @param irq irq a cui associare il vettore
* @param vec vettore di interruzione
*/
void set_VECT(natl irq, natb vec);
}
/*! @brief Invia l'End Of Interrupt.
*
* Funzione analoga a apic::send_EOI, ma invocabile dall'assembly più comodamente
*/
extern "C" void apic_send_EOI();
/// @}
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup libexc Eccezioni
///
/// Dispensa: https://calcolatori.iet.unipi.it/resources/eccezioni.pdf
/// @{
//////////////////////////////////////////////////////////////////////////////
/// @name Eccezione di Page Fault (14)
///
/// Il microprogramma di gestione delle eccezioni di page fault lascia in cima
/// alla pila (oltre ai valori consueti) una parola quadrupla i cui 4 bit meno
/// significativi specificano più precisamente il motivo per cui si è
/// verificato un page fault.
///
/// @{
/// @brief Page fault causato da errore di protezione.
///
/// Se questo bit vale 1 la traduzione era presente, ma c'è stato un errore
/// diverso, per es. il processore si trovava a livello utente e la pagina era
/// di livello sistema (bit US = 0 in una qualunque delle tabelle dell'albero
/// che porta al descrittore della pagina). Se invece il bit PF_PROT è zero,
/// la pagina o una delle tabelle erano assenti (bit P = 0)
static const natq PF_PROT = 1U << 0;
/// @brief Page fault con accesso in scrittura.
///
/// L'accesso che ha causato il page fault era in scrittura.
///
/// @note Questo non implica che la pagina non fosse scrivibile: il page fault
/// potrebbe essere stato causato da altro (per es. traduzione assente o
/// accesso da livello utente a pagina di livello sistema).
static const natq PF_WRITE = 1U << 1;
/// @brief Page fault con accesso da livello utente.
///
/// L'accesso che ha causato il fault è avvenuto mentre il processore si
/// trovava a livello utente.
///
/// @note Questo non implica che la pagina fosse invece di livello sistema:
/// il page fault potrebbe essere stato causato da altro (per es. traduzione
/// assente, o accesso in scrittura su pagina di sola lettura).
static const natq PF_USER = 1U << 2;
/// @brief Page fault con bit riservati non validi.
///
/// Uno dei bit riservati nel descrittore di pagina o di tabella non
/// avevano il valore richiesto (il bit D deve essere 0 per i descrittori di
/// tabella e il bit PS deve essere 0 per i descrittori di pagina).
static const natq PF_RES = 1U << 3;
/// @}
/// @name Eccezioni con codice di errore.
///
/// Alcune eccezioni, per lo più legate al meccanismo della segmentazione,
/// lasciano in pila un codice di errore che è l'offset all'interno di una delle
/// tre tabelle IDT, GDT o LDT (quest'ultima non usata da noi) e di tre bit che
/// specificano meglio il tipo di errore.
/// @{
/// @brief Eccezione causata da evento esterno.
///
/// L'eccezione è stata sollevata mentre il processore cercava di gestire un
/// evento esterno al programma (per esempio, una richiesta di interruzione
/// esterna).
static const natq SE_EXT = 1U << 0;
/// @brief Eccezione durante accesso alla IDT.
///
/// L'eccezione è stata sollevata durante un accesso a un gate della IDT. In
/// questo caso il resto del codice di errore è l'offset del gate all'interno
/// della IDT.
static const natq SE_IDT = 1U << 1;
/// @brief Table Indicator dell'eccezione.
///
/// Se @ref SE_IDT è 0, il bit SE_IDT indica se l'eccezione è stata sollevata
/// durante un accesso a un descrittore della LDT (SE_TI==1) o alla GDT
/// (SE_TI==0). In questo caso il resto del codice di errore è l'offset del
/// descrittore all'interno della tabella interessata.
static const natq SE_TI = 1U << 2;
/// @}
/// @brief Decodifica delle eccezioni.
///
/// Invia sul log alcuni messaggi che mostrano i dettagli associati ad una
/// eccezione.
///
/// @param tipo tipo dell'eccezione
/// @param errore eventuale codice di errore salvato in pila dal
/// processore (0 se assente)
/// @param rip istruction pointer salvato in pila dal processore
void log_exception(int tipo, natq errore, vaddr rip);
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup autocorr Supporto per l'autocorrezione
///
/// Per permettere al sito dell'autocorrezione di eseguire i programmi in
/// maniera non interattiva e controllarne l'output, cambiamo il significato
/// di alcune funzioni quando è definita la macro AUTOCORR. In particolare,
/// redirigiamo printf() su autocorr_printf(), che invia i messaggi sul log
/// invece che sullo schermo della macchina QEMU, e redirigiamo pause()
/// suo autocorr_pause(), che ritorna immediatamente.
/// @{
//////////////////////////////////////////////////////////////////////////////
#ifdef AUTOCORR
int autocorr_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
void autocorr_pause();
#define printf(fmt_, ...) autocorr_printf(fmt_, ##__VA_ARGS__)
#define pause() autocorr_pause()
#endif /*AUTOCORR */
/// @}
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup kern Funzioni di uso meno generale, usate dal nucleo
///
/// Questa sezione raggruppa alcune funzioni che sono realizzate dalla libce
/// per comodità, ma è improbabile che sia necessario usarle al di fuori
/// degli usi specifici all'interno del nucleo.
/// @{
//////////////////////////////////////////////////////////////////////////////
/*! @brief Come @ref snprintf, ma usa una `va_list` esplicita invece di essere variadica.
*
* Questa funzione contiene l'implementazione vera e propria del parser delle
* stringhe di formato ed è chiamata sia da @ref printf che da @ref snprintf.
*
* @param buf buffer di destinazione
* @param size dimensione di _buf_
* @param fmt stringa di formato
* @param ap lista degli argomenti per _fmt_
* @return il numero di caratteri necessari per scrivere l'intera stringa
*/
int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap);
/*! @brief Trova l'exception header all'interno di un file ELF caricato in memoria.
*
* L'exception header è generato dal compilatore e serve a recuperare le informazioni
* per lo stack-unwinding. Lo usiamo per implementare il backtrace in caso di errori.
*
* @param elf indirizzo virtuale dell'header ELF caricato in memoria
* @param[out] eh_frame indirizzo virtuale dell'exception header
* @param[out] eh_frame_len lunghezza dell'exception header
* @return true se trovata, false altrimenti
*/
bool find_eh_frame(vaddr elf, vaddr& eh_frame, natq& eh_frame_len);
/*! @brief Riavvia il sistema.
*
* Nella configurazione standard QEMU è stato impostato per fare lo shutdown
* invece di riavviare, quindi questa funzione ha l'effetto di spegnere la
* macchina virtuale.
*/
extern "C" void reboot();
/*! @brief Esegue l'istruzione `hlt`.
*
* Mette in pausa il processore in attesa di una richiesta di interruzione
* esterna
*/
extern "C" void halt();
/*! @brief Esegue lo shutdown del sistema.
*
* Chiama reboot() ed esegue `hlt` disabilitando le interruzioni.
*/
extern "C" [[noreturn]] void end_program();
/*! @brief Funzione di basso livello per la scrittura sul log.
*
* @ref flog() formatta il messaggio e poi chiama do_log() per inviarlo effettivamente
* (i programmi 'bare' e il nucleo possono usare la do_log fornita dalla libce,
* che scrive direttamente sulla porta seriale, ma nei moduli I/O e utente do_log
* è ridefinita in modo da invocare una primitiva di sistema)
*
* @param sev severità del messaggio
* @param buf buffer contenente il messaggio
* @param quanti dimensione in byte del messaggio
*/
extern "C" void do_log(log_sev sev, const char* buf, natl quanti);
extern char stack[], stack_end[];
/// @}
#else /* __ASSEMBLER__ */
//! @brief salva in pila tutti i registri generali.
.macro salva_registri
pushq %rax
pushq %rcx
pushq %rdx
pushq %rbx
pushq %rsi
pushq %rdi
pushq %rbp
pushq %r8
pushq %r9
pushq %r10
pushq %r11
pushq %r12
pushq %r13
pushq %r14
pushq %r15
.endm
//! @brief ripristina tutti i registri generali dalla pila.
.macro carica_registri
popq %r15
popq %r14
popq %r13
popq %r12
popq %r11
popq %r10
popq %r9
popq %r8
popq %rbp
popq %rdi
popq %rsi
popq %rbx
popq %rdx
popq %rcx
popq %rax
.endm
#endif /* __ASSEMBLER__ */
#define CE_FAULT 0x0
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup boot
/// @{
/// @addtogroup boothw
///
/// @{
//////////////////////////////////////////////////////////////////////////////
/// dimensione in byte di una entrata della GDT
#define DIM_GDT_ENTRY 8
/// @brief numero di righe della GDT
///
/// 5 entrate compresa la 0 che deve essere nulla, ma l'entrata del TSS occupa
/// due righe.
#define NUM_GDT_ENTRIES 6
/// dimensione in byte del segmento TSS
#define DIM_TSS 104
/// @name Indici all'interno della GDT
/// @{
/// segmento codice/sistema
#define ID_CODE_SYS 1
/// segmento codice/utente
#define ID_CODE_USR 2
/// segmento dati/utente
#define ID_DATA_USR 3
/// segmento TSS
#define ID_SYS_TSS 4
/// @}
/// @name Selettori di segmento
/// @{
/// selettore nullo
#define SEL_NULLO 0
/// selettore del segmento codice di livello sistema
#define SEL_CODICE_SISTEMA ((ID_CODE_SYS << 3) | LIV_SISTEMA)
/// selettore del segmento codice di livello utente
#define SEL_CODICE_UTENTE ((ID_CODE_USR << 3) | LIV_UTENTE)
/// selettore del segmento dati scrivibili di livello utente
#define SEL_DATI_UTENTE ((ID_DATA_USR << 3) | LIV_UTENTE)
/// selettore del segmento TSS
#define SEL_SYS_TSS ((ID_SYS_TSS << 3) | LIV_SISTEMA)
/// @}
/// @}
/// @}