[Tar] [Tar] [About] [Copertina] [indice] [indice]

Articoli


Traduzione di Andrea Cisternino acister@sunfe3.fe.infn.it

Questo è l'ultimo di una serie di cinque articoli sui character device drivers. In questa parte finale Georg si occupa dei devices per i quali è possibile il memory mapping, iniziando con una descrizione generale del sistema di memory management di Linux. di Georg v. Zezschwitz

Anche se pochi driver implementano la tecnica del memory mapping, essa ci fornisce interessanti punti di vista sul funzionamento di Linux. Introdurrò la gestione della memoria e le sue caratteristiche, permettendoci così di giocare con la console, di aggiungere il memory mapping ai drivers e di "piantare" il sistema...

Spazi di indirizzamento ed altre cose irreali.

Sin dai giorni del 80386, il mondo Intel supporta una tecnica chiamata indirizzamento virtuale. Proveniendo dallo Z80 e dal mondo 68000, la prima cosa che ho associato a questo termine è stata ``è possibile allocare più memoria della RAM fisica, poiché alcuni indirizzi verranno associati a parti del disco rigido''.

Per essere più accademico: ogni indirizzo usato dal programma per accedere alla memoria (non importa se codice o dati) sarà tradotto---o in un indirizzo fisico nella RAM, o in una eccezione che sarà gestita dal sistema per fornire la memoria richiesta. Alcune volte, tuttavia, l'accesso ad una particolare locazione di memoria virtuale rivela che il programma non si sta comportando correttamente---in questo caso, il sistema operativo genererà una ``vera'' eccezione (normalmente il segnale 11, SIGSEGV).

L'unità più piccola utilizzata da questo sistema di traduzione è la pagina, che è di 4 kB sull'architettura Intel e di 8 kB sull'Alpha (è definita in asm/page.h).

Quando si cerca di comprendere il sistema di traduzione dell'indirizzo si entra in una confusione di page table descriptors, segment descriptors, page table flags e diversi spazi di indirizzamento. Per ora diciamo che l'indirizzo logico (o virtuale) è quello usato dal programma; esso viene trasformato tramite le page-tables in un indirizzo fisico (o un page-fault). La Linux Kernel Hacker's Guide impiega una ventina di pagine per spiegare questo meccanismo, e non credo che sia possibile diminuirle ulteriormente, anche perché l'implementazione Intel è leggermente più complicata.

Per comprendere meglio l'inizializzazione, l'utilizzo e la tecnica sottostante l'uso delle pagine in Linux, specialmente per i processori Intel, dovete leggere la Linux Kernel Hacker's Guide. Essa è liberamente disponibile nella directory /pub/linux/docs/LDP di ogni mirror di sunsite.unc.edu. Anche se il libro è vecchio, niente è cambiato nel funzionamento del i386, ed altri processori sono simili (in particolare il Pentium è praticamente uguale ad un 386). Una versione un po' più aggiornata del libro si può trovare su Web, all'indirizzo http://www.redhat.com:8080/HyperNews/get/khg.html.

Pagine---più che un po' di memoria.

Se volete conoscere meglio la gestione delle pagine, potete leggere la KHG adesso o credere a questa breve descrizione.

Ci concentreremo ora su alcuni aspetti importanti delle pagine dal punto di vista della CPU: Altre importanti caratteristiche delle pagine dal punto di vista del Sistema Operativo sono: La memoria virtuale permette di fare cose interessanti come:

Memory Mapping: un Esempio

La prima assunzione che si deve fare quando si pensa di mappare in memoria un device è quella di poter definire una posizione esatta ed una lunghezza per quel device. Naturalmente è possibile contare l'ennesimo carattere in arrivo dalla porta seriale ad es., ma il paradigma su cui si basa la mmap() si applica molto più chiaramente a device che abbiano una dimensione ben definita. Anche perché mmap() si basa sulla costruzione di page-tables, e quindi solo dati che vivono in un indizzo fisico di memoria possono essere mappati.

Un character ``device'' impiegato ogni volta che usate la svgalib o il server X è /dev/mem. Questo device rappresenta la vostra memoria fisica. Il server X e la svgalib lo usano per mappare i buffer video della scheda grafica nel proprio spazio di indirizzamento.

Una volta (sono così vecchio?) la gente scriveva in BASIC giochi come Tetris che dovevano funzionare da una console di tipo testo. Piuttosto che usare i lenti comandi del BASIC i programmatori scrivevano direttamente nella memoria video. Questo è esattamente come usare il memory-mapping.

Per creare un piccolo esempio di utilizzo della mmap(), ho scritto un piccolo programma chiamato nasty. Come potete sapete, la scrittura araba è da destra verso sinistra. Anche se non credo che qualcuno possa preferire questo stile con le normali lettere dell'alfabeto latino, il programma seguente vi dà un idea di questo stile. Nasty gira esclusivamente su architettura Intel con scheda grafica VGA, in quanto usa esplicitamente gli indirizzi VGA. Questo limite non si applica a mmap() in generale, solo a questo esempio.

Se farete mai girare questo programma, fatelo come root (altrimenti non avrete accesso a /dev/mem), fatelo dalla console (non vedrete nulla se lo lanciate sotto X) e accertatevi di avere una scheda VGA o EGA.


/*
 * nasty.c - flips right and left on the VGA console.
 * "Arabic" display
 */

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int main(int argc, char **argv)
{
    FILE *fh;
    short *vid_addr, temp;
    int x, y, ofs;

    fh = fopen("/dev/mem", "r+");

    vid_addr = (short *) mmap(
    /* where to map to: don't mind */
                 NULL,
    /* how many bytes ? */
                 0x4000,
    /* want to read and write */
                 PROT_READ | PROT_WRITE,
    /* no copy on write */
                 MAP_SHARED,
    /* handle to /dev/mem */
                 fileno(fh),
    /* hopefully the Text-buffer :-) */
                 0xB8000);

    if (vid_addr)
        for (y = 0; y < 100; y++)
            for (x = 0; x < 40; x++) {
                ofs = y * 80;
                temp = vid_addr[ofs + x];
                vid_addr[ofs + x] = vid_addr[ofs + 79 - x];
                vid_addr[ofs + 79 - x] = temp;
            }

    munmap((caddr_t) vid_addr, 0x4000);
    fclose(fh);
    return 0;
}

Giochiamo con la mmap()

Cosa è possibile cambiare nella chiamata alla mmap() vista prima?

È possibile cambiare i privilegi per le pagine mappate modificando uno dei flag PROT chiedendo di poter scrivere, leggere o eseguire (PROT_READ, PROT_WRITE, e PROT_EXEC) i dati mappati nel programma.

Rimpiazzando MAP_SHARED con MAP_PRIVATE verrà settato il flag Copy-On-Write delle pagine interessate permettendovi di scrivere nel text buffer senza però modificarlo. Le eventuali modifiche saranno apportate alla vostra copia privata delle pagine.

Cambiando il parametro offset è possibile adattare nasty alle schede monocromatiche Hercules (usando 0xB0000 invece di 0xB8000), o ottenere risultati inaspettati, anche far piantare la macchina (usando un indirizzo casuale).

La mmap() può essere ``applicata'' anche ad un file su disco, invece cha alla memoria di sistema, convertendone il contenuto in stile ``arabo'' (ma verificate che le dimensioni mappate non siano superiori alla lunghezza del file). Non preoccupatevi se la vostra pagina del manuale dice che mmap() è una funzione di BSD---La questione con Linux ormai è ``chi scrive la documentazione'' piuttosto che ``chi implementa cosa''...

Invece che il NULL utilizzato come primo parametro è possibile specificare un indirizzo preciso (nello spazio di indirizzamento del vostro processo) a partire dal quale volete mappare le pagine fisiche richieste. Nelle versioni di Linux più recenti questa richiesta viene ignorata se non viene contemporaneamente utilizzato il flag MAP_FIXED. In questo caso Linux eliminerà qualsiasi mappatura precedente per l'indirizzo in questione, rimpiazzandola con quella richiesta. Se utilizzerete questa possibilità, dovete essere sicuri che l'indirizzo specificato sia quello del primo byte di una pagina ((addr & PAGE_MASK) == addr).

Concludendo, abbiamo visto uno degli usi preferiti della mmap()--- specialmente quando avrete a che fare con piccole porzioni di file di grandi dimensioni come i database, troverete utile---e più veloce--- mappare l'intero file in memoria per poterlo leggere e scrivere come se fosse memoria fisica, lasciando agli algoritmi di gestione dei buffer di Linux tutti i problemi di caching. L'accesso al file sarà molto più rapido che l'uso delle classiche fread() e fwrite().

VMA ed altri cyberspazi

La persona che si dovrà occupare di tutte queste utili caratteristiche è il vostro povero programmatore di device driver. Mentre il supporto per la mmap() sui file è cura del kernel (dei singoli filesystem in realtà), il sistema di memory mapping per altri device deve essere supportato direttamente dai driver, rendendo disponibile una apposita funzione nella struttura fops che è stata descritta in un precedente articolo.

Come prima cosa daremo un'occhiata ad una delle poche ``vere'' implementazioni di questo supporto, basando la nostra discussione sul driver per il device /dev/mem. Quindi vi mostrerò una particolare implementazione utile per schede tipo frame-grabber, schede di I/O analogico-digitale per laboratorio e, probabilmente, anche per altre periferiche.

La funzione mmap() è fondamentalmente basata sulla do_mmap(), definita nel file mm/mmap.c del kernel. La do_mmap() fà due cose importanti:

Le VMA hanno bisogno di qualche spiegazione: esse rappresentano gli indirizzi, i metodi, le protezioni ed i flag di parti dello spazio di indirizzamento del processo. L'area di memoria da voi mappata aggiungerà una propria vm_area_struct a quelle normalmente possedute dal processo. Le strutture VMA sono gestite dal kernel ed ordinate in un albero bilanciato per permettere un accesso veloce.

I campi della struct vm_area_struct sono definiti in linux/mm.h. I contenuti ed il numero di VMA per un dato processo possono essere osservati guardando i file /proc/pid/maps di qualsiasi processo in esecuzione, ove pid è il process id di quel particolare processo. Vediamo il contenuto di maps per il programma nasty visto in precedenza e compilato in formato ELF. Durante l'esecuzione del programma il file maps assomiglierà a quanto segue (senza commenti):


# /dev/sdb2: nasty CSS
08000000-08001000 rwxp 00000000 08:12 36890
# /dev/sdb2: nasty DSS
08001000-08002000 rw-p 00000000 08:12 36890
# BSS for nasty
08002000-08008000 rwxp 00000000 00:00 0
# /dev/sda2: /lib/ld-linux.so.1.7.3 CSS
40000000-40005000 r-xp 00000000 08:02 38908
# /dev/sda2: /lib/ld-linux.so.1.7.3 DSS
40005000-40006000 rw-p 00004000 08:02 38908
# BSS for ld-linux.so
40006000-40007000 rw-p 00000000 00:00 0
# /dev/sda2: /lib/libc.so.5.2.18 CSS
40009000-4007f000 rwxp 00000000 08:02 38778
# /dev/sda2: /lib/libc.so.5.2.18 DSS
4007f000-40084000 rw-p 00075000 08:02 38778
# BSS for libc.so
40084000-400b6000 rw-p 00000000 00:00 0
# /dev/sda2: /dev/mem (our mmap)
400b6000-400c6000 rw-s 000b8000 08:02 32767
# the user stack
bfffe000-c0000000 rwxp fffff000 00:00 0

I primi due campi di ogni linea, separati da un trattino, rappresentano l'indirizzo al quale sono mappati i dati. Il campo successivo mostra le protezioni per le pagine in questione (r significa leggibile, w scrivibile, p private ed s shared). Il quarto campo indica a quale offset all'interno del file è effettuato il mapping mentre i successivi due campi sono rispettivamente il device fisico e l'inode del file. Il numero di device rappresenta un disco (hard disk o altro) montato (ad es. 03:01 è /dev/hda1, 08:01 è /dev/sda1). Per conoscere il file associato ad un certo inode il metodo più semplice (ma anche il più lento) è:

        % cd your_mount_point
        % find . -inum your_inode_number -print

Nel cercare di comprendere il file maps ed i commenti, ricordate che Linux distingue tra ``Code Storage Segment'' (CSS), a volte chiamato ``text'' segment; ``Data Storage Segment'' (DSS), contenenti dati inizializzati; e ``Block Storage Segment'' (BSS), area generica per variabili non inizializzate e normalmente azzerata alla partenza. Poichè non devono essere caricati da disco valori iniziali per le variabili del BSS, tutti i campi di questo tipo in maps non hanno né numero di device né inode (un major number ``0'' equivale a NODEV). Questo ci mostra un altro possibile impiego della mmap(): passando MAP_ANONYMOUS come file handle è possibile richiedere blocchi di memoria libera per il programma (alcune versioni di malloc() ottengono memoria dal sistema in questo modo).

Il vostro turno

Quando la do_mmap() chiama la funzione del vostro driver per il mmapping, una VMA per la nuova regione di memoria è già stata creata, ma non ancora inserita nella struttura task_struct (definita in linux/sched.h) del processo.

La funzione del device driver deve implementare la seguente interfaccia:


	int skel_mmap (struct inode *inode,
	               struct file *file,
	               struct vm_area_struct *vma)

vma->vm_start conterrà l'indirizzo nello user space a partire dal quale deve essere mappata la memoria. vma->vm_end contiene il primo indirizzo non valido alla fine dell'area cosicché la differenza fra i due campi corrisponde all'argomento length della chiamata alla mmap(). Il campo vma->vm_offset è l'offset all'interno del file a partire da quale il kernel mapperà la memoria ed è identico all'argomento offset della mmap().

Studiamo ora come il driver per /dev/mem implementa il memory mapping. Troverete il codice nella funzione mmap_mem() del file drivers/char/mem.c. Se vi siete aspettati qualcosa di complesso rimarrete delusi: essa chiama semplicemente remap_page_range() (in mm/memory.c). Se veramente volete comprendere il funzionamento di questa funzione dovete leggere le relative venti pagine della Kernel Hacker's Guide. In breve, vengono prima creati i page descriptors per lo spazio di indirizzamento del processo e quindi vengono inizializzati con link alla memoria fisica. Notate che la struttura vm_area_struct è utilizzata per il memory management, mentre i page descriptor sono direttamente interpretati dalla CPU nel generare l'indirizzo fisico.

Se la remap_page_range() ritorna zero, dicendo così che nessun errore è avvenuto, il vostro mmap-handler dovrebbe fare lo stesso. In questo caso la do_mmap() ritornerà l'indirizzo a partire dal quale sono state mappate le pagine. Qualsiasi altro valore restituito verrà considerato un errore.

Un driver reale

È impossibile fornire esempi di codice per ogni possibile applicazione del memory mapping di character devices. Alessandro ed io ci siamo occupati di un problema concreto nel caso di una scheda per laboratorio dotata di RAM, CPU e naturalmente convertitori ADC e DAC, ingressi ed uscite digitali, generatori di clock ed altro.

Per prima cosa descriverò il funzionamento dell'interfaccia per meglio comprendere i problemi che un programmatore deve affrontare nello scrivere device drivers.

La scheda di cui ci siamo occupati può campionare i suoi ingressi direttamente nella sua memoria in maniera continua. Lo stato di questa operazione può essere investigato dall'esterno tramite una porta di I/O chiamata ``character channel''. Questo canale accetta un flusso di caratteri ASCII che rappresentano i comandi. I processi interagiscono con la scheda attraverso questo canale usando le classiche funzioni read() e write().

I reali trasferimenti di dati sono effettuati in maniera indipendente dal funzionamento del character channel: inviando su quest'ultimo un comando come TOHOST interface address, length, host address, la scheda genererà un interrupt dicendo al PC che desidera trasferire, in DMA, un blocco di dati in un certo indirizzo della memoria centrale. Ma dove dobbiamo mettere questi dati? Abbiamo deciso di non complicare la semplice interfaccia a caratteri con i traferimenti di dati. In più, poiché l'utente può inviare qualsiasi comando alla scheda, non abbiamo fatto assunzioni sull'ordinamento ed il significato dei dati.

Per queste ragioni abbiamo deciso lasciare il pieno controllo all'utente, permettendogli di richiedere al sistema regioni di memoria accessibili al DMA e di mapparle nel proprio spazio di indirizzamento. Ogni richiesta di DMA proveniente dalla scheda verrà controllata confrontandola con queste aree. In altre parole abbiamo implementato qualcosa di simile ad una skel_malloc() od una skel_free() per mezzo di chiamate a comandi ioctl(), disabilitando contemporaneamente ogni trasferimento verso altre regioni per rendere sicuro il tutto.

Vi chiederete perché non abbiamo usato direttamente la mmap() per questo lavoro. Principalmente perché non era possibile realizzare l'equivalente munmap(). Il driver infatti non riesce a sapere quando termina l'operazione di memory mapping. Linux fà tutto da solo: rimuove la vm_area_struct e le page tables decrementando il numero di utilizzatori nel caso di pagine shared.

Poiché dobbiamo allocare i buffer per il DMA con la kmalloc(), essi devono essere liberati con la kfree() ma Linux non ci permetterà di farlo quando automaticamente eliminerà il mapping. Ma se non esiste più il mapping per i buffer, essi non sono più necessari. Perciò abbiamo implementato una skel_malloc() che alloca i buffer mappandoli contemporaneamente nello user space, ed una skel_free() che rilascia i buffer eliminando il mapping (dopo aver controllato che non stia avvenendo un trasferimento in DMA).

Avremmo potuto implementare il sistema di memory mapping nella libreria di funzioni che accompagna il driver con lo stesso sistema usato nel programma nasty visto prima. Ma, per valide ragioni, /dev/mem è accessibile solo da root, mentre i programmi che usano il device devono poter essere eseguiti da qualsiasi utente.

Abbiamo usato due trucchi nel nostro codice. Per prima cosa modifichiamo direttamente l'array mem_map comunicando a Linux l'utilizzo ed i permessi delle nostre pagine di memoria fisica. L'array mem_map (mm/memory.c) di strutture mem_map_t (linux/mm.h), ed è usato per mantenere informazioni su tutta la memoria fisica.

Per ogni pagina allocata settiamo il flag PG_reserved. Questo è un metodo piuttosto sporco, ma ragiunge il suo scopo con tutte le versioni di Linux (almeno a partire dalla 1.2.x): Linux tiene le sue mani lontane dalle nostre pagine! Le considera come RAM video o una ROM o qualsiasi cosa non possa swappare o utilizzare come memoria libera. Lo stesso array mem_map usa questo trucco per proteggersi da processi troppo avidi di memoria.

Il secondo trucco che abbiamo utilizzato è quello di generare velocemente uno pseudo-file che assomiglia in qualche modo ad un /dev/mem già aperto. La mancanza di una mmap_mem() nel kernel ci ha fatto riscrivere una funzione simile prendendo come esempio quella del driver per /dev/mem. Questa semplice funzione è quindi utilizzata con remap_page_range(). Questo sistema non è sicuramente il più ``pulito'' ma funziona. Si sarebbe potuta ottenenere la stessa funzionalità in maniera più corretta ma con codice più complesso.

Oltre a ciò, viene tenuta traccia dei buffer DMA allocati dalla nostra skel_malloc() inserendoli in liste per poter verificare se le richieste di DMA coinvolgono aree di memoria valide. Le liste sono usate anche per rilasciare i buffer quando il programma chiude il device senza chiamare precedentemente skel_free(). dma_desc è il tipo di queste liste, come mostrato dalle seguenti linee di codice. Esse mostrano le nostre skel_malloc() e skel_free() implementate tramite chiamate a ioctl():

 
/* =============================================
 * SKEL_MALLOC
 *
 * The user desires a(nother) dma-buffer, that
 * is allocated by kmalloc (GFP_DMA) (continous
 * and in lower 16 MB).
 * The allocated buffer is mapped into
 * user-space by
 *  a) a pseudo-file as you would get it when
 *     opening /dev/mem
 *  b) the buffer-pages tagged as "reserved"
 *     in memmap
 *  c) calling the normal entry point for
 *     mmap-calls "do_mmap" with our pseudo-file
 *
 * 0 or < 0 means an error occured, otherwise
 * the user space address is returned.
 * This is the main basis of the Skel_Malloc
 * Library-Call
 */

/* ---------------------------------------------
 * Ma's little helper replaces the mmap
 * file_operation for /dev/mem which is declared
 * static in Linux and has to be rebuilt by us.
 * But ain't that much work; we better drop more
 * comments before they exceed the code in length.
 */

static int
skel_mmap_mem (struct inode *inode,
               struct file *file,
               struct vm_area_struct *vma)
{
    if (remap_page_range (vma->vm_start,
                          vma->vm_offset,
                          vma->vm_end - vma->vm_start,
                          vma->vm_page_prot))
        return -EAGAIN;
    vma->vm_inode = NULL;
    return 0;
}

static unsigned long
skel_malloc (struct file *file,
             unsigned long size)
{
    unsigned long pAdr, uAdr;
    dma_desc *dpi;
    skel_file_info *fip;
    struct file_operations fops;
    struct file memfile;

    /* Our helpful pseudo-file only ... */
    fops.mmap = skel_mmap_mem;
    /* ... support mmap */
    memfile.f_op = &fops;
    /* and is read'n write */
    memfile.f_mode = 0x3;
    fip = (skel_file_info *) (file->private_data);
    if (!fip)
        return 0;
    dpi = kmalloc (sizeof (dma_desc), GFP_KERNEL);
    if (!dpi)
        return 0;
    PDEBUG ("skel: Size requested: %ld\n", size);
    if (size <= PAGE_SIZE / 2)
        size = PAGE_SIZE - 0x10;
    if (size > 0x1FFF0)
        return 0;
    pAdr = (unsigned long) kmalloc (size,
                                    GFP_DMA | GFP_BUFFER);
    if (!pAdr) {
        printk ("skel: Trying to get %ld bytes"
                "buffer failed - no mem\n", size);
        kfree_s (dpi, sizeof (dma_desc));
        return 0;
    }
    for (uAdr = pAdr & PAGE_MASK;
         uAdr < pAdr + size;
         uAdr += PAGE_SIZE)
#if LINUX_VERSION_CODE < 0x01031D
        /* before 1.3.29 */
        mem_map[MAP_NR (uAdr)].reserved |=
            MAP_PAGE_RESERVED;
#elseif        /* LINUX_VERSION_CODE < 0x01033A */
        /* before 1.3.58 */
        mem_map[MAP_NR (uAdr)].reserved = 1;
#else
        /* most recent versions */
        mem_map_reserve (MAP_NR (uAdr));
#endif
    uAdr = do_mmap (&memfile, 0,
                    (size + ~PAGE_MASK) & PAGE_MASK,
                    PROT_READ | PROT_WRITE | PROT_EXEC,
                    MAP_SHARED, pAdr & PAGE_MASK);
    if ((signed long) uAdr <= 0) {
        printk ("skel: A pity - "
                "do_mmap returned %lX\n", uAdr);
        kfree_s (dpi, sizeof (dma_desc));
        kfree_s ((void *) pAdr, size);
        return uAdr;
    }
    PDEBUG ("skel: Mapped physical %lX to %lX\n",
            pAdr, uAdr);
    uAdr |= pAdr & ~PAGE_MASK;
    dpi->dma_adr = pAdr;
    dpi->user_adr = uAdr;
    dpi->dma_size = size;
    dpi->next = fip->dmabuf_info;
    fip->dmabuf_info = dpi;
    return uAdr;
}


/* =============================================
 * SKEL_FREE
 *
 * Releases memory previously allocated by
 * skel_malloc
 */

static int
skel_free (struct file *file,
           unsigned long ptr)
{
    dma_desc *dpi, **dpil;
    skel_file_info *fip;

    fip = (skel_file_info *) (file->private_data);
    if (!fip)
        return 0;
    dpil = &(fip->dmabuf_info);
    for (dpi = fip->dmabuf_info;
         dpi; dpi = dpi->next) {
        if (dpi->user_adr == ptr)
            break;
        dpil = &(dpi->next);
    }
    if (!dpi)
        return -EINVAL;
    PDEBUG ("skel: Releasing %lX bytes at %lX\n",
            dpi->dma_size, dpi->dma_adr);
    do_munmap (ptr & PAGE_MASK,
               (dpi->dma_size + (~PAGE_MASK)) & PAGE_MASK);
    ptr = dpi->dma_adr;
    do {
#if LINUX_VERSION_CODE < 0x01031D
        /* before 1.3.29 */
        mem_map[MAP_NR (ptr)] &= ~MAP_PAGE_RESERVED;
#elseif        /* LINUX_VERSION_CODE \ 0x01033A */
        /* before 1.3.58 */
        mem_map[MAP_NR (ptr)].reserved = 0;
#else
        mem_map_unreserve (MAP_NR (ptr));
#endif
        ptr += PAGE_SIZE;
    }
    while (ptr < dpi->dma_adr + dpi->dma_size);
    *dpil = dpi->next;
    kfree_s ((void *) dpi->dma_adr, dpi->dma_size);
    kfree_s (dpi, sizeof (dma_desc));
    return 0;
}

Considerazioni finali sul PCI

La tecnologia evolve, ma le idee spesso rimangono le stesse. Nel vecchio mondo ISA le periferiche avevano i loro buffer ``alla fine dello spazio di indirizzamento'': sopra i 640 kB. Molte schede PCI moderne fanno la stessa cosa, ma oggi questa frase và riferita allo spazio di indirizzamento a 32-bit (ad es. indirizzi come 0xF0100000).

Se volete avere accesso a buffer posti a questi indirizzi dovrete usare la vremap() definita in mm/vmalloc.c per rimappare le pagine di questa memoria fisica nel vostro spaizio di indirizzamento.

vremap() lavora in modo simile alla funzione mmap() vista in nasty, ma è molto più semplice: void * vremap (unsigned long offset, unsigned long size); Dovete semplicemente passare l'indirizzo fisico del buffer e la sua dimensione. Ricordate che vengono sempre mappate pagine quindi offset e size devono essere multipli della dimensione di una pagina. Se il buffer è più piccolo di una pagina, mappate tutta la pagina e fate attenzione a non usare indirizzi non validi.

Non ho provato personalmente questa funzione, e non sono sicuro se i trucchi visti precedentemente per mappare i buffer in user space funzionano con memoria sul bus PCI. Se vorrete provare, dovrete sicuramente eliminare la ``brutale'' manipolazione dell'array mem_map: esso è solo per normale memoria fisica. Provate a rimpiazzare le kmalloc() e kfree() con le corrispondenti vremap() e vfree() ed effettuate un ulteriore mapping (questa volta in user space) con la do_mmap().

Come avrete capito, siamo giunti al termine di questa serie di articoli. Ora spetta a voi andare spavaldamente dove nessun Linuxer è mai andato prima...

Buona fortuna!

Georg è un ventisettenne appassionato di Linux che ama l'hacking notturno e odia le scadenze.


[Tar] [Tar] [About] [Copertina] [indice] [indice]