diff options
Diffstat (limited to 'sys/dev/acpica/acpi_ec.c')
-rw-r--r-- | sys/dev/acpica/acpi_ec.c | 315 |
1 files changed, 206 insertions, 109 deletions
diff --git a/sys/dev/acpica/acpi_ec.c b/sys/dev/acpica/acpi_ec.c index c6649fb9f6e8..96669b319dc9 100644 --- a/sys/dev/acpica/acpi_ec.c +++ b/sys/dev/acpica/acpi_ec.c @@ -187,15 +187,15 @@ typedef UINT8 EC_COMMAND; * | | | +--------- Burst Mode Enabled? * | | +----------- SCI Event? * | +------------- SMI Event? - * +--------------- <Reserved> + * +--------------- <reserved> * */ typedef UINT8 EC_STATUS; #define EC_FLAG_OUTPUT_BUFFER ((EC_STATUS) 0x01) #define EC_FLAG_INPUT_BUFFER ((EC_STATUS) 0x02) +#define EC_FLAG_DATA_IS_CMD ((EC_STATUS) 0x08) #define EC_FLAG_BURST_MODE ((EC_STATUS) 0x10) -#define EC_FLAG_SCI ((EC_STATUS) 0x20) /* * EC_EVENT: @@ -207,6 +207,10 @@ typedef UINT8 EC_EVENT; #define EC_EVENT_OUTPUT_BUFFER_FULL ((EC_EVENT) 0x01) #define EC_EVENT_INPUT_BUFFER_EMPTY ((EC_EVENT) 0x02) #define EC_EVENT_SCI ((EC_EVENT) 0x20) +#define EC_EVENT_SMI ((EC_EVENT) 0x40) + +/* Data byte returned after burst enable indicating it was successful. */ +#define EC_BURST_ACK 0x90 /* * Register access primitives @@ -265,8 +269,11 @@ struct acpi_ec_softc { bus_space_tag_t ec_csr_tag; bus_space_handle_t ec_csr_handle; + struct mtx ec_mtx; int ec_glk; int ec_glkhandle; + int ec_burstactive; + int ec_sci_pend; }; /* @@ -276,11 +283,14 @@ struct acpi_ec_softc { */ #define EC_LOCK_TIMEOUT 1000 -/* Default interval in microseconds for the status polling loop. */ +/* Default delay in microseconds between each run of the status polling loop. */ #define EC_POLL_DELAY 10 -/* Total time in ms spent in the poll loop waiting for a response. */ -#define EC_POLL_TIMEOUT 100 +/* Default time in microseconds spent polling before sleep waiting. */ +#define EC_POLL_TIME 500 + +/* Total time in ms spent waiting for a response from EC. */ +#define EC_TIMEOUT 500 #define EVENT_READY(event, status) \ (((event) == EC_EVENT_OUTPUT_BUFFER_FULL && \ @@ -288,25 +298,47 @@ struct acpi_ec_softc { ((event) == EC_EVENT_INPUT_BUFFER_EMPTY && \ ((status) & EC_FLAG_INPUT_BUFFER) == 0)) -static int ec_poll_timeout = EC_POLL_TIMEOUT; -TUNABLE_INT("hw.acpi.ec.poll_timeout", &ec_poll_timeout); - ACPI_SERIAL_DECL(ec, "ACPI embedded controller"); +SYSCTL_DECL(_debug_acpi); +SYSCTL_NODE(_debug_acpi, OID_AUTO, ec, CTLFLAG_RD, NULL, "EC debugging"); + +static int ec_burst_mode = TRUE; +TUNABLE_INT("debug.acpi.ec.burst", &ec_burst_mode); +SYSCTL_INT(_debug_acpi_ec, OID_AUTO, burst, CTLFLAG_RW, &ec_burst_mode, TRUE, + "Enable use of burst mode (faster for nearly all systems)"); +static int ec_poll_time = EC_POLL_TIME; +TUNABLE_INT("debug.acpi.ec.poll_time", &ec_poll_time); +SYSCTL_INT(_debug_acpi_ec, OID_AUTO, poll_time, CTLFLAG_RW, &ec_poll_time, + EC_POLL_TIME, "Time spent polling vs. sleeping (CPU intensive)"); +static int ec_timeout = EC_TIMEOUT; +TUNABLE_INT("debug.acpi.ec.timeout", &ec_timeout); +SYSCTL_INT(_debug_acpi_ec, OID_AUTO, timeout, CTLFLAG_RW, &ec_timeout, + EC_TIMEOUT, "Total time spent waiting for a response (poll+sleep)"); + static __inline ACPI_STATUS -EcLock(struct acpi_ec_softc *sc) +EcLock(struct acpi_ec_softc *sc, int serialize) { ACPI_STATUS status; - /* Always acquire the exclusive lock. */ + /* + * If caller is executing a series of commands, acquire the exclusive lock + * to serialize with other users. + * To sync with bottom-half interrupt handler, always acquire the mutex. + */ status = AE_OK; - ACPI_SERIAL_BEGIN(ec); + if (serialize) + ACPI_SERIAL_BEGIN(ec); + mtx_lock(&sc->ec_mtx); /* If _GLK is non-zero, also acquire the global lock. */ if (sc->ec_glk) { status = AcpiAcquireGlobalLock(EC_LOCK_TIMEOUT, &sc->ec_glkhandle); - if (ACPI_FAILURE(status)) - ACPI_SERIAL_END(ec); + if (ACPI_FAILURE(status)) { + mtx_unlock(&sc->ec_mtx); + if (serialize) + ACPI_SERIAL_END(ec); + } } return (status); @@ -317,7 +349,9 @@ EcUnlock(struct acpi_ec_softc *sc) { if (sc->ec_glk) AcpiReleaseGlobalLock(sc->ec_glkhandle); - ACPI_SERIAL_END(ec); + mtx_unlock(&sc->ec_mtx); + if (sx_xlocked(&ec_sxlock)) + ACPI_SERIAL_END(ec); } static uint32_t EcGpeHandler(void *Context); @@ -558,6 +592,7 @@ acpi_ec_attach(device_t dev) params = acpi_get_private(dev); sc->ec_dev = dev; sc->ec_handle = acpi_get_handle(dev); + mtx_init(&sc->ec_mtx, "ACPI EC lock", NULL, MTX_DEF); /* Retrieve previously probed values via device ivars. */ sc->ec_glk = params->glk; @@ -640,6 +675,7 @@ error: if (sc->ec_data_res) bus_release_resource(sc->ec_dev, SYS_RES_IOPORT, sc->ec_data_rid, sc->ec_data_res); + mtx_destroy(&sc->ec_mtx); return (ENXIO); } @@ -687,13 +723,13 @@ EcGpeQueryHandler(void *Context) struct acpi_ec_softc *sc = (struct acpi_ec_softc *)Context; UINT8 Data; ACPI_STATUS Status; - EC_STATUS EcStatus; char qxx[5]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); KASSERT(Context != NULL, ("EcGpeQueryHandler called with NULL")); - Status = EcLock(sc); + /* Serialize user access with EcSpaceHandler(). */ + Status = EcLock(sc, TRUE); if (ACPI_FAILURE(Status)) { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "GpeQuery lock error: %s\n", AcpiFormatException(Status)); @@ -701,19 +737,6 @@ EcGpeQueryHandler(void *Context) } /* - * If the EC_SCI bit of the status register is not set, then pass - * it along to any potential waiters as it may be an IBE/OBF event. - */ - EcStatus = EC_GET_CSR(sc); - if ((EcStatus & EC_EVENT_SCI) == 0) { - CTR1(KTR_ACPI, "ec event was not SCI, status %#x", EcStatus); - sc->ec_csrvalue = EcStatus; - wakeup(&sc->ec_csrvalue); - EcUnlock(sc); - goto re_enable; - } - - /* * Send a query command to the EC to find out which _Qxx call it * wants to make. This command clears the SCI bit and also the * interrupt source since we are edge-triggered. @@ -726,6 +749,9 @@ EcGpeQueryHandler(void *Context) goto re_enable; } Data = EC_GET_DATA(sc); + sc->ec_sci_pend = FALSE; + + /* Drop locks before evaluating _Qxx method since it may trigger GPEs. */ EcUnlock(sc); /* Ignore the value for "no outstanding event". (13.3.5) */ @@ -734,7 +760,7 @@ EcGpeQueryHandler(void *Context) goto re_enable; /* Evaluate _Qxx to respond to the controller. */ - sprintf(qxx, "_Q%02x", Data); + snprintf(qxx, sizeof(qxx), "_Q%02x", Data); AcpiUtStrupr(qxx); Status = AcpiEvaluateObject(sc->ec_handle, qxx, NULL, NULL); if (ACPI_FAILURE(Status) && Status != AE_NOT_FOUND) { @@ -745,7 +771,7 @@ EcGpeQueryHandler(void *Context) re_enable: /* Re-enable the GPE event so we'll get future requests. */ - Status = AcpiEnableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_NOT_ISR); + Status = AcpiEnableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_ISR); if (ACPI_FAILURE(Status)) printf("EcGpeQueryHandler: AcpiEnableEvent failed\n"); } @@ -760,27 +786,61 @@ EcGpeHandler(void *Context) { struct acpi_ec_softc *sc = Context; ACPI_STATUS Status; + EC_STATUS EcStatus; KASSERT(Context != NULL, ("EcGpeHandler called with NULL")); /* * Disable further GPEs while we handle this one. Since we are directly * called by ACPI-CA and it may have unknown locks held, we specify the - * ACPI_ISR flag to keep it from acquiring any more mutexes (which could - * potentially sleep.) + * ACPI_ISR flag to keep it from acquiring any more mutexes (although + * sleeping would be ok since we're in an ithread.) */ AcpiDisableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_ISR); - /* Schedule the GPE query handler. */ - Status = AcpiOsQueueForExecution(OSD_PRIORITY_GPE, EcGpeQueryHandler, - Context); + /* For interrupt (GPE) handler, don't acquire serialization lock. */ + Status = EcLock(sc, FALSE); if (ACPI_FAILURE(Status)) { - printf("Queuing GPE query handler failed.\n"); - Status = AcpiEnableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_ISR); - if (ACPI_FAILURE(Status)) - printf("EcGpeHandler: AcpiEnableEvent failed\n"); + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "GpeQuery lock error: %s\n", AcpiFormatException(Status)); + return (-1); } + /* + * If burst was active, but the status bit was cleared, the EC had to + * exit burst mode for some reason. Record this for later. + */ + EcStatus = EC_GET_CSR(sc); + if (sc->ec_burstactive && (EcStatus & EC_FLAG_BURST_MODE) == 0) { + CTR0(KTR_ACPI, "ec burst disabled in query handler"); + sc->ec_burstactive = FALSE; + } + + /* + * If the EC_SCI bit of the status register is not set, then pass + * it along to any potential waiters as it may be an IBE/OBF event. + * If it is set, queue a query handler. + */ + if ((EcStatus & EC_EVENT_SCI) == 0) { + CTR1(KTR_ACPI, "ec event was IBE/OBF, status %#x", EcStatus); + sc->ec_csrvalue = EcStatus; + wakeup(&sc->ec_csrvalue); + } else if (!sc->ec_sci_pend) { + /* SCI bit set and no pending query handler, so schedule one. */ + CTR0(KTR_ACPI, "ec queueing gpe handler"); + Status = AcpiOsQueueForExecution(OSD_PRIORITY_GPE, EcGpeQueryHandler, + Context); + if (ACPI_SUCCESS(Status)) { + sc->ec_sci_pend = TRUE; + } else { + printf("Queuing GPE query handler failed.\n"); + Status = AcpiEnableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_ISR); + if (ACPI_FAILURE(Status)) + printf("EcGpeHandler: AcpiEnableEvent failed\n"); + } + } + + EcUnlock(sc); return (0); } @@ -824,7 +884,8 @@ EcSpaceHandler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, UINT32 width, EcAddr = Address; Status = AE_ERROR; - Status = EcLock(sc); + /* Grab serialization lock to hold across command sequence. */ + Status = EcLock(sc, TRUE); if (ACPI_FAILURE(Status)) return_ACPI_STATUS (Status); @@ -859,87 +920,103 @@ EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT Event) { EC_STATUS EcStatus; ACPI_STATUS Status; - int count, i, period, retval, slp_ival; + int count, i, retval, slp_ival; ACPI_SERIAL_ASSERT(ec); Status = AE_NO_HARDWARE_RESPONSE; - - /* - * Wait for 1 us before checking the CSR. Testing shows about - * 50% of requests complete in 1 us and 90% of them complete - * in 5 us or less. - */ - AcpiOsStall(1); + EcStatus = 0; /* - * Poll the EC status register for up to 1 ms in chunks of 10 us - * to detect completion of the last command. + * Poll for up to ec_poll_time microseconds since many ECs complete + * the command quickly, especially if in burst mode. */ - for (i = 0; i < 1000 / EC_POLL_DELAY; i++) { +#if 0 /* Enable this as a possible workaround if EC times out. */ + AcpiOsStall(EC_POLL_DELAY); +#endif + count = ec_poll_time / EC_POLL_DELAY; + if (count <= 0) + count = 1; + for (i = 0; i < count; i++) { EcStatus = EC_GET_CSR(sc); + if (sc->ec_burstactive && (EcStatus & EC_FLAG_BURST_MODE) == 0) { + CTR0(KTR_ACPI, "ec burst disabled in waitevent (poll)"); + sc->ec_burstactive = FALSE; + } if (EVENT_READY(Event, EcStatus)) { + CTR1(KTR_ACPI, "ec poll wait ready, status %#x", EcStatus); Status = AE_OK; break; } AcpiOsStall(EC_POLL_DELAY); } - period = i * EC_POLL_DELAY; /* * If we still don't have a response and we're up and running, wait up - * to ec_poll_timeout ms for completion, sleeping for chunks of 10 ms. + * to ec_timeout ms for completion, sleeping for chunks of 1 ms or the + * smallest resolution hz supports. */ slp_ival = 0; if (Status != AE_OK) { retval = ENXIO; - count = ec_poll_timeout / 10; - if (count == 0) - count = 1; - slp_ival = hz / 100; - if (slp_ival == 0) - slp_ival = 1; + if (!cold) { + slp_ival = hz / 1000; + if (slp_ival != 0) { + count = ec_timeout / slp_ival; + } else { + /* hz has less than 1000 Hz resolution so scale timeout. */ + slp_ival = 1; + count = ec_timeout / (1000 / hz); + } + } else + count = ec_timeout; for (i = 0; i < count; i++) { if (retval != 0) EcStatus = EC_GET_CSR(sc); else EcStatus = sc->ec_csrvalue; + if (sc->ec_burstactive && (EcStatus & EC_FLAG_BURST_MODE) == 0) { + CTR0(KTR_ACPI, "ec burst disabled in waitevent (slp)"); + sc->ec_burstactive = FALSE; + } if (EVENT_READY(Event, EcStatus)) { + CTR1(KTR_ACPI, "ec sleep wait ready, status %#x", EcStatus); Status = AE_OK; break; } - if (!cold) - retval = tsleep(&sc->ec_csrvalue, PZERO, "ecpoll", slp_ival); - else - AcpiOsStall(10000); + if (!cold) { + retval = msleep(&sc->ec_csrvalue, &sc->ec_mtx, PZERO, "ecpoll", + slp_ival); + } else + AcpiOsStall(1000); } } - /* Calculate new delay and log it. */ - if (slp_ival > 0) - period += i * 10000; - CTR2(KTR_ACPI, "ec got event %#x after %d us", EcStatus, period); - return (Status); } static ACPI_STATUS EcCommand(struct acpi_ec_softc *sc, EC_COMMAND cmd) { - ACPI_STATUS Status; - EC_EVENT Event; + ACPI_STATUS status; + EC_EVENT event; + EC_STATUS ec_status; ACPI_SERIAL_ASSERT(ec); + /* Don't use burst mode if user disabled it. */ + if (!ec_burst_mode && cmd == EC_COMMAND_BURST_ENABLE) + return (AE_ERROR); + /* Decide what to wait for based on command type. */ switch (cmd) { case EC_COMMAND_READ: case EC_COMMAND_WRITE: case EC_COMMAND_BURST_DISABLE: - Event = EC_EVENT_INPUT_BUFFER_EMPTY; + event = EC_EVENT_INPUT_BUFFER_EMPTY; break; case EC_COMMAND_QUERY: case EC_COMMAND_BURST_ENABLE: - Event = EC_EVENT_OUTPUT_BUFFER_FULL; + event = EC_EVENT_OUTPUT_BUFFER_FULL; break; default: ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), @@ -948,50 +1025,64 @@ EcCommand(struct acpi_ec_softc *sc, EC_COMMAND cmd) } /* Run the command and wait for the chosen event. */ + CTR1(KTR_ACPI, "ec running command %#x", cmd); EC_SET_CSR(sc, cmd); - Status = EcWaitEvent(sc, Event); - if (ACPI_FAILURE(Status)) { + status = EcWaitEvent(sc, event); + if (ACPI_SUCCESS(status)) { + /* If we succeeded, burst flag should now be present. */ + if (cmd == EC_COMMAND_BURST_ENABLE) { + ec_status = EC_GET_CSR(sc); + if ((ec_status & EC_FLAG_BURST_MODE) == 0) + status = AE_ERROR; + } + } else { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "EcCommand: no response to %#x\n", cmd); } - return (Status); + return (status); } static ACPI_STATUS EcRead(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data) { - ACPI_STATUS Status; + ACPI_STATUS status; + UINT8 data; ACPI_SERIAL_ASSERT(ec); CTR1(KTR_ACPI, "ec read from %#x", Address); -#ifdef notyet /* If we can't start burst mode, continue anyway. */ - EcCommand(sc, EC_COMMAND_BURST_ENABLE); -#endif + status = EcCommand(sc, EC_COMMAND_BURST_ENABLE); + if (status == AE_OK) { + data = EC_GET_DATA(sc); + if (data == EC_BURST_ACK) { + CTR0(KTR_ACPI, "ec burst enabled"); + sc->ec_burstactive = TRUE; + } + } - Status = EcCommand(sc, EC_COMMAND_READ); - if (ACPI_FAILURE(Status)) - return (Status); + status = EcCommand(sc, EC_COMMAND_READ); + if (ACPI_FAILURE(status)) + return (status); EC_SET_DATA(sc, Address); - Status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL); - if (ACPI_FAILURE(Status)) { + status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL); + if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "EcRead: Failed waiting for EC to send data.\n"); - return (Status); + return (status); } *Data = EC_GET_DATA(sc); -#ifdef notyet if (sc->ec_burstactive) { - Status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); - if (ACPI_FAILURE(Status)) - return (Status); + status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); + if (ACPI_FAILURE(status)) + return (status); + sc->ec_burstactive = FALSE; + CTR0(KTR_ACPI, "ec disabled burst ok"); } -#endif return (AE_OK); } @@ -999,43 +1090,49 @@ EcRead(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data) static ACPI_STATUS EcWrite(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data) { - ACPI_STATUS Status; + ACPI_STATUS status; + UINT8 data; ACPI_SERIAL_ASSERT(ec); CTR2(KTR_ACPI, "ec write to %#x, data %#x", Address, *Data); -#ifdef notyet /* If we can't start burst mode, continue anyway. */ - EcCommand(sc, EC_COMMAND_BURST_ENABLE); -#endif + status = EcCommand(sc, EC_COMMAND_BURST_ENABLE); + if (status == AE_OK) { + data = EC_GET_DATA(sc); + if (data == EC_BURST_ACK) { + CTR0(KTR_ACPI, "ec burst enabled"); + sc->ec_burstactive = TRUE; + } + } - Status = EcCommand(sc, EC_COMMAND_WRITE); - if (ACPI_FAILURE(Status)) - return (Status); + status = EcCommand(sc, EC_COMMAND_WRITE); + if (ACPI_FAILURE(status)) + return (status); EC_SET_DATA(sc, Address); - Status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY); - if (ACPI_FAILURE(Status)) { + status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY); + if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "EcRead: Failed waiting for EC to process address\n"); - return (Status); + return (status); } EC_SET_DATA(sc, *Data); - Status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY); - if (ACPI_FAILURE(Status)) { + status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY); + if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "EcWrite: Failed waiting for EC to process data\n"); - return (Status); + return (status); } -#ifdef notyet if (sc->ec_burstactive) { - Status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); - if (ACPI_FAILURE(Status)) - return (Status); + status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); + if (ACPI_FAILURE(status)) + return (status); + sc->ec_burstactive = FALSE; + CTR0(KTR_ACPI, "ec disabled burst ok"); } -#endif return (AE_OK); } |