#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/spinlock.h>
+#include <linux/slab.h>
#include <asm/io.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
enum {
EC_FLAGS_QUERY_PENDING, /* Query is pending */
EC_FLAGS_GPE_STORM, /* GPE storm detected */
- EC_FLAGS_HANDLERS_INSTALLED /* Handlers for GPE and
+ EC_FLAGS_HANDLERS_INSTALLED, /* Handlers for GPE and
* OpReg are installed */
+ EC_FLAGS_BLOCKED, /* Transactions are blocked */
};
/* If we find an EC via the ECDT, we need to keep a ptr to its context */
if (t->rdata)
memset(t->rdata, 0, t->rlen);
mutex_lock(&ec->lock);
+ 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)) {
pr_debug(PREFIX "transaction start\n");
/* disable GPE during transaction if storm is detected */
if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) {
+ /*
+ * It has to be disabled at the hardware level regardless of the
+ * GPE reference counting, so that it doesn't trigger.
+ */
acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_DISABLE);
}
ec_check_sci_sync(ec, acpi_ec_read_status(ec));
if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) {
msleep(1);
- /* it is safe to enable GPE outside of transaction */
+ /*
+ * It is safe to enable the GPE outside of the transaction. Use
+ * acpi_set_gpe() for that, since we used it to disable the GPE
+ * above.
+ */
acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_ENABLE);
} else if (t->irq_count > ACPI_EC_STORM_THRESHOLD) {
pr_info(PREFIX "GPE storm detected, "
EXPORT_SYMBOL(ec_transaction);
+void acpi_ec_block_transactions(void)
+{
+ struct acpi_ec *ec = first_ec;
+
+ if (!ec)
+ return;
+
+ mutex_lock(&ec->lock);
+ /* Prevent transactions from being carried out */
+ set_bit(EC_FLAGS_BLOCKED, &ec->flags);
+ mutex_unlock(&ec->lock);
+}
+
+void acpi_ec_unblock_transactions(void)
+{
+ struct acpi_ec *ec = first_ec;
+
+ if (!ec)
+ return;
+
+ mutex_lock(&ec->lock);
+ /* Allow transactions to be carried out again */
+ clear_bit(EC_FLAGS_BLOCKED, &ec->flags);
+ mutex_unlock(&ec->lock);
+}
+
+void acpi_ec_unblock_transactions_early(void)
+{
+ /*
+ * Allow transactions to happen again (this function is called from
+ * 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;
static acpi_status
acpi_ec_space_handler(u32 function, acpi_physical_address address,
- u32 bits, acpi_integer *value,
+ u32 bits, u64 *value64,
void *handler_context, void *region_context)
{
struct acpi_ec *ec = handler_context;
- int result = 0, i;
- u8 temp = 0;
+ int result = 0, i, bytes = bits / 8;
+ u8 *value = (u8 *)value64;
if ((address > 0xFF) || !value || !handler_context)
return AE_BAD_PARAMETER;
if (function != ACPI_READ && function != ACPI_WRITE)
return AE_BAD_PARAMETER;
- if (bits != 8 && acpi_strict)
- return AE_BAD_PARAMETER;
-
- if (EC_FLAGS_MSI)
+ if (EC_FLAGS_MSI || bits > 8)
acpi_ec_burst_enable(ec);
- if (function == ACPI_READ) {
- result = acpi_ec_read(ec, address, &temp);
- *value = temp;
- } else {
- temp = 0xff & (*value);
- result = acpi_ec_write(ec, address, temp);
- }
-
- for (i = 8; unlikely(bits - i > 0); i += 8) {
- ++address;
- if (function == ACPI_READ) {
- result = acpi_ec_read(ec, address, &temp);
- (*value) |= ((acpi_integer)temp) << i;
- } else {
- temp = 0xff & ((*value) >> i);
- result = acpi_ec_write(ec, address, temp);
- }
- }
+ for (i = 0; i < bytes; ++i, ++address, ++value)
+ result = (function == ACPI_READ) ?
+ acpi_ec_read(ec, address, value) :
+ acpi_ec_write(ec, address, *value);
- if (EC_FLAGS_MSI)
+ if (EC_FLAGS_MSI || bits > 8)
acpi_ec_burst_disable(ec);
switch (result) {
/* Don't trust ECDT, which comes from ASUSTek */
if (!EC_FLAGS_VALIDATE_ECDT)
goto install;
- saved_ec = kmalloc(sizeof(struct acpi_ec), GFP_KERNEL);
+ saved_ec = kmemdup(boot_ec, sizeof(struct acpi_ec), GFP_KERNEL);
if (!saved_ec)
return -ENOMEM;
- memcpy(saved_ec, boot_ec, sizeof(struct acpi_ec));
/* fall through */
}
static int acpi_ec_suspend(struct acpi_device *device, pm_message_t state)
{
struct acpi_ec *ec = acpi_driver_data(device);
- /* Stop using GPE */
+ /* Stop using the GPE, but keep it reference counted. */
acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_DISABLE);
return 0;
}
static int acpi_ec_resume(struct acpi_device *device)
{
struct acpi_ec *ec = acpi_driver_data(device);
- /* Enable use of GPE back */
+ /* Enable the GPE again, but don't reference count it once more. */
acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_ENABLE);
return 0;
}