forked from mirrors/linux
		
	ACPI / EC: Fix an issue caused by the serialized _Qxx evaluations
It is proven that Windows evaluates _Qxx handlers in a parallel way. This patch follows this fact, splits _Qxx evaluations from the NOTIFY queue to form a separate queue, so that _Qxx evaluations can be queued up on different CPUs rather than being queued up on a CPU0 bound queue. Event handling related callbacks are also renamed and sorted in this patch. Link: https://bugzilla.kernel.org/show_bug.cgi?id=94411 Reported-and-tested-by: Gabriele Mazzotta <gabriele.mzt@gmail.com> Signed-off-by: Lv Zheng <lv.zheng@intel.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
		
							parent
							
								
									c13dcf9f2d
								
							
						
					
					
						commit
						02b771b64b
					
				
					 1 changed files with 60 additions and 22 deletions
				
			
		|  | @ -165,8 +165,16 @@ struct transaction { | |||
| 	u8 flags; | ||||
| }; | ||||
| 
 | ||||
| struct acpi_ec_query { | ||||
| 	struct transaction transaction; | ||||
| 	struct work_struct work; | ||||
| 	struct acpi_ec_query_handler *handler; | ||||
| }; | ||||
| 
 | ||||
| static int acpi_ec_query(struct acpi_ec *ec, u8 *data); | ||||
| static void advance_transaction(struct acpi_ec *ec); | ||||
| static void acpi_ec_event_handler(struct work_struct *work); | ||||
| static void acpi_ec_event_processor(struct work_struct *work); | ||||
| 
 | ||||
| struct acpi_ec *boot_ec, *first_ec; | ||||
| EXPORT_SYMBOL(first_ec); | ||||
|  | @ -978,60 +986,90 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit) | |||
| } | ||||
| EXPORT_SYMBOL_GPL(acpi_ec_remove_query_handler); | ||||
| 
 | ||||
| static void acpi_ec_run(void *cxt) | ||||
| static struct acpi_ec_query *acpi_ec_create_query(u8 *pval) | ||||
| { | ||||
| 	struct acpi_ec_query_handler *handler = cxt; | ||||
| 	struct acpi_ec_query *q; | ||||
| 	struct transaction *t; | ||||
| 
 | ||||
| 	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; | ||||
| 	return q; | ||||
| } | ||||
| 
 | ||||
| static void acpi_ec_delete_query(struct acpi_ec_query *q) | ||||
| { | ||||
| 	if (q) { | ||||
| 		if (q->handler) | ||||
| 			acpi_ec_put_query_handler(q->handler); | ||||
| 		kfree(q); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
| 	if (!handler) | ||||
| 		return; | ||||
| 	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); | ||||
| 	acpi_ec_put_query_handler(handler); | ||||
| 	acpi_ec_delete_query(q); | ||||
| } | ||||
| 
 | ||||
| static int acpi_ec_query(struct acpi_ec *ec, u8 *data) | ||||
| { | ||||
| 	u8 value = 0; | ||||
| 	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}; | ||||
| 	struct acpi_ec_query *q; | ||||
| 
 | ||||
| 	q = acpi_ec_create_query(&value); | ||||
| 	if (!q) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * 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; | ||||
| 	result = acpi_ec_transaction(ec, &q->transaction); | ||||
| 	if (!value) | ||||
| 		return -ENODATA; | ||||
| 		result = -ENODATA; | ||||
| 	if (result) | ||||
| 		goto err_exit; | ||||
| 
 | ||||
| 	mutex_lock(&ec->mutex); | ||||
| 	list_for_each_entry(handler, &ec->list, node) { | ||||
| 		if (value == handler->query_bit) { | ||||
| 			/* have custom handler for this bit */ | ||||
| 			handler = acpi_ec_get_query_handler(handler); | ||||
| 			q->handler = acpi_ec_get_query_handler(handler); | ||||
| 			ec_dbg_evt("Query(0x%02x) scheduled", | ||||
| 				   handler->query_bit); | ||||
| 			status = acpi_os_execute((handler->func) ? | ||||
| 				OSL_NOTIFY_HANDLER : OSL_GPE_HANDLER, | ||||
| 				acpi_ec_run, handler); | ||||
| 			if (ACPI_FAILURE(status)) | ||||
| 				   q->handler->query_bit); | ||||
| 			/*
 | ||||
| 			 * It is reported that _Qxx are evaluated in a | ||||
| 			 * parallel way on Windows: | ||||
| 			 * https://bugzilla.kernel.org/show_bug.cgi?id=94411
 | ||||
| 			 */ | ||||
| 			if (!schedule_work(&q->work)) | ||||
| 				result = -EBUSY; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	mutex_unlock(&ec->mutex); | ||||
| 
 | ||||
| err_exit: | ||||
| 	if (result && q) | ||||
| 		acpi_ec_delete_query(q); | ||||
| 	if (data) | ||||
| 		*data = value; | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Lv Zheng
						Lv Zheng