acpi_ec: changes in communication with hardware
Short description of the changes: - attempt to retry some commands for which it is possible (read, query) - always make a short sleep before checking EC status in polled mode - periodically poll EC status in interrupt mode - change logic for detecting broken interrupt delivery and falling back to polled mode - check that EC is ready for input before starting a new command, wait if necessary This commit is based on the original patch by David Naylor. PR: kern/150517 Submitted by: David Naylor <naylor.b.david@gmail.com> Reviewed by: jkim MFC after: 3 weeks
This commit is contained in:
parent
55173efe7f
commit
25fc6050f9
@ -153,7 +153,7 @@ struct acpi_ec_softc {
|
||||
int ec_glkhandle;
|
||||
int ec_burstactive;
|
||||
int ec_sci_pend;
|
||||
u_int ec_gencount;
|
||||
volatile u_int ec_gencount;
|
||||
int ec_suspending;
|
||||
};
|
||||
|
||||
@ -165,7 +165,7 @@ struct acpi_ec_softc {
|
||||
#define EC_LOCK_TIMEOUT 1000
|
||||
|
||||
/* Default delay in microseconds between each run of the status polling loop. */
|
||||
#define EC_POLL_DELAY 5
|
||||
#define EC_POLL_DELAY 50
|
||||
|
||||
/* Total time in ms spent waiting for a response from EC. */
|
||||
#define EC_TIMEOUT 750
|
||||
@ -599,12 +599,32 @@ acpi_ec_write_method(device_t dev, u_int addr, UINT64 val, int width)
|
||||
return (0);
|
||||
}
|
||||
|
||||
static ACPI_STATUS
|
||||
EcCheckStatus(struct acpi_ec_softc *sc, const char *msg, EC_EVENT event)
|
||||
{
|
||||
ACPI_STATUS status;
|
||||
EC_STATUS ec_status;
|
||||
|
||||
status = AE_NO_HARDWARE_RESPONSE;
|
||||
ec_status = EC_GET_CSR(sc);
|
||||
if (sc->ec_burstactive && !(ec_status & EC_FLAG_BURST_MODE)) {
|
||||
CTR1(KTR_ACPI, "ec burst disabled in waitevent (%s)", msg);
|
||||
sc->ec_burstactive = FALSE;
|
||||
}
|
||||
if (EVENT_READY(event, ec_status)) {
|
||||
CTR2(KTR_ACPI, "ec %s wait ready, status %#x", msg, ec_status);
|
||||
status = AE_OK;
|
||||
}
|
||||
return (status);
|
||||
}
|
||||
|
||||
static void
|
||||
EcGpeQueryHandler(void *Context)
|
||||
{
|
||||
struct acpi_ec_softc *sc = (struct acpi_ec_softc *)Context;
|
||||
UINT8 Data;
|
||||
ACPI_STATUS Status;
|
||||
int retry;
|
||||
char qxx[5];
|
||||
|
||||
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
|
||||
@ -625,7 +645,16 @@ EcGpeQueryHandler(void *Context)
|
||||
* that may arise from running the query from causing another query
|
||||
* to be queued, we clear the pending flag only after running it.
|
||||
*/
|
||||
Status = EcCommand(sc, EC_COMMAND_QUERY);
|
||||
for (retry = 0; retry < 2; retry++) {
|
||||
Status = EcCommand(sc, EC_COMMAND_QUERY);
|
||||
if (ACPI_SUCCESS(Status))
|
||||
break;
|
||||
if (EcCheckStatus(sc, "retr_check",
|
||||
EC_EVENT_INPUT_BUFFER_EMPTY) == AE_OK)
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
sc->ec_sci_pend = FALSE;
|
||||
if (ACPI_FAILURE(Status)) {
|
||||
EcUnlock(sc);
|
||||
@ -678,7 +707,7 @@ EcGpeHandler(void *Context)
|
||||
* address and then data values.)
|
||||
*/
|
||||
atomic_add_int(&sc->ec_gencount, 1);
|
||||
wakeup(&sc->ec_gencount);
|
||||
wakeup(&sc);
|
||||
|
||||
/*
|
||||
* If the EC_SCI bit of the status register is set, queue a query handler.
|
||||
@ -788,69 +817,28 @@ EcSpaceHandler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, UINT32 Width,
|
||||
return_ACPI_STATUS (Status);
|
||||
}
|
||||
|
||||
static ACPI_STATUS
|
||||
EcCheckStatus(struct acpi_ec_softc *sc, const char *msg, EC_EVENT event)
|
||||
{
|
||||
ACPI_STATUS status;
|
||||
EC_STATUS ec_status;
|
||||
|
||||
status = AE_NO_HARDWARE_RESPONSE;
|
||||
ec_status = EC_GET_CSR(sc);
|
||||
if (sc->ec_burstactive && !(ec_status & EC_FLAG_BURST_MODE)) {
|
||||
CTR1(KTR_ACPI, "ec burst disabled in waitevent (%s)", msg);
|
||||
sc->ec_burstactive = FALSE;
|
||||
}
|
||||
if (EVENT_READY(event, ec_status)) {
|
||||
CTR2(KTR_ACPI, "ec %s wait ready, status %#x", msg, ec_status);
|
||||
status = AE_OK;
|
||||
}
|
||||
return (status);
|
||||
}
|
||||
|
||||
static ACPI_STATUS
|
||||
EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT Event, u_int gen_count)
|
||||
{
|
||||
static int no_intr = 0;
|
||||
ACPI_STATUS Status;
|
||||
int count, i, slp_ival;
|
||||
int count, i, need_poll, slp_ival;
|
||||
|
||||
ACPI_SERIAL_ASSERT(ec);
|
||||
Status = AE_NO_HARDWARE_RESPONSE;
|
||||
int need_poll = cold || rebooting || ec_polled_mode || sc->ec_suspending;
|
||||
/*
|
||||
* The main CPU should be much faster than the EC. So the status should
|
||||
* be "not ready" when we start waiting. But if the main CPU is really
|
||||
* slow, it's possible we see the current "ready" response. Since that
|
||||
* can't be distinguished from the previous response in polled mode,
|
||||
* this is a potential issue. We really should have interrupts enabled
|
||||
* during boot so there is no ambiguity in polled mode.
|
||||
*
|
||||
* If this occurs, we add an additional delay before actually entering
|
||||
* the status checking loop, hopefully to allow the EC to go to work
|
||||
* and produce a non-stale status.
|
||||
*/
|
||||
if (need_poll) {
|
||||
static int once;
|
||||
|
||||
if (EcCheckStatus(sc, "pre-check", Event) == AE_OK) {
|
||||
if (!once) {
|
||||
device_printf(sc->ec_dev,
|
||||
"warning: EC done before starting event wait\n");
|
||||
once = 1;
|
||||
}
|
||||
AcpiOsStall(10);
|
||||
}
|
||||
}
|
||||
need_poll = cold || rebooting || ec_polled_mode || sc->ec_suspending;
|
||||
|
||||
/* Wait for event by polling or GPE (interrupt). */
|
||||
if (need_poll) {
|
||||
count = (ec_timeout * 1000) / EC_POLL_DELAY;
|
||||
if (count == 0)
|
||||
count = 1;
|
||||
DELAY(10);
|
||||
for (i = 0; i < count; i++) {
|
||||
Status = EcCheckStatus(sc, "poll", Event);
|
||||
if (Status == AE_OK)
|
||||
break;
|
||||
AcpiOsStall(EC_POLL_DELAY);
|
||||
DELAY(EC_POLL_DELAY);
|
||||
}
|
||||
} else {
|
||||
slp_ival = hz / 1000;
|
||||
@ -869,34 +857,37 @@ EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT Event, u_int gen_count)
|
||||
* EC query).
|
||||
*/
|
||||
for (i = 0; i < count; i++) {
|
||||
if (gen_count != sc->ec_gencount) {
|
||||
/*
|
||||
* Record new generation count. It's possible the GPE was
|
||||
* just to notify us that a query is needed and we need to
|
||||
* wait for a second GPE to signal the completion of the
|
||||
* event we are actually waiting for.
|
||||
*/
|
||||
gen_count = sc->ec_gencount;
|
||||
Status = EcCheckStatus(sc, "sleep", Event);
|
||||
if (Status == AE_OK)
|
||||
break;
|
||||
if (gen_count == sc->ec_gencount)
|
||||
tsleep(&sc, 0, "ecgpe", slp_ival);
|
||||
/*
|
||||
* Record new generation count. It's possible the GPE was
|
||||
* just to notify us that a query is needed and we need to
|
||||
* wait for a second GPE to signal the completion of the
|
||||
* event we are actually waiting for.
|
||||
*/
|
||||
Status = EcCheckStatus(sc, "sleep", Event);
|
||||
if (Status == AE_OK) {
|
||||
if (gen_count == sc->ec_gencount)
|
||||
no_intr++;
|
||||
else
|
||||
no_intr = 0;
|
||||
break;
|
||||
}
|
||||
tsleep(&sc->ec_gencount, PZERO, "ecgpe", slp_ival);
|
||||
gen_count = sc->ec_gencount;
|
||||
}
|
||||
|
||||
/*
|
||||
* We finished waiting for the GPE and it never arrived. Try to
|
||||
* read the register once and trust whatever value we got. This is
|
||||
* the best we can do at this point. Then, force polled mode on
|
||||
* since this system doesn't appear to generate GPEs.
|
||||
* the best we can do at this point.
|
||||
*/
|
||||
if (Status != AE_OK) {
|
||||
if (Status != AE_OK)
|
||||
Status = EcCheckStatus(sc, "sleep_end", Event);
|
||||
device_printf(sc->ec_dev,
|
||||
"wait timed out (%sresponse), forcing polled mode\n",
|
||||
Status == AE_OK ? "" : "no ");
|
||||
ec_polled_mode = TRUE;
|
||||
}
|
||||
}
|
||||
if (!need_poll && no_intr > 10) {
|
||||
device_printf(sc->ec_dev,
|
||||
"not getting interrupts, switched to polled mode\n");
|
||||
ec_polled_mode = 1;
|
||||
}
|
||||
if (Status != AE_OK)
|
||||
CTR0(KTR_ACPI, "error: ec wait timed out");
|
||||
@ -933,6 +924,14 @@ EcCommand(struct acpi_ec_softc *sc, EC_COMMAND cmd)
|
||||
return (AE_BAD_PARAMETER);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure empty input buffer before issuing command.
|
||||
* Use generation count of zero to force a quick check.
|
||||
*/
|
||||
status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY, 0);
|
||||
if (ACPI_FAILURE(status))
|
||||
return (status);
|
||||
|
||||
/* Run the command and wait for the chosen event. */
|
||||
CTR1(KTR_ACPI, "ec running command %#x", cmd);
|
||||
gen_count = sc->ec_gencount;
|
||||
@ -955,24 +954,31 @@ EcRead(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data)
|
||||
{
|
||||
ACPI_STATUS status;
|
||||
u_int gen_count;
|
||||
int retry;
|
||||
|
||||
ACPI_SERIAL_ASSERT(ec);
|
||||
CTR1(KTR_ACPI, "ec read from %#x", Address);
|
||||
|
||||
status = EcCommand(sc, EC_COMMAND_READ);
|
||||
if (ACPI_FAILURE(status))
|
||||
return (status);
|
||||
for (retry = 0; retry < 2; retry++) {
|
||||
status = EcCommand(sc, EC_COMMAND_READ);
|
||||
if (ACPI_FAILURE(status))
|
||||
return (status);
|
||||
|
||||
gen_count = sc->ec_gencount;
|
||||
EC_SET_DATA(sc, Address);
|
||||
status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL, gen_count);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
device_printf(sc->ec_dev, "EcRead: failed waiting to get data\n");
|
||||
return (status);
|
||||
gen_count = sc->ec_gencount;
|
||||
EC_SET_DATA(sc, Address);
|
||||
status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL, gen_count);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
if (EcCheckStatus(sc, "retr_check",
|
||||
EC_EVENT_INPUT_BUFFER_EMPTY) == AE_OK)
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
*Data = EC_GET_DATA(sc);
|
||||
return (AE_OK);
|
||||
}
|
||||
*Data = EC_GET_DATA(sc);
|
||||
|
||||
return (AE_OK);
|
||||
device_printf(sc->ec_dev, "EcRead: failed waiting to get data\n");
|
||||
return (status);
|
||||
}
|
||||
|
||||
static ACPI_STATUS
|
||||
@ -992,7 +998,7 @@ EcWrite(struct acpi_ec_softc *sc, UINT8 Address, UINT8 Data)
|
||||
EC_SET_DATA(sc, Address);
|
||||
status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY, gen_count);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
device_printf(sc->ec_dev, "EcRead: failed waiting for sent address\n");
|
||||
device_printf(sc->ec_dev, "EcWrite: failed waiting for sent address\n");
|
||||
return (status);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user