AHCI Port COMRESET ( & How to handle PortConnectChange INT)

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

AHCI Port COMRESET ( & How to handle PortConnectChange INT)

Post by devc1 »

Hello. I was struggling with my AHCI Driver, I made it work on QEMU and VBOX But on my 2 computers, the Port (COM)Reset is stuck on PORT_CONNECT_STATUS_CHANGE Interrupt and no D2H Is sent.

So how can I handle PORT_CONNECT_STATUS_CHANGE Event And how to do a COMRESET.

The interrupt handling works well, I can ready data from Real Hardware if I do not COMRESET (But I cannot know which of the ports is ATAPI and the ATAPI Port on my laptop doesn't respond (I already know it is ATAPI))

Here is the code of the COMRESET :

Code: Select all

HbaPort->CommandStatus.Start = 0;
    HbaPort->CommandStatus.FisReceiveEnable = 0;
    while(HbaPort->CommandStatus.CurrentCommandSlot || HbaPort->CommandStatus.FisReceiveRunning);
 
    Port->FirstD2h = 0;

    // Some code allocating Received FIS & Command List ...

Port->Port->InterruptEnable.D2HRegisterFisInterruptEnable = 1;
    Port->Port->InterruptEnable.PioSetupFisInterruptEnable = 1;
    Port->Port->InterruptEnable.DmaSetupFisInterruptEnable = 1;
    Port->Port->InterruptEnable.SetDeviceBitsFisInterruptEnable = 1;
    Port->Port->InterruptEnable.UnknownFisInterruptEnable = 1;
    Port->Port->InterruptEnable.DescriptorProcessedInterruptEnable = 1;
    Port->Port->InterruptEnable.PortChangeInterruptEnable = 1;
    Port->Port->InterruptEnable.DeviceMechanicalPresenceEnable = 1;
    Port->Port->InterruptEnable.PhyRdyChangeInterruptEnable = 1;
    Port->Port->InterruptEnable.IncorrectPortMultiplierEnable = 1;
    Port->Port->InterruptEnable.OverflowEnable = 1;
    Port->Port->InterruptEnable.InterfaceNonFatalErrorEnable = 1;
    Port->Port->InterruptEnable.InterfaceFatalErrorEnable = 1;
    Port->Port->InterruptEnable.HostBusDataErrorEnable = 1;
    Port->Port->InterruptEnable.HostBusFatalErrorEnable = 1;
    Port->Port->InterruptEnable.TaskFileErrorEnable = 1;
    Port->Port->InterruptEnable.ColdPresenceDetectEnable = 1;

    wr32(&Port->Port->SataError, -1); // Static function I made
    Port->Port->CommandStatus.FisReceiveEnable = 1;
    while(!Port->Port->CommandStatus.FisReceiveRunning);

    BOOL DeviceDetected = 0;
    if(Port->Ahci->Hba->HostCapabilities.SupportsStaggeredSpinup) {
        Port->Port->CommandStatus.SpinupDevice = 1;
    }
        Port->Port->SataControl.DeviceDetectionInitialization = 1;
        Sleep(3);
        Port->Port->SataControl.DeviceDetectionInitialization = 0;

    UINT Countdown = 20;
    for(;;) {
        // if(Port->FirstD2h && !HbaPort->SataStatus.DeviceDetection) break;
        if(!Countdown) break;
        Countdown--; 
        if(HbaPort->SataStatus.DeviceDetection == 3) {
            if(Port->Ahci->Hba->HostCapabilities.SupportsStaggeredSpinup) {
            }
            DeviceDetected = 1;
            break;
        }
        Sleep(1);
    }




    if(!DeviceDetected) {
        if(Port->Ahci->Hba->HostCapabilities.SupportsStaggeredSpinup) {
            Port->Port->CommandStatus.SpinupDevice = 0;
        }
        Port->DeviceDetected = 0;
        return;
    } else {
        Port->DeviceDetected = 1;
        // SystemDebugPrint(L"ATTACHED_DEVICE_ON_PORT");
    }
     // wait for first D2H FIS (Containing device signature)
    Countdown = 25;
    while(!Port->FirstD2h) {
        Countdown--;
        if(!Countdown) {
            if(Port->Port->PortSignature.SectorCount == 0x101) break; // Port signnature received but no interrupt delivered
            Port->DeviceDetected = 0;
            return;
        }
        Sleep(1);
    }



    *(DWORD*)&Port->Port->SataError = -1; // Clear SATA_ERROR

    if(*(DWORD*)&HbaPort->PortSignature == 0xEB140101) {
        HbaPort->CommandStatus.DeviceIsAtapi = 1;
    }


    // Wait for device initialization (transfer of initial FIS & Device Signature...)
    
    SystemDebugPrint(L"Port Signature : %x", HbaPort->PortSignature);

    Port->Port->CommandStatus.FisReceiveEnable = 1;
    Port->Port->CommandStatus.Start = 1;


Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by Octocontrabass »

devc1 wrote:Hello. I was struggling with my AHCI Driver, I made it work on QEMU and VBOX But on my 2 computers, the Port (COM)Reset is stuck on PORT_CONNECT_STATUS_CHANGE Interrupt and no D2H Is sent.
Have you performed all of the initialization steps as listed in section 10.1.2 of the AHCI specification? Does your interrupt handler acknowledge the interrupt by clearing PxSERR.DIAG.X?
devc1 wrote:

Code: Select all

HbaPort->CommandStatus.Start = 0;
    HbaPort->CommandStatus.FisReceiveEnable = 0;
    while(HbaPort->CommandStatus.CurrentCommandSlot || HbaPort->CommandStatus.FisReceiveRunning);
There are two problems with this code. The first is that you're reading PxCMD.CCS instead of PxCMD.CR to determine whether the port is running. The second is that you must ensure the port has stopped before you clear PxCMD.FRE instead of clearing PxCMD.FRE at the same time you clear PxCMD.ST. (See section 10.3 of the AHCI specification for details.)
devc1 wrote:

Code: Select all

Port->Port->InterruptEnable.D2HRegisterFisInterruptEnable = 1;
    Port->Port->InterruptEnable.PioSetupFisInterruptEnable = 1;
    Port->Port->InterruptEnable.DmaSetupFisInterruptEnable = 1;
    Port->Port->InterruptEnable.SetDeviceBitsFisInterruptEnable = 1;
    Port->Port->InterruptEnable.UnknownFisInterruptEnable = 1;
    Port->Port->InterruptEnable.DescriptorProcessedInterruptEnable = 1;
    Port->Port->InterruptEnable.PortChangeInterruptEnable = 1;
    Port->Port->InterruptEnable.DeviceMechanicalPresenceEnable = 1;
    Port->Port->InterruptEnable.PhyRdyChangeInterruptEnable = 1;
    Port->Port->InterruptEnable.IncorrectPortMultiplierEnable = 1;
    Port->Port->InterruptEnable.OverflowEnable = 1;
    Port->Port->InterruptEnable.InterfaceNonFatalErrorEnable = 1;
    Port->Port->InterruptEnable.InterfaceFatalErrorEnable = 1;
    Port->Port->InterruptEnable.HostBusDataErrorEnable = 1;
    Port->Port->InterruptEnable.HostBusFatalErrorEnable = 1;
    Port->Port->InterruptEnable.TaskFileErrorEnable = 1;
    Port->Port->InterruptEnable.ColdPresenceDetectEnable = 1;
GCC does not recommend using volatile bitfields for MMIO. If nothing else, it makes your code much bigger and slower by turning each of those 17 assignments into a separate read/modify/write.
devc1 wrote:

Code: Select all

    wr32(&Port->Port->SataError, -1); // Static function I made
Why do you need a separate function for this?
devc1 wrote:

Code: Select all

    *(DWORD*)&Port->Port->SataError = -1; // Clear SATA_ERROR
Type punning by casts is undefined behavior, and this cast discards the volatile qualifier.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by devc1 »

Well I managed to get this work on real hardware, by setting Interface Communication control, Spinup device and DevicePowerOn (if staggered spinup is supported) and I declare that the fis has been sent after PhyRdy change interrupt and wait for busy, I know this is not the right implementation and Thanks also for the things you have mentionned I think this is why I do not get D2H Interrupt on real hardware on reset.

I reset the HBA And take AHCI Ownership according to specification, then I setup and reset ports and I do a test read from devices, currently I do not know how to IDENTIFY_DEVICE and how PIO works.

Secondly I am using MSVC with optimizations disabled and dword aligned bitfields and I have no problem with other drivers, BUT what u said is true writing registers Bit-Per'Bit really slows done the driver.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by devc1 »

Well it's still does not send a First D2H Interrupt
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by Octocontrabass »

According to the AHCI spec, the HBA will raise the D2H register FIS received interrupt when the FIS is copied into memory. Since you're waiting for the interrupt before you've enabled the DMA engines, the HBA can't copy the FIS into memory, and you won't receive the interrupt.

Looking at the spec, I'm not sure you'll ever receive a D2H register FIS interrupt for the first D2H register FIS - you might need to use a different interrupt to detect when the drive is ready to communicate after a reset.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by devc1 »

What is this DMA engines, are u talking about CMDxFRE. I used PhyRdy change interrupt and wait for the Port Busy & DRQ as an alternative to D2H interrupt on real hardware and it is really working without problems. Then I check port signature and set CMDxATAPI if it has ATAPI Signature, after that I just enable Start and Fis receive on port.

I use interrupts and command queuing, everything works fine until the last (asynchronous) command which I do not get an interrupt for it (muti-processor-ing), is there anyway to tell the hba that I received only this Command Issue bits, I doesn't wanna implement an IPC server because I can have a great deal in performace with synchronized instructions (and muti-processor-ing). And I do not have a clue what does PORTxSATA_ACTIVE, this also minimises work on CPU cause interrupts with task scheduling will optimize cpu work by putting waiting threads on an IO_WAIT status and not running them.
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by xeyes »

I always poll during the initialization and ID phase. Many things can go wrong and IMO it's not worth it to get stuck waiting for an interrupt that won't come, in order to shave 10 ms off the total boot time.

Tried these just out of curiosity though:

On Qemu the card sets D2H bit in the interrupt state register.

On hardware, seems that the card doesn't set any bit there even after seeing the correct 0x101 signature.

@Octocontrabass have a related question about the reset sequence, AHCI spec 1.3 section 10.1.1 step 7 says:
If PxTFD.STS.BSY, PxTFD.STS.DRQ, and PxTFD.STS.ERR are all ‘0’,
prior to the maximum allowed time as specified in the ATA/ATAPI-7 specification, the
device is ready.
but I can't find anything about this in the ATA-ATAPI-7 or 8 draft. Any idea on what the timeout should be here?
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by Octocontrabass »

xeyes wrote:On Qemu the card sets D2H bit in the interrupt state register.

On hardware, seems that the card doesn't set any bit there even after seeing the correct 0x101 signature.
This might be a QEMU bug. I haven't checked earlier versions of the AHCI specification, but 1.3 says the D2H interrupt is used only to indicate a D2H register FIS has been written to memory, and the state machine in the spec doesn't seem to allow a D2H register FIS to both update the signature register and be written to memory.
xeyes wrote:Any idea on what the timeout should be here?
All I can find is clause 11.1, which says a single parallel device must clear BSY within 5 seconds, and clause 13.3, which suggests that serial devices must adhere to that timing to maintain compatibility with parallel devices. I would take that to mean the timeout must be at least 5 seconds.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by devc1 »

Great, so now I'm not making anything wrong but, How to report that I only received some specific bits in Command Issue, do I need a mutex if it is multi processor. And what do PORTxSATA_ACTIVE.

Another question, how PIO works (with AHCI). And how to send an IDENTIFY_DEVICE with PIO and how to get data from the IO Ports.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by Octocontrabass »

devc1 wrote:Great, so now I'm not making anything wrong but, How to report that I only received some specific bits in Command Issue, do I need a mutex if it is multi processor.
I don't understand this question.
devc1 wrote:And what do PORTxSATA_ACTIVE.
Do you mean PxSACT? That's the register that tracks queued command completion. Before you write to PxCI to issue a NCQ command, you write the same value to PxSACT to tell the HBA you're going to issue a queued command. When you receive an IRQ for command completion after issuing NCQ commands, you read PxSACT to see which queued commands have completed instead of reading PxCI.

Keep in mind you may not be able to use all 32 bits of PxSACT, depending on the HBA and drive capabilities.
devc1 wrote:Another question, how PIO works (with AHCI).
PIO works the same as DMA, but there are some limitations: the HBA may not support more than one DRQ block per command, and DRQ blocks can't be bigger than 8kB.
devc1 wrote:And how to send an IDENTIFY_DEVICE with PIO and how to get data from the IO Ports.
Sending the IDENTIFY DEVICE command works exactly the same as sending any other non-queued read command. IDENTIFY DEVICE always uses one 512-byte DRQ block, so the AHCI PIO limitations won't get in the way. There are no I/O ports involved.
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by xeyes »

Octocontrabass wrote:
xeyes wrote:On Qemu the card sets D2H bit in the interrupt state register.

On hardware, seems that the card doesn't set any bit there even after seeing the correct 0x101 signature.
This might be a QEMU bug. I haven't checked earlier versions of the AHCI specification, but 1.3 says the D2H interrupt is used only to indicate a D2H register FIS has been written to memory, and the state machine in the spec doesn't seem to allow a D2H register FIS to both update the signature register and be written to memory.
xeyes wrote:Any idea on what the timeout should be here?
All I can find is clause 11.1, which says a single parallel device must clear BSY within 5 seconds, and clause 13.3, which suggests that serial devices must adhere to that timing to maintain compatibility with parallel devices. I would take that to mean the timeout must be at least 5 seconds.
Could be, IIRC Q means quick so 100% accurate is probably not a high priority, it can't be anyway due to timing.

Thanks for digging up the timeout value!

The draft I got doesn't even have clause 11, only then did I realize that there are a few different volumes #-o
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by devc1 »

Octocontrabass wrote:
devc1 wrote:Great, so now I'm not making anything wrong but, How to report that I only received some specific bits in Command Issue, do I need a mutex if it is multi processor.
I don't understand this question.
I mean because when you have a multiprocessor system, you may set command issue or sata active(SACT) when u are receiving the interrupt and handling command issue, for e.g. CI=0x81 when u are about to ack the interrupt and you have passed slot 7 it may be cleared, and u have acked the interrupt and u will get no interrupt after until a next command. When I try a SataRead with asynchronous commands it gets stuck on the last command even on uniprocessor mode in QEMU (and Real hardware)
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by Octocontrabass »

When you're accessing the HBA from multiple CPUs concurrently, you need some kind of mutex to ensure you're not setting any bits in PxCI or PxSACT before your interrupt handler has acknowledged those command slots as completed.

I'm not sure why you're not receiving an interrupt when the last command completes, though.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by devc1 »

Okay thanks, I will be here responding after a 1 or 2 days because I'm completly changing my scheduler from simple to priority preemtive (and optimized), My computer supports AVX so I'm also planning to make AVX/AVX512 versions of my scheduler, This is to optimize & give more idle cpu time and faster task switching. Thanks another time. The good thing about my kernel is that it only supports 64 bit and ACPI Hardware (likely 15 years+). So every processor will have atleast SSE3 Support.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: AHCI Port COMRESET ( & How to handle PortConnectChange I

Post by Octocontrabass »

devc1 wrote:The good thing about my kernel is that it only supports 64 bit and ACPI Hardware (likely 15 years+). So every processor will have atleast SSE3 Support.
All 64-bit hardware supports ACPI, but some 64-bit hardware does not support SSE3.
Post Reply