Ziel: USB-Treiber



  • XanClic schrieb:

    Netzwerk ist ja auch was anderes und meiner Meinung nach (aus Erfahrung) um einiges einfacher.

    Schon mal einen TCP/IP Stack oder sowas wie PPP programmiert? 😃


  • Mod

    Ich habe mal in pci.c begonnen die wichtigsten Daten mittels der USB-Basis-Adresse heraus zu lesen, z.B. Adresse der Operational Registers (=Basis+CapLength) oder Vorhandensein von Extended Capabilities.

    Ich denke, dass hier die "Musik" des Host-Controllers spielt:

    OpRegs:

    Offset  Mnemonic           Register-Name 
    00h     USBCMD             USB Command 
    04h     USBSTS             USB Status 
    08h     USBINTR            USB Interrupt Enable 
    0Ch     FRINDEX            USB Frame Index 
    10h     CTRLDSSEGMENT      4G Segment Selector 
    14h     PERIODICLISTBASE   Frame List Base Address 
    18h     ASYNCLISTADDR      Current Asynchronous List Address Register 
    1C-3Fh                    (Reserved) 
    40h     CONFIGFLAG         Configure Flag Register  
    44h     PORTSC(1-N_PORTS)  Port Status/Control
    

    USB Command Register indicates the command to be executed by the serial bus host controller. Writing to the register causes a command to be executed.
    USB Status Register indicates pending interrupts and various states of the Host Controller. The status resulting from a transaction on the serial bus is not indicated in this register. Software sets a bit to 0 in this register by
    writing a 1 to it.
    USB Interrupt Enable Register enables and disables reporting of the corresponding interrupt to the software. When a bit is set and the corresponding interrupt is active, an interrupt is generated to the host. Interrupt sources that are disabled in this register still appear in the USBSTS to allow the software to poll for events.
    USB Frame Index Register is used by the host controller to index into the periodic frame list. The register updates every 125 microseconds (once each micro-frame). Bits [N:3] are used to select a particular entry in the Periodic
    Frame List during periodic schedule execution.
    Control Data Structure Segment Register allows the host software to locate all control data structures within the same 4 Gigabyte memory segment.
    Periodic Frame List Base Address Register contains the beginning address of the Periodic Frame List in the system memory.
    Current Asynchronous List Address Register contains the address of the next asynchronous queue head to be executed.
    Configure Flag Register is only reset by hardware when the auxiliary power is initially applied or in response to a host controller reset.
    Port Status and Control Register is only reset by hardware when the auxiliary power is initially applied or in response to a host controller reset.

    Sehe ich das richtig? 😕



  • Z schrieb:

    XanClic schrieb:

    Netzwerk ist ja auch was anderes und meiner Meinung nach (aus Erfahrung) um einiges einfacher.

    Schon mal einen TCP/IP Stack oder sowas wie PPP programmiert? 😃

    TCP/IP ja, für IRC und etwas HTTP sowie ein ls mit FTP hats auch gereicht. 😉

    Erhard Henkes schrieb:

    Sehe ich das richtig? 😕

    Ja und nein. Ich persönlich würde sagen, das wichtige spielt sich eher in der Frameliste und in den dort eingetragenen Deskriptoren ab (wenn EHCI einigermaßen ähnlich zu UHCI ist, wie ich gehört habe).


  • Mod

    Frameliste und in den dort eingetragenen Deskriptoren

    Ja, die "Data Structures" sind wichtig. Bin gerade an Kap. 3 u. 4 der EHCI-Spec dran, interessant, aber auch herausfordernd komplex. 🙂


  • Mod

    Enhanced Host Controller Interface Specification for Universal Serial Bus, March 12, 2002, Revision: 1.0, Kap. 4.1 "Host Controller Initialization":

    When the system boots, the host controller is enumerated, assigned a base address for the register space and BIOS sets the FLADJ register to a system-specific value. After initial power-on or HCReset (hardware or via HCReset bit in the USBCMD register), all of the operational registers will be at their default values. After a hardware reset, only the operational registers not contained in the Auxiliary power well will be at their default values.

    In order to initialize the host controller, software (Anm.: unser Treiber, den wir erstellen wollen) should perform the following steps:
    • Program the CTRLDSSEGMENT register with 4-Gigabyte segment where all of the interface data structures are allocated.
    • Write the appropriate value to the USBINTR register to enable the appropriate interrupts.
    • Write the base address of the Periodic Frame List to the PERIODICLIST BASE register. If there are no work items in the periodic schedule, all elements of the Periodic Frame List should have their T-Bits set to a one.
    • Write the USBCMD register to set the desired interrupt threshold, frame list size (if applicable) and turn the host controller ON via setting the Run/Stop bit.
    • Write a 1 to CONFIGFLAG register to route all ports to the EHCI controller

    Das dürfte doch der Anfang sein?

    Kann man damit die Funktionalität bereits prüfen? (z.B. USB-Stick einstecken / entfernen usw.?)

    At this point, the host controller is up and running and the port registers will begin reporting device connects, etc.


  • Mod

    Ich hab das mal versuchsweise - hoffentlich richtig - umgesetzt, damit man Register usw. testen kann, siehe: https://prettyos.svn.sourceforge.net/svnroot/prettyos/trunk/Source/kernel/ehci.c und https://prettyos.svn.sourceforge.net/svnroot/prettyos/trunk/Source/kernel/include/ehci.h


  • Mod

    Eine funktionierende Vorgehensweise: (Rev. 156)

    void initEHCIHostController()
    {
        // Program the CTRLDSSEGMENT register with 4-Gigabyte segment where all of the interface data structures are allocated.
        pOpRegs->CTRLDSSEGMENT = *((volatile uint32_t*)(opregs + 0x10)); // default
    
        // Write the appropriate value to the USBINTR register to enable the appropriate interrupts.
        // pOpRegs->USBINTR       = *((volatile uint32_t*)(opregs + 0x08)) = 0x3F; // 63 = 00111111b
        pOpRegs->USBINTR       = *((volatile uint32_t*)(opregs + 0x08)) = 0x1; /// TEST: Bit 0, USB Interrupt Enable
    
        // Write the base address of the Periodic Frame List to the PERIODICLIST BASE register.
        uint32_t virtualMemoryPERIODICLISTBASE = (uint32_t) malloc(0x1000,PAGESIZE);
        uint32_t physicalMemoryPERIODICLISTBASE = paging_get_phys_addr( kernel_pd, (void*)virtualMemoryPERIODICLISTBASE );
        pOpRegs->PERIODICLISTBASE = *((volatile uint32_t*)(opregs + 0x14)) = physicalMemoryPERIODICLISTBASE;
    
        // If there are no work items in the periodic schedule,
        // all elements of the Periodic Frame List should have their T-Bits set to 1.
        /// TODO: set T-Bits
    
        // Write the USBCMD register to set the desired interrupt threshold
        // and turn the host controller ON via setting the Run/Stop bit.
        // Software must not write a one to this field unless the host controller is in the Halted state
        // (i.e. HCHalted in the USBSTS register is a one). Doing so will yield undefined results.
        pOpRegs->USBSTS = (*((volatile uint32_t*)(opregs + 0x04)) |= (1<<12)   ); // set Bit 12
        pOpRegs->USBCMD = (*((volatile uint32_t*)(opregs + 0x00)) |= (0x8<<16) ); // Bits 23-16: 08h, means 8 micro-frames
        pOpRegs->USBCMD = (*((volatile uint32_t*)(opregs + 0x00)) |=  0x1      ); // set Start-Stop-Bit                                                           |=  0x1 );    // Run-Stop-Bit
    
        // Write a 1 to CONFIGFLAG register to route all ports to the EHCI controller
        pOpRegs->CONFIGFLAG    = *((volatile uint32_t*)(opregs + 0x40)) = 1;
    
        printformat("\n\nAfter Init of EHCI:");
        printformat("\nCTRLDSSEGMENT:              %X", *((volatile uint32_t*)(opregs + 0x10)) );
        printformat("\nUSBINTR:                    %X", *((volatile uint32_t*)(opregs + 0x08)) );
        printformat("\nPERIODICLISTBASE phys addr: %X", *((volatile uint32_t*)(opregs + 0x14)) );
        printformat("  virt addr: %X", virtualMemoryPERIODICLISTBASE);
        printformat("\nUSBCMD:                     %X", *((volatile uint32_t*)(opregs + 0x00)) );
        printformat("\nCONFIGFLAG:                 %X", *((volatile uint32_t*)(opregs + 0x40)) );
    }
    

    Setzt man alle 6 Bits in USBINTR, dann kommen keine Tastenanschläge mehr an. 😉 Mit Bit0 geht es. Rest muss getestet werden, ob man es setzen kann.


  • Mod

    Tests zeigen, dass man Bit3 im USBINTR zu Beginn nicht setzen darf:

    pOpRegs->USBINTR       = *((volatile uint32_t*)(opregs + 0x08)) = 0x1 | 0x2 | 0x4 | /*0x8 |*/ 0x10 | 0x20;
        /// TEST: Bit 3 cannot be set to 1, Frame List Rollover Enable, otherwise keyboard does not come through
    

    Den Rest darf man auf 1 setzen.



  • Ich habe jetzt zumindest genau die Stelle gefunden wo der Fehler auftritt. Das setzen der Adresse für die Periodic Frame List:

    uint32_t virtualMemoryPERIODICLISTBASE = (uint32_t) malloc(0x1000,PAGESIZE);
    uint32_t physicalMemoryPERIODICLISTBASE = paging_get_phys_addr( kernel_pd, (void*)virtualMemoryPERIODICLISTBASE );
    pOpRegs->PERIODICLISTBASE = *((volatile uint32_t*)(opregs + 0x14)) = physicalMemoryPERIODICLISTBASE;
    

    Kommentiert man die letzte Zeile aus hängt es nicht mehr. Da mir der Code komisch vorkommt habe ich den mal neu geschrieben und auch wie in der Spezifikation die T-Bits gesetzt, hängen tut es aber trotzdem noch.

    Mein Code:

    void* virtualMemoryPERIODICLISTBASE = malloc(0x1000,PAGESIZE);
    uint32_t physicalMemoryPERIODICLISTBASE = paging_get_phys_addr(kernel_pd, virtualMemoryPERIODICLISTBASE);
    pOpRegs->PERIODICLISTBASE = physicalMemoryPERIODICLISTBASE;
    memset(virtualMemoryPERIODICLISTBASE, 0xFF, 0x1000); // Alles auf 1 Setzen setzt auch das T-Bit auf 1
    

  • Mod

    Danke für die Unterstützung! Bei Memory-Adressen bevorzuge ich i.d.R. uint32_t, caste es nach void*, wenn notwendig. Hier macht void* aber wirklich mehr Sinn. Ins Register PERIODICLISTBASE muss die physikalische Adresse der Frame-Pointer.

    Es sind nur 1024 Elemente in der Pointer-Liste, also 0x400 nicht 0x1000.


  • Mod

    Tobiking: Ich kann das nicht nachvollziehen mit dem "Nichthängen". Leider hängt es immer noch.

    Hier die Rev. 162: http://www.c-plusplus.net/forum/viewtopic-var-p-is-1863106.html#1863106

    Versuche bitte nochmal die genaue Stelle zu finden und den Trick, wie man es "laufend" halten kann beim Einstecken von USB-Devices.

    Bei mir läuft es mit real PC und mit Sun VB (aber nur, wenn man nur funktionierende USB-Devices einhängt). Alles sehr eigenwillig.


  • Mod

    Mit rev. 168 sind wir dem ersten Meilenstein "HC Init" schon nahe. Bei mir läuft es gut auf dem Test-PC, in VB nicht sicher, bei Tobiking gibt es noch "Hänger".
    Bitte um Mithilfe.

    Die Interrupts müssen noch per installHandler und Handler verarbeitet werden. Bisher sind (Periodic)FrameListRollover und PortChangeDetect angefallen.



  • Hallo,

    ich hab noch mal nach Debug-Möglichkeiten für den USB recherchiert, vor allem auf der Embedded-World.

    Gefunden hab ich das http://www.lecroy.com/ProtocolAnalyzer/ProtocolStandard.aspx?standardID=4&capid=103, hier fangen die Preise erst oberhalb 1000$ an aber man bekommt auch wirklich ne tolle Rund-um-Sorglos-Lösung die einem wirklich jeden Aspekt des USB-Trafics analysieren lässt.
    Als einzige Alternative hab ich noch dieses http://www.totalphase.com/products/beagle_usb12/ gefunden, was zwar noch einen nicht total abschreckenden Preis hat aber eben auch nur bis Full-Speed kommt oder jenes http://www.totalphase.com/products/beagle_usb480/ was dann High-Speed bietet aber eben auch wieder die 1000$-Marke deutlich überschreitet. Die Software macht einen ähnlich guten Eindruck wie bei LeCroy aber die Hardware ist etwas bescheidener.

    Für die Bedürfnisse eines USB-Host-Treiber-Programmierers sind diese Analyse-Produkte sicher sehr interessant aber für ein Hobby-Projekt eben auch unbezahlbar. Ich persönlich bin der Meinung dass das Olimex-Board http://www.olimex.com/dev/lpc-h2888.html die beste Lösung (zu einem vertretbaren Preis) darstellt und zusätzlich noch etwas Wissen über die µC-Programmierung vermittelt (was für jemanden der ein OS bauen will, also auch auf der Low-Level-Ebene arbeitet, sicher von Vorteil ist). Andere Boards mit vergelichbarer Ausstattung und vernünftigem Preis (unter 150Euro) hab ich nicht gefunden. Keiner der üblichen Verdächtigen (welche alle auf der Messe präsent waren) konnte mir was passendes zeigen.
    Wer wirklich Geld investieren möchte der sollte sich IMHO für den Beagle-USB480 entscheiden, die Vorführung hat einen wirklich guten Eindruck gemacht. Außerdem hab ich noch einen 15%-Rabat-Coupon bekommen der allerdings nur ein paar Monate gilt.

    Grüße
    Erik


  • Mod

    Momentan fehlt uns eine zuverlässige gemeinsame Simulationsbasis. VBox erweist sich bei mir als störrisch, was die dauerhafte Anbindung der USB-Geräte an die Simulation angeht.

    EDIT: Tobiking und ich haben VMWare Player mit EHCI getestet, und das klappt hervorragend! 🙂
    Wer also bei EHCI mitmachen will, unbedingt VMWare Player installieren.



  • Hier der Code für das Prüfen der EECP (EHCI Extended Capabilities):

    uint32_t offset = (pCapRegs->HCCPARAMS & 0xFF00) >> 8;
    uint32_t* pointer = (uint32_t*)(ubar+offset);
    
    while(offset) {
        offset = (*pointer & 0xFF00) >> 8;
        printformat("Pointer: %X Value: %X Next: %X\n", pointer, *pointer, offset); 
        pointer = (uint32_t*)(ubar + offset);
    }
    

    Wenn ich keinen Fehler gemacht habe hilft das allerdings nicht weiter. VirtualBox besitzt demnach keine EECP und mein Laptop hat zwar zwei Listeneinträge, allerdings ist dort die Capability ID 0 was bedeutet das kein Handoff ?möglich/nötig? ist.



  • Hm, du versuchst, auf die EECP im Hauptspeicher zuzugreifen? Da sind aber keine, die sind im PCI-Konfigurationsraum. Bei mir sieht das Deaktivieren des Legacy Supports so aus:

    eecp = (hc->usbbase->hcc_params & 0x0000FF00) >> 8;
        if (eecp >= 0x40)
        {
            int eecp_id;
    
            while (eecp)
            {
                eecp_id = pci_inb(pci->bus, pci->dev, pci->func, eecp);
                // Legacy-Support-Zeugs
                if (eecp_id == 1)
                    break;
                eecp = pci_inb(pci->bus, pci->dev, pci->func, eecp + 0x01);
            }
    
            // Nachsehen, ob eine Legacy-Support-EC gefunden wurde und der BIOS-Semaphor gesetzt ist
            if ((eecp_id == 1) && pci_inb(pci->bus, pci->dev, pci->func, eecp + 0x02) & 0x01)
            {
                // OS-Semaphor setzen
                pci_outb(pci->bus, pci->dev, pci->func, eecp + 0x03, 0x01);
    
                failed = 1;
                // Eine Sekunde
                timeout = heart_beat_count + 1000000000LL;
                // Warten, bis der BIOS-Semaphor nicht mehr gesetzt ist
                while ((pci_inb(pci->bus, pci->dev, pci->func, eecp + 0x02) & 0x01) && (heart_beat_count < timeout));
                if (!(pci_inb(pci->bus, pci->dev, pci->func, eecp + 0x02) & 0x01))
                {
                    // Wieder eine Sekunde
                    timeout = heart_beat_count + 1000000000LL;
                    // Warten, bis der OS-Semaphor gesetzt ist
                    while (!(pci_inb(pci->bus, pci->dev, pci->func, eecp + 0x03) & 0x01) && (heart_beat_count < timeout));
                    if (pci_inb(pci->bus, pci->dev, pci->func, eecp + 0x03) & 0x01)
                        failed = 0;
                }
    
                if (failed)
                {
                    // Manuell den Legacy Support deaktivieren
                    pci_outl(pci->bus, pci->dev, pci->func, eecp + 0x04, 0x0000);
                }
            }
        }
    

    Hinweis: heart_beat_count ist die Zeitzählvariable meines OS. Sie ist vom Typ unsigned long long und zählt in Nanosekunden (eine Millisekunde ist also 1000000).


  • Mod

    Danke für den Versuch und den Code.

    Nach Diskussion mit XanClic im IRC gehört der Code offensichtlich hierhin:

    void initEHCIHostController(uint32_t number)
    {
        irq_install_handler(32 + pciDev_Array[number].irq, ehci_handler);
    
        /// TODO: with EECP: DeactivateLegacySupport(number)
    
        // ...
    
        /// TODO 
    
        pOpRegs->USBCMD &= ~CMD_RUN_STOP; // set Run-Stop-Bit to 0
    
        // ...
    }
    

    Aber gekapselt in eine Funktion "DeactivateLegacySupport(number)".


  • Mod

    Rev. 181:
    ...

    PC-Test bei Cuervo:

    jetzt kommt er bis zum 2. 'waiting for HC reset'



  • Hab mal wieder angefangenen nicht funktionierenden Code zum Datentransfer:

    ehci.c
    ehci.h

    Bei mir schaltet der Host Controller auf Halted. Ich hab versucht das ganze recht ausführlich zu Kommentieren, da ich teilweise echt suchen musste wie manche Werte gesetzt werden müssen.



  • Mein aktueller Stand:

    ehci.c

    Es fehlt noch nen ganzes Stückchen, aber man sieht wie es funktionieren soll. CreateQH/CreateQTD erstellt eine recht generische Version, die dann in den spezifischen Funktionen wie die IN und OUT Varianten weiter manipuliert werden. Durch die übergebene Adresse werden die Einträge direkt an der richtigen Stelle erstellt. Am Ende müssen diese nur noch zusammen gehängt werden. Fehlen tun auf jeden Fall noch ein paar Parameter und die spezifische Funktion für SETUP Einträge. Das mit dem SETUP ist allerdings wieder etwas komplexer.


Anmelden zum Antworten