Auslesen des PCI Config Space



  • Hallo,

    ich möchte ein paar Daten über den PCI Bus und die angeschlossenen Geräte auslesen.

    Im PM bin ich bereits.
    Nach meinem Buch braucht man "nur" das Adressregister (0xCF8) zu beschreiben und
    anschließend das Datenregister (0xCFC) auszulesen.

    Der Aufbau des Adressregisters ist wie folgt:

    31:			Enable configuration space mapping
    30:24		reserviert
    23:16		Busnummer
    15:11		Gerätenummer
    10:8		Funktion
    7:2			Konfigurationsregisternummer
    1:0			reserviert
    

    Was erhalte ich denn im Datenregister wenn ich es auslese?
    Steht dort die Adresse an der ich die 256Byte vorfinde, die mir sagen ob und wenn
    ja, was am BUS hängt?

    Ich kann nur prüfen ob überhaupt ein Gerät am BUS hängt wenn ich die Vendor-ID
    auslese. Aber die muss ich ja erstmal bekommen.

    Hat jemand einen Tipp für mich? 😕

    Gruß, Nicky



  • In das Adressregister schreibst Du, welches Register welchen Gerätes (bus, dev, func) Du auslesen willst. Über das Datenregister bekommst Du dann den Wert des Registers zurück, keinen Speicherbereich.

    Ein Beispiel findest Du in PrettyOS in der Datei pci.c, Funktion pci_config_read().


  • Mod

    Hallo supernicky,
    wenn Du Unterlagen benötigst über die Ansteuerung der Hardware, findest du hier entsprechende Erklärungen und Daten: http://henkessoft.de/OS_Dev/OSDEV Ressourcen.html

    Hat es geklappt mit dem PCI-Bus?



  • Hallo Erhard,
    Es geht langsam voran...
    ich habe noch ein Problem beim auslesen des Datenregisters wenn das Funktionsregister größer NULL ist.
    Dazu habe ich ein Beispiel aus dem Buch von Benjamin Lunt.

    #define  PCI_ADDR     
    0x0CF8#define  PCI_DATA     0x0CFC
    
    bit32u read_pci(bit8u bus, bit8u dev, bit8u func, bit8u port, bit8u len) {  
    
    bit32u ret;  
    
    const bit32u val = 0x80000000 |    
    (bus << 16) |    
    (dev << 11) |    
    (func << 8) |    
    (port & 0xFC);  
    outpd(PCI_ADDR, val);  
    ret = inpd(PCI_DATA+(port & 0x3));  
    ret &= (0xFFFFFFFF >> ((4-len) * 8));  
    
    return ret;}
    

    Der Aufruf sieht in seiner Schleife wie folgt aus:

    for (i=0; i<64; i++)            
    pcidata[i] = read_pci(pci_bus, pci_dev, pci_func, (i<<2),sizeof(bit32u));
    

    Mein Problem ist der Parameter "port" (Bytewert).
    2x shift links löscht gleichzeitig Bit 0 und 1.

    In Zeile 12 wird port mit 0xFC verknüpft was nochmals Bit 0 und 1 löscht(zur Sicherheit?)
    In Zeile 14 wird port jedoch nochmal mit 0x3 AND verknüpft was diesmal Bit 7bis 2 löschtSomit ist der Wert von Port immer NULL oder nicht?

    Ich verstehe an der folgenden Zeile nicht welcher Wert zum Adressregister 0xCFC addiert werden soll..

    ret = inpd(PCI_DATA+(port & 0x3));
    

    Das Auslesen der Vendor ID funktioniert immer, da der Wert von port hier ja immer NULL ist.

    Ich bitte wie immer um Erleuchtung

    Gruß, Nicky


  • Mod

    Hast Du Dir schon unsere Funktion in PrettyOS angeschaut?

    uint32_t pci_config_read(uint8_t bus, uint8_t device, uint8_t func, uint8_t reg_off, uint8_t length)
    {
        uint8_t reg    = reg_off & 0xFC;     // bit mask: 11111100b
        uint8_t offset = reg_off % 0x04;     // remainder of modulo operation provides offset
    
        outportl(PCI_CONFIGURATION_ADDRESS,
            0x80000000
            | (bus    << 16)
            | (device << 11)
            | (func   <<  8)
            |  reg);
    
        // use offset to find searched content
        uint32_t readVal = inportl(PCI_CONFIGURATION_DATA) >> (8 * offset);
    
        switch (length)
        {
            case 1:
                readVal &= 0x000000FF;
                break;
            case 2:
                readVal &= 0x0000FFFF;
                break;
            case 4:
                readVal &= 0xFFFFFFFF;
                break;
        }
        return readVal;
    }
    

    Wichtig sind die Konstanten in pci.h:

    #define PCI_CONFIGURATION_ADDRESS 0x0CF8   // Address I/O Port
    #define PCI_CONFIGURATION_DATA    0x0CFC   // Data    I/O Port
    
    #define PCI_VENDOR_ID   0x00 // length: 0x02 reg: 0x00 offset: 0x00
    #define PCI_DEVICE_ID   0x02 // length: 0x02 reg: 0x00 offset: 0x02
    #define PCI_COMMAND     0x04
    #define PCI_STATUS      0x06
    #define PCI_REVISION    0x08
    #define PCI_CLASS       0x0B
    #define PCI_SUBCLASS    0x0A
    #define PCI_INTERFACE   0x09
    #define PCI_HEADERTYPE  0x0E
    #define PCI_BAR0        0x10
    #define PCI_BAR1        0x14
    #define PCI_BAR2        0x18
    #define PCI_BAR3        0x1C
    #define PCI_BAR4        0x20
    #define PCI_BAR5        0x24
    #define PCI_CAPLIST     0x34
    #define PCI_IRQLINE     0x3C
    

    Anwendungsbeispiele findest Du in pci.c:

    PCIdev->deviceID    = pci_config_read(bus, device, func, PCI_DEVICE_ID, 2);
    PCIdev->classID     = pci_config_read(bus, device, func, PCI_CLASS,     1);
    PCIdev->subclassID  = pci_config_read(bus, device, func, PCI_SUBCLASS,  1);
    PCIdev->interfaceID = pci_config_read(bus, device, func, PCI_INTERFACE, 1);
    PCIdev->revID       = pci_config_read(bus, device, func, PCI_REVISION,  1);
    PCIdev->irq         = pci_config_read(bus, device, func, PCI_IRQLINE,   1);
    

    Die Port-Funktionen findest Du in util.h:

    static inline uint32_t inportl(uint16_t port)
    {
        uint32_t ret_val;
        __asm__ volatile ("inl %1, %0" : "=a" (ret_val) : "d"(port));
        return ret_val;
    }
    
    static inline void outportl(uint16_t port, uint32_t val)
    {
        __asm__ volatile ("outl %0, %1" : : "a"(val), "d"(port));
    }
    

    Durch Kombination von register und offset können wir als Konstante einen Wert übergeben, den wir in der Funktion zerlegen.

    uint8_t reg    = reg_off & 0xFC;     // bit mask: 11111100b
    uint8_t offset = reg_off % 0x04;     // remainder of modulo operation provides offset
    

    Ich denke, unsere pci_config_read ist selbsterklärend strukturiert:
    Wir setzen bus/device/func/reg,
    wir suchen mit dem Offset in dem erhaltenen Wert durch entsprechenden Rechtsshift,
    wir setzen eine Längenmaske, damit wir keinen Blödsinn aus Nachbarfeldern erhalten.



  • Hallo nochmal,

    Leider komme ich immer noch zu keinem zufriedenstellenden Ergebnis.
    Ich weiß aus einem anderen Programm, das die Vendor ID 0x8086 sein sollte.

    Mein Programm findet vier von diesen Einträgen, jedoch keinen Eintrag mit
    Class Code: 0xC und Sub Class: 0x3

    Ich stelle mal meinen Programmcode rein, vielleicht kann hier noch jemand helfen.
    Hier das "Hauptprogramm" geschrieben in NASM, ausgeführt im P-Mode (32Bit)

    call clearscreen		;Bildschirm löschen
    			mov ecx, 300000d		;Schleifenzähler
    
    .einlesen:		
    		push ecx
    			;B = Bus, D = Device, R = Register und F = Funktion
    			;Startadresse ausgeben B/D/R/F alles 0
    			mov eax, [adresse]
    			mov edx, [adressport]
    			out dx, eax			;Aktiviere PCI BUS mit B/D/R/F = 0
    
    			mov edx, [datenport]
    			in eax, dx				;Register NULL einlesen für Vendor ID
    			cmp ax, 0xFFFF		;Wenn AX = FFFF dann kein Gerät
    			je .nextdevice
    
    			mov edi, pci_conf		;EDI auf Anfang der PCI Struktur
    			stosd				;DWORD schreiben
    
    			call show_pci			;Struktur füllen
    
    .nextdevice:	
    			call next_device		;Nächstes Gerät
    			call clear_pciport		;Konfigurations-Register-Nummer auf NULL
    		pop ecx
    			loop .einlesen
    

    pci_conf ist eine einfache Struktur mit einer Groesse von 256 Byte
    Hier ein Auszug vom Aufbau:

    pci_conf:
    vendor dw 0
    device dw 0
    command dw 0
    status dw 0
    rev db 0
    interface db 0
    subclass db 0
    classcode db 0
    cacheline db 0
    latency db 0
    header db 0
    selftest db 0
    base_0 dd 0
    base_1 dd 0
    base_2 dd 0
    base_3 dd 0
    base_4 dd 0
    base_5 dd 0
    cardbus dd 0
    sub_vendor dw 0
    subsystem dw 0
    rombase dd 0
    c_list db 0
    res_1 db 0,0,0
    res_2 dd 0
    interrupt_line db 0
    interrupt_pin db 0
    

    Nun kommt glaube ich, die wichtigste Funktion.
    show_pci
    Diese durchläuft alle Konfigurationeregister (0 - 63) und schreibt alle DWORD'S
    nacheinander in die Struktur pci_conf

    show_pci:
    
    			call clear_pciport		;Register Number löschen
    								;11111111_11111111_11111111_xxxxxx11
    								;x = Registernummer
    	pushad
    			push dword [adresse]	;Adresse z.B. 0x80003B00 auf Stack
    			call itoh				;umwandeln nach Hexadezimal
    			push eax				;Beginn der Hexzahl auf Stack
    			call prints				;Adresse auf Schirm ausgeben!
    			call crlf				;Zeilenumbruch einfügen
    
    			mov edi, pci_conf		;EDI zeigt auf die Struktur
    
    			mov ecx, 63d			;Zähler mit 63 initialisieren
    
    .eintragen:
    			mov edx, [adressport]	;Adresse mit gefundener Vendor ID
    			mov eax, [adresse]		;in Adressregister schreiben
    			out dx, eax
    
    			mov edx, [datenport]		;Datenport einlesen
    			in eax, dx
    
    			stosd				;4 Byte aus Register schreiben in die Struktur
    
    			call set_next_pciport		;Nächste Register Nummer
    
    			loop .eintragen
    

    Der Code ist nicht soviel. Ich hoffe es kann mir jemand sagen ob ich etwas
    vergessen habe.
    Falls noch was fehlt einfach bescheid geben.. ich ergänze dann entsprechend.

    Hier mal ein Auszug von meinem Bildschirm was mein Programm zeigt:

    0x80000000
    Vendor: 0x8086 SubC_Code: 0 C_Code: 6 Base: 0x0 IRQ: 0
    
    0x80003800
    Vendor: 0x8086 SubC_Code: 1 C_Code: 6 Base: 0x0 IRQ: 0
    
    0x80003900
    Vendor: 0x8086 SubC_Code: 1 C_Code: 1 Base: 0x0 IRQ: 0
    
    0x80003A00
    Vendor: 0x8086 SubC_Code: 0 C_Code: 0 Base: 0x0 IRQ: 4
    
    0x80003B00
    Vendor: 0x8086 SubC_Code: 128 C_Code: 6 Base: 0x0 IRQ: 1
    
    0x80004000
    Vendor: 0x5333 SubC_Code: 0 C_Code: 3 Base: 0xF8000000 IRQ: 0
    
    0x80005000
    Vendor: 0x1011 SubC_Code: 0 C_Code: 2 Base: 0xEC01 IRQ: 1
    

    Leider nichts brauchbares dabei... 😕

    Nicky



  • Aber so richtig unplausibel ist die Bildschirmausgabe ja nicht. Zumindest ist nicht alles völlig blödsinnig, 0x8086 als Vendor bekommst Du ja. Ich würde den Fehler an der Stelle vermuten, wo Du die anderen Werte liest.



  • Hallo Mr X,

    ich habe mein Programm mal auf dem Laptop starten lassen.

    Hier werden mir wohl alle Geräte mit Sub Class 12 (0xC) und Class Code 0x3 angezeigt.

    Insgesamt vier Geräte. 3x UHCI und 1x xHCI
    UHCI mit einer 16 Bit Base Adresse (0x1821; 0x1841 und 0x1861) und
    xHCI mit einer 32 Bit Base Adresse (0xF6504000)

    Kann das vielleicht nur an Virtual PC liegen? 😕
    Jedes mal das Programm auf Disk ziehen und umstöpseln ist nicht gerade
    hilfreich und ziemlich zeitaufwändig 🙄

    Aber es geht schonmal... 👍

    Nicky



  • Virtual PC gibt die Hardware deines PCs nicht wieder, sondern hat seine eigene, die er emuliert.



  • Sollten aber nicht gerade deshalb die Geräte auf gleiche Weise
    zu finden sein wie auf echter Hardware?



  • Nein, eben deshalb ist es andere Hardware (nämlich immer die gleiche, je nach Emulator). Hier ist eine Übersicht über die Hardware von VPC: http://www.lowlevel.eu/wiki/Microsoft_Virtual_PC



  • Hallo,

    das Auslesen klappt nun sehr gut. 👍

    Nach einigen Test mit Virtual PC und Virtual Box habe ich herausgefunden, das
    die Basisadresse beim Linear Frame Buffer meiner Grafikkarte bei

    VPC : 0xF8000000
    VBOX: 0xE0000008

    liegt. Diesen Wert finde ich auch im Register Basis Adresse der VGA Karte im PCI Bus.
    Ich möchte dort gern den Wert 0x1000000 (16MB) schreiben, da ich nicht immer mit 4GB Ram ausgestattet bin 😞

    Nach dem Schreiben erhalte ich jedoch beim nochmaligen Auslesen des Registers immer den Wert 0x0.

    Nach meine Lektüre ist es durchaus möglich andere Werte einzutragen.

    Ist es bei Grafikkarten vielleicht nicht "vorgesehen" einen anderen Wert einzutragen?

    Hat jemand einen Tipp für mich?

    Gruß, Nicky



  • Ich möchte dort gern den Wert 0x1000000 (16MB) schreiben, da ich nicht immer mit 4GB Ram ausgestattet bin

    Auch wenn Du nur ein Byte RAM hättest - der virtuelle Adressraum ist 4 GiB groß beim x86. Mit Paging stellt sich die Frage daher eigentlich nicht.



  • Soll ich mit Paging den Bereich umleiten?



  • Du kannst ihn umleiten, musst Du aber nicht. Wie gesagt, der Adressraum ist hinreichend groß und der Speicher, den Du hier beschreibst, ist der der VGA. Der ist bloß in den normalen Adressraum gemappt.


  • Mod

    Unterscheide zwischen dem physischen und dem virtuellen Adressraum.



  • Hätte ich das mal eher gewußt...
    Ich bin immer davon ausgegangen das man physisch auch den Speicher haben muss den man ansprechen will.

    Bin jetzt jedenfalls am schreiben der Ausgaberoutine im 256 Farbmodus.

    Danke nochmal 👍


Anmelden zum Antworten