Merge branch 'acpi-ec'
* acpi-ec: ACPI / EC: Add GPE reference counting debugging messages ACPI / EC: Add query flushing support ACPI / EC: Refine command storm prevention support ACPI / EC: Add command flushing support. ACPI / EC: Introduce STARTED/STOPPED flags to replace BLOCKED flag ACPI / EC: Update revision due to raw handler mode. ACPI / EC: Reduce ec_poll() by referencing the last register access timestamp. ACPI / EC: Fix several GPE handling issues by deploying ACPI_GPE_DISPATCH_RAW_HANDLER mode. ACPI / EC: Cleanup QR_EC related code ACPI / EC: Fix issues related to the SCI_EVT handling ACPI / EC: Fix a code path that global lock is not held ACPI / EC: Fix returning values in acpi_ec_sync_query() ACPI / EC: Add reference counting for query handlers ACPI / EC: Cleanup transaction wakeup code
This commit is contained in:
commit
716bc4132a
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
* ec.c - ACPI Embedded Controller Driver (v2.2)
|
||||
* ec.c - ACPI Embedded Controller Driver (v3)
|
||||
*
|
||||
* Copyright (C) 2001-2014 Intel Corporation
|
||||
* Author: 2014 Lv Zheng <lv.zheng@intel.com>
|
||||
* Copyright (C) 2001-2015 Intel Corporation
|
||||
* Author: 2014, 2015 Lv Zheng <lv.zheng@intel.com>
|
||||
* 2006, 2007 Alexey Starikovskiy <alexey.y.starikovskiy@intel.com>
|
||||
* 2006 Denis Sadykov <denis.m.sadykov@intel.com>
|
||||
* 2004 Luming Yu <luming.yu@intel.com>
|
||||
|
@ -31,6 +31,7 @@
|
|||
|
||||
/* Uncomment next line to get verbose printout */
|
||||
/* #define DEBUG */
|
||||
#define DEBUG_REF 0
|
||||
#define pr_fmt(fmt) "ACPI : EC: " fmt
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
@ -71,20 +72,32 @@ enum ec_command {
|
|||
#define ACPI_EC_DELAY 500 /* Wait 500ms max. during EC ops */
|
||||
#define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */
|
||||
#define ACPI_EC_MSI_UDELAY 550 /* Wait 550us for MSI EC */
|
||||
#define ACPI_EC_UDELAY_POLL 1000 /* Wait 1ms for EC transaction polling */
|
||||
#define ACPI_EC_CLEAR_MAX 100 /* Maximum number of events to query
|
||||
* when trying to clear the EC */
|
||||
|
||||
enum {
|
||||
EC_FLAGS_QUERY_PENDING, /* Query is pending */
|
||||
EC_FLAGS_GPE_STORM, /* GPE storm detected */
|
||||
EC_FLAGS_EVENT_ENABLED, /* Event is enabled */
|
||||
EC_FLAGS_EVENT_PENDING, /* Event is pending */
|
||||
EC_FLAGS_EVENT_DETECTED, /* Event is detected */
|
||||
EC_FLAGS_HANDLERS_INSTALLED, /* Handlers for GPE and
|
||||
* OpReg are installed */
|
||||
EC_FLAGS_BLOCKED, /* Transactions are blocked */
|
||||
EC_FLAGS_STARTED, /* Driver is started */
|
||||
EC_FLAGS_STOPPED, /* Driver is stopped */
|
||||
EC_FLAGS_COMMAND_STORM, /* GPE storms occurred to the
|
||||
* current command processing */
|
||||
};
|
||||
|
||||
#define ACPI_EC_COMMAND_POLL 0x01 /* Available for command byte */
|
||||
#define ACPI_EC_COMMAND_COMPLETE 0x02 /* Completed last byte */
|
||||
|
||||
#define ec_debug_ref(ec, fmt, ...) \
|
||||
do { \
|
||||
if (DEBUG_REF) \
|
||||
pr_debug("%lu: " fmt, ec->reference_count, \
|
||||
## __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/* ec.c is compiled in acpi namespace so this shows up as acpi.ec_delay param */
|
||||
static unsigned int ec_delay __read_mostly = ACPI_EC_DELAY;
|
||||
module_param(ec_delay, uint, 0644);
|
||||
|
@ -105,6 +118,7 @@ struct acpi_ec_query_handler {
|
|||
acpi_handle handle;
|
||||
void *data;
|
||||
u8 query_bit;
|
||||
struct kref kref;
|
||||
};
|
||||
|
||||
struct transaction {
|
||||
|
@ -117,8 +131,12 @@ struct transaction {
|
|||
u8 wlen;
|
||||
u8 rlen;
|
||||
u8 flags;
|
||||
unsigned long timestamp;
|
||||
};
|
||||
|
||||
static int acpi_ec_query(struct acpi_ec *ec, u8 *data);
|
||||
static void advance_transaction(struct acpi_ec *ec);
|
||||
|
||||
struct acpi_ec *boot_ec, *first_ec;
|
||||
EXPORT_SYMBOL(first_ec);
|
||||
|
||||
|
@ -129,7 +147,28 @@ static int EC_FLAGS_CLEAR_ON_RESUME; /* Needs acpi_ec_clear() on boot/resume */
|
|||
static int EC_FLAGS_QUERY_HANDSHAKE; /* Needs QR_EC issued when SCI_EVT set */
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Transaction Management
|
||||
* Device Flags
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
static bool acpi_ec_started(struct acpi_ec *ec)
|
||||
{
|
||||
return test_bit(EC_FLAGS_STARTED, &ec->flags) &&
|
||||
!test_bit(EC_FLAGS_STOPPED, &ec->flags);
|
||||
}
|
||||
|
||||
static bool acpi_ec_flushed(struct acpi_ec *ec)
|
||||
{
|
||||
return ec->reference_count == 1;
|
||||
}
|
||||
|
||||
static bool acpi_ec_has_pending_event(struct acpi_ec *ec)
|
||||
{
|
||||
return test_bit(EC_FLAGS_EVENT_DETECTED, &ec->flags) ||
|
||||
test_bit(EC_FLAGS_EVENT_PENDING, &ec->flags);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* EC Registers
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
static inline u8 acpi_ec_read_status(struct acpi_ec *ec)
|
||||
|
@ -151,6 +190,7 @@ static inline u8 acpi_ec_read_data(struct acpi_ec *ec)
|
|||
{
|
||||
u8 x = inb(ec->data_addr);
|
||||
|
||||
ec->curr->timestamp = jiffies;
|
||||
pr_debug("EC_DATA(R) = 0x%2.2x\n", x);
|
||||
return x;
|
||||
}
|
||||
|
@ -159,12 +199,14 @@ static inline void acpi_ec_write_cmd(struct acpi_ec *ec, u8 command)
|
|||
{
|
||||
pr_debug("EC_SC(W) = 0x%2.2x\n", command);
|
||||
outb(command, ec->command_addr);
|
||||
ec->curr->timestamp = jiffies;
|
||||
}
|
||||
|
||||
static inline void acpi_ec_write_data(struct acpi_ec *ec, u8 data)
|
||||
{
|
||||
pr_debug("EC_DATA(W) = 0x%2.2x\n", data);
|
||||
outb(data, ec->data_addr);
|
||||
ec->curr->timestamp = jiffies;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
@ -188,6 +230,203 @@ static const char *acpi_ec_cmd_string(u8 cmd)
|
|||
#define acpi_ec_cmd_string(cmd) "UNDEF"
|
||||
#endif
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* GPE Registers
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
static inline bool acpi_ec_is_gpe_raised(struct acpi_ec *ec)
|
||||
{
|
||||
acpi_event_status gpe_status = 0;
|
||||
|
||||
(void)acpi_get_gpe_status(NULL, ec->gpe, &gpe_status);
|
||||
return (gpe_status & ACPI_EVENT_FLAG_SET) ? true : false;
|
||||
}
|
||||
|
||||
static inline void acpi_ec_enable_gpe(struct acpi_ec *ec, bool open)
|
||||
{
|
||||
if (open)
|
||||
acpi_enable_gpe(NULL, ec->gpe);
|
||||
else {
|
||||
BUG_ON(ec->reference_count < 1);
|
||||
acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_ENABLE);
|
||||
}
|
||||
if (acpi_ec_is_gpe_raised(ec)) {
|
||||
/*
|
||||
* On some platforms, EN=1 writes cannot trigger GPE. So
|
||||
* software need to manually trigger a pseudo GPE event on
|
||||
* EN=1 writes.
|
||||
*/
|
||||
pr_debug("***** Polling quirk *****\n");
|
||||
advance_transaction(ec);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void acpi_ec_disable_gpe(struct acpi_ec *ec, bool close)
|
||||
{
|
||||
if (close)
|
||||
acpi_disable_gpe(NULL, ec->gpe);
|
||||
else {
|
||||
BUG_ON(ec->reference_count < 1);
|
||||
acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_DISABLE);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void acpi_ec_clear_gpe(struct acpi_ec *ec)
|
||||
{
|
||||
/*
|
||||
* GPE STS is a W1C register, which means:
|
||||
* 1. Software can clear it without worrying about clearing other
|
||||
* GPEs' STS bits when the hardware sets them in parallel.
|
||||
* 2. As long as software can ensure only clearing it when it is
|
||||
* set, hardware won't set it in parallel.
|
||||
* So software can clear GPE in any contexts.
|
||||
* Warning: do not move the check into advance_transaction() as the
|
||||
* EC commands will be sent without GPE raised.
|
||||
*/
|
||||
if (!acpi_ec_is_gpe_raised(ec))
|
||||
return;
|
||||
acpi_clear_gpe(NULL, ec->gpe);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Transaction Management
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
static void acpi_ec_submit_request(struct acpi_ec *ec)
|
||||
{
|
||||
ec->reference_count++;
|
||||
if (ec->reference_count == 1)
|
||||
acpi_ec_enable_gpe(ec, true);
|
||||
}
|
||||
|
||||
static void acpi_ec_complete_request(struct acpi_ec *ec)
|
||||
{
|
||||
bool flushed = false;
|
||||
|
||||
ec->reference_count--;
|
||||
if (ec->reference_count == 0)
|
||||
acpi_ec_disable_gpe(ec, true);
|
||||
flushed = acpi_ec_flushed(ec);
|
||||
if (flushed)
|
||||
wake_up(&ec->wait);
|
||||
}
|
||||
|
||||
static void acpi_ec_set_storm(struct acpi_ec *ec, u8 flag)
|
||||
{
|
||||
if (!test_bit(flag, &ec->flags)) {
|
||||
acpi_ec_disable_gpe(ec, false);
|
||||
pr_debug("+++++ Polling enabled +++++\n");
|
||||
set_bit(flag, &ec->flags);
|
||||
}
|
||||
}
|
||||
|
||||
static void acpi_ec_clear_storm(struct acpi_ec *ec, u8 flag)
|
||||
{
|
||||
if (test_bit(flag, &ec->flags)) {
|
||||
clear_bit(flag, &ec->flags);
|
||||
acpi_ec_enable_gpe(ec, false);
|
||||
pr_debug("+++++ Polling disabled +++++\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* acpi_ec_submit_flushable_request() - Increase the reference count unless
|
||||
* the flush operation is not in
|
||||
* progress
|
||||
* @ec: the EC device
|
||||
* @allow_event: whether event should be handled
|
||||
*
|
||||
* This function must be used before taking a new action that should hold
|
||||
* the reference count. If this function returns false, then the action
|
||||
* must be discarded or it will prevent the flush operation from being
|
||||
* completed.
|
||||
*
|
||||
* During flushing, QR_EC command need to pass this check when there is a
|
||||
* pending event, so that the reference count held for the pending event
|
||||
* can be decreased by the completion of the QR_EC command.
|
||||
*/
|
||||
static bool acpi_ec_submit_flushable_request(struct acpi_ec *ec,
|
||||
bool allow_event)
|
||||
{
|
||||
if (!acpi_ec_started(ec)) {
|
||||
if (!allow_event || !acpi_ec_has_pending_event(ec))
|
||||
return false;
|
||||
}
|
||||
acpi_ec_submit_request(ec);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void acpi_ec_submit_event(struct acpi_ec *ec)
|
||||
{
|
||||
if (!test_bit(EC_FLAGS_EVENT_DETECTED, &ec->flags) ||
|
||||
!test_bit(EC_FLAGS_EVENT_ENABLED, &ec->flags))
|
||||
return;
|
||||
/* Hold reference for pending event */
|
||||
if (!acpi_ec_submit_flushable_request(ec, true))
|
||||
return;
|
||||
ec_debug_ref(ec, "Increase event\n");
|
||||
if (!test_and_set_bit(EC_FLAGS_EVENT_PENDING, &ec->flags)) {
|
||||
pr_debug("***** Event query started *****\n");
|
||||
schedule_work(&ec->work);
|
||||
return;
|
||||
}
|
||||
acpi_ec_complete_request(ec);
|
||||
ec_debug_ref(ec, "Decrease event\n");
|
||||
}
|
||||
|
||||
static void acpi_ec_complete_event(struct acpi_ec *ec)
|
||||
{
|
||||
if (ec->curr->command == ACPI_EC_COMMAND_QUERY) {
|
||||
clear_bit(EC_FLAGS_EVENT_PENDING, &ec->flags);
|
||||
pr_debug("***** Event query stopped *****\n");
|
||||
/* Unhold reference for pending event */
|
||||
acpi_ec_complete_request(ec);
|
||||
ec_debug_ref(ec, "Decrease event\n");
|
||||
/* Check if there is another SCI_EVT detected */
|
||||
acpi_ec_submit_event(ec);
|
||||
}
|
||||
}
|
||||
|
||||
static void acpi_ec_submit_detection(struct acpi_ec *ec)
|
||||
{
|
||||
/* Hold reference for query submission */
|
||||
if (!acpi_ec_submit_flushable_request(ec, false))
|
||||
return;
|
||||
ec_debug_ref(ec, "Increase query\n");
|
||||
if (!test_and_set_bit(EC_FLAGS_EVENT_DETECTED, &ec->flags)) {
|
||||
pr_debug("***** Event detection blocked *****\n");
|
||||
acpi_ec_submit_event(ec);
|
||||
return;
|
||||
}
|
||||
acpi_ec_complete_request(ec);
|
||||
ec_debug_ref(ec, "Decrease query\n");
|
||||
}
|
||||
|
||||
static void acpi_ec_complete_detection(struct acpi_ec *ec)
|
||||
{
|
||||
if (ec->curr->command == ACPI_EC_COMMAND_QUERY) {
|
||||
clear_bit(EC_FLAGS_EVENT_DETECTED, &ec->flags);
|
||||
pr_debug("***** Event detetion unblocked *****\n");
|
||||
/* Unhold reference for query submission */
|
||||
acpi_ec_complete_request(ec);
|
||||
ec_debug_ref(ec, "Decrease query\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void acpi_ec_enable_event(struct acpi_ec *ec)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ec->lock, flags);
|
||||
set_bit(EC_FLAGS_EVENT_ENABLED, &ec->flags);
|
||||
/*
|
||||
* An event may be pending even with SCI_EVT=0, so QR_EC should
|
||||
* always be issued right after started.
|
||||
*/
|
||||
acpi_ec_submit_detection(ec);
|
||||
spin_unlock_irqrestore(&ec->lock, flags);
|
||||
}
|
||||
|
||||
static int ec_transaction_completed(struct acpi_ec *ec)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
@ -200,7 +439,7 @@ static int ec_transaction_completed(struct acpi_ec *ec)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool advance_transaction(struct acpi_ec *ec)
|
||||
static void advance_transaction(struct acpi_ec *ec)
|
||||
{
|
||||
struct transaction *t;
|
||||
u8 status;
|
||||
|
@ -208,6 +447,12 @@ static bool advance_transaction(struct acpi_ec *ec)
|
|||
|
||||
pr_debug("===== %s (%d) =====\n",
|
||||
in_interrupt() ? "IRQ" : "TASK", smp_processor_id());
|
||||
/*
|
||||
* By always clearing STS before handling all indications, we can
|
||||
* ensure a hardware STS 0->1 change after this clearing can always
|
||||
* trigger a GPE interrupt.
|
||||
*/
|
||||
acpi_ec_clear_gpe(ec);
|
||||
status = acpi_ec_read_status(ec);
|
||||
t = ec->curr;
|
||||
if (!t)
|
||||
|
@ -223,6 +468,7 @@ static bool advance_transaction(struct acpi_ec *ec)
|
|||
t->rdata[t->ri++] = acpi_ec_read_data(ec);
|
||||
if (t->rlen == t->ri) {
|
||||
t->flags |= ACPI_EC_COMMAND_COMPLETE;
|
||||
acpi_ec_complete_event(ec);
|
||||
if (t->command == ACPI_EC_COMMAND_QUERY)
|
||||
pr_debug("***** Command(%s) hardware completion *****\n",
|
||||
acpi_ec_cmd_string(t->command));
|
||||
|
@ -233,25 +479,29 @@ static bool advance_transaction(struct acpi_ec *ec)
|
|||
} else if (t->wlen == t->wi &&
|
||||
(status & ACPI_EC_FLAG_IBF) == 0) {
|
||||
t->flags |= ACPI_EC_COMMAND_COMPLETE;
|
||||
acpi_ec_complete_event(ec);
|
||||
wakeup = true;
|
||||
}
|
||||
return wakeup;
|
||||
goto out;
|
||||
} else {
|
||||
if (EC_FLAGS_QUERY_HANDSHAKE &&
|
||||
!(status & ACPI_EC_FLAG_SCI) &&
|
||||
(t->command == ACPI_EC_COMMAND_QUERY)) {
|
||||
t->flags |= ACPI_EC_COMMAND_POLL;
|
||||
acpi_ec_complete_detection(ec);
|
||||
t->rdata[t->ri++] = 0x00;
|
||||
t->flags |= ACPI_EC_COMMAND_COMPLETE;
|
||||
acpi_ec_complete_event(ec);
|
||||
pr_debug("***** Command(%s) software completion *****\n",
|
||||
acpi_ec_cmd_string(t->command));
|
||||
wakeup = true;
|
||||
} else if ((status & ACPI_EC_FLAG_IBF) == 0) {
|
||||
acpi_ec_write_cmd(ec, t->command);
|
||||
t->flags |= ACPI_EC_COMMAND_POLL;
|
||||
acpi_ec_complete_detection(ec);
|
||||
} else
|
||||
goto err;
|
||||
return wakeup;
|
||||
goto out;
|
||||
}
|
||||
err:
|
||||
/*
|
||||
|
@ -259,28 +509,27 @@ err:
|
|||
* otherwise will take a not handled IRQ as a false one.
|
||||
*/
|
||||
if (!(status & ACPI_EC_FLAG_SCI)) {
|
||||
if (in_interrupt() && t)
|
||||
++t->irq_count;
|
||||
if (in_interrupt() && t) {
|
||||
if (t->irq_count < ec_storm_threshold)
|
||||
++t->irq_count;
|
||||
/* Allow triggering on 0 threshold */
|
||||
if (t->irq_count == ec_storm_threshold)
|
||||
acpi_ec_set_storm(ec, EC_FLAGS_COMMAND_STORM);
|
||||
}
|
||||
}
|
||||
return wakeup;
|
||||
out:
|
||||
if (status & ACPI_EC_FLAG_SCI)
|
||||
acpi_ec_submit_detection(ec);
|
||||
if (wakeup && in_interrupt())
|
||||
wake_up(&ec->wait);
|
||||
}
|
||||
|
||||
static void start_transaction(struct acpi_ec *ec)
|
||||
{
|
||||
ec->curr->irq_count = ec->curr->wi = ec->curr->ri = 0;
|
||||
ec->curr->flags = 0;
|
||||
(void)advance_transaction(ec);
|
||||
}
|
||||
|
||||
static int acpi_ec_sync_query(struct acpi_ec *ec, u8 *data);
|
||||
|
||||
static int ec_check_sci_sync(struct acpi_ec *ec, u8 state)
|
||||
{
|
||||
if (state & ACPI_EC_FLAG_SCI) {
|
||||
if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags))
|
||||
return acpi_ec_sync_query(ec, NULL);
|
||||
}
|
||||
return 0;
|
||||
ec->curr->timestamp = jiffies;
|
||||
advance_transaction(ec);
|
||||
}
|
||||
|
||||
static int ec_poll(struct acpi_ec *ec)
|
||||
|
@ -291,20 +540,25 @@ static int ec_poll(struct acpi_ec *ec)
|
|||
while (repeat--) {
|
||||
unsigned long delay = jiffies +
|
||||
msecs_to_jiffies(ec_delay);
|
||||
unsigned long usecs = ACPI_EC_UDELAY_POLL;
|
||||
do {
|
||||
/* don't sleep with disabled interrupts */
|
||||
if (EC_FLAGS_MSI || irqs_disabled()) {
|
||||
udelay(ACPI_EC_MSI_UDELAY);
|
||||
usecs = ACPI_EC_MSI_UDELAY;
|
||||
udelay(usecs);
|
||||
if (ec_transaction_completed(ec))
|
||||
return 0;
|
||||
} else {
|
||||
if (wait_event_timeout(ec->wait,
|
||||
ec_transaction_completed(ec),
|
||||
msecs_to_jiffies(1)))
|
||||
usecs_to_jiffies(usecs)))
|
||||
return 0;
|
||||
}
|
||||
spin_lock_irqsave(&ec->lock, flags);
|
||||
(void)advance_transaction(ec);
|
||||
if (time_after(jiffies,
|
||||
ec->curr->timestamp +
|
||||
usecs_to_jiffies(usecs)))
|
||||
advance_transaction(ec);
|
||||
spin_unlock_irqrestore(&ec->lock, flags);
|
||||
} while (time_before(jiffies, delay));
|
||||
pr_debug("controller reset, restart transaction\n");
|
||||
|
@ -325,21 +579,29 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec,
|
|||
udelay(ACPI_EC_MSI_UDELAY);
|
||||
/* start transaction */
|
||||
spin_lock_irqsave(&ec->lock, tmp);
|
||||
/* Enable GPE for command processing (IBF=0/OBF=1) */
|
||||
if (!acpi_ec_submit_flushable_request(ec, true)) {
|
||||
ret = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
ec_debug_ref(ec, "Increase command\n");
|
||||
/* following two actions should be kept atomic */
|
||||
ec->curr = t;
|
||||
pr_debug("***** Command(%s) started *****\n",
|
||||
acpi_ec_cmd_string(t->command));
|
||||
start_transaction(ec);
|
||||
if (ec->curr->command == ACPI_EC_COMMAND_QUERY) {
|
||||
clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags);
|
||||
pr_debug("***** Event stopped *****\n");
|
||||
}
|
||||
spin_unlock_irqrestore(&ec->lock, tmp);
|
||||
ret = ec_poll(ec);
|
||||
spin_lock_irqsave(&ec->lock, tmp);
|
||||
if (t->irq_count == ec_storm_threshold)
|
||||
acpi_ec_clear_storm(ec, EC_FLAGS_COMMAND_STORM);
|
||||
pr_debug("***** Command(%s) stopped *****\n",
|
||||
acpi_ec_cmd_string(t->command));
|
||||
ec->curr = NULL;
|
||||
/* Disable GPE for command processing (IBF=0/OBF=1) */
|
||||
acpi_ec_complete_request(ec);
|
||||
ec_debug_ref(ec, "Decrease command\n");
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&ec->lock, tmp);
|
||||
return ret;
|
||||
}
|
||||
|
@ -354,10 +616,6 @@ static int acpi_ec_transaction(struct acpi_ec *ec, struct transaction *t)
|
|||
if (t->rdata)
|
||||
memset(t->rdata, 0, t->rlen);
|
||||
mutex_lock(&ec->mutex);
|
||||
if (test_bit(EC_FLAGS_BLOCKED, &ec->flags)) {
|
||||
status = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
if (ec->global_lock) {
|
||||
status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
|
@ -365,26 +623,11 @@ static int acpi_ec_transaction(struct acpi_ec *ec, struct transaction *t)
|
|||
goto unlock;
|
||||
}
|
||||
}
|
||||
/* disable GPE during transaction if storm is detected */
|
||||
if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) {
|
||||
/* It has to be disabled, so that it doesn't trigger. */
|
||||
acpi_disable_gpe(NULL, ec->gpe);
|
||||
}
|
||||
|
||||
status = acpi_ec_transaction_unlocked(ec, t);
|
||||
|
||||
/* check if we received SCI during transaction */
|
||||
ec_check_sci_sync(ec, acpi_ec_read_status(ec));
|
||||
if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) {
|
||||
if (test_bit(EC_FLAGS_COMMAND_STORM, &ec->flags))
|
||||
msleep(1);
|
||||
/* It is safe to enable the GPE outside of the transaction. */
|
||||
acpi_enable_gpe(NULL, ec->gpe);
|
||||
} else if (t->irq_count > ec_storm_threshold) {
|
||||
pr_info("GPE storm detected(%d GPEs), "
|
||||
"transactions will use polling mode\n",
|
||||
t->irq_count);
|
||||
set_bit(EC_FLAGS_GPE_STORM, &ec->flags);
|
||||
}
|
||||
if (ec->global_lock)
|
||||
acpi_release_global_lock(glk);
|
||||
unlock:
|
||||
|
@ -500,7 +743,7 @@ static void acpi_ec_clear(struct acpi_ec *ec)
|
|||
u8 value = 0;
|
||||
|
||||
for (i = 0; i < ACPI_EC_CLEAR_MAX; i++) {
|
||||
status = acpi_ec_sync_query(ec, &value);
|
||||
status = acpi_ec_query(ec, &value);
|
||||
if (status || !value)
|
||||
break;
|
||||
}
|
||||
|
@ -511,6 +754,57 @@ static void acpi_ec_clear(struct acpi_ec *ec)
|
|||
pr_info("%d stale EC events cleared\n", i);
|
||||
}
|
||||
|
||||
static void acpi_ec_start(struct acpi_ec *ec, bool resuming)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ec->lock, flags);
|
||||
if (!test_and_set_bit(EC_FLAGS_STARTED, &ec->flags)) {
|
||||
pr_debug("+++++ Starting EC +++++\n");
|
||||
/* Enable GPE for event processing (SCI_EVT=1) */
|
||||
if (!resuming) {
|
||||
acpi_ec_submit_request(ec);
|
||||
ec_debug_ref(ec, "Increase driver\n");
|
||||
}
|
||||
pr_info("+++++ EC started +++++\n");
|
||||
}
|
||||
spin_unlock_irqrestore(&ec->lock, flags);
|
||||
}
|
||||
|
||||
static bool acpi_ec_stopped(struct acpi_ec *ec)
|
||||
{
|
||||
unsigned long flags;
|
||||
bool flushed;
|
||||
|
||||
spin_lock_irqsave(&ec->lock, flags);
|
||||
flushed = acpi_ec_flushed(ec);
|
||||
spin_unlock_irqrestore(&ec->lock, flags);
|
||||
return flushed;
|
||||
}
|
||||
|
||||
static void acpi_ec_stop(struct acpi_ec *ec, bool suspending)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ec->lock, flags);
|
||||
if (acpi_ec_started(ec)) {
|
||||
pr_debug("+++++ Stopping EC +++++\n");
|
||||
set_bit(EC_FLAGS_STOPPED, &ec->flags);
|
||||
spin_unlock_irqrestore(&ec->lock, flags);
|
||||
wait_event(ec->wait, acpi_ec_stopped(ec));
|
||||
spin_lock_irqsave(&ec->lock, flags);
|
||||
/* Disable GPE for event processing (SCI_EVT=1) */
|
||||
if (!suspending) {
|
||||
acpi_ec_complete_request(ec);
|
||||
ec_debug_ref(ec, "Decrease driver\n");
|
||||
}
|
||||
clear_bit(EC_FLAGS_STARTED, &ec->flags);
|
||||
clear_bit(EC_FLAGS_STOPPED, &ec->flags);
|
||||
pr_info("+++++ EC stopped +++++\n");
|
||||
}
|
||||
spin_unlock_irqrestore(&ec->lock, flags);
|
||||
}
|
||||
|
||||
void acpi_ec_block_transactions(void)
|
||||
{
|
||||
struct acpi_ec *ec = first_ec;
|
||||
|
@ -520,7 +814,7 @@ void acpi_ec_block_transactions(void)
|
|||
|
||||
mutex_lock(&ec->mutex);
|
||||
/* Prevent transactions from being carried out */
|
||||
set_bit(EC_FLAGS_BLOCKED, &ec->flags);
|
||||
acpi_ec_stop(ec, true);
|
||||
mutex_unlock(&ec->mutex);
|
||||
}
|
||||
|
||||
|
@ -531,14 +825,11 @@ void acpi_ec_unblock_transactions(void)
|
|||
if (!ec)
|
||||
return;
|
||||
|
||||
mutex_lock(&ec->mutex);
|
||||
/* Allow transactions to be carried out again */
|
||||
clear_bit(EC_FLAGS_BLOCKED, &ec->flags);
|
||||
acpi_ec_start(ec, true);
|
||||
|
||||
if (EC_FLAGS_CLEAR_ON_RESUME)
|
||||
acpi_ec_clear(ec);
|
||||
|
||||
mutex_unlock(&ec->mutex);
|
||||
}
|
||||
|
||||
void acpi_ec_unblock_transactions_early(void)
|
||||
|
@ -548,36 +839,33 @@ void acpi_ec_unblock_transactions_early(void)
|
|||
* atomic context during wakeup, so we don't need to acquire the mutex).
|
||||
*/
|
||||
if (first_ec)
|
||||
clear_bit(EC_FLAGS_BLOCKED, &first_ec->flags);
|
||||
}
|
||||
|
||||
static int acpi_ec_query_unlocked(struct acpi_ec *ec, u8 *data)
|
||||
{
|
||||
int result;
|
||||
u8 d;
|
||||
struct transaction t = {.command = ACPI_EC_COMMAND_QUERY,
|
||||
.wdata = NULL, .rdata = &d,
|
||||
.wlen = 0, .rlen = 1};
|
||||
|
||||
if (!ec || !data)
|
||||
return -EINVAL;
|
||||
/*
|
||||
* Query the EC to find out which _Qxx method we need to evaluate.
|
||||
* Note that successful completion of the query causes the ACPI_EC_SCI
|
||||
* bit to be cleared (and thus clearing the interrupt source).
|
||||
*/
|
||||
result = acpi_ec_transaction_unlocked(ec, &t);
|
||||
if (result)
|
||||
return result;
|
||||
if (!d)
|
||||
return -ENODATA;
|
||||
*data = d;
|
||||
return 0;
|
||||
acpi_ec_start(first_ec, true);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
Event Management
|
||||
-------------------------------------------------------------------------- */
|
||||
static struct acpi_ec_query_handler *
|
||||
acpi_ec_get_query_handler(struct acpi_ec_query_handler *handler)
|
||||
{
|
||||
if (handler)
|
||||
kref_get(&handler->kref);
|
||||
return handler;
|
||||
}
|
||||
|
||||
static void acpi_ec_query_handler_release(struct kref *kref)
|
||||
{
|
||||
struct acpi_ec_query_handler *handler =
|
||||
container_of(kref, struct acpi_ec_query_handler, kref);
|
||||
|
||||
kfree(handler);
|
||||
}
|
||||
|
||||
static void acpi_ec_put_query_handler(struct acpi_ec_query_handler *handler)
|
||||
{
|
||||
kref_put(&handler->kref, acpi_ec_query_handler_release);
|
||||
}
|
||||
|
||||
int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit,
|
||||
acpi_handle handle, acpi_ec_query_func func,
|
||||
void *data)
|
||||
|
@ -593,6 +881,7 @@ int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit,
|
|||
handler->func = func;
|
||||
handler->data = data;
|
||||
mutex_lock(&ec->mutex);
|
||||
kref_init(&handler->kref);
|
||||
list_add(&handler->node, &ec->list);
|
||||
mutex_unlock(&ec->mutex);
|
||||
return 0;
|
||||
|
@ -602,15 +891,18 @@ EXPORT_SYMBOL_GPL(acpi_ec_add_query_handler);
|
|||
void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit)
|
||||
{
|
||||
struct acpi_ec_query_handler *handler, *tmp;
|
||||
LIST_HEAD(free_list);
|
||||
|
||||
mutex_lock(&ec->mutex);
|
||||
list_for_each_entry_safe(handler, tmp, &ec->list, node) {
|
||||
if (query_bit == handler->query_bit) {
|
||||
list_del(&handler->node);
|
||||
kfree(handler);
|
||||
list_del_init(&handler->node);
|
||||
list_add(&handler->node, &free_list);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ec->mutex);
|
||||
list_for_each_entry(handler, &free_list, node)
|
||||
acpi_ec_put_query_handler(handler);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_ec_remove_query_handler);
|
||||
|
||||
|
@ -626,59 +918,58 @@ static void acpi_ec_run(void *cxt)
|
|||
else if (handler->handle)
|
||||
acpi_evaluate_object(handler->handle, NULL, NULL, NULL);
|
||||
pr_debug("##### Query(0x%02x) stopped #####\n", handler->query_bit);
|
||||
kfree(handler);
|
||||
acpi_ec_put_query_handler(handler);
|
||||
}
|
||||
|
||||
static int acpi_ec_sync_query(struct acpi_ec *ec, u8 *data)
|
||||
static int acpi_ec_query(struct acpi_ec *ec, u8 *data)
|
||||
{
|
||||
u8 value = 0;
|
||||
int status;
|
||||
struct acpi_ec_query_handler *handler, *copy;
|
||||
int result;
|
||||
acpi_status status;
|
||||
struct acpi_ec_query_handler *handler;
|
||||
struct transaction t = {.command = ACPI_EC_COMMAND_QUERY,
|
||||
.wdata = NULL, .rdata = &value,
|
||||
.wlen = 0, .rlen = 1};
|
||||
|
||||
status = acpi_ec_query_unlocked(ec, &value);
|
||||
/*
|
||||
* Query the EC to find out which _Qxx method we need to evaluate.
|
||||
* Note that successful completion of the query causes the ACPI_EC_SCI
|
||||
* bit to be cleared (and thus clearing the interrupt source).
|
||||
*/
|
||||
result = acpi_ec_transaction(ec, &t);
|
||||
if (result)
|
||||
return result;
|
||||
if (data)
|
||||
*data = value;
|
||||
if (status)
|
||||
return status;
|
||||
if (!value)
|
||||
return -ENODATA;
|
||||
|
||||
mutex_lock(&ec->mutex);
|
||||
list_for_each_entry(handler, &ec->list, node) {
|
||||
if (value == handler->query_bit) {
|
||||
/* have custom handler for this bit */
|
||||
copy = kmalloc(sizeof(*handler), GFP_KERNEL);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
memcpy(copy, handler, sizeof(*copy));
|
||||
handler = acpi_ec_get_query_handler(handler);
|
||||
pr_debug("##### Query(0x%02x) scheduled #####\n",
|
||||
handler->query_bit);
|
||||
return acpi_os_execute((copy->func) ?
|
||||
status = acpi_os_execute((handler->func) ?
|
||||
OSL_NOTIFY_HANDLER : OSL_GPE_HANDLER,
|
||||
acpi_ec_run, copy);
|
||||
acpi_ec_run, handler);
|
||||
if (ACPI_FAILURE(status))
|
||||
result = -EBUSY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void acpi_ec_gpe_query(void *ec_cxt)
|
||||
{
|
||||
struct acpi_ec *ec = ec_cxt;
|
||||
|
||||
if (!ec)
|
||||
return;
|
||||
mutex_lock(&ec->mutex);
|
||||
acpi_ec_sync_query(ec, NULL);
|
||||
mutex_unlock(&ec->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int ec_check_sci(struct acpi_ec *ec, u8 state)
|
||||
static void acpi_ec_gpe_poller(struct work_struct *work)
|
||||
{
|
||||
if (state & ACPI_EC_FLAG_SCI) {
|
||||
if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) {
|
||||
pr_debug("***** Event started *****\n");
|
||||
return acpi_os_execute(OSL_NOTIFY_HANDLER,
|
||||
acpi_ec_gpe_query, ec);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
struct acpi_ec *ec = container_of(work, struct acpi_ec, work);
|
||||
|
||||
pr_debug("***** Event poller started *****\n");
|
||||
acpi_ec_query(ec, NULL);
|
||||
pr_debug("***** Event poller stopped *****\n");
|
||||
}
|
||||
|
||||
static u32 acpi_ec_gpe_handler(acpi_handle gpe_device,
|
||||
|
@ -688,11 +979,9 @@ static u32 acpi_ec_gpe_handler(acpi_handle gpe_device,
|
|||
struct acpi_ec *ec = data;
|
||||
|
||||
spin_lock_irqsave(&ec->lock, flags);
|
||||
if (advance_transaction(ec))
|
||||
wake_up(&ec->wait);
|
||||
advance_transaction(ec);
|
||||
spin_unlock_irqrestore(&ec->lock, flags);
|
||||
ec_check_sci(ec, acpi_ec_read_status(ec));
|
||||
return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE;
|
||||
return ACPI_INTERRUPT_HANDLED;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
|
@ -750,11 +1039,11 @@ static struct acpi_ec *make_acpi_ec(void)
|
|||
|
||||
if (!ec)
|
||||
return NULL;
|
||||
ec->flags = 1 << EC_FLAGS_QUERY_PENDING;
|
||||
mutex_init(&ec->mutex);
|
||||
init_waitqueue_head(&ec->wait);
|
||||
INIT_LIST_HEAD(&ec->list);
|
||||
spin_lock_init(&ec->lock);
|
||||
INIT_WORK(&ec->work, acpi_ec_gpe_poller);
|
||||
return ec;
|
||||
}
|
||||
|
||||
|
@ -810,13 +1099,13 @@ static int ec_install_handlers(struct acpi_ec *ec)
|
|||
|
||||
if (test_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags))
|
||||
return 0;
|
||||
status = acpi_install_gpe_handler(NULL, ec->gpe,
|
||||
status = acpi_install_gpe_raw_handler(NULL, ec->gpe,
|
||||
ACPI_GPE_EDGE_TRIGGERED,
|
||||
&acpi_ec_gpe_handler, ec);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -ENODEV;
|
||||
|
||||
acpi_enable_gpe(NULL, ec->gpe);
|
||||
acpi_ec_start(ec, false);
|
||||
status = acpi_install_address_space_handler(ec->handle,
|
||||
ACPI_ADR_SPACE_EC,
|
||||
&acpi_ec_space_handler,
|
||||
|
@ -831,7 +1120,7 @@ static int ec_install_handlers(struct acpi_ec *ec)
|
|||
pr_err("Fail in evaluating the _REG object"
|
||||
" of EC device. Broken bios is suspected.\n");
|
||||
} else {
|
||||
acpi_disable_gpe(NULL, ec->gpe);
|
||||
acpi_ec_stop(ec, false);
|
||||
acpi_remove_gpe_handler(NULL, ec->gpe,
|
||||
&acpi_ec_gpe_handler);
|
||||
return -ENODEV;
|
||||
|
@ -846,7 +1135,7 @@ static void ec_remove_handlers(struct acpi_ec *ec)
|
|||
{
|
||||
if (!test_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags))
|
||||
return;
|
||||
acpi_disable_gpe(NULL, ec->gpe);
|
||||
acpi_ec_stop(ec, false);
|
||||
if (ACPI_FAILURE(acpi_remove_address_space_handler(ec->handle,
|
||||
ACPI_ADR_SPACE_EC, &acpi_ec_space_handler)))
|
||||
pr_err("failed to remove space handler\n");
|
||||
|
@ -900,14 +1189,11 @@ static int acpi_ec_add(struct acpi_device *device)
|
|||
ret = ec_install_handlers(ec);
|
||||
|
||||
/* EC is fully operational, allow queries */
|
||||
clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags);
|
||||
acpi_ec_enable_event(ec);
|
||||
|
||||
/* Clear stale _Q events if hardware might require that */
|
||||
if (EC_FLAGS_CLEAR_ON_RESUME) {
|
||||
mutex_lock(&ec->mutex);
|
||||
if (EC_FLAGS_CLEAR_ON_RESUME)
|
||||
acpi_ec_clear(ec);
|
||||
mutex_unlock(&ec->mutex);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -122,11 +122,13 @@ struct acpi_ec {
|
|||
unsigned long data_addr;
|
||||
unsigned long global_lock;
|
||||
unsigned long flags;
|
||||
unsigned long reference_count;
|
||||
struct mutex mutex;
|
||||
wait_queue_head_t wait;
|
||||
struct list_head list;
|
||||
struct transaction *curr;
|
||||
spinlock_t lock;
|
||||
struct work_struct work;
|
||||
};
|
||||
|
||||
extern struct acpi_ec *first_ec;
|
||||
|
|
Loading…
Reference in New Issue