/* * Copyright (c) International Business Machines Corp., 2001 * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. * * 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 */ /* * HISTORY: * 06/09/2003 Initial creation mridge@us.ibm.com * -Ported * updated - 01/09/2005 Updates from Intel to add functionality * * 01/03/2009 Márton Németh <nm127@freemail.hu> * - Updated for Linux kernel 2.6.28 */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/blkdev.h> #include <linux/ioctl.h> #include <linux/pm.h> #include <linux/acpi.h> #include <linux/genhd.h> #include <linux/dmi.h> #include <linux/nls.h> #include "ltp_acpi.h" MODULE_AUTHOR("Martin Ridgeway <mridge@us.ibm.com>"); MODULE_AUTHOR("Alexey Kodanev <alexey.kodanev@oracle.com>"); MODULE_DESCRIPTION("ACPI LTP Test Driver"); MODULE_LICENSE("GPL"); ACPI_MODULE_NAME("LTP_ACPI") #define prk_err(fmt, ...) \ pr_err(ACPI_TEST_NAME ": " fmt "\n", ##__VA_ARGS__) #define prk_alert(fmt, ...) \ pr_alert(ACPI_TEST_NAME ": " fmt "\n", ##__VA_ARGS__) #define prk_info(fmt, ...) \ pr_info(ACPI_TEST_NAME ": " fmt "\n", ##__VA_ARGS__) static int acpi_failure(acpi_status status, const char *name) { if (ACPI_FAILURE(status)) { ACPI_EXCEPTION((AE_INFO, status, name)); return 1; } return 0; } /* points to the string of the last found object _STR */ static char *str_obj_result; /* sysfs device path of the last found device */ static char *sysfs_path; /* first found device with _CRS */ static acpi_handle res_handle; static acpi_status get_str_object(acpi_handle handle) { int res; acpi_status status; acpi_handle temp = 0; union acpi_object *str_obj; char *buf = NULL; struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; status = acpi_get_handle(handle, "_STR", &temp); if (ACPI_SUCCESS(status) && !acpi_evaluate_object(handle, "_STR", NULL, &buffer)) { str_obj = buffer.pointer; buf = kmalloc(str_obj->buffer.length / 2, GFP_KERNEL); if (!buf) { kfree(str_obj); return AE_NO_MEMORY; } res = utf16s_to_utf8s((wchar_t *)str_obj->buffer.pointer, str_obj->buffer.length, UTF16_LITTLE_ENDIAN, buf, str_obj->buffer.length / 2); buf[res] = '\0'; kfree(str_obj_result); str_obj_result = buf; kfree(str_obj); } return status; } static void get_crs_object(acpi_handle handle) { acpi_status status; acpi_handle temp; if (!res_handle) { status = acpi_get_handle(handle, METHOD_NAME__CRS, &temp); if (ACPI_SUCCESS(status)) res_handle = handle; } } static void get_sysfs_path(acpi_handle handle) { acpi_status status; struct acpi_device *device; kfree(sysfs_path); sysfs_path = NULL; status = acpi_bus_get_device(handle, &device); if (ACPI_SUCCESS(status)) sysfs_path = kobject_get_path(&device->dev.kobj, GFP_KERNEL); } /* acpi handle of the last visited device */ static acpi_handle start_parent; static int acpi_traverse(acpi_handle parent, acpi_handle child) { static char indent[64]; const char * const ind_end = indent + 63; static const char *ind = ind_end; acpi_status status; struct acpi_device_info *dev_info; acpi_handle new_child; if (!indent[0]) memset(indent, 0x20, 63); while (parent) { status = acpi_get_next_object(ACPI_TYPE_DEVICE, parent, child, &new_child); if (ACPI_FAILURE(status)) { ind += 4; child = parent; status = acpi_get_parent(child, &parent); /* no more devices */ if (ACPI_FAILURE(status)) { start_parent = 0; kfree(str_obj_result); str_obj_result = NULL; return 0; } continue; } status = acpi_get_object_info(new_child, &dev_info); if (acpi_failure(status, "acpi_object_info failed")) return 1; get_sysfs_path(new_child); get_crs_object(new_child); if (ind < indent) ind = indent; else if (ind > ind_end) ind = ind_end; /* * if we find _STR object we will stop here * and save last visited child */ if (ACPI_SUCCESS(get_str_object(new_child))) { prk_info("%s%4.4s: has '_STR' '%s' path '%s'", ind, (char *)&dev_info->name, str_obj_result, (sysfs_path) ? sysfs_path : "no path"); ind -= 4; start_parent = new_child; kfree(dev_info); return 0; } prk_info("%s%4.4s: path '%s'", ind, (char *)&dev_info->name, (sysfs_path) ? sysfs_path : "no path"); ind -= 4; parent = new_child; child = 0; kfree(dev_info); } return 0; } static int acpi_traverse_from_root(void) { acpi_status status; struct acpi_device_info *dev_info; acpi_handle parent = 0, child = 0; if (!start_parent) { status = acpi_get_handle(NULL, ACPI_NS_ROOT_PATH, &parent); if (acpi_failure(status, "acpi_get_handle")) return 1; status = acpi_get_object_info(parent, &dev_info); if (acpi_failure(status, "acpi_object_info failed")) return 1; prk_info("start from %4.4s", (char *)&dev_info->name); } else { /* continue with the last visited child */ parent = start_parent; } return acpi_traverse(parent, child); } /* first found device with _STR */ static acpi_handle dev_handle; static int acpi_hw_reduced; static int acpi_init(void) { acpi_status status; acpi_handle parent_handle; struct acpi_table_fadt *fadt; struct acpi_table_header *table; struct acpi_device_info *dev_info; status = acpi_get_table(ACPI_SIG_FADT, 0, &table); if (ACPI_SUCCESS(status)) { fadt = (struct acpi_table_fadt *)table; if (fadt->flags & ACPI_FADT_HW_REDUCED) acpi_hw_reduced = 1; } if (acpi_hw_reduced) prk_alert("Detected the Hardware-reduced ACPI mode"); prk_alert("TEST -- acpi_get_handle "); status = acpi_get_handle(NULL, "\\_SB", &parent_handle); if (acpi_failure(status, "acpi_get_handle")) return 1; /* get first device on SYS bus, it will be used in other tests */ while (acpi_get_next_object(ACPI_TYPE_DEVICE, parent_handle, 0, &dev_handle) == 0) { parent_handle = dev_handle; } status = acpi_get_object_info(dev_handle, &dev_info); if (acpi_failure(status, "acpi_object_info failed")) return 1; prk_alert("ACPI object name %4.4s, type %d", (char *)&dev_info->name, dev_info->type); kfree(dev_info); prk_alert("TEST -- acpi_get_parent "); status = acpi_get_parent(dev_handle, &parent_handle); return acpi_failure(status, "acpi_get_parent failed"); } /* * acpi_bus_notify * --------------- * Callback for all 'system-level' device notifications (values 0x00-0x7F). */ static void acpi_bus_notify(acpi_handle handle, u32 type, void *data) { prk_alert("Register ACPI Bus Notify callback function"); } static int acpi_test_notify_handler(void) { acpi_status status; prk_alert("TEST -- acpi_install_notify_handler"); status = acpi_install_notify_handler(dev_handle, ACPI_SYSTEM_NOTIFY, &acpi_bus_notify, NULL); if (ACPI_SUCCESS(status)) { prk_alert("TEST -- acpi_remove_notify_handler"); status = acpi_remove_notify_handler(dev_handle, ACPI_SYSTEM_NOTIFY, &acpi_bus_notify); return acpi_failure(status, "acpi_remove_notify_handler"); } else if (status != AE_ALREADY_EXISTS) { return acpi_failure(status, "acpi_install_notify_handler"); } return 0; } static u32 ltp_test_power_button_ev_handler(void *context) { prk_alert("ltp_test_power_button_ev_handler"); return 1; } static u32 ltp_test_sleep_button_ev_handler(void *context) { prk_alert("ltp_test_sleep_button_ev_handler"); return 1; } static int acpi_test_event_handler(void) { int err = 0; acpi_status status; prk_alert("TEST -- acpi_install_fixed_event_handler"); if (acpi_hw_reduced) { prk_alert("Skipped due to the HW-reduced mode"); return 0; } status = acpi_install_fixed_event_handler(ACPI_EVENT_POWER_BUTTON, ltp_test_power_button_ev_handler, NULL); if (ACPI_SUCCESS(status)) { prk_alert("TEST -- acpi_remove_fixed_event_handler"); status = acpi_remove_fixed_event_handler( ACPI_EVENT_POWER_BUTTON, ltp_test_power_button_ev_handler); err = acpi_failure(status, "remove fixed event handler"); } else if (status != AE_ALREADY_EXISTS) { err = acpi_failure(status, "install fixed event handler"); } prk_alert("TEST -- acpi_install_fixed_event_handler"); status = acpi_install_fixed_event_handler(ACPI_EVENT_RTC, ltp_test_sleep_button_ev_handler, NULL); if (ACPI_SUCCESS(status)) { prk_alert("TEST -- acpi_remove_fixed_event_handler"); status = acpi_remove_fixed_event_handler( ACPI_EVENT_RTC, ltp_test_sleep_button_ev_handler); err |= acpi_failure(status, "remove fixed event handler"); } else if (status != AE_ALREADY_EXISTS) { err |= acpi_failure(status, "install fixed event handler"); } return err; } #ifndef ACPI_EC_UDELAY_GLK #define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */ #endif static int acpi_global_lock(void) { acpi_status status; u32 global_lock = 0; prk_alert("TEST -- acpi_acquire_global_lock "); if (acpi_hw_reduced) { prk_alert("Skipped due to the HW-reduced mode"); return 0; } status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &global_lock); if (acpi_failure(status, "acpi_acquire_global_lock")) return 1; prk_alert("TEST -- acpi_release_global_lock "); status = acpi_release_global_lock(global_lock); return acpi_failure(status, "acpi_release_global_lock"); } static int acpi_test_bus(void) { int state = 0; acpi_status status; acpi_handle bus_handle; struct acpi_device *device; status = acpi_get_handle(NULL, "\\_SB", &bus_handle); if (acpi_failure(status, "acpi_get_handle")) return 1; prk_alert("TEST -- acpi_bus_get_device"); status = acpi_bus_get_device(bus_handle, &device); if (acpi_failure(status, "acpi_bus_get_device")) return 1; prk_alert("TEST -- acpi_bus_update_power "); status = acpi_bus_update_power(device->handle, &state); if (acpi_failure(status, "error reading power state")) return 1; prk_info("acpi bus power state is %d", state); return 0; } static acpi_status acpi_ec_io_ports(struct acpi_resource *resource, void *context) { return 0; } static int acpi_test_resources(void) { int err = 0; acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; /* skip if we don't find device with _CRC */ if (res_handle == 0) return 0; prk_alert("TEST -- acpi_get_current_resources"); status = acpi_get_current_resources(res_handle, &buffer); err = acpi_failure(status, "failed get_current_resources"); #ifdef ACPI_FUTURE_USAGE prk_alert("TEST -- acpi_get_possible_resources"); status = acpi_get_possible_resources(res_handle, &buffer); err |= acpi_failure(status, "get_possible_resources"); #endif prk_alert("TEST -- acpi_walk_resources "); status = acpi_walk_resources(res_handle, METHOD_NAME__CRS, acpi_ec_io_ports, NULL); err |= acpi_failure(status, "Failed walk_resources"); return err; } static int acpi_sleep_test(void) { int err = 0; acpi_status status; u32 i; u8 type_a, type_b; prk_alert("TEST -- acpi_get_sleep_type_data "); for (i = 0; i < ACPI_S_STATE_COUNT; ++i) { status = acpi_get_sleep_type_data(i, &type_a, &type_b); if (ACPI_SUCCESS(status)) { prk_info("get_sleep_type_data S%d a:%d b:%d", i, type_a, type_b); } else if (status != AE_NOT_FOUND) { err |= 1; } } return err; } static int acpi_test_register(void) { int i, err = 0; u32 val; acpi_status status; prk_alert("TEST -- acpi_read_bit_register"); if (acpi_hw_reduced) { prk_alert("Skipped due to the HW-reduced mode"); return 0; } /* * ACPICA: Remove obsolete Flags parameter. * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git; * a=commitdiff;h=d8c71b6d3b21cf21ad775e1cf6da95bf87bd5ad4 * * ACPICA: Rename ACPI bit register access functions * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ * commit/?id=50ffba1bd3120b069617455545bc27bcf3cf7579 */ for (i = 0; i < ACPI_NUM_BITREG; ++i) { status = acpi_read_bit_register(i, &val); err |= acpi_failure(status, "acpi_read_bit_register"); if (ACPI_SUCCESS(status)) prk_alert("get register: %02x val: %04x", i, val); } return err; } static acpi_status ltp_get_dev_callback(acpi_handle obj, u32 depth, void *context, void **ret) { char *name = context; char fullname[20]; /* * Only SBA shows up in ACPI namespace, so its CSR space * includes both SBA and IOC. Make SBA and IOC show up * separately in PCI space. */ sprintf(fullname, "%s SBA", name); prk_info("get_dev_callback SBA name %s", fullname); sprintf(fullname, "%s IOC", name); prk_info("get_dev_callback IOC name %s", fullname); return 0; } static int acpi_test_dev_callback(void) { acpi_status status; prk_alert("TEST -- acpi_get_devices "); status = acpi_get_devices(NULL, ltp_get_dev_callback, "LTP0001", NULL); return acpi_failure(status, "acpi_get_devices"); } static int current_test_case; static int test_result; static void device_release(struct device *dev) { prk_info("device released"); } static struct device tdev = { .init_name = ACPI_TEST_NAME, .release = device_release, }; /* print test result to sysfs file */ static ssize_t sys_result(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "%d\n", test_result); } static DEVICE_ATTR(result, S_IRUSR, sys_result, NULL); /* print found device description */ static ssize_t sys_str(struct device *dev, struct device_attribute *attr, char *buf) { if (str_obj_result) return scnprintf(buf, PAGE_SIZE, "%s", str_obj_result); else return 0; } static DEVICE_ATTR(str, S_IRUSR, sys_str, NULL); /* print found device's sysfs path */ static ssize_t sys_path(struct device *dev, struct device_attribute *attr, char *buf) { if (sysfs_path) return scnprintf(buf, PAGE_SIZE, "%s", sysfs_path); else return 0; } static DEVICE_ATTR(path, S_IRUSR, sys_path, NULL); static ssize_t sys_acpi_disabled(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "%d", acpi_disabled); } static DEVICE_ATTR(acpi_disabled, S_IRUSR, sys_acpi_disabled, NULL); static ssize_t sys_tcase(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { sscanf(buf, "%d", ¤t_test_case); prk_info("test-case %d", current_test_case); switch (current_test_case) { case ACPI_INIT: test_result = acpi_init(); break; case ACPI_TRAVERSE: test_result = acpi_traverse_from_root(); break; case ACPI_NOTIFY_HANDLER: test_result = acpi_test_notify_handler(); break; case ACPI_EVENT_HANDLER: test_result = acpi_test_event_handler(); break; case ACPI_GLOBAL_LOCK: test_result = acpi_global_lock(); break; case ACPI_TEST_BUS: test_result = acpi_test_bus(); break; case ACPI_TEST_RESOURCES: test_result = acpi_test_resources(); break; case ACPI_SLEEP_TEST: test_result = acpi_sleep_test(); break; case ACPI_TEST_REGISTER: test_result = acpi_test_register(); break; case ACPI_TEST_DEV_CALLBACK: test_result = acpi_test_dev_callback(); break; } return count; } static DEVICE_ATTR(tcase, S_IWUSR, NULL, sys_tcase); int init_module(void) { int err = 0; prk_info("Starting module"); err = device_register(&tdev); if (err) { prk_err("Unable to register device"); goto err0; } prk_info("device registered"); err = device_create_file(&tdev, &dev_attr_result); if (err) { prk_err("Can't create sysfs file 'result'"); goto err1; } err = device_create_file(&tdev, &dev_attr_str); if (err) { prk_err("Can't create sysfs file 'str'"); goto err2; } err = device_create_file(&tdev, &dev_attr_tcase); if (err) { prk_err(": Can't create sysfs file 'tc'"); goto err3; } err = device_create_file(&tdev, &dev_attr_path); if (err) { prk_err(": Can't create sysfs file 'path'"); goto err4; } err = device_create_file(&tdev, &dev_attr_acpi_disabled); if (err) { prk_err("Can't create sysfs file 'acpi_disabled'"); goto err5; } return 0; err5: device_remove_file(&tdev, &dev_attr_path); err4: device_remove_file(&tdev, &dev_attr_tcase); err3: device_remove_file(&tdev, &dev_attr_str); err2: device_remove_file(&tdev, &dev_attr_result); err1: device_unregister(&tdev); err0: return err; } void cleanup_module(void) { prk_info("Unloading module\n"); kfree(str_obj_result); kfree(sysfs_path); device_remove_file(&tdev, &dev_attr_result); device_remove_file(&tdev, &dev_attr_str); device_remove_file(&tdev, &dev_attr_tcase); device_remove_file(&tdev, &dev_attr_path); device_unregister(&tdev); }