PCI-Scan



  • +gjm+ schrieb:

    Allerdings hat ein 16-bit-Prozessor dieses Bit ignoriert.

    Sehr schlechtes Argument. Dann hätte man das Bit ja auch für etwas anderes nutzen können und eben bei 32-Bit-Prozessoren sagen müssen: "Tja, 16-Bit-PM funktioniert hier nicht mehr, Leute."
    Wenn man so denkt, kann man sich den ganzen Schmus mit A20-Gate und Real Mode sparen.

    +gjm+ schrieb:

    Meine "Ansicht" ist ja bekannt:

    Das sind aber keine Register. Das ist ein Adressraum, wie der I/O-Adressraum und der Speicheradressraum einer ist. Für dich, also für den Programmierer, sind das nur Bytes und keine Register.
    Siehe auch MMIO: Der Speicher besteht aus Bytes. Und alle Memory-Mapped-Register sind auch erstmal Bytes. Dass es Register sind, spielt beim Ansprechen keine Rolle.
    Die Frage ist auch, was du als Register bezeichnest. Sehen wir uns das "Register" 3 an (Offset 0x0C). Da sind vier Einzelbytes, die meiner Meinung nach nichts miteinander zu tun haben. Das wären also für mich vier Register.
    Wenn die Registernummer dem Offset mal 4 entspricht, dann müsste jedes dieser vier Register jeweils 32 Bits lang sein. Ich revidiere meine Meinung, wenn du mir glaubhaft vermitteln kannst, dass das ein Register und nicht vier sind. 😉


  • Mod

    Ist der Begriff "Register" bezüglich Bedeutung und Größe überhaupt sauber definiert? 😕

    Bezüglich der Größe in Bytes oder Bits ist da doch alles möglich?
    http://www.henkessoft.de/OS_Dev/Bilder/Register386.png



  • Erhard Henkes schrieb:

    Bezüglich der Größe in Bytes oder Bits ist da doch alles möglich?

    Klar, ich meinte nicht, dass jedes Register 32 Bits lang sein muss. Aber wenn man sagt, man übergibt eine Registernummer und liest ein DWord, dann muss jedes Register 32 Bits lang sein.
    Und ich meine nur, dass für mich ein Register zumindest eine logische Einheit sein muss. Klar ist ein Register wie das CR0-Register bei x86-Prozessoren eine Art Flickenteppich, aber an sich sind es alles wieder Bits, die den Prozessorzustand kontrollieren. Am Offset 0x0C im Konfigurationsadressraum hingegen habe ich ein Byte, mit dem ich einen Selbsttest durchführen kann, ein Byte, dass den Aufbau des Adressraums angibt, eines, das mir etwas über das Gerät sagt (Cache Line Size) und ein Byte, dessen Bedeutung ich nicht kenne. 😃 Und diese Bytes haben für mich keine gemeinsame Bedeutung (natürlich sind es im ganz Groben Bytes, die die Eigenschaften des Geräts anzeigen und seinen Zustand ändern können, aber das sind alle Register im Konfigurationsadressraum).



  • Erhard Henkes schrieb:

    Ist der Begriff "Register" bezüglich Bedeutung und Größe überhaupt sauber definiert?

    Der Begriff "Register" bezieht sich auf "alles", was in einer Hardwarekomponente u.a. als Speicher benutzt werden kann. Beispielsweise sind die "Register" im CMOS 8-bit breit.



  • Man braucht sich da gar nicht groß streiten, was denn nun ein Register ist, denn die PCI-Spezifikation benutzt dieses Wort selbst. Und die Register im Sinn der Spezifikation sind bei weitem nicht alle 32 Bit breit - die BARs sind es zum Beispiel, Vendor- und Device-ID sind 16 Bit und mit dem Class Code haben wir sogar ein lustiges 24-Bit-Register.



  • XanClic schrieb:

    Ich revidiere meine Meinung, wenn du mir glaubhaft vermitteln kannst, dass das ein Register und nicht vier sind. 😉

    Ich revidiere meine Meinung, wenn du mir zeigst, wie man bei einem PCI-Gerät die VendorID auslesen kann, ohne daß man mit einem "auf 4 ausgerichteten Offset" herumgurken muß. 🙂



  • Auf qemu kann ich dir das vermutlich ohne Probleme zeigen. 😃
    Aber wie taljeth sagt, sind die Register nicht alle 32 Bit breit. Er nennt zum Beispiel Vendor- und Device-ID, die jeweils ein 16-Bit-Register sind. Nach deiner Zählweise sind aber beide Register Nummer 0, also ein 32-Bit-Register.



  • Und man erhält die Vendor- und die DeviceID immer nur zusammen. Niemals getrennt. Darum kommt das mit einem Offset auch nicht hin. Im (32 bit breitem) Register Nr. 0 hat der Hardwarehersteller eben zwei Informationen abgelegt, die sich nur zusammen auslesen lassen.



  • Wie gesagt.

    XanClic schrieb:

    Ich revidiere meine Meinung, wenn du mir glaubhaft vermitteln kannst, dass das [DWord an Offset 0x0C] ein Register und nicht vier sind.

    😉

    EDIT: Ich möchte nachtragen, dass ich jetzt besser verstehe, was du meinst, auch wenn ich noch nicht genau der gleichen Meinung bin. 🙂



  • Du solltest Begriffe lieber so benutzen, wie sie definiert sind und wie sie der Rest der Welt benutzt, nicht so wie du es für logischer hältst.

    Du kannst CONFIG_ADDRESS nur auf einen 32-Bit-alignten Wert setzen, richtig. Du kannst die Register aber einzeln auslesen, indem du ein 16-Bit- oder 8-Bit-in auf CONFIG_DATA machst. Nur beim Class Code funktioniert das wieder nicht so gut, weil es kein 24-Bit-in gibt...



  • Um zum ursprünglichen Thema von wegen "& 0xFC" zurückzukommen... Linux tut es. Hierzu der Code zum Lesen eines Registers:

    #define PCI_CONF1_ADDRESS(bus, devfn, reg) \
            (0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
            | (devfn << 8) | (reg & 0xFC))
    
    static int pci_conf1_read(unsigned int seg, unsigned int bus,
                              unsigned int devfn, int reg, int len, u32 *value)
    {
            unsigned long flags;
    
            if ((bus > 255) || (devfn > 255) || (reg > 4095)) {
                    *value = -1;
                    return -EINVAL;
            }
    
            spin_lock_irqsave(&pci_config_lock, flags);
    
            outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
    
            switch (len) {
            case 1:
                    *value = inb(0xCFC + (reg & 3));
                    break;
            case 2:
                    *value = inw(0xCFC + (reg & 2));
                    break;
            case 4:
                    *value = inl(0xCFC);
                    break;
            }
    
            spin_unlock_irqrestore(&pci_config_lock, flags);
    
            return 0;
    }
    

    Mit diesem Code kann man, wie schon von taljeth angesprochen, auch 16- und 8-Bit-Register auslesen.



  • taljeth schrieb:

    Du solltest Begriffe lieber so benutzen, wie sie definiert sind und wie sie der Rest der Welt benutzt, nicht so wie du es für logischer hältst.

    Daran arbeite ich ja. 🙂

    Bits Nr.
    | Register | Offset | 31-24 |    23-16    |    15-8       |      7-0        |
    |   0x03   |  0x0C  | BIST  | Header Type | Latency Timer | Cache Line Size |
    

    CONFIG_ADDRESS ist auf Register 0x03 und/oder Offset 0x0C gesetzt. Was liefert nun ein 8-bit-IN (immer und ewig)?



  • XanClic schrieb:

    Um zum ursprünglichen Thema von wegen "& 0xFC" zurückzukommen... Linux tut es.

    tyndur tut es auch und Erhard hat doch sogar explizit gefragt, wie die Funktion funkioniert.

    +gjm+ schrieb:

    Bits Nr.
    | Register | Offset | 31-24 |    23-16    |    15-8       |      7-0        |
    |   0x03   |  0x0C  | BIST  | Header Type | Latency Timer | Cache Line Size |
    

    CONFIG_ADDRESS ist auf Register 0x03 und/oder Offset 0x0C gesetzt. Was liefert nun ein 8-bit-IN (immer und ewig)?

    Du verwirrst mich schon wieder mit deinen Begriffen. CONFIG_ADDRESS hat nur einen Wert, ich hoffe mal, dass 0x0c das ist, was du meinst. Ein Byte-read von CONFIG_DATA würde dann den Wert von Cache Line Size lesen. Eins von CONFIG_DATA + 2 würde den Subclass Code lesen.


  • Mod

    Bits Nr.
    | Register | Offset | 31-24 |    23-16    |    15-8       |      7-0        |
    |   0x03   |  0x0C  | BIST  | Header Type | Latency Timer | Cache Line Size |
    

    Wo finde ich das mit dem Register 0x03? Das habe ich bisher so nicht gesehen, nur immer register number == offset. Vermutlich liegt darin ein Teil der Missverständnisse bezüglich Offset & 0xFC und Register << 2 begraben.



  • Soweit ich das verstanden habe, ist das mit dieser Registernummer einfach nur eine Erfindung von +gjm+. Er versteht darunter Offset >> 2 und hält es offenbar aus irgendeinem Grund für sinnvoll, damit zu arbeiten, auch wenn keiner auf Anhieb versteht, was er meint.


  • Mod

    Erfindung von +gjm+

    @+gjm+: Ist das wirklich so, oder gibt es das auch von offizieller Seite? 😃

    Die Wissenschaft neigt auch mehr zum Offset:
    http://techwww.in.tu-clausthal.de/site/Projekte/PCI-Tutorial-Konfiguration//bilder/pci_config_header.gif

    Hier ist auch eine interessante Darstellung der letzten zwei Bits als "Type":
    http://www.martin-schreiber.net/data/pages/progref/bussysteme/pci/configaddress.gif

    00b = Konfigurationszyklus
    01b = Einheit ist hinter dieser Bridge. Diese kopiert den Inhalt von CONFIG_ADDRESS unverändert auf den Adress-/Datenbus
    10b = ?
    11b = ?


  • Mod

    Einschub nur zum Festhalten in diesem Thread bezüglich BARs und IRQ:
    https://ezs.kr.hsnr.de/lectures/treiber/html/x2161.html

    Base Address

    Bis zu 6 unterschiedliche Speicher- oder IO-Bereiche kann ein Gerät besitzen. Die Adressen dieser Bereiche werden in den Zellen Base Address 0 bis Base Address 5 abgelegt. In so einer Speicherzelle ist die Adresslage, die Art und auch die Größe des Speicherbereiches kodiert.

    Ein Speicherbereich hat eine Mindestgröße von 16 Byte und ist immer ein Vielfaches von 2. Dadurch stehen die 4 unteren Bits für zusätzliche Codierungen zur Verfügung. Bit 0 gibt Auskunft darüber, ob es sich um eine IO Adresse (der Zugriff muß über Port-Befehle erfolgen) oder eine Memory Adresse handelt. Handelt es sich um eine Memory Adresse, geben Bit 1 und 2 den Adresstyp an. 00 signalisiert, dass es sich um eine gewöhnliche 32 bit Adresse handelt. Die Kombination 01 bedeutet, dass die Adresse unterhalb der aus alten Zeiten bekannten 1 Megabyte Grenze liegt und die Kombination 10 wird für eine 64 bit Adresse verwednet. Bei einer 64 bit Adresse werden zwei „Base Address“ Einträge zur Adressbestimmung gebraucht.

    Bit 3 schließlich gibt Auskunft darüber, ob auf diesen Adressbereich im Prefetch-Mode zugegriffen werden darf oder nicht. Ein gesetztes Bit bedeutet dabei, dass nicht im Prefetch-Mode zugegriffen werden darf. Im Prefetch-Mode speichert die PCI-Bridge (d.h. der Baustein, der den PCI-Bus an den Processorbus ankoppelt) Daten zwischen, bevor die eigentliche Übertragung zur CPU (lesen oder schreiben) gestartet wird.

    Laut Spezifikation sind nur die Bereiche der Base Address auch schreibbar, die die Adresslage festlegen. Die übrigen Bits bleiben 0. Dieser Umstand wird genutzt, um die Größe eines Adressbereiches zu bestimmen. Dazu beschreibt man das Adressregister mit lauter Einsen und liest das Register wieder aus. Die Bits, die jetzt mit 0 belegt sind, identifizieren die Adressbereichgröße.

    Das folgende Codefragment zeigt, wie die Größe des Speicherbereiches bestimmt werden kann:

    pci_read_config_dword( PciDev, PCI_BASE_ADDRESS_0, &OrginalValue );
            cli();
            pci_write_config_dword( PciDev, PCI_BASE_ADDRESS_0, 0xffffffff );
            pci_read_config_dword( PciDev, PCI_BASE_ADDRESS_0, &Base );
            pci_write_config_dword( PciDev, PCI_BASE_ADDRESS_0, OrginalValue );
            sti();
            printk("size of base address 0: %i\n", ~Base+1 );
    

    IRQ-Line

    In dieser Speicherzelle findet sich der Interrupt, der dem Board vom PCI-Bios zugeteilt wurde.

    Hauptseite, die mir bezüglich Treiber wirklich interessant erscheint:
    https://ezs.kr.hsnr.de/lectures/treiber/html/book1.html



  • Na gut. Letzter Versuch. Mit dem von XanClic geposteten Code kann man gezielt ein Byte aus dem "Config-Space" auslesen:

    case 1:
     *value = inb(0xCFC + (reg & 3));
      break;
    

    Aber dazu muß u.U. die Portadresse geändert werden. Das ist nur typisch für den Zugriff auf (Hardware) Register. 🙂

    P.S.: Im Internet werden Falschmeldungen zu Tatsachen einzig und alleine durch Verbreitung.


  • Mod

    Bei mir sieht das nun praktisch wie folgt aus:

    #include "pci.h"
    
    static uint32_t pci_config_read(uint32_t bus, uint32_t device, uint32_t func, uint32_t reg)
    {
        outportl(PCI_CONFIGURATION_ADDRESS,
            0x80000000
            | (bus    << 16)
            | (device << 11)
            | (func   <<  8)
            | (reg & 0xFC  )); // Real hardware ignores bits 0 and 1, and reads only bits 7-2.
                               // The bit mask FCh = 11111100b ensures register numbers aligned with 4.
    
        return inportl(PCI_CONFIGURATION_DATA);
    }
    
    uint32_t pci_config_read_32 (uint32_t bus, uint32_t device, uint32_t func, uint32_t reg)
    {
        return pci_config_read(bus, device, func, reg);
    }
    
    uint16_t pci_config_read_16 (uint32_t bus, uint32_t device, uint32_t func, uint32_t reg)
    {
        return pci_config_read(bus, device, func, reg) & 0xFFFF;
    }
    
    uint8_t pci_config_read_8 (uint32_t bus, uint32_t device, uint32_t func, uint32_t reg)
    {
        return pci_config_read(bus, device, func, reg) & 0xFF;
    }
    
    void pci_config_write(uint32_t bus, uint32_t device, uint32_t func, uint32_t reg, uint32_t val)
    {
        outportl(PCI_CONFIGURATION_ADDRESS,
            0x80000000
            | (bus     << 16)
            | (device  << 11)
            | (func    <<  8)
            | (reg & 0xFC   ));
    
        outportl(PCI_CONFIGURATION_DATA, val);
    }
    

    Die write-Funktion benötigen für die BARs (siehe oben).

    Bei tyndur ist der Schutz übrigens mit ... &=~11b und ... &11111100b m.E. übertrieben:

    static dword pci_config_read(..., dword reg)
    {
        ...
        reg &= ~0x3;
        outl(... | ((reg & 0xFC)));
        ...
    }
    

    Ich würde vorschlagen, eine von beiden Schutzmasken zu entfernen. Dann läuft der PCI Scan deutlich schneller. 😉



  • Deine pci_config_read_16 und pci_config_read_8 können übrigens keine Werte, die nicht 4 Byte aligned sind, lesen. Nicht dass du dich z.B. irgendwann wunderst, dass du damit nicht die Device ID nicht auslesen kannst.


Anmelden zum Antworten