1. Indice

2. Bus PCI

Il bus PCI (Peripheral Component Interconnect) è uno standard per i bus introdotto da Intel.

La maggior parte dei computer dell’epoca (come oggi) erano espandibili, ovvero potevano essere inserite delle schede di espansione indipendenti da IBM per aggiungere funzionalità al sistema.

Tuttavia questa possibilità generò dei problemi, specialmente in un mercato dove non era presente il coordinamento tra i vari produttori di schede. Poteva infatti capitare che due schede diverse facessero riferimento agli stessi indirizzi di memoria, e che quindi quegli indirizzamenti fossero sovrapposti. Questo problema si propagava anche in come venivano scritti i driver, che non erano quindi più in grado di distinguere se la scheda desiderata era effettivamente inserita, se si trattava di una scheda diversa oppure se non ci fosse installato nulla.

Dal momento che il PC AT era costruito con parti standard, il suo bus venne chiamato ISA (Industry Standard Architecture). Questo bus era però molto limitato e descriveva tutti i limiti del PC AT.

Nel 1992 la Intel propose lo standard Peripheral Component Interconnect, all’epoca a 32bit, valido per qualsiasi tipo di processore, non solo Intel. Nei computer moderni si utilizza un’evoluzione del PCI, ovvero il PCIexpress, molto più complesso ma comunque compatibile con il precedente. Purtroppo non vedremo il PCIexpress in questo corso.

Lo standard PCI stabilisce:

Lo standard definisce inoltre tre spazi di indirizzamento: spazio di memoria, spazio di I/O e spazio di configurazione.

I primi due spazi sono completamente analoghi a quelli che già conosciamo e sono quelli che il software deve utilizzare per dialogare con le periferiche connesse al bus.

Per garantire il vincolo che i registri delle periferiche non occupino indirizzi sovrapposti si introducono due regole:

  1. Le periferiche che rispettano lo standard non possono scegliere autonomamente gli indirizzi dei propri registri, ma devono contenere dei comparatori programmabili in modo che questi indirizzi siano impostati dal software della macchina.

  2. Il software può impostare questi comparatori all’avvio del sistema, tramite il nuovo spazio di configurazione.

Lo spazio di configurazione è fatto in modo da poter accedere a dei registri di configurazione che ogni periferica deve avere per rispettare lo _standard_ in modo univoco e senza conflitti.

Tramite questi registri il software di avvio (PCI BIOS) può scoprire quali periferiche sono connesse al bus. Non solo, capisce anche di quanti indirizzi hanno bisogno e programma di conseguenza i comparatori affinché non ci siano sovrapposizioni.

La struttura che permette al bus di essere indipendente dal processore è quella del ponte ospite-PCI (detto North-Gate), un dispositivo che fa da tramite tra il bus locale (quello dove risiede la CPU) e il bus PCI.

Lo standard PCI non prevede infatti un unico bus, bensì un albero di bus, che ha come radice il bus collegato al ponte ospite-PCI. Ogni bus è collegato all’albero tramite ponti PCI-PCI. Per ogni albero si possono avere fino ad un massimo di 256 bus. Ogni bus può inoltre collegare fino a 32 dispositivi, ciascuno contenente da 1 a 8 funzioni (audio, video, …). Alcune funzioni possono persino fare da ponte verso altri tipi di bus, formando ponti PCI-ISA, ponti PCI-ATA, ponti PCI-USB, …

Esempio di architettura con bus PCI.

Le operazioni (lettura, scrittura, …) sul bus PCI sono dette transazioni. Le transazioni sono iniziate da un dispositivo iniziatore che cerca di operare su un altro dispositivo obiettivo, e seguono il clock.

Lo standard permette a qualsiasi dispositivo di essere iniziatore o obiettivo in transazioni diverse. Questo meccanismo permette il meccanismo di accesso diretto alla memoria di cui parleremo in seguito.

Immaginiamo per ora (per semplicità) che l’unico iniziatore sia il ponte ospite-PCI, che inizia le transazioni sul bus PCI per tradurre le operazioni analoghe avviate sul bus locale.

Le transazioni si svolgono in più fasi:

I principali collegamenti del bus sono i seguenti: (# indica gli attivi bassi)

Nome Iniziatore Obiettivo Ruolo
FRAME# uscita ingresso Delimita l’inizio e il termine di ogni transazione
DEVSEL# ingresso uscita Segnale attivato dall’obiettivo che riconosce uno dei propri indirizzi durante l’indirizzamento
C[3:0] uscita ingresso Codificano il tipo di operazione nell’indirizzamento (C[3:0])
BE#[3:0] uscita ingresso Fungono da byte-enabler nelle fasi di trasferimento (BE#[3:0])
AD[31:0] ingresso/uscita ingresso/uscita Codificano l’indirizzo nella fase di indirizzamento.
Trasportano i dati nelle fasi di trasferimento dati
TRDY# ingresso uscita (target ready), Handshake nelle fasi di scambio dati
IRDY# uscita ingresso (initiator ready), Handshake nelle fasi di scambio dati
STOP# ingresso uscita Serve a terminare prematuramente una transazione
CLK ingresso ingresso Segnale di clock, tutti i dispositivi campionano i loro segnali in ingresso sul suo fronte di salita.
È molto più lento di quello della CPU (all’inizio era 33MHz poi è passato a 64MHz)

Alcuni esempi di temporizzazioni:

Lettura con 4 fasi di trasferimento dati.

Scrittura con 4 fasi di trasferimento dati.

Il collegamento segue il seguente protocollo:

Nelle operazioni di lettura:

Nelle operazioni di scrittura invece avviene l’opposto:

Ogni fase dati trasferisce al più 4Byte allineati naturalmente. Ciascuna fase si conclude quando sia IRDY# che TRDY# sono attivi sullo stesso fronte di salita del clock.

Il riutilizzo delle stesse linee per scopi diversi riduce i costi a scapito però della velocità di trasferimento. Fortunatamente, la possibilità di eseguire più fasi dati con una singola fase di indirizzamento ci fa recuperare un po’ di velocità. È infatti sufficiente che l’iniziatore mantenga FRAME# attivo quando lo è anche IRDY#.

L’obiettivo può inoltre attivare STOP# per terminare forzatamente la transazione.

2.1. Spazio di Configurazione

Lo standard PCI prevede uno spazio di configurazione di 4 Byte (64 parole) per ogni funzione. In questo spazio ciascuna funzione rende accessibili i propri registri di configurazione.

Un indirizzo nello spazio di configurazione è composto da tre parti:

Il bus più vicino alla CPU ha il codice 0, gli altri vengono assegnati in maniera dinamica.

Le funzioni rendono accessibili i loro registri di configurazione secondo lo schema sulla destra.

Non tutti i registri devono essere sempre presenti, ma, tranne per il ponte ospite-PCI, tutte le funzioni devono avere i seguenti registri:

  • Vendor ID: (lettura) un codice che identifica il produttore della funzione. È assegnato ad ogni produttore da una autorità centrale (PCI SIG). L’Intel ha come ID: 8086

  • Device ID: (lettura) un codice che identifica la funzione. È assegnato dal produttore

  • Command: (lettura/scrittura) permette di abilitare o disabilitare varie capacità della funzione (vedere sotto);

  • Status: (lettura) descrive alcune capacità della funzione, più lo stato di alcuni eventi, per lo più legati al verificarsi di errori

  • Revision ID: (lettura) numero di revisione della funzione, assegnato dal produttore;

  • Class Code: (lettura) un codice che identifica in modo generico il tipo di funzione;

  • Header Type: (lettura) tipo di header; deve essere 0 per tutte le funzioni che non siano ponti PCI-PCI o ponti PCI-CardBus.

In particolare, del registro Command ci interessano solo i bit 0, 1 e 2. Questi bit si occupano di:

  • Abilitare la funzione nella risposta alle transazioni nello spazio di I/O (0) e di memoria (1).
  • Abilitare la funzione a comportarsi da bus master qualore ne abbia la capacità (2)

Questi bit valgono di default zero, ed è il software di inizializzazione che deve settarli a 1 dopo aver assegnato alla funzione gli indirizzi necessari nei corrispondenti spazi, qualora questi corrispondano. Se ad esempio una funzione non ha registri nello spazio di I/O il suo bit 0 non sarà scrivibile.

I registri con sfondo grigio sono obbligatori.

Nella nostra macchina il ponte ospite-PCI è collegato come se fosse una periferica che esprime dei registri nello spazio di I/O:

Questi due registri possono essere usati per accedere allo spazio di configurazione in modo simile a come facciamo per la memoria video. Per recuperare il risultato dell’operazione IN/OUT si utilizza CDP.

Lo scopo principale della fase di configurazione dei dispositivi è quello di assegnare indirizzi univoci negli spazi di I/O e/o di memoria.

Una volta che CAP è stato impostato, il ponte ospite-PCI tradurrà le letture e le scritture in CDP in corrispondenti transizioni nello spazio di configurazione.

Prima di impostare CAP però il ponte attiverà l’uscita IDSEL, che è un entrata presente in ogni slot di espansione che è necessario attivare per poter trasmettere il numero della funzione e l’offset della parola che contiene il registro (oltre ai byte enable relativi al registro).

Un vincolo presente in tutti i dispositivi è che la funzione 0 deve essere sempre presente. In questo modo si permette allo spazio di configurazione di essere accessibile già all’avvio del sistema.

Il costruttore di un blocco, oltre a deciderne la locazione, l’organizzazione interna e i blocchi di indirizzi all’interno dei quali rendere disponibili i registri specifici della funzione, deve prevedere un BAR Base Address Register nello spazio di configurazione.

BAR di memoria, $b \ge 4$

BAR di I/O, $b \ge 2$

La dimensione del blocco è $2^b$ dove $b$ è il numero di bit non scrivibili utilizzati per fornire al software di inizializzazione indicazioni sul tipo di blocco e sulla sua dimensione. In particolare il bit 0 serve a capire se il blocco è pensato per lo spazio di I/O (1) o di memoria (0). Si può scoprire il suo valore provando a scrivere tutti 1 nel BAR e rileggendo il risultato, per vedere quanti sono stati effettivamente scritti.

I blocchi possono essere assegnati solo a regioni allineate naturalmente alla loro dimensione. Il software di inizializzazione sceglierà quindi la regione e ne scriverà il numero nei bit scrivibili del BAR, così che contenga l’indirizzo di partenza del blocco.

Dopo aver assegnato una regione il software di inizializzazione setterà il bit 0 o 1 del registro Command. Da quel momento in poi la funzione risponderà alle transazioni i cui indirizzi cadono in quella regione.

Nella macchina QEMU l’inizializzazione è fatta dal PCI BIOS, e viene completata prima che il vostro software parta. Noi ci limiteremo ad accettare le impostazioni del BIOS, perciò tutto quello che ci serve è sapere quali indirizzi il BIOS ha assegnato ai vari blocchi, e possiamo farlo leggendo i BAR.

2.2. Gestione Interruzioni

I dispositivi PCI possono generare richieste di interruzioni, ma lo standard non prevede dettagli a riguardo.

Ogni dispositivo ha fino a quattro piedini in uscita per le richieste di interruzione: INTA#, INTB#, INTC# e INTD#.

Ogni funzione può quindi essere collegata ad al più uno di questi piedini. Inoltre, funzioni dello stesso dispositivo possono essere collegate allo stesso piedino.

Il piedino utilizzato deve essere leggibile dal registro di configurazione chiamato Intr. Pin, grande 1Byte e di sola lettura. Se vale 0 indica che la funzione non genera interruzione, 1 che utilizza INTA#, 2 che utilizza INTB# e così via…

Chi scrive il software però vorrebbe sapere a quale piedino dell’APIC ciascuna funzione è collegata, informazione che dipende però dal progettista in quanto non standardizzata. Questo deve quindi garantire che il BIOS scriva qualche informazione nel registro Intr. line (1Byte, lettura/scrittura) affinché il software possa ricavare l’informazione.

Nel nostro caso il BIOS scrive in Intr. line proprio il numero del piedino dell’APIC, quindi possiamo limitarci a leggere questo registro.

2.3. Il bus nel PC AT

Nella nostra macchina QEMU la struttura del bus è quella a destra.

Abbiamo un unico bus PCI e due ponti:

  1. Verso il bus ISA dove si trovano le vecchie periferiche del PC AT, come tastiera e timer
  2. Verso il bus ATA, dove si trovano gli HD

In questa architettura, comune nei primi anni 2000, il bus ospite-PCI veniva chiamato North Bridge e spesso conteneva altre funzioni, come quella di controllare una porizone della memoria.

Inoltre i ponti PCI-ISA e PCI-ATA erano spesso inglobati in un altro dispositivo che svolgeva tutte queste funzioni, chiamato South Bridge

I numero all’interno delle funzioni PCI rappresentano VendorID:DeviceID