Port-i386 archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: ACPI Exception AE_NO_MEMORY



On Tue, Apr 20, 2010 at 03:06:15PM +0000, Andrew Doran wrote:
> From a design standpoint one can assume that it will always fail,
> because in practice it will fail very often.  There are situations where
> that can be tolerated but they are few and far between.  Most places where
> we allocate memory, we must get that memory or the user experience will
> be terminally compromised.

To be more precise, it is sysmon_taskq(9) that allocates with M_NOWAIT and
fails.

> On Tue, Apr 20, 2010 at 03:41:22PM +0300, Jukka Marin wrote:
> > I don't know how the ACPI events work, but if works like this, I can see why
> > the system dies:
> > 
> >   1. an interrupt occurs and an ACPI event is generated
> > 
> >   2. memory allocation for the event fails, so the event
> >      can not be enqueued
> > 
> >   3. the interrupt is not cleared because the event could not be
> >      enqueued (so we don't miss the event completely)
> > 
> >   4. interrupt routine exits
> > 
> >   5. the interrupt is still active, loop back to 1.
> > 
> > Again, I don't know if the kernel works like this, but this would explain
> > the problem I'm having..

This is pretty much how it works.

To my understanding ACPICA follows the specification to the letter here, and
it goes like this (ACPI 4.0, p. 174). When a so-called general purpose event
is received:

        1. Disables the interrupt source.

        2. If an edge event, clears the status bit.

        3. Perform one of the following: (a) dispatches to an ACPI-aware
           device driver, (b) queues the matching control method for execution
           (our case here), or (c) manages wake event.

        4. If a level event, clears the status bit.

        5. Enables the interrupt source.

Now if we look at the comments in AcpiEvGpeDispatch() (in file evgpe.c):

UINT32
AcpiEvGpeDispatch (
    ACPI_GPE_EVENT_INFO     *GpeEventInfo,
    UINT32                  GpeNumber)
{ 
    ACPI_STATUS             Status;

  
    ACPI_FUNCTION_TRACE (EvGpeDispatch);
     

    AcpiGpeCount++;
  
    /*
     * If edge-triggered, clear the GPE status bit now. Note that
     * level-triggered events are cleared after the GPE is serviced.
     */
    if ((GpeEventInfo->Flags & ACPI_GPE_XRUPT_TYPE_MASK) ==
            ACPI_GPE_EDGE_TRIGGERED)
    {
        Status = AcpiHwClearGpe (GpeEventInfo);
        if (ACPI_FAILURE (Status))
        {
            ACPI_EXCEPTION ((AE_INFO, Status,
                "Unable to clear GPE[%2X]", GpeNumber));
            return_UINT32 (ACPI_INTERRUPT_NOT_HANDLED);
        }
    }

    /*
     * Dispatch the GPE to either an installed handler, or the control method
     * associated with this GPE (_Lxx or _Exx). If a handler exists, we invoke
     * it and do not attempt to run the method. If there is neither a handler
     * nor a method, we disable this GPE to prevent further such pointless
     * events from firing.
     */
    switch (GpeEventInfo->Flags & ACPI_GPE_DISPATCH_MASK)
    {
    case ACPI_GPE_DISPATCH_HANDLER:

        /*
         * Invoke the installed handler (at interrupt level)
         * Ignore return status for now.
         * TBD: leave GPE disabled on error?
         */
        (void) GpeEventInfo->Dispatch.Handler->Address (
                        GpeEventInfo->Dispatch.Handler->Context);

        /* It is now safe to clear level-triggered events. */

        if ((GpeEventInfo->Flags & ACPI_GPE_XRUPT_TYPE_MASK) ==
                ACPI_GPE_LEVEL_TRIGGERED)
        { 
            Status = AcpiHwClearGpe (GpeEventInfo);
            if (ACPI_FAILURE (Status))  
            {
                ACPI_EXCEPTION ((AE_INFO, Status,
                    "Unable to clear GPE[%2X]", GpeNumber));
                return_UINT32 (ACPI_INTERRUPT_NOT_HANDLED);
            }
        }
        break;

    case ACPI_GPE_DISPATCH_METHOD:

        /*
         * Disable the GPE, so it doesn't keep firing before the method has a
         * chance to run (it runs asynchronously with interrupts enabled).
         */
        Status = AcpiEvDisableGpe (GpeEventInfo);
        if (ACPI_FAILURE (Status))
        {
            ACPI_EXCEPTION ((AE_INFO, Status,
                 "Unable to disable GPE[%2X]", GpeNumber));
            return_UINT32 (ACPI_INTERRUPT_NOT_HANDLED);
        }

        /*   
         * Execute the method associated with the GPE
         * NOTE: Level-triggered GPEs are cleared after the method
           completes.
         */
[1]      Status = AcpiOsExecute (OSL_GPE_HANDLER,
                    AcpiEvAsynchExecuteGpeMethod, GpeEventInfo);
        if (ACPI_FAILURE (Status))
        {
            ACPI_EXCEPTION ((AE_INFO, Status,
                "Unable to queue handler for GPE[%2X] - event disabled",
[2]              GpeNumber));
        }
        break;
        
        [...]


So in [1] it fails. In [2] it is not cleared, i.e. it does not invoke
AcpiHwClearGpe(), if my reading is correct.

- Jukka.


Home | Main Index | Thread Index | Old Index