diff mbox series

acpi: devices: add a new test for ACPI battery device

Message ID 1506984936-10185-1-git-send-email-alex.hung@canonical.com
State Accepted
Headers show
Series acpi: devices: add a new test for ACPI battery device | expand

Commit Message

Alex Hung Oct. 2, 2017, 10:55 p.m. UTC
The method tested in acpi_battery are from the original tests with
some major changes listed below:

 - replace METHOD_MOBILE by METHOD_OPTIONAL or METHOD_MANDATORY
 - change failure severity if functions are impacted
 - cross check _BIF and _BIX
 - remove cycle check in _BIX
 - add measurement accuracy in _BIX

Signed-off-by: Alex Hung <alex.hung@canonical.com>
---
 src/Makefile.am                    |   1 +
 src/acpi/devices/battery/battery.c | 673 +++++++++++++++++++++++++++++++++++++
 2 files changed, 674 insertions(+)
 create mode 100644 src/acpi/devices/battery/battery.c

Comments

Colin Ian King Oct. 4, 2017, 8:34 a.m. UTC | #1
On 02/10/17 23:55, Alex Hung wrote:
> The method tested in acpi_battery are from the original tests with
> some major changes listed below:
> 
>  - replace METHOD_MOBILE by METHOD_OPTIONAL or METHOD_MANDATORY
>  - change failure severity if functions are impacted
>  - cross check _BIF and _BIX
>  - remove cycle check in _BIX
>  - add measurement accuracy in _BIX
> 
> Signed-off-by: Alex Hung <alex.hung@canonical.com>
> ---
>  src/Makefile.am                    |   1 +
>  src/acpi/devices/battery/battery.c | 673 +++++++++++++++++++++++++++++++++++++
>  2 files changed, 674 insertions(+)
>  create mode 100644 src/acpi/devices/battery/battery.c
> 
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 357d12b..c1dccf6 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -46,6 +46,7 @@ endif
>  fwts_SOURCES = main.c 				\
>  	acpi/ac_adapter/ac_adapter.c 		\
>  	acpi/devices/ac_adapter/ac.c		\
> +	acpi/devices/battery/battery.c		\
>  	acpi/acpidump/acpidump.c 		\
>  	acpi/acpiinfo/acpiinfo.c 		\
>  	acpi/acpitables/acpitables.c 		\
> diff --git a/src/acpi/devices/battery/battery.c b/src/acpi/devices/battery/battery.c
> new file mode 100644
> index 0000000..ec1322a
> --- /dev/null
> +++ b/src/acpi/devices/battery/battery.c
> @@ -0,0 +1,673 @@
> +/*
> + * Copyright (C) 2017 Canonical
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version 2
> + * of the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
> + *
> + */
> +#include "fwts.h"
> +
> +#if defined(FWTS_HAS_ACPI)
> +
> +#include "fwts_acpi_object_eval.h"
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <inttypes.h>
> +#include <string.h>
> +
> +#define FWTS_ACPI_BATTERY_HID "PNP0C0A"
> +
> +static ACPI_HANDLE device;
> +
> +static ACPI_STATUS get_device_handle(ACPI_HANDLE handle, uint32_t level,
> +					  void *context, void **ret_val)
> +{
> +	FWTS_UNUSED(level);
> +	FWTS_UNUSED(context);
> +	FWTS_UNUSED(ret_val);
> +
> +	device = handle;
> +	return AE_CTRL_TERMINATE;
> +}
> +
> +static int acpi_battery_init(fwts_framework *fw)
> +{
> +	ACPI_STATUS status;
> +
> +	if (fwts_acpica_init(fw) != FWTS_OK)
> +		return FWTS_ERROR;
> +
> +	status = AcpiGetDevices(FWTS_ACPI_BATTERY_HID, get_device_handle, NULL, NULL);
> +	if (ACPI_FAILURE(status)) {
> +		fwts_log_error(fw, "Cannot find the ACPI device");
> +		return FWTS_ERROR;
> +	}
> +
> +	if (!device) {
> +		fwts_log_error(fw, "ACPI Battery device does not exist, skipping test");
> +		return FWTS_SKIP;
> +	} else {
> +		ACPI_BUFFER buffer;
> +		char full_name[128];
> +
> +		buffer.Length = sizeof(full_name);
> +		buffer.Pointer = full_name;
> +
> +		status = AcpiGetName(device, ACPI_FULL_PATHNAME, &buffer);
> +		if (ACPI_SUCCESS(status)) {
> +			fwts_log_info_verbatim(fw, "ACPI Battery Device: %s", full_name);
> +			fwts_log_nl(fw);
> +		}
> +	}
> +
> +	return FWTS_OK;
> +}
> +
> +static void method_test_BIF_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	bool failed = false;
> +
> +	static const fwts_package_element elements[] = {
> +		{ ACPI_TYPE_INTEGER,	"Power Unit" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capacity" },
> +		{ ACPI_TYPE_INTEGER,	"Last Full Charge Capacity" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Technology" },
> +		{ ACPI_TYPE_INTEGER,	"Design Voltage" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capacity of Warning" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capactty of Low" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 1" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 2" },
> +		{ ACPI_TYPE_STRING,	"Model Number" },
> +		{ ACPI_TYPE_STRING,	"Serial Number" },
> +		{ ACPI_TYPE_STRING,	"Battery Type" },
> +		{ ACPI_TYPE_STRING,	"OEM Information" }
> +	};
> +
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_count_equal(fw, name, "_BIF", obj, 13) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_type(fw, name, "_BIF", obj, elements, 13) != FWTS_OK)
> +		return;
> +
> +	/* Sanity check each field */
> +	/* Power Unit */
> +	if (obj->Package.Elements[0].Integer.Value > 0x00000002) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFBadUnits",
> +			"%s: Expected Power Unit (Element 0) to be "
> +			"0 (mWh) or 1 (mAh), got 0x%8.8" PRIx64 ".",
> +			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
> +		failed = true;
> +	}
> +#ifdef FWTS_METHOD_PEDANDTIC
> +	/*
> +	 * Since this information may be evaluated by communicating with
> +	 * the EC we skip these checks as we can't do this from userspace
> +	 */
> +	/* Design Capacity */
> +	if (obj->Package.Elements[1].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFBadCapacity",
> +			"%s: Design Capacity (Element 1) is "
> +			"unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[1].Integer.Value);
> +		failed = true;
> +	}
> +	/* Last Full Charge Capacity */
> +	if (obj->Package.Elements[2].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFChargeCapacity",
> +			"%s: Last Full Charge Capacity (Element 2) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[2].Integer.Value);
> +		failed = true;
> +	}
> +#endif
> +	/* Battery Technology */
> +	if (obj->Package.Elements[3].Integer.Value > 0x00000002) {
> +		fwts_failed(fw, LOG_LEVEL_MEDIUM,
> +			"Method_BIFBatTechUnit",
> +			"%s: Expected Battery Technology Unit "
> +			"(Element 3) to be 0 (Primary) or 1 "
> +			"(Secondary), got 0x%8.8" PRIx64 ".",
> +			name, (uint64_t)obj->Package.Elements[3].Integer.Value);
> +		failed = true;
> +	}
> +#ifdef FWTS_METHOD_PEDANDTIC
> +	/*
> +	 * Since this information may be evaluated by communicating with
> +	 * the EC we skip these checks as we can't do this from userspace
> +	 */
> +	/* Design Voltage */
> +	if (obj->Package.Elements[4].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFDesignVoltage",
> +			"%s: Design Voltage (Element 4) is "
> +			"unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[4].Integer.Value);
> +		failed = true;
> +	}
> +	/* Design capacity warning */
> +	if (obj->Package.Elements[5].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFDesignCapacityE5",
> +			"%s: Design Capacity Warning (Element 5) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[5].Integer.Value);
> +		failed = true;
> +	}
> +	/* Design capacity low */
> +	if (obj->Package.Elements[6].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFDesignCapacityE6",
> +			"%s: Design Capacity Warning (Element 6) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[6].Integer.Value);
> +		failed = true;
> +	}
> +#endif
> +	if (failed)
> +		fwts_advice(fw,
> +			"Battery %s package contains errors. It is "
> +			"worth running the firmware test suite "
> +			"interactive 'battery' test to see if this "
> +			"is problematic.  This is a bug an needs to "
> +			"be fixed.", name);
> +	else
> +		fwts_method_passed_sane(fw, name, "package");
> +}
> +
> +static int method_test_BIF(fwts_framework *fw)
> +{
> +	ACPI_STATUS status;
> +	ACPI_HANDLE	method;
> +
> +	/* _BIF can be superseded by _BIX */
> +	status = AcpiGetHandle (device, "_BIX", &method);
> +	if (ACPI_SUCCESS(status))
> +		return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BIF", NULL, 0, method_test_BIF_return, NULL);
> +	else
> +		return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
> +			"_BIF", NULL, 0, method_test_BIF_return, NULL);
> +}
> +
> +static void method_test_BIX_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	bool failed = false;
> +
> +	static const fwts_package_element elements[] = {
> +		{ ACPI_TYPE_INTEGER,	"Revision" },
> +		{ ACPI_TYPE_INTEGER,	"Power Unit" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capacity" },
> +		{ ACPI_TYPE_INTEGER,	"Last Full Charge Capacity" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Technology" },
> +		{ ACPI_TYPE_INTEGER,	"Design Voltage" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capacity of Warning" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capactty of Low" },
> +		{ ACPI_TYPE_INTEGER,	"Cycle Count" },
> +		{ ACPI_TYPE_INTEGER,	"Measurement Accuracy" },
> +		{ ACPI_TYPE_INTEGER,	"Max Sampling Time" },
> +		{ ACPI_TYPE_INTEGER,	"Min Sampling Time" },
> +		{ ACPI_TYPE_INTEGER,	"Max Averaging Interval" },
> +		{ ACPI_TYPE_INTEGER,	"Min Averaging Interval" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 1" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 2" },
> +		{ ACPI_TYPE_STRING,	"Model Number" },
> +		{ ACPI_TYPE_STRING,	"Serial Number" },
> +		{ ACPI_TYPE_STRING,	"Battery Type" },
> +		{ ACPI_TYPE_STRING,	"OEM Information" }
> +	};
> +
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_count_equal(fw, name, "_BIX", obj, 20) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_type(fw, name, "_BIX", obj, elements, 20) != FWTS_OK)
> +		return;
> +
> +	/* Sanity check each field */
> +	/* Power Unit */
> +	if (obj->Package.Elements[1].Integer.Value > 0x00000002) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXPowerUnit",
> +			"%s: Expected %s (Element 1) to be "
> +			"0 (mWh) or 1 (mAh), got 0x%8.8" PRIx64 ".",
> +			name, elements[1].name,
> +			(uint64_t)obj->Package.Elements[1].Integer.Value);
> +		failed = true;
> +	}
> +#ifdef FWTS_METHOD_PEDANDTIC
> +	/*
> +	 * Since this information may be evaluated by communicating with
> +	 * the EC we skip these checks as we can't do this from userspace
> +	 */
> +	/* Design Capacity */
> +	if (obj->Package.Elements[2].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXDesignCapacity",
> +			"%s: %s (Element 2) is "
> +			"unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[2].name,
> +			obj->Package.Elements[2].Integer.Value);
> +		failed = true;
> +	}
> +	/* Last Full Charge Capacity */
> +	if (obj->Package.Elements[3].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXFullChargeCapacity",
> +			"%s: %s (Element 3) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[3].name,
> +			obj->Package.Elements[3].Integer.Value);
> +		failed = true;
> +	}
> +#endif
> +	/* Battery Technology */
> +	if (obj->Package.Elements[4].Integer.Value > 0x00000002) {
> +		fwts_failed(fw, LOG_LEVEL_MEDIUM,
> +			"Method_BIXBatteryTechUnit",
> +			"%s: %s "
> +			"(Element 4) to be 0 (Primary) or 1 "
> +			"(Secondary), got 0x%8.8" PRIx64 ".",
> +			name, elements[4].name,
> +			(uint64_t)obj->Package.Elements[4].Integer.Value);
> +		failed = true;
> +	}
> +#ifdef FWTS_METHOD_PEDANDTIC
> +	/*
> +	 * Since this information may be evaluated by communicating with
> +	 * the EC we skip these checks as we can't do this from userspace
> +	 */
> +	/* Design Voltage */
> +	if (obj->Package.Elements[5].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXDesignVoltage",
> +			"%s: %s (Element 5) is unknown: "
> +			"0x%8.8" PRIx64 ".",
> +			name, elements[5].name,
> +			obj->Package.Elements[5].Integer.Value);
> +		failed = true;
> +	}
> +	/* Design capacity warning */
> +	if (obj->Package.Elements[6].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXDesignCapacityE6",
> +			"%s: %s (Element 6) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[6].name,
> +			obj->Package.Elements[6].Integer.Value);
> +		failed = true;
> +	}
> +	/* Design capacity low */
> +	if (obj->Package.Elements[7].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXDesignCapacityE7",
> +			"%s: %s (Element 7) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[7].name,
> +			obj->Package.Elements[7].Integer.Value);
> +		failed = true;
> +	}
> +	/* Cycle Count: value = 0 ~ 0xFFFFFFFE or 0xFFFFFFFF (unknown) */
> +
> +	/* Measurement Accuracy */
> +	if (obj->Package.Elements[9].Integer.Value > 100000) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXMeasurementAccuracy",
> +			"%s: %s (Element 9) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[9].name,
> +			obj->Package.Elements[9].Integer.Value);
> +		failed = true;
> +	}
> +
> +#endif
> +	if (failed)
> +		fwts_advice(fw,
> +			"Battery %s package contains errors. It is "
> +			"worth running the firmware test suite "
> +			"interactive 'battery' test to see if this "
> +			"is problematic.  This is a bug an needs to "
> +			"be fixed.", name);
> +	else
> +		fwts_method_passed_sane(fw, name, "package");
> +}
> +
> +static int method_test_BIX(fwts_framework *fw)
> +{
> +	ACPI_STATUS status;
> +	ACPI_HANDLE	method;
> +
> +	/* _BIX may not supported by older firmware */
> +	status = AcpiGetHandle (device, "_BIF", &method);
> +	if (ACPI_SUCCESS(status))
> +		return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BIX", NULL, 0, method_test_BIX_return, NULL);
> +	else
> +		return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
> +			"_BIX", NULL, 0, method_test_BIX_return, NULL);
> +}
> +
> +static int method_test_BMA(fwts_framework *fw)
> +{
> +	ACPI_OBJECT arg[1];
> +	arg[0].Type = ACPI_TYPE_INTEGER;
> +	arg[0].Integer.Value = 1;
> +
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_BMA", arg, 1, fwts_method_test_integer_return, NULL);
> +}
> +
> +static int method_test_BMS(fwts_framework *fw)
> +{
> +	ACPI_OBJECT arg[1];
> +	arg[0].Type = ACPI_TYPE_INTEGER;
> +	arg[0].Integer.Value = 1;
> +
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_BMS", arg, 1, fwts_method_test_integer_return, NULL);
> +}
> +
> +static void method_test_BST_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	bool failed = false;
> +
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_count_equal(fw, name, "_BST", obj, 4) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_all_type(fw, name, "_BST", obj, ACPI_TYPE_INTEGER) != FWTS_OK)
> +		return;
> +
> +	/* Sanity check each field */
> +	/* Battery State */
> +	if ((obj->Package.Elements[0].Integer.Value) > 7) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BSTBadState",
> +			"%s: Expected Battery State (Element 0) to "
> +			"be 0..7, got 0x%8.8" PRIx64 ".",
> +			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
> +		failed = true;
> +	}
> +	/* Ensure bits 0 (discharging) and 1 (charging) are not both set, see 10.2.2.6 */
> +	if (((obj->Package.Elements[0].Integer.Value) & 3) == 3) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BSTBadState",
> +			"%s: Battery State (Element 0) is "
> +			"indicating both charging and discharginng "
> +			"which is not allowed. Got value 0x%8.8" PRIx64 ".",
> +			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
> +		failed = true;
> +	}
> +	/* Battery Present Rate - cannot check, pulled from EC */
> +	/* Battery Remaining Capacity - cannot check, pulled from EC */
> +	/* Battery Present Voltage - cannot check, pulled from EC */
> +	if (failed)
> +		fwts_advice(fw,
> +			"Battery %s package contains errors. It is "
> +			"worth running the firmware test suite "
> +			"interactive 'battery' test to see if this "
> +			"is problematic.  This is a bug an needs to "
> +			"be fixed.", name);
> +		else
> +			fwts_method_passed_sane(fw, name, "package");
> +}
> +
> +static int method_test_BST(fwts_framework *fw)
> +{
> +	return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
> +		"_BST", NULL, 0, method_test_BST_return, NULL);
> +}
> +
> +static int method_test_BTH(fwts_framework *fw)
> +{
> +	ACPI_OBJECT arg[1];
> +	uint8_t i, ret;
> +	arg[0].Type = ACPI_TYPE_INTEGER;
> +
> +	for (i = 0; i <= 100; i++) {
> +		arg[0].Integer.Value = i;
> +		ret = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BTH", arg, 1, fwts_method_test_NULL_return, NULL);
> +
> +		if (ret != FWTS_OK)
> +			break;
> +	}
> +	return ret;
> +}
> +
> +static int method_test_BTP(fwts_framework *fw)
> +{
> +	static const int values[] = { 0, 1, 100, 200, 0x7fffffff };
> +	ACPI_STATUS status;
> +	uint8_t i;
> +
> +	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
> +		ACPI_OBJECT arg[1];
> +		arg[0].Type = ACPI_TYPE_INTEGER;
> +		arg[0].Integer.Value = values[i];
> +		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BTP", arg, 1, fwts_method_test_NULL_return, NULL);
> +		if (ACPI_FAILURE(status))
> +			break;
> +	}
> +	return FWTS_OK;
> +}
> +
> +
> +static void method_test_PCL_return(fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_all_type(fw, name, "_PCL", obj, ACPI_TYPE_LOCAL_REFERENCE) != FWTS_OK)
> +		return;
> +
> +	fwts_passed(fw,	"%s returned a sane package of %" PRIu32 " references.", name, obj->Package.Count);
> +}
> +
> +static int method_test_PCL(fwts_framework *fw)
> +{
> +	return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
> +		 "_PCL", NULL, 0, method_test_PCL_return, NULL);
> +}
> +
> +static void method_test_STA_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	bool failed = false;
> +
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_INTEGER) != FWTS_OK)
> +		return;
> +
> +	if ((obj->Integer.Value & 3) == 2) {
> +		fwts_failed(fw, LOG_LEVEL_HIGH,
> +			"Method_STAEnabledNotPresent",
> +			"%s indicates that the device is enabled "
> +			"but not present, which is impossible.", name);
> +		failed = true;
> +	}
> +	if ((obj->Integer.Value & ~0x1f) != 0) {
> +		fwts_failed(fw, LOG_LEVEL_HIGH,
> +			"Method_STAReservedBitsSet",
> +			"%s is returning non-zero reserved "
> +			"bits 5-31. These should be zero.", name);
> +		failed = true;
> +	}
> +
> +	if (!failed)
> +		fwts_method_passed_sane_uint64(fw, name, obj->Integer.Value);
> +}
> +
> +static int method_test_STA(fwts_framework *fw)
> +{
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_STA", NULL, 0, method_test_STA_return, NULL);
> +}
> +
> +static int method_test_BTM(fwts_framework *fw)
> +{
> +	static const int values[] = { 0, 1, 100, 200, 0x7fffffff };
> +	ACPI_STATUS status;
> +	uint8_t i;
> +
> +	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
> +		ACPI_OBJECT arg[1];
> +		arg[0].Type = ACPI_TYPE_INTEGER;
> +		arg[0].Integer.Value = values[i];
> +		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BTM", arg, 1, fwts_method_test_NULL_return, NULL);
> +		if (ACPI_FAILURE(status))
> +			break;
> +	}
> +	return FWTS_OK;
> +}
> +
> +static int method_test_BCT(fwts_framework *fw)
> +{
> +	ACPI_OBJECT arg[1];
> +	arg[0].Type = ACPI_TYPE_INTEGER;
> +	arg[0].Integer.Value = 50;	/* 50% */
> +
> +	/*
> +	 * For now, just check that we get some integer back, values
> +	 * can be 0x00000000, 0x00000001-0xfffffffe and 0xffffffff,
> +	 * so anything is valid as long as it is an integer
> +	 */
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_BCT", arg, 1, fwts_method_test_integer_return, NULL);
> +}
> +
> +static void method_test_BMD_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_count_equal(fw, name, "_BMD", obj, 5) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_all_type(fw, name, "_BMD", obj, ACPI_TYPE_INTEGER) != FWTS_OK)
> +		return;
> +
> +	fwts_method_passed_sane(fw, name, "package");
> +}
> +
> +static int method_test_BMD(fwts_framework *fw)
> +{
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_BMD", NULL, 0, method_test_BMD_return, NULL);
> +}
> +
> +static int method_test_BMC(fwts_framework *fw)
> +{
> +	static const int values[] = { 0, 1, 2, 4 };
> +	ACPI_STATUS status;
> +	uint8_t i;
> +
> +	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
> +		ACPI_OBJECT arg[1];
> +		arg[0].Type = ACPI_TYPE_INTEGER;
> +		arg[0].Integer.Value = values[i];
> +		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BMC", arg, 1, fwts_method_test_NULL_return, NULL);
> +		if (ACPI_FAILURE(status))
> +			break;
> +	}
> +	return FWTS_OK;
> +}
> +
> +static fwts_framework_minor_test acpi_battery_tests[] = {
> +	{ method_test_BIF, "Test _BIF (Battery Information)." },
> +	{ method_test_BIX, "Test _BIX (Battery Information Extended)." },
> +	{ method_test_BMA, "Test _BMA (Battery Measurement Averaging)." },
> +	{ method_test_BMS, "Test _BMS (Battery Measurement Sampling Time)." },
> +	{ method_test_BST, "Test _BST (Battery Status)." },
> +	{ method_test_BTH, "Test _BTH (Battery Throttle Limit)." },
> +	{ method_test_BTP, "Test _BTP (Battery Trip Point)." },
> +	{ method_test_PCL, "Test _PCL (Power Consumer List)." },
> +	{ method_test_STA, "Test _STA (Status)." },
> +	{ method_test_BTM, "Test _BTM (Battery Time)." },
> +	{ method_test_BCT, "Test _BCT (Battery Charge Time)." },
> +	{ method_test_BMD, "Test _BMD (Battery Maintenance Data)." },
> +	{ method_test_BMC, "Test _BMC (Battery Maintenance Control)." },
> +	{ NULL, NULL }
> +};
> +
> +static int acpi_battery_deinit(fwts_framework *fw)
> +{
> +	FWTS_UNUSED(fw);
> +	fwts_acpica_deinit();
> +
> +	return FWTS_OK;
> +}
> +
> +static fwts_framework_ops acpi_battery_ops = {
> +	.description = "ACPI battery device test",
> +	.init        = acpi_battery_init,
> +	.deinit      = acpi_battery_deinit,
> +	.minor_tests = acpi_battery_tests
> +};
> +
> +FWTS_REGISTER("acpi_battery", &acpi_battery_ops, FWTS_TEST_ANYTIME, FWTS_FLAG_BATCH | FWTS_FLAG_TEST_ACPI)
> +
> +#endif
> 

Acked-by: Colin Ian King <colin.king@canonical.com>
Ivan Hu Oct. 11, 2017, 9:55 a.m. UTC | #2
On 10/03/2017 06:55 AM, Alex Hung wrote:
> The method tested in acpi_battery are from the original tests with
> some major changes listed below:
> 
>   - replace METHOD_MOBILE by METHOD_OPTIONAL or METHOD_MANDATORY
>   - change failure severity if functions are impacted
>   - cross check _BIF and _BIX
>   - remove cycle check in _BIX
>   - add measurement accuracy in _BIX
> 
> Signed-off-by: Alex Hung <alex.hung@canonical.com>
> ---
>   src/Makefile.am                    |   1 +
>   src/acpi/devices/battery/battery.c | 673 +++++++++++++++++++++++++++++++++++++
>   2 files changed, 674 insertions(+)
>   create mode 100644 src/acpi/devices/battery/battery.c
> 
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 357d12b..c1dccf6 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -46,6 +46,7 @@ endif
>   fwts_SOURCES = main.c 				\
>   	acpi/ac_adapter/ac_adapter.c 		\
>   	acpi/devices/ac_adapter/ac.c		\
> +	acpi/devices/battery/battery.c		\
>   	acpi/acpidump/acpidump.c 		\
>   	acpi/acpiinfo/acpiinfo.c 		\
>   	acpi/acpitables/acpitables.c 		\
> diff --git a/src/acpi/devices/battery/battery.c b/src/acpi/devices/battery/battery.c
> new file mode 100644
> index 0000000..ec1322a
> --- /dev/null
> +++ b/src/acpi/devices/battery/battery.c
> @@ -0,0 +1,673 @@
> +/*
> + * Copyright (C) 2017 Canonical
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version 2
> + * of the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
> + *
> + */
> +#include "fwts.h"
> +
> +#if defined(FWTS_HAS_ACPI)
> +
> +#include "fwts_acpi_object_eval.h"
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <inttypes.h>
> +#include <string.h>
> +
> +#define FWTS_ACPI_BATTERY_HID "PNP0C0A"
> +
> +static ACPI_HANDLE device;
> +
> +static ACPI_STATUS get_device_handle(ACPI_HANDLE handle, uint32_t level,
> +					  void *context, void **ret_val)
> +{
> +	FWTS_UNUSED(level);
> +	FWTS_UNUSED(context);
> +	FWTS_UNUSED(ret_val);
> +
> +	device = handle;
> +	return AE_CTRL_TERMINATE;
> +}
> +
> +static int acpi_battery_init(fwts_framework *fw)
> +{
> +	ACPI_STATUS status;
> +
> +	if (fwts_acpica_init(fw) != FWTS_OK)
> +		return FWTS_ERROR;
> +
> +	status = AcpiGetDevices(FWTS_ACPI_BATTERY_HID, get_device_handle, NULL, NULL);
> +	if (ACPI_FAILURE(status)) {
> +		fwts_log_error(fw, "Cannot find the ACPI device");
> +		return FWTS_ERROR;
> +	}
> +
> +	if (!device) {
> +		fwts_log_error(fw, "ACPI Battery device does not exist, skipping test");
> +		return FWTS_SKIP;
> +	} else {
> +		ACPI_BUFFER buffer;
> +		char full_name[128];
> +
> +		buffer.Length = sizeof(full_name);
> +		buffer.Pointer = full_name;
> +
> +		status = AcpiGetName(device, ACPI_FULL_PATHNAME, &buffer);
> +		if (ACPI_SUCCESS(status)) {
> +			fwts_log_info_verbatim(fw, "ACPI Battery Device: %s", full_name);
> +			fwts_log_nl(fw);
> +		}
> +	}
> +
> +	return FWTS_OK;
> +}
> +
> +static void method_test_BIF_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	bool failed = false;
> +
> +	static const fwts_package_element elements[] = {
> +		{ ACPI_TYPE_INTEGER,	"Power Unit" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capacity" },
> +		{ ACPI_TYPE_INTEGER,	"Last Full Charge Capacity" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Technology" },
> +		{ ACPI_TYPE_INTEGER,	"Design Voltage" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capacity of Warning" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capactty of Low" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 1" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 2" },
> +		{ ACPI_TYPE_STRING,	"Model Number" },
> +		{ ACPI_TYPE_STRING,	"Serial Number" },
> +		{ ACPI_TYPE_STRING,	"Battery Type" },
> +		{ ACPI_TYPE_STRING,	"OEM Information" }
> +	};
> +
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_count_equal(fw, name, "_BIF", obj, 13) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_type(fw, name, "_BIF", obj, elements, 13) != FWTS_OK)
> +		return;
> +
> +	/* Sanity check each field */
> +	/* Power Unit */
> +	if (obj->Package.Elements[0].Integer.Value > 0x00000002) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFBadUnits",
> +			"%s: Expected Power Unit (Element 0) to be "
> +			"0 (mWh) or 1 (mAh), got 0x%8.8" PRIx64 ".",
> +			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
> +		failed = true;
> +	}
> +#ifdef FWTS_METHOD_PEDANDTIC
> +	/*
> +	 * Since this information may be evaluated by communicating with
> +	 * the EC we skip these checks as we can't do this from userspace
> +	 */
> +	/* Design Capacity */
> +	if (obj->Package.Elements[1].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFBadCapacity",
> +			"%s: Design Capacity (Element 1) is "
> +			"unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[1].Integer.Value);
> +		failed = true;
> +	}
> +	/* Last Full Charge Capacity */
> +	if (obj->Package.Elements[2].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFChargeCapacity",
> +			"%s: Last Full Charge Capacity (Element 2) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[2].Integer.Value);
> +		failed = true;
> +	}
> +#endif
> +	/* Battery Technology */
> +	if (obj->Package.Elements[3].Integer.Value > 0x00000002) {
> +		fwts_failed(fw, LOG_LEVEL_MEDIUM,
> +			"Method_BIFBatTechUnit",
> +			"%s: Expected Battery Technology Unit "
> +			"(Element 3) to be 0 (Primary) or 1 "
> +			"(Secondary), got 0x%8.8" PRIx64 ".",
> +			name, (uint64_t)obj->Package.Elements[3].Integer.Value);
> +		failed = true;
> +	}
> +#ifdef FWTS_METHOD_PEDANDTIC
> +	/*
> +	 * Since this information may be evaluated by communicating with
> +	 * the EC we skip these checks as we can't do this from userspace
> +	 */
> +	/* Design Voltage */
> +	if (obj->Package.Elements[4].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFDesignVoltage",
> +			"%s: Design Voltage (Element 4) is "
> +			"unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[4].Integer.Value);
> +		failed = true;
> +	}
> +	/* Design capacity warning */
> +	if (obj->Package.Elements[5].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFDesignCapacityE5",
> +			"%s: Design Capacity Warning (Element 5) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[5].Integer.Value);
> +		failed = true;
> +	}
> +	/* Design capacity low */
> +	if (obj->Package.Elements[6].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIFDesignCapacityE6",
> +			"%s: Design Capacity Warning (Element 6) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, obj->Package.Elements[6].Integer.Value);
> +		failed = true;
> +	}
> +#endif
> +	if (failed)
> +		fwts_advice(fw,
> +			"Battery %s package contains errors. It is "
> +			"worth running the firmware test suite "
> +			"interactive 'battery' test to see if this "
> +			"is problematic.  This is a bug an needs to "
> +			"be fixed.", name);
> +	else
> +		fwts_method_passed_sane(fw, name, "package");
> +}
> +
> +static int method_test_BIF(fwts_framework *fw)
> +{
> +	ACPI_STATUS status;
> +	ACPI_HANDLE	method;
> +
> +	/* _BIF can be superseded by _BIX */
> +	status = AcpiGetHandle (device, "_BIX", &method);
> +	if (ACPI_SUCCESS(status))
> +		return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BIF", NULL, 0, method_test_BIF_return, NULL);
> +	else
> +		return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
> +			"_BIF", NULL, 0, method_test_BIF_return, NULL);
> +}
> +
> +static void method_test_BIX_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	bool failed = false;
> +
> +	static const fwts_package_element elements[] = {
> +		{ ACPI_TYPE_INTEGER,	"Revision" },
> +		{ ACPI_TYPE_INTEGER,	"Power Unit" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capacity" },
> +		{ ACPI_TYPE_INTEGER,	"Last Full Charge Capacity" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Technology" },
> +		{ ACPI_TYPE_INTEGER,	"Design Voltage" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capacity of Warning" },
> +		{ ACPI_TYPE_INTEGER,	"Design Capactty of Low" },
> +		{ ACPI_TYPE_INTEGER,	"Cycle Count" },
> +		{ ACPI_TYPE_INTEGER,	"Measurement Accuracy" },
> +		{ ACPI_TYPE_INTEGER,	"Max Sampling Time" },
> +		{ ACPI_TYPE_INTEGER,	"Min Sampling Time" },
> +		{ ACPI_TYPE_INTEGER,	"Max Averaging Interval" },
> +		{ ACPI_TYPE_INTEGER,	"Min Averaging Interval" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 1" },
> +		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 2" },
> +		{ ACPI_TYPE_STRING,	"Model Number" },
> +		{ ACPI_TYPE_STRING,	"Serial Number" },
> +		{ ACPI_TYPE_STRING,	"Battery Type" },
> +		{ ACPI_TYPE_STRING,	"OEM Information" }
> +	};
> +
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_count_equal(fw, name, "_BIX", obj, 20) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_type(fw, name, "_BIX", obj, elements, 20) != FWTS_OK)
> +		return;
> +
> +	/* Sanity check each field */
> +	/* Power Unit */
> +	if (obj->Package.Elements[1].Integer.Value > 0x00000002) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXPowerUnit",
> +			"%s: Expected %s (Element 1) to be "
> +			"0 (mWh) or 1 (mAh), got 0x%8.8" PRIx64 ".",
> +			name, elements[1].name,
> +			(uint64_t)obj->Package.Elements[1].Integer.Value);
> +		failed = true;
> +	}
> +#ifdef FWTS_METHOD_PEDANDTIC
> +	/*
> +	 * Since this information may be evaluated by communicating with
> +	 * the EC we skip these checks as we can't do this from userspace
> +	 */
> +	/* Design Capacity */
> +	if (obj->Package.Elements[2].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXDesignCapacity",
> +			"%s: %s (Element 2) is "
> +			"unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[2].name,
> +			obj->Package.Elements[2].Integer.Value);
> +		failed = true;
> +	}
> +	/* Last Full Charge Capacity */
> +	if (obj->Package.Elements[3].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXFullChargeCapacity",
> +			"%s: %s (Element 3) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[3].name,
> +			obj->Package.Elements[3].Integer.Value);
> +		failed = true;
> +	}
> +#endif
> +	/* Battery Technology */
> +	if (obj->Package.Elements[4].Integer.Value > 0x00000002) {
> +		fwts_failed(fw, LOG_LEVEL_MEDIUM,
> +			"Method_BIXBatteryTechUnit",
> +			"%s: %s "
> +			"(Element 4) to be 0 (Primary) or 1 "
> +			"(Secondary), got 0x%8.8" PRIx64 ".",
> +			name, elements[4].name,
> +			(uint64_t)obj->Package.Elements[4].Integer.Value);
> +		failed = true;
> +	}
> +#ifdef FWTS_METHOD_PEDANDTIC
> +	/*
> +	 * Since this information may be evaluated by communicating with
> +	 * the EC we skip these checks as we can't do this from userspace
> +	 */
> +	/* Design Voltage */
> +	if (obj->Package.Elements[5].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXDesignVoltage",
> +			"%s: %s (Element 5) is unknown: "
> +			"0x%8.8" PRIx64 ".",
> +			name, elements[5].name,
> +			obj->Package.Elements[5].Integer.Value);
> +		failed = true;
> +	}
> +	/* Design capacity warning */
> +	if (obj->Package.Elements[6].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXDesignCapacityE6",
> +			"%s: %s (Element 6) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[6].name,
> +			obj->Package.Elements[6].Integer.Value);
> +		failed = true;
> +	}
> +	/* Design capacity low */
> +	if (obj->Package.Elements[7].Integer.Value > 0x7fffffff) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXDesignCapacityE7",
> +			"%s: %s (Element 7) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[7].name,
> +			obj->Package.Elements[7].Integer.Value);
> +		failed = true;
> +	}
> +	/* Cycle Count: value = 0 ~ 0xFFFFFFFE or 0xFFFFFFFF (unknown) */
> +
> +	/* Measurement Accuracy */
> +	if (obj->Package.Elements[9].Integer.Value > 100000) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BIXMeasurementAccuracy",
> +			"%s: %s (Element 9) "
> +			"is unknown: 0x%8.8" PRIx64 ".",
> +			name, elements[9].name,
> +			obj->Package.Elements[9].Integer.Value);
> +		failed = true;
> +	}
> +
> +#endif
> +	if (failed)
> +		fwts_advice(fw,
> +			"Battery %s package contains errors. It is "
> +			"worth running the firmware test suite "
> +			"interactive 'battery' test to see if this "
> +			"is problematic.  This is a bug an needs to "
> +			"be fixed.", name);
> +	else
> +		fwts_method_passed_sane(fw, name, "package");
> +}
> +
> +static int method_test_BIX(fwts_framework *fw)
> +{
> +	ACPI_STATUS status;
> +	ACPI_HANDLE	method;
> +
> +	/* _BIX may not supported by older firmware */
> +	status = AcpiGetHandle (device, "_BIF", &method);
> +	if (ACPI_SUCCESS(status))
> +		return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BIX", NULL, 0, method_test_BIX_return, NULL);
> +	else
> +		return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
> +			"_BIX", NULL, 0, method_test_BIX_return, NULL);
> +}
> +
> +static int method_test_BMA(fwts_framework *fw)
> +{
> +	ACPI_OBJECT arg[1];
> +	arg[0].Type = ACPI_TYPE_INTEGER;
> +	arg[0].Integer.Value = 1;
> +
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_BMA", arg, 1, fwts_method_test_integer_return, NULL);
> +}
> +
> +static int method_test_BMS(fwts_framework *fw)
> +{
> +	ACPI_OBJECT arg[1];
> +	arg[0].Type = ACPI_TYPE_INTEGER;
> +	arg[0].Integer.Value = 1;
> +
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_BMS", arg, 1, fwts_method_test_integer_return, NULL);
> +}
> +
> +static void method_test_BST_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	bool failed = false;
> +
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_count_equal(fw, name, "_BST", obj, 4) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_all_type(fw, name, "_BST", obj, ACPI_TYPE_INTEGER) != FWTS_OK)
> +		return;
> +
> +	/* Sanity check each field */
> +	/* Battery State */
> +	if ((obj->Package.Elements[0].Integer.Value) > 7) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BSTBadState",
> +			"%s: Expected Battery State (Element 0) to "
> +			"be 0..7, got 0x%8.8" PRIx64 ".",
> +			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
> +		failed = true;
> +	}
> +	/* Ensure bits 0 (discharging) and 1 (charging) are not both set, see 10.2.2.6 */
> +	if (((obj->Package.Elements[0].Integer.Value) & 3) == 3) {
> +		fwts_failed(fw, LOG_LEVEL_CRITICAL,
> +			"Method_BSTBadState",
> +			"%s: Battery State (Element 0) is "
> +			"indicating both charging and discharginng "
> +			"which is not allowed. Got value 0x%8.8" PRIx64 ".",
> +			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
> +		failed = true;
> +	}
> +	/* Battery Present Rate - cannot check, pulled from EC */
> +	/* Battery Remaining Capacity - cannot check, pulled from EC */
> +	/* Battery Present Voltage - cannot check, pulled from EC */
> +	if (failed)
> +		fwts_advice(fw,
> +			"Battery %s package contains errors. It is "
> +			"worth running the firmware test suite "
> +			"interactive 'battery' test to see if this "
> +			"is problematic.  This is a bug an needs to "
> +			"be fixed.", name);
> +		else
> +			fwts_method_passed_sane(fw, name, "package");
> +}
> +
> +static int method_test_BST(fwts_framework *fw)
> +{
> +	return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
> +		"_BST", NULL, 0, method_test_BST_return, NULL);
> +}
> +
> +static int method_test_BTH(fwts_framework *fw)
> +{
> +	ACPI_OBJECT arg[1];
> +	uint8_t i, ret;
> +	arg[0].Type = ACPI_TYPE_INTEGER;
> +
> +	for (i = 0; i <= 100; i++) {
> +		arg[0].Integer.Value = i;
> +		ret = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BTH", arg, 1, fwts_method_test_NULL_return, NULL);
> +
> +		if (ret != FWTS_OK)
> +			break;
> +	}
> +	return ret;
> +}
> +
> +static int method_test_BTP(fwts_framework *fw)
> +{
> +	static const int values[] = { 0, 1, 100, 200, 0x7fffffff };
> +	ACPI_STATUS status;
> +	uint8_t i;
> +
> +	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
> +		ACPI_OBJECT arg[1];
> +		arg[0].Type = ACPI_TYPE_INTEGER;
> +		arg[0].Integer.Value = values[i];
> +		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BTP", arg, 1, fwts_method_test_NULL_return, NULL);
> +		if (ACPI_FAILURE(status))
> +			break;
> +	}
> +	return FWTS_OK;
> +}
> +
> +
> +static void method_test_PCL_return(fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_all_type(fw, name, "_PCL", obj, ACPI_TYPE_LOCAL_REFERENCE) != FWTS_OK)
> +		return;
> +
> +	fwts_passed(fw,	"%s returned a sane package of %" PRIu32 " references.", name, obj->Package.Count);
> +}
> +
> +static int method_test_PCL(fwts_framework *fw)
> +{
> +	return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
> +		 "_PCL", NULL, 0, method_test_PCL_return, NULL);
> +}
> +
> +static void method_test_STA_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	bool failed = false;
> +
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_INTEGER) != FWTS_OK)
> +		return;
> +
> +	if ((obj->Integer.Value & 3) == 2) {
> +		fwts_failed(fw, LOG_LEVEL_HIGH,
> +			"Method_STAEnabledNotPresent",
> +			"%s indicates that the device is enabled "
> +			"but not present, which is impossible.", name);
> +		failed = true;
> +	}
> +	if ((obj->Integer.Value & ~0x1f) != 0) {
> +		fwts_failed(fw, LOG_LEVEL_HIGH,
> +			"Method_STAReservedBitsSet",
> +			"%s is returning non-zero reserved "
> +			"bits 5-31. These should be zero.", name);
> +		failed = true;
> +	}
> +
> +	if (!failed)
> +		fwts_method_passed_sane_uint64(fw, name, obj->Integer.Value);
> +}
> +
> +static int method_test_STA(fwts_framework *fw)
> +{
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_STA", NULL, 0, method_test_STA_return, NULL);
> +}
> +
> +static int method_test_BTM(fwts_framework *fw)
> +{
> +	static const int values[] = { 0, 1, 100, 200, 0x7fffffff };
> +	ACPI_STATUS status;
> +	uint8_t i;
> +
> +	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
> +		ACPI_OBJECT arg[1];
> +		arg[0].Type = ACPI_TYPE_INTEGER;
> +		arg[0].Integer.Value = values[i];
> +		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BTM", arg, 1, fwts_method_test_NULL_return, NULL);
> +		if (ACPI_FAILURE(status))
> +			break;
> +	}
> +	return FWTS_OK;
> +}
> +
> +static int method_test_BCT(fwts_framework *fw)
> +{
> +	ACPI_OBJECT arg[1];
> +	arg[0].Type = ACPI_TYPE_INTEGER;
> +	arg[0].Integer.Value = 50;	/* 50% */
> +
> +	/*
> +	 * For now, just check that we get some integer back, values
> +	 * can be 0x00000000, 0x00000001-0xfffffffe and 0xffffffff,
> +	 * so anything is valid as long as it is an integer
> +	 */
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_BCT", arg, 1, fwts_method_test_integer_return, NULL);
> +}
> +
> +static void method_test_BMD_return(
> +	fwts_framework *fw,
> +	char *name,
> +	ACPI_BUFFER *buf,
> +	ACPI_OBJECT *obj,
> +	void *private)
> +{
> +	FWTS_UNUSED(private);
> +
> +	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_count_equal(fw, name, "_BMD", obj, 5) != FWTS_OK)
> +		return;
> +
> +	if (fwts_method_package_elements_all_type(fw, name, "_BMD", obj, ACPI_TYPE_INTEGER) != FWTS_OK)
> +		return;
> +
> +	fwts_method_passed_sane(fw, name, "package");
> +}
> +
> +static int method_test_BMD(fwts_framework *fw)
> +{
> +	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +		"_BMD", NULL, 0, method_test_BMD_return, NULL);
> +}
> +
> +static int method_test_BMC(fwts_framework *fw)
> +{
> +	static const int values[] = { 0, 1, 2, 4 };
> +	ACPI_STATUS status;
> +	uint8_t i;
> +
> +	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
> +		ACPI_OBJECT arg[1];
> +		arg[0].Type = ACPI_TYPE_INTEGER;
> +		arg[0].Integer.Value = values[i];
> +		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
> +			"_BMC", arg, 1, fwts_method_test_NULL_return, NULL);
> +		if (ACPI_FAILURE(status))
> +			break;
> +	}
> +	return FWTS_OK;
> +}
> +
> +static fwts_framework_minor_test acpi_battery_tests[] = {
> +	{ method_test_BIF, "Test _BIF (Battery Information)." },
> +	{ method_test_BIX, "Test _BIX (Battery Information Extended)." },
> +	{ method_test_BMA, "Test _BMA (Battery Measurement Averaging)." },
> +	{ method_test_BMS, "Test _BMS (Battery Measurement Sampling Time)." },
> +	{ method_test_BST, "Test _BST (Battery Status)." },
> +	{ method_test_BTH, "Test _BTH (Battery Throttle Limit)." },
> +	{ method_test_BTP, "Test _BTP (Battery Trip Point)." },
> +	{ method_test_PCL, "Test _PCL (Power Consumer List)." },
> +	{ method_test_STA, "Test _STA (Status)." },
> +	{ method_test_BTM, "Test _BTM (Battery Time)." },
> +	{ method_test_BCT, "Test _BCT (Battery Charge Time)." },
> +	{ method_test_BMD, "Test _BMD (Battery Maintenance Data)." },
> +	{ method_test_BMC, "Test _BMC (Battery Maintenance Control)." },
> +	{ NULL, NULL }
> +};
> +
> +static int acpi_battery_deinit(fwts_framework *fw)
> +{
> +	FWTS_UNUSED(fw);
> +	fwts_acpica_deinit();
> +
> +	return FWTS_OK;
> +}
> +
> +static fwts_framework_ops acpi_battery_ops = {
> +	.description = "ACPI battery device test",
> +	.init        = acpi_battery_init,
> +	.deinit      = acpi_battery_deinit,
> +	.minor_tests = acpi_battery_tests
> +};
> +
> +FWTS_REGISTER("acpi_battery", &acpi_battery_ops, FWTS_TEST_ANYTIME, FWTS_FLAG_BATCH | FWTS_FLAG_TEST_ACPI)
> +
> +#endif
> 

Acked-by: Ivan Hu <ivan.hu@canonical.com>
diff mbox series

Patch

diff --git a/src/Makefile.am b/src/Makefile.am
index 357d12b..c1dccf6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -46,6 +46,7 @@  endif
 fwts_SOURCES = main.c 				\
 	acpi/ac_adapter/ac_adapter.c 		\
 	acpi/devices/ac_adapter/ac.c		\
+	acpi/devices/battery/battery.c		\
 	acpi/acpidump/acpidump.c 		\
 	acpi/acpiinfo/acpiinfo.c 		\
 	acpi/acpitables/acpitables.c 		\
diff --git a/src/acpi/devices/battery/battery.c b/src/acpi/devices/battery/battery.c
new file mode 100644
index 0000000..ec1322a
--- /dev/null
+++ b/src/acpi/devices/battery/battery.c
@@ -0,0 +1,673 @@ 
+/*
+ * Copyright (C) 2017 Canonical
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+#include "fwts.h"
+
+#if defined(FWTS_HAS_ACPI)
+
+#include "fwts_acpi_object_eval.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <string.h>
+
+#define FWTS_ACPI_BATTERY_HID "PNP0C0A"
+
+static ACPI_HANDLE device;
+
+static ACPI_STATUS get_device_handle(ACPI_HANDLE handle, uint32_t level,
+					  void *context, void **ret_val)
+{
+	FWTS_UNUSED(level);
+	FWTS_UNUSED(context);
+	FWTS_UNUSED(ret_val);
+
+	device = handle;
+	return AE_CTRL_TERMINATE;
+}
+
+static int acpi_battery_init(fwts_framework *fw)
+{
+	ACPI_STATUS status;
+
+	if (fwts_acpica_init(fw) != FWTS_OK)
+		return FWTS_ERROR;
+
+	status = AcpiGetDevices(FWTS_ACPI_BATTERY_HID, get_device_handle, NULL, NULL);
+	if (ACPI_FAILURE(status)) {
+		fwts_log_error(fw, "Cannot find the ACPI device");
+		return FWTS_ERROR;
+	}
+
+	if (!device) {
+		fwts_log_error(fw, "ACPI Battery device does not exist, skipping test");
+		return FWTS_SKIP;
+	} else {
+		ACPI_BUFFER buffer;
+		char full_name[128];
+
+		buffer.Length = sizeof(full_name);
+		buffer.Pointer = full_name;
+
+		status = AcpiGetName(device, ACPI_FULL_PATHNAME, &buffer);
+		if (ACPI_SUCCESS(status)) {
+			fwts_log_info_verbatim(fw, "ACPI Battery Device: %s", full_name);
+			fwts_log_nl(fw);
+		}
+	}
+
+	return FWTS_OK;
+}
+
+static void method_test_BIF_return(
+	fwts_framework *fw,
+	char *name,
+	ACPI_BUFFER *buf,
+	ACPI_OBJECT *obj,
+	void *private)
+{
+	bool failed = false;
+
+	static const fwts_package_element elements[] = {
+		{ ACPI_TYPE_INTEGER,	"Power Unit" },
+		{ ACPI_TYPE_INTEGER,	"Design Capacity" },
+		{ ACPI_TYPE_INTEGER,	"Last Full Charge Capacity" },
+		{ ACPI_TYPE_INTEGER,	"Battery Technology" },
+		{ ACPI_TYPE_INTEGER,	"Design Voltage" },
+		{ ACPI_TYPE_INTEGER,	"Design Capacity of Warning" },
+		{ ACPI_TYPE_INTEGER,	"Design Capactty of Low" },
+		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 1" },
+		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 2" },
+		{ ACPI_TYPE_STRING,	"Model Number" },
+		{ ACPI_TYPE_STRING,	"Serial Number" },
+		{ ACPI_TYPE_STRING,	"Battery Type" },
+		{ ACPI_TYPE_STRING,	"OEM Information" }
+	};
+
+	FWTS_UNUSED(private);
+
+	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_count_equal(fw, name, "_BIF", obj, 13) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_elements_type(fw, name, "_BIF", obj, elements, 13) != FWTS_OK)
+		return;
+
+	/* Sanity check each field */
+	/* Power Unit */
+	if (obj->Package.Elements[0].Integer.Value > 0x00000002) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIFBadUnits",
+			"%s: Expected Power Unit (Element 0) to be "
+			"0 (mWh) or 1 (mAh), got 0x%8.8" PRIx64 ".",
+			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
+		failed = true;
+	}
+#ifdef FWTS_METHOD_PEDANDTIC
+	/*
+	 * Since this information may be evaluated by communicating with
+	 * the EC we skip these checks as we can't do this from userspace
+	 */
+	/* Design Capacity */
+	if (obj->Package.Elements[1].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIFBadCapacity",
+			"%s: Design Capacity (Element 1) is "
+			"unknown: 0x%8.8" PRIx64 ".",
+			name, obj->Package.Elements[1].Integer.Value);
+		failed = true;
+	}
+	/* Last Full Charge Capacity */
+	if (obj->Package.Elements[2].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIFChargeCapacity",
+			"%s: Last Full Charge Capacity (Element 2) "
+			"is unknown: 0x%8.8" PRIx64 ".",
+			name, obj->Package.Elements[2].Integer.Value);
+		failed = true;
+	}
+#endif
+	/* Battery Technology */
+	if (obj->Package.Elements[3].Integer.Value > 0x00000002) {
+		fwts_failed(fw, LOG_LEVEL_MEDIUM,
+			"Method_BIFBatTechUnit",
+			"%s: Expected Battery Technology Unit "
+			"(Element 3) to be 0 (Primary) or 1 "
+			"(Secondary), got 0x%8.8" PRIx64 ".",
+			name, (uint64_t)obj->Package.Elements[3].Integer.Value);
+		failed = true;
+	}
+#ifdef FWTS_METHOD_PEDANDTIC
+	/*
+	 * Since this information may be evaluated by communicating with
+	 * the EC we skip these checks as we can't do this from userspace
+	 */
+	/* Design Voltage */
+	if (obj->Package.Elements[4].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIFDesignVoltage",
+			"%s: Design Voltage (Element 4) is "
+			"unknown: 0x%8.8" PRIx64 ".",
+			name, obj->Package.Elements[4].Integer.Value);
+		failed = true;
+	}
+	/* Design capacity warning */
+	if (obj->Package.Elements[5].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIFDesignCapacityE5",
+			"%s: Design Capacity Warning (Element 5) "
+			"is unknown: 0x%8.8" PRIx64 ".",
+			name, obj->Package.Elements[5].Integer.Value);
+		failed = true;
+	}
+	/* Design capacity low */
+	if (obj->Package.Elements[6].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIFDesignCapacityE6",
+			"%s: Design Capacity Warning (Element 6) "
+			"is unknown: 0x%8.8" PRIx64 ".",
+			name, obj->Package.Elements[6].Integer.Value);
+		failed = true;
+	}
+#endif
+	if (failed)
+		fwts_advice(fw,
+			"Battery %s package contains errors. It is "
+			"worth running the firmware test suite "
+			"interactive 'battery' test to see if this "
+			"is problematic.  This is a bug an needs to "
+			"be fixed.", name);
+	else
+		fwts_method_passed_sane(fw, name, "package");
+}
+
+static int method_test_BIF(fwts_framework *fw)
+{
+	ACPI_STATUS status;
+	ACPI_HANDLE	method;
+
+	/* _BIF can be superseded by _BIX */
+	status = AcpiGetHandle (device, "_BIX", &method);
+	if (ACPI_SUCCESS(status))
+		return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+			"_BIF", NULL, 0, method_test_BIF_return, NULL);
+	else
+		return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
+			"_BIF", NULL, 0, method_test_BIF_return, NULL);
+}
+
+static void method_test_BIX_return(
+	fwts_framework *fw,
+	char *name,
+	ACPI_BUFFER *buf,
+	ACPI_OBJECT *obj,
+	void *private)
+{
+	bool failed = false;
+
+	static const fwts_package_element elements[] = {
+		{ ACPI_TYPE_INTEGER,	"Revision" },
+		{ ACPI_TYPE_INTEGER,	"Power Unit" },
+		{ ACPI_TYPE_INTEGER,	"Design Capacity" },
+		{ ACPI_TYPE_INTEGER,	"Last Full Charge Capacity" },
+		{ ACPI_TYPE_INTEGER,	"Battery Technology" },
+		{ ACPI_TYPE_INTEGER,	"Design Voltage" },
+		{ ACPI_TYPE_INTEGER,	"Design Capacity of Warning" },
+		{ ACPI_TYPE_INTEGER,	"Design Capactty of Low" },
+		{ ACPI_TYPE_INTEGER,	"Cycle Count" },
+		{ ACPI_TYPE_INTEGER,	"Measurement Accuracy" },
+		{ ACPI_TYPE_INTEGER,	"Max Sampling Time" },
+		{ ACPI_TYPE_INTEGER,	"Min Sampling Time" },
+		{ ACPI_TYPE_INTEGER,	"Max Averaging Interval" },
+		{ ACPI_TYPE_INTEGER,	"Min Averaging Interval" },
+		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 1" },
+		{ ACPI_TYPE_INTEGER,	"Battery Capacity Granularity 2" },
+		{ ACPI_TYPE_STRING,	"Model Number" },
+		{ ACPI_TYPE_STRING,	"Serial Number" },
+		{ ACPI_TYPE_STRING,	"Battery Type" },
+		{ ACPI_TYPE_STRING,	"OEM Information" }
+	};
+
+	FWTS_UNUSED(private);
+
+	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_count_equal(fw, name, "_BIX", obj, 20) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_elements_type(fw, name, "_BIX", obj, elements, 20) != FWTS_OK)
+		return;
+
+	/* Sanity check each field */
+	/* Power Unit */
+	if (obj->Package.Elements[1].Integer.Value > 0x00000002) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIXPowerUnit",
+			"%s: Expected %s (Element 1) to be "
+			"0 (mWh) or 1 (mAh), got 0x%8.8" PRIx64 ".",
+			name, elements[1].name,
+			(uint64_t)obj->Package.Elements[1].Integer.Value);
+		failed = true;
+	}
+#ifdef FWTS_METHOD_PEDANDTIC
+	/*
+	 * Since this information may be evaluated by communicating with
+	 * the EC we skip these checks as we can't do this from userspace
+	 */
+	/* Design Capacity */
+	if (obj->Package.Elements[2].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIXDesignCapacity",
+			"%s: %s (Element 2) is "
+			"unknown: 0x%8.8" PRIx64 ".",
+			name, elements[2].name,
+			obj->Package.Elements[2].Integer.Value);
+		failed = true;
+	}
+	/* Last Full Charge Capacity */
+	if (obj->Package.Elements[3].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIXFullChargeCapacity",
+			"%s: %s (Element 3) "
+			"is unknown: 0x%8.8" PRIx64 ".",
+			name, elements[3].name,
+			obj->Package.Elements[3].Integer.Value);
+		failed = true;
+	}
+#endif
+	/* Battery Technology */
+	if (obj->Package.Elements[4].Integer.Value > 0x00000002) {
+		fwts_failed(fw, LOG_LEVEL_MEDIUM,
+			"Method_BIXBatteryTechUnit",
+			"%s: %s "
+			"(Element 4) to be 0 (Primary) or 1 "
+			"(Secondary), got 0x%8.8" PRIx64 ".",
+			name, elements[4].name,
+			(uint64_t)obj->Package.Elements[4].Integer.Value);
+		failed = true;
+	}
+#ifdef FWTS_METHOD_PEDANDTIC
+	/*
+	 * Since this information may be evaluated by communicating with
+	 * the EC we skip these checks as we can't do this from userspace
+	 */
+	/* Design Voltage */
+	if (obj->Package.Elements[5].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIXDesignVoltage",
+			"%s: %s (Element 5) is unknown: "
+			"0x%8.8" PRIx64 ".",
+			name, elements[5].name,
+			obj->Package.Elements[5].Integer.Value);
+		failed = true;
+	}
+	/* Design capacity warning */
+	if (obj->Package.Elements[6].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIXDesignCapacityE6",
+			"%s: %s (Element 6) "
+			"is unknown: 0x%8.8" PRIx64 ".",
+			name, elements[6].name,
+			obj->Package.Elements[6].Integer.Value);
+		failed = true;
+	}
+	/* Design capacity low */
+	if (obj->Package.Elements[7].Integer.Value > 0x7fffffff) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIXDesignCapacityE7",
+			"%s: %s (Element 7) "
+			"is unknown: 0x%8.8" PRIx64 ".",
+			name, elements[7].name,
+			obj->Package.Elements[7].Integer.Value);
+		failed = true;
+	}
+	/* Cycle Count: value = 0 ~ 0xFFFFFFFE or 0xFFFFFFFF (unknown) */
+
+	/* Measurement Accuracy */
+	if (obj->Package.Elements[9].Integer.Value > 100000) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BIXMeasurementAccuracy",
+			"%s: %s (Element 9) "
+			"is unknown: 0x%8.8" PRIx64 ".",
+			name, elements[9].name,
+			obj->Package.Elements[9].Integer.Value);
+		failed = true;
+	}
+
+#endif
+	if (failed)
+		fwts_advice(fw,
+			"Battery %s package contains errors. It is "
+			"worth running the firmware test suite "
+			"interactive 'battery' test to see if this "
+			"is problematic.  This is a bug an needs to "
+			"be fixed.", name);
+	else
+		fwts_method_passed_sane(fw, name, "package");
+}
+
+static int method_test_BIX(fwts_framework *fw)
+{
+	ACPI_STATUS status;
+	ACPI_HANDLE	method;
+
+	/* _BIX may not supported by older firmware */
+	status = AcpiGetHandle (device, "_BIF", &method);
+	if (ACPI_SUCCESS(status))
+		return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+			"_BIX", NULL, 0, method_test_BIX_return, NULL);
+	else
+		return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
+			"_BIX", NULL, 0, method_test_BIX_return, NULL);
+}
+
+static int method_test_BMA(fwts_framework *fw)
+{
+	ACPI_OBJECT arg[1];
+	arg[0].Type = ACPI_TYPE_INTEGER;
+	arg[0].Integer.Value = 1;
+
+	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+		"_BMA", arg, 1, fwts_method_test_integer_return, NULL);
+}
+
+static int method_test_BMS(fwts_framework *fw)
+{
+	ACPI_OBJECT arg[1];
+	arg[0].Type = ACPI_TYPE_INTEGER;
+	arg[0].Integer.Value = 1;
+
+	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+		"_BMS", arg, 1, fwts_method_test_integer_return, NULL);
+}
+
+static void method_test_BST_return(
+	fwts_framework *fw,
+	char *name,
+	ACPI_BUFFER *buf,
+	ACPI_OBJECT *obj,
+	void *private)
+{
+	bool failed = false;
+
+	FWTS_UNUSED(private);
+
+	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_count_equal(fw, name, "_BST", obj, 4) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_elements_all_type(fw, name, "_BST", obj, ACPI_TYPE_INTEGER) != FWTS_OK)
+		return;
+
+	/* Sanity check each field */
+	/* Battery State */
+	if ((obj->Package.Elements[0].Integer.Value) > 7) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BSTBadState",
+			"%s: Expected Battery State (Element 0) to "
+			"be 0..7, got 0x%8.8" PRIx64 ".",
+			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
+		failed = true;
+	}
+	/* Ensure bits 0 (discharging) and 1 (charging) are not both set, see 10.2.2.6 */
+	if (((obj->Package.Elements[0].Integer.Value) & 3) == 3) {
+		fwts_failed(fw, LOG_LEVEL_CRITICAL,
+			"Method_BSTBadState",
+			"%s: Battery State (Element 0) is "
+			"indicating both charging and discharginng "
+			"which is not allowed. Got value 0x%8.8" PRIx64 ".",
+			name, (uint64_t)obj->Package.Elements[0].Integer.Value);
+		failed = true;
+	}
+	/* Battery Present Rate - cannot check, pulled from EC */
+	/* Battery Remaining Capacity - cannot check, pulled from EC */
+	/* Battery Present Voltage - cannot check, pulled from EC */
+	if (failed)
+		fwts_advice(fw,
+			"Battery %s package contains errors. It is "
+			"worth running the firmware test suite "
+			"interactive 'battery' test to see if this "
+			"is problematic.  This is a bug an needs to "
+			"be fixed.", name);
+		else
+			fwts_method_passed_sane(fw, name, "package");
+}
+
+static int method_test_BST(fwts_framework *fw)
+{
+	return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
+		"_BST", NULL, 0, method_test_BST_return, NULL);
+}
+
+static int method_test_BTH(fwts_framework *fw)
+{
+	ACPI_OBJECT arg[1];
+	uint8_t i, ret;
+	arg[0].Type = ACPI_TYPE_INTEGER;
+
+	for (i = 0; i <= 100; i++) {
+		arg[0].Integer.Value = i;
+		ret = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+			"_BTH", arg, 1, fwts_method_test_NULL_return, NULL);
+
+		if (ret != FWTS_OK)
+			break;
+	}
+	return ret;
+}
+
+static int method_test_BTP(fwts_framework *fw)
+{
+	static const int values[] = { 0, 1, 100, 200, 0x7fffffff };
+	ACPI_STATUS status;
+	uint8_t i;
+
+	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
+		ACPI_OBJECT arg[1];
+		arg[0].Type = ACPI_TYPE_INTEGER;
+		arg[0].Integer.Value = values[i];
+		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+			"_BTP", arg, 1, fwts_method_test_NULL_return, NULL);
+		if (ACPI_FAILURE(status))
+			break;
+	}
+	return FWTS_OK;
+}
+
+
+static void method_test_PCL_return(fwts_framework *fw,
+	char *name,
+	ACPI_BUFFER *buf,
+	ACPI_OBJECT *obj,
+	void *private)
+{
+	FWTS_UNUSED(private);
+
+	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_elements_all_type(fw, name, "_PCL", obj, ACPI_TYPE_LOCAL_REFERENCE) != FWTS_OK)
+		return;
+
+	fwts_passed(fw,	"%s returned a sane package of %" PRIu32 " references.", name, obj->Package.Count);
+}
+
+static int method_test_PCL(fwts_framework *fw)
+{
+	return fwts_evaluate_method(fw, METHOD_MANDATORY, &device,
+		 "_PCL", NULL, 0, method_test_PCL_return, NULL);
+}
+
+static void method_test_STA_return(
+	fwts_framework *fw,
+	char *name,
+	ACPI_BUFFER *buf,
+	ACPI_OBJECT *obj,
+	void *private)
+{
+	bool failed = false;
+
+	FWTS_UNUSED(private);
+
+	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_INTEGER) != FWTS_OK)
+		return;
+
+	if ((obj->Integer.Value & 3) == 2) {
+		fwts_failed(fw, LOG_LEVEL_HIGH,
+			"Method_STAEnabledNotPresent",
+			"%s indicates that the device is enabled "
+			"but not present, which is impossible.", name);
+		failed = true;
+	}
+	if ((obj->Integer.Value & ~0x1f) != 0) {
+		fwts_failed(fw, LOG_LEVEL_HIGH,
+			"Method_STAReservedBitsSet",
+			"%s is returning non-zero reserved "
+			"bits 5-31. These should be zero.", name);
+		failed = true;
+	}
+
+	if (!failed)
+		fwts_method_passed_sane_uint64(fw, name, obj->Integer.Value);
+}
+
+static int method_test_STA(fwts_framework *fw)
+{
+	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+		"_STA", NULL, 0, method_test_STA_return, NULL);
+}
+
+static int method_test_BTM(fwts_framework *fw)
+{
+	static const int values[] = { 0, 1, 100, 200, 0x7fffffff };
+	ACPI_STATUS status;
+	uint8_t i;
+
+	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
+		ACPI_OBJECT arg[1];
+		arg[0].Type = ACPI_TYPE_INTEGER;
+		arg[0].Integer.Value = values[i];
+		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+			"_BTM", arg, 1, fwts_method_test_NULL_return, NULL);
+		if (ACPI_FAILURE(status))
+			break;
+	}
+	return FWTS_OK;
+}
+
+static int method_test_BCT(fwts_framework *fw)
+{
+	ACPI_OBJECT arg[1];
+	arg[0].Type = ACPI_TYPE_INTEGER;
+	arg[0].Integer.Value = 50;	/* 50% */
+
+	/*
+	 * For now, just check that we get some integer back, values
+	 * can be 0x00000000, 0x00000001-0xfffffffe and 0xffffffff,
+	 * so anything is valid as long as it is an integer
+	 */
+	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+		"_BCT", arg, 1, fwts_method_test_integer_return, NULL);
+}
+
+static void method_test_BMD_return(
+	fwts_framework *fw,
+	char *name,
+	ACPI_BUFFER *buf,
+	ACPI_OBJECT *obj,
+	void *private)
+{
+	FWTS_UNUSED(private);
+
+	if (fwts_method_check_type(fw, name, buf, ACPI_TYPE_PACKAGE) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_count_equal(fw, name, "_BMD", obj, 5) != FWTS_OK)
+		return;
+
+	if (fwts_method_package_elements_all_type(fw, name, "_BMD", obj, ACPI_TYPE_INTEGER) != FWTS_OK)
+		return;
+
+	fwts_method_passed_sane(fw, name, "package");
+}
+
+static int method_test_BMD(fwts_framework *fw)
+{
+	return fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+		"_BMD", NULL, 0, method_test_BMD_return, NULL);
+}
+
+static int method_test_BMC(fwts_framework *fw)
+{
+	static const int values[] = { 0, 1, 2, 4 };
+	ACPI_STATUS status;
+	uint8_t i;
+
+	for (i = 0; i < sizeof(values) / sizeof(int); i++) {
+		ACPI_OBJECT arg[1];
+		arg[0].Type = ACPI_TYPE_INTEGER;
+		arg[0].Integer.Value = values[i];
+		status = fwts_evaluate_method(fw, METHOD_OPTIONAL, &device,
+			"_BMC", arg, 1, fwts_method_test_NULL_return, NULL);
+		if (ACPI_FAILURE(status))
+			break;
+	}
+	return FWTS_OK;
+}
+
+static fwts_framework_minor_test acpi_battery_tests[] = {
+	{ method_test_BIF, "Test _BIF (Battery Information)." },
+	{ method_test_BIX, "Test _BIX (Battery Information Extended)." },
+	{ method_test_BMA, "Test _BMA (Battery Measurement Averaging)." },
+	{ method_test_BMS, "Test _BMS (Battery Measurement Sampling Time)." },
+	{ method_test_BST, "Test _BST (Battery Status)." },
+	{ method_test_BTH, "Test _BTH (Battery Throttle Limit)." },
+	{ method_test_BTP, "Test _BTP (Battery Trip Point)." },
+	{ method_test_PCL, "Test _PCL (Power Consumer List)." },
+	{ method_test_STA, "Test _STA (Status)." },
+	{ method_test_BTM, "Test _BTM (Battery Time)." },
+	{ method_test_BCT, "Test _BCT (Battery Charge Time)." },
+	{ method_test_BMD, "Test _BMD (Battery Maintenance Data)." },
+	{ method_test_BMC, "Test _BMC (Battery Maintenance Control)." },
+	{ NULL, NULL }
+};
+
+static int acpi_battery_deinit(fwts_framework *fw)
+{
+	FWTS_UNUSED(fw);
+	fwts_acpica_deinit();
+
+	return FWTS_OK;
+}
+
+static fwts_framework_ops acpi_battery_ops = {
+	.description = "ACPI battery device test",
+	.init        = acpi_battery_init,
+	.deinit      = acpi_battery_deinit,
+	.minor_tests = acpi_battery_tests
+};
+
+FWTS_REGISTER("acpi_battery", &acpi_battery_ops, FWTS_TEST_ANYTIME, FWTS_FLAG_BATCH | FWTS_FLAG_TEST_ACPI)
+
+#endif