Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ACPI: SBS: Fix handling of Smart Battery Selectors

The "Smart Battery Selector" standard says that when writing
SelectorState (0x1), the nibbles which should not be modified
need to be masked with 0xff. This is necessary since in contrast
to a "Smart Battery Manager", the last three nibbles are writable.

Failing to do so might trigger the following cycle:
1. Host accidentally changes power source of the system (3rd nibble)
when selecting a battery.
2. Power source is invalid, Selector changes to another power source.
3. Selector notifies host that it changed the power source.
4. Host re-reads some batteries.
5. goto 1 for each re-read battery.

This loop might also be entered when a battery which is not present
is selected for SMBus access. In the end some workqueues fill up,
which causes the system to lockup upon suspend/shutdown.

Fix this by correctly masking the value to be written, and avoid
selecting batteries which are absent.

Tested on a Acer Travelmate 4002WLMi.

Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Armin Wolf and committed by
Rafael J. Wysocki
3bd554e0 e5b492c6

+18 -9
+18 -9
drivers/acpi/sbs.c
··· 473 473 -------------------------------------------------------------------------- */ 474 474 static int acpi_battery_read(struct acpi_battery *battery) 475 475 { 476 - int result = 0, saved_present = battery->present; 476 + int result, saved_present = battery->present; 477 477 u16 state; 478 478 479 479 if (battery->sbs->manager_present) { 480 480 result = acpi_smbus_read(battery->sbs->hc, SMBUS_READ_WORD, 481 481 ACPI_SBS_MANAGER, 0x01, (u8 *)&state); 482 - if (!result) 483 - battery->present = state & (1 << battery->id); 484 - state &= 0x0fff; 482 + if (result) 483 + return result; 484 + 485 + battery->present = state & (1 << battery->id); 486 + if (!battery->present) 487 + return 0; 488 + 489 + /* Masking necessary for Smart Battery Selectors */ 490 + state = 0x0fff; 485 491 state |= 1 << (battery->id + 12); 486 492 acpi_smbus_write(battery->sbs->hc, SMBUS_WRITE_WORD, 487 493 ACPI_SBS_MANAGER, 0x01, (u8 *)&state, 2); 488 - } else if (battery->id == 0) 489 - battery->present = 1; 490 - 491 - if (result || !battery->present) 492 - return result; 494 + } else { 495 + if (battery->id == 0) { 496 + battery->present = 1; 497 + } else { 498 + if (!battery->present) 499 + return 0; 500 + } 501 + } 493 502 494 503 if (saved_present != battery->present) { 495 504 battery->update_time = 0;