Interfaccia software PCI

Il codice BDF

Esistono due diversi metodi per interfacciarsi al PCI Controller ma solo uno è usato e documentato di frequente e verrà di seguito discusso.
Al PCI Controller sono assegnate staticamente due porte di I/O:

Entrambe le porte sono a 32bit e richiedono quindi l'utilizzo di operandi appropriati per la lettura e scrittura dei bit.
La porta CONFIG_ADDRESS permette di specificare l'indirizzo BDF − Bus Device and Function − ed ha il seguente formato:

| enable | reserved |   bus   | device | function | register |
:     31 : 30    24 : 23   16 : 15  11 : 10     8 : 7      0 :

Configuration Space

I registri della periferica fanno parte di un'area di 256 bytes obbligatoria presente su tutti i dispositivi PCI, denominata PCI Device Structure.
I campi fondamentali di questa struttura, allineati a 32bit, sono:

Register : 31       24 : 23       16 : 15       8 : 7       0 :
---------:-------------:-------------:------------:-----------:
   00    | Device ID   :             | Vendor ID  :           |
   04    | Status      :             | Command    :           |
   08    | Class ID    | Subclass ID | Prog IF    | Rev ID    |
   0C    | BIST        | Header type | Lat. timer | Cache sz  |
---------:-------------:-------------:------------:-----------:
0x0 specific Header
---------:-------------:-------------:------------:-----------:
   10    | BAR0        :             :            :           |
   14    | BAR1        :             :            :           |
   18    | BAR2        :             :            :           |
   1C    | BAR3        :             :            :           |
   20    | BAR4        :             :            :           |
   24    | BAR5        :             :            :           |
   ::    :             :             :            :           :
   3C    | Max latency | Min Grant   | Int PIN    | Int Line  |

Per una trattazione più approfondita della tabella, si rimanda a OSDev.

Ricerca dispositivi

Il bus PCI è un bus plug & play, il che significa che è possibile aggiungere nuovi dispositivi semplicemente collegandoli all'opportuno slot. E' il software che gestisce il bus che, di volta in volta, deve prendersi carico della corretta inizializzazione dei dispositivi.
Una delle prime azioni è quella di ricerca di dispositivi collegati. Esistono vari algoritmi per determinare i dispositivi connessi. Il più semplice è la ricerca brutale operata per tutte le combinazioni di bus e device (256*32=8192 combinazioni).
Una tecnica poco più avanzata ma piuttosto efficente è quella di Recursive Scan, che può essere così schematizzata:

RicercaBus(bus_no):
    Per ogni device in range 32:
        Se device.esiste():
            device.configura()
            Se device.type == PCI to PCI:
                RicercaBus(device.secondary_bus)
RicercaBus(0)

in particolare i dispositivi PCI-to-PCI sono caratterizzati da un Class ID = 0x06 e da un Subclass ID = 0x04. Il tutto avviene inviando il codice BDF alla porta CONFIG_ADDRESS e scambiando dati tramite porta DATA_ADDRESS.

I registri BAR

Una prima informazione per l'identificazione dello spazio d'indirizzamento richiesto da un dispositivo è data dall'analisi dei suoi registri BAR. Questi registri si distinguono nelle due categorie di mappatura in base al valore del bit meno significativo.
Ecco i due formati e la loro descrizione:

Memory Mapped
|  16-Byte aligned Base address  |  Prefetchable?  |  Type  |  0  |
: 31                           4 :               3 : 2    1 :   0 :
I/O Mapped
| 4-Byte aligned Base address | Reserved |  1  |
: 31                        2 :        1 :   0 :
NB. indirizzo allineato a n-bytes significa semplicemente che gli indirizzi dei registri BAR si trovano a multipli di n-bytes all'interno della PCI Device Structure, non ha nulla a che vedere con la grandezza o allineamento delle aree di memoria, peraltro specificate dal campo Type.

Nel caso di indirizzamento a 64 bits, la parte alta dell'indirizzo è contenuta nel registro BAR successivo.

Decodifica indirizzo

Lo standard PCI impone che ogni dispositivo PCI sia dotato di un decoder programmabile per poter decodificare l'intero spazio indirizzi (complete deconding in contrapposizione al partial deconding utilizzato da molti dispositivi per limitare i costi).
Tuttavia, per poter usare efficacemente lo spazio indirizzi, le architetture PC utilizzano solo i bit superiori come discriminante per determinare il dispositivo interessato. Per tale motivo, un decoder di dispositivo PCI deve garantire una programmabilità solo per i bit superiori dei registri BAR. I restanti bit sono gestiti staticamente dal dispositivo.

Il programma di mappatura deve quindi occuparsi di capire solo quanti bit dei registri BAR sono dedicati a linee statiche e quindi effettivamente utilizzate dal dispositivo. Questo ci da due informazioni:

sulla base di questo, il software di assegnamento indirizzo assegna un range di indirizzi appropriato, di solito più grande per far fronte ai limiti imposti dallo schema di indirizzamento, e imposta i bit alti del registro BAR per puntare al primo indirizzo del range.

Assegnamento indirizzo

Per ottenere queste informazioni, si invia al registro BAR il valore 'magico' 0xFFFF-FFFF. Questo funziona perchè, secondo lo standard PCI, i bit non utilizzati vanno settati a zero; scrivendo quindi un valore 1 nei bit di indirizzo del BAR e leggendo gli stessi bit, si può vedere quali di questi sono rimasti a 0, ovvero non sono programmabili.
Il valore a 32bit letto si chiama mask e, per lo standard, è sempre naturally alligned, ovvero formato da due parti: un certo numero di valori binari 1 a sinistra e un numero di valori binari 0 a destra.
Per effettuare la decodifica della grandezza della memoria è quindi necessario effettuare la negazione binaria dei bit della mask e sommare 1. E' bene notare che questo schema assicura che tutti i valori ottenuti dalla decodifica sono potenze di 2, e cioè la grandezza delle aree di memoria è potenza di 2.

Ad esempio, se il registro BAR4 risponde con una mask di 0xFFC0-0000, il valore decodificato è 2^22, il che significa che il dispositivo richiede uno spazio di indirizzamento grande 2^22=4MB.
Sappiamo anche che i 21-0 del bus indirizzi sono assegnati staticamente dal dispositivo, mentre i bit alti nel range 31-22 potranno essere configurati dinamicamente dal nostro software.

Una volta fatta l'assegnazione, il valore deciso dovrà essere scritto nel registro BAR4...che però tiene indirizzi a 28bit...Supponendo, ad esempio, una mask 0xFFFF-FFF8 si dovrebbe settare dinamicamente il bit 3 del registro BAR, che però è dedicato ad altri scopi (Prefetchable).
Questo non accade perchè lo standard PCI impone una grandezza minima di 2^4=16 bytes per le aree di memoria e pertanto la mask 0xFFFF-FFF8 non potrebbe essere valida.

Esempio assegnamento indirizzo

Supponiamo di avere un dispositivo PCI che dispone di un area di interfaccia con l'esterno di 256MB, segnalata nel registro BAR0 come area memory mapped (bit0=0). Supponiamo anche che il dispositivo utilizzi lo schema di indirizzamento a 32bit(type=0x0).
Questo significa che i 256MB hanno bisogno di uno spazio di indirizzamento grande 2^28 bytes. In virtù di ciò, i 28 bit inferiori saranno allocati staticamente mentre i 4 bit superiori potranno essere allocati dinamicamente dal software, in una qualsiasi combinazione.
Per rispecchiare questa configurazione, la mask dovrà assumere il valore 0xF000-0000 che, una volta decodificato, ridarà luogo a 2^28=256MB, grandezza dello spazio indirizzi riservato.
Supponiamo che il nostro software assegni allora al nostro dispositivo l'indirizzo dinamico 0x5xxx-xxxx. Per fare questo, si occuperà di scrivere il valore 0x5 nei 4 bit più significativi del BAR0, lasciando invariati i restanti bit.
Ecco quindi lo schema finale di BAR0 dopo l'assegnamento:

|       Base address     |  Prefetchable?  |  Type  |  M  |
:        0x5xxx-xxx      :               x :   00   :  0  :

A questo punto, se volessimo accedere alla prima locazione di memoria del dispositivo, dovremmo porre sul bus indirizzi il valore 0x5000-0000. In modo analogo, per accedere all'ultima locazione, il valore da utilizzare sarebbe 0x5FFF-FFFF.

Come si evince, non c'è alcuna informazione sull'effettiva organizzazione dei 256MB di memoria del dispositivo. Per comunicare con lo stesso si presume infatti che il programmatore sia in possesso del manuale specifico, dove viene riportata la struttura ed il significato della memoria dell'interfaccia.


Home < Prev | Next >