forked from mirrors/linux
		
	ACPI: EC: Rework flushing of EC work while suspended to idle
The flushing of pending work in the EC driver uses drain_workqueue()
to flush the event handling work that can requeue itself via
advance_transaction(), but this is problematic, because that
work may also be requeued from the query workqueue.
Namely, if an EC transaction is carried out during the execution of
a query handler, it involves calling advance_transaction() which
may queue up the event handling work again.  This causes the kernel
to complain about attempts to add a work item to the EC event
workqueue while it is being drained and worst-case it may cause a
valid event to be skipped.
To avoid this problem, introduce two new counters, events_in_progress
and queries_in_progress, incremented when a work item is queued on
the event workqueue or the query workqueue, respectively, and
decremented at the end of the corresponding work function, and make
acpi_ec_dispatch_gpe() the workqueues in a loop until the both of
these counters are zero (or system wakeup is pending) instead of
calling acpi_ec_flush_work().
At the same time, change __acpi_ec_flush_work() to call
flush_workqueue() instead of drain_workqueue() to flush the event
workqueue.
While at it, use the observation that the work item queued in
acpi_ec_query() cannot be pending at that time, because it is used
only once, to simplify the code in there.
Additionally, clean up a comment in acpi_ec_query() and adjust white
space in acpi_ec_event_processor().
Fixes: f0ac20c3f6 ("ACPI: EC: Fix flushing of pending work")
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
			
			
This commit is contained in:
		
							parent
							
								
									d58071a8a7
								
							
						
					
					
						commit
						4a9af6cac0
					
				
					 2 changed files with 45 additions and 14 deletions
				
			
		|  | @ -166,6 +166,7 @@ struct acpi_ec_query { | |||
| 	struct transaction transaction; | ||||
| 	struct work_struct work; | ||||
| 	struct acpi_ec_query_handler *handler; | ||||
| 	struct acpi_ec *ec; | ||||
| }; | ||||
| 
 | ||||
| static int acpi_ec_query(struct acpi_ec *ec, u8 *data); | ||||
|  | @ -452,6 +453,7 @@ static void acpi_ec_submit_query(struct acpi_ec *ec) | |||
| 		ec_dbg_evt("Command(%s) submitted/blocked", | ||||
| 			   acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY)); | ||||
| 		ec->nr_pending_queries++; | ||||
| 		ec->events_in_progress++; | ||||
| 		queue_work(ec_wq, &ec->work); | ||||
| 	} | ||||
| } | ||||
|  | @ -518,7 +520,7 @@ static void acpi_ec_enable_event(struct acpi_ec *ec) | |||
| #ifdef CONFIG_PM_SLEEP | ||||
| static void __acpi_ec_flush_work(void) | ||||
| { | ||||
| 	drain_workqueue(ec_wq); /* flush ec->work */ | ||||
| 	flush_workqueue(ec_wq); /* flush ec->work */ | ||||
| 	flush_workqueue(ec_query_wq); /* flush queries */ | ||||
| } | ||||
| 
 | ||||
|  | @ -1103,7 +1105,7 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit) | |||
| } | ||||
| EXPORT_SYMBOL_GPL(acpi_ec_remove_query_handler); | ||||
| 
 | ||||
| static struct acpi_ec_query *acpi_ec_create_query(u8 *pval) | ||||
| static struct acpi_ec_query *acpi_ec_create_query(struct acpi_ec *ec, u8 *pval) | ||||
| { | ||||
| 	struct acpi_ec_query *q; | ||||
| 	struct transaction *t; | ||||
|  | @ -1111,11 +1113,13 @@ static struct acpi_ec_query *acpi_ec_create_query(u8 *pval) | |||
| 	q = kzalloc(sizeof (struct acpi_ec_query), GFP_KERNEL); | ||||
| 	if (!q) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	INIT_WORK(&q->work, acpi_ec_event_processor); | ||||
| 	t = &q->transaction; | ||||
| 	t->command = ACPI_EC_COMMAND_QUERY; | ||||
| 	t->rdata = pval; | ||||
| 	t->rlen = 1; | ||||
| 	q->ec = ec; | ||||
| 	return q; | ||||
| } | ||||
| 
 | ||||
|  | @ -1132,13 +1136,21 @@ static void acpi_ec_event_processor(struct work_struct *work) | |||
| { | ||||
| 	struct acpi_ec_query *q = container_of(work, struct acpi_ec_query, work); | ||||
| 	struct acpi_ec_query_handler *handler = q->handler; | ||||
| 	struct acpi_ec *ec = q->ec; | ||||
| 
 | ||||
| 	ec_dbg_evt("Query(0x%02x) started", handler->query_bit); | ||||
| 
 | ||||
| 	if (handler->func) | ||||
| 		handler->func(handler->data); | ||||
| 	else if (handler->handle) | ||||
| 		acpi_evaluate_object(handler->handle, NULL, NULL, NULL); | ||||
| 
 | ||||
| 	ec_dbg_evt("Query(0x%02x) stopped", handler->query_bit); | ||||
| 
 | ||||
| 	spin_lock_irq(&ec->lock); | ||||
| 	ec->queries_in_progress--; | ||||
| 	spin_unlock_irq(&ec->lock); | ||||
| 
 | ||||
| 	acpi_ec_delete_query(q); | ||||
| } | ||||
| 
 | ||||
|  | @ -1148,7 +1160,7 @@ static int acpi_ec_query(struct acpi_ec *ec, u8 *data) | |||
| 	int result; | ||||
| 	struct acpi_ec_query *q; | ||||
| 
 | ||||
| 	q = acpi_ec_create_query(&value); | ||||
| 	q = acpi_ec_create_query(ec, &value); | ||||
| 	if (!q) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
|  | @ -1170,19 +1182,20 @@ static int acpi_ec_query(struct acpi_ec *ec, u8 *data) | |||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * It is reported that _Qxx are evaluated in a parallel way on | ||||
| 	 * Windows: | ||||
| 	 * It is reported that _Qxx are evaluated in a parallel way on Windows: | ||||
| 	 * https://bugzilla.kernel.org/show_bug.cgi?id=94411
 | ||||
| 	 * | ||||
| 	 * Put this log entry before schedule_work() in order to make | ||||
| 	 * it appearing before any other log entries occurred during the | ||||
| 	 * work queue execution. | ||||
| 	 * Put this log entry before queue_work() to make it appear in the log | ||||
| 	 * before any other messages emitted during workqueue handling. | ||||
| 	 */ | ||||
| 	ec_dbg_evt("Query(0x%02x) scheduled", value); | ||||
| 	if (!queue_work(ec_query_wq, &q->work)) { | ||||
| 		ec_dbg_evt("Query(0x%02x) overlapped", value); | ||||
| 		result = -EBUSY; | ||||
| 	} | ||||
| 
 | ||||
| 	spin_lock_irq(&ec->lock); | ||||
| 
 | ||||
| 	ec->queries_in_progress++; | ||||
| 	queue_work(ec_query_wq, &q->work); | ||||
| 
 | ||||
| 	spin_unlock_irq(&ec->lock); | ||||
| 
 | ||||
| err_exit: | ||||
| 	if (result) | ||||
|  | @ -1240,6 +1253,10 @@ static void acpi_ec_event_handler(struct work_struct *work) | |||
| 	ec_dbg_evt("Event stopped"); | ||||
| 
 | ||||
| 	acpi_ec_check_event(ec); | ||||
| 
 | ||||
| 	spin_lock_irqsave(&ec->lock, flags); | ||||
| 	ec->events_in_progress--; | ||||
| 	spin_unlock_irqrestore(&ec->lock, flags); | ||||
| } | ||||
| 
 | ||||
| static void acpi_ec_handle_interrupt(struct acpi_ec *ec) | ||||
|  | @ -2021,6 +2038,7 @@ void acpi_ec_set_gpe_wake_mask(u8 action) | |||
| 
 | ||||
| bool acpi_ec_dispatch_gpe(void) | ||||
| { | ||||
| 	bool work_in_progress; | ||||
| 	u32 ret; | ||||
| 
 | ||||
| 	if (!first_ec) | ||||
|  | @ -2041,8 +2059,19 @@ bool acpi_ec_dispatch_gpe(void) | |||
| 	if (ret == ACPI_INTERRUPT_HANDLED) | ||||
| 		pm_pr_dbg("ACPI EC GPE dispatched\n"); | ||||
| 
 | ||||
| 	/* Flush the event and query workqueues. */ | ||||
| 	acpi_ec_flush_work(); | ||||
| 	/* Drain EC work. */ | ||||
| 	do { | ||||
| 		acpi_ec_flush_work(); | ||||
| 
 | ||||
| 		pm_pr_dbg("ACPI EC work flushed\n"); | ||||
| 
 | ||||
| 		spin_lock_irq(&first_ec->lock); | ||||
| 
 | ||||
| 		work_in_progress = first_ec->events_in_progress + | ||||
| 			first_ec->queries_in_progress > 0; | ||||
| 
 | ||||
| 		spin_unlock_irq(&first_ec->lock); | ||||
| 	} while (work_in_progress && !pm_wakeup_pending()); | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
|  |  | |||
|  | @ -183,6 +183,8 @@ struct acpi_ec { | |||
| 	struct work_struct work; | ||||
| 	unsigned long timestamp; | ||||
| 	unsigned long nr_pending_queries; | ||||
| 	unsigned int events_in_progress; | ||||
| 	unsigned int queries_in_progress; | ||||
| 	bool busy_polling; | ||||
| 	unsigned int polling_guard; | ||||
| }; | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Rafael J. Wysocki
						Rafael J. Wysocki