diff options
Diffstat (limited to 'drivers/acpi/button.c')
| -rw-r--r-- | drivers/acpi/button.c | 85 | 
1 files changed, 82 insertions, 3 deletions
diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 31abb0bdd4f2..e19f530f1083 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -19,6 +19,8 @@   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   */ +#define pr_fmt(fmt) "ACPI : button: " fmt +  #include <linux/kernel.h>  #include <linux/module.h>  #include <linux/init.h> @@ -104,6 +106,8 @@ struct acpi_button {  	struct input_dev *input;  	char phys[32];			/* for input device */  	unsigned long pushed; +	int last_state; +	ktime_t last_time;  	bool suspended;  }; @@ -111,6 +115,10 @@ static BLOCKING_NOTIFIER_HEAD(acpi_lid_notifier);  static struct acpi_device *lid_device;  static u8 lid_init_state = ACPI_BUTTON_LID_INIT_METHOD; +static unsigned long lid_report_interval __read_mostly = 500; +module_param(lid_report_interval, ulong, 0644); +MODULE_PARM_DESC(lid_report_interval, "Interval (ms) between lid key events"); +  /* --------------------------------------------------------------------------                                FS Interface (/proc)     -------------------------------------------------------------------------- */ @@ -134,10 +142,79 @@ static int acpi_lid_notify_state(struct acpi_device *device, int state)  {  	struct acpi_button *button = acpi_driver_data(device);  	int ret; +	ktime_t next_report; +	bool do_update; + +	/* +	 * In lid_init_state=ignore mode, if user opens/closes lid +	 * frequently with "open" missing, and "last_time" is also updated +	 * frequently, "close" cannot be delivered to the userspace. +	 * So "last_time" is only updated after a timeout or an actual +	 * switch. +	 */ +	if (lid_init_state != ACPI_BUTTON_LID_INIT_IGNORE || +	    button->last_state != !!state) +		do_update = true; +	else +		do_update = false; + +	next_report = ktime_add(button->last_time, +				ms_to_ktime(lid_report_interval)); +	if (button->last_state == !!state && +	    ktime_after(ktime_get(), next_report)) { +		/* Complain the buggy firmware */ +		pr_warn_once("The lid device is not compliant to SW_LID.\n"); -	/* input layer checks if event is redundant */ -	input_report_switch(button->input, SW_LID, !state); -	input_sync(button->input); +		/* +		 * Send the unreliable complement switch event: +		 * +		 * On most platforms, the lid device is reliable. However +		 * there are exceptions: +		 * 1. Platforms returning initial lid state as "close" by +		 *    default after booting/resuming: +		 *     https://bugzilla.kernel.org/show_bug.cgi?id=89211 +		 *     https://bugzilla.kernel.org/show_bug.cgi?id=106151 +		 * 2. Platforms never reporting "open" events: +		 *     https://bugzilla.kernel.org/show_bug.cgi?id=106941 +		 * On these buggy platforms, the usage model of the ACPI +		 * lid device actually is: +		 * 1. The initial returning value of _LID may not be +		 *    reliable. +		 * 2. The open event may not be reliable. +		 * 3. The close event is reliable. +		 * +		 * But SW_LID is typed as input switch event, the input +		 * layer checks if the event is redundant. Hence if the +		 * state is not switched, the userspace cannot see this +		 * platform triggered reliable event. By inserting a +		 * complement switch event, it then is guaranteed that the +		 * platform triggered reliable one can always be seen by +		 * the userspace. +		 */ +		if (lid_init_state == ACPI_BUTTON_LID_INIT_IGNORE) { +			do_update = true; +			/* +			 * Do generate complement switch event for "close" +			 * as "close" is reliable and wrong "open" won't +			 * trigger unexpected behaviors. +			 * Do not generate complement switch event for +			 * "open" as "open" is not reliable and wrong +			 * "close" will trigger unexpected behaviors. +			 */ +			if (!state) { +				input_report_switch(button->input, +						    SW_LID, state); +				input_sync(button->input); +			} +		} +	} +	/* Send the platform triggered reliable event */ +	if (do_update) { +		input_report_switch(button->input, SW_LID, !state); +		input_sync(button->input); +		button->last_state = !!state; +		button->last_time = ktime_get(); +	}  	if (state)  		pm_wakeup_event(&device->dev, 0); @@ -411,6 +488,8 @@ static int acpi_button_add(struct acpi_device *device)  		strcpy(name, ACPI_BUTTON_DEVICE_NAME_LID);  		sprintf(class, "%s/%s",  			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_LID); +		button->last_state = !!acpi_lid_evaluate_state(device); +		button->last_time = ktime_get();  	} else {  		printk(KERN_ERR PREFIX "Unsupported hid [%s]\n", hid);  		error = -ENODEV;  |